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