Commit 1d934839 authored by Patrick McHardy's avatar Patrick McHardy Committed by Stephen Hemminger

iplink: use netlink for link configuration

Add support for using netlink for link configuration. Kernel-support is
probed, when not available it falls back to using ioctls.
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
parent f7cd9b03
...@@ -22,3 +22,5 @@ install: all ...@@ -22,3 +22,5 @@ install: all
clean: clean:
rm -f $(ALLOBJ) $(TARGETS) rm -f $(ALLOBJ) $(TARGETS)
LDLIBS += -ldl
LDFLAGS += -Wl,-export-dynamic
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
int preferred_family = AF_UNSPEC; int preferred_family = AF_UNSPEC;
int show_stats = 0; int show_stats = 0;
int show_details = 0;
int resolve_hosts = 0; int resolve_hosts = 0;
int oneline = 0; int oneline = 0;
int timestamp = 0; int timestamp = 0;
...@@ -48,7 +49,7 @@ static void usage(void) ...@@ -48,7 +49,7 @@ static void usage(void)
" ip [ -force ] [-batch filename\n" " ip [ -force ] [-batch filename\n"
"where OBJECT := { link | addr | route | rule | neigh | ntable | tunnel |\n" "where OBJECT := { link | addr | route | rule | neigh | ntable | tunnel |\n"
" maddr | mroute | monitor | xfrm | veth }\n" " maddr | mroute | monitor | xfrm | veth }\n"
" OPTIONS := { -V[ersion] | -s[tatistics] | -r[esolve] |\n" " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
" -f[amily] { inet | inet6 | ipx | dnet | link } |\n" " -f[amily] { inet | inet6 | ipx | dnet | link } |\n"
" -o[neline] | -t[imestamp] }\n"); " -o[neline] | -t[imestamp] }\n");
exit(-1); exit(-1);
...@@ -190,6 +191,8 @@ int main(int argc, char **argv) ...@@ -190,6 +191,8 @@ int main(int argc, char **argv)
} else if (matches(opt, "-stats") == 0 || } else if (matches(opt, "-stats") == 0 ||
matches(opt, "-statistics") == 0) { matches(opt, "-statistics") == 0) {
++show_stats; ++show_stats;
} else if (matches(opt, "-details") == 0) {
++show_details;
} else if (matches(opt, "-resolve") == 0) { } else if (matches(opt, "-resolve") == 0) {
++resolve_hosts; ++resolve_hosts;
} else if (matches(opt, "-oneline") == 0) { } else if (matches(opt, "-oneline") == 0) {
......
...@@ -45,6 +45,21 @@ static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb) ...@@ -45,6 +45,21 @@ static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb)
extern struct rtnl_handle rth; extern struct rtnl_handle rth;
struct link_util
{
struct link_util *next;
const char *id;
int maxattr;
int (*parse_opt)(struct link_util *, int, char **,
struct nlmsghdr *);
void (*print_opt)(struct link_util *, FILE *,
struct rtattr *[]);
void (*print_xstats)(struct link_util *, FILE *,
struct rtattr *);
};
struct link_util *get_link_kind(const char *kind);
#ifndef INFINITY_LIFE_TIME #ifndef INFINITY_LIFE_TIME
#define INFINITY_LIFE_TIME 0xFFFFFFFFU #define INFINITY_LIFE_TIME 0xFFFFFFFFU
#endif #endif
...@@ -136,6 +136,41 @@ void print_queuelen(char *name) ...@@ -136,6 +136,41 @@ void print_queuelen(char *name)
printf("qlen %d", ifr.ifr_qlen); printf("qlen %d", ifr.ifr_qlen);
} }
static void print_linktype(FILE *fp, struct rtattr *tb)
{
struct rtattr *linkinfo[IFLA_INFO_MAX+1];
struct link_util *lu;
char *kind;
parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
if (!linkinfo[IFLA_INFO_KIND])
return;
kind = RTA_DATA(linkinfo[IFLA_INFO_KIND]);
fprintf(fp, "%s", _SL_);
fprintf(fp, " %s ", kind);
lu = get_link_kind(kind);
if (!lu || !lu->print_opt)
return;
if (1) {
struct rtattr *attr[lu->maxattr+1], **data = NULL;
if (linkinfo[IFLA_INFO_DATA]) {
parse_rtattr_nested(attr, lu->maxattr,
linkinfo[IFLA_INFO_DATA]);
data = attr;
}
lu->print_opt(lu, fp, data);
if (linkinfo[IFLA_INFO_XSTATS] && show_stats &&
lu->print_xstats)
lu->print_xstats(lu, fp, linkinfo[IFLA_INFO_XSTATS]);
}
}
int print_linkinfo(const struct sockaddr_nl *who, int print_linkinfo(const struct sockaddr_nl *who,
struct nlmsghdr *n, void *arg) struct nlmsghdr *n, void *arg)
{ {
...@@ -223,6 +258,10 @@ int print_linkinfo(const struct sockaddr_nl *who, ...@@ -223,6 +258,10 @@ int print_linkinfo(const struct sockaddr_nl *who,
b1, sizeof(b1))); b1, sizeof(b1)));
} }
} }
if (do_link && tb[IFLA_LINKINFO] && show_details)
print_linktype(fp, tb[IFLA_LINKINFO]);
if (do_link && tb[IFLA_STATS] && show_stats) { if (do_link && tb[IFLA_STATS] && show_stats) {
struct rtnl_link_stats slocal; struct rtnl_link_stats slocal;
struct rtnl_link_stats *s = RTA_DATA(tb[IFLA_STATS]); struct rtnl_link_stats *s = RTA_DATA(tb[IFLA_STATS]);
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <unistd.h> #include <unistd.h>
#include <syslog.h> #include <syslog.h>
#include <fcntl.h> #include <fcntl.h>
#include <dlfcn.h>
#include <errno.h> #include <errno.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <linux/if.h> #include <linux/if.h>
...@@ -31,6 +32,7 @@ ...@@ -31,6 +32,7 @@
#include "utils.h" #include "utils.h"
#include "ip_common.h" #include "ip_common.h"
#define IPLINK_IOCTL_COMPAT 1
static void usage(void) __attribute__((noreturn)); static void usage(void) __attribute__((noreturn));
...@@ -62,6 +64,290 @@ static int on_off(char *msg) ...@@ -62,6 +64,290 @@ static int on_off(char *msg)
return -1; return -1;
} }
static void *BODY; /* cached dlopen(NULL) handle */
static struct link_util *linkutil_list;
struct link_util *get_link_kind(const char *id)
{
void *dlh;
char buf[256];
struct link_util *l;
for (l = linkutil_list; l; l = l->next)
if (strcmp(l->id, id) == 0)
return l;
snprintf(buf, sizeof(buf), "/usr/lib/ip/link_%s.so", id);
dlh = dlopen(buf, RTLD_LAZY);
if (dlh == NULL) {
/* look in current binary, only open once */
dlh = BODY;
if (dlh == NULL) {
dlh = BODY = dlopen(NULL, RTLD_LAZY);
if (dlh == NULL)
return NULL;
}
}
snprintf(buf, sizeof(buf), "%s_link_util", id);
l = dlsym(dlh, buf);
if (l == NULL)
return NULL;
l->next = linkutil_list;
linkutil_list = l;
return l;
}
#if IPLINK_IOCTL_COMPAT
static int have_rtnl_newlink = -1;
static int accept_msg(const struct sockaddr_nl *who,
struct nlmsghdr *n, void *arg)
{
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n);
if (n->nlmsg_type == NLMSG_ERROR && err->error == -EOPNOTSUPP)
have_rtnl_newlink = 0;
else
have_rtnl_newlink = 1;
return -1;
}
static int iplink_have_newlink(void)
{
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char buf[1024];
} req;
if (have_rtnl_newlink < 0) {
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK;
req.n.nlmsg_type = RTM_NEWLINK;
req.i.ifi_family = AF_UNSPEC;
rtnl_send(&rth, (char *)&req.n, req.n.nlmsg_len);
rtnl_listen(&rth, accept_msg, NULL);
}
return have_rtnl_newlink;
}
#else /* IPLINK_IOCTL_COMPAT */
static int iplink_have_newlink(void)
{
return 1;
}
#endif /* ! IPLINK_IOCTL_COMPAT */
static int iplink_modify(int cmd, unsigned int flags, int argc, char **argv)
{
int qlen = -1;
int mtu = -1;
int len;
char abuf[32];
char *dev = NULL;
char *name = NULL;
char *link = NULL;
char *type = NULL;
struct link_util *lu = NULL;
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char buf[1024];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_flags = NLM_F_REQUEST|flags;
req.n.nlmsg_type = cmd;
req.i.ifi_family = preferred_family;
while (argc > 0) {
if (strcmp(*argv, "up") == 0) {
req.i.ifi_change |= IFF_UP;
req.i.ifi_flags |= IFF_UP;
} else if (strcmp(*argv, "down") == 0) {
req.i.ifi_change |= IFF_UP;
req.i.ifi_flags &= ~IFF_UP;
} else if (strcmp(*argv, "name") == 0) {
NEXT_ARG();
name = *argv;
} else if (matches(*argv, "link") == 0) {
NEXT_ARG();
link = *argv;
} else if (matches(*argv, "address") == 0) {
NEXT_ARG();
len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
addattr_l(&req.n, sizeof(req), IFLA_ADDRESS, abuf, len);
} else if (matches(*argv, "broadcast") == 0 ||
strcmp(*argv, "brd") == 0) {
NEXT_ARG();
len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
addattr_l(&req.n, sizeof(req), IFLA_BROADCAST, abuf, len);
} else if (matches(*argv, "txqueuelen") == 0 ||
strcmp(*argv, "qlen") == 0 ||
matches(*argv, "txqlen") == 0) {
NEXT_ARG();
if (qlen != -1)
duparg("txqueuelen", *argv);
if (get_integer(&qlen, *argv, 0))
invarg("Invalid \"txqueuelen\" value\n", *argv);
addattr_l(&req.n, sizeof(req), IFLA_TXQLEN, &qlen, 4);
} else if (strcmp(*argv, "mtu") == 0) {
NEXT_ARG();
if (mtu != -1)
duparg("mtu", *argv);
if (get_integer(&mtu, *argv, 0))
invarg("Invalid \"mtu\" value\n", *argv);
addattr_l(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);
} else if (strcmp(*argv, "multicast") == 0) {
NEXT_ARG();
req.i.ifi_change |= IFF_MULTICAST;
if (strcmp(*argv, "on") == 0) {
req.i.ifi_flags |= IFF_MULTICAST;
} else if (strcmp(*argv, "off") == 0) {
req.i.ifi_flags &= ~IFF_MULTICAST;
} else
return on_off("multicast");
} else if (strcmp(*argv, "allmulticast") == 0) {
NEXT_ARG();
req.i.ifi_change |= IFF_ALLMULTI;
if (strcmp(*argv, "on") == 0) {
req.i.ifi_flags |= IFF_ALLMULTI;
} else if (strcmp(*argv, "off") == 0) {
req.i.ifi_flags &= ~IFF_ALLMULTI;
} else
return on_off("allmulticast");
} else if (strcmp(*argv, "promisc") == 0) {
NEXT_ARG();
req.i.ifi_change |= IFF_PROMISC;
if (strcmp(*argv, "on") == 0) {
req.i.ifi_flags |= IFF_PROMISC;
} else if (strcmp(*argv, "off") == 0) {
req.i.ifi_flags &= ~IFF_PROMISC;
} else
return on_off("promisc");
} else if (strcmp(*argv, "trailers") == 0) {
NEXT_ARG();
req.i.ifi_change |= IFF_NOTRAILERS;
if (strcmp(*argv, "off") == 0) {
req.i.ifi_flags |= IFF_NOTRAILERS;
} else if (strcmp(*argv, "on") == 0) {
req.i.ifi_flags &= ~IFF_NOTRAILERS;
} else
return on_off("trailers");
} else if (strcmp(*argv, "arp") == 0) {
NEXT_ARG();
req.i.ifi_change |= IFF_NOARP;
if (strcmp(*argv, "on") == 0) {
req.i.ifi_flags &= ~IFF_NOARP;
} else if (strcmp(*argv, "off") == 0) {
req.i.ifi_flags |= IFF_NOARP;
} else
return on_off("noarp");
#ifdef IFF_DYNAMIC
} else if (matches(*argv, "dynamic") == 0) {
NEXT_ARG();
req.i.ifi_change |= IFF_DYNAMIC;
if (strcmp(*argv, "on") == 0) {
req.i.ifi_flags |= IFF_DYNAMIC;
} else if (strcmp(*argv, "off") == 0) {
req.i.ifi_flags &= ~IFF_DYNAMIC;
} else
return on_off("dynamic");
#endif
} else if (matches(*argv, "type") == 0) {
NEXT_ARG();
type = *argv;
argc--; argv++;
break;
} else {
if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
}
if (dev)
duparg2("dev", *argv);
dev = *argv;
}
argc--; argv++;
}
ll_init_map(&rth);
if (type) {
struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type,
strlen(type));
lu = get_link_kind(type);
if (lu && argc) {
struct rtattr * data = NLMSG_TAIL(&req.n);
addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
if (lu->parse_opt &&
lu->parse_opt(lu, argc, argv, &req.n))
return -1;
data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
} else if (argc) {
if (matches(*argv, "help") == 0)
usage();
fprintf(stderr, "Garbage instead of arguments \"%s ...\". "
"Try \"ip link help\".\n", *argv);
return -1;
}
linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;
}
if (!(flags & NLM_F_CREATE)) {
if (!dev) {
fprintf(stderr, "Not enough information: \"dev\" "
"argument is required.\n");
exit(-1);
}
req.i.ifi_index = ll_name_to_index(dev);
if (req.i.ifi_index == 0) {
fprintf(stderr, "Cannot find device \"%s\"\n", dev);
return -1;
}
} else {
/* Allow "ip link add dev" and "ip link add name" */
if (!name)
name = dev;
if (link) {
int ifindex;
ifindex = ll_name_to_index(link);
if (ifindex == 0) {
fprintf(stderr, "Cannot find device \"%s\"\n",
link);
return -1;
}
addattr_l(&req.n, sizeof(req), IFLA_LINK, &ifindex, 4);
}
}
if (name) {
len = strlen(name) + 1;
if (len > IFNAMSIZ)
invarg("\"name\" too long\n", *argv);
addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, len);
}
if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
exit(2);
return 0;
}
#if IPLINK_IOCTL_COMPAT
static int get_ctl_fd(void) static int get_ctl_fd(void)
{ {
int s_errno; int s_errno;
...@@ -410,12 +696,33 @@ static int do_set(int argc, char **argv) ...@@ -410,12 +696,33 @@ static int do_set(int argc, char **argv)
return do_chflags(dev, flags, mask); return do_chflags(dev, flags, mask);
return 0; return 0;
} }
#endif /* IPLINK_IOCTL_COMPAT */
int do_iplink(int argc, char **argv) int do_iplink(int argc, char **argv)
{ {
if (argc > 0) { if (argc > 0) {
if (matches(*argv, "set") == 0) if (iplink_have_newlink()) {
return do_set(argc-1, argv+1); if (matches(*argv, "add") == 0)
return iplink_modify(RTM_NEWLINK,
NLM_F_CREATE|NLM_F_EXCL,
argc-1, argv+1);
if (matches(*argv, "set") == 0 ||
matches(*argv, "change") == 0)
return iplink_modify(RTM_NEWLINK, 0,
argc-1, argv+1);
if (matches(*argv, "replace") == 0)
return iplink_modify(RTM_NEWLINK,
NLM_F_CREATE|NLM_F_REPLACE,
argc-1, argv+1);
if (matches(*argv, "delete") == 0)
return iplink_modify(RTM_DELLINK, 0,
argc-1, argv+1);
} else {
#if IPLINK_IOCTL_COMPAT
if (matches(*argv, "set") == 0)
return do_set(argc-1, argv+1);
#endif
}
if (matches(*argv, "show") == 0 || if (matches(*argv, "show") == 0 ||
matches(*argv, "lst") == 0 || matches(*argv, "lst") == 0 ||
matches(*argv, "list") == 0) matches(*argv, "list") == 0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment