Packet Manipulation

The 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 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 (INGRESS) that is destined for MAC address 00:E0:2B:00:00:00 will be put on the socket and also forwarded (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, select() can be used to block for the next packet and 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 string containing the entire packet, including L2 headers, and addr is an 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.

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

class exos.api.expkt.socket(*args, **kwargs)[source]

An EXPKT socket. This socket object subclasses Python’s socket class and can be used as a drop-in replacement. It adds EXOS-specific features, such as virtual router awareness and the ability to send and receive data plane traffic.

detach_all_filters()[source]

Removes all filters attached to this socket. Runs on close().

set_callback(func, buf_size=9000, flags)[source]

Allows asynchoronous reading of the socket. Takes a function to call back when a packet is received on the socket. The function must accept one arguement: the socket.

set_vr(vr_name)[source]

Set the virtual router that the socket operates on. For L3 sockets only.

setup_filter(name, action=UP_AND_CONTINUE, dstmac=None, ethertype=None, vr=None, ethertype_offset=24)[source]

Set up the necessary filter to control what packets are sent to the socket.

name is required. Also, either dstmac, ethertype, or both are required.

Parameters:

name
Name to use when creating an access-list for this filter. This name will be visible in the show access-list CLI command, for example.
dstmac
Destination MAC address to filter on. If not provided, then ethertype must be provided.
ethertype
Ethertype to filter on. If not provided, then dstmac must be provided.
vr
Name of a virtual router to filter on. By default, the filter will run on all virtual routers.
action
Action to take when the filter is matched. Valid actions include UP_AND_CONTINUE and UP_AND_DROP.
ethertype_offset
The location of the ethertype in the packet (measured in bytes). The default is 24.
unset_callback()[source]

Removes the callback associated with the socket. Runs on close().

exos.api.expkt.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.

exos.api.expkt.ETH_P_ALL

Socket protocol that indicates the socket receives all protocols.

exos.api.expkt.UP_AND_CONTINUE

Filter action indicating a matched packet must be put on the socket and also forwarded by the data plane.

exos.api.expkt.UP_AND_DROP

Filter action indicating a matched packet must be put on the socket and not forwarded by the data plane.

exos.api.expkt.INGRESS

Filter direction indicating the filter patches only incoming packets.

exos.api.expkt.EGRESS

Filter direction indicating the filter patches only outgoing packets.

exos.api.expkt.EGRESS_AND_INGRESS

Filter direction indicating the filter patches both incoming and outgoing packets.

exos.api.expkt.SO_VRID

Socket option to get or set a socket’s virtual router, to be used with getsockopt() and setsockopt(). The value is the VR’s id (vrid), not the VR’s name. To set the virtual router by name, see set_vrid().

For example, the following puts sock2 in the same VR as sock1:

vrid=sock1.getsockopt(SOL_SOCKET, socket.SO_VRID)
sock2.setsockopt(SOL_SOCKET, socket.SO_VRID, vrid)