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
28import scapy.all as scapy
29
30MOBLY_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'
56ETH_TYPE_IP = 2048
57SAP_SPANNING_TREE = 0x42
58SNAP_OUI = 12
59SNAP_SSAP = 170
60SNAP_DSAP = 170
61SNAP_CTRL = 3
62LLC_XID_CONTROL = 191
63PAD_LEN_BYTES = 128
64
65
66def create(configs):
67    """Creates PacketSender controllers from a json config.
68
69    Args:
70        The json configs that represent this controller
71
72    Returns:
73        A new PacketSender
74    """
75    return [PacketSender(c) for c in configs]
76
77
78def destroy(objs):
79    """Destroys a list of PacketSenders and stops sending (if active).
80
81    Args:
82        objs: A list of PacketSenders
83    """
84    for pkt_sender in objs:
85        pkt_sender.stop_sending(True)
86    return
87
88
89def get_info(objs):
90    """Get information on a list of packet senders.
91
92    Args:
93        objs: A list of PacketSenders
94
95    Returns:
96        Network interface name that is being used by each packet sender
97    """
98    return [pkt_sender.interface for pkt_sender in objs]
99
100
101class ThreadSendPacket(multiprocessing.Process):
102    """Creates a thread that keeps sending the same packet until a stop signal.
103
104    Attributes:
105        stop_signal: signal to stop the thread execution
106        packet: desired packet to keep sending
107        interval: interval between consecutive packets (s)
108        interface: network interface name (e.g., 'eth0')
109        log: object used for logging
110    """
111
112    def __init__(self, signal, packet, interval, interface, log):
113        multiprocessing.Process.__init__(self)
114        self.stop_signal = signal
115        self.packet = packet
116        self.interval = interval
117        self.interface = interface
118        self.log = log
119
120    def run(self):
121        self.log.info('Packet Sending Started.')
122        while True:
123            if self.stop_signal.is_set():
124                # Poison pill means shutdown
125                self.log.info('Packet Sending Stopped.')
126                break
127
128            try:
129                scapy.sendp(self.packet, iface=self.interface, verbose=0)
130                time.sleep(self.interval)
131            except Exception:
132                self.log.exception('Exception when trying to send packet')
133                return
134
135        return
136
137
138class PacketSenderError(acts.signals.ControllerError):
139    """Raises exceptions encountered in packet sender lib."""
140
141
142class PacketSender(object):
143    """Send any custom packet over a desired interface.
144
145    Attributes:
146        log: class logging object
147        thread_active: indicates whether or not the send thread is active
148        thread_send: thread object for the concurrent packet transmissions
149        stop_signal: event to stop the thread
150        interface: network interface name (e.g., 'eth0')
151    """
152
153    def __init__(self, ifname):
154        """Initiallize the PacketGenerator class.
155
156        Args:
157            ifname: network interface name that will be used packet generator
158        """
159        self.log = logging.getLogger()
160        self.packet = None
161        self.thread_active = False
162        self.thread_send = None
163        self.stop_signal = multiprocessing.Event()
164        self.interface = ifname
165
166    def send_ntimes(self, packet, ntimes, interval):
167        """Sends a packet ntimes at a given interval.
168
169        Args:
170            packet: custom built packet from Layer 2 up to Application layer
171            ntimes: number of packets to send
172            interval: interval between consecutive packet transmissions (s)
173        """
174        if packet is None:
175            raise PacketSenderError(
176                'There is no packet to send. Create a packet first.')
177
178        for _ in range(ntimes):
179            try:
180                scapy.sendp(packet, iface=self.interface, verbose=0)
181                time.sleep(interval)
182            except socket.error as excpt:
183                self.log.exception('Caught socket exception : %s' % excpt)
184                return
185
186    def send_receive_ntimes(self, packet, ntimes, interval):
187        """Sends a packet and receives the reply ntimes at a given interval.
188
189        Args:
190            packet: custom built packet from Layer 2 up to Application layer
191            ntimes: number of packets to send
192            interval: interval between consecutive packet transmissions and
193                      the corresponding reply (s)
194        """
195        if packet is None:
196            raise PacketSenderError(
197                'There is no packet to send. Create a packet first.')
198
199        for _ in range(ntimes):
200            try:
201                scapy.srp1(
202                    packet, iface=self.interface, timeout=interval, verbose=0)
203                time.sleep(interval)
204            except socket.error as excpt:
205                self.log.exception('Caught socket exception : %s' % excpt)
206                return
207
208    def start_sending(self, packet, interval):
209        """Sends packets in parallel with the main process.
210
211        Creates a thread and keeps sending the same packet at a given interval
212        until a stop signal is received
213
214        Args:
215            packet: custom built packet from Layer 2 up to Application layer
216            interval: interval between consecutive packets (s)
217        """
218        if packet is None:
219            raise PacketSenderError(
220                'There is no packet to send. Create a packet first.')
221
222        if self.thread_active:
223            raise PacketSenderError(
224                ('There is already an active thread. Stop it'
225                 'before starting another transmission.'))
226
227        self.thread_send = ThreadSendPacket(self.stop_signal, packet, interval,
228                                            self.interface, self.log)
229        self.thread_send.start()
230        self.thread_active = True
231
232    def stop_sending(self, ignore_status=False):
233        """Stops the concurrent thread that is continuously sending packets.
234
235       """
236        if not self.thread_active:
237            if ignore_status:
238                return
239            else:
240                raise PacketSenderError(
241                    'Error: There is no acive thread running to stop.')
242
243        # Stop thread
244        self.stop_signal.set()
245        self.thread_send.join()
246
247        # Just as precaution
248        if self.thread_send.is_alive():
249            self.thread_send.terminate()
250            self.log.warning('Packet Sending forced to terminate')
251
252        self.stop_signal.clear()
253        self.thread_send = None
254        self.thread_active = False
255
256
257class ArpGenerator(object):
258    """Creates a custom ARP packet
259
260    Attributes:
261        packet: desired built custom packet
262        src_mac: MAC address (Layer 2) of the source node
263        src_ipv4: IPv4 address (Layer 3) of the source node
264        dst_ipv4: IPv4 address (Layer 3) of the destination node
265    """
266
267    def __init__(self, **config_params):
268        """Initialize the class with the required network and packet params.
269
270        Args:
271            config_params: a dictionary with all the necessary packet fields.
272              Some fields can be generated automatically. For example:
273              {'subnet_mask': '255.255.255.0',
274               'dst_ipv4': '192.168.1.3',
275               'src_ipv4: 'get_local', ...
276              The key can also be 'get_local' which means the code will read
277              and use the local interface parameters
278        """
279        interf = config_params['interf']
280        self.packet = None
281        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
282            self.src_mac = scapy.get_if_hwaddr(interf)
283        else:
284            self.src_mac = config_params['src_mac']
285
286        self.dst_ipv4 = config_params['dst_ipv4']
287        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
288            self.src_ipv4 = scapy.get_if_addr(interf)
289        else:
290            self.src_ipv4 = config_params['src_ipv4']
291
292    def generate(self,
293                 op='who-has',
294                 ip_dst=None,
295                 ip_src=None,
296                 hwsrc=None,
297                 hwdst=None,
298                 eth_dst=None):
299        """Generates a custom ARP packet.
300
301        Args:
302            op: ARP type (request or reply)
303            ip_dst: ARP ipv4 destination (Optional)
304            ip_src: ARP ipv4 source address (Optional)
305            hwsrc: ARP hardware source address (Optional)
306            hwdst: ARP hardware destination address (Optional)
307            eth_dst: Ethernet (layer 2) destination address (Optional)
308        """
309        # Create IP layer
310        hw_src = (hwsrc if hwsrc is not None else self.src_mac)
311        hw_dst = (hwdst if hwdst is not None else ARP_DST)
312        ipv4_dst = (ip_dst if ip_dst is not None else self.dst_ipv4)
313        ipv4_src = (ip_src if ip_src is not None else self.src_ipv4)
314        ip4 = scapy.ARP(
315            op=op, pdst=ipv4_dst, psrc=ipv4_src, hwdst=hw_dst, hwsrc=hw_src)
316
317        # Create Ethernet layer
318        mac_dst = (eth_dst if eth_dst is not None else MAC_BROADCAST)
319        ethernet = scapy.Ether(src=self.src_mac, dst=mac_dst)
320
321        self.packet = ethernet / ip4
322        return self.packet
323
324
325class DhcpOfferGenerator(object):
326    """Creates a custom DHCP offer packet
327
328    Attributes:
329        packet: desired built custom packet
330        subnet_mask: local network subnet mask
331        src_mac: MAC address (Layer 2) of the source node
332        dst_mac: MAC address (Layer 2) of the destination node
333        src_ipv4: IPv4 address (Layer 3) of the source node
334        dst_ipv4: IPv4 address (Layer 3) of the destination node
335        gw_ipv4: IPv4 address (Layer 3) of the Gateway
336    """
337
338    def __init__(self, **config_params):
339        """Initialize the class with the required network and packet params.
340
341        Args:
342            config_params: contains all the necessary packet parameters.
343              Some fields can be generated automatically. For example:
344              {'subnet_mask': '255.255.255.0',
345               'dst_ipv4': '192.168.1.3',
346               'src_ipv4: 'get_local', ...
347              The key can also be 'get_local' which means the code will read
348              and use the local interface parameters
349        """
350        interf = config_params['interf']
351        self.packet = None
352        self.subnet_mask = config_params['subnet_mask']
353        self.dst_mac = config_params['dst_mac']
354        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
355            self.src_mac = scapy.get_if_hwaddr(interf)
356        else:
357            self.src_mac = config_params['src_mac']
358
359        self.dst_ipv4 = config_params['dst_ipv4']
360        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
361            self.src_ipv4 = scapy.get_if_addr(interf)
362        else:
363            self.src_ipv4 = config_params['src_ipv4']
364
365        self.gw_ipv4 = config_params['gw_ipv4']
366
367    def generate(self, cha_mac=None, dst_ip=None):
368        """Generates a DHCP offer packet.
369
370        Args:
371            cha_mac: hardware target address for DHCP offer (Optional)
372            dst_ip: ipv4 address of target host for renewal (Optional)
373        """
374
375        # Create DHCP layer
376        dhcp = scapy.DHCP(options=[
377            ('message-type', 'offer'),
378            ('subnet_mask', self.subnet_mask),
379            ('server_id', self.src_ipv4),
380            ('end'),
381        ])
382
383        # Overwrite standard DHCP fields
384        sta_hw = (cha_mac if cha_mac is not None else self.dst_mac)
385        sta_ip = (dst_ip if dst_ip is not None else self.dst_ipv4)
386
387        # Create Boot
388        bootp = scapy.BOOTP(
389            op=DHCP_OFFER_OP,
390            yiaddr=sta_ip,
391            siaddr=self.src_ipv4,
392            giaddr=self.gw_ipv4,
393            chaddr=scapy.mac2str(sta_hw),
394            xid=DHCP_TRANS_ID)
395
396        # Create UDP
397        udp = scapy.UDP(sport=DHCP_OFFER_SRC_PORT, dport=DHCP_OFFER_DST_PORT)
398
399        # Create IP layer
400        ip4 = scapy.IP(src=self.src_ipv4, dst=IPV4_BROADCAST)
401
402        # Create Ethernet layer
403        ethernet = scapy.Ether(dst=MAC_BROADCAST, src=self.src_mac)
404
405        self.packet = ethernet / ip4 / udp / bootp / dhcp
406        return self.packet
407
408
409class NsGenerator(object):
410    """Creates a custom Neighbor Solicitation (NS) packet
411
412    Attributes:
413        packet: desired built custom packet
414        src_mac: MAC address (Layer 2) of the source node
415        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
416        src_ipv6: IPv6 address (Layer 3) of the source node
417        dst_ipv6: IPv6 address (Layer 3) of the destination node
418    """
419
420    def __init__(self, **config_params):
421        """Initialize the class with the required network and packet params.
422
423        Args:
424            config_params: contains all the necessary packet parameters.
425              Some fields can be generated automatically. For example:
426              {'subnet_mask': '255.255.255.0',
427               'dst_ipv4': '192.168.1.3',
428               'src_ipv4: 'get_local', ...
429              The key can also be 'get_local' which means the code will read
430              and use the local interface parameters
431        """
432        interf = config_params['interf']
433        self.packet = None
434        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
435            self.src_mac = scapy.get_if_hwaddr(interf)
436        else:
437            self.src_mac = config_params['src_mac']
438
439        self.dst_ipv6 = config_params['dst_ipv6']
440        self.src_ipv6_type = config_params['src_ipv6_type']
441        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
442            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
443        else:
444            self.src_ipv6 = config_params['src_ipv6']
445
446    def generate(self, ip_dst=None, eth_dst=None):
447        """Generates a Neighbor Solicitation (NS) packet (ICMP over IPv6).
448
449        Args:
450            ip_dst: NS ipv6 destination (Optional)
451            eth_dst: Ethernet (layer 2) destination address (Optional)
452        """
453        # Compute IP addresses
454        target_ip6 = ip_dst if ip_dst is not None else self.dst_ipv6
455        ndst_ip = socket.inet_pton(socket.AF_INET6, target_ip6)
456        nnode_mcast = scapy.in6_getnsma(ndst_ip)
457        node_mcast = socket.inet_ntop(socket.AF_INET6, nnode_mcast)
458        # Compute MAC addresses
459        hw_dst = (eth_dst
460                  if eth_dst is not None else scapy.in6_getnsmac(nnode_mcast))
461
462        # Create IPv6 layer
463        base = scapy.IPv6(dst=node_mcast, src=self.src_ipv6)
464        neighbor_solicitation = scapy.ICMPv6ND_NS(tgt=target_ip6)
465        src_ll_addr = scapy.ICMPv6NDOptSrcLLAddr(lladdr=self.src_mac)
466        ip6 = base / neighbor_solicitation / src_ll_addr
467
468        # Create Ethernet layer
469        ethernet = scapy.Ether(src=self.src_mac, dst=hw_dst)
470
471        self.packet = ethernet / ip6
472        return self.packet
473
474
475class RaGenerator(object):
476    """Creates a custom Router Advertisement (RA) packet
477
478    Attributes:
479        packet: desired built custom packet
480        src_mac: MAC address (Layer 2) of the source node
481        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
482        src_ipv6: IPv6 address (Layer 3) of the source node
483    """
484
485    def __init__(self, **config_params):
486        """Initialize the class with the required network and packet params.
487
488        Args:
489            config_params: contains all the necessary packet parameters.
490              Some fields can be generated automatically. For example:
491              {'subnet_mask': '255.255.255.0',
492               'dst_ipv4': '192.168.1.3',
493               'src_ipv4: 'get_local', ...
494              The key can also be 'get_local' which means the code will read
495              and use the local interface parameters
496        """
497        interf = config_params['interf']
498        self.packet = None
499        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
500            self.src_mac = scapy.get_if_hwaddr(interf)
501        else:
502            self.src_mac = config_params['src_mac']
503
504        self.src_ipv6_type = config_params['src_ipv6_type']
505        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
506            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
507        else:
508            self.src_ipv6 = config_params['src_ipv6']
509
510    def generate(self,
511                 lifetime,
512                 enableDNS=False,
513                 dns_lifetime=0,
514                 ip_dst=None,
515                 eth_dst=None):
516        """Generates a Router Advertisement (RA) packet (ICMP over IPv6).
517
518        Args:
519            lifetime: RA lifetime
520            enableDNS: Add RDNSS option to RA (Optional)
521            dns_lifetime: Set DNS server lifetime (Optional)
522            ip_dst: IPv6 destination address (Optional)
523            eth_dst: Ethernet (layer 2) destination address (Optional)
524        """
525        # Overwrite standard fields if desired
526        ip6_dst = (ip_dst if ip_dst is not None else RA_IP)
527        hw_dst = (eth_dst if eth_dst is not None else RA_MAC)
528
529        # Create IPv6 layer
530        base = scapy.IPv6(dst=ip6_dst, src=self.src_ipv6)
531        router_solicitation = scapy.ICMPv6ND_RA(routerlifetime=lifetime)
532        src_ll_addr = scapy.ICMPv6NDOptSrcLLAddr(lladdr=self.src_mac)
533        prefix = scapy.ICMPv6NDOptPrefixInfo(
534            prefixlen=RA_PREFIX_LEN, prefix=RA_PREFIX)
535        if enableDNS:
536            rndss = scapy.ICMPv6NDOptRDNSS(
537                lifetime=dns_lifetime, dns=[self.src_ipv6], len=DNS_LEN)
538            ip6 = base / router_solicitation / src_ll_addr / prefix / rndss
539        else:
540            ip6 = base / router_solicitation / src_ll_addr / prefix
541
542        # Create Ethernet layer
543        ethernet = scapy.Ether(src=self.src_mac, dst=hw_dst)
544
545        self.packet = ethernet / ip6
546        return self.packet
547
548
549class Ping6Generator(object):
550    """Creates a custom Ping v6 packet (i.e., ICMP over IPv6)
551
552    Attributes:
553        packet: desired built custom packet
554        src_mac: MAC address (Layer 2) of the source node
555        dst_mac: MAC address (Layer 2) of the destination node
556        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
557        src_ipv6: IPv6 address (Layer 3) of the source node
558        dst_ipv6: IPv6 address (Layer 3) of the destination node
559    """
560
561    def __init__(self, **config_params):
562        """Initialize the class with the required network and packet params.
563
564        Args:
565            config_params: contains all the necessary packet parameters.
566              Some fields can be generated automatically. For example:
567              {'subnet_mask': '255.255.255.0',
568               'dst_ipv4': '192.168.1.3',
569               'src_ipv4: 'get_local', ...
570              The key can also be 'get_local' which means the code will read
571              and use the local interface parameters
572        """
573        interf = config_params['interf']
574        self.packet = None
575        self.dst_mac = config_params['dst_mac']
576        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
577            self.src_mac = scapy.get_if_hwaddr(interf)
578        else:
579            self.src_mac = config_params['src_mac']
580
581        self.dst_ipv6 = config_params['dst_ipv6']
582        self.src_ipv6_type = config_params['src_ipv6_type']
583        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
584            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
585        else:
586            self.src_ipv6 = config_params['src_ipv6']
587
588    def generate(self, ip_dst=None, eth_dst=None):
589        """Generates a Ping6 packet (i.e., Echo Request)
590
591        Args:
592            ip_dst: IPv6 destination address (Optional)
593            eth_dst: Ethernet (layer 2) destination address (Optional)
594        """
595        # Overwrite standard fields if desired
596        ip6_dst = (ip_dst if ip_dst is not None else self.dst_ipv6)
597        hw_dst = (eth_dst if eth_dst is not None else self.dst_mac)
598
599        # Create IPv6 layer
600        base = scapy.IPv6(dst=ip6_dst, src=self.src_ipv6)
601        echo_request = scapy.ICMPv6EchoRequest(data=PING6_DATA)
602
603        ip6 = base / echo_request
604
605        # Create Ethernet layer
606        ethernet = scapy.Ether(src=self.src_mac, dst=hw_dst)
607
608        self.packet = ethernet / ip6
609        return self.packet
610
611
612class Ping4Generator(object):
613    """Creates a custom Ping v4 packet (i.e., ICMP over IPv4)
614
615    Attributes:
616        packet: desired built custom packet
617        src_mac: MAC address (Layer 2) of the source node
618        dst_mac: MAC address (Layer 2) of the destination node
619        src_ipv4: IPv4 address (Layer 3) of the source node
620        dst_ipv4: IPv4 address (Layer 3) of the destination node
621    """
622
623    def __init__(self, **config_params):
624        """Initialize the class with the required network and packet params.
625
626        Args:
627            config_params: contains all the necessary packet parameters.
628              Some fields can be generated automatically. For example:
629              {'subnet_mask': '255.255.255.0',
630               'dst_ipv4': '192.168.1.3',
631               'src_ipv4: 'get_local', ...
632              The key can also be 'get_local' which means the code will read
633              and use the local interface parameters
634        """
635        interf = config_params['interf']
636        self.packet = None
637        self.dst_mac = config_params['dst_mac']
638        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
639            self.src_mac = scapy.get_if_hwaddr(interf)
640        else:
641            self.src_mac = config_params['src_mac']
642
643        self.dst_ipv4 = config_params['dst_ipv4']
644        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
645            self.src_ipv4 = scapy.get_if_addr(interf)
646        else:
647            self.src_ipv4 = config_params['src_ipv4']
648
649    def generate(self, ip_dst=None, eth_dst=None):
650        """Generates a Ping4 packet (i.e., Echo Request)
651
652        Args:
653            ip_dst: IP destination address (Optional)
654            eth_dst: Ethernet (layer 2) destination address (Optional)
655        """
656
657        # Overwrite standard fields if desired
658        sta_ip = (ip_dst if ip_dst is not None else self.dst_ipv4)
659        sta_hw = (eth_dst if eth_dst is not None else self.dst_mac)
660
661        # Create IPv6 layer
662        base = scapy.IP(src=self.src_ipv4, dst=sta_ip)
663        echo_request = scapy.ICMP(type=PING4_TYPE)
664
665        ip4 = base / echo_request
666
667        # Create Ethernet layer
668        ethernet = scapy.Ether(src=self.src_mac, dst=sta_hw)
669
670        self.packet = ethernet / ip4
671        return self.packet
672
673
674class Mdns6Generator(object):
675    """Creates a custom mDNS IPv6 packet
676
677    Attributes:
678        packet: desired built custom packet
679        src_mac: MAC address (Layer 2) of the source node
680        src_ipv6_type: IPv6 source address type (e.g., Link Local, Global, etc)
681        src_ipv6: IPv6 address (Layer 3) of the source node
682    """
683
684    def __init__(self, **config_params):
685        """Initialize the class with the required network and packet params.
686
687        Args:
688            config_params: contains all the necessary packet parameters.
689              Some fields can be generated automatically. For example:
690              {'subnet_mask': '255.255.255.0',
691               'dst_ipv4': '192.168.1.3',
692               'src_ipv4: 'get_local', ...
693              The key can also be 'get_local' which means the code will read
694              and use the local interface parameters
695        """
696        interf = config_params['interf']
697        self.packet = None
698        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
699            self.src_mac = scapy.get_if_hwaddr(interf)
700        else:
701            self.src_mac = config_params['src_mac']
702
703        self.src_ipv6_type = config_params['src_ipv6_type']
704        if config_params['src_ipv6'] == GET_FROM_LOCAL_INTERFACE:
705            self.src_ipv6 = wputils.get_if_addr6(interf, self.src_ipv6_type)
706        else:
707            self.src_ipv6 = config_params['src_ipv6']
708
709    def generate(self, ip_dst=None, eth_dst=None):
710        """Generates a mDNS v6 packet for multicast DNS config
711
712        Args:
713            ip_dst: IPv6 destination address (Optional)
714            eth_dst: Ethernet (layer 2) destination address (Optional)
715        """
716
717        # Overwrite standard fields if desired
718        sta_ip = (ip_dst if ip_dst is not None else MDNS_V6_IP_DST)
719        sta_hw = (eth_dst if eth_dst is not None else MDNS_V6_MAC_DST)
720
721        # Create mDNS layer
722        qdServer = scapy.DNSQR(qname=self.src_ipv6, qtype=MDNS_QTYPE)
723        mDNS = scapy.DNS(rd=MDNS_RECURSIVE, qd=qdServer)
724
725        # Create UDP
726        udp = scapy.UDP(sport=MDNS_UDP_PORT, dport=MDNS_UDP_PORT)
727
728        # Create IP layer
729        ip6 = scapy.IPv6(src=self.src_ipv6, dst=sta_ip)
730
731        # Create Ethernet layer
732        ethernet = scapy.Ether(src=self.src_mac, dst=sta_hw)
733
734        self.packet = ethernet / ip6 / udp / mDNS
735        return self.packet
736
737
738class Mdns4Generator(object):
739    """Creates a custom mDNS v4 packet
740
741    Attributes:
742        packet: desired built custom packet
743        src_mac: MAC address (Layer 2) of the source node
744        src_ipv4: IPv4 address (Layer 3) of the source node
745    """
746
747    def __init__(self, **config_params):
748        """Initialize the class with the required network and packet params.
749
750        Args:
751            config_params: contains all the necessary packet parameters.
752              Some fields can be generated automatically. For example:
753              {'subnet_mask': '255.255.255.0',
754               'dst_ipv4': '192.168.1.3',
755               'src_ipv4: 'get_local', ...
756              The key can also be 'get_local' which means the code will read
757              and use the local interface parameters
758        """
759        interf = config_params['interf']
760        self.packet = None
761        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
762            self.src_mac = scapy.get_if_hwaddr(interf)
763        else:
764            self.src_mac = config_params['src_mac']
765
766        if config_params['src_ipv4'] == GET_FROM_LOCAL_INTERFACE:
767            self.src_ipv4 = scapy.get_if_addr(interf)
768        else:
769            self.src_ipv4 = config_params['src_ipv4']
770
771    def generate(self, ip_dst=None, eth_dst=None):
772        """Generates a mDNS v4 packet for multicast DNS config
773
774        Args:
775            ip_dst: IP destination address (Optional)
776            eth_dst: Ethernet (layer 2) destination address (Optional)
777        """
778
779        # Overwrite standard fields if desired
780        sta_ip = (ip_dst if ip_dst is not None else MDNS_V4_IP_DST)
781        sta_hw = (eth_dst if eth_dst is not None else MDNS_V4_MAC_DST)
782
783        # Create mDNS layer
784        qdServer = scapy.DNSQR(qname=self.src_ipv4, qtype=MDNS_QTYPE)
785        mDNS = scapy.DNS(rd=MDNS_RECURSIVE, qd=qdServer)
786
787        # Create UDP
788        udp = scapy.UDP(sport=MDNS_UDP_PORT, dport=MDNS_UDP_PORT)
789
790        # Create IP layer
791        ip4 = scapy.IP(src=self.src_ipv4, dst=sta_ip, ttl=255)
792
793        # Create Ethernet layer
794        ethernet = scapy.Ether(src=self.src_mac, dst=sta_hw)
795
796        self.packet = ethernet / ip4 / udp / mDNS
797        return self.packet
798
799
800class Dot3Generator(object):
801    """Creates a custom 802.3 Ethernet Frame
802
803    Attributes:
804        packet: desired built custom packet
805        src_mac: MAC address (Layer 2) of the source node
806        src_ipv4: IPv4 address (Layer 3) of the source node
807    """
808
809    def __init__(self, **config_params):
810        """Initialize the class with the required network and packet params.
811
812        Args:
813            config_params: contains all the necessary packet parameters.
814              Some fields can be generated automatically. For example:
815              {'subnet_mask': '255.255.255.0',
816               'dst_ipv4': '192.168.1.3',
817               'src_ipv4: 'get_local', ...
818              The key can also be 'get_local' which means the code will read
819              and use the local interface parameters
820        """
821        interf = config_params['interf']
822        self.packet = None
823        self.dst_mac = config_params['dst_mac']
824        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
825            self.src_mac = scapy.get_if_hwaddr(interf)
826        else:
827            self.src_mac = config_params['src_mac']
828
829    def _build_ether(self, eth_dst=None):
830        """Creates the basic frame for 802.3
831
832        Args:
833            eth_dst: Ethernet (layer 2) destination address (Optional)
834        """
835        # Overwrite standard fields if desired
836        sta_hw = (eth_dst if eth_dst is not None else self.dst_mac)
837        # Create Ethernet layer
838        dot3_base = scapy.Dot3(src=self.src_mac, dst=sta_hw)
839
840        return dot3_base
841
842    def _pad_frame(self, frame):
843        """Pads the frame with default length and values
844
845        Args:
846            frame: Ethernet (layer 2) to be padded
847        """
848        frame.len = PAD_LEN_BYTES
849        pad = scapy.Padding()
850        pad.load = '\x00' * PAD_LEN_BYTES
851        return frame / pad
852
853    def generate(self, eth_dst=None):
854        """Generates the basic 802.3 frame and adds padding
855
856        Args:
857            eth_dst: Ethernet (layer 2) destination address (Optional)
858        """
859        # Create 802.3 Base
860        ethernet = self._build_ether(eth_dst)
861
862        self.packet = self._pad_frame(ethernet)
863        return self.packet
864
865    def generate_llc(self, eth_dst=None, dsap=2, ssap=3, ctrl=LLC_XID_CONTROL):
866        """Generates the 802.3 frame with LLC and adds padding
867
868        Args:
869            eth_dst: Ethernet (layer 2) destination address (Optional)
870            dsap: Destination Service Access Point (Optional)
871            ssap: Source Service Access Point (Optional)
872            ctrl: Control (Optional)
873        """
874        # Create 802.3 Base
875        ethernet = self._build_ether(eth_dst)
876
877        # Create LLC layer
878        llc = scapy.LLC(dsap=dsap, ssap=ssap, ctrl=ctrl)
879
880        # Append and create packet
881        self.packet = self._pad_frame(ethernet / llc)
882        return self.packet
883
884    def generate_snap(self,
885                      eth_dst=None,
886                      dsap=SNAP_DSAP,
887                      ssap=SNAP_SSAP,
888                      ctrl=SNAP_CTRL,
889                      oui=SNAP_OUI,
890                      code=ETH_TYPE_IP):
891        """Generates the 802.3 frame with LLC and SNAP and adds padding
892
893        Args:
894            eth_dst: Ethernet (layer 2) destination address (Optional)
895            dsap: Destination Service Access Point (Optional)
896            ssap: Source Service Access Point (Optional)
897            ctrl: Control (Optional)
898            oid: Protocol Id or Org Code (Optional)
899            code: EtherType (Optional)
900        """
901        # Create 802.3 Base
902        ethernet = self._build_ether(eth_dst)
903
904        # Create 802.2 LLC header
905        llc = scapy.LLC(dsap=dsap, ssap=ssap, ctrl=ctrl)
906
907        # Create 802.3 SNAP header
908        snap = scapy.SNAP(OUI=oui, code=code)
909
910        # Append and create packet
911        self.packet = self._pad_frame(ethernet / llc / snap)
912        return self.packet
913