diff --git a/CHANGELOG b/CHANGELOG index d2bea96..22ff7b6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -821,5 +821,27 @@ release 2.3 embedded systems without a stable RTC. Oleg Vdovikin helped work out how to make that work. +release 2.4 + Fixed inability to start when the lease file doesn't + already exist. Thanks to Dag Wieers for reporting that. + + Fixed problem were dhcp-host configuration options did + not play well with entries in /etc/ethers for the same + host. Thanks again to Dag Wieers. + + Tweaked DHCP code to favour moving to a newly-configured + static IP address rather than an old lease when doing + DHCP allocation. + + Added --alias configuration option. This provides IPv4 + rewrite facilities like Cisco "DNS doctoring". Suggested + by Chad Skeeters. + + Fixed bug in /etc/ethers parsing code triggered by tab + characters. Qudos to Dag Wieers for hepling to nail that + one. + + Added "bind-interfaces" option correctly. + diff --git a/dnsmasq-mdk.spec b/dnsmasq-mdk.spec index bd0995c..9d29891 100644 --- a/dnsmasq-mdk.spec +++ b/dnsmasq-mdk.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.3 +Version: 2.4 Release: 1 Copyright: GPL Group: System Environment/Daemons diff --git a/dnsmasq-rh.spec b/dnsmasq-rh.spec index c766624..e0b578f 100644 --- a/dnsmasq-rh.spec +++ b/dnsmasq-rh.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.3 +Version: 2.4 Release: 1 Copyright: GPL Group: System Environment/Daemons diff --git a/dnsmasq-suse.spec b/dnsmasq-suse.spec index 01761a3..e4d9f76 100644 --- a/dnsmasq-suse.spec +++ b/dnsmasq-suse.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.3 +Version: 2.4 Release: 1 Copyright: GPL Group: Productivity/Networking/DNS/Servers diff --git a/dnsmasq.8 b/dnsmasq.8 index 0ed6328..81e80ff 100644 --- a/dnsmasq.8 +++ b/dnsmasq.8 @@ -114,6 +114,14 @@ running another nameserver on the same machine. Bogus private reverse lookups. All reverse lookups for private IP ranges (ie 192.168.x.x, etc) which are not found in /etc/hosts or the DHCP leases file are resolved to the IP address in dotted-quad form. .TP +.B \-V, --alias=,[,] +Modify IPv4 addresses returned from upstream nameservers; old-ip is +replaced by new-ip. If the optional mask is given then any address +which matches the masked old-ip will be re-written. So, for instance +.B --alias=1.2.3.0,6.7.8.0,255.255.255.0 +will map 1.2.3.56 to 6.7.8.56 and 1.2.3.67 to 6.7.8.67. This is what +Cisco PIX routers call "DNS doctoring". +.TP .B \-B, --bogus-nxdomain= Transform replies which contain the IP address given into "No such domain" replies. This is intended to counteract a devious move made by diff --git a/dnsmasq.conf.example b/dnsmasq.conf.example index 4932858..73e96e0 100644 --- a/dnsmasq.conf.example +++ b/dnsmasq.conf.example @@ -243,6 +243,15 @@ filterwin2k # registries which have implemented wildcard A records. #bogus-nxdomain=64.94.110.11 +# If you want to fix up DNS results from upstream servers, use the +# alias option. This only works for IPv4. +# This alias makes a result of 1.2.3.4 appear as 5.6.7.8 +#alias=1.2.3.4,5.6.7.8 +# and this maps 1.2.3.x to 5.6.7.x +#alias=1.2.3.0,5.6.7.0,255.255.255.0 + + + # For debugging purposes, log each DNS query as it passes through # dnsmasq. #log-queries diff --git a/src/cache.c b/src/cache.c index 84d639c..8dae411 100644 --- a/src/cache.c +++ b/src/cache.c @@ -641,7 +641,7 @@ void cache_add_dhcp_entry(char *host_name, struct in_addr *host_address, time_t if (crec->flags & F_HOSTS) { if (crec->addr.addr.addr4.s_addr != host_address->s_addr) - syslog(LOG_WARNING, "Not naming DHCP lease for %s because it clashes with an /etc/hosts entry.", host_name); + syslog(LOG_WARNING, "not naming DHCP lease for %s because it clashes with an /etc/hosts entry.", host_name); } else if (!(crec->flags & F_DHCP)) { @@ -653,7 +653,7 @@ void cache_add_dhcp_entry(char *host_name, struct in_addr *host_address, time_t goto newrec; } else - syslog(LOG_WARNING, "Not naming DHCP lease for %s because it clashes with a cached name.", cache_get_name(crec)); + syslog(LOG_WARNING, "not naming DHCP lease for %s because it clashes with a cached name.", cache_get_name(crec)); } return; } @@ -686,7 +686,7 @@ void cache_add_dhcp_entry(char *host_name, struct in_addr *host_address, time_t void dump_cache(int debug, int cache_size) { - syslog(LOG_INFO, "Cache size %d, %d/%d cache insertions re-used unexpired cache entries.", + syslog(LOG_INFO, "cache size %d, %d/%d cache insertions re-used unexpired cache entries.", cache_size, cache_live_freed, cache_inserted); if (debug) diff --git a/src/config.h b/src/config.h index 2c0ca63..610deb7 100644 --- a/src/config.h +++ b/src/config.h @@ -12,7 +12,7 @@ /* Author's email: simon@thekelleys.org.uk */ -#define VERSION "2.3" +#define VERSION "2.4" #define FTABSIZ 150 /* max number of outstanding requests */ #define TIMEOUT 20 /* drop queries after TIMEOUT seconds */ diff --git a/src/dhcp.c b/src/dhcp.c index 94f6bd9..c7e0e60 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -21,7 +21,7 @@ void dhcp_init(int *fdp, int* rfdp) int opt = 1; if (fd == -1) - die ("Cannot create DHCP socket : %s", NULL); + die ("cannot create DHCP socket : %s", NULL); if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || #if defined(IP_PKTINFO) @@ -49,11 +49,11 @@ void dhcp_init(int *fdp, int* rfdp) if ((fd = open(filename, O_RDWR, 0)) != -1) break; if (errno != EBUSY) - die("Cannot create DHCP BPF socket: %s", NULL); + die("cannot create DHCP BPF socket: %s", NULL); } #else if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETHERTYPE_IP))) == -1) - die("Cannot create DHCP packet socket: %s", NULL); + die("cannot create DHCP packet socket: %s", NULL); #endif *rfdp = fd; @@ -426,7 +426,7 @@ struct dhcp_config *find_config(struct dhcp_config *configs, if (hostname) for (config = configs; config; config = config->next) - if (config->hostname && strcmp(config->hostname, hostname) == 0 && + if (config->hostname && hostname_isequal(config->hostname, hostname) && is_addr_in_context(context, config)) return config; @@ -439,24 +439,25 @@ struct dhcp_config *dhcp_read_ethers(struct dhcp_config *configs, char *buff) unsigned int e0, e1, e2, e3, e4, e5; char *ip, *cp, *name; struct in_addr addr; - struct dhcp_config *new; + struct dhcp_config *config; if (!f) - die("Failed to open " ETHERSFILE ":%s", NULL); + die("failed to open " ETHERSFILE ":%s", NULL); while (fgets(buff, MAXDNAME, f)) { while (strlen(buff) > 0 && (buff[strlen(buff)-1] == '\n' || buff[strlen(buff)-1] == ' ' || + buff[strlen(buff)-1] == '\r' || buff[strlen(buff)-1] == '\t')) buff[strlen(buff)-1] = 0; if ((*buff == '#') || (*buff == '+')) continue; - for (ip = buff; *ip && *ip != ' '; ip++); - for(; *ip && *ip == ' '; ip++) + for (ip = buff; *ip && *ip != ' ' && *ip != '\t'; ip++); + for(; *ip && (*ip == ' ' || *ip == '\t'); ip++) *ip = 0; if (!*ip) continue; @@ -473,29 +474,42 @@ struct dhcp_config *dhcp_read_ethers(struct dhcp_config *configs, char *buff) { name = NULL; if ((addr.s_addr = inet_addr(ip)) == (in_addr_t)-1) - continue; + continue; + + for (config = configs; config; config = config->next) + if (config->addr.s_addr == addr.s_addr) + break; } else { - name = safe_string_alloc(ip); + if (!canonicalise(ip)) + continue; + name = ip; addr.s_addr = 0; + + for (config = configs; config; config = config->next) + if (config->hostname && hostname_isequal(config->hostname, name)) + break; } - new = safe_malloc(sizeof(struct dhcp_config)); - new->clid_len = 0; - new->clid = NULL; - new->hwaddr[0] = e0; - new->hwaddr[1] = e1; - new->hwaddr[2] = e2; - new->hwaddr[3] = e3; - new->hwaddr[4] = e4; - new->hwaddr[5] = e5; - new->hostname = name; - new->addr = addr; - new->lease_time = 0; - new->next = configs; - - configs = new; + if (!config) + { + config = safe_malloc(sizeof(struct dhcp_config)); + config->clid_len = 0; + config->clid = NULL; + config->lease_time = 0; + config->hostname = safe_string_alloc(name); + config->addr = addr; + config->next = configs; + configs = config; + } + + config->hwaddr[0] = e0; + config->hwaddr[1] = e1; + config->hwaddr[2] = e2; + config->hwaddr[3] = e3; + config->hwaddr[4] = e4; + config->hwaddr[5] = e5; } fclose(f); diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 9253a63..c626ed9 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -44,6 +44,7 @@ int main (int argc, char **argv) time_t now, last = 0; struct irec *interfaces = NULL; struct listener *listener, *listeners; + struct doctor *doctors = NULL; char *mxname = NULL; char *mxtarget = NULL; char *lease_file = NULL; @@ -109,7 +110,8 @@ int main (int argc, char **argv) &if_names, &if_addrs, &if_except, &bogus_addr, &serv_addrs, &cachesize, &port, &query_port, &local_ttl, &addn_hosts, &dhcp, &dhcp_configs, &dhcp_options, - &dhcp_file, &dhcp_sname, &dhcp_next_server, &maxleases, &min_leasetime); + &dhcp_file, &dhcp_sname, &dhcp_next_server, &maxleases, &min_leasetime, + &doctors); /* if we cannot support binding the wildcard address, set the "bind only interfaces in use" option */ @@ -152,7 +154,6 @@ int main (int argc, char **argv) leasefd = lease_init(lease_file, domain_suffix, dnamebuff, packet, now, maxleases); if (options & OPT_ETHERS) dhcp_configs = dhcp_read_ethers(dhcp_configs, dnamebuff); - dhcp_update_configs(dhcp_configs); lease_update_from_configs(dhcp_configs, domain_suffix); /* must follow cache_init and lease_init */ lease_update_file(0, now); lease_update_dns(); @@ -390,7 +391,7 @@ int main (int argc, char **argv) for (serverfdp = sfds; serverfdp; serverfdp = serverfdp->next) if (FD_ISSET(serverfdp->fd, &rset)) last_server = reply_query(serverfdp->fd, options, packet, now, - dnamebuff, last_server, bogus_addr); + dnamebuff, last_server, bogus_addr, doctors); if (dhcp && FD_ISSET(dhcpfd, &rset)) dhcp_packet(dhcp, packet, dhcp_options, dhcp_configs, diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 07b0588..4b05749 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -105,6 +105,12 @@ struct bogus_addr { struct bogus_addr *next; }; +/* dns doctor param */ +struct doctor { + struct in_addr in, out, mask; + struct doctor *next; +}; + union bigname { char name[MAXDNAME]; union bigname *next; /* freelist */ @@ -304,7 +310,8 @@ unsigned short extract_request(HEADER *header, unsigned int qlen, char *name); int setup_reply(HEADER *header, unsigned int qlen, struct all_addr *addrp, unsigned short flags, unsigned long local_ttl); -void extract_addresses(HEADER *header, unsigned int qlen, char *namebuff, time_t now); +void extract_addresses(HEADER *header, unsigned int qlen, char *namebuff, + time_t now, struct doctor *doctors); void extract_neg_addrs(HEADER *header, unsigned int qlen, char *namebuff, time_t now); int answer_request(HEADER *header, char *limit, unsigned int qlen, char *mxname, char *mxtarget, unsigned int options, time_t now, unsigned long local_ttl, @@ -334,13 +341,13 @@ unsigned int read_opts(int argc, char **argv, char *buff, struct resolvc **resol int *port, int *query_port, unsigned long *local_ttl, char **addn_hosts, struct dhcp_context **dhcp, struct dhcp_config **dhcp_conf, struct dhcp_opt **opts, char **dhcp_file, char **dhcp_sname, struct in_addr *dhcp_next_server, - int *maxleases, unsigned int *min_leasetime); + int *maxleases, unsigned int *min_leasetime, struct doctor **doctors); /* forward.c */ void forward_init(int first); struct server *reply_query(int fd, int options, char *packet, time_t now, char *dnamebuff, struct server *last_server, - struct bogus_addr *bogus_nxdomain); + struct bogus_addr *bogus_nxdomain, struct doctor *doctors); struct server *receive_query(struct listener *listen, char *packet, char *mxname, char *mxtarget, unsigned int options, time_t now, diff --git a/src/forward.c b/src/forward.c index a1df544..71426b6 100644 --- a/src/forward.c +++ b/src/forward.c @@ -314,7 +314,8 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, /* returns new last_server */ struct server *reply_query(int fd, int options, char *packet, time_t now, - char *dnamebuff, struct server *last_server, struct bogus_addr *bogus_nxdomain) + char *dnamebuff, struct server *last_server, + struct bogus_addr *bogus_nxdomain, struct doctor *doctors) { /* packet from peer server, extract data for cache, and send to original requester */ @@ -338,7 +339,7 @@ struct server *reply_query(int fd, int options, char *packet, time_t now, check_for_bogus_wildcard(header, (unsigned int)n, dnamebuff, bogus_nxdomain, now))) { if (header->rcode == NOERROR && ntohs(header->ancount) != 0) - extract_addresses(header, (unsigned int)n, dnamebuff, now); + extract_addresses(header, (unsigned int)n, dnamebuff, now, doctors); else if (!(options & OPT_NO_NEG)) extract_neg_addrs(header, (unsigned int)n, dnamebuff, now); } diff --git a/src/lease.c b/src/lease.c index 465a378..450851f 100644 --- a/src/lease.c +++ b/src/lease.c @@ -34,9 +34,13 @@ int lease_init(char *filename, char *domain, char *buff, leases = NULL; leases_left = maxleases; - if (!(lease_file = fopen(filename, "r+"))) + /* NOTE: need a+ mode to create file if it doesn't exist */ + if (!(lease_file = fopen(filename, "a+"))) die("cannot open or create leases file: %s", NULL); + /* a+ mode lease pointer at end. */ + rewind(lease_file); + while (fscanf(lease_file, "%lu %x:%x:%x:%x:%x:%x %d.%d.%d.%d %256s %500s", &ei, &e0, &e1, &e2, &e3, &e4, &e5, &a0, &a1, &a2, &a3, buff, buff2) == 13) { diff --git a/src/network.c b/src/network.c index 26cad13..2f4168f 100644 --- a/src/network.c +++ b/src/network.c @@ -33,12 +33,10 @@ static struct irec *add_iface(struct irec *list, char *name, union mysockaddr *a for (tmp = names; tmp; tmp = tmp->next) if (tmp->name && (strcmp(tmp->name, name) == 0)) break; - if (!tmp && !addrs) - return NULL; - - for (tmp = addrs; tmp; tmp = tmp->next) - if (sockaddr_isequal(&tmp->addr, addr)) - break; + if (!tmp) + for (tmp = addrs; tmp; tmp = tmp->next) + if (sockaddr_isequal(&tmp->addr, addr)) + break; if (!tmp) return NULL; } diff --git a/src/option.c b/src/option.c index f37e9af..c1c8ea5 100644 --- a/src/option.c +++ b/src/option.c @@ -21,7 +21,7 @@ struct myoption { int val; }; -#define OPTSTRING "ZDNLERzowefnbvhdqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:" +#define OPTSTRING "ZDNLERzowefnbvhdqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:" static struct myoption opts[] = { {"version", 0, 0, 'v'}, @@ -67,7 +67,9 @@ static struct myoption opts[] = { {"except-interface", 1, 0, 'I'}, {"domain-needed", 0, 0, 'D'}, {"dhcp-lease-max", 1, 0, 'X' }, + {"bind-interfaces", 0, 0, 'z'}, {"read-ethers", 0, 0, 'Z' }, + {"alias", 1, 0, 'V' }, {0, 0, 0, 0} }; @@ -138,6 +140,7 @@ static char *usage = "-T, --local-ttl=time Specify time-to-live in seconds for replies from /etc/hosts.\n" "-u, --user=username Change to this user after startup. (defaults to " CHUSER ").\n" "-v, --version Display dnsmasq version.\n" +"-V, --alias=addr,addr,mask Translate IPv4 addresses from upstream servers.\n" "-w, --help Display this message.\n" "-x, --pid-file=path Specify path of PID file. (defaults to " RUNFILE ").\n" "-X, --dhcp-lease-max=number Specify maximum number of DHCP leases (defaults to %d).\n" @@ -154,7 +157,7 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso int *query_port, unsigned long *local_ttl, char **addn_hosts, struct dhcp_context **dhcp, struct dhcp_config **dhcp_conf, struct dhcp_opt **dhcp_opts, char **dhcp_file, char **dhcp_sname, struct in_addr *dhcp_next_server, int *dhcp_max, - unsigned int *min_leasetime) + unsigned int *min_leasetime, struct doctor **doctors) { int option = 0, i; unsigned int flags = 0; @@ -194,7 +197,8 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso /* fgets gets end of line char too. */ while (strlen(buff) > 0 && (buff[strlen(buff)-1] == '\n' || - buff[strlen(buff)-1] == ' ' || + buff[strlen(buff)-1] == ' ' || + buff[strlen(buff)-1] == '\r' || buff[strlen(buff)-1] == '\t')) buff[strlen(buff)-1] = 0; if (*buff == 0) @@ -920,6 +924,44 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso } break; } + + case 'V': + { + char *a[3] = { NULL, NULL, NULL }; + int k; + struct in_addr in, out, mask; + struct doctor *new; + + mask.s_addr = 0xffffffff; + + a[0] = optarg; + for (k = 1; k < 4; k++) + { + if (!(a[k] = strchr(a[k-1], ','))) + break; + *(a[k]++) = 0; + } + + if ((k < 2) || + ((in.s_addr = inet_addr(a[0])) == (in_addr_t)-1) || + ((out.s_addr = inet_addr(a[1])) == (in_addr_t)-1)) + { + option = '?'; + break; + } + + if (k == 3) + mask.s_addr = inet_addr(a[2]); + + new = safe_malloc(sizeof(struct doctor)); + new->in = in; + new->out = out; + new->mask = mask; + new->next = *doctors; + *doctors = new; + + break; + } } } diff --git a/src/rfc1035.c b/src/rfc1035.c index 63f56d1..4b9d086 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -440,7 +440,19 @@ void extract_neg_addrs(HEADER *header, unsigned int qlen, char *name, time_t now cache_end_insert(); } -void extract_addresses(HEADER *header, unsigned int qlen, char *name, time_t now) +static void dns_doctor(struct doctor *doctor, struct in_addr *addr) +{ + for (; doctor; doctor = doctor->next) + if ((doctor->in.s_addr & doctor->mask.s_addr) == (addr->s_addr & doctor->mask.s_addr)) + { + addr->s_addr &= ~doctor->mask.s_addr; + addr->s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); + break; + } +} + +void extract_addresses(HEADER *header, unsigned int qlen, char *name, + time_t now, struct doctor *doctors) { unsigned char *p, *psave, *endrr; int qtype, qclass, rdlen; @@ -477,8 +489,11 @@ void extract_addresses(HEADER *header, unsigned int qlen, char *name, time_t now } if (qtype == T_A) /* A record. */ - cache_insert(name, (struct all_addr *)p, now, - ttl, F_IPV4 | F_FORWARD); + { + dns_doctor(doctors, (struct in_addr *)p); + cache_insert(name, (struct all_addr *)p, now, + ttl, F_IPV4 | F_FORWARD); + } #ifdef HAVE_IPV6 else if (qtype == T_AAAA) /* IPV6 address record. */ cache_insert(name, (struct all_addr *)p, now, @@ -546,8 +561,11 @@ void extract_addresses(HEADER *header, unsigned int qlen, char *name, time_t now return; if (qtype == T_A) /* A record. */ - cache_insert(name, (struct all_addr *)p, now, - cttl, F_IPV4 | F_FORWARD); + { + dns_doctor(doctors, (struct in_addr *)p); + cache_insert(name, (struct all_addr *)p, now, + cttl, F_IPV4 | F_FORWARD); + } #ifdef HAVE_IPV6 else if (qtype == T_AAAA) /* IPV6 address record. */ cache_insert(name, (struct all_addr *)p, now, diff --git a/src/rfc2131.c b/src/rfc2131.c index e6f922b..6e2d9c6 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -246,11 +246,12 @@ int dhcp_reply(struct dhcp_context *context, log_packet("DISCOVER", opt ? &mess->yiaddr : NULL, mess->chaddr, iface_name, NULL); - if (lease && - ((lease->addr.s_addr & context->netmask.s_addr) == (context->start.s_addr & context->netmask.s_addr))) - mess->yiaddr = lease->addr; - else if (config && config->addr.s_addr && !lease_find_by_addr(config->addr)) + if (config && config->addr.s_addr && !lease_find_by_addr(config->addr)) mess->yiaddr = config->addr; + else if (lease && + ((lease->addr.s_addr & context->netmask.s_addr) == + (context->start.s_addr & context->netmask.s_addr))) + mess->yiaddr = lease->addr; else if ((!opt || !address_available(context, mess->yiaddr)) && !address_allocate(context, dhcp_configs, &mess->yiaddr)) {