1# Copyright 2016 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import ipaddress 16import re 17 18from acts.controllers.utils_lib.ssh import connection 19 20 21class Error(Exception): 22 """Exception thrown when a valid ip command experiences errors.""" 23 24 25class NetworkInterfaceDown(Error): 26 """Exception thrown when a network interface is down.""" 27 28 29class LinuxRouteCommand(object): 30 """Interface for doing standard ip route commands on a linux system.""" 31 32 DEFAULT_ROUTE = 'default' 33 34 def __init__(self, runner): 35 """ 36 Args: 37 runner: Object that can take unix commands and run them in an 38 enviroment. 39 """ 40 self._runner = runner 41 42 def add_route(self, net_interface, address): 43 """Add an entry to the ip routing table. 44 45 Will add a route for either a specific ip address, or a network. 46 47 Args: 48 net_interface: string, Any packet that sends through this route 49 will be sent using this network interface 50 (eg. wlan0). 51 address: ipaddress.IPv4Address, ipaddress.IPv4Network, 52 or DEFAULT_ROUTE. The address to use. If a network 53 is given then the entire subnet will be routed. 54 If DEFAULT_ROUTE is given then this will set the 55 default route. 56 57 Raises: 58 NetworkInterfaceDown: Raised when the network interface is down. 59 """ 60 try: 61 self._runner.run('ip route add %s dev %s' % 62 (address, net_interface)) 63 except connection.CommandError as e: 64 if 'File exists' in e.result.stderr: 65 raise Error('Route already exists.') 66 if 'Network is down' in e.result.stderr: 67 raise NetworkInterfaceDown( 68 'Device must be up for adding a route.') 69 raise 70 71 def get_routes(self, net_interface=None): 72 """Get the routes in the ip routing table. 73 74 Args: 75 net_interface: string, If given, only retrive routes that have 76 been registered to go through this network 77 interface (eg. wlan0). 78 79 Returns: An iterator that returns a tuple of (address, net_interface). 80 If it is the default route then address 81 will be the DEFAULT_ROUTE. If the route is a subnet then 82 it will be a ipaddress.IPv4Network otherwise it is a 83 ipaddress.IPv4Address. 84 """ 85 result = self._runner.run('ip route show') 86 87 lines = result.stdout.splitlines() 88 89 # Scan through each line for valid route entries 90 # Example output: 91 # default via 192.168.1.254 dev eth0 proto static 92 # 192.168.1.0/24 dev eth0 proto kernel scope link src 172.22.100.19 metric 1 93 # 192.168.2.1 dev eth2 proto kernel scope link metric 1 94 for line in lines: 95 if not 'dev' in line: 96 continue 97 98 if line.startswith(self.DEFAULT_ROUTE): 99 # The default route entry is formatted differently. 100 match = re.search('dev (?P<net_interface>.*)', line) 101 pair = None 102 if match: 103 # When there is a match for the route entry pattern create 104 # A pair to hold the info. 105 pair = (self.DEFAULT_ROUTE, 106 match.groupdict()['net_interface']) 107 else: 108 # Test the normal route entry pattern. 109 match = re.search( 110 '(?P<address>[^\s]*) dev (?P<net_interface>[^\s]*)', line) 111 pair = None 112 if match: 113 # When there is a match for the route entry pattern create 114 # A pair to hold the info. 115 d = match.groupdict() 116 # Route can be either a network or specific address 117 try: 118 address = ipaddress.IPv4Address(d['address']) 119 except ipaddress.AddressValueError: 120 address = ipaddress.IPv4Network(d['address']) 121 122 pair = (address, d['net_interface']) 123 124 # No pair means no pattern was found. 125 if not pair: 126 continue 127 128 if net_interface: 129 # If a net_interface was passed in then only give the pair when it is 130 # The correct net_interface. 131 if pair[1] == net_interface: 132 yield pair 133 else: 134 # No net_interface given give all valid route entries. 135 yield pair 136 137 def is_route(self, address, net_interface=None): 138 """Checks to see if a route exists. 139 140 Args: 141 address: ipaddress.IPv4Address, ipaddress.IPv4Network, 142 or DEFAULT_ROUTE, The address to use. 143 net_interface: string, If specified, the route must be 144 registered to go through this network interface 145 (eg. wlan0). 146 147 Returns: True if the route is found, False otherwise. 148 """ 149 for route, _ in self.get_routes(net_interface): 150 if route == address: 151 return True 152 153 return False 154 155 def remove_route(self, address, net_interface=None): 156 """Removes a route from the ip routing table. 157 158 Removes a route from the ip routing table. If the route does not exist 159 nothing is done. 160 161 Args: 162 address: ipaddress.IPv4Address, ipaddress.IPv4Network, 163 or DEFAULT_ROUTE, The address of the route to remove. 164 net_interface: string, If specified the route being removed is 165 registered to go through this network interface 166 (eg. wlan0) 167 """ 168 try: 169 if net_interface: 170 self._runner.run('ip route del %s dev %s' % 171 (address, net_interface)) 172 else: 173 self._runner.run('ip route del %s' % address) 174 except connection.CommandError as e: 175 if 'No such process' in e.result.stderr: 176 # The route didn't exist. 177 return 178 raise 179 180 def clear_routes(self, net_interface=None): 181 """Clears all routes. 182 183 Args: 184 net_interface: The network interface to clear routes on. 185 If not given then all routes will be removed on all network 186 interfaces (eg. wlan0). 187 """ 188 routes = self.get_routes(net_interface) 189 190 for a, d in routes: 191 self.remove_route(a, d) 192