1 /* 2 * Copyright (C) 2026 Nick Hainke <vincent@systemli.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License version 2.1 6 * as published by the Free Software Foundation 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * Bind the sockets of a cgroup to a VRF device, the way 'ip vrf exec' does. 14 */ 15 16 #define _GNU_SOURCE 17 #include <sys/socket.h> 18 #include <sys/syscall.h> 19 #include <linux/bpf.h> 20 #include <linux/if.h> 21 #include <linux/if_link.h> 22 #include <linux/netlink.h> 23 #include <linux/rtnetlink.h> 24 #include <errno.h> 25 #include <stdbool.h> 26 #include <stddef.h> 27 #include <stdint.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include "../log.h" 32 #include "vrf.h" 33 34 #define BPF_MOV64_IMM(DST, IMM) \ 35 ((struct bpf_insn){ .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = DST, .imm = IMM }) 36 37 #define BPF_STX_MEM(SIZE, DST, SRC, OFF) \ 38 ((struct bpf_insn){ .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF }) 39 40 #define BPF_EXIT_INSN() \ 41 ((struct bpf_insn){ .code = BPF_JMP | BPF_EXIT }) 42 43 static int 44 vrf_bpf(int cmd, union bpf_attr *attr) 45 { 46 return syscall(__NR_bpf, cmd, attr, sizeof(*attr)); 47 } 48 49 static bool 50 vrf_linkinfo_is_vrf(struct rtattr *linkinfo) 51 { 52 struct rtattr *rta = RTA_DATA(linkinfo); 53 int len = RTA_PAYLOAD(linkinfo); 54 55 for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) 56 if (rta->rta_type == IFLA_INFO_KIND) 57 return RTA_PAYLOAD(rta) >= strlen("vrf") && 58 !strncmp(RTA_DATA(rta), "vrf", RTA_PAYLOAD(rta)); 59 60 return false; 61 } 62 63 /* returns the ifindex of vrf_name, also checks that it is a VRF device */ 64 static int 65 vrf_get_ifindex(const char *vrf_name) 66 { 67 struct { 68 struct nlmsghdr hdr; 69 struct ifinfomsg ifi; 70 char attrbuf[RTA_SPACE(IFNAMSIZ)]; 71 } req = { 72 .hdr = { 73 .nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), 74 .nlmsg_type = RTM_GETLINK, 75 .nlmsg_flags = NLM_F_REQUEST, 76 .nlmsg_seq = 1, 77 }, 78 .ifi = { .ifi_family = AF_UNSPEC }, 79 }; 80 size_t namelen = strlen(vrf_name) + 1; 81 struct nlmsghdr *nh; 82 struct ifinfomsg *ifi; 83 struct rtattr *rta; 84 bool is_vrf = false; 85 char buf[16384]; 86 int sock, len, ret; 87 ssize_t n; 88 89 if (namelen > IFNAMSIZ) 90 return -EINVAL; 91 92 rta = (struct rtattr *)((char *)&req + NLMSG_ALIGN(req.hdr.nlmsg_len)); 93 rta->rta_type = IFLA_IFNAME; 94 rta->rta_len = RTA_LENGTH(namelen); 95 memcpy(RTA_DATA(rta), vrf_name, namelen); 96 req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_ALIGN(rta->rta_len); 97 98 sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); 99 if (sock < 0) 100 return -errno; 101 102 if (send(sock, &req, req.hdr.nlmsg_len, 0) < 0) { 103 ret = -errno; 104 close(sock); 105 return ret; 106 } 107 108 n = recv(sock, buf, sizeof(buf), 0); 109 ret = -errno; 110 close(sock); 111 if (n < 0) 112 return ret; 113 114 nh = (struct nlmsghdr *)buf; 115 if (!NLMSG_OK(nh, (size_t)n)) 116 return -EIO; 117 118 if (nh->nlmsg_type == NLMSG_ERROR) { 119 struct nlmsgerr *err = NLMSG_DATA(nh); 120 121 if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(*err)) || !err->error) 122 return -EIO; 123 124 return err->error; 125 } 126 127 if (nh->nlmsg_type != RTM_NEWLINK || nh->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi))) 128 return -EIO; 129 130 ifi = NLMSG_DATA(nh); 131 len = IFLA_PAYLOAD(nh); 132 for (rta = IFLA_RTA(ifi); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) 133 if (rta->rta_type == IFLA_LINKINFO) 134 is_vrf = vrf_linkinfo_is_vrf(rta); 135 136 if (!is_vrf) { 137 ERROR("%s is not a VRF device\n", vrf_name); 138 return -EINVAL; 139 } 140 141 return ifi->ifi_index; 142 } 143 144 /* 145 * Attach a program to the cgroup which binds every new AF_INET/AF_INET6 146 * socket to the VRF. It replaces a program attached earlier. 147 */ 148 int 149 vrf_attach(int cgroup_fd, const char *vrf_name) 150 { 151 int ifindex = vrf_get_ifindex(vrf_name); 152 struct bpf_insn prog[] = { 153 /* r1 = struct bpf_sock *; r1->bound_dev_if = ifindex */ 154 BPF_MOV64_IMM(BPF_REG_3, ifindex), 155 BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, 156 offsetof(struct bpf_sock, bound_dev_if)), 157 /* allow */ 158 BPF_MOV64_IMM(BPF_REG_0, 1), 159 BPF_EXIT_INSN(), 160 }; 161 union bpf_attr load_attr = { 162 .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, 163 .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, 164 .license = (uint64_t)(uintptr_t)"GPL", 165 .insns = (uint64_t)(uintptr_t)prog, 166 .insn_cnt = sizeof(prog) / sizeof(prog[0]), 167 }; 168 union bpf_attr attach_attr = { 169 .attach_type = BPF_CGROUP_INET_SOCK_CREATE, 170 .target_fd = cgroup_fd, 171 }; 172 int prog_fd, ret = 0; 173 174 if (ifindex < 0) 175 return ifindex; 176 177 prog_fd = vrf_bpf(BPF_PROG_LOAD, &load_attr); 178 if (prog_fd < 0) 179 return -errno; 180 181 attach_attr.attach_bpf_fd = prog_fd; 182 if (vrf_bpf(BPF_PROG_ATTACH, &attach_attr)) 183 ret = -errno; 184 185 close(prog_fd); 186 return ret; 187 } 188 189 void 190 vrf_detach(int cgroup_fd) 191 { 192 union bpf_attr attr = { 193 .attach_type = BPF_CGROUP_INET_SOCK_CREATE, 194 .target_fd = cgroup_fd, 195 }; 196 197 vrf_bpf(BPF_PROG_DETACH, &attr); 198 } 199
This page was automatically generated by LXR 0.3.1. • OpenWrt