diff --git a/CHANGELOG b/CHANGELOG index 0a34b64..fb67b4b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,9 @@ version 2.67 Fix --dhcp-match, --dhcp-vendorclass and --dhcp-userclass to work with BOOTP and well as DHCP. Thanks to Peter Korsgaard for spotting the problem. + + Add --synth-domain. Thanks to Vishvananda Ishaya for + suggesting this. version 2.66 diff --git a/Makefile b/Makefile index 7240fbf..fe63aee 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' objs = cache.o rfc1035.o util.o option.o forward.o network.o \ dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ - dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o + dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o domain.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h diff --git a/bld/Android.mk b/bld/Android.mk index 47182c0..46e4d03 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ netlink.c network.c option.c rfc1035.c \ rfc2131.c tftp.c util.c conntrack.c \ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ - radv.c slaac.c auth.c ipset.c + radv.c slaac.c auth.c ipset.c domain.c LOCAL_MODULE := dnsmasq diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index fc12b1c..3ea487b 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -519,6 +519,21 @@ the name. More than one name may be associated with an interface address by repeating the flag; in that case the first instance is used for the reverse address-to-name mapping. .TP +.B --synth-domain=,
+Create artificial A/AAAA and PTR records for an address range. The +records use the address, with periods (or colons for IPv6) replaced +with dashes. + +An example should make this clearer. +.B --synth-domain=thekelleys.org.uk,192.168.0.0/24 +will result in a query for 192-168-0-56.thekelleys.org.uk returning +192.168.0.56 and a reverse query vice versa. The same applies to IPv6, but IPv6 addresses may start with '::' +but DNS labels may not start with '-' so in this case a zero is added +in front of the label. ::1 becomes 0--1. + +The address range can be of the form +, or / +.TP .B --add-mac Add the MAC address of the requestor to DNS queries which are forwarded upstream. This may be used to DNS filtering by the upstream diff --git a/src/cache.c b/src/cache.c index 88e9641..6fdeba2 100644 --- a/src/cache.c +++ b/src/cache.c @@ -971,38 +971,6 @@ void cache_reload(void) total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz); } -char *get_domain(struct in_addr addr) -{ - struct cond_domain *c; - - for (c = daemon->cond_domain; c; c = c->next) - if (!c->is6 && - ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && - ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) - return c->domain; - - return daemon->domain_suffix; -} - - -#ifdef HAVE_IPV6 -char *get_domain6(struct in6_addr *addr) -{ - struct cond_domain *c; - - u64 addrpart = addr6part(addr); - - for (c = daemon->cond_domain; c; c = c->next) - if (c->is6 && - is_same_net6(addr, &c->start6, 64) && - addrpart >= addr6part(&c->start6) && - addrpart <= addr6part(&c->end6)) - return c->domain; - - return daemon->domain_suffix; -} -#endif - #ifdef HAVE_DHCP struct in_addr a_record_from_hosts(char *name, time_t now) { diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 41e2798..cccab44 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -789,7 +789,7 @@ extern struct daemon { struct name_list *secondary_forward_server; int group_set, osport; char *domain_suffix; - struct cond_domain *cond_domain; + struct cond_domain *cond_domain, *synth_domains; char *runfile; char *lease_change_command; struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers; @@ -907,15 +907,19 @@ void cache_unhash_dhcp(void); void dump_cache(time_t now); char *cache_get_name(struct crec *crecp); struct crec *cache_enumerate(int init); -char *get_domain(struct in_addr addr); -#ifdef HAVE_IPV6 -char *get_domain6(struct in6_addr *addr); -#endif #ifdef HAVE_DNSSEC struct keydata *keydata_alloc(char *data, size_t len); void keydata_free(struct keydata *blocks); #endif +/* domain.c */ +char *get_domain(struct in_addr addr); +#ifdef HAVE_IPV6 +char *get_domain6(struct in6_addr *addr); +#endif +int is_name_synthetic(int flags, char *name, struct all_addr *addr); +int is_rev_synth(int flag, struct all_addr *addr, char *name); + /* rfc1035.c */ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep); diff --git a/src/domain.c b/src/domain.c new file mode 100644 index 0000000..2812fb7 --- /dev/null +++ b/src/domain.c @@ -0,0 +1,200 @@ +/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "dnsmasq.h" + + +static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c); +#ifdef HAVE_IPV6 +static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c); +#endif + + +int is_name_synthetic(int flags, char *name, struct all_addr *addr) +{ + char *p; + struct cond_domain *c = NULL; + int prot = AF_INET; + +#ifdef HAVE_IPV6 + if (flags & F_IPV6) + prot = AF_INET6; +#endif + + /* NB, must not alter name if we return zero */ + for (p = name; *p; p++) + { + char c = *p; + + if ((c >='0' && c <= '9') || c == '-') + continue; + +#ifdef HAVE_IPV6 + if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f'))) + continue; +#endif + + break; + } + + if (*p != '.') + return 0; + + *p = 0; + + for (p = name; *p; p++) + if (*p == '-') + { + if (prot == AF_INET) + *p = '.'; +#ifdef HAVE_IPV6 + else + *p = ':'; +#endif + } + + if (inet_pton(prot, name, addr)) + for (c = daemon->synth_domains; c; c = c->next) + if (hostname_isequal(c->domain, p+1)) + { + if (prot == AF_INET) + { + if (!c->is6 && + ntohl(addr->addr.addr4.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr->addr.addr4.s_addr) <= ntohl(c->end.s_addr)) + break; + } +#ifdef HAVE_IPV6 + else + { + u64 addrpart = addr6part(&addr->addr.addr6); + + if (c->is6 && + is_same_net6(&addr->addr.addr6, &c->start6, 64) && + addrpart >= addr6part(&c->start6) && + addrpart <= addr6part(&c->end6)) + break; + } +#endif + } + + /* restore name */ + for (p = name; *p; p++) + if (*p == '.' || *p == ':') + *p = '-'; + + *p = '.'; + + return (c != NULL); + +} + + +int is_rev_synth(int flag, struct all_addr *addr, char *name) +{ + struct cond_domain *c; + + if (flag & F_IPV4 && (c = search_domain(addr->addr.addr4, daemon->synth_domains))) + { + char *p; + + inet_ntop(AF_INET, &addr->addr.addr4, name, ADDRSTRLEN); + for (p = name; *p; p++) + if (*p == '.') + *p = '-'; + + strncat(name, ".", MAXDNAME); + strncat(name, c->domain, MAXDNAME); + + return 1; + } + +#ifdef HAVE_IPV6 + if (flag & F_IPV6 && (c = search_domain6(&addr->addr.addr6, daemon->synth_domains))) + { + char *p; + + inet_ntop(AF_INET6, &addr->addr.addr6, name, ADDRSTRLEN); + + /* IPv6 presentation address can start with ":", but valid domain names + cannot start with "-" so prepend a zero in that case. */ + if (*name == ':') + { + *name = '0'; + inet_ntop(AF_INET6, &addr->addr.addr6, name+1, ADDRSTRLEN); + } + + for (p = name; *p; p++) + if (*p == ':') + *p = '-'; + + strncat(name, ".", MAXDNAME); + strncat(name, c->domain, MAXDNAME); + + return 1; + } +#endif + + return 0; +} + + +static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c) +{ + for (; c; c = c->next) + if (!c->is6 && + ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) + return c; + + return NULL; +} + +char *get_domain(struct in_addr addr) +{ + struct cond_domain *c; + + if ((c = search_domain(addr, daemon->cond_domain))) + return c->domain; + + return daemon->domain_suffix; +} + +#ifdef HAVE_IPV6 +static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c) +{ + u64 addrpart = addr6part(addr); + + for (; c; c = c->next) + if (c->is6 && + is_same_net6(addr, &c->start6, 64) && + addrpart >= addr6part(&c->start6) && + addrpart <= addr6part(&c->end6)) + return c; + + return NULL; +} + +char *get_domain6(struct in6_addr *addr) +{ + struct cond_domain *c; + + if ((c = search_domain6(addr, daemon->cond_domain))) + return c->domain; + + return daemon->domain_suffix; +} +#endif diff --git a/src/option.c b/src/option.c index 2a61017..a92b712 100644 --- a/src/option.c +++ b/src/option.c @@ -128,8 +128,9 @@ struct myoption { #define LOPT_AUTHSFS 317 #define LOPT_AUTHPEER 318 #define LOPT_IPSET 319 +#define LOPT_SYNTH 320 #ifdef OPTION6_PREFIX_CLASS -#define LOPT_PREF_CLSS 320 +#define LOPT_PREF_CLSS 321 #endif #ifdef HAVE_GETOPT_LONG @@ -264,6 +265,7 @@ static const struct myoption opts[] = { "auth-sec-servers", 1, 0, LOPT_AUTHSFS }, { "auth-peer", 1, 0, LOPT_AUTHPEER }, { "ipset", 1, 0, LOPT_IPSET }, + { "synth-domain", 1, 0, LOPT_SYNTH }, #ifdef OPTION6_PREFIX_CLASS { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, #endif @@ -406,6 +408,7 @@ static struct { { LOPT_AUTHSFS, ARG_DUP, "[,...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL }, { LOPT_AUTHPEER, ARG_DUP, "[,...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, { LOPT_IPSET, ARG_DUP, "//[,...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, + { LOPT_SYNTH, ARG_DUP, ",", gettext_noop("Specify a domain and address range for sythesised names"), NULL }, #ifdef OPTION6_PREFIX_CLASS { LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, #endif @@ -1687,7 +1690,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma break; - case 's': /* --domain */ + case 's': /* --domain */ + case LOPT_SYNTH: /* --synth-domain */ if (strcmp (arg, "#") == 0) set_option_bool(OPT_RESOLV_DOMAIN); else @@ -1702,7 +1706,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { struct cond_domain *new = opt_malloc(sizeof(struct cond_domain)); char *netpart; - + unhide_metas(comma); if ((netpart = split_chr(comma, '/'))) { @@ -1723,7 +1727,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma local=// local=/xxx.yyy.zzz.in-addr.arpa/ */ - if (strcmp(arg, "local") != 0 || + if (strcmp(arg, "local") != 0 || + option != 's' || (msize != 8 && msize != 16 && msize != 24)) ret_err(gen_err); else @@ -1779,7 +1784,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma local=// local=/xxx.yyy.zzz.ip6.arpa/ */ - if (strcmp(arg, "local") != 0 || ((msize & 4) != 0)) + if (strcmp(arg, "local") != 0 || + option != 's' || + ((msize & 4) != 0)) ret_err(gen_err); else { @@ -1813,7 +1820,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma else ret_err(gen_err); } - else + else { arg = split(comma); if (inet_pton(AF_INET, comma, &new->start)) @@ -1839,11 +1846,21 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } new->domain = d; - new->next = daemon->cond_domain; - daemon->cond_domain = new; + if (option == 's') + { + new->next = daemon->cond_domain; + daemon->cond_domain = new; + } + else + { + new->next = daemon->synth_domains; + daemon->synth_domains = new; + } } - else + else if (option == 's') daemon->domain_suffix = d; + else + ret_err(gen_err); } } break; diff --git a/src/rfc1035.c b/src/rfc1035.c index 8d55ffd..1ba62e1 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1517,6 +1517,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); + else if (is_rev_synth(is_arpa, &addr, name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", name)) + anscount++; + } + } else if (is_arpa == F_IPV4 && option_bool(OPT_BOGUSPRIV) && private_net(addr.addr.addr4, 1)) @@ -1687,6 +1700,17 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); } + else if (is_name_synthetic(flag, name, &addr)) + { + ans = 1; + if (!dryrun) + { + log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr)) + anscount++; + } + } } if (qtype == T_CNAME || qtype == T_ANY)