59#include <netinet/in.h>
61#include <sys/socket.h>
66#define LOG_MODULE "NAT64"
67#define LOG_LEVEL LOG_LEVEL_INFO
69#ifndef NAT64_MAX_SESSIONS
70#define NAT64_MAX_SESSIONS 128
73#ifndef NAT64_SESSION_TIMEOUT
74#define NAT64_SESSION_TIMEOUT (5 * 60 * CLOCK_SECOND)
77#define NAT64_PRIO CONTIKI_VERBOSE_PRIO + 40
79#ifndef NAT64_MAX_SESSIONS_PER_NODE
80#define NAT64_MAX_SESSIONS_PER_NODE 8
87#ifndef NAT64_DEFAULT_ENABLED
88#define NAT64_DEFAULT_ENABLED 0
90static bool nat64_enabled = NAT64_DEFAULT_ENABLED;
130 if(s->proto == NAT64_PROTO_TCP) {
134 select_set_callback(s->fd, NULL);
139 s->proto = NAT64_PROTO_NONE;
153 if(s->proto == NAT64_PROTO_TCP &&
162 const uip_ip6addr_t *ip6_src, uint16_t srcport,
166 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
170 s->ip6_peer_port == srcport &&
171 s->ip4_remote_port == dstport &&
172 uip_ip6addr_cmp(&s->ip6_peer, ip6_src) &&
185count_node_sessions(
const uip_ip6addr_t *ip6_src)
187 unsigned i,
count = 0;
188 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
189 if(sessions[i].active &&
191 uip_ip6addr_cmp(&sessions[i].
ip6_peer, ip6_src)) {
199alloc_session(
const uip_ip6addr_t *ip6_src)
203 if(count_node_sessions(ip6_src) >= NAT64_MAX_SESSIONS_PER_NODE) {
204 LOG_WARN(
"Per-node session limit reached (%u)\n",
205 NAT64_MAX_SESSIONS_PER_NODE);
209 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
210 if(!sessions[i].active) {
218 LOG_WARN(
"Session table full\n");
224 const uip_ip6addr_t *ip6_src, uint16_t srcport,
228 uip_ip6addr_copy(&s->ip6_peer, ip6_src);
229 s->ip6_peer_port = srcport;
231 s->ip4_remote_port = dstport;
233 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
239static struct sockaddr_in
242 struct sockaddr_in sa;
243 memset(&sa, 0,
sizeof(sa));
244 sa.sin_family = AF_INET;
245 sa.sin_port = htons(port);
258 socklen_t errlen =
sizeof(err);
260 if(getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0 || err != 0) {
261 int e = err ? err : errno;
262 LOG_WARN(
"TCP connect failed: %s\n", strerror(e));
264 &s->ip4_remote, s->ip4_remote_port,
270 LOG_INFO(
"TCP connected to %u.%u.%u.%u:%u (fd %d)\n",
271 s->ip4_remote.u8[0], s->ip4_remote.u8[1],
272 s->ip4_remote.u8[2], s->ip4_remote.u8[3],
273 s->ip4_remote_port, s->fd);
280generic_set_fd(fd_set *rset, fd_set *wset)
291 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
293 if(!s->active || s->fd < 0) {
296 if(s->proto == NAT64_PROTO_TCP &&
299 }
else if(s->proto == NAT64_PROTO_TCP &&
312generic_handle_fd(fd_set *rset, fd_set *wset)
315 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
317 if(!s->active || s->fd < 0) {
326 if(s->proto == NAT64_PROTO_TCP &&
328 FD_ISSET(s->fd, wset)) {
329 handle_tcp_connect_complete(s);
333 if(!FD_ISSET(s->fd, rset)) {
337 if(s->proto == NAT64_PROTO_TCP &&
340 ssize_t n = recv(s->fd, buf,
sizeof(buf), 0);
342 LOG_INFO(
"TCP recv %zd bytes from server (fd %d)\n", n, s->fd);
344 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
346 LOG_INFO(
"TCP server closed connection (fd %d)\n", s->fd);
351 LOG_INFO(
"TCP both sides FIN'd, destroying session\n");
354 }
else if(errno != EAGAIN && errno != EWOULDBLOCK) {
355 LOG_ERR(
"TCP recv error (fd %d): %s\n", s->fd, strerror(errno));
359 LOG_INFO(
"TCP both sides done, destroying session\n");
363 }
else if(s->proto == NAT64_PROTO_UDP) {
365 ssize_t n = recv(s->fd, buf,
sizeof(buf), 0);
368 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
369 }
else if(n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
372 LOG_ERR(
"UDP recvfrom error (fd %d): %s\n", s->fd, strerror(e));
374 &s->ip4_remote, s->ip4_remote_port,
378 }
else if(s->proto == NAT64_PROTO_ICMP) {
380 ssize_t n = recv(s->fd, buf,
sizeof(buf), 0);
383 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
384 }
else if(n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
385 LOG_ERR(
"ICMP recv error (fd %d): %s\n", s->fd, strerror(errno));
391static const struct select_callback nat64_select_cb = {
399 if(fcntl(s->fd, F_SETFL, O_NONBLOCK) < 0) {
403 LOG_ERR(
"fcntl(F_SETFL, O_NONBLOCK) failed for fd %d: %s\n",
404 s->fd, strerror(errno));
410 if(!select_set_callback(s->fd, &nat64_select_cb)) {
411 LOG_ERR(
"select_set_callback failed for fd %d\n", s->fd);
426 const uip_ip6addr_t *ip6_src, uint16_t srcport,
427 const uint8_t *payload, uint16_t len)
432 s = find_session(NAT64_PROTO_UDP, ip6_src, srcport, dst, dstport);
434 s = alloc_session(ip6_src);
440 s->fd = socket(AF_INET, SOCK_DGRAM, 0);
442 LOG_ERR(
"socket(DGRAM): %s\n", strerror(errno));
445 fill_session(s, NAT64_PROTO_UDP, ip6_src, srcport, dst, dstport);
446 if(!register_fd(s)) {
451 struct sockaddr_in peer = make_addr(dst, dstport);
452 if(connect(s->fd, (
struct sockaddr *)&peer,
sizeof(peer)) < 0) {
454 LOG_ERR(
"UDP connect: %s\n", strerror(e));
460 LOG_DBG(
"New UDP session fd %d\n", s->fd);
463 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
465 sent = send(s->fd, payload, len, 0);
468 LOG_ERR(
"sendto: %s\n", strerror(e));
478 const uip_ip6addr_t *ip6_src, uint16_t srcport,
484 s = find_session(NAT64_PROTO_TCP, ip6_src, srcport, dst, dstport);
489 s = alloc_session(ip6_src);
496 s->fd = socket(AF_INET, SOCK_STREAM, 0);
498 LOG_ERR(
"socket(STREAM): %s\n", strerror(errno));
502 fill_session(s, NAT64_PROTO_TCP, ip6_src, srcport, dst, dstport);
506 if(!register_fd(s)) {
510 struct sockaddr_in dest = make_addr(dst, dstport);
511 ret = connect(s->fd, (
struct sockaddr *)&dest,
sizeof(dest));
512 if(ret < 0 && errno != EINPROGRESS) {
514 LOG_ERR(
"connect: %s\n", strerror(e));
522 handle_tcp_connect_complete(s);
525 LOG_DBG(
"TCP connecting fd %d to %u.%u.%u.%u:%u\n",
527 dst->u8[0], dst->u8[1], dst->u8[2], dst->u8[3], dstport);
533 const uint8_t *data, uint16_t len)
541 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
543 sent = send(s->fd, data, len, 0);
545 if(errno == EAGAIN || errno == EWOULDBLOCK) {
549 LOG_WARN(
"TCP send would block (fd %d), IoT will retransmit\n",
553 LOG_ERR(
"TCP send error (fd %d): %s\n", s->fd, strerror(errno));
556 LOG_INFO(
"TCP sent %zd bytes to server (fd %d)\n", sent, s->fd);
566 LOG_DBG(
"TCP shutdown(WR) fd %d\n", s->fd);
572 shutdown(s->fd, SHUT_WR);
581 LOG_DBG(
"TCP destroy fd %d\n", s->fd);
595 struct linger lin = { .l_onoff = 1, .l_linger = 0 };
596 setsockopt(s->fd, SOL_SOCKET, SO_LINGER, &lin,
sizeof(lin));
598 LOG_DBG(
"TCP abort fd %d\n", s->fd);
604 const uip_ip6addr_t *ip6_src, uint16_t identifier,
605 const uint8_t *icmp_pkt, uint16_t icmp_len)
613 s = find_session(NAT64_PROTO_ICMP, ip6_src, identifier, dst, 0);
615 s = alloc_session(ip6_src);
624 s->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
627 LOG_ERR(
"socket(ICMP): %s\n", strerror(e));
628 if(e == EACCES || e == EPERM) {
629 LOG_ERR(
"Hint: add the running user's GID to "
630 "net.ipv4.ping_group_range\n");
639 fill_session(s, NAT64_PROTO_ICMP, ip6_src, identifier, dst, 0);
641 if(!register_fd(s)) {
646 struct sockaddr_in peer = make_addr(dst, 0);
647 if(connect(s->fd, (
struct sockaddr *)&peer,
sizeof(peer)) < 0) {
649 LOG_ERR(
"ICMP connect: %s\n", strerror(e));
656 LOG_DBG(
"New ICMP session fd %d id=%u\n", s->fd, identifier);
659 timer_set(&s->expiry, NAT64_SESSION_TIMEOUT);
661 sent = send(s->fd, icmp_pkt, icmp_len, 0);
664 LOG_ERR(
"ICMP send: %s\n", strerror(e));
673read_urandom(
void *buf,
size_t len)
675 int fd = open(
"/dev/urandom", O_RDONLY);
677 LOG_ERR(
"Failed to open /dev/urandom: %s\n", strerror(errno));
680 ssize_t n =
read(fd, buf, len);
682 if(n != (ssize_t)len) {
683 LOG_ERR(
"Short read from /dev/urandom\n");
695 memset(sessions, 0,
sizeof(sessions));
696 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
700 if(!read_urandom(isn_key,
sizeof(isn_key))) {
701 LOG_ERR(
"Cannot seed ISN secret โ /dev/urandom unavailable\n");
705 memset(isn_key, 0,
sizeof(isn_key));
708 LOG_INFO(
"Socket-based NAT64 initialized (%u max sessions)\n",
714nat64_option_callback(
const char *optarg)
716 nat64_enabled =
true;
720 nat64_option_callback,
721 "Enable NAT64 gateway (socket-based, no TUN device needed)\n");
726 return nat64_enabled;
static int read(void *buf, unsigned short bufsize)
static volatile uint64_t count
Num.
#define CONTIKI_OPTION(prio,...)
Add a command line option when the compilation unit is present.
void nat64_tcp_flush_acks(void)
Flush deferred TCP ACKs.
void nat64_platform_tcp_destroy(struct nat64_session *s)
Fully tear down a TCP session.
int nat64_platform_udp_send(const uip_ip4addr_t *dst, uint16_t dstport, const uip_ip6addr_t *ip6_src, uint16_t srcport, const uint8_t *payload, uint16_t len)
Forward a UDP payload to an IPv4 server.
int nat64_platform_icmp_send(const uip_ip4addr_t *dst, const uip_ip6addr_t *ip6_src, uint16_t identifier, const uint8_t *icmp_pkt, uint16_t icmp_len)
Forward an ICMPv4 Echo Request to an IPv4 destination.
nat64_session_proto
Transport protocol tracked by a NAT64 session.
void nat64_queue_icmp6_unreach_tuple(const uip_ip6addr_t *ip6_src, uint16_t src_port, const uip_ip4addr_t *ip4_dst, uint16_t dst_port, uint8_t ipproto, uint8_t code)
Queue an ICMPv6 Destination Unreachable for a 5-tuple whose connection failed.
struct nat64_session * nat64_platform_tcp_connect(const uip_ip4addr_t *dst, uint16_t dstport, const uip_ip6addr_t *ip6_src, uint16_t srcport, uint32_t peer_isn)
Initiate a TCP connection to an IPv4 server.
bool nat64_tcp_has_pending_data(const struct nat64_session *s)
Check whether a session has buffered data awaiting delivery.
void nat64_tcp_free_seqstate(const struct nat64_session *s)
Free any TCP sequence state associated with a session.
bool nat64_tcp_peer_fin_received(const struct nat64_session *s)
Check whether the IoT node has already half-closed the session.
static void expire_session(struct nat64_session *s)
Reap an expired session, notifying its peer if applicable.
void nat64_platform_tcp_close(struct nat64_session *s)
Half-close a TCP session (send FIN).
bool nat64_platform_init(void)
Initialize the platform layer.
void nat64_platform_tcp_abort(struct nat64_session *s)
Abort a TCP session by sending RST upstream.
void nat64_tcp_set_isn_secret(const uint8_t key[16])
Set the 128-bit secret key for TCP ISN generation.
#define NAT64_ICMP6_ADDR
Address unreachable.
void nat64_udp_input(struct nat64_session *s, const uint8_t *payload, uint16_t payload_len)
Inject a UDP response from an IPv4 server.
void nat64_tcp_closed(struct nat64_session *s)
Notify that an IPv4 server closed a TCP connection.
void nat64_tcp_data_in(struct nat64_session *s, const uint8_t *data, uint16_t len)
Forward TCP data from an IPv4 server to the IoT node.
void nat64_tcp_established(struct nat64_session *s)
Notify that a TCP connection to an IPv4 server completed.
void nat64_activate(void)
Initialize the NAT64 gateway.
static uint8_t errno_to_icmp6_code(int err)
Map a Linux errno to an ICMPv6 Destination Unreachable code.
#define NAT64_ICMP6_PORT
Port unreachable.
int nat64_platform_tcp_send(struct nat64_session *s, const uint8_t *data, uint16_t len)
Send data on an established TCP session.
bool nat64_is_enabled(void)
Check whether the NAT64 gateway has been enabled at runtime.
void nat64_flush_icmp6(void)
Drain the queue of pending ICMPv6 errors into the uIP stack.
#define NAT64_ICMP6_ADMIN
Communication administratively prohibited.
void nat64_icmp_input(struct nat64_session *s, const uint8_t *icmp_pkt, uint16_t len)
Inject an ICMPv4 Echo Reply received from an IPv4 host.
#define NAT64_ICMP6_NOROUTE
No route to destination.
@ NAT64_TCP_ESTABLISHED
Connection open, data can flow.
@ NAT64_TCP_CONNECTING
Non-blocking connect() in progress.
@ NAT64_TCP_CLOSING
Half-closed (SHUT_WR sent).
void timer_set(struct timer *t, clock_time_t interval)
Set a timer.
bool timer_expired(struct timer *t)
Check if a timer has expired.
#define uip_ip4addr_cmp(addr1, addr2)
Compare two IP addresses.
Header file for the logging system.
A NAT64 session binding an IoT node's IPv6 flow to an IPv4 socket.
bool active
Session slot in use.
struct timer expiry
Session lifetime timer.
uip_ip6addr_t ip6_peer
IoT node's IPv6 address.
enum nat64_session_proto proto
UDP or TCP.
uint32_t peer_isn
IoT node's ISN (TCP only).
Representation of an IP address.