Commit cdeda28f authored by Simon Kelley's avatar Simon Kelley

import of dnsmasq-2.27.tar.gz

parent aedef830
...@@ -1639,3 +1639,58 @@ version 2.26 ...@@ -1639,3 +1639,58 @@ version 2.26
network. Thanks to Lutz Pressler for the bug report and network. Thanks to Lutz Pressler for the bug report and
patch. patch.
version 2.27
Tweaked DHCP behaviour when a client attempts to renew a lease
which dnsmasq doesn't know about. Previously that would always
result in a DHCPNAK. Now, in dhcp-authoritative mode, the
lease will be created, if it's legal. This makes dnsmasq work
better if the lease database is lost, for example on an OpenWRT
system which reboots. Thanks to Stephen Rose for work on
this.
Added the ability to support RFC-3442 style destination
descriptors in dhcp-options. This makes classless static
routes easy to do, eg dhcp-option=121,192.168.1.0/24,1.2.3.4
Added error-checking to the code which writes the lease
file. If this fails for any reason, an error is logged,
and a retry occurs after one minute. This should improve
things eg when a filesystem is full. Thanks to Jens Holze
for the bug report.
Fixed breakage of the "/#/ matches any domain" facility
which happened in 2.24. Thanks to Peter Surda for the bug
report.
Use "size_t" and "ssize_t" types where appropriate in the
code.
Fix buggy CNAME handling in mixed IPv4 and IPv6
queries. Thanks to Andreas Pelme for help finding that.
Added some code to attempt to re-transmit DNS queries when
a network interface comes up. This helps on DoD links,
where frequently the packet which triggers dialling is
a DNS query, which then gets lost. By re-sending, we can
avoid the lookup failing. This function is only active
when netlink support is compiled in, and therefore only
under Linux. Thanks to Jean Wolter for help with this.
Tweaked the DHCP tag-matching code to work correctly with
NOT-tag conditions. Thanks to Lutz Pressler for finding
the bug.
Generalised netid-tag matching in dhcp-range statements to
allow more than one tag.
Added --dhcp-mac to do MAC address matching in the same
way as vendorclass and userclass matching. A good
suggestion from Lutz Pressler.
Add workaround for buggy early Microsoft DHCP clients
which need zero-termination in string options.
Thanks to Fabiano Pires for help with this.
Generalised the DHCP code to cope with any hardware
address type, at least on Linux. *BSD is still limited to
ethernet only.
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
############################################################################### ###############################################################################
Name: dnsmasq Name: dnsmasq
Version: 2.26 Version: 2.27
Release: 1 Release: 1
License: GPL License: GPL
Group: System Environment/Daemons Group: System Environment/Daemons
...@@ -127,7 +127,7 @@ fi ...@@ -127,7 +127,7 @@ fi
%attr(0755,root,root) /etc/rc.d/init.d/dnsmasq %attr(0755,root,root) /etc/rc.d/init.d/dnsmasq
%attr(0664,root,root) /etc/dnsmasq.conf %attr(0664,root,root) /etc/dnsmasq.conf
%attr(0755,root,root) /usr/sbin/dnsmasq %attr(0755,root,root) /usr/sbin/dnsmasq
%attr(0644,root,root) /usr/share/man/*/man8/dnsmasq* #%attr(0644,root,root) /usr/share/man/*/man8/dnsmasq*
%attr(0644,root,root) /usr/share/man/man8/dnsmasq* %attr(0644,root,root) /usr/share/man/man8/dnsmasq*
%attr(0644,root,root) /usr/share/locale/*/LC_MESSAGES/* %attr(0644,root,root) /usr/share/locale/*/LC_MESSAGES/*
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
############################################################################### ###############################################################################
Name: dnsmasq Name: dnsmasq
Version: 2.26 Version: 2.27
Release: 1 Release: 1
Copyright: GPL Copyright: GPL
Group: Productivity/Networking/DNS/Servers Group: Productivity/Networking/DNS/Servers
......
...@@ -184,6 +184,10 @@ bogus-priv ...@@ -184,6 +184,10 @@ bogus-priv
# of whose DHCP userclass strings includes the substring "accounts" # of whose DHCP userclass strings includes the substring "accounts"
#dhcp-userclass=red,accounts #dhcp-userclass=red,accounts
# Send extra options which are tagged as "red" to any machine whose
# MAC address matches the pattern.
#dhcp-mac=red,00:60:8C:*:*:*
# If this line is uncommented, dnsmasq will read /etc/ethers and act # If this line is uncommented, dnsmasq will read /etc/ethers and act
# on the ethernet-address/IP pairs found there just as if they had # on the ethernet-address/IP pairs found there just as if they had
# been given as --dhcp-host options. Useful if you keep # been given as --dhcp-host options. Useful if you keep
...@@ -204,6 +208,10 @@ bogus-priv ...@@ -204,6 +208,10 @@ bogus-priv
# DNS server - 6 # DNS server - 6
# broadcast address - 28 # broadcast address - 28
# Override the default route supplied by dnsmasq, which assumes the
# router is the same machine as the one running dnsmasq.
#dhcp-option=3,1.2.3.4
# Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5 # Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5
#dhcp-option=42,192.168.0.4,10.10.0.5 #dhcp-option=42,192.168.0.4,10.10.0.5
...@@ -244,6 +252,9 @@ bogus-priv ...@@ -244,6 +252,9 @@ bogus-priv
# probably doesn't support this...... # probably doesn't support this......
#dhcp-option=119,eng.apple.com,marketing.apple.com #dhcp-option=119,eng.apple.com,marketing.apple.com
# Send RFC-3442 classless static routes (note the netmask encoding)
#dhcp-option=121,192.168.1.0/24,1.2.3.4,10.0.0.0/8,5.6.7.8
# Send encapsulated vendor-class specific options. The vendor-class # Send encapsulated vendor-class specific options. The vendor-class
# is sent as DHCP option 60, and all the options marked with the # is sent as DHCP option 60, and all the options marked with the
# vendor class are send encapsulated in DHCP option 43. The meaning of # vendor class are send encapsulated in DHCP option 43. The meaning of
......
...@@ -349,7 +349,7 @@ allowed to have more than one dhcp-range in a single subnet. The optional ...@@ -349,7 +349,7 @@ allowed to have more than one dhcp-range in a single subnet. The optional
network-id is a alphanumeric label which marks this network so that network-id is a alphanumeric label which marks this network so that
dhcp options may be specified on a per-network basis. dhcp options may be specified on a per-network basis.
When it is prefixed with 'net:' then its meaning changes from setting When it is prefixed with 'net:' then its meaning changes from setting
a tag to matching it. a tag to matching it. Only one tag may be set, but more than one tag may be matched.
The end address may be replaced by the keyword The end address may be replaced by the keyword
.B static .B static
which tells dnsmasq to enable DHCP for the network specified, but not which tells dnsmasq to enable DHCP for the network specified, but not
...@@ -368,7 +368,7 @@ which case the IP address and lease times will apply to any machine ...@@ -368,7 +368,7 @@ which case the IP address and lease times will apply to any machine
claiming that name. For example claiming that name. For example
.B --dhcp-host=00:20:e0:3b:13:af,wap,infinite .B --dhcp-host=00:20:e0:3b:13:af,wap,infinite
tells dnsmasq to give tells dnsmasq to give
the machine with ethernet address 00:20:e0:3b:13:af the name wap, and the machine with hardware address 00:20:e0:3b:13:af the name wap, and
an infinite DHCP lease. an infinite DHCP lease.
.B --dhcp-host=lap,192.168.0.199 .B --dhcp-host=lap,192.168.0.199
tells tells
...@@ -401,9 +401,15 @@ for this host. ...@@ -401,9 +401,15 @@ for this host.
Ethernet addresses (but not client-ids) may have Ethernet addresses (but not client-ids) may have
wildcard bytes, so for example wildcard bytes, so for example
.B --dhcp-host=00:20:e0:3b:13:*,ignore .B --dhcp-host=00:20:e0:3b:13:*,ignore
will cause dnsmasq to ignore a range of ethernet addresses. Note that will cause dnsmasq to ignore a range of hardware addresses. Note that
the "*" will need to be escaped or quoted on a command line, but not the "*" will need to be escaped or quoted on a command line, but not
in the configuration file. in the configuration file. Hardware addresses normally match any
network (ARP) type, but it is possible to restrict them to a single
ARP type by preceding them with the ARP-type (in HEX) and "-". so
.B --dhcp-host=06-00:20:e0:3b:13:af,1.2.3.4
will only match a
Token-Ring hardware address, since the ARP-address type for token ring
is 6.
.TP .TP
.B \-Z, --read-ethers .B \-Z, --read-ethers
Read /etc/ethers for information about hosts for the DHCP server. The Read /etc/ethers for information about hosts for the DHCP server. The
...@@ -432,6 +438,11 @@ dotted-quad IP addresses, a decimal number, colon-separated hex digits ...@@ -432,6 +438,11 @@ dotted-quad IP addresses, a decimal number, colon-separated hex digits
and a text string. If the optional network-ids are given then and a text string. If the optional network-ids are given then
this option is only sent when all the network-ids are matched. this option is only sent when all the network-ids are matched.
Special processing is done on a text argument for option 119, to
conform with RFC 3397, and dotted-quad IP addresses which are followed
by a slash and then a netmask size are encoded as described in RFC
3442.
Be careful: no checking is done that the correct type of data for the Be careful: no checking is done that the correct type of data for the
option number is sent, it is quite possible to option number is sent, it is quite possible to
persuade dnsmasq to generate illegal DHCP packets with injudicious use persuade dnsmasq to generate illegal DHCP packets with injudicious use
...@@ -476,7 +487,13 @@ to different classes of hosts. It is possible, for instance to use ...@@ -476,7 +487,13 @@ to different classes of hosts. It is possible, for instance to use
this to set a different printer server for hosts in the class this to set a different printer server for hosts in the class
"accounts" than for hosts in the class "engineering". "accounts" than for hosts in the class "engineering".
.TP .TP
.B \ -J, --dhcp-ignore=<network-id>[,<network-id>] .B \-4, --dhcp-mac=<network-id>,<MAC address>
Map from a MAC address to a network-id. The MAC address may include
wildcards. For example
.B --dhcp-mac=3com,01:34:23:*:*:*
will set the tag "3com" for any host whose MAC address matches the pattern.
.TP
.B \-J, --dhcp-ignore=<network-id>[,<network-id>]
When all the given network-ids match the set of network-ids derived When all the given network-ids match the set of network-ids derived
from the net, host, vendor and user classes, ignore the host and do from the net, host, vendor and user classes, ignore the host and do
not allocate it a DHCP lease. not allocate it a DHCP lease.
...@@ -498,7 +515,9 @@ process. ...@@ -498,7 +515,9 @@ process.
Should be set when dnsmasq is definately the only DHCP server on a network. Should be set when dnsmasq is definately the only DHCP server on a network.
It changes the behaviour from strict RFC compliance so that DHCP requests on It changes the behaviour from strict RFC compliance so that DHCP requests on
unknown leases from unknown hosts are not ignored. This allows new hosts unknown leases from unknown hosts are not ignored. This allows new hosts
to get a lease without a tedious timeout under all circumstances. to get a lease without a tedious timeout under all circumstances. It also
allows dnsmasq to rebuild its lease database without each client needing to
reaquire a lease, if the database is lost.
.TP .TP
.B \-3, --bootp-dynamic .B \-3, --bootp-dynamic
Enable dynamic allocation of IP addresses to BOOTP clients. Use this Enable dynamic allocation of IP addresses to BOOTP clients. Use this
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -194,7 +194,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign ...@@ -194,7 +194,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
} }
} }
else if ((crecp->flags & F_FORWARD) && else if ((crecp->flags & F_FORWARD) &&
((flags & crecp->flags & (F_IPV4 | F_IPV6)) || (crecp->flags & F_CNAME)) && ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME)) &&
hostname_isequal(cache_get_name(crecp), name)) hostname_isequal(cache_get_name(crecp), name))
{ {
if (crecp->flags & (F_HOSTS | F_DHCP)) if (crecp->flags & (F_HOSTS | F_DHCP))
......
...@@ -12,13 +12,14 @@ ...@@ -12,13 +12,14 @@
/* Author's email: simon@thekelleys.org.uk */ /* Author's email: simon@thekelleys.org.uk */
#define VERSION "2.26" #define VERSION "2.27"
#define FTABSIZ 150 /* max number of outstanding requests */ #define FTABSIZ 150 /* max number of outstanding requests */
#define MAX_PROCS 20 /* max no children for TCP requests */ #define MAX_PROCS 20 /* max no children for TCP requests */
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
#define EDNS_PKTSZ 1280 /* default max EDNS.0 UDP packet from RFC2671 */ #define EDNS_PKTSZ 1280 /* default max EDNS.0 UDP packet from RFC2671 */
#define TIMEOUT 20 /* drop UDP queries after TIMEOUT seconds */ #define TIMEOUT 20 /* drop UDP queries after TIMEOUT seconds */
#define LEASE_RETRY 60 /* on error, retry writing leasefile after LEASE_RETRY seconds */
#define LOGRATE 120 /* log table overflows every LOGRATE seconds */ #define LOGRATE 120 /* log table overflows every LOGRATE seconds */
#define CACHESIZ 150 /* default cache size */ #define CACHESIZ 150 /* default cache size */
#define MAXTOK 50 /* token in DHCP leases */ #define MAXTOK 50 /* token in DHCP leases */
......
/* dnsmasq is Copyright (c) 2000-2005 Simon Kelley /* dnsmasq is Copyright (c) 2000-2006 Simon Kelley
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
...@@ -115,8 +115,9 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -115,8 +115,9 @@ void dhcp_packet(struct daemon *daemon, time_t now)
struct msghdr msg; struct msghdr msg;
struct iovec iov[2]; struct iovec iov[2];
struct cmsghdr *cmptr; struct cmsghdr *cmptr;
int sz, newlen, iface_index = 0; ssize_t sz;
int unicast_dest = 0; size_t newlen;
int iface_index = 0, unicast_dest = 0;
struct in_addr iface_addr; struct in_addr iface_addr;
#ifdef HAVE_BPF #ifdef HAVE_BPF
unsigned char iface_hwaddr[ETHER_ADDR_LEN]; unsigned char iface_hwaddr[ETHER_ADDR_LEN];
...@@ -144,7 +145,7 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -144,7 +145,7 @@ void dhcp_packet(struct daemon *daemon, time_t now)
sz = recvmsg(daemon->dhcpfd, &msg, 0); sz = recvmsg(daemon->dhcpfd, &msg, 0);
if (sz < (int)(sizeof(*mess) - sizeof(mess->options))) if (sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options)))
return; return;
#if defined (IP_PKTINFO) #if defined (IP_PKTINFO)
...@@ -248,8 +249,8 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -248,8 +249,8 @@ void dhcp_packet(struct daemon *daemon, time_t now)
} }
lease_prune(NULL, now); /* lose any expired leases */ lease_prune(NULL, now); /* lose any expired leases */
newlen = dhcp_reply(daemon, context, ifr.ifr_name, sz, now, unicast_dest); newlen = dhcp_reply(daemon, context, ifr.ifr_name, (size_t)sz, now, unicast_dest);
lease_update_file(0, now); lease_update_file(daemon, 0, now);
lease_update_dns(daemon); lease_update_dns(daemon);
if (newlen == 0) if (newlen == 0)
...@@ -289,16 +290,16 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -289,16 +290,16 @@ void dhcp_packet(struct daemon *daemon, time_t now)
the kernel IP stack */ the kernel IP stack */
u32 i, sum; u32 i, sum;
unsigned char hwdest[ETHER_ADDR_LEN]; unsigned char hwdest[DHCP_CHADDR_MAX];
if (ntohs(mess->flags) & 0x8000) if (ntohs(mess->flags) & 0x8000)
{ {
memset(hwdest, 255, ETHER_ADDR_LEN); memset(hwdest, 255, mess->hlen);
rawpacket->ip.ip_dst.s_addr = INADDR_BROADCAST; rawpacket->ip.ip_dst.s_addr = INADDR_BROADCAST;
} }
else else
{ {
memcpy(hwdest, mess->chaddr, ETHER_ADDR_LEN); memcpy(hwdest, mess->chaddr, mess->hlen);
rawpacket->ip.ip_dst.s_addr = mess->yiaddr.s_addr; rawpacket->ip.ip_dst.s_addr = mess->yiaddr.s_addr;
} }
...@@ -322,6 +323,7 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -322,6 +323,7 @@ void dhcp_packet(struct daemon *daemon, time_t now)
rawpacket->udp.uh_sport = htons(DHCP_SERVER_PORT); rawpacket->udp.uh_sport = htons(DHCP_SERVER_PORT);
rawpacket->udp.uh_dport = htons(DHCP_CLIENT_PORT); rawpacket->udp.uh_dport = htons(DHCP_CLIENT_PORT);
if (newlen & 1)
((u8 *)&rawpacket->data)[newlen] = 0; /* for checksum, in case length is odd. */ ((u8 *)&rawpacket->data)[newlen] = 0; /* for checksum, in case length is odd. */
rawpacket->udp.uh_sum = 0; rawpacket->udp.uh_sum = 0;
rawpacket->udp.uh_ulen = sum = htons(sizeof(struct udphdr) + newlen); rawpacket->udp.uh_ulen = sum = htons(sizeof(struct udphdr) + newlen);
...@@ -338,6 +340,12 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -338,6 +340,12 @@ void dhcp_packet(struct daemon *daemon, time_t now)
#ifdef HAVE_BPF #ifdef HAVE_BPF
struct ether_header header; struct ether_header header;
/* Only know how to do ethernet on *BSD */
if (mess->htype != ARPHRD_ETHER || mess->hlen != ETHER_ADDR_LEN)
syslog(LOG_WARNING, _("DHCP request for unsupported hardware type (%d) recieved on %s"),
mess->htype, ifr.ifr_name);
else
{
header.ether_type = htons(ETHERTYPE_IP); header.ether_type = htons(ETHERTYPE_IP);
memcpy(header.ether_shost, iface_hwaddr, ETHER_ADDR_LEN); memcpy(header.ether_shost, iface_hwaddr, ETHER_ADDR_LEN);
memcpy(header.ether_dhost, hwdest, ETHER_ADDR_LEN); memcpy(header.ether_dhost, hwdest, ETHER_ADDR_LEN);
...@@ -349,15 +357,26 @@ void dhcp_packet(struct daemon *daemon, time_t now) ...@@ -349,15 +357,26 @@ void dhcp_packet(struct daemon *daemon, time_t now)
iov[1].iov_base = (char *)rawpacket; iov[1].iov_base = (char *)rawpacket;
iov[1].iov_len = ntohs(rawpacket->ip.ip_len); iov[1].iov_len = ntohs(rawpacket->ip.ip_len);
while (writev(daemon->dhcp_raw_fd, iov, 2) == -1 && retry_send()); while (writev(daemon->dhcp_raw_fd, iov, 2) == -1 && retry_send());
}
#else #else
struct sockaddr_ll dest; /* Most definitions of this only include 8 bytes of address,
so we roll our own, since later kernels allow more. */
struct {
unsigned short int sll_family;
unsigned short int sll_protocol;
int sll_ifindex;
unsigned short int sll_hatype;
unsigned char sll_pkttype;
unsigned char sll_halen;
unsigned char sll_addr[DHCP_CHADDR_MAX];
} dest;
memset(&dest, 0, sizeof(dest)); memset(&dest, 0, sizeof(dest));
dest.sll_family = AF_PACKET; dest.sll_family = AF_PACKET;
dest.sll_halen = ETHER_ADDR_LEN; dest.sll_halen = mess->hlen;
dest.sll_ifindex = iface_index; dest.sll_ifindex = iface_index;
dest.sll_protocol = htons(ETHERTYPE_IP); dest.sll_protocol = htons(ETHERTYPE_IP);
memcpy(dest.sll_addr, hwdest, ETHER_ADDR_LEN); memcpy(dest.sll_addr, hwdest, mess->hlen);
while (sendto(daemon->dhcp_raw_fd, rawpacket, ntohs(rawpacket->ip.ip_len), while (sendto(daemon->dhcp_raw_fd, rawpacket, ntohs(rawpacket->ip.ip_len),
0, (struct sockaddr *)&dest, sizeof(dest)) == -1 && 0, (struct sockaddr *)&dest, sizeof(dest)) == -1 &&
retry_send()); retry_send());
...@@ -493,12 +512,13 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i ...@@ -493,12 +512,13 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i
return NULL; return NULL;
} }
/* Is every member of check matched by a member of pool? */ /* Is every member of check matched by a member of pool?
int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool) If negonly, match unless there's a negative tag which matches. */
int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int negonly)
{ {
struct dhcp_netid *tmp1; struct dhcp_netid *tmp1;
if (!check) if (!check && !negonly)
return 0; return 0;
for (; check; check = check->next) for (; check; check = check->next)
...@@ -508,7 +528,7 @@ int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool) ...@@ -508,7 +528,7 @@ int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool)
for (tmp1 = pool; tmp1; tmp1 = tmp1->next) for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
if (strcmp(check->net, tmp1->net) == 0) if (strcmp(check->net, tmp1->net) == 0)
break; break;
if (!tmp1) if (!tmp1 || negonly)
return 0; return 0;
} }
else else
...@@ -520,30 +540,28 @@ int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool) ...@@ -520,30 +540,28 @@ int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool)
} }
int address_allocate(struct dhcp_context *context, struct daemon *daemon, int address_allocate(struct dhcp_context *context, struct daemon *daemon,
struct in_addr *addrp, unsigned char *hwaddr, struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
struct dhcp_netid *netids, time_t now) struct dhcp_netid *netids, time_t now)
{ {
/* Find a free address: exclude anything in use and anything allocated to /* Find a free address: exclude anything in use and anything allocated to
a particular hwaddr/clientid/hostname in our configuration. a particular hwaddr/clientid/hostname in our configuration.
Try to return from contexts which mathc netis first. */ Try to return from contexts which match netids first. */
struct in_addr start, addr ; struct in_addr start, addr ;
struct dhcp_context *c; struct dhcp_context *c;
unsigned int i, j; int i, pass;
unsigned int j;
for (pass = 0; pass <= 1; pass++)
for (c = context; c; c = c->current) for (c = context; c; c = c->current)
if (c->flags & CONTEXT_STATIC) if (c->flags & CONTEXT_STATIC)
continue; continue;
else if (netids && !(c->flags & CONTEXT_FILTER)) else if (!match_netid(c->filter, netids, pass))
continue;
else if (!netids && (c->flags & CONTEXT_FILTER))
continue;
else if (netids && (c->flags & CONTEXT_FILTER) && !match_netid(&c->netid, netids))
continue; continue;
else else
{ {
/* pick a seed based on hwaddr then iterate until we find a free address. */ /* pick a seed based on hwaddr then iterate until we find a free address. */
for (j = c->addr_epoch, i = 0; i < ETHER_ADDR_LEN; i++) for (j = c->addr_epoch, i = 0; i < hw_len; i++)
j += hwaddr[i] + (hwaddr[i] << 8) + (hwaddr[i] << 16); j += hwaddr[i] + (hwaddr[i] << 8) + (hwaddr[i] << 16);
start.s_addr = addr.s_addr = start.s_addr = addr.s_addr =
...@@ -607,10 +625,6 @@ int address_allocate(struct dhcp_context *context, struct daemon *daemon, ...@@ -607,10 +625,6 @@ int address_allocate(struct dhcp_context *context, struct daemon *daemon,
} while (addr.s_addr != start.s_addr); } while (addr.s_addr != start.s_addr);
} }
if (netids)
return address_allocate(context, daemon, addrp, hwaddr, NULL, now);
return 0; return 0;
} }
...@@ -631,7 +645,8 @@ static int is_addr_in_context(struct dhcp_context *context, struct dhcp_config * ...@@ -631,7 +645,8 @@ static int is_addr_in_context(struct dhcp_context *context, struct dhcp_config *
struct dhcp_config *find_config(struct dhcp_config *configs, struct dhcp_config *find_config(struct dhcp_config *configs,
struct dhcp_context *context, struct dhcp_context *context,
unsigned char *clid, int clid_len, unsigned char *clid, int clid_len,
unsigned char *hwaddr, char *hostname) unsigned char *hwaddr, int hw_len,
int hw_type, char *hostname)
{ {
struct dhcp_config *config; struct dhcp_config *config;
...@@ -653,11 +668,12 @@ struct dhcp_config *find_config(struct dhcp_config *configs, ...@@ -653,11 +668,12 @@ struct dhcp_config *find_config(struct dhcp_config *configs,
} }
if (hwaddr)
for (config = configs; config; config = config->next) for (config = configs; config; config = config->next)
if ((config->flags & CONFIG_HWADDR) && if ((config->flags & CONFIG_HWADDR) &&
config->wildcard_mask == 0 && config->wildcard_mask == 0 &&
memcmp(config->hwaddr, hwaddr, ETHER_ADDR_LEN) == 0 && config->hwaddr_len == hw_len &&
(config->hwaddr_type == hw_type || config->hwaddr_type == 0) &&
memcmp(config->hwaddr, hwaddr, hw_len) == 0 &&
is_addr_in_context(context, config)) is_addr_in_context(context, config))
return config; return config;
...@@ -669,18 +685,19 @@ struct dhcp_config *find_config(struct dhcp_config *configs, ...@@ -669,18 +685,19 @@ struct dhcp_config *find_config(struct dhcp_config *configs,
is_addr_in_context(context, config)) is_addr_in_context(context, config))
return config; return config;
if (hwaddr)
for (config = configs; config; config = config->next) for (config = configs; config; config = config->next)
if ((config->flags & CONFIG_HWADDR) && if ((config->flags & CONFIG_HWADDR) &&
config->wildcard_mask != 0 && config->wildcard_mask != 0 &&
config->hwaddr_len == hw_len &&
(config->hwaddr_type == hw_type || config->hwaddr_type == 0) &&
is_addr_in_context(context, config)) is_addr_in_context(context, config))
{ {
int i; int i;
unsigned int mask = config->wildcard_mask; unsigned int mask = config->wildcard_mask;
for (i = ETHER_ADDR_LEN - 1; i >= 0; i--, mask = mask >> 1) for (i = hw_len - 1; i >= 0; i--, mask = mask >> 1)
if (mask & 1) if (mask & 1)
config->hwaddr[i] = hwaddr[i]; config->hwaddr[i] = hwaddr[i];
if (memcmp(config->hwaddr, hwaddr, ETHER_ADDR_LEN) == 0) if (memcmp(config->hwaddr, hwaddr, hw_len) == 0)
return config; return config;
} }
...@@ -719,7 +736,7 @@ void dhcp_read_ethers(struct daemon *daemon) ...@@ -719,7 +736,7 @@ void dhcp_read_ethers(struct daemon *daemon)
for (ip = buff; *ip && !isspace(*ip); ip++); for (ip = buff; *ip && !isspace(*ip); ip++);
for(; *ip && isspace(*ip); ip++) for(; *ip && isspace(*ip); ip++)
*ip = 0; *ip = 0;
if (!*ip || parse_hex(buff, hwaddr, 6, NULL) != 6) if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN)
{ {
syslog(LOG_ERR, _("bad line at %s line %d"), ETHERSFILE, lineno); syslog(LOG_ERR, _("bad line at %s line %d"), ETHERSFILE, lineno);
continue; continue;
...@@ -764,6 +781,8 @@ void dhcp_read_ethers(struct daemon *daemon) ...@@ -764,6 +781,8 @@ void dhcp_read_ethers(struct daemon *daemon)
for (config = configs; config; config = config->next) for (config = configs; config; config = config->next)
if ((config->flags & CONFIG_HWADDR) && if ((config->flags & CONFIG_HWADDR) &&
config->wildcard_mask == 0 && config->wildcard_mask == 0 &&
config->hwaddr_len == ETHER_ADDR_LEN &&
(config->hwaddr_type == ARPHRD_ETHER || config->hwaddr_type == 0) &&
memcmp(config->hwaddr, hwaddr, ETHER_ADDR_LEN) == 0) memcmp(config->hwaddr, hwaddr, ETHER_ADDR_LEN) == 0)
break; break;
...@@ -793,7 +812,8 @@ void dhcp_read_ethers(struct daemon *daemon) ...@@ -793,7 +812,8 @@ void dhcp_read_ethers(struct daemon *daemon)
config->flags |= CONFIG_HWADDR | CONFIG_NOCLID; config->flags |= CONFIG_HWADDR | CONFIG_NOCLID;
memcpy(config->hwaddr, hwaddr, ETHER_ADDR_LEN); memcpy(config->hwaddr, hwaddr, ETHER_ADDR_LEN);
config->hwaddr_len = ETHER_ADDR_LEN;
config->hwaddr_type = ARPHRD_ETHER;
count++; count++;
} }
......
/* dnsmasq is Copyright (c) 2000-2005 Simon Kelley /* dnsmasq is Copyright (c) 2000-2006 Simon Kelley
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
...@@ -176,7 +176,6 @@ int main (int argc, char **argv) ...@@ -176,7 +176,6 @@ int main (int argc, char **argv)
die(_("DBus error: %s"), err); die(_("DBus error: %s"), err);
} }
#else #else
if (daemon->options & OPT_DBUS)
die(_("DBus not available: set HAVE_DBUS in src/config.h"), NULL); die(_("DBus not available: set HAVE_DBUS in src/config.h"), NULL);
#endif #endif
...@@ -256,7 +255,7 @@ int main (int argc, char **argv) ...@@ -256,7 +255,7 @@ int main (int argc, char **argv)
#endif #endif
if (daemon->dhcp && if (daemon->dhcp &&
(i == daemon->lease_fd || (i == fileno(daemon->lease_stream) ||
i == daemon->dhcpfd || i == daemon->dhcpfd ||
i == daemon->dhcp_raw_fd || i == daemon->dhcp_raw_fd ||
i == daemon->dhcp_icmp_fd)) i == daemon->dhcp_icmp_fd))
...@@ -314,15 +313,15 @@ int main (int argc, char **argv) ...@@ -314,15 +313,15 @@ int main (int argc, char **argv)
if (if_tmp->name && !if_tmp->used) if (if_tmp->name && !if_tmp->used)
syslog(LOG_WARNING, _("warning: interface %s does not currently exist"), if_tmp->name); syslog(LOG_WARNING, _("warning: interface %s does not currently exist"), if_tmp->name);
if (daemon->dhcp)
{
struct dhcp_context *dhcp_tmp;
#ifdef HAVE_RTNETLINK #ifdef HAVE_RTNETLINK
/* Must do this after daemonizing so that the pid is right */ /* Must do this after daemonizing so that the pid is right */
daemon->netlinkfd = netlink_init(); netlink_init(daemon);
#endif #endif
if (daemon->dhcp)
{
struct dhcp_context *dhcp_tmp;
for (dhcp_tmp = daemon->dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next) for (dhcp_tmp = daemon->dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next)
{ {
prettyprint_time(daemon->dhcp_buff2, dhcp_tmp->lease_time); prettyprint_time(daemon->dhcp_buff2, dhcp_tmp->lease_time);
...@@ -375,7 +374,7 @@ int main (int argc, char **argv) ...@@ -375,7 +374,7 @@ int main (int argc, char **argv)
{ {
if (daemon->dhcp) if (daemon->dhcp)
{ {
lease_update_file(1, now); lease_update_file(daemon, 1, now);
#ifdef HAVE_BROKEN_RTC #ifdef HAVE_BROKEN_RTC
alarm(daemon->min_leasetime); alarm(daemon->min_leasetime);
#endif #endif
...@@ -400,6 +399,15 @@ int main (int argc, char **argv) ...@@ -400,6 +399,15 @@ int main (int argc, char **argv)
maxfd = daemon->dhcpfd; maxfd = daemon->dhcpfd;
} }
#ifdef HAVE_RTNETLINK
if (daemon->netlinkfd != -1)
{
FD_SET(daemon->netlinkfd, &rset);
if (daemon->netlinkfd > maxfd)
maxfd = daemon->netlinkfd;
}
#endif
/* Whilst polling for the dbus, wake every quarter second */ /* Whilst polling for the dbus, wake every quarter second */
#ifdef HAVE_PSELECT #ifdef HAVE_PSELECT
{ {
...@@ -498,6 +506,11 @@ int main (int argc, char **argv) ...@@ -498,6 +506,11 @@ int main (int argc, char **argv)
} }
} }
#ifdef HAVE_RTNETLINK
if (daemon->netlinkfd != -1 && FD_ISSET(daemon->netlinkfd, &rset))
netlink_multicast(daemon);
#endif
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
/* if we didn't create a DBus connection, retry now. */ /* if we didn't create a DBus connection, retry now. */
if ((daemon->options & OPT_DBUS) && !daemon->dbus) if ((daemon->options & OPT_DBUS) && !daemon->dbus)
...@@ -522,9 +535,9 @@ int main (int argc, char **argv) ...@@ -522,9 +535,9 @@ int main (int argc, char **argv)
if (daemon->dhcp) if (daemon->dhcp)
{ {
#ifdef HAVE_BROKEN_RTC #ifdef HAVE_BROKEN_RTC
lease_update_file(1, now); lease_update_file(daemon, 1, now);
#endif #endif
close(daemon->lease_fd); fclose(daemon->lease_stream);
} }
return 0; return 0;
...@@ -565,7 +578,7 @@ void clear_cache_and_reload(struct daemon *daemon, time_t now) ...@@ -565,7 +578,7 @@ void clear_cache_and_reload(struct daemon *daemon, time_t now)
dhcp_read_ethers(daemon); dhcp_read_ethers(daemon);
dhcp_update_configs(daemon->dhcp_conf); dhcp_update_configs(daemon->dhcp_conf);
lease_update_from_configs(daemon); lease_update_from_configs(daemon);
lease_update_file(0, now); lease_update_file(daemon, 0, now);
lease_update_dns(daemon); lease_update_dns(daemon);
} }
} }
......
This diff is collapsed.
...@@ -36,7 +36,7 @@ void forward_init(int first) ...@@ -36,7 +36,7 @@ void forward_init(int first)
/* Send a UDP packet with it's source address set as "source" /* Send a UDP packet with it's source address set as "source"
unless nowild is true, when we just send it with the kernel default */ unless nowild is true, when we just send it with the kernel default */
static void send_from(int fd, int nowild, char *packet, int len, static void send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, struct all_addr *source, union mysockaddr *to, struct all_addr *source,
unsigned int iface) unsigned int iface)
{ {
...@@ -164,7 +164,7 @@ static unsigned short search_servers(struct daemon *daemon, time_t now, struct a ...@@ -164,7 +164,7 @@ static unsigned short search_servers(struct daemon *daemon, time_t now, struct a
if (namelen >= domainlen && if (namelen >= domainlen &&
hostname_isequal(matchstart, serv->domain) && hostname_isequal(matchstart, serv->domain) &&
domainlen >= matchlen && domainlen >= matchlen &&
(namelen == domainlen || *(serv->domain) == '.' || *(matchstart-1) == '.' )) (domainlen == 0 || namelen == domainlen || *(serv->domain) == '.' || *(matchstart-1) == '.' ))
{ {
unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6;
*type = SERV_HAS_DOMAIN; *type = SERV_HAS_DOMAIN;
...@@ -212,14 +212,14 @@ static unsigned short search_servers(struct daemon *daemon, time_t now, struct a ...@@ -212,14 +212,14 @@ static unsigned short search_servers(struct daemon *daemon, time_t now, struct a
/* returns new last_server */ /* returns new last_server */
static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *udpaddr, static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *udpaddr,
struct all_addr *dst_addr, unsigned int dst_iface, struct all_addr *dst_addr, unsigned int dst_iface,
HEADER *header, int plen, time_t now, struct frec *forward) HEADER *header, size_t plen, time_t now, struct frec *forward)
{ {
char *domain = NULL; char *domain = NULL;
int type = 0; int type = 0;
struct all_addr *addrp = NULL; struct all_addr *addrp = NULL;
unsigned int crc = questions_crc(header, (unsigned int)plen, daemon->namebuff); unsigned int crc = questions_crc(header, plen, daemon->namebuff);
unsigned short flags = 0; unsigned short flags = 0;
unsigned short gotname = extract_request(header, (unsigned int)plen, daemon->namebuff, NULL); unsigned short gotname = extract_request(header, plen, daemon->namebuff, NULL);
struct server *start = NULL; struct server *start = NULL;
/* may be no servers available. */ /* may be no servers available. */
...@@ -302,6 +302,10 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud ...@@ -302,6 +302,10 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud
} }
else else
{ {
/* Keep info in case we want to re-send this packet */
daemon->srv_save = start;
daemon->packet_len = plen;
if (!gotname) if (!gotname)
strcpy(daemon->namebuff, "query"); strcpy(daemon->namebuff, "query");
if (start->addr.sa.sa_family == AF_INET) if (start->addr.sa.sa_family == AF_INET)
...@@ -340,18 +344,19 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud ...@@ -340,18 +344,19 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud
/* could not send on, return empty answer or address if known for whole domain */ /* could not send on, return empty answer or address if known for whole domain */
if (udpfd != -1) if (udpfd != -1)
{ {
plen = setup_reply(header, (unsigned int)plen, addrp, flags, daemon->local_ttl); plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl);
send_from(udpfd, daemon->options & OPT_NOWILD, (char *)header, plen, udpaddr, dst_addr, dst_iface); send_from(udpfd, daemon->options & OPT_NOWILD, (char *)header, plen, udpaddr, dst_addr, dst_iface);
} }
return; return;
} }
static int process_reply(struct daemon *daemon, HEADER *header, time_t now, static size_t process_reply(struct daemon *daemon, HEADER *header, time_t now,
unsigned int query_crc, struct server *server, unsigned int n) unsigned int query_crc, struct server *server, size_t n)
{ {
unsigned char *pheader, *sizep; unsigned char *pheader, *sizep;
unsigned int plen, munged = 0; int munged = 0;
size_t plen;
/* If upstream is advertising a larger UDP packet size /* If upstream is advertising a larger UDP packet size
than we allow, trim it so that we don't get overlarge than we allow, trim it so that we don't get overlarge
...@@ -433,7 +438,11 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now) ...@@ -433,7 +438,11 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now)
HEADER *header; HEADER *header;
union mysockaddr serveraddr; union mysockaddr serveraddr;
socklen_t addrlen = sizeof(serveraddr); socklen_t addrlen = sizeof(serveraddr);
int n = recvfrom(sfd->fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); ssize_t n = recvfrom(sfd->fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen);
size_t nn;
/* packet buffer overwritten */
daemon->srv_save = NULL;
/* Determine the address of the server replying so that we can mark that as good */ /* Determine the address of the server replying so that we can mark that as good */
serveraddr.sa.sa_family = sfd->source_addr.sa.sa_family; serveraddr.sa.sa_family = sfd->source_addr.sa.sa_family;
...@@ -453,14 +462,14 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now) ...@@ -453,14 +462,14 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now)
/* for broken servers, attempt to send to another one. */ /* for broken servers, attempt to send to another one. */
{ {
unsigned char *pheader; unsigned char *pheader;
unsigned int plen; size_t plen;
int nn;
/* recreate query from reply */ /* recreate query from reply */
pheader = find_pseudoheader(header, n, &plen, NULL); pheader = find_pseudoheader(header, (size_t)n, &plen, NULL);
header->ancount = htons(0); header->ancount = htons(0);
header->nscount = htons(0); header->nscount = htons(0);
header->arcount = htons(0); header->arcount = htons(0);
if ((nn = resize_packet(header, n, pheader, plen))) if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
{ {
forward->forwardall = 1; forward->forwardall = 1;
header->qr = 0; header->qr = 0;
...@@ -496,11 +505,11 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now) ...@@ -496,11 +505,11 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now)
if (forward->forwardall == 0 || --forward->forwardall == 1 || if (forward->forwardall == 0 || --forward->forwardall == 1 ||
(header->rcode != REFUSED && header->rcode != SERVFAIL)) (header->rcode != REFUSED && header->rcode != SERVFAIL))
{ {
if ((n = process_reply(daemon, header, now, forward->crc, server, (unsigned int)n))) if ((nn = process_reply(daemon, header, now, forward->crc, server, (size_t)n)))
{ {
header->id = htons(forward->orig_id); header->id = htons(forward->orig_id);
header->ra = 1; /* recursion if available */ header->ra = 1; /* recursion if available */
send_from(forward->fd, daemon->options & OPT_NOWILD, daemon->packet, n, send_from(forward->fd, daemon->options & OPT_NOWILD, daemon->packet, nn,
&forward->source, &forward->dest, forward->iface); &forward->source, &forward->dest, forward->iface);
} }
forward->new_id = 0; /* cancel */ forward->new_id = 0; /* cancel */
...@@ -516,7 +525,9 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now) ...@@ -516,7 +525,9 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now)
struct iname *tmp; struct iname *tmp;
struct all_addr dst_addr; struct all_addr dst_addr;
struct in_addr netmask, dst_addr_4; struct in_addr netmask, dst_addr_4;
int m, n, if_index = 0; size_t m;
ssize_t n;
int if_index = 0;
struct iovec iov[1]; struct iovec iov[1];
struct msghdr msg; struct msghdr msg;
struct cmsghdr *cmptr; struct cmsghdr *cmptr;
...@@ -533,6 +544,9 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now) ...@@ -533,6 +544,9 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now)
#endif #endif
} control_u; } control_u;
/* packet buffer overwritten */
daemon->srv_save = NULL;
if (listen->family == AF_INET && (daemon->options & OPT_NOWILD)) if (listen->family == AF_INET && (daemon->options & OPT_NOWILD))
{ {
dst_addr_4 = listen->iface->addr.in.sin_addr; dst_addr_4 = listen->iface->addr.in.sin_addr;
...@@ -658,7 +672,7 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now) ...@@ -658,7 +672,7 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now)
} }
} }
if (extract_request(header, (unsigned int)n, daemon->namebuff, &type)) if (extract_request(header, (size_t)n, daemon->namebuff, &type))
{ {
if (listen->family == AF_INET) if (listen->family == AF_INET)
log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff,
...@@ -670,18 +684,18 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now) ...@@ -670,18 +684,18 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now)
#endif #endif
} }
m = answer_request (header, ((char *) header) + PACKETSZ, (unsigned int)n, daemon, m = answer_request (header, ((char *) header) + PACKETSZ, (size_t)n, daemon,
dst_addr_4, netmask, now); dst_addr_4, netmask, now);
if (m >= 1) if (m >= 1)
send_from(listen->fd, daemon->options & OPT_NOWILD, (char *)header, m, &source_addr, &dst_addr, if_index); send_from(listen->fd, daemon->options & OPT_NOWILD, (char *)header, m, &source_addr, &dst_addr, if_index);
else else
forward_query(daemon, listen->fd, &source_addr, &dst_addr, if_index, forward_query(daemon, listen->fd, &source_addr, &dst_addr, if_index,
header, n, now, NULL); header, (size_t)n, now, NULL);
} }
static int read_write(int fd, unsigned char *packet, int size, int rw) static int read_write(int fd, unsigned char *packet, int size, int rw)
{ {
int n, done; ssize_t n, done;
for (done = 0; done < size; done += n) for (done = 0; done < size; done += n)
{ {
...@@ -711,7 +725,8 @@ static int read_write(int fd, unsigned char *packet, int size, int rw) ...@@ -711,7 +725,8 @@ static int read_write(int fd, unsigned char *packet, int size, int rw)
unsigned char *tcp_request(struct daemon *daemon, int confd, time_t now, unsigned char *tcp_request(struct daemon *daemon, int confd, time_t now,
struct in_addr local_addr, struct in_addr netmask) struct in_addr local_addr, struct in_addr netmask)
{ {
int size = 0, m; int size = 0;
size_t m;
unsigned short qtype, gotname; unsigned short qtype, gotname;
unsigned char c1, c2; unsigned char c1, c2;
/* Max TCP packet + slop */ /* Max TCP packet + slop */
......
This diff is collapsed.
This diff is collapsed.
...@@ -503,7 +503,7 @@ void check_servers(struct daemon *daemon) ...@@ -503,7 +503,7 @@ void check_servers(struct daemon *daemon)
/* forward table rules reference servers, so have to blow them away */ /* forward table rules reference servers, so have to blow them away */
forward_init(0); forward_init(0);
daemon->last_server = NULL; daemon->last_server = daemon->srv_save = NULL;
for (new = daemon->servers; new; new = tmp) for (new = daemon->servers; new; new = tmp)
{ {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment