From 233f343715de43128e0c605d32fe1d647d50adad Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 14 Feb 2013 07:55:15 +0100 Subject: Initial commit. --- COPYING | 367 +++++++++++++++++++++++++++++++++++++++++++++ Makefile | 13 ++ README.md | 132 ++++++++++++++++ ipset-dns.c | 447 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ sample-script.sh | 38 +++++ 5 files changed, 997 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README.md create mode 100644 ipset-dns.c create mode 100755 sample-script.sh diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0cebc18 --- /dev/null +++ b/COPYING @@ -0,0 +1,367 @@ +Copyright (C) 2013 Jason A. Donenfeld . All Rights Reserved. +Copyright (C) 1998 Kenneth Albanowski , The Silver Hammer Group, Ltd. +Copyright (C) 1985, 1993 The Regents of the University of California. All Rights Reserved. +--- + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. + + + Copyright (C) + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. + + , 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 Lesser General +Public License instead of this License. +--- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5011c76 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS ?= -O2 -pipe -fomit-frame-pointer -march=native +ifeq ($(OLD_IPSET),1) + CFLAGS += -DOLD_IPSET +else + CFLAGS += -lmnl +endif + +.PHONY: clean + +ipset-dns: + +clean: + rm -f ipset-dns diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb72cc7 --- /dev/null +++ b/README.md @@ -0,0 +1,132 @@ +# ipset-dns +#### Jason A. Donenfeld () + +`ipset-dns` is a lightweight DNS forwarding server that adds all resolved IPs +to a given [netfilter ipset](http://ipset.netfilter.org/). It is designed to be +used in conjunction with [`dnsmasq`](http://www.thekelleys.org.uk/dnsmasq/doc.html)'s +upstream server directive. + +Practical use cases include routing over a given gateway traffic for particular +web services or webpages that do not have a priori predictable IP addresses and +instead rely on dizzying arrays of DNS resolutions. + +### Why? + +Some ISPs throttle connections to services like YouTube. Other times, +you live places where there's no Netflix/Pandora/Hulu, but you've got a VPN. + +The problem is, you don't want to route *all* your internet traffic over VPN -- just +for YouTube and Pandora, say. It'd be nice to just whitelist a static IP range, +but some services, like YouTube, have a thousands of caching servers in a modicum +of IP ranges, and it's just too much of a hassle to compile the list beforehand. + +So instead, you put `ipset-dns` on your router, and then everyone and every +XBox/PS3/whatever on your wifi network will benefit from the superior +bandwidth and/or geo-availability. + +### Usage + + # ipset-dns name-of-ipset listening-port upstream-dns-server + +`ipset-dns` binds only to localhost. It will daemonize unless the `NO_DAEMONIZE` +environment variable is set. + +### Building + +Linux >= 2.6.32: + + $ make + +Linux >= 2.6.16 or >= 2.4.36: + + $ make OLD_IPSET=1 + +### Example + +In `dnsmasq.conf`: + + server=/c.youtube.com/127.0.0.1#1919 + +Make an ipset: + + # ipset -N youtube iphash + +Start the `ipset-dns` server: + + # ipset-dns youtube 1919 8.8.8.8 + +Query a hostname: + + # host r4---bru02t12.c.youtube.com + r4---bru02t12.c.youtube.com is an alias for r4.bru02t12.c.youtube.com. + r4.bru02t12.c.youtube.com has address 74.125.216.51 + +Observe that it was added to the ipset: + + # ipset -L youtube + Name: youtube + Type: iphash + References: 1 + Header: hashsize: 1024 probes: 8 resize: 50 + Members: + 74.125.216.51 + + +### Sample Script + +The following script routes youtube and netflix over two different repective +gateways. It assumes you're using `dnsmasq` or similar to manage caching and +selectively using upstream servers: + + server=/c.youtube.com/127.0.0.1#39128 + server=/netflix.com/127.0.0.1#39129 + +The network interfaces `tun11` and `tun12` are assumed to be OpenVPN tunnels, +though they may be any other kind of interface with a route. These devices are +assumed to have some form of masquerading and IP forwarding turned on already. + +The `mangle` `iptables` table is used to set a firewall mark on packets that +match an ipset tended to by `ipset-dns`. A routing table is created and a rule +is entered that sends packets marked by `iptables` to the correct routing table. +Finally, a default route is given to the marked routing table. + +Two `ipset-dns` daemons are started, one for each of the routes, using the ports +given by `dnsmasq`. Lastly, `SIGHUP` is sent to `dnsmasq` to flush its cache. + + sets() { + iptables -t mangle -D PREROUTING -m set --set "$1" dst,src -j MARK --set-mark "$2" 2>/dev/null + ipset -X "$1" 2>/dev/null + ipset -N "$1" iphash + iptables -t mangle -A PREROUTING -m set --set "$1" dst,src -j MARK --set-mark "$2" + } + + sets youtube 1 + sets netflix 2 + + routes() { + echo 0 > /proc/sys/net/ipv4/conf/$2/rp_filter + ip route flush table $1 2>/dev/null + ip rule del table $1 2>/dev/null + ip rule add fwmark $1 table $1 priority 1000 + ip route add default via "$(ip route show dev $2 | head -n 1 | cut -d ' ' -f 1)" table $1 + } + + routes 1 tun12 + routes 2 tun11 + + killall ipset-dns 2>/dev/null + ipset-dns youtube 39128 8.8.8.8 + ipset-dns netflix 39129 8.8.8.8 + + killall -SIGHUP dnsmasq + +### License + +* Copyright (C) 2013 Jason A. Donenfeld . All Rights Reserved. + +DNS parsing code loosely based on uClibc's [resolv.c](http://git.uclibc.org/uClibc/tree/libc/inet/resolv.c): + +* Copyright (C) 1998 Kenneth Albanowski , The Silver Hammer Group, Ltd. +* Copyright (C) 1985, 1993 The Regents of the University of California. All Rights Reserved. + +This project is licensed under the GPLv2. Please see COPYING for more information. diff --git a/ipset-dns.c b/ipset-dns.c new file mode 100644 index 0000000..80c7cc1 --- /dev/null +++ b/ipset-dns.c @@ -0,0 +1,447 @@ +/* ipset-dns: lightweight DNS IPSet forwarding server + * by Jason A. Donenfeld (zx2c4) + * + * This is a lightweight DNS forwarding server that adds all resolved IPs + * to a given netfilter ipset. It is designed to be used in conjunction with + * dnsmasq's upstream server directive. + * + * Copyright (C) 2013 Jason A. Donenfeld . All Rights Reserved. + * DNS parsing code loosely based on uClibc's resolv.c: + * Copyright (C) 1998 Kenneth Albanowski , The Silver Hammer Group, Ltd. + * Copyright (C) 1985, 1993 The Regents of the University of California. All Rights Reserved. + * This file is licensed under the GPLv2. Please see COPYING for more information. + * + * + * Usage Example: + * + * In dnsmasq.conf: + * server=/c.youtube.com/127.0.0.1#1919 + * Make an ipset: + * # ipset -N youtube iphash + * Start the ipset-dns server: + * # ipset-dns youtube 1919 8.8.8.8 + * Query a hostname: + * # host r4---bru02t12.c.youtube.com + * r4---bru02t12.c.youtube.com is an alias for r4.bru02t12.c.youtube.com. + * r4.bru02t12.c.youtube.com has address 74.125.216.51 + * Observe that it was added to the ipset: + * # ipset -L youtube + * Name: youtube + * Type: iphash + * References: 1 + * Header: hashsize: 1024 probes: 8 resize: 50 + * Members: + * 74.125.216.51 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef OLD_IPSET +#include +#include +#include +#endif + +struct resolv_header { + int id; + int qr, opcode, aa, tc, rd, ra, rcode; + int qdcount; + int ancount; + int nscount; + int arcount; +}; + +struct resolv_answer { + char dotted[256]; + int atype; + int aclass; + int ttl; + int rdlength; + const unsigned char *rdata; + int rdoffset; +}; + +static void decode_header(unsigned char *data, struct resolv_header *h) +{ + h->id = (data[0] << 8) | data[1]; + h->qr = (data[2] & 0x80) ? 1 : 0; + h->opcode = (data[2] >> 3) & 0x0f; + h->aa = (data[2] & 0x04) ? 1 : 0; + h->tc = (data[2] & 0x02) ? 1 : 0; + h->rd = (data[2] & 0x01) ? 1 : 0; + h->ra = (data[3] & 0x80) ? 1 : 0; + h->rcode = data[3] & 0x0f; + h->qdcount = (data[4] << 8) | data[5]; + h->ancount = (data[6] << 8) | data[7]; + h->nscount = (data[8] << 8) | data[9]; + h->arcount = (data[10] << 8) | data[11]; +} +static int length_question(const unsigned char *data, int maxlen) +{ + const unsigned char *start; + unsigned int b; + + if (!data) + return -1; + + start = data; + for (;;) { + if (maxlen <= 0) + return -1; + b = *data++; + if (b == 0) + break; + if ((b & 0xc0) == 0xc0) { + /* It's a "compressed" name. */ + ++data; /* skip lsb of redirected offset */ + maxlen -= 2; + break; + } + data += b; + maxlen -= (b + 1); /* account for data++ above */ + } + /* Up to here we were skipping encoded name */ + + /* Account for QTYPE and QCLASS fields */ + if (maxlen < 4) + return -1; + return data - start + 2 + 2; +} +static int decode_dotted(const unsigned char *packet, int offset, int packet_len, char *dest, int dest_len) +{ + unsigned int b, total = 0, used = 0; + int measure = 1; + + if (!packet) + return -1; + + for (;;) { + if (offset >= packet_len) + return -1; + b = packet[offset++]; + if (b == 0) + break; + + if (measure) + ++total; + + if ((b & 0xc0) == 0xc0) { + if (offset >= packet_len) + return -1; + if (measure) + ++total; + /* compressed item, redirect */ + offset = ((b & 0x3f) << 8) | packet[offset]; + measure = 0; + continue; + } + + if (used + b + 1 >= dest_len || offset + b >= packet_len) + return -1; + memcpy(dest + used, packet + offset, b); + offset += b; + used += b; + + if (measure) + total += b; + + if (packet[offset] != 0) + dest[used++] = '.'; + else + dest[used++] = '\0'; + } + + if (measure) + ++total; + + return total; +} +static int decode_answer(const unsigned char *message, int offset, int len, struct resolv_answer *a) +{ + int i; + + i = decode_dotted(message, offset, len, a->dotted, sizeof(a->dotted)); + if (i < 0) + return i; + + message += offset + i; + len -= i + RRFIXEDSZ + offset; + if (len < 0) + return len; + + a->atype = (message[0] << 8) | message[1]; + message += 2; + a->aclass = (message[0] << 8) | message[1]; + message += 2; + a->ttl = (message[0] << 24) | (message[1] << 16) | (message[2] << 8) | (message[3] << 0); + message += 4; + a->rdlength = (message[0] << 8) | message[1]; + message += 2; + a->rdata = message; + a->rdoffset = offset + i + RRFIXEDSZ; + + if (len < a->rdlength) + return -1; + return i + RRFIXEDSZ + a->rdlength; +} + +static int add_to_ipset(const char *setname, const void *ipaddr, int af) +{ +#ifndef OLD_IPSET + struct nlmsghdr *nlh; + struct nfgenmsg *nfg; + struct mnl_socket *mnl; + struct nlattr *nested[2]; + char buffer[256]; + int rc; + + rc = 0; + + if (strlen(setname) >= IPSET_MAXNAMELEN) { + errno = ENAMETOOLONG; + return -1; + } + if (af != AF_INET && af != AF_INET6) { + errno = EAFNOSUPPORT; + return -1; + } + + nlh = mnl_nlmsg_put_header(buffer); + nlh->nlmsg_type = IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << 8); + nlh->nlmsg_flags = NLM_F_REQUEST; + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); + nfg->nfgen_family = af; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(0); + + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + mnl_attr_put(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname); + nested[0] = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA); + nested[1] = mnl_attr_nest_start(nlh, IPSET_ATTR_IP); + mnl_attr_put(nlh, (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) + | NLA_F_NET_BYTEORDER, (af == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr)), ipaddr); + mnl_attr_nest_end(nlh, nested[1]); + mnl_attr_nest_end(nlh, nested[0]); + + mnl = mnl_socket_open(NETLINK_NETFILTER); + if (mnl <= 0) + return -1; + if (mnl_socket_bind(mnl, 0, MNL_SOCKET_AUTOPID) < 0) { + rc = -1; + goto close; + } + if (mnl_socket_sendto(mnl, nlh, nlh->nlmsg_len) < 0) { + rc = -1; + goto close; + } +close: + mnl_socket_close(mnl); + return rc; +#else + int sock, rc; + socklen_t size; + struct ip_set_req_adt_get { + unsigned op; + unsigned version; + union { + char name[32]; + uint16_t index; + } set; + char typename[32]; + } req_adt_get; + struct ip_set_req_adt { + unsigned op; + uint16_t index; + uint32_t ip; + } req_adt; + + rc = 0; + + if (strlen(setname) >= sizeof(req_adt_get.set.name)) { + errno = ENAMETOOLONG; + return -1; + } + if (af != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + + sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (sock < 0) + return -1; + + req_adt_get.op = 0x10; + req_adt_get.version = 3; + strcpy(req_adt_get.set.name, setname); + size = sizeof(req_adt_get); + if (getsockopt(sock, SOL_IP, 83, &req_adt_get, &size) < 0) { + rc = -1; + goto close; + } + req_adt.op = 0x101; + req_adt.index = req_adt_get.set.index; + req_adt.ip = ntohl(*(uint32_t *)ipaddr); + if (setsockopt(sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0) { + rc = -1; + goto close; + } +close: + close(sock); + return rc; +#endif +} + +int main(int argc, char *argv[]) +{ + struct sockaddr_in client_addr, listen_addr, upstream_addr; + struct resolv_header question_header, answer_header; + struct resolv_answer answer; + struct timeval tv; + char msg[512]; + char ip[INET6_ADDRSTRLEN]; + char *ipset; + int listen_sock, upstream_sock; + int pos, i, size, af; + socklen_t len; + size_t received; + pid_t child; + + if (argc != 4) { + fprintf(stderr, "Usage: %s ipset port upstream\n", argv[0]); + return 1; + } + + ipset = argv[1]; + + listen_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (listen_sock < 0) { + perror("socket"); + return 1; + } + + memset(&listen_addr, 0, sizeof(listen_addr)); + listen_addr.sin_family = AF_INET; + listen_addr.sin_port = htons(atoi(argv[2])); + listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + i = 1; + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); + if (bind(listen_sock, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) { + perror("bind"); + return 1; + } + + memset(&upstream_addr, 0, sizeof(upstream_addr)); + upstream_addr.sin_family = AF_INET; + upstream_addr.sin_port = htons(53); + inet_aton(argv[3], &upstream_addr.sin_addr); + + /* TODO: Put all of the below code in several forks all listening on the same sock. */ + + if (!getenv("NO_DAEMONIZE")) { + if (daemon(0, 0) < 0) { + perror("daemon"); + return 1; + } + } + + upstream_sock = -1; + + for (;;) { + if (upstream_sock >= 0) + close(upstream_sock); + + len = sizeof(client_addr); + received = recvfrom(listen_sock, msg, sizeof(msg), 0, (struct sockaddr *)&client_addr, &len); + if (received < 0) { + perror("recvfrom"); + continue; + } + if (received < HFIXEDSZ) { + fprintf(stderr, "Did not receive full DNS header from client.\n"); + continue; + } + + decode_header(msg, &question_header); + + upstream_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (upstream_sock < 0) { + perror("socket"); + continue; + } + tv.tv_sec = 1; + tv.tv_usec = 0; + setsockopt(upstream_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + if (sendto(upstream_sock, msg, received, 0, (struct sockaddr *)&upstream_addr, sizeof(upstream_addr)) < 0) { + perror("sendto"); + continue; + } + received = recv(upstream_sock, msg, sizeof(msg), 0); + if (received < 0) { + perror("recv"); + continue; + } + if (received < HFIXEDSZ) { + fprintf(stderr, "Did not receive full DNS header from upstream.\n"); + continue; + } + close(upstream_sock); + upstream_sock = -1; + + decode_header(msg, &answer_header); + if (answer_header.id != question_header.id || !answer_header.qr) { + fprintf(stderr, "Unsolicited response from upstream.\n"); + continue; + } + if (answer_header.rcode || answer_header.ancount <= 0) + goto send_back; + + pos = HFIXEDSZ; + for (i = 0; i < answer_header.qdcount; ++i) { + if (pos >= received || pos < 0) + goto send_back; + size = length_question(msg + pos, received - pos); + if (size < 0) + goto send_back; + pos += size; + } + for (i = 0; i < answer_header.ancount; ++i) { + if (pos >= received || pos < 0) + goto send_back; + size = decode_answer(msg, pos, received, &answer); + if (size < 0) { + if (i && answer_header.tc) + break; + goto send_back; + } + pos += size; + + if (!(answer.atype == T_A && answer.rdlength == sizeof(struct in_addr)) && + !(answer.atype == T_AAAA && answer.rdlength == sizeof(struct in6_addr))) + continue; + + af = answer.atype == T_A ? AF_INET : AF_INET6; + + if (!inet_ntop(af, answer.rdata, ip, sizeof(ip))) { + perror("inet_ntop"); + continue; + } + + printf("%s: %s\n", answer.dotted, ip); + if (add_to_ipset(ipset, answer.rdata, af) < 0) + perror("add_to_ipset"); + } + + send_back: + if (sendto(listen_sock, msg, received, 0, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) + perror("sendto"); + } + return 0; +} diff --git a/sample-script.sh b/sample-script.sh new file mode 100755 index 0000000..35d6a2a --- /dev/null +++ b/sample-script.sh @@ -0,0 +1,38 @@ +#!/bin/sh +set -x + +# Inside of dnsmasq.conf we have: +# +# server=/c.youtube.com/127.0.0.1#39128 +# server=/netflix.com/127.0.0.1#39129 +# +# The devices tun11 and tun12 are OpenVPN tun network interfaces. +# +# This script routes youtube videos over tun12 and netflix over tun11. + +sets() { + iptables -t mangle -D PREROUTING -m set --set "$1" dst,src -j MARK --set-mark "$2" 2>/dev/null + ipset -X "$1" 2>/dev/null + ipset -N "$1" iphash + iptables -t mangle -A PREROUTING -m set --set "$1" dst,src -j MARK --set-mark "$2" +} + +sets youtube 1 +sets netflix 2 + +routes() { + echo 0 > /proc/sys/net/ipv4/conf/$2/rp_filter + ip route flush table $1 2>/dev/null + ip rule del table $1 2>/dev/null + ip rule add fwmark $1 table $1 priority 1000 + ip route add default via "$(ip route show dev $2 | head -n 1 | cut -d ' ' -f 1)" table $1 +} + +routes 1 tun12 +routes 2 tun11 + +killall ipset-dns 2>/dev/null +ipset-dns youtube 39128 8.8.8.8 +ipset-dns netflix 39129 8.8.8.8 + +killall -SIGHUP dnsmasq # Clear dnsmasq's cache -- cgit v1.2.3-59-g8ed1b