Skip to content

Commit dd47d74

Browse files
committed
ipv4-dns-over-ipv6
1 parent fd47d7b commit dd47d74

4 files changed

Lines changed: 117 additions & 0 deletions

File tree

include/libwebsockets/lws-network-helper.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,19 @@ lws_parse_numeric_address(const char *ads, uint8_t *result, size_t max_len);
231231
LWS_VISIBLE LWS_EXTERN int
232232
lws_sa46_parse_numeric_address(const char *ads, lws_sockaddr46 *sa46);
233233

234+
/**
235+
* lws_sa46_is_ipv4_mapped() - is an AF_INET6 sa46 an IPv4-mapped address?
236+
*
237+
* \param sa46: the sa46 to test
238+
*
239+
* Returns 1 if \p sa46 is AF_INET6 and holds an IPv4-mapped IPv6 address
240+
* (::ffff:a.b.c.d), else 0. Useful for deciding that an AF_INET6 socket
241+
* targeting this address must be dual-stack (IPV6_V6ONLY = 0) to actually
242+
* reach the IPv4 peer through the mapping.
243+
*/
244+
LWS_VISIBLE LWS_EXTERN int
245+
lws_sa46_is_ipv4_mapped(const lws_sockaddr46 *sa46);
246+
234247
/**
235248
* lws_write_numeric_address() - convert network byte order ads to text
236249
*

lib/core-net/adopt.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads,
826826

827827
#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY)
828828
if (s->dest.sa4.sin_family == AF_INET6 &&
829+
!lws_sa46_is_ipv4_mapped(&s->dest) &&
829830
(!(wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) ||
830831
(wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE))) {
831832
int opt = 1;
@@ -1016,6 +1017,7 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads,
10161017

10171018
#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY)
10181019
if (dest.sa4.sin_family == AF_INET6 &&
1020+
!lws_sa46_is_ipv4_mapped(&dest) &&
10191021
(!(wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) ||
10201022
(wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE))) {
10211023
int opt = 1;

lib/core-net/network.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,11 +967,57 @@ lws_sa46_parse_numeric_address(const char *ads, lws_sockaddr46 *sa46)
967967
sizeof(sa46->sa4.sin_addr.s_addr));
968968

969969
return 0;
970+
#elif defined(LWS_WITH_IPV6)
971+
/*
972+
* IPv4 literal in a build with IPv4 compiled out: represent it as an
973+
* IPv4-mapped IPv6 address (::ffff:a.b.c.d) so it can be stored in an
974+
* AF_INET6 sockaddr and, on a dual-stack host (or behind NAT64),
975+
* actually reached. On a host with no IPv4 route this connect will
976+
* simply fail cleanly at the socket layer; the alternative ("refuse
977+
* the literal") makes DNS-server discovery and similar config unusable
978+
* on otherwise-capable dual-stack hosts for no benefit.
979+
*/
980+
{
981+
static uint8_t did_notice;
982+
983+
sa46->sa6.sin6_family = AF_INET6;
984+
memset(sa46->sa6.sin6_addr.s6_addr, 0, 10);
985+
sa46->sa6.sin6_addr.s6_addr[10] = 0xff;
986+
sa46->sa6.sin6_addr.s6_addr[11] = 0xff;
987+
memcpy(&sa46->sa6.sin6_addr.s6_addr[12], a, 4);
988+
989+
if (!did_notice) {
990+
did_notice = 1;
991+
lwsl_notice("IPv4 literal '%s' mapped to ::ffff: in this "
992+
"IPv6-only build; reachable only on dual-stack "
993+
"or via NAT64\n", ads);
994+
}
995+
996+
return 0;
997+
}
970998
#else
971999
return -1;
9721000
#endif
9731001
}
9741002

1003+
int
1004+
lws_sa46_is_ipv4_mapped(const lws_sockaddr46 *sa46)
1005+
{
1006+
#if defined(LWS_WITH_IPV6)
1007+
if (sa46 && sa46->sa4.sin_family == AF_INET6) {
1008+
const uint8_t *a = sa46->sa6.sin6_addr.s6_addr;
1009+
1010+
return !a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5] &&
1011+
!a[6] && !a[7] && !a[8] && !a[9] &&
1012+
a[10] == 0xff && a[11] == 0xff;
1013+
}
1014+
#else
1015+
(void)sa46;
1016+
#endif
1017+
1018+
return 0;
1019+
}
1020+
9751021
int
9761022
lws_write_numeric_address(const uint8_t *ads, int size, char *buf, size_t len)
9771023
{
@@ -1313,6 +1359,27 @@ lws_is_lan_address(const char *ads)
13131359
#if defined(LWS_WITH_IPV6)
13141360
uint8_t *p = (uint8_t *)&sa46.sa6.sin6_addr.s6_addr;
13151361

1362+
/*
1363+
* An IPv4-mapped address (::ffff:a.b.c.d) is evaluated by its
1364+
* embedded IPv4 address, so "is this LAN" agrees whether the
1365+
* address is stored as AF_INET or as a mapped AF_INET6 (as
1366+
* happens in an IPv6-only build).
1367+
*/
1368+
if (lws_sa46_is_ipv4_mapped(&sa46)) {
1369+
uint8_t *q = &p[12];
1370+
1371+
if (q[0] == 10)
1372+
return 1;
1373+
if (q[0] == 172 && q[1] >= 16 && q[1] <= 31)
1374+
return 1;
1375+
if (q[0] == 192 && q[1] == 168)
1376+
return 1;
1377+
if (q[0] == 127)
1378+
return 1;
1379+
1380+
return 0;
1381+
}
1382+
13161383
/* fc00::/7 */
13171384
if ((p[0] & 0xfe) == 0xfc)
13181385
return 1;

minimal-examples-lowlevel/api-tests/api-test-network/main.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
int main(void)
55
{
6+
lws_sockaddr46 sa46;
67
int e = 0;
78

89
lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, NULL);
@@ -19,6 +20,40 @@ int main(void)
1920
e++;
2021
}
2122

23+
/*
24+
* An IPv4 literal must parse in every build. In a dual-stack build it
25+
* stays AF_INET; in an IPv6-only build (LWS_WITH_IPV4 off) it is mapped
26+
* to ::ffff:a.b.c.d as AF_INET6 so DNS-server discovery and similar
27+
* config still works on dual-stack hosts.
28+
*/
29+
memset(&sa46, 0, sizeof(sa46));
30+
if (lws_sa46_parse_numeric_address("192.168.1.1", &sa46) < 0) {
31+
lwsl_err("sa46 parse '192.168.1.1' failed\n");
32+
e++;
33+
} else {
34+
#if defined(LWS_WITH_IPV4)
35+
if (sa46.sa4.sin_family != AF_INET) {
36+
lwsl_err("sa46 family %d != AF_INET\n",
37+
sa46.sa4.sin_family);
38+
e++;
39+
}
40+
#elif defined(LWS_WITH_IPV6)
41+
if (sa46.sa4.sin_family != AF_INET6 ||
42+
!lws_sa46_is_ipv4_mapped(&sa46)) {
43+
lwsl_err("sa46 IPv6-only mapping failed (family %d)\n",
44+
sa46.sa4.sin_family);
45+
e++;
46+
}
47+
/* a native IPv6 literal is not mapped */
48+
memset(&sa46, 0, sizeof(sa46));
49+
if (lws_sa46_parse_numeric_address("2001:db8::1", &sa46) < 0 ||
50+
lws_sa46_is_ipv4_mapped(&sa46)) {
51+
lwsl_err("sa46 native IPv6 mapped wrongly\n");
52+
e++;
53+
}
54+
#endif
55+
}
56+
2257
lwsl_user("Completed: %d fails\n", e);
2358

2459
return e;

0 commit comments

Comments
 (0)