diff --git a/sys/netinet/libalias/alias.h b/sys/netinet/libalias/alias.h --- a/sys/netinet/libalias/alias.h +++ b/sys/netinet/libalias/alias.h @@ -227,6 +227,30 @@ */ #define PKT_ALIAS_UNREGISTERED_CGN 0x400 +/* + * If PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING is set, UDP mapping is + * endpoint-independent as per RFC 4787 req 1 (a.k.a. "full cone" NAT of + * RFC 3489). This means that all UDP packets from the same internal + * address:port, are mapped to the same NAT address:port, regardless of their + * destination. Furthermore, if PKT_ALIAS_DENY_INCOMING is unset, any other + * external address:port can send to that internal address:port through the + * mapped NAT address:port. UDP applications can discover their NAT + * addess:port by using a STUN server, and communicate it to others who can + * contact them there, which is highly desirable for VPNs, VOIP, peer-to-peer, + * games, etc. + * + * If this bit is unset, UDP mapping is address and port-dependent + * ("symmetric" NAT). The internal application cannot know or predict the NAT + * address:port, and is crippled to communicating with only a single external + * address:port that is known ahead of time. Two such applications each behind + * its own such NAT can only connect to each other by tunnelling through an + * intermediate server (eg. TURN, RFC 8656). However, up to 2^32 UDP mappings + * can be created per external interface, while with the bit set, only 2^16 + * can be. This bit is set after a call to PacketAliasInit(), so it is + * a default mode of operation. + */ +#define PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING 0x800 + /* Function return codes. */ #define PKT_ALIAS_ERROR -1 #define PKT_ALIAS_OK 1 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,19 @@ max_trials = GET_NEW_PORT_MAX_ATTEMPTS; + if ((la->packetAliasMode & PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING) && + lnk->link_type == LINK_UDP) { + /* Try reuse the same alias address:port for all destinations + * from the same internal address:port, as per RFC 4787. + */ + 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 +268,20 @@ 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 (!(la->packetAliasMode & PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING) || + 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 +517,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 +720,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; } @@ -964,6 +991,14 @@ lnk = _FindLinkIn(la, dst_addr, alias_addr, dst_port, alias_port, link_type, replace_partial_links); + if (lnk == NULL && + (la->packetAliasMode & PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING) && + link_type == LINK_UDP && + !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) { + lnk = _FindLinkIn(la, ANY_ADDR, alias_addr, 0, alias_port, + link_type, replace_partial_links); + } + if (lnk == NULL) { /* * The following allows permanent links to be specified as @@ -980,6 +1015,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 +2159,7 @@ SPLAY_INIT(&la->linkSplayIn); SPLAY_INIT(&la->linkSplayOut); + SPLAY_INIT(&la->linkSplayBySource); LIST_INIT(&la->pptpList); TAILQ_INIT(&la->checkExpire); #ifdef _KERNEL @@ -2145,7 +2195,8 @@ #ifndef NO_USE_SOCKETS | PKT_ALIAS_USE_SOCKETS #endif - | PKT_ALIAS_RESET_ON_ADDR_CHANGE; + | PKT_ALIAS_RESET_ON_ADDR_CHANGE + | PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING; #ifndef NO_FW_PUNCH la->fireWallFD = -1; #endif 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/sys/netinet/libalias/libalias.3 b/sys/netinet/libalias/libalias.3 --- a/sys/netinet/libalias/libalias.3 +++ b/sys/netinet/libalias/libalias.3 @@ -92,6 +92,8 @@ .Dv PKT_ALIAS_USE_SOCKETS .It .Dv PKT_ALIAS_RESET_ON_ADDR_CHANGE +.It +.Dv PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING .El .Pp This function will always return the packet aliasing engine to the same @@ -270,6 +272,24 @@ in .Xr ipfw 8 for more details. +.It Dv PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING +If this bit is set, UDP mapping is endpoint-independent as per RFC 4787 req 1 +(a.k.a. "full cone" NAT of RFC 3489). This means that all UDP packets from the +same internal address:port, are mapped to the same NAT address:port, +regardless of their destination. Furthermore, if PKT_ALIAS_DENY_INCOMING is +unset, any other external address:port can send to that internal address:port +through the mapped NAT address:port. UDP applications can discover their NAT +addess:port by using a STUN server, and communicate it to others who can +contact them there, which is highly desirable for VPNs, VOIP, peer-to-peer, +games, etc. +.Pp +If this bit is unset, UDP mapping is address and port-dependent ("symmetric" +NAT). The internal application cannot know or predict the NAT address:port, +and is crippled to communicating with only a single external address:port that +is known ahead of time. Two such applications each behind its own such NAT can +only connect to each other by tunnelling through an intermediate server (eg. +TURN, RFC 8656). However, up to 2^32 UDP mappings can be created per external +interface, while with the bit set, only 2^16 can be. .El .Ed .Pp 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,172 @@ 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, PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING, ~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_TC_WITHOUT_HEAD(10_udp_out_in); +ATF_TC_BODY(10_udp_out_in, 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; + + ATF_REQUIRE(la != NULL); + LibAliasSetAddress(la, masq); + LibAliasSetMode(la, PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING, ~0); + + po = ip_packet(0, 64); + UDP_NAT_CHECK(po, uo, prv1, sport, pub, dport, masq); + aport = ntohs(uo->uh_sport); + + /* Accepts inbound packets from different port */ + po2 = ip_packet(0, 64); + UDP_UNNAT_CHECK(po2, uo2, pub, dport2, masq, aport, prv1, sport); + + /* Accepts inbound packets from differerent host and port */ + po3 = ip_packet(0, 64); + UDP_UNNAT_CHECK(po3, uo3, pub2, dport2, masq, aport, prv1, sport); + + free(po); + free(po2); + free(po3); + LibAliasUninit(la); +} + +ATF_TC_WITHOUT_HEAD(11_udp_with_deny_incoming); +ATF_TC_BODY(11_udp_with_deny_incoming, dummy) +{ + struct libalias *la = LibAliasInit(NULL); + struct ip *po, *po2, *po3, *po4; + struct udphdr *uo; + uint16_t sport = 0x1234; + uint16_t dport = 0x5678; + uint16_t dport2 = 0x6789; + uint16_t aport; + int ret; + + ATF_REQUIRE(la != NULL); + LibAliasSetAddress(la, masq); + LibAliasSetMode(la, + PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING | PKT_ALIAS_DENY_INCOMING, + ~0); + + po = ip_packet(0, 64); + UDP_NAT_CHECK(po, uo, prv1, sport, pub, dport, masq); + aport = ntohs(uo->uh_sport); + + po2 = ip_packet(0, 64); + po2->ip_src = pub; + po2->ip_dst = masq; + set_udp(po2, dport, aport); + ret = LibAliasIn(la, po2, 64); + ATF_CHECK_EQ_MSG(PKT_ALIAS_OK, ret, + "LibAliasIn failed with error %d\n", ret); + + po3 = ip_packet(0, 64); + po3->ip_src = pub; + po3->ip_dst = masq; + set_udp(po3, dport2, aport); + ret = LibAliasIn(la, po3, 64); + ATF_CHECK_EQ_MSG(PKT_ALIAS_IGNORED, ret, + "incoming packet from different port not ignored " + "with PKT_ALIAS_DENY_INCOMING"); + + po4 = ip_packet(0, 64); + po4->ip_src = pub2; + po4->ip_dst = masq; + set_udp(po4, dport2, aport); + ret = LibAliasIn(la, po4, 64); + ATF_CHECK_EQ_MSG(PKT_ALIAS_IGNORED, ret, + "incoming packet from different address and port not ignored " + "with PKT_ALIAS_DENY_INCOMING"); + + free(po); + free(po2); + free(po3); + free(po4); + LibAliasUninit(la); +} + +ATF_TC_WITHOUT_HEAD(12_udp_hairpinning); +ATF_TC_BODY(12_udp_hairpinning, dummy) +{ + struct libalias *la = LibAliasInit(NULL); + struct ip *po, *po2, *po3; + struct udphdr *uo, *uo2, *uo3; + uint16_t sport1 = 0x1234; + uint16_t sport2 = 0x2345; + uint16_t dport = 0x5678; + uint16_t extport1, extport2; + + ATF_REQUIRE(la != NULL); + LibAliasSetAddress(la, masq); + LibAliasSetMode(la, PKT_ALIAS_ENDPOINT_INDEPENDENT_MAPPING, ~0); + + /* prv1 sends out somewhere (eg. a STUN server) */ + po = ip_packet(0, 64); + UDP_NAT_CHECK(po, uo, prv1, sport1, pub, dport, masq); + extport1 = ntohs(uo->uh_sport); + + /* prv2, behind the same NAT as prv1, also sends out somewhere */ + po2 = ip_packet(0, 64); + UDP_NAT_CHECK(po2, uo2, prv2, sport2, pub, dport, masq); + extport2 = ntohs(uo2->uh_sport); + + /* hairpin: prv1 sends to prv2's external NAT mapping + * (unaware it could address it internally instead). + */ + po3 = ip_packet(0, 64); + UDP_NAT_CHECK(po3, uo3, prv1, sport1, masq, extport2, masq); + UDP_UNNAT_CHECK(po3, uo3, masq, extport1, masq, extport2, + prv2, sport2); + + 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 +541,10 @@ 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); + ATF_TP_ADD_TC(natout, 10_udp_out_in); + ATF_TP_ADD_TC(natout, 11_udp_with_deny_incoming); + ATF_TP_ADD_TC(natout, 12_udp_hairpinning); 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) };