net_utils.py revision 12b45582c04b2417036a6f11afc843ac5fddea50
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
8import 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