]>
Commit | Line | Data |
---|---|---|
cc73685d | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
bfcedc7e | 2 | |
d38dd64a CB |
3 | #ifndef _GNU_SOURCE |
4 | #define _GNU_SOURCE 1 | |
5 | #endif | |
0ad19a3f | 6 | #include <errno.h> |
0ad19a3f | 7 | #include <linux/netlink.h> |
8 | #include <linux/rtnetlink.h> | |
d38dd64a CB |
9 | #include <stdio.h> |
10 | #include <stdlib.h> | |
11 | #include <string.h> | |
12 | #include <sys/socket.h> | |
13 | #include <time.h> | |
14 | #include <unistd.h> | |
f549edcc | 15 | |
d38dd64a | 16 | #include "config.h" |
bfcedc7e | 17 | #include "log.h" |
f549edcc | 18 | #include "nl.h" |
0ad19a3f | 19 | |
bfcedc7e CB |
20 | lxc_log_define(nl, lxc); |
21 | ||
6822ba9b | 22 | static size_t nlmsg_len(const struct nlmsg *nlmsg) |
0ad19a3f | 23 | { |
06f976ca | 24 | return nlmsg->nlmsghdr->nlmsg_len - NLMSG_HDRLEN; |
0ad19a3f | 25 | } |
26 | ||
19bfd55a | 27 | void *nlmsg_data(struct nlmsg *nlmsg) |
0ad19a3f | 28 | { |
19bfd55a CB |
29 | char *data; |
30 | ||
31 | data = ((char *)nlmsg) + NLMSG_HDRLEN; | |
0ad19a3f | 32 | if (!nlmsg_len(nlmsg)) |
19bfd55a CB |
33 | return ret_set_errno(NULL, EINVAL); |
34 | ||
0ad19a3f | 35 | return data; |
36 | } | |
37 | ||
f79d43bb | 38 | static int nla_put(struct nlmsg *nlmsg, int attr, |
0ad19a3f | 39 | const void *data, size_t len) |
40 | { | |
41 | struct rtattr *rta; | |
42 | size_t rtalen = RTA_LENGTH(len); | |
06f976ca | 43 | size_t tlen = NLMSG_ALIGN(nlmsg->nlmsghdr->nlmsg_len) + RTA_ALIGN(rtalen); |
84760c11 | 44 | |
06f976ca | 45 | if (tlen > nlmsg->cap) |
19bfd55a | 46 | return ret_errno(ENOMEM); |
06f976ca SZ |
47 | |
48 | rta = NLMSG_TAIL(nlmsg->nlmsghdr); | |
49 | rta->rta_type = attr; | |
50 | rta->rta_len = rtalen; | |
c8f05589 RM |
51 | if (data && len) |
52 | memcpy(RTA_DATA(rta), data, len); | |
06f976ca | 53 | nlmsg->nlmsghdr->nlmsg_len = tlen; |
19bfd55a | 54 | |
0ad19a3f | 55 | return 0; |
56 | } | |
57 | ||
19bfd55a | 58 | int nla_put_buffer(struct nlmsg *nlmsg, int attr, const void *data, size_t size) |
0ad19a3f | 59 | { |
60 | return nla_put(nlmsg, attr, data, size); | |
61 | } | |
62 | ||
19bfd55a | 63 | int nla_put_string(struct nlmsg *nlmsg, int attr, const char *string) |
0ad19a3f | 64 | { |
d028235d | 65 | return nla_put(nlmsg, attr, string, strlen(string) + 1); |
0ad19a3f | 66 | } |
67 | ||
19bfd55a | 68 | int nla_put_u32(struct nlmsg *nlmsg, int attr, int value) |
0ad19a3f | 69 | { |
70 | return nla_put(nlmsg, attr, &value, sizeof(value)); | |
71 | } | |
72 | ||
19bfd55a | 73 | int nla_put_u16(struct nlmsg *nlmsg, int attr, unsigned short value) |
9ddaf3bf JHS |
74 | { |
75 | return nla_put(nlmsg, attr, &value, 2); | |
76 | } | |
77 | ||
19bfd55a | 78 | int nla_put_attr(struct nlmsg *nlmsg, int attr) |
0ad19a3f | 79 | { |
80 | return nla_put(nlmsg, attr, NULL, 0); | |
81 | } | |
82 | ||
83 | struct rtattr *nla_begin_nested(struct nlmsg *nlmsg, int attr) | |
84 | { | |
19bfd55a | 85 | struct rtattr *rtattr; |
0ad19a3f | 86 | |
19bfd55a | 87 | rtattr = NLMSG_TAIL(nlmsg->nlmsghdr); |
0ad19a3f | 88 | if (nla_put_attr(nlmsg, attr)) |
19bfd55a | 89 | return ret_set_errno(NULL, ENOMEM); |
0ad19a3f | 90 | |
91 | return rtattr; | |
92 | } | |
93 | ||
94 | void nla_end_nested(struct nlmsg *nlmsg, struct rtattr *attr) | |
95 | { | |
06f976ca | 96 | attr->rta_len = (void *)NLMSG_TAIL(nlmsg->nlmsghdr) - (void *)attr; |
0ad19a3f | 97 | } |
98 | ||
19bfd55a | 99 | struct nlmsg *nlmsg_alloc(size_t size) |
0ad19a3f | 100 | { |
19bfd55a | 101 | __do_free struct nlmsg *nlmsg = NULL; |
b5887164 | 102 | size_t len = NLMSG_HDRLEN + NLMSG_ALIGN(size); |
0ad19a3f | 103 | |
19bfd55a | 104 | nlmsg = malloc(sizeof(struct nlmsg)); |
0ad19a3f | 105 | if (!nlmsg) |
19bfd55a | 106 | return ret_set_errno(NULL, ENOMEM); |
0ad19a3f | 107 | |
19bfd55a | 108 | nlmsg->nlmsghdr = malloc(len); |
3d88831c | 109 | if (!nlmsg->nlmsghdr) |
19bfd55a | 110 | return ret_set_errno(NULL, ENOMEM); |
06f976ca SZ |
111 | |
112 | memset(nlmsg->nlmsghdr, 0, len); | |
113 | nlmsg->cap = len; | |
114 | nlmsg->nlmsghdr->nlmsg_len = NLMSG_HDRLEN; | |
0ad19a3f | 115 | |
19bfd55a | 116 | return move_ptr(nlmsg); |
06f976ca SZ |
117 | } |
118 | ||
19bfd55a | 119 | void *nlmsg_reserve(struct nlmsg *nlmsg, size_t len) |
06f976ca SZ |
120 | { |
121 | void *buf; | |
122 | size_t nlmsg_len = nlmsg->nlmsghdr->nlmsg_len; | |
123 | size_t tlen = NLMSG_ALIGN(len); | |
124 | ||
125 | if (nlmsg_len + tlen > nlmsg->cap) | |
19bfd55a | 126 | return ret_set_errno(NULL, ENOMEM); |
06f976ca SZ |
127 | |
128 | buf = ((char *)(nlmsg->nlmsghdr)) + nlmsg_len; | |
129 | nlmsg->nlmsghdr->nlmsg_len += tlen; | |
130 | ||
131 | if (tlen > len) | |
132 | memset(buf + len, 0, tlen - len); | |
133 | ||
134 | return buf; | |
135 | } | |
136 | ||
19bfd55a | 137 | struct nlmsg *nlmsg_alloc_reserve(size_t size) |
06f976ca SZ |
138 | { |
139 | struct nlmsg *nlmsg; | |
140 | ||
141 | nlmsg = nlmsg_alloc(size); | |
142 | if (!nlmsg) | |
19bfd55a | 143 | return ret_set_errno(NULL, ENOMEM); |
06f976ca | 144 | |
1a0e70ac | 145 | /* Just set message length to cap directly. */ |
06f976ca | 146 | nlmsg->nlmsghdr->nlmsg_len = nlmsg->cap; |
0ad19a3f | 147 | return nlmsg; |
148 | } | |
149 | ||
19bfd55a | 150 | void nlmsg_free(struct nlmsg *nlmsg) |
0ad19a3f | 151 | { |
19bfd55a CB |
152 | if (nlmsg) { |
153 | free(nlmsg->nlmsghdr); | |
154 | free(nlmsg); | |
155 | } | |
0ad19a3f | 156 | } |
157 | ||
19bfd55a | 158 | int __netlink_recv(struct nl_handler *handler, struct nlmsghdr *nlmsghdr) |
0ad19a3f | 159 | { |
160 | int ret; | |
d028235d SG |
161 | struct sockaddr_nl nladdr; |
162 | struct iovec iov = { | |
9fbbc427 CB |
163 | .iov_base = nlmsghdr, |
164 | .iov_len = nlmsghdr->nlmsg_len, | |
d028235d | 165 | }; |
1a0e70ac | 166 | |
0ad19a3f | 167 | struct msghdr msg = { |
9fbbc427 CB |
168 | .msg_name = &nladdr, |
169 | .msg_namelen = sizeof(nladdr), | |
170 | .msg_iov = &iov, | |
171 | .msg_iovlen = 1, | |
d028235d | 172 | }; |
1a0e70ac | 173 | |
d028235d SG |
174 | memset(&nladdr, 0, sizeof(nladdr)); |
175 | nladdr.nl_family = AF_NETLINK; | |
176 | nladdr.nl_pid = 0; | |
177 | nladdr.nl_groups = 0; | |
0ad19a3f | 178 | |
179 | again: | |
180 | ret = recvmsg(handler->fd, &msg, 0); | |
181 | if (ret < 0) { | |
182 | if (errno == EINTR) | |
183 | goto again; | |
9fbbc427 | 184 | |
19bfd55a | 185 | return ret_errno(errno); |
0ad19a3f | 186 | } |
187 | ||
188 | if (!ret) | |
189 | return 0; | |
190 | ||
19bfd55a CB |
191 | if (msg.msg_flags & MSG_TRUNC && (ret == nlmsghdr->nlmsg_len)) |
192 | return ret_errno(EMSGSIZE); | |
0ad19a3f | 193 | |
194 | return ret; | |
195 | } | |
196 | ||
19bfd55a | 197 | int netlink_rcv(struct nl_handler *handler, struct nlmsg *answer) |
9fbbc427 CB |
198 | { |
199 | return __netlink_recv(handler, answer->nlmsghdr); | |
200 | } | |
201 | ||
19bfd55a | 202 | int __netlink_send(struct nl_handler *handler, struct nlmsghdr *nlmsghdr) |
0ad19a3f | 203 | { |
9fbbc427 | 204 | int ret; |
d028235d SG |
205 | struct sockaddr_nl nladdr; |
206 | struct iovec iov = { | |
9fbbc427 CB |
207 | .iov_base = nlmsghdr, |
208 | .iov_len = nlmsghdr->nlmsg_len, | |
d028235d | 209 | }; |
0ad19a3f | 210 | struct msghdr msg = { |
9fbbc427 CB |
211 | .msg_name = &nladdr, |
212 | .msg_namelen = sizeof(nladdr), | |
213 | .msg_iov = &iov, | |
214 | .msg_iovlen = 1, | |
d028235d | 215 | }; |
1a0e70ac | 216 | |
d028235d SG |
217 | memset(&nladdr, 0, sizeof(nladdr)); |
218 | nladdr.nl_family = AF_NETLINK; | |
219 | nladdr.nl_pid = 0; | |
220 | nladdr.nl_groups = 0; | |
0ad19a3f | 221 | |
57b1ab60 | 222 | ret = sendmsg(handler->fd, &msg, MSG_NOSIGNAL); |
e239ff31 | 223 | if (ret < 0) |
19bfd55a | 224 | return ret_errno(errno); |
0ad19a3f | 225 | |
226 | return ret; | |
227 | } | |
228 | ||
9fbbc427 CB |
229 | extern int netlink_send(struct nl_handler *handler, struct nlmsg *nlmsg) |
230 | { | |
231 | return __netlink_send(handler, nlmsg->nlmsghdr); | |
232 | } | |
233 | ||
234 | extern int __netlink_transaction(struct nl_handler *handler, | |
235 | struct nlmsghdr *request, | |
236 | struct nlmsghdr *answer) | |
0ad19a3f | 237 | { |
0ad19a3f | 238 | int ret; |
239 | ||
9fbbc427 | 240 | ret = __netlink_send(handler, request); |
0ad19a3f | 241 | if (ret < 0) |
19bfd55a | 242 | return ret; |
0ad19a3f | 243 | |
9fbbc427 | 244 | ret = __netlink_recv(handler, answer); |
0ad19a3f | 245 | if (ret < 0) |
19bfd55a | 246 | return ret; |
0ad19a3f | 247 | |
9fbbc427 CB |
248 | if (answer->nlmsg_type == NLMSG_ERROR) { |
249 | struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(answer); | |
bfcedc7e | 250 | if (err->error < 0) |
19bfd55a | 251 | return ret_errno(-err->error); |
0ad19a3f | 252 | } |
e239ff31 | 253 | |
19bfd55a | 254 | return 0; |
0ad19a3f | 255 | } |
256 | ||
9fbbc427 CB |
257 | extern int netlink_transaction(struct nl_handler *handler, |
258 | struct nlmsg *request, struct nlmsg *answer) | |
259 | { | |
260 | return __netlink_transaction(handler, request->nlmsghdr, | |
261 | answer->nlmsghdr); | |
262 | } | |
263 | ||
0ad19a3f | 264 | extern int netlink_open(struct nl_handler *handler, int protocol) |
265 | { | |
19bfd55a | 266 | __do_close int fd = -EBADF; |
0ad19a3f | 267 | socklen_t socklen; |
d028235d SG |
268 | int sndbuf = 32768; |
269 | int rcvbuf = 32768; | |
0ad19a3f | 270 | |
d028235d | 271 | memset(handler, 0, sizeof(*handler)); |
19bfd55a | 272 | handler->fd = -EBADF; |
0ad19a3f | 273 | |
19bfd55a CB |
274 | fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); |
275 | if (fd < 0) | |
276 | return ret_errno(errno); | |
0ad19a3f | 277 | |
19bfd55a CB |
278 | if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) |
279 | return ret_errno(errno); | |
0ad19a3f | 280 | |
19bfd55a CB |
281 | if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf,sizeof(rcvbuf)) < 0) |
282 | return ret_errno(errno); | |
0ad19a3f | 283 | |
d028235d SG |
284 | memset(&handler->local, 0, sizeof(handler->local)); |
285 | handler->local.nl_family = AF_NETLINK; | |
286 | handler->local.nl_groups = 0; | |
0ad19a3f | 287 | |
19bfd55a CB |
288 | if (bind(fd, (struct sockaddr*)&handler->local, sizeof(handler->local)) < 0) |
289 | return ret_errno(errno); | |
0ad19a3f | 290 | |
d028235d | 291 | socklen = sizeof(handler->local); |
19bfd55a CB |
292 | if (getsockname(fd, (struct sockaddr*)&handler->local, &socklen) < 0) |
293 | return ret_errno(errno); | |
0ad19a3f | 294 | |
19bfd55a CB |
295 | if (socklen != sizeof(handler->local)) |
296 | return ret_errno(EINVAL); | |
0ad19a3f | 297 | |
19bfd55a CB |
298 | if (handler->local.nl_family != AF_NETLINK) |
299 | return ret_errno(EINVAL); | |
0ad19a3f | 300 | |
301 | handler->seq = time(NULL); | |
19bfd55a | 302 | handler->fd = move_fd(fd); |
d028235d | 303 | return 0; |
0ad19a3f | 304 | } |
305 | ||
d16bda44 | 306 | extern void netlink_close(struct nl_handler *handler) |
0ad19a3f | 307 | { |
d16bda44 | 308 | close_prot_errno_disarm(handler->fd); |
0ad19a3f | 309 | } |
310 | ||
cc6119a0 CB |
311 | int addattr(struct nlmsghdr *n, size_t maxlen, int type, const void *data, |
312 | size_t alen) | |
313 | { | |
314 | int len = RTA_LENGTH(alen); | |
315 | struct rtattr *rta; | |
316 | ||
cc6119a0 | 317 | if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) |
19bfd55a | 318 | return ret_errno(EMSGSIZE); |
cc6119a0 CB |
319 | |
320 | rta = NLMSG_TAIL(n); | |
321 | rta->rta_type = type; | |
322 | rta->rta_len = len; | |
323 | if (alen) | |
324 | memcpy(RTA_DATA(rta), data, alen); | |
325 | n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); | |
326 | ||
327 | return 0; | |
328 | } |