• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/procd/service/vrf.c

  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