.. _expkt: .. module:: exos.api.expkt Packet Manipulation ================================================ The :mod:`expkt` module provides packet manipulation capabilities that can be used by a Python application to implement a network protocol. * At Layer 2, data plane packets can be identified by MAC address or Ethertype and received into the Python application. From there, the application can process the packet as it pleases. It can also generate new packets and inject them back into the data plane. * At Layer 3, the application, or individual sockets within the application, can be bound to a virtual router, thereby scoping it. The expkt module subclasses Python's :mod:`socket` module and can be used as a drop-in replacement:: import exos.api as exosapi import exosapi.expkt as socket Layer 2 ------------------------------------------------ A Layer 2 socket is typically created as follows:: sock = socket.socket(socket.PF_EXOS_PACKET, socket.SOCK_RAW, socket.htons(socket.ETH_P_ALL)) This socket uses the EXOS packet interface and is able to send and receive raw packets to and from the data plane. Receive ++++++++++++++++++++++++++++++++++++++++++++++++ Prior to receiving packets on an L2 socket, a filter must be set up:: sock.setup_filter("filter1", action=socket.UP_AND_CONTINUE, direction=socket.INGRESS, dstmac=[0x00,0xE0,0x2B,0x00,0x00,0x00]) This filter means that any packet coming into the data plane (:data:`INGRESS`) that is destined for MAC address 00:E0:2B:00:00:00 will be put on the socket and also forwarded (:data:`UP_AND_CONTINUE`). Ethertype could also be used:: sock.setup_filter("filter2", ethertype=0x0842) Once the filter is set up, the socket behaves as any other socket. For example, :func:`~select.select` can be used to block for the next packet and :meth:`~socket.socket.recvfrom` can be used to retrieve a packet. The following could be used to receive a packet:: pkt, addr = sock.recvfrom(8192) In the above, `pkt` is a :class:`string` containing the entire packet, including L2 headers, and `addr` is an :class:`ExosSockAddr`. Send ++++++++++++++++++++++++++++++++++++++++++++++++ When sending on an L2 socket using the EXOS packet interface, a few extra attributes are available: slot_ports A sequence of tuples. Each tuple is a (`slot`, `port`) pair identifying a port on which the packet should be sent. The following example sends on ports 1:1 and 1:2:: sock.sendto(pkt, slot_ports=[(1,1),(1,2)]) vlan_name The VLAN on which the packet is sent. The following example forwards the packet on VLAN foo:: sock.sendto(pkt, vlan_name="foo") Layer 3 ------------------------------------------------ Layer 3 sockets are created as normal. Examples include:: udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Every EXOS process has an affinity to a particular virtual router. This means the process will tend to do things, like open a socket, in the scope of that virtual router. In the EXOS Python API, the VR affinity defaults to "VR-Mgmt". However, this can be overridden at process startup via the "vr" option. How this option is passed depends on the startup mechanism. For example, when running EXPY directly, it is the "-v" option and when using "create process", it is the "vr" option. An individual socket can be bound to a different virtual router:: tcp_sock.set_vr("VR-Default") Impacket ------------------------------------------------ Impacket is a Python library that helps encode and decode packets. It is included as a convenience, but is not part of the EXOS Python API. More information can be found at the `Impacket website`_. .. _Impacket website: http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=tool&name=Impacket The following example uses Impacket to build an ICMP packet. The packet is then sent:: ip=ImpactPacket.IP() ip.set_ip_src("10.11.12.13") ip.set_ip_dst("13.12.11.10") icmp=ImpactPacket.ICMP() icmp.set_icmp_type(icmp.ICMP_ECHO) icmp.contains(ImpactPacket.Data("PYTHON_COMPLETES_EXOS"*5)) icmp.set_icmp_id(42) icmp.set_icmp_cksum(0) icmp.auto_checksum = 1 ip.contains(icmp) sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) sock.sendto(ip.get_packet(), ("13.12.11.10", 0)) Asynchronous Callbacks ------------------------------------------------ Sockets can be used asynchronously by setting a callback. When data is received on the socket, the callback is called. Typically, the callback would then receive on the socket and do something with the packet:: def packet_handler(handled_sock): pkt, addr = handled_sock.recvfrom(8192) # Do something with pkt sock.set_callback(packet_handler) Registering a socket callback has a similar result as creating an extra thread to block on the socket, except that the extra thread is not needed. Instead, a shared thread is used to block on many sockets, including ones internal to EXOS. API ------------------------------------------------ .. autoclass:: socket :members: setup_filter, set_vr, set_callback, unset_callback, detach_all_filters .. data:: PF_EXOS_PACKET Socket domain for the EXOS packet (EXPKT) interface. Sockets created in this domain will be able to send and receive raw packets to and from the data plane. .. data:: ETH_P_ALL Socket protocol that indicates the socket receives all protocols. .. data:: UP_AND_CONTINUE Filter action indicating a matched packet must be put on the socket and also forwarded by the data plane. .. data:: UP_AND_DROP Filter action indicating a matched packet must be put on the socket and not forwarded by the data plane. .. data:: INGRESS Filter direction indicating the filter patches only incoming packets. .. data:: EGRESS Filter direction indicating the filter patches only outgoing packets. .. data:: EGRESS_AND_INGRESS Filter direction indicating the filter patches both incoming and outgoing packets.