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