API Reference¶
The “net” object¶
-
class
switchyard.switchy_real.
PyLLNet
(devlist, name=None)[source]¶ A class that represents a collection of network devices on which packets can be received and sent.
An object of this class is passed into the main function of a user’s Switchyard program. Using methods on this object, a user can send/receive packets and query the device for what interfaces are available and how they are configured.
-
interface_by_ipaddr
(ipaddr)¶ Given an IP address, return the interface that ‘owns’ this address
-
interface_by_macaddr
(macaddr)¶ Given a MAC address, return the interface that ‘owns’ this address
-
interface_by_name
(name)¶ Given a device name, return the corresponding interface object
-
interfaces
()¶ Return a list of interfaces incident on this node/router. Each item in the list is an Interface object, each of which includes name, ethaddr, ipaddr, and netmask attributes.
-
port_by_ipaddr
(ipaddr)¶ Alias for interface_by_ipaddr
-
port_by_macaddr
(macaddr)¶ Alias for interface_by_macaddr
-
port_by_name
(name)¶ Alias for interface_by_name
-
ports
()¶ Alias for interfaces() method.
-
recv_packet
(timeout=None, timestamp=False)[source]¶ Receive packets from any device on which one is available. Blocks until it receives a packet, unless a timeout value >=0 is given. Raises Shutdown exception when device(s) are shut down (i.e., on a SIGINT to the process). Raises NoPackets when there are no packets that can be read.
Returns a tuple of length 2 or 3, depending on whether the timestamp is desired.
- device: network device name on which packet was received as a string
- timestamp: floating point value of time at which packet was received (optionally returned; only if timestamp=True)
- packet: Switchyard Packet object.
-
Packet parsing and construction¶
-
class
switchyard.lib.packet.
Packet
(raw=None, first_header=None)[source]¶ Base class for packet headers.
The Packet class acts as a container for packet headers. The + and += operators are defined for use with the Packet class to add on headers (to the end of the packet). Indexing can also be done with Packet objects to access individual header objects. Indexes may be integers (from 0 up to, but not including, the number of packet headers), or indexes may also be packet header class names. Exceptions are raised for invaliding indexing of either kind.
>>> p = Packet() >>> p += Ethernet() >>> p[0] <switchyard.lib.packet.ethernet.Ethernet object at 0x10632bb08> >>> p[Ethernet] <switchyard.lib.packet.ethernet.Ethernet object at 0x10632bb08> >>> str(p) 'Ethernet 00:00:00:00:00:00->00:00:00:00:00:00 IP' >>> str(p[0]) 'Ethernet 00:00:00:00:00:00->00:00:00:00:00:00 IP' >>> str(p[Ethernet]) 'Ethernet 00:00:00:00:00:00->00:00:00:00:00:00 IP' >>>
-
add_header
(ph)[source]¶ Add a PacketHeaderBase derived class object, or a raw bytes object as the next “header” item in this packet. Note that ‘header’ may be a slight misnomer since the last portion of a packet is considered application payload and not a header per se.
-
static
from_bytes
(raw, first_header)[source]¶ Create a new packet by parsing the contents of a bytestring
-
get_header
(hdrclass, returnval=None)[source]¶ Return the first header object that is of class hdrclass, or None if the header class isn’t found.
-
get_header_index
(hdrclass, startidx=0)[source]¶ Return the first index of the header class hdrclass starting at startidx (default=0), or -1 if the header class isn’t found in the list of headers.
-
insert_header
(idx, ph)[source]¶ Insert a PacketHeaderBase-derived object at index idx the list of headers. Any headers previously in the Packet from index idx:len(ph) are shifted to make room for the new packet.
-
To delete/remove a header, you can use the del
operator as if the packet
object is a Python list:
>>> del p[0] # delete/remove first header in packet
>>>
You can assign new header objects to a packet by integer index, but not by packet header class index:
>>> p[0] = Ethernet() # assign a new Ethernet header to index 0
>>>
Header classes¶
In this section, detailed documentation for all packet header classes is given. For each header class, there are three common methods that may be useful and which are not documented below for clarity:
size()
: returns the number of bytes that the header would consist of when serialized to wire formatto_bytes()
: returns the serialized (wire format) representation of the packet as a byte stringfrom_bytes(b)
: parses a byte string representing this packet header and constructs the various header fields from the raw bytes
Ethernet header¶
-
class
switchyard.lib.packet.
Ethernet
(**kwargs)[source]¶ Represents an Ethernet header with fields src (source Ethernet address), dst (destination Ethernet address), and ethertype (type of header to come in the packet after the Ethernet header). All valid ethertypes are defined below.
-
dst
¶
-
ethertype
¶
-
src
¶
-
-
class
switchyard.lib.packet.common.
EtherType
[source]¶ -
IP = 0x0800
-
IPv4 = 0x0800
-
ARP = 0x0806
-
x8021Q = 0x8100
-
IPv6 = 0x86dd
-
SLOW = 0x8809
-
MPLS = 0x8847
-
x8021AD = 0x88a8
-
LLDP = 0x88cc
-
x8021AH = 0x88e7
-
IEEE8023 = 0x05dc
The EtherType class is derived from the built-in Python Enumerated class type. Note that some values start with ‘x’ since they must start with an alphabetic character to be valid in the enum.
-
By default, the Ethernet header addresses are all zeroes (“00:00:00:00:00:00”), and the ethertype is IPv4. Here is an example of creating an Ethernet header and setting the header fields to non-default values:
>>> e = Ethernet()
>>> e.src = "de:ad:00:00:be:ef"
>>> e.dst = "ff:ff:ff:ff:ff:ff"
>>> e.ethertype = EtherType.ARP
ARP (address resolution protocol) header¶
-
class
switchyard.lib.packet.
Arp
(**kwargs)[source]¶ -
hardwaretype
¶
-
operation
¶
-
protocoltype
¶
-
senderhwaddr
¶
-
senderprotoaddr
¶
-
targethwaddr
¶
-
targetprotoaddr
¶
-
The Arp
class is used for constructing ARP (address resolution protocol)
requests and replies. The hardwaretype
property defaults to Ethernet
,
so you don’t need to set that when an Arp
object is instantiated. The
operation can be set using the enumerated type ArpOperation
, as indicated
above. The remaining fields hold either EthAddr
or IPv4Address
objects,
and can be initialized using string representations of Ethernet or IPv4
addresses as appropriate. Below is an example of creating an ARP request.
You can assume in the example that the senders Ethernet and IPv4
addresses are srchw
and srcip
, respectively. You can also
assume that the IPv4 address for which we are requesting the Ethernet
address is targetip
.
ether = Ethernet()
ether.src = srchw
ether.dst = 'ff:ff:ff:ff:ff:ff'
ether.ethertype = EtherType.ARP
arp = Arp()
arp.operation = ArpOperation.Request
arp.senderhwaddr = srchw
arp.senderprotoaddr = srcip
arp.targethwaddr = 'ff:ff:ff:ff:ff:ff'
arp.targetprotoaddr = targetip
arppacket = ether + arp
IP version 4 header¶
-
class
switchyard.lib.packet.
IPv4
(**kwargs)[source]¶ Represents an IP version 4 packet header. All properties relate to specific fields in the header and can be inspected and/or modified.
Note that the field named “hl” (“h-ell”) stands for “header length”. It is the size of the header in 4-octet quantities. It is a read-only property (cannot be set).
Note also that some IPv4 header option classes are available in Switchyard, but are currently undocumented.
-
dscp
¶
-
dst
¶
-
dstip
¶ Deprecated property. Use dst instead.
-
ecn
¶
-
flags
¶
-
fragment_offset
¶
-
hl
¶
-
ipid
¶
-
options
¶
-
protocol
¶
-
src
¶
-
srcip
¶ Deprecated property. Use src instead.
-
tos
¶
-
total_length
¶
-
ttl
¶
-
-
class
switchyard.lib.packet.common.
IPProtocol
[source]¶ -
ICMP = 1
-
TCP = 6
-
UDP = 17
The IPProtocol class derives from the Python 3-builtin Enumerated class type. There are other protocol numbers defined. See switchyard.lib.packet.common for all defined values.
-
A just-constructed IPv4 header defaults to having all zeroes for the source and destination addresses (‘0.0.0.0’) and the protocol number defaults to ICMP. An example of creating an IPv4 header and setting various fields is shown below:
>>> ip = IPv4()
>>> ip.srcip = '10.0.1.1'
>>> ip.dstip = '10.0.2.42'
>>> ip.protocol = IPProtocol.UDP
>>> ip.ttl = 64
UDP (user datagram protocol) header¶
-
class
switchyard.lib.packet.
UDP
(**kwargs)[source]¶ The UDP header contains just source and destination port fields.
-
dstport
¶
-
srcport
¶
-
To construct a packet that includes an UDP header as well as some application data, the same pattern of packet construction can be followed:
>>> p = Ethernet() + IPv4() + UDP()
>>> p[1].protocol = IPProtocol.UDP
>>> p[2].srcport = 4444
>>> p[2].dstport = 5555
>>> p += b'These are some application data bytes'
>>> print (p)
Ethernet 00:00:00:00:00:00->00:00:00:00:00:00 IP | IPv4 0.0.0.0->0.0.0.0 UDP | UDP 4444->5555 | RawPacketContents (37 bytes) b'These are '...
>>>
Note that we didn’t set the IP addresses or Ethernet addresses above, but
did set the IP protocol to correctly match the next header (UDP). Adding
a payload to a packet is as simple as tacking on a Python bytes
object.
You can also construct a RawPacketContents
header, which is just a
packet header class that wraps a set of raw bytes.
TCP (transmission control protocol) header¶
-
class
switchyard.lib.packet.
TCP
(**kwargs)[source]¶ Represents a TCP header. Includes properties to access/modify TCP header fields.
-
ACK
¶
-
CWR
¶
-
ECE
¶
-
FIN
¶
-
NS
¶
-
PSH
¶
-
RST
¶
-
SYN
¶
-
URG
¶
-
ack
¶
-
dstport
¶
-
flags
¶
-
offset
¶
-
options
¶
-
seq
¶
-
srcport
¶
-
urgent_pointer
¶
-
window
¶
-
Setting TCP header flags can be done by assigning 1 to any of the mnemonic flag properties:
>>> t = TCP()
>>> t.SYN = 1
To check whether a flag has been set, you can simply inspect the the flag value:
>>> if t.SYN:
>>> ...
ICMP (Internet control message protocol) header¶
-
class
switchyard.lib.packet.
ICMP
(**kwargs)[source]¶ A mother class for all ICMP message types. It holds a reference to another object that contains the specific ICMP data (icmpdata), given a particular ICMP type. Just setting the icmptype causes the data object to change (the change happens automatically when you set the icmptype). The icmpcode field will also change, but it only changes to some valid code given the new icmptype.
Represents an ICMP packet header.
-
icmpcode
¶
-
icmpdata
¶
-
icmptype
¶
-
-
class
switchyard.lib.packet.common.
ICMPType
[source]¶ -
EchoReply = 0
-
DestinationUnreachable = 3
-
SourceQuench = 4
-
Redirect = 5
-
EchoRequest = 8
-
TimeExceeded = 11
-
The icmptype and icmpcode header fields determine the value stored in the icmpdata property. When the icmptype is set to a new value, the icmpdata field is automatically set to the correct object.
>>> i = ICMP()
>>> print (i)
ICMP EchoRequest 0 0 (0 data bytes)
>>> i.icmptype = ICMPType.TimeExceeded
>>> print (i)
ICMP TimeExceeded:TTLExpired 0 bytes of raw payload (b'') OrigDgramLen: 0
>>> i.icmpcode
<ICMPCodeTimeExceeded.TTLExpired: 0>
>>> i.icmpdata
<switchyard.lib.packet.icmp.ICMPTimeExceeded object at 0x10d3a3308>
Notice above that when the icmptype changes, other contents in the ICMP header object change appropriately.
To access and/or modify the payload (i.e., data) that comes after the ICMP header, use icmpdata.data
. This object is a raw bytes object and can be accessed and or set. For example, with many ICMP error messages, up to the first 28 bytes of the “dead” packet should be included, starting with the IPv4 header. To do that, you must set the icmpdata.data
attribute with the byte-level representation of the IP header data you want to include, as follows:
>>> i.icmpdata.data
b''
>>> i.icmpdata.data = pkt.to_bytes()[:28]
>>> i.icmpdata.origdgramlen = len(pkt)
>>> print (i)
ICMP TimeExceeded:TTLExpired 28 bytes of raw payload (b'E\x00\x00\x14\x00\x00\x00\x00\x00\x01') OrigDgramLen: 42
>>>
In the above code segment, pkt
should be a Packet object that just contains the IPv4 header and any subsequent headers and data. It must not include an Ethernet header. If you need to strip an Ethernet header, you can get its index (pkt.get_header_index(Ethernet)
), then remove the header by index (del pkt[index]
).
Notice that above, the to_bytes
method returns the byte-level representation of the IP header we’re including as the payload. The to_bytes
method can be called on any packet header, or on an packet object (in which case all packet headers will be byte-serialized).
To set the icmpcode, a dictionary called ICMPTypeCodeMap
is defined
in switchyard.lib.packet
. Keys in the dictionary are of type ICMPType
, and values for each key is another enumerated type indicating the valid
codes for the given type.
>>> from switchyard.lib.packet import *
>>> ICMPTypeCodeMap[ICMPType.DestinationUnreachable]
<enum 'DestinationUnreachable'>
Just getting the dictionary value isn’t particularly helpful, but if you coerce the enum to a list, you can see all valid values:
>>> list(ICMPTypeCodeMap[ICMPType.DestinationUnreachable])
[ <DestinationUnreachable.ProtocolUnreachable: 2>,
<DestinationUnreachable.SourceHostIsolated: 8>,
<DestinationUnreachable.FragmentationRequiredDFSet: 4>,
<DestinationUnreachable.HostUnreachable: 1>,
<DestinationUnreachable.DestinationNetworkUnknown: 6>,
<DestinationUnreachable.NetworkUnreachableForTOS: 11>,
<DestinationUnreachable.HostAdministrativelyProhibited: 10>,
<DestinationUnreachable.DestinationHostUnknown: 7>,
<DestinationUnreachable.HostPrecedenceViolation: 14>,
<DestinationUnreachable.PrecedenceCutoffInEffect: 15>,
<DestinationUnreachable.NetworkAdministrativelyProhibited: 9>,
<DestinationUnreachable.NetworkUnreachable: 0>,
<DestinationUnreachable.SourceRouteFailed: 5>,
<DestinationUnreachable.PortUnreachable: 3>,
<DestinationUnreachable.CommunicationAdministrativelyProhibited: 13>,
<DestinationUnreachable.HostUnreachableForTOS: 12> ]
Another example, but with the much simpler EchoRequest:
>>> list(ICMPTypeCodeMap[ICMPType.EchoRequest])
[<EchoRequest.EchoRequest: 0>]
If you try to set the icmpcode to an invalid value, an exception will be raised:
>>> i = ICMP()
>>> i.icmptype = ICMPType.DestinationUnreachable
>>> i.icmpcode = 44
Traceback (most recent call last):
...
>>>
You can either (validly) set the code using an integer, or a valid enumerated type value:
>>> i.icmpcode = 2
>>> print(i)
ICMP DestinationUnreachable:ProtocolUnreachable 0 bytes of raw payload (b'') NextHopMTU: 0
>>> i.icmpcode = ICMPTypeCodeMap[i.icmptype].HostUnreachable
>>> print (i)
ICMP DestinationUnreachable:HostUnreachable 0 bytes of raw payload (b'') NextHopMTU: 0
Below are shown the ICMP data classes, as well as any properties that can be inspected and/or modified on them.
Utility functions¶
-
exception
switchyard.lib.common.
NoPackets
[source]¶ Exception that is raised in user Switchyard program when the recv_packet() method is called on the net object and there are no packets available.