From ac9373c82eca2f02316679d73bd7756cc4ed98b0 Mon Sep 17 00:00:00 2001
From: Yoel Caspersen <yoel@kviknet.dk>
Date: Sun, 17 Oct 2021 10:41:06 +0200
Subject: [PATCH] Increasing IP and UDP length headers to match Ethernet frame
 tail adjustment. Adding map client_vlans which allows matching server
 responses with client requests. Reformatting Option 82 to include interface
 name (work in progress, we need to figure out how to convert VLAN tags to
 ASCII).

---
 dhcp-relay/dhcp-relay.h    |  2 +-
 dhcp-relay/dhcp_kern_xdp.c | 89 ++++++++++++++++++++++++++++++++------
 dhcp-relay/dhcp_user_xdp.c | 32 +++++++++++++-
 3 files changed, 107 insertions(+), 16 deletions(-)

diff --git a/dhcp-relay/dhcp-relay.h b/dhcp-relay/dhcp-relay.h
index 2a0c6ed..ef85a02 100644
--- a/dhcp-relay/dhcp-relay.h
+++ b/dhcp-relay/dhcp-relay.h
@@ -21,7 +21,7 @@
 struct sub_option {
 	__u8 option_id;
 	__u8 len;
-	__u16 val;
+	char val[IF_NAMESIZE];
 };
 
 /*structure for dhcp option 82 */
diff --git a/dhcp-relay/dhcp_kern_xdp.c b/dhcp-relay/dhcp_kern_xdp.c
index 281088c..099eb84 100644
--- a/dhcp-relay/dhcp_kern_xdp.c
+++ b/dhcp-relay/dhcp_kern_xdp.c
@@ -2,6 +2,7 @@
 
 #include <linux/bpf.h>
 #include <linux/in.h>
+#include <net/if.h>				/* IF_NAMESIZE */
 #include <bpf/bpf_helpers.h>
 #include <xdp/parsing_helpers.h>
 #include <xdp/context_helpers.h>
@@ -23,22 +24,48 @@ struct {
 	__uint(max_entries, 3);
 } relay_config SEC(".maps");
 
+/*
+ * This map is for storing the device name in clear text.
+ * Device name is used for DHCP option 82.
+ */
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, __u32);
+	__type(value, char[IF_NAMESIZE]);
+	__uint(max_entries, 1);
+} device_name SEC(".maps");
+
+/*
+ * This map is used for storing client requests along with their matching
+ * VLAN tags. That way, we can handle DHCP server replies.
+ * Client MAC address is used as key, VLAN headers as value.
+ */
+
+struct {
+	__uint(type, BPF_MAP_TYPE_LRU_HASH);
+	__type(key, __u64);
+	__type(value, struct collect_vlans);
+	__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) {
+	struct collect_vlans *vlans, char *dev) {
 	struct dhcp_option_82 option;
 
 	option.t = DHO_DHCP_AGENT_OPTIONS;
-	option.len = 8;
+	option.len = sizeof(struct sub_option) + sizeof(struct sub_option);
 	option.circuit_id.option_id = RAI_CIRCUIT_ID;
-	option.circuit_id.len = RAI_OPTION_LEN;
-	option.circuit_id.val = bpf_ntohs(vlans->id[0]);
+	option.circuit_id.len = IF_NAMESIZE;
+	memcpy(option.circuit_id.val, dev, IF_NAMESIZE);
+	//option.circuit_id.val = bpf_ntohs(vlans->id[0]);
 	option.remote_id.option_id = RAI_REMOTE_ID;
-	option.remote_id.len = RAI_OPTION_LEN;
-	option.remote_id.val = bpf_ntohs(vlans->id[1]);
-
+	option.remote_id.len = IF_NAMESIZE;
+	//option.remote_id.val = bpf_ntohs(vlans->id[1]);
+	
 	return xdp_store_bytes(ctx, offset, &option, sizeof (option), 0);
 }
 
@@ -104,9 +131,8 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 		bpf_printk("Cannot tail extend packet, delta %i - error code %i", delta, res);
 		return XDP_ABORTED;
 	}
-
-	//bpf_printk("static_offset %i (hex %x)", static_offset, static_offset);
-	//bpf_printk("dhcp_offset %i (hex %x)", dhcp_offset, dhcp_offset);
+	
+	bpf_printk("Tail extended packet by %i bytes", delta);
 
 	void *data_end = (void *) (long) ctx->data_end;
 	void *data = (void *) (long) ctx->data;
@@ -126,6 +152,8 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 	__u16 vlan_length = 0;
 	__u8 option_code = 0;
 	__u8 option_length = 0;
+	__u64 client_mac = 0;
+	char *dev;
 	int i = 0;
 
 	/* These keep track of the next header type and iterator pointer */
@@ -177,6 +205,12 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 	if (udp->dest != bpf_htons(DHCP_SERVER_PORT) && udp->dest != bpf_htons(DHCP_CLIENT_PORT))
 		goto out;
 
+	/* Increase IP length header */
+	ip->tot_len += bpf_htons(delta);
+	
+	/* Increase UDP length header */
+	udp->len += bpf_htons(delta);
+	
 	/* Read DHCP server IP from config map */
 	key = 0;
 	dhcp_srv_ip = bpf_map_lookup_elem(&relay_config, &key);
@@ -194,6 +228,12 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 	relay_hwaddr = bpf_map_lookup_elem(&relay_config, &key);
 	if (relay_hwaddr == NULL)
 		goto out;
+	
+	/* Read device name from device map */
+	key = 0;
+	dev = bpf_map_lookup_elem(&device_name, &key);
+	if (dev == NULL)
+		goto out;
 
 	/* Copy headers of packet to buf */
 	//if (xdp_load_bytes(ctx, 0, buf, static_offset))
@@ -223,6 +263,12 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 	}
 	dhcp = data + vlan_length + dhcp_offset;
 
+	/* Store client MAC */
+	if (dhcp->chaddr + ETH_ALEN > data_end) {
+		goto out;
+	}
+	memcpy(&client_mac, dhcp->chaddr, ETH_ALEN);
+
 	bpf_printk("Parsing DHCP packet, opcode %i, hops %i", dhcp->op, dhcp->hops);
 
 	if (dhcp->op == DHCP_REQUEST && (eth->h_dest[0] == 0xff
@@ -243,12 +289,18 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 		//memcpy(eth->h_source, relay_hwaddr, ETH_ALEN);
 
 		// Set GIADDR
-		if(&dhcp->giaddr.s_addr + sizeof(relay_agent_ip) > data_end) {
+		if (&dhcp->giaddr.s_addr + sizeof (relay_agent_ip) > data_end) {
 			rc = XDP_ABORTED;
 			goto out;
 		}
 		dhcp->giaddr.s_addr = *relay_agent_ip;
 
+		/* Save client VLAN in state map */
+		if (bpf_map_update_elem(&client_vlans, &client_mac, &vlans, BPF_ANY)) {
+			bpf_printk("Could not save DHCP request in state map");
+			goto out;
+		}
+
 
 	} else if (dhcp->op == DHCP_REPLY && (eth->h_dest[0] != 0xff
 		|| eth->h_dest[1] != 0xff
@@ -270,6 +322,15 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 		 * to the end user
 		 */
 
+		struct collect_vlans *new_vlans;
+		new_vlans = bpf_map_lookup_elem(&client_vlans, &client_mac);
+		if (new_vlans == NULL) {
+			bpf_printk("Could not find map entry for MAC %i", client_mac);
+			goto out;
+		}
+
+		bpf_printk("Found map entry for MAC %i", client_mac);
+
 	}
 
 	/* Check hops */
@@ -307,7 +368,7 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 			bpf_printk("Going to write DHCP option at offset %i", option_offset);
 
 			/* Insert Option 82 before END option */
-			if (write_dhcp_option_82(ctx, option_offset, &vlans)) {
+			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;
 			}
@@ -325,6 +386,8 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 				bpf_printk("Could not write DHCP option 255 at offset %i", option_offset);
 				return XDP_ABORTED;
 			}
+			
+			bpf_printk("Wrote DHCP option 255 at offset %i, returning XDP_PASS", option_offset);
 
 			break;
 		}
@@ -384,8 +447,6 @@ int xdp_dhcp_relay(struct xdp_md *ctx) {
 	ip->check = ~sum;
 	rc = XDP_PASS;
 
-	bpf_printk("Wrote DHCP option at offset %i, returning XDP_PASS", offset);
-
 	goto out;
 
 out:
diff --git a/dhcp-relay/dhcp_user_xdp.c b/dhcp-relay/dhcp_user_xdp.c
index 26afd91..efe2a21 100644
--- a/dhcp-relay/dhcp_user_xdp.c
+++ b/dhcp-relay/dhcp_user_xdp.c
@@ -17,6 +17,7 @@
 #include <unistd.h>
 
 #define SERVER_MAP "relay_config"
+#define DEVICE_MAP "device_name"
 #define XDP_OBJ "dhcp_kern_xdp.o"
 
 static const struct option options[] = {
@@ -102,8 +103,10 @@ int main(int argc, char **argv) {
 	char dev[IF_NAMESIZE] = "";
 	bool do_unload = 0;
 	struct bpf_map *map = NULL;
+	struct bpf_map *device_map = NULL;
 	struct bpf_object *obj = NULL;
 	int map_fd;
+	int device_map_fd;
 	int key = 0;
 	struct in_addr dhcp_server_addr = {};
 	struct in_addr relay_agent_addr = {};
@@ -197,7 +200,7 @@ int main(int argc, char **argv) {
 	memcpy(&hwaddr, (unsigned char *) ifr.ifr_hwaddr.sa_data, 6);
 	
 	//display mac address
-	printf("Mac : %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+	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 */
 	err = bpf_prog_load(filename, BPF_PROG_TYPE_XDP, &obj, &prog_fd);
@@ -255,6 +258,33 @@ int main(int argc, char **argv) {
 		exit(-1);
 	}
 
+	
+	/* read the map from prog object file and update the real
+	 * server IP to the map
+	 */
+	device_map = bpf_object__find_map_by_name(obj, DEVICE_MAP);
+	err = libbpf_get_error(device_map);
+	if (err) {
+		fprintf(stderr, "Could not find map %s in %s: %s\n", DEVICE_MAP,
+			XDP_OBJ, strerror(err));
+		device_map = NULL;
+		exit(-1);
+	}
+	device_map_fd = bpf_map__fd(device_map);
+	if (device_map_fd < 0) {
+		fprintf(stderr, "Could not get device map fd\n");
+		exit(-1);
+	}
+
+	// Set device name in map
+	key = 0;
+	err = bpf_map_update_elem(device_map_fd, &key, dev, BPF_ANY);
+	if (err) {
+		fprintf(stderr, "Could not update map %s in %s\n", DEVICE_MAP,
+			XDP_OBJ);
+		exit(-1);
+	}
+	
 	err = xdp_link_attach(ifindex, xdp_flags, prog_fd);
 	if (err)
 		return err;