1#./usr/bin/env python3.4
2#
3#   Copyright 2017 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16"""Collection of utility functions to generate and send custom packets.
17
18"""
19import logging
20import multiprocessing
21import socket
22import time
23
24import acts.signals
25from acts.test_utils.wifi import wifi_power_test_utils as wputils
26# http://www.secdev.org/projects/scapy/
27# On ubuntu, sudo pip3 install scapy-python3
28import scapy.all as scapy
29
30ACTS_CONTROLLER_CONFIG_NAME = 'PacketSender'
31ACTS_CONTROLLER_REFERENCE_NAME = 'packet_senders'
32
33GET_FROM_LOCAL_INTERFACE = 'get_local'
34MAC_BROADCAST = 'ff:ff:ff:ff:ff:ff'
35IPV4_BROADCAST = '255.255.255.255'
36ARP_DST = '00:00:00:00:00:00'
37RA_MAC = '33:33:00:00:00:01'
38RA_IP = 'ff02::1'
39RA_PREFIX = 'd00d::'
40RA_PREFIX_LEN = 64
41DHCP_OFFER_OP = 2
42DHCP_OFFER_SRC_PORT = 67
43DHCP_OFFER_DST_PORT = 68
44DHCP_TRANS_ID = 0x01020304
45DNS_LEN = 3
46PING6_DATA = 'BEST PING6 EVER'
47PING4_TYPE = 8
48MDNS_TTL = 255
49MDNS_QTYPE = 'PTR'
50MDNS_UDP_PORT = 5353
51MDNS_V4_IP_DST = '224.0.0.251'
52MDNS_V4_MAC_DST = '01:00:5E:00:00:FB'
53MDNS_RECURSIVE = 1
54MDNS_V6_IP_DST = 'FF02::FB'
55MDNS_V6_MAC_DST = '33:33:00:00:00:FB'
56
57
58def create(configs):
59    """Creates PacketSender controllers from a json config.
60
61    Args:
62        The json configs that represent this controller
63
64    Returns:
65        A new PacketSender
66    """
67    return [PacketSender(c) for c in configs]
68
69
70def destroy(objs):
71    """Destroys a list of PacketSenders and stops sending (if active).
72
73    Args:
74        objs: A list of PacketSenders
75    """
76    for pkt_sender in objs:
77        pkt_sender.stop_sending(True)
78    return
79
80
81def get_info(objs):
82    """Get information on a list of packet senders.
83
84    Args:
85        objs: A list of PacketSenders
86
87    Returns:
88        Network interface name that is being used by each packet sender
89    """
90    return [pkt_sender.interface for pkt_sender in objs]
91
92
93class ThreadSendPacket(multiprocessing.Process):
94    """Creates a thread that keeps sending the same packet until a stop signal.
95
96    Attributes:
97        stop_signal: signal to stop the thread execution
98        packet: desired packet to keep sending
99        interval: interval between consecutive packets (s)
100        interface: network interface name (e.g., 'eth0')
101        log: object used for logging
102    """
103
104    def __init__(self, signal, packet, interval, interface, log):
105        multiprocessing.Process.__init__(self)
106        self.stop_signal = signal
107        self.packet = packet
108        self.interval = interval
109        self.interface = interface
110        self.log = log
111
112    def run(self):
113        self.log.info('Packet Sending Started.')
114        while True:
115            if self.stop_signal.is_set():
116                # Poison pill means shutdown
117                self.log.info('Packet Sending Stopped.')
118                break
119
120            try:
121                scapy.sendp(self.packet, iface=self.interface, verbose=0)
122                time.sleep(self.interval)
123            except Exception:
124                self.log.exception('Exception when trying to send packet')
125                return
126
127        return
128
129
130class PacketSenderError(acts.signals.ControllerError):
131    """Raises exceptions encountered in packet sender lib."""
132
133
134class PacketSender(object):
135    """Send any custom packet over a desired interface.
136
137    Attributes:
138        log: class logging object
139        thread_active: indicates whether or not the send thread is active
140        thread_send: thread object for the concurrent packet transmissions
141        stop_signal: event to stop the thread
142        interface: network interface name (e.g., 'eth0')
143    """
144
145    def __init__(self, ifname):
146        """Initiallize the PacketGenerator class.
147
148        Args:
149            ifname: network interface name that will be used packet generator
150        """
151        self.log = logging.getLogger()
152        self.packet = None
153        self.thread_active = False
154        self.thread_send = None
155        self.stop_signal = multiprocessing.Event()
156        self.interface = ifname
157
158    def send_ntimes(self, packet, ntimes, interval):
159        """Sends a packet ntimes at a given interval.
160
161        Args:
162            packet: custom built packet from Layer 2 up to Application layer
163            ntimes: number of packets to send
164            interval: interval between consecutive packet transmissions (s)
165        """
166        if packet is None:
167            raise PacketSenderError(
168                'There is no packet to send. Create a packet first.')
169
170        for _ in range(ntimes):
171            try:
172                scapy.sendp(packet, iface=self.interface, verbose=0)
173                time.sleep(interval)
174            except socket.error as excpt:
175                self.log.exception('Caught socket exception : %s' % excpt)
176                return
177
178    def send_receive_ntimes(self, packet, ntimes, interval):
179        """Sends a packet and receives the reply ntimes at a given interval.
180
181        Args:
182            packet: custom built packet from Layer 2 up to Application layer
183            ntimes: number of packets to send
184            interval: interval between consecutive packet transmissions and
185                      the corresponding reply (s)
186        """
187        if packet is None:
188            raise PacketSenderError(
189                'There is no packet to send. Create a packet first.')
190
191        for _ in range(ntimes):
192            try:
193                scapy.srp1(
194                    packet, iface=self.interface, timeout=interval, verbose=0)
195                time.sleep(interval)
196            except socket.error as excpt:
197                self.log.exception('Caught socket exception : %s' % excpt)
198                return
199
200    def start_sending(self, packet, interval):
201        """Sends packets in parallel with the main process.
202
203        Creates a thread and keeps sending the same packet at a given interval
204        until a stop signal is received
205
206        Args:
207            packet: custom built packet from Layer 2 up to Application layer
208            interval: interval between consecutive packets (s)
209        """
210        if packet is None:
211            raise PacketSenderError(
212                'There is no packet to send. Create a packet first.')
213
214        if self.thread_active:
215            raise PacketSenderError(
216                ('There is already an active thread. Stop it'
217                 'before starting another transmission.'))
218
219        self.thread_send = ThreadSendPacket(self.stop_signal, packet, interval,
220                                            self.interface, self.log)
221        self.thread_send.start()
222        self.thread_active = True
223
224    def stop_sending(self, ignore_status=False):
225        """Stops the concurrent thread that is continuously sending packets.
226
227       """
228        if not self.thread_active:
229            if ignore_status:
230                return
231            else:
232                raise PacketSenderError(
233                    'Error: There is no acive thread running to stop.')
234
235        # Stop thread
236        self.stop_signal.set()
237        self.thread_send.join()
238
239        # Just as precaution
240        if self.thread_send.is_alive():
241            self.thread_send.terminate()
242            self.log.warning('Packet Sending forced to terminate')
243
244        self.stop_signal.clear()
245        self.thread_send = None
246        self.thread_active = False
247
248
249class ArpGenerator(object):
250    """Creates a custom ARP packet
251
252    Attributes:
253        packet: desired built custom packet
254        src_mac: MAC address (Layer 2) of the source node
255        src_ipv4: IPv4 address (Layer 3) of the source node
256        dst_ipv4: IPv4 address (Layer 3) of the destination node
257    """
258
259    def __init__(self, **config_params):
260        """Initialize the class with the required network and packet params.
261
262        Args:
263            config_params: a dictionary with all the necessary packet fields.
264              Some fields can be generated automatically. For example:
265              {'subnet_mask': '255.255.255.0',
266               'dst_ipv4': '192.168.1.3',
267               'src_ipv4: 'get_local', ...
268              The key can also be 'get_local' which means the code will read
269              and use the local interface parameters
270        """
271        interf = config_params['interf']
272        self.packet = None
273        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
274            self.src_mac = scapy.get_if_hwaddr(interf)
275        else:
276            self.src_mac = config_params['src_mac']
277
278        self.dst_ipv4 = config_params['dst_ipv4']
279        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
280            self.src_ipv4 = scapy.get_if_addr(interf)
281        else:
282            self.src_ipv4 = config_params['src_ipv4']
283
284    def generate(self, ip_dst=None, hwsrc=None, hwdst=None, eth_dst=None):
285        """Generates a custom ARP packet.
286
287        Args:
288            ip_dst: ARP ipv4 destination (Optional)
289            hwsrc: ARP hardware source address (Optional)
290            hwdst: ARP hardware destination address (Optional)
291            eth_dst: Ethernet (layer 2) destination address (Optional)
292        """
293        # Create IP layer
294        hw_src = (hwsrc if hwsrc is not None else self.src_mac)
295        hw_dst = (hwdst if hwdst is not None else ARP_DST)
296        ipv4_dst = (ip_dst if ip_dst is not None else self.dst_ipv4)
297        ip4 = scapy.ARP(
298            pdst=ipv4_dst, psrc=self.src_ipv4, hwdst=hw_dst, hwsrc=hw_src)
299
300        # Create Ethernet layer
301        mac_dst = (eth_dst if eth_dst is not None else MAC_BROADCAST)
302        ethernet = scapy.Ether(src=self.src_mac, dst=mac_dst)
303
304        self.packet = ethernet / ip4
305        return self.packet
306
307
308class DhcpOfferGenerator(object):
309    """Creates a custom DHCP offer packet
310
311    Attributes:
312        packet: desired built custom packet
313        subnet_mask: local network subnet mask
314        src_mac: MAC address (Layer 2) of the source node
315        dst_mac: MAC address (Layer 2) of the destination node
316        src_ipv4: IPv4 address (Layer 3) of the source node
317        dst_ipv4: IPv4 address (Layer 3) of the destination node
318        gw_ipv4: IPv4 address (Layer 3) of the Gateway
319    """
320
321    def __init__(self, **config_params):
322        """Initialize the class with the required network and packet params.
323
324        Args:
325            config_params: contains all the necessary packet parameters.
326              Some fields can be generated automatically. For example:
327              {'subnet_mask': '255.255.255.0',
328               'dst_ipv4': '192.168.1.3',
329               'src_ipv4: 'get_local', ...
330              The key can also be 'get_local' which means the code will read
331              and use the local interface parameters
332        """
333        interf = config_params['interf']
334        self.packet = None
335        self.subnet_mask = config_params['subnet_mask']
336        self.dst_mac = config_params['dst_mac']
337        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
338            self.src_mac = scapy.get_if_hwaddr(interf)
339        else:
340            self.src_mac = config_params['src_mac']
341
342        self.dst_ipv4 = config_params['dst_ipv4']
343        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
344            self.src_ipv4 = scapy.get_if_addr(interf)
345        else:
346            self.src_ipv4 = config_params['src_ipv4']
347
348        self.gw_ipv4 = config_params['gw_ipv4']
349
350    def generate(self, cha_mac=None, dst_ip=None):
351        """Generates a DHCP offer packet.
352
353        Args:
354            cha_mac: hardware target address for DHCP offer (Optional)
355            dst_ip: ipv4 address of target host for renewal (Optional)
356        """
357
358        # Create DHCP layer
359        dhcp = scapy.DHCP(options=[
360            ('message-type', 'offer'),
361            ('subnet_mask', self.subnet_mask),
362            ('server_id', self.src_ipv4),
363            ('end'),
364        ])
365
366        # Overwrite standard DHCP fields
367        sta_hw = (cha_mac if cha_mac is not None else self.dst_mac)
368        sta_ip = (dst_ip if dst_ip is not None else self.dst_ipv4)
369
370        # Create Boot
371        bootp = scapy.BOOTP(
372            op=DHCP_OFFER_OP,
373            yiaddr=sta_ip,
374            siaddr=self.src_ipv4,
375            giaddr=self.gw_ipv4,
376            chaddr=scapy.mac2str(sta_hw),
377            xid=DHCP_TRANS_ID)
378
379        # Create UDP
380        udp = scapy.UDP(sport=DHCP_OFFER_SRC_PORT, dport=DHCP_OFFER_DST_PORT)
381
382        # Create IP layer
383        ip4 = scapy.IP(src=self.src_ipv4, dst=IPV4_BROADCAST)
384
385        # Create Ethernet layer
386        ethernet = scapy.Ether(dst=MAC_BROADCAST, src=self.src_mac)
387
388        self.packet = ethernet / ip4 / udp / bootp / dhcp
389        return self.packet
390
391
392class NsGenerator(object):
393    """Creates a custom Neighbor Solicitation (NS) packet
394
395    Attributes:
396        packet: desired built custom packet
397        src_mac: MAC address (Layer 2) of the source node
398        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
399        src_ipv6: IPv6 address (Layer 3) of the source node
400        dst_ipv6: IPv6 address (Layer 3) of the destination node
401    """
402
403    def __init__(self, **config_params):
404        """Initialize the class with the required network and packet params.
405
406        Args:
407            config_params: contains all the necessary packet parameters.
408              Some fields can be generated automatically. For example:
409              {'subnet_mask': '255.255.255.0',
410               'dst_ipv4': '192.168.1.3',
411               'src_ipv4: 'get_local', ...
412              The key can also be 'get_local' which means the code will read
413              and use the local interface parameters
414        """
415        interf = config_params['interf']
416        self.packet = None
417        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
418            self.src_mac = scapy.get_if_hwaddr(interf)
419        else:
420            self.src_mac = config_params['src_mac']
421
422        self.dst_ipv6 = config_params['dst_ipv6']
423        self.src_ipv6_type = config_params['src_ipv6_type']
424        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
425            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
426        else:
427            self.src_ipv6 = config_params['src_ipv6']
428
429    def generate(self, ip_dst=None, eth_dst=None):
430        """Generates a Neighbor Solicitation (NS) packet (ICMP over IPv6).
431
432        Args:
433            ip_dst: NS ipv6 destination (Optional)
434            eth_dst: Ethernet (layer 2) destination address (Optional)
435        """
436        # Compute IP addresses
437        target_ip6 = ip_dst if ip_dst is not None else self.dst_ipv6
438        ndst_ip = socket.inet_pton(socket.AF_INET6, target_ip6)
439        nnode_mcast = scapy.in6_getnsma(ndst_ip)
440        node_mcast = socket.inet_ntop(socket.AF_INET6, nnode_mcast)
441        # Compute MAC addresses
442        hw_dst = (eth_dst
443                  if eth_dst is not None else scapy.in6_getnsmac(nnode_mcast))
444
445        # Create IPv6 layer
446        base = scapy.IPv6(dst=node_mcast, src=self.src_ipv6)
447        neighbor_solicitation = scapy.ICMPv6ND_NS(tgt=target_ip6)
448        src_ll_addr = scapy.ICMPv6NDOptSrcLLAddr(lladdr=self.src_mac)
449        ip6 = base / neighbor_solicitation / src_ll_addr
450
451        # Create Ethernet layer
452        ethernet = scapy.Ether(src=self.src_mac, dst=hw_dst)
453
454        self.packet = ethernet / ip6
455        return self.packet
456
457
458class RaGenerator(object):
459    """Creates a custom Router Advertisement (RA) packet
460
461    Attributes:
462        packet: desired built custom packet
463        src_mac: MAC address (Layer 2) of the source node
464        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
465        src_ipv6: IPv6 address (Layer 3) of the source node
466    """
467
468    def __init__(self, **config_params):
469        """Initialize the class with the required network and packet params.
470
471        Args:
472            config_params: contains all the necessary packet parameters.
473              Some fields can be generated automatically. For example:
474              {'subnet_mask': '255.255.255.0',
475               'dst_ipv4': '192.168.1.3',
476               'src_ipv4: 'get_local', ...
477              The key can also be 'get_local' which means the code will read
478              and use the local interface parameters
479        """
480        interf = config_params['interf']
481        self.packet = None
482        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
483            self.src_mac = scapy.get_if_hwaddr(interf)
484        else:
485            self.src_mac = config_params['src_mac']
486
487        self.src_ipv6_type = config_params['src_ipv6_type']
488        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
489            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
490        else:
491            self.src_ipv6 = config_params['src_ipv6']
492
493    def generate(self,
494                 lifetime,
495                 enableDNS=False,
496                 dns_lifetime=0,
497                 ip_dst=None,
498                 eth_dst=None):
499        """Generates a Router Advertisement (RA) packet (ICMP over IPv6).
500
501        Args:
502            lifetime: RA lifetime
503            enableDNS: Add RDNSS option to RA (Optional)
504            dns_lifetime: Set DNS server lifetime (Optional)
505            ip_dst: IPv6 destination address (Optional)
506            eth_dst: Ethernet (layer 2) destination address (Optional)
507        """
508        # Overwrite standard fields if desired
509        ip6_dst = (ip_dst if ip_dst is not None else RA_IP)
510        hw_dst = (eth_dst if eth_dst is not None else RA_MAC)
511
512        # Create IPv6 layer
513        base = scapy.IPv6(dst=ip6_dst, src=self.src_ipv6)
514        router_solicitation = scapy.ICMPv6ND_RA(routerlifetime=lifetime)
515        src_ll_addr = scapy.ICMPv6NDOptSrcLLAddr(lladdr=self.src_mac)
516        prefix = scapy.ICMPv6NDOptPrefixInfo(
517            prefixlen=RA_PREFIX_LEN, prefix=RA_PREFIX)
518        if enableDNS:
519            rndss = scapy.ICMPv6NDOptRDNSS(
520                lifetime=dns_lifetime, dns=[self.src_ipv6], len=DNS_LEN)
521            ip6 = base / router_solicitation / src_ll_addr / prefix / rndss
522        else:
523            ip6 = base / router_solicitation / src_ll_addr / prefix
524
525        # Create Ethernet layer
526        ethernet = scapy.Ether(src=self.src_mac, dst=hw_dst)
527
528        self.packet = ethernet / ip6
529        return self.packet
530
531
532class Ping6Generator(object):
533    """Creates a custom Ping v6 packet (i.e., ICMP over IPv6)
534
535    Attributes:
536        packet: desired built custom packet
537        src_mac: MAC address (Layer 2) of the source node
538        dst_mac: MAC address (Layer 2) of the destination node
539        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
540        src_ipv6: IPv6 address (Layer 3) of the source node
541        dst_ipv6: IPv6 address (Layer 3) of the destination node
542    """
543
544    def __init__(self, **config_params):
545        """Initialize the class with the required network and packet params.
546
547        Args:
548            config_params: contains all the necessary packet parameters.
549              Some fields can be generated automatically. For example:
550              {'subnet_mask': '255.255.255.0',
551               'dst_ipv4': '192.168.1.3',
552               'src_ipv4: 'get_local', ...
553              The key can also be 'get_local' which means the code will read
554              and use the local interface parameters
555        """
556        interf = config_params['interf']
557        self.packet = None
558        self.dst_mac = config_params['dst_mac']
559        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
560            self.src_mac = scapy.get_if_hwaddr(interf)
561        else:
562            self.src_mac = config_params['src_mac']
563
564        self.dst_ipv6 = config_params['dst_ipv6']
565        self.src_ipv6_type = config_params['src_ipv6_type']
566        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
567            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
568        else:
569            self.src_ipv6 = config_params['src_ipv6']
570
571    def generate(self, ip_dst=None, eth_dst=None):
572        """Generates a Ping6 packet (i.e., Echo Request)
573
574        Args:
575            ip_dst: IPv6 destination address (Optional)
576            eth_dst: Ethernet (layer 2) destination address (Optional)
577        """
578        # Overwrite standard fields if desired
579        ip6_dst = (ip_dst if ip_dst is not None else self.dst_ipv6)
580        hw_dst = (eth_dst if eth_dst is not None else self.dst_mac)
581
582        # Create IPv6 layer
583        base = scapy.IPv6(dst=ip6_dst, src=self.src_ipv6)
584        echo_request = scapy.ICMPv6EchoRequest(data=PING6_DATA)
585
586        ip6 = base / echo_request
587
588        # Create Ethernet layer
589        ethernet = scapy.Ether(src=self.src_mac, dst=hw_dst)
590
591        self.packet = ethernet / ip6
592        return self.packet
593
594
595class Ping4Generator(object):
596    """Creates a custom Ping v4 packet (i.e., ICMP over IPv4)
597
598    Attributes:
599        packet: desired built custom packet
600        src_mac: MAC address (Layer 2) of the source node
601        dst_mac: MAC address (Layer 2) of the destination node
602        src_ipv4: IPv4 address (Layer 3) of the source node
603        dst_ipv4: IPv4 address (Layer 3) of the destination node
604    """
605
606    def __init__(self, **config_params):
607        """Initialize the class with the required network and packet params.
608
609        Args:
610            config_params: contains all the necessary packet parameters.
611              Some fields can be generated automatically. For example:
612              {'subnet_mask': '255.255.255.0',
613               'dst_ipv4': '192.168.1.3',
614               'src_ipv4: 'get_local', ...
615              The key can also be 'get_local' which means the code will read
616              and use the local interface parameters
617        """
618        interf = config_params['interf']
619        self.packet = None
620        self.dst_mac = config_params['dst_mac']
621        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
622            self.src_mac = scapy.get_if_hwaddr(interf)
623        else:
624            self.src_mac = config_params['src_mac']
625
626        self.dst_ipv4 = config_params['dst_ipv4']
627        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
628            self.src_ipv4 = scapy.get_if_addr(interf)
629        else:
630            self.src_ipv4 = config_params['src_ipv4']
631
632    def generate(self, ip_dst=None, eth_dst=None):
633        """Generates a Ping4 packet (i.e., Echo Request)
634
635        Args:
636            ip_dst: IP destination address (Optional)
637            eth_dst: Ethernet (layer 2) destination address (Optional)
638        """
639
640        # Overwrite standard fields if desired
641        sta_ip = (ip_dst if ip_dst is not None else self.dst_ipv4)
642        sta_hw = (eth_dst if eth_dst is not None else self.dst_mac)
643
644        # Create IPv6 layer
645        base = scapy.IP(src=self.src_ipv4, dst=sta_ip)
646        echo_request = scapy.ICMP(type=PING4_TYPE)
647
648        ip4 = base / echo_request
649
650        # Create Ethernet layer
651        ethernet = scapy.Ether(src=self.src_mac, dst=sta_hw)
652
653        self.packet = ethernet / ip4
654        return self.packet
655
656
657class Mdns6Generator(object):
658    """Creates a custom mDNS IPv6 packet
659
660    Attributes:
661        packet: desired built custom packet
662        src_mac: MAC address (Layer 2) of the source node
663        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
664        src_ipv6: IPv6 address (Layer 3) of the source node
665    """
666
667    def __init__(self, **config_params):
668        """Initialize the class with the required network and packet params.
669
670        Args:
671            config_params: contains all the necessary packet parameters.
672              Some fields can be generated automatically. For example:
673              {'subnet_mask': '255.255.255.0',
674               'dst_ipv4': '192.168.1.3',
675               'src_ipv4: 'get_local', ...
676              The key can also be 'get_local' which means the code will read
677              and use the local interface parameters
678        """
679        interf = config_params['interf']
680        self.packet = None
681        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
682            self.src_mac = scapy.get_if_hwaddr(interf)
683        else:
684            self.src_mac = config_params['src_mac']
685
686        self.src_ipv6_type = config_params['src_ipv6_type']
687        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
688            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
689        else:
690            self.src_ipv6 = config_params['src_ipv6']
691
692    def generate(self, ip_dst=None, eth_dst=None):
693        """Generates a mDNS v6 packet for multicast DNS config
694
695        Args:
696            ip_dst: IPv6 destination address (Optional)
697            eth_dst: Ethernet (layer 2) destination address (Optional)
698        """
699
700        # Overwrite standard fields if desired
701        sta_ip = (ip_dst if ip_dst is not None else MDNS_V6_IP_DST)
702        sta_hw = (eth_dst if eth_dst is not None else MDNS_V6_MAC_DST)
703
704        # Create mDNS layer
705        qdServer = scapy.DNSQR(qname=self.src_ipv6, qtype=MDNS_QTYPE)
706        mDNS = scapy.DNS(rd=MDNS_RECURSIVE, qd=qdServer)
707
708        # Create UDP
709        udp = scapy.UDP(sport=MDNS_UDP_PORT, dport=MDNS_UDP_PORT)
710
711        # Create IP layer
712        ip6 = scapy.IPv6(src=self.src_ipv6, dst=sta_ip)
713
714        # Create Ethernet layer
715        ethernet = scapy.Ether(src=self.src_mac, dst=sta_hw)
716
717        self.packet = ethernet / ip6 / udp / mDNS
718        return self.packet
719
720
721class Mdns4Generator(object):
722    """Creates a custom mDNS v4 packet
723
724    Attributes:
725        packet: desired built custom packet
726        src_mac: MAC address (Layer 2) of the source node
727        src_ipv4: IPv4 address (Layer 3) of the source node
728    """
729
730    def __init__(self, **config_params):
731        """Initialize the class with the required network and packet params.
732
733        Args:
734            config_params: contains all the necessary packet parameters.
735              Some fields can be generated automatically. For example:
736              {'subnet_mask': '255.255.255.0',
737               'dst_ipv4': '192.168.1.3',
738               'src_ipv4: 'get_local', ...
739              The key can also be 'get_local' which means the code will read
740              and use the local interface parameters
741        """
742        interf = config_params['interf']
743        self.packet = None
744        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
745            self.src_mac = scapy.get_if_hwaddr(interf)
746        else:
747            self.src_mac = config_params['src_mac']
748
749        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
750            self.src_ipv4 = scapy.get_if_addr(interf)
751        else:
752            self.src_ipv4 = config_params['src_ipv4']
753
754    def generate(self, ip_dst=None, eth_dst=None):
755        """Generates a mDNS v4 packet for multicast DNS config
756
757        Args:
758            ip_dst: IP destination address (Optional)
759            eth_dst: Ethernet (layer 2) destination address (Optional)
760        """
761
762        # Overwrite standard fields if desired
763        sta_ip = (ip_dst if ip_dst is not None else MDNS_V4_IP_DST)
764        sta_hw = (eth_dst if eth_dst is not None else MDNS_V4_MAC_DST)
765
766        # Create mDNS layer
767        qdServer = scapy.DNSQR(qname=self.src_ipv4, qtype=MDNS_QTYPE)
768        mDNS = scapy.DNS(rd=MDNS_RECURSIVE, qd=qdServer)
769
770        # Create UDP
771        udp = scapy.UDP(sport=MDNS_UDP_PORT, dport=MDNS_UDP_PORT)
772
773        # Create IP layer
774        ip4 = scapy.IP(src=self.src_ipv4, dst=sta_ip, ttl=255)
775
776        # Create Ethernet layer
777        ethernet = scapy.Ether(src=self.src_mac, dst=sta_hw)
778
779        self.packet = ethernet / ip4 / udp / mDNS
780        return self.packet
781