Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAT64 support #722

Merged
merged 1 commit into from May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion daemon/cachedump.c
Expand Up @@ -859,7 +859,8 @@ int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm,
/* go up? */
if(iter_dp_is_useless(&qinfo, BIT_RD, dp,
(worker->env.cfg->do_ip4 && worker->back->num_ip4 != 0),
(worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0))) {
(worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0),
worker->env.cfg->do_nat64)) {
print_dp_main(ssl, dp, msg);
print_dp_details(ssl, worker, dp);
if(!ssl_printf(ssl, "cache delegation was "
Expand Down
3 changes: 3 additions & 0 deletions doc/Changelog
@@ -1,3 +1,6 @@
5 November 2022: equinox (David Lamparter)
- Implemented NAT64 support

21 October 2022: George
- Merge #767 from jonathangray: consistently use IPv4/IPv6 in
unbound.conf.5.
Expand Down
20 changes: 20 additions & 0 deletions doc/README.DNS64
Expand Up @@ -28,3 +28,23 @@ prefix. For example:
;; ANSWER SECTION:
jazz-v4.viagenie.ca. 86400 IN AAAA 64:ff9b::ce7b:1f02


NAT64 support was added by David Lamparter in 2022; license(s) of the
surrounding code apply. Note that NAT64 is closely related but functionally
orthogonal to DNS64; it allows Unbound to send outgoing queries to IPv4-only
servers over IPv6 through the configured NAT64 prefix. This allows running
an Unbound instance on an IPv6-only host without breaking every single domain
that only has IPv4 servers. Whether that Unbound instance also does DNS64 is
an independent choice.

To enable NAT64 in Unbound, add to unbound.conf's "server" section:

do-nat64: yes

The NAT64 prefix defaults to the DNS64 prefix, which in turn defaults to the
standard 64:FF9B::/96 prefix. You can reconfigure it with:

nat64-prefix: 64:FF9B::/96

To test NAT64 operation, pick a domain that only has IPv4 reachability for its
nameservers and try resolving any names in that domain.
10 changes: 10 additions & 0 deletions doc/example.conf.in
Expand Up @@ -229,6 +229,16 @@ server:
# Enable IPv6, "yes" or "no".
# do-ip6: yes

# If running unbound on an IPv6-only host, domains that only have
# IPv4 servers would become unresolveable. If NAT64 is available in
# the network, unbound can use NAT64 to reach these servers with
# the following option. This is NOT needed for enabling DNS64 on a
# system that has IPv4 connectivity.
# do-nat64: no

# NAT64 prefix. Defaults to using dns64-prefix value.
# nat64-prefix: 64:ff9b::0/96

# Enable UDP, "yes" or "no".
# do-udp: yes

Expand Down
12 changes: 12 additions & 0 deletions doc/unbound.conf.5.in
Expand Up @@ -2268,6 +2268,18 @@ List domain for which the AAAA records are ignored and the A record is
used by dns64 processing instead. Can be entered multiple times, list a
new domain for which it applies, one per line. Applies also to names
underneath the name given.
.SS "NAT64 Operation"
.LP
NAT64 operation allows using a NAT64 prefix for outbound requests to IPv4-only
servers. It is controlled by two options in the \fBserver:\fR section:
.TP
.B do\-nat64: \fI<yes or no>\fR
Use NAT64 to reach IPv4-only servers. Default no.
.TP
.B nat64\-prefix: \fI<IPv6 prefix>\fR
Use a specific NAT64 prefix to reach IPv4-only servers. Defaults to using
the prefix configured in \fBdns64\-prefix\fR, which in turn defaults to
64:ff9b::/96. Must be /96 or shorter.
.SS "DNSCrypt Options"
.LP
The
Expand Down
41 changes: 39 additions & 2 deletions iterator/iter_utils.c
Expand Up @@ -71,6 +71,11 @@
/** time when nameserver glue is said to be 'recent' */
#define SUSPICION_RECENT_EXPIRY 86400

/** if NAT64 is enabled and no NAT64 prefix is configured, first fall back to
* DNS64 prefix. If that is not configured, fall back to this default value.
*/
static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96";

/** fillup fetch policy array */
static void
fetch_fill(struct iter_env* ie, const char* str)
Expand Down Expand Up @@ -142,6 +147,7 @@ caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg)
int
iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
{
const char *nat64_prefix;
int i;
/* target fetch policy */
if(!read_fetch_policy(iter_env, cfg->target_fetch_policy))
Expand Down Expand Up @@ -172,8 +178,34 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
}

}

nat64_prefix = cfg->nat64_prefix;
if (!nat64_prefix)
nat64_prefix = cfg->dns64_prefix;
if (!nat64_prefix)
nat64_prefix = DEFAULT_NAT64_PREFIX;
if (!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr,
&iter_env->nat64_prefix_addrlen,
&iter_env->nat64_prefix_net)) {
log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
return 0;
}
if (!addr_is_ip6(&iter_env->nat64_prefix_addr,
iter_env->nat64_prefix_addrlen)) {
log_err("nat64_prefix is not IPv6: %s", cfg->nat64_prefix);
return 0;
}
if (iter_env->nat64_prefix_net != 32 && iter_env->nat64_prefix_net != 40 &&
iter_env->nat64_prefix_net != 48 && iter_env->nat64_prefix_net != 56 &&
iter_env->nat64_prefix_net != 64 && iter_env->nat64_prefix_net != 96 ) {
log_err("dns64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
nat64_prefix);
return 0;
}

iter_env->supports_ipv6 = cfg->do_ip6;
iter_env->supports_ipv4 = cfg->do_ip4;
iter_env->use_nat64 = cfg->do_nat64;
iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
return 1;
}
Expand Down Expand Up @@ -238,7 +270,8 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env,
if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) {
return -1; /* there is no ip6 available */
}
if(!iter_env->supports_ipv4 && !addr_is_ip6(&a->addr, a->addrlen)) {
if(!iter_env->supports_ipv4 && !iter_env->use_nat64 &&
!addr_is_ip6(&a->addr, a->addrlen)) {
return -1; /* there is no ip4 available */
}
/* check lameness - need zone , class info */
Expand Down Expand Up @@ -745,10 +778,14 @@ iter_mark_pside_cycle_targets(struct module_qstate* qstate, struct delegpt* dp)

int
iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
struct delegpt* dp, int supports_ipv4, int supports_ipv6)
struct delegpt* dp, int supports_ipv4, int supports_ipv6, int use_nat64)
{
struct delegpt_ns* ns;
struct delegpt_addr* a;

if (supports_ipv6 && use_nat64)
supports_ipv4 = 1;

/* check:
* o RD qflag is on.
* o no addresses are provided.
Expand Down
3 changes: 2 additions & 1 deletion iterator/iter_utils.h
Expand Up @@ -192,7 +192,8 @@ void iter_mark_pside_cycle_targets(struct module_qstate* qstate,
* @return true if dp is useless.
*/
int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
struct delegpt* dp, int supports_ipv4, int supports_ipv6);
struct delegpt* dp, int supports_ipv4, int supports_ipv6,
int use_nat64);

/**
* See if qname has DNSSEC needs. This is true if there is a trust anchor above
Expand Down
52 changes: 44 additions & 8 deletions iterator/iterator.c
Expand Up @@ -255,7 +255,7 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super)
log_err("out of memory adding missing");
}
delegpt_mark_neg(dpns, qstate->qinfo.qtype);
if((dpns->got4 == 2 || !ie->supports_ipv4) &&
if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
(dpns->got6 == 2 || !ie->supports_ipv6)) {
dpns->resolved = 1; /* mark as failed */
target_count_increase_nx(super_iq, 1);
Expand Down Expand Up @@ -1571,7 +1571,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
* same server reply) if useless-checked.
*/
if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
iq->dp, ie->supports_ipv4, ie->supports_ipv6)) {
iq->dp, ie->supports_ipv4, ie->supports_ipv6,
ie->use_nat64)) {
struct delegpt* retdp = NULL;
if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &retdp)) {
if(retdp) {
Expand Down Expand Up @@ -2085,14 +2086,14 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
/* if this nameserver is at a delegation point, but that
* delegation point is a stub and we cannot go higher, skip*/
if( ((ie->supports_ipv6 && !ns->done_pside6) ||
(ie->supports_ipv4 && !ns->done_pside4)) &&
((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) &&
!can_have_last_resort(qstate->env, ns->name, ns->namelen,
iq->qchase.qclass, NULL)) {
log_nametypeclass(VERB_ALGO, "cannot pside lookup ns "
"because it is also a stub/forward,",
ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
if(ie->supports_ipv6) ns->done_pside6 = 1;
if(ie->supports_ipv4) ns->done_pside4 = 1;
if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1;
continue;
}
/* query for parent-side A and AAAA for nameservers */
Expand All @@ -2117,7 +2118,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
return 0;
}
}
if(ie->supports_ipv4 && !ns->done_pside4) {
if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) {
/* Send the A request. */
if(!generate_parentside_target_query(qstate, iq, id,
ns->name, ns->namelen,
Expand Down Expand Up @@ -2384,7 +2385,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
}
if(!ie->supports_ipv6)
delegpt_no_ipv6(iq->dp);
if(!ie->supports_ipv4)
if(!ie->supports_ipv4 && !ie->use_nat64)
delegpt_no_ipv4(iq->dp);
delegpt_log(VERB_ALGO, iq->dp);

Expand Down Expand Up @@ -2811,6 +2812,41 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
iq->dnssec_expected?"expected": "not expected",
iq->dnssec_lame_query?" but lame_query anyway": "");
}

struct sockaddr_storage real_addr = target->addr;
socklen_t real_addrlen = target->addrlen;

if (ie->use_nat64 && real_addr.ss_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)&target->addr;
struct sockaddr_in6 *sin6;
int plen = ie->nat64_prefix_net;
uint8_t *v4_byte;

real_addr = ie->nat64_prefix_addr;
real_addrlen = ie->nat64_prefix_addrlen;

sin6 = (struct sockaddr_in6 *)&real_addr;
sin6->sin6_flowinfo = 0;
sin6->sin6_port = sin->sin_port;

/* config validation enforces these prefix lengths too */
log_assert(plen == 32 || plen == 40 || plen == 48 || plen == 56
|| plen == 64 || plen == 96);
plen = plen / 8;

v4_byte = (uint8_t *)&sin->sin_addr.s_addr;
for (int i = 0; i < 4; i++) {
if (plen == 8)
/* bits 64...72 are MBZ */
sin6->sin6_addr.s6_addr[plen++] = 0;

sin6->sin6_addr.s6_addr[plen++] = *v4_byte++;
}

log_name_addr(VERB_QUERY, "applied NAT64:", iq->dp->name,
&real_addr, real_addrlen);
}

fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query));
outq = (*qstate->env->send_query)(&iq->qinfo_out,
iq->chase_flags | (iq->chase_to_rd?BIT_RD:0),
Expand All @@ -2821,7 +2857,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
!qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env,
&iq->qinfo_out)||target->attempts==1)?0:BIT_CD),
iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted(
ie, iq), sq_check_ratelimit, &target->addr, target->addrlen,
ie, iq), sq_check_ratelimit, &real_addr, real_addrlen,
iq->dp->name, iq->dp->namelen,
(iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream),
(iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream),
Expand Down Expand Up @@ -3564,7 +3600,7 @@ processTargetResponse(struct module_qstate* qstate, int id,
} else {
verbose(VERB_ALGO, "iterator TargetResponse failed");
delegpt_mark_neg(dpns, qstate->qinfo.qtype);
if((dpns->got4 == 2 || !ie->supports_ipv4) &&
if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
(dpns->got6 == 2 || !ie->supports_ipv6)) {
dpns->resolved = 1; /* fail the target */
/* do not count cached answers */
Expand Down
12 changes: 12 additions & 0 deletions iterator/iterator.h
Expand Up @@ -117,6 +117,18 @@ struct iter_env {
/** A flag to indicate whether or not we have an IPv4 route */
int supports_ipv4;

/** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */
int use_nat64;

/** NAT64 prefix address, cf. dns64_env->prefix_addr */
struct sockaddr_storage nat64_prefix_addr;

/** sizeof(sockaddr_in6) */
socklen_t nat64_prefix_addrlen;

/** CIDR mask length of NAT64 prefix */
int nat64_prefix_net;

/** A set of inetaddrs that should never be queried. */
struct iter_donotq* donotq;

Expand Down
5 changes: 3 additions & 2 deletions pythonmod/interface.i
Expand Up @@ -1378,7 +1378,7 @@ struct delegpt* dns_cache_find_delegation(struct module_env* env,
struct regional* region, struct dns_msg** msg, uint32_t timenow,
int noexpiredabove, uint8_t* expiretop, size_t expiretoplen);
int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
struct delegpt* dp, int supports_ipv4, int supports_ipv6);
struct delegpt* dp, int supports_ipv4, int supports_ipv6, int use_nat64);
struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints,
uint8_t* qname, uint16_t qclass, struct delegpt* dp);

Expand Down Expand Up @@ -1409,7 +1409,8 @@ struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t n
if(!dp)
return NULL;
if(iter_dp_is_useless(&qinfo, BIT_RD, dp,
qstate->env->cfg->do_ip4, qstate->env->cfg->do_ip6)) {
qstate->env->cfg->do_ip4, qstate->env->cfg->do_ip6,
qstate->env->cfg->do_nat64)) {
if (dname_is_root((uint8_t*)nm))
return NULL;
nm = (char*)dp->name;
Expand Down