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 <driesm dot michiels gmail com>
MFC after:	1 month
Differential Revision:	https://reviews.freebsd.org/D17765
This commit is contained in:
ae 2018-11-12 11:20:59 +00:00
parent c18ab92e2c
commit 1382ea4ffb
6 changed files with 211 additions and 21 deletions

View File

@ -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

View File

@ -294,6 +294,7 @@ enum tokens {
TOK_INTPREFIX,
TOK_EXTPREFIX,
TOK_PREFIXLEN,
TOK_EXTIF,
TOK_TCPSETMSS,

View File

@ -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);
}

View File

@ -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_ */

View File

@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/counter.h>
#include <sys/eventhandler.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/lock.h>
@ -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);

View File

@ -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);