ipv6 scan & ping 75/5375/1
authorYaroslav Brustinov <[email protected]>
Sun, 29 Jan 2017 15:14:41 +0000 (17:14 +0200)
committerYaroslav Brustinov <[email protected]>
Thu, 2 Feb 2017 11:42:36 +0000 (13:42 +0200)
Change-Id: I4f8112b4c942d149da5ea3f0ee01ac82d7fe32cc
Signed-off-by: Yaroslav Brustinov <[email protected]>
22 files changed:
scripts/automation/trex_control_plane/stl/console/trex_console.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_api.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_arp.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_icmp.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py [new file with mode: 0755]
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py
scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py
scripts/external_libs/scapy-2.3.1/python2/scapy/layers/inet6.py
scripts/external_libs/scapy-2.3.1/python3/scapy/layers/inet6.py
src/main_dpdk.cpp
src/rpc-server/commands/trex_rpc_cmd_general.cpp
src/stateless/cp/trex_api_class.h
src/stateless/cp/trex_stateless.cpp
src/stateless/cp/trex_stateless_port.cpp
src/stateless/cp/trex_stateless_port.h
src/trex_port_attr.cpp
src/trex_port_attr.h

index 5e68cdf..5d758fb 100755 (executable)
@@ -350,6 +350,11 @@ class TRexConsole(TRexGeneralCmd):
         '''Resolve ARP for ports'''
         self.stateless_client.resolve_line(line)
 
+    @verify_connected
+    def do_scan6(self, line):
+        '''Search for IPv6 neighbors'''
+        self.stateless_client.scan6_line(line)
+
     def help_resolve (self):
         self.do_resolve("-h")
 
index b090438..d6a620a 100644 (file)
@@ -9,10 +9,16 @@ class RXServiceAPI(object):
     LAYER_MODE_L2  = 1
     LAYER_MODE_L3  = 2
     
-    def __init__ (self, port, layer_mode = LAYER_MODE_ANY, queue_size = 100):
+    def __init__(self, port, layer_mode = LAYER_MODE_ANY, queue_size = 100, timeout = None, retries = None, retry_delay = 0.1):
         self.port = port
         self.queue_size = queue_size
         self.layer_mode = layer_mode
+        self.timeout = timeout
+        self.retries = retries
+        if retries is None and timeout is None:
+            self.retries = 0
+        self.retry_delay = retry_delay
+        self.init_ts = time.time()
 
     ################### virtual methods ######################
     
@@ -47,7 +53,7 @@ class RXServiceAPI(object):
         """
         raise NotImplementedError()
 
-    def on_pkt_rx (self, pkt, start_ts):
+    def on_pkt_rx(self, pkt, start_ts):
         """
             called for each packet arriving on RX
 
@@ -65,13 +71,10 @@ class RXServiceAPI(object):
         raise NotImplementedError()
 
         
-    def on_timeout_err (self, retries):
+    def on_timeout(self):
         """
             called when a timeout occurs
 
-            :parameters:
-                retries - how many times was the service retring before failing
-                
             :returns:
                 RC object
 
@@ -80,7 +83,7 @@ class RXServiceAPI(object):
 
         
     ##################### API ######################
-    def execute (self, retries = 0):
+    def execute(self, *a, **k):
         
         # sanity check
         rc = self.__sanity()
@@ -97,12 +100,12 @@ class RXServiceAPI(object):
         try:
 
             # add the stream(s)
-            self.port.add_streams(self.generate_request())
+            self.port.add_streams(self.generate_request(*a, **k))
             rc = self.port.set_rx_queue(size = self.queue_size)
             if not rc:
                 return rc
 
-            return self.__execute_internal(retries)
+            return self.__execute_internal()
 
         finally:
             # best effort restore
@@ -137,20 +140,21 @@ class RXServiceAPI(object):
         
 
     # main resolve function
-    def __execute_internal (self, retries):
+    def __execute_internal (self):
 
-        # retry for 'retries'
+        # retry for 'retries' or until timeout
         index = 0
         while True:
             rc = self.execute_iteration()
             if rc is not None:
                 return rc
 
-            if index >= retries:
-                return self.on_timeout_err(retries)
+            if (self.retries is not None and index >= self.retries or
+                        self.timeout is not None and time.time() - self.init_ts >= self.timeout):
+                return self.on_timeout()
 
             index += 1
-            time.sleep(0.1)
+            time.sleep(self.retry_delay)
 
 
 
index 3cf9704..a82c66d 100644 (file)
@@ -8,8 +8,8 @@ from scapy.layers.l2 import Ether, ARP
 
 class RXServiceARP(RXServiceAPI):
     
-    def __init__ (self, port_id):
-        super(RXServiceARP, self).__init__(port_id, layer_mode = RXServiceAPI.LAYER_MODE_L3)
+    def __init__(self, port_id, *a, **k):
+        super(RXServiceARP, self).__init__(port_id, layer_mode = RXServiceAPI.LAYER_MODE_L3, *a, **k)
 
     def get_name (self):
         return "ARP"
@@ -49,8 +49,8 @@ class RXServiceARP(RXServiceAPI):
         return self.port.ok({'psrc' : arp.psrc, 'hwsrc': arp.hwsrc})
 
 
-    def on_timeout_err (self, retries):
-        return self.port.err('failed to receive ARP response ({0} retries)'.format(retries))
+    def on_timeout(self):
+        return self.port.err('failed to receive ARP response ({0} retries)'.format(self.retries))
 
 
 
index ae57b16..612b6a2 100644 (file)
@@ -9,9 +9,9 @@ from scapy.layers.inet import IP, ICMP
 
 class RXServiceICMP(RXServiceAPI):
     
-    def __init__ (self, port, ping_ip, pkt_size):
+    def __init__ (self, port, ping_ip, pkt_size, *a, **k):
         
-        super(RXServiceICMP, self).__init__(port, layer_mode = RXServiceAPI.LAYER_MODE_L3)
+        super(RXServiceICMP, self).__init__(port, layer_mode = RXServiceAPI.LAYER_MODE_L3, *a, **k)
         self.ping_ip  = ping_ip
         self.pkt_size = pkt_size
 
@@ -78,6 +78,6 @@ class RXServiceICMP(RXServiceAPI):
 
 
     # return the str of a timeout err
-    def on_timeout_err (self, retries):
+    def on_timeout(self):
         return self.port.ok('Request timed out.')
 
diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/rx_services/trex_stl_rx_service_ipv6.py
new file mode 100755 (executable)
index 0000000..c2c8ebc
--- /dev/null
@@ -0,0 +1,179 @@
+from .trex_stl_rx_service_api import RXServiceAPI
+
+from ..trex_stl_streams import STLStream, STLTXSingleBurst
+from ..trex_stl_packet_builder_scapy import *
+from ..trex_stl_types import *
+
+from scapy.layers.l2 import Ether
+from scapy.layers.inet6 import *
+import time
+
+
+class RXServiceIPv6(RXServiceAPI):
+
+    def __init__(self, port, dst_ip, *a, **k):
+        RXServiceAPI.__init__(self, port, *a, **k)
+        self.attr = port.get_ts_attr()
+        self.src_mac = self.attr['layer_cfg']['ether']['src']
+        mac_for_ip   = port.info.get('hw_mac', self.src_mac)
+        self.src_ip  = generate_ipv6(mac_for_ip)
+        self.mld_ip  = generate_ipv6_solicited_node(mac_for_ip)
+        self.dst_ip  = dst_ip
+        self.responses = {}
+
+    def pre_execute(self):
+        return self.port.ok()
+
+    def send_intermediate(self, streams):
+        self.port.remove_all_streams()
+        self.port.add_streams(streams)
+        mult = {'op': 'abs', 'type' : 'percentage', 'value': 100}
+        self.port.start(mul = mult, force = True, duration = -1, mask = 0xffffffff)
+
+    def generate_ns(self, dst_mac, dst_ip):
+        pkt = (Ether(src = self.src_mac, dst = dst_mac) /
+               IPv6(src = self.src_ip, dst = dst_ip) /
+               ICMPv6ND_NS(tgt = dst_ip) /
+               ICMPv6NDOptSrcLLAddr(lladdr = self.src_mac))
+        return STLStream(packet = STLPktBuilder(pkt = pkt), mode = STLTXSingleBurst(total_pkts = 2))
+
+    def generate_na(self, dst_mac, dst_ip):
+        pkt = (Ether(src = self.src_mac, dst = dst_mac) /
+               IPv6(src = self.src_ip, dst = dst_ip) /
+               ICMPv6ND_NA(tgt = self.src_ip, R = 0, S = 1, O = 1))
+        return STLStream(packet = STLPktBuilder(pkt = pkt), mode = STLTXSingleBurst(total_pkts = 2))
+
+    def generate_ns_na(self, dst_mac, dst_ip):
+        return [self.generate_ns(dst_mac, dst_ip), self.generate_na(dst_mac, dst_ip)]
+
+    def execute(self, *a, **k):
+        mult = self.attr['multicast']['enabled']
+        try:
+            if mult != True:
+                self.port.set_attr(multicast = True) # response might be multicast
+            return RXServiceAPI.execute(self, *a, **k)
+        finally:
+            if mult != True:
+                self.port.set_attr(multicast = False)
+
+
+class RXServiceIPv6Scan(RXServiceIPv6):
+    ''' Ping with given IPv6 (usually all nodes address) and wait for responses until timeout '''
+
+    def get_name(self):
+        return 'IPv6 scanning'
+
+    def generate_request(self):
+        dst_mac = multicast_mac_from_ipv6(self.dst_ip)
+        dst_mld_ip = 'ff02::16'
+        dst_mld_mac = multicast_mac_from_ipv6(dst_mld_ip)
+
+        mld_pkt = (Ether(src = self.src_mac, dst = dst_mld_mac) /
+                   IPv6(src = self.src_ip, dst = dst_mld_ip, hlim = 1) /
+                   IPv6ExtHdrHopByHop(options = [RouterAlert(), PadN()]) /
+                   ICMPv6MLReportV2() /
+                   MLDv2Addr(type = 4, len = 0, multicast_addr = 'ff02::2'))
+        ping_pkt = (Ether(src = self.src_mac, dst = dst_mac) /
+                    IPv6(src = self.src_ip, dst = self.dst_ip, hlim = 1) /
+                    ICMPv6EchoRequest())
+
+        mld_stream = STLStream(packet = STLPktBuilder(pkt = mld_pkt),
+                               mode = STLTXSingleBurst(total_pkts = 2))
+        ping_stream = STLStream(packet = STLPktBuilder(pkt = ping_pkt),
+                                mode = STLTXSingleBurst(total_pkts = 2))
+        return [mld_stream, ping_stream]
+
+
+    def on_pkt_rx(self, pkt, start_ts):
+        # convert to scapy
+        scapy_pkt = Ether(pkt['binary'])
+
+        if scapy_pkt.haslayer('ICMPv6ND_NS') and scapy_pkt.haslayer('ICMPv6NDOptSrcLLAddr'):
+            node_mac = scapy_pkt.getlayer(ICMPv6NDOptSrcLLAddr).lladdr
+            node_ip = scapy_pkt.getlayer(IPv6).src
+            if node_ip not in self.responses:
+                self.send_intermediate(self.generate_ns_na(node_mac, node_ip))
+
+        elif scapy_pkt.haslayer('ICMPv6ND_NA'):
+            is_router = scapy_pkt.getlayer(ICMPv6ND_NA).R
+            node_ip = scapy_pkt.getlayer(ICMPv6ND_NA).tgt
+            dst_ip  = scapy_pkt.getlayer(IPv6).dst
+            node_mac = scapy_pkt.src
+            if node_ip not in self.responses and dst_ip == self.src_ip:
+                self.responses[node_ip] = {'type': 'Router' if is_router else 'Host', 'mac': node_mac}
+
+        elif scapy_pkt.haslayer('ICMPv6EchoReply'):
+            node_mac = scapy_pkt.src
+            node_ip = scapy_pkt.getlayer(IPv6).src
+            if node_ip == self.dst_ip:
+                return self.port.ok([{'ipv6': node_ip, 'mac': node_mac}])
+            if node_ip not in self.responses:
+                self.send_intermediate(self.generate_ns_na(node_mac, node_ip))
+
+
+    def on_timeout(self):
+        return self.port.ok([{'type': v['type'], 'mac':  v['mac'], 'ipv6': k} for k, v in self.responses.items()])
+
+
+class RXServiceICMPv6(RXServiceIPv6):
+    '''
+    Ping some IPv6 location.
+    If the dest MAC is found from scanning, use it.
+    Otherwise, send to default port dest.
+    '''
+
+    def __init__(self, port, pkt_size, dst_mac = None, *a, **k):
+        super(RXServiceICMPv6, self).__init__(port, *a, **k)
+        self.pkt_size = pkt_size
+        self.dst_mac  = dst_mac
+
+    def get_name(self):
+        return 'PING6'
+
+    def pre_execute(self):
+        if self.dst_mac is None and not self.port.is_resolved():
+            return self.port.err('ping - port has an unresolved destination, cannot determine next hop MAC address')
+        return self.port.ok()
+
+
+    # return a list of streams for request
+    def generate_request(self):
+        attrs = self.port.get_ts_attr()
+
+        if self.dst_mac is None:
+            self.dst_mac = attrs['layer_cfg']['ether']['dst']
+
+        ping_pkt = (Ether(src = self.src_mac, dst = self.dst_mac) / 
+                    IPv6(src = self.src_ip, dst = self.dst_ip) /
+                    ICMPv6EchoRequest())
+        pad = max(0, self.pkt_size - len(ping_pkt))
+        ping_pkt /= pad * 'x'
+
+        return STLStream( packet = STLPktBuilder(pkt = ping_pkt), mode = STLTXSingleBurst(total_pkts = 2) )
+
+
+    def on_pkt_rx(self, pkt, start_ts):
+        scapy_pkt = Ether(pkt['binary'])
+
+        if scapy_pkt.haslayer('ICMPv6EchoReply'):
+            node_ip = scapy_pkt.getlayer(IPv6).src
+            hlim = scapy_pkt.getlayer(IPv6).hlim
+            dst_ip = scapy_pkt.getlayer(IPv6).dst
+
+            if dst_ip != self.src_ip: # not our ping
+                return
+
+            dt = pkt['ts'] - start_ts
+            return self.port.ok('Reply from {0}: bytes={1}, time={2:.2f}ms, hlim={3}'.format(node_ip, len(pkt['binary']), dt * 1000, hlim))
+
+        if scapy_pkt.haslayer('ICMPv6ND_NS') and scapy_pkt.haslayer('ICMPv6NDOptSrcLLAddr'):
+            node_mac = scapy_pkt.getlayer(ICMPv6NDOptSrcLLAddr).lladdr
+            node_ip = scapy_pkt.getlayer(IPv6).src
+            self.send_intermediate(self.generate_ns_na(node_mac, node_ip))
+
+
+    # return the str of a timeout err
+    def on_timeout(self):
+        return self.port.ok('Request timed out.')
+
+
index c88a68b..a6aa47a 100755 (executable)
@@ -13,12 +13,12 @@ from .trex_stl_async_client import CTRexAsyncClient
 
 from .utils import parsing_opts, text_tables, common
 from .utils.common import *
+from .utils.text_tables import TRexTextTable
 from .utils.text_opts import *
 from functools import wraps
 from texttable import ansi_len
 
-
-from collections import namedtuple
+from collections import namedtuple, defaultdict
 from yaml import YAMLError
 import time
 import datetime
@@ -547,7 +547,7 @@ class STLClient(object):
         self.connected = False
 
         # API classes
-        self.api_vers = [ {'type': 'core', 'major': 3, 'minor': 0 } ]
+        self.api_vers = [ {'type': 'core', 'major': 3, 'minor': 1 } ]
         self.api_h = {'core': None}
 
         # logger
@@ -820,6 +820,17 @@ class STLClient(object):
         return rc
 
 
+    def __scan6(self, port_id_list = None, timeout = 5):
+        port_id_list = self.__ports(port_id_list)
+
+        resp = {}
+
+        for port_id in port_id_list:
+            resp[port_id] = self.ports[port_id].scan6(timeout)
+
+        return resp
+
+
     def __set_port_attr (self, port_id_list = None, attr_dict = None):
 
         port_id_list = self.__ports(port_id_list)
@@ -1893,13 +1904,13 @@ class STLClient(object):
             
 
     @__api_check(True)
-    def ping_ip (self, src_port, dst_ipv4, pkt_size = 64, count = 5, interval_sec = 1):
+    def ping_ip (self, src_port, dst_ip, pkt_size = 64, count = 5, interval_sec = 1):
         """
             Pings an IP address through a port
 
             :parameters:
                  src_port     - on which port_id to send the ICMP PING request
-                 dst_ipv4     - which IP to ping
+                 dst_ip       - which IP to ping
                  pkt_size     - packet size to use
                  count        - how many times to ping
                  interval_sec - how much time to wait between pings
@@ -1912,8 +1923,8 @@ class STLClient(object):
         if src_port not in self.get_all_ports():
             raise STLError("src port is not a valid port id")
         
-        if not is_valid_ipv4(dst_ipv4):
-            raise STLError("dst_ipv4 is not a valid IPv4 address: '{0}'".format(dst_ipv4))
+        if not (is_valid_ipv4(dst_ip) or is_valid_ipv6(dst_ip)):
+            raise STLError("dst_ip is not a valid IPv4/6 address: '{0}'".format(dst_ip))
             
         if (pkt_size < 64) or (pkt_size > 9216):
             raise STLError("pkt_size should be a value between 64 and 9216: '{0}'".format(pkt_size))
@@ -1921,15 +1932,23 @@ class STLClient(object):
         validate_type('count', count, int)
         validate_type('interval_sec', interval_sec, (int, float))
         
-        self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ipv4,
+        self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ip,
                                                                                        src_port,
                                                                                        pkt_size))
         
         # no async messages
         with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC):
             self.logger.log('')
+            dst_mac = None
+            if ':' in dst_ip: # ipv6
+                rc = self.ports[src_port].scan6(dst_ip = dst_ip)
+                if not rc:
+                    raise STLError(rc)
+                replies = rc.data()
+                if len(replies) == 1:
+                    dst_mac = replies[0]['mac']
             for i in range(count):
-                rc = self.ports[src_port].ping(ping_ipv4 = dst_ipv4, pkt_size = pkt_size)
+                rc = self.ports[src_port].ping(ping_ip = dst_ip, pkt_size = pkt_size, dst_mac = dst_mac)
                 if not rc:
                     raise STLError(rc)
                     
@@ -2895,7 +2914,8 @@ class STLClient(object):
                        link_up = None,
                        led_on = None,
                        flow_ctrl = None,
-                       resolve = True):
+                       resolve = True,
+                       multicast = None):
         """
             Set port attributes
 
@@ -2905,6 +2925,7 @@ class STLClient(object):
                 led_on           - True or False
                 flow_ctrl        - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable
                 resolve          - if true, in case a destination address is configured as IPv4 try to resolve it
+                multicast        - enable receiving multicast, True or False
             :raises:
                 + :exe:'STLError'
 
@@ -2918,6 +2939,7 @@ class STLClient(object):
         validate_type('link_up', link_up, (bool, type(None)))
         validate_type('led_on', led_on, (bool, type(None)))
         validate_type('flow_ctrl', flow_ctrl, (int, type(None)))
+        validate_type('multicast', multicast, (bool, type(None)))
     
         # common attributes for all ports
         cmn_attr_dict = {}
@@ -2926,6 +2948,7 @@ class STLClient(object):
         cmn_attr_dict['link_status']     = link_up
         cmn_attr_dict['led_status']      = led_on
         cmn_attr_dict['flow_ctrl_mode']  = flow_ctrl
+        cmn_attr_dict['multicast']       = multicast
         
         # each port starts with a set of the common attributes
         attr_dict = [dict(cmn_attr_dict) for _ in ports]
@@ -3022,8 +3045,62 @@ class STLClient(object):
 
     # alias
     arp = resolve
-            
-        
+
+
+    @__api_check(True)
+    def scan6(self, ports = None, timeout = 5, verbose = False):
+        """
+            Search for IPv6 devices on ports
+
+            :parameters:
+                ports          - for which ports to apply a unique sniffer (each port gets a unique file)
+                timeout        - how much time to wait for responses
+                verbose        - log for each request the response
+            :return:
+                list of dictionaries per neighbor:
+                    type   - type of device: 'Router' or 'Host'
+                    mac    - MAC address of device
+                    ipv6   - IPv6 address of device
+            :raises:
+                + :exe:'STLError'
+
+        """
+        ports = ports if ports is not None else self.get_acquired_ports()
+        ports = self._validate_port_list(ports)
+
+        self.logger.pre_cmd('Scanning network for IPv6 nodes on port(s) {0}:'.format(ports))
+
+        with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC):
+            rc_per_port = self.__scan6(ports, timeout)
+
+        self.logger.post_cmd(rc_per_port)
+
+        if verbose:
+            for port, rc in rc_per_port.items():
+                if not rc:
+                    self.logger.log(format_text(rc, 'bold'))
+                elif rc.data():
+                    scan_table = TRexTextTable()
+                    scan_table.set_cols_align(['c', 'c', 'l'])
+                    scan_table.header(['Device', 'MAC', 'IPv6 address'])
+                    scan_table.set_cols_width([9, 19, 42])
+
+                    resp = 'Port %s - IPv6 search result:' % port
+                    self.logger.log(format_text(resp, 'bold'))
+                    node_types = defaultdict(list)
+                    for reply in rc.data():
+                        node_types[reply['type']].append(reply)
+                    for key in sorted(node_types.keys()):
+                        for reply in node_types[key]:
+                            scan_table.add_row([key, reply['mac'], reply['ipv6']])
+                    self.logger.log(scan_table.draw())
+                    self.logger.log('')
+                else:
+                    self.logger.log(format_text('Port %s: no replies! Try to ping with explicit address.' % port, 'bold'))
+
+        return rc_per_port
+
+
     @__api_check(True)
     def start_capture (self, tx_ports = None, rx_ports = None, limit = 1000, mode = 'fixed'):
         """
@@ -3372,7 +3449,7 @@ class STLClient(object):
                                          "ping",
                                          self.ping_line.__doc__,
                                          parsing_opts.SINGLE_PORT,
-                                         parsing_opts.PING_IPV4,
+                                         parsing_opts.PING_IP,
                                          parsing_opts.PKT_SIZE,
                                          parsing_opts.PING_COUNT)
 
@@ -3382,7 +3459,7 @@ class STLClient(object):
             
         # IP ping
         # source ports maps to ports as a single port
-        self.ping_ip(opts.ports[0], opts.ping_ipv4, opts.pkt_size, opts.count)
+        self.ping_ip(opts.ports[0], opts.ping_ip, opts.pkt_size, opts.count)
 
         
     @__console
@@ -3918,6 +3995,7 @@ class STLClient(object):
                                          parsing_opts.LED_STATUS,
                                          parsing_opts.FLOW_CTRL,
                                          parsing_opts.SUPPORTED,
+                                         parsing_opts.MULTICAST,
                                          )
 
         opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports())
@@ -3925,6 +4003,7 @@ class STLClient(object):
             return opts
 
         opts.prom            = parsing_opts.ON_OFF_DICT.get(opts.prom)
+        opts.mult            = parsing_opts.ON_OFF_DICT.get(opts.mult)
         opts.link            = parsing_opts.UP_DOWN_DICT.get(opts.link)
         opts.led             = parsing_opts.ON_OFF_DICT.get(opts.led)
         opts.flow_ctrl       = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl)
@@ -3940,6 +4019,7 @@ class STLClient(object):
             print('')
             print('Supported attributes for current NICs:')
             print('  Promiscuous:   yes')
+            print('  Multicast:     yes')
             print('  Link status:   %s' % info['link_change_supported'])
             print('  LED status:    %s' % info['led_change_supported'])
             print('  Flow control:  %s' % info['fc_supported'])
@@ -3951,10 +4031,29 @@ class STLClient(object):
                                opts.prom,
                                opts.link,
                                opts.led,
-                               opts.flow_ctrl)
-                         
-               
-             
+                               opts.flow_ctrl,
+                               multicast = opts.mult)
+
+
+    @__console
+    def set_rx_sniffer_line (self, line):
+        '''Sets a port sniffer on RX channel in form of a PCAP file'''
+
+        parser = parsing_opts.gen_parser(self,
+                                         "set_rx_sniffer",
+                                         self.set_rx_sniffer_line.__doc__,
+                                         parsing_opts.PORT_LIST_WITH_ALL,
+                                         parsing_opts.OUTPUT_FILENAME,
+                                         parsing_opts.LIMIT)
+
+        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
+        if not opts:
+            return opts
+
+        self.set_rx_sniffer(opts.ports, opts.output_filename, opts.limit)
+
+        return RC_OK()
+        
 
     @__console
     def resolve_line (self, line):
@@ -3976,6 +4075,27 @@ class STLClient(object):
         return RC_OK()
         
     
+    @__console
+    def scan6_line(self, line):
+        '''Search for IPv6 neighbors'''
+
+        parser = parsing_opts.gen_parser(self,
+                                         "scan6",
+                                         self.scan6_line.__doc__,
+                                         parsing_opts.PORT_LIST_WITH_ALL,
+                                         parsing_opts.TIMEOUT)
+
+        opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
+        if not opts:
+            return opts
+
+        rc_per_port = self.scan6(ports = opts.ports, timeout = opts.timeout, verbose = True)
+        #for port, rc in rc_per_port.items():
+        #    if rc and len(rc.data()) == 1 and not self.ports[port].is_resolved():
+        #        self.ports[port].set_l2_mode(rc.data()[0][1])
+        return RC_OK()
+
+
     @__console
     def set_l2_mode_line (self, line):
         '''Configures a port in L2 mode'''
index c5fbab9..679068a 100755 (executable)
@@ -57,6 +57,40 @@ def mac_str_to_num (mac_buffer):
     return _buffer_to_num(mac_buffer)
 
 
+# RFC 3513
+def generate_ipv6(mac_str, prefix = 'fe80'):
+    mac_arr = mac_str.split(':')
+    assert len(mac_arr) == 6, 'mac should be in format of 11:22:33:44:55:66, got: %s' % mac_str
+    mac_arr[0] = '%x' % (int(mac_arr[0], 16) ^ 2) # invert second bit
+    return '%s::%s%s:%sff:fe%s:%s%s' % tuple([prefix] + mac_arr[:3] + mac_arr[3:])
+
+# RFC 4291
+def generate_ipv6_solicited_node(mac_str):
+    mac_arr = mac_str.split(':')
+    assert len(mac_arr) == 6, 'mac should be in format of 11:22:33:44:55:66, got: %s' % mac_str
+    return 'ff02::1:ff%s:%s%s' % tuple(mac_arr[3:])
+
+
+# return full ipv6 ff02::1 -> ff02:0:0:0:0:0:0:1
+def expand_ipv6(addr):
+    addr_arr = addr.split(':')
+    if addr.startswith(':'):
+        addr_arr[0] = '0'
+    if addr.endswith(':'):
+        addr_arr[-1] = '0'
+    for i, e in enumerate(addr_arr):
+        if not e:
+            return ':'.join(addr_arr[:i] + ['0'] * (9 - len(addr_arr)) + addr_arr[i + 1:])
+    return ':'.join(addr_arr)
+
+
+# return multicast mac based on ipv6 ff02::1 -> 33:33:00:00:00:01
+def multicast_mac_from_ipv6(addr):
+    addr = expand_ipv6(addr)
+    addr_arr = addr.split(':')
+    return '33:33:%02x:%02x:%02x:%02x' % (divmod(int(addr_arr[-2], 16), 256) + divmod(int(addr_arr[-1], 16), 256))
+
+
 def is_valid_ipv4_ret(ip_addr):
     """
     Return buffer in network order
index 1ef3a8f..b87a8a5 100644 (file)
@@ -7,6 +7,7 @@ from .trex_stl_types import *
 
 from .rx_services.trex_stl_rx_service_arp import RXServiceARP
 from .rx_services.trex_stl_rx_service_icmp import RXServiceICMP
+from .rx_services.trex_stl_rx_service_ipv6 import *
 
 from . import trex_stl_stats
 from .utils.constants import FLOW_CTRL_DICT_REVERSED
@@ -127,7 +128,7 @@ class Port(object):
 
 
     def err(self, msg):
-        return RC_ERR("port {0} : *** {1}".format(self.port_id, msg))
+        return RC_ERR("Port {0} : *** {1}".format(self.port_id, msg))
 
     def ok(self, data = ""):
         return RC_OK(data)
@@ -662,7 +663,10 @@ class Port(object):
         
         if kwargs.get('promiscuous') is not None:
             json_attr['promiscuous'] = {'enabled': kwargs.get('promiscuous')}
-        
+
+        if kwargs.get('multicast') is not None:
+            json_attr['multicast'] = {'enabled': kwargs.get('multicast')}
+
         if kwargs.get('link_status') is not None:
             json_attr['link_status'] = {'up': kwargs.get('link_status')}
         
@@ -812,6 +816,11 @@ class Port(object):
         else:
             info['prom'] = "N/A"
 
+        if 'multicast' in attr:
+            info['mult'] = "on" if attr['multicast']['enabled'] else "off"
+        else:
+            info['mult'] = "N/A"
+
         if 'description' not in info:
             info['description'] = "N/A"
 
@@ -909,10 +918,10 @@ class Port(object):
             
     
     @writeable
-    def arp_resolve (self, retries):
+    def arp_resolve(self, retries):
         
         # execute the ARP service
-        rc = RXServiceARP(self).execute(retries)
+        rc = RXServiceARP(self, retries = retries).execute()
         if not rc:
             return rc
             
@@ -934,8 +943,18 @@ class Port(object):
         
 
     @writeable
-    def ping (self, ping_ipv4, pkt_size):
-        return RXServiceICMP(self, ping_ipv4, pkt_size).execute()
+    def scan6(self, timeout = None, dst_ip = 'ff02::1'):
+        if timeout is None:
+            timeout = 5
+        return RXServiceIPv6Scan(self, timeout = timeout, dst_ip = dst_ip).execute()
+
+
+    @writeable
+    def ping(self, ping_ip, pkt_size, dst_mac = None):
+        if '.' in ping_ip:
+            return RXServiceICMP(self, ping_ip, pkt_size).execute()
+        else:
+            return RXServiceICMPv6(self, pkt_size, dst_mac, dst_ip = ping_ip).execute()
 
         
     ################# stats handler ######################
@@ -962,6 +981,7 @@ class Port(object):
                 "port status": info['status'],
                 "link status": info['link'],
                 "promiscuous" : info['prom'],
+                "multicast" : info['mult'],
                 "flow ctrl" : info['fc'],
 
                 "layer mode": format_text(info['layer_mode'], 'green' if info['layer_mode'] == 'IPv4' else 'magenta'),
index 540bba6..0c721cc 100644 (file)
@@ -539,7 +539,7 @@ def test_multi_core (r, options):
 
     for core_count in range(1, 9):
         r.run(input_list = options.input_file,
-              outfile = '{0}.cap'.format(core_count),
+              outfile = 'generated/{0}.cap'.format(core_count),
               dp_core_count = core_count,
               is_debug = (not options.release),
               pkt_limit = options.limit,
@@ -553,7 +553,7 @@ def test_multi_core (r, options):
 
     for core_count in range(1, 9):
         print(format_text("comparing {0} cores to 1 core:\n".format(core_count), 'underline'))
-        rc = compare_caps_strict('1.cap', '{0}.cap'.format(core_count))
+        rc = compare_caps_strict('generated/1.cap', 'generated/{0}.cap'.format(core_count))
         if rc:
             print("[Passed]\n")
 
index 21c9af8..fce3768 100644 (file)
@@ -668,6 +668,7 @@ class CTRexInfoGenerator(object):
                                         ("link speed", []),
                                         ("port status", []),
                                         ("promiscuous", []),
+                                        ("multicast", []),
                                         ("flow ctrl", []),
                                         ("--", []),
                                         ("layer mode", []),
index 7cb94b2..6b90a3b 100644 (file)
@@ -99,7 +99,14 @@ def is_valid_ipv4 (addr):
         return True
     except (socket.error, TypeError):
         return False
-        
+
+def is_valid_ipv6(addr):
+    try:
+        socket.inet_pton(socket.AF_INET6, addr)
+        return True
+    except (socket.error, TypeError):
+        return False
+
 def is_valid_mac (mac):
     return bool(re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower()))
 
index 53db533..cb2c981 100755 (executable)
@@ -1,6 +1,6 @@
 import argparse
 from collections import namedtuple, OrderedDict
-from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_mac, list_remove_dup
+from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_ipv6, is_valid_mac, list_remove_dup
 from .text_opts import format_text
 from ..trex_stl_types import *
 from .constants import ON_OFF_DICT, UP_DOWN_DICT, FLOW_CTRL_DICT
@@ -26,6 +26,7 @@ FILE_FROM_DB
 SERVER_IP
 STREAM_FROM_PATH_OR_FILE
 DURATION
+TIMEOUT
 FORCE
 DRY_RUN
 XTERM
@@ -36,6 +37,7 @@ MIN_IPG
 SPEEDUP
 COUNT
 PROMISCUOUS
+MULTICAST
 LINK_STATUS
 LED_STATUS
 TUNABLES
@@ -57,7 +59,7 @@ RETRIES
 SINGLE_PORT
 DST_MAC
 
-PING_IPV4
+PING_IP
 PING_COUNT
 PKT_SIZE
 
@@ -263,6 +265,12 @@ def check_ipv4_addr (ipv4_str):
 
     return ipv4_str
 
+def check_ip_addr(addr):
+    if not (is_valid_ipv4(addr) or is_valid_ipv6(addr)):
+        raise argparse.ArgumentTypeError("invalid IPv4/6 address: '{0}'".format(addr))
+
+    return addr
+
 def check_pkt_size (pkt_size):
     try:
         pkt_size = int(pkt_size)
@@ -357,6 +365,10 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
                                         {'help': "Set port promiscuous on/off",
                                          'choices': ON_OFF_DICT}),
 
+              MULTICAST: ArgumentPack(['--mult'],
+                                      {'help': "Set port multicast on/off",
+                                       'choices': ON_OFF_DICT}),
+
               LINK_STATUS: ArgumentPack(['--link'],
                                      {'help': 'Set link status up/down',
                                       'choices': UP_DOWN_DICT}),
@@ -446,11 +458,11 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
                                          'help': 'source port for the action',
                                          'required': True}),
               
-              PING_IPV4: ArgumentPack(['-d'],
-                                      {'help': 'which IPv4 to ping',
-                                      'dest': 'ping_ipv4',
+              PING_IP: ArgumentPack(['-d'],
+                                      {'help': 'which IPv4/6 to ping',
+                                      'dest': 'ping_ip',
                                       'required': True,
-                                      'type': check_ipv4_addr}),
+                                      'type': check_ip_addr}),
               
               PING_COUNT: ArgumentPack(['-n', '--count'],
                                        {'help': 'How many times to ping [default is 5]',
@@ -479,6 +491,14 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
                                          'default': -1.0,
                                          'help': "Set duration time for job."}),
 
+              TIMEOUT: ArgumentPack(['-t'],
+                                        {'action': "store",
+                                         'metavar': 'TIMEOUT',
+                                         'dest': 'timeout',
+                                         'type': int,
+                                         'default': None,
+                                         'help': "Timeout for operation in seconds."}),
+
               FORCE: ArgumentPack(['--force'],
                                         {"action": "store_true",
                                          'default': False,
index 46cd85e..b4202a7 100644 (file)
@@ -1113,7 +1113,7 @@ icmp6typescls = {    1: "ICMPv6DestUnreach",
                    140: "ICMPv6NIReply",
                    141: "ICMPv6ND_INDSol",
                    142: "ICMPv6ND_INDAdv",
-                  #143: Do Me - RFC 3810
+                   143: "ICMPv6MLReportV2",
                    144: "ICMPv6HAADRequest", 
                    145: "ICMPv6HAADReply",
                    146: "ICMPv6MPSol",
@@ -1143,6 +1143,7 @@ icmp6typesminhdrlen = {    1: 8,
                          #140
                          141: 8,
                          142: 8,
+                         143: 16,
                          144: 8,
                          145: 8,
                          146: 8,
@@ -1186,6 +1187,14 @@ icmp6types = { 1 : "Destination unreachable",
              200 : "Private Experimentation",
              201 : "Private Experimentation" }
 
+mldv2_group_types = {
+    1: 'Mode is include (1)',
+    2: 'Mode is exclude (2)',
+    3: 'Change to include mode (3)',
+    4: 'Change to exclude mode (4)',
+    5: 'Alloc new sources (5)',
+    6: 'Block old sources (6)',
+    }
 
 class _ICMPv6(Packet):
     name = "ICMPv6 dummy class"
@@ -1345,6 +1354,35 @@ class ICMPv6MLDone(_ICMPv6ML): # RFC 2710
     overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1, "nh": 58}}
 
 
+class ICMPv6MLReportV2(_ICMPv6): # RFC 3810
+    name = 'MLDv2 - Multicast Listener Report'
+    fields_desc = [ ByteEnumField('type', 143, icmp6types),
+                    ByteField('code', 0),
+                    XShortField('cksum', None),
+                    ShortField('reserved', 0),
+                    ShortField('records_count', 1) ] # for now it's fixed 1 record
+    overload_fields = {IPv6: { 'dst': 'ff02::16', 'hlim': 1, 'nh': 58 }}
+
+    def default_payload_class(self, p):
+        return MLDv2Addr
+
+
+# assumes empty aux
+class MLDv2Addr(Packet):
+    name = 'MLDv2 - Address group'
+    fields_desc = [ 
+            ByteEnumField('type', 3, mldv2_group_types),
+            ByteField('aux_len', 0),
+            ShortField('len', 0),
+            IP6Field('multicast_addr', '::'),
+            IP6ListField('addrlist', [], count_from = lambda pkt:pkt.len)
+            ]
+
+    def default_payload_class(self, p):
+        return MLDv2Addr
+
+
+
 ########## ICMPv6 MRD - Multicast Router Discovery (RFC 4286) ###############
 
 # TODO: 
index c2e4a03..ddfdc4a 100644 (file)
@@ -1121,7 +1121,7 @@ icmp6typescls = {    1: "ICMPv6DestUnreach",
                    140: "ICMPv6NIReply",
                    141: "ICMPv6ND_INDSol",
                    142: "ICMPv6ND_INDAdv",
-                  #143: Do Me - RFC 3810
+                   143: "ICMPv6MLReportV2",
                    144: "ICMPv6HAADRequest", 
                    145: "ICMPv6HAADReply",
                    146: "ICMPv6MPSol",
@@ -1151,6 +1151,7 @@ icmp6typesminhdrlen = {    1: 8,
                          #140
                          141: 8,
                          142: 8,
+                         143: 16,
                          144: 8,
                          145: 8,
                          146: 8,
@@ -1194,6 +1195,14 @@ icmp6types = { 1 : "Destination unreachable",
              200 : "Private Experimentation",
              201 : "Private Experimentation" }
 
+mldv2_group_types = {
+    1: 'Mode is include (1)',
+    2: 'Mode is exclude (2)',
+    3: 'Change to include mode (3)',
+    4: 'Change to exclude mode (4)',
+    5: 'Alloc new sources (5)',
+    6: 'Block old sources (6)',
+    }
 
 class _ICMPv6(Packet):
     name = "ICMPv6 dummy class"
@@ -1353,6 +1362,35 @@ class ICMPv6MLDone(_ICMPv6ML): # RFC 2710
     overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1, "nh": 58}}
 
 
+class ICMPv6MLReportV2(_ICMPv6): # RFC 3810
+    name = 'MLDv2 - Multicast Listener Report'
+    fields_desc = [ ByteEnumField('type', 143, icmp6types),
+                    ByteField('code', 0),
+                    XShortField('cksum', None),
+                    ShortField('reserved', 0),
+                    ShortField('records_count', 1) ] # for now it's fixed 1 record
+    overload_fields = {IPv6: { 'dst': 'ff02::16', 'hlim': 1, 'nh': 58 }}
+
+    def default_payload_class(self, p):
+        return MLDv2Addr
+
+
+# assumes empty aux
+class MLDv2Addr(Packet):
+    name = 'MLDv2 - Address group'
+    fields_desc = [ 
+            ByteEnumField('type', 3, mldv2_group_types),
+            ByteField('aux_len', 0),
+            ShortField('len', 0),
+            IP6Field('multicast_addr', '::'),
+            IP6ListField('addrlist', [], count_from = lambda pkt:pkt.len)
+            ]
+
+    def default_payload_class(self, p):
+        return MLDv2Addr
+
+
+
 ########## ICMPv6 MRD - Multicast Router Discovery (RFC 4286) ###############
 
 # TODO: 
index ebad39d..687cf45 100644 (file)
@@ -1874,6 +1874,15 @@ int DpdkTRexPortAttr::set_promiscuous(bool enable){
     return 0;
 }
 
+int DpdkTRexPortAttr::set_multicast(bool enable){
+    if (enable) {
+        rte_eth_allmulticast_enable(m_port_id);
+    }else{
+        rte_eth_allmulticast_disable(m_port_id);
+    }
+    return 0;
+}
+
 int DpdkTRexPortAttr::set_link_up(bool up){
     if (up) {
         return rte_eth_dev_set_link_up(m_port_id);
@@ -1893,6 +1902,17 @@ bool DpdkTRexPortAttr::get_promiscuous(){
     return ( ret?true:false);
 }
 
+bool DpdkTRexPortAttr::get_multicast(){
+    int ret=rte_eth_allmulticast_get(m_port_id);
+    if (ret<0) {
+        rte_exit(EXIT_FAILURE, "rte_eth_allmulticast_get: "
+                 "err=%d, port=%u\n",
+                 ret, m_port_id);
+
+    }
+    return ( ret?true:false);
+}
+
 
 void DpdkTRexPortAttr::get_hw_src_mac(struct ether_addr *mac_addr){
     rte_eth_macaddr_get(m_port_id , mac_addr);
index 6018065..d254674 100644 (file)
@@ -296,12 +296,14 @@ TrexRpcCmdGetSysInfo::_run(const Json::Value &params, Json::Value &result) {
         string driver;
         string pci_addr;
         string description;
+        string hw_mac;
         supp_speeds_t supp_speeds;
         int numa;
 
         TrexStatelessPort *port = main->get_port_by_id(i);
 
         port->get_properties(driver);
+        port->get_hw_mac(hw_mac);
 
         port->get_pci_info(pci_addr, numa);
         main->get_platform_api()->getPortAttrObj(i)->get_description(description);
@@ -314,6 +316,7 @@ TrexRpcCmdGetSysInfo::_run(const Json::Value &params, Json::Value &result) {
 
         section["ports"][i]["pci_addr"]     = pci_addr;
         section["ports"][i]["numa"]         = numa;
+        section["ports"][i]["hw_mac"]       = hw_mac;
 
         uint16_t caps = port->get_rx_caps();
         section["ports"][i]["rx"]["caps"]      = Json::arrayValue;
@@ -368,6 +371,11 @@ TrexRpcCmdSetPortAttr::_run(const Json::Value &params, Json::Value &result) {
             ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_promiscuous(enabled);
         }
 
+        else if (name == "multicast") {
+            bool enabled = parse_bool(attr[name], "enabled", result);
+            ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_multicast(enabled);
+        }
+
         else if (name == "link_status") {
             bool up = parse_bool(attr[name], "up", result);
             ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_link_up(up);
index 748d147..a4c4bb6 100644 (file)
@@ -82,7 +82,7 @@ public:
     }
 
     std::string get_server_ver() {
-        return ver(m_major, m_major);
+        return ver(m_major, m_minor);
     }
 
     std::string & verify_api(int major, int minor) {
index 6ab9b41..b42391f 100644 (file)
@@ -56,7 +56,7 @@ TrexStateless::TrexStateless(const TrexStatelessCfg &cfg) {
 
     /* API core version */
     const int API_VER_MAJOR = 3;
-    const int API_VER_MINOR = 0;
+    const int API_VER_MINOR = 1;
     m_api_classes[APIClass::API_CLASS_TYPE_CORE].init(APIClass::API_CLASS_TYPE_CORE,
                                                       API_VER_MAJOR,
                                                       API_VER_MINOR);
index b88ac71..b0366fb 100644 (file)
@@ -915,6 +915,11 @@ TrexStatelessPort::get_pci_info(std::string &pci_addr, int &numa_node) {
     numa_node = m_api_info.numa_node;
 }
 
+void
+TrexStatelessPort::get_hw_mac(std::string &hw_mac) {
+    utl_macaddr_to_str(m_api_info.hw_macaddr, hw_mac);
+}
+
 void
 TrexStatelessPort::add_stream(TrexStream *stream) {
 
index 4b8ea3d..296e0d0 100644 (file)
@@ -388,6 +388,21 @@ public:
 
     void get_pci_info(std::string &pci_addr, int &numa_node);
 
+    void get_hw_mac(std::string &hw_mac);
+
+
+    /**
+     * enable RX capture on port
+     * 
+     */
+    void start_rx_capture(const std::string &pcap_filename, uint64_t limit);
+
+    /**
+     * disable RX capture if on
+     * 
+     */
+    void stop_rx_capture();
+
     /**
      * start RX queueing of packets
      * 
index 4c5ac9e..f2c7585 100644 (file)
@@ -150,6 +150,7 @@ void
 TRexPortAttr::to_json(Json::Value &output) {
 
     output["promiscuous"]["enabled"] = get_promiscuous();
+    output["multicast"]["enabled"]   = get_multicast();
     output["link"]["up"]             = is_link_up();
     output["speed"]                  = get_link_speed() / 1000; // make sure we have no cards of less than 1 Gbps
     output["rx_filter_mode"]         = get_rx_filter_mode();
index a2fcf7e..7a1a88f 100755 (executable)
@@ -203,6 +203,7 @@ public:
     
 /*    GETTERS    */
     virtual bool get_promiscuous() = 0;
+    virtual bool get_multicast() = 0;
     virtual void get_hw_src_mac(struct ether_addr *mac_addr) = 0;
     virtual uint32_t get_link_speed() { return m_link.link_speed; } // L1 Mbps
     virtual bool is_link_duplex() { return (m_link.link_duplex ? true : false); }
@@ -223,6 +224,7 @@ public:
 
 /*    SETTERS    */
     virtual int set_promiscuous(bool enabled) = 0;
+    virtual int set_multicast(bool enabled) = 0;
     virtual int add_mac(char * mac) = 0;
     virtual int set_link_up(bool up) = 0;
     virtual int set_flow_ctrl(int mode) = 0;
@@ -323,6 +325,7 @@ public:
 
 /*    GETTERS    */
     virtual bool get_promiscuous();
+    virtual bool get_multicast();
     virtual void get_hw_src_mac(struct ether_addr *mac_addr);
     virtual int get_xstats_values(xstats_values_t &xstats_values);
     virtual int get_xstats_names(xstats_names_t &xstats_names);
@@ -332,6 +335,7 @@ public:
     
 /*    SETTERS    */
     virtual int set_promiscuous(bool enabled);
+    virtual int set_multicast(bool enabled);
     virtual int add_mac(char * mac);
     virtual int set_link_up(bool up);
     virtual int set_flow_ctrl(int mode);
@@ -377,6 +381,7 @@ public:
     void reset_xstats() {}
     void update_description() {}
     bool get_promiscuous() { return false; }
+    bool get_multicast() { return false; }
     void get_hw_src_mac(struct ether_addr *mac_addr) {}
     int get_xstats_values(xstats_values_t &xstats_values) { return -ENOTSUP; }
     int get_xstats_names(xstats_names_t &xstats_names) { return -ENOTSUP; }
@@ -384,6 +389,7 @@ public:
     void get_description(std::string &description) {}
     void get_supported_speeds(supp_speeds_t &supp_speeds) {}
     int set_promiscuous(bool enabled) { return -ENOTSUP; }
+    int set_multicast(bool enabled) { return -ENOTSUP; }
     int add_mac(char * mac) { return -ENOTSUP; }
     int set_link_up(bool up) { return -ENOTSUP; }
     int set_flow_ctrl(int mode) { return -ENOTSUP; }