From b2b56606889cd11b155472009a991d458ff119f7 Mon Sep 17 00:00:00 2001 From: "Andrey V. Elsukov" Date: Mon, 12 Nov 2018 11:20:59 +0000 Subject: [PATCH] Add ability to use dynamic external prefix in ipfw_nptv6 module. Now an interface name can be specified for nptv6 instance instead of ext_prefix. The module will track if_addr_ext events and when suitable IPv6 address will be added to specified interface, it will be configured as external prefix. When address disappears instance becomes unusable, i.e. it doesn't match any packets. Reviewed by: 0mp (manpages) Tested by: Dries Michiels MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D17765 --- sbin/ipfw/ipfw.8 | 11 +- sbin/ipfw/ipfw2.h | 1 + sbin/ipfw/nptv6.c | 30 +++++- sys/netinet6/ip_fw_nptv6.h | 6 +- sys/netpfil/ipfw/nptv6/nptv6.c | 177 ++++++++++++++++++++++++++++++--- sys/netpfil/ipfw/nptv6/nptv6.h | 7 +- 6 files changed, 211 insertions(+), 21 deletions(-) diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 17f02c67161e..e19b8a1efec4 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1,7 +1,7 @@ .\" .\" $FreeBSD$ .\" -.Dd October 21, 2018 +.Dd November 12, 2018 .Dt IPFW 8 .Os .Sh NAME @@ -3495,6 +3495,15 @@ NPTv6 module translates source address when it matches this prefix. .It Cm ext_prefix Ar ipv6_prefix IPv6 prefix used in external network. NPTv6 module translates destination address when it matches this prefix. +.It Cm ext_if Ar nic +The NPTv6 module will use first global IPv6 address from interface +.Ar nic +as external prefix. +It can be useful when IPv6 prefix of external network is dynamically obtained. +.Cm ext_prefix +and +.Cm ext_if +options are mutually exclusive. .It Cm prefixlen Ar length The length of specified IPv6 prefixes. It must be in range from 8 to 64. .El diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index 6f1481001861..bb0a4cdfdeb3 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -294,6 +294,7 @@ enum tokens { TOK_INTPREFIX, TOK_EXTPREFIX, TOK_PREFIXLEN, + TOK_EXTIF, TOK_TCPSETMSS, diff --git a/sbin/ipfw/nptv6.c b/sbin/ipfw/nptv6.c index 6164d8b1ef1d..a2f6a97ae2a7 100644 --- a/sbin/ipfw/nptv6.c +++ b/sbin/ipfw/nptv6.c @@ -152,6 +152,7 @@ static struct _s_x nptv6newcmds[] = { { "int_prefix", TOK_INTPREFIX }, { "ext_prefix", TOK_EXTPREFIX }, { "prefixlen", TOK_PREFIXLEN }, + { "ext_if", TOK_EXTIF }, { NULL, 0 } }; @@ -214,6 +215,9 @@ nptv6_create(const char *name, uint8_t set, int ac, char *av[]) ac--; av++; break; case TOK_EXTPREFIX: + if (flags & NPTV6_HAS_EXTPREFIX) + errx(EX_USAGE, + "Only one ext_prefix or ext_if allowed"); NEED1("IPv6 prefix required"); nptv6_parse_prefix(*av, &cfg->external, &plen); flags |= NPTV6_HAS_EXTPREFIX; @@ -221,6 +225,18 @@ nptv6_create(const char *name, uint8_t set, int ac, char *av[]) goto check_prefix; ac--; av++; break; + case TOK_EXTIF: + if (flags & NPTV6_HAS_EXTPREFIX) + errx(EX_USAGE, + "Only one ext_prefix or ext_if allowed"); + NEED1("Interface name required"); + if (strlen(*av) >= sizeof(cfg->if_name)) + errx(EX_USAGE, "Invalid interface name"); + flags |= NPTV6_HAS_EXTPREFIX; + cfg->flags |= NPTV6_DYNAMIC_PREFIX; + strncpy(cfg->if_name, *av, sizeof(cfg->if_name)); + ac--; av++; + break; case TOK_PREFIXLEN: NEED1("IPv6 prefix length required"); plen = strtol(*av, &p, 10); @@ -245,13 +261,14 @@ nptv6_create(const char *name, uint8_t set, int ac, char *av[]) if ((flags & NPTV6_HAS_INTPREFIX) != NPTV6_HAS_INTPREFIX) errx(EX_USAGE, "int_prefix required"); if ((flags & NPTV6_HAS_EXTPREFIX) != NPTV6_HAS_EXTPREFIX) - errx(EX_USAGE, "ext_prefix required"); + errx(EX_USAGE, "ext_prefix or ext_if required"); if ((flags & NPTV6_HAS_PREFIXLEN) != NPTV6_HAS_PREFIXLEN) errx(EX_USAGE, "prefixlen required"); n2mask(&mask, cfg->plen); APPLY_MASK(&cfg->internal, &mask); - APPLY_MASK(&cfg->external, &mask); + if ((cfg->flags & NPTV6_DYNAMIC_PREFIX) == 0) + APPLY_MASK(&cfg->external, &mask); olh->count = 1; olh->objsize = sizeof(*cfg); @@ -350,8 +367,13 @@ nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set) printf("set %u ", cfg->set); inet_ntop(AF_INET6, &cfg->internal, abuf, sizeof(abuf)); printf("nptv6 %s int_prefix %s ", cfg->name, abuf); - inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf)); - printf("ext_prefix %s prefixlen %u\n", abuf, cfg->plen); + if (cfg->flags & NPTV6_DYNAMIC_PREFIX) + printf("ext_if %s ", cfg->if_name); + else { + inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf)); + printf("ext_prefix %s ", abuf); + } + printf("prefixlen %u\n", cfg->plen); return (0); } diff --git a/sys/netinet6/ip_fw_nptv6.h b/sys/netinet6/ip_fw_nptv6.h index e2357effd1f7..a1c12c26e21d 100644 --- a/sys/netinet6/ip_fw_nptv6.h +++ b/sys/netinet6/ip_fw_nptv6.h @@ -40,11 +40,15 @@ struct ipfw_nptv6_stats { typedef struct _ipfw_nptv6_cfg { char name[64]; /* NPTv6 instance name */ struct in6_addr internal; /* NPTv6 internal prefix */ - struct in6_addr external; /* NPTv6 external prefix */ + union { + struct in6_addr external; /* NPTv6 external prefix */ + char if_name[IF_NAMESIZE]; + }; uint8_t plen; /* Prefix length */ uint8_t set; /* Named instance set [0..31] */ uint8_t spare[2]; uint32_t flags; +#define NPTV6_DYNAMIC_PREFIX 1 /* Use dynamic external prefix */ } ipfw_nptv6_cfg; #endif /* _NETINET6_IP_FW_NPTV6_H_ */ diff --git a/sys/netpfil/ipfw/nptv6/nptv6.c b/sys/netpfil/ipfw/nptv6/nptv6.c index f4724fc683e8..72608e2030c4 100644 --- a/sys/netpfil/ipfw/nptv6/nptv6.c +++ b/sys/netpfil/ipfw/nptv6/nptv6.c @@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -65,6 +66,8 @@ VNET_DEFINE_STATIC(uint16_t, nptv6_eid) = 0; #define V_nptv6_eid VNET(nptv6_eid) #define IPFW_TLV_NPTV6_NAME IPFW_TLV_EACTION_NAME(V_nptv6_eid) +static eventhandler_tag nptv6_ifaddr_event; + static struct nptv6_cfg *nptv6_alloc_config(const char *name, uint8_t set); static void nptv6_free_config(struct nptv6_cfg *cfg); static struct nptv6_cfg *nptv6_find(struct namedobj_instance *ni, @@ -357,7 +360,8 @@ ipfw_nptv6(struct ip_fw_chain *chain, struct ip_fw_args *args, if (cmd->opcode != O_EXTERNAL_ACTION || cmd->arg1 != V_nptv6_eid || icmd->opcode != O_EXTERNAL_INSTANCE || - (cfg = NPTV6_LOOKUP(chain, icmd)) == NULL) + (cfg = NPTV6_LOOKUP(chain, icmd)) == NULL || + (cfg->flags & NPTV6_READY) == 0) return (ret); /* * We need act as router, so when forwarding is disabled - @@ -442,7 +446,10 @@ nptv6_export_config(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, { uc->internal = cfg->internal; - uc->external = cfg->external; + if (cfg->flags & NPTV6_DYNAMIC_PREFIX) + memcpy(uc->if_name, cfg->if_name, IF_NAMESIZE); + else + uc->external = cfg->external; uc->plen = cfg->plen; uc->flags = cfg->flags & NPTV6_FLAGSMASK; uc->set = cfg->no.set; @@ -497,6 +504,140 @@ nptv6_calculate_adjustment(struct nptv6_cfg *cfg) cfg->adjustment = cksum_add(~e, i); } +static int +nptv6_check_prefix(const struct in6_addr *addr) +{ + + if (IN6_IS_ADDR_MULTICAST(addr) || + IN6_IS_ADDR_LINKLOCAL(addr) || + IN6_IS_ADDR_LOOPBACK(addr) || + IN6_IS_ADDR_UNSPECIFIED(addr)) + return (EINVAL); + return (0); +} + +static void +nptv6_set_external(struct nptv6_cfg *cfg, struct in6_addr *addr) +{ + + cfg->external = *addr; + IN6_MASK_ADDR(&cfg->external, &cfg->mask); + nptv6_calculate_adjustment(cfg); + cfg->flags |= NPTV6_READY; +} + +/* + * Try to determine what prefix to use as external for + * configured interface name. + */ +static void +nptv6_find_prefix(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, + struct ifnet *ifp) +{ + struct ifaddr *ifa; + struct in6_ifaddr *ia; + + MPASS(cfg->flags & NPTV6_DYNAMIC_PREFIX); + IPFW_UH_WLOCK_ASSERT(ch); + + if (ifp == NULL) { + ifp = ifunit_ref(cfg->if_name); + if (ifp == NULL) + return; + } + if_addr_rlock(ifp); + CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + ia = (struct in6_ifaddr *)ifa; + if (nptv6_check_prefix(&ia->ia_addr.sin6_addr) || + IN6_ARE_MASKED_ADDR_EQUAL(&ia->ia_addr.sin6_addr, + &cfg->internal, &cfg->mask)) + continue; + /* Suitable address is found. */ + nptv6_set_external(cfg, &ia->ia_addr.sin6_addr); + break; + } + if_addr_runlock(ifp); + if_rele(ifp); +} + +struct ifaddr_event_args { + struct ifnet *ifp; + const struct in6_addr *addr; + int event; +}; + +static int +ifaddr_cb(struct namedobj_instance *ni, struct named_object *no, + void *arg) +{ + struct ifaddr_event_args *args; + struct ip_fw_chain *ch; + struct nptv6_cfg *cfg; + + ch = &V_layer3_chain; + cfg = (struct nptv6_cfg *)SRV_OBJECT(ch, no->kidx); + if ((cfg->flags & NPTV6_DYNAMIC_PREFIX) == 0) + return (0); + + args = arg; + /* If interface name doesn't match, ignore */ + if (strncmp(args->ifp->if_xname, cfg->if_name, IF_NAMESIZE)) + return (0); + if (args->ifp->if_flags & IFF_DYING) { /* XXX: is it possible? */ + cfg->flags &= ~NPTV6_READY; + return (0); + } + if (args->event == IFADDR_EVENT_DEL) { + /* If instance is not ready, ignore */ + if ((cfg->flags & NPTV6_READY) == 0) + return (0); + /* If address does not match the external prefix, ignore */ + if (IN6_ARE_MASKED_ADDR_EQUAL(&cfg->external, args->addr, + &cfg->mask) != 0) + return (0); + /* Otherwise clear READY flag */ + cfg->flags &= ~NPTV6_READY; + } else {/* IFADDR_EVENT_ADD */ + /* If instance is already ready, ignore */ + if (cfg->flags & NPTV6_READY) + return (0); + /* If address is not suitable for prefix, ignore */ + if (nptv6_check_prefix(args->addr) || + IN6_ARE_MASKED_ADDR_EQUAL(args->addr, &cfg->internal, + &cfg->mask)) + return (0); + /* FALLTHROUGH */ + } + MPASS(!(cfg->flags & NPTV6_READY)); + /* Try to determine the prefix */ + if_ref(args->ifp); + nptv6_find_prefix(ch, cfg, args->ifp); + return (0); +} + +static void +nptv6_ifaddrevent_handler(void *arg __unused, struct ifnet *ifp, + struct ifaddr *ifa, int event) +{ + struct ifaddr_event_args args; + struct ip_fw_chain *ch; + + if (ifa->ifa_addr->sa_family != AF_INET6) + return; + + args.ifp = ifp; + args.addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + args.event = event; + + ch = &V_layer3_chain; + IPFW_UH_WLOCK(ch); + ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), ifaddr_cb, &args, + IPFW_TLV_NPTV6_NAME); + IPFW_UH_WUNLOCK(ch); +} + /* * Creates new NPTv6 instance. * Data layout (v0)(current): @@ -523,15 +664,12 @@ nptv6_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, return (EINVAL); if (uc->plen < 8 || uc->plen > 64 || uc->set >= IPFW_MAX_SETS) return (EINVAL); - if (IN6_IS_ADDR_MULTICAST(&uc->internal) || - IN6_IS_ADDR_MULTICAST(&uc->external) || - IN6_IS_ADDR_UNSPECIFIED(&uc->internal) || - IN6_IS_ADDR_UNSPECIFIED(&uc->external) || - IN6_IS_ADDR_LINKLOCAL(&uc->internal) || - IN6_IS_ADDR_LINKLOCAL(&uc->external)) + if (nptv6_check_prefix(&uc->internal)) return (EINVAL); in6_prefixlen2mask(&mask, uc->plen); - if (IN6_ARE_MASKED_ADDR_EQUAL(&uc->internal, &uc->external, &mask)) + if ((uc->flags & NPTV6_DYNAMIC_PREFIX) == 0 && ( + nptv6_check_prefix(&uc->external) || + IN6_ARE_MASKED_ADDR_EQUAL(&uc->external, &uc->internal, &mask))) return (EINVAL); ni = CHAIN_TO_SRV(ch); @@ -544,14 +682,22 @@ nptv6_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, cfg = nptv6_alloc_config(uc->name, uc->set); cfg->plen = uc->plen; + cfg->flags = uc->flags & NPTV6_FLAGSMASK; if (cfg->plen <= 48) cfg->flags |= NPTV6_48PLEN; - cfg->internal = uc->internal; - cfg->external = uc->external; cfg->mask = mask; + cfg->internal = uc->internal; IN6_MASK_ADDR(&cfg->internal, &mask); - IN6_MASK_ADDR(&cfg->external, &mask); - nptv6_calculate_adjustment(cfg); + if (cfg->flags & NPTV6_DYNAMIC_PREFIX) + memcpy(cfg->if_name, uc->if_name, IF_NAMESIZE); + else + nptv6_set_external(cfg, &uc->external); + + if ((uc->flags & NPTV6_DYNAMIC_PREFIX) != 0 && + nptv6_ifaddr_event == NULL) + nptv6_ifaddr_event = EVENTHANDLER_REGISTER( + ifaddr_event_ext, nptv6_ifaddrevent_handler, NULL, + EVENTHANDLER_PRI_ANY); IPFW_UH_WLOCK(ch); if (ipfw_objhash_alloc_idx(ni, &cfg->no.kidx) != 0) { @@ -561,7 +707,10 @@ nptv6_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, } ipfw_objhash_add(ni, &cfg->no); SRV_OBJECT(ch, cfg->no.kidx) = cfg; + if (cfg->flags & NPTV6_DYNAMIC_PREFIX) + nptv6_find_prefix(ch, cfg, NULL); IPFW_UH_WUNLOCK(ch); + return (0); } @@ -870,6 +1019,8 @@ void nptv6_uninit(struct ip_fw_chain *ch, int last) { + if (last && nptv6_ifaddr_event != NULL) + EVENTHANDLER_DEREGISTER(ifaddr_event_ext, nptv6_ifaddr_event); IPFW_DEL_OBJ_REWRITER(last, opcodes); IPFW_DEL_SOPT_HANDLER(last, scodes); ipfw_del_eaction(ch, V_nptv6_eid); diff --git a/sys/netpfil/ipfw/nptv6/nptv6.h b/sys/netpfil/ipfw/nptv6/nptv6.h index 95b04bfe724f..df5a4e8bf0ea 100644 --- a/sys/netpfil/ipfw/nptv6/nptv6.h +++ b/sys/netpfil/ipfw/nptv6/nptv6.h @@ -51,11 +51,14 @@ struct nptv6_cfg { uint16_t adjustment; /* Checksum adjustment value */ uint8_t plen; /* Prefix length */ uint8_t flags; /* Flags for internal use */ -#define NPTV6_48PLEN 0x0001 +#define NPTV6_READY 0x80 +#define NPTV6_48PLEN 0x40 + + char if_name[IF_NAMESIZE]; char name[64]; /* Instance name */ counter_u64_t stats[NPTV6STATS]; /* Statistics counters */ }; -#define NPTV6_FLAGSMASK 0 +#define NPTV6_FLAGSMASK (NPTV6_DYNAMIC_PREFIX) int nptv6_init(struct ip_fw_chain *ch, int first); void nptv6_uninit(struct ip_fw_chain *ch, int last);