iplink.c revision ba371d989038e0dda803528c194e12e5177f7008
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>
18#include <dlfcn.h>
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
35#define IPLINK_IOCTL_COMPAT	1
36
37static void usage(void) __attribute__((noreturn));
38
39void iplink_usage(void)
40{
41	fprintf(stderr, "Usage: ip link set DEVICE { up | down |\n");
42	fprintf(stderr, "	                     arp { on | off } |\n");
43	fprintf(stderr, "	                     dynamic { on | off } |\n");
44	fprintf(stderr, "	                     multicast { on | off } |\n");
45	fprintf(stderr, "	                     allmulticast { on | off } |\n");
46	fprintf(stderr, "	                     promisc { on | off } |\n");
47	fprintf(stderr, "	                     trailers { on | off } |\n");
48	fprintf(stderr, "	                     txqueuelen PACKETS |\n");
49	fprintf(stderr, "	                     name NEWNAME |\n");
50	fprintf(stderr, "	                     address LLADDR | broadcast LLADDR |\n");
51	fprintf(stderr, "	                     mtu MTU }\n");
52	fprintf(stderr, "       ip link show [ DEVICE ]\n");
53	exit(-1);
54}
55
56static void usage(void)
57{
58	iplink_usage();
59}
60
61static int on_off(char *msg)
62{
63	fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\"\n", msg);
64	return -1;
65}
66
67static void *BODY;		/* cached dlopen(NULL) handle */
68static struct link_util *linkutil_list;
69
70struct link_util *get_link_kind(const char *id)
71{
72	void *dlh;
73	char buf[256];
74	struct link_util *l;
75
76	for (l = linkutil_list; l; l = l->next)
77		if (strcmp(l->id, id) == 0)
78			return l;
79
80	snprintf(buf, sizeof(buf), "/usr/lib/ip/link_%s.so", id);
81	dlh = dlopen(buf, RTLD_LAZY);
82	if (dlh == NULL) {
83		/* look in current binary, only open once */
84		dlh = BODY;
85		if (dlh == NULL) {
86			dlh = BODY = dlopen(NULL, RTLD_LAZY);
87			if (dlh == NULL)
88				return NULL;
89		}
90	}
91
92	snprintf(buf, sizeof(buf), "%s_link_util", id);
93	l = dlsym(dlh, buf);
94	if (l == NULL)
95		return NULL;
96
97	l->next = linkutil_list;
98	linkutil_list = l;
99	return l;
100}
101
102#if IPLINK_IOCTL_COMPAT
103static int have_rtnl_newlink = -1;
104
105static int accept_msg(const struct sockaddr_nl *who,
106		      struct nlmsghdr *n, void *arg)
107{
108	struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n);
109
110	if (n->nlmsg_type == NLMSG_ERROR && err->error == -EOPNOTSUPP)
111		have_rtnl_newlink = 0;
112	else
113		have_rtnl_newlink = 1;
114	return -1;
115}
116
117static int iplink_have_newlink(void)
118{
119	struct {
120		struct nlmsghdr		n;
121		struct ifinfomsg	i;
122		char			buf[1024];
123	} req;
124
125	if (have_rtnl_newlink < 0) {
126		memset(&req, 0, sizeof(req));
127
128		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
129		req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK;
130		req.n.nlmsg_type = RTM_NEWLINK;
131		req.i.ifi_family = AF_UNSPEC;
132
133		rtnl_send(&rth, (char *)&req.n, req.n.nlmsg_len);
134		rtnl_listen(&rth, accept_msg, NULL);
135	}
136	return have_rtnl_newlink;
137}
138#else /* IPLINK_IOCTL_COMPAT */
139static int iplink_have_newlink(void)
140{
141	return 1;
142}
143#endif /* ! IPLINK_IOCTL_COMPAT */
144
145static int iplink_modify(int cmd, unsigned int flags, int argc, char **argv)
146{
147	int qlen = -1;
148	int mtu = -1;
149	int len;
150	char abuf[32];
151	char *dev = NULL;
152	char *name = NULL;
153	char *link = NULL;
154	char *type = NULL;
155	struct link_util *lu = NULL;
156	struct {
157		struct nlmsghdr		n;
158		struct ifinfomsg	i;
159		char			buf[1024];
160	} req;
161
162	memset(&req, 0, sizeof(req));
163
164	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
165	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
166	req.n.nlmsg_type = cmd;
167	req.i.ifi_family = preferred_family;
168
169	while (argc > 0) {
170		if (strcmp(*argv, "up") == 0) {
171			req.i.ifi_change |= IFF_UP;
172			req.i.ifi_flags |= IFF_UP;
173		} else if (strcmp(*argv, "down") == 0) {
174			req.i.ifi_change |= IFF_UP;
175			req.i.ifi_flags &= ~IFF_UP;
176		} else if (strcmp(*argv, "name") == 0) {
177			NEXT_ARG();
178			name = *argv;
179		} else if (matches(*argv, "link") == 0) {
180			NEXT_ARG();
181			link = *argv;
182		} else if (matches(*argv, "address") == 0) {
183			NEXT_ARG();
184			len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
185			addattr_l(&req.n, sizeof(req), IFLA_ADDRESS, abuf, len);
186		} else if (matches(*argv, "broadcast") == 0 ||
187			   strcmp(*argv, "brd") == 0) {
188			NEXT_ARG();
189			len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
190			addattr_l(&req.n, sizeof(req), IFLA_BROADCAST, abuf, len);
191		} else if (matches(*argv, "txqueuelen") == 0 ||
192			   strcmp(*argv, "qlen") == 0 ||
193			   matches(*argv, "txqlen") == 0) {
194			NEXT_ARG();
195			if (qlen != -1)
196				duparg("txqueuelen", *argv);
197			if (get_integer(&qlen,  *argv, 0))
198				invarg("Invalid \"txqueuelen\" value\n", *argv);
199			addattr_l(&req.n, sizeof(req), IFLA_TXQLEN, &qlen, 4);
200		} else if (strcmp(*argv, "mtu") == 0) {
201			NEXT_ARG();
202			if (mtu != -1)
203				duparg("mtu", *argv);
204			if (get_integer(&mtu, *argv, 0))
205				invarg("Invalid \"mtu\" value\n", *argv);
206			addattr_l(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);
207		} else if (strcmp(*argv, "multicast") == 0) {
208			NEXT_ARG();
209			req.i.ifi_change |= IFF_MULTICAST;
210			if (strcmp(*argv, "on") == 0) {
211				req.i.ifi_flags |= IFF_MULTICAST;
212			} else if (strcmp(*argv, "off") == 0) {
213				req.i.ifi_flags &= ~IFF_MULTICAST;
214			} else
215				return on_off("multicast");
216		} else if (strcmp(*argv, "allmulticast") == 0) {
217			NEXT_ARG();
218			req.i.ifi_change |= IFF_ALLMULTI;
219			if (strcmp(*argv, "on") == 0) {
220				req.i.ifi_flags |= IFF_ALLMULTI;
221			} else if (strcmp(*argv, "off") == 0) {
222				req.i.ifi_flags &= ~IFF_ALLMULTI;
223			} else
224				return on_off("allmulticast");
225		} else if (strcmp(*argv, "promisc") == 0) {
226			NEXT_ARG();
227			req.i.ifi_change |= IFF_PROMISC;
228			if (strcmp(*argv, "on") == 0) {
229				req.i.ifi_flags |= IFF_PROMISC;
230			} else if (strcmp(*argv, "off") == 0) {
231				req.i.ifi_flags &= ~IFF_PROMISC;
232			} else
233				return on_off("promisc");
234		} else if (strcmp(*argv, "trailers") == 0) {
235			NEXT_ARG();
236			req.i.ifi_change |= IFF_NOTRAILERS;
237			if (strcmp(*argv, "off") == 0) {
238				req.i.ifi_flags |= IFF_NOTRAILERS;
239			} else if (strcmp(*argv, "on") == 0) {
240				req.i.ifi_flags &= ~IFF_NOTRAILERS;
241			} else
242				return on_off("trailers");
243		} else if (strcmp(*argv, "arp") == 0) {
244			NEXT_ARG();
245			req.i.ifi_change |= IFF_NOARP;
246			if (strcmp(*argv, "on") == 0) {
247				req.i.ifi_flags &= ~IFF_NOARP;
248			} else if (strcmp(*argv, "off") == 0) {
249				req.i.ifi_flags |= IFF_NOARP;
250			} else
251				return on_off("noarp");
252#ifdef IFF_DYNAMIC
253		} else if (matches(*argv, "dynamic") == 0) {
254			NEXT_ARG();
255			req.i.ifi_change |= IFF_DYNAMIC;
256			if (strcmp(*argv, "on") == 0) {
257				req.i.ifi_flags |= IFF_DYNAMIC;
258			} else if (strcmp(*argv, "off") == 0) {
259				req.i.ifi_flags &= ~IFF_DYNAMIC;
260			} else
261				return on_off("dynamic");
262#endif
263		} else if (matches(*argv, "type") == 0) {
264			NEXT_ARG();
265			type = *argv;
266			argc--; argv++;
267			break;
268		} else {
269                        if (strcmp(*argv, "dev") == 0) {
270				NEXT_ARG();
271			}
272			if (dev)
273				duparg2("dev", *argv);
274			dev = *argv;
275		}
276		argc--; argv++;
277	}
278
279	ll_init_map(&rth);
280
281	if (type) {
282		struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
283		addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
284		addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type,
285			 strlen(type));
286
287		lu = get_link_kind(type);
288		if (lu && argc) {
289			struct rtattr * data = NLMSG_TAIL(&req.n);
290			addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
291
292			if (lu->parse_opt &&
293			    lu->parse_opt(lu, argc, argv, &req.n))
294				return -1;
295
296			data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
297		} else if (argc) {
298			if (matches(*argv, "help") == 0)
299				usage();
300			fprintf(stderr, "Garbage instead of arguments \"%s ...\". "
301					"Try \"ip link help\".\n", *argv);
302			return -1;
303		}
304		linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;
305	}
306
307	if (!(flags & NLM_F_CREATE)) {
308		if (!dev) {
309			fprintf(stderr, "Not enough information: \"dev\" "
310					"argument is required.\n");
311			exit(-1);
312		}
313
314		req.i.ifi_index = ll_name_to_index(dev);
315		if (req.i.ifi_index == 0) {
316			fprintf(stderr, "Cannot find device \"%s\"\n", dev);
317			return -1;
318		}
319	} else {
320		/* Allow "ip link add dev" and "ip link add name" */
321		if (!name)
322			name = dev;
323
324		if (link) {
325			int ifindex;
326
327			ifindex = ll_name_to_index(link);
328			if (ifindex == 0) {
329				fprintf(stderr, "Cannot find device \"%s\"\n",
330					link);
331				return -1;
332			}
333			addattr_l(&req.n, sizeof(req), IFLA_LINK, &ifindex, 4);
334		}
335	}
336
337	if (name) {
338		len = strlen(name) + 1;
339		if (len == 1)
340			invarg("null string is not valid name\n");
341		if (len > IFNAMSIZ)
342			invarg("\"name\" too long\n", *argv);
343		addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, len);
344	}
345
346	if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
347		exit(2);
348
349	return 0;
350}
351
352#if IPLINK_IOCTL_COMPAT
353static int get_ctl_fd(void)
354{
355	int s_errno;
356	int fd;
357
358	fd = socket(PF_INET, SOCK_DGRAM, 0);
359	if (fd >= 0)
360		return fd;
361	s_errno = errno;
362	fd = socket(PF_PACKET, SOCK_DGRAM, 0);
363	if (fd >= 0)
364		return fd;
365	fd = socket(PF_INET6, SOCK_DGRAM, 0);
366	if (fd >= 0)
367		return fd;
368	errno = s_errno;
369	perror("Cannot create control socket");
370	return -1;
371}
372
373static int do_chflags(const char *dev, __u32 flags, __u32 mask)
374{
375	struct ifreq ifr;
376	int fd;
377	int err;
378
379	strncpy(ifr.ifr_name, dev, IFNAMSIZ);
380	fd = get_ctl_fd();
381	if (fd < 0)
382		return -1;
383	err = ioctl(fd, SIOCGIFFLAGS, &ifr);
384	if (err) {
385		perror("SIOCGIFFLAGS");
386		close(fd);
387		return -1;
388	}
389	if ((ifr.ifr_flags^flags)&mask) {
390		ifr.ifr_flags &= ~mask;
391		ifr.ifr_flags |= mask&flags;
392		err = ioctl(fd, SIOCSIFFLAGS, &ifr);
393		if (err)
394			perror("SIOCSIFFLAGS");
395	}
396	close(fd);
397	return err;
398}
399
400static int do_changename(const char *dev, const char *newdev)
401{
402	struct ifreq ifr;
403	int fd;
404	int err;
405
406	strncpy(ifr.ifr_name, dev, IFNAMSIZ);
407	strncpy(ifr.ifr_newname, newdev, IFNAMSIZ);
408	fd = get_ctl_fd();
409	if (fd < 0)
410		return -1;
411	err = ioctl(fd, SIOCSIFNAME, &ifr);
412	if (err) {
413		perror("SIOCSIFNAME");
414		close(fd);
415		return -1;
416	}
417	close(fd);
418	return err;
419}
420
421static int set_qlen(const char *dev, int qlen)
422{
423	struct ifreq ifr;
424	int s;
425
426	s = get_ctl_fd();
427	if (s < 0)
428		return -1;
429
430	memset(&ifr, 0, sizeof(ifr));
431	strncpy(ifr.ifr_name, dev, IFNAMSIZ);
432	ifr.ifr_qlen = qlen;
433	if (ioctl(s, SIOCSIFTXQLEN, &ifr) < 0) {
434		perror("SIOCSIFXQLEN");
435		close(s);
436		return -1;
437	}
438	close(s);
439
440	return 0;
441}
442
443static int set_mtu(const char *dev, int mtu)
444{
445	struct ifreq ifr;
446	int s;
447
448	s = get_ctl_fd();
449	if (s < 0)
450		return -1;
451
452	memset(&ifr, 0, sizeof(ifr));
453	strncpy(ifr.ifr_name, dev, IFNAMSIZ);
454	ifr.ifr_mtu = mtu;
455	if (ioctl(s, SIOCSIFMTU, &ifr) < 0) {
456		perror("SIOCSIFMTU");
457		close(s);
458		return -1;
459	}
460	close(s);
461
462	return 0;
463}
464
465static int get_address(const char *dev, int *htype)
466{
467	struct ifreq ifr;
468	struct sockaddr_ll me;
469	socklen_t alen;
470	int s;
471
472	s = socket(PF_PACKET, SOCK_DGRAM, 0);
473	if (s < 0) {
474		perror("socket(PF_PACKET)");
475		return -1;
476	}
477
478	memset(&ifr, 0, sizeof(ifr));
479	strncpy(ifr.ifr_name, dev, IFNAMSIZ);
480	if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
481		perror("SIOCGIFINDEX");
482		close(s);
483		return -1;
484	}
485
486	memset(&me, 0, sizeof(me));
487	me.sll_family = AF_PACKET;
488	me.sll_ifindex = ifr.ifr_ifindex;
489	me.sll_protocol = htons(ETH_P_LOOP);
490	if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) {
491		perror("bind");
492		close(s);
493		return -1;
494	}
495
496	alen = sizeof(me);
497	if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) {
498		perror("getsockname");
499		close(s);
500		return -1;
501	}
502	close(s);
503	*htype = me.sll_hatype;
504	return me.sll_halen;
505}
506
507static int parse_address(const char *dev, int hatype, int halen,
508		char *lla, struct ifreq *ifr)
509{
510	int alen;
511
512	memset(ifr, 0, sizeof(*ifr));
513	strncpy(ifr->ifr_name, dev, IFNAMSIZ);
514	ifr->ifr_hwaddr.sa_family = hatype;
515	alen = ll_addr_a2n(ifr->ifr_hwaddr.sa_data, 14, lla);
516	if (alen < 0)
517		return -1;
518	if (alen != halen) {
519		fprintf(stderr, "Wrong address (%s) length: expected %d bytes\n", lla, halen);
520		return -1;
521	}
522	return 0;
523}
524
525static int set_address(struct ifreq *ifr, int brd)
526{
527	int s;
528
529	s = get_ctl_fd();
530	if (s < 0)
531		return -1;
532	if (ioctl(s, brd?SIOCSIFHWBROADCAST:SIOCSIFHWADDR, ifr) < 0) {
533		perror(brd?"SIOCSIFHWBROADCAST":"SIOCSIFHWADDR");
534		close(s);
535		return -1;
536	}
537	close(s);
538	return 0;
539}
540
541
542static int do_set(int argc, char **argv)
543{
544	char *dev = NULL;
545	__u32 mask = 0;
546	__u32 flags = 0;
547	int qlen = -1;
548	int mtu = -1;
549	char *newaddr = NULL;
550	char *newbrd = NULL;
551	struct ifreq ifr0, ifr1;
552	char *newname = NULL;
553	int htype, halen;
554
555	while (argc > 0) {
556		if (strcmp(*argv, "up") == 0) {
557			mask |= IFF_UP;
558			flags |= IFF_UP;
559		} else if (strcmp(*argv, "down") == 0) {
560			mask |= IFF_UP;
561			flags &= ~IFF_UP;
562		} else if (strcmp(*argv, "name") == 0) {
563			NEXT_ARG();
564			newname = *argv;
565		} else if (matches(*argv, "address") == 0) {
566			NEXT_ARG();
567			newaddr = *argv;
568		} else if (matches(*argv, "broadcast") == 0 ||
569			   strcmp(*argv, "brd") == 0) {
570			NEXT_ARG();
571			newbrd = *argv;
572		} else if (matches(*argv, "txqueuelen") == 0 ||
573			   strcmp(*argv, "qlen") == 0 ||
574			   matches(*argv, "txqlen") == 0) {
575			NEXT_ARG();
576			if (qlen != -1)
577				duparg("txqueuelen", *argv);
578			if (get_integer(&qlen,  *argv, 0))
579				invarg("Invalid \"txqueuelen\" value\n", *argv);
580		} else if (strcmp(*argv, "mtu") == 0) {
581			NEXT_ARG();
582			if (mtu != -1)
583				duparg("mtu", *argv);
584			if (get_integer(&mtu, *argv, 0))
585				invarg("Invalid \"mtu\" value\n", *argv);
586		} else if (strcmp(*argv, "multicast") == 0) {
587			NEXT_ARG();
588			mask |= IFF_MULTICAST;
589			if (strcmp(*argv, "on") == 0) {
590				flags |= IFF_MULTICAST;
591			} else if (strcmp(*argv, "off") == 0) {
592				flags &= ~IFF_MULTICAST;
593			} else
594				return on_off("multicast");
595		} else if (strcmp(*argv, "allmulticast") == 0) {
596			NEXT_ARG();
597			mask |= IFF_ALLMULTI;
598			if (strcmp(*argv, "on") == 0) {
599				flags |= IFF_ALLMULTI;
600			} else if (strcmp(*argv, "off") == 0) {
601				flags &= ~IFF_ALLMULTI;
602			} else
603				return on_off("allmulticast");
604		} else if (strcmp(*argv, "promisc") == 0) {
605			NEXT_ARG();
606			mask |= IFF_PROMISC;
607			if (strcmp(*argv, "on") == 0) {
608				flags |= IFF_PROMISC;
609			} else if (strcmp(*argv, "off") == 0) {
610				flags &= ~IFF_PROMISC;
611			} else
612				return on_off("promisc");
613		} else if (strcmp(*argv, "trailers") == 0) {
614			NEXT_ARG();
615			mask |= IFF_NOTRAILERS;
616			if (strcmp(*argv, "off") == 0) {
617				flags |= IFF_NOTRAILERS;
618			} else if (strcmp(*argv, "on") == 0) {
619				flags &= ~IFF_NOTRAILERS;
620			} else
621				return on_off("trailers");
622		} else if (strcmp(*argv, "arp") == 0) {
623			NEXT_ARG();
624			mask |= IFF_NOARP;
625			if (strcmp(*argv, "on") == 0) {
626				flags &= ~IFF_NOARP;
627			} else if (strcmp(*argv, "off") == 0) {
628				flags |= IFF_NOARP;
629			} else
630				return on_off("noarp");
631#ifdef IFF_DYNAMIC
632		} else if (matches(*argv, "dynamic") == 0) {
633			NEXT_ARG();
634			mask |= IFF_DYNAMIC;
635			if (strcmp(*argv, "on") == 0) {
636				flags |= IFF_DYNAMIC;
637			} else if (strcmp(*argv, "off") == 0) {
638				flags &= ~IFF_DYNAMIC;
639			} else
640				return on_off("dynamic");
641#endif
642		} else {
643                        if (strcmp(*argv, "dev") == 0) {
644				NEXT_ARG();
645			}
646			if (matches(*argv, "help") == 0)
647				usage();
648			if (dev)
649				duparg2("dev", *argv);
650			dev = *argv;
651		}
652		argc--; argv++;
653	}
654
655	if (!dev) {
656		fprintf(stderr, "Not enough of information: \"dev\" argument is required.\n");
657		exit(-1);
658	}
659
660	if (newaddr || newbrd) {
661		halen = get_address(dev, &htype);
662		if (halen < 0)
663			return -1;
664		if (newaddr) {
665			if (parse_address(dev, htype, halen, newaddr, &ifr0) < 0)
666				return -1;
667		}
668		if (newbrd) {
669			if (parse_address(dev, htype, halen, newbrd, &ifr1) < 0)
670				return -1;
671		}
672	}
673
674	if (newname && strcmp(dev, newname)) {
675		if (do_changename(dev, newname) < 0)
676			return -1;
677		dev = newname;
678	}
679	if (qlen != -1) {
680		if (set_qlen(dev, qlen) < 0)
681			return -1;
682	}
683	if (mtu != -1) {
684		if (set_mtu(dev, mtu) < 0)
685			return -1;
686	}
687	if (newaddr || newbrd) {
688		if (newbrd) {
689			if (set_address(&ifr1, 1) < 0)
690				return -1;
691		}
692		if (newaddr) {
693			if (set_address(&ifr0, 0) < 0)
694				return -1;
695		}
696	}
697	if (mask)
698		return do_chflags(dev, flags, mask);
699	return 0;
700}
701#endif /* IPLINK_IOCTL_COMPAT */
702
703int do_iplink(int argc, char **argv)
704{
705	if (argc > 0) {
706		if (iplink_have_newlink()) {
707			if (matches(*argv, "add") == 0)
708				return iplink_modify(RTM_NEWLINK,
709						     NLM_F_CREATE|NLM_F_EXCL,
710						     argc-1, argv+1);
711			if (matches(*argv, "set") == 0 ||
712			    matches(*argv, "change") == 0)
713				return iplink_modify(RTM_NEWLINK, 0,
714						     argc-1, argv+1);
715			if (matches(*argv, "replace") == 0)
716				return iplink_modify(RTM_NEWLINK,
717						     NLM_F_CREATE|NLM_F_REPLACE,
718						     argc-1, argv+1);
719			if (matches(*argv, "delete") == 0)
720				return iplink_modify(RTM_DELLINK, 0,
721						     argc-1, argv+1);
722		} else {
723#if IPLINK_IOCTL_COMPAT
724			if (matches(*argv, "set") == 0)
725				return do_set(argc-1, argv+1);
726#endif
727		}
728		if (matches(*argv, "show") == 0 ||
729		    matches(*argv, "lst") == 0 ||
730		    matches(*argv, "list") == 0)
731			return ipaddr_list_link(argc-1, argv+1);
732		if (matches(*argv, "help") == 0)
733			usage();
734	} else
735		return ipaddr_list_link(0, NULL);
736
737	fprintf(stderr, "Command \"%s\" is unknown, try \"ip link help\".\n", *argv);
738	exit(-1);
739}
740