git: 61bf830cbb26 - main - libalias: Add support for EIM NAT
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 05 Dec 2024 16:26:37 UTC
The branch main has been updated by thj: URL: https://cgit.FreeBSD.org/src/commit/?id=61bf830cbb260c2a046cb44421d319184393e028 commit 61bf830cbb260c2a046cb44421d319184393e028 Author: Damjan Jovanovic <damjan.jov@gmail.com> AuthorDate: 2024-12-05 16:19:13 +0000 Commit: Tom Jones <thj@FreeBSD.org> CommitDate: 2024-12-05 16:19:13 +0000 libalias: Add support for EIM NAT Add support for endpoint-independent mapping ("full cone NAT") in Libalias's UDP NAT. This conforms to RFC 4787 requirements 1 and 3. All UDP packets sent out from a particular internal address:port leave via the same NAT address:port, regardless of their destination. Add some libalias tests and supporting defines. Reviewed by: igoro, thj Differential Revision: https://reviews.freebsd.org/D46689D --- sys/netinet/libalias/alias.h | 20 ++++ sys/netinet/libalias/alias_db.c | 58 +++++++++++- sys/netinet/libalias/alias_db.h | 43 ++++++--- sys/netinet/libalias/alias_local.h | 6 +- sys/netinet/libalias/libalias.3 | 22 ++++- tests/sys/netinet/libalias/2_natout.c | 170 ++++++++++++++++++++++++++++++++++ tests/sys/netinet/libalias/util.c | 1 + tests/sys/netinet/libalias/util.h | 2 +- 8 files changed, 304 insertions(+), 18 deletions(-) diff --git a/sys/netinet/libalias/alias.h b/sys/netinet/libalias/alias.h index 706184552429..96d8ceec28be 100644 --- a/sys/netinet/libalias/alias.h +++ b/sys/netinet/libalias/alias.h @@ -227,6 +227,26 @@ struct mbuf *m_megapullup(struct mbuf *, int); */ #define PKT_ALIAS_UNREGISTERED_CGN 0x400 +/* + * When this bit is set, UDP uses endpoint-independent mapping (EIM), as per + * RFC 4787 ("full cone" NAT of RFC 3489). All packets from the same internal + * address:port are mapped to the same NAT address:port, regardless of their + * destination address:port. If filtering rules allow, and if + * PKT_ALIAS_DENY_INCOMING is unset, any other external address:port can also + * send to the internal address:port through its mapped NAT address:port. This + * is more compatible with applications, and can reduce the need for port + * forwarding, but less scalable as each NAT address:port can only be + * concurrently used by at most one internal address:port. + * + * When this bit is unset, UDP packets use endpoint-dependent mapping (EDM) + * ("symmetric" NAT). Each connection from a particular internal address:port + * to different external addresses:ports is mapped to a random and + * unpredictable NAT address:port. Two appplications behind EDM NATs can only + * connect to each other by port forwarding on the NAT, or tunnelling through + * an in-between server. + */ +#define PKT_ALIAS_UDP_EIM 0x800 + /* Function return codes. */ #define PKT_ALIAS_ERROR -1 #define PKT_ALIAS_OK 1 diff --git a/sys/netinet/libalias/alias_db.c b/sys/netinet/libalias/alias_db.c index 4bb95549aaaf..b09e41935d93 100644 --- a/sys/netinet/libalias/alias_db.c +++ b/sys/netinet/libalias/alias_db.c @@ -93,6 +93,8 @@ DECLARE_MODULE(alias, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND); SPLAY_GENERATE(splay_out, alias_link, all.out, cmp_out); SPLAY_GENERATE(splay_in, group_in, in, cmp_in); +SPLAY_GENERATE(splay_internal_endpoint, alias_link, all.internal_endpoint, + cmp_internal_endpoint); static struct group_in * StartPointIn(struct libalias *la, @@ -235,6 +237,19 @@ GetNewPort(struct libalias *la, struct alias_link *lnk, int alias_port_param) max_trials = GET_NEW_PORT_MAX_ATTEMPTS; + if ((la->packetAliasMode & PKT_ALIAS_UDP_EIM) && + 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 = FindLinkByInternalEndpoint( + 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,10 +269,18 @@ GetNewPort(struct libalias *la, struct alias_link *lnk, int alias_port_param) if (grp == NULL) break; + /* As per RFC 4787, UDP cannot share the same alias port among + * multiple internal endpoints + */ + if ((la->packetAliasMode & PKT_ALIAS_UDP_EIM) && + lnk->link_type == LINK_UDP) + continue; + LIST_FOREACH(search_result, &grp->full, all.in) { - if (lnk->dst_addr.s_addr == search_result->dst_addr.s_addr && + if (lnk->dst_addr.s_addr == + search_result->dst_addr.s_addr && lnk->dst_port == search_result->dst_port) - break; /* found match */ + break; /* found match */ } if (search_result == NULL) break; @@ -496,6 +519,10 @@ DeleteLink(struct alias_link **plnk, int deletePermanent) /* Adjust input table pointers */ LIST_REMOVE(lnk, all.in); + /* Adjust "internal endpoint" table pointer */ + SPLAY_REMOVE(splay_internal_endpoint, + &la->linkSplayInternalEndpoint, lnk); + /* Remove intermediate node, if empty */ grp = StartPointIn(la, lnk->alias_addr, lnk->alias_port, lnk->link_type, 0); if (grp != NULL && @@ -696,6 +723,10 @@ AddLink(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, LIST_INSERT_HEAD(&grp->partial, lnk, all.in); else LIST_INSERT_HEAD(&grp->full, lnk, all.in); + + /* Set up pointers for "internal endpoint" lookup table */ + SPLAY_INSERT(splay_internal_endpoint, + &la->linkSplayInternalEndpoint, lnk); } break; } @@ -964,6 +995,14 @@ FindLinkIn(struct libalias *la, struct in_addr dst_addr, lnk = _FindLinkIn(la, dst_addr, alias_addr, dst_port, alias_port, link_type, replace_partial_links); + if (lnk == NULL && + (la->packetAliasMode & PKT_ALIAS_UDP_EIM) && + 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 +1019,20 @@ FindLinkIn(struct libalias *la, struct in_addr dst_addr, return (lnk); } +static struct alias_link * +FindLinkByInternalEndpoint(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_internal_endpoint, &la->linkSplayInternalEndpoint, &needle); +} + /* External routines for finding/adding links -- "external" means outside alias_db.c, but within alias*.c -- @@ -2110,6 +2163,7 @@ LibAliasInit(struct libalias *la) SPLAY_INIT(&la->linkSplayIn); SPLAY_INIT(&la->linkSplayOut); + SPLAY_INIT(&la->linkSplayInternalEndpoint); LIST_INIT(&la->pptpList); TAILQ_INIT(&la->checkExpire); #ifdef _KERNEL diff --git a/sys/netinet/libalias/alias_db.h b/sys/netinet/libalias/alias_db.h index 35858099bce2..7175d0a50f4b 100644 --- a/sys/netinet/libalias/alias_db.h +++ b/sys/netinet/libalias/alias_db.h @@ -208,12 +208,14 @@ static struct in_addr const ANY_ADDR = { INADDR_ANY }; stored in the auxiliary space. Pointers to unresolved fragments can also be stored. - The link records support two independent chainings. Lookup + The link records support several independent chainings. Lookup tables for input and out tables hold the initial pointers the link chains. On input, the lookup table indexes on alias port and link type. On output, the lookup table indexes on source address, destination address, source port, destination - port and link type. + port and link type. A internal_endpoint table is used for + endpoint-independent mapping, and indexes on source address, + source port and link type. */ /* used to save changes to ACK/sequence numbers */ @@ -292,6 +294,7 @@ struct alias_link { struct { SPLAY_ENTRY(alias_link) out; LIST_ENTRY (alias_link) in; + SPLAY_ENTRY(alias_link) internal_endpoint; } all; struct { LIST_ENTRY (alias_link) list; @@ -374,25 +377,38 @@ cmp_in(struct group_in *a, struct group_in *b) { } SPLAY_PROTOTYPE(splay_in, group_in, in, cmp_in); +static inline int +cmp_internal_endpoint(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_internal_endpoint, alias_link, all.internal_endpoint, + cmp_internal_endpoint); + /* Internal routines for finding, deleting and adding links Port Allocation: - GetNewPort() -- find and reserve new alias port number - GetSocket() -- try to allocate a socket for a given port + GetNewPort() -- find and reserve new alias port number + GetSocket() -- try to allocate a socket for a given port Link creation and deletion: - CleanupAliasData() - remove all link chains from lookup table - CleanupLink() - look for a stale link - DeleteLink() - remove link - AddLink() - add link - ReLink() - change link + CleanupAliasData() - remove all link chains from lookup table + CleanupLink() - look for a stale link + DeleteLink() - remove link + AddLink() - add link + ReLink() - change link Link search: - FindLinkOut() - find link for outgoing packets - FindLinkIn() - find link for incoming packets + FindLinkOut() - find link for outgoing packets + FindLinkIn() - find link for incoming packets + FindLinkByInternalEndpoint() - find link by a packet's internal endpoint Port search: - FindNewPortGroup() - find an available group of ports + FindNewPortGroup() - find an available group of ports */ /* Local prototypes */ @@ -417,6 +433,9 @@ FindLinkOut(struct libalias *, struct in_addr, struct in_addr, u_short, u_short, static struct alias_link * FindLinkIn(struct libalias *, struct in_addr, struct in_addr, u_short, u_short, int, int); +static struct alias_link * +FindLinkByInternalEndpoint(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_local.h b/sys/netinet/libalias/alias_local.h index 7b82621a105b..ef6c89e675d6 100644 --- a/sys/netinet/libalias/alias_local.h +++ b/sys/netinet/libalias/alias_local.h @@ -94,10 +94,12 @@ struct libalias { * 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 "internal endpoint" lookup tables. */ SPLAY_HEAD(splay_out, alias_link) linkSplayOut; SPLAY_HEAD(splay_in, group_in) linkSplayIn; + SPLAY_HEAD(splay_internal_endpoint, alias_link) + linkSplayInternalEndpoint; 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 index b4d123682f0b..c19acffe03ae 100644 --- a/sys/netinet/libalias/libalias.3 +++ b/sys/netinet/libalias/libalias.3 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 31, 2021 +.Dd November 29, 2024 .Dt LIBALIAS 3 .Os .Sh NAME @@ -270,6 +270,26 @@ See section in .Xr ipfw 8 for more details. +.It Dv PKT_ALIAS_UDP_EIM +When this bit is set, UDP uses endpoint-independent mapping (EIM), as per +RFC 4787 ("full cone" NAT of RFC 3489). +All packets from the same internal address:port are mapped to the same NAT +address:port, regardless of their destination address:port. +If filtering rules allow, and if +.Em PKT_ALIAS_DENY_INCOMING +is unset, any other external address:port can +also send to the internal address:port through its mapped NAT address:port. +This is more compatible with applications, and can reduce the need for port +forwarding, but less scalable as each NAT address:port can only be +concurrently used by at most one internal address:port. +.Pp +When this bit is unset, UDP packets use endpoint-dependent mapping (EDM) +("symmetric" NAT). +Each connection from a particular internal address:port to different +external addresses:ports is mapped to a random and unpredictable NAT +address:port. +Two appplications behind EDM NATs can only connect to each other +by port forwarding on the NAT, or tunnelling through an in-between server. .El .Ed .Pp diff --git a/tests/sys/netinet/libalias/2_natout.c b/tests/sys/netinet/libalias/2_natout.c index c6f5797b2db7..24ca06d11bf4 100644 --- a/tests/sys/netinet/libalias/2_natout.c +++ b/tests/sys/netinet/libalias/2_natout.c @@ -359,6 +359,172 @@ ATF_TC_BODY(8_portrange, dummy) LibAliasUninit(la); } +ATF_TC_WITHOUT_HEAD(9_udp_eim_mapping); +ATF_TC_BODY(9_udp_eim_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_UDP_EIM, ~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_eim_out_in); +ATF_TC_BODY(10_udp_eim_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_UDP_EIM, ~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_eim_with_deny_incoming); +ATF_TC_BODY(11_udp_eim_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_UDP_EIM | 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_eim_hairpinning); +ATF_TC_BODY(12_udp_eim_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_UDP_EIM, ~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 +538,10 @@ ATF_TP_ADD_TCS(natout) 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_eim_mapping); + ATF_TP_ADD_TC(natout, 10_udp_eim_out_in); + ATF_TP_ADD_TC(natout, 11_udp_eim_with_deny_incoming); + ATF_TP_ADD_TC(natout, 12_udp_eim_hairpinning); return atf_no_error(); } diff --git a/tests/sys/netinet/libalias/util.c b/tests/sys/netinet/libalias/util.c index 14ba196a59a5..8ceb8355c8ff 100644 --- 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) }; diff --git a/tests/sys/netinet/libalias/util.h b/tests/sys/netinet/libalias/util.h index 786e48e41f37..f58a1ad26248 100644 --- 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);