diff --git a/include/linux/if_map.h b/include/linux/if_map.h index e69de29..e57d9f0 100644 --- a/include/linux/if_map.h +++ b/include/linux/if_map.h @@ -0,0 +1,48 @@ +#ifndef _IF_MAP_H_ +#define _IF_MAP_H_ + +#include +#include + +#define SIOCGETMAP (SIOCDEVPRIVATE + 0) +#define SIOCADDMAP (SIOCDEVPRIVATE + 1) +#define SIOCDELMAP (SIOCDEVPRIVATE + 2) +#define SIOCCHGMAP (SIOCDEVPRIVATE + 3) +#define SIOCGETMAPRULES (SIOCDEVPRIVATE + 4) +#define SIOCADDMAPRULES (SIOCDEVPRIVATE + 5) +#define SIOCDELMAPRULES (SIOCDEVPRIVATE + 6) +#define SIOCCHGMAPRULES (SIOCDEVPRIVATE + 7) + +#define MAP_ROLE_BR (1 << 0) +#define MAP_ROLE_CE (1 << 1) + +#define MAP_FORWARDING_MODE_T (1 << 0) +#define MAP_FORWARDING_MODE_E (1 << 1) + +#define MAP_FORWARDING_RULE_T (1 << 0) +#define MAP_FORWARDING_RULE_F (1 << 1) + +struct map_rule_parm { + struct in6_addr ipv6_prefix; + __be32 ipv4_prefix; + __u8 ipv6_prefix_length; + __u8 ipv4_prefix_length; + __u8 ea_length; + __u8 psid_offset; + __u8 forwarding_mode; + __u8 forwarding_rule; +}; + +struct map_parm { + char name[IFNAMSIZ]; + int tunnel_source; + struct in6_addr br_address; + __u8 br_address_length; + __u8 role; + __u8 default_forwarding_mode; + __u8 default_forwarding_rule; + __u8 rule_num; + struct map_rule_parm rule[0]; +}; + +#endif /* _IF_MAP_H_ */ diff --git a/ip/Makefile b/ip/Makefile index 3f8c25b..c236475 100644 --- a/ip/Makefile +++ b/ip/Makefile @@ -3,7 +3,7 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \ ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o \ ipxfrm.o xfrm_state.o xfrm_policy.o xfrm_monitor.o \ iplink_vlan.o link_veth.o link_gre.o iplink_can.o \ - iplink_macvlan.o iplink_macvtap.o ipl2tp.o + iplink_macvlan.o iplink_macvtap.o ipl2tp.o ipmap.o RTMONOBJ=rtmon.o diff --git a/ip/ip.c b/ip/ip.c index 20dc3b5..7a143f4 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -45,7 +45,7 @@ static void usage(void) " ip [ -force ] -batch filename\n" "where OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |\n" " tunnel | tuntap | maddr | mroute | mrule | monitor | xfrm |\n" -" netns | l2tp }\n" +" netns | l2tp | map }\n" " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n" " -f[amily] { inet | inet6 | ipx | dnet | link } |\n" " -l[oops] { maximum-addr-flush-attempts } |\n" @@ -83,6 +83,7 @@ static const struct cmd { { "mroute", do_multiroute }, { "mrule", do_multirule }, { "netns", do_netns }, + { "map", do_ipmap }, { "help", do_help }, { 0 } }; diff --git a/ip/ip_common.h b/ip/ip_common.h index b45c5ee..d2dbb12 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -41,6 +41,7 @@ extern int do_multirule(int argc, char **argv); extern int do_netns(int argc, char **argv); extern int do_xfrm(int argc, char **argv); extern int do_ipl2tp(int argc, char **argv); +extern int do_ipmap(int argc, char **argv); static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb) { diff --git a/ip/ipmap.c b/ip/ipmap.c index e69de29..4c41f10 100644 --- a/ip/ipmap.c +++ b/ip/ipmap.c @@ -0,0 +1,509 @@ +/* + * ipmap.c "ip map" + * + * Authors: Masakazu Asama, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "ip_common.h" + +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: ip map { add | change | del | show | add-rule | del-rule } [ NAME ]\n"); + fprintf(stderr, " [ role { br | ce } ] [ tunnel-source STRING ] [ br-address ADDR ]\n"); + fprintf(stderr, " [ default-forwarding-mode { translation | encapsulation } ]\n"); + fprintf(stderr, " [ default-forwarding-rule BOOL ]\n"); + fprintf(stderr, " [ ipv6-prefix ADDR ] [ ipv4-prefix ADDR ] [ ea-length NUMBER ]\n"); + fprintf(stderr, " [ forwarding-mode { translation | encapsulation } ]\n"); + fprintf(stderr, " [ forwarding-rule BOOL ] [ psid-offset NUMBER ] [ dev PHYS_DEV ]\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Where: NAME := STRING\n"); + fprintf(stderr, " ADDR := IP_ADDRESS\n"); + fprintf(stderr, " BOOL := { true | false }\n"); + exit(-1); +} + +static inline int ipv6_addr_any(const struct in6_addr *a) +{ + return (a->s6_addr32[0] | a->s6_addr32[1] | a->s6_addr32[2] | a->s6_addr32[3]) == 0; +} + +static inline void ipv6_addr_prefix(struct in6_addr *pfx, const struct in6_addr *addr, int plen) +{ + int o = plen >> 3, b = plen & 0x7; + memset(pfx->s6_addr, 0, sizeof(pfx->s6_addr)); + memcpy(pfx->s6_addr, addr, o); + if (b != 0) + pfx->s6_addr[o] = addr->s6_addr[o] & (0xff00 >> b); +} + +static inline int ipv6_addr_equal(const struct in6_addr *a1, const struct in6_addr *a2) +{ + return ((a1->s6_addr32[0] ^ a2->s6_addr32[0]) | + (a1->s6_addr32[1] ^ a2->s6_addr32[1]) | + (a1->s6_addr32[2] ^ a2->s6_addr32[2]) | + (a1->s6_addr32[3] ^ a2->s6_addr32[3])) == 0; +} + +static void debug_dump_map_parm(struct map_parm *p) +{ + char s1[1024]; + fprintf(stderr, "debug_dump_map_parm:\n"); + fprintf(stderr, " name = \"%s\"\n", p->name); + fprintf(stderr, " tunnel_source = %d\n", p->tunnel_source); + fprintf(stderr, " br_address = %s\n", inet_ntop(AF_INET6, &p->br_address, s1, sizeof(s1))); + fprintf(stderr, " br_address_length = %d\n", p->br_address_length); + fprintf(stderr, " role = %02x\n", p->role); + fprintf(stderr, " default_forwarding_mode = %02x\n", p->default_forwarding_mode); + fprintf(stderr, " default_forwarding_rule = %02x\n", p->default_forwarding_rule); +} + +static void debug_dump_map_rule_parm(struct map_rule_parm *r) +{ + char s1[1024]; + char s2[1024]; + fprintf(stderr, "debug_dump_map_rule_parm:\n"); + fprintf(stderr, " ipv6_prefix = %s\n", inet_ntop(AF_INET6, &r->ipv6_prefix, s1, sizeof(s1))); + fprintf(stderr, " ipv4_prefix = %s\n", format_host(AF_INET, 4, &r->ipv4_prefix, s2, sizeof(s2))); + fprintf(stderr, " ipv6_prefix_length = %d\n", r->ipv6_prefix_length); + fprintf(stderr, " ipv4_prefix_length = %d\n", r->ipv4_prefix_length); + fprintf(stderr, " ea_length = %d\n", r->ea_length); + fprintf(stderr, " psid_offset = %d\n", r->psid_offset); + fprintf(stderr, " forwarding_mode = %02x\n", r->forwarding_mode); + fprintf(stderr, " forwarding_rule = %02x\n", r->forwarding_rule); +} + +static int map_ioctl(const char *name, int cmd, void *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (cmd == SIOCADDMAP || cmd == SIOCDELMAP) + strncpy(ifr.ifr_name, "mapfb", IFNAMSIZ - 1); + else + strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); + ifr.ifr_ifru.ifru_data = p; + fd = socket(AF_INET6, SOCK_DGRAM, 0); + err = ioctl(fd, cmd, &ifr); + if (err) + fprintf(stderr, "%s ioctl failed: %s\n", + ((cmd == SIOCADDMAP || cmd == SIOCDELMAP) ? "mapfb" : name), + strerror(errno)); + close(fd); + return err; +} + +static int parse_args(int argc, char **argv, int cmd, struct map_parm *p, struct map_rule_parm *r) +{ + int count = 0; + inet_prefix prefix; + + memset(p, 0, sizeof(*p)); + memset(r, 0, sizeof(*r)); + + /* default settings */ + p->role = MAP_ROLE_CE; + p->default_forwarding_rule = MAP_FORWARDING_RULE_T; + r->psid_offset = 4; + + while (argc > 0) { + if (strcmp(*argv, "role") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "br") == 0) { + p->role = MAP_ROLE_BR; + } else if (strcmp(*argv, "ce") == 0) { + p->role = MAP_ROLE_CE; + } else { + fprintf(stderr, "Cannot guess MAP role.\n"); + exit(-1); + } + } else if (strcmp(*argv, "tunnel-source") == 0) { + NEXT_ARG(); + p->tunnel_source = if_nametoindex(*argv); + if (p->tunnel_source == 0) { + fprintf(stderr, "No such interface: %s.\n", *argv); + exit(-1); + } + } else if (strcmp(*argv, "br-address") == 0) { + NEXT_ARG(); + if (get_prefix(&prefix, *argv, AF_INET6)) { + fprintf(stderr, "Invalid br-address: %s.\n", *argv); + exit(-1); + } + memcpy(&p->br_address, prefix.data, 16); + p->br_address_length = prefix.bitlen; + } else if (strcmp(*argv, "default-forwarding-mode") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "translation") == 0) { + p->default_forwarding_mode = MAP_FORWARDING_MODE_T; + } else if (strcmp(*argv, "encapsulation") == 0) { + p->default_forwarding_mode = MAP_FORWARDING_MODE_E; + } else { + fprintf(stderr, "Cannot guess default forwarding mode.\n"); + exit(-1); + } + } else if (strcmp(*argv, "forwarding-mode") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "translation") == 0) { + r->forwarding_mode = MAP_FORWARDING_MODE_T; + } else if (strcmp(*argv, "encapsulation") == 0) { + r->forwarding_mode = MAP_FORWARDING_MODE_E; + } else { + fprintf(stderr, "Cannot guess forwarding mode.\n"); + exit(-1); + } + } else if (strcmp(*argv, "ipv6-prefix") == 0) { + NEXT_ARG(); + if (get_prefix(&prefix, *argv, AF_INET6)) { + fprintf(stderr, "Invalid ipv6-prefix: %s.\n", *argv); + exit(-1); + } + memcpy(&r->ipv6_prefix, prefix.data, 16); + r->ipv6_prefix_length = prefix.bitlen; + } else if (strcmp(*argv, "ipv4-prefix") == 0) { + NEXT_ARG(); + if (get_prefix(&prefix, *argv, AF_INET)) { + fprintf(stderr, "Invalid ipv4-prefix: %s.\n", *argv); + exit(-1); + } + memcpy(&r->ipv4_prefix, prefix.data, 4); + r->ipv4_prefix_length = prefix.bitlen; + } else if (strcmp(*argv, "ea-length") == 0) { + unsigned uval; + NEXT_ARG(); + if (get_unsigned(&uval, *argv, 0)) { + fprintf(stderr, "Invalid ea-length: %s.\n", *argv); + exit(-1); + } + if (uval > 48) { + fprintf(stderr, "Invalid ea-length: %s.\n", *argv); + exit(-1); + } + r->ea_length = uval; + } else if (strcmp(*argv, "default-forwarding-rule") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "true") == 0) { + p->default_forwarding_rule = MAP_FORWARDING_RULE_T; + } else if (strcmp(*argv, "false") == 0) { + p->default_forwarding_rule = MAP_FORWARDING_RULE_F; + } else { + fprintf(stderr, "Cannot guess default forwardng rule.\n"); + exit(-1); + } + } else if (strcmp(*argv, "forwarding-rule") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "true") == 0) { + r->forwarding_rule = MAP_FORWARDING_RULE_T; + } else if (strcmp(*argv, "false") == 0) { + r->forwarding_rule = MAP_FORWARDING_RULE_F; + } else { + fprintf(stderr, "Cannot guess forwarding rule.\n"); + exit(-1); + } + } else if (strcmp(*argv, "psid-offset") == 0) { + unsigned uval; + NEXT_ARG(); + if (get_unsigned(&uval, *argv, 0)) { + fprintf(stderr, "Invalid psid-offset: %s.\n", *argv); + exit(-1); + } + if (uval > 16) { + fprintf(stderr, "Invalid psid-offset: %s.\n", *argv); + exit(-1); + } + r->psid_offset = uval; + } else if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + strncpy(p->name, *argv, IFNAMSIZ - 1); + } else { + if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + } else if (matches(*argv, "help") == 0) + usage(); + if (p->name[0]) { + fprintf(stderr, "Duplicated name:%s.\n", *argv); + exit(-1); + } + strncpy(p->name, *argv, IFNAMSIZ - 1); + if (cmd == SIOCCHGMAP && count == 0) { + struct map_parm old_p; + memset(&old_p, 0, sizeof(old_p)); + if (map_ioctl(*argv, SIOCGETMAP, &old_p)) + return -1; + *p = old_p; + } + } + count++; + argc--; argv++; + } + + if (!p->name[0]) { + fprintf(stderr, "name or dev required.\n"); + exit(-1); + } + + if (cmd == SIOCADDMAP || cmd == SIOCCHGMAP) { + if (p->role == MAP_ROLE_CE && p->tunnel_source == 0) { + fprintf(stderr, "tunnel-source required if role equals ce.\n"); + exit(-1); + } + if (ipv6_addr_any(&p->br_address)) { + fprintf(stderr, "br-address required.\n"); + exit(-1); + } + if (p->br_address_length > 96) { + fprintf(stderr, "br-address prefix length invalid.\n"); + exit(-1); + } + if (!p->default_forwarding_mode) { + fprintf(stderr, "default-forwarding-mode required.\n"); + exit(-1); + } + } + + if (cmd == SIOCADDMAPRULES || cmd == SIOCCHGMAPRULES) { + struct in6_addr t; + if (r->psid_offset > 16) { + fprintf(stderr, "psid-offset invalid.\n"); + exit(-1); + } + if (r->ea_length == 0 || r->ea_length > 48) { + fprintf(stderr, "ea-length required or invalid.\n"); + exit(-1); + } + if (r->ipv6_prefix_length + r->ea_length > 64) { + fprintf(stderr, "ipv6-prefix length or ea-length invalid.\n"); + exit(-1); + } + if (r->ipv4_prefix_length + r->ea_length + r->psid_offset > 48) { + fprintf(stderr, "ipv4-prefix length or ea-length or psid-offset invalid.\n"); + exit(-1); + } + if (ipv6_addr_any(&r->ipv6_prefix)) { + fprintf(stderr, "ipv6-prefix required or invalid.\n"); + exit(-1); + } + ipv6_addr_prefix(&t, &r->ipv6_prefix, r->ipv6_prefix_length); + if (!ipv6_addr_equal(&t, &r->ipv6_prefix)) { + fprintf(stderr, "ipv6-prefix invalid.\n"); + exit(-1); + } + if (!r->ipv4_prefix) { + fprintf(stderr, "ipv4-prefix required or invalid.\n"); + exit(-1); + } + /* + if ((r->ipv4_prefix & (~((1 << (32 - r->ipv4_prefix_length)) - 1))) != r->ipv4_prefix) { + fprintf(stderr, "ipv4-prefix invalid.\n"); + exit(-1); + } + */ + } + + if (cmd == SIOCDELMAPRULES) { + if (ipv6_addr_any(&r->ipv6_prefix)) { + fprintf(stderr, "ipv6-prefix required or invalid.\n"); + exit(-1); + } + if (!r->ipv4_prefix) { + fprintf(stderr, "ipv4-prefix required or invalid.\n"); + exit(-1); + } + } + + /* XXX: */ + //debug_dump_map_parm(p); + //debug_dump_map_rule_parm(r); + + return 0; +} + +static int do_add(int argc, char **argv) +{ + struct map_parm p; + struct map_rule_parm r; + + if (parse_args(argc, argv, SIOCADDMAP, &p, &r) < 0) + return -1; + + if (map_ioctl(p.name, SIOCADDMAP, &p)) + return -1; + + return 0; +} + +static int do_change(int argc, char **argv) +{ + struct map_parm p; + struct map_rule_parm r; + + if (parse_args(argc, argv, SIOCCHGMAP, &p, &r) < 0) + return -1; + + if (map_ioctl(p.name, SIOCCHGMAP, &p)) + return -1; + + return 0; +} + +static int do_del(int argc, char **argv) +{ + struct map_parm p; + struct map_rule_parm r; + + if (parse_args(argc, argv, SIOCDELMAP, &p, &r) < 0) + return -1; + + if (map_ioctl(p.name, SIOCDELMAP, &p)) + return -1; + + return 0; +} + +static int do_show(int argc, char **argv) +{ + struct map_parm p; + struct map_rule_parm r; + struct map_parm *pptr; + struct map_rule_parm *rptr; + char s1[1024]; + char s2[1024]; + int i; + + if (parse_args(argc, argv, SIOCGETMAP, &p, &r) < 0) + return -1; + + if (map_ioctl(p.name, SIOCGETMAP, &p)) + return -1; + memset(s1, 0, 1024); + if_indextoname(p.tunnel_source, s1); + if (!s1[0]) + sprintf(s1, "(null)"); + printf("%s: role %s tunnel-source %s br-address %s/%d default-forwarding-mode %s default-forwarding-rule %s\n", + p.name, + (p.role == MAP_ROLE_BR ? "br" : "ce"), + s1, + inet_ntop(AF_INET6, &p.br_address, s2, sizeof(s2)), + p.br_address_length, + (p.default_forwarding_mode == MAP_FORWARDING_MODE_T ? "translation" : "encapsulation"), + (p.default_forwarding_rule == MAP_FORWARDING_RULE_T ? "true" : "false")); + + pptr = malloc(sizeof(struct map_parm) + sizeof(struct map_rule_parm) * p.rule_num); + if (!pptr) + return -1; + if (map_ioctl(p.name, SIOCGETMAPRULES, pptr)) + return -1; + for (i = 0; i < pptr->rule_num; i++) { + rptr = &pptr->rule[i]; + printf(" %d: ipv6-prefix %s/%d ipv4-prefix %s/%d ea-length %d psid-offset %d " + "forwarding-mode %s forwarding-rule %s\n", + i, + inet_ntop(AF_INET6, &rptr->ipv6_prefix, s1, sizeof(s1)), + rptr->ipv6_prefix_length, + format_host(AF_INET, 4, &rptr->ipv4_prefix, s2, sizeof(s2)), + rptr->ipv4_prefix_length, + rptr->ea_length, + rptr->psid_offset, + (rptr->forwarding_mode == MAP_FORWARDING_MODE_T ? "translation" : "encapsulation"), + (rptr->forwarding_rule == MAP_FORWARDING_RULE_T ? "true" : "false")); + } + free(pptr); + + return 0; +} + +static int do_add_rule(int argc, char **argv) +{ + struct map_parm p; + struct map_parm *pptr; + struct map_rule_parm *rptr; + + pptr = malloc(sizeof(struct map_parm) + sizeof(struct map_rule_parm)); + if (!pptr) + return -1; + rptr = &pptr->rule[0]; + + //printf("pptr = %p, rptr = %p, sizeof(*pptr) = %d.\n", pptr, rptr, sizeof(*pptr)); + + if (parse_args(argc, argv, SIOCADDMAPRULES, pptr, rptr) < 0) + return -1; + + if (!rptr->forwarding_mode || !rptr->forwarding_rule) { + if (map_ioctl(pptr->name, SIOCGETMAP, &p)) { + //printf("XXX\n"); + return -1; + } + if (!rptr->forwarding_mode) + rptr->forwarding_mode = p.default_forwarding_mode; + if (!rptr->forwarding_rule) + rptr->forwarding_rule = p.default_forwarding_rule; + } + + pptr->rule_num = 1; + + if (map_ioctl(pptr->name, SIOCADDMAPRULES, pptr)) + return -1; + + return 0; +} + +static int do_del_rule(int argc, char **argv) +{ + struct map_parm *pptr; + struct map_rule_parm *rptr; + + pptr = malloc(sizeof(struct map_parm) + sizeof(struct map_rule_parm)); + if (!pptr) + return -1; + rptr = &pptr->rule[0]; + + if (parse_args(argc, argv, SIOCDELMAPRULES, pptr, rptr) < 0) + return -1; + + pptr->rule_num = 1; + + if (map_ioctl(pptr->name, SIOCDELMAPRULES, pptr)) + return -1; + + return 0; +} + +int do_ipmap(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "add") == 0) + return do_add(argc - 1, argv + 1); + if (matches(*argv, "change") == 0) + return do_change(argc - 1, argv + 1); + if (matches(*argv, "del") == 0) + return do_del(argc - 1, argv + 1); + if (matches(*argv, "show") == 0) + return do_show(argc - 1, argv + 1); + if (matches(*argv, "add-rule") == 0) + return do_add_rule(argc - 1, argv + 1); + if (matches(*argv, "del-rule") == 0) + return do_del_rule(argc - 1, argv + 1); + if (matches(*argv, "help") == 0) + usage(); + } else + return do_show(0, NULL); + + fprintf(stderr, "Command \"%s\" is unknown, try \"ip map help\".\n", *argv); + exit(-1); +}