diff options
author | Simon Kelley <simon@thekelleys.org.uk> | 2004-07-27 20:28:58 +0100 |
---|---|---|
committer | Simon Kelley <simon@thekelleys.org.uk> | 2012-01-05 17:31:10 +0000 |
commit | feba5c1d2510b2a5dd9ec74d43a588696b485c74 (patch) | |
tree | d057cedd368e67adbf356658c721d76f9dac7023 | |
parent | de37951cf4c3a380fd11a2bf4f03a4eed1137673 (diff) | |
download | dnsmasq-2.10.tar.gz |
import of dnsmasq-2.10.tar.gzv2.10
-rw-r--r-- | CHANGELOG | 68 | ||||
-rw-r--r-- | FAQ | 12 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | contrib/dnslist/dhcp.css | 57 | ||||
-rwxr-xr-x | contrib/dnslist/dnslist.pl | 608 | ||||
-rw-r--r-- | contrib/dnslist/dnslist.tt2 | 32 | ||||
-rw-r--r-- | dnsmasq-rh.spec | 2 | ||||
-rw-r--r-- | dnsmasq-suse.spec | 6 | ||||
-rw-r--r-- | dnsmasq.8 | 60 | ||||
-rw-r--r-- | doc.html | 4 | ||||
-rw-r--r-- | rpm/dnsmasq-SuSE.patch | 42 | ||||
-rw-r--r-- | src/config.h | 15 | ||||
-rw-r--r-- | src/dhcp.c | 9 | ||||
-rw-r--r-- | src/dnsmasq.c | 207 | ||||
-rw-r--r-- | src/dnsmasq.h | 32 | ||||
-rw-r--r-- | src/forward.c | 557 | ||||
-rw-r--r-- | src/lease.c | 6 | ||||
-rw-r--r-- | src/network.c | 206 | ||||
-rw-r--r-- | src/option.c | 27 | ||||
-rw-r--r-- | src/rfc1035.c | 529 | ||||
-rw-r--r-- | src/rfc2131.c | 51 |
21 files changed, 1949 insertions, 583 deletions
@@ -1056,3 +1056,71 @@ release 2.9 Allow # as the argument to --domain, meaning "read the domain from the first search directive in /etc.resolv.conf". Feature suggested by Evan Jones. + +release 2.10 + Allow --query-port to be set to a low port by creating and + binding the socket before dropping root. (Suggestion from + Jamie Lokier) + + Support TCP queries. It turned out to be possible to do + this with a couple of hundred lines of code, once I knew + how. The executable size went up by a few K on i386. + There are a few limitations: data obtained via TCP is not + cached, and dynamically-created interfaces may break under + certain circumstances. Source-address or query-port + specifications are ignored for TCP. + + NAK attempts to renew a DHCP lease where the DHCP range + has changed and the lease is no longer in the allowed + range. Jamie Lokier pointed out this bug. + + NAK attempts to renew a pool DHCP lease when a statically + allocated address has become available, forcing a host to + move to it's allocated address. Lots of people have + suggested this change and been rebuffed (they know who + they are) the straws that broke the camel's back were Tim + Cutts and Jamie Lokier. + + Remove any nameserver records from answers which are + modified by --alias flags. If the answer is modified, it + cannot any longer be authoritative. + + Change behaviour of "bogus-priv" option to return NXDOMAIN + rather than a PTR record with the dotted-quad address as + name. The new behaviour doesn't provoke tcpwrappers like + the old behavior did. + + Added a patch for the Suse rpm. That changes the default + group to one suitable for Suse and disables inclusion of + the ISC lease-file reader code. Thanks to Andy Cambeis for + his ongoing work on Suse packaging. + + Support forwarding of EDNS.0 The maximum UDP packet size + defaults to 1280, but may be changed with the + --edns-packet-max option. Detect queries with the do bit + set and always forward them, since DNSSEC records are + not cached. This behaviour is required to make + DNSSECbis work properly though dnsmasq. Thanks to Simon + Josefsson for help with this. + + Move default config file location under OpenBSD from + /usr/local/etc/dnsmasq.conf to /etc/dnsmasq.conf. Bug + report from Jonathan Weiss. + + Use a lease with matching MAC address for a host which + doesn't present a client-id, even if there was a client ID + at some point in the past. This reduces surprises when + changing DHCP clients, adding id:* to a host, and from the + semantics change of /etc/ethers in 2.9. Thanks to Bernard + Sammer for finding that. + + Added a "contrib" directory and in it the dnslist utility, + from Thomas Tuttle. + + Fixed "fail to start up" problems under Linux with IPv6 + enabled. It's not clear that these were an issue in + released versions, but they manifested themselves when TCP + support was added. Thanks to Michael Hamilton for + assistance with this. + + @@ -20,12 +20,10 @@ A: The high ports that dnsmasq opens is for replies from the upstream Q: Why doesn't dnsmasq support DNS queries over TCP? Don't the RFC's specify that? -A: Yes, they do, so technically dnsmasq is not RFC-compliant. In practice, the - sorts of queries which dnsmasq is used for are always sent via UDP. Adding - TCP support would make dnsmasq much more heavyweight for no practical - benefit. If you really want to do zone transfers, forward port 53 TCP - using in-kernel port-forwarding or a port-fowarder like rinetd. - +A: Update: from version 2.10, it does. There are a few limitations: + data obtained via TCP is not cached, and dynamically-created + interfaces may break under certain circumstances. Source-address + or query-port specifications are ignored for TCP. Q: When I send SIGUSR1 to dump the contents of the cache, some entries have no IP address and are for names like mymachine.mydomain.com.mydomain.com. @@ -74,6 +72,8 @@ A: Use the standard DNS convention of <reversed address>.in-addr.arpa. For instance to send reverse queries on the range 192.168.0.0 to 192.168.0.255 to a nameserver at 10.0.0.1 do server=/0.168.192.in-addr.arpa/10.0.0.1 + Note that the "bogus-priv" option take priority over this option, + so the above will not work when the bogus-priv option is set. Q: Dnsmasq fails to start with an error like this: "dnsmasq: bind failed: Cannot assign requested address". What's the problem? @@ -10,7 +10,7 @@ all : @cd $(SRC); $(MAKE) dnsmasq clean : - rm -f *~ */*~ $(SRC)/*.o $(SRC)/dnsmasq core build + rm -f *~ contrib/*/*~ */*~ $(SRC)/*.o $(SRC)/dnsmasq core build install : all install -d $(DESTDIR)$(BINDIR) -d $(DESTDIR)$(MANDIR)/man8 diff --git a/contrib/dnslist/dhcp.css b/contrib/dnslist/dhcp.css new file mode 100644 index 0000000..79cea39 --- /dev/null +++ b/contrib/dnslist/dhcp.css @@ -0,0 +1,57 @@ +body +{ + font-family: sans-serif; + color: #000; +} + +h1 +{ + font-size: medium; + font-weight: bold; +} + +h1 .updated +{ + color: #999; +} + +table +{ + border-collapse: collapse; + border-bottom: 2px solid #000; +} + +th +{ + background: #DDD; + border-top: 2px solid #000; + text-align: left; + font-weight: bold; +} + +/* Any row */ + +tr +{ + border-top: 2px solid #000; +} + +/* Any row but the first or second (overrides above rule) */ + +tr + tr + tr +{ + border-top: 2px solid #999; +} + +tr.offline td.hostname +{ + color: #999; +} + +.hostname { width: 10em; } +.ip_addr { width: 10em; background: #DDD; } +.ether_addr { width: 15em; } +.client_id { width: 15em; background: #DDD; } +.status { width: 5em; } +.since { width: 10em; background: #DDD; } +.lease { width: 10em; } diff --git a/contrib/dnslist/dnslist.pl b/contrib/dnslist/dnslist.pl new file mode 100755 index 0000000..7ce2720 --- /dev/null +++ b/contrib/dnslist/dnslist.pl @@ -0,0 +1,608 @@ +#!/usr/bin/perl + +# dnslist - Read state file from dnsmasq and create a nice web page to display +# a list of DHCP clients. +# +# Copyright (C) 2004 Thomas Tuttle +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program*; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# * The license is in fact included at the end of this file, and can +# either be viewed by reading everything after "__DATA__" or by +# running dnslist with the '-l' option. +# +# Version: 0.2 +# Author: Thomas Tuttle +# Email: dnslist.20.thinkinginbinary@spamgourmet.org +# License: GNU General Public License, version 2.0 +# +# v. 0.0: Too ugly to publish, thrown out. +# +# v. 0.1: First rewrite. +# Added master host list so offline hosts can still be displayed. +# Fixed modification detection (a newer modification time is lower.) +# +# v. 0.2: Fixed Client ID = "*" => "None" +# Fixed HTML entities (a client ID of ????<? screwed it up) +# Fixed command-line argument processing (apparently, "shift @ARGV" != +# "$_ = shift @ARGV"...) +# Added license information. + +use Template; + +# Location of state file. (This is the dnsmasq default.) +# Change with -s <file> +my $dnsmasq_state_file = '/var/lib/misc/dnsmasq.leases'; +# Location of template. (Assumed to be in current directory.) +# Change with -t <file> +my $html_template_file = 'dnslist.tt2'; +# File to write HTML page to. (This is where Slackware puts WWW pages. It may +# be different on other systems. Make sure the permissions are set correctly +# for it.) +my $html_output_file = '/var/www/htdocs/dhcp.html'; +# Time to wait after each page update. (The state file is checked for changes +# before each update but is not read in each time, in case it is very big. The +# page is rewritten just so the "(updated __/__ __:__:__)" text changes ;-) +my $wait_time = 2; + +# Read command-line arguments. +while ($_ = shift @ARGV) { + if (/-s/) { $dnsmasq_state_file = shift; next; } + if (/-t/) { $html_template_file = shift; next; } + if (/-o/) { $html_output_file = shift; next; } + if (/-d/) { $wait_time = shift; next; } + if (/-l/) { show_license(); exit; } + die "usage: dnslist [-s state_file] [-t template_file] [-o output_file] [-d delay_time]\n"; +} + +# Master list of clients, offline and online. +my $list = {}; +# Sorted host list. (It's actually sorted by IP--the sub &byip() compares two +# IP addresses, octet by octet, and figures out which is higher.) +my @hosts = (); +# Last time the state file was changed. +my $last_state_change; + +# Check for a change to the state file. +sub check_state { + if (defined $last_state_change) { + if (-M $dnsmasq_state_file < $last_state_change) { + print "check_state: state file has been changed.\n"; + $last_state_change = -M $dnsmasq_state_file; + return 1; + } else { + return 0; + } + } else { + # Last change undefined, so we are running for the first time. + print "check_state: reading state file at startup.\n"; + read_state(); + $last_state_change = -M $dnsmasq_state_file; + return 1; + } +} + +# Read data in state file. +sub read_state { + my $old; + my $new; + # Open file. + unless (open STATE, $dnsmasq_state_file) { + warn "read_state: can't open $dnsmasq_state_file!\n"; + return 0; + } + # Mark all hosts as offline, saving old state. + foreach $ether (keys %{$list}) { + $list->{$ether}->{'old_online'} = $list->{$ether}->{'online'}; + $list->{$ether}->{'online'} = 0; + } + # Read hosts. + while (<STATE>) { + chomp; + @host{qw/raw_lease ether_addr ip_addr hostname raw_client_id/} = split /\s+/; + $ether = $host{ether_addr}; + # Mark each online host as online. + $list->{$ether}->{'online'} = 1; + # Copy data to master list. + foreach $key (keys %host) { + $list->{$ether}->{$key} = $host{$key}; + } + } + close STATE; + # Handle changes in offline/online state. (The sub &do_host() handles + # all of the extra stuff to do with a host's data once it is read. + foreach $ether (keys %{$list}) { + $old = $list->{$ether}->{'old_online'}; + $new = $list->{$ether}->{'online'}; + if (not $old) { + if (not $new) { + do_host($ether, 'offline'); + } else { + do_host($ether, 'join'); + } + } else { + if (not $new) { + do_host($ether, 'leave'); + } else { + do_host($ether, 'online'); + } + } + } + # Sort hosts by IP ;-) + @hosts = sort byip values %{$list}; + # Copy sorted list to template data store. + $data->{'hosts'} = [ @hosts ]; +} + +# Do stuff per host. +sub do_host { + my ($ether, $status) = @_; + + # Find textual representation of DHCP client ID. + if ($list->{$ether}->{'raw_client_id'} eq '*') { + $list->{$ether}->{'text_client_id'} = 'None'; + } else { + my $text = ""; + foreach $char (split /:/, $list->{$ether}->{'raw_client_id'}) { + $char = pack('H2', $char); + if (ord($char) >= 32 and ord($char) <= 127) { + $text .= $char; + } else { + $text .= "?"; + } + } + $list->{$ether}->{'text_client_id'} = $text; + } + + # Convert lease expiration date/time to text. + if ($list->{$ether}->{'raw_lease'} == 0) { + $list->{$ether}->{'text_lease'} = 'Never'; + } else { + $list->{$ether}->{'text_lease'} = nice_time($list->{$ether}->{'raw_lease'}); + } + + if ($status eq 'offline') { + # Nothing to do. + } elsif ($status eq 'online') { + # Nothing to do. + } elsif ($status eq 'join') { + # Update times for joining host. + print "do_host: $ether joined the network.\n"; + $list->{$ether}->{'join_time'} = time; + $list->{$ether}->{'since'} = nice_time(time); + } elsif ($status eq 'leave') { + # Update times for leaving host. + print "do_host: $ether left the network.\n"; + $list->{$ether}->{'leave_time'} = time; + $list->{$ether}->{'since'} = nice_time(time); + } + +} + +# Convert time to a string representation. +sub nice_time { + my $time = shift; + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $dst) = localtime($time); + $sec = pad($sec, '0', 2); + $min = pad($min, '0', 2); + $hour = pad($hour, '0', 2); + $mon = pad($mon, '0', 2); + $mday = pad($mday, '0', 2); + return "$mon/$mday $hour:$min:$sec"; +} + +# Pad string to a certain length by repeatedly prepending another string. +sub pad { + my ($text, $pad, $length) = @_; + while (length($text) < $length) { + $text = "$pad$text"; + } + return $text; +} + +# Compare two IP addresses. (Uses $a and $b from sort.) +sub byip { + # Split into octets. + my @a = split /\./, $a->{ip_addr}; + my @b = split /\./, $b->{ip_addr}; + # Compare octets. + foreach $n (0..3) { + return $a[$n] <=> $b[$n] if ($a[$n] != $b[$n]); + } + # If we get here there is no difference. + return 0; +} + +# Output HTML file. +sub write_output { + # Create new template object. + my $template = Template->new( + { + ABSOLUTE => 1, # /var/www/... is an absolute path + OUTPUT => $html_output_file # put it here, not STDOUT + } + ); + $data->{'updated'} = nice_time(time); # add "(updated ...)" to file + unless ($template->process($html_template_file, $data)) { # do it + warn "write_output: Template Toolkit error: " . $template->error() . "\n"; + return 0; + } + print "write_output: page updated.\n"; + return 1; +} + +sub show_license { + while (<DATA>) { + print; + $line++; + if ($line == 24) { <>; $line = 1; } + } +} + +# Main loop. +while (1) { + # Check for state change. + if (check_state()) { + read_state(); + sleep 1; # Sleep for a second just so we don't wear anything + # out. (By not sleeping the whole time after a change + # we can detect rapid changes more easily--like if 300 + # hosts all come back online, they show up quicker.) + } else { + sleep $wait_time; # Take a nap. + } + write_output(); # Write the file anyway. +} +__DATA__ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/contrib/dnslist/dnslist.tt2 b/contrib/dnslist/dnslist.tt2 new file mode 100644 index 0000000..1998e5f --- /dev/null +++ b/contrib/dnslist/dnslist.tt2 @@ -0,0 +1,32 @@ +<html> + <head> + <title>DHCP Clients</title> + <link rel="stylesheet" href="dhcp.css"/> + <meta http-equiv="Refresh" content="2"/> + </head> + <body> + <h1>DHCP Clients <span class="updated">(updated [% updated %])</span></h1> + <table cols="7"> + <tr> + <th class="hostname">Hostname</th> + <th class="ip_addr">IP Address</th> + <th class="ether_addr">Ethernet Address</th> + <th class="client_id">DHCP Client ID</th> + <th class="status">Status</th> + <th class="since">Since</th> + <th class="lease">Lease Expires</th> + </tr> + [% FOREACH host IN hosts %] + <tr class="[% IF host.online %]online[% ELSE %]offline[% END %]"> + <td class="hostname">[% host.hostname %]</td> + <td class="ip_addr">[% host.ip_addr %]</td> + <td class="ether_addr">[% host.ether_addr %]</td> + <td class="client_id">[% host.text_client_id %] ([% host.raw_client_id %])</td> + <td class="status">[% IF host.online %]Online[% ELSE %]Offline[% END %]</td> + <td class="since">[% host.since %]</td> + <td class="lease">[% host.text_lease %]</td> + </tr> + [% END %] + </table> + </body> +</html> diff --git a/dnsmasq-rh.spec b/dnsmasq-rh.spec index 4ec82dd..787886e 100644 --- a/dnsmasq-rh.spec +++ b/dnsmasq-rh.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.9 +Version: 2.10 Release: 1 Copyright: GPL Group: System Environment/Daemons diff --git a/dnsmasq-suse.spec b/dnsmasq-suse.spec index 147ff07..bc7c34e 100644 --- a/dnsmasq-suse.spec +++ b/dnsmasq-suse.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.9 +Version: 2.10 Release: 1 Copyright: GPL Group: Productivity/Networking/DNS/Servers @@ -16,7 +16,7 @@ Provides: dns_daemon Conflicts: bind bind8 bind9 PreReq: %fillup_prereq %insserv_prereq Autoreqprov: on -Source0: %{name}-%{version}.tar.gz +Source0: %{name}-%{version}.tar.bz2 BuildRoot: /var/tmp/%{name}-%{version} Summary: A lightweight caching nameserver @@ -39,6 +39,8 @@ leases and BOOTP for network booting of diskless machines. %prep %setup -q +patch -p0 <rpm/%{name}-SuSE.patch + %build %{?suse_update_config:%{suse_update_config -f}} make @@ -73,19 +73,29 @@ Print the version number. Listen on <port> instead of the standard DNS port (53). Useful mainly for debugging. .TP +.B \-P, --edns-packet-max=<size> +Specify the largest EDNS.0 UDP packet which is supported by the DNS +forwarder. Defaults to 1280, which is the RFC2671-recommended maximum +for ethernet. +.TP .B \-Q, --query-port=<query_port> Send outbound DNS queries from, and listen for their replies on, the specific UDP port <query_port> instead of using one chosen at runtime. Useful to simplify your firewall rules; without this, your firewall would have to allow connections from outside DNS servers to a range of UDP ports, or dynamically adapt to the port being used by the current dnsmasq instance. .TP .B \-i, --interface=<interface name> -Listen only on the specified interface. More than one interface may be specified. Dnsmasq always listens on the loopback (local) interface. If no -.B \-i -flags are given, dnsmasq listens on all available interfaces unless overridden by -.B \-a +Listen only on the specified interface(s). Dnsmasq automatically adds +the loopback (local) interface to the list of interfaces to use when +the +.B \--interface +option is used. If no +.B \--interface or -.B \-I -flags. If IP alias interfaces (eg "eth1:0") are used with +.B \--listen-address +options are given dnsmasq listens on all available interfaces except any +given in +.B \--except-interface +options. If IP alias interfaces (eg "eth1:0") are used with .B --interface or .B --except-interface @@ -95,21 +105,30 @@ 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. +Do not listen on the specified interface. Note that the order of +.B \--listen-address +.B --interface +and +.B --except-interface +options does not matter and that +.B --except-interface +options always override the others. .TP .B \-a, --listen-address=<ipaddr> -Listen only on the given IP address. As with -.B \-i -more than one address may be specified. Unlike -.B \-i -the loopback interface is not special: if dnsmasq is to listen on the loopback interface, -it's IP, 127.0.0.1, must be explicitly given. If no -.B \-a -flags are given, dnsmasq listens on all available interfaces unless overridden by -.B \-i -or -.B \-I -flags. +Listen on the given IP address(es). Both +.B \--interface +and +.B \--listen-address +options may be given, in which case the set of both interfaces and +addresses is used. Note that if no +.B \--interface +option is given, but +.B \--listen-address +is, dnsmasq will not automatically listen on the loopback +interface. To achieve this, its IP address, 127.0.0.1, must be +explicitly given as a +.B \--listen-address +option. .TP .B \-z, --bind-interfaces On systems which support it, dnsmasq binds the wildcard address, @@ -126,7 +145,8 @@ broadcast packets. .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. +which are not found in /etc/hosts or the DHCP leases file are answered +with "no such domain" rather than being forwarded upstream. .TP .B \-V, --alias=<old-ip>,<new-ip>[,<mask>] Modify IPv4 addresses returned from upstream nameservers; old-ip is @@ -18,9 +18,11 @@ connected to the internet via a modem, cable-modem or ADSL connection but would be a good choice for any small network where low resource use and ease of configuration are important. <P> +Supported platforms include Linux (with glibc and uclibc), *BSD and +Mac OS X. Dnsmasq is included in at least the following Linux distributions: Gentoo, Debian, Slackware, Suse, -Smoothwall, IP-Cop, floppyfw, Firebox, Freesco, CoyoteLinux and +Smoothwall, IP-Cop, floppyfw, Firebox, LEAF, Freesco, CoyoteLinux and Clarkconnect. It is also available as a FreeBSD port and is used in Linksys wireless routers. <P> Dnsmasq provides the following features: diff --git a/rpm/dnsmasq-SuSE.patch b/rpm/dnsmasq-SuSE.patch new file mode 100644 index 0000000..faf15e4 --- /dev/null +++ b/rpm/dnsmasq-SuSE.patch @@ -0,0 +1,42 @@ +--- dnsmasq.8 2004-06-21 21:55:47.000000000 +0200 ++++ dnsmasq.8 2004-06-22 23:30:18.000000000 +0200 +@@ -63,7 +63,7 @@ + .TP + .B \-g, --group=<groupname> + Specify the group which dnsmasq will run +-as. The defaults to "dip", if available, to facilitate access to ++as. The defaults to "dialout", if available, to facilitate access to + /etc/ppp/resolv.conf which is not normally world readable. + .TP + .B \-v, --version +--- dnsmasq.conf.example 2004-05-26 12:59:56.000000000 +0200 ++++ dnsmasq.conf.example 2004-06-22 23:32:36.000000000 +0200 +@@ -62,7 +62,7 @@ + + # You no longer (as of version 1.7) need to set these to enable + # dnsmasq to read /etc/ppp/resolv.conf since dnsmasq now uses the +-# "dip" group to achieve this. ++# "dialout" group to achieve this. + #user= + #group= + +--- src/config.h 2004-06-22 21:14:50.000000000 +0200 ++++ src/config.h 2004-06-22 23:31:46.000000000 +0200 +@@ -38,7 +38,7 @@ + #endif + #define DEFLEASE 3600 /* default lease time, 1 hour */ + #define CHUSER "nobody" +-#define CHGRP "dip" ++#define CHGRP "dialout" + #define IP6INTERFACES "/proc/net/if_inet6" + #define UPTIME "/proc/uptime" + #define DHCP_SERVER_PORT 67 +@@ -171,7 +171,7 @@ + + /* platform independent options. */ + #undef HAVE_BROKEN_RTC +-#define HAVE_ISC_READER ++#undef HAVE_ISC_READER + + #if defined(HAVE_BROKEN_RTC) && defined(HAVE_ISC_READER) + # error HAVE_ISC_READER is not compatible with HAVE_BROKEN_RTC diff --git a/src/config.h b/src/config.h index a2f13da..b072a48 100644 --- a/src/config.h +++ b/src/config.h @@ -12,10 +12,13 @@ /* Author's email: simon@thekelleys.org.uk */ -#define VERSION "2.9" +#define VERSION "2.10" #define FTABSIZ 150 /* max number of outstanding requests */ -#define TIMEOUT 20 /* drop queries after TIMEOUT seconds */ +#define MAX_PROCS 20 /* max no children for TCP requests */ +#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ +#define EDNS_PKTSZ 1280 /* default max EDNS.0 UDP packet from RFC2671 */ +#define TIMEOUT 20 /* drop UDP queries after TIMEOUT seconds */ #define LOGRATE 120 /* log table overflows every LOGRATE seconds */ #define CACHESIZ 150 /* default cache size */ #define MAXTOK 50 /* token in DHCP leases */ @@ -31,9 +34,12 @@ #define RUNFILE "/var/run/dnsmasq.pid" #if defined(__FreeBSD__) || defined (__OpenBSD__) # define LEASEFILE "/var/db/dnsmasq.leases" -# define CONFFILE "/usr/local/etc/dnsmasq.conf" #else # define LEASEFILE "/var/lib/misc/dnsmasq.leases" +#endif +#if defined(__FreeBSD__) +# define CONFFILE "/usr/local/etc/dnsmasq.conf" +#else # define CONFFILE "/etc/dnsmasq.conf" #endif #define DEFLEASE 3600 /* default lease time, 1 hour */ @@ -63,7 +69,7 @@ /* We assume that systems which don't have IPv6 headers don't have ntop and pton either */ -#if defined(INET6_ADDRSTRLEN) && !defined(NO_IPV6) +#if defined(INET6_ADDRSTRLEN) && defined(IPV6_V6ONLY) && !defined(NO_IPV6) # define HAVE_IPV6 # define ADDRSTRLEN INET6_ADDRSTRLEN # if defined(SOL_IPV6) @@ -89,7 +95,6 @@ new system, you may want to edit these. May replace this with Autoconf one day. - HAVE_LINUX_IPV6_PROC define this to do IPv6 interface discovery using proc/net/if_inet6 ala LINUX. @@ -368,28 +368,25 @@ int address_available(struct dhcp_context *context, struct in_addr taddr) if (addr > end) return 0; - if (lease_find_by_addr(taddr)) - return 0; - return 1; } int address_allocate(struct dhcp_context *context, struct dhcp_config *configs, struct in_addr *addrp, unsigned char *hwaddr) { - /* Find a free address: exlude 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 */ struct dhcp_config *config; struct in_addr start, addr ; - int i, j; + unsigned int i, j; /* start == end means no dynamic leases. */ if (context->end.s_addr == context->start.s_addr) return 0; /* pick a seed based on hwaddr then iterate until we find a free address. */ - for (j = 0, i = 0; i < ETHER_ADDR_LEN; i++) + for (j = context->addr_epoch, i = 0; i < ETHER_ADDR_LEN; i++) j += hwaddr[i] + (hwaddr[i] << 8) + (hwaddr[i] << 16); start.s_addr = addr.s_addr = diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 7ace797..bd2b72a 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -10,13 +10,11 @@ GNU General Public License for more details. */ -/* See RFC1035 for details of the protocol this code talks. */ - /* Author's email: simon@thekelleys.org.uk */ #include "dnsmasq.h" -static int sigterm, sighup, sigusr1, sigalarm; +static int sigterm, sighup, sigusr1, sigalarm, num_kids, in_child; static void sig_handler(int sig) { @@ -27,7 +25,22 @@ static void sig_handler(int sig) else if (sig == SIGUSR1) sigusr1 = 1; else if (sig == SIGALRM) - sigalarm = 1; + { + /* alarm is used to kill children after a fixed time. */ + if (in_child) + exit(0); + else + sigalarm = 1; + } + else if (sig == SIGCHLD) + { + /* See Stevens 5.10 */ + pid_t pid; + int stat; + + while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) + num_kids--; + } } int main (int argc, char **argv) @@ -35,6 +48,7 @@ int main (int argc, char **argv) int cachesize = CACHESIZ; int port = NAMESERVER_PORT; int maxleases = MAXLEASES; + unsigned short edns_pktsz = EDNS_PKTSZ; int query_port = 0; int first_loop = 1; int bind_fallback = 0; @@ -82,6 +96,8 @@ int main (int argc, char **argv) #else sigalarm = 0; /* or not */ #endif + num_kids = 0; + in_child = 0; sigact.sa_handler = sig_handler; sigact.sa_flags = 0; @@ -90,6 +106,11 @@ int main (int argc, char **argv) sigaction(SIGHUP, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); sigaction(SIGALRM, &sigact, NULL); + sigaction(SIGCHLD, &sigact, NULL); + + /* ignore SIGPIPE */ + sigact.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sigact, NULL); /* now block all the signals, they stay that way except during the call to pselect */ @@ -97,6 +118,7 @@ int main (int argc, char **argv) sigaddset(&sigact.sa_mask, SIGTERM); sigaddset(&sigact.sa_mask, SIGHUP); sigaddset(&sigact.sa_mask, SIGALRM); + sigaddset(&sigact.sa_mask, SIGCHLD); sigprocmask(SIG_BLOCK, &sigact.sa_mask, &sigmask); /* These get allocated here to avoid overflowing the small stack @@ -104,7 +126,6 @@ int main (int argc, char **argv) maximal sixed domain name and gets passed into all the processing code. We manage to get away with one buffer. */ dnamebuff = safe_malloc(MAXDNAME); - packet = safe_malloc(DNSMASQ_PACKETSZ); dhcp_next_server.s_addr = 0; options = read_opts(argc, argv, dnamebuff, &resolv, &mxnames, &mxtarget, &lease_file, @@ -113,7 +134,11 @@ int main (int argc, char **argv) &serv_addrs, &cachesize, &port, &query_port, &local_ttl, &addn_hosts, &dhcp, &dhcp_configs, &dhcp_options, &dhcp_vendors, &dhcp_file, &dhcp_sname, &dhcp_next_server, &maxleases, &min_leasetime, - &doctors); + &doctors, &edns_pktsz); + + if (edns_pktsz < PACKETSZ) + edns_pktsz = PACKETSZ; + packet = safe_malloc(edns_pktsz > DNSMASQ_PACKETSZ ? edns_pktsz : DNSMASQ_PACKETSZ); if (!lease_file) { @@ -136,7 +161,7 @@ int main (int argc, char **argv) if (options & OPT_NOWILD) { struct iname *if_tmp; - listeners = create_bound_listeners(interfaces); + listeners = create_bound_listeners(interfaces, port); for (if_tmp = if_names; if_tmp; if_tmp = if_tmp->next) if (if_tmp->name && !if_tmp->used) @@ -184,6 +209,31 @@ int main (int argc, char **argv) dhcp_init(&dhcpfd, &dhcp_raw_fd); leasefd = lease_init(lease_file, domain_suffix, dnamebuff, packet, now, maxleases); } + + /* If query_port is set then create a socket now, before dumping root + for use to access nameservers without more specific source addresses. + This allows query_port to be a low port */ + if (query_port) + { + union mysockaddr addr; + addr.in.sin_family = AF_INET; + addr.in.sin_addr.s_addr = INADDR_ANY; + addr.in.sin_port = htons(query_port); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in.sin_len = sizeof(struct sockaddr_in); +#endif + allocate_sfd(&addr, &sfds); +#ifdef HAVE_IPV6 + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_addr = in6addr_any; + addr.in6.sin6_port = htons(query_port); + addr.in6.sin6_flowinfo = htonl(0); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); +#endif + allocate_sfd(&addr, &sfds); +#endif + } setbuf(stdout, NULL); @@ -221,8 +271,12 @@ int main (int argc, char **argv) for (i=0; i<64; i++) { for (listener = listeners; listener; listener = listener->next) - if (listener->fd == i) - break; + { + if (listener->fd == i) + break; + if (listener->tcpfd == i) + break; + } if (listener) continue; @@ -232,6 +286,12 @@ int main (int argc, char **argv) i == dhcp_raw_fd) continue; + for (serverfdp = sfds; serverfdp; serverfdp = serverfdp->next) + if (serverfdp->fd == i) + break; + if (serverfdp) + continue; + close(i); } @@ -256,13 +316,13 @@ int main (int argc, char **argv) DNSMASQ_LOG_OPT(options & OPT_DEBUG), DNSMASQ_LOG_FAC(options & OPT_DEBUG)); - if (cachesize) + if (cachesize != 0) syslog(LOG_INFO, "started, version %s cachesize %d", VERSION, cachesize); else syslog(LOG_INFO, "started, version %s cache disabled", VERSION); if (bind_fallback) - syslog(LOG_WARNING, "setting --bind-interfaces option because if OS limitations"); + syslog(LOG_WARNING, "setting --bind-interfaces option because of OS limitations"); for (dhcp_tmp = dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next) { @@ -270,7 +330,15 @@ int main (int argc, char **argv) if (dhcp_tmp->lease_time == 0) sprintf(packet, "infinite"); else - sprintf(packet, "%ds", (int)dhcp_tmp->lease_time); + { + unsigned int x, p = 0, t = (unsigned int)dhcp_tmp->lease_time; + if ((x = t/3600)) + p += sprintf(&packet[p], "%dh", x); + if ((x = (t/60)%60)) + p += sprintf(&packet[p], "%dm", x); + if ((x = t%60)) + p += sprintf(&packet[p], "%ds", x); + } syslog(LOG_INFO, dhcp_tmp->start.s_addr == dhcp_tmp->end.s_addr ? "DHCP, static leases only on %.0s%s, lease time %s" : @@ -282,9 +350,9 @@ int main (int argc, char **argv) if (dhcp) syslog(LOG_INFO, "DHCP, %s will be written every %ds", lease_file, min_leasetime/3); #endif - - if (getuid() == 0 || geteuid() == 0) - syslog(LOG_WARNING, "failed to drop root privs"); + + if (!(options & OPT_DEBUG) && (getuid() == 0 || geteuid() == 0)) + syslog(LOG_WARNING, "running as root"); servers = check_servers(serv_addrs, interfaces, &sfds); last_server = NULL; @@ -350,6 +418,9 @@ int main (int argc, char **argv) FD_SET(listener->fd, &rset); if (listener->fd > maxfd) maxfd = listener->fd; + FD_SET(listener->tcpfd, &rset); + if (listener->tcpfd > maxfd) + maxfd = listener->tcpfd; } if (dhcp) @@ -428,7 +499,8 @@ int main (int argc, char **argv) for (serverfdp = sfds; serverfdp; serverfdp = serverfdp->next) if (FD_ISSET(serverfdp->fd, &rset)) last_server = reply_query(serverfdp, options, packet, now, - dnamebuff, servers, last_server, bogus_addr, doctors); + dnamebuff, servers, last_server, + bogus_addr, doctors, edns_pktsz); if (dhcp && FD_ISSET(dhcpfd, &rset)) dhcp_packet(dhcp, packet, dhcp_options, dhcp_configs, dhcp_vendors, @@ -437,10 +509,105 @@ int main (int argc, char **argv) if_names, if_addrs, if_except); for (listener = listeners; listener; listener = listener->next) - if (FD_ISSET(listener->fd, &rset)) - last_server = receive_query(listener, packet, - mxnames, mxtarget, options, now, local_ttl, dnamebuff, - if_names, if_addrs, if_except, last_server, servers); + { + if (FD_ISSET(listener->fd, &rset)) + last_server = receive_query(listener, packet, + mxnames, mxtarget, options, now, local_ttl, dnamebuff, + if_names, if_addrs, if_except, last_server, servers, edns_pktsz); + + if (FD_ISSET(listener->tcpfd, &rset)) + { + int confd; + + while((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR); + + if (confd != -1) + { + int match = 1; + if (!(options & OPT_NOWILD)) + { + /* Check for allowed interfaces when binding the wildcard address */ + /* Don't know how to get interface of a connection, so we have to + check by address. This will break when interfaces change address */ + union mysockaddr tcp_addr; + socklen_t tcp_len = sizeof(union mysockaddr); + struct iname *tmp; + + if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) != -1) + { +#ifdef HAVE_IPV6 + if (tcp_addr.sa.sa_family == AF_INET6) + tcp_addr.in6.sin6_flowinfo = htonl(0); +#endif + for (match = 1, tmp = if_except; tmp; tmp = tmp->next) + if (sockaddr_isequal(&tmp->addr, &tcp_addr)) + match = 0; + + if (match && (if_names || if_addrs)) + { + match = 0; + for (tmp = if_names; tmp; tmp = tmp->next) + if (sockaddr_isequal(&tmp->addr, &tcp_addr)) + match = 1; + for (tmp = if_addrs; tmp; tmp = tmp->next) + if (sockaddr_isequal(&tmp->addr, &tcp_addr)) + match = 1; + } + } + } + + if (!match || (num_kids >= MAX_PROCS)) + close(confd); + else if (!(options & OPT_DEBUG) && fork()) + { + num_kids++; + close(confd); + } + else + { + char *buff; + struct server *s; + int flags; + + /* Arrange for SIGALARM after CHILD_LIFETIME seconds to + terminate the process. */ + if (!(options & OPT_DEBUG)) + { + sigemptyset(&sigact.sa_mask); + sigaddset(&sigact.sa_mask, SIGALRM); + sigprocmask(SIG_UNBLOCK, &sigact.sa_mask, NULL); + alarm(CHILD_LIFETIME); + in_child = 1; + } + + /* start with no upstream connections. */ + for (s = servers; s; s = s->next) + s->tcpfd = -1; + + /* The connected socket inherits non-blocking + attribute from the listening socket. + Reset that here. */ + if ((flags = fcntl(confd, F_GETFL, 0)) != -1) + fcntl(confd, F_SETFL, flags & ~O_NONBLOCK); + + buff = tcp_request(confd, mxnames, mxtarget, options, now, + local_ttl, dnamebuff, last_server, servers, + bogus_addr, doctors, edns_pktsz); + + + if (!(options & OPT_DEBUG)) + exit(0); + + close(confd); + if (buff) + free(buff); + for (s = servers; s; s = s->next) + if (s->tcpfd != -1) + close(s->tcpfd); + } + } + } + } } syslog(LOG_INFO, "exiting on receipt of SIGTERM"); diff --git a/src/dnsmasq.h b/src/dnsmasq.h index cf8d1e7..1f0394a 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -11,7 +11,8 @@ */ /* Author's email: simon@thekelleys.org.uk */ - + +#define COPYRIGHT "Copyright (C) 2000-2004 Simon Kelley" #ifdef __linux__ /* for pselect.... */ @@ -36,6 +37,7 @@ #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/select.h> +#include <sys/wait.h> #if defined(__sun) || defined(__sun__) # include <sys/sockio.h> #endif @@ -194,7 +196,7 @@ struct server { struct serverfd *sfd; /* non-NULL if this server has its own fd bound to a source port */ char *domain; /* set if this server only handles a domain. */ - int flags; + int flags, tcpfd; struct server *next; }; @@ -204,7 +206,7 @@ struct irec { }; struct listener { - int fd, family; + int fd, tcpfd, family; struct listener *next; }; @@ -285,7 +287,7 @@ struct dhcp_vendor { }; struct dhcp_context { - unsigned int lease_time; + unsigned int lease_time, addr_epoch; struct in_addr netmask, broadcast; struct in_addr start, end; /* range of available addresses */ struct dhcp_netid netid; @@ -346,9 +348,10 @@ void extract_addresses(HEADER *header, unsigned int qlen, char *namebuff, void extract_neg_addrs(HEADER *header, unsigned int qlen, char *namebuff, time_t now); int answer_request(HEADER *header, char *limit, unsigned int qlen, struct mx_record *mxnames, char *mxtarget, unsigned int options, time_t now, unsigned long local_ttl, - char *namebuff); + char *namebuff, unsigned short edns_pcktsz); int check_for_bogus_wildcard(HEADER *header, unsigned int qlen, char *name, struct bogus_addr *addr, time_t now); +unsigned char *find_pseudoheader(HEADER *header, unsigned int plen); /* util.c */ unsigned short rand16(void); @@ -376,20 +379,30 @@ unsigned int read_opts(int argc, char **argv, char *buff, struct resolvc **resol struct dhcp_context **dhcp, struct dhcp_config **dhcp_conf, struct dhcp_opt **opts, struct dhcp_vendor **dhcp_vendors, char **dhcp_file, char **dhcp_sname, struct in_addr *dhcp_next_server, - int *maxleases, unsigned int *min_leasetime, struct doctor **doctors); + int *maxleases, unsigned int *min_leasetime, struct doctor **doctors, + unsigned short *edns_pktsz); /* forward.c */ void forward_init(int first); struct server *reply_query(struct serverfd *sfd, int options, char *packet, time_t now, char *dnamebuff, struct server *servers, struct server *last_server, - struct bogus_addr *bogus_nxdomain, struct doctor *doctors); + struct bogus_addr *bogus_nxdomain, + struct doctor *doctors, unsigned short edns_pcktsz); struct server *receive_query(struct listener *listen, char *packet, struct mx_record *mxnames, char *mxtarget, unsigned int options, time_t now, unsigned long local_ttl, char *namebuff, struct iname *names, struct iname *addrs, struct iname *except, - struct server *last_server, struct server *servers); + struct server *last_server, struct server *servers, unsigned short edns_pcktsz); + +char *tcp_request(int confd, struct mx_record *mxnames, + char *mxtarget, unsigned int options, time_t now, + unsigned long local_ttl, char *namebuff, + struct server *last_server, struct server *servers, + struct bogus_addr *bogus_nxdomain, struct doctor *doctors, + unsigned short edns_pcktsz); /* network.c */ +struct serverfd *allocate_sfd(union mysockaddr *addr, struct serverfd **sfds); struct server *reload_servers(char *fname, char *buff, struct server *servers, int query_port); struct server *check_servers(struct server *new, struct irec *interfaces, struct serverfd **sfds); struct irec *enumerate_interfaces(struct iname **names, @@ -397,7 +410,7 @@ struct irec *enumerate_interfaces(struct iname **names, struct iname *except, int port); struct listener *create_wildcard_listeners(int port); -struct listener *create_bound_listeners(struct irec *interfaces); +struct listener *create_bound_listeners(struct irec *interfaces, int port); /* dhcp.c */ void dhcp_init(int *fdp, int* rfdp); void dhcp_packet(struct dhcp_context *contexts, char *packet, @@ -448,3 +461,4 @@ int dhcp_reply(struct dhcp_context *context, #ifdef HAVE_ISC_READER void load_dhcp(char *file, char *suffix, time_t now, char *hostname); #endif + diff --git a/src/forward.c b/src/forward.c index fbf8790..4d2354a 100644 --- a/src/forward.c +++ b/src/forward.c @@ -26,7 +26,7 @@ static unsigned short get_id(void); void forward_init(int first) { struct frec *f; - + if (first) frec_list = NULL; for (f = frec_list; f; f = f->next) @@ -40,7 +40,6 @@ static void send_from(int fd, int nowild, char *packet, int len, { struct msghdr msg; struct iovec iov[1]; - struct cmsghdr *cmptr; union { struct cmsghdr align; /* this ensures alignment */ #if defined(IP_PKTINFO) @@ -52,64 +51,143 @@ static void send_from(int fd, int nowild, char *packet, int len, char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; #endif } control_u; - + iov[0].iov_base = packet; iov[0].iov_len = len; - if (nowild) - { - msg.msg_control = NULL; - msg.msg_controllen = 0; - } - else - { - msg.msg_control = &control_u; - msg.msg_controllen = sizeof(control_u); - } + msg.msg_control = NULL; + msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = to; msg.msg_namelen = sa_len(to); msg.msg_iov = iov; msg.msg_iovlen = 1; - - cmptr = CMSG_FIRSTHDR(&msg); - -#if defined(IP_PKTINFO) + if (!nowild && to->sa.sa_family == AF_INET) { - struct in_pktinfo *pkt = (struct in_pktinfo *)CMSG_DATA(cmptr); - pkt->ipi_ifindex = 0; - pkt->ipi_spec_dst = source->addr.addr4; - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - cmptr->cmsg_level = SOL_IP; - cmptr->cmsg_type = IP_PKTINFO; - } + msg.msg_control = &control_u; + msg.msg_controllen = sizeof(control_u); + { + struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); +#if defined(IP_PKTINFO) + struct in_pktinfo *pkt = (struct in_pktinfo *)CMSG_DATA(cmptr); + pkt->ipi_ifindex = 0; + pkt->ipi_spec_dst = source->addr.addr4; + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmptr->cmsg_level = SOL_IP; + cmptr->cmsg_type = IP_PKTINFO; #elif defined(IP_SENDSRCADDR) - if (!nowild && to->sa.sa_family == AF_INET) - { - struct in_addr *a = (struct in_addr *)CMSG_DATA(cmptr); - *a = source->addr.addr4; - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); - cmptr->cmsg_level = IPPROTO_IP; - cmptr->cmsg_type = IP_SENDSRCADDR; - } + struct in_addr *a = (struct in_addr *)CMSG_DATA(cmptr); + *a = source->addr.addr4; + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + cmptr->cmsg_level = IPPROTO_IP; + cmptr->cmsg_type = IP_SENDSRCADDR; #endif + } + } #ifdef HAVE_IPV6 - if (!nowild && to->sa.sa_family == AF_INET6) + if (to->sa.sa_family == AF_INET6) { - struct in6_pktinfo *pkt = (struct in6_pktinfo *)CMSG_DATA(cmptr); - pkt->ipi6_ifindex = 0; - pkt->ipi6_addr = source->addr.addr6; - msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); - cmptr->cmsg_type = IPV6_PKTINFO; - cmptr->cmsg_level = IPV6_LEVEL; + msg.msg_control = &control_u; + msg.msg_controllen = sizeof(control_u); + { + struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); + struct in6_pktinfo *pkt = (struct in6_pktinfo *)CMSG_DATA(cmptr); + pkt->ipi6_ifindex = 0; + pkt->ipi6_addr = source->addr.addr6; + msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmptr->cmsg_type = IPV6_PKTINFO; + cmptr->cmsg_level = IPV6_LEVEL; + } } #endif - - sendmsg(fd, &msg, 0); + + /* certain Linux kernels seem to object to setting the source address in the IPv6 stack + by returning EINVAL from sendmsg. In that case, try again without setting the + source address, since it will nearly alway be correct anyway. IPv6 stinks. */ + if (sendmsg(fd, &msg, 0) == -1 && errno == EINVAL) + { + msg.msg_controllen = 0; + sendmsg(fd, &msg, 0); + } } +unsigned short search_servers(struct server *servers, unsigned int options, struct all_addr **addrpp, + unsigned short qtype, char *qdomain, int *type, char **domain) + +{ + /* If the query ends in the domain in one of our servers, set + domain to point to that name. We find the largest match to allow both + domain.org and sub.domain.org to exist. */ + + unsigned int namelen = strlen(qdomain); + unsigned int matchlen = 0; + struct server *serv; + unsigned short flags = 0; + + for (serv=servers; serv; serv=serv->next) + /* domain matches take priority over NODOTS matches */ + if ((serv->flags & SERV_FOR_NODOTS) && *type != SERV_HAS_DOMAIN && !strchr(qdomain, '.')) + { + unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; + *type = SERV_FOR_NODOTS; + flags = 0; + if (serv->flags & SERV_NO_ADDR) + flags = F_NOERR; + else if ((serv->flags & SERV_LITERAL_ADDRESS) && (sflag & qtype)) + { + flags = sflag; + if (serv->addr.sa.sa_family == AF_INET) + *addrpp = (struct all_addr *)&serv->addr.in.sin_addr; +#ifdef HAVE_IPV6 + else + *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr; +#endif + } + } + else if (serv->flags & SERV_HAS_DOMAIN) + { + unsigned int domainlen = strlen(serv->domain); + if (namelen >= domainlen && + hostname_isequal(qdomain + namelen - domainlen, serv->domain) && + domainlen >= matchlen) + { + unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; + *type = SERV_HAS_DOMAIN; + *domain = serv->domain; + matchlen = domainlen; + flags = 0; + if (serv->flags & SERV_NO_ADDR) + flags = F_NOERR; + else if ((serv->flags & SERV_LITERAL_ADDRESS) && ((sflag | F_QUERY ) & qtype)) + { + flags = qtype; + if (serv->addr.sa.sa_family == AF_INET) + *addrpp = (struct all_addr *)&serv->addr.in.sin_addr; +#ifdef HAVE_IPV6 + else + *addrpp = (struct all_addr *)&serv->addr.in6.sin6_addr; +#endif + } + } + } + + if (flags & ~F_NOERR) /* flags set here means a literal found */ + { + if (flags & F_QUERY) + log_query(F_CONFIG | F_FORWARD | F_NEG, qdomain, NULL); + else + log_query(F_CONFIG | F_FORWARD | flags, qdomain, *addrpp); + } + else if (qtype && (options & OPT_NODOTS_LOCAL) && !strchr(qdomain, '.')) + flags = F_NXDOMAIN; + + if (flags & (F_NOERR | F_NXDOMAIN)) + log_query(F_CONFIG | F_FORWARD | F_NEG | qtype | (flags & F_NXDOMAIN), qdomain, NULL); + + return flags; +} /* returns new last_server */ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, @@ -146,75 +224,11 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, else { if (gotname) - { - /* If the query ends in the domain in one of our servers, set - domain to point to that name. We find the largest match to allow both - domain.org and sub.domain.org to exist. */ - - unsigned int namelen = strlen(dnamebuff); - unsigned int matchlen = 0; - struct server *serv; - - for (serv=servers; serv; serv=serv->next) - /* domain matches take priority over NODOTS matches */ - if ((serv->flags & SERV_FOR_NODOTS) && type != SERV_HAS_DOMAIN && !strchr(dnamebuff, '.')) - { - unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; - type = SERV_FOR_NODOTS; - flags = 0; - if ((serv->flags & SERV_LITERAL_ADDRESS) && (sflag & gotname)) - { - flags = sflag; - if (serv->addr.sa.sa_family == AF_INET) - addrp = (struct all_addr *)&serv->addr.in.sin_addr; -#ifdef HAVE_IPV6 - else - addrp = (struct all_addr *)&serv->addr.in6.sin6_addr; -#endif - } - } - else if (serv->flags & SERV_HAS_DOMAIN) - { - unsigned int domainlen = strlen(serv->domain); - if (namelen >= domainlen && - hostname_isequal(dnamebuff + namelen - domainlen, serv->domain) && - domainlen >= matchlen) - { - unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; - type = SERV_HAS_DOMAIN; - domain = serv->domain; - matchlen = domainlen; - flags = 0; - if ((serv->flags & SERV_LITERAL_ADDRESS) && ((sflag | F_QUERY ) & gotname)) - { - flags = gotname; - if (serv->addr.sa.sa_family == AF_INET) - addrp = (struct all_addr *)&serv->addr.in.sin_addr; -#ifdef HAVE_IPV6 - else - addrp = (struct all_addr *)&serv->addr.in6.sin6_addr; -#endif - } - } - } - } + flags = search_servers(servers, options, &addrp, gotname, dnamebuff, &type, &domain); - if (flags) /* flags set here means a literal found */ - { - if (flags & F_QUERY) - log_query(F_CONFIG | F_FORWARD | F_NEG, dnamebuff, NULL); - else - log_query(F_CONFIG | F_FORWARD | flags, dnamebuff, addrp); - } - else - { - /* we may by policy not forward names without a domain part */ - if (gotname && (options & OPT_NODOTS_LOCAL) && !strchr(dnamebuff, '.')) - flags = F_NXDOMAIN; - else if (!(forward = get_new_frec(now))) - /* table full - server failure. */ - flags = F_NEG; - } + if (!flags && !(forward = get_new_frec(now))) + /* table full - server failure. */ + flags = F_NEG; if (forward) { @@ -229,7 +243,7 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, start = servers; forwardall = 1; } - + forward->source = *udpaddr; forward->dest = *dst_addr; forward->new_id = get_id(); @@ -238,7 +252,7 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, header->id = htons(forward->new_id); } } - + /* check for send errors here (no route to host) if we fail to send to all nameservers, send back an error packet straight away (helps modem users when offline) */ @@ -257,12 +271,10 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, if (type == (start->flags & SERV_TYPE) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain))) { - if (start->flags & SERV_NO_ADDR) - flags = F_NOERR; /* NULL servers are OK. */ - else if (!(start->flags & SERV_LITERAL_ADDRESS) && - sendto(start->sfd->fd, (char *)header, plen, 0, - &start->addr.sa, - sa_len(&start->addr)) != -1) + if (!(start->flags & SERV_LITERAL_ADDRESS) && + sendto(start->sfd->fd, (char *)header, plen, 0, + &start->addr.sa, + sa_len(&start->addr)) != -1) { if (!gotname) strcpy(dnamebuff, "query"); @@ -282,7 +294,7 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, } if (!(start = start->next)) - start = servers; + start = servers; if (start == firstsentto) break; @@ -300,16 +312,65 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, plen = setup_reply(header, (unsigned int)plen, addrp, flags, local_ttl); send_from(udpfd, options & OPT_NOWILD, (char *)header, plen, udpaddr, dst_addr); - if (flags & (F_NOERR | F_NXDOMAIN)) - log_query(F_CONFIG | F_FORWARD | F_NEG | gotname | (flags & F_NXDOMAIN), dnamebuff, NULL); - return last_server; } +static int process_reply(HEADER *header, time_t now, char *dnamebuff, struct bogus_addr *bogus_nxdomain, + struct doctor *doctors, union mysockaddr *serveraddr, + int n, int options, unsigned short edns_pcktsz) +{ + unsigned char *pheader; + + /* If upstream is advertising a larger UDP packet size + than we allow, trim it so that we don't get overlarge + requests for the client. */ + + if ((pheader = find_pseudoheader(header, n))) + { + unsigned short udpsz; + unsigned char *psave = pheader; + + GETSHORT(udpsz, pheader); + if (udpsz > edns_pcktsz) + PUTSHORT(edns_pcktsz, psave); + } + + /* Complain loudly if the upstream server is non-recursive. */ + if (!header->ra && header->rcode == NOERROR && ntohs(header->ancount) == 0) + { + char addrbuff[ADDRSTRLEN]; +#ifdef HAVE_IPV6 + if (serveraddr->sa.sa_family == AF_INET) + inet_ntop(AF_INET, &serveraddr->in.sin_addr, addrbuff, ADDRSTRLEN); + else if (serveraddr->sa.sa_family == AF_INET6) + inet_ntop(AF_INET6, &serveraddr->in6.sin6_addr, addrbuff, ADDRSTRLEN); +#else + strcpy(addrbuff, inet_ntoa(serveraddr->in.sin_addr)); +#endif + syslog(LOG_WARNING, "nameserver %s refused to do a recursive query", addrbuff); + return 0; + } + + if ((header->rcode == NOERROR || header->rcode == NXDOMAIN) && header->opcode == QUERY) + { + if (!(bogus_nxdomain && + header->rcode == NOERROR && + 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, doctors); + else if (!(options & OPT_NO_NEG)) + extract_neg_addrs(header, (unsigned int)n, dnamebuff, now); + } + } + + return 1; +} + /* returns new last_server */ struct server *reply_query(struct serverfd *sfd, int options, char *packet, time_t now, char *dnamebuff, struct server *servers, struct server *last_server, - struct bogus_addr *bogus_nxdomain, struct doctor *doctors) + struct bogus_addr *bogus_nxdomain, struct doctor *doctors, unsigned short edns_pcktsz) { /* packet from peer server, extract data for cache, and send to original requester */ @@ -317,7 +378,7 @@ struct server *reply_query(struct serverfd *sfd, int options, char *packet, time HEADER *header; union mysockaddr serveraddr; socklen_t addrlen = sizeof(serveraddr); - int n = recvfrom(sfd->fd, packet, PACKETSZ, 0, &serveraddr.sa, &addrlen); + int n = recvfrom(sfd->fd, packet, edns_pcktsz, 0, &serveraddr.sa, &addrlen); /* 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; @@ -339,41 +400,11 @@ struct server *reply_query(struct serverfd *sfd, int options, char *packet, time if (!last_server) last_server = forward->sentto; } - - /* Complain loudly if the upstream server is non-recursive. */ - if (!header->ra && header->rcode == NOERROR && ntohs(header->ancount) == 0) - { - char addrbuff[ADDRSTRLEN]; -#ifdef HAVE_IPV6 - if (serveraddr.sa.sa_family == AF_INET) - inet_ntop(AF_INET, &serveraddr.in.sin_addr, addrbuff, ADDRSTRLEN); - else if (serveraddr.sa.sa_family == AF_INET6) - inet_ntop(AF_INET6, &serveraddr.in6.sin6_addr, addrbuff, ADDRSTRLEN); -#else - strcpy(addrbuff, inet_ntoa(serveraddr.in.sin_addr)); -#endif - syslog(LOG_WARNING, "nameserver %s refused to do a recursive query", addrbuff); - return NULL; - } - if ((header->rcode == NOERROR || header->rcode == NXDOMAIN) && header->opcode == QUERY) - { - if (!(bogus_nxdomain && - header->rcode == NOERROR && - 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, doctors); - else if (!(options & OPT_NO_NEG)) - extract_neg_addrs(header, (unsigned int)n, dnamebuff, now); - } - } - + if (!process_reply(header, now, dnamebuff, bogus_nxdomain, doctors, &serveraddr, n, options, edns_pcktsz)) + return NULL; + header->id = htons(forward->orig_id); - /* There's no point returning an upstream reply marked as truncated, - since that will prod the resolver into moving to TCP - which we - don't support. */ - header->tc = 0; /* goodbye truncate */ send_from(forward->fd, options & OPT_NOWILD, packet, n, &forward->source, &forward->dest); forward->new_id = 0; /* cancel */ } @@ -385,12 +416,13 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re char *mxtarget, unsigned int options, time_t now, unsigned long local_ttl, char *namebuff, struct iname *names, struct iname *addrs, struct iname *except, - struct server *last_server, struct server *servers) + struct server *last_server, struct server *servers, unsigned short edns_pcktsz) { HEADER *header = (HEADER *)packet; union mysockaddr source_addr; struct iname *tmp; struct all_addr dst_addr; + int check_dst = !(options & OPT_NOWILD); int m, n, if_index = 0; struct iovec iov[1]; struct msghdr msg; @@ -409,7 +441,7 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re } control_u; iov[0].iov_base = packet; - iov[0].iov_len = PACKETSZ; + iov[0].iov_len = edns_pcktsz; msg.msg_control = control_u.control; msg.msg_controllen = sizeof(control_u); @@ -425,14 +457,17 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re source_addr.sa.sa_family = listen->family; #ifdef HAVE_IPV6 if (listen->family == AF_INET6) - source_addr.in6.sin6_flowinfo = htonl(0); + { + check_dst = 1; + source_addr.in6.sin6_flowinfo = htonl(0); + } #endif - if (!(options & OPT_NOWILD) && msg.msg_controllen < sizeof(struct cmsghdr)) + if (check_dst && msg.msg_controllen < sizeof(struct cmsghdr)) return last_server; #if defined(IP_PKTINFO) - if (!(options & OPT_NOWILD) && listen->family == AF_INET) + if (check_dst && listen->family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) { @@ -440,7 +475,7 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re 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) + if (check_dst && 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) @@ -451,7 +486,7 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re #endif #ifdef HAVE_IPV6 - if (!(options & OPT_NOWILD) && listen->family == AF_INET6) + if (listen->family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == IPV6_PKTINFO) @@ -466,7 +501,7 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re return last_server; /* enforce available interface configuration */ - if (!(options & OPT_NOWILD)) + if (check_dst) { struct ifreq ifr; @@ -527,7 +562,7 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re } m = answer_request (header, ((char *) header) + PACKETSZ, (unsigned int)n, - mxnames, mxtarget, options, now, local_ttl, namebuff); + mxnames, mxtarget, options, now, local_ttl, namebuff, edns_pcktsz); if (m >= 1) send_from(listen->fd, options & OPT_NOWILD, (char *)header, m, &source_addr, &dst_addr); else @@ -537,6 +572,194 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re return last_server; } +static int read_write(int fd, char *packet, int size, int rw) +{ + int n, done; + + for (done = 0; done < size; done += n) + { + retry: + if (rw) + n = read(fd, &packet[done], (size_t)(size - done)); + else + n = write(fd, &packet[done], (size_t)(size - done)); + + if (n == 0) + return 0; + else if (n == -1) + { + if (errno == EINTR) + goto retry; + else if (errno == EAGAIN) + { + struct timespec waiter; + waiter.tv_sec = 0; + waiter.tv_nsec = 10000; + nanosleep(&waiter, NULL); + goto retry; + } + else + return 0; + } + } + return 1; +} + +/* The daemon forks before calling this: it should deal with one connection, + blocking as neccessary, and then return. Note, need to be a bit careful + about resources for debug mode, when the fork is suppressed: that's + done by the caller. */ +char *tcp_request(int confd, struct mx_record *mxnames, + char *mxtarget, unsigned int options, time_t now, + unsigned long local_ttl, char *namebuff, + struct server *last_server, struct server *servers, + struct bogus_addr *bogus_nxdomain, struct doctor *doctors, + unsigned short edns_pktsz) +{ + int size = 0, m; + unsigned char c1, c2; + /* Max TCP packet + slop */ + char *packet = malloc(65536 + MAXDNAME + RRFIXEDSZ); + HEADER *header; + + while (1) + { + if (!packet || + !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || + !(size = c1 << 8 | c2) || + !read_write(confd, packet, size, 1)) + return packet; + + if (size < (int)sizeof(HEADER)) + continue; + + header = (HEADER *)packet; + + if (extract_request(header, (unsigned int)size, namebuff)) + { + union mysockaddr peer_addr; + socklen_t peer_len = sizeof(union mysockaddr); + + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) + { + if (peer_addr.sa.sa_family == AF_INET) + log_query(F_QUERY | F_IPV4 | F_FORWARD, namebuff, + (struct all_addr *)&peer_addr.in.sin_addr); +#ifdef HAVE_IPV6 + else + log_query(F_QUERY | F_IPV6 | F_FORWARD, namebuff, + (struct all_addr *)&peer_addr.in6.sin6_addr); +#endif + } + } + + /* m > 0 if answered from cache */ + m = answer_request (header, ((char *) header) + 65536, (unsigned int)size, + mxnames, mxtarget, options, now, local_ttl, namebuff, edns_pktsz); + + if (m == 0) + { + unsigned short flags = 0; + unsigned short gotname = extract_request(header, (unsigned int)size, namebuff); + struct all_addr *addrp = NULL; + int type = 0; + char *domain = NULL; + + if (gotname) + flags = search_servers(servers, options, &addrp, gotname, namebuff, &type, &domain); + + if (type != 0 || (options & OPT_ORDER) || !last_server) + last_server = servers; + + if (!flags && last_server) + { + struct server *firstsendto = NULL; + + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtley ensures that consecutive queries on this connection + which can go to the same server, do so. */ + while (1) + { + if (!firstsendto) + firstsendto = last_server; + else + { + if (!(last_server = last_server->next)) + last_server = servers; + + if (last_server == firstsendto) + break; + } + + /* server for wrong domain */ + if (type != (last_server->flags & SERV_TYPE) || + (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) + continue; + + if ((last_server->tcpfd == -1) && + (last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) != -1 && + connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + } + + if (last_server->tcpfd == -1) + continue; + + c1 = size >> 8; + c2 = size; + + if (!read_write(last_server->tcpfd, &c1, 1, 0) || + !read_write(last_server->tcpfd, &c2, 1, 0) || + !read_write(last_server->tcpfd, packet, size, 0) || + !read_write(last_server->tcpfd, &c1, 1, 1) || + !read_write(last_server->tcpfd, &c2, 1, 1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + + m = (c1 << 8) | c2; + if (!read_write(last_server->tcpfd, packet, m, 1)) + return packet; + + if (!gotname) + strcpy(namebuff, "query"); + if (last_server->addr.sa.sa_family == AF_INET) + log_query(F_SERVER | F_IPV4 | F_FORWARD, namebuff, + (struct all_addr *)&last_server->addr.in.sin_addr); +#ifdef HAVE_IPV6 + else + log_query(F_SERVER | F_IPV6 | F_FORWARD, namebuff, + (struct all_addr *)&last_server->addr.in6.sin6_addr); +#endif + + /* There's no point in updating the cache, since this process will exit and + lose the information after one query. We make this call for the alias and + bogus-nxdomain side-effects. */ + process_reply(header, now, namebuff, bogus_nxdomain, doctors, + &last_server->addr, m, options, edns_pktsz); + + break; + } + } + + /* In case of local answer or no connections made. */ + if (m == 0) + m = setup_reply(header, (unsigned int)size, addrp, flags, local_ttl); + } + + c1 = m>>8; + c2 = m; + if (!read_write(confd, &c1, 1, 0) || + !read_write(confd, &c2, 1, 0) || + !read_write(confd, packet, m, 0)) + return packet; + } +} + static struct frec *get_new_frec(time_t now) { struct frec *f = frec_list, *oldest = NULL; @@ -603,8 +826,8 @@ static struct frec *lookup_frec(unsigned short id) static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr) { - struct frec *f; - + struct frec *f; + for(f = frec_list; f; f = f->next) if (f->new_id && f->orig_id == id && diff --git a/src/lease.c b/src/lease.c index 59afe5f..fa74df4 100644 --- a/src/lease.c +++ b/src/lease.c @@ -220,9 +220,6 @@ void lease_prune(struct dhcp_lease *target, time_t now) struct dhcp_lease *lease_find_by_client(unsigned char *clid, int clid_len) { - /* zero length means clid from hwaddr: never match am option clid to - a hardware-address derived clid */ - struct dhcp_lease *lease; if (clid_len) @@ -235,8 +232,7 @@ struct dhcp_lease *lease_find_by_client(unsigned char *clid, int clid_len) else { for (lease = leases; lease; lease = lease->next) - if (!lease->clid && - memcmp(clid, lease->hwaddr, ETHER_ADDR_LEN) == 0) + if (memcmp(clid, lease->hwaddr, ETHER_ADDR_LEN) == 0) return lease; } diff --git a/src/network.c b/src/network.c index 2c0e0f4..4c8c207 100644 --- a/src/network.c +++ b/src/network.c @@ -25,8 +25,12 @@ static struct irec *add_iface(struct irec *list, char *name, union mysockaddr *a if (except) for (tmp = except; tmp; tmp = tmp->next) if (tmp->name && strcmp(tmp->name, name) == 0) - return list; - + { + /* record address of named interfaces, for TCP access control */ + tmp->addr = *addr; + return list; + } + /* we may need to check the whitelist */ if (names || addrs) { @@ -34,8 +38,11 @@ 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)) - found = tmp->used = 1; - + { + tmp->addr = *addr; + found = tmp->used = 1; + } + for (tmp = addrs; tmp; tmp = tmp->next) if (sockaddr_isequal(&tmp->addr, addr)) found = tmp->used = 1; @@ -220,6 +227,65 @@ struct irec *enumerate_interfaces(struct iname **names, return iface; } +#ifdef HAVE_IPV6 +static int create_ipv6_listener(struct listener **link, int port) +{ + union mysockaddr addr; + int tcpfd, fd, flags, save; + struct listener *l; + int opt = 1; + + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_addr = in6addr_any; + addr.in6.sin6_port = htons(port); + addr.in6.sin6_flowinfo = htonl(0); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); +#endif + + /* No error of the kernel doesn't support IPv6 */ + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) + return (errno == EPROTONOSUPPORT || + errno == EAFNOSUPPORT || + errno == EINVAL); + + if ((tcpfd = socket(AF_INET6, SOCK_STREAM, 0)) == -1) + { + save = errno; + close(fd); + errno = save; + return 0; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || + setsockopt(tcpfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || + setsockopt(fd, IPV6_LEVEL, IPV6_V6ONLY, &opt, sizeof(opt)) == -1 || + setsockopt(tcpfd, IPV6_LEVEL, IPV6_V6ONLY, &opt, sizeof(opt)) == -1 || + (flags = fcntl(tcpfd, F_GETFL, 0)) == -1 || + fcntl(tcpfd, F_SETFL, flags | O_NONBLOCK) == -1 || + setsockopt(fd, IPV6_LEVEL, IPV6_PKTINFO, &opt, sizeof(opt)) == -1 || + bind(tcpfd, (struct sockaddr *)&addr, sa_len(&addr)) == -1 || + listen(tcpfd, 5) == -1 || + bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == -1) + { + save = errno; + close(fd); + close(tcpfd); + errno = save; + return 0; + } + + l = safe_malloc(sizeof(struct listener)); + l->fd = fd; + l->tcpfd = tcpfd; + l->family = AF_INET6; + l->next = NULL; + *link = l; + + return 1; +} +#endif + struct listener *create_wildcard_listeners(int port) { #if !(defined(IP_PKTINFO) || (defined(IP_RECVDSTADDR) && defined(IP_RECVIF) && defined(IP_SENDSRCADDR))) @@ -227,10 +293,9 @@ struct listener *create_wildcard_listeners(int port) #else union mysockaddr addr; int opt = 1; - struct listener *listen; -#ifdef HAVE_IPV6 - int fd; -#endif + struct listener *l, *l6 = NULL; + int flags; + int tcpfd, fd; addr.in.sin_family = AF_INET; addr.in.sin_addr.s_addr = INADDR_ANY; @@ -238,94 +303,87 @@ struct listener *create_wildcard_listeners(int port) #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(struct sockaddr_in); #endif - listen = safe_malloc(sizeof(struct listener)); - if ((listen->fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return NULL; + + if ((tcpfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - free(listen); + close (fd); return NULL; } - if (setsockopt(listen->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || + + if (setsockopt(tcpfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || + bind(tcpfd, (struct sockaddr *)&addr, sa_len(&addr)) == -1 || + listen(tcpfd, 5) == -1 || + (flags = fcntl(tcpfd, F_GETFL, 0)) == -1 || + fcntl(tcpfd, F_SETFL, flags | O_NONBLOCK) == -1 || +#ifdef HAVE_IPV6 + !create_ipv6_listener(&l6, port) || +#endif + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || #if defined(IP_PKTINFO) - setsockopt(listen->fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1 || + setsockopt(fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1 || #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) - setsockopt(listen->fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) == -1 || - setsockopt(listen->fd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt)) == -1 || #endif - bind(listen->fd, (struct sockaddr *)&addr, sa_len(&addr)) == -1) + bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == -1) { - close(listen->fd); - free(listen); + close(fd); + close(tcpfd); return NULL; } - listen->next = NULL; - listen->family = AF_INET; -#ifdef HAVE_IPV6 - addr.in6.sin6_family = AF_INET6; - addr.in6.sin6_addr = in6addr_any; - addr.in6.sin6_port = htons(port); - addr.in6.sin6_flowinfo = htonl(0); -#ifdef HAVE_SOCKADDR_SA_LEN - addr.in6.sin6_len = sizeof(struct sockaddr_in6); -#endif - if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) - { - if (errno != EPROTONOSUPPORT && - errno != EAFNOSUPPORT && - errno != EINVAL) - { - close(listen->fd); - free(listen); - return NULL; - } - } - else - { - listen->next = safe_malloc(sizeof(struct listener)); - listen->next->fd = fd; - listen->next->family = AF_INET6; - listen->next->next = NULL; - if (setsockopt(listen->next->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || - setsockopt(listen->next->fd, IPV6_LEVEL, IPV6_PKTINFO, &opt, sizeof(opt)) == -1 || - bind(listen->next->fd, (struct sockaddr *)&addr, sa_len(&addr)) == -1) - { - close(listen->next->fd); - free(listen->next); - close(listen->fd); - free(listen); - return NULL; - } - } -#endif - - return listen; + l = safe_malloc(sizeof(struct listener)); + l->family = AF_INET; + l->fd = fd; + l->tcpfd = tcpfd; + l->next = l6; + + return l; + #endif } -struct listener *create_bound_listeners(struct irec *interfaces) +struct listener *create_bound_listeners(struct irec *interfaces, int port) { struct listener *listeners = NULL; struct irec *iface; - int opt = 1; + int flags = port, opt = 1; + + /* Create bound listeners only for IPv4, IPv6 always binds the wildcard */ - for (iface = interfaces ;iface; iface = iface->next) - { - struct listener *new = safe_malloc(sizeof(struct listener)); - new->family = iface->addr.sa.sa_family; - new->next = listeners; - listeners = new; - if ((new->fd = socket(iface->addr.sa.sa_family, SOCK_DGRAM, 0)) == -1) - die("failed to create socket: %s", NULL); - if (setsockopt(new->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || - bind(new->fd, &iface->addr.sa, sa_len(&iface->addr)) == -1) - die("failed to bind socket: %s", NULL); - } +#ifdef HAVE_IPV6 + if (!create_ipv6_listener(&listeners, port)) + die("failed to to create listening socket: %s", NULL); +#endif + for (iface = interfaces ;iface; iface = iface->next) + if (iface->addr.sa.sa_family == AF_INET) + { + struct listener *new = safe_malloc(sizeof(struct listener)); + new->family = iface->addr.sa.sa_family; + new->next = listeners; + listeners = new; + if ((new->tcpfd = socket(iface->addr.sa.sa_family, SOCK_STREAM, 0)) == -1 || + (new->fd = socket(iface->addr.sa.sa_family, SOCK_DGRAM, 0)) == -1 || + setsockopt(new->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || + setsockopt(new->tcpfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || + /* See Stevens 16.6 */ + (flags = fcntl(new->tcpfd, F_GETFL, 0)) == -1 || + fcntl(new->tcpfd, F_SETFL, flags | O_NONBLOCK) == -1 || + bind(new->tcpfd, &iface->addr.sa, sa_len(&iface->addr)) == -1 || + bind(new->fd, &iface->addr.sa, sa_len(&iface->addr)) == -1 || + listen(new->tcpfd, 5) == -1) + die("failed to to create listening socket: %s", NULL); + } + return listeners; } -static struct serverfd *allocate_sfd(union mysockaddr *addr, struct serverfd **sfds) +struct serverfd *allocate_sfd(union mysockaddr *addr, struct serverfd **sfds) { struct serverfd *sfd; diff --git a/src/option.c b/src/option.c index 8b1c51b..b2ef37a 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:V:U:j:" +#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:U:j:P:" static struct myoption opts[] = { {"version", 0, 0, 'v'}, @@ -72,6 +72,7 @@ static struct myoption opts[] = { {"alias", 1, 0, 'V' }, {"dhcp-vendorclass", 1, 0, 'U'}, {"dhcp-userclass", 1, 0, 'j'}, + {"edns-packet-max", 1, 0, 'P'}, {0, 0, 0, 0} }; @@ -132,6 +133,7 @@ static char *usage = "-o, --strict-order Use nameservers strictly in the order given in " RESOLVFILE ".\n" "-O, --dhcp-option=<optspec> Set extra options to be set to DHCP clients.\n" "-p, --port=number Specify port to listen for DNS requests on (defaults to 53).\n" +"-P, --edns-packet-max=<size> Maximum supported UDP packet size for EDNS.0 (defaults to %d).\n" "-q, --log-queries Log queries.\n" "-Q, --query-port=number Force the originating port for upstream queries.\n" "-R, --no-resolv Do NOT read resolv.conf.\n" @@ -143,7 +145,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" "-U, --dhcp-vendorclass=<id>,<class> Map DHCP vendor class to option set.\n" -"-v, --version Display dnsmasq version.\n" +"-v, --version Display dnsmasq version and copyright information.\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" @@ -161,7 +163,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, struct dhcp_vendor **dhcp_vendors, char **dhcp_file, char **dhcp_sname, struct in_addr *dhcp_next_server, int *dhcp_max, - unsigned int *min_leasetime, struct doctor **doctors) + unsigned int *min_leasetime, struct doctor **doctors, unsigned short *edns_pktsz) { int option = 0, i; unsigned int flags = 0; @@ -256,13 +258,16 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso if (!f && option == 'w') { - fprintf (stderr, usage, CACHESIZ, MAXLEASES); + fprintf (stderr, usage, CACHESIZ, EDNS_PKTSZ, MAXLEASES); exit(0); } if (!f && option == 'v') { - fprintf(stderr, "dnsmasq version %s\n", VERSION); + fprintf(stderr, "Dnsmasq version %s %s\n\n", VERSION, COPYRIGHT); + fprintf(stderr, "This software comes with ABSOLUTELY NO WARRANTY.\n"); + fprintf(stderr, "Dnsmasq is free software, and you are welcome to redistribute it\n"); + fprintf(stderr, "under the terms of the GNU General Public License, version 2.\n"); exit(0); } @@ -658,6 +663,15 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso option = '?'; break; + case 'P': + { + int i; + if (!atoi_check(optarg, &i)) + option = '?'; + *edns_pktsz = (unsigned short)i; + break; + } + case 'Q': if (!atoi_check(optarg, query_port)) option = '?'; @@ -685,7 +699,8 @@ unsigned int read_opts (int argc, char **argv, char *buff, struct resolvc **reso struct dhcp_context *new = safe_malloc(sizeof(struct dhcp_context)); new->next = *dhcp; - new->lease_time = DEFLEASE; + new->lease_time = DEFLEASE; + new->addr_epoch = 0; new->netmask.s_addr = 0; new->broadcast.s_addr = 0; new->netid.net = NULL; diff --git a/src/rfc1035.c b/src/rfc1035.c index a587426..d75a68b 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -244,52 +244,60 @@ static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) return 0; } -static unsigned char *skip_questions(HEADER *header, unsigned int plen) +static unsigned char *skip_name(unsigned char *ansp, HEADER *header, unsigned int plen) { - int q, qdcount = ntohs(header->qdcount); - unsigned char *ansp = (unsigned char *)(header+1); - - for (q=0; q<qdcount; q++) + while(1) { - while (1) + unsigned int label_type = (*ansp) & 0xc0; + + if ((unsigned int)(ansp - (unsigned char *)header) >= plen) + return NULL; + + if (label_type == 0xc0) { - unsigned int label_type = (*ansp) & 0xc0; + /* pointer for compression. */ + ansp += 2; + break; + } + else if (label_type == 0x80) + return NULL; /* reserved */ + else if (label_type == 0x40) + { + /* Extended label type */ + unsigned int count; - if ((unsigned int)(ansp - (unsigned char *)header) >= plen) - return NULL; + if (((*ansp++) & 0x3f) != 1) + return NULL; /* we only understand bitstrings */ - if (label_type == 0xc0) - { - /* pointer for compression. */ - ansp += 2; - break; - } - else if (label_type == 0x80) - return NULL; /* reserved */ - else if (label_type == 0x40) - { - /* Extended label type */ - unsigned int count; - - if (((*ansp++) & 0x3f) != 1) - return NULL; /* we only understand bitstrings */ - - count = *(ansp++); /* Bits in bitstring */ - - if (count == 0) /* count == 0 means 256 bits */ - ansp += 32; - else - ansp += ((count-1)>>3)+1; - } + count = *(ansp++); /* Bits in bitstring */ + + if (count == 0) /* count == 0 means 256 bits */ + ansp += 32; else - { /* label type == 0 Bottom six bits is length */ - unsigned int len = (*ansp++) & 0x3f; - if (len == 0) - break; /* zero length label marks the end. */ - - ansp += len; - } + ansp += ((count-1)>>3)+1; + } + else + { /* label type == 0 Bottom six bits is length */ + unsigned int len = (*ansp++) & 0x3f; + if (len == 0) + break; /* zero length label marks the end. */ + + ansp += len; } + } + + return ansp; +} + +static unsigned char *skip_questions(HEADER *header, unsigned int plen) +{ + int q, qdcount = ntohs(header->qdcount); + unsigned char *ansp = (unsigned char *)(header+1); + + for (q = 0; q<qdcount; q++) + { + if (!(ansp = skip_name(ansp, header, plen))) + return NULL; ansp += 4; /* class and type */ } if ((unsigned int)(ansp - (unsigned char *)header) > plen) @@ -298,6 +306,49 @@ static unsigned char *skip_questions(HEADER *header, unsigned int plen) return ansp; } +unsigned char *find_pseudoheader(HEADER *header, unsigned int plen) +{ + /* See if packet has an RFC2671 pseudoheader, and if so return a pointer to it. */ + + int i, arcount = ntohs(header->arcount); + unsigned char *ansp; + unsigned short rdlen, type; + + if (arcount == 0 || !(ansp = skip_questions(header, plen))) + return NULL; + + for (i = 0; i < (ntohs(header->ancount) + ntohs(header->nscount)); i++) + { + if (!(ansp = skip_name(ansp, header, plen))) + return NULL; + ansp += 8; /* type, class, TTL */ + GETSHORT(rdlen, ansp); + if ((unsigned int)(ansp + rdlen - (unsigned char *)header) > plen) + return NULL; + ansp += rdlen; + } + + for (i = 0; i < arcount; i++) + { + unsigned char *save; + if (!(ansp = skip_name(ansp, header, plen))) + return NULL; + + GETSHORT(type, ansp); + save = ansp; + ansp += 6; /* class, TTL */ + GETSHORT(rdlen, ansp); + if ((unsigned int)(ansp + rdlen - (unsigned char *)header) > plen) + return NULL; + if (type == ns_t_opt) + return save; + ansp += rdlen; + } + + return NULL; +} + + /* is addr in the non-globally-routed IP space? */ static int private_net(struct all_addr *addrp) { @@ -440,13 +491,16 @@ void extract_neg_addrs(HEADER *header, unsigned int qlen, char *name, time_t now cache_end_insert(); } -static void dns_doctor(struct doctor *doctor, struct in_addr *addr) +static void dns_doctor(HEADER *header, struct doctor *doctor, struct in_addr *addr) { for (; doctor; doctor = doctor->next) if (is_same_net(doctor->in, *addr, doctor->mask)) { addr->s_addr &= ~doctor->mask.s_addr; addr->s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); + /* Since we munged the data, the server it came from is no longer authoritative */ + header->nscount = htons(0); + header->arcount = htons(0); break; } } @@ -490,7 +544,7 @@ void extract_addresses(HEADER *header, unsigned int qlen, char *name, if (qtype == T_A) /* A record. */ { - dns_doctor(doctors, (struct in_addr *)p); + dns_doctor(header, doctors, (struct in_addr *)p); cache_insert(name, (struct all_addr *)p, now, ttl, F_IPV4 | F_FORWARD); } @@ -562,7 +616,7 @@ void extract_addresses(HEADER *header, unsigned int qlen, char *name, if (qtype == T_A) /* A record. */ { - dns_doctor(doctors, (struct in_addr *)p); + dns_doctor(header, doctors, (struct in_addr *)p); cache_insert(name, (struct all_addr *)p, now, cttl, F_IPV4 | F_FORWARD); } @@ -635,7 +689,7 @@ int setup_reply(HEADER *header, unsigned int qlen, header->tc = 0; /* not truncated */ header->nscount = htons(0); header->arcount = htons(0); - header->ancount = htons(0); /* no answers unless changed below*/ + header->ancount = htons(0); /* no answers unless changed below */ if (flags == F_NEG) header->rcode = SERVFAIL; /* couldn't get memory */ else if (flags == F_NOERR || flags == F_QUERY) @@ -730,27 +784,58 @@ int check_for_bogus_wildcard(HEADER *header, unsigned int qlen, char *name, /* return zero if we can't answer from cache, or packet size if we can */ int answer_request(HEADER *header, char *limit, unsigned int qlen, struct mx_record *mxnames, char *mxtarget, unsigned int options, time_t now, - unsigned long local_ttl, char *name) + unsigned long local_ttl, char *name, unsigned short edns_pcktsz) { - unsigned char *p, *ansp; + unsigned char *p, *ansp, *pheader; int qtype, qclass, is_arpa; struct all_addr addr; unsigned int nameoffset; - int q, qdcount = ntohs(header->qdcount); - int ans, anscount = 0; + unsigned short flag; + int qdcount = ntohs(header->qdcount); + int q, ans, anscount; + int dryrun = 0, sec_reqd = 0; struct crec *crecp; - int nxdomain = 0, auth = 1; + int nxdomain, auth; if (!qdcount || header->opcode != QUERY ) return 0; + /* If there is an RFC2671 pseudoheader then it will be overwritten by + partial replies, so we have to do a dry run to see if we can answer + the query. We check to see if the do bit is set, if so we always + forward rather than answering from the cache, which doesn't include + security information. */ + + if ((pheader = find_pseudoheader(header, qlen))) + { + unsigned short udpsz, ext_rcode, flags; + unsigned char *psave = pheader; + + GETSHORT(udpsz, pheader); + GETSHORT(ext_rcode, pheader); + GETSHORT(flags, pheader); + + sec_reqd = flags & 0x8000; /* do bit */ + + /* If our client is advertising a larger UDP packet size + than we allow, trim it so that we don't get an overlarge + response from upstream */ + + if (udpsz > edns_pcktsz) + PUTSHORT(edns_pcktsz, psave); + + dryrun = 1; + } + + rerun: /* determine end of question section (we put answers there) */ if (!(ansp = skip_questions(header, qlen))) return 0; /* bad packet */ /* now process each question, answers go in RRs after the question */ p = (unsigned char *)(header+1); - + nxdomain = 0, auth = 1, anscount = 0; + for (q=0; q<qdcount; q++) { /* save pointer to name for copying into answers */ @@ -769,16 +854,19 @@ int answer_request(HEADER *header, char *limit, unsigned int qlen, struct mx_rec ans = 0; /* have we answered this question */ - if (qclass == C_CHAOS) + if (qclass == C_CHAOS && qtype == T_TXT) /* special query to get version. */ { - if (qtype == T_TXT) + ans = 1; + if (!dryrun) { int len; if (hostname_isequal(name, "version.bind")) sprintf(name, "dnsmasq-%s", VERSION); else if (hostname_isequal(name, "authors.bind")) sprintf(name, "Simon Kelley"); + else if (hostname_isequal(name, "copyright.bind")) + sprintf(name, COPYRIGHT); else *name = 0; len = strlen(name); @@ -790,235 +878,192 @@ int answer_request(HEADER *header, char *limit, unsigned int qlen, struct mx_rec *ansp++ = len; memcpy(ansp, name, len); ansp += len; - ans = 1; anscount++; - - if (((unsigned char *)limit - ansp) < 0) - return 0; } - else - return 0; - } - else if (qclass != C_IN) - return 0; /* we can't answer non-inet queries */ - else + } + else if (qclass == C_IN) { - - if ((options & OPT_FILTER) && (qtype == T_SOA || qtype == T_SRV)) + if ((options & OPT_FILTER) && + (qtype == T_SOA || qtype == T_SRV || (qtype == T_ANY && strchr(name, '_')))) ans = 1; - - if (qtype == T_PTR || qtype == T_ANY) + else { - crecp = NULL; - while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))) - { - unsigned long ttl; - /* Return 0 ttl for DHCP entries, which might change - before the lease expires. */ - if (crecp->flags & (F_IMMORTAL | F_DHCP)) - ttl = local_ttl; - else - ttl = crecp->ttd - now; - - /* don't answer wildcard queries with data not from /etc/hosts - or dhcp leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - return 0; - - ans = 1; - if (crecp->flags & F_NEG) - { - log_query(crecp->flags & ~F_FORWARD, name, &addr); - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; + if (qtype == T_PTR || qtype == T_ANY) + { + if (!(crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) + { + if (is_arpa == F_IPV4 && (options & OPT_BOGUSPRIV) && private_net(&addr)) + { + /* if not in cache, enabled and private IPV4 address, return NXDOMAIN */ + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_REVERSE | F_IPV4 | F_NEG | F_NXDOMAIN, name, &addr); + nxdomain = 1; + } + } } - else + else do { - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - ansp = add_text_record(nameoffset, ansp, ttl, 0, T_PTR, - cache_get_name(crecp)); - - log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr); - anscount++; + /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) + continue; - /* if last answer exceeded packet size, give up */ - if (((unsigned char *)limit - ansp) < 0) - return 0; - } + if (crecp->flags & F_NEG) + { + ans = 1; + if (!dryrun) + { + log_query(crecp->flags & ~F_FORWARD, name, &addr); + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + } + } + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) + { + ans = 1; + if (!dryrun) + { + unsigned long ttl; + /* Return 0 ttl for DHCP entries, which might change + before the lease expires. */ + if (crecp->flags & (F_IMMORTAL | F_DHCP)) + ttl = local_ttl; + else + ttl = crecp->ttd - now; + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + + ansp = add_text_record(nameoffset, ansp, ttl, 0, T_PTR, + cache_get_name(crecp)); + + log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr); + anscount++; + + /* if last answer exceeded packet size, give up */ + if (((unsigned char *)limit - ansp) < 0) + return 0; + } + } + } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); } - /* if not in cache, enabled and private IPV4 address, fake up answer */ - if (ans == 0 && is_arpa == F_IPV4 && - (options & OPT_BOGUSPRIV) && - private_net(&addr)) + for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) { - struct in_addr addr4 = *((struct in_addr *)&addr); - ansp = add_text_record(nameoffset, ansp, local_ttl, 0, T_PTR, inet_ntoa(addr4)); - log_query(F_CONFIG | F_REVERSE | F_IPV4, inet_ntoa(addr4), &addr); - anscount++; - ans = 1; + unsigned short type = T_A; + int addrsz = INADDRSZ; + + if (flag == F_IPV6) + { +#ifdef HAVE_IPV6 + type = T_AAAA; + addrsz = IN6ADDRSZ; +#else + break; +#endif + } + + if (qtype != type && qtype != T_ANY) + continue; - if (((unsigned char *)limit - ansp) < 0) - return 0; - } - } - - if (qtype == T_A || qtype == T_ANY) - { - /* T_ANY queries for hostnames with underscores are spam - from win2k - don't forward them. */ - if ((options & OPT_FILTER) && - qtype == T_ANY && - (strchr(name, '_') != NULL)) - ans = 1; - else - { crecp = NULL; - while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4))) + while ((crecp = cache_find_by_name(crecp, name, now, flag))) { - unsigned long ttl; - if (crecp->flags & (F_IMMORTAL | F_DHCP)) - ttl = local_ttl; - else - ttl = crecp->ttd - now; - /* don't answer wildcard queries with data not from /etc/hosts or DHCP leases */ if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - return 0; - - /* If we have negative cache entry, it's OK - to return no answer. */ - ans = 1; + continue; if (crecp->flags & F_NEG) { - log_query(crecp->flags, name, NULL); - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; + ans = 1; + if (!dryrun) + { + log_query(crecp->flags, name, NULL); + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + } } - else + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) { - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr); - - /* copy question as first part of answer (use compression) */ - PUTSHORT(nameoffset | 0xc000, ansp); - PUTSHORT(T_A, ansp); - PUTSHORT(C_IN, ansp); - PUTLONG(ttl, ansp); /* TTL */ - - PUTSHORT(INADDRSZ, ansp); - memcpy(ansp, &crecp->addr, INADDRSZ); - ansp += INADDRSZ; - anscount++; - - if (((unsigned char *)limit - ansp) < 0) - return 0; + ans = 1; + if (!dryrun) + { + unsigned long ttl; + + if (crecp->flags & (F_IMMORTAL | F_DHCP)) + ttl = local_ttl; + else + ttl = crecp->ttd - now; + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr); + + /* copy question as first part of answer (use compression) */ + PUTSHORT(nameoffset | 0xc000, ansp); + PUTSHORT(type, ansp); + PUTSHORT(C_IN, ansp); + PUTLONG(ttl, ansp); /* TTL */ + + PUTSHORT(addrsz, ansp); + memcpy(ansp, &crecp->addr, addrsz); + ansp += addrsz; + anscount++; + + if (((unsigned char *)limit - ansp) < 0) + return 0; + } } - } } - } - -#ifdef HAVE_IPV6 - if (qtype == T_AAAA || qtype == T_ANY) - { - /* T_ANY queries for hostnames with underscores are spam - from win2k - don't forward them. */ - if ((options & OPT_FILTER) && - qtype == T_ANY - && (strchr(name, '_') != NULL)) - ans = 1; - else - { - crecp = NULL; - while ((crecp = cache_find_by_name(crecp, name, now, F_IPV6))) - { - unsigned long ttl; - if (crecp->flags & (F_IMMORTAL | F_DHCP)) - ttl = local_ttl; - else - ttl = crecp->ttd - now; - - /* don't answer wildcard queries with data not from /etc/hosts - or DHCP leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - return 0; - - /* If we have negative cache entry, it's OK - to return no answer. */ + + if (qtype == T_MX || qtype == T_ANY) + { + struct mx_record *mx; + for (mx = mxnames; mx; mx = mx->next) + if (hostname_isequal(name, mx->mxname)) + break; + if (mx) + { ans = 1; - - if (crecp->flags & F_NEG) + if (!dryrun) { - log_query(crecp->flags, name, NULL); - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; + ansp = add_text_record(nameoffset, ansp, local_ttl, 1, T_MX, + mx->mxtarget ? mx->mxtarget : mxtarget); + anscount++; } - else + } + else if ((options & (OPT_SELFMX | OPT_LOCALMX)) && + cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP)) + { + ans = 1; + if (!dryrun) { - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr); - - /* copy question as first part of answer (use compression) */ - PUTSHORT(nameoffset | 0xc000, ansp); - PUTSHORT(T_AAAA, ansp); - PUTSHORT(C_IN, ansp); - PUTLONG(ttl, ansp); /* TTL */ - - PUTSHORT(IN6ADDRSZ, ansp); - memcpy(ansp, &crecp->addr, IN6ADDRSZ); - ansp += IN6ADDRSZ; + ansp = add_text_record(nameoffset, ansp, local_ttl, 1, T_MX, + (options & OPT_SELFMX) ? name : mxtarget); anscount++; - - if (((unsigned char *)limit - ansp) < 0) - return 0; } } } - } -#endif - - if (qtype == T_MX || qtype == T_ANY) - { - struct mx_record *mx; - for (mx = mxnames; mx; mx = mx->next) - if (hostname_isequal(name, mx->mxname)) - break; - if (mx) - { - ansp = add_text_record(nameoffset, ansp, local_ttl, 1, T_MX, - mx->mxtarget ? mx->mxtarget : mxtarget); - anscount++; - ans = 1; - } - else if ((options & (OPT_SELFMX | OPT_LOCALMX)) && - cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP)) - { - ansp = add_text_record(nameoffset, ansp, local_ttl, 1, T_MX, - (options & OPT_SELFMX) ? name : mxtarget); - anscount++; - ans = 1; - } - if (((unsigned char *)limit - ansp) < 0) - return 0; - } - - if (qtype == T_MAILB) - ans = 1, nxdomain = 1; + if (qtype == T_MAILB) + ans = 1, nxdomain = 1; + + } } - if (!ans) + if (!ans || ((unsigned char *)limit - ansp) < 0) return 0; /* failed to answer a question */ + } + if (dryrun) + { + dryrun = 0; + goto rerun; } /* done all questions, set up header and return length of result */ diff --git a/src/rfc2131.c b/src/rfc2131.c index 3ceef7d..b5c25b9 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -105,9 +105,7 @@ int dhcp_reply(struct dhcp_context *context, struct in_addr addr; unsigned short fuzz = 0; - if (mess->op != BOOTREQUEST || - mess->hlen != ETHER_ADDR_LEN || - mess->cookie != htonl(DHCP_COOKIE)) + if (mess->op != BOOTREQUEST || mess->cookie != htonl(DHCP_COOKIE)) return 0; /* Token ring is supported when we have packet sockets @@ -116,14 +114,19 @@ int dhcp_reply(struct dhcp_context *context, token ring hwaddrs are the same size as ethernet hwaddrs. */ #ifdef HAVE_BPF - if (mess->htype != ARPHRD_ETHER) - return 0; + if (mess->htype != ARPHRD_ETHER) #else - if (mess->htype != ARPHRD_ETHER && - mess->htype != ARPHRD_IEEE802) - return 0; + if (mess->htype != ARPHRD_ETHER && mess->htype != ARPHRD_IEEE802) #endif + { + syslog(LOG_WARNING, "DHCP request for unsupported hardware type (%d) recieved on %s", + mess->htype, iface_name); + return 0; + } + if (mess->hlen != ETHER_ADDR_LEN) + return 0; + mess->op = BOOTREPLY; if ((opt = option_find(mess, sz, OPTION_MAXMESSAGE))) @@ -317,6 +320,9 @@ int dhcp_reply(struct dhcp_context *context, syslog(LOG_WARNING, "disabling DHCP static address %s", inet_ntoa(config->addr)); config->flags &= ~CONFIG_ADDR ; } + else + /* make sure this host gets a different address next time. */ + context->addr_epoch++; return 0; @@ -335,18 +341,16 @@ int dhcp_reply(struct dhcp_context *context, case DHCPDISCOVER: if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP))) addr = option_addr(opt); - if (have_config(config, CONFIG_DISABLE)) message = "ignored"; else if (have_config(config, CONFIG_ADDR) && !lease_find_by_addr(config->addr)) mess->yiaddr = config->addr; - else if (lease && is_same_net(lease->addr, context->start, context->netmask)) + else if (lease && address_available(context, lease->addr)) mess->yiaddr = lease->addr; - else if (opt && address_available(context, addr)) + else if (opt && address_available(context, addr) && !lease_find_by_addr(addr)) mess->yiaddr = addr; else if (!address_allocate(context, dhcp_configs, &mess->yiaddr, mess->chaddr)) - message = "no address available"; - + message = "no address available"; log_packet("DISCOVER", opt ? &addr : NULL, mess->chaddr, iface_name, message); if (message) @@ -393,12 +397,10 @@ int dhcp_reply(struct dhcp_context *context, lease_prune(lease, now); lease = NULL; } - - /* accept addresses in the dynamic range or ones allocated statically to - particular hosts or an address which the host already has. */ + if (!lease) { - if (!address_available(context, mess->yiaddr) && + if ((!address_available(context, mess->yiaddr) || lease_find_by_addr(mess->yiaddr)) && (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr)) message = "address unavailable"; else if (!(lease = lease_allocate(clid, clid_len, mess->yiaddr))) @@ -425,7 +427,20 @@ int dhcp_reply(struct dhcp_context *context, /* If a machine moves networks whilst it has a lease, we catch that here. */ if (!message && !is_same_net(mess->yiaddr, context->start, context->netmask)) message = "wrong network"; - + + /* Check for renewal of a lease which is now outside the allowed range. */ + if (!message && !address_available(context, mess->yiaddr) && + (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr)) + message = "address no longer available"; + + /* Check if a new static address has been configured. Be very sure that + when the client does DISCOVER, it will get the static address, otherwise + an endless protocol loop will ensue. */ + if (!message && have_config(config, CONFIG_ADDR) && + !have_config(config, CONFIG_DISABLE) && + !lease_find_by_addr(config->addr)) + message = "static lease available"; + log_packet("REQUEST", &mess->yiaddr, mess->chaddr, iface_name, NULL); if (message) |