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 DNS packet buffer. */
79#define RD16(bytes) (((uint16_t)(bytes)[0] << 8) | (bytes)[1])
80/* Write a big-endian uint16 into a DNS packet buffer. */
81#define WR16(bytes, value) do { (bytes)[0] = (uint8_t)((value) >> 8); \
82 (bytes)[1] = (uint8_t)(value); } while(0)
83/*---------------------------------------------------------------------------*/
84/*
85 * DNS names can be either inline label chains or two-byte compression
86 * pointers. Callers only need the byte range occupied by the encoded
87 * name; pointer targets are never dereferenced here.
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 /* Ask the upstream IPv4 resolver for A records on behalf of each
127 * IoT-side AAAA question. */
128 for(uint16_t i = 0; i < qdcount; i++) {
129 const uint8_t *after_name = skip_dns_name(p, end);
130 if(after_name == NULL || after_name + DNS_QTAIL_SIZE > end) {
131 LOG_WARN("DNS64 6to4: malformed question section\n");
132 return;
133 }
134 /* Point to the fixed question-tail (QTYPE, QCLASS). */
135 uint8_t *qtail = (uint8_t *)after_name;
136 if(RD16(&qtail[DNS_QTAIL_QTYPE]) == DNS_TYPE_AAAA &&
137 RD16(&qtail[DNS_QTAIL_QCLASS]) == DNS_CLASS_IN) {
138 WR16(&qtail[DNS_QTAIL_QTYPE], DNS_TYPE_A);
139 }
140 p = qtail + DNS_QTAIL_SIZE;
141 }
142}
143/*---------------------------------------------------------------------------*/
144uint16_t
145nat64_dns64_4to6(const uint8_t *ipv4data, uint16_t ipv4len,
146 uint8_t *ipv6data, uint16_t ipv6len,
147 uint16_t ipv6bufsiz)
148{
149 uint16_t qdcount, ancount;
150 const uint8_t *src;
151 uint8_t *dst;
152 const uint8_t *src_end;
153 const uint8_t *dst_end = ipv6data + ipv6bufsiz;
154
155 if(ipv4len < DNS_HDR_SIZE) {
156 return ipv6len;
157 }
158
159 qdcount = RD16(&ipv4data[DNS_HDR_QDCOUNT]);
160 ancount = RD16(&ipv4data[DNS_HDR_ANCOUNT]);
161 src = ipv4data + DNS_HDR_SIZE;
162 dst = ipv6data + DNS_HDR_SIZE;
163 src_end = ipv4data + ipv4len;
164
165 LOG_DBG("DNS64 4to6: id=0x%04x, %u answers\n",
166 RD16(&ipv4data[DNS_HDR_ID]), ancount);
167
168 /* The response should still look like an answer to the IoT node's
169 * original AAAA question, even though the upstream query was A. */
170 for(uint16_t i = 0; i < qdcount; i++) {
171 const uint8_t *after_name = skip_dns_name(src, src_end);
172 if(after_name == NULL || after_name + DNS_QTAIL_SIZE > src_end) {
173 return ipv6len;
174 }
175 /* Advance dst by the same amount (data was already copied by caller). */
176 dst = ipv6data + (after_name - ipv4data);
177 if(RD16(&dst[DNS_QTAIL_QTYPE]) == DNS_TYPE_A &&
178 RD16(&dst[DNS_QTAIL_QCLASS]) == DNS_CLASS_IN) {
179 WR16(&dst[DNS_QTAIL_QTYPE], DNS_TYPE_AAAA);
180 }
181 src = after_name + DNS_QTAIL_SIZE;
182 dst = ipv6data + (src - ipv4data);
183 }
184
185 /* Expanding A records shifts every later byte in the DNS message.
186 * We rewrite complete answers only, then drop authority/additional
187 * sections whose compressed names may point at offsets that moved. */
188 uint16_t emitted_ancount = 0;
189 /* Marks the start of the current answer's output bytes. On a
190 * mid-RR truncate, dst is rewound to this point so the message
191 * doesn't end on a partially-written record. */
192 uint8_t *rr_start = dst;
193 for(uint16_t i = 0; i < ancount; i++) {
194 rr_start = dst;
195
196 const uint8_t *name_end = skip_dns_name(src, src_end);
197 if(name_end == NULL) {
198 goto truncate;
199 }
200 size_t name_len = name_end - src;
201 if(dst + name_len > dst_end) {
202 LOG_WARN("DNS64 4to6: output buffer full at name copy\n");
203 goto truncate;
204 }
205 memcpy(dst, src, name_len);
206 src += name_len;
207 dst += name_len;
208
209 if(src + 10 > src_end) {
210 goto truncate;
211 }
212
213 uint16_t rr_type = RD16(&src[0]);
214 uint16_t rdlength = RD16(&src[8]);
215
216 if(rr_type == DNS_TYPE_A && rdlength == 4 && src + 10 + 4 <= src_end) {
217 /* Rewrite A -> AAAA: change type and expand the 4-byte address
218 * to a 16-byte NAT64-prefixed IPv6 address (10 + 16 = 26 bytes). */
219 if(dst + 10 + 16 > dst_end) {
220 LOG_WARN("DNS64 4to6: output buffer full\n");
221 goto truncate;
222 }
223 WR16(&dst[0], DNS_TYPE_AAAA); /* TYPE = AAAA */
224 memcpy(&dst[2], &src[2], 2 + 4); /* CLASS + TTL unchanged */
225 WR16(&dst[8], 16); /* RDLENGTH = 16 */
226 dst += 10;
227 src += 10;
228
229 /* Synthesize IPv6 address from the IPv4 address. */
230 uip_ip4addr_t a4;
231 memcpy(&a4, src, 4);
232 ip64_addr_4to6(&a4, (uip_ip6addr_t *)dst);
233
234 src += 4;
235 dst += 16;
236 } else {
237 /* Non-A or unexpected RDLENGTH: copy verbatim. */
238 size_t rr_total = 10 + rdlength;
239 if(src + rr_total > src_end) {
240 goto truncate;
241 }
242 if(dst + rr_total > dst_end) {
243 LOG_WARN("DNS64 4to6: output buffer full\n");
244 goto truncate;
245 }
246 memcpy(dst, src, rr_total);
247 src += rr_total;
248 dst += rr_total;
249 }
250 emitted_ancount++;
251 }
252 /* All answers emitted successfully: nothing to rewind. */
253 rr_start = dst;
254
255truncate:
256 /* Rewind any partially-written final RR so the message ends on a
257 * complete record, then publish the count actually emitted. */
258 dst = rr_start;
259 WR16(&ipv6data[DNS_HDR_ANCOUNT], emitted_ancount);
260 /* Drop authority and additional sections — they would be at wrong
261 * offsets after answer expansion, and IoT resolvers don't need them. */
262 WR16(&ipv6data[DNS_HDR_NSCOUNT], 0);
263 WR16(&ipv6data[DNS_HDR_ARCOUNT], 0);
264 return (uint16_t)(dst - ipv6data);
265}
266/*---------------------------------------------------------------------------*/
267/** @} */
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