Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a911ccc75 | ||
|
|
1cff166d37 |
38
CHANGELOG
38
CHANGELOG
@@ -821,5 +821,43 @@ 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.
|
||||
|
||||
release 2.5
|
||||
Made "where are we allocating addresses?" code in DHCP
|
||||
server cope with requests via a relay which is on a
|
||||
directly connected network for which there is not a
|
||||
configured netmask. This strange state of affairs occurs
|
||||
with win4lin. Thanks to Alex Melt and Jim Horner for bug
|
||||
reports and testing with this.
|
||||
|
||||
Fixed trivial-but-irritating missing #include which broke
|
||||
compilation on *BSD.
|
||||
|
||||
Force --bind-interfaces if IP-aliased interface
|
||||
specifications are used, since the sockets API provides
|
||||
no other sane way to determine which alias of an
|
||||
interface a packet was sent to. Thanks to Javier Kohen
|
||||
for the bug report.
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
###############################################################################
|
||||
|
||||
Name: dnsmasq
|
||||
Version: 2.3
|
||||
Version: 2.5
|
||||
Release: 1
|
||||
Copyright: GPL
|
||||
Group: System Environment/Daemons
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
###############################################################################
|
||||
|
||||
Name: dnsmasq
|
||||
Version: 2.3
|
||||
Version: 2.5
|
||||
Release: 1
|
||||
Copyright: GPL
|
||||
Group: System Environment/Daemons
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
###############################################################################
|
||||
|
||||
Name: dnsmasq
|
||||
Version: 2.3
|
||||
Version: 2.5
|
||||
Release: 1
|
||||
Copyright: GPL
|
||||
Group: Productivity/Networking/DNS/Servers
|
||||
|
||||
21
dnsmasq.8
21
dnsmasq.8
@@ -82,7 +82,14 @@ flags are given, dnsmasq listens on all available interfaces unless overridden b
|
||||
.B \-a
|
||||
or
|
||||
.B \-I
|
||||
flags.
|
||||
flags. If IP alias interfaces (eg "eth1:0") are used with
|
||||
.B --interface
|
||||
or
|
||||
.B --except-interface
|
||||
options, then the
|
||||
.B --bind-interfaces
|
||||
option will be automatically set. This is required for deeply boring
|
||||
sockets-API reasons.
|
||||
.TP
|
||||
.B \-I, --except-interface=<interface name>
|
||||
Do not listen on the specified interface.
|
||||
@@ -108,12 +115,22 @@ requests that it shouldn't reply to. This has the advantage of
|
||||
working even when interfaces come and go and change address. This
|
||||
option forces dnsmasq to really bind only the interfaces it is
|
||||
listening on. About the only time when this is useful is when
|
||||
running another nameserver on the same machine.
|
||||
running another nameserver on the same machine or using IP
|
||||
alias. Specifying interfaces with IP alias automatically turns this
|
||||
option on.
|
||||
.TP
|
||||
.B \-b, --bogus-priv
|
||||
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=<old-ip>,<new-ip>[,<mask>]
|
||||
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=<ipaddr>
|
||||
Transform replies which contain the IP address given into "No such
|
||||
domain" replies. This is intended to counteract a devious move made by
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
/* Author's email: simon@thekelleys.org.uk */
|
||||
|
||||
#define VERSION "2.3"
|
||||
#define VERSION "2.5"
|
||||
|
||||
#define FTABSIZ 150 /* max number of outstanding requests */
|
||||
#define TIMEOUT 20 /* drop queries after TIMEOUT seconds */
|
||||
|
||||
121
src/dhcp.c
121
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;
|
||||
@@ -75,7 +75,8 @@ void dhcp_packet(struct dhcp_context *contexts, char *packet,
|
||||
struct iovec iov[2];
|
||||
struct cmsghdr *cmptr;
|
||||
int sz, newlen, iface_index = 0;
|
||||
struct in_addr source, real_netmask, iface_addr, netmask_save, broadcast_save;
|
||||
struct in_addr source, iface_netmask, iface_addr, iface_broadcast;
|
||||
struct in_addr netmask_save, broadcast_save, router;
|
||||
#ifdef HAVE_BPF
|
||||
unsigned char iface_hwaddr[ETHER_ADDR_LEN];
|
||||
#endif
|
||||
@@ -112,7 +113,8 @@ void dhcp_packet(struct dhcp_context *contexts, char *packet,
|
||||
if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO)
|
||||
iface_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex;
|
||||
|
||||
if (!iface_index || !if_indextoname(iface_index, ifr.ifr_name))
|
||||
if (!(ifr.ifr_ifindex = iface_index) ||
|
||||
ioctl(dhcp_fd, SIOCGIFNAME, &ifr) == -1)
|
||||
return;
|
||||
|
||||
#elif defined(IP_RECVIF)
|
||||
@@ -169,17 +171,30 @@ void dhcp_packet(struct dhcp_context *contexts, char *packet,
|
||||
/* If the packet came via a relay, use that address to look up the context,
|
||||
else use the address of the interface is arrived on. */
|
||||
source = mess->giaddr.s_addr ? mess->giaddr : iface_addr;
|
||||
|
||||
iface_netmask.s_addr = 0;
|
||||
iface_broadcast.s_addr = 0;
|
||||
|
||||
if (ioctl(dhcp_fd, SIOCGIFNETMASK, &ifr) != -1)
|
||||
{
|
||||
iface_netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
|
||||
/* we can use the interface netmask if either the packet came direct,
|
||||
or it came via a relay listening on the same network. This sounds unlikely,
|
||||
but it happens with win4lin. */
|
||||
if ((source.s_addr & iface_netmask.s_addr) != (iface_addr.s_addr & iface_netmask.s_addr))
|
||||
iface_netmask.s_addr = 0;
|
||||
else if (ioctl(dhcp_fd, SIOCGIFBRDADDR, &ifr) != -1)
|
||||
iface_broadcast = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
|
||||
|
||||
}
|
||||
|
||||
for (context = contexts; context; context = context->next)
|
||||
{
|
||||
if (!context->netmask.s_addr && !mess->giaddr.s_addr && ioctl(dhcp_fd, SIOCGIFNETMASK, &ifr) != -1)
|
||||
real_netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
|
||||
else
|
||||
real_netmask = context->netmask;
|
||||
|
||||
if (real_netmask.s_addr &&
|
||||
(source.s_addr & real_netmask.s_addr) == (context->start.s_addr & real_netmask.s_addr) &&
|
||||
(source.s_addr & real_netmask.s_addr) == (context->end.s_addr & real_netmask.s_addr))
|
||||
struct in_addr netmask = context->netmask.s_addr ? context->netmask : iface_netmask;
|
||||
|
||||
if (netmask.s_addr &&
|
||||
(source.s_addr & netmask.s_addr) == (context->start.s_addr & netmask.s_addr) &&
|
||||
(source.s_addr & netmask.s_addr) == (context->end.s_addr & netmask.s_addr))
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -192,26 +207,34 @@ void dhcp_packet(struct dhcp_context *contexts, char *packet,
|
||||
netmask_save = context->netmask;
|
||||
broadcast_save = context->broadcast;
|
||||
|
||||
context->netmask = real_netmask;
|
||||
if (!context->netmask.s_addr)
|
||||
context->netmask = iface_netmask;
|
||||
|
||||
if (!context->broadcast.s_addr)
|
||||
{
|
||||
if (mess->giaddr.s_addr)
|
||||
context->broadcast.s_addr = (mess->giaddr.s_addr & real_netmask.s_addr) | ~real_netmask.s_addr;
|
||||
else if (ioctl(dhcp_fd, SIOCGIFBRDADDR, &ifr) != -1)
|
||||
context->broadcast = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
|
||||
if (iface_broadcast.s_addr)
|
||||
context->broadcast = iface_broadcast;
|
||||
else
|
||||
context->broadcast.s_addr = (iface_addr.s_addr & real_netmask.s_addr) | ~real_netmask.s_addr;
|
||||
context->broadcast.s_addr = (source.s_addr & context->netmask.s_addr) | ~context->netmask.s_addr;
|
||||
}
|
||||
|
||||
if (ioctl(dhcp_fd, SIOCGIFMTU, &ifr) == -1)
|
||||
ifr.ifr_mtu = ETHERMTU;
|
||||
|
||||
/* Normally, we set the default route to point to the machine which is getting the
|
||||
DHCP broadcast, either this machine or a relay. In the special case that the relay
|
||||
is on the same network as us, we set the default route to us, not the relay.
|
||||
This is the win4lin scenario again. */
|
||||
if ((source.s_addr & context->netmask.s_addr) == (iface_addr.s_addr & context->netmask.s_addr))
|
||||
router = iface_addr;
|
||||
else
|
||||
router = source;
|
||||
|
||||
lease_prune(NULL, now); /* lose any expired leases */
|
||||
newlen = dhcp_reply(context, iface_addr, ifr.ifr_name, ifr.ifr_mtu,
|
||||
rawpacket, sz, now, namebuff,
|
||||
dhcp_opts, dhcp_configs, domain_suffix, dhcp_file,
|
||||
dhcp_sname, dhcp_next_server);
|
||||
dhcp_sname, dhcp_next_server, router);
|
||||
lease_update_file(0, now);
|
||||
lease_update_dns();
|
||||
|
||||
@@ -426,7 +449,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 +462,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 +497,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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
# include <sys/sockio.h>
|
||||
#endif
|
||||
#include <sys/time.h>
|
||||
#include <limits.h>
|
||||
#include <net/if.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
@@ -105,6 +106,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 +311,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 +342,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,
|
||||
@@ -396,5 +404,5 @@ int dhcp_reply(struct dhcp_context *context,
|
||||
unsigned int sz, time_t now, char *namebuff,
|
||||
struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
|
||||
char *domain_suffix, char *dhcp_file, char *dhcp_sname,
|
||||
struct in_addr dhcp_next_server);
|
||||
struct in_addr dhcp_next_server, struct in_addr router);
|
||||
|
||||
|
||||
@@ -104,7 +104,6 @@ static void send_from(int fd, int nowild, char *packet, int len,
|
||||
msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
|
||||
cmptr->cmsg_type = IPV6_PKTINFO;
|
||||
cmptr->cmsg_level = IPV6_LEVEL;
|
||||
cmptr->cmsg_level = IPPROTO_IPV6;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -314,7 +313,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 +338,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);
|
||||
}
|
||||
@@ -367,11 +367,10 @@ struct server *receive_query(struct listener *listen, char *packet, char *mxname
|
||||
union mysockaddr source_addr;
|
||||
struct iname *tmp;
|
||||
struct all_addr dst_addr;
|
||||
int m, n, gotit = 0;
|
||||
int m, n, if_index = 0;
|
||||
struct iovec iov[1];
|
||||
struct msghdr msg;
|
||||
struct cmsghdr *cmptr;
|
||||
char if_name[IF_NAMESIZE];
|
||||
union {
|
||||
struct cmsghdr align; /* this ensures alignment */
|
||||
#ifdef HAVE_IPV6
|
||||
@@ -413,20 +412,16 @@ struct server *receive_query(struct listener *listen, char *packet, char *mxname
|
||||
if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO)
|
||||
{
|
||||
dst_addr.addr.addr4 = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_spec_dst;
|
||||
if_indextoname(((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex, if_name);
|
||||
gotit = 1;
|
||||
if_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex;
|
||||
}
|
||||
#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
|
||||
if (!(options & OPT_NOWILD) && listen->family == AF_INET)
|
||||
{
|
||||
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
|
||||
if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
|
||||
{
|
||||
dst_addr.addr.addr4 = *((struct in_addr *)CMSG_DATA(cmptr));
|
||||
gotit = 1;
|
||||
}
|
||||
dst_addr.addr.addr4 = *((struct in_addr *)CMSG_DATA(cmptr));
|
||||
else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
|
||||
if_indextoname(((struct sockaddr_dl *)CMSG_DATA(cmptr))->sdl_index, if_name);
|
||||
if_index = ((struct sockaddr_dl *)CMSG_DATA(cmptr))->sdl_index;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -437,8 +432,7 @@ struct server *receive_query(struct listener *listen, char *packet, char *mxname
|
||||
if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == IPV6_PKTINFO)
|
||||
{
|
||||
dst_addr.addr.addr6 = ((struct in6_pktinfo *)CMSG_DATA(cmptr))->ipi6_addr;
|
||||
if_indextoname(((struct in6_pktinfo *)CMSG_DATA(cmptr))->ipi6_ifindex, if_name);
|
||||
gotit = 1;
|
||||
if_index =((struct in6_pktinfo *)CMSG_DATA(cmptr))->ipi6_ifindex;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -449,17 +443,31 @@ struct server *receive_query(struct listener *listen, char *packet, char *mxname
|
||||
/* enforce available interface configuration */
|
||||
if (!(options & OPT_NOWILD))
|
||||
{
|
||||
if (!gotit)
|
||||
struct ifreq ifr;
|
||||
|
||||
if (if_index == 0)
|
||||
return last_server;
|
||||
|
||||
if (except || names)
|
||||
{
|
||||
#ifdef SIOCGIFNAME
|
||||
ifr.ifr_ifindex = if_index;
|
||||
if (ioctl(listen->fd, SIOCGIFNAME, &ifr) == -1)
|
||||
return last_server;
|
||||
#else
|
||||
if (!if_indextoname(if_index, ifr.ifr_name))
|
||||
return last_server;
|
||||
#endif
|
||||
}
|
||||
|
||||
for (tmp = except; tmp; tmp = tmp->next)
|
||||
if (tmp->name && (strcmp(tmp->name, if_name) == 0))
|
||||
if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0))
|
||||
return last_server;
|
||||
|
||||
if (names || addrs)
|
||||
{
|
||||
for (tmp = names; tmp; tmp = tmp->next)
|
||||
if (tmp->name && (strcmp(tmp->name, if_name) == 0))
|
||||
if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0))
|
||||
break;
|
||||
if (!tmp)
|
||||
for (tmp = addrs; tmp; tmp = tmp->next)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -147,7 +145,7 @@ struct irec *enumerate_interfaces(struct iname *names,
|
||||
lo->next = names->next;
|
||||
names->next = lo;
|
||||
}
|
||||
|
||||
|
||||
if ((new = add_iface(iface, ifr->ifr_name,
|
||||
&addr, names, addrs, except)))
|
||||
{
|
||||
|
||||
72
src/option.c
72
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;
|
||||
@@ -162,7 +165,6 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso
|
||||
char *conffile = CONFFILE;
|
||||
int conffile_set = 0;
|
||||
int lineno = 0;
|
||||
|
||||
opterr = 0;
|
||||
|
||||
*min_leasetime = UINT_MAX;
|
||||
@@ -194,7 +196,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)
|
||||
@@ -355,6 +358,8 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso
|
||||
/* new->name may be NULL if someone does
|
||||
"interface=" to disable all interfaces except loop. */
|
||||
new->name = safe_string_alloc(optarg);
|
||||
if (strchr(optarg, ':'))
|
||||
flags |= OPT_NOWILD;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -364,6 +369,8 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso
|
||||
new->next = *if_except;
|
||||
*if_except = new;
|
||||
new->name = safe_string_alloc(optarg);
|
||||
if (strchr(optarg, ':'))
|
||||
flags |= OPT_NOWILD;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -680,14 +687,13 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso
|
||||
}
|
||||
|
||||
new->lease_time = atoi(a[leasepos]) * fac;
|
||||
if (new->lease_time < *min_leasetime)
|
||||
*min_leasetime = new->lease_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new->last = new->start;
|
||||
|
||||
if (new->lease_time < *min_leasetime)
|
||||
*min_leasetime = new->lease_time;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -799,17 +805,17 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso
|
||||
new->hostname = safe_string_alloc(a[j]);
|
||||
}
|
||||
else
|
||||
{
|
||||
new->lease_time = atoi(a[j]) * fac;
|
||||
if (new->lease_time < *min_leasetime)
|
||||
*min_leasetime = new->lease_time;
|
||||
}
|
||||
new->lease_time = atoi(a[j]) * fac;
|
||||
}
|
||||
|
||||
if (option == '?')
|
||||
free(new);
|
||||
else
|
||||
*dhcp_conf = new;
|
||||
{
|
||||
if (new->lease_time < *min_leasetime)
|
||||
*min_leasetime = new->lease_time;
|
||||
*dhcp_conf = new;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -920,6 +926,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -981,7 +1025,7 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso
|
||||
*resolv_files = 0;
|
||||
else if (*resolv_files && (*resolv_files)->next && (flags & OPT_NO_POLL))
|
||||
die("only one resolv.conf file allowed in no-poll mode.", NULL);
|
||||
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -61,7 +61,7 @@ static unsigned char *do_req_options(struct dhcp_context *context,
|
||||
unsigned char *req_options,
|
||||
struct dhcp_opt *config_opts,
|
||||
char *domainname, char *hostname,
|
||||
struct in_addr relay,
|
||||
struct in_addr router,
|
||||
struct in_addr iface_addr,
|
||||
int iface_mtu);
|
||||
|
||||
@@ -74,7 +74,7 @@ int dhcp_reply(struct dhcp_context *context,
|
||||
unsigned int sz, time_t now, char *namebuff,
|
||||
struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
|
||||
char *domain_suffix, char *dhcp_file, char *dhcp_sname,
|
||||
struct in_addr dhcp_next_server)
|
||||
struct in_addr dhcp_next_server, struct in_addr router)
|
||||
{
|
||||
unsigned char *opt, *clid;
|
||||
struct dhcp_lease *lease;
|
||||
@@ -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))
|
||||
{
|
||||
@@ -264,7 +265,7 @@ int dhcp_reply(struct dhcp_context *context,
|
||||
p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
|
||||
p = option_put(p, end, OPTION_LEASE_TIME, 4, expires_time);
|
||||
p = do_req_options(context, p, end, req_options, dhcp_opts, domain_suffix,
|
||||
NULL, mess->giaddr, iface_addr, iface_mtu);
|
||||
NULL, router, iface_addr, iface_mtu);
|
||||
p = option_put(p, end, OPTION_END, 0, 0);
|
||||
|
||||
log_packet("OFFER" , &mess->yiaddr, mess->chaddr, iface_name, NULL);
|
||||
@@ -353,7 +354,7 @@ int dhcp_reply(struct dhcp_context *context,
|
||||
p = option_put(p, end, OPTION_T2, 4, ((renewal_time * 7)/8) - fuzz);
|
||||
}
|
||||
p = do_req_options(context, p, end, req_options, dhcp_opts, domain_suffix,
|
||||
hostname, mess->giaddr, iface_addr, iface_mtu);
|
||||
hostname, router, iface_addr, iface_mtu);
|
||||
p = option_put(p, end, OPTION_END, 0, 0);
|
||||
return p - (unsigned char *)mess;
|
||||
|
||||
@@ -363,7 +364,7 @@ int dhcp_reply(struct dhcp_context *context,
|
||||
p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
|
||||
p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
|
||||
p = do_req_options(context, p, end, req_options, dhcp_opts, domain_suffix,
|
||||
hostname, mess->giaddr, iface_addr, iface_mtu);
|
||||
hostname, router, iface_addr, iface_mtu);
|
||||
p = option_put(p, end, OPTION_END, 0, 0);
|
||||
|
||||
log_packet("ACK", &mess->ciaddr, mess->chaddr, iface_name, hostname);
|
||||
@@ -537,7 +538,7 @@ static unsigned char *do_req_options(struct dhcp_context *context,
|
||||
unsigned char *req_options,
|
||||
struct dhcp_opt *config_opts,
|
||||
char *domainname, char *hostname,
|
||||
struct in_addr relay,
|
||||
struct in_addr router,
|
||||
struct in_addr iface_addr,
|
||||
int iface_mtu)
|
||||
{
|
||||
@@ -562,7 +563,7 @@ static unsigned char *do_req_options(struct dhcp_context *context,
|
||||
if (in_list(req_options, OPTION_ROUTER) &&
|
||||
!option_find2(context, config_opts, OPTION_ROUTER))
|
||||
p = option_put(p, end, OPTION_ROUTER, INADDRSZ,
|
||||
ntohl(relay.s_addr ? relay.s_addr : iface_addr.s_addr ));
|
||||
ntohl(router.s_addr));
|
||||
|
||||
if (in_list(req_options, OPTION_DNSSERVER) &&
|
||||
!option_find2(context, config_opts, OPTION_DNSSERVER))
|
||||
|
||||
Reference in New Issue
Block a user