Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions xdp-forward/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# SPDX-License-Identifier: GPL-2.0

ifdef VLANS_PATCHED
CFLAGS += -DVLANS_PATCHED
BPF_CFLAGS += -DVLANS_PATCHED
endif

ifdef VLANS_USERSPACE
CFLAGS += -DVLANS_USERSPACE
BPF_CFLAGS += -DVLANS_USERSPACE
endif

XDP_TARGETS := xdp_forward.bpf xdp_flowtable.bpf xdp_flowtable_sample.bpf
BPF_SKEL_TARGETS := $(XDP_TARGETS)

Expand Down
27 changes: 27 additions & 0 deletions xdp-forward/README.org
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,33 @@ EOF
#xdp-forward load -f flowtable n0 n1
#+end_src


* VLAN support
Vlan support adds ~4% overhead for packet processing. Therefore, it's disabled
by default. To enable it, compilation flags are required, based on desired mode.

** Userspace mode
This can be enabled by compiling =xdp-forward= with the =-DVLANS_USERSPACE= flag:

#+begin_src sh
VLANS_USERSPACE=1 make
#+end_src

In this mode, userspace program will query netlink for VLAN devices on top of
defined physical interfaces. This is then provided to the XDP program via
BPF map, which is not updated during runtime. Which means, that if VLAN interface
is changed, these changes are not propagated to the =xdp-forward=.

** Kernel mode
This mode requires specific kernel patch, that extends FIB lookup to support
VLANS. This is not yet upstream, therefore this mode needs to be manually enabled
by compilation vlag =-DVLANS_PATCHED=:

#+begin_src sh
VLANs_PATCHED=1 make
#+end_src


* SEE ALSO
=libxdp(3)= for details on the XDP loading semantics and kernel compatibility
requirements.
Expand Down
23 changes: 23 additions & 0 deletions xdp-forward/xdp-forward.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include "logging.h"
#include "compat.h"

#include "xdp-userspace-vlans.c"

#include "xdp_forward.skel.h"
#include "xdp_flowtable.skel.h"
#include "xdp_flowtable_sample.skel.h"
Expand Down Expand Up @@ -145,6 +147,9 @@ static int do_load(const void *cfg, __unused const char *pin_root_path)
struct xdp_program *xdp_prog = NULL;
const struct load_opts *opt = cfg;
struct bpf_program *prog = NULL;
#ifdef VLANS_USERSPACE
struct bpf_map *vlan_map_obj = NULL;
#endif
struct bpf_map *map = NULL;
struct bpf_object *obj;
int ret = EXIT_FAILURE;
Expand Down Expand Up @@ -189,6 +194,9 @@ static int do_load(const void *cfg, __unused const char *pin_root_path)
goto end;
}
map = xdp_forward_skel->maps.xdp_tx_ports;
#ifdef VLANS_USERSPACE
vlan_map_obj = xdp_forward_skel->maps.vlan_map;
#endif
obj = xdp_forward_skel->obj;
skel = (void *)xdp_forward_skel;
}
Expand Down Expand Up @@ -240,6 +248,21 @@ static int do_load(const void *cfg, __unused const char *pin_root_path)
strerror(errno));
goto end_detach;
}
#ifdef VLANS_USERSPACE
struct vlan_info vlan_list[MAX_VLANS_PER_IFACE];
int vlans = find_vlan_interfaces(iface->ifindex, vlan_list);
if (vlan_map_obj) {
for (int i = 0; i < vlans; i++) {
ret = bpf_map_update_elem(bpf_map__fd(vlan_map_obj),
&(vlan_list[i].vlan_ifindex), &vlan_list[i], 0);
if (ret) {
pr_warn("Failed to update VLAN map value: %s\n",
strerror(errno));
goto end_detach;
}
}
}
#endif
pr_info("Loaded on interface %s\n", iface->ifname);
}

Expand Down
235 changes: 235 additions & 0 deletions xdp-forward/xdp-userspace-vlans.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/bpf.h>
#include <unistd.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>

#define MAX_VLANS_PER_IFACE 16
struct vlan_info {
__u16 vlan_id; // VLAN ID
int phys_ifindex; // Physical interface index
int vlan_ifindex; // VLAN interface index
};

static int init_netlink_socket()
{
struct sockaddr_nl addr;
int sock;

sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0) {
perror("Failed to open netlink socket");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = 0;

if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Failed to bind netlink socket");
close(sock);
return -1;
}
return sock;
}

static int send_getlink_request(int sock)
{
struct sockaddr_nl addr;
struct {
struct nlmsghdr nlh;
struct ifinfomsg ifm;
} req;

memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;

memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.nlh.nlmsg_type = RTM_GETLINK;
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.nlh.nlmsg_seq = 1;
req.nlh.nlmsg_pid = getpid();
req.ifm.ifi_family = AF_PACKET; // request all interfaces

if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) {
perror("Failed to send netlink message");
return -1;
}
return 0;
}

static int parse_vlan_id_from_data(struct rtattr *li_attr)
{
struct rtattr *vlan_attr;
int vlan_remaining = RTA_PAYLOAD(li_attr);
int vlan_id = 0;

for (vlan_attr = (struct rtattr *)RTA_DATA(li_attr);
RTA_OK(vlan_attr, vlan_remaining);
vlan_attr = RTA_NEXT(vlan_attr, vlan_remaining)) {
if (vlan_attr->rta_type == IFLA_VLAN_ID) {
vlan_id = *(uint16_t *)RTA_DATA(vlan_attr);
break; // vid found
}
}
return vlan_id;
}

static void parse_link_info_attr(struct rtattr *attr, int *is_vlan,
int *vlan_id)
{
struct rtattr *li_attr;
int li_remaining = RTA_PAYLOAD(attr);

for (li_attr = (struct rtattr *)RTA_DATA(attr);
RTA_OK(li_attr, li_remaining);
li_attr = RTA_NEXT(li_attr, li_remaining)) {
if (li_attr->rta_type == IFLA_INFO_KIND) {
char *kind = RTA_DATA(li_attr);
if (strncmp(kind, "vlan", 4) == 0)
*is_vlan = 1;

} else if (li_attr->rta_type == IFLA_INFO_DATA) {
int vid = parse_vlan_id_from_data(li_attr);
if (vid) // first valid vid is 1
*vlan_id = vid;
}
}
}

static void parse_interface_attributes(struct nlmsghdr *nlmsg, int *is_vlan,
int *vlan_id, int *link_ifindex)
{
struct ifinfomsg *ifinfo = NLMSG_DATA(nlmsg);
struct rtattr *attr;
int remaining =
nlmsg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));

*is_vlan = 0;
*vlan_id = 0;
*link_ifindex = -1;

for (attr = IFLA_RTA(ifinfo); RTA_OK(attr, remaining);
attr = RTA_NEXT(attr, remaining)) {
if (attr->rta_type == IFLA_LINKINFO)
parse_link_info_attr(attr, is_vlan, vlan_id);
else if (attr->rta_type == IFLA_LINK)
*link_ifindex = *(int *)RTA_DATA(attr);
}
}

static void handle_found_vlan(struct ifinfomsg *ifinfo, int vlan_id,
int link_ifindex, struct vlan_info *vlan_list,
int *found_vlans)
{
if (*found_vlans < 1024) {
printf("%d\t%d\n", vlan_id, ifinfo->ifi_index);
vlan_list[*found_vlans].vlan_id = vlan_id;
vlan_list[*found_vlans].phys_ifindex = link_ifindex;
vlan_list[*found_vlans].vlan_ifindex = ifinfo->ifi_index;
(*found_vlans)++;
} else
fprintf(stderr, "Warning: VLAN list capacity exceeded.\n");
}

static int process_netlink_message(struct nlmsghdr *nlmsg, int target_ifindex,
struct vlan_info *vlan_list,
int *found_vlans)
{
struct ifinfomsg *ifinfo;
int is_vlan, vlan_id, link_ifindex;

if (nlmsg->nlmsg_type == NLMSG_DONE)
return NLMSG_DONE; // all msgs processed

if (nlmsg->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlmsg);
// If the error field is zero, it's an ACK, not an error. Ignore.
if (err->error == 0)
return NLMSG_DONE;

errno = -err->error;
perror("Netlink error");
return NLMSG_ERROR;
}

if (nlmsg->nlmsg_type != RTM_NEWLINK)
return 0;

ifinfo = NLMSG_DATA(nlmsg);
parse_interface_attributes(nlmsg, &is_vlan, &vlan_id, &link_ifindex);

if (is_vlan && link_ifindex == target_ifindex && vlan_id)
handle_found_vlan(ifinfo, vlan_id, link_ifindex, vlan_list,
found_vlans);

return 0;
}

static int receive_and_process_responses(int sock, int target_ifindex,
struct vlan_info *vlan_list)
{
char buf[8192];
int found_vlans = 0;
int status;

printf("VLAN interfaces using physical ifindex %d:\n", target_ifindex);
printf("VLAN ID\tVLAN ifindex\n");

while (1) {
int len = recv(sock, buf, sizeof(buf), 0);
if (len < 0) {
perror("Failed to receive netlink message");
return -1;
}

struct nlmsghdr *nlmsg;
for (nlmsg = (struct nlmsghdr *)buf; NLMSG_OK(nlmsg, len);
nlmsg = NLMSG_NEXT(nlmsg, len)) {
status = process_netlink_message(
nlmsg, target_ifindex, vlan_list, &found_vlans);

if (status == NLMSG_DONE)
return found_vlans;

if (status == NLMSG_ERROR)
return -1;
}
if (len > 0 && !NLMSG_OK(nlmsg, len))
fprintf(stderr,
"Warning: Potentially incomplete netlink message processed.\n");
}
return found_vlans;
}

int find_vlan_interfaces(int target_ifindex, struct vlan_info *vlan_list)
{
/**
* find_vlan_interfaces - Find VLAN interfaces linked
* to a given physical interface as well as VLAN id
* assigned to that VLAN interface. netlink socket is
* used.
* This function is called for each physical interface,
* where our program should be attached, their VLAN
* interfaces are found and added to the map.
*/
int sock;
int result = -1;

sock = init_netlink_socket();
if (sock < 0)
return -1;

if (send_getlink_request(sock) < 0) {
close(sock);
return -1;
}

result = receive_and_process_responses(sock, target_ifindex, vlan_list);

close(sock);
return result; // Returns count of found VLANs or -1 on error
}
Loading
Loading