diff --git a/conf.c b/conf.c index 279fdfe..21e9bc0 100644 --- a/conf.c +++ b/conf.c @@ -279,7 +279,7 @@ static void get_dns(struct ctx *c) dns4_set = !c->v4 || !!*dns4; dns6_set = !c->v6 || !IN6_IS_ADDR_UNSPECIFIED(dns6); dnss_set = !!*s->n || c->no_dns_search; - dns_set = dns4_set || dns6_set || c->no_dns; + dns_set = (dns4_set && dns6_set) || c->no_dns; if (dns_set && dnss_set) return; @@ -583,21 +583,35 @@ static void usage(const char *name) info( " default: gateway from interface with default route"); info( " -i, --interface NAME Interface for addresses and routes"); info( " default: interface with first default route"); - info( " -D, --dns ADDR Pass IPv4 or IPv6 address as DNS"); + info( " -D, --dns ADDR Use IPv4 or IPv6 address as DNS"); info( " can be specified multiple times"); info( " a single, empty option disables DNS information"); if (strstr(name, "pasta")) - info( " default: don't send any addresses"); + info( " default: don't use any addresses"); else info( " default: use addresses from /etc/resolv.conf"); info( " -S, --search LIST Space-separated list, search domains"); info( " a single, empty option disables the DNS search list"); if (strstr(name, "pasta")) - info( " default: don't send any search list"); + info( " default: don't use any search list"); else info( " default: use search list from /etc/resolv.conf"); + if (strstr(name, "pasta")) + info(" --dhcp-dns: \tPass DNS list via DHCP/DHCPv6/NDP"); + else + info(" --no-dhcp-dns: No DNS list in DHCP/DHCPv6/NDP"); + + if (strstr(name, "pasta")) + info(" --dhcp-search: Pass list via DHCP/DHCPv6/NDP"); + else + info(" --no-dhcp-search: No list in DHCP/DHCPv6/NDP"); + + info( " --dns-forward ADDR Forward DNS queries sent to ADDR"); + info( " can be specified zero to two times (for IPv4 and IPv6)"); + info( " default: don't forward DNS queries"); + info( " --no-tcp Disable TCP protocol handler"); info( " --no-udp Disable UDP protocol handler"); info( " --no-icmp Disable ICMP/ICMPv6 protocol handler"); @@ -699,22 +713,18 @@ void conf_print(struct ctx *c) info(" router: %s", inet_ntop(AF_INET, &c->gw4, buf4, sizeof(buf4))); } - } - if (!c->no_dns && !(c->no_dhcp && c->no_ndp && c->no_dhcpv6)) { for (i = 0; c->dns4[i]; i++) { if (!i) - info(" DNS:"); + info("DNS:"); inet_ntop(AF_INET, &c->dns4[i], buf4, sizeof(buf4)); - info(" %s", buf4); + info(" %s", buf4); } - } - if (!c->no_dns_search && !(c->no_dhcp && c->no_ndp && c->no_dhcpv6)) { for (i = 0; *c->dns_search[i].n; i++) { if (!i) - info(" search:"); - info(" %s", c->dns_search[i].n); + info("DNS search list:"); + info(" %s", c->dns_search[i].n); } } @@ -728,7 +738,7 @@ void conf_print(struct ctx *c) else if (!c->no_dhcpv6) info("NDP:"); else - return; + goto dns6; info(" assign: %s", inet_ntop(AF_INET6, &c->addr6, buf6, sizeof(buf6))); @@ -737,17 +747,18 @@ void conf_print(struct ctx *c) info(" our link-local: %s", inet_ntop(AF_INET6, &c->addr6_ll, buf6, sizeof(buf6))); +dns6: for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->dns6[i]); i++) { if (!i) - info(" DNS:"); + info("DNS:"); inet_ntop(AF_INET6, &c->dns6[i], buf6, sizeof(buf6)); - info(" %s", buf6); + info(" %s", buf6); } for (i = 0; *c->dns_search[i].n; i++) { if (!i) - info(" search:"); - info(" %s", c->dns_search[i].n); + info("DNS search list:"); + info(" %s", c->dns_search[i].n); } } } @@ -797,6 +808,11 @@ void conf(struct ctx *c, int argc, char **argv) {"nsrun-dir", required_argument, NULL, 3 }, {"config-net", no_argument, &c->pasta_conf_ns, 1 }, {"ns-mac-addr", required_argument, NULL, 4 }, + {"dhcp-dns", no_argument, NULL, 5 }, + {"no-dhcp-dns", no_argument, NULL, 6 }, + {"dhcp-search", no_argument, NULL, 7 }, + {"no-dhcp-search", no_argument, NULL, 8 }, + {"dns-forward", required_argument, NULL, 9 }, { 0 }, }; struct get_bound_ports_ns_arg ns_ports_arg = { .c = c }; @@ -808,6 +824,9 @@ void conf(struct ctx *c, int argc, char **argv) int name, ret, mask, b, i; uint32_t *dns4 = c->dns4; + if (c->mode == MODE_PASTA) + c->no_dhcp_dns = c->no_dhcp_dns_search = 1; + do { enum conf_port_type *set = NULL; const char *optstring; @@ -873,6 +892,51 @@ void conf(struct ctx *c, int argc, char **argv) c->mac_guest[i] = b; } break; + case 5: + if (c->mode != MODE_PASTA) { + err("--dhcp-dns is for pasta mode only"); + usage(argv[0]); + } + c->no_dhcp_dns = 0; + break; + case 6: + if (c->mode != MODE_PASST) { + err("--no-dhcp-dns is for passt mode only"); + usage(argv[0]); + } + c->no_dhcp_dns = 1; + break; + case 7: + if (c->mode != MODE_PASTA) { + err("--dhcp-search is for pasta mode only"); + usage(argv[0]); + } + c->no_dhcp_dns_search = 0; + break; + case 8: + if (c->mode != MODE_PASST) { + err("--no-dhcp-search is for passt mode only"); + usage(argv[0]); + } + c->no_dhcp_dns_search = 1; + break; + case 9: + if (IN6_IS_ADDR_UNSPECIFIED(&c->dns6_fwd) && + inet_pton(AF_INET6, optarg, &c->dns6_fwd) && + !IN6_IS_ADDR_UNSPECIFIED(&c->dns6_fwd) && + !IN6_IS_ADDR_LOOPBACK(&c->dns6_fwd)) + break; + + if (c->dns4_fwd == INADDR_ANY && + inet_pton(AF_INET, optarg, &c->dns4_fwd) && + c->dns4_fwd != INADDR_ANY && + c->dns4_fwd != INADDR_BROADCAST && + c->dns4_fwd != INADDR_LOOPBACK) + break; + + err("Invalid DNS forwarding address: %s", optarg); + usage(argv[0]); + break; case 'd': if (c->debug) { err("Multiple --debug options given"); @@ -1189,10 +1253,6 @@ void conf(struct ctx *c, int argc, char **argv) if (!c->mtu) c->mtu = ROUND_DOWN(ETH_MAX_MTU - ETH_HLEN, sizeof(uint32_t)); - if (c->mode == MODE_PASTA && dns4 == c->dns4 && dns6 == c->dns6) - c->no_dns = 1; - if (c->mode == MODE_PASTA && dnss == c->dns_search) - c->no_dns_search = 1; get_dns(c); if (!*c->pasta_ifn) diff --git a/dhcp.c b/dhcp.c index a052397..ab1249c 100644 --- a/dhcp.c +++ b/dhcp.c @@ -333,12 +333,13 @@ int dhcp(struct ctx *c, struct ethhdr *eh, size_t len) opts[26].s[1] = c->mtu % 256; } - for (i = 0, opts[6].slen = 0; c->dns4[i]; i++) { + for (i = 0, opts[6].slen = 0; !c->no_dhcp_dns && c->dns4[i]; i++) { ((uint32_t *)opts[6].s)[i] = c->dns4[i]; opts[6].slen += sizeof(uint32_t); } - opt_set_dns_search(c, sizeof(m->o)); + if (!c->no_dhcp_dns_search) + opt_set_dns_search(c, sizeof(m->o)); uh->len = htons(len = offsetof(struct msg, o) + fill(m) + sizeof(*uh)); uh->check = 0; diff --git a/dhcpv6.c b/dhcpv6.c index e4113bc..b79a8e9 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -394,6 +394,9 @@ static size_t dhcpv6_dns_fill(struct ctx *c, char *buf, int offset) char *p = NULL; int i; + if (c->no_dhcp_dns) + goto search; + for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->dns6[i]); i++) { if (!i) { srv = (struct opt_dns_servers *)(buf + offset); @@ -410,6 +413,10 @@ static size_t dhcpv6_dns_fill(struct ctx *c, char *buf, int offset) if (srv) srv->hdr.l = htons(srv->hdr.l); +search: + if (c->no_dhcp_dns_search) + return offset; + for (i = 0; *c->dns_search[i].n; i++) { if (!i) { srch = (struct opt_dns_search *)(buf + offset); diff --git a/ndp.c b/ndp.c index 386098c..6b1c1a8 100644 --- a/ndp.c +++ b/ndp.c @@ -127,6 +127,9 @@ int ndp(struct ctx *c, struct ethhdr *eh, size_t len) p += 4; } + if (c->no_dhcp_dns) + goto dns_done; + for (n = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->dns6[n]); n++); if (n) { *p++ = 25; /* RDNSS */ @@ -144,7 +147,7 @@ int ndp(struct ctx *c, struct ethhdr *eh, size_t len) dns_s_len += strlen(c->dns_search[n].n) + 2; } - if (dns_s_len) { + if (!c->no_dhcp_dns_search && dns_s_len) { *p++ = 31; /* DNSSL */ *p++ = (len + 8 - 1) / 8 + 1; /* length */ p += 2; /* reserved */ @@ -171,6 +174,7 @@ int ndp(struct ctx *c, struct ethhdr *eh, size_t len) p += 8 - dns_s_len % 8; } +dns_done: *p++ = 1; /* source ll */ *p++ = 1; /* length */ memcpy(p, c->mac, ETH_ALEN); diff --git a/passt.1 b/passt.1 index 92681f6..7070a31 100644 --- a/passt.1 +++ b/passt.1 @@ -165,19 +165,62 @@ Default is to use the interface with the first default route. .TP .BR \-D ", " \-\-dns " " \fIaddr -Assign IPv4 \fIaddr\fR via DHCP (option 23) or IPv6 \fIaddr\fR via NDP Router -Advertisement (option type 25) and DHCPv6 (option 23) as DNS resolver. +Use \fIaddr\fR (IPv4 or IPv6) for DHCP, DHCPv6, NDP or DNS forwarding, as +configured (see options \fB--no-dhcp-dns\fR, \fB--dhcp-dns\fR, +\fB--dns-forward\fR) instead of reading addresses from \fI/etc/resolv.conf\fR. This option can be specified multiple times, and a single, empty option disables -DNS options altogether. -In \fBpasst\fR mode, default is to use addresses from \fI/etc/resolv.conf\fR, -and, in \fBpasta\fR mode, no addresses are sent by default. +usage of DNS addresses altogether. + +.TP +.BR \-D ", " \-\-dns " " \fIaddr +Use \fIaddr\fR (IPv4 or IPv6) for DHCP, DHCPv6, NDP or DNS forwarding, as +configured (see options \fB--no-dhcp-dns\fR, \fB--dhcp-dns\fR, +\fB--dns-forward\fR) instead of reading addresses from \fI/etc/resolv.conf\fR. +This option can be specified multiple times, and a single, empty option disables +usage of DNS addresses altogether. + +.TP +.BR \-\-dns-forward " " \fIaddr +Map \fIaddr\fR (IPv4 or IPv6) as seen from guest or namespace to the first +configured DNS resolver (with corresponding IP version). Mapping is limited to +UDP traffic directed to port 53, and DNS answers are translated back with a +reverse mapping. +This option can be specified zero to two times (once for IPv4, once for IPv6). + .TP .BR \-S ", " \-\-search " " \fIlist -Assign space-separated \fIlist\fR via DHCP (option 119), via NDP Router -Advertisement (option type 31) and DHCPv6 (option 24) as DNS domain search list. -A single, empty option disables sending the DNS domain search list. -In \fBpasst\fR mode, default is to use the search list from -\fI/etc/resolv.conf\fR, and, in \fBpasta\fR mode, no list is sent by default. +Use space-separated \fIlist\fR for DHCP, DHCPv6, and NDP purposes, instead of +reading entries from \fI/etc/resolv.conf\fR. See options \fB--no-dhcp-search\fR +and \fB--dhcp-search\fR. A single, empty option disables the DNS domain search +list altogether. + +.TP +.BR \-\-no-dhcp-dns " " \fIaddr +In \fIpasst\fR mode, do not assign IPv4 addresses via DHCP (option 23) or IPv6 +addresses via NDP Router Advertisement (option type 25) and DHCPv6 (option 23) +as DNS resolvers. +By default, all the configured addresses are passed. + +.TP +.BR \-\-dhcp-dns " " \fIaddr +In \fIpasta\fR mode, assign IPv4 addresses via DHCP (option 23) or IPv6 +addresses via NDP Router Advertisement (option type 25) and DHCPv6 (option 23) +as DNS resolvers. +By default, configured addresses, if any, are not passed. + +.TP +.BR \-\-no-dhcp-search " " \fIaddr +In \fIpasst\fR mode, do not send the DNS domain search list addresses via DHCP +(option 119), via NDP Router Advertisement (option type 31) and DHCPv6 (option +24). +By default, the DNS domain search list resulting from configuration is passed. + +.TP +.BR \-\-dhcp-search " " \fIaddr +In \fIpasta\fR mode, send the DNS domain search list addresses via DHCP (option +119), via NDP Router Advertisement (option type 31) and DHCPv6 (option 24). +By default, the DNS domain search list resulting from configuration is not +passed. .TP .BR \-\-no-tcp diff --git a/passt.h b/passt.h index d7011da..2589ee7 100644 --- a/passt.h +++ b/passt.h @@ -114,6 +114,7 @@ enum passt_modes { * @mask4: IPv4 netmask, network order * @gw4: Default IPv4 gateway, network order * @dns4: IPv4 DNS addresses, zero-terminated, network order + * @dns4_fwd: Address forwarded (UDP) to first IPv4 DNS, network order * @dns_search: DNS search list * @v6: Enable IPv6 transport * @addr6: IPv6 address for external, routable interface @@ -121,7 +122,8 @@ enum passt_modes { * @addr6_seen: Latest IPv6 global/site address seen as source from tap * @addr6_ll_seen: Latest IPv6 link-local address seen as source from tap * @gw6: Default IPv6 gateway - * @dns4: IPv4 DNS addresses, zero-terminated + * @dns6: IPv6 DNS addresses, zero-terminated + * @dns6_fwd: Address forwarded (UDP) to first IPv6 DNS, network order * @ifi: Index of routable interface * @pasta_ifn: Name of namespace interface for pasta * @pasta_ifn: Index of namespace interface for pasta @@ -133,8 +135,10 @@ enum passt_modes { * @no_icmp: Disable ICMP operation * @icmp: Context for ICMP protocol handler * @mtu: MTU passed via DHCP/NDP - * @no_dns: Do not assign any DNS server via DHCP/DHCPv6/NDP - * @no_dns_search: Do not assign any DNS domain search via DHCP/DHCPv6/NDP + * @no_dns: Do not source/use DNS servers for any purpose + * @no_dns_search: Do not source/use domain search lists for any purpose + * @no_dhcp_dns: Do not assign any DNS server via DHCP/DHCPv6/NDP + * @no_dhcp_dns_search: Do not assign any DNS domain search via DHCP/DHCPv6/NDP * @no_dhcp: Disable DHCP server * @no_dhcpv6: Disable DHCPv6 server * @no_ndp: Disable NDP handler altogether @@ -172,6 +176,7 @@ struct ctx { uint32_t mask4; uint32_t gw4; uint32_t dns4[MAXNS + 1]; + uint32_t dns4_fwd; struct fqdn dns_search[MAXDNSRCH]; @@ -182,6 +187,7 @@ struct ctx { struct in6_addr addr6_ll_seen; struct in6_addr gw6; struct in6_addr dns6[MAXNS + 1]; + struct in6_addr dns6_fwd; unsigned int ifi; char pasta_ifn[IF_NAMESIZE]; @@ -198,6 +204,8 @@ struct ctx { int mtu; int no_dns; int no_dns_search; + int no_dhcp_dns; + int no_dhcp_dns_search; int no_dhcp; int no_dhcpv6; int no_ndp; diff --git a/slirp4netns.sh b/slirp4netns.sh index 1784926..ff12a52 100755 --- a/slirp4netns.sh +++ b/slirp4netns.sh @@ -16,7 +16,7 @@ # Author: Stefano Brivio PASTA_PID="$(mktemp)" -PASTA_OPTS="-q --ipv4-only -a 10.0.2.0 -n 24 -g 10.0.2.2 -m 1500 --no-ndp --no-dhcpv6 --no-dhcp -P ${PASTA_PID}" +PASTA_OPTS="-q --ipv4-only -a 10.0.2.0 -n 24 -g 10.0.2.2 --dns-forward 10.0.2.3 -m 1500 --no-ndp --no-dhcpv6 --no-dhcp -P ${PASTA_PID}" PASTA="$(command -v ./pasta || command -v pasta || :)" API_SOCKET= diff --git a/udp.c b/udp.c index 348f695..2fc52d3 100644 --- a/udp.c +++ b/udp.c @@ -718,6 +718,12 @@ void udp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events, udp_tap_map[V6][src].loopback = 0; bitmap_set(udp_act[V6][UDP_ACT_TAP], src); + } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->dns6_fwd) && + !memcmp(&b->s_in6.sin6_addr, &c->dns6_fwd, + sizeof(c->dns6_fwd)) && + ntohs(b->s_in6.sin6_port) == 53) { + b->ip6h.daddr = c->addr6_seen; + b->ip6h.saddr = c->dns6_fwd; } else { b->ip6h.daddr = c->addr6_seen; b->ip6h.saddr = b->s_in6.sin6_addr; @@ -797,6 +803,10 @@ void udp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events, udp_tap_map[V4][src].loopback = 1; bitmap_set(udp_act[V4][UDP_ACT_TAP], src); + } else if (c->dns4_fwd && + s_addr == ntohl(c->dns4[0]) && + ntohs(b->s_in.sin_port) == 53) { + b->iph.saddr = c->dns4_fwd; } else { b->iph.saddr = b->s_in.sin_addr.s_addr; } @@ -958,6 +968,9 @@ int udp_tap_handler(struct ctx *c, int af, void *addr, s_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); else s_in.sin_addr.s_addr = c->addr4_seen; + } else if (s_in.sin_addr.s_addr == c->dns4_fwd && + ntohs(s_in.sin_port) == 53) { + s_in.sin_addr.s_addr = c->dns4[0]; } } else { s_in6 = (struct sockaddr_in6) { @@ -976,6 +989,9 @@ int udp_tap_handler(struct ctx *c, int af, void *addr, s_in6.sin6_addr = in6addr_loopback; else s_in6.sin6_addr = c->addr6_seen; + } else if (!memcmp(addr, &c->dns6_fwd, sizeof(c->dns6_fwd)) && + ntohs(s_in6.sin6_port) == 53) { + s_in6.sin6_addr = c->dns6[0]; } else if (IN6_IS_ADDR_LINKLOCAL(&s_in6.sin6_addr)) { bind_to = BIND_LL; }