mirror_iproute2/ip/iptunnel.c
Serhey Popovych 3e95393871 iptunnel/ip6tunnel: Use netlink to walk through tunnels list
Both tunnels use legacy /proc/net/dev interface to get tunnel device and
it's statistics. This may cause problems for cases when procfs either
not mounted or not unshare(2)d for given network namespace.

Use netlink to walk through list of tunnel devices which is network
namespace aware and provides additional information such as statistics
in the dump message.

Since both address family specific variants of do_tunnels_list() nearly
the same, except for tunnel parameters structure initialization,
matching and printing we can introduce common one in tunnel.c.

To implement address family specific parts introduce new data structure
@struct tnl_print_nlmsg_info what contains all necessary information as
well as pointers to ->init(), ->match() and ->print() callbacks.

Annotate data structures by const where appropriate.

Signed-off-by: Serhey Popovych <serhe.popovych@gmail.com>
Signed-off-by: David Ahern <dsahern@gmail.com>
2018-02-07 16:15:42 -08:00

589 lines
15 KiB
C

/*
* iptunnel.c "ip tunnel"
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <linux/ip.h>
#include <linux/if_tunnel.h>
#include "rt_names.h"
#include "utils.h"
#include "ip_common.h"
#include "tunnel.h"
static void usage(void) __attribute__((noreturn));
static void usage(void)
{
fprintf(stderr, "Usage: ip tunnel { add | change | del | show | prl | 6rd } [ NAME ]\n");
fprintf(stderr, " [ mode { ipip | gre | sit | isatap | vti } ] [ remote ADDR ] [ local ADDR ]\n");
fprintf(stderr, " [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
fprintf(stderr, " [ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ]\n");
fprintf(stderr, " [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]\n");
fprintf(stderr, " [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Where: NAME := STRING\n");
fprintf(stderr, " ADDR := { IP_ADDRESS | any }\n");
fprintf(stderr, " TOS := { STRING | 00..ff | inherit | inherit/STRING | inherit/00..ff }\n");
fprintf(stderr, " TTL := { 1..255 | inherit }\n");
fprintf(stderr, " KEY := { DOTTED_QUAD | NUMBER }\n");
exit(-1);
}
static void set_tunnel_proto(struct ip_tunnel_parm *p, int proto)
{
if (p->iph.protocol && p->iph.protocol != proto) {
fprintf(stderr,
"You managed to ask for more than one tunnel mode.\n");
exit(-1);
}
p->iph.protocol = proto;
}
static int parse_args(int argc, char **argv, int cmd, struct ip_tunnel_parm *p)
{
int count = 0;
const char *medium = NULL;
int isatap = 0;
memset(p, 0, sizeof(*p));
p->iph.version = 4;
p->iph.ihl = 5;
#ifndef IP_DF
#define IP_DF 0x4000 /* Flag: "Don't Fragment" */
#endif
p->iph.frag_off = htons(IP_DF);
while (argc > 0) {
if (strcmp(*argv, "mode") == 0) {
NEXT_ARG();
if (strcmp(*argv, "ipip") == 0 ||
strcmp(*argv, "ip/ip") == 0) {
set_tunnel_proto(p, IPPROTO_IPIP);
} else if (strcmp(*argv, "gre") == 0 ||
strcmp(*argv, "gre/ip") == 0) {
set_tunnel_proto(p, IPPROTO_GRE);
} else if (strcmp(*argv, "sit") == 0 ||
strcmp(*argv, "ipv6/ip") == 0) {
set_tunnel_proto(p, IPPROTO_IPV6);
} else if (strcmp(*argv, "isatap") == 0) {
set_tunnel_proto(p, IPPROTO_IPV6);
isatap++;
} else if (strcmp(*argv, "vti") == 0) {
set_tunnel_proto(p, IPPROTO_IPIP);
p->i_flags |= VTI_ISVTI;
} else {
fprintf(stderr,
"Unknown tunnel mode \"%s\"\n", *argv);
exit(-1);
}
} else if (strcmp(*argv, "key") == 0) {
NEXT_ARG();
p->i_flags |= GRE_KEY;
p->o_flags |= GRE_KEY;
p->i_key = p->o_key = tnl_parse_key("key", *argv);
} else if (strcmp(*argv, "ikey") == 0) {
NEXT_ARG();
p->i_flags |= GRE_KEY;
p->i_key = tnl_parse_key("ikey", *argv);
} else if (strcmp(*argv, "okey") == 0) {
NEXT_ARG();
p->o_flags |= GRE_KEY;
p->o_key = tnl_parse_key("okey", *argv);
} else if (strcmp(*argv, "seq") == 0) {
p->i_flags |= GRE_SEQ;
p->o_flags |= GRE_SEQ;
} else if (strcmp(*argv, "iseq") == 0) {
p->i_flags |= GRE_SEQ;
} else if (strcmp(*argv, "oseq") == 0) {
p->o_flags |= GRE_SEQ;
} else if (strcmp(*argv, "csum") == 0) {
p->i_flags |= GRE_CSUM;
p->o_flags |= GRE_CSUM;
} else if (strcmp(*argv, "icsum") == 0) {
p->i_flags |= GRE_CSUM;
} else if (strcmp(*argv, "ocsum") == 0) {
p->o_flags |= GRE_CSUM;
} else if (strcmp(*argv, "nopmtudisc") == 0) {
p->iph.frag_off = 0;
} else if (strcmp(*argv, "pmtudisc") == 0) {
p->iph.frag_off = htons(IP_DF);
} else if (strcmp(*argv, "remote") == 0) {
NEXT_ARG();
p->iph.daddr = get_addr32(*argv);
} else if (strcmp(*argv, "local") == 0) {
NEXT_ARG();
p->iph.saddr = get_addr32(*argv);
} else if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
medium = *argv;
} else if (strcmp(*argv, "ttl") == 0 ||
strcmp(*argv, "hoplimit") == 0 ||
strcmp(*argv, "hlim") == 0) {
__u8 uval;
NEXT_ARG();
if (strcmp(*argv, "inherit") != 0) {
if (get_u8(&uval, *argv, 0))
invarg("invalid TTL\n", *argv);
p->iph.ttl = uval;
}
} else if (strcmp(*argv, "tos") == 0 ||
strcmp(*argv, "tclass") == 0 ||
matches(*argv, "dsfield") == 0) {
char *dsfield;
__u32 uval;
NEXT_ARG();
dsfield = *argv;
strsep(&dsfield, "/");
if (strcmp(*argv, "inherit") != 0) {
dsfield = *argv;
p->iph.tos = 0;
} else
p->iph.tos = 1;
if (dsfield) {
if (rtnl_dsfield_a2n(&uval, dsfield))
invarg("bad TOS value", *argv);
p->iph.tos |= uval;
}
} else {
if (strcmp(*argv, "name") == 0)
NEXT_ARG();
else if (matches(*argv, "help") == 0)
usage();
if (p->name[0])
duparg2("name", *argv);
if (get_ifname(p->name, *argv))
invarg("\"name\" not a valid ifname", *argv);
if (cmd == SIOCCHGTUNNEL && count == 0) {
struct ip_tunnel_parm old_p = {};
if (tnl_get_ioctl(*argv, &old_p))
return -1;
*p = old_p;
}
}
count++;
argc--; argv++;
}
if (p->iph.protocol == 0) {
if (memcmp(p->name, "gre", 3) == 0)
p->iph.protocol = IPPROTO_GRE;
else if (memcmp(p->name, "ipip", 4) == 0)
p->iph.protocol = IPPROTO_IPIP;
else if (memcmp(p->name, "sit", 3) == 0)
p->iph.protocol = IPPROTO_IPV6;
else if (memcmp(p->name, "isatap", 6) == 0) {
p->iph.protocol = IPPROTO_IPV6;
isatap++;
} else if (memcmp(p->name, "vti", 3) == 0) {
p->iph.protocol = IPPROTO_IPIP;
p->i_flags |= VTI_ISVTI;
}
}
if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) {
if (!(p->i_flags & VTI_ISVTI) &&
(p->iph.protocol != IPPROTO_GRE)) {
fprintf(stderr, "Keys are not allowed with ipip and sit tunnels\n");
return -1;
}
}
if (medium) {
p->link = ll_name_to_index(medium);
if (p->link == 0) {
fprintf(stderr, "Cannot find device \"%s\"\n", medium);
return -1;
}
}
if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
p->i_key = p->iph.daddr;
p->i_flags |= GRE_KEY;
}
if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
p->o_key = p->iph.daddr;
p->o_flags |= GRE_KEY;
}
if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) {
fprintf(stderr, "A broadcast tunnel requires a source address\n");
return -1;
}
if (isatap)
p->i_flags |= SIT_ISATAP;
return 0;
}
static const char *tnl_defname(const struct ip_tunnel_parm *p)
{
switch (p->iph.protocol) {
case IPPROTO_IPIP:
if (p->i_flags & VTI_ISVTI)
return "ip_vti0";
else
return "tunl0";
case IPPROTO_GRE:
return "gre0";
case IPPROTO_IPV6:
return "sit0";
}
return NULL;
}
static int do_add(int cmd, int argc, char **argv)
{
struct ip_tunnel_parm p;
const char *basedev;
if (parse_args(argc, argv, cmd, &p) < 0)
return -1;
if (p.iph.ttl && p.iph.frag_off == 0) {
fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
return -1;
}
basedev = tnl_defname(&p);
if (!basedev) {
fprintf(stderr,
"cannot determine tunnel mode (ipip, gre, vti or sit)\n");
return -1;
}
return tnl_add_ioctl(cmd, basedev, p.name, &p);
}
static int do_del(int argc, char **argv)
{
struct ip_tunnel_parm p;
if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0)
return -1;
return tnl_del_ioctl(tnl_defname(&p) ? : p.name, p.name, &p);
}
static void print_tunnel(const void *t)
{
const struct ip_tunnel_parm *p = t;
struct ip_tunnel_6rd ip6rd = {};
char s1[1024];
char s2[1024];
/* Do not use format_host() for local addr,
* symbolic name will not be useful.
*/
printf("%s: %s/ip remote %s local %s",
p->name,
tnl_strproto(p->iph.protocol),
p->iph.daddr ? format_host_r(AF_INET, 4, &p->iph.daddr, s1, sizeof(s1)) : "any",
p->iph.saddr ? rt_addr_n2a_r(AF_INET, 4, &p->iph.saddr, s2, sizeof(s2)) : "any");
if (p->iph.protocol == IPPROTO_IPV6 && (p->i_flags & SIT_ISATAP)) {
struct ip_tunnel_prl prl[16] = {};
int i;
prl[0].datalen = sizeof(prl) - sizeof(prl[0]);
prl[0].addr = htonl(INADDR_ANY);
if (!tnl_prl_ioctl(SIOCGETPRL, p->name, prl))
for (i = 1; i < ARRAY_SIZE(prl); i++) {
if (prl[i].addr != htonl(INADDR_ANY)) {
printf(" %s %s ",
(prl[i].flags & PRL_DEFAULT) ? "pdr" : "pr",
format_host(AF_INET, 4, &prl[i].addr));
}
}
}
if (p->link) {
const char *n = ll_index_to_name(p->link);
if (n)
printf(" dev %s", n);
}
if (p->iph.ttl)
printf(" ttl %u", p->iph.ttl);
else
printf(" ttl inherit");
if (p->iph.tos) {
SPRINT_BUF(b1);
printf(" tos");
if (p->iph.tos & 1)
printf(" inherit");
if (p->iph.tos & ~1)
printf("%c%s ", p->iph.tos & 1 ? '/' : ' ',
rtnl_dsfield_n2a(p->iph.tos & ~1, b1, sizeof(b1)));
}
if (!(p->iph.frag_off & htons(IP_DF)))
printf(" nopmtudisc");
if (p->iph.protocol == IPPROTO_IPV6 && !tnl_ioctl_get_6rd(p->name, &ip6rd) && ip6rd.prefixlen) {
printf(" 6rd-prefix %s/%u",
inet_ntop(AF_INET6, &ip6rd.prefix, s1, sizeof(s1)),
ip6rd.prefixlen);
if (ip6rd.relay_prefix) {
printf(" 6rd-relay_prefix %s/%u",
format_host(AF_INET, 4, &ip6rd.relay_prefix),
ip6rd.relay_prefixlen);
}
}
if ((p->i_flags & GRE_KEY) && (p->o_flags & GRE_KEY) && p->o_key == p->i_key)
printf(" key %u", ntohl(p->i_key));
else if ((p->i_flags | p->o_flags) & GRE_KEY) {
if (p->i_flags & GRE_KEY)
printf(" ikey %u", ntohl(p->i_key));
if (p->o_flags & GRE_KEY)
printf(" okey %u", ntohl(p->o_key));
}
if (p->i_flags & GRE_SEQ)
printf("%s Drop packets out of sequence.", _SL_);
if (p->i_flags & GRE_CSUM)
printf("%s Checksum in received packet is required.", _SL_);
if (p->o_flags & GRE_SEQ)
printf("%s Sequence packets on output.", _SL_);
if (p->o_flags & GRE_CSUM)
printf("%s Checksum output packets.", _SL_);
}
static void ip_tunnel_parm_initialize(const struct tnl_print_nlmsg_info *info)
{
struct ip_tunnel_parm *p2 = info->p2;
memset(p2, 0, sizeof(*p2));
}
static bool ip_tunnel_parm_match(const struct tnl_print_nlmsg_info *info)
{
const struct ip_tunnel_parm *p1 = info->p1;
const struct ip_tunnel_parm *p2 = info->p2;
return ((!p1->link || p1->link == p2->link) &&
(!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
(!p1->iph.daddr || p1->iph.daddr == p2->iph.daddr) &&
(!p1->iph.saddr || p1->iph.saddr == p2->iph.saddr) &&
(!p1->i_key || p1->i_key == p2->i_key));
}
static int do_show(int argc, char **argv)
{
struct ip_tunnel_parm p, p1;
const char *basedev;
if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
return -1;
basedev = tnl_defname(&p);
if (!basedev) {
struct tnl_print_nlmsg_info info = {
.p1 = &p,
.p2 = &p1,
.init = ip_tunnel_parm_initialize,
.match = ip_tunnel_parm_match,
.print = print_tunnel,
};
return do_tunnels_list(&info);
}
if (tnl_get_ioctl(p.name[0] ? p.name : basedev, &p))
return -1;
print_tunnel(&p);
fputc('\n', stdout);
return 0;
}
static int do_prl(int argc, char **argv)
{
struct ip_tunnel_prl p = {};
int count = 0;
int cmd = 0;
const char *medium = NULL;
while (argc > 0) {
if (strcmp(*argv, "prl-default") == 0) {
NEXT_ARG();
cmd = SIOCADDPRL;
p.addr = get_addr32(*argv);
p.flags |= PRL_DEFAULT;
count++;
} else if (strcmp(*argv, "prl-nodefault") == 0) {
NEXT_ARG();
cmd = SIOCADDPRL;
p.addr = get_addr32(*argv);
count++;
} else if (strcmp(*argv, "prl-delete") == 0) {
NEXT_ARG();
cmd = SIOCDELPRL;
p.addr = get_addr32(*argv);
count++;
} else if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
if (check_ifname(*argv))
invarg("\"dev\" not a valid ifname", *argv);
medium = *argv;
} else {
fprintf(stderr,
"Invalid PRL parameter \"%s\"\n", *argv);
exit(-1);
}
if (count > 1) {
fprintf(stderr,
"One PRL entry at a time\n");
exit(-1);
}
argc--; argv++;
}
if (!medium) {
fprintf(stderr, "Must specify device\n");
exit(-1);
}
return tnl_prl_ioctl(cmd, medium, &p);
}
static int do_6rd(int argc, char **argv)
{
struct ip_tunnel_6rd ip6rd = {};
int cmd = 0;
const char *medium = NULL;
inet_prefix prefix;
while (argc > 0) {
if (strcmp(*argv, "6rd-prefix") == 0) {
NEXT_ARG();
if (get_prefix(&prefix, *argv, AF_INET6))
invarg("invalid 6rd_prefix\n", *argv);
cmd = SIOCADD6RD;
memcpy(&ip6rd.prefix, prefix.data, 16);
ip6rd.prefixlen = prefix.bitlen;
} else if (strcmp(*argv, "6rd-relay_prefix") == 0) {
NEXT_ARG();
if (get_prefix(&prefix, *argv, AF_INET))
invarg("invalid 6rd-relay_prefix\n", *argv);
cmd = SIOCADD6RD;
memcpy(&ip6rd.relay_prefix, prefix.data, 4);
ip6rd.relay_prefixlen = prefix.bitlen;
} else if (strcmp(*argv, "6rd-reset") == 0) {
cmd = SIOCDEL6RD;
} else if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
if (check_ifname(*argv))
invarg("\"dev\" not a valid ifname", *argv);
medium = *argv;
} else {
fprintf(stderr,
"Invalid 6RD parameter \"%s\"\n", *argv);
exit(-1);
}
argc--; argv++;
}
if (!medium) {
fprintf(stderr, "Must specify device\n");
exit(-1);
}
return tnl_6rd_ioctl(cmd, medium, &ip6rd);
}
static int tunnel_mode_is_ipv6(char *tunnel_mode)
{
static const char * const ipv6_modes[] = {
"ipv6/ipv6", "ip6ip6",
"vti6",
"ip/ipv6", "ipv4/ipv6", "ipip6", "ip4ip6",
"ip6gre", "gre/ipv6",
"any/ipv6", "any"
};
int i;
for (i = 0; i < ARRAY_SIZE(ipv6_modes); i++) {
if (strcmp(ipv6_modes[i], tunnel_mode) == 0)
return 1;
}
return 0;
}
int do_iptunnel(int argc, char **argv)
{
int i;
for (i = 0; i < argc - 1; i++) {
if (strcmp(argv[i], "mode") == 0) {
if (tunnel_mode_is_ipv6(argv[i + 1]))
preferred_family = AF_INET6;
break;
}
}
switch (preferred_family) {
case AF_UNSPEC:
preferred_family = AF_INET;
break;
case AF_INET:
break;
/*
* This is silly enough but we have no easy way to make it
* protocol-independent because of unarranged structure between
* IPv4 and IPv6.
*/
case AF_INET6:
return do_ip6tunnel(argc, argv);
default:
fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
exit(-1);
}
if (argc > 0) {
if (matches(*argv, "add") == 0)
return do_add(SIOCADDTUNNEL, argc - 1, argv + 1);
if (matches(*argv, "change") == 0)
return do_add(SIOCCHGTUNNEL, argc - 1, argv + 1);
if (matches(*argv, "delete") == 0)
return do_del(argc - 1, argv + 1);
if (matches(*argv, "show") == 0 ||
matches(*argv, "lst") == 0 ||
matches(*argv, "list") == 0)
return do_show(argc - 1, argv + 1);
if (matches(*argv, "prl") == 0)
return do_prl(argc - 1, argv + 1);
if (matches(*argv, "6rd") == 0)
return do_6rd(argc - 1, argv + 1);
if (matches(*argv, "help") == 0)
usage();
} else
return do_show(0, NULL);
fprintf(stderr, "Command \"%s\" is unknown, try \"ip tunnel help\"\n", *argv);
exit(-1);
}