net_utils.py revision 53da18eddf69243ca175d9a4603cba5b55300726
1"""Convenience functions for use by network tests or whomever. 2 3This library is to release in the public repository. 4""" 5 6import commands, os, re, socket, sys, time, struct 7from autotest_lib.client.common_lib import error 8from autotest_lib.client.bin import utils 9 10TIMEOUT = 10 # Used for socket timeout and barrier timeout 11 12 13class network_utils(object): 14 def reset(self, ignore_status=False): 15 utils.system('service network restart', ignore_status=ignore_status) 16 17 18 def start(self, ignore_status=False): 19 utils.system('service network start', ignore_status=ignore_status) 20 21 22 def stop(self, ignore_status=False): 23 utils.system('service network stop', ignore_status=ignore_status) 24 25 26 def disable_ip_local_loopback(self, ignore_status=False): 27 utils.system("echo '1' > /proc/sys/net/ipv4/route/no_local_loopback", 28 ignore_status=ignore_status) 29 utils.system('echo 1 > /proc/sys/net/ipv4/route/flush', 30 ignore_status=ignore_status) 31 32 33 def enable_ip_local_loopback(self, ignore_status=False): 34 utils.system("echo '0' > /proc/sys/net/ipv4/route/no_local_loopback", 35 ignore_status=ignore_status) 36 utils.system('echo 1 > /proc/sys/net/ipv4/route/flush', 37 ignore_status=ignore_status) 38 39 40 def process_mpstat(self, mpstat_out, sample_count, loud = True): 41 """Parses mpstat output of the following two forms: 42 02:10:17 0 0.00 0.00 0.00 0.00 0.00 0.00 \ 43 0.00 100.00 1012.87 44 02:10:13 PM 0 0.00 0.00 0.00 0.00 0.00 0.00 \ 45 0.00 100.00 1019.00 46 """ 47 mpstat_keys = ['time', 'CPU', 'user', 'nice', 'sys', 'iowait', 'irq', 48 'soft', 'steal', 'idle', 'intr/s'] 49 if loud: 50 print mpstat_out 51 52 # Remove the optional AM/PM appearing in time format 53 mpstat_out = mpstat_out.replace('AM', '') 54 mpstat_out = mpstat_out.replace('PM', '') 55 56 regex = re.compile('(\S+)') 57 stats = [] 58 for line in mpstat_out.splitlines()[3:]: 59 match = regex.findall(line) 60 # Skip the "Average" computed by mpstat. We are gonna compute the 61 # average ourself. Pick only the aggregate 'all' CPU results 62 if match and match[0] != 'Average:' and match[1] == 'all': 63 stats.append(dict(zip(mpstat_keys, match))) 64 65 if sample_count >= 5: 66 # Throw away first and last sample 67 stats = stats[1:-1] 68 69 cpu_stats = {} 70 for key in ['user', 'nice', 'sys', 'iowait', 'irq', 'soft', 'steal', 71 'idle', 'intr/s']: 72 x = [float(row[key]) for row in stats] 73 if len(x): 74 count = len(x) 75 else: 76 print 'net_utils.network_utils.process_mpstat: count is 0!!!\n' 77 count = 1 78 cpu_stats[key] = sum(x) / count 79 80 return cpu_stats 81 82 83def network(): 84 try: 85 from autotest_lib.client.bin.net import site_net_utils 86 return site_net_utils.network_utils() 87 except: 88 return network_utils() 89 90 91class network_interface(object): 92 93 ENABLE, DISABLE = (True, False) 94 95 def __init__(self, name): 96 autodir = os.environ['AUTODIR'] 97 self.ethtool = 'ethtool' 98 self._name = name 99 self.was_down = self.is_down() 100 self.orig_ipaddr = self.get_ipaddr() 101 self.was_loopback_enabled = self.is_loopback_enabled() 102 self._socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) 103 self._socket.settimeout(TIMEOUT) 104 self._socket.bind((name, raw_socket.ETH_P_ALL)) 105 106 107 def restore(self): 108 self.set_ipaddr(self.orig_ipaddr) 109 # TODO (msb): The additional conditional guard needs cleanup: 110 # Underlying driver should simply perform a noop 111 # for disabling loopback on an already-disabled device, 112 # instead of returning non-zero exit code. 113 114 # To avoid sending a RST to the autoserv ssh connection 115 # don't disable loopback until the IP address is restored. 116 if not self.was_loopback_enabled and self.is_loopback_enabled(): 117 self.disable_loopback() 118 if self.was_down: 119 self.down() 120 121 122 def get_name(self): 123 return self._name 124 125 126 def parse_ethtool(self, field, match, option='', next_field=''): 127 output = utils.system_output('%s %s %s' % (self.ethtool, 128 option, self._name)) 129 if output: 130 match = re.search('\n\s*%s:\s*(%s)%s' % 131 (field, match, next_field), output, re.S) 132 if match: 133 return match.group(1) 134 135 return '' 136 137 138 def get_stats(self): 139 stats = {} 140 stats_path = '/sys/class/net/%s/statistics/' % self._name 141 for stat in os.listdir(stats_path): 142 f = open(stats_path + stat, 'r') 143 if f: 144 stats[stat] = int(f.read()) 145 f.close() 146 return stats 147 148 149 def get_stats_diff(self, orig_stats): 150 stats = self.get_stats() 151 for stat in stats.keys(): 152 if stat in orig_stats: 153 stats[stat] = stats[stat] - orig_stats[stat] 154 else: 155 stats[stat] = stats[stat] 156 return stats 157 158 159 def get_driver(self): 160 driver_path = os.readlink('/sys/class/net/%s/device/driver' % 161 self._name) 162 return os.path.basename(driver_path) 163 164 165 def get_carrier(self): 166 f = open('/sys/class/net/%s/carrier' % self._name) 167 if not f: 168 return '' 169 carrier = f.read().strip() 170 f.close() 171 return carrier 172 173 174 def get_supported_link_modes(self): 175 result = self.parse_ethtool('Supported link modes', '.*', 176 next_field='Supports auto-negotiation') 177 return result.split() 178 179 180 def get_advertised_link_modes(self): 181 result = self.parse_ethtool('Advertised link modes', '.*', 182 next_field='Advertised auto-negotiation') 183 return result.split() 184 185 186 def is_autoneg_advertised(self): 187 result = self.parse_ethtool('Advertised auto-negotiation', 188 'Yes|No') 189 return result == 'Yes' 190 191 192 def get_speed(self): 193 return int(self.parse_ethtool('Speed', '\d+')) 194 195 196 def is_full_duplex(self): 197 result = self.parse_ethtool('Duplex', 'Full|Half') 198 return result == 'Full' 199 200 201 def is_autoneg_on(self): 202 result = self.parse_ethtool('Auto-negotiation', 'on|off') 203 return result == 'on' 204 205 206 def get_wakeon(self): 207 return self.parse_ethtool('Wake-on', '\w+') 208 209 210 def is_rx_summing_on(self): 211 result = self.parse_ethtool('rx-checksumming', 'on|off', '-k') 212 return result == 'on' 213 214 215 def is_tx_summing_on(self): 216 result = self.parse_ethtool('tx-checksumming', 'on|off', '-k') 217 return result == 'on' 218 219 220 def is_scatter_gather_on(self): 221 result = self.parse_ethtool('scatter-gather', 'on|off', '-k') 222 return result == 'on' 223 224 225 def is_tso_on(self): 226 result = self.parse_ethtool('tcp segmentation offload', 227 'on|off', '-k') 228 return result == 'on' 229 230 231 def is_pause_autoneg_on(self): 232 result = self.parse_ethtool('Autonegotiate', 'on|off', '-a') 233 return result == 'on' 234 235 236 def is_tx_pause_on(self): 237 result = self.parse_ethtool('TX', 'on|off', '-a') 238 return result == 'on' 239 240 241 def is_rx_pause_on(self): 242 result = self.parse_ethtool('RX', 'on|off', '-a') 243 return result == 'on' 244 245 246 def _set_loopback(self, mode, enable_disable): 247 return utils.system('%s -L %s %s %s' % 248 (self.ethtool, self._name, mode, enable_disable), 249 ignore_status=True) 250 251 252 def enable_loopback(self): 253 # If bonded do not set loopback mode. 254 # Try mac loopback first then phy loopback 255 # If both fail, raise an error 256 if (bond().is_enabled() or 257 (self._set_loopback('phyint', 'enable') > 0 and 258 self._set_loopback('mac', 'enable') > 0)): 259 raise error.TestError('Unable to enable loopback') 260 # Add a 1 second wait for drivers which do not have 261 # a synchronous loopback enable 262 # TODO (msb); Remove this wait once the drivers are fixed 263 if self.get_driver() in ['tg3', 'bnx2x']: 264 time.sleep(1) 265 self.wait_for_carrier(timeout=30) 266 267 268 def disable_loopback(self): 269 # If bonded, to not disable loopback. 270 # Try mac loopback first then phy loopback 271 # If both fail, raise an error 272 if (bond().is_enabled() or 273 (self._set_loopback('phyint', 'disable') > 0 and 274 self._set_loopback('mac', 'disable') > 0)): 275 raise error.TestError('Unable to disable loopback') 276 277 278 def is_loopback_enabled(self): 279 # Don't try ethtool -l on a bonded host 280 if bond().is_enabled(): 281 return False 282 output = utils.system_output('%s -l %s' % (self.ethtool, self._name)) 283 if output: 284 return 'enabled' in output 285 return False 286 287 288 def enable_promisc(self): 289 utils.system('ifconfig %s promisc' % self._name) 290 291 292 def disable_promisc(self): 293 utils.system('ifconfig %s -promisc' % self._name) 294 295 296 def get_hwaddr(self): 297 f = open('/sys/class/net/%s/address' % self._name) 298 hwaddr = f.read().strip() 299 f.close() 300 return hwaddr 301 302 303 def set_hwaddr(self, hwaddr): 304 utils.system('ifconfig %s hw ether %s' % (self._name, hwaddr)) 305 306 307 def add_maddr(self, maddr): 308 utils.system('ip maddr add %s dev %s' % (maddr, self._name)) 309 310 311 def del_maddr(self, maddr): 312 utils.system('ip maddr del %s dev %s' % (maddr, self._name)) 313 314 315 def get_ipaddr(self): 316 ipaddr = "0.0.0.0" 317 output = utils.system_output('ifconfig %s' % self._name) 318 if output: 319 match = re.search("inet addr:([\d\.]+)", output) 320 if match: 321 ipaddr = match.group(1) 322 return ipaddr 323 324 325 def set_ipaddr(self, ipaddr): 326 utils.system('ifconfig %s %s' % (self._name, ipaddr)) 327 328 329 def is_down(self): 330 output = utils.system_output('ifconfig %s' % self._name) 331 if output: 332 return 'UP' not in output 333 return False 334 335 def up(self): 336 utils.system('ifconfig %s up' % self._name) 337 338 339 def down(self): 340 utils.system('ifconfig %s down' % self._name) 341 342 343 def wait_for_carrier(self, timeout=60): 344 while timeout and self.get_carrier() != '1': 345 timeout -= 1 346 time.sleep(1) 347 if timeout == 0: 348 raise error.TestError('Timed out waiting for carrier.') 349 350 351 def send(self, buf): 352 self._socket.send(buf) 353 354 355 def recv(self, len): 356 return self._socket.recv(len) 357 358 359 def flush(self): 360 self._socket.close() 361 self._socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) 362 self._socket.settimeout(TIMEOUT) 363 self._socket.bind((self._name, raw_socket.ETH_P_ALL)) 364 365 366def netif(name): 367 try: 368 from autotest_lib.client.bin.net import site_net_utils 369 return site_net_utils.network_interface(name) 370 except: 371 return network_interface(name) 372 373 374class bonding(object): 375 """This class implements bonding interface abstraction.""" 376 377 NO_MODE = 0 378 AB_MODE = 1 379 AD_MODE = 2 380 381 def is_enabled(self): 382 raise error.TestError('Undefined') 383 384 385 def is_bondable(self): 386 raise error.TestError('Undefined') 387 388 389 def enable(self): 390 raise error.TestError('Undefined') 391 392 393 def disable(self): 394 raise error.TestError('Undefined') 395 396 397 def get_mii_status(self): 398 return {} 399 400 401 def get_mode(self): 402 return bonding.NO_MODE 403 404 405 def wait_for_state_change(self): 406 """Wait for bonding state change. 407 408 Wait up to 90 seconds to successfully ping the gateway. 409 This is to know when LACP state change has converged. 410 (0 seconds is 3x lacp timeout, use by protocol) 411 """ 412 413 netif('eth0').wait_for_carrier(timeout=60) 414 wait_time = 0 415 while wait_time < 100: 416 time.sleep(10) 417 if not utils.ping_default_gateway(): 418 return True 419 wait_time += 10 420 return False 421 422 423 def get_active_interfaces(self): 424 return [] 425 426 427 def get_slave_interfaces(self): 428 return [] 429 430 431def bond(): 432 try: 433 from autotest_lib.client.bin.net import site_net_utils 434 return site_net_utils.bonding() 435 except: 436 return bonding() 437 438 439class raw_socket(object): 440 """This class implements an raw socket abstraction.""" 441 ETH_P_ALL = 0x0003 # Use for binding a RAW Socket to all protocols 442 SOCKET_TIMEOUT = 1 443 def __init__(self, iface_name): 444 """Initialize an interface for use. 445 446 Args: 447 iface_name: 'eth0' interface name ('eth0, eth1,...') 448 """ 449 self._name = iface_name 450 self._socket = None 451 self._socket_timeout = raw_socket.SOCKET_TIMEOUT 452 socket.setdefaulttimeout(self._socket_timeout) 453 if self._name is None: 454 raise error.TestError('Invalid interface name') 455 456 457 def socket(self): 458 return self._socket 459 460 461 def socket_timeout(self): 462 """Get the timeout use by recv_from""" 463 return self._socket_timeout 464 465 466 def set_socket_timeout(self, timeout): 467 """Set the timeout use by recv_from. 468 469 Args: 470 timeout: time in seconds 471 """ 472 self._socket_timeout = timeout 473 474 def open(self, protocol=None): 475 """Opens the raw socket to send and receive. 476 477 Args: 478 protocol : short in host byte order. None if ALL 479 """ 480 if self._socket is not None: 481 raise error.TestError('Raw socket already open') 482 483 if protocol is None: 484 self._socket = socket.socket(socket.PF_PACKET, 485 socket.SOCK_RAW) 486 487 self._socket.bind((self._name, self.ETH_P_ALL)) 488 else: 489 self._socket = socket.socket(socket.PF_PACKET, 490 socket.SOCK_RAW, 491 socket.htons(protocol)) 492 self._socket.bind((self._name, self.ETH_P_ALL)) 493 494 self._socket.settimeout(1) # always running with 1 second timeout 495 496 def close(self): 497 """ Close the raw socket""" 498 if self._socket is not None: 499 self._socket.close() 500 self._socket = None 501 else: 502 raise error.TestError('Raw socket not open') 503 504 505 def recv(self, timeout): 506 """Synchroneous receive. 507 508 Receives one packet from the interface and returns its content 509 in a string. Wait up to timeout for the packet if timeout is 510 not 0. This function filters out all the packets that are 511 less than the minimum ethernet packet size (60+crc). 512 513 Args: 514 timeout: max time in seconds to wait for the read to complete. 515 '0', wait for ever until a valid packet is received 516 517 Returns: 518 packet: None no packet was received 519 a binary string containing the received packet. 520 time_left: amount of time left in timeout 521 """ 522 if self._socket is None: 523 raise error.TestError('Raw socket not open') 524 525 time_left = timeout 526 packet = None 527 while time_left or (timeout == 0): 528 try: 529 packet = self._socket.recv(ethernet.ETH_PACKET_MAX_SIZE) 530 if len(packet) >= (ethernet.ETH_PACKET_MIN_SIZE-4): 531 break 532 packet = None 533 if timeout and time_left: 534 time_left -= raw_socket.SOCKET_TIMEOUT 535 except socket.timeout: 536 packet = None 537 if timeout and time_left: 538 time_left -= raw_socket.SOCKET_TIMEOUT 539 540 return packet, time_left 541 542 543 def send(self, packet): 544 """Send an ethernet packet.""" 545 if self._socket is None: 546 raise error.TestError('Raw socket not open') 547 548 self._socket.send(packet) 549 550 551 def send_to(self, dst_mac, src_mac, protocol, payload): 552 """Send an ethernet frame. 553 554 Send an ethernet frame, formating the header. 555 556 Args: 557 dst_mac: 'byte string' 558 src_mac: 'byte string' 559 protocol: short in host byte order 560 payload: 'byte string' 561 """ 562 if self._socket is None: 563 raise error.TestError('Raw socket not open') 564 try: 565 packet = ethernet.pack(dst_mac, src_mac, protocol, payload) 566 except: 567 raise error.TestError('Invalid Packet') 568 self.send(packet) 569 570 571 def recv_from(self, dst_mac, src_mac, protocol): 572 """Receive an ethernet frame that matches the dst, src and proto. 573 574 Filters all received packet to find a matching one, then unpack 575 it and present it to the caller as a frame. 576 577 Waits up to self._socket_timeout for a matching frame before 578 returning. 579 580 Args: 581 dst_mac: 'byte string'. None do not use in filter. 582 src_mac: 'byte string'. None do not use in filter. 583 protocol: short in host byte order. None do not use in filter. 584 585 Returns: 586 ethernet frame: { 'dst' : byte string, 587 'src' : byte string, 588 'proto' : short in host byte order, 589 'payload' : byte string 590 } 591 """ 592 start_time = time.clock() 593 timeout = self._socket_timeout 594 while 1: 595 frame = None 596 packet, timeout = self.recv(timeout) 597 if packet is not None: 598 frame = ethernet.unpack(packet) 599 if ((src_mac is None or frame['src'] == src_mac) and 600 (dst_mac is None or frame['dst'] == dst_mac) and 601 (protocol is None or frame['proto'] == protocol)): 602 break; 603 elif (timeout == 0 or 604 time.clock() - start_time > float(self._socket_timeout)): 605 frame = None 606 break 607 else: 608 if (timeout == 0 or 609 time.clock() - start_time > float(self._socket_timeout)): 610 frame = None 611 break 612 continue 613 614 return frame 615 616 617class ethernet(object): 618 """Provide ethernet packet manipulation methods.""" 619 HDR_LEN = 14 # frame header length 620 CHECKSUM_LEN = 4 # frame checksum length 621 622 # Ethernet payload types - http://standards.ieee.org/regauth/ethertype 623 ETH_TYPE_IP = 0x0800 # IP protocol 624 ETH_TYPE_ARP = 0x0806 # address resolution protocol 625 ETH_TYPE_CDP = 0x2000 # Cisco Discovery Protocol 626 ETH_TYPE_8021Q = 0x8100 # IEEE 802.1Q VLAN tagging 627 ETH_TYPE_IP6 = 0x86DD # IPv6 protocol 628 ETH_TYPE_LOOPBACK = 0x9000 # used to test interfaces 629 ETH_TYPE_LLDP = 0x88CC # LLDP frame type 630 631 ETH_PACKET_MAX_SIZE = 1518 # maximum ethernet frane size 632 ETH_PACKET_MIN_SIZE = 64 # minimum ethernet frane size 633 634 ETH_LLDP_DST_MAC = '01:80:C2:00:00:0E' # LLDP destination mac 635 636 FRAME_KEY_DST_MAC = 'dst' # frame destination mac address 637 FRAME_KEY_SRC_MAC = 'src' # frame source mac address 638 FRAME_KEY_PROTO = 'proto' # frame protocol 639 FRAME_KEY_PAYLOAD = 'payload' # frame payload 640 641 642 def __init__(self): 643 pass; 644 645 646 @staticmethod 647 def mac_string_to_binary(hwaddr): 648 """Converts a MAC address text string to byte string. 649 650 Converts a MAC text string from a text string 'aa:aa:aa:aa:aa:aa' 651 to a byte string 'xxxxxxxxxxxx' 652 653 Args: 654 hwaddr: a text string containing the MAC address to convert. 655 656 Returns: 657 A byte string. 658 """ 659 val = ''.join([chr(b) for b in [int(c, 16) \ 660 for c in hwaddr.split(':',6)]]) 661 return val 662 663 664 @staticmethod 665 def mac_binary_to_string(hwaddr): 666 """Converts a MAC address byte string to text string. 667 668 Converts a MAC byte string 'xxxxxxxxxxxx' to a text string 669 'aa:aa:aa:aa:aa:aa' 670 671 Args: 672 hwaddr: a byte string containing the MAC address to convert. 673 674 Returns: 675 A text string. 676 """ 677 return "%02x:%02x:%02x:%02x:%02x:%02x" % tuple(map(ord,hwaddr)) 678 679 680 @staticmethod 681 def pack(dst, src, protocol, payload): 682 """Pack a frame in a byte string. 683 684 Args: 685 dst: destination mac in byte string format 686 src: src mac address in byte string format 687 protocol: short in network byte order 688 payload: byte string payload data 689 690 Returns: 691 An ethernet frame with header and payload in a byte string. 692 """ 693 # numbers are converted to network byte order (!) 694 frame = struct.pack("!6s6sH", dst, src, protocol) + payload 695 return frame 696 697 698 @staticmethod 699 def unpack(raw_frame): 700 """Unpack a raw ethernet frame. 701 702 Returns: 703 None on error 704 { 'dst' : byte string, 705 'src' : byte string, 706 'proto' : short in host byte order, 707 'payload' : byte string 708 } 709 """ 710 packet_len = len(raw_frame) 711 if packet_len < ethernet.HDR_LEN: 712 return None 713 714 payload_len = packet_len - ethernet.HDR_LEN 715 frame = {} 716 frame[ethernet.FRAME_KEY_DST_MAC], \ 717 frame[ethernet.FRAME_KEY_SRC_MAC], \ 718 frame[ethernet.FRAME_KEY_PROTO] = \ 719 struct.unpack("!6s6sH", raw_frame[:ethernet.HDR_LEN]) 720 frame[ethernet.FRAME_KEY_PAYLOAD] = \ 721 raw_frame[ethernet.HDR_LEN:ethernet.HDR_LEN+payload_len] 722 return frame 723 724 725def ethernet_packet(): 726 try: 727 from autotest_lib.client.bin.net import site_net_utils 728 return site_net_utils.ethernet() 729 except: 730 return ethernet() 731