conman:  Route management changes.

Manage subnet routes as well as default routes.

Change-Id: I0907301f75aaeac15ffe4917ff937ada601677f7
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index 040e812..975b431 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -212,6 +212,7 @@
   COMMAND_FILE_REGEXP = WLAN_FILE_REGEXP_FMT % COMMAND_FILE_PREFIX
   ACCESS_POINT_FILE_REGEXP = WLAN_FILE_REGEXP_FMT % ACCESS_POINT_FILE_PREFIX
   GATEWAY_FILE_PREFIX = 'gateway.'
+  SUBNET_FILE_PREFIX = 'subnet.'
   MOCA_NODE_FILE_PREFIX = 'node'
   WIFI_SETCLIENT = ['wifi', 'setclient']
   IFUP = ['ifup']
@@ -295,6 +296,7 @@
               self._wpa_control_interface)
 
     for path, prefix in ((self._tmp_dir, self.GATEWAY_FILE_PREFIX),
+                         (self._tmp_dir, self.SUBNET_FILE_PREFIX),
                          (self._interface_status_dir, ''),
                          (self._moca_tmp_dir, self.MOCA_NODE_FILE_PREFIX),
                          (self._config_dir, self.COMMAND_FILE_PREFIX)):
@@ -583,7 +585,7 @@
     """Update the contents of /tmp/hosts."""
     lowest_metric_interface = None
     for ifc in [self.bridge] + self.wifi:
-      route = ifc.current_route()
+      route = ifc.current_routes().get('default', None)
       if route:
         metric = route.get('metric', 0)
         # Skip temporary connection_check routes.
@@ -689,6 +691,14 @@
           logging.info('Received gateway %r for interface %s', contents,
                        ifc.name)
 
+      if filename.startswith(self.SUBNET_FILE_PREFIX):
+        interface_name = filename.split(self.SUBNET_FILE_PREFIX)[-1]
+        ifc = self.interface_by_name(interface_name)
+        if ifc:
+          ifc.set_subnet(contents)
+          logging.info('Received subnet %r for interface %s', contents,
+                       ifc.name)
+
     elif path == self._moca_tmp_dir:
       match = re.match(r'^%s\d+$' % self.MOCA_NODE_FILE_PREFIX, filename)
       if match:
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 1f90f96..5bd5f13 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -208,11 +208,12 @@
       #
       # 1)  Write an interface status file.
       # 2)  Call run-dhclient, which would call dhclient-script, which would
-      #     write a gateway file.
+      #     call ipapply, which would write gateway and subnet files.
       #
       # Fake both of these things instead.
       self.write_interface_status_file('1')
       self.write_gateway_file()
+      self.write_subnet_file()
 
   def stop_client(self):
     client_was_up = self.client_up
@@ -233,6 +234,13 @@
       # This value doesn't matter to conman, so it's fine to hard code it here.
       f.write('192.168.1.1')
 
+  def write_subnet_file(self):
+    subnet_file = os.path.join(self.tmp_dir,
+                               self.subnet_file_prefix + self.wifi.name)
+    with open(subnet_file, 'w') as f:
+      # This value doesn't matter to conman, so it's fine to hard code it here.
+      f.write('192.168.1.0/24')
+
   def write_interface_status_file(self, value):
     status_file = os.path.join(self.interface_status_dir, self.wifi.name)
     with open(status_file, 'w') as f:
@@ -374,6 +382,7 @@
     wlan_configuration.tmp_dir = self._tmp_dir
     wlan_configuration.interface_status_dir = self._interface_status_dir
     wlan_configuration.gateway_file_prefix = self.GATEWAY_FILE_PREFIX
+    wlan_configuration.subnet_file_prefix = self.SUBNET_FILE_PREFIX
 
     super(ConnectionManager, self)._update_wlan_configuration(
         wlan_configuration)
@@ -394,6 +403,8 @@
     if up and not dhcp_failure:
       self.write_gateway_file('br0' if interface_name in ('eth0', 'moca0')
                               else interface_name)
+      self.write_subnet_file('br0' if interface_name in ('eth0', 'moca0')
+                             else interface_name)
 
   def _binwifi(self, *command):
     super(ConnectionManager, self)._binwifi(*command)
@@ -438,6 +449,13 @@
       # This value doesn't matter to conman, so it's fine to hard code it here.
       f.write('192.168.1.1')
 
+  def write_subnet_file(self, interface_name):
+    subnet_file = os.path.join(self._tmp_dir,
+                               self.SUBNET_FILE_PREFIX + interface_name)
+    with open(subnet_file, 'w') as f:
+      # This value doesn't matter to conman, so it's fine to hard code it here.
+      f.write('192.168.1.0/24')
+
   def write_interface_status_file(self, interface_name, value):
     status_file = os.path.join(self._interface_status_dir, interface_name)
     with open(status_file, 'w') as f:
@@ -616,10 +634,10 @@
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
   wvtest.WVPASS(os.path.exists(acs_autoprov_filepath))
   for wifi in c.wifi:
-    wvtest.WVFAIL(wifi.current_route())
+    wvtest.WVFAIL(wifi.current_routes())
   wvtest.WVFAIL(c.has_status_files([status.P.CONNECTED_TO_WLAN,
                                     status.P.HAVE_CONFIG]))
 
@@ -628,7 +646,7 @@
   c.run_once()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
   wvtest.WVFAIL(os.path.exists(acs_autoprov_filepath))
   wvtest.WVFAIL(c.has_status_files([status.P.CAN_REACH_ACS,
                                     status.P.CAN_REACH_INTERNET]))
@@ -638,35 +656,35 @@
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Bring up ethernet, access via both moca and ethernet.
   c.set_ethernet(True)
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Bring down moca, still have access via ethernet.
   c.set_moca(False)
   c.run_once()
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # The bridge interfaces are up, but they can't reach anything.
   c.bridge.set_connection_check_result('fail')
   c.run_until_interface_update()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
 
   # Now c connects to a restricted network.
   c.bridge.set_connection_check_result('restricted')
   c.run_until_interface_update()
   wvtest.WVPASS(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Now the wired connection goes away.
   c.set_ethernet(False)
@@ -674,7 +692,7 @@
   c.run_once()
   wvtest.WVFAIL(c.acs())
   wvtest.WVFAIL(c.internet())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
   check_tmp_hosts('127.0.0.1 localhost')
 
   # Now there are some scan results.
@@ -696,7 +714,7 @@
   wvtest.WVPASS(c.acs())
   wvtest.WVPASS(c.internet())
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
   wvtest.WVPASSEQ(c.log_upload_count, 1)
   # Disable scan results again.
   c.interface_with_scan_results = None
@@ -710,7 +728,7 @@
   c.disable_access_point(band)
   c.run_once()
   wvtest.WVPASS(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
   wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_WLAN]))
 
   # Kill wpa_supplicant.  conman should restart it.
@@ -751,7 +769,7 @@
   c.disable_access_point(band)
   c.run_once()
   wvtest.WVPASS(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
   wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_WLAN]))
 
   # Now enable the AP.  Since we have no wired connection, this should have no
@@ -759,8 +777,8 @@
   c.enable_access_point(band)
   c.run_once()
   wvtest.WVPASS(c.client_up(band))
-  wvtest.WVPASS(c.wifi_for_band(band).current_route())
-  wvtest.WVFAIL(c.bridge.current_route())
+  wvtest.WVPASS(c.wifi_for_band(band).current_routes())
+  wvtest.WVFAIL(c.bridge.current_routes())
   c.run_until_interface_update()
   check_tmp_hosts('192.168.1.100 %s\n127.0.0.1 localhost' % hostname)
 
@@ -772,8 +790,8 @@
   c.run_until_interface_update()
   wvtest.WVPASS(c.access_point_up(band))
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVFAIL(c.wifi_for_band(band).current_route())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVFAIL(c.wifi_for_band(band).current_routes())
+  wvtest.WVPASS(c.bridge.current_routes())
   check_tmp_hosts('192.168.1.101 %s\n127.0.0.1 localhost' % hostname)
 
   # Now move (rather than delete) the configuration file.  The AP should go
@@ -785,8 +803,8 @@
   c.run_once()
   wvtest.WVFAIL(c.access_point_up(band))
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVFAIL(c.wifi_for_band(band).current_route())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVFAIL(c.wifi_for_band(band).current_routes())
+  wvtest.WVPASS(c.bridge.current_routes())
   wvtest.WVFAIL(c.has_status_files([status.P.HAVE_CONFIG]))
 
   # Now move it back, and the AP should come back.
@@ -794,8 +812,8 @@
   c.run_once()
   wvtest.WVPASS(c.access_point_up(band))
   wvtest.WVFAIL(c.client_up(band))
-  wvtest.WVFAIL(c.wifi_for_band(band).current_route())
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVFAIL(c.wifi_for_band(band).current_routes())
+  wvtest.WVPASS(c.bridge.current_routes())
 
   # Now delete the config and bring down the bridge and make sure we reprovision
   # via the last working BSS.
@@ -979,11 +997,11 @@
   c.run_once()
   wvtest.WVPASS(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
-  wvtest.WVPASS(c.bridge.current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
   wvtest.WVFAIL(c.client_up('2.4'))
   wvtest.WVFAIL(c.client_up('5'))
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # Disable the 2.4 GHz AP, make sure the 5 GHz AP stays up.  2.4 GHz should
   # join the WLAN.
@@ -992,9 +1010,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVPASS(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # Delete the 2.4 GHz WLAN configuration; it should leave the WLAN but nothing
   # else should change.
@@ -1003,9 +1021,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVFAIL(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # Disable the wired connection and remove the WLAN configurations.  Both
   # radios should scan.  Wait for 5 GHz to scan, then enable scan results for
@@ -1014,18 +1032,18 @@
   c.set_ethernet(False)
   c.run_once()
   wvtest.WVFAIL(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # The 5 GHz scan has no results.
   c.run_until_scan('5')
   c.run_once()
   c.run_until_interface_update()
   wvtest.WVFAIL(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # The next 2.4 GHz scan will have results.
   c.interface_with_scan_results = c.wifi_for_band('2.4').name
@@ -1035,9 +1053,9 @@
     c.run_once()
   c.run_until_interface_update()
   wvtest.WVPASS(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
   c.run_once()
   wvtest.WVPASSEQ(c.log_upload_count, 1)
 
@@ -1085,9 +1103,9 @@
   c.run_once()
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # Disable the 2.4 GHz AP; nothing should change.  The 2.4 GHz client should
   # not be up because the same radio is being used to run a 5 GHz AP.
@@ -1096,9 +1114,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVFAIL(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # Delete the 2.4 GHz WLAN configuration; nothing should change.
   c.delete_wlan_config('2.4')
@@ -1106,9 +1124,9 @@
   wvtest.WVFAIL(c.access_point_up('2.4'))
   wvtest.WVPASS(c.access_point_up('5'))
   wvtest.WVFAIL(c.client_up('2.4'))
-  wvtest.WVPASS(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVPASS(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # Disable the wired connection and remove the WLAN configurations.  There
   # should be a single scan that leads to ACS access.  (It doesn't matter which
@@ -1118,9 +1136,9 @@
   c.set_ethernet(False)
   c.run_once()
   wvtest.WVFAIL(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
-  wvtest.WVFAIL(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVFAIL(c.wifi_for_band('5').current_routes())
 
   # The scan will have results that will lead to ACS access.
   c.interface_with_scan_results = c.wifi_for_band('2.4').name
@@ -1129,9 +1147,9 @@
     c.run_once()
   c.run_until_interface_update()
   wvtest.WVPASS(c.acs())
-  wvtest.WVFAIL(c.bridge.current_route())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
-  wvtest.WVPASS(c.wifi_for_band('5').current_route())
+  wvtest.WVFAIL(c.bridge.current_routes())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
+  wvtest.WVPASS(c.wifi_for_band('5').current_routes())
   c.run_once()
   wvtest.WVPASSEQ(c.log_upload_count, 1)
 
@@ -1176,7 +1194,7 @@
   c.run_once()
   wvtest.WVPASS(c.wifi_for_band('2.4').acs())
   wvtest.WVPASS(c.wifi_for_band('2.4').internet())
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
 
 
 @wvtest.wvtest
@@ -1189,7 +1207,7 @@
     c:  The ConnectionManager set up by @connection_manager_test.
   """
   wvtest.WVPASS(c._connected_to_wlan(c.wifi_for_band('2.4')))
-  wvtest.WVPASS(c.wifi_for_band('2.4').current_route)
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_routes())
 
 
 @wvtest.wvtest
diff --git a/conman/interface.py b/conman/interface.py
index e172a82..fd0df6f 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -40,7 +40,7 @@
     self._has_acs = None
     self._has_internet = None
 
-    # The gateway IP for this interface.
+    self._subnet = None
     self._gateway_ip = None
     self.metric = metric
 
@@ -76,7 +76,7 @@
     # Give it a high metric so that it won't interfere with normal default
     # routes.
     added_temporary_route = False
-    if not self.current_route():
+    if 'default' not in self.current_routes():
       logging.debug('Adding temporary connection check route for dev %s',
                     self.name)
       self._ip_route('add', 'default',
@@ -121,11 +121,10 @@
 
     return self._has_internet
 
-  def add_route(self):
-    """Adds a default route for this interface.
+  def add_routes(self):
+    """Update default routes for this interface.
 
-    First, checks whether an equivalent route already exists, and if so,
-    returns.
+    Remove any stale routes and add any missing desired routes.
     """
     if self.metric is None:
       logging.info('Cannot add route for %s without a metric.', self.name)
@@ -135,45 +134,76 @@
       logging.info('Cannot add route for %s without a gateway IP.', self.name)
       return
 
-    # If the current default route is the same, there is nothing to do.  If it
+    # If the current routes are the same, there is nothing to do.  If either
     # exists but is different, delete it before adding an updated one.
-    current = self.current_route()
-    if current:
-      if (current.get('via', None) == self._gateway_ip and
-          current.get('metric', None) == str(self.metric)):
-        return
-      else:
-        self.delete_route()
+    current = self.current_routes()
+    default = current.get('default', {})
+    subnet = current.get('subnet', {})
+    if ((default.get('via', None), default.get('metric', None)) !=
+        (self._gateway_ip, str(self.metric))):
+      logging.debug('Adding default route for dev %s', self.name)
+      self.delete_route(default=True)
+      self._ip_route('add', 'default',
+                     'via', self._gateway_ip,
+                     'dev', self.name,
+                     'metric', str(self.metric))
 
-    logging.debug('Adding default route for dev %s', self.name)
-    self._ip_route('add', 'default',
-                   'via', self._gateway_ip,
-                   'dev', self.name,
-                   'metric', str(self.metric))
+    if (self._subnet and
+        (subnet.get('via', None), subnet.get('metric', None)) !=
+        (self._gateway_ip, str(self.metric))):
+      logging.debug('Adding subnet route for dev %s', self.name)
+      self.delete_route(subnet=True)
+      self._ip_route('add', self._subnet,
+                     'dev', self.name,
+                     'metric', str(self.metric))
 
-  def delete_route(self):
-    while self.current_route():
-      logging.debug('Deleting default route for dev %s', self.name)
-      self._ip_route('del', 'default',
-                     'dev', self.name)
+  def delete_route(self, default=False, subnet=False):
+    """Delete default and/or subnet routes for this interface.
 
-  def current_route(self):
-    """Read the current default route for this interface.
+    Args:
+      default:  Whether to delete default routes.  Must be true if subnet isn't.
+      subnet:  Whether to delete subnet routes.  Must be true if default isn't.
+
+    Raises:
+      ValueError:  If neither default nor subnet is True.
+    """
+    route_types = []
+    if default:
+      route_types.append(('default', 'default'))
+    if subnet:
+      route_types.append(('subnet', self._subnet))
+
+    if not route_types:
+      raise ValueError(
+          'Must specify at least one of default or subnet to delete.')
+
+    for route_type, key in route_types:
+      while route_type in self.current_routes():
+        logging.debug('Deleting %s route for dev %s', route_type, self.name)
+        self._ip_route('del', key, 'dev', self.name)
+
+  def current_routes(self):
+    """Read the current routes for this interface.
 
     Returns:
-      A dict containing the gateway [and metric] of the route, or an empty dict
-      if there is currently no default route for this interface.
+      A dict mapping 'default' and/or 'subnet' to a dict containing the gateway
+      [and metric] of the route.  Only contains keys for routes that are
+      present.
     """
     result = {}
     for line in self._ip_route().splitlines():
-      if line.startswith('default') and 'dev %s' % self.name in line:
+      if 'dev %s' % self.name in line:
+        route_type = 'default' if line.startswith('default') else 'subnet'
+        route = {}
         key = None
         for token in line.split():
           if token in ['via', 'metric']:
             key = token
           elif key:
-            result[key] = token
+            route[key] = token
             key = None
+        if route:
+          result[route_type] = route
 
     return result
 
@@ -209,7 +239,12 @@
   def set_gateway_ip(self, gateway_ip):
     logging.info('New gateway IP %s for %s', gateway_ip, self.name)
     self._gateway_ip = gateway_ip
-    self.update_routes()
+    self.update_routes(expire_cache=True)
+
+  def set_subnet(self, subnet):
+    logging.info('New subnet %s for %s', subnet, self.name)
+    self._subnet = subnet
+    self.update_routes(expire_cache=True)
 
   def _set_link_status(self, link, is_up):
     """Set whether a link is up or not."""
@@ -270,9 +305,9 @@
     maybe_had_access = maybe_had_acs != False or maybe_had_internet != False
     has_access = has_acs or has_internet
     if not had_access and has_access:
-      self.add_route()
+      self.add_routes()
     elif maybe_had_access and not has_access:
-      self.delete_route()
+      self.delete_route(default=True, subnet=True)
 
   def initialize(self):
     """Tell the interface it has its initial state.
@@ -319,16 +354,16 @@
       self._moca_stations.remove(node_id)
       self.moca = bool(self._moca_stations)
 
-  def add_route(self):
+  def add_routes(self):
     """We only want ACS autoprovisioning when we're using a wired route."""
-    super(Bridge, self).add_route()
+    super(Bridge, self).add_routes()
     open(self._acs_autoprovisioning_filepath, 'w')
 
-  def delete_route(self):
+  def delete_route(self, *args, **kwargs):
     """We only want ACS autoprovisioning when we're using a wired route."""
     if os.path.exists(self._acs_autoprovisioning_filepath):
       os.unlink(self._acs_autoprovisioning_filepath)
-    super(Bridge, self).delete_route()
+    super(Bridge, self).delete_route(*args, **kwargs)
 
   def _connection_check(self, check_acs):
     """Support for WifiSimulateWireless."""
diff --git a/conman/interface_test.py b/conman/interface_test.py
index 13dcf14..866d1db 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -48,14 +48,16 @@
   def _really_ip_route(self, *args):
     if not args:
       return '\n'.join(self.routing_table.values() +
-                       ['1.2.3.4/24 dev %s proto kernel scope link' % self.name,
+                       ['1.2.3.4/24 dev fake0 proto kernel scope link',
                         'default via 1.2.3.4 dev fake0',
                         'random junk'])
 
     metric = None
     if 'metric' in args:
       metric = args[args.index('metric') + 1]
-    key = (self.name, metric)
+    if args[0] in ('add', 'del'):
+      route = args[1]
+    key = (self.name, route, metric)
     if args[0] == 'add' and key not in self.routing_table:
       logging.debug('Adding route for %r', key)
       self.routing_table[key] = ' '.join(args[1:])
@@ -63,7 +65,7 @@
       if key in self.routing_table:
         logging.debug('Deleting route for %r', key)
         del self.routing_table[key]
-      elif key[1] is None:
+      elif key[2] is None:
         # pylint: disable=g-builtin-op
         for k in self.routing_table.keys():
           if k[0] == key[0]:
@@ -330,53 +332,59 @@
 
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    wvtest.WVFAIL(b.current_routes())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
 
     b.add_moca_station(0)
+    wvtest.WVFAIL(os.path.exists(autoprov_filepath))
     b.set_gateway_ip('192.168.1.1')
+    b.set_subnet('192.168.1.0/24')
+    wvtest.WVFAIL(os.path.exists(autoprov_filepath))
     # Everything should fail because the interface is not initialized.
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    wvtest.WVFAIL(b.current_routes())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
     b.initialize()
     wvtest.WVPASS(b.acs())
     wvtest.WVPASS(b.internet())
-    wvtest.WVPASS(b.current_route())
+    current_routes = b.current_routes()
+    wvtest.WVPASS(current_routes)
+    wvtest.WVPASS('default' in current_routes)
+    wvtest.WVPASS('subnet' in current_routes)
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     b.add_moca_station(1)
     wvtest.WVPASS(b.acs())
     wvtest.WVPASS(b.internet())
-    wvtest.WVPASS(b.current_route())
+    wvtest.WVPASS(b.current_routes())
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     b.remove_moca_station(0)
     b.remove_moca_station(1)
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    wvtest.WVFAIL(b.current_routes())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
 
     b.add_moca_station(2)
     wvtest.WVPASS(b.acs())
     wvtest.WVPASS(b.internet())
-    wvtest.WVPASS(b.current_route())
+    wvtest.WVPASS(b.current_routes())
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     b.set_connection_check_result('fail')
     b.update_routes()
     wvtest.WVFAIL(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVFAIL(b.current_route())
+    wvtest.WVFAIL(b.current_routes())
     wvtest.WVFAIL(os.path.exists(autoprov_filepath))
 
     b.set_connection_check_result('restricted')
     b.update_routes()
     wvtest.WVPASS(b.acs())
     wvtest.WVFAIL(b.internet())
-    wvtest.WVPASS(b.current_route())
+    wvtest.WVPASS(b.current_routes())
     wvtest.WVPASS(os.path.exists(autoprov_filepath))
 
     wvtest.WVFAIL(b.get_ip_address())