Contiki-NG
Loading...
Searching...
No Matches
nat64-dns64.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2026, RISE Research Institutes of Sweden AB.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the copyright holder nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * \addtogroup nat64
33 * @{
34 *
35 * \file
36 * NAT64 DNS64 translation -- rewrites DNS queries (AAAA->A)
37 * and responses (A->AAAA with NAT64 prefix) inline.
38 *
39 * The translator handles compressed DNS names (RFC 1035
40 * §4.1.4), grows A records to AAAA records via the NAT64
41 * prefix synthesis from `ip64_addr_4to6`, and truncates the
42 * authority and additional sections after expansion since
43 * their offsets become invalid (and constrained resolvers
44 * do not consume them).
45 * \author
46 * Nicolas Tsiftes <nicolas.tsiftes@ri.se>
47 */
48
49#include "nat64-dns64.h"
50#include "net/ipv6/uip.h"
51#include "ipv6/ip64-addr.h"
52
53#include <string.h>
54
55/* Log configuration */
56#include "sys/log.h"
57#define LOG_MODULE "NAT64"
58#define LOG_LEVEL LOG_LEVEL_INFO
59
60/* DNS record types per RFC 1035 / RFC 3596. */
61#define DNS_TYPE_A 1
62#define DNS_TYPE_AAAA 28
63#define DNS_CLASS_IN 1
64
65/* Offsets within the fixed-size DNS header (RFC 1035, Section 4.1.1). */
66#define DNS_HDR_SIZE 12
67#define DNS_HDR_ID 0
68#define DNS_HDR_QDCOUNT 4
69#define DNS_HDR_ANCOUNT 6
70#define DNS_HDR_NSCOUNT 8
71#define DNS_HDR_ARCOUNT 10
72
73/* Offsets within the question tail (after the QNAME labels). */
74#define DNS_QTAIL_QTYPE 0
75#define DNS_QTAIL_QCLASS 2
76#define DNS_QTAIL_SIZE 4
77
78/* Read a big-endian uint16 from a byte buffer. */
79#define RD16(p) (((uint16_t)(p)[0] << 8) | (p)[1])
80/* Write a big-endian uint16 into a byte buffer. */
81#define WR16(p, v) do { (p)[0] = (uint8_t)((v) >> 8); \
82 (p)[1] = (uint8_t)(v); } while(0)
83/*---------------------------------------------------------------------------*/
84/*
85 * Skip over a DNS name in a packet (handling label sequences and
86 * compressed pointers per RFC 1035 Section 4.1.4).
87 * Returns a pointer past the name, or NULL on error.
88 */
89static const uint8_t *
90skip_dns_name(const uint8_t *p, const uint8_t *end)
91{
92 while(p < end) {
93 uint8_t len = *p;
94 if(len == 0) {
95 /* Root label terminates the name. */
96 return p + 1;
97 }
98 if((len & 0xc0) == 0xc0) {
99 /* Compressed pointer: two bytes total. */
100 return (p + 2 <= end) ? p + 2 : NULL;
101 }
102 /* Regular label: skip length byte + label bytes. */
103 p += 1 + len;
104 }
105 return NULL;
106}
107/*---------------------------------------------------------------------------*/
108void
109nat64_dns64_6to4(uint8_t *data, uint16_t len)
110{
111 uint16_t qdcount;
112 uint8_t *p;
113 const uint8_t *end;
114
115 if(len < DNS_HDR_SIZE) {
116 return;
117 }
118
119 qdcount = RD16(&data[DNS_HDR_QDCOUNT]);
120 p = data + DNS_HDR_SIZE;
121 end = data + len;
122
123 LOG_DBG("DNS64 6to4: id=0x%04x, %u questions\n",
124 RD16(&data[DNS_HDR_ID]), qdcount);
125
126 /* Walk each question and change AAAA queries to A. */
127 for(uint16_t i = 0; i < qdcount; i++) {
128 const uint8_t *after_name = skip_dns_name(p, end);
129 if(after_name == NULL || after_name + DNS_QTAIL_SIZE > end) {
130 LOG_WARN("DNS64 6to4: malformed question section\n");
131 return;
132 }
133 /* Point to the fixed question-tail (QTYPE, QCLASS). */
134 uint8_t *qtail = (uint8_t *)after_name;
135 if(RD16(&qtail[DNS_QTAIL_QTYPE]) == DNS_TYPE_AAAA &&
136 RD16(&qtail[DNS_QTAIL_QCLASS]) == DNS_CLASS_IN) {
137 WR16(&qtail[DNS_QTAIL_QTYPE], DNS_TYPE_A);
138 }
139 p = qtail + DNS_QTAIL_SIZE;
140 }
141}
142/*---------------------------------------------------------------------------*/
143uint16_t
144nat64_dns64_4to6(const uint8_t *ipv4data, uint16_t ipv4len,
145 uint8_t *ipv6data, uint16_t ipv6len,
146 uint16_t ipv6bufsiz)
147{
148 uint16_t qdcount, ancount;
149 const uint8_t *src;
150 uint8_t *dst;
151 const uint8_t *src_end;
152 const uint8_t *dst_end = ipv6data + ipv6bufsiz;
153
154 if(ipv4len < DNS_HDR_SIZE) {
155 return ipv6len;
156 }
157
158 qdcount = RD16(&ipv4data[DNS_HDR_QDCOUNT]);
159 ancount = RD16(&ipv4data[DNS_HDR_ANCOUNT]);
160 src = ipv4data + DNS_HDR_SIZE;
161 dst = ipv6data + DNS_HDR_SIZE;
162 src_end = ipv4data + ipv4len;
163
164 LOG_DBG("DNS64 4to6: id=0x%04x, %u answers\n",
165 RD16(&ipv4data[DNS_HDR_ID]), ancount);
166
167 /* Skip past the question section in both src and dst buffers.
168 * Also patch QTYPE from A back to AAAA in the output. */
169 for(uint16_t i = 0; i < qdcount; i++) {
170 const uint8_t *after_name = skip_dns_name(src, src_end);
171 if(after_name == NULL || after_name + DNS_QTAIL_SIZE > src_end) {
172 return ipv6len;
173 }
174 /* Advance dst by the same amount (data was already copied by caller). */
175 dst = ipv6data + (after_name - ipv4data);
176 if(RD16(&dst[DNS_QTAIL_QTYPE]) == DNS_TYPE_A &&
177 RD16(&dst[DNS_QTAIL_QCLASS]) == DNS_CLASS_IN) {
178 WR16(&dst[DNS_QTAIL_QTYPE], DNS_TYPE_AAAA);
179 }
180 src = after_name + DNS_QTAIL_SIZE;
181 dst = ipv6data + (src - ipv4data);
182 }
183
184 /* Now process each answer RR. A (4-byte RDATA) records are expanded
185 * to AAAA (16 bytes), shifting all subsequent data by +12 per record.
186 * After the answer section we truncate authority and additional
187 * sections rather than attempting to relocate them — their data would
188 * be at wrong offsets after expansion, and IoT resolvers do not need
189 * them. */
190 uint16_t emitted_ancount = 0;
191 /* Marks the start of the current answer's output bytes. On a
192 * mid-RR truncate, dst is rewound to this point so the message
193 * doesn't end on a partially-written record. */
194 uint8_t *rr_start = dst;
195 for(uint16_t i = 0; i < ancount; i++) {
196 rr_start = dst;
197
198 /* Copy/skip the name in the answer. */
199 const uint8_t *name_end = skip_dns_name(src, src_end);
200 if(name_end == NULL) {
201 goto truncate;
202 }
203 size_t name_len = name_end - src;
204 if(dst + name_len > dst_end) {
205 LOG_WARN("DNS64 4to6: output buffer full at name copy\n");
206 goto truncate;
207 }
208 memcpy(dst, src, name_len);
209 src += name_len;
210 dst += name_len;
211
212 /* We need at least 10 bytes for TYPE(2)+CLASS(2)+TTL(4)+RDLENGTH(2). */
213 if(src + 10 > src_end) {
214 goto truncate;
215 }
216
217 uint16_t rr_type = RD16(&src[0]);
218 uint16_t rdlength = RD16(&src[8]);
219
220 if(rr_type == DNS_TYPE_A && rdlength == 4 && src + 10 + 4 <= src_end) {
221 /* Rewrite A -> AAAA: change type and expand the 4-byte address
222 * to a 16-byte NAT64-prefixed IPv6 address (10 + 16 = 26 bytes). */
223 if(dst + 10 + 16 > dst_end) {
224 LOG_WARN("DNS64 4to6: output buffer full\n");
225 goto truncate;
226 }
227 WR16(&dst[0], DNS_TYPE_AAAA); /* TYPE = AAAA */
228 memcpy(&dst[2], &src[2], 2 + 4); /* CLASS + TTL unchanged */
229 WR16(&dst[8], 16); /* RDLENGTH = 16 */
230 dst += 10;
231 src += 10;
232
233 /* Synthesize IPv6 address from the IPv4 address. */
234 uip_ip4addr_t a4;
235 memcpy(&a4, src, 4);
236 ip64_addr_4to6(&a4, (uip_ip6addr_t *)dst);
237
238 src += 4;
239 dst += 16;
240 } else {
241 /* Non-A or unexpected RDLENGTH: copy verbatim. */
242 size_t rr_total = 10 + rdlength;
243 if(src + rr_total > src_end) {
244 goto truncate;
245 }
246 if(dst + rr_total > dst_end) {
247 LOG_WARN("DNS64 4to6: output buffer full\n");
248 goto truncate;
249 }
250 memcpy(dst, src, rr_total);
251 src += rr_total;
252 dst += rr_total;
253 }
254 emitted_ancount++;
255 }
256 /* All answers emitted successfully: nothing to rewind. */
257 rr_start = dst;
258
259truncate:
260 /* Rewind any partially-written final RR so the message ends on a
261 * complete record, then publish the count actually emitted. */
262 dst = rr_start;
263 WR16(&ipv6data[DNS_HDR_ANCOUNT], emitted_ancount);
264 /* Drop authority and additional sections — they would be at wrong
265 * offsets after answer expansion, and IoT resolvers don't need them. */
266 WR16(&ipv6data[DNS_HDR_NSCOUNT], 0);
267 WR16(&ipv6data[DNS_HDR_ARCOUNT], 0);
268 return (uint16_t)(dst - ipv6data);
269}
270/*---------------------------------------------------------------------------*/
271/** @} */
uint16_t nat64_dns64_4to6(const uint8_t *ipv4data, uint16_t ipv4len, uint8_t *ipv6data, uint16_t ipv6len, uint16_t ipv6bufsiz)
Rewrite an incoming DNS response from A to AAAA.
void nat64_dns64_6to4(uint8_t *data, uint16_t len)
Rewrite an outgoing DNS query from AAAA to A.
Header file for the logging system.
NAT64 DNS64 translation (RFC 6147).
Header file for the uIP TCP/IP stack.
Representation of an IP address.
Definition uip.h:95