Skip to content

Commit

Permalink
xdp: T2666: initial XDP (generic mode) forwarding support
Browse files Browse the repository at this point in the history
The CLI command 'set interfaces ethernet <interface> offload-options xdp" enables
the XDP generic mode on the given interface.

vyos@vyos:~$ show interfaces ethernet eth1
eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 xdpgeneric/id:151 qdisc mq state DOWN group default qlen 1000
    link/ether 00:50:56:bf:ef:aa brd ff:ff:ff:ff:ff:ff
    inet6 fe80::250:56ff:febf:efaa/64 scope link tentative
       valid_lft forever preferred_lft forever
    Description: fooa

XDP code is thankfully copied from [1], thank you for this nice tutorial.

NOTE: this is an experimental feature which might break your
forwarding/filtering.

[1]: https://medium.com/swlh/building-a-xdp-express-data-path-based-peering-router-20db4995da66
  • Loading branch information
c-po committed Dec 17, 2020
1 parent a6b3582 commit bd3ff67
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Makefile
Expand Up @@ -3,6 +3,7 @@ OP_TMPL_DIR := templates-op
BUILD_DIR := build
DATA_DIR := data
SHIM_DIR := src/shim
EBPF_DIR := src/ebpf
CC := gcc
LIBS := -lzmq
CFLAGS :=
Expand Down Expand Up @@ -96,15 +97,20 @@ component_versions: $(BUILD_DIR) $(obj)
vyshim:
$(MAKE) -C $(SHIM_DIR)

.PHONY: vyebpf
vyebpf:
$(MAKE) -C $(EBPF_DIR)

.PHONY: all
all: clean interface_definitions op_mode_definitions component_versions vyshim
all: clean interface_definitions op_mode_definitions component_versions vyshim vyebpf

.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
rm -rf $(TMPL_DIR)
rm -rf $(OP_TMPL_DIR)
$(MAKE) -C $(SHIM_DIR) clean
$(MAKE) -C $(EBPF_DIR) clean

.PHONY: test
test:
Expand Down
4 changes: 4 additions & 0 deletions debian/rules
Expand Up @@ -78,6 +78,10 @@ override_dh_auto_install:
mkdir -p $(DIR)/$(VYOS_DATA_DIR)
cp -r data/* $(DIR)/$(VYOS_DATA_DIR)

# Install eBPF plugins
mkdir -p $(DIR)/$(VYOS_DATA_DIR)/ebpf
cp -r src/ebpf/*.o $(DIR)/$(VYOS_DATA_DIR)/ebpf

# Install etc configuration files
mkdir -p $(DIR)/etc
cp -r src/etc/* $(DIR)/etc
Expand Down
6 changes: 6 additions & 0 deletions interface-definitions/interfaces-ethernet.xml.in
Expand Up @@ -165,6 +165,12 @@
<constraintErrorMessage>Must be either 'on' or 'off'</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="xdp">
<properties>
<help>Enable eXpress Data Path</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
<leafNode name="speed">
Expand Down
21 changes: 21 additions & 0 deletions python/vyos/ifconfig/ethernet.py
Expand Up @@ -251,6 +251,23 @@ def set_ufo(self, state):
"""
return self.set_interface('ufo', state)

def set_xdp(self, enabled):
"""
"""
ifname = self.config['ifname']
cmd = f'ip link set dev {ifname} xdp off'
if enabled:
# use 'xdpgeneric' for the time beeing until we can detect supported
# drivers or have a lookup table of whatever kind. This then can be
# replaced by xdpdrv
cmd = f'ip -force link set dev {ifname} xdpgeneric obj /usr/share/vyos/ebpf/xdp_router.o'
try:
return self._cmd(cmd)
except:
from vyos import ConfigError
raise ConfigError('Error: Device does not allow enslaving to a bridge.')


def set_ring_buffer(self, b_type, b_size):
"""
Example:
Expand Down Expand Up @@ -306,6 +323,10 @@ def update(self, config):
value = tmp if (tmp != None) else 'off'
self.set_ufo(value)

# UDP fragmentation offloading
tmp = dict_search('offload_options.xdp', config)
self.set_xdp(tmp != None) # enable or disable

# Set physical interface speed and duplex
if {'speed', 'duplex'} <= set(config):
speed = config.get('speed')
Expand Down
1 change: 1 addition & 0 deletions src/ebpf/.gitignore
@@ -0,0 +1 @@
*.o
16 changes: 16 additions & 0 deletions src/ebpf/Makefile
@@ -0,0 +1,16 @@
#clang -target bpf -O2 -c xdp-drop-ebpf.c -o xdp-drop-ebpf.o

src = $(wildcard *.c)
obj = $(src:.c=.o)
CLANG = clang
CFLAGS = -Wall -Wno-unused-value -Wno-pointer-sign -Wno-compare-distinct-pointer-types -Werror -O2

%.o: %.c
$(CLANG) -target bpf $(CFLAGS) -o $@ -c $<

.PHONY: all
all: $(obj)

.PHONY: clean
clean:
rm -f *.o
97 changes: 97 additions & 0 deletions src/ebpf/xdp_drop_ebpf.c
@@ -0,0 +1,97 @@
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/udp.h>

#include <stdint.h>

/* IP flags. */
#define IP_CE 0x8000 /* Flag: "Congestion" */
#define IP_DF 0x4000 /* Flag: "Don't Fragment" */
#define IP_MF 0x2000 /* Flag: "More Fragments" */
#define IP_OFFSET 0x1FFF /* "Fragment Offset" part */

#define SEC(NAME) __attribute__((section(NAME), used))

#define htons(x) ((__be16)___constant_swab16((x)))
#define htonl(x) ((__be32)___constant_swab32((x)))

struct vlan_hdr {
__be16 h_vlan_TCI;
__be16 h_vlan_encapsulated_proto;
};

SEC("prog")
int xdp_drop(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;

uint64_t nh_off = sizeof(*eth);
if (data + nh_off > data_end) {
return XDP_PASS;
}

uint16_t h_proto = eth->h_proto;
int i;

/* Handle double VLAN tagged packet. See https://en.wikipedia.org/wiki/IEEE_802.1ad */
for (i = 0; i < 2; i++) {
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;

vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end) {
return XDP_PASS;
}
h_proto = vhdr->h_vlan_encapsulated_proto;
}
}

if (h_proto == htons(ETH_P_IP)) {
struct iphdr *iph = data + nh_off;
struct udphdr *udph = data + nh_off + sizeof(struct iphdr);

uint32_t hostid = iph->daddr >> 24;

if (udph + 1 > (struct udphdr *)data_end) {
return XDP_PASS;
}
if (hostid == 0 || hostid == 255) {
return XDP_DROP;
}
if (iph->frag_off & htons(IP_MF | IP_OFFSET)) {
return XDP_DROP;
}
if (iph->protocol == IPPROTO_UDP) {
__be16 dport = htons(udph->dest);
__be16 sport = htons(udph->source);

if (dport == 53 || sport == 53) {
return XDP_DROP;
}
}
} else if (h_proto == htons(ETH_P_IPV6)) {
struct ipv6hdr *ip6h = data + nh_off;
struct udphdr *udph = data + nh_off + sizeof(struct ipv6hdr);

if (udph + 1 > (struct udphdr *)data_end) {
return XDP_PASS;
}
if (ip6h->nexthdr == IPPROTO_UDP) {
__be16 dport = htons(udph->dest);
__be16 sport = htons(udph->source);

if (dport == 53 || sport == 53) {

This comment has been minimized.

Copy link
@jack9603301

jack9603301 Dec 18, 2020

Contributor

Why discard port 53

return XDP_DROP;
}
}
}

return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

0 comments on commit bd3ff67

Please sign in to comment.