diff --git a/dhcp-relay/Makefile b/dhcp-relay/Makefile index 964f707..6a3f5f6 100644 --- a/dhcp-relay/Makefile +++ b/dhcp-relay/Makefile @@ -4,6 +4,7 @@ USER_TARGETS := dhcp_user_xdp BPF_TARGETS :=dhcp_kern_xdp EXTRA_DEPS := dhcp-relay.h #EXTRA_CFLAGS := $(if $(IPV6),-DIPV6) +EXTRA_CFLAGS := -fno-builtin LIB_DIR = ../lib diff --git a/dhcp-relay/cleanup_test.sh b/dhcp-relay/cleanup_test.sh new file mode 100755 index 0000000..d7ae11e --- /dev/null +++ b/dhcp-relay/cleanup_test.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# -x + +OUTER_VLAN=83 +INNER_VLAN=20 +UPLINK_VLAN=84 +IF="ens6f0" + +DHCP_SERVER="185.107.12.59" +IPADDR_BNG="194.45.77.57" +IPADDR_UPLINK="185.107.12.99" +CLIENT_IP="194.45.77.59" + +echo "Unloading XDP program" +./dhcp_user_xdp -i $IF -d $DHCP_SERVER -s $IPADDR_BNG -u + +echo "Deleting VLAN interfaces" +# Delete inner VLAN interface +ip link del $IF.$OUTER_VLAN.$INNER_VLAN +# Delete outer VLAN interface +ip link del $IF.$OUTER_VLAN + +echo "Deleting uplink interface" +ip link del $IF.$UPLINK_VLAN + +# Remove BNG address from loopback interface +ip addr del $IPADDR_BNG/29 dev lo \ No newline at end of file diff --git a/dhcp-relay/dhcp-relay.h b/dhcp-relay/dhcp-relay.h index 09bca44..cbda2f0 100644 --- a/dhcp-relay/dhcp-relay.h +++ b/dhcp-relay/dhcp-relay.h @@ -10,7 +10,7 @@ #define DHO_DHCP_AGENT_OPTIONS 82 #define RAI_CIRCUIT_ID 1 #define RAI_REMOTE_ID 2 -#define RAI_OPTION_LEN 40 +#define RAI_OPTION_LEN IF_NAMESIZE #define VLAN_ASCII_MAX 4 /* Max bytes needed to store VLAN in ASCII format */ #define DHCP_SERVER_PORT 67 @@ -18,11 +18,16 @@ #define DHCP_REQUEST 1 #define DHCP_REPLY 2 +#define MAX_LOOPS 20 +#define U16_ASCII_LEN 5 /* Max value: 65535 */ + +#define IP_ADDR_BCAST 0xFFFFFFFF /* 255.255.255.255 in hex */ + /* Structure for sub-options in option 82 */ struct sub_option { __u8 option_id; __u8 len; - char val[IF_NAMESIZE]; + char val[RAI_OPTION_LEN]; }; /*structure for dhcp option 82 */ @@ -38,6 +43,10 @@ struct dhcp_option_255 { __u8 t; }; +struct dev_name { + char name[IF_NAMESIZE]; +}; + struct dhcp_packet { __u8 op; /* 0: Message opcode/type */ __u8 htype; /* 1: Hardware addr type (net/if_types.h) */ diff --git a/dhcp-relay/dhcp_kern_xdp.c b/dhcp-relay/dhcp_kern_xdp.c index 56cd040..6b45d36 100644 --- a/dhcp-relay/dhcp_kern_xdp.c +++ b/dhcp-relay/dhcp_kern_xdp.c @@ -1,13 +1,20 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include /* IF_NAMESIZE */ #include #include -#include /* IF_NAMESIZE */ #include #include #include #include "dhcp-relay.h" +#define bpf_printk(fmt, ...) \ +({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), \ + ##__VA_ARGS__); \ +}) + /* * This map is for storing the DHCP relay configuration, including: * @@ -49,78 +56,93 @@ struct { __uint(max_entries, 16384); } client_vlans SEC(".maps"); -/* Inserts DHCP option 82 into the received DHCP packet - * at the specified offset. - */ -static __always_inline int write_dhcp_option_82(void *ctx, int offset, - struct collect_vlans *vlans, char *dev) { - struct dhcp_option_82 option; - - option.t = DHO_DHCP_AGENT_OPTIONS; - option.len = sizeof (struct sub_option) + sizeof (struct sub_option); - option.circuit_id.option_id = RAI_CIRCUIT_ID; - option.circuit_id.len = sizeof(option.circuit_id.val); - - /* Reconstruct VLAN device name - * Convert VLAN tags to ASCII from right to left, starting with - * inner VLAN tag. - * Device name is 16 characters long and prepended with dash, e.g.: - * ----ens6f0.83.20 - * We avoid null bytes to ensure compatibility with DHCP servers that - * interpret null as a string terminator. - */ - - char buf[IF_NAMESIZE]; - memset(buf, '-', sizeof (buf)); - - int c = VLAN_ASCII_MAX; /* We will need 4 bytes at most */ - int i = IF_NAMESIZE - 1; - __u16 inner_vlan = vlans->id[1]; - __u16 outer_vlan = vlans->id[0]; - - for (c = VLAN_ASCII_MAX; c > 0; c--) { - buf[i--] = (inner_vlan % 10) + '0'; - inner_vlan /= 10; - if (inner_vlan == 0) { - break; - } - } - - buf[i--] = '.'; - - for (c = VLAN_ASCII_MAX; c > 0; c--) { - buf[i--] = (outer_vlan % 10) + '0'; - outer_vlan /= 10; - if (outer_vlan == 0) { +static int u16_to_ascii(struct dev_name *buf, __u8 end_offset, __u16 num) { + int i; + + if (buf == NULL || + end_offset > sizeof(buf->name) || + end_offset < U16_ASCII_LEN) + return -1; + + for (i = end_offset - 1; i >= 0; i--) { + buf->name[i] = (__u8) (num % 10) + '0'; + num /= 10; + if (!num) break; - } } - - buf[i--] = '.'; - - for (c = IF_NAMESIZE - 1; c >= 0; c--) { + return i; +} - if (dev[c] != 0) { - buf[i--] = dev[c]; - } - - if (i < 0) { +static int copy_dev_name(struct sub_option *dest, __u8 wr_offset, + struct dev_name *dev, __u8 rd_offset) { + int i; + + if (dest == NULL || dev == NULL || wr_offset > sizeof(dest->val) - 1) + return -1; + + for (i = 0; i < sizeof(dest->val) - wr_offset - 1; i++) { + if (i + rd_offset > sizeof(dev->name) - 1 || + !dev->name[i + rd_offset]) break; - } + dest->val[i + wr_offset] = dev->name[i + rd_offset]; } - if(sizeof(option.circuit_id.val) == sizeof(buf)) { - memcpy(option.circuit_id.val, buf, sizeof(buf)); - } - - /* Initialize remote ID */ - memset(option.remote_id.val, 0, sizeof(option.remote_id.val)); - option.remote_id.option_id = RAI_REMOTE_ID; - option.remote_id.len = sizeof(option.remote_id.val); - - return xdp_store_bytes(ctx, offset, &option, sizeof (option), 0); + return i; +} + +/* Inserts DHCP option 82 into the received DHCP packet + * at the specified offset. + */ +int write_dhcp_option_82(struct xdp_md *ctx, int pkt_offset, + struct collect_vlans *vlans, struct dev_name *dev) { + + __u16 inner_vlan, outer_vlan; + struct dev_name buf = {}; + int len, vlan_offset; + struct dhcp_option_82 option = { + .t = DHO_DHCP_AGENT_OPTIONS, + .len = sizeof (struct sub_option) + sizeof (struct sub_option), + .circuit_id.option_id = RAI_CIRCUIT_ID, + .circuit_id.len = sizeof (option.circuit_id.val), + .remote_id.option_id = RAI_REMOTE_ID, + .remote_id.len = sizeof (option.remote_id.val) + }; + + if (!ctx || !vlans || !dev) + return -1; + + inner_vlan = vlans->id[1]; + outer_vlan = vlans->id[0]; + + if (!inner_vlan || !outer_vlan) + return -1; + + /* The u16_to_ascii function works backwards from the end of the buffer, + * so start out at the end of the string. + */ + vlan_offset = sizeof(buf.name); + + vlan_offset = u16_to_ascii(&buf, vlan_offset, inner_vlan); + if (vlan_offset < 0) + return vlan_offset; + buf.name[--vlan_offset] = '.'; + + vlan_offset = u16_to_ascii(&buf, vlan_offset, outer_vlan); + if (vlan_offset < 0) + return vlan_offset; + buf.name[--vlan_offset] = '.'; + + len = copy_dev_name(&option.circuit_id, 0, dev, 0); + if (len < 0) + return len; + + len = copy_dev_name(&option.circuit_id, len, &buf, vlan_offset); + if (len < 0) + return len; + + return xdp_store_bytes(ctx, pkt_offset, &option, sizeof(option), 0); } /* Inserts DHCP option 255 into the received dhcp packet @@ -136,10 +158,10 @@ static __always_inline int write_dhcp_option_255(void *ctx, int offset) { /* Calculates the IP checksum */ static __always_inline int calc_ip_csum(struct iphdr *oldip, struct iphdr *ip, - __u32 oldcsum) { + __u32 oldcsum) { __u32 size = sizeof (struct iphdr); __u32 csum = bpf_csum_diff((__be32 *) oldip, size, (__be32 *) ip, size, - ~oldcsum); + ~oldcsum); __u32 sum = (csum >> 16) + (csum & 0xffff); sum += (sum >> 16); return sum; @@ -154,7 +176,7 @@ static __always_inline int calc_ip_csum(struct iphdr *oldip, struct iphdr *ip, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + \ offsetof(struct dhcp_packet, options) -/* Delta value to be adjusted at xdp head*/ +/* Delta value for tail adjustment */ #define delta sizeof(struct dhcp_option_82) #ifndef DHCP_MAX_OPTIONS @@ -166,27 +188,18 @@ static __always_inline int calc_ip_csum(struct iphdr *oldip, struct iphdr *ip, */ //static __u8 buf[static_offset + VLAN_MAX_DEPTH * sizeof (struct vlan_hdr)]; -#define bpf_printk(fmt, ...) \ -({ \ - char ____fmt[] = fmt; \ - bpf_trace_printk(____fmt, sizeof(____fmt), \ - ##__VA_ARGS__); \ -}) - /* XDP program for parsing the DHCP packet and inserting the option 82*/ SEC(XDP_PROG_SEC) int xdp_dhcp_relay(struct xdp_md *ctx) { - bpf_printk("\n"); - /* Tail extend packet */ int res = bpf_xdp_adjust_tail(ctx, delta); if (res != 0) { bpf_printk("Cannot tail extend packet, delta %i - error code %i", delta, res); - return XDP_ABORTED; + return XDP_PASS; } - bpf_printk("Tail extended packet by %i bytes", delta); + //bpf_printk("Tail extended packet by %i bytes", delta); void *data_end = (void *) (long) ctx->data_end; void *data = (void *) (long) ctx->data; @@ -196,6 +209,7 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { struct iphdr oldip; struct udphdr *udp; struct dhcp_packet *dhcp; + struct dev_name dev = {0}; __u32 *dhcp_srv_ip; __u32 *relay_agent_ip; __u64 *relay_hwaddr; @@ -207,8 +221,9 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { __u8 option_code = 0; __u8 option_length = 0; __u64 client_mac = 0; - char *dev; - int i = 0; + char *dev_config; + __u8 i = 0; + __u8 head_adjusted = 0; /* These keep track of the next header type and iterator pointer */ struct hdr_cursor nh; @@ -217,34 +232,38 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { int key = 0; int len = 0; - if (data + 1 > data_end) - return XDP_ABORTED; + if (data + 1 > data_end) { + goto out; + } nh.pos = data; ether_type = parse_ethhdr_vlan(&nh, data_end, ð, &vlans); - /* check for valid ether type */ + /* Check for valid EtherType */ if (ether_type < 0) { - bpf_printk("Cannot determine ethertype"); - rc = XDP_ABORTED; + bpf_printk("Cannot determine EtherType"); goto out; } + if (ether_type != bpf_htons(ETH_P_IP)) { - bpf_printk("Ethertype %#x is not ETH_P_IP", bpf_ntohs(ether_type)); + //bpf_printk("Ethertype %x is not ETH_P_IP", bpf_ntohs(ether_type)); goto out; } - bpf_printk("Ethertype %x", bpf_ntohs(ether_type)); + /* Check at least one vlan tag is present */ + if (vlans.id[0] == 0) { + bpf_printk("No outer VLAN tag set"); + goto out; + } - /* Check at least two vlan tags are present */ if (vlans.id[1] == 0) { - bpf_printk("No VLAN tags set"); - goto out; + bpf_printk("No inner VLAN tag set"); } h_proto = parse_iphdr(&nh, data_end, &ip); /* Only handle fixed-size IP header due to static copy */ if (h_proto != IPPROTO_UDP || ip->ihl > 5) { + bpf_printk("Not UDP"); goto out; } @@ -256,8 +275,10 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { goto out; /* Handle DHCP packets only */ - if (udp->dest != bpf_htons(DHCP_SERVER_PORT) && udp->dest != bpf_htons(DHCP_CLIENT_PORT)) + if (udp->dest != bpf_htons(DHCP_SERVER_PORT) && udp->dest != bpf_htons(DHCP_CLIENT_PORT)) { + bpf_printk("Not DHCP"); goto out; + } /* Increase IP length header */ ip->tot_len += bpf_htons(delta); @@ -265,6 +286,8 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { /* Increase UDP length header */ udp->len += bpf_htons(delta); + udp->check = 0; + /* Read DHCP server IP from config map */ key = 0; dhcp_srv_ip = bpf_map_lookup_elem(&relay_config, &key); @@ -285,23 +308,19 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { /* Read device name from device map */ key = 0; - dev = bpf_map_lookup_elem(&device_name, &key); - if (dev == NULL) + dev_config = bpf_map_lookup_elem(&device_name, &key); + if (dev_config == NULL) goto out; - /* Copy headers of packet to buf */ - //if (xdp_load_bytes(ctx, 0, buf, static_offset)) - // goto out; + memcpy(dev.name, dev_config, RAI_OPTION_LEN); /* Increment offset by 4 bytes for each VLAN (to accomodate VLAN headers */ +#pragma unroll VLAN_MAX_DEPTH for (i = 0; i < VLAN_MAX_DEPTH; i++) { if (vlans.id[i]) { bpf_printk("Found VLAN tag %i at depth %i", vlans.id[i], i); - /* For each VLAN present, copy 4 bytes of DHCP options to buffer */ - //if (xdp_load_bytes(ctx, offset, buf + offset, 4)) - // goto out; offset += 4; vlan_length += 4; } @@ -317,6 +336,16 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { } dhcp = data + vlan_length + dhcp_offset; + /* Check hops */ + if (dhcp->hops > 16) { + bpf_printk("Max hops exceeded, discarding packet"); + rc = XDP_ABORTED; + goto out; + } + + /* Increment hops */ + dhcp->hops++; + /* Store client MAC */ if (dhcp->chaddr + ETH_ALEN > data_end) { goto out; @@ -326,11 +355,11 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { bpf_printk("Parsing DHCP packet, opcode %i, hops %i", dhcp->op, dhcp->hops); if (dhcp->op == DHCP_REQUEST && (eth->h_dest[0] == 0xff - && eth->h_dest[1] == 0xff - && eth->h_dest[2] == 0xff - && eth->h_dest[3] == 0xff - && eth->h_dest[4] == 0xff - && eth->h_dest[5] == 0xff)) { + && eth->h_dest[1] == 0xff + && eth->h_dest[2] == 0xff + && eth->h_dest[3] == 0xff + && eth->h_dest[4] == 0xff + && eth->h_dest[5] == 0xff)) { /* Request from client received as broadcast */ @@ -357,25 +386,16 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { } else if (dhcp->op == DHCP_REPLY && (eth->h_dest[0] != 0xff - || eth->h_dest[1] != 0xff - || eth->h_dest[2] != 0xff - || eth->h_dest[3] != 0xff - || eth->h_dest[4] != 0xff - || eth->h_dest[5] != 0xff)) { + || eth->h_dest[1] != 0xff + || eth->h_dest[2] != 0xff + || eth->h_dest[3] != 0xff + || eth->h_dest[4] != 0xff + || eth->h_dest[5] != 0xff)) { /* Response from server received as unicast */ bpf_printk("Unicast packet received, opcode %i, hops %i", dhcp->op, dhcp->hops); - /* FIXME: Add code for reply packets - * Basically: - * - Set dest and src MAC - * - Add VLAN tags - * - Remove option 82 - * - Use XDP_TX (or XDP_REDIRECT) to send the response - * to the end user - */ - struct collect_vlans *new_vlans; new_vlans = bpf_map_lookup_elem(&client_vlans, &client_mac); if (new_vlans == NULL) { @@ -383,19 +403,89 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { goto out; } - bpf_printk("Found map entry for MAC %i", client_mac); + bpf_printk("Found map entry for MAC %x", client_mac); - } + /* Set destination MAC */ + memcpy(eth->h_dest, dhcp->chaddr, ETH_ALEN); - /* Check hops */ - if (dhcp->hops > 16) { - bpf_printk("Max hops exceeded, discarding packet"); - rc = XDP_ABORTED; - goto out; - } + /* Set source MAC */ + memcpy(eth->h_source, relay_hwaddr, ETH_ALEN); - /* Increment hops */ - dhcp->hops++; + /* Set destination IP */ + ip->daddr = IP_ADDR_BCAST; + + /* Set source IP */ + ip->saddr = *relay_agent_ip; + + /* Add / replace VLAN tags */ + if (vlans.id[0] != 0) { + bpf_printk("Outer VLAN %i found, will change to %i", vlans.id[0], new_vlans->id[0]); + + struct vlan_hdr *outer_vlh = data + ETH_HLEN; + outer_vlh->h_vlan_TCI = bpf_htons((bpf_ntohs(outer_vlh->h_vlan_TCI) & 0xf000) | new_vlans->id[0]); + + } + + if (vlans.id[1] != 0) { + bpf_printk("Inner VLAN %i found, will change to %i", vlans.id[1], new_vlans->id[1]); + + struct vlan_hdr *inner_vlh = data + ETH_HLEN + sizeof (struct vlan_hdr); + inner_vlh->h_vlan_TCI = bpf_htons((bpf_ntohs(inner_vlh->h_vlan_TCI) & 0xf000) | new_vlans->id[1]); + + } else { + bpf_printk("Inner VLAN not found, will insert %i", new_vlans->id[1]); + + /* Adjust header by -4 bytes to make space for VLAN header */ + if (bpf_xdp_adjust_head(ctx, -(int) sizeof (struct vlan_hdr))) { + bpf_printk("Cannot head-adjust packet by %i bytes, aborting", -(int) sizeof (struct vlan_hdr)); + rc = XDP_ABORTED; + goto out; + } + + bpf_printk("Head-adjusted packet by %i bytes", -(int) sizeof (struct vlan_hdr)); + + head_adjusted = 1; + + data_end = (void *) (long) ctx->data_end; + data = (void *) (long) ctx->data; + + /* Verifier check */ + if (data + ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr) + sizeof (struct vlan_hdr) > data_end) { + rc = XDP_ABORTED; + goto out; + } + + /* Move MAC address headers + outer VLAN tag to beginning of packet */ + memmove(data, data + sizeof (struct vlan_hdr), ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr)); + + bpf_printk("Moved %i bytes from offset %i to offset %i", ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr), sizeof (struct vlan_hdr), 0); + + /* Make new inner VLAN header (copy from outer VLAN header) */ + memcpy(data + ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr), data + ETH_ALEN + ETH_ALEN, sizeof (struct vlan_hdr)); + + bpf_printk("Copied %i bytes from offset %i to offset %i", sizeof (struct vlan_hdr), ETH_ALEN + ETH_ALEN, ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr)); + + bpf_printk("Will modify VLAN header at offset %i", ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr)); + + struct vlan_hdr *vlh = data + ETH_ALEN + ETH_ALEN + sizeof (struct vlan_hdr) + 2; + vlh->h_vlan_TCI = bpf_htons((bpf_ntohs(vlh->h_vlan_TCI) & 0xf000) | new_vlans->id[1]); + + offset += sizeof (struct vlan_hdr); + vlan_length += sizeof (struct vlan_hdr); + + /* Parse DHCP packet */ + if (data + vlan_length + dhcp_offset + sizeof (dhcp) > data_end) { + goto out; + } + dhcp = data + vlan_length + dhcp_offset; + + bpf_printk("Inserted VLAN header"); + + } + + rc = XDP_TX; + + } /* Check if we exceed boundaries to make verifier happy */ if (data + offset > data_end) @@ -403,28 +493,60 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { option_offset = offset; + __u8 n = 0; + __u8 *pos = (__u8 *) (data + option_offset); /* Loop through all DHCP options */ -#pragma unroll DHCP_MAX_OPTIONS + //#pragma unroll DHCP_MAX_OPTIONS for (i = 0; i < DHCP_MAX_OPTIONS; i++) { /* Verifier check */ if (pos + 1 > data_end) break; + /* Read option code */ option_code = *pos; bpf_printk("Got option code %i at offset %i, hex %x", option_code, option_offset, option_offset); + if (option_code == 82 && dhcp->op == DHCP_REPLY) { + + bpf_printk("Will erase DHCP option 82"); + + /* Set new option 255 */ + *pos = 255; + + /* Increment pointer 2nd byte of option 82 */ + pos++; + + /* Verifier check */ + if (pos + 1 > data_end) + break; + + /* Erase remainder of option 82 */ + for (n = 0; n < sizeof (struct dhcp_option_82); n++) { + + if (pos + 1 > data_end) + break; + + *pos++ = 0; + + } + + break; + + } + if (option_code == 255) { - bpf_printk("Going to write DHCP option at offset %i", option_offset); + bpf_printk("Going to write DHCP option 82 at offset %i", option_offset); /* Insert Option 82 before END option */ - if (write_dhcp_option_82(ctx, option_offset, &vlans, dev)) { + if (write_dhcp_option_82(ctx, option_offset, &vlans, &dev)) { bpf_printk("Could not write DHCP option 82 at offset %i", option_offset); - return XDP_ABORTED; + //return XDP_ABORTED; + break; } /* Set END option */ @@ -438,57 +560,50 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { if (write_dhcp_option_255(ctx, option_offset)) { bpf_printk("Could not write DHCP option 255 at offset %i", option_offset); - return XDP_ABORTED; + //return XDP_ABORTED; + break; } bpf_printk("Wrote DHCP option 255 at offset %i, returning XDP_PASS", option_offset); break; } + pos++; + /* Verifier check */ + if (pos + 1 > data_end) { + break; + } + option_length = *pos; option_offset += option_length + 2; + /* Verifier check */ if (pos + 1 > data_end) { break; } pos++; + /* Verifier check */ if (pos + option_length > data_end) { break; } + + /* Skip option value (go to next option) */ pos += option_length; } - //return XDP_PASS; - - /* Copy stored headers from buf to context */ - /*if (xdp_store_bytes(ctx, 0, buf, static_offset, 0)) { - - bpf_printk("xdp_store_bytes(ctx, 0, buf, %i) failed", static_offset); - return XDP_ABORTED; - }*/ - - - /* make space for option 82 - copy DHCP options after increasing offset */ - /*if (offset > static_offset) { - offset = static_offset; - for (i = 0; i < VLAN_MAX_DEPTH; i++) { - if (vlans.id[i]) {*/ - /* */ - /*if (xdp_store_bytes(ctx, offset, buf + offset, - 4, 0)) - return XDP_ABORTED; - offset += 4; -} + /* Adjust IP offset for VLAN header when VLAN header has been added */ + if (head_adjusted) { + ip = data + ip_offset + sizeof (struct vlan_hdr); + } else { + ip = data + ip_offset; } - }*/ - - ip = data + ip_offset; - if (ip + 1 > data_end) + if (ip + 1 > data_end) { return XDP_ABORTED; + } /* Overwrite the destination IP in IP header */ ip->daddr = *dhcp_srv_ip; @@ -499,7 +614,6 @@ int xdp_dhcp_relay(struct xdp_md *ctx) { /* Re-calculate ip checksum */ __u32 sum = calc_ip_csum(&oldip, ip, oldip.check); ip->check = ~sum; - rc = XDP_PASS; goto out; diff --git a/dhcp-relay/dhcp_user_xdp.c b/dhcp-relay/dhcp_user_xdp.c index 2aeb56e..3ba4dc7 100644 --- a/dhcp-relay/dhcp_user_xdp.c +++ b/dhcp-relay/dhcp_user_xdp.c @@ -28,6 +28,7 @@ static const struct option options[] = { { "relay-agent-address", required_argument, NULL, 's'}, { "mode", required_argument, NULL, 'm'}, { "unload", no_argument, NULL, 'u'}, + { "verbose", no_argument, NULL, 'v'}, { 0, 0, NULL, 0} }; @@ -89,62 +90,14 @@ int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd) { return 0; } -/* User program takes two or three arguments - * interface name, relay server IP and prog - * unload flag - */ -int main(int argc, char **argv) { - - /*char device[500] = "ens6f0np0"; - char o82[30] = { 0 }; - - int outer_vlan = 80; - int inner_vlan = 25; +static int libbpf_print_func(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} - char str[30] = {0}; // large enough for an int even on 64-bit - int i = 30; - int c = 0; - for(c = 4; c > 0; c--) { - str[i--] = (inner_vlan % 10) + '0'; - inner_vlan /= 10; - if(inner_vlan == 0) { - break; - } - } - - str[i--] = '.'; - - for(c = 4; c > 0; c--) { - str[i--] = (outer_vlan % 10) + '0'; - outer_vlan /= 10; - if(outer_vlan == 0) { - break; - } - } - - str[i--] = '.'; - - int y; - for(y = sizeof(device) - 1; y >= 0; y--) { - if(device[y] != 0) { - str[i] = device[y]; - i--; - } - } - - printf("i is %i\n", i); - - memset(o82, 0, 30); - memcpy(o82, str + i + 1, 30 - i); - - printf("The number was: %s\n", str + i + 1); - - printf("Option 82: %s\n", o82); - - printf("Option 82 length was %i\n", 30 - i); - - return 0;*/ +int main(int argc, char **argv) { char filename[256] = "dhcp_kern_xdp.o"; int prog_fd, err; @@ -168,54 +121,58 @@ int main(int argc, char **argv) { unsigned char *mac; struct ifreq ifr; - while ((opt = getopt_long(argc, argv, "hui:d:m:s:", options, NULL)) != - -1) { + while ((opt = getopt_long(argc, argv, "huvi:d:m:s:", options, NULL)) != -1) { switch (opt) { - case 'i': - strncpy(dev, optarg, IF_NAMESIZE); - dev[IF_NAMESIZE - 1] = '\0'; - ifindex = if_nametoindex(dev); - if (ifindex <= 0) { - printf("Couldn't find ifname:%s \n", dev); - return -EINVAL; - } - break; - case 'd': // DHCP server address - if (inet_aton(optarg, &dhcp_server_addr) == 0) { - fprintf(stderr, - "Couldn't validate DHCP server IP address:%s\n", - optarg); - return -EINVAL; - } - dhcp_server_addr_set = true; - break; - case 's': // Relay agent address - if (inet_aton(optarg, &relay_agent_addr) == 0) { - fprintf(stderr, - "Couldn't validate relay agent IP address:%s\n", - optarg); - return -EINVAL; - } - relay_agent_addr_set = true; - break; - case 'm': - if (strcmp(optarg, "skb") == 0) { - xdp_flags = XDP_FLAGS_SKB_MODE; - } else if (strcmp(optarg, "drv") != 0) { - fprintf(stderr, "Invalid mode: %s\n", optarg); - return -EINVAL; - } - - break; - case 'u': - do_unload = 1; - break; - case 'h': - print_usage(argv); - exit(0); - default: - fprintf(stderr, "Unknown option %s\n", argv[optind]); + case 'i': /* Physical interface */ + strncpy(dev, optarg, IF_NAMESIZE); + dev[IF_NAMESIZE - 1] = '\0'; + ifindex = if_nametoindex(dev); + if (ifindex <= 0) { + printf("Couldn't find ifname:%s \n", dev); + return -EINVAL; + } + break; + case 'd': /* DHCP server address */ + if (inet_aton(optarg, &dhcp_server_addr) == 0) { + fprintf(stderr, + "Couldn't validate DHCP server IP address:%s\n", + optarg); + return -EINVAL; + } + dhcp_server_addr_set = true; + break; + case 's': /* Relay agent address */ + if (inet_aton(optarg, &relay_agent_addr) == 0) { + fprintf(stderr, + "Couldn't validate relay agent IP address:%s\n", + optarg); return -EINVAL; + } + relay_agent_addr_set = true; + break; + case 'm': /* Mode: skb or native */ + if (strcmp(optarg, "skb") == 0) { + xdp_flags = XDP_FLAGS_SKB_MODE; + } else if (strcmp(optarg, "drv") != 0) { + fprintf(stderr, "Invalid mode: %s\n", optarg); + return -EINVAL; + } + + break; + case 'u': /* Unload XDP program */ + do_unload = 1; + break; + + case 'v': /* Verbose libbpf logging */ + libbpf_set_print(libbpf_print_func); + break; + + case 'h': /* Help menu */ + print_usage(argv); + exit(0); + default: + fprintf(stderr, "Unknown option %s\n", argv[optind]); + return -EINVAL; } } @@ -235,7 +192,7 @@ int main(int argc, char **argv) { if (do_unload) return xdp_link_detach(ifindex, xdp_flags); - // Find MAC address of interface + /* Find MAC address of interface */ fd = socket(AF_INET, SOCK_DGRAM, 0); ifr.ifr_addr.sa_family = AF_INET; @@ -250,7 +207,7 @@ int main(int argc, char **argv) { __u64 hwaddr = 0; memcpy(&hwaddr, (unsigned char *) ifr.ifr_hwaddr.sa_data, 6); - //display mac address + /* Display MAC address */ printf("Using device %s MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", dev, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); /* Load the BPF-ELF object file and get back first BPF_prog FD */ @@ -265,9 +222,7 @@ int main(int argc, char **argv) { return -1; } - /* read the map from prog object file and update the real - * server IP to the map - */ + /* Open server config map */ map = bpf_object__find_map_by_name(obj, SERVER_MAP); err = libbpf_get_error(map); if (err) { @@ -282,7 +237,7 @@ int main(int argc, char **argv) { exit(-1); } - // Set DHCP server address + /* Set DHCP server address */ key = 0; err = bpf_map_update_elem(map_fd, &key, &dhcp_server_addr.s_addr, BPF_ANY); if (err) { @@ -291,7 +246,7 @@ int main(int argc, char **argv) { exit(-1); } - // Set relay agent IP address + /* Set relay agent IP address */ key = 1; err = bpf_map_update_elem(map_fd, &key, &relay_agent_addr.s_addr, BPF_ANY); if (err) { @@ -300,7 +255,7 @@ int main(int argc, char **argv) { exit(-1); } - // Set relay agent MAC address + /* Set relay agent MAC address */ key = 2; err = bpf_map_update_elem(map_fd, &key, &hwaddr, BPF_ANY); if (err) { @@ -310,9 +265,7 @@ int main(int argc, char **argv) { } - /* read the map from prog object file and update the real - * server IP to the map - */ + /* Open device map */ device_map = bpf_object__find_map_by_name(obj, DEVICE_MAP); err = libbpf_get_error(device_map); if (err) { @@ -327,7 +280,7 @@ int main(int argc, char **argv) { exit(-1); } - // Set device name in map + /* Set device name in map */ key = 0; err = bpf_map_update_elem(device_map_fd, &key, dev, BPF_ANY); if (err) { @@ -336,6 +289,7 @@ int main(int argc, char **argv) { exit(-1); } + /* Attach XDP program to interface */ err = xdp_link_attach(ifindex, xdp_flags, prog_fd); if (err) return err; diff --git a/dhcp-relay/test.sh b/dhcp-relay/test.sh new file mode 100755 index 0000000..dc5f026 --- /dev/null +++ b/dhcp-relay/test.sh @@ -0,0 +1,65 @@ +#!/bin/bash -x + +OUTER_VLAN=83 +INNER_VLAN=20 +UPLINK_VLAN=84 +IF="ens6f0" + +DHCP_SERVER="185.107.12.59" +IPADDR_BNG="194.45.77.57" +IPADDR_UPLINK="185.107.12.99" +UPLINK_GW="185.107.12.97" +CLIENT_IP="194.45.77.59" + +echo "Setting up VLAN interfaces" +ethtool -K $IF txvlan off +ethtool -K $IF rxvlan off +# Increase MTU to allow second VLAN tag (QinQ) +ip link set dev $IF mtu 1504 +ip link set dev $IF up +# Set outer VLAN interface +ip link add link $IF name $IF.$OUTER_VLAN type vlan id $OUTER_VLAN +ip link set $IF.$OUTER_VLAN up + +CLIENT_IF=$IF.$OUTER_VLAN.$INNER_VLAN + +# Set inner VLAN interface +ip link add link $IF.$OUTER_VLAN name $CLIENT_IF type vlan id $INNER_VLAN +ip link set $CLIENT_IF up + +# Set accept_local for VLAN interface +echo 1 > /proc/sys/net/ipv4/conf/$CLIENT_IF/accept_local + +# Disable reverse path filtering for VLAN interface +echo 0 > /proc/sys/net/ipv4/conf/$CLIENT_IF/rp_filter + +# Enable ARP proxy for VLAN interface +echo 1 > /proc/sys/net/ipv4/conf/$CLIENT_IF/proxy_arp + +# Insert /32 route to client +ip route add $CLIENT_IP/32 dev $CLIENT_IF + +# Set IP forwarding +echo 1 > /proc/sys/net/ipv4/ip_forward + +# Set L3 config for BNG interface +ip addr add $IPADDR_BNG/29 dev lo + +# Create upstream interface +ip link add link $IF name $IF.$UPLINK_VLAN type vlan id $UPLINK_VLAN +ip link set dev $IF.$UPLINK_VLAN +ip link set $IF.$UPLINK_VLAN up + +# Disable RP filtering globally to receive DHCP requests through unnumbered +# interface +echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter + +# Set L3 config for upstream interface +ip addr add $IPADDR_UPLINK/28 dev $IF.$UPLINK_VLAN +ip route replace default via $UPLINK_GW + +echo "Compiling XDP program" +make + +echo "Launching XDP program" +./dhcp_user_xdp -i $IF -d $DHCP_SERVER -s $IPADDR_UPLINK \ No newline at end of file diff --git a/dhcp-relay/trace.sh b/dhcp-relay/trace.sh new file mode 100755 index 0000000..d364282 --- /dev/null +++ b/dhcp-relay/trace.sh @@ -0,0 +1,3 @@ +#!/bin/bash -x + +cat /sys/kernel/debug/tracing/trace_pipe \ No newline at end of file