Skip to content

Helper API

Gregory Shimansky edited this page May 6, 2019 · 13 revisions

There are two main steps of developing network function using NFF-GO

  1. Packet processing graph construction - covered by previous chapter - Building graph
  2. Implementing user defined functions inside processing graph - covered by this chapter
Table of Contents

IPv4, IPv6, TCP, UDP packets

Parsing

All UDFs get a pointer to the packet. If it is a first UDF in a flow packet is unparsed and should be parsed. Ethernet (L2) pointer is automatically parsed, other levels should be parsed in lazy mode by user request. Following FFs will get a packet in the parsed state, which is preserved for further processing. Parsing is done in place following a no copy paradigm. Packet structure is filled with pointers to specified headers which are represented by structures of headers. After filing developer has easy access to all header fields.

The first variant of parsing: "Parse" packet methods will set appropriate packet structure fields and must be called before any "Get" methods of corresponding levels. "Get" methods check exact protocol (with performance penalty for checking) and return either this protocol header or nil. "Get_NoCheck" methods convert some packet data to required protocol header without checking (no performance penalties), however, they should be used only after condition from normal "Get" methods.

If VLAN tags are present developer should use VLAN methods instead of below methods

Methods for L4 level should be called only after methods for L3 level.

  • ParseL3 - sets L3 pointer. Should be first called method for L3 level
  • GetIPv4 - returns IPv4 header of packet or nil if this packet is not IPv4
  • GetIPv4NoCheck - returns IPv4 header without checking, can return garbage
  • GetIPv6 - returns IPv6 header of packet or nil if this packet is not IPv6
  • GetIPv6NoCheck - returns IPv6 header without checking, not nil, can return garbage
  • ParseL4ForIPv4 - sets L4 pointer. Should be first called method for L4 level of IPv4 packet
  • ParseL4ForIPv6 - sets L4 pointer. Should be first called method for L4 level of IPv6 packet
  • GetTCPForIPv4 - returns TCP header of IPv4 packet or nil if this packet is not TCP
  • GetTCPForIPv6 - returns TCP header of IPv6 packet or nil if this packet is not TCP
  • GetTCPNoCheck - returns TCP header without checking, can return garbage
  • GetUDPForIPv4 - returns UDP header of IPv4 packet or nil if this packet is not UDP
  • GetUDPForIPv6 - returns UDP header of IPv6 packet or nil if this packet is not UDP
  • GetUDPNoCheck - returns UDP header without checking, can return garbage

The second variant of parsing: Use of "ParseAll" functions. These functions return pointers to all known protocol headers, all of them will be "nil" except packet protocol:

ParseAllKnownL3 - returns IPv4, IPv6 and ARP headers. Nil if packet doesn't have protocol

ParseAllKnownL4ForIPv4 - returns TCP, UDP and ICMP headers. Nil if packet doesn't have protocol

ParseAllKnownL4ForIPv6 - returns TCP, UDP and ICMP headers. Nil if packet doesn't have protocol

For higher protocols developer can use the following methods:

ParseL7 - gets L4 protocol ID. Sets Data pointer after end of L4 header. It is supposed to use this method after full parsing was done.

ParseData - sets Data pointer after L4 header. Parses the whole packet from L2. Returns 0 for success and -1 for fail. It is supposed to use this function if whole parsing is required.

GetPacketPayload

Creating

NFF_GO uses "generate" FF for the creation of flow with new packets. The idea is the following: YANFF automatically allocates future packet (or vector of packets) and give them to UDF. UDF should set the appropriate size to each packet and fill it with the required information. So the type of UDF is the same as in "handle" FF: packet pointer and current context. However here input packets are empty and developer needs to fill them. This can be done manually via encapsulate plus parse functions but it is not very efficient. YANFF provides a range of functions for this purpose. Developer can simply copy required bytes inside packet:

Or there are several functions which will prepare a packet for filling. All of them get a length and empty packet and returns success or fail. Preparation means a set up all basic protocol fields and parses appropriate L3, L4 and Data pointers:

  • InitEmptyPacket - prepares packet for filling as Ethernet packet
  • InitEmptyIPv4Packet - prepares packet for filling as IPv4 packet
  • InitEmptyIPv6Packet - prepares packet for filling as IPv6 packet
  • InitEmptyIPv4TCPPacket - prepares packet for filling as IPv4/TCP packet
  • InitEmptyIPv4UDPPacket - prepares packet for filling as IPv4/UDP packet
  • InitEmptyIPv6TCPPacket - prepares packet for filling as IPv6/TCP packet
  • InitEmptyIPv6UDPPacket - prepares packet for filling as IPv6/UDP packet

After these functions developer should fill custom protocol fields like addresses and ports.

Access control lists (ACL)

The developer can compare packets manually after parsing. Besides that YANFF allows usage of automatic comparison like access control lists - ACL. YANFF has an abstraction for rules which can be created via three functions:

  • GetL2ACLFromJSON - gets filename of JSON structured file with L2 rules, returns created L2Rules
  • GetL2ACLFromORIG - gets filename of tuple structured file with L2 rules, returns created L2Rules
  • GetL3ACLFromJSON - gets filename of JSON structured file with L3 and L4 rules, returns created L3Rules
  • GetL3ACLFromORIG - gets filename of tuple structured file with L3 and L4 rules, returns created L3Rules

JSON structured file is a simple JSON, tuple structured file uses the following structure:

  • "#" is used for commenting a whole string

  • Other strings should have four (for L2) or six (for L3/L4) fields corresponding to the source and destination addresses, next protocol ID, source and destination ports (for L3/L4) and output number

  • All fields except output number can use "ANY" for pointing that this condition should not be used

  • Output number field can be positive or empty / "0" / "Reject" - packet is treated as rejected

These construction functions can be used in a separate goroutine for dynamically changing ACLs. Four packet methods can be used after rules construction:

  • L2ACLpermit - gets L2Rules. Returns accept or reject for packet
  • L2ACLport - gets L2Rules. Returns output number for packet (0 for rejected packets)
  • L3ACLpermit - gets L3Rules. Returns accept or reject for packet
  • L3ACLport - gets L3Rules. Returns output number for packet (0 for rejected packets)

Permit functions are expected to be used in "separate" FFs, port functions are expected to be used in "split" FFs. Important note: packets cannot be parsed before using ACL functions. Parsing will be automatic.

Longest prefix match (LPM)

  • CreateLPM - creates LPM table
  • Add - adds rule with given IPv4 address, depth (number of significant bits) and next hop
  • Lookup - returns next hop identifier for given IPv4 address
  • Delete - removes rule with given IPv4 address and depth
  • Free - for better performance LPM tables are stored in C memory, GO garbage collector can't remove them, so LPM entity should be freed after work done.
func init() {
        lpm = packet.CreateLPM("newLPMTable", 0 /*socket*/, 100 /*max number of rules*/, 256*256)
        lpm.Add(types.BytesToIPv4(22, 33, 44, 55), 32, types.BytesToIPv4(66, 77, 88, 99))
}
func route(pkt *packet.Packet, context flow.UserContext) bool {
	pkt.ParseL3()
	ipv4 := pkt.GetIPv4()
        if ipv4 == nil || lpm.Lookup(ipv4.DstAddr, &ipv4.DstAddr) == false {
		return false
	}
	return true
}

Checksum

Software checksum

Checksums can be calculated by software implemented functions or can be offloaded to a network card (hardware offloading).

Set of functions for software checksum calculation include the following functions:

  • CalculateIPv4Checksum - gets pointer to IPv4 header. Returns checksum of IPv4 header.

  • CalculateIPv4TCPChecksum - gets pointers to IPv4 and TCP headers and unsafe.Pointer to packet data. Data pointer should point to end of minimal TCP header because TCP options are considered as part of data. Returns TCP checksum.

  • CalculateIPv6TCPChecksum - gets pointers to IPv6 and TCP headers and unsafe.Pointer to packet data. Data pointer should point to end of minimal TCP header because TCP options are considered as part of data. Returns TCP checksum.

  • CalculateIPv4UDPChecksum - gets pointers to IPv4 and UDP headers and unsafe.Pointer to packet data. Returns UDP checksum.

  • CalculateIPv6UDPChecksum - gets pointers to IPv6 and UDP headers and unsafe.Pointer to packet data. Returns UDP checksum.

Hardware checksum

TODO: checksum flags setting

Hardware checksum offloading requires pre-calculation of pseudo-header checksums. Set of functions for calculation of pseudo-header checksums include the following functions:

  • CalculatePseudoHdrIPv4TCPCksum - gets pointer to IPv4 header. Returns separately computed checksum for TCP pseudo-header for case if L3 protocol is IPv4.

  • CalculatePseudoHdrIPv4UDPCksum - gets pointers to IPv4 and UDP headers. Returns separately computed checksum for UDP pseudo-header for the case if L3 protocol is IPv4.

  • CalculatePseudoHdrIPv6TCPCksum - gets pointer to IPv6 header. Returns separately computed checksum for UDP pseudo-header for case if L3 protocol is IPv6.

  • CalculatePseudoHdrIPv6UDPCksum - gets pointers to IPv6 and UDP headers. Returns separately computed checksum for UDP pseudo-header for a case if L3 protocol is IPv6.

  • SetPseudoHdrChecksum - gets pointer to packet. Makes pre-calculation of pseudo header checksum. Separately computes checksum for required pseudo-header and writes result to correct place.

ARP, ICMP, VLAN, GTP, GRE, MPLS packets

ARP packets

Parsing

ParseL3 should be used before any function.

  • GetARP - returns ARP header of proceeding packet or nil if this packet is not ARP
  • GetARPNoCheck - returns ARP header without checking, can return garbage

Creating

Developer should use following functions for initiating ARP requests and replays. These functions fill empty packet with appropriate information

  • InitARPRequestPacket - gets SHA, SPA, TPA. THA is set to 0xffffffff.
  • InitARPReplyPacket - gets SHA, THA, SPA, TPA
  • InitGARPAnnouncementRequestPacket - gets SHA, SPA. THA is set to 0xffffffff, TPA is set to SPA.
  • InitGARPAnnouncementReplyPacket - gets SHA, SPA. THA is set to 0xffffffff, TPA is set to SPA.

Besides this NFF-GO provides general function, which is however should be used very carefully

InitEmptyARPPacket - prepares packet for filling as ARP packet

ICMP packets

Parsing

ParseL4ForIPv4 or ParseL4ForIPv6 should be used before any function.

  • GetICMPForIPv4 - returns ICMP header of IPv4 packet or nil if this packet is not ICMP
  • GetICMPForIPv6 - returns ICMP header of IPv6 packet or nil if this packet is not ICMP
  • GetICMPNoCheck - returns ICMP header without checking, can return garbage

Checksum

  • CalculateIPv4ICMPChecksum - gets pointers to IPv4 and ICMP headers. Returns ICMP checksum. Before calling this function make sure that ICMP L4 checksum is set to zero, otherwise you can get a wrong calculation.
  • CalculateIPv6ICMPChecksum - gets pointers to IPv6 and ICMP headers. Returns ICMP checksum.

Creating

  • InitEmptyIPv4ICMPPacket - Prepares packet for filling as IPv4/ICMP packet
  • InitEmptyIPv6ICMPPacket - Prepares packet for filling as IPv6/ICMP packet
  • InitICMPv6NeighborSolicitationPacket
  • InitICMPv6NeighborAdvertisementPacket

TODO: Functions for IPv6 ICMP

VLAN tags

NFF-GO provides several packet methods for dealing with VLAN tagged packets

Duplication of main parsing and getting functions:

  • ParseL3CheckVLAN - parses L3 taking VLAN tag into account. Returns VLAN header if tag is present.
  • GetIPv4CheckVLAN - returns IPv4 header of packet taking VLAN tag into account
  • GetIPv6CheckVLAN - returns IPv6 header of packet taking VLAN tag into account
  • GetARPCheckVLAN - returns ARP header of packet taking VLAN tag into account
  • ParseAllKnownL3CheckVLAN - returns all known protocols taking VLAN tag into account
  • ParseDataCheckVLAN GetEtherType - returns EtherType taking VLAN tag into account

Creating

  • AddVLANTag - gets tag and inserts it in packet

Parsing

VLAN header can be taken by ParseL3CheckVLAN method as well as following special methods:

  • GetVLAN - returns VLAN header structure if present
  • GetVLANNoCheck - returns VLAN header structure or something at its place

Changing

  • GetVLANTagIdentifier - returns tag
  • SetVLANTagIdentifier - gets tag and fills VLAN header structure with it

GTP packets

Parsing

  • GetGTP - assumes that packet is already parsed up to to data pointer. Returns GTP header, can return garbage
  • GTPIPv4FastParsing - assumes that nothing was parsed, however packet has ether->IPv4->UDP->GTP->payload data structure with standart IPv4 header size. Returns GTP header, can return garbage
  • GTPIPv4AllParsing - assumes that nothing was parsed, however packet has ether->IPv4->UDP->GTP->payload data structure. Returns GTP header, fills L3, L4 and Data packet fields, can return garbage.

Creating

  • EncapsulateIPv4GTP - encapsulates packet to structure Ethernet->IPv4->UDP->GTP(with given TEID)->payload. Developer should use new parsing after this. (It is assumed that payload is IPv4, no ethernet type changing)

Removing

  • DecapsulateIPv4GTP - decapsulate packet. SHould be used only if packet has structure like Ethernet->IPv4(standard size)->UDP->GTP->Payload. Will leave Ethernet->Payload part. Developer should use new parsing after this. (It is assumed that payload is IPv4, no ethernet type changing)

GRE packets

Parsing

  • GetGREForIPv4 - returns GRE header of packet or nil if this packet is not GRE. L4 pointer should be parsed first.
  • GetGRENoCheck - returns GRE header without checking, can return garbage. L4 pointer should be parsed first.

MPLS packets

Parsing

  • GetMPLS() - returns MPLS header of packet or nil if this packet is not MPLS
  • GetMPLSNoCheck() - returns MPLS header without checking, can return garbage
  • ParseL3CheckMPLS() - set pointer to beginning of L3 header taking MPLS into account

Creating

  • AddMPLS - gets MPLS whole uint32 value and insert it into current packet

Changing

  • GetMPLSLabel - returns Label (20 first bits of MPLS header)
  • SetMPLSLabel - sets Label (20 first bits of MPLS header to specified value)
  • GetMPLSTC - returns the Traffic Class (formerly known as EXP)
  • GetMPLSS - returns the Bottom-Of-Stack value
  • GetMPLSTTL - returns the Time-to-Live value
  • DecreaseTTL - decrease time to live. Returns false if new TTL == 0.

Removing

  • RemoveMPLS - removes MPLS from current packet. Doesn't check for MPLS, doesn't change ethernet type from MPLS.

All packets

Parsing

StartAtOffset - returns pointer to processing packet data start

Packet Length

  • GetPacketLen - returns full length of packet (sums of length for reassembled packets)
  • GetPacketSegmentLen - returns length of current packet segment (full length for non-reassembled packets)

Encapsulation/Decapsulation

  • EncapsulateHead - gets start and length of added segment. Encapsulate by shifting packet head
  • EncapsulateTail - gets start and length of added segment. Encapsulate by shifting packet tail
  • DecapsulateHead - gets start and length of removed segment. Decapsulate by shifting packet head
  • DecapsulateTail - gets start and length of removed segment. Decapsulate by shifting packet tail

Changing

  • GetRawPacketBytes - returns slice with all packet data
  • PacketBytesChange - gets start and slice of new bytes. Writes given bytes into the packet

Creating

GeneratePacketFromByte - gets a slice of bytes of any size and empty packet. Fills packet with these bytes, returns success or failure.

Direct sending

If it is required to create and send packet outside packet processing graph developer can use following functions. This can be used for answering ARP or ICMP requests. It is forbidden to use these functions to add new packets inside graph or send them to ports that are not mentioned in input functions.

NewPacket - returns empty non-initialized packet. This function is not optimized for performance. Packet should be initialized via Init... functions like in generate FFs.

SendPacket - gets DPDK port number as a parameter and sends packet to this port. System should have at least one SetReceiver or SetSender at this port. This function is not performance optimized and packets which are sent through this function are not counted by counters.

Following handle drop user defined function checks each packet to be ARP request and replays it.

func handleARP(pkt *packet.Packet, context flow.UserContext) bool {
	pkt.ParseL3()
	arp := pkt.GetARP()
	if arp != nil && packet.SwapBytesUint16(arp.Operation) == packet.ARPRequest {
		answerPacket, _ := packet.NewPacket()
		packet.InitARPReplyPacket(answerPacket, SrcMACAddress, arp.SHA,
					packet.ArrayToIPv4(arp.TPA), packet.ArrayToIPv4(arp.SPA))
		answerPacket.SendPacket(0)
		return false
	}
	return true
}

Endian and conversions

  • SwapBytesUint16 - swaps uint16 for switching between big/little endian
  • SwapBytesUint32 - swaps uint32 for switching between big/little endian
  • BytesToIPv4 - gets four bytes, returns IPv4Address
  • ArrayToIPv4 - gets array with IPv4AddrLen bytes, returns IPv4Address
  • SliceToIPv4 - gets slice, returns IPv4Address
  • IPv4ToBytes - gets IPv4Address, returns array with IPv4AddrLen bytes
  • IPv4ArrayToString - gets array with IPv4AddrLen bytes, returns string representation
  • StringToMACAddress - parses a string which contains a MAC address

TODO: UnmarshalJSON

Timestamps

  • GetPacketTimestamp - retrieves timestamp field from packet mbuf. This field is set by NIC hardware that support this feature.

PCAP files

Developer can use read and write FFs to start flow from packet trace or dump flow to packet trace. Additionally if it is required to use PCAP files in UDFs directly it is possible to use four methods:

  • WritePcapGlobalHdr - gets output file descriptor. Writes global PCAP header into a file.
  • WritePcapOnePacket - gets output file descriptor. Writes one packet with PCAP header into a file. Should be called after WritePcapGlobalHdr.
  • ReadPcapGlobalHdr - gets input file descriptor and pointer to PcapGlobHdr struct (which is global PCAP header representation according to PCAP format specification). The function reads global PCAP header from a file into given struct.
  • ReadPcapOnePacket - gets input file descriptor. Read one packet with PCAP header from a file. Returns true if the end of file is reached. Should be called after ReadPcapGlobalHdr.

Compatibility with Gopacket package

NFF-GO library can compat with Gopacket library. There are several options:

  1. Explicitly convert YANFF Packet to gopacket.Packet inside user-defined-function.
gopacketPkt := gopacket.NewPacket(currentPacket.GetRawPacketBytes(), layers.LayerTypeEthernet, gopacket.Default)

This approach is not very performant due to a new gopacket.Packet structure is created for each received packet.

  1. Create known headers in handler UDF to avoid extra gopacket.Packet allocation. This can be used to decode known packet structure and works faster.
var eth layers.Ethernet
var ip4 layers.IPv4
var ip6 layers.IPv6
var tcp layers.TCP
var udp layers.UDP
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &ip6, &tcp, &udp)
decoded := []gopacket.LayerType{}
packetData := currentPacket.GetRawPacketBytes()
err := parser.DecodeLayers(packetData, &decoded)

If this snippet used ‘as is’ inside UDF “HandleFunction”, it will run for every packet in flow. Actually temporary headers and parser can be common for all packets processed by handler, so it is recommended to apply the next option:

  1. Pre-allocate known headers and parser once for each handler and then pass it as context to the handler UDF. This avoids redundant memory allocations (headers and parser creation) on each received packet. Refer to the examples/gopacket_parser_example.go for sample application.

Timers

User defined functions in packet processing graph is called for each packet or vector of packets. If it is required to call some function without packets developer can use timer notation. For example it can be used to finish TCP connections by timeout or similar tasks. Timers are controlled by three following functions:

  • AddTimer - gets duration and handler function (handler function should receive context parameter). Returns new timer.
  • AddVariant - adds variant to existing timer (timer must have at least one variant to work). AddVariant gets context which will be passed to handler function from AddTimer. AddVariant returns pointer to bool value which should be set to true every time to prevent invocation of timer (for example for each incoming packet)
  • Stop - removes timer from list.

It is supposed that all TCP connections will have different timer variants. After timer variant is invocated it is automatically dropped. It is supposed that TCP connection was closed.

The following example shows calling handler function every 2 seconds after last packet arrived.

var t *flow.Timer
var check *bool
func main() {
        flow.SystemInit(nil)

        firstFlow, _ := flow.SetReceiver(0)
        flow.SetHandler(firstFlow, handler, nil)
        flow.SetStopper(firstFlow)

        t = flow.AddTimer(2000 /*Milliseconds*/, react)
        flow.SystemStart()
}
func handler(currentPacket *packet.Packet, context flow.UserContext) {
        if check == nil {
                check = t.AddVariant(nil)
        }
        *check = true
}
func react(context flow.UserContext) {
        fmt.Println("2 seconds after last packet was arrived")
        // Answer packet or so
        check = nil
}