]>
Commit | Line | Data |
---|---|---|
aba5acdf SH |
1 | /* |
2 | * iplink.c "ip link". | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version | |
7 | * 2 of the License, or (at your option) any later version. | |
8 | * | |
9 | * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> | |
10 | * | |
11 | */ | |
12 | ||
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <unistd.h> | |
16 | #include <syslog.h> | |
17 | #include <fcntl.h> | |
1d934839 | 18 | #include <dlfcn.h> |
aba5acdf SH |
19 | #include <errno.h> |
20 | #include <sys/socket.h> | |
21 | #include <linux/if.h> | |
22 | #include <linux/if_packet.h> | |
23 | #include <linux/if_ether.h> | |
24 | #include <linux/sockios.h> | |
25 | #include <netinet/in.h> | |
26 | #include <arpa/inet.h> | |
27 | #include <string.h> | |
28 | #include <sys/ioctl.h> | |
29 | #include <linux/sockios.h> | |
30 | ||
31 | #include "rt_names.h" | |
32 | #include "utils.h" | |
33 | #include "ip_common.h" | |
34 | ||
1d934839 | 35 | #define IPLINK_IOCTL_COMPAT 1 |
5e3bb534 AH |
36 | #ifndef LIBDIR |
37 | #define LIBDIR "/usr/lib/" | |
b514b358 | 38 | #endif |
aba5acdf SH |
39 | |
40 | static void usage(void) __attribute__((noreturn)); | |
750a405a | 41 | static int iplink_have_newlink(void); |
aba5acdf SH |
42 | |
43 | void iplink_usage(void) | |
44 | { | |
750a405a SH |
45 | if (iplink_have_newlink()) { |
46 | fprintf(stderr, "Usage: ip link add link DEV [ name ] NAME\n"); | |
47 | fprintf(stderr, " [ txqueuelen PACKETS ]\n"); | |
48 | fprintf(stderr, " [ address LLADDR ]\n"); | |
49 | fprintf(stderr, " [ broadcast LLADDR ]\n"); | |
50 | fprintf(stderr, " [ mtu MTU ]\n"); | |
51 | fprintf(stderr, " type TYPE [ ARGS ]\n"); | |
52 | fprintf(stderr, " ip link delete DEV type TYPE [ ARGS ]\n"); | |
53 | fprintf(stderr, "\n"); | |
54 | fprintf(stderr, " ip link set DEVICE [ { up | down } ]\n"); | |
55 | } else | |
56 | fprintf(stderr, "Usage: ip link set DEVICE [ { up | down } ]\n"); | |
57 | ||
58 | fprintf(stderr, " [ arp { on | off } ]\n"); | |
59 | fprintf(stderr, " [ dynamic { on | off } ]\n"); | |
60 | fprintf(stderr, " [ multicast { on | off } ]\n"); | |
61 | fprintf(stderr, " [ allmulticast { on | off } ]\n"); | |
62 | fprintf(stderr, " [ promisc { on | off } ]\n"); | |
63 | fprintf(stderr, " [ trailers { on | off } ]\n"); | |
64 | fprintf(stderr, " [ txqueuelen PACKETS ]\n"); | |
65 | fprintf(stderr, " [ name NEWNAME ]\n"); | |
66 | fprintf(stderr, " [ address LLADDR ]\n"); | |
67 | fprintf(stderr, " [ broadcast LLADDR ]\n"); | |
68 | fprintf(stderr, " [ mtu MTU ]\n"); | |
69 | fprintf(stderr, " [ netns PID ]\n"); | |
ace9c961 | 70 | fprintf(stderr, " [ alias NAME ]\n"); |
ae7229d5 WM |
71 | fprintf(stderr, " [ vf NUM [ mac LLADDR ]\n"); |
72 | fprintf(stderr, " [ vlan VLANID [ qos VLAN-QOS ] ]\n"); | |
1598b9ef | 73 | fprintf(stderr, " [ rate TXRATE ] ] \n"); |
ac694c33 | 74 | fprintf(stderr, " ip link show [ DEVICE | group GROUP ]\n"); |
750a405a SH |
75 | |
76 | if (iplink_have_newlink()) { | |
77 | fprintf(stderr, "\n"); | |
5a204478 | 78 | fprintf(stderr, "TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | can }\n"); |
750a405a | 79 | } |
aba5acdf SH |
80 | exit(-1); |
81 | } | |
82 | ||
83 | static void usage(void) | |
84 | { | |
85 | iplink_usage(); | |
86 | } | |
87 | ||
88 | static int on_off(char *msg) | |
89 | { | |
90 | fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\"\n", msg); | |
91 | return -1; | |
92 | } | |
93 | ||
1d934839 PM |
94 | static void *BODY; /* cached dlopen(NULL) handle */ |
95 | static struct link_util *linkutil_list; | |
96 | ||
97 | struct link_util *get_link_kind(const char *id) | |
98 | { | |
99 | void *dlh; | |
100 | char buf[256]; | |
101 | struct link_util *l; | |
102 | ||
103 | for (l = linkutil_list; l; l = l->next) | |
104 | if (strcmp(l->id, id) == 0) | |
105 | return l; | |
106 | ||
5e3bb534 | 107 | snprintf(buf, sizeof(buf), LIBDIR "/ip/link_%s.so", id); |
1d934839 PM |
108 | dlh = dlopen(buf, RTLD_LAZY); |
109 | if (dlh == NULL) { | |
110 | /* look in current binary, only open once */ | |
111 | dlh = BODY; | |
112 | if (dlh == NULL) { | |
113 | dlh = BODY = dlopen(NULL, RTLD_LAZY); | |
114 | if (dlh == NULL) | |
115 | return NULL; | |
116 | } | |
117 | } | |
118 | ||
119 | snprintf(buf, sizeof(buf), "%s_link_util", id); | |
120 | l = dlsym(dlh, buf); | |
121 | if (l == NULL) | |
122 | return NULL; | |
123 | ||
124 | l->next = linkutil_list; | |
125 | linkutil_list = l; | |
126 | return l; | |
127 | } | |
128 | ||
129 | #if IPLINK_IOCTL_COMPAT | |
130 | static int have_rtnl_newlink = -1; | |
131 | ||
132 | static int accept_msg(const struct sockaddr_nl *who, | |
133 | struct nlmsghdr *n, void *arg) | |
134 | { | |
135 | struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n); | |
136 | ||
66e529f5 PM |
137 | if (n->nlmsg_type == NLMSG_ERROR && |
138 | (err->error == -EOPNOTSUPP || err->error == -EINVAL)) | |
1d934839 PM |
139 | have_rtnl_newlink = 0; |
140 | else | |
141 | have_rtnl_newlink = 1; | |
142 | return -1; | |
143 | } | |
144 | ||
145 | static int iplink_have_newlink(void) | |
146 | { | |
147 | struct { | |
148 | struct nlmsghdr n; | |
149 | struct ifinfomsg i; | |
150 | char buf[1024]; | |
151 | } req; | |
152 | ||
153 | if (have_rtnl_newlink < 0) { | |
154 | memset(&req, 0, sizeof(req)); | |
155 | ||
156 | req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); | |
157 | req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK; | |
158 | req.n.nlmsg_type = RTM_NEWLINK; | |
159 | req.i.ifi_family = AF_UNSPEC; | |
160 | ||
161 | rtnl_send(&rth, (char *)&req.n, req.n.nlmsg_len); | |
162 | rtnl_listen(&rth, accept_msg, NULL); | |
163 | } | |
164 | return have_rtnl_newlink; | |
165 | } | |
166 | #else /* IPLINK_IOCTL_COMPAT */ | |
167 | static int iplink_have_newlink(void) | |
168 | { | |
169 | return 1; | |
170 | } | |
171 | #endif /* ! IPLINK_IOCTL_COMPAT */ | |
172 | ||
909dfe2c PE |
173 | struct iplink_req { |
174 | struct nlmsghdr n; | |
175 | struct ifinfomsg i; | |
176 | char buf[1024]; | |
177 | }; | |
178 | ||
1598b9ef SH |
179 | int iplink_parse_vf(int vf, int *argcp, char ***argvp, |
180 | struct iplink_req *req) | |
3fd86630 CW |
181 | { |
182 | int len, argc = *argcp; | |
183 | char **argv = *argvp; | |
184 | struct rtattr *vfinfo; | |
1598b9ef SH |
185 | |
186 | vfinfo = addattr_nest(&req->n, sizeof(*req), IFLA_VF_INFO); | |
3fd86630 CW |
187 | |
188 | while (NEXT_ARG_OK()) { | |
189 | NEXT_ARG(); | |
1598b9ef SH |
190 | if (matches(*argv, "mac") == 0) { |
191 | struct ifla_vf_mac ivm; | |
3fd86630 | 192 | NEXT_ARG(); |
1598b9ef SH |
193 | ivm.vf = vf; |
194 | len = ll_addr_a2n((char *)ivm.mac, 32, *argv); | |
195 | if (len < 0) | |
196 | return -1; | |
197 | addattr_l(&req->n, sizeof(*req), IFLA_VF_MAC, &ivm, sizeof(ivm)); | |
3fd86630 | 198 | } else if (matches(*argv, "vlan") == 0) { |
1598b9ef | 199 | struct ifla_vf_vlan ivv; |
3fd86630 | 200 | NEXT_ARG(); |
1598b9ef SH |
201 | if (get_unsigned(&ivv.vlan, *argv, 0)) { |
202 | invarg("Invalid \"vlan\" value\n", *argv); | |
203 | } | |
204 | ivv.vf = vf; | |
205 | ivv.qos = 0; | |
3fd86630 CW |
206 | if (NEXT_ARG_OK()) { |
207 | NEXT_ARG(); | |
208 | if (matches(*argv, "qos") == 0) { | |
209 | NEXT_ARG(); | |
1598b9ef SH |
210 | if (get_unsigned(&ivv.qos, *argv, 0)) { |
211 | invarg("Invalid \"qos\" value\n", *argv); | |
212 | } | |
3fd86630 CW |
213 | } else { |
214 | /* rewind arg */ | |
215 | PREV_ARG(); | |
216 | } | |
217 | } | |
1598b9ef | 218 | addattr_l(&req->n, sizeof(*req), IFLA_VF_VLAN, &ivv, sizeof(ivv)); |
3fd86630 | 219 | } else if (matches(*argv, "rate") == 0) { |
1598b9ef | 220 | struct ifla_vf_tx_rate ivt; |
3fd86630 | 221 | NEXT_ARG(); |
1598b9ef SH |
222 | if (get_unsigned(&ivt.rate, *argv, 0)) { |
223 | invarg("Invalid \"rate\" value\n", *argv); | |
224 | } | |
225 | ivt.vf = vf; | |
226 | addattr_l(&req->n, sizeof(*req), IFLA_VF_TX_RATE, &ivt, sizeof(ivt)); | |
227 | ||
3fd86630 CW |
228 | } else { |
229 | /* rewind arg */ | |
230 | PREV_ARG(); | |
231 | break; | |
232 | } | |
233 | } | |
234 | ||
235 | if (argc == *argcp) | |
236 | incomplete_command(); | |
237 | ||
1598b9ef | 238 | addattr_nest_end(&req->n, vfinfo); |
3fd86630 CW |
239 | |
240 | *argcp = argc; | |
241 | *argvp = argv; | |
1598b9ef | 242 | return 0; |
3fd86630 CW |
243 | } |
244 | ||
245 | ||
909dfe2c PE |
246 | int iplink_parse(int argc, char **argv, struct iplink_req *req, |
247 | char **name, char **type, char **link, char **dev) | |
1d934839 | 248 | { |
909dfe2c PE |
249 | int ret, len; |
250 | char abuf[32]; | |
1d934839 PM |
251 | int qlen = -1; |
252 | int mtu = -1; | |
e2613dc8 | 253 | int netns = -1; |
ae7229d5 | 254 | int vf = -1; |
1d934839 | 255 | |
909dfe2c | 256 | ret = argc; |
1d934839 PM |
257 | |
258 | while (argc > 0) { | |
259 | if (strcmp(*argv, "up") == 0) { | |
909dfe2c PE |
260 | req->i.ifi_change |= IFF_UP; |
261 | req->i.ifi_flags |= IFF_UP; | |
1d934839 | 262 | } else if (strcmp(*argv, "down") == 0) { |
909dfe2c PE |
263 | req->i.ifi_change |= IFF_UP; |
264 | req->i.ifi_flags &= ~IFF_UP; | |
1d934839 PM |
265 | } else if (strcmp(*argv, "name") == 0) { |
266 | NEXT_ARG(); | |
909dfe2c | 267 | *name = *argv; |
1d934839 PM |
268 | } else if (matches(*argv, "link") == 0) { |
269 | NEXT_ARG(); | |
909dfe2c | 270 | *link = *argv; |
1d934839 PM |
271 | } else if (matches(*argv, "address") == 0) { |
272 | NEXT_ARG(); | |
273 | len = ll_addr_a2n(abuf, sizeof(abuf), *argv); | |
cb2eb999 AH |
274 | if (len < 0) |
275 | return -1; | |
909dfe2c | 276 | addattr_l(&req->n, sizeof(*req), IFLA_ADDRESS, abuf, len); |
1d934839 | 277 | } else if (matches(*argv, "broadcast") == 0 || |
909dfe2c | 278 | strcmp(*argv, "brd") == 0) { |
1d934839 PM |
279 | NEXT_ARG(); |
280 | len = ll_addr_a2n(abuf, sizeof(abuf), *argv); | |
cb2eb999 AH |
281 | if (len < 0) |
282 | return -1; | |
909dfe2c | 283 | addattr_l(&req->n, sizeof(*req), IFLA_BROADCAST, abuf, len); |
1d934839 | 284 | } else if (matches(*argv, "txqueuelen") == 0 || |
909dfe2c PE |
285 | strcmp(*argv, "qlen") == 0 || |
286 | matches(*argv, "txqlen") == 0) { | |
1d934839 PM |
287 | NEXT_ARG(); |
288 | if (qlen != -1) | |
289 | duparg("txqueuelen", *argv); | |
290 | if (get_integer(&qlen, *argv, 0)) | |
291 | invarg("Invalid \"txqueuelen\" value\n", *argv); | |
909dfe2c | 292 | addattr_l(&req->n, sizeof(*req), IFLA_TXQLEN, &qlen, 4); |
1d934839 PM |
293 | } else if (strcmp(*argv, "mtu") == 0) { |
294 | NEXT_ARG(); | |
295 | if (mtu != -1) | |
296 | duparg("mtu", *argv); | |
297 | if (get_integer(&mtu, *argv, 0)) | |
298 | invarg("Invalid \"mtu\" value\n", *argv); | |
909dfe2c | 299 | addattr_l(&req->n, sizeof(*req), IFLA_MTU, &mtu, 4); |
e2613dc8 BT |
300 | } else if (strcmp(*argv, "netns") == 0) { |
301 | NEXT_ARG(); | |
302 | if (netns != -1) | |
303 | duparg("netns", *argv); | |
304 | if (get_integer(&netns, *argv, 0)) | |
305 | invarg("Invalid \"netns\" value\n", *argv); | |
306 | addattr_l(&req->n, sizeof(*req), IFLA_NET_NS_PID, &netns, 4); | |
1d934839 PM |
307 | } else if (strcmp(*argv, "multicast") == 0) { |
308 | NEXT_ARG(); | |
909dfe2c | 309 | req->i.ifi_change |= IFF_MULTICAST; |
1d934839 | 310 | if (strcmp(*argv, "on") == 0) { |
909dfe2c | 311 | req->i.ifi_flags |= IFF_MULTICAST; |
1d934839 | 312 | } else if (strcmp(*argv, "off") == 0) { |
909dfe2c | 313 | req->i.ifi_flags &= ~IFF_MULTICAST; |
1d934839 PM |
314 | } else |
315 | return on_off("multicast"); | |
316 | } else if (strcmp(*argv, "allmulticast") == 0) { | |
317 | NEXT_ARG(); | |
909dfe2c | 318 | req->i.ifi_change |= IFF_ALLMULTI; |
1d934839 | 319 | if (strcmp(*argv, "on") == 0) { |
909dfe2c | 320 | req->i.ifi_flags |= IFF_ALLMULTI; |
1d934839 | 321 | } else if (strcmp(*argv, "off") == 0) { |
909dfe2c | 322 | req->i.ifi_flags &= ~IFF_ALLMULTI; |
1d934839 PM |
323 | } else |
324 | return on_off("allmulticast"); | |
325 | } else if (strcmp(*argv, "promisc") == 0) { | |
326 | NEXT_ARG(); | |
909dfe2c | 327 | req->i.ifi_change |= IFF_PROMISC; |
1d934839 | 328 | if (strcmp(*argv, "on") == 0) { |
909dfe2c | 329 | req->i.ifi_flags |= IFF_PROMISC; |
1d934839 | 330 | } else if (strcmp(*argv, "off") == 0) { |
909dfe2c | 331 | req->i.ifi_flags &= ~IFF_PROMISC; |
1d934839 PM |
332 | } else |
333 | return on_off("promisc"); | |
334 | } else if (strcmp(*argv, "trailers") == 0) { | |
335 | NEXT_ARG(); | |
909dfe2c | 336 | req->i.ifi_change |= IFF_NOTRAILERS; |
1d934839 | 337 | if (strcmp(*argv, "off") == 0) { |
909dfe2c | 338 | req->i.ifi_flags |= IFF_NOTRAILERS; |
1d934839 | 339 | } else if (strcmp(*argv, "on") == 0) { |
909dfe2c | 340 | req->i.ifi_flags &= ~IFF_NOTRAILERS; |
1d934839 PM |
341 | } else |
342 | return on_off("trailers"); | |
343 | } else if (strcmp(*argv, "arp") == 0) { | |
344 | NEXT_ARG(); | |
909dfe2c | 345 | req->i.ifi_change |= IFF_NOARP; |
1d934839 | 346 | if (strcmp(*argv, "on") == 0) { |
909dfe2c | 347 | req->i.ifi_flags &= ~IFF_NOARP; |
1d934839 | 348 | } else if (strcmp(*argv, "off") == 0) { |
909dfe2c | 349 | req->i.ifi_flags |= IFF_NOARP; |
1d934839 PM |
350 | } else |
351 | return on_off("noarp"); | |
ae7229d5 | 352 | } else if (strcmp(*argv, "vf") == 0) { |
1598b9ef | 353 | struct rtattr *vflist; |
ae7229d5 WM |
354 | NEXT_ARG(); |
355 | if (get_integer(&vf, *argv, 0)) { | |
356 | invarg("Invalid \"vf\" value\n", *argv); | |
357 | } | |
1598b9ef SH |
358 | vflist = addattr_nest(&req->n, sizeof(*req), |
359 | IFLA_VFINFO_LIST); | |
360 | len = iplink_parse_vf(vf, &argc, &argv, req); | |
361 | if (len < 0) | |
362 | return -1; | |
363 | addattr_nest_end(&req->n, vflist); | |
1d934839 PM |
364 | #ifdef IFF_DYNAMIC |
365 | } else if (matches(*argv, "dynamic") == 0) { | |
366 | NEXT_ARG(); | |
909dfe2c | 367 | req->i.ifi_change |= IFF_DYNAMIC; |
1d934839 | 368 | if (strcmp(*argv, "on") == 0) { |
909dfe2c | 369 | req->i.ifi_flags |= IFF_DYNAMIC; |
1d934839 | 370 | } else if (strcmp(*argv, "off") == 0) { |
909dfe2c | 371 | req->i.ifi_flags &= ~IFF_DYNAMIC; |
1d934839 PM |
372 | } else |
373 | return on_off("dynamic"); | |
374 | #endif | |
375 | } else if (matches(*argv, "type") == 0) { | |
376 | NEXT_ARG(); | |
909dfe2c | 377 | *type = *argv; |
1d934839 PM |
378 | argc--; argv++; |
379 | break; | |
ace9c961 SH |
380 | } else if (matches(*argv, "alias") == 0) { |
381 | NEXT_ARG(); | |
382 | addattr_l(&req->n, sizeof(*req), IFLA_IFALIAS, | |
383 | *argv, strlen(*argv)); | |
384 | argc--; argv++; | |
385 | break; | |
1d934839 | 386 | } else { |
909dfe2c | 387 | if (strcmp(*argv, "dev") == 0) { |
1d934839 PM |
388 | NEXT_ARG(); |
389 | } | |
05325552 PM |
390 | if (matches(*argv, "help") == 0) |
391 | usage(); | |
909dfe2c | 392 | if (*dev) |
1d934839 | 393 | duparg2("dev", *argv); |
909dfe2c | 394 | *dev = *argv; |
1d934839 PM |
395 | } |
396 | argc--; argv++; | |
397 | } | |
398 | ||
909dfe2c PE |
399 | return ret - argc; |
400 | } | |
401 | ||
402 | static int iplink_modify(int cmd, unsigned int flags, int argc, char **argv) | |
403 | { | |
404 | int len; | |
405 | char *dev = NULL; | |
406 | char *name = NULL; | |
407 | char *link = NULL; | |
408 | char *type = NULL; | |
409 | struct link_util *lu = NULL; | |
410 | struct iplink_req req; | |
411 | int ret; | |
412 | ||
413 | memset(&req, 0, sizeof(req)); | |
414 | ||
415 | req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); | |
416 | req.n.nlmsg_flags = NLM_F_REQUEST|flags; | |
417 | req.n.nlmsg_type = cmd; | |
418 | req.i.ifi_family = preferred_family; | |
419 | ||
420 | ret = iplink_parse(argc, argv, &req, &name, &type, &link, &dev); | |
421 | if (ret < 0) | |
422 | return ret; | |
423 | ||
424 | argc -= ret; | |
425 | argv += ret; | |
1d934839 PM |
426 | ll_init_map(&rth); |
427 | ||
428 | if (type) { | |
429 | struct rtattr *linkinfo = NLMSG_TAIL(&req.n); | |
430 | addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0); | |
431 | addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type, | |
432 | strlen(type)); | |
433 | ||
434 | lu = get_link_kind(type); | |
435 | if (lu && argc) { | |
436 | struct rtattr * data = NLMSG_TAIL(&req.n); | |
437 | addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0); | |
438 | ||
439 | if (lu->parse_opt && | |
440 | lu->parse_opt(lu, argc, argv, &req.n)) | |
441 | return -1; | |
442 | ||
443 | data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data; | |
444 | } else if (argc) { | |
445 | if (matches(*argv, "help") == 0) | |
446 | usage(); | |
447 | fprintf(stderr, "Garbage instead of arguments \"%s ...\". " | |
448 | "Try \"ip link help\".\n", *argv); | |
449 | return -1; | |
450 | } | |
451 | linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo; | |
3ef0c859 PM |
452 | } else if (flags & NLM_F_CREATE) { |
453 | fprintf(stderr, "Not enough information: \"type\" argument " | |
454 | "is required\n"); | |
455 | return -1; | |
1d934839 PM |
456 | } |
457 | ||
458 | if (!(flags & NLM_F_CREATE)) { | |
459 | if (!dev) { | |
460 | fprintf(stderr, "Not enough information: \"dev\" " | |
461 | "argument is required.\n"); | |
462 | exit(-1); | |
463 | } | |
464 | ||
465 | req.i.ifi_index = ll_name_to_index(dev); | |
466 | if (req.i.ifi_index == 0) { | |
467 | fprintf(stderr, "Cannot find device \"%s\"\n", dev); | |
468 | return -1; | |
469 | } | |
470 | } else { | |
471 | /* Allow "ip link add dev" and "ip link add name" */ | |
472 | if (!name) | |
473 | name = dev; | |
474 | ||
475 | if (link) { | |
476 | int ifindex; | |
477 | ||
478 | ifindex = ll_name_to_index(link); | |
479 | if (ifindex == 0) { | |
480 | fprintf(stderr, "Cannot find device \"%s\"\n", | |
481 | link); | |
482 | return -1; | |
483 | } | |
484 | addattr_l(&req.n, sizeof(req), IFLA_LINK, &ifindex, 4); | |
485 | } | |
486 | } | |
487 | ||
488 | if (name) { | |
489 | len = strlen(name) + 1; | |
ca78b0e7 PM |
490 | if (len == 1) |
491 | invarg("\"\" is not a valid device identifier\n", "name"); | |
1d934839 | 492 | if (len > IFNAMSIZ) |
ca78b0e7 | 493 | invarg("\"name\" too long\n", name); |
1d934839 PM |
494 | addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, len); |
495 | } | |
496 | ||
497 | if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) | |
498 | exit(2); | |
499 | ||
500 | return 0; | |
501 | } | |
502 | ||
503 | #if IPLINK_IOCTL_COMPAT | |
aba5acdf SH |
504 | static int get_ctl_fd(void) |
505 | { | |
506 | int s_errno; | |
507 | int fd; | |
508 | ||
509 | fd = socket(PF_INET, SOCK_DGRAM, 0); | |
510 | if (fd >= 0) | |
511 | return fd; | |
512 | s_errno = errno; | |
513 | fd = socket(PF_PACKET, SOCK_DGRAM, 0); | |
514 | if (fd >= 0) | |
515 | return fd; | |
516 | fd = socket(PF_INET6, SOCK_DGRAM, 0); | |
517 | if (fd >= 0) | |
518 | return fd; | |
519 | errno = s_errno; | |
520 | perror("Cannot create control socket"); | |
521 | return -1; | |
522 | } | |
523 | ||
71058eb8 | 524 | static int do_chflags(const char *dev, __u32 flags, __u32 mask) |
aba5acdf SH |
525 | { |
526 | struct ifreq ifr; | |
527 | int fd; | |
528 | int err; | |
529 | ||
71058eb8 | 530 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); |
aba5acdf SH |
531 | fd = get_ctl_fd(); |
532 | if (fd < 0) | |
533 | return -1; | |
534 | err = ioctl(fd, SIOCGIFFLAGS, &ifr); | |
535 | if (err) { | |
536 | perror("SIOCGIFFLAGS"); | |
537 | close(fd); | |
538 | return -1; | |
539 | } | |
540 | if ((ifr.ifr_flags^flags)&mask) { | |
541 | ifr.ifr_flags &= ~mask; | |
542 | ifr.ifr_flags |= mask&flags; | |
543 | err = ioctl(fd, SIOCSIFFLAGS, &ifr); | |
544 | if (err) | |
545 | perror("SIOCSIFFLAGS"); | |
546 | } | |
547 | close(fd); | |
548 | return err; | |
549 | } | |
550 | ||
71058eb8 | 551 | static int do_changename(const char *dev, const char *newdev) |
aba5acdf SH |
552 | { |
553 | struct ifreq ifr; | |
554 | int fd; | |
555 | int err; | |
556 | ||
71058eb8 SH |
557 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); |
558 | strncpy(ifr.ifr_newname, newdev, IFNAMSIZ); | |
aba5acdf SH |
559 | fd = get_ctl_fd(); |
560 | if (fd < 0) | |
561 | return -1; | |
562 | err = ioctl(fd, SIOCSIFNAME, &ifr); | |
563 | if (err) { | |
564 | perror("SIOCSIFNAME"); | |
565 | close(fd); | |
566 | return -1; | |
567 | } | |
568 | close(fd); | |
569 | return err; | |
570 | } | |
571 | ||
71058eb8 | 572 | static int set_qlen(const char *dev, int qlen) |
aba5acdf SH |
573 | { |
574 | struct ifreq ifr; | |
575 | int s; | |
576 | ||
577 | s = get_ctl_fd(); | |
578 | if (s < 0) | |
579 | return -1; | |
580 | ||
581 | memset(&ifr, 0, sizeof(ifr)); | |
ae665a52 SH |
582 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); |
583 | ifr.ifr_qlen = qlen; | |
aba5acdf SH |
584 | if (ioctl(s, SIOCSIFTXQLEN, &ifr) < 0) { |
585 | perror("SIOCSIFXQLEN"); | |
586 | close(s); | |
587 | return -1; | |
588 | } | |
589 | close(s); | |
590 | ||
ae665a52 | 591 | return 0; |
aba5acdf SH |
592 | } |
593 | ||
71058eb8 | 594 | static int set_mtu(const char *dev, int mtu) |
aba5acdf SH |
595 | { |
596 | struct ifreq ifr; | |
597 | int s; | |
598 | ||
599 | s = get_ctl_fd(); | |
600 | if (s < 0) | |
601 | return -1; | |
602 | ||
603 | memset(&ifr, 0, sizeof(ifr)); | |
ae665a52 SH |
604 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); |
605 | ifr.ifr_mtu = mtu; | |
aba5acdf SH |
606 | if (ioctl(s, SIOCSIFMTU, &ifr) < 0) { |
607 | perror("SIOCSIFMTU"); | |
608 | close(s); | |
609 | return -1; | |
610 | } | |
611 | close(s); | |
612 | ||
ae665a52 | 613 | return 0; |
aba5acdf SH |
614 | } |
615 | ||
71058eb8 | 616 | static int get_address(const char *dev, int *htype) |
aba5acdf SH |
617 | { |
618 | struct ifreq ifr; | |
619 | struct sockaddr_ll me; | |
f332d169 | 620 | socklen_t alen; |
aba5acdf SH |
621 | int s; |
622 | ||
623 | s = socket(PF_PACKET, SOCK_DGRAM, 0); | |
ae665a52 | 624 | if (s < 0) { |
aba5acdf SH |
625 | perror("socket(PF_PACKET)"); |
626 | return -1; | |
627 | } | |
628 | ||
629 | memset(&ifr, 0, sizeof(ifr)); | |
71058eb8 | 630 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); |
aba5acdf SH |
631 | if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { |
632 | perror("SIOCGIFINDEX"); | |
633 | close(s); | |
634 | return -1; | |
635 | } | |
636 | ||
637 | memset(&me, 0, sizeof(me)); | |
638 | me.sll_family = AF_PACKET; | |
639 | me.sll_ifindex = ifr.ifr_ifindex; | |
640 | me.sll_protocol = htons(ETH_P_LOOP); | |
641 | if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) { | |
642 | perror("bind"); | |
643 | close(s); | |
644 | return -1; | |
645 | } | |
646 | ||
647 | alen = sizeof(me); | |
648 | if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) { | |
649 | perror("getsockname"); | |
650 | close(s); | |
651 | return -1; | |
652 | } | |
653 | close(s); | |
654 | *htype = me.sll_hatype; | |
655 | return me.sll_halen; | |
656 | } | |
657 | ||
ae665a52 | 658 | static int parse_address(const char *dev, int hatype, int halen, |
7b565754 | 659 | char *lla, struct ifreq *ifr) |
aba5acdf SH |
660 | { |
661 | int alen; | |
662 | ||
663 | memset(ifr, 0, sizeof(*ifr)); | |
71058eb8 | 664 | strncpy(ifr->ifr_name, dev, IFNAMSIZ); |
aba5acdf SH |
665 | ifr->ifr_hwaddr.sa_family = hatype; |
666 | alen = ll_addr_a2n(ifr->ifr_hwaddr.sa_data, 14, lla); | |
667 | if (alen < 0) | |
668 | return -1; | |
669 | if (alen != halen) { | |
670 | fprintf(stderr, "Wrong address (%s) length: expected %d bytes\n", lla, halen); | |
671 | return -1; | |
672 | } | |
ae665a52 | 673 | return 0; |
aba5acdf SH |
674 | } |
675 | ||
676 | static int set_address(struct ifreq *ifr, int brd) | |
677 | { | |
678 | int s; | |
679 | ||
680 | s = get_ctl_fd(); | |
681 | if (s < 0) | |
682 | return -1; | |
683 | if (ioctl(s, brd?SIOCSIFHWBROADCAST:SIOCSIFHWADDR, ifr) < 0) { | |
684 | perror(brd?"SIOCSIFHWBROADCAST":"SIOCSIFHWADDR"); | |
685 | close(s); | |
686 | return -1; | |
687 | } | |
688 | close(s); | |
ae665a52 | 689 | return 0; |
aba5acdf SH |
690 | } |
691 | ||
692 | ||
693 | static int do_set(int argc, char **argv) | |
694 | { | |
695 | char *dev = NULL; | |
696 | __u32 mask = 0; | |
697 | __u32 flags = 0; | |
698 | int qlen = -1; | |
699 | int mtu = -1; | |
700 | char *newaddr = NULL; | |
701 | char *newbrd = NULL; | |
702 | struct ifreq ifr0, ifr1; | |
703 | char *newname = NULL; | |
704 | int htype, halen; | |
705 | ||
706 | while (argc > 0) { | |
707 | if (strcmp(*argv, "up") == 0) { | |
708 | mask |= IFF_UP; | |
709 | flags |= IFF_UP; | |
710 | } else if (strcmp(*argv, "down") == 0) { | |
711 | mask |= IFF_UP; | |
712 | flags &= ~IFF_UP; | |
713 | } else if (strcmp(*argv, "name") == 0) { | |
714 | NEXT_ARG(); | |
715 | newname = *argv; | |
716 | } else if (matches(*argv, "address") == 0) { | |
717 | NEXT_ARG(); | |
718 | newaddr = *argv; | |
719 | } else if (matches(*argv, "broadcast") == 0 || | |
720 | strcmp(*argv, "brd") == 0) { | |
721 | NEXT_ARG(); | |
722 | newbrd = *argv; | |
723 | } else if (matches(*argv, "txqueuelen") == 0 || | |
724 | strcmp(*argv, "qlen") == 0 || | |
725 | matches(*argv, "txqlen") == 0) { | |
726 | NEXT_ARG(); | |
727 | if (qlen != -1) | |
728 | duparg("txqueuelen", *argv); | |
729 | if (get_integer(&qlen, *argv, 0)) | |
730 | invarg("Invalid \"txqueuelen\" value\n", *argv); | |
731 | } else if (strcmp(*argv, "mtu") == 0) { | |
732 | NEXT_ARG(); | |
733 | if (mtu != -1) | |
734 | duparg("mtu", *argv); | |
735 | if (get_integer(&mtu, *argv, 0)) | |
736 | invarg("Invalid \"mtu\" value\n", *argv); | |
737 | } else if (strcmp(*argv, "multicast") == 0) { | |
738 | NEXT_ARG(); | |
739 | mask |= IFF_MULTICAST; | |
740 | if (strcmp(*argv, "on") == 0) { | |
741 | flags |= IFF_MULTICAST; | |
742 | } else if (strcmp(*argv, "off") == 0) { | |
743 | flags &= ~IFF_MULTICAST; | |
744 | } else | |
745 | return on_off("multicast"); | |
d27b1b5b | 746 | } else if (strcmp(*argv, "allmulticast") == 0) { |
747 | NEXT_ARG(); | |
748 | mask |= IFF_ALLMULTI; | |
749 | if (strcmp(*argv, "on") == 0) { | |
750 | flags |= IFF_ALLMULTI; | |
751 | } else if (strcmp(*argv, "off") == 0) { | |
752 | flags &= ~IFF_ALLMULTI; | |
753 | } else | |
754 | return on_off("allmulticast"); | |
755 | } else if (strcmp(*argv, "promisc") == 0) { | |
756 | NEXT_ARG(); | |
757 | mask |= IFF_PROMISC; | |
758 | if (strcmp(*argv, "on") == 0) { | |
759 | flags |= IFF_PROMISC; | |
760 | } else if (strcmp(*argv, "off") == 0) { | |
761 | flags &= ~IFF_PROMISC; | |
762 | } else | |
763 | return on_off("promisc"); | |
764 | } else if (strcmp(*argv, "trailers") == 0) { | |
765 | NEXT_ARG(); | |
766 | mask |= IFF_NOTRAILERS; | |
767 | if (strcmp(*argv, "off") == 0) { | |
768 | flags |= IFF_NOTRAILERS; | |
769 | } else if (strcmp(*argv, "on") == 0) { | |
770 | flags &= ~IFF_NOTRAILERS; | |
771 | } else | |
772 | return on_off("trailers"); | |
aba5acdf SH |
773 | } else if (strcmp(*argv, "arp") == 0) { |
774 | NEXT_ARG(); | |
775 | mask |= IFF_NOARP; | |
776 | if (strcmp(*argv, "on") == 0) { | |
777 | flags &= ~IFF_NOARP; | |
778 | } else if (strcmp(*argv, "off") == 0) { | |
779 | flags |= IFF_NOARP; | |
780 | } else | |
781 | return on_off("noarp"); | |
782 | #ifdef IFF_DYNAMIC | |
783 | } else if (matches(*argv, "dynamic") == 0) { | |
784 | NEXT_ARG(); | |
785 | mask |= IFF_DYNAMIC; | |
786 | if (strcmp(*argv, "on") == 0) { | |
787 | flags |= IFF_DYNAMIC; | |
788 | } else if (strcmp(*argv, "off") == 0) { | |
789 | flags &= ~IFF_DYNAMIC; | |
790 | } else | |
791 | return on_off("dynamic"); | |
792 | #endif | |
793 | } else { | |
794 | if (strcmp(*argv, "dev") == 0) { | |
795 | NEXT_ARG(); | |
796 | } | |
797 | if (matches(*argv, "help") == 0) | |
798 | usage(); | |
799 | if (dev) | |
800 | duparg2("dev", *argv); | |
801 | dev = *argv; | |
802 | } | |
803 | argc--; argv++; | |
804 | } | |
805 | ||
806 | if (!dev) { | |
807 | fprintf(stderr, "Not enough of information: \"dev\" argument is required.\n"); | |
808 | exit(-1); | |
809 | } | |
810 | ||
811 | if (newaddr || newbrd) { | |
812 | halen = get_address(dev, &htype); | |
813 | if (halen < 0) | |
814 | return -1; | |
815 | if (newaddr) { | |
816 | if (parse_address(dev, htype, halen, newaddr, &ifr0) < 0) | |
817 | return -1; | |
818 | } | |
819 | if (newbrd) { | |
820 | if (parse_address(dev, htype, halen, newbrd, &ifr1) < 0) | |
ae665a52 | 821 | return -1; |
aba5acdf SH |
822 | } |
823 | } | |
824 | ||
825 | if (newname && strcmp(dev, newname)) { | |
ca78b0e7 PM |
826 | if (strlen(newname) == 0) |
827 | invarg("\"\" is not a valid device identifier\n", "name"); | |
aba5acdf SH |
828 | if (do_changename(dev, newname) < 0) |
829 | return -1; | |
830 | dev = newname; | |
831 | } | |
ae665a52 | 832 | if (qlen != -1) { |
aba5acdf | 833 | if (set_qlen(dev, qlen) < 0) |
ae665a52 | 834 | return -1; |
aba5acdf | 835 | } |
ae665a52 | 836 | if (mtu != -1) { |
aba5acdf | 837 | if (set_mtu(dev, mtu) < 0) |
ae665a52 | 838 | return -1; |
aba5acdf SH |
839 | } |
840 | if (newaddr || newbrd) { | |
841 | if (newbrd) { | |
842 | if (set_address(&ifr1, 1) < 0) | |
ae665a52 | 843 | return -1; |
aba5acdf SH |
844 | } |
845 | if (newaddr) { | |
846 | if (set_address(&ifr0, 0) < 0) | |
847 | return -1; | |
848 | } | |
849 | } | |
850 | if (mask) | |
851 | return do_chflags(dev, flags, mask); | |
852 | return 0; | |
853 | } | |
1d934839 | 854 | #endif /* IPLINK_IOCTL_COMPAT */ |
aba5acdf SH |
855 | |
856 | int do_iplink(int argc, char **argv) | |
857 | { | |
858 | if (argc > 0) { | |
1d934839 PM |
859 | if (iplink_have_newlink()) { |
860 | if (matches(*argv, "add") == 0) | |
861 | return iplink_modify(RTM_NEWLINK, | |
862 | NLM_F_CREATE|NLM_F_EXCL, | |
863 | argc-1, argv+1); | |
864 | if (matches(*argv, "set") == 0 || | |
865 | matches(*argv, "change") == 0) | |
866 | return iplink_modify(RTM_NEWLINK, 0, | |
867 | argc-1, argv+1); | |
868 | if (matches(*argv, "replace") == 0) | |
869 | return iplink_modify(RTM_NEWLINK, | |
870 | NLM_F_CREATE|NLM_F_REPLACE, | |
871 | argc-1, argv+1); | |
872 | if (matches(*argv, "delete") == 0) | |
873 | return iplink_modify(RTM_DELLINK, 0, | |
874 | argc-1, argv+1); | |
875 | } else { | |
876 | #if IPLINK_IOCTL_COMPAT | |
877 | if (matches(*argv, "set") == 0) | |
878 | return do_set(argc-1, argv+1); | |
879 | #endif | |
880 | } | |
aba5acdf SH |
881 | if (matches(*argv, "show") == 0 || |
882 | matches(*argv, "lst") == 0 || | |
883 | matches(*argv, "list") == 0) | |
884 | return ipaddr_list_link(argc-1, argv+1); | |
885 | if (matches(*argv, "help") == 0) | |
886 | usage(); | |
887 | } else | |
888 | return ipaddr_list_link(0, NULL); | |
889 | ||
890 | fprintf(stderr, "Command \"%s\" is unknown, try \"ip link help\".\n", *argv); | |
891 | exit(-1); | |
892 | } |