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
84static bool nat64_enabled;
124 if(s->
proto == NAT64_PROTO_TCP) {
128 select_set_callback(s->
fd, NULL);
133 s->
proto = NAT64_PROTO_NONE;
147 if(s->
proto == NAT64_PROTO_TCP &&
156 const uip_ip6addr_t *ip6_src, uint16_t srcport,
160 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
166 uip_ip6addr_cmp(&s->
ip6_peer, ip6_src) &&
179count_node_sessions(
const uip_ip6addr_t *ip6_src)
181 unsigned i,
count = 0;
182 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
185 uip_ip6addr_cmp(&sessions[i].
ip6_peer, ip6_src)) {
193alloc_session(
const uip_ip6addr_t *ip6_src)
197 if(count_node_sessions(ip6_src) >= NAT64_MAX_SESSIONS_PER_NODE) {
198 LOG_WARN(
"Per-node session limit reached (%u)\n",
199 NAT64_MAX_SESSIONS_PER_NODE);
203 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
212 LOG_WARN(
"Session table full\n");
218 const uip_ip6addr_t *ip6_src, uint16_t srcport,
222 uip_ip6addr_copy(&s->
ip6_peer, ip6_src);
233static struct sockaddr_in
236 struct sockaddr_in sa;
237 memset(&sa, 0,
sizeof(sa));
238 sa.sin_family = AF_INET;
239 sa.sin_port = htons(port);
252 socklen_t errlen =
sizeof(err);
254 if(getsockopt(s->
fd, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0 || err != 0) {
255 int e = err ? err : errno;
256 LOG_WARN(
"TCP connect failed: %s\n", strerror(e));
264 LOG_INFO(
"TCP connected to %u.%u.%u.%u:%u (fd %d)\n",
274generic_set_fd(fd_set *rset, fd_set *wset)
285 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
290 if(s->
proto == NAT64_PROTO_TCP &&
293 }
else if(s->
proto == NAT64_PROTO_TCP &&
305generic_handle_fd(fd_set *rset, fd_set *wset)
308 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
319 if(s->
proto == NAT64_PROTO_TCP &&
321 FD_ISSET(s->
fd, wset)) {
322 handle_tcp_connect_complete(s);
326 if(!FD_ISSET(s->
fd, rset)) {
330 if(s->
proto == NAT64_PROTO_TCP &&
333 ssize_t n = recv(s->
fd, buf,
sizeof(buf), 0);
335 LOG_INFO(
"TCP recv %zd bytes from server (fd %d)\n", n, s->
fd);
339 LOG_INFO(
"TCP server closed connection (fd %d)\n", s->
fd);
344 LOG_INFO(
"TCP both sides FIN'd, destroying session\n");
347 }
else if(errno != EAGAIN && errno != EWOULDBLOCK) {
348 LOG_ERR(
"TCP recv error (fd %d): %s\n", s->
fd, strerror(errno));
352 LOG_INFO(
"TCP both sides done, destroying session\n");
356 }
else if(s->
proto == NAT64_PROTO_UDP) {
358 ssize_t n = recv(s->
fd, buf,
sizeof(buf), 0);
361 }
else if(n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
362 LOG_ERR(
"UDP recvfrom error (fd %d): %s\n", s->
fd, strerror(errno));
364 }
else if(s->
proto == NAT64_PROTO_ICMP) {
366 ssize_t n = recv(s->
fd, buf,
sizeof(buf), 0);
370 }
else if(n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
371 LOG_ERR(
"ICMP recv error (fd %d): %s\n", s->
fd, strerror(errno));
377static const struct select_callback nat64_select_cb = {
385 if(fcntl(s->
fd, F_SETFL, O_NONBLOCK) < 0) {
389 LOG_ERR(
"fcntl(F_SETFL, O_NONBLOCK) failed for fd %d: %s\n",
390 s->
fd, strerror(errno));
396 if(!select_set_callback(s->
fd, &nat64_select_cb)) {
397 LOG_ERR(
"select_set_callback failed for fd %d\n", s->
fd);
412 const uip_ip6addr_t *ip6_src, uint16_t srcport,
413 const uint8_t *payload, uint16_t len)
418 s = find_session(NAT64_PROTO_UDP, ip6_src, srcport, dst, dstport);
420 s = alloc_session(ip6_src);
426 s->
fd = socket(AF_INET, SOCK_DGRAM, 0);
428 LOG_ERR(
"socket(DGRAM): %s\n", strerror(errno));
431 fill_session(s, NAT64_PROTO_UDP, ip6_src, srcport, dst, dstport);
432 if(!register_fd(s)) {
437 struct sockaddr_in peer = make_addr(dst, dstport);
438 if(connect(s->
fd, (
struct sockaddr *)&peer,
sizeof(peer)) < 0) {
440 LOG_ERR(
"UDP connect: %s\n", strerror(e));
446 LOG_DBG(
"New UDP session fd %d\n", s->
fd);
451 sent = send(s->
fd, payload, len, 0);
454 LOG_ERR(
"sendto: %s\n", strerror(e));
464 const uip_ip6addr_t *ip6_src, uint16_t srcport,
470 s = find_session(NAT64_PROTO_TCP, ip6_src, srcport, dst, dstport);
475 s = alloc_session(ip6_src);
482 s->
fd = socket(AF_INET, SOCK_STREAM, 0);
484 LOG_ERR(
"socket(STREAM): %s\n", strerror(errno));
488 fill_session(s, NAT64_PROTO_TCP, ip6_src, srcport, dst, dstport);
492 if(!register_fd(s)) {
496 struct sockaddr_in dest = make_addr(dst, dstport);
497 ret = connect(s->
fd, (
struct sockaddr *)&dest,
sizeof(dest));
498 if(ret < 0 && errno != EINPROGRESS) {
500 LOG_ERR(
"connect: %s\n", strerror(e));
508 handle_tcp_connect_complete(s);
511 LOG_DBG(
"TCP connecting fd %d to %u.%u.%u.%u:%u\n",
513 dst->u8[0], dst->u8[1], dst->u8[2], dst->u8[3], dstport);
519 const uint8_t *data, uint16_t len)
529 sent = send(s->
fd, data, len, 0);
531 if(errno == EAGAIN || errno == EWOULDBLOCK) {
542 LOG_WARN(
"TCP send would block (fd %d), IoT will retransmit\n",
546 LOG_ERR(
"TCP send error (fd %d): %s\n", s->
fd, strerror(errno));
549 LOG_INFO(
"TCP sent %zd bytes to server (fd %d)\n", sent, s->
fd);
559 LOG_DBG(
"TCP shutdown(WR) fd %d\n", s->
fd);
565 shutdown(s->
fd, SHUT_WR);
574 LOG_DBG(
"TCP destroy fd %d\n", s->
fd);
588 struct linger lin = { .l_onoff = 1, .l_linger = 0 };
589 setsockopt(s->
fd, SOL_SOCKET, SO_LINGER, &lin,
sizeof(lin));
591 LOG_DBG(
"TCP abort fd %d\n", s->
fd);
597 const uip_ip6addr_t *ip6_src, uint16_t identifier,
598 const uint8_t *icmp_pkt, uint16_t icmp_len)
606 s = find_session(NAT64_PROTO_ICMP, ip6_src, identifier, dst, 0);
608 s = alloc_session(ip6_src);
617 s->
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
620 LOG_ERR(
"socket(ICMP): %s\n", strerror(e));
621 if(e == EACCES || e == EPERM) {
622 LOG_ERR(
"Hint: add the running user's GID to "
623 "net.ipv4.ping_group_range\n");
632 fill_session(s, NAT64_PROTO_ICMP, ip6_src, identifier, dst, 0);
634 if(!register_fd(s)) {
639 struct sockaddr_in peer = make_addr(dst, 0);
640 if(connect(s->
fd, (
struct sockaddr *)&peer,
sizeof(peer)) < 0) {
642 LOG_ERR(
"ICMP connect: %s\n", strerror(e));
649 LOG_DBG(
"New ICMP session fd %d id=%u\n", s->
fd, identifier);
654 sent = send(s->
fd, icmp_pkt, icmp_len, 0);
657 LOG_ERR(
"ICMP send: %s\n", strerror(e));
666read_urandom(
void *buf,
size_t len)
668 int fd = open(
"/dev/urandom", O_RDONLY);
670 LOG_ERR(
"Failed to open /dev/urandom: %s\n", strerror(errno));
673 ssize_t n = read(fd, buf, len);
675 if(n != (ssize_t)len) {
676 LOG_ERR(
"Short read from /dev/urandom\n");
688 memset(sessions, 0,
sizeof(sessions));
689 for(i = 0; i < NAT64_MAX_SESSIONS; i++) {
693 if(!read_urandom(isn_key,
sizeof(isn_key))) {
694 LOG_ERR(
"Cannot seed ISN secret — /dev/urandom unavailable\n");
698 memset(isn_key, 0,
sizeof(isn_key));
701 LOG_INFO(
"Socket-based NAT64 initialized (%u max sessions)\n",
707nat64_option_callback(
const char *optarg)
709 nat64_enabled =
true;
713 nat64_option_callback,
714 "Enable NAT64 gateway (socket-based, no TUN device needed)\n");
719 return nat64_enabled;
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.
uint16_t ip4_remote_port
IPv4 server port.
struct timer expiry
Session lifetime timer.
int fd
IPv4 socket file descriptor.
uip_ip6addr_t ip6_peer
IoT node's IPv6 address.
enum nat64_session_proto proto
UDP or TCP.
enum nat64_tcp_state tcp_state
TCP connection state.
uint32_t peer_isn
IoT node's ISN (TCP only).
uip_ip4addr_t ip4_remote
IPv4 server address.
uint16_t ip6_peer_port
IoT node's transport port.
Representation of an IP address.