diff --git a/sys/netinet/libalias/alias_db.h b/sys/netinet/libalias/alias_db.h --- a/sys/netinet/libalias/alias_db.h +++ b/sys/netinet/libalias/alias_db.h @@ -292,6 +292,7 @@ struct { SPLAY_ENTRY(alias_link) out; LIST_ENTRY (alias_link) in; + SPLAY_ENTRY(alias_link) source; } all; struct { LIST_ENTRY (alias_link) list; @@ -374,6 +375,17 @@ } SPLAY_PROTOTYPE(splay_in, group_in, in, cmp_in); +static inline int +cmp_source(struct alias_link *a, struct alias_link *b) { + int i = a->link_type - b->link_type; + if (i != 0) return (i); + if (a->src_addr.s_addr > b->src_addr.s_addr) return (1); + if (a->src_addr.s_addr < b->src_addr.s_addr) return (-1); + i = a->src_port - b->src_port; + return (i); +} +SPLAY_PROTOTYPE(splay_source, alias_link, all.source, cmp_source); + /* Internal routines for finding, deleting and adding links Port Allocation: @@ -390,6 +402,7 @@ Link search: FindLinkOut() - find link for outgoing packets FindLinkIn() - find link for incoming packets + FindLinkBySource() - find link by a packet's source address and port Port search: FindNewPortGroup() - find an available group of ports @@ -417,6 +430,9 @@ static struct alias_link * FindLinkIn(struct libalias *, struct in_addr, struct in_addr, u_short, u_short, int, int); +static struct alias_link * +FindLinkBySource(struct libalias *, struct in_addr, u_short, int); + static u_short _RandomPort(struct libalias *la); #define GET_NEW_PORT_MAX_ATTEMPTS 20 diff --git a/sys/netinet/libalias/alias_db.c b/sys/netinet/libalias/alias_db.c --- a/sys/netinet/libalias/alias_db.c +++ b/sys/netinet/libalias/alias_db.c @@ -93,6 +93,7 @@ SPLAY_GENERATE(splay_out, alias_link, all.out, cmp_out); SPLAY_GENERATE(splay_in, group_in, in, cmp_in); +SPLAY_GENERATE(splay_source, alias_link, all.source, cmp_source); static struct group_in * StartPointIn(struct libalias *la, @@ -235,6 +236,16 @@ max_trials = GET_NEW_PORT_MAX_ATTEMPTS; + /* For UDP, try to reuse the same alias address:port for all destinations + from the same internal address:port, as per RFC 4787. */ + if (lnk->link_type == LINK_UDP) { + struct alias_link *search_result = FindLinkBySource(la, lnk->src_addr, lnk->src_port, lnk->link_type); + if (search_result != NULL) { + lnk->alias_port = search_result->alias_port; + return (0); + } + } + /* * When the PKT_ALIAS_SAME_PORTS option is chosen, * the first try will be the actual source port. If @@ -254,13 +265,16 @@ if (grp == NULL) break; - LIST_FOREACH(search_result, &grp->full, all.in) { - if (lnk->dst_addr.s_addr == search_result->dst_addr.s_addr && - lnk->dst_port == search_result->dst_port) - break; /* found match */ + /* As per RFC 4787, UDP cannot share the same alias port among multiple internal endpoints */ + if (lnk->link_type != LINK_UDP) { + LIST_FOREACH(search_result, &grp->full, all.in) { + if (lnk->dst_addr.s_addr == search_result->dst_addr.s_addr && + lnk->dst_port == search_result->dst_port) + break; /* found match */ + } + if (search_result == NULL) + break; } - if (search_result == NULL) - break; } if (i >= max_trials) { @@ -496,6 +510,9 @@ /* Adjust input table pointers */ LIST_REMOVE(lnk, all.in); + /* Adjust "by source" table pointer */ + SPLAY_REMOVE(splay_source, &la->linkSplayBySource, lnk); + /* Remove intermediate node, if empty */ grp = StartPointIn(la, lnk->alias_addr, lnk->alias_port, lnk->link_type, 0); if (grp != NULL && @@ -696,6 +713,9 @@ LIST_INSERT_HEAD(&grp->partial, lnk, all.in); else LIST_INSERT_HEAD(&grp->full, lnk, all.in); + + /* Set up pointers for "by source" lookup table */ + SPLAY_INSERT(splay_source, &la->linkSplayBySource, lnk); } break; } @@ -980,6 +1000,20 @@ return (lnk); } +static struct alias_link * +FindLinkBySource(struct libalias *la, struct in_addr src_addr, + u_short src_port, + int link_type) +{ + struct alias_link needle = { + .src_addr = src_addr, + .src_port = src_port, + .link_type = link_type + }; + LIBALIAS_LOCK_ASSERT(la); + return SPLAY_FIND(splay_source, &la->linkSplayBySource, &needle); +} + /* External routines for finding/adding links -- "external" means outside alias_db.c, but within alias*.c -- @@ -2110,6 +2144,7 @@ SPLAY_INIT(&la->linkSplayIn); SPLAY_INIT(&la->linkSplayOut); + SPLAY_INIT(&la->linkSplayBySource); LIST_INIT(&la->pptpList); TAILQ_INIT(&la->checkExpire); #ifdef _KERNEL diff --git a/sys/netinet/libalias/alias_local.h b/sys/netinet/libalias/alias_local.h --- a/sys/netinet/libalias/alias_local.h +++ b/sys/netinet/libalias/alias_local.h @@ -94,10 +94,11 @@ * if no aliasing link already exists */ struct in_addr targetAddress; /* Lookup table of pointers to chains of link records. - * Each link record is doubly indexed into input and - * output lookup tables. */ + * Each link record is indexed into input, + * output and "by source" lookup tables. */ SPLAY_HEAD(splay_out, alias_link) linkSplayOut; SPLAY_HEAD(splay_in, group_in) linkSplayIn; + SPLAY_HEAD(splay_source, alias_link) linkSplayBySource; LIST_HEAD (, alias_link) pptpList; /* HouseKeeping */ TAILQ_HEAD (, alias_link) checkExpire; diff --git a/tests/sys/netinet/libalias/2_natout.c b/tests/sys/netinet/libalias/2_natout.c --- a/tests/sys/netinet/libalias/2_natout.c +++ b/tests/sys/netinet/libalias/2_natout.c @@ -311,7 +311,12 @@ struct libalias *la = LibAliasInit(NULL); struct ip *po; struct udphdr *uo; - uint16_t sport = 0x1234; + uint16_t sport1 = 0x1234; + uint16_t sport2 = 0x1235; + uint16_t sport3 = 0x1236; + uint16_t sport4 = 0x1237; + uint16_t sport5 = 0x1238; + uint16_t sport6 = 0x1239; uint16_t dport = 0x5678; uint16_t aport; @@ -321,37 +326,35 @@ po = ip_packet(0, 64); LibAliasSetAliasPortRange(la, 0, 0); /* reinit like ipfw */ - UDP_NAT_CHECK(po, uo, prv1, sport, ext, dport, masq); + UDP_NAT_CHECK(po, uo, prv1, sport1, ext, dport, masq); aport = ntohs(uo->uh_sport); ATF_CHECK(aport >= 0x8000); /* Different larger range */ LibAliasSetAliasPortRange(la, 2000, 3000); dport++; - UDP_NAT_CHECK(po, uo, prv1, sport, ext, dport, masq); + UDP_NAT_CHECK(po, uo, prv1, sport2, ext, dport, masq); aport = ntohs(uo->uh_sport); ATF_CHECK(aport >= 2000 && aport < 3000); /* Different small range (contains two ports) */ LibAliasSetAliasPortRange(la, 4000, 4001); dport++; - UDP_NAT_CHECK(po, uo, prv1, sport, ext, dport, masq); + UDP_NAT_CHECK(po, uo, prv1, sport3, ext, dport, masq); aport = ntohs(uo->uh_sport); ATF_CHECK(aport >= 4000 && aport <= 4001); - sport++; - UDP_NAT_CHECK(po, uo, prv1, sport, ext, dport, masq); + UDP_NAT_CHECK(po, uo, prv1, sport4, ext, dport, masq); aport = ntohs(uo->uh_sport); ATF_CHECK(aport >= 4000 && aport <= 4001); /* Third port not available in the range */ - sport++; - UDP_NAT_FAIL(po, uo, prv1, sport, ext, dport); + UDP_NAT_FAIL(po, uo, prv1, sport5, ext, dport); /* Back to normal */ LibAliasSetAliasPortRange(la, 0, 0); dport++; - UDP_NAT_CHECK(po, uo, prv1, sport, ext, dport, masq); + UDP_NAT_CHECK(po, uo, prv1, sport6, ext, dport, masq); aport = ntohs(uo->uh_sport); ATF_CHECK(aport >= 0x8000); @@ -359,6 +362,43 @@ LibAliasUninit(la); } +ATF_TC_WITHOUT_HEAD(9_udp_mapping); +ATF_TC_BODY(9_udp_mapping, dummy) +{ + struct libalias *la = LibAliasInit(NULL); + struct ip *po, *po2, *po3; + struct udphdr *uo, *uo2, *uo3; + uint16_t sport = 0x1234; + uint16_t dport = 0x5678; + uint16_t dport2 = 0x6789; + uint16_t aport, aport2, aport3; + + ATF_REQUIRE(la != NULL); + LibAliasSetAddress(la, masq); + LibAliasSetMode(la, 0, ~0); + + po = ip_packet(0, 64); + UDP_NAT_CHECK(po, uo, prv1, sport, ext, dport, masq); + aport = ntohs(uo->uh_sport); + + /* Change of dst port shouldn't change alias port */ + po2 = ip_packet(0, 64); + UDP_NAT_CHECK(po2, uo2, prv1, sport, ext, dport2, masq); + aport2 = ntohs(uo2->uh_sport); + ATF_CHECK_EQ_MSG(aport, aport2, "NAT uses address- and port-dependent mapping (%uh -> %uh)", aport, aport2); + + /* Change of dst address shouldn't change alias port */ + po3 = ip_packet(0, 64); + UDP_NAT_CHECK(po3, uo3, prv1, sport, pub, dport, masq); + aport3 = ntohs(uo3->uh_sport); + ATF_CHECK_EQ_MSG(aport, aport3, "NAT uses address-dependent mapping"); + + free(po); + free(po2); + free(po3); + LibAliasUninit(la); +} + ATF_TP_ADD_TCS(natout) { /* Use "dd if=/dev/random bs=2 count=1 | od -x" to reproduce */ @@ -372,6 +412,7 @@ ATF_TP_ADD_TC(natout, 6_cleartable); ATF_TP_ADD_TC(natout, 7_stress); ATF_TP_ADD_TC(natout, 8_portrange); + ATF_TP_ADD_TC(natout, 9_udp_mapping); return atf_no_error(); } diff --git a/tests/sys/netinet/libalias/util.h b/tests/sys/netinet/libalias/util.h --- a/tests/sys/netinet/libalias/util.h +++ b/tests/sys/netinet/libalias/util.h @@ -41,7 +41,7 @@ #define _UTIL_H /* common ip ranges */ -extern struct in_addr masq, pub, prv1, prv2, prv3, cgn, ext, ANY_ADDR; +extern struct in_addr masq, pub, pub2, prv1, prv2, prv3, cgn, ext, ANY_ADDR; int randcmp(const void *a, const void *b); void hexdump(void *p, size_t len); diff --git a/tests/sys/netinet/libalias/util.c b/tests/sys/netinet/libalias/util.c --- a/tests/sys/netinet/libalias/util.c +++ b/tests/sys/netinet/libalias/util.c @@ -41,6 +41,7 @@ /* common ip ranges */ struct in_addr masq = { htonl(0x01020304) }; struct in_addr pub = { htonl(0x0102dead) }; +struct in_addr pub2 = { htonl(0x0102beef) }; struct in_addr prv1 = { htonl(0x0a00dead) }; struct in_addr prv2 = { htonl(0xac10dead) }; struct in_addr prv3 = { htonl(0xc0a8dead) };