Add DHCP relay program.

Initial working version of DHCP relay using XDP is created. Currently, this code
has user program and a xdp ebpf program. User program takes network interface and
dhcp relay server IP as inputs and store it in a map. XDP program filters the
incoming DHCP requests and inserts option 82 in the DHCP request packets and
overwrites the destination IP to that of DHCP relay server IP.An optional argu
-ment for user program is also provided to unload the xdp program.

README file provides to instructions to build and load the xdp program.

Signed-off-by: Sachin Tiptur <sachin.tiptur.satyanarayana.gupta@hof-university.de>
[ whitespace fixes ]
Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
This commit is contained in:
Sachin Tiptur
2021-06-11 01:00:27 +02:00
committed by Toke Høiland-Jørgensen
parent 7bb3c6ac91
commit cb9fc3025a
6 changed files with 471 additions and 0 deletions

3
dhcp-relay/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.ll
*.o
dhcp_user_xdp

10
dhcp-relay/Makefile Normal file
View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
USER_TARGETS := dhcp_user_xdp
BPF_TARGETS :=dhcp_kern_xdp
EXTRA_DEPS := dhcp-relay.h
#EXTRA_CFLAGS := $(if $(IPV6),-DIPV6)
LIB_DIR = ../lib
include $(LIB_DIR)/common.mk

25
dhcp-relay/README Normal file
View File

@ -0,0 +1,25 @@
Usage
-----
dhcp_user_xdp takes network interface and dhcp relay server IP
as inputs and stores it in a map. Filters the incoming DHCP requests and inserts
option 82 in the DHCP request packets and overwrites the destination IP to that
of DHCP relay server IP.
Build instructions:
cd bpf-examples/dhcp-relay
make
Loading bpf program:
sudo ./dhcp_user_xdp -i <netif> -d <dhcp relay IP>
where,
netif: Ingress network interface name
unloading program:
sudo ./dhcp_user_xdp -i <netif> -u
To run in SKB mode:
add option "-m skb" for both load and uload commands
Verify using tcpdump:
sudo tcpdump -s 0 -i <netif> port 67 and port 68 -vvv

52
dhcp-relay/dhcp-relay.h Normal file
View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <linux/bpf.h>
#include <linux/types.h>
#define XDP_PROG_SEC "xdp"
#define DHCP_SNAME_LEN 64
#define DHCP_FILE_LEN 128
#define DHO_DHCP_AGENT_OPTIONS 82
#define RAI_CIRCUIT_ID 1
#define RAI_REMOTE_ID 2
#define RAI_OPTION_LEN 2
#define DEST_PORT 67 /* UDP destination port for dhcp */
#define MAX_BYTES 280 /* Max bytes supported by xdp load/store apis */
/* structure for sub-options in option 82*/
struct sub_option {
__u8 option_id;
__u8 len;
__u16 val;
};
/*structure for dhcp option 82 */
struct dhcp_option_82 {
__u8 t;
__u8 len;
struct sub_option circuit_id;
struct sub_option remote_id;
};
struct dhcp_packet {
__u8 op; /* 0: Message opcode/type */
__u8 htype; /* 1: Hardware addr type (net/if_types.h) */
__u8 hlen; /* 2: Hardware addr length */
__u8 hops; /* 3: Number of relay agent hops from client */
__u32 xid; /* 4: Transaction ID */
__u16 secs; /* 8: Seconds since client started looking */
__u16 flags; /* 10: Flag bits */
struct in_addr ciaddr; /* 12: Client IP address (if already in use) */
struct in_addr yiaddr; /* 16: Client IP address */
struct in_addr siaddr; /* 18: IP address of next server to talk to */
struct in_addr giaddr; /* 20: DHCP relay agent IP address */
unsigned char chaddr[16]; /* 24: Client hardware address */
char sname[DHCP_SNAME_LEN]; /* 40: Server name */
char file[DHCP_FILE_LEN]; /* 104: Boot filename */
__u32 cookie; /* 232: Magic cookie */
unsigned char options[0];
/* 236: Optional parameters
(actual length dependent on MTU). */
};

183
dhcp-relay/dhcp_kern_xdp.c Normal file
View File

@ -0,0 +1,183 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <linux/bpf.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <xdp/parsing_helpers.h>
#include <xdp/context_helpers.h>
#include "dhcp-relay.h"
/*
* This map is for storing the DHCP relay server
* IP address configured by user. It is received
* as an argument by user program.
*/
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u32);
__uint(max_entries, 1);
} dhcp_server SEC(".maps");
/* Inserts DHCP option 82 into the received dhcp packet
* at the specified offset.
*/
static __always_inline int write_dhcp_option(void *ctx, int offset,
struct collect_vlans *vlans)
{
struct dhcp_option_82 option;
option.t = DHO_DHCP_AGENT_OPTIONS;
option.len = 8;
option.circuit_id.option_id = RAI_CIRCUIT_ID;
option.circuit_id.len = RAI_OPTION_LEN;
option.circuit_id.val = bpf_htons(vlans->id[0]);
option.remote_id.option_id = RAI_REMOTE_ID;
option.remote_id.len = RAI_OPTION_LEN;
option.remote_id.val = bpf_htons(vlans->id[1]);
return xdp_store_bytes(ctx, offset, &option, sizeof(option), 0);
}
/* Calculates the IP checksum */
static __always_inline int calc_ip_csum(struct iphdr *oldip, struct iphdr *ip,
__u32 oldcsum)
{
__u32 size = sizeof(struct iphdr);
__u32 csum = bpf_csum_diff((__be32 *)oldip, size, (__be32 *)ip, size,
~oldcsum);
__u32 sum = (csum >> 16) + (csum & 0xffff);
sum += (sum >> 16);
return sum;
}
/* Offset to DHCP Options part of the packet */
#define static_offset \
sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + \
offsetof(struct dhcp_packet, options)
/* Delta value to be adjusted at xdp head*/
#define delta sizeof(struct dhcp_option_82)
/* buf needs to be a static global var because the verifier won't allow
* unaligned stack accesses
*/
static __u8 buf[static_offset + VLAN_MAX_DEPTH * sizeof(struct vlan_hdr)];
/* XDP program for parsing the DHCP packet and inserting the option 82*/
SEC(XDP_PROG_SEC)
int xdp_dhcp_relay(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct collect_vlans vlans = { 0 };
struct ethhdr *eth;
struct iphdr *ip;
struct iphdr oldip;
struct udphdr *udp;
__u32 *dhcp_srv;
int rc = XDP_PASS;
__u16 offset = static_offset;
__u16 ip_offset = 0;
int i = 0;
/* These keep track of the next header type and iterator pointer */
struct hdr_cursor nh;
int ether_type;
int h_proto = 0;
int key = 0;
int len = 0;
if (data + 1 > data_end)
return XDP_ABORTED;
nh.pos = data;
ether_type = parse_ethhdr_vlan(&nh, data_end, &eth, &vlans);
/* check for valid ether type */
if (ether_type < 0) {
rc = XDP_ABORTED;
goto out;
}
if (ether_type != bpf_htons(ETH_P_IP))
goto out;
/* Check at least two vlan tags are present */
if (vlans.id[1] == 0)
goto out;
/* Read dhcp relay server IP from map */
dhcp_srv = bpf_map_lookup_elem(&dhcp_server, &key);
if (dhcp_srv == NULL)
goto out;
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) {
goto out;
}
/*old ip hdr backup for re-calculating the checksum later*/
oldip = *ip;
ip_offset = ((void *)ip - data) & 0x3fff;
len = parse_udphdr(&nh, data_end, &udp);
if (len < 0)
goto out;
if (udp->dest != bpf_htons(DEST_PORT))
goto out;
if (xdp_load_bytes(ctx, 0, buf, static_offset))
goto out;
for (i = 0; i < VLAN_MAX_DEPTH; i++) {
if (vlans.id[i]) {
if (xdp_load_bytes(ctx, offset, buf + offset, 4))
goto out;
offset += 4;
}
}
/* adjusting the packet head by delta size to insert option82 */
if (bpf_xdp_adjust_head(ctx, 0 - delta) < 0)
return XDP_ABORTED;
data_end = (void *)(long)ctx->data_end;
data = (void *)(long)ctx->data;
if (data + offset > data_end)
return XDP_ABORTED;
if (xdp_store_bytes(ctx, 0, buf, static_offset, 0))
return XDP_ABORTED;
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;
}
}
}
if (write_dhcp_option(ctx, offset, &vlans))
return XDP_ABORTED;
ip = data + ip_offset;
if (ip + 1 > data_end)
return XDP_ABORTED;
/* overwrite the destination IP in IP header */
ip->daddr = *dhcp_srv;
//re-calc ip checksum
__u32 sum = calc_ip_csum(&oldip, ip, oldip.check);
ip->check = ~sum;
rc = XDP_PASS;
goto out;
out:
return rc;
}

198
dhcp-relay/dhcp_user_xdp.c Normal file
View File

@ -0,0 +1,198 @@
/* SPDX-License-Identifier: GPL-2.0 */
static const char *__doc__ = "DHCP relay program to add Option 82\n";
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <net/if.h>
#include <linux/if_link.h> /* depend on kernel-headers installed */
#include <arpa/inet.h>
#define SERVER_MAP "dhcp_server"
#define XDP_OBJ "dhcp_kern_xdp.o"
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "interface", required_argument, NULL,
'i' }, // Name of interface to run on
{ "dhcp-server", required_argument, NULL, 'd' },
{ "mode", required_argument, NULL, 'm' },
{ "unload", no_argument, NULL, 'u' },
{ 0, 0, NULL, 0 }
};
static void print_usage(char *argv[])
{
int i;
printf("Usage:\n");
printf("%s\n", argv[0]);
for (i = 0; options[i].name != 0; i++) {
printf(" --%-12s", options[i].name);
if (options[i].flag != NULL)
printf(" flag (internal value:%d)", *options[i].flag);
else
printf(" short-option: -%c", options[i].val);
printf("\n");
}
printf("Example:\n");
printf("To load program:\n %s -i eth0 -d 10.0.0.1\n", argv[0]);
printf("To unload program:\n %s -i eth0 -u\n", argv[0]);
printf("\n");
}
static int xdp_link_detach(int ifindex, __u32 xdp_flags)
{
int err;
if ((err = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags)) < 0) {
fprintf(stderr, "ERR: link set xdp unload failed (err=%d):%s\n",
err, strerror(-err));
return -1;
}
return 0;
}
int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd)
{
int err;
/* libbpf provide the XDP net_device link-level hook attach helper */
err = bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags);
if (err < 0) {
fprintf(stderr,
"ERR: "
"ifindex(%d) link set xdp fd failed (%d): %s\n",
ifindex, -err, strerror(-err));
switch (-err) {
case EBUSY:
case EEXIST:
fprintf(stderr, "Hint: XDP already loaded on device\n");
break;
case EOPNOTSUPP:
fprintf(stderr, "Hint: Native-XDP not supported\n");
break;
default:
break;
}
return -1;
}
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 filename[256] = "dhcp_kern_xdp.o";
int prog_fd, err;
int opt;
__u32 xdp_flags = XDP_FLAGS_DRV_MODE;
char dev[IF_NAMESIZE] = "";
bool do_unload = 0;
struct bpf_map *map = NULL;
struct bpf_obj *obj = NULL;
int map_fd;
int key = 0;
char server[15] = "";
struct in_addr addr;
__u16 ifindex;
while ((opt = getopt_long(argc, argv, "hui:d:m:", 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':
if (inet_aton(optarg, &addr) == 0) {
fprintf(stderr,
"Couldn't validate IP address:%s\n",
optarg);
return -EINVAL;
}
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]);
return -EINVAL;
}
}
if (do_unload)
return xdp_link_detach(ifindex, xdp_flags);
/* 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);
if (err) {
fprintf(stderr, "ERR: loading BPF-OBJ file(%s) (%d): %s\n",
filename, err, strerror(-err));
return -1;
}
if (prog_fd <= 0) {
printf("ERR: loading file: %s\n");
return -1;
}
/* read the map from prog object file and update the real
* server IP to the map
*/
map = bpf_object__find_map_by_name(obj, SERVER_MAP);
err = libbpf_get_error(map);
if (err) {
fprintf(stderr, "Could not find map %s in %s: %s\n", SERVER_MAP,
XDP_OBJ, strerror(err));
map = NULL;
exit(-1);
}
map_fd = bpf_map__fd(map);
if (map_fd < 0) {
fprintf(stderr, "Could not get map fd\n");
exit(-1);
}
err = bpf_map_update_elem(map_fd, &key, &addr.s_addr, BPF_ANY);
if (err) {
fprintf(stderr, "Could not update map %s in %s\n", SERVER_MAP,
XDP_OBJ);
exit(-1);
}
err = xdp_link_attach(ifindex, xdp_flags, prog_fd);
if (err)
return err;
printf("Success: Loading xdp program\n");
return 0;
}