diff --git a/CHANGELOG b/CHANGELOG index 731df84..eeebac1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,11 @@ iodine - http://code.kryo.se/iodine CHANGES: master: + - Multiple fragments can be in transit at one time to improve + throughput on high-latency connections + Server can now store multiple pending queries per user. + - Added bidirectional compression flags + - Added statistics report every few seconds in iodine (-V) - Mac OS X: Support native utun VPN devices. Patch by Peter Sagerson, ported from OpenVPN by Catalin Patulea. - Fix compilation failure on kFreeBSD and Hurd, by Gregor Herrmann diff --git a/Makefile b/Makefile index 4be98d1..fa7dec2 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,9 @@ TARGETOS = `uname` all: @(cd src; $(MAKE) TARGETOS=$(TARGETOS) all) + +debug: + @(cd src; $(MAKE) TARGETOS=$(TARGETOS) debug) install: all $(MKDIR) $(MKDIR_FLAGS) $(DESTDIR)$(sbindir) diff --git a/README.md b/README.md index 1c76829..b396946 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ relevant header files are found in `/usr/include`. (See script at `./src/osflags`) Run `make` to compile the server and client binaries. +Run `make debug` to compile the binaries with extra debugging enabled. Run `make install` to copy binaries and manpage to the destination directory. Run `make test` to compile and run the unit tests. (Requires the `check` library) @@ -24,11 +25,11 @@ QUICKSTART ---------- Try it out within your own LAN! Follow these simple steps: -- On your server, run: `./iodined -f 10.0.0.1 test.com`. - If you already use the `10.0.0.0` network, use another internal net like - `172.16.0.0`. +- On your server, run: `./iodined -f test.com 10.0.0.1`. + If you already use the `10.0.0.0/8` network, use another internal net like + `172.16.0.0/12`. - Enter a password. -- On the client, run: `./iodine -f -r 192.168.0.1 test.com`. +- On the client, run: `./iodine -f -r test.com 192.168.0.1`. Replace `192.168.0.1` with your server's ip address. - Enter the same password. - Now the client has the tunnel ip `10.0.0.2` and the server has `10.0.0.1`. @@ -91,13 +92,13 @@ Resulting commandline in this example situation: ./iodined -f -c -P secretpassword 192.168.99.1 t1.mydomain.com ### Client side -All the setup is done, just start `iodine`. It takes one or two arguments, the -first is the local relaying DNS server (optional) and the second is the domain -you used (`t1.mydomain.com`). If you don't specify the first argument, the -system's current DNS setting will be consulted. +All the setup is done, just start `iodine`. It takes one or more arguments, the +first is the the domain you used (`t1.mydomain.com`) and the remaining options +are a list of local relaying DNS server (optional). If you don't specify more +than one argument, the system's current DNS setting will be consulted. If DNS queries are allowed to any computer, you can directly give the `iodined` -server's address as first argument (in the example: `t1ns.mydomain.com` or +server's address as second argument (in the example: `t1ns.mydomain.com` or `10.15.213.99`). In that case, it may also happen that _any_ traffic is allowed to the DNS port (53 UDP) of any computer. Iodine will detect this, and switch to raw UDP tunneling if possible. To force DNS tunneling in any case, use the @@ -184,15 +185,15 @@ these DNS relays much more stable. This is also useful on some “de-optimizing DNS relays that stuff the response with two full copies of the query, leaving very little space for downstream data (also not capable of EDNS0). The `-M` switch can trade some upstream bandwidth for downstream bandwidth. Note that -the minimum `-M` value is about 100, since the protocol can split packets (1200 -bytes max) in only 16 fragments, requiring at least 75 real data bytes per -fragment. +the minimum `-M` value is about 20, since the first 10 bytes or so are the +Base32 encoded data header and the remainder is the actual encoded data and +packets can be split into up to 255 fragments. The upstream data is sent gzipped encoded with Base32; or Base64 if the relay server supports mixed case and `+` in domain names; or Base64u if `_` is supported instead; or Base128 if high-byte-value characters are supported. This upstream encoding is autodetected. The DNS protocol allows one query per -packet, and one query can be max 256 chars. Each domain name part can be max +packet, and one query can be max 255 chars. Each domain name part can be max 63 chars. So your domain name and subdomain should be as short as possible to allow maximum upstream throughput. @@ -220,24 +221,24 @@ when that hostname exceeds ca. 180 characters. In these and similar cases, use the `-O` option to try other downstream codecs; Base32 should always work. Normal operation now is for the server to _not_ answer a DNS request until -the next DNS request has come in, a.k.a. being “lazy”. This way, the server +it has timed out (see server timeout), a.k.a. being “lazy”. This way, the server will always have a DNS request handy when new downstream data has to be sent. This greatly improves (interactive) performance and latency, and allows to slow down the quiescent ping requests to 4 second intervals by default, and -possibly much slower. In fact, the main purpose of the pings now is to force -a reply to the previous ping, and prevent DNS server timeouts (usually at -least 5-10 seconds per RFC1035). Some DNS servers are more impatient and will -give SERVFAIL errors (timeouts) in periods without tunneled data traffic. All -data should still get through in these cases, but `iodine` will reduce the ping -interval to 1 second anyway (-I1) to reduce the number of error messages. This -may not help for very impatient DNS relays like `dnsadvantage.com` (ultradns), -which time out in 1 second or even less. Yet data will still get trough, and -you can ignore the `SERVFAIL` errors. +possibly much slower. Some DNS servers are more impatient and will give SERVFAIL +errors (timeouts) randomly or consistently if the target timeout is too high. All +data should still get through in these cases, but `iodine` will reduce the target +interval slowly to reduce the number of SERVFAILS. In these scenarios, it is best +to manually set the target interval to something which is definitely less than the +most impatient DNS server timeout in the connection to ensure maximum reliability. +Some very impatient DNS relays like `dnsadvantage.com` (ultradns), which time +out in 1 second or even less can cause issues. Yet data will still get trough, and +you can probably ignore the `SERVFAIL` errors. If you are running on a local network without any DNS server in-between, try `-I 50` (iodine and iodined close the connection after 60 seconds of silence). The only time you'll notice a slowdown, is when DNS reply packets go missing; -the `iodined` server then has to wait for a new ping to re-send the data. You can +the `iodined` server fragment will have to time-out to be resent. You can speed this up by generating some upstream traffic (keypress, ping). If this happens often, check your network for bottlenecks and/or run with `-I1`. @@ -256,7 +257,8 @@ If you have problems, try inspecting the traffic with network monitoring tools like tcpdump or ethereal/wireshark, and make sure that the relaying DNS server has not cached the response. A cached error message could mean that you started the client before the server. The `-D` (and `-DD`) option on the server -can also show received and sent queries. +can also show received and sent queries. To assist in diagnosis, you may wish to +recompile with `make debug` and use `-DDDDD` to see more debug output. TIPS & TRICKS @@ -290,8 +292,12 @@ undefine it to save a few more kilobytes. PERFORMANCE ----------- -This section tabulates some performance measurements. To view properly, use -a fixed-width font like Courier. +**Please note:** The following performance is outdated and does not apply to +the current version of iodine. Since the protocol was updated to use a sliding +window for sending fragments, the throughput was greatly increased and should +be much higher even on high-latency connections. + +This section tabulates some performance measurements. Measurements were done in protocol 00000502 in lazy mode; upstream encoding always Base128; `iodine -M255`; `iodined -m1130`. Network conditions were not @@ -306,9 +312,8 @@ explains why some values are exactly equal. Ping round-trip times measured with `ping -c100`, presented are average rtt and mean deviation (indicating spread around the average), in milliseconds. - ### Situation 1: `Laptop -> Wifi AP -> Home server -> DSL provider -> Datacenter` - +``` iodine DNS "relay" bind9 DNS cache iodined downstr. upstream downstr. ping-up ping-down @@ -336,9 +341,9 @@ and mean deviation (indicating spread around the average), in milliseconds. [174.7* : these all have 2frag/packet] - +``` ### Situation 2: `Laptop -> Wifi+vpn / wired -> Home server` - +``` iodine iodined downstr. upstream downstr. ping-up ping-down @@ -348,16 +353,15 @@ and mean deviation (indicating spread around the average), in milliseconds. wifi + openvpn -Tnull 1186 166.0 1022.3 6.3 1.3 6.6 1.6 wired -Tnull 1186 677.2 2464.1 1.3 0.2 1.3 0.1 - +``` ### Notes -Performance is strongly coupled to low ping times, as iodine requires -confirmation for every data fragment before moving on to the next. Allowing -multiple fragments in-flight like TCP could possibly increase performance, -but it would likely cause serious overload for the intermediary DNS servers. -The current protocol scales performance with DNS responsivity, since the -DNS servers are on average handling at most one DNS request per client. +Multiple fragments in-flight like TCP is allowed in iodine, which does increase +performance, but it may overload some low-bandwidth intermediary DNS servers. +Using carrier-grade DNS servers such as those provided by your ISP should be able +to handle a high volume of DNS queries, and it is recommended to use as many DNS +nameservers as possible to balance the load. PORTABILITY @@ -388,8 +392,8 @@ THANKS AUTHORS & LICENSE ----------------- -Copyright (c) 2006-2014 Erik Ekman , 2006-2009 Bjorn -Andersson . Also major contributions by Anne Bezemer. +Copyright (c) 2006-2014 Erik Ekman , 2015 Frekk van Blagh, +2006-2009 Bjorn Andersson . Also major contributions by Anne Bezemer. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt new file mode 100644 index 0000000..fbec870 --- /dev/null +++ b/doc/proto_00000800.txt @@ -0,0 +1,327 @@ +Detailed specification of protocol in version 00000800 +====================================================== + +Note: work in progress!! + +====================================================== +1. DNS protocol +====================================================== + +Quick alphabetical index / register: + 0-9 Data packet + A-F Data packet + I IP address + L Login + N Downstream fragsize (NS.topdomain A-type reply) + O Options + P Ping + R Downstream fragsize probe + S Switch upstream codec + V Version + W (WWW.topdomain A-type reply) + Y Downstream codec check + Z Upstream codec check + + +CMC = Cache Miss Counter, increased every time it is used + +Version: +Client sends: + First byte v or V + Rest encoded with base32: + 4 bytes big endian protocol version + CMC +Server replies: + 4 chars: + VACK (version ok), followed by login challenge + VNAK (version differs), followed by server protocol version + VFUL (server has no free slots), followed by max users + 4 byte value: means login challenge/server protocol version/max users + 1 byte userid of the new user, or any byte if not VACK + +Login: +Client sends: + First byte l or L + Rest encoded with base32: + 1 byte userid + 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) + CMC +Server replies: + LNAK means not accepted + x.x.x.x-y.y.y.y-mtu-netmask means accepted (server ip, client ip, mtu, netmask bits) + +IP Request: (for where to try raw login or set data pipe mode) +Client sends: + First byte i or I + 5 bits coded as Base32 char, meaning userid + CMC as 3 Base32 chars +Server replies + BADIP if bad userid + First byte I + Then comes external IP address of iodined server + as 4 bytes (IPv4) or 16 bytes (IPv6) + +Upstream codec check / bounce: +Client sends: + First byte z or Z + Lots of data that should not be decoded +Server replies: + The requested domain copied raw, in the lowest-grade downstream codec + available for the request type. + +Downstream codec check: +Client sends: + First byte y or Y + 1 char, meaning downstream codec to use + 5 bits coded as Base32 char, meaning check variant + CMC as 3 Base32 chars + Possibly extra data, depending on check variant +Server sends: + Data encoded with requested downstream codec; data content depending + on check variant number. + BADCODEC if requested downstream codec not available. + BADLEN if check variant is not available, or problem with extra data. + + Downstream codec chars are same as in 'O' Option request, below. + + Check variants: + 1: Send encoded DOWNCODECCHECK1 string as defined in encoding.h + + (Other variants reserved; possibly variant that sends a decoded-encoded + copy of Base32-encoded extra data in the request) + +Switch codec: +Client sends: + First byte s or S + 5 bits coded as Base32 char, meaning userid + 5 bits coded as Base32 char, representing number of raw bits per + encoded byte: + 5: Base32 (a-z0-5) + 6: Base64 (a-zA-Z0-9+-) + 26: Base64u (a-zA-Z0-9_-) + 7: Base128 (a-zA-Z0-9\274-\375) + CMC as 3 Base32 chars +Server sends: + Name of codec if accepted. After this all upstream data packets must + be encoded with the new codec. + BADCODEC if not accepted. Client must then revert to previous codec + BADLEN if length of query is too short + +Options: +Client sends: + First byte o or O + 5 bits coded as Base32 char, meaning userid + number of options (n) as decimal digit + n chars, each a valid option (to be processed in order) + CMC as 3 Base32 chars +Server sends: + Option chars in the same order as request, indicating that options were + accepted by the server. + BADCODEC if not accepted. Previous situation remains. + BADLEN if number of options doesn't match length of query. + All options affect only the requesting client. + + Option chars: + t or T: Downstream encoding Base32, for TXT/CNAME/A/MX (default) + s or S: Downstream encoding Base64, for TXT/CNAME/A/MX + u or U: Downstream encoding Base64u, for TXT/CNAME/A/MX + v or V: Downstream encoding Base128, for TXT/CNAME/A/MX + r or R: Downstream encoding Raw, for PRIVATE/TXT/NULL (assumed for + PRIVATE and NULL) + If codec unsupported for request type, server will use Base32; note + that server will answer any mix of request types that a client sends. + Server may disregard this option; client must always use the downstream + encoding type indicated in every downstream DNS packet. + + c or C: Downstream compression enabled (compressed before encoding) + d or D: Downstream compression disabled + l or L: Lazy mode, server will keep a number of requests waiting until data + becomes available to send downstream or the requests time out. The + timeout value for requests is controlled by the client. + Applies only to data transfer; handshake is always answered immediately. + i or I: Immediate (non-lazy) mode, server will answer all requests + (nearly) immediately. + +Probe downstream fragment size: +Client sends: + First byte r or R + Second byte userid char + 2 bytes big-endian fragsize encoded as 4 bytes base32 + Then follows a long random query which contents does not matter. +Server sends: + Requested number of bytes as a response. The first two bytes contain + the requested length. The third byte is 107 (0x6B). The fourth byte + is a random value, and each following byte is incremented with 107. + This is checked by the client to determine corruption. + BADFRAG if requested length not accepted. + +Set downstream fragment size: +Client sends: + First byte n or N + Rest encoded with base32: + 1 byte userid + 2 bytes new downstream fragment size (big-endian) + CMC +Server sends: + 2 bytes new downstream fragment size. After this all downstream + payloads will be max (fragsize + 2) bytes long. + BADFRAG if not accepted. + + +Upstream data header: + 76543 21076 54321076 54321076 5432 + +!----+!----+!----!--+--!----!+----+ + |0UUUU|UDCMC| Seq ID | Dn ACK |ACFL| + +-----+-----+--------+--------+----+ + +Downstream data header: |=> only if ping (P) flag set | + 0 1 2 3 4 5 6 + +--------+--------+76543210+--------+--------+--------+--------+ + | Seq ID | Up ACK |00IPACFL|Dn Wsize|Up Wsize|DnWstart|UpWstart| + +--------+--------+--------+--------+--------+--------+--------+ + +UUUU = Userid +L = Last fragment flag +A = ACK flag +F = First fragment flag +C = Compression enabled for downstream packet +P = ping flag: extra header present +I = responded to immediately (for RTT calculation) - downstream only +UDCMC = Upstream Data CMC char (base36 [a-z0-9]) + +Up/Dn Wsize/Wstart = upstream/downstream window size/window start Seq ID + +Upstream data packet starts with 1 byte ASCII hex coded user byte; then +1 char data-CMC; then 4 bytes Base32 encoded header; then comes the payload +data, encoded with the chosen upstream codec. + +Downstream data starts with 3 byte header, followed by data, which may be +compressed. If Ping flag is set, another 4 bytes are appended to the header, +containing upstream and downstream window sizes and window start sequence IDs. +The response does not need to contain data. If the server has no data to send, +the response will always include the ping header and the ping flag will be set. + +In NULL and PRIVATE responses, downstream data is always raw. In all other +response types, downstream data is encoded (see Options above). +Encoding type is indicated by 1 prefix char (before the data header): +TXT: + End result is always DNS-chopped (series of len-prefixed strings + <=255 bytes) + t or T: Base32 encoded before chop, decoded after un-chop + s or S: Base64 encoded before chop, decoded after un-chop + u or U: Base64u encoded before chop, decoded after un-chop + v or V: Base128 encoded before chop, decoded after un-chop + r or R: Raw no encoding, only DNS-chop +SRV/MX/CNAME/A: + h or H: Hostname encoded with Base32 + i or I: Hostname encoded with Base64 + j or J: Hostname encoded with Base64u + k or K: Hostname encoded with Base128 +SRV and MX may reply with multiple hostnames, each encoded separately. Each +has a 10-multiple priority, and encoding/decoding is done in strictly +increasing priority sequence 10, 20, 30, etc. without gaps. Note that some DNS +relays will shuffle the answer records in the response. + + +Ping: +Client sends: + First byte p or P + Second byte CMC + Rest encoded with Base32: + 0 1 2...8 9-10 + +--------+--------+---+76543210+---+ + |0000UUUU|Dn SeqID|...|000WTANR|CMC| + +--------+--------+---+--------+---+ + 4 bits Userid + 1 byte Downstream seq ID ACK + 1 byte window size (upstream) + 1 byte window size (downstream) + 1 byte window start (upstream) + 1 byte window start (downstream) + 2 bytes big-endian server timeout in ms + 2 bytes big-endian downstream fragment ACK timeout in ms + + 1 byte flags: + W = update window frag timeout + T = update server timeout + A = is ACKing downstream frag + N = is NACKing downstream frag (unused) + R = response must contain ping header (data optional) + 2 bytes CMC + +The server responses to Ping and Data packets are compatible, and are described +above (refer to downstream data header). + +If R (respond) bit is set, the server responds immediately with a ping header. +The server must also adjust its window sizes to those provided by the ping. +If the T but is set, the server sets the user's DNS timeout to the value spec- +ified by the packet. + +In lazy mode, unless the R flag is set, the server will hold the ping until it +times out or more data becomes available to send. + + +"Lazy-mode" operation +===================== + +Client-server DNS traffic sequence has been reordered to provide increased +(interactive) performance and greatly reduced latency. + +Idea taken from Lucas Nussbaum's slides (24th IFIP International Security +Conference, 2009) at http://www.loria.fr/~lnussbau/tuns.html. Current +implementation is original to iodine, no code or documentation from any other +project was consulted during development. + +Server: +In lazy mode, except where otherwise specified, responses are sent using the +oldest pending query held in the server's buffer (QMEM). The server responds +to a stored pending query when the query times out, an upstream ACK is pending +(for that user), or the server has an excess of pending queries (more than the +user's downstream window size). + +Upstream data fragments are ACK'd immediately to keep data flowing. + +Upstream pings are answered immediately only when the Respond flag is set (see +ping header), in which case the response is to the same DNS query as the ping. +Immediate responses (<10ms old) to either ping or data requests are marked +and used to calculate the round-trip-time for the connection. + +Client: +The client keeps track of all queries it sends, and maintains a minimum of + pending queries to fill the server buffer. +Downstream data is always ACK'd immediately with a new request (either a ping +or data if available). The client sends excess requests (ie. already has enough +pending queries) for ACKs or for new data. + + +====================================================== +2. Raw UDP protocol +====================================================== + +This protocol does not implement data windowing and does not guarantee data +delivery, however it is likely faster due to the fact that data is not split +into fragments for sending. Full packets are compressed and sent when they +arrive on the tun device, and are processed immediately on the other side. + +All Raw UDP protcol messages start with a 3 byte header: 0x10d19e +This is not the start of a valid DNS message so it is easy to identify. +The fourth byte contains the command (C) and the user id (U). + + 7654 3210 + +----+----+ + |CCCC|UUUU| + +----+----+ + +Login message (command = 1): +The header is followed by a MD5 hash with the same password as in the DNS +login. The client starts the raw mode by sending this message, and uses +the login challenge +1, and the server responds using the login challenge -1. +After the login message has been exchanged, both the server and the client +switch to raw udp mode for the rest of the connection. + +Data message (command = 2): +After the header comes the payload data, which is always compressed. + +Ping message (command = 3): +Sent from client to server and back to keep session open. Has no payload. + diff --git a/man/iodine.8 b/man/iodine.8 index 0eb9b9b..2c54b37 100644 --- a/man/iodine.8 +++ b/man/iodine.8 @@ -1,5 +1,5 @@ .\" groff -man -Tascii iodine.8 -.TH IODINE 8 "APR 2012" "User Manuals" +.TH IODINE 8 "OCT 2015" "User Manuals" .SH NAME iodine, iodined \- tunnel IPv4 over DNS .SH SYNOPSIS @@ -7,7 +7,9 @@ iodine, iodined \- tunnel IPv4 over DNS .B iodine [-h] -.B iodine [-4] [-6] [-f] [-r] [-u +.B iodine [-4] [-6] [-f] [-D] [-r] [-V +.I sec +.B [-u .I user .B ] [-P .I password @@ -21,8 +23,22 @@ iodine, iodined \- tunnel IPv4 over DNS .I rdomain .B ] [-m .I fragsize +.B ] [-w +.I downfrags +.B ] [-W +.I upfrags +.B ] [-i +.I sec +.B ] [-j +.I sec +.B ] [-c +.I 0|1 +.B ] [-C +.I 0|1 +.B ] [-s +.I ms .B ] [-M -.I namelen +.I maxlen .B ] [-z .I context .B ] [-F @@ -36,16 +52,19 @@ iodine, iodined \- tunnel IPv4 over DNS .B ] [-I .I interval .B ] +.I topdomain .B [ .I nameserver -.B ] -.I topdomain +.B [ +.I nameserver2 ... +.B ] ] + .B iodined [-v] .B iodined [-h] -.B iodined [-4] [-6] [-c] [-s] [-f] [-D] [-u +.B iodined [-4] [-6] [-f] [-D] [-c] [-s] [-u .I user .B ] [-t .I chrootdir @@ -81,17 +100,13 @@ iodine, iodined \- tunnel IPv4 over DNS .I /netmask .B ] .I topdomain + .SH DESCRIPTION .B iodine lets you tunnel IPv4 data through a DNS server. This can be useful in situations where Internet access is firewalled, -but DNS queries are allowed. It needs a TUN/TAP device to operate. The -bandwidth is asymmetrical, -with a measured maximum of 680 kbit/s upstream and 2.3 Mbit/s -downstream in a wired LAN test network. -Realistic sustained throughput on a Wifi network using a carrier-grade -DNS cache has been measured at some 50 kbit/s upstream and over 200 kbit/s -downstream. +but DNS queries are allowed. It needs a TUN/TAP device to operate. + .B iodine is the client application, .B iodined @@ -100,6 +115,28 @@ is the server. Note: server and client are required to speak the exact same protocol. In most cases, this means running the same iodine version. Unfortunately, implementing backward and forward protocol compatibility is usually not feasible. + +.SH PERFORMANCE +The bandwidth is asymmetrical, +with a measured maximum of 13.7 Mbits/sec upstream and 53.4 Mbits/sec downstream +with all data compression disabled in a wired LAN test network. +In the same situation with compression enabled, the measured data throughput was +approximately 46.1 Mbits/sec upstream and 37.4 Mbits/sec downstream. +Compression is enabled by default, and can allow faster +data transfer in both directions depending on the type of data being +transferred. +Realistic sustained throughput on a Wifi network using a carrier-grade +DNS cache has been measured at some 600 Kbit/s upstream and over 2 Mbits/sec +downstream once more with compression disabled, however that may be increased with +compression enabled. + +All of the above test scenarios used lazy mode with upstream/downstream windowsizes of +8 fragments (default) and fixed fragment, DNS and server timeouts. These parameters +were manually adjusted to best suit the environment, and can be specified using the +.B iodine +options described under +.IR "Fine-tuning Options" . + .SH OPTIONS .SS Common Options: .TP @@ -226,18 +263,67 @@ In these situations use \-L0 to force running in legacy mode (implies \-I1). .TP .B -I interval -Maximum interval between requests (pings) so that intermediate DNS -servers will not time out. Default is 4 in lazy mode, which will work -fine in most cases. When too many SERVFAIL errors occur, iodine -will automatically reduce this to 1. -To get absolute minimum DNS traffic, -increase well above 4, but not so high that SERVFAIL errors start to occur. +Target timeout for queries. This should be less than the smallest timeout for +any intermediate DNS servers to reduce SERVFAILS. If this is specified, the +server timeout will be adjusted automatically based on the round-trip time +so that queries remain pending for as long as possible (only in lazy mode). +This value will be used as the polling interval in immediate mode. + +When too many SERVFAIL errors occur, iodine will gradually reduce this until +it reaches 0.5 seconds or below. If SERVFAILs continue to occur, lazy mode +will be disabled and the server will respond to all queries immediately. + +To reduce DNS traffic, set this interval to something large and disable lazy +mode, or set the upstream and downstream window sizes to 1. There are some DNS relays with very small timeouts, notably dnsadvantage.com (ultradns), that will give -SERVFAIL errors even with \-I1; data will still get trough, +SERVFAIL errors even with \-I1; data will still get through, and these errors can be ignored. Maximum useful value is 59, since iodined will close a client's connection after 60 seconds of inactivity. + +.SS Fine-tuning Options (Client-side): +.TP +.B -s milliseconds +Minimum query send interval. Increase this gradually if you notice that the +nameserver(s) tend to fail more often with a high data load (and frequent +queries) or drop excess DNS queries. This will affect throughput so use with +caution. +.B -w windowsize +Size of downstream fragment sending window, or the number of fragments that +can be in transit downstream at any point in time. The client will attempt to +maintain at least this number of queries pending on the server in lazy mode, +even when idle, so that the server can always send this number of fragments +immediately if new data arrives on the tun device. +The default value is 8 fragments. Increase this for high latency connections +to improve throughput. The maximum usable value is probably around 128, however note +that although higher values are possible there may be fragment overlaps and you may +experience problems. +.TP +.B -W windowsize +Number of fragments that can be in transit upstream at any point in time. The +client will send a maximum of this number of queries immediately to the server +when new data is received in addition to any already pending queries (such as +those used to maintain the downstream windowsize). The server will respond to +any excess queries using the oldest pending query first. The same limits apply +as the downstream window size. +.TP +.B -i timeout +The maximum amount of time in seconds the server should hold on to a pending +query so as to not cause any intermediate DNS relays to time out. This should +be less than the target timeout (set with +.BR -I ) +by at least the round-trip time for the connection. +If not set, this will be calculated automatically based on the round-trip time +and the target timeout. +.TP +.B -c 0|1 +Enable or disable downstream data compression. Enabled by default. This may +increase overall downstream throughput, or it may not depending on the type +of data being transferred. +.B -C 0|1 +Enable/disable upstream data compression, also enabled by default. + .SS Server Options: .TP .B -c @@ -252,7 +338,9 @@ This should only be used if you have already configured the device that will be used. .TP .B -D -Increase debug level. Level 1 prints info about each RX/TX packet. +Increase debug level. Higher levels (>2) will spam the terminal with LOTS +of debug messages. Recompile using `make debug` to enable extra debug output +and debug timestamping. Implies the .B -f option. @@ -303,29 +391,30 @@ Make the server stop itself after max_idle_time seconds if no traffic have been This should be combined with systemd or upstart on demand activation for being effective. .SS Client Arguments: .TP -.B nameserver -The nameserver to use to relay the dns traffic. This can be any relaying +.B nameservers +The nameservers to use to relay the dns traffic. This can be any relaying nameserver or the server running iodined if reachable. This field can be given as an IPv4/IPv6 address or as a hostname. This argument is optional, and if not specified a nameserver will be read from the .I /etc/resolv.conf file. +Multiple nameservers can be specified, separated by spaces. .TP .B topdomain The dns traffic will be sent as queries for subdomains under \'topdomain'. This is normally a subdomain to a domain you own. Use a short domain name to get better throughput. If .B nameserver -is the iodined server, then the topdomain can be chosen freely. This argument +is the iodined server address, then the topdomain can be chosen freely. This argument must be the same on both the client and the server. .SS Server Arguments: .TP .B tunnel_ip[/netmask] This is the server's ip address on the tun interface. The client will be given the next ip number in the range. It is recommended to use the -10.0.0.0 or 172.16.0.0 ranges. The default netmask is /27, can be overridden -by specifying it here. Using a smaller network will limit the number of -concurrent users. +10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16 ranges. The default netmask is /27, +which can be overridden by specifying it here. Using a smaller network will +limit the number of concurrent users. .TP .B topdomain The dns traffic is expected to arrive as queries for @@ -333,10 +422,12 @@ subdomains under 'topdomain'. This is normally a subdomain to a domain you own. Use a short domain name to get better throughput. This argument must be the same on both the client and the server. Queries for domains other than 'topdomain' will be forwarded when the \-b option is given, otherwise -they will be dropped. +they will be ignored. + .SH EXAMPLES See the README file for both a quick test scenario, and a detailed description of real-world deployment. + .SH SECURITY Login is a relatively secure challenge-response MD5 hash, with the password never passing the wire. @@ -365,11 +456,14 @@ is set, iodined will use the value it is set to as password instead of asking for one. The .B -P option still has precedence. + .SH SEE ALSO -The README file in the source distribution contains some more elaborate +The README.md file in the source distribution contains some more elaborate information. + .SH BUGS File bugs at http://dev.kryo.se/iodine/ + .SH AUTHORS -Erik Ekman and Bjorn Andersson . Major -contributions by Anne Bezemer. +Erik Ekman , Bjorn Andersson and Frekky. +Major contributions by Anne Bezemer. diff --git a/src/Makefile b/src/Makefile index 04cef2e..14465c8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ -COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o common.o -CLIENTOBJS = iodine.o client.o util.o +COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o window.o common.o util.o +CLIENTOBJS = iodine.o client.o CLIENT = ../bin/iodine -SERVEROBJS = iodined.o user.o fw_query.o +SERVEROBJS = iodined.o user.o fw_query.o server.o SERVER = ../bin/iodined OS = `echo $(TARGETOS) | tr "a-z" "A-Z"` @@ -10,9 +10,18 @@ HEAD_COMMIT = `git rev-parse --short HEAD` LIBPATH = -L. LDFLAGS += -lz `sh osflags $(TARGETOS) link` $(LIBPATH) -CFLAGS += -std=c99 -c -g -Wall -D$(OS) -pedantic `sh osflags $(TARGETOS) cflags` -DGITREVISION=\"$(HEAD_COMMIT)\" +CFLAGS += -std=c99 -c -Wall -D$(OS) -pedantic `sh osflags $(TARGETOS) cflags` -DGITREVISION=\"$(HEAD_COMMIT)\" -all: stateos $(CLIENT) $(SERVER) +CFLAGS_RELEASE = -O3 -fno-strict-aliasing +CFLAGS_DEBUG = -g -O0 -DDEBUG_BUILD + +all: CFLAGS += $(CFLAGS_RELEASE) +all: executables + +debug: CFLAGS += $(CFLAGS_DEBUG) +debug: executables + +executables: stateos $(CLIENT) $(SERVER) stateos: @echo OS is $(OS), arch is $(ARCH) diff --git a/src/base128.c b/src/base128.c index be7ba86..98784a3 100644 --- a/src/base128.c +++ b/src/base128.c @@ -42,21 +42,23 @@ * accent chars since they might readily be entered in normal use, * don't use 254-255 because of possible function overloading in DNS systems. */ -static const unsigned char cb128[] = +static const uint8_t cb128[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" "\274\275\276\277" "\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317" "\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357" "\360\361\362\363\364\365\366\367\370\371\372\373\374\375"; -static unsigned char rev128[256]; +static uint8_t rev128[256]; static int reverse_init = 0; -static int base128_encode(char *, size_t *, const void *, size_t); -static int base128_decode(void *, size_t *, const char *, size_t); +static size_t base128_encode(uint8_t *, size_t *, const uint8_t *, size_t); +static size_t base128_decode(uint8_t *, size_t *, const uint8_t *, size_t); static int base128_handles_dots(); -static int base128_blksize_raw(); -static int base128_blksize_enc(); +static size_t base128_blksize_raw(); +static size_t base128_blksize_enc(); +static size_t base128_encoded_length(size_t inputlen); +static size_t base128_raw_length(size_t inputlen); static struct encoder base128_encoder = { @@ -66,7 +68,9 @@ static struct encoder base128_encoder = base128_handles_dots, base128_handles_dots, base128_blksize_raw, - base128_blksize_enc + base128_blksize_enc, + base128_encoded_length, + base128_raw_length }; struct encoder @@ -81,18 +85,30 @@ base128_handles_dots() return 0; } -static int +static size_t base128_blksize_raw() { return BLKSIZE_RAW; } -static int +static size_t base128_blksize_enc() { return BLKSIZE_ENC; } +static size_t +base128_encoded_length(size_t inputlen) +{ + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + (((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0); +} + +static size_t +base128_raw_length(size_t inputlen) +{ + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + (((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0); +} + inline static void base128_reverse_init() { @@ -109,8 +125,8 @@ base128_reverse_init() } } -static int -base128_encode(char *buf, size_t *buflen, const void *data, size_t size) +static size_t +base128_encode(uint8_t *ubuf, size_t *buflen, const uint8_t *udata, size_t size) /* * Fills *buf with max. *buflen characters, encoding size bytes of *data. * @@ -121,10 +137,8 @@ base128_encode(char *buf, size_t *buflen, const void *data, size_t size) * sets *buflen to : #bytes encoded from data */ { - unsigned char *ubuf = (unsigned char *) buf; - unsigned char *udata = (unsigned char *) data; - int iout = 0; /* to-be-filled output char */ - int iin = 0; /* one more than last input byte that can be + size_t iout = 0; /* to-be-filled output char */ + size_t iin = 0; /* one more than last input byte that can be successfully decoded */ /* Note: Don't bother to optimize manually. GCC optimizes @@ -203,8 +217,8 @@ base128_encode(char *buf, size_t *buflen, const void *data, size_t size) #define REV128(x) rev128[(int) (x)] -static int -base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) +static size_t +base128_decode(uint8_t *buf, size_t *buflen, const uint8_t *str, size_t slen) /* * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. * Decoding stops early when *str contains \0. @@ -217,8 +231,6 @@ base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) * return value : #bytes filled in buf (excluding \0) */ { - unsigned char *ustr = (unsigned char *) str; - unsigned char *ubuf = (unsigned char *) buf; int iout = 0; /* to-be-filled output byte */ int iin = 0; /* next input char to use in decoding */ @@ -231,61 +243,61 @@ base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x7f) << 1) | - ((REV128(ustr[iin + 1]) & 0x40) >> 6); + buf[iout] = ((REV128(str[iin]) & 0x7f) << 1) | + ((REV128(str[iin + 1]) & 0x40) >> 6); iin++; /* 0 used up, iin=1 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x3f) << 2) | - ((REV128(ustr[iin + 1]) & 0x60) >> 5); + buf[iout] = ((REV128(str[iin]) & 0x3f) << 2) | + ((REV128(str[iin + 1]) & 0x60) >> 5); iin++; /* 1 used up, iin=2 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x1f) << 3) | - ((REV128(ustr[iin + 1]) & 0x70) >> 4); + buf[iout] = ((REV128(str[iin]) & 0x1f) << 3) | + ((REV128(str[iin + 1]) & 0x70) >> 4); iin++; /* 2 used up, iin=3 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x0f) << 4) | - ((REV128(ustr[iin + 1]) & 0x78) >> 3); + buf[iout] = ((REV128(str[iin]) & 0x0f) << 4) | + ((REV128(str[iin + 1]) & 0x78) >> 3); iin++; /* 3 used up, iin=4 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x07) << 5) | - ((REV128(ustr[iin + 1]) & 0x7c) >> 2); + buf[iout] = ((REV128(str[iin]) & 0x07) << 5) | + ((REV128(str[iin + 1]) & 0x7c) >> 2); iin++; /* 4 used up, iin=5 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x03) << 6) | - ((REV128(ustr[iin + 1]) & 0x7e) >> 1); + buf[iout] = ((REV128(str[iin]) & 0x03) << 6) | + ((REV128(str[iin + 1]) & 0x7e) >> 1); iin++; /* 5 used up, iin=6 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x01) << 7) | - ((REV128(ustr[iin + 1]) & 0x7f)); + buf[iout] = ((REV128(str[iin]) & 0x01) << 7) | + ((REV128(str[iin + 1]) & 0x7f)); iin += 2; /* 6,7 used up, iin=8 */ iout++; } - ubuf[iout] = '\0'; + buf[iout] = '\0'; return iout; } diff --git a/src/base32.c b/src/base32.c index a058d38..ccb2133 100644 --- a/src/base32.c +++ b/src/base32.c @@ -33,11 +33,14 @@ static const char cb32_ucase[] = static unsigned char rev32[256]; static int reverse_init = 0; -static int base32_encode(char *, size_t *, const void *, size_t); -static int base32_decode(void *, size_t *, const char *, size_t); +static size_t base32_encode(uint8_t *, size_t *, const uint8_t *, size_t); +static size_t base32_decode(uint8_t *, size_t *, const uint8_t *, size_t); static int base32_handles_dots(); -static int base32_blksize_raw(); -static int base32_blksize_enc(); +static size_t base32_blksize_raw(); +static size_t base32_blksize_enc(); +static size_t base32_encoded_length(size_t inputlen); +static size_t base32_raw_length(size_t inputlen); + static struct encoder base32_encoder = { @@ -47,7 +50,9 @@ static struct encoder base32_encoder = base32_handles_dots, base32_handles_dots, base32_blksize_raw, - base32_blksize_enc + base32_blksize_enc, + base32_encoded_length, + base32_raw_length }; struct encoder @@ -62,18 +67,31 @@ base32_handles_dots() return 0; } -static int +static size_t base32_blksize_raw() { return BLKSIZE_RAW; } -static int +static size_t base32_blksize_enc() { return BLKSIZE_ENC; } +static size_t +base32_encoded_length(size_t inputlen) +{ + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + (((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0); +} + +static size_t +base32_raw_length(size_t inputlen) +{ + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + (((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0); +} + + inline static void base32_reverse_init() { @@ -105,8 +123,8 @@ b32_8to5(int in) return rev32[in]; } -static int -base32_encode(char *buf, size_t *buflen, const void *data, size_t size) +static size_t +base32_encode(uint8_t *buf, size_t *buflen, const uint8_t *udata, size_t size) /* * Fills *buf with max. *buflen characters, encoding size bytes of *data. * @@ -117,9 +135,8 @@ base32_encode(char *buf, size_t *buflen, const void *data, size_t size) * sets *buflen to : #bytes encoded from data */ { - unsigned char *udata = (unsigned char *) data; - int iout = 0; /* to-be-filled output char */ - int iin = 0; /* one more than last input byte that can be + size_t iout = 0; /* to-be-filled output char */ + size_t iin = 0; /* one more than last input byte that can be successfully decoded */ /* Note: Don't bother to optimize manually. GCC optimizes @@ -196,8 +213,8 @@ base32_encode(char *buf, size_t *buflen, const void *data, size_t size) #define REV32(x) rev32[(int) (x)] -static int -base32_decode(void *buf, size_t *buflen, const char *str, size_t slen) +static size_t +base32_decode(uint8_t *ubuf, size_t *buflen, const uint8_t *str, size_t slen) /* * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. * Decoding stops early when *str contains \0. @@ -210,9 +227,8 @@ base32_decode(void *buf, size_t *buflen, const char *str, size_t slen) * return value : #bytes filled in buf (excluding \0) */ { - unsigned char *ubuf = (unsigned char *) buf; - int iout = 0; /* to-be-filled output byte */ - int iin = 0; /* next input char to use in decoding */ + size_t iout = 0; /* to-be-filled output byte */ + size_t iin = 0; /* next input char to use in decoding */ base32_reverse_init (); diff --git a/src/base64.c b/src/base64.c index 7834731..fb189e3 100644 --- a/src/base64.c +++ b/src/base64.c @@ -33,11 +33,13 @@ static const char cb64[] = static unsigned char rev64[256]; static int reverse_init = 0; -static int base64_encode(char *, size_t *, const void *, size_t); -static int base64_decode(void *, size_t *, const char *, size_t); +static size_t base64_encode(uint8_t *, size_t *, const uint8_t *, size_t); +static size_t base64_decode(uint8_t *, size_t *, const uint8_t *, size_t); static int base64_handles_dots(); -static int base64_blksize_raw(); -static int base64_blksize_enc(); +static size_t base64_blksize_raw(); +static size_t base64_blksize_enc(); +static size_t base64_encoded_length(size_t inputlen); +static size_t base64_raw_length(size_t inputlen); static struct encoder base64_encoder = { @@ -47,7 +49,9 @@ static struct encoder base64_encoder = base64_handles_dots, base64_handles_dots, base64_blksize_raw, - base64_blksize_enc + base64_blksize_enc, + base64_encoded_length, + base64_raw_length }; struct encoder @@ -62,18 +66,30 @@ base64_handles_dots() return 0; } -static int +static size_t base64_blksize_raw() { return BLKSIZE_RAW; } -static int +static size_t base64_blksize_enc() { return BLKSIZE_ENC; } +static size_t +base64_encoded_length(size_t inputlen) +{ + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + (((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0); +} + +static size_t +base64_raw_length(size_t inputlen) +{ + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + (((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0); +} + inline static void base64_reverse_init() { @@ -90,8 +106,8 @@ base64_reverse_init() } } -static int -base64_encode(char *buf, size_t *buflen, const void *data, size_t size) +static size_t +base64_encode(uint8_t *buf, size_t *buflen, const uint8_t *udata, size_t size) /* * Fills *buf with max. *buflen characters, encoding size bytes of *data. * @@ -102,9 +118,8 @@ base64_encode(char *buf, size_t *buflen, const void *data, size_t size) * sets *buflen to : #bytes encoded from data */ { - unsigned char *udata = (unsigned char *) data; - int iout = 0; /* to-be-filled output char */ - int iin = 0; /* one more than last input byte that can be + size_t iout = 0; /* to-be-filled output char */ + size_t iin = 0; /* one more than last input byte that can be successfully decoded */ /* Note: Don't bother to optimize manually. GCC optimizes @@ -151,8 +166,8 @@ base64_encode(char *buf, size_t *buflen, const void *data, size_t size) #define REV64(x) rev64[(int) (x)] -static int -base64_decode(void *buf, size_t *buflen, const char *str, size_t slen) +static size_t +base64_decode(uint8_t *ubuf, size_t *buflen, const uint8_t *str, size_t slen) /* * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. * Decoding stops early when *str contains \0. @@ -165,9 +180,8 @@ base64_decode(void *buf, size_t *buflen, const char *str, size_t slen) * return value : #bytes filled in buf (excluding \0) */ { - unsigned char *ubuf = (unsigned char *) buf; - int iout = 0; /* to-be-filled output byte */ - int iin = 0; /* next input char to use in decoding */ + size_t iout = 0; /* to-be-filled output byte */ + size_t iin = 0; /* next input char to use in decoding */ base64_reverse_init (); diff --git a/src/client.c b/src/client.c index 38b573b..e403002 100644 --- a/src/client.c +++ b/src/client.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2014 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,9 +25,9 @@ #include #include #include -#include #include #include +#include #include #ifdef WINDOWS32 @@ -55,35 +56,71 @@ #include "login.h" #include "tun.h" #include "version.h" +#include "window.h" +#include "util.h" #include "client.h" -static void handshake_lazyoff(int dns_fd); +/* Output flags for debug and time between stats update */ +int debug; +int stats; static int running; static const char *password; -static struct sockaddr_storage nameserv; -static int nameserv_len; -static struct sockaddr_storage raw_serv; -static int raw_serv_len; +/* Nameserver/domain info */ +static struct socket *nameserv_addrs; +static int nameserv_addrs_len; +static int current_nameserver; +static struct socket raw_serv; static const char *topdomain; static uint16_t rand_seed; -/* Current up/downstream IP packet */ -static struct packet outpkt; -static struct packet inpkt; -int outchunkresent = 0; +/* Current up/downstream window data */ +static struct frag_buffer *outbuf; +static struct frag_buffer *inbuf; +static size_t windowsize_up; +static size_t windowsize_down; +static size_t maxfragsize_up; + +/* Next downstream seqID to be ACK'd (-1 if none pending) */ +static int next_downstream_ack; + +/* Remembering queries we sent for tracking purposes */ +static struct query_tuple *pending_queries; +static size_t num_pending; +static time_t max_timeout_ms; +static time_t send_interval_ms; +static time_t min_send_interval_ms; + +/* Server response timeout in ms and downstream window timeout */ +static time_t server_timeout_ms; +static time_t downstream_timeout_ms; +static int autodetect_server_timeout; + +/* Cumulative Round-Trip-Time in ms */ +static time_t rtt_total_ms; +static size_t num_immediate; + +/* Connection statistics */ +static size_t num_timeouts; +static size_t num_untracked; +static size_t num_servfail; +static size_t num_badip; +static size_t num_sent; +static size_t num_recv; +static size_t send_query_sendcnt = 0; +static size_t send_query_recvcnt = 0; +static size_t num_frags_sent; +static size_t num_frags_recv; +static size_t num_pings; /* My userid at the server */ static char userid; static char userid_char; /* used when sending (lowercase) */ static char userid_char2; /* also accepted when receiving (uppercase) */ -/* DNS id for next packet */ static uint16_t chunkid; -static uint16_t chunkid_prev; -static uint16_t chunkid_prev2; /* Base32 encoder used for non-data packets and replies */ static struct encoder *b32; @@ -96,22 +133,24 @@ static struct encoder *b128; * Defaults to Base32, can be changed after handshake */ static struct encoder *dataenc; +/* Upstream/downstream compression flags */ +static int compression_up; +static int compression_down; + /* The encoder to use for downstream data */ static char downenc = ' '; /* set query type to send */ -static unsigned short do_qtype = T_UNSET; +static uint16_t do_qtype = T_UNSET; /* My connection mode */ static enum connection conn; +static int connected; -static int selecttimeout; /* RFC says timeout minimum 5sec */ static int lazymode; static long send_ping_soon; static time_t lastdownstreamtime; -static long send_query_sendcnt = -1; -static long send_query_recvcnt = 0; -static int hostname_maxlen = 0xFF; +static size_t hostname_maxlen = 0xFF; void client_init() @@ -122,21 +161,36 @@ client_init() b64u = get_base64u_encoder(); b128 = get_base128_encoder(); dataenc = get_base32_encoder(); - rand_seed = ((unsigned int) rand()) & 0xFFFF; + rand_seed = (uint16_t) rand(); send_ping_soon = 1; /* send ping immediately after startup */ conn = CONN_DNS_NULL; - chunkid = ((unsigned int) rand()) & 0xFFFF; - chunkid_prev = 0; - chunkid_prev2 = 0; + chunkid = (uint16_t) rand(); - outpkt.len = 0; - outpkt.seqno = 0; - outpkt.fragment = 0; - outchunkresent = 0; - inpkt.len = 0; - inpkt.seqno = 0; - inpkt.fragment = 0; + /* RFC says timeout minimum 5sec */ + max_timeout_ms = 5000; + + windowsize_up = 8; + windowsize_down = 8; + + compression_up = 0; + compression_down = 1; + + next_downstream_ack = -1; + current_nameserver = 0; + + maxfragsize_up = 100; + + num_immediate = 1; + rtt_total_ms = 1000; + send_interval_ms = 1000; + min_send_interval_ms = 1; + downstream_timeout_ms = 5000; + + outbuf = NULL; + inbuf = NULL; + pending_queries = NULL; + connected = 0; } void @@ -152,10 +206,10 @@ client_get_conn() } void -client_set_nameserver(struct sockaddr_storage *addr, int addrlen) +client_set_nameservers(struct socket *addr, int addrslen) { - memcpy(&nameserv, addr, addrlen); - nameserv_len = addrlen; + nameserv_addrs = addr; + nameserv_addrs_len = addrslen; } void @@ -222,9 +276,26 @@ client_set_downenc(char *encoding) } void -client_set_selecttimeout(int select_timeout) +client_set_compression(int up, int down) { - selecttimeout = select_timeout; + compression_up = up; + compression_down = down; +} + +void +client_set_dnstimeout(int timeout, int servertimeout, int downfrag, int autodetect) +{ + max_timeout_ms = timeout; + server_timeout_ms = servertimeout; + downstream_timeout_ms = downfrag; + autodetect_server_timeout = autodetect; +} + +void +client_set_interval(int interval_msec, int mininterval_msec) +{ + send_interval_ms = interval_msec; + min_send_interval_ms = mininterval_msec; } void @@ -234,81 +305,300 @@ client_set_lazymode(int lazy_mode) } void -client_set_hostname_maxlen(int i) +client_set_windowsize(size_t up, size_t down) +/* set window sizes for upstream and downstream + * XXX upstream/downstream windowsizes might as well be the same */ { - if (i <= 0xFF) + windowsize_up = up; + windowsize_down = down; +} + +void +client_set_hostname_maxlen(size_t i) +{ + if (i <= 0xFF && i != hostname_maxlen) { hostname_maxlen = i; + maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); + if (outbuf) + outbuf->maxfraglen = maxfragsize_up; + } } const char * client_get_raw_addr() { - return format_addr(&raw_serv, raw_serv_len); + return format_addr(&raw_serv.addr, raw_serv.length); +} + +void +client_rotate_nameserver() +{ + current_nameserver ++; + if (current_nameserver >= nameserv_addrs_len) + current_nameserver = 0; +} + +void +immediate_mode_defaults() +{ + send_interval_ms = MIN(rtt_total_ms / num_immediate, 1000); + max_timeout_ms = MAX(4 * rtt_total_ms / num_immediate, 5000); + server_timeout_ms = 0; +} + +/* Client-side query tracking for lazy mode */ + +/* Handy macro for printing stats with messages */ +#ifdef DEBUG_BUILD +#define QTRACK_DEBUG(l, ...) \ + if (debug >= l) {\ + TIMEPRINT("[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", num_pending, PENDING_QUERIES_LENGTH, \ + num_untracked, num_timeouts, window_sending(outbuf, NULL), outbuf->numitems); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } +#else +#define QTRACK_DEBUG(...) +#endif + +static int +update_server_timeout(int dns_fd, int handshake) +/* Calculate server timeout based on average RTT, send ping "handshake" to set + * if handshake sent, return query ID */ +{ + time_t rtt_ms; + static size_t num_rtt_timeouts = 0; + + /* Get average RTT in ms */ + rtt_ms = rtt_total_ms / num_immediate; + if (rtt_ms >= max_timeout_ms && num_immediate > 5) { + num_rtt_timeouts++; + if (num_rtt_timeouts < 3) { + fprintf(stderr, "Target interval of %ld ms less than average round-trip of " + "%ld ms! Try increasing interval with -I.\n", max_timeout_ms, rtt_ms); + } else { + /* bump up target timeout */ + max_timeout_ms = rtt_ms + 1000; + server_timeout_ms = 1000; + if (lazymode) + fprintf(stderr, "Adjusting server timeout to %ld ms, target interval %ld ms. Try -I%.1f next time with this network.\n", + server_timeout_ms, max_timeout_ms, max_timeout_ms / 1000.0); + + num_rtt_timeouts = 0; + } + } else { + /* Set server timeout based on target interval and RTT */ + server_timeout_ms = max_timeout_ms - rtt_ms; + if (server_timeout_ms <= 0) { + server_timeout_ms = 0; + fprintf(stderr, "Setting server timeout to 0 ms: if this continues try disabling lazy mode. (-L0)\n"); + } + } + + /* update up/down window timeouts to something reasonable */ + downstream_timeout_ms = rtt_ms * 2; + outbuf->timeout = ms_to_timeval(downstream_timeout_ms); + + if (handshake) { + /* Send ping handshake to set server timeout */ + return send_ping(dns_fd, 1, -1, 1); + } + return -1; } static void -send_query(int fd, char *hostname) +check_pending_queries() +/* Updates pending queries list */ { - char packet[4096]; + num_pending = 0; + struct timeval now, qtimeout, max_timeout; + gettimeofday(&now, NULL); + /* Max timeout for queries is max interval + 1 second extra */ + max_timeout = ms_to_timeval(max_timeout_ms + 1000); + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].time.tv_sec > 0 && pending_queries[i].id >= 0) { + timeradd(&pending_queries[i].time, &max_timeout, &qtimeout); + if (!timercmp(&qtimeout, &now, >)) { + /* Query has timed out, clear timestamp but leave ID */ + pending_queries[i].time.tv_sec = 0; + num_timeouts++; + } + num_pending++; + } + } +} + +static void +query_sent_now(int id) +{ + int i = 0, found = 0; + if (!pending_queries) + return; + + if (id < 0 || id > 65535) + return; + + /* Replace any empty queries first, then timed out ones if necessary */ + for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].id < 0) { + found = 1; + break; + } + } + if (!found) { + for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].time.tv_sec == 0) { + found = 1; + break; + } + } + } + /* if no slots found after both checks */ + if (!found) { + QTRACK_DEBUG(1, "Buffer full! Failed to add id %d.", id); + } else { + /* Add query into found location */ + pending_queries[i].id = id; + gettimeofday(&pending_queries[i].time, NULL); + num_pending ++; + QTRACK_DEBUG(4, "Adding query id %d into pending_queries[%d]", id, i); + id = -1; + } +} + +static void +got_response(int id, int immediate, int fail) +{ + struct timeval now, rtt; + time_t rtt_ms; + gettimeofday(&now, NULL); + + QTRACK_DEBUG(4, "Got answer id %d (%s)%s", id, immediate ? "immediate" : "lazy", + fail ? ", FAIL" : ""); + + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (id >= 0 && pending_queries[i].id == id) { + if (num_pending > 0) + num_pending--; + + if (pending_queries[i].time.tv_sec == 0) { + if (num_timeouts > 0) { + /* If query has timed out but is still stored - just in case + * ID is kept on timeout in check_pending_queries() */ + num_timeouts --; + immediate = 0; + } else { + /* query is empty */ + continue; + } + } + + if (immediate || debug >= 4) { + timersub(&now, &pending_queries[i].time, &rtt); + rtt_ms = timeval_to_ms(&rtt); + } + + QTRACK_DEBUG(5, " found answer id %d in pending queries[%d], %ld ms old", id, i, rtt_ms); + + if (immediate) { + /* If this was an immediate response we can use it to get + more detailed connection statistics like RTT. + This lets us determine and adjust server lazy response time + during the session much more accurately. */ + rtt_total_ms += rtt_ms; + num_immediate++; + + if (autodetect_server_timeout) + update_server_timeout(-1, 0); + } + + /* Remove query info from buffer to mark it as answered */ + id = -1; + pending_queries[i].id = -1; + pending_queries[i].time.tv_sec = 0; + break; + } + } + if (id > 0) { + QTRACK_DEBUG(4, " got untracked response to id %d.", id); + num_untracked++; + } +} + +static int +send_query(int fd, uint8_t *hostname) +/* Returns DNS ID of sent query */ +{ + uint8_t packet[4096]; struct query q; size_t len; - chunkid_prev2 = chunkid_prev; - chunkid_prev = chunkid; + DEBUG(3, "TX: pkt len %lu: hostname '%s'", strlen((char *)hostname), hostname); + chunkid += 7727; if (chunkid == 0) /* 0 is used as "no-query" in iodined.c */ - chunkid = 7727; + chunkid = rand() & 0xFF; q.id = chunkid; q.type = do_qtype; - len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, hostname, strlen(hostname)); + len = dns_encode((char *)packet, sizeof(packet), &q, QR_QUERY, (char *)hostname, strlen((char *)hostname)); if (len < 1) { warnx("dns_encode doesn't fit"); - return; + return -1; } -#if 0 - fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]); -#endif + DEBUG(4, " Sendquery: id %5d name[0] '%c'", q.id, hostname[0]); - sendto(fd, packet, len, 0, (struct sockaddr*)&nameserv, nameserv_len); + sendto(fd, packet, len, 0, (struct sockaddr*) &nameserv_addrs[current_nameserver].addr, + nameserv_addrs[current_nameserver].length); + + client_rotate_nameserver(); /* There are DNS relays that time out quickly but don't send anything back on timeout. And there are relays where, in lazy mode, our new query apparently _replaces_ our previous query, and we get no answers at all in lazy mode while legacy immediate-ping-pong works just fine. + In this case, the up/down windowsizes may need to be set to 1 for there + to only ever be one query pending. Here we detect and fix these situations. (Can't very well do this anywhere else; this is the only place we'll reliably get to in such situations.) - */ + Note: only start fixing up connection AFTER we have connected + and if user hasn't specified server timeout/window timeout etc. */ - if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode) { + num_sent++; + if (send_query_sendcnt > 0 && send_query_sendcnt < 100 && + lazymode && connected && autodetect_server_timeout) { send_query_sendcnt++; - if ((send_query_sendcnt > 6 && send_query_recvcnt <= 0) || - (send_query_sendcnt > 10 && - 4 * send_query_recvcnt < send_query_sendcnt)) { - if (selecttimeout > 1) { - warnx("Receiving too few answers. Setting interval to 1 (-I1)"); - selecttimeout = 1; + if ((send_query_sendcnt > windowsize_down && send_query_recvcnt <= 0) || + (send_query_sendcnt > 2 * windowsize_down && 4 * send_query_recvcnt < send_query_sendcnt)) { + if (max_timeout_ms > 500) { + max_timeout_ms -= 200; + double secs = (double) max_timeout_ms / 1000.0; + fprintf(stderr, "Receiving too few answers. Setting target timeout to %.1fs (-I%.1f)\n", secs, secs); + /* restart counting */ send_query_sendcnt = 0; send_query_recvcnt = 0; + } else if (lazymode) { - warnx("Receiving too few answers. Will try to switch lazy mode off, but that may not always work any more. Start with -L0 next time on this network."); + fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" + " always work any more. Start with -L0 next time on this network.\n"); lazymode = 0; - selecttimeout = 1; - handshake_lazyoff(fd); + server_timeout_ms = 0; } + update_server_timeout(fd, 1); } } + return q.id; } static void -send_raw(int fd, char *buf, int buflen, int user, int cmd) +send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd) { char packet[4096]; int len; @@ -321,104 +611,133 @@ send_raw(int fd, char *buf, int buflen, int user, int cmd) } len += RAW_HDR_LEN; - packet[RAW_HDR_CMD] = cmd | (user & 0x0F); + packet[RAW_HDR_CMD] = (cmd & 0xF0) | (user & 0x0F); - sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv)); + sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv.addr, raw_serv.length); } static void -send_raw_data(int dns_fd) +send_raw_data(int dns_fd, uint8_t *data, size_t datalen) { - send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); - outpkt.len = 0; + send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA); } -static void -send_packet(int fd, char cmd, const char *data, const size_t datalen) +static int +send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) +/* Base32 encodes data and sends as single DNS query + * Returns ID of sent query */ { - char buf[4096]; + uint8_t buf[4096]; buf[0] = cmd; - build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain, - b32, hostname_maxlen); - send_query(fd, buf); + build_hostname(buf, sizeof(buf), data, datalen, topdomain, b32, hostname_maxlen, 1); + + return send_query(fd, buf); } -static inline int -is_sending() +int +send_ping(int fd, int ping_response, int ack, int set_timeout) { - return (outpkt.len != 0); + num_pings++; + if (conn == CONN_DNS_NULL) { + uint8_t data[13]; + int id; + + /* Build ping header (see doc/proto_xxxxxxxx.txt) */ + data[0] = userid; + data[1] = ack & 0xFF; + + if (outbuf && inbuf) { + data[2] = outbuf->windowsize & 0xff; /* Upstream window size */ + data[3] = inbuf->windowsize & 0xff; /* Downstream window size */ + data[4] = outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[5] = inbuf->start_seq_id & 0xff; /* Downstream window start */ + } + + *(uint16_t *) (data + 6) = htons(server_timeout_ms); + *(uint16_t *) (data + 8) = htons(downstream_timeout_ms); + + /* update server frag/lazy timeout, ack flag, respond with ping flag */ + data[10] = ((set_timeout & 1) << 4) | ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[11] = (rand_seed >> 8) & 0xff; + data[12] = (rand_seed >> 0) & 0xff; + rand_seed += 1; + + DEBUG(3, " SEND PING: respond %d, ack %d, %s(server %ld ms, downfrag %ld ms), flags %02X", + ping_response, ack, set_timeout ? "SET " : "", server_timeout_ms, + downstream_timeout_ms, data[8]); + + id = send_packet(fd, 'p', data, sizeof(data)); + + /* Log query ID as being sent now */ + query_sent_now(id); + return id; + } else { + send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); + return -1; + } } static void -send_chunk(int fd) +send_next_frag(int fd) +/* Sends next available fragment of data from the outgoing window buffer */ { - char buf[4096]; - int avail; - int code; - char *p; + static uint8_t buf[MAX_FRAGSIZE], hdr[5]; + int code, id; static int datacmc = 0; - char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; + static char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; + struct fragment *f; + size_t buflen; - p = outpkt.data; - p += outpkt.offset; - avail = outpkt.len - outpkt.offset; - - /* Note: must be same, or smaller than send_fragsize_probe() */ - outpkt.sentlen = build_hostname(buf + 5, sizeof(buf) - 5, p, avail, - topdomain, dataenc, hostname_maxlen); + /* Get next fragment to send */ + f = window_get_next_sending_fragment(outbuf, &next_downstream_ack); + if (!f) { + if (outbuf->numitems > 0) { + /* There is stuff to send but we're out of sync, so send a ping + * to get things back in order and keep the packets flowing */ + send_ping(fd, 1, next_downstream_ack, 1); + next_downstream_ack = -1; + window_tick(outbuf); + } + return; /* nothing to send */ + } /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ - buf[0] = userid_char; /* First byte is hex userid */ - code = ((outpkt.seqno & 7) << 2) | ((outpkt.fragment & 15) >> 2); - buf[1] = b32_5to8(code); /* Second byte is 3 bits seqno, 2 upper bits fragment count */ + buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ - code = ((outpkt.fragment & 3) << 3) | (inpkt.seqno & 7); - buf[2] = b32_5to8(code); /* Third byte is 2 bits lower fragment count, 3 bits downstream packet seqno */ + /* Next 3 bytes is seq ID, downstream ACK and flags */ + code = ((f->ack_other < 0 ? 0 : 1) << 3) | (f->compressed << 2) + | (f->start << 1) | f->end; - code = ((inpkt.fragment & 15) << 1) | (outpkt.sentlen == avail); - buf[3] = b32_5to8(code); /* Fourth byte is 4 bits downstream fragment count, 1 bit last frag flag */ + hdr[0] = f->seqID & 0xFF; + hdr[1] = f->ack_other & 0xFF; + hdr[2] = code << 4; /* Flags are in upper 4 bits - lower 4 unused */ + + buflen = sizeof(buf) - 1; + /* Encode 3 bytes data into 2 bytes after buf */ + b32->encode(buf + 2, &buflen, hdr, 3); + + /* Encode data into buf after header (6 = user + CMC + 4 bytes header) */ + build_hostname(buf, sizeof(buf), f->data, f->len, topdomain, + dataenc, hostname_maxlen, 6); - buf[4] = datacmcchars[datacmc]; /* Fifth byte is data-CMC */ datacmc++; if (datacmc >= 36) datacmc = 0; -#if 0 - fprintf(stderr, " Send: down %d/%d up %d/%d, %d bytes\n", - inpkt.seqno, inpkt.fragment, outpkt.seqno, outpkt.fragment, - outpkt.sentlen); -#endif + DEBUG(3, " SEND DATA: seq %d, ack %d, len %lu, s%d e%d c%d flags %1X", + f->seqID, f->ack_other, f->len, f->start, f->end, f->compressed, hdr[2] >> 4); - send_query(fd, buf); -} + id = send_query(fd, buf); + /* Log query ID as being sent now */ + query_sent_now(id); -static void -send_ping(int fd) -{ - if (conn == CONN_DNS_NULL) { - char data[4]; - - data[0] = userid; - data[1] = ((inpkt.seqno & 7) << 4) | (inpkt.fragment & 15); - data[2] = (rand_seed >> 8) & 0xff; - data[3] = (rand_seed >> 0) & 0xff; - - rand_seed++; - -#if 0 - fprintf(stderr, " Send: down %d/%d (ping)\n", - inpkt.seqno, inpkt.fragment); -#endif - - send_packet(fd, 'p', data, sizeof(data)); - } else { - send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); - } + window_tick(outbuf); + num_frags_sent++; } static void @@ -426,14 +745,24 @@ write_dns_error(struct query *q, int ignore_some_errors) /* This is called from: 1. handshake_waitdns() when already checked that reply fits to our latest query. - 2. tunnel_dns() when already checked that reply is for our ping or data - packet, but not necessarily the most recent (SERVFAIL mostly comes - after long delay). - So ignorable errors are never printed. + 2. tunnel_dns() when already checked that reply is for a ping or data + packet, but possibly timed out. + Errors should not be ignored, but too many can be annoying. */ { + static size_t errorcounts[24] = {0}; if (!q) return; + if (q->rcode < 24) { + errorcounts[q->rcode]++; + if (errorcounts[q->rcode] == 20) { + warnx("Too many error replies, not logging any more."); + return; + } else if (errorcounts[q->rcode] > 20) { + return; + } + } + switch (q->rcode) { case NOERROR: /* 0 */ if (!ignore_some_errors) @@ -461,8 +790,8 @@ write_dns_error(struct query *q, int ignore_some_errors) } } -static int -dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) +static size_t +dns_namedec(uint8_t *outdata, size_t outdatalen, uint8_t *buf, size_t buflen) /* Decodes *buf to *outdata. * *buf WILL be changed by undotify. * Note: buflen must be _exactly_ strlen(buf) before undotifying. @@ -480,8 +809,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b32); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b32); case 'i': /* Hostname++ with base64 */ case 'I': @@ -490,8 +818,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b64); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b64); case 'j': /* Hostname++ with base64u */ case 'J': @@ -500,8 +827,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b64u); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b64u); case 'k': /* Hostname++ with base128 */ case 'K': @@ -510,8 +836,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b128); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b128); case 't': /* plain base32(Thirty-two) from TXT */ case 'T': @@ -559,22 +884,20 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) } static int -read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) -/* FIXME: tun_fd needed for raw handling */ +read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query *q) /* Returns -1 on receive error or decode error, including DNS error replies. Returns 0 on replies that could be correct but are useless, and are not DNS error replies. Returns >0 on correct replies; value is #valid bytes in *buf. */ { - struct sockaddr_storage from; - char data[64*1024]; - socklen_t addrlen; + struct socket from; + uint8_t data[64*1024]; int r; - addrlen = sizeof(from); + from.length = sizeof(from.addr); if ((r = recvfrom(dns_fd, data, sizeof(data), 0, - (struct sockaddr*)&from, &addrlen)) < 0) { + (struct sockaddr*)&from.addr, &from.length)) < 0) { warn("recvfrom"); return -1; } @@ -585,7 +908,7 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) /* useless packet */ return 0; - rv = dns_decode(buf, buflen, q, QR_ANSWER, data, r); + rv = dns_decode((char *)buf, buflen, q, QR_ANSWER, (char *)data, r); if (rv <= 0) return rv; @@ -615,7 +938,7 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) int thispartlen, dataspace, datanew; while (1) { - thispartlen = strlen(buf); + thispartlen = strlen((char *)buf); thispartlen = MIN(thispartlen, buftotal-bufoffset); dataspace = sizeof(data) - dataoffset; if (thispartlen <= 0 || dataspace <= 0) @@ -635,38 +958,44 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) memcpy(buf, data, rv); } + DEBUG(2, "RX: id %5d name[0]='%c'", q->id, q->name[0]); + return rv; } else { /* CONN_RAW_UDP */ - unsigned long datalen; - char buf[64*1024]; + size_t datalen; + uint8_t buf[64*1024]; /* minimum length */ - if (r < RAW_HDR_LEN) return 0; + if (r < RAW_HDR_LEN) + return 0; /* should start with header */ - if (memcmp(data, raw_header, RAW_HDR_IDENT_LEN)) return 0; + if (memcmp(data, raw_header, RAW_HDR_IDENT_LEN)) + return 0; /* should be my user id */ - if (RAW_HDR_GET_USR(data) != userid) return 0; + if (RAW_HDR_GET_USR(data) != userid) + return 0; if (RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_DATA || RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_PING) lastdownstreamtime = time(NULL); /* should be data packet */ - if (RAW_HDR_GET_CMD(data) != RAW_HDR_CMD_DATA) return 0; + if (RAW_HDR_GET_CMD(data) != RAW_HDR_CMD_DATA) + return 0; r -= RAW_HDR_LEN; datalen = sizeof(buf); - if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) &data[RAW_HDR_LEN], r) == Z_OK) { + if (uncompress(buf, &datalen, data + RAW_HDR_LEN, r) == Z_OK) { write_tun(tun_fd, buf, datalen); } - /* don't process any further */ + /* all done */ return 0; } } -static int -handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeout) +int +handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) /* Wait for DNS reply fitting to our latest query and returns it. Returns length of reply = #bytes used in buf. Returns 0 if fitting reply happens to be useless. @@ -683,6 +1012,9 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo int r, rv; fd_set fds; struct timeval tv; + char qcmd; + + cmd = toupper(cmd); while (1) { tv.tv_sec = timeout; @@ -696,14 +1028,13 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo if (r == 0) return -3; /* select timeout */ - q.id = 0; + q.id = -1; q.name[0] = '\0'; - rv = read_dns_withq(dns_fd, 0, buf, buflen, &q); + rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q); - if (q.id != chunkid || (q.name[0] != c1 && q.name[0] != c2)) { -#if 0 - fprintf(stderr, "Ignoring unfitting reply id %d starting with '%c'\n", q.id, q.name[0]); -#endif + qcmd = toupper(q.name[0]); + if (q.id != chunkid || qcmd != cmd) { + DEBUG(1, "Ignoring unfitting reply id %d starting with '%c'", q.id, q.name[0]); continue; } @@ -748,41 +1079,80 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo return -1; } +int +parse_data(uint8_t *data, size_t len, struct fragment *f, int *immediate) +{ + size_t headerlen = DOWNSTREAM_HDR; + int ping; + memset(f, 0, sizeof(struct fragment)); + + f->seqID = data[0]; + + /* Flags */ + f->end = data[2] & 1; + f->start = (data[2] >> 1) & 1; + f->compressed = (data[2] >> 2) & 1; + f->ack_other = (data[2] >> 3) & 1 ? data[1] : -1; + ping = (data[2] >> 4) & 1; + + if (immediate) + *immediate = (data[2] >> 5) & 1; + + if (ping) { /* Handle ping stuff */ + static unsigned dn_start_seq, up_start_seq, dn_wsize, up_wsize; + + headerlen = DOWNSTREAM_PING_HDR; + if (len < headerlen) return -1; /* invalid packet - continue */ + + /* Parse data/ping header */ + dn_wsize = data[3]; + up_wsize = data[4]; + dn_start_seq = data[5]; + up_start_seq = data[6]; + DEBUG(3, "PING pkt data=%lu WS: up=%u, dn=%u; Start: up=%u, dn=%u", + len - headerlen, up_wsize, dn_wsize, up_start_seq, dn_start_seq); + } + f->len = len - headerlen; + if (f->len > 0) + memcpy(f->data, data + headerlen, MIN(f->len, sizeof(f->data))); + return ping; /* return ping flag (if corresponding query was a ping) */ +} + static int tunnel_tun(int tun_fd, int dns_fd) { - unsigned long outlen; - unsigned long inlen; - char out[64*1024]; - char in[64*1024]; + size_t datalen; + uint8_t out[64*1024]; + uint8_t in[64*1024]; + uint8_t *data; ssize_t read; if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) return -1; - /* We may be here only to empty the tun device; then return -1 - to force continue in select loop. */ - if (is_sending()) - return -1; + DEBUG(2, " IN: %lu bytes on tunnel, to be compressed: %d", read, compression_up); - outlen = sizeof(out); - inlen = read; - compress2((uint8_t*)out, &outlen, (uint8_t*)in, inlen, 9); - - memcpy(outpkt.data, out, MIN(outlen, sizeof(outpkt.data))); - outpkt.sentlen = 0; - outpkt.offset = 0; - outpkt.seqno = (outpkt.seqno + 1) & 7; - outpkt.len = outlen; - outpkt.fragment = 0; - outchunkresent = 0; + if (conn != CONN_DNS_NULL || compression_up) { + datalen = sizeof(out); + compress2(out, &datalen, in, read, 9); + data = out; + } else { + datalen = read; + data = in; + } if (conn == CONN_DNS_NULL) { - send_chunk(dns_fd); + /* Check if outgoing buffer can hold data */ + if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { + DEBUG(1, " Outgoing buffer full (%lu/%lu), not adding data!", + outbuf->numitems, outbuf->length); + return -1; + } - send_ping_soon = 0; + window_add_outgoing_data(outbuf, data, datalen, compression_up); + /* Don't send anything here to respect min. send interval */ } else { - send_raw_data(dns_fd); + send_raw_data(dns_fd, data, datalen); } return read; @@ -791,30 +1161,20 @@ tunnel_tun(int tun_fd, int dns_fd) static int tunnel_dns(int tun_fd, int dns_fd) { - static long packrecv = 0; - static long packrecv_oos = 0; - static long packrecv_servfail = 0; - int up_ack_seqno; - int up_ack_fragment; - int new_down_seqno; - int new_down_fragment; struct query q; - unsigned long datalen; - char buf[64*1024]; - int read; - int send_something_now = 0; + size_t datalen, buflen; + uint8_t buf[64*1024], cbuf[64*1024], *data; + struct fragment f; + int read, compressed, res, immediate; - memset(q.name, 0, sizeof(q.name)); - read = read_dns_withq(dns_fd, tun_fd, buf, sizeof(buf), &q); + memset(&q, 0, sizeof(q)); + memset(buf, 0, sizeof(buf)); + memset(cbuf, 0, sizeof(cbuf)); + read = read_dns_withq(dns_fd, tun_fd, cbuf, sizeof(cbuf), &q); if (conn != CONN_DNS_NULL) return 1; /* everything already done */ -#if 0 - fprintf(stderr, " Recv: id %5d name[0]='%c'\n", - q.id, q.name[0]); -#endif - /* Don't process anything that isn't data for us; usually error replies from fragsize probes etc. However a sequence of those, mostly 1 sec apart, will continuously break the >=2-second select @@ -823,272 +1183,138 @@ tunnel_dns(int tun_fd, int dns_fd) if (q.name[0] != 'P' && q.name[0] != 'p' && q.name[0] != userid_char && q.name[0] != userid_char2) { send_ping_soon = 700; + got_response(q.id, 0, 0); return -1; /* nothing done */ } - if (read < 2) { + if (read < DOWNSTREAM_HDR) { /* Maybe SERVFAIL etc. Send ping to get things back in order, - but wait a bit to prevent fast ping-pong loops. */ + but wait a bit to prevent fast ping-pong loops. + Only change options if user hasn't specified server timeout */ if (read < 0) write_dns_error(&q, 0); - if (read < 0 && q.rcode == SERVFAIL && lazymode && - selecttimeout > 1) { - if (packrecv < 500 && packrecv_servfail < 4) { - packrecv_servfail++; - warnx("Hmm, that's %ld. Your data should still go through...", packrecv_servfail); - } else if (packrecv < 500 && packrecv_servfail == 4) { - packrecv_servfail++; - warnx("I think %ld is too many. Setting interval to 1 to hopefully reduce SERVFAILs. But just ignore them if data still comes through. (Use -I1 next time on this network.)", packrecv_servfail); - selecttimeout = 1; - send_query_sendcnt = 0; - send_query_recvcnt = 0; - } else if (packrecv >= 500 && packrecv_servfail > 0) { - warnx("(Sorry, stopped counting; try -I1 if you experience hiccups.)"); - packrecv_servfail = 0; + if (q.rcode == SERVFAIL && read < 0) { + num_servfail++; + + if (lazymode) { + + if (send_query_recvcnt < 500 && num_servfail < 4) { + fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...\n", num_servfail); + + } else if (send_query_recvcnt < 500 && num_servfail >= 10 && + autodetect_server_timeout && max_timeout_ms >= 500 && num_servfail % 5 == 0) { + + max_timeout_ms -= 200; + double target_timeout = (float) max_timeout_ms / 1000.0; + fprintf(stderr, "Too many SERVFAILs (%ld), reducing timeout to" + " %.1f secs. (use -I%.1f next time on this network)\n", + num_servfail, target_timeout, target_timeout); + + /* Reset query counts stats */ + send_query_sendcnt = 0; + send_query_recvcnt = 0; + update_server_timeout(dns_fd, 1); + + } else if (send_query_recvcnt < 500 && num_servfail >= 40 && + autodetect_server_timeout && max_timeout_ms < 500) { + + /* last-ditch attempt to fix SERVFAILs - disable lazy mode */ + immediate_mode_defaults(); + fprintf(stderr, "Attempting to disable lazy mode due to excessive SERVFAILs\n"); + handshake_switch_options(dns_fd, 0, compression_down, downenc); + } } } - /* read==1 happens with "QMEM" illegal replies, caused by - heavy reordering, or after short disconnections when - data-CMC has looped around into the "duplicate" values. - All these cases are helped by faster pinging. */ -#if 0 - if (read == 1) - fprintf(stderr, " q=%c id %5d 1-byte illegal \"QMEM\" reply\n", q.name[0], q.id); -#endif - send_ping_soon = 900; + + /* Mark query as received */ + got_response(q.id, 0, 1); return -1; /* nothing done */ } - if (read == 5 && !strncmp("BADIP", buf, 5)) { - warnx("BADIP: Server rejected sender IP address (maybe iodined -c will help), or server kicked us due to timeout. Will exit if no downstream data is received in 60 seconds."); - return -1; /* nothing done */ - } + send_query_recvcnt++; /* unlikely we will ever overflow (2^64 queries is a LOT) */ - if (send_ping_soon) { - send_something_now = 1; - send_ping_soon = 0; - } - - /* Decode the data header, update seqno and frag; - already checked read>=2 - Note that buf[] gets overwritten when down-pkt complete */ - new_down_seqno = (buf[1] >> 5) & 7; - new_down_fragment = (buf[1] >> 1) & 15; - up_ack_seqno = (buf[0] >> 4) & 7; - up_ack_fragment = buf[0] & 15; - -#if 0 - fprintf(stderr, " Recv: id %5d down %d/%d up %d/%d, %d bytes\n", - q.id, new_down_seqno, new_down_fragment, up_ack_seqno, - up_ack_fragment, read); -#endif - - /* Downstream data traffic */ - - if (read > 2 && new_down_seqno != inpkt.seqno && - recent_seqno(inpkt.seqno, new_down_seqno)) { - /* This is the previous seqno, or a bit earlier. - Probably out-of-sequence dupe due to unreliable - intermediary DNS. Don't get distracted, but send - ping quickly to get things back in order. - Ping will send our current seqno idea. - If it's really a new packet that skipped multiple seqnos - (why??), server will re-send and drop a few times and - eventually everything will work again. */ - read = 2; - send_ping_soon = 500; - /* Still process upstream ack, if any */ - } - - if (!(packrecv & 0x1000000)) - packrecv++; - send_query_recvcnt++; /* overflow doesn't matter */ - - /* Don't process any non-recent stuff any further. - No need to remember more than 3 ids: in practice any older replies - arrive after new/current replies, and whatever data the old replies - have, it has become useless in the mean time. - Actually, ever since iodined is replying to both the original query - and the last dupe, this hardly triggers any more. - */ - if (q.id != chunkid && q.id != chunkid_prev && q.id != chunkid_prev2) { - packrecv_oos++; -#if 0 - fprintf(stderr, " q=%c Packs received = %8ld Out-of-sequence = %8ld\n", q.name[0], packrecv, packrecv_oos); -#endif - if (lazymode && packrecv < 1000 && packrecv_oos == 5) { - if (selecttimeout > 1) - warnx("Hmm, getting some out-of-sequence DNS replies. Setting interval to 1 (use -I1 next time on this network). If data traffic still has large hiccups, try if -L0 works better."); - else - warnx("Hmm, getting some out-of-sequence DNS replies. If data traffic often has large hiccups, try running with -L0 ."); - selecttimeout = 1; - send_query_sendcnt = 0; - send_query_recvcnt = 0; - } - - if (send_something_now) { - send_ping(dns_fd); - send_ping_soon = 0; + if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { + num_badip++; + if (num_badip % 5 == 1) { + fprintf(stderr, "BADIP (%ld): Server rejected sender IP address (maybe iodined -c will help), or server " + "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.\n", num_badip); } return -1; /* nothing done */ } -#if 0 - fprintf(stderr, " q=%c Packs received = %8ld Out-of-sequence = %8ld\n", q.name[0], packrecv, packrecv_oos); -#endif /* Okay, we have a recent downstream packet */ lastdownstreamtime = time(NULL); - /* In lazy mode, we shouldn't get much replies to our most-recent - query, only during heavy data transfer. Since this means the server - doesn't have any packets left, send one relatively fast (but not - too fast, to avoid runaway ping-pong loops..) */ - if (q.id == chunkid && lazymode) { - if (!send_ping_soon || send_ping_soon > 900) - send_ping_soon = 900; + num_recv++; + + /* Decode the downstream data header and fragment-ify ready for processing */ + res = parse_data(cbuf, read, &f, &immediate); + + /* Mark query as received */ + got_response(q.id, immediate, 0); + + if ((debug >= 3 && res) || (debug >= 2 && !res)) + fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %lu, s%d e%d\n", + res ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); + + + window_ack(outbuf, f.ack_other); + window_tick(outbuf); + + /* In lazy mode, we shouldn't get immediate replies to our most-recent + query, only during heavy data transfer. Since this means the server + doesn't have any packets to send, send one relatively fast (but not + too fast, to avoid runaway ping-pong loops..) */ + /* Don't send anything too soon; no data waiting from server */ + if (f.len == 0) { + if (!res) + DEBUG(1, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); + if (!lazymode) + send_ping_soon = 100; + else + send_ping_soon = 700; + return -1; } - if (read == 2 && new_down_seqno != inpkt.seqno && - !recent_seqno(inpkt.seqno, new_down_seqno)) { - /* This is a seqno that we didn't see yet, but it has - no data any more. Possible since iodined will send - fitting packs just once and not wait for ack. - Real data got lost, or will arrive shortly. - Update our idea of the seqno, and drop any waiting - old pack. Send ping to get things back on track. */ - inpkt.seqno = new_down_seqno; - inpkt.fragment = new_down_fragment; - inpkt.len = 0; - send_ping_soon = 500; + /* Get next ACK if nothing already pending: if we get a new ack + * then we must send it immediately. */ + if (next_downstream_ack >= 0) { + /* If this happens something is wrong (or last frag was a re-send) + * May result in ACKs being delayed. */ + DEBUG(1, "next_downstream_ack NOT -1! (%d), %u resends, %u oos", next_downstream_ack, outbuf->resends, outbuf->oos); } - while (read > 2) { - /* "if" with easy exit */ + /* Downstream data traffic + ack data fragment */ + next_downstream_ack = f.seqID; + window_process_incoming_fragment(inbuf, &f); - if (new_down_seqno != inpkt.seqno) { - /* New packet (and not dupe of recent; checked above) */ - /* Forget any old packet, even if incomplete */ - inpkt.seqno = new_down_seqno; - inpkt.fragment = new_down_fragment; /* hopefully 0 */ - inpkt.len = 0; - } else if (inpkt.fragment == 0 && new_down_fragment == 0 && - inpkt.len == 0) { - /* Weird situation: we probably got a no-data reply - for this seqno (see above), and the actual data - is following now. */ - /* okay, nothing to do here, just so that next else-if - doesn't trigger */ - } else if (new_down_fragment <= inpkt.fragment) { - /* Same packet but duplicate fragment, ignore. - If the server didn't get our ack for it, the next - ping or chunk will do that. */ - send_ping_soon = 500; - break; - } else if (new_down_fragment > inpkt.fragment + 1) { - /* Quite impossible. We missed a fragment, but the - server got our ack for it and is sending the next - fragment already. Don't handle it but let server - re-send and drop. */ - send_ping_soon = 500; - break; - } - inpkt.fragment = new_down_fragment; + num_frags_recv++; - datalen = MIN(read - 2, sizeof(inpkt.data) - inpkt.len); - - /* we are here only when read > 2, so datalen "always" >=1 */ - - /* Skip 2 byte data header and append to packet */ - memcpy(&inpkt.data[inpkt.len], &buf[2], datalen); - inpkt.len += datalen; - - if (buf[1] & 1) { /* If last fragment flag is set */ - /* Uncompress packet and send to tun */ - /* RE-USES buf[] */ - datalen = sizeof(buf); - if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) inpkt.data, inpkt.len) == Z_OK) { - write_tun(tun_fd, buf, datalen); - } - inpkt.len = 0; - /* Keep .seqno and .fragment as is, so that we won't - reassemble from duplicate fragments */ - } - - /* Send anything to ack the received seqno/frag, and get more */ - if (inpkt.len == 0) { - /* was last frag; wait just a trifle because our - tun will probably return TCP-ack immediately. - 5msec = 200 DNSreq/sec */ - send_ping_soon = 5; - } else { - /* server certainly has more data */ - send_something_now = 1; - } - - break; - } - - /* NOTE: buf[] was overwritten when down-packet complete */ - - - /* Upstream data traffic */ - - if (is_sending()) { - /* already checked read>=2 */ -#if 0 - fprintf(stderr, "Got ack for %d,%d - expecting %d,%d - id=%d cur=%d prev=%d prev2=%d\n", - up_ack_seqno, up_ack_fragment, outpkt.seqno, outpkt.fragment, - q.id, chunkid, chunkid_prev, chunkid_prev2); -#endif - - if (up_ack_seqno == outpkt.seqno && - up_ack_fragment == outpkt.fragment) { - /* Okay, previously sent fragment has arrived */ - - outpkt.offset += outpkt.sentlen; - if (outpkt.offset >= outpkt.len) { - /* Packet completed */ - outpkt.offset = 0; - outpkt.len = 0; - outpkt.sentlen = 0; - outchunkresent = 0; - - /* Normally, server still has a query in queue, - but sometimes not. So send a ping. - (Comment this out and you'll see occasional - hiccups.) - But since the server often still has a - query and we can expect a TCP-ack returned - from our tun device quickly in many cases, - don't be too fast. - 20msec still is 50 DNSreq/second... */ - if (!send_ping_soon || send_ping_soon > 20) - send_ping_soon = 20; + datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed); + if (datalen > 0) { + if (compressed) { + buflen = sizeof(buf); + if ((res = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { + DEBUG(1, "Uncompress failed (%d) for data len %lu: reassembled data corrupted or incomplete!", res, datalen); + datalen = 0; } else { - /* More to send */ - outpkt.fragment++; - outchunkresent = 0; - send_chunk(dns_fd); - send_ping_soon = 0; - send_something_now = 0; + datalen = buflen; } + data = buf; + } else { + data = cbuf; } - /* else: Some wrong fragment has arrived, or old fragment is - acked again, mostly by ping responses. - Don't resend chunk, usually not needed; select loop will - re-send on timeout (1sec if is_sending()). */ + + if (datalen) + write_tun(tun_fd, data, datalen); } - - /* Send ping if we didn't send anything yet */ - if (send_something_now) { - send_ping(dns_fd); - send_ping_soon = 0; - } + /* Move window along after doing all data processing */ + window_tick(inbuf); return read; } @@ -1096,46 +1322,162 @@ tunnel_dns(int tun_fd, int dns_fd) int client_tunnel(int tun_fd, int dns_fd) { - struct timeval tv; + struct timeval tv, nextresend, tmp, now, now2; fd_set fds; int rv; - int i; + int i, use_min_send; + int sending, total; + time_t last_stats; + size_t sent_since_report, recv_since_report; + connected = 1; + + /* start counting now */ rv = 0; lastdownstreamtime = time(NULL); - send_query_sendcnt = 0; /* start counting now */ + last_stats = time(NULL); + + /* reset connection statistics */ + num_badip = 0; + num_servfail = 0; + num_timeouts = 0; + send_query_recvcnt = 0; + send_query_sendcnt = 0; + num_sent = 0; + num_recv = 0; + num_frags_sent = 0; + num_frags_recv = 0; + num_pings = 0; + + sent_since_report = 0; + recv_since_report = 0; + + use_min_send = 0; + + if (debug >= 5) + window_debug = debug - 3; while (running) { - tv.tv_sec = selecttimeout; - tv.tv_usec = 0; + if (!use_min_send) + tv = ms_to_timeval(max_timeout_ms); - if (is_sending()) { - /* fast timeout for retransmits */ - tv.tv_sec = 1; - tv.tv_usec = 0; + /* TODO: detect DNS servers which drop frequent requests + * TODO: adjust number of pending queries based on current data rate */ + if (conn == CONN_DNS_NULL && !use_min_send) { + + /* Send a single query per loop */ + sending = window_sending(outbuf, &nextresend); + total = sending; + check_pending_queries(); + if (num_pending < windowsize_down && lazymode) + total = MAX(total, windowsize_down - num_pending); + else if (num_pending < 1 && !lazymode) + total = MAX(total, 1); + + /* Upstream traffic - this is where all ping/data queries are sent */ + if (sending > 0 || total > 0 || next_downstream_ack >= 0) { + + if (sending > 0) { + /* More to send - next fragment */ + send_next_frag(dns_fd); + } else { + /* Send ping if we didn't send anything yet */ + send_ping(dns_fd, 0, next_downstream_ack, (num_pings > 20 && num_pings % 50 == 0)); + next_downstream_ack = -1; + } + + sending--; + total--; + QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu, will send another %d", + lazymode ? windowsize_down : 1, total); + + if (sending > 0 || (total > 0 && lazymode)) { + /* If sending any data fragments, or server has too few + * pending queries, send another one after min. interval */ + /* TODO: enforce min send interval even if we get new data */ + tv = ms_to_timeval(min_send_interval_ms); + if (min_send_interval_ms) + use_min_send = 1; + tv.tv_usec += 1; + } else if (total > 0 && !lazymode) { + /* In immediate mode, use normal interval when needing + * to send non-data queries to probe server. */ + tv = ms_to_timeval(send_interval_ms); + } + + if (sending == 0 && !use_min_send) { + /* check next resend time when not sending any data */ + if (timercmp(&nextresend, &tv, <)) + tv = nextresend; + } + + send_ping_soon = 0; + } } - if (send_ping_soon) { + if (stats) { + if (difftime(time(NULL), last_stats) >= stats) { + /* print useful statistics report */ + fprintf(stderr, "\n============ iodine connection statistics (user %1d) ============\n", userid); + fprintf(stderr, " Queries sent: %8lu" ", answered: %8lu" ", SERVFAILs: %4lu\n", + num_sent, num_recv, num_servfail); + fprintf(stderr, " last %3d secs: %7lu" " (%4lu/s), replies: %7lu" " (%4lu/s)\n", + stats, num_sent - sent_since_report, (num_sent - sent_since_report) / stats, + num_recv - recv_since_report, (num_recv - recv_since_report) / stats); + fprintf(stderr, " num IP rejected: %4lu, untracked: %4lu, lazy mode: %1d\n", + num_badip, num_untracked, lazymode); + fprintf(stderr, " Min send: %5ld ms, Avg RTT: %5ld ms Timeout server: %4ld ms\n", + min_send_interval_ms, rtt_total_ms / num_immediate, server_timeout_ms); + fprintf(stderr, " Queries immediate: %5lu, timed out: %4lu target: %4ld ms\n", + num_immediate, num_timeouts, max_timeout_ms); + if (conn == CONN_DNS_NULL) { + fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4ld ms\n", + outbuf->resends, inbuf->oos, downstream_timeout_ms); + fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", + num_frags_sent, num_frags_recv, num_pings); + } + /* update since-last-report stats */ + sent_since_report = num_sent; + recv_since_report = num_recv; + last_stats = time(NULL); + + } + } + + if (send_ping_soon && !use_min_send) { tv.tv_sec = 0; tv.tv_usec = send_ping_soon * 1000; + send_ping_soon = 0; } FD_ZERO(&fds); - if (!is_sending() || outchunkresent >= 2) { - /* If re-sending upstream data, chances are that - we're several seconds behind already and TCP - will start filling tun buffer with (useless) - retransmits. - Get up-to-date fast by simply dropping stuff, - that's what TCP is designed to handle. */ + if (conn != CONN_DNS_NULL || window_buffer_available(outbuf) > 16) { + /* Fill up outgoing buffer with available data if it has enough space + * The windowing protocol manages data retransmits, timeouts etc. */ FD_SET(tun_fd, &fds); } FD_SET(dns_fd, &fds); + DEBUG(4, "Waiting %ld ms before sending more... (min_send %d)", timeval_to_ms(&tv), use_min_send); + + if (use_min_send) { + gettimeofday(&now, NULL); + } + i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); - if (lastdownstreamtime + 60 < time(NULL)) { - warnx("No downstream data received in 60 seconds, shutting down."); + if (use_min_send && i > 0) { + /* enforce min_send_interval if we get interrupted by new tun data */ + gettimeofday(&now2, NULL); + timersub(&now2, &now, &tmp); + timersub(&tv, &tmp, &now); + tv = now; + } else { + use_min_send = 0; + } + + if (difftime(time(NULL), lastdownstreamtime) > 60) { + fprintf(stderr, "No downstream data received in 60 seconds, shutting down.\n"); running = 0; } @@ -1143,37 +1485,11 @@ client_tunnel(int tun_fd, int dns_fd) break; if (i < 0) - err(1, "select"); + err(1, "select < 0"); if (i == 0) { - /* timeout */ - if (is_sending()) { - /* Re-send current fragment; either frag - or ack probably dropped somewhere. - But problem: no cache-miss-counter, - so hostname will be identical. - Just drop whole packet after 3 retries, - and TCP retransmit will solve it. - NOTE: tun dropping above should be - >=(value_here - 1) */ - if (outchunkresent < 3) { - outchunkresent++; - send_chunk(dns_fd); - } else { - outpkt.offset = 0; - outpkt.len = 0; - outpkt.sentlen = 0; - outchunkresent = 0; - - send_ping(dns_fd); - } - } else { - send_ping(dns_fd); - } - send_ping_soon = 0; - + /* timed out - no new packets recv'd */ } else { - if (FD_ISSET(tun_fd, &fds)) { if (tunnel_tun(tun_fd, dns_fd) <= 0) continue; @@ -1182,9 +1498,9 @@ client_tunnel(int tun_fd, int dns_fd) we need to _not_ do tunnel_dns() then. If chunk sent, sets send_ping_soon=0. */ } + if (FD_ISSET(dns_fd, &fds)) { - if (tunnel_dns(tun_fd, dns_fd) <= 0) - continue; + tunnel_dns(tun_fd, dns_fd); } } } @@ -1195,7 +1511,7 @@ client_tunnel(int tun_fd, int dns_fd) static void send_login(int fd, char *login, int len) { - char data[19]; + uint8_t data[19]; memset(data, 0, sizeof(data)); data[0] = userid; @@ -1210,42 +1526,39 @@ send_login(int fd, char *login, int len) } static void -send_fragsize_probe(int fd, int fragsize) +send_fragsize_probe(int fd, uint16_t fragsize) { - char probedata[256]; - char buf[4096]; + uint8_t probedata[256]; + uint8_t buf[MAX_FRAGSIZE]; + uint8_t hdr[3]; + size_t hdr_len_enc = 6; - /* - * build a large query domain which is random and maximum size, - * will also take up maximal space in the return packet - */ + buf[0] = 'r'; /* Probe downstream fragsize packet */ + + hdr[0] = userid; + *(uint16_t *) (hdr + 1) = htons(fragsize); + + b32->encode(buf + 1, &hdr_len_enc, hdr, 3); + /* build a large query domain which is random and maximum size, + * will also take up maximum space in the return packet */ memset(probedata, MAX(1, rand_seed & 0xff), sizeof(probedata)); probedata[1] = MAX(1, (rand_seed >> 8) & 0xff); rand_seed++; /* Note: must either be same, or larger, than send_chunk() */ - build_hostname(buf + 5, sizeof(buf) - 5, probedata, sizeof(probedata), - topdomain, dataenc, hostname_maxlen); - - fragsize &= 2047; - - buf[0] = 'r'; /* Probe downstream fragsize packet */ - buf[1] = b32_5to8((userid << 1) | ((fragsize >> 10) & 1)); - buf[2] = b32_5to8((fragsize >> 5) & 31); - buf[3] = b32_5to8(fragsize & 31); - buf[4] = 'd'; /* dummy to match send_chunk() */ + build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), topdomain, + dataenc, hostname_maxlen, 6); send_query(fd, buf); } static void -send_set_downstream_fragsize(int fd, int fragsize) +send_set_downstream_fragsize(int fd, uint16_t fragsize) { - char data[5]; + uint8_t data[5]; data[0] = userid; - data[1] = (fragsize & 0xff00) >> 8; - data[2] = (fragsize & 0x00ff); + *(uint16_t *) (data + 1) = htons(fragsize); data[3] = (rand_seed >> 8) & 0xff; data[4] = (rand_seed >> 0) & 0xff; @@ -1257,12 +1570,10 @@ send_set_downstream_fragsize(int fd, int fragsize) static void send_version(int fd, uint32_t version) { - char data[6]; + uint8_t data[6]; - data[0] = (version >> 24) & 0xff; - data[1] = (version >> 16) & 0xff; - data[2] = (version >> 8) & 0xff; - data[3] = (version >> 0) & 0xff; + version = htonl(version); + *(uint32_t *) data = version; data[4] = (rand_seed >> 8) & 0xff; data[5] = (rand_seed >> 0) & 0xff; @@ -1275,7 +1586,7 @@ send_version(int fd, uint32_t version) static void send_ip_request(int fd, int userid) { - char buf[512] = "i____."; + uint8_t buf[512] = "i____."; buf[1] = b32_5to8(userid); buf[2] = b32_5to8((rand_seed >> 10) & 0x1f); @@ -1283,7 +1594,7 @@ send_ip_request(int fd, int userid) buf[4] = b32_5to8((rand_seed ) & 0x1f); rand_seed++; - strncat(buf, topdomain, 512 - strlen(buf)); + strncat((char *)buf, topdomain, 512 - strlen((char *)buf)); send_query(fd, buf); } @@ -1293,7 +1604,7 @@ send_raw_udp_login(int dns_fd, int userid, int seed) char buf[16]; login_calculate(buf, 16, password, seed + 1); - send_raw(dns_fd, buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN); + send_raw(dns_fd, (uint8_t *) buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN); } static void @@ -1307,10 +1618,10 @@ send_upenctest(int fd, char *s) buf[3] = b32_5to8((rand_seed ) & 0x1f); rand_seed++; - strncat(buf, s, 512); - strncat(buf, ".", 512); + strncat(buf, s, 512 - strlen(buf)); + strncat(buf, ".", 512 - strlen(buf)); strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static void @@ -1328,7 +1639,7 @@ send_downenctest(int fd, char downenc, int variant, char *s, int slen) rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static void @@ -1344,44 +1655,29 @@ send_codec_switch(int fd, int userid, int bits) rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); -} - - -static void -send_downenc_switch(int fd, int userid) -{ - char buf[512] = "o_____."; - buf[1] = b32_5to8(userid); - buf[2] = tolower(downenc); - - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; - - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static void -send_lazy_switch(int fd, int userid) +send_server_options(int fd, int userid, int lazy, int compression, char denc, char *options) +/* Options must be length >=4 */ { - char buf[512] = "o_____."; + char buf[512] = "oU3___CMC."; buf[1] = b32_5to8(userid); - if (lazymode) - buf[2] = 'l'; - else - buf[2] = 'i'; + options[0] = tolower(denc); + options[1] = lazy ? 'l' : 'i'; + options[2] = compression ? 'c' : 'd'; + options[3] = 0; + strncpy(buf + 3, options, 3); - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed ) & 0x1f); + buf[6] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[7] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[8] = b32_5to8((rand_seed) & 0x1f); rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static int @@ -1398,7 +1694,7 @@ handshake_version(int dns_fd, int *seed) send_version(dns_fd, PROTOCOL_VERSION); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'v', 'V', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'V', i+1); if (read >= 9) { payload = (((in[4] & 0xff) << 24) | @@ -1406,7 +1702,7 @@ handshake_version(int dns_fd, int *seed) ((in[6] & 0xff) << 8) | ((in[7] & 0xff))); - if (strncmp("VACK", in, 4) == 0) { + if (strncmp("VACK", (char *)in, 4) == 0) { *seed = payload; userid = in[8]; userid_char = hex[userid & 15]; @@ -1415,11 +1711,11 @@ handshake_version(int dns_fd, int *seed) fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", PROTOCOL_VERSION, userid); return 0; - } else if (strncmp("VNAK", in, 4) == 0) { + } else if (strncmp("VNAK", (char *)in, 4) == 0) { warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up", PROTOCOL_VERSION, payload); return 1; - } else if (strncmp("VFUL", in, 4) == 0) { + } else if (strncmp("VFUL", (char *)in, 4) == 0) { warnx("Server full, all %d slots are taken. Try again later", payload); return 1; } @@ -1449,7 +1745,7 @@ handshake_login(int dns_fd, int seed) send_login(dns_fd, login, 16); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'l', 'L', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'L', i+1); if (read > 0) { int netmask; @@ -1499,25 +1795,25 @@ handshake_raw_udp(int dns_fd, int seed) send_ip_request(dns_fd, userid); - len = handshake_waitdns(dns_fd, in, sizeof(in), 'i', 'I', i+1); + len = handshake_waitdns(dns_fd, in, sizeof(in), 'I', i+1); if (len == 5 && in[0] == 'I') { /* Received IPv4 address */ - struct sockaddr_in *raw4_serv = (struct sockaddr_in *) &raw_serv; + struct sockaddr_in *raw4_serv = (struct sockaddr_in *) &raw_serv.addr; raw4_serv->sin_family = AF_INET; memcpy(&raw4_serv->sin_addr, &in[1], sizeof(struct in_addr)); raw4_serv->sin_port = htons(53); - raw_serv_len = sizeof(struct sockaddr_in); + raw_serv.length = sizeof(struct sockaddr_in); got_addr = 1; break; } if (len == 17 && in[0] == 'I') { /* Received IPv6 address */ - struct sockaddr_in6 *raw6_serv = (struct sockaddr_in6 *) &raw_serv; + struct sockaddr_in6 *raw6_serv = (struct sockaddr_in6 *) &raw_serv.addr; raw6_serv->sin6_family = AF_INET6; memcpy(&raw6_serv->sin6_addr, &in[1], sizeof(struct in6_addr)); raw6_serv->sin6_port = htons(53); - raw_serv_len = sizeof(struct sockaddr_in6); + raw_serv.length = sizeof(struct sockaddr_in6); got_addr = 1; break; } @@ -1533,7 +1829,7 @@ handshake_raw_udp(int dns_fd, int seed) fprintf(stderr, "Failed to get raw server IP, will use DNS mode.\n"); return 0; } - fprintf(stderr, "Server is at %s, trying raw login: ", format_addr(&raw_serv, raw_serv_len)); + fprintf(stderr, "Server is at %s, trying raw login: ", format_addr(&raw_serv.addr, raw_serv.length)); fflush(stderr); /* do login against port 53 on remote server @@ -1594,7 +1890,7 @@ handshake_upenctest(int dns_fd, char *s) send_upenctest(dns_fd, s); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'z', 'Z', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Z', i+1); if (read == -2) return 0; /* hard error */ @@ -1604,13 +1900,6 @@ handshake_upenctest(int dns_fd, char *s) if (read > 0) { int k; -#if 0 - /* in[56] = '@'; */ - /* in[56] = '_'; */ - /* if (in[29] == '\344') in[29] = 'a'; */ - in[read] = '\0'; - fprintf(stderr, "BounceReply: >%s<\n", in); -#endif /* quick check if case swapped, to give informative error msg */ if (in[4] == 'A') { fprintf(stderr, "DNS queries get changed to uppercase, keeping upstream codec Base32\n"); @@ -1762,7 +2051,7 @@ handshake_downenctest(int dns_fd, char trycodec) send_downenctest(dns_fd, trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -1800,7 +2089,7 @@ handshake_downenc_autodetect(int dns_fd) if (do_qtype == T_NULL || do_qtype == T_PRIVATE) { /* no other choice than raw */ fprintf(stderr, "No alternative downstream codec available, using default (Raw)\n"); - return ' '; + return 'R'; } fprintf(stderr, "Autodetecting downstream codec (use -O to override)\n"); @@ -1862,7 +2151,7 @@ handshake_qtypetest(int dns_fd, int timeout) send_downenctest(dns_fd, trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', timeout); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', timeout); if (read != slen) return 0; /* incorrect */ @@ -1929,10 +2218,7 @@ handshake_qtype_autodetect(int dns_fd) if (handshake_qtypetest(dns_fd, timeout)) { /* okay */ highestworking = qtypenum; -#if 0 - fprintf(stderr, " Type %s timeout %d works\n", - client_get_qtype(), timeout); -#endif + DEBUG(1, " Type %s timeout %d works", client_get_qtype(), timeout); break; /* try others with longer timeout */ } @@ -1988,7 +2274,7 @@ handshake_edns0_check(int dns_fd) send_downenctest(dns_fd, trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -2039,22 +2325,25 @@ handshake_switch_codec(int dns_fd, int bits) send_codec_switch(dns_fd, userid, bits); - read = handshake_waitdns(dns_fd, in, sizeof(in), 's', 'S', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'S', i+1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length. "); + fprintf(stderr, "Server got bad message length.\n"); goto codec_revert; } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address. "); + fprintf(stderr, "Server rejected sender IP address.\n"); goto codec_revert; } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected the selected codec. "); + fprintf(stderr, "Server rejected the selected codec.\n"); goto codec_revert; } in[read] = 0; /* zero terminate */ fprintf(stderr, "Server switched upstream to codec %s\n", in); dataenc = tempenc; + + /* Update outgoing buffer max (decoded) fragsize */ + maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); return; } @@ -2063,133 +2352,73 @@ handshake_switch_codec(int dns_fd, int bits) if (!running) return; - fprintf(stderr, "No reply from server on codec switch. "); + fprintf(stderr, "No reply from server on codec switch.\n"); codec_revert: fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name); } -static void -handshake_switch_downenc(int dns_fd) +void +handshake_switch_options(int dns_fd, int lazy, int compression, char denc) { char in[4096]; - int i; int read; - char *dname; + char *dname, *comp_status, *lazy_status; + char opts[4]; + + comp_status = compression ? "enabled" : "disabled"; dname = "Base32"; - if (downenc == 'S') + if (denc == 'S') dname = "Base64"; - else if (downenc == 'U') + else if (denc == 'U') dname = "Base64u"; - else if (downenc == 'V') + else if (denc == 'V') dname = "Base128"; - else if (downenc == 'R') + else if (denc == 'R') dname = "Raw"; - fprintf(stderr, "Switching downstream to codec %s\n", dname); - for (i=0; running && i<5 ;i++) { + lazy_status = lazy ? "lazy" : "immediate"; - send_downenc_switch(dns_fd, userid); + fprintf(stderr, "Switching server options: %s mode, downstream codec %s, compression %s...\n", + lazy_status, dname, comp_status); + for (int i = 0; running && i < 5; i++) { - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); + send_server_options(dns_fd, userid, lazy, compression, denc, opts); + + read = handshake_waitdns(dns_fd, in, sizeof(in) - 1, 'O', i + 1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length. "); - goto codec_revert; + fprintf(stderr, "Server got bad message length.\n"); + goto opt_revert; } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address. "); - goto codec_revert; + fprintf(stderr, "Server rejected sender IP address.\n"); + goto opt_revert; } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected the selected codec. "); - goto codec_revert; + fprintf(stderr, "Server rejected the selected options.\n"); + goto opt_revert; } - in[read] = 0; /* zero terminate */ - fprintf(stderr, "Server switched downstream to codec %s\n", in); + fprintf(stderr, "Switched server options successfully. (%s)\n", opts); + lazymode = lazy; + compression_down = compression; + downenc = denc; return; } - fprintf(stderr, "Retrying codec switch...\n"); + fprintf(stderr, "Retrying options switch...\n"); } if (!running) return; - fprintf(stderr, "No reply from server on codec switch. "); + fprintf(stderr, "No reply from server on options switch.\n"); -codec_revert: - fprintf(stderr, "Falling back to downstream codec Base32\n"); -} +opt_revert: + comp_status = compression_down ? "enabled" : "disabled"; + lazy_status = lazymode ? "lazy" : "immediate"; -static void -handshake_try_lazy(int dns_fd) -{ - char in[4096]; - int i; - int read; - - fprintf(stderr, "Switching to lazy mode for low-latency\n"); - for (i=0; running && i<5; i++) { - - send_lazy_switch(dns_fd, userid); - - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); - - if (read > 0) { - if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length. "); - goto codec_revert; - } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address. "); - goto codec_revert; - } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected lazy mode. "); - goto codec_revert; - } else if (strncmp("Lazy", in, 4) == 0) { - fprintf(stderr, "Server switched to lazy mode\n"); - lazymode = 1; - return; - } - } - - fprintf(stderr, "Retrying lazy mode switch...\n"); - } - if (!running) - return; - - fprintf(stderr, "No reply from server on lazy switch. "); - -codec_revert: - fprintf(stderr, "Falling back to legacy mode\n"); - lazymode = 0; - selecttimeout = 1; -} - -static void -handshake_lazyoff(int dns_fd) -/* Used in the middle of data transfer, timing is different and no error msgs */ -{ - char in[4096]; - int i; - int read; - - for (i=0; running && i<5; i++) { - - send_lazy_switch(dns_fd, userid); - - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', 1); - - if (read == 9 && strncmp("Immediate", in, 9) == 0) { - warnx("Server switched back to legacy mode.\n"); - lazymode = 0; - selecttimeout = 1; - return; - } - } - if (!running) - return; - - warnx("No reply from server on legacy mode switch.\n"); + fprintf(stderr, "Falling back to previous configuration: downstream codec %s, %s mode, compression %s.\n", + dataenc->name, lazy_status, comp_status); } static int @@ -2229,8 +2458,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) /* in[123] = 123; */ if ((in[2] & 0xff) != 107) { - fprintf(stderr, "\n"); - warnx("corruption at byte 2, this won't work. Try -O Base32, or other -T options."); + warnx("\ncorruption at byte 2, this won't work. Try -O Base32, or other -T options."); *max_fragsize = -1; return 1; } @@ -2268,7 +2496,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) static int handshake_autoprobe_fragsize(int dns_fd) { - char in[4096]; + char in[MAX_FRAGSIZE]; int i; int read; int proposed_fragsize = 768; @@ -2276,14 +2504,14 @@ handshake_autoprobe_fragsize(int dns_fd) int max_fragsize; max_fragsize = 0; - fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)\n"); + fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)"); while (running && range > 0 && (range >= 8 || max_fragsize < 300)) { /* stop the slow probing early when we have enough bytes anyway */ for (i=0; running && i<3 ;i++) { send_fragsize_probe(dns_fd, proposed_fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'r', 'R', 1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'R', 1); if (read > 0) { /* We got a reply */ @@ -2309,21 +2537,19 @@ handshake_autoprobe_fragsize(int dns_fd) } } if (!running) { - fprintf(stderr, "\n"); - warnx("stopped while autodetecting fragment size (Try setting manually with -m)"); + warnx("\nstopped while autodetecting fragment size (Try setting manually with -m)"); return 0; } - if (max_fragsize <= 2) { + if (max_fragsize <= 6) { /* Tried all the way down to 2 and found no good size. But we _did_ do all handshake before this, so there must be some workable connection. */ - fprintf(stderr, "\n"); - warnx("found no accepted fragment size."); + warnx("\nfound no accepted fragment size."); warnx("try setting -M to 200 or lower, or try other -T or -O options."); return 0; } - /* data header adds 2 bytes */ - fprintf(stderr, "will use %d-2=%d\n", max_fragsize, max_fragsize - 2); + /* data header adds 6 bytes */ + fprintf(stderr, "will use %d-6=%d\n", max_fragsize, max_fragsize - 6); /* need 1200 / 16frags = 75 bytes fragsize */ if (max_fragsize < 82) { @@ -2351,12 +2577,12 @@ handshake_set_fragsize(int dns_fd, int fragsize) send_set_downstream_fragsize(dns_fd, fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'n', 'N', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'N', i+1); if (read > 0) { if (strncmp("BADFRAG", in, 7) == 0) { - fprintf(stderr, "Server rejected fragsize. Keeping default."); + fprintf(stderr, "Server rejected fragsize. Keeping default.\n"); return; } else if (strncmp("BADIP", in, 5) == 0) { fprintf(stderr, "Server rejected sender IP address.\n"); @@ -2376,6 +2602,49 @@ handshake_set_fragsize(int dns_fd, int fragsize) fprintf(stderr, "No reply from server when setting fragsize. Keeping default.\n"); } +static void +handshake_set_timeout(int dns_fd) +{ + char in[4096]; + int read, id; + + if (autodetect_server_timeout && lazymode) { + fprintf(stderr, "Calculating round-trip time for optimum server timeout..."); + } else { + fprintf(stderr, "Setting window sizes to %lu frags upstream, %lu frags downstream...", + windowsize_up, windowsize_down); + } + + for (int i = 0; running && i < 5; i++) { + + id = autodetect_server_timeout ? + update_server_timeout(dns_fd, 1) : send_ping(dns_fd, 1, -1, 1); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'P', i + 1); + got_response(id, 1, 0); + + fprintf(stderr, "."); + if (read > 0) { + if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address.\n"); + } + if (autodetect_server_timeout) + continue; + else + break; + } + + } + if (!running) + return; + + if (autodetect_server_timeout) + fprintf(stderr, "\nDetermined round-trip time of %ld ms, server timeout of %ld ms.\n", + rtt_total_ms / num_immediate, server_timeout_ms); + else + fprintf(stderr, " done\n"); +} + int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize) { @@ -2407,7 +2676,9 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (raw_mode && handshake_raw_udp(dns_fd, seed)) { conn = CONN_RAW_UDP; - selecttimeout = 20; + max_timeout_ms = 10000; + compression_down = 1; + compression_up = 1; } else { if (raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); @@ -2427,11 +2698,11 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (!running) return -1; - if (upcodec == 1) { + if (upcodec == 1) { /* Base64 */ handshake_switch_codec(dns_fd, 6); - } else if (upcodec == 2) { + } else if (upcodec == 2) { /* Base64u */ handshake_switch_codec(dns_fd, 26); - } else if (upcodec == 3) { + } else if (upcodec == 3) { /* Base128 */ handshake_switch_codec(dns_fd, 7); } if (!running) @@ -2443,20 +2714,19 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (!running) return -1; - if (downenc != ' ') { - handshake_switch_downenc(dns_fd); - } - if (!running) - return -1; - - if (lazymode) { - handshake_try_lazy(dns_fd); - } + /* Set options for compression, lazymode and downstream codec */ + handshake_switch_options(dns_fd, lazymode, compression_down, downenc); if (!running) return -1; if (autodetect_frag_size) { fragsize = handshake_autoprobe_fragsize(dns_fd); + if (fragsize > MAX_FRAGSIZE) { + /* This is very unlikely except perhaps over LAN */ + fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d." + " To fully utilize this connection, please recompile iodine/iodined.\n", fragsize, MAX_FRAGSIZE); + fragsize = MAX_FRAGSIZE; + } if (!fragsize) { return 1; } @@ -2465,6 +2735,22 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz handshake_set_fragsize(dns_fd, fragsize); if (!running) return -1; + + /* init windowing protocol */ + outbuf = window_buffer_init(64, windowsize_up, maxfragsize_up, WINDOW_SENDING); + outbuf->timeout = ms_to_timeval(downstream_timeout_ms); + /* Incoming buffer max fragsize doesn't matter */ + inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); + + /* init query tracking */ + num_untracked = 0; + num_pending = 0; + pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) + pending_queries[i].id = -1; + + /* set server window/timeout parameters and calculate RTT */ + handshake_set_timeout(dns_fd); } return 0; diff --git a/src/client.h b/src/client.h index c2493f1..b6fb9c0 100644 --- a/src/client.h +++ b/src/client.h @@ -18,23 +18,44 @@ #ifndef __CLIENT_H__ #define __CLIENT_H__ +#include "window.h" + +extern int debug; +extern int stats; + +#define PENDING_QUERIES_LENGTH (MAX(windowsize_up, windowsize_down) * 3) + +struct query_tuple { + int id; /* DNS query / response ID */ + struct timeval time; /* time sent or 0 if cleared */ +}; + void client_init(); void client_stop(); enum connection client_get_conn(); const char *client_get_raw_addr(); -void client_set_nameserver(struct sockaddr_storage *, int); +void client_rotate_nameserver(); +void client_set_nameservers(struct socket *, int); void client_set_topdomain(const char *cp); void client_set_password(const char *cp); int client_set_qtype(char *qtype); char *client_get_qtype(); void client_set_downenc(char *encoding); -void client_set_selecttimeout(int select_timeout); +void client_set_compression(int up, int down); +void client_set_dnstimeout(int, int, int, int); void client_set_lazymode(int lazy_mode); -void client_set_hostname_maxlen(int i); +void client_set_windowsize(size_t, size_t); +void client_set_hostname_maxlen(size_t i); +void client_set_interval(int, int); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); +int parse_data(uint8_t *data, size_t len, struct fragment *f, int *immediate); +int handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout); +void handshake_switch_options(int dns_fd, int lazy, int compression, char denc); +int send_ping(int fd, int ping_response, int ack, int timeout); + #endif diff --git a/src/common.c b/src/common.c index ce6c2aa..19b4461 100644 --- a/src/common.c +++ b/src/common.c @@ -282,7 +282,9 @@ do_detach() { #ifndef WINDOWS32 fprintf(stderr, "Detaching from terminal...\n"); - daemon(0, 0); + if (daemon(0, 0) != 0) { + err(1, "Failed to detach from terminal. Try running in foreground."); + } umask(0); alarm(0); #else @@ -310,7 +312,8 @@ read_password(char *buf, size_t len) fprintf(stderr, "Enter password: "); fflush(stderr); #ifndef WINDOWS32 - fscanf(stdin, "%79[^\n]", pwd); + if (!fscanf(stdin, "%79[^\n]", pwd)) + err(1, "EOF while reading password!"); #else for (i = 0; i < sizeof(pwd); i++) { pwd[i] = getch(); @@ -455,22 +458,6 @@ errx(int eval, const char *fmt, ...) } #endif - -int recent_seqno(int ourseqno, int gotseqno) -/* Return 1 if we've seen gotseqno recently (current or up to 3 back). - Return 0 if gotseqno is new (or very old). -*/ -{ - int i; - for (i = 0; i < 4; i++, ourseqno--) { - if (ourseqno < 0) - ourseqno = 7; - if (gotseqno == ourseqno) - return 1; - } - return 0; -} - #ifndef WINDOWS32 /* Set FD_CLOEXEC flag on file descriptor. * This stops it from being inherited by system() calls. diff --git a/src/common.h b/src/common.h index 2de23e9..99d7175 100644 --- a/src/common.h +++ b/src/common.h @@ -40,6 +40,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #include #include #include +#include #endif #define DNS_PORT 53 @@ -67,33 +68,62 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; # define DONT_FRAG_VALUE 1 #endif +#ifndef GITREVISION +#define GITREVISION "GIT" +#endif + #define T_PRIVATE 65399 /* Undefined RR type; "private use" range, see http://www.bind9.net/dns-parameters */ #define T_UNSET 65432 /* Unused RR type, never actually sent */ -struct packet -{ - int len; /* Total packet length */ - int sentlen; /* Length of chunk currently transmitted */ - int offset; /* Current offset */ - char data[64*1024]; /* The data */ - char seqno; /* The packet sequence number */ - char fragment; /* Fragment index */ -}; +#define DOWNSTREAM_HDR 3 +#define DOWNSTREAM_PING_HDR 7 +#define UPSTREAM_HDR 6 +#define UPSTREAM_PING 11 + +/* handy debug printing macro */ +#ifdef DEBUG_BUILD +#define TIMEPRINT(...) \ + struct timeval currenttime;\ + gettimeofday(¤ttime, NULL);\ + fprintf(stderr, "%03ld.%03ld ", (long) currenttime.tv_sec, (long) currenttime.tv_usec / 1000);\ + fprintf(stderr, __VA_ARGS__); + +#define DEBUG(level, ...) \ + if (debug >= level) {\ + TIMEPRINT("[D%d %s:%d] ", level, __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } +#else +#define TIMEPRINT(...) \ + fprintf(stderr, __VA_ARGS__); + +#define DEBUG(level, ...) \ + if (debug >= level) {\ + fprintf(stderr, "[D%d] ", level); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } +#endif + struct query { char name[QUERY_NAME_SIZE]; unsigned short type; unsigned short rcode; - unsigned short id; + int id; /* id < 0: unusued */ struct sockaddr_storage destination; socklen_t dest_len; struct sockaddr_storage from; socklen_t fromlen; - unsigned short id2; - struct sockaddr_storage from2; - socklen_t fromlen2; + struct timeval time_recv; +}; + +struct socket { + socklen_t length; + struct sockaddr_storage addr; }; enum connection { @@ -130,8 +160,6 @@ void errx(int eval, const char *fmt, ...); void warnx(const char *fmt, ...); #endif -int recent_seqno(int , int); - #ifndef WINDOWS32 void fd_set_close_on_exec(int fd); #endif diff --git a/src/dns.c b/src/dns.c index 6eecab2..e6a807e 100644 --- a/src/dns.c +++ b/src/dns.c @@ -408,10 +408,9 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz unsigned short type; char *data; unsigned short rlen; - int id; + uint16_t id; int rv; - q->id2 = 0; rv = 0; header = (HEADER*)packet; @@ -429,7 +428,6 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz ancount = ntohs(header->ancount); id = ntohs(header->id); - id = id & 0xFFFF; /* Kill any sign extension */ rlen = 0; diff --git a/src/encoding.c b/src/encoding.c index 4906a6b..3ebfa8b 100644 --- a/src/encoding.c +++ b/src/encoding.c @@ -19,29 +19,58 @@ #include "common.h" #include "encoding.h" -int -build_hostname(char *buf, size_t buflen, - const char *data, const size_t datalen, - const char *topdomain, struct encoder *encoder, int maxlen) +size_t +get_raw_length_from_dns(size_t enc_bytes, struct encoder *enc, const char *topdomain) +/* Returns the maximum length of raw data that can be encoded into enc_bytes */ { - size_t space; - char *b; + /* 2 byte for something - seems necessary */ + size_t enc_datalen = enc_bytes - strlen(topdomain) - 2; + /* Number of dots in length of encoded data */ + size_t dots = 1; + if (!enc->eats_dots()) /* Dots are not included in encoded data length */ + dots += enc_datalen / (DNS_MAXLABEL); + enc_datalen -= dots; + return enc->get_raw_length(enc_datalen); +} - space = MIN((size_t)maxlen, buflen) - strlen(topdomain) - 8; - /* 8 = 5 max header length + 1 dot before topdomain + 2 safety */ +size_t +get_encoded_dns_length(size_t raw_bytes, struct encoder *enc, const char *topdomain) +/* Returns length of encoded data from original data length orig_len; */ +{ + size_t dots = 1; /* dot before topdomain */ + size_t len = enc->get_encoded_length(raw_bytes); + if (!enc->places_dots()) + dots += len / DNS_MAXLABEL; /* number of dots needed in data */ + return len + dots + strlen(topdomain); +} - if (!encoder->places_dots()) - space -= (space / 57); /* space for dots */ +size_t +build_hostname(uint8_t *buf, size_t buflen, const uint8_t *data, const size_t datalen, + const char *topdomain, struct encoder *encoder, size_t maxlen, size_t header_len) +/* Builds DNS-compatible hostname for data using specified encoder and topdomain + * Encoded data is placed into buf. */ +{ + size_t space, enc; + uint8_t *b; + buflen -= header_len; + buf += header_len; + maxlen -= header_len; memset(buf, 0, buflen); - encoder->encode(buf, &space, data, datalen); + maxlen = MIN(maxlen, buflen); + + /* 1 byte for dot before topdomain + 1 byte extra for something */ + space = maxlen - strlen(topdomain) - (maxlen / DNS_MAXLABEL) - 2; + + enc = encoder->encode(buf, &space, data, datalen); +// warnx("build_hostname: enc %lu, predicted %lu; maxlen %lu, header %lu, datalen %lu, space %lu", +// encdata_len, encoder->get_encoded_length(datalen), maxlen, header_len, datalen, space); if (!encoder->places_dots()) - inline_dotify(buf, buflen); + enc = inline_dotify(buf - header_len, buflen + header_len) - header_len; - b = buf; - b += strlen(buf); + b = buf + enc; /* move b back one step to see if the dot is there */ b--; @@ -50,48 +79,49 @@ build_hostname(char *buf, size_t buflen, b++; /* move b ahead of the string so we can copy to it */ - strncpy(b, topdomain, strlen(topdomain)+1); + strncpy((char *)b, topdomain, strlen(topdomain)+1); +// warnx("build_hostname: host '%s' (sl %lu, actual %lu), topdomain '%s'", +// buf - header_len, strlen(buf - header_len), encdata_len + header_len + strlen(topdomain)+1, b); return space; } -int -unpack_data(char *buf, size_t buflen, char *data, size_t datalen, struct encoder *enc) +size_t +unpack_data(uint8_t *buf, size_t buflen, uint8_t *data, size_t datalen, struct encoder *enc) { if (!enc->eats_dots()) datalen = inline_undotify(data, datalen); return enc->decode(buf, &buflen, data, datalen); } -int -inline_dotify(char *buf, size_t buflen) +size_t +inline_dotify(uint8_t *buf, size_t buflen) { unsigned dots; - unsigned pos; - unsigned total; - char *reader, *writer; + size_t pos, total; + uint8_t *reader, *writer; - total = strlen(buf); - dots = total / 57; + total = strlen((char *)buf); + dots = total / DNS_MAXLABEL; writer = buf; writer += total; writer += dots; total += dots; - if (strlen(buf) + dots > buflen) { + if (strlen((char *)buf) + dots > buflen) { writer = buf; writer += buflen; total = buflen; } reader = writer - dots; - pos = (unsigned) (reader - buf) + 1; + pos = (reader - buf) + 1; while (dots) { *writer-- = *reader--; pos--; - if (pos % 57 == 0) { + if (pos % DNS_MAXLABEL == 0) { *writer-- = '.'; dots--; } @@ -101,12 +131,12 @@ inline_dotify(char *buf, size_t buflen) return total; } -int -inline_undotify(char *buf, size_t len) +size_t +inline_undotify(uint8_t *buf, size_t len) { - unsigned pos; + size_t pos; unsigned dots; - char *reader, *writer; + uint8_t *reader, *writer; writer = buf; reader = writer; diff --git a/src/encoding.h b/src/encoding.h index abb82da..aed7c28 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -18,6 +18,8 @@ #ifndef _ENCODING_H_ #define _ENCODING_H_ +#include + /* All-0, all-1, 01010101, 10101010: each 4 times to make sure the pattern spreads across multiple encoded chars -> 16 bytes total. Followed by 32 bytes from my /dev/random; should be enough. @@ -25,20 +27,27 @@ #define DOWNCODECCHECK1 "\000\000\000\000\377\377\377\377\125\125\125\125\252\252\252\252\201\143\310\322\307\174\262\027\137\117\316\311\111\055\122\041\141\251\161\040\045\263\006\163\346\330\104\060\171\120\127\277" #define DOWNCODECCHECK1_LEN 48 +#define DNS_MAXLABEL 63 + struct encoder { char name[8]; - int (*encode) (char *, size_t *, const void *, size_t); - int (*decode) (void *, size_t *, const char *, size_t); + size_t (*encode) (uint8_t *, size_t *, const uint8_t *, size_t); + size_t (*decode) (uint8_t *, size_t *, const uint8_t *, size_t); int (*places_dots) (void); int (*eats_dots) (void); - int (*blocksize_raw)(void); - int (*blocksize_encoded)(void); + size_t (*blocksize_raw)(void); + size_t (*blocksize_encoded)(void); + size_t (*get_encoded_length)(size_t); + size_t (*get_raw_length)(size_t); }; -int build_hostname(char *, size_t, const char *, const size_t, const char *, struct encoder *, int); -int unpack_data(char *, size_t, char *, size_t, struct encoder *); -int inline_dotify(char *, size_t); -int inline_undotify(char *, size_t); +size_t get_raw_length_from_dns(size_t enc_bytes, struct encoder *enc, const char *topdomain); +size_t get_encoded_dns_length(size_t raw_bytes, struct encoder *enc, const char *topdomain); + +size_t build_hostname(uint8_t *, size_t, const uint8_t *, const size_t, const char *, struct encoder *, size_t, size_t); +size_t unpack_data(uint8_t *, size_t, uint8_t *, size_t, struct encoder *); +size_t inline_dotify(uint8_t *, size_t); +size_t inline_undotify(uint8_t *, size_t); #endif /* _ENCODING_H_ */ diff --git a/src/iodine.c b/src/iodine.c index 2599a6d..77745e8 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -37,6 +36,7 @@ #endif #include "common.h" +#include "version.h" #include "tun.h" #include "client.h" #include "util.h" @@ -65,61 +65,81 @@ static void usage() __attribute__((noreturn)); #endif static void -usage() { +print_usage() +{ extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " - "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " - "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname); + fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + "[-w downfrags] [-W upfrags] [-i sec -j sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " + "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-R rdomain] " + "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [...]]]\n", __progname); +} + +static void +usage() +{ + print_usage(); exit(2); } static void -help() { - extern char *__progname; - +help() +{ fprintf(stderr, "iodine IP over DNS tunneling client\n"); - fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " - "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " - "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname); - fprintf(stderr, "Options to try if connection doesn't work:\n"); + print_usage(); + fprintf(stderr, "\nOptions to try if connection doesn't work:\n"); fprintf(stderr, " -T force dns type: NULL, PRIVATE, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); fprintf(stderr, " -O force downstream encoding for -T other than NULL: Base32, Base64, Base64u,\n"); fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); - fprintf(stderr, " -I max interval between requests (default 4 sec) to prevent DNS timeouts\n"); + fprintf(stderr, " -I target interval between sending and receiving requests (default: 4 secs)\n"); + fprintf(stderr, " or ping interval in immediate mode (default: 1 sec)\n"); + fprintf(stderr, " -s minimum interval between queries (default: 0ms)\n"); fprintf(stderr, " -L 1: use lazy mode for low-latency (default). 0: don't (implies -I1)\n"); fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); fprintf(stderr, " -r to skip raw UDP mode attempt\n"); - fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n\n"); + + fprintf(stderr, "Fine-tuning options:\n"); + fprintf(stderr, " -w downstream fragment window size (default: 8 frags)\n"); + fprintf(stderr, " -W upstream fragment window size (default: 8 frags)\n"); + fprintf(stderr, " -i server-side request timeout in lazy mode (default: auto)\n"); + fprintf(stderr, " -j downstream fragment ACK timeout, implies -i4 (default: 2 sec)\n"); + fprintf(stderr, " -c 1: use downstream compression (default), 0: disable\n"); + fprintf(stderr, " -C 1: use upstream compression (default), 0: disable\n\n"); + fprintf(stderr, "Other options:\n"); fprintf(stderr, " -v to print version info and exit\n"); fprintf(stderr, " -h to print this help and exit\n"); + fprintf(stderr, " -V sec to print connection statistics at specified intervals\n"); fprintf(stderr, " -f to keep running in foreground\n"); + fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); fprintf(stderr, " -t dir to chroot to directory dir\n"); fprintf(stderr, " -d device to set tunnel device name\n"); fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); - fprintf(stderr, " -F pidfile to write pid to a file\n"); - fprintf(stderr, "nameserver is the IP number/hostname of the relaying nameserver. if absent, /etc/resolv.conf is used\n"); + fprintf(stderr, " -R routing domain (OpenBSD only)\n"); + fprintf(stderr, " -F pidfile to write pid to a file\n\n"); + + fprintf(stderr, "nameserver is the IP/hostname of the relaying nameserver(s).\n"); + fprintf(stderr, " multiple nameservers can be specified (used in round-robin). \n"); + fprintf(stderr, " if absent, system default is used\n"); fprintf(stderr, "topdomain is the FQDN that is delegated to the tunnel endpoint.\n"); exit(0); } static void -version() { - +version() +{ fprintf(stderr, "iodine IP over DNS tunneling client\n"); - fprintf(stderr, "Git version: %s\n", GITREVISION); - + fprintf(stderr, "Git version: %s; protocol version %08X\n", GITREVISION, PROTOCOL_VERSION); exit(0); } int main(int argc, char **argv) { - char *nameserv_host; char *topdomain; char *errormsg; #ifndef WINDOWS32 @@ -137,18 +157,38 @@ main(int argc, char **argv) int dns_fd; int max_downstream_frag_size; int autodetect_frag_size; + int hostname_maxlen; + int retval; int raw_mode; int lazymode; - int selecttimeout; - int hostname_maxlen; + int target_interval_ms; + int server_timeout_ms; + int downstream_timeout_ms; + int min_interval_ms; + int autodetect_server_timeout; + int up_compression; + int down_compression; + + int up_windowsize; + int down_windowsize; + #ifdef OPENBSD int rtable = 0; #endif + + char *nameserv_host; + char **nameserv_hosts; + int nameserv_hosts_len; struct sockaddr_storage nameservaddr; + struct socket *nameserv_addrs; + size_t nameserv_addrs_len; int nameservaddr_len; int nameserv_family; + /* Set default values */ + nameserv_addrs_len = 0; + nameservaddr_len = 0; nameserv_host = NULL; topdomain = NULL; errormsg = NULL; @@ -163,15 +203,26 @@ main(int argc, char **argv) context = NULL; device = NULL; pidfile = NULL; + debug = 0; + stats = 0; autodetect_frag_size = 1; max_downstream_frag_size = 3072; retval = 0; raw_mode = 1; lazymode = 1; - selecttimeout = 4; + target_interval_ms = 5000; /* DNS RFC says 5 seconds minimum */ + min_interval_ms = 0; + server_timeout_ms = 4000; /* Safe value for RTT <1s */ + downstream_timeout_ms = 2000; + autodetect_server_timeout = 1; hostname_maxlen = 0xFF; nameserv_family = AF_UNSPEC; + up_compression = 1; + down_compression = 1; + + up_windowsize = 8; + down_windowsize = 8; #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); @@ -188,7 +239,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfhru:t:d:R:P:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhrs:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -200,9 +251,17 @@ main(int argc, char **argv) version(); /* NOTREACHED */ break; + case 'V': + stats = atoi(optarg); + if (stats < 0) + stats = 0; + break; case 'f': foreground = 1; break; + case 'D': + debug++; + break; case 'h': help(); /* NOTREACHED */ @@ -252,7 +311,7 @@ main(int argc, char **argv) if (client_set_qtype(optarg)) errx(5, "Invalid query type '%s'", optarg); break; - case 'O': /* not -D, is Debug in server */ + case 'O': client_set_downenc(optarg); break; case 'L': @@ -261,13 +320,36 @@ main(int argc, char **argv) lazymode = 1; if (lazymode < 0) lazymode = 0; - if (!lazymode) - selecttimeout = 1; break; case 'I': - selecttimeout = atoi(optarg); - if (selecttimeout < 1) - selecttimeout = 1; + target_interval_ms = strtod(optarg, NULL) * 1000; + break; + case 'i': + server_timeout_ms = strtod(optarg, NULL) * 1000; + autodetect_server_timeout = 0; + break; + case 'j': + downstream_timeout_ms = strtod(optarg, NULL) * 1000; + if (autodetect_server_timeout) { + autodetect_server_timeout = 0; + server_timeout_ms = 4000; + } + break; + case 's': + min_interval_ms = atoi(optarg); + if (min_interval_ms < 0) + min_interval_ms = 0; + case 'w': + down_windowsize = atoi(optarg); + break; + case 'W': + up_windowsize = atoi(optarg); + break; + case 'C': + up_compression = atoi(optarg) & 1; + break; + case 'c': + down_compression = atoi(optarg) & 1; break; default: usage(); @@ -280,36 +362,58 @@ main(int argc, char **argv) argc -= optind; argv += optind; - switch (argc) { - case 1: - nameserv_host = get_resolvconf_addr(); - topdomain = strdup(argv[0]); - break; - case 2: - nameserv_host = argv[0]; - topdomain = strdup(argv[1]); - break; - default: - usage(); - /* NOTREACHED */ + if (debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", debug); + fprintf(stderr, "Add more -D switches to set higher debug level.\n"); + foreground = 1; } - if (max_downstream_frag_size < 1 || max_downstream_frag_size > 0xffff) { - warnx("Use a max frag size between 1 and 65535 bytes.\n"); - usage(); - /* NOTREACHED */ - } + nameserv_hosts_len = argc - 1; + if (nameserv_hosts_len <= 0) + nameserv_hosts_len = 1; - if (nameserv_host) { + // Preallocate memory with expected number of hosts + nameserv_hosts = malloc(sizeof(char *) * nameserv_hosts_len); + nameserv_addrs = malloc(sizeof(struct socket) * nameserv_hosts_len); + + if (argc == 0) { + usage(); + /* NOT REACHED */ + } else if (argc == 1) { + nameserv_hosts[0] = get_resolvconf_addr(); + } else if (argc > 1) { + for (int h = 0; h < nameserv_hosts_len; h++) { + nameserv_hosts[h] = strdup(argv[h + 1]); + } + } + topdomain = strdup(argv[0]); + + for (int n = 0; n < nameserv_hosts_len; n++) { + nameserv_host = nameserv_hosts[n]; + if (!nameserv_host) { + errx(1, "Error processing nameserver hostnames!\n"); + } nameservaddr_len = get_addr(nameserv_host, DNS_PORT, nameserv_family, 0, &nameservaddr); if (nameservaddr_len < 0) { errx(1, "Cannot lookup nameserver '%s': %s ", - nameserv_host, gai_strerror(nameservaddr_len)); + nameserv_host, gai_strerror(nameservaddr_len)); } - client_set_nameserver(&nameservaddr, nameservaddr_len); - } else { + nameserv_addrs[n].length = nameservaddr_len; + memcpy(&nameserv_addrs[n].addr, &nameservaddr, sizeof(struct sockaddr_storage)); + nameserv_addrs_len ++; + nameserv_host = NULL; + } + + if (nameserv_addrs_len <= 0 || !nameserv_hosts[0]) { warnx("No nameserver found - not connected to any network?\n"); usage(); + } + + client_set_nameservers(nameserv_addrs, nameserv_addrs_len); + + if (max_downstream_frag_size < 1 || max_downstream_frag_size > 0xffff) { + warnx("Use a max frag size between 1 and 65535 bytes.\n"); + usage(); /* NOTREACHED */ } @@ -319,10 +423,38 @@ main(int argc, char **argv) /* NOTREACHED */ } - client_set_selecttimeout(selecttimeout); + if (up_windowsize < 1 || down_windowsize < 1) { + warnx("Windowsize (-w or -W) must be greater than 0!"); + usage(); + } + + if (target_interval_ms < 100) { + warnx("Target interval must be greater than 0.1 seconds!"); + usage(); + } + + if (server_timeout_ms < 100 || server_timeout_ms >= target_interval_ms) { + warnx("Server timeout must be greater than 0.1 sec and less than target interval!"); + usage(); + } + + if (downstream_timeout_ms < 100) { + warnx("Downstream fragment timeout must be more than 0.1 sec to prevent excessive retransmits."); + usage(); + } + + if (!lazymode && target_interval_ms > 1000) { + warnx("Warning: Target interval of >1 second in immediate mode will cause high latency."); + usage(); + } + + client_set_compression(up_compression, down_compression); + client_set_dnstimeout(target_interval_ms, server_timeout_ms, downstream_timeout_ms, autodetect_server_timeout); + client_set_interval(target_interval_ms, min_interval_ms); client_set_lazymode(lazymode); client_set_topdomain(topdomain); client_set_hostname_maxlen(hostname_maxlen); + client_set_windowsize(up_windowsize, down_windowsize); if (username != NULL) { #ifndef WINDOWS32 @@ -359,8 +491,12 @@ main(int argc, char **argv) signal(SIGINT, sighandler); signal(SIGTERM, sighandler); - fprintf(stderr, "Sending DNS queries for %s to %s\n", - topdomain, format_addr(&nameservaddr, nameservaddr_len)); + fprintf(stderr, "Sending DNS queries for %s to ", topdomain); + for (int a = 0; a < nameserv_addrs_len; a++) { + fprintf(stderr, "%s%s", format_addr(&nameserv_addrs[a].addr, nameserv_addrs[a].length), + (a != nameserv_addrs_len-1) ? ", " : ""); + } + fprintf(stderr, "\n"); if (client_handshake(dns_fd, raw_mode, autodetect_frag_size, max_downstream_frag_size)) { retval = 1; diff --git a/src/iodined.c b/src/iodined.c index 2e6f7bf..6cc5a61 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2015 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,11 +30,13 @@ #include #include "common.h" +#include "version.h" #ifdef WINDOWS32 #include "windows.h" #include #else +#include #include #ifdef DARWIN #define BIND_8_COMPAT @@ -60,6 +63,7 @@ #include "tun.h" #include "fw_query.h" #include "version.h" +#include "server.h" #ifdef HAVE_SYSTEMD # include @@ -70,65 +74,6 @@ WORD req_version = MAKEWORD(2, 2); WSADATA wsa_data; #endif -#define PASSWORD_ENV_VAR "IODINED_PASS" - -#if defined IP_RECVDSTADDR -# define DSTADDR_SOCKOPT IP_RECVDSTADDR -# define dstaddr(x) ((struct in_addr *) CMSG_DATA(x)) -#elif defined IP_PKTINFO -# define DSTADDR_SOCKOPT IP_PKTINFO -# define dstaddr(x) (&(((struct in_pktinfo *)(CMSG_DATA(x)))->ipi_addr)) -#endif - -#ifndef IPV6_RECVPKTINFO -#define IPV6_RECVPKTINFO IPV6_PKTINFO -#endif - -static int running = 1; -static char *topdomain; -static char password[33]; -static struct encoder *b32; -static struct encoder *b64; -static struct encoder *b64u; -static struct encoder *b128; -static int created_users; - -static int check_ip; -static int my_mtu; -static in_addr_t my_ip; -static int netmask; - -static in_addr_t ns_ip; - -static int bind_port; -static int debug; - -#if !defined(BSD) && !defined(__GLIBC__) -static char *__progname; -#endif - -/* Struct with IPv4 and IPv6 file descriptors. - * Need to be passed on down to tunneling code since we can get a - * packet on one fd meant for a user on the other. - */ -struct dnsfd { - int v4fd; - int v6fd; -}; - -static int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); -static void write_dns(int fd, struct query *q, char *data, int datalen, char downenc); -static void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid); - -static int -get_dns_fd(struct dnsfd *fds, struct sockaddr_storage *addr) -{ - if (addr->ss_family == AF_INET6) { - return fds->v6fd; - } - return fds->v4fd; -} - /* Ask ipify.org webservice to get external ip */ static int get_external_ip(struct in_addr *ip) @@ -185,2118 +130,18 @@ get_external_ip(struct in_addr *ip) static void sigint(int sig) { - running = 0; -} - -#ifdef WINDOWS32 -#define LOG_EMERG 0 -#define LOG_ALERT 1 -#define LOG_CRIT 2 -#define LOG_ERR 3 -#define LOG_WARNING 4 -#define LOG_NOTICE 5 -#define LOG_INFO 6 -#define LOG_DEBUG 7 -static void -syslog(int a, const char *str, ...) -{ - /* TODO: implement (add to event log), move to common.c */ - ; -} -#endif - -/* This will not check that user has passed login challenge */ -static int -check_user_and_ip(int userid, struct query *q) -{ - /* Note: duplicate in handle_raw_login() except IP-address check */ - - if (userid < 0 || userid >= created_users ) { - return 1; - } - if (!users[userid].active || users[userid].disabled) { - return 1; - } - if (users[userid].last_pkt + 60 < time(NULL)) { - return 1; - } - - /* return early if IP checking is disabled */ - if (!check_ip) { - return 0; - } - - if (q->from.ss_family != users[userid].host.ss_family) { - return 1; - } - /* Check IPv4 */ - if (q->from.ss_family == AF_INET) { - struct sockaddr_in *expected, *received; - - expected = (struct sockaddr_in *) &(users[userid].host); - received = (struct sockaddr_in *) &(q->from); - return memcmp(&(expected->sin_addr), &(received->sin_addr), sizeof(struct in_addr)); - } - /* Check IPv6 */ - if (q->from.ss_family == AF_INET6) { - struct sockaddr_in6 *expected, *received; - - expected = (struct sockaddr_in6 *) &(users[userid].host); - received = (struct sockaddr_in6 *) &(q->from); - return memcmp(&(expected->sin6_addr), &(received->sin6_addr), sizeof(struct in6_addr)); - } - /* Unknown address family */ - return 1; -} - -/* This checks that user has passed normal (non-raw) login challenge */ -static int -check_authenticated_user_and_ip(int userid, struct query *q) -{ - int res = check_user_and_ip(userid, q); - if (res) - return res; - - if (!users[userid].authenticated) - return 1; - - return 0; -} - -static void -send_raw(int fd, char *buf, int buflen, int user, int cmd, struct query *q) -{ - char packet[4096]; - int len; - - len = MIN(sizeof(packet) - RAW_HDR_LEN, buflen); - - memcpy(packet, raw_header, RAW_HDR_LEN); - if (len) { - memcpy(&packet[RAW_HDR_LEN], buf, len); - } - - len += RAW_HDR_LEN; - packet[RAW_HDR_CMD] = cmd | (user & 0x0F); - - if (debug >= 2) { - fprintf(stderr, "TX-raw: client %s, cmd %d, %d bytes\n", - format_addr(&q->from, q->fromlen), cmd, len); - } - - sendto(fd, packet, len, 0, (struct sockaddr *) &q->from, q->fromlen); -} - - -static void -start_new_outpacket(int userid, char *data, int datalen) -/* Copies data to .outpacket and resets all counters. - data is expected to be compressed already. */ -{ - datalen = MIN(datalen, sizeof(users[userid].outpacket.data)); - memcpy(users[userid].outpacket.data, data, datalen); - users[userid].outpacket.len = datalen; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outpacket.seqno = (users[userid].outpacket.seqno + 1) & 7; - users[userid].outpacket.fragment = 0; - users[userid].outfragresent = 0; -} - -#ifdef OUTPACKETQ_LEN - -static int -save_to_outpacketq(int userid, char *data, int datalen) -/* Find space in outpacket-queue and store data (expected compressed already). - Returns: 1 = okay, 0 = no space. */ -{ - int fill; - - if (users[userid].outpacketq_filled >= OUTPACKETQ_LEN) - /* no space */ - return 0; - - fill = users[userid].outpacketq_nexttouse + - users[userid].outpacketq_filled; - if (fill >= OUTPACKETQ_LEN) - fill -= OUTPACKETQ_LEN; - - datalen = MIN(datalen, sizeof(users[userid].outpacketq[fill].data)); - memcpy(users[userid].outpacketq[fill].data, data, datalen); - users[userid].outpacketq[fill].len = datalen; - - users[userid].outpacketq_filled++; - - if (debug >= 3) - fprintf(stderr, " Qstore, now %d\n", - users[userid].outpacketq_filled); - - return 1; -} - -static int -get_from_outpacketq(int userid) -/* Starts new outpacket from queue, if any. - Returns: 1 = okay, 0 = no packets were waiting. */ -{ - int use; - - if (users[userid].outpacketq_filled <= 0) - /* no packets */ - return 0; - - use = users[userid].outpacketq_nexttouse; - - start_new_outpacket(userid, users[userid].outpacketq[use].data, - users[userid].outpacketq[use].len); - - use++; - if (use >= OUTPACKETQ_LEN) - use = 0; - users[userid].outpacketq_nexttouse = use; - users[userid].outpacketq_filled--; - - if (debug >= 3) - fprintf(stderr, " Qget, now %d\n", - users[userid].outpacketq_filled); - - return 1; -} - -#endif /* OUTPACKETQ_LEN */ - -#ifdef DNSCACHE_LEN - -/* On the DNS cache: - - This cache is implemented to better handle the aggressively impatient DNS - servers that very quickly re-send requests when we choose to not - immediately answer them in lazy mode. This cache works much better than - pruning(=dropping) the improper requests, since the DNS server will - actually get an answer instead of silence. - - Because of the CMC in both ping and upstream data, unwanted cache hits - are prevented. Data-CMC is only 36 counts, so our cache length should - not exceed 36/2=18 packets. (This quick rule assumes all packets are - otherwise equal, which they arent: up/downstream seq/frag, tcp sequence - number, and of course data.) -*/ - -static void -save_to_dnscache(int userid, struct query *q, char *answer, int answerlen) -/* Store answer in our little DNS cache. */ -{ - int fill; - - if (answerlen > sizeof(users[userid].dnscache_answer[fill])) - return; /* can't store this */ - - fill = users[userid].dnscache_lastfilled + 1; - if (fill >= DNSCACHE_LEN) - fill = 0; - - memcpy(&(users[userid].dnscache_q[fill]), q, sizeof(struct query)); - memcpy(users[userid].dnscache_answer[fill], answer, answerlen); - users[userid].dnscache_answerlen[fill] = answerlen; - - users[userid].dnscache_lastfilled = fill; -} - -static int -answer_from_dnscache(int dns_fd, int userid, struct query *q) -/* Checks cache and sends repeated answer if we alreay saw this query recently. - Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is - a new query. */ -{ - int i; - int use; - - for (i = 0; i < DNSCACHE_LEN ; i++) { - /* Try cache most-recent-first */ - use = users[userid].dnscache_lastfilled - i; - if (use < 0) - use += DNSCACHE_LEN; - - if (users[userid].dnscache_q[use].id == 0) - continue; - if (users[userid].dnscache_answerlen[use] <= 0) - continue; - - if (users[userid].dnscache_q[use].type != q->type || - strcmp(users[userid].dnscache_q[use].name, q->name)) - continue; - - /* okay, match */ - if (debug >= 1) - fprintf(stderr, "OUT user %d %s from dnscache\n", userid, q->name); - - write_dns(dns_fd, q, users[userid].dnscache_answer[use], - users[userid].dnscache_answerlen[use], - users[userid].downenc); - - q->id = 0; /* this query was used */ - return 1; - } - - /* here only when no match found */ - return 0; -} - -#endif /* DNSCACHE_LEN */ - -static inline void -save_to_qmem(unsigned char *qmem_cmc, unsigned short *qmem_type, int qmem_len, - int *qmem_lastfilled, unsigned char *cmc_to_add, - unsigned short type_to_add) -/* Remember query to check for duplicates */ -{ - int fill; - - fill = *qmem_lastfilled + 1; - if (fill >= qmem_len) - fill = 0; - - memcpy(qmem_cmc + fill * 4, cmc_to_add, 4); - qmem_type[fill] = type_to_add; - *qmem_lastfilled = fill; -} - -static inline void -save_to_qmem_pingordata(int userid, struct query *q) -{ - /* Our CMC is a bit more than the "official" CMC; we store 4 bytes - just because we can, and because it may prevent some false matches. - For ping, we save the 4 decoded bytes: userid + seq/frag + CMC. - For data, we save the 4 _un_decoded chars in lowercase: seq/frag's - + 1 char CMC; that last char is non-Base32. - */ - - char cmc[8]; - int i; - - if (q->name[0] == 'P' || q->name[0] == 'p') { - /* Ping packet */ - - size_t cmcsize = sizeof(cmc); - char *cp = strchr(q->name, '.'); - - if (cp == NULL) - return; /* illegal hostname; shouldn't happen */ - - /* We already unpacked in handle_null_request(), but that's - lost now... Note: b32 directly, we want no undotify here! */ - i = b32->decode(cmc, &cmcsize, q->name + 1, (cp - q->name) - 1); - - if (i < 4) - return; /* illegal ping; shouldn't happen */ - - save_to_qmem(users[userid].qmemping_cmc, - users[userid].qmemping_type, QMEMPING_LEN, - &users[userid].qmemping_lastfilled, - (void *) cmc, q->type); - } else { - /* Data packet, hopefully not illegal */ - if (strlen(q->name) < 5) - return; - - /* We store CMC in lowercase; if routing via multiple parallel - DNS servers, one may do case-switch and another may not, - and we still want to detect duplicates. - Data-header is always base32, so case-swap won't hurt. - */ - for (i = 0; i < 4; i++) - if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') - cmc[i] = q->name[i+1] + ('a' - 'A'); - else - cmc[i] = q->name[i+1]; - - save_to_qmem(users[userid].qmemdata_cmc, - users[userid].qmemdata_type, QMEMDATA_LEN, - &users[userid].qmemdata_lastfilled, - (void *) cmc, q->type); - } -} - -static int -answer_from_qmem(int dns_fd, struct query *q, unsigned char *qmem_cmc, - unsigned short *qmem_type, int qmem_len, - unsigned char *cmc_to_check) -/* Checks query memory and sends an (illegal) answer if this is a duplicate. - Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is - not a duplicate. */ -{ - int i; - - for (i = 0; i < qmem_len ; i++) { - - if (qmem_type[i] == T_UNSET) - continue; - if (qmem_type[i] != q->type) - continue; - if (memcmp(qmem_cmc + i * 4, cmc_to_check, 4)) - continue; - - /* okay, match */ - if (debug >= 1) - fprintf(stderr, "OUT from qmem for %s == duplicate, sending illegal reply\n", q->name); - - write_dns(dns_fd, q, "x", 1, 'T'); - - q->id = 0; /* this query was used */ - return 1; - } - - /* here only when no match found */ - return 0; -} - -static inline int -answer_from_qmem_data(int dns_fd, int userid, struct query *q) -/* Quick helper function to keep handle_null_request() clean */ -{ - char cmc[4]; - int i; - - for (i = 0; i < 4; i++) - if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') - cmc[i] = q->name[i+1] + ('a' - 'A'); - else - cmc[i] = q->name[i+1]; - - return answer_from_qmem(dns_fd, q, users[userid].qmemdata_cmc, - users[userid].qmemdata_type, QMEMDATA_LEN, - (void *) cmc); -} - -static int -send_chunk_or_dataless(int dns_fd, int userid, struct query *q) -/* Sends current fragment to user, or dataless packet if there is no - current fragment available (-> normal "quiet" ping reply). - Does not update anything, except: - - discards q always (query is used) - - forgets entire users[userid].outpacket if it was sent in one go, - and then tries to get new packet from outpacket-queue - Returns: 1 = can call us again immediately, new packet from queue; - 0 = don't call us again for now. -*/ -{ - char pkt[4096]; - int datalen = 0; - int last = 0; - - /* If re-sent too many times, drop entire packet */ - if (users[userid].outpacket.len > 0 && - users[userid].outfragresent > 5) { - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outfragresent = 0; - -#ifdef OUTPACKETQ_LEN - /* Maybe more in queue, use immediately */ - get_from_outpacketq(userid); -#endif - } - - if (users[userid].outpacket.len > 0) { - datalen = MIN(users[userid].fragsize, users[userid].outpacket.len - users[userid].outpacket.offset); - datalen = MIN(datalen, sizeof(pkt)-2); - - memcpy(&pkt[2], users[userid].outpacket.data + users[userid].outpacket.offset, datalen); - users[userid].outpacket.sentlen = datalen; - last = (users[userid].outpacket.len == users[userid].outpacket.offset + datalen); - - users[userid].outfragresent++; - } - - /* Build downstream data header (see doc/proto_xxxxxxxx.txt) */ - - /* First byte is 1 bit compression flag, 3 bits upstream seqno, 4 bits upstream fragment */ - pkt[0] = (1<<7) | ((users[userid].inpacket.seqno & 7) << 4) | - (users[userid].inpacket.fragment & 15); - /* Second byte is 3 bits downstream seqno, 4 bits downstream fragment, 1 bit last flag */ - pkt[1] = ((users[userid].outpacket.seqno & 7) << 5) | - ((users[userid].outpacket.fragment & 15) << 1) | (last & 1); - - if (debug >= 1) { - fprintf(stderr, "OUT pkt seq# %d, frag %d (last=%d), offset %d, fragsize %d, total %d, to user %d\n", - users[userid].outpacket.seqno & 7, users[userid].outpacket.fragment & 15, - last, users[userid].outpacket.offset, datalen, users[userid].outpacket.len, userid); - } - write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); - - if (q->id2 != 0) { - q->id = q->id2; - q->fromlen = q->fromlen2; - memcpy(&(q->from), &(q->from2), q->fromlen2); - if (debug >= 1) - fprintf(stderr, "OUT again to last duplicate\n"); - write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); - } - - save_to_qmem_pingordata(userid, q); - -#ifdef DNSCACHE_LEN - save_to_dnscache(userid, q, pkt, datalen + 2); -#endif - - q->id = 0; /* this query is used */ - - if (datalen > 0 && datalen == users[userid].outpacket.len) { - /* Whole packet was sent in one chunk, dont wait for ack */ - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outfragresent = 0; - -#ifdef OUTPACKETQ_LEN - /* Maybe more in queue, prepare for next time */ - if (get_from_outpacketq(userid) == 1) { - if (debug >= 3) - fprintf(stderr, " Chunk & fromqueue: callagain\n"); - return 1; /* call us again */ - } -#endif - } - - return 0; /* don't call us again */ -} - -static int -tunnel_tun(int tun_fd, struct dnsfd *dns_fds) -{ - unsigned long outlen; - struct ip *header; - char out[64*1024]; - char in[64*1024]; - int userid; - int read; - - if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) - return 0; - - /* find target ip in packet, in is padded with 4 bytes TUN header */ - header = (struct ip*) (in + 4); - userid = find_user_by_ip(header->ip_dst.s_addr); - if (userid < 0) - return 0; - - outlen = sizeof(out); - compress2((uint8_t*)out, &outlen, (uint8_t*)in, read, 9); - - if (users[userid].conn == CONN_DNS_NULL) { -#ifdef OUTPACKETQ_LEN - /* If a packet is being sent, try storing the new one in the queue. - If the queue is full, drop the packet. TCP will hopefully notice - and reduce the packet rate. */ - if (users[userid].outpacket.len > 0) { - save_to_outpacketq(userid, out, outlen); - return 0; - } -#endif - - start_new_outpacket(userid, out, outlen); - - /* Start sending immediately if query is waiting */ - if (users[userid].q_sendrealsoon.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); - send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } else if (users[userid].q.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - } - - return outlen; - } else { /* CONN_RAW_UDP */ - int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); - send_raw(dns_fd, out, outlen, userid, RAW_HDR_CMD_DATA, &users[userid].q); - return outlen; - } -} - -typedef enum { - VERSION_ACK, - VERSION_NACK, - VERSION_FULL -} version_ack_t; - -static void -send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, struct query *q) -{ - char out[9]; - - switch (ack) { - case VERSION_ACK: - strncpy(out, "VACK", sizeof(out)); - break; - case VERSION_NACK: - strncpy(out, "VNAK", sizeof(out)); - break; - case VERSION_FULL: - strncpy(out, "VFUL", sizeof(out)); - break; - } - - out[4] = ((payload >> 24) & 0xff); - out[5] = ((payload >> 16) & 0xff); - out[6] = ((payload >> 8) & 0xff); - out[7] = ((payload) & 0xff); - out[8] = userid & 0xff; - - write_dns(fd, q, out, sizeof(out), users[userid].downenc); -} - -static void -process_downstream_ack(int userid, int down_seq, int down_frag) -/* Process acks from downstream fragments. - After this, .offset and .fragment are updated (if ack correct), - or .len is set to zero when all is done. -*/ -{ - if (users[userid].outpacket.len <= 0) - /* No packet to apply acks to */ - return; - - if (users[userid].outpacket.seqno != down_seq || - users[userid].outpacket.fragment != down_frag) - /* Not the ack we're waiting for; probably duplicate of old - ack, happens a lot with ping packets */ - return; - - /* Received proper ack */ - users[userid].outpacket.offset += users[userid].outpacket.sentlen; - users[userid].outpacket.sentlen = 0; - users[userid].outpacket.fragment++; - users[userid].outfragresent = 0; - - /* Is packet done? */ - if (users[userid].outpacket.offset >= users[userid].outpacket.len) { - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.fragment--; /* unneeded ++ above */ - /* ^keep last seqno/frag, are always returned on pings */ - /* users[userid].outfragresent = 0; already above */ - -#ifdef OUTPACKETQ_LEN - /* Possibly get new packet from queue */ - get_from_outpacketq(userid); -#endif - } -} - -static void -handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) -{ - struct in_addr tempip; - char in[512]; - char logindata[16]; - char out[64*1024]; - char unpacked[64*1024]; - char *tmp[2]; - int userid; - int read; - - userid = -1; - - /* Everything here needs at least two chars in the name */ - if (domain_len < 2) - return; - - memcpy(in, q->name, MIN(domain_len, sizeof(in))); - - if(in[0] == 'V' || in[0] == 'v') { - int version = 0; - - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - /* Version greeting, compare and send ack/nak */ - if (read > 4) { - /* Received V + 32bits version */ - version = (((unpacked[0] & 0xff) << 24) | - ((unpacked[1] & 0xff) << 16) | - ((unpacked[2] & 0xff) << 8) | - ((unpacked[3] & 0xff))); - } - - if (version == PROTOCOL_VERSION) { - userid = find_available_user(); - if (userid >= 0) { - int i; - - users[userid].seed = rand(); - /* Store remote IP number */ - memcpy(&(users[userid].host), &(q->from), q->fromlen); - users[userid].hostlen = q->fromlen; - - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].encoder = get_base32_encoder(); - users[userid].downenc = 'T'; - send_version_response(dns_fd, VERSION_ACK, users[userid].seed, userid, q); - syslog(LOG_INFO, "accepted version for user #%d from %s", - userid, format_addr(&q->from, q->fromlen)); - users[userid].q.id = 0; - users[userid].q.id2 = 0; - users[userid].q_sendrealsoon.id = 0; - users[userid].q_sendrealsoon.id2 = 0; - users[userid].q_sendrealsoon_new = 0; - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outpacket.seqno = 0; - users[userid].outpacket.fragment = 0; - users[userid].outfragresent = 0; - users[userid].inpacket.len = 0; - users[userid].inpacket.offset = 0; - users[userid].inpacket.seqno = 0; - users[userid].inpacket.fragment = 0; - users[userid].fragsize = 100; /* very safe */ - users[userid].conn = CONN_DNS_NULL; - users[userid].lazy = 0; -#ifdef OUTPACKETQ_LEN - users[userid].outpacketq_nexttouse = 0; - users[userid].outpacketq_filled = 0; -#endif -#ifdef DNSCACHE_LEN - { - for (i = 0; i < DNSCACHE_LEN; i++) { - users[userid].dnscache_q[i].id = 0; - users[userid].dnscache_answerlen[i] = 0; - } - } - users[userid].dnscache_lastfilled = 0; -#endif - for (i = 0; i < QMEMPING_LEN; i++) - users[userid].qmemping_type[i] = T_UNSET; - users[userid].qmemping_lastfilled = 0; - for (i = 0; i < QMEMDATA_LEN; i++) - users[userid].qmemdata_type[i] = T_UNSET; - users[userid].qmemdata_lastfilled = 0; - } else { - /* No space for another user */ - send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); - syslog(LOG_INFO, "dropped user from %s, server full", - format_addr(&q->from, q->fromlen)); - } - } else { - send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q); - syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", - format_addr(&q->from, q->fromlen), version); - } - return; - } else if(in[0] == 'L' || in[0] == 'l') { - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - if (read < 17) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Login phase, handle auth */ - userid = unpacked[0]; - - if (check_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - syslog(LOG_WARNING, "dropped login request from user #%d from unexpected source %s", - userid, format_addr(&q->from, q->fromlen)); - return; - } else { - users[userid].last_pkt = time(NULL); - login_calculate(logindata, 16, password, users[userid].seed); - - if (read >= 18 && (memcmp(logindata, unpacked+1, 16) == 0)) { - /* Store login ok */ - users[userid].authenticated = 1; - - /* Send ip/mtu/netmask info */ - tempip.s_addr = my_ip; - tmp[0] = strdup(inet_ntoa(tempip)); - tempip.s_addr = users[userid].tun_ip; - tmp[1] = strdup(inet_ntoa(tempip)); - - read = snprintf(out, sizeof(out), "%s-%s-%d-%d", - tmp[0], tmp[1], my_mtu, netmask); - - write_dns(dns_fd, q, out, read, users[userid].downenc); - q->id = 0; - syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); - - free(tmp[1]); - free(tmp[0]); - } else { - write_dns(dns_fd, q, "LNAK", 4, 'T'); - syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password", - userid, format_addr(&q->from, q->fromlen)); - } - } - return; - } else if(in[0] == 'I' || in[0] == 'i') { - /* Request for IP number */ - char reply[17]; - int length; - - userid = b32_8to5(in[1]); - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - reply[0] = 'I'; - if (q->from.ss_family == AF_INET) { - if (ns_ip != INADDR_ANY) { - /* If set, use assigned external ip (-n option) */ - memcpy(&reply[1], &ns_ip, sizeof(ns_ip)); - } else { - /* otherwise return destination ip from packet */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr)); - } - length = 1 + sizeof(struct in_addr); - } else { - struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; - memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr)); - length = 1 + sizeof(struct in6_addr); - } - - write_dns(dns_fd, q, reply, length, 'T'); - } else if(in[0] == 'Z' || in[0] == 'z') { - /* Check for case conservation and chars not allowed according to RFC */ - - /* Reply with received hostname as data */ - /* No userid here, reply with lowest-grade downenc */ - write_dns(dns_fd, q, in, domain_len, 'T'); - return; - } else if(in[0] == 'S' || in[0] == 's') { - int codec; - struct encoder *enc; - if (domain_len < 3) { /* len at least 3, example: "S15" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - userid = b32_8to5(in[1]); - - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - codec = b32_8to5(in[2]); - - switch (codec) { - case 5: /* 5 bits per byte = base32 */ - enc = get_base32_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 6: /* 6 bits per byte = base64 */ - enc = get_base64_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ - enc = get_base64u_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 7: /* 7 bits per byte = base128 */ - enc = get_base128_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - break; - } - return; - } else if(in[0] == 'O' || in[0] == 'o') { - if (domain_len < 3) { /* len at least 3, example: "O1T" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - userid = b32_8to5(in[1]); - - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - switch (in[2]) { - case 'T': - case 't': - users[userid].downenc = 'T'; - write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); - break; - case 'S': - case 's': - users[userid].downenc = 'S'; - write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); - break; - case 'U': - case 'u': - users[userid].downenc = 'U'; - write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); - break; - case 'V': - case 'v': - users[userid].downenc = 'V'; - write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); - break; - case 'R': - case 'r': - users[userid].downenc = 'R'; - write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); - break; - case 'L': - case 'l': - users[userid].lazy = 1; - write_dns(dns_fd, q, "Lazy", 4, users[userid].downenc); - break; - case 'I': - case 'i': - users[userid].lazy = 0; - write_dns(dns_fd, q, "Immediate", 9, users[userid].downenc); - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - break; - } - return; - } else if(in[0] == 'Y' || in[0] == 'y') { - int i; - char *datap; - int datalen; - - if (domain_len < 6) { /* len at least 6, example: "YTxCMC" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - i = b32_8to5(in[2]); /* check variant */ - - switch (i) { - case 1: - datap = DOWNCODECCHECK1; - datalen = DOWNCODECCHECK1_LEN; - break; - default: - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - switch (in[1]) { - case 'T': - case 't': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'T'); - return; - } - break; - case 'S': - case 's': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'S'); - return; - } - break; - case 'U': - case 'u': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'U'); - return; - } - break; - case 'V': - case 'v': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'V'); - return; - } - break; - case 'R': - case 'r': - if (q->type == T_NULL || q->type == T_TXT) { - write_dns(dns_fd, q, datap, datalen, 'R'); - return; - } - break; - } - - /* if still here, then codec not available */ - write_dns(dns_fd, q, "BADCODEC", 8, 'T'); - return; - - } else if(in[0] == 'R' || in[0] == 'r') { - int req_frag_size; - - if (domain_len < 16) { /* we'd better have some chars for data... */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Downstream fragsize probe packet */ - userid = (b32_8to5(in[1]) >> 1) & 15; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31); - if (req_frag_size < 2 || req_frag_size > 2047) { - write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); - } else { - char buf[2048]; - int i; - unsigned int v = ((unsigned int) rand()) & 0xff ; - - memset(buf, 0, sizeof(buf)); - buf[0] = (req_frag_size >> 8) & 0xff; - buf[1] = req_frag_size & 0xff; - /* make checkable pseudo-random sequence */ - buf[2] = 107; - for (i = 3; i < 2048; i++, v = (v + 107) & 0xff) - buf[i] = v; - write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); - } - return; - } else if(in[0] == 'N' || in[0] == 'n') { - int max_frag_size; - - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - - if (read < 3) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Downstream fragsize packet */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - max_frag_size = ((unpacked[1] & 0xff) << 8) | (unpacked[2] & 0xff); - if (max_frag_size < 2) { - write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); - } else { - users[userid].fragsize = max_frag_size; - write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc); - } - return; - } else if(in[0] == 'P' || in[0] == 'p') { - int dn_seq; - int dn_frag; - int didsend = 0; - - /* We can't handle id=0, that's "no packet" to us. So drop - request completely. Note that DNS servers rewrite the id. - We'll drop 1 in 64k times. If DNS server retransmits with - different id, then all okay. - Else client won't retransmit, and we'll just keep the - previous ping in cache, no problem either. */ - if (q->id == 0) - return; - - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - if (read < 4) - return; - - /* Ping packet, store userid */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - -#ifdef DNSCACHE_LEN - /* Check if cached */ - if (answer_from_dnscache(dns_fd, userid, q)) - return; -#endif - - /* Check if duplicate (and not in full dnscache any more) */ - if (answer_from_qmem(dns_fd, q, users[userid].qmemping_cmc, - users[userid].qmemping_type, QMEMPING_LEN, - (void *) unpacked)) - return; - - /* Check if duplicate of waiting queries; impatient DNS relays - like to re-try early and often (with _different_ .id!) */ - if (users[userid].q.id != 0 && - q->type == users[userid].q.type && - !strcmp(q->name, users[userid].q.name) && - users[userid].lazy) { - /* We have this ping already, and it's waiting to be - answered. Always keep the last duplicate, since the - relay may have forgotten its first version already. - Our answer will go to both. - (If we already sent an answer, qmem/cache will - have triggered.) */ - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q.id2 = q->id; - users[userid].q.fromlen2 = q->fromlen; - memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); - return; - } - - if (users[userid].q_sendrealsoon.id != 0 && - q->type == users[userid].q_sendrealsoon.type && - !strcmp(q->name, users[userid].q_sendrealsoon.name)) { - /* Outer select loop will send answer immediately, - to both queries. */ - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q_sendrealsoon.id2 = q->id; - users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), - &(q->from), q->fromlen); - return; - } - - dn_seq = unpacked[1] >> 4; - dn_frag = unpacked[1] & 15; - - if (debug >= 1) { - fprintf(stderr, "PING pkt from user %d, ack for downstream %d/%d\n", - userid, dn_seq, dn_frag); - } - - process_downstream_ack(userid, dn_seq, dn_frag); - - if (debug >= 3) { - fprintf(stderr, "PINGret (if any) will ack upstream %d/%d\n", - users[userid].inpacket.seqno, users[userid].inpacket.fragment); - } - - /* If there is a query that must be returned real soon, do it. - May contain new downstream data if the ping had a new ack. - Otherwise, may also be re-sending old data. */ - if (users[userid].q_sendrealsoon.id != 0) { - send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } - - /* We need to store a new query, so if there still is an - earlier query waiting, always send a reply to finish it. - May contain new downstream data if the ping had a new ack. - Otherwise, may also be re-sending old data. - (This is duplicate data if we had q_sendrealsoon above.) */ - if (users[userid].q.id != 0) { - didsend = 1; - if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); - - /* If anything waiting and we didn't already send above, send - it now. And always send immediately if we're not lazy - (then above won't have sent at all). */ - if ((!didsend && users[userid].outpacket.len > 0) || - !users[userid].lazy) - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - - } else if((in[0] >= '0' && in[0] <= '9') - || (in[0] >= 'a' && in[0] <= 'f') - || (in[0] >= 'A' && in[0] <= 'F')) { - int up_seq, up_frag, dn_seq, dn_frag, lastfrag; - int upstream_ok = 1; - int didsend = 0; - int code = -1; - - /* Need 5char header + >=1 char data */ - if (domain_len < 6) - return; - - /* We can't handle id=0, that's "no packet" to us. So drop - request completely. Note that DNS servers rewrite the id. - We'll drop 1 in 64k times. If DNS server retransmits with - different id, then all okay. - Else client doesn't get our ack, and will retransmit in - 1 second. */ - if (q->id == 0) - return; - - if ((in[0] >= '0' && in[0] <= '9')) - code = in[0] - '0'; - if ((in[0] >= 'a' && in[0] <= 'f')) - code = in[0] - 'a' + 10; - if ((in[0] >= 'A' && in[0] <= 'F')) - code = in[0] - 'A' + 10; - - userid = code; - /* Check user and sending ip number */ - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - -#ifdef DNSCACHE_LEN - /* Check if cached */ - if (answer_from_dnscache(dns_fd, userid, q)) - return; -#endif - - /* Check if duplicate (and not in full dnscache any more) */ - if (answer_from_qmem_data(dns_fd, userid, q)) - return; - - /* Check if duplicate of waiting queries; impatient DNS relays - like to re-try early and often (with _different_ .id!) */ - if (users[userid].q.id != 0 && - q->type == users[userid].q.type && - !strcmp(q->name, users[userid].q.name) && - users[userid].lazy) { - /* We have this packet already, and it's waiting to be - answered. Always keep the last duplicate, since the - relay may have forgotten its first version already. - Our answer will go to both. - (If we already sent an answer, qmem/cache will - have triggered.) */ - if (debug >= 2) { - fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q.id2 = q->id; - users[userid].q.fromlen2 = q->fromlen; - memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); - return; - } - - if (users[userid].q_sendrealsoon.id != 0 && - q->type == users[userid].q_sendrealsoon.type && - !strcmp(q->name, users[userid].q_sendrealsoon.name)) { - /* Outer select loop will send answer immediately, - to both queries. */ - if (debug >= 2) { - fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q_sendrealsoon.id2 = q->id; - users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), - &(q->from), q->fromlen); - return; - } - - - /* Decode data header */ - up_seq = (b32_8to5(in[1]) >> 2) & 7; - up_frag = ((b32_8to5(in[1]) & 3) << 2) | ((b32_8to5(in[2]) >> 3) & 3); - dn_seq = (b32_8to5(in[2]) & 7); - dn_frag = b32_8to5(in[3]) >> 1; - lastfrag = b32_8to5(in[3]) & 1; - - process_downstream_ack(userid, dn_seq, dn_frag); - - if (up_seq == users[userid].inpacket.seqno && - up_frag <= users[userid].inpacket.fragment) { - /* Got repeated old packet _with data_, probably - because client didn't receive our ack. So re-send - our ack(+data) immediately to keep things flowing - fast. - If it's a _really_ old frag, it's a nameserver - that tries again, and sending our current (non- - matching) fragno won't be a problem. */ - if (debug >= 1) { - fprintf(stderr, "IN pkt seq# %d, frag %d, dropped duplicate frag\n", - up_seq, up_frag); - } - upstream_ok = 0; - } - else if (up_seq != users[userid].inpacket.seqno && - recent_seqno(users[userid].inpacket.seqno, up_seq)) { - /* Duplicate of recent upstream data packet; probably - need to answer this to keep DNS server happy */ - if (debug >= 1) { - fprintf(stderr, "IN pkt seq# %d, frag %d, dropped duplicate recent seqno\n", - up_seq, up_frag); - } - upstream_ok = 0; - } - else if (up_seq != users[userid].inpacket.seqno) { - /* Really new packet has arrived, no recent duplicate */ - /* Forget any old packet, even if incomplete */ - users[userid].inpacket.seqno = up_seq; - users[userid].inpacket.fragment = up_frag; - users[userid].inpacket.len = 0; - users[userid].inpacket.offset = 0; - } else { - /* seq is same, frag is higher; don't care about - missing fragments, TCP checksum will fail */ - users[userid].inpacket.fragment = up_frag; - } - - if (debug >= 3) { - fprintf(stderr, "INpack with upstream %d/%d, we are going to ack upstream %d/%d\n", - up_seq, up_frag, - users[userid].inpacket.seqno, users[userid].inpacket.fragment); - } - - if (upstream_ok) { - /* decode with this user's encoding */ - read = unpack_data(unpacked, sizeof(unpacked), &(in[5]), domain_len - 5, - users[userid].encoder); - - /* copy to packet buffer, update length */ - read = MIN(read, sizeof(users[userid].inpacket.data) - users[userid].inpacket.offset); - memcpy(users[userid].inpacket.data + users[userid].inpacket.offset, unpacked, read); - users[userid].inpacket.len += read; - users[userid].inpacket.offset += read; - - if (debug >= 1) { - fprintf(stderr, "IN pkt seq# %d, frag %d (last=%d), fragsize %d, total %d, from user %d\n", - up_seq, up_frag, lastfrag, read, users[userid].inpacket.len, userid); - } - } - - if (upstream_ok && lastfrag) { /* packet is complete */ - handle_full_packet(tun_fd, dns_fds, userid); - } - - /* If there is a query that must be returned real soon, do it. - Includes an ack of the just received upstream fragment, - may contain new data. */ - if (users[userid].q_sendrealsoon.id != 0) { - didsend = 1; - if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } - - /* If we already have an earlier query waiting, we need to - get rid of it to store the new query. - - If we have new data waiting and not yet sent above, - send immediately. - - If this wasn't the last upstream fragment, then we expect - more, so ack immediately if we didn't already. - - If we are in non-lazy mode, there should be no query - waiting, but if there is, send immediately. - - In all other cases (mostly the last-fragment cases), - we can afford to wait just a tiny little while for the - TCP ack to arrive from our tun. Note that this works best - when there is only one client. - */ - if (users[userid].q.id != 0) { - if ((users[userid].outpacket.len > 0 && !didsend) || - (upstream_ok && !lastfrag && !didsend) || - (!upstream_ok && !didsend) || - !users[userid].lazy) { - didsend = 1; - if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } else { - memcpy(&(users[userid].q_sendrealsoon), - &(users[userid].q), - sizeof(struct query)); - users[userid].q_sendrealsoon_new = 1; - users[userid].q.id = 0; /* used */ - didsend = 1; - } - } - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); - - /* If we still need to ack this upstream frag, do it to keep - upstream flowing. - - If we have new data waiting and not yet sent above, - send immediately. - - If this wasn't the last upstream fragment, then we expect - more, so ack immediately if we didn't already or are - in non-lazy mode. - - If this was the last fragment, and we didn't ack already - or are in non-lazy mode, send the ack after just a tiny - little while so that the TCP ack may have arrived from - our tun device. - - In all other cases, don't send anything now. - */ - if (users[userid].outpacket.len > 0 && !didsend) - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - else if (!didsend || !users[userid].lazy) { - if (upstream_ok && lastfrag) { - memcpy(&(users[userid].q_sendrealsoon), - &(users[userid].q), - sizeof(struct query)); - users[userid].q_sendrealsoon_new = 1; - users[userid].q.id = 0; /* used */ - } else { - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - } - } - } -} - -static void -handle_ns_request(int dns_fd, struct query *q) -/* Mostly identical to handle_a_request() below */ -{ - char buf[64*1024]; - int len; - - if (ns_ip != INADDR_ANY) { - /* If ns_ip set, overwrite destination addr with it. - * Destination addr will be sent as additional record (A, IN) */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); - } - - len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain); - if (len < 1) { - warnx("dns_encode_ns_response doesn't fit"); - return; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes NS reply\n", - format_addr(&q->from, q->fromlen), q->type, q->name, len); - } - if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { - warn("ns reply send error"); - } -} - -static void -handle_a_request(int dns_fd, struct query *q, int fakeip) -/* Mostly identical to handle_ns_request() above */ -{ - char buf[64*1024]; - int len; - - if (fakeip) { - in_addr_t ip = inet_addr("127.0.0.1"); - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ip, sizeof(ip)); - - } else if (ns_ip != INADDR_ANY) { - /* If ns_ip set, overwrite destination addr with it. - * Destination addr will be sent as additional record (A, IN) */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); - } - - len = dns_encode_a_response(buf, sizeof(buf), q); - if (len < 1) { - warnx("dns_encode_a_response doesn't fit"); - return; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes A reply\n", - format_addr(&q->from, q->fromlen), q->type, q->name, len); - } - if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { - warn("a reply send error"); - } -} - -static void -forward_query(int bind_fd, struct query *q) -{ - char buf[64*1024]; - int len; - struct fw_query fwq; - struct sockaddr_in *myaddr; - in_addr_t newaddr; - - len = dns_encode(buf, sizeof(buf), q, QR_QUERY, q->name, strlen(q->name)); - if (len < 1) { - warnx("dns_encode doesn't fit"); - return; - } - - /* Store sockaddr for q->id */ - memcpy(&(fwq.addr), &(q->from), q->fromlen); - fwq.addrlen = q->fromlen; - fwq.id = q->id; - fw_query_put(&fwq); - - newaddr = inet_addr("127.0.0.1"); - myaddr = (struct sockaddr_in *) &(q->from); - memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t)); - myaddr->sin_port = htons(bind_port); - - if (debug >= 2) { - fprintf(stderr, "TX: NS reply \n"); - } - - if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { - warn("forward query error"); - } -} - -static int -tunnel_bind(int bind_fd, struct dnsfd *dns_fds) -{ - char packet[64*1024]; - struct sockaddr_storage from; - socklen_t fromlen; - struct fw_query *query; - unsigned short id; - int dns_fd; - int r; - - fromlen = sizeof(struct sockaddr); - r = recvfrom(bind_fd, packet, sizeof(packet), 0, - (struct sockaddr*)&from, &fromlen); - - if (r <= 0) - return 0; - - id = dns_get_id(packet, r); - - if (debug >= 2) { - fprintf(stderr, "RX: Got response on query %u from DNS\n", (id & 0xFFFF)); - } - - /* Get sockaddr from id */ - fw_query_get(id, &query); - if (!query) { - if (debug >= 2) { - fprintf(stderr, "Lost sender of id %u, dropping reply\n", (id & 0xFFFF)); - } - return 0; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s id %u, %d bytes\n", - format_addr(&query->addr, query->addrlen), (id & 0xffff), r); - } - - dns_fd = get_dns_fd(dns_fds, &query->addr); - if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), - query->addrlen) <= 0) { - warn("forward reply error"); - } - - return 0; -} - -static int -tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) -{ - struct query q; - int read; - int domain_len; - int inside_topdomain = 0; - - if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) - return 0; - - if (debug >= 2) { - fprintf(stderr, "RX: client %s, type %d, name %s\n", - format_addr(&q.from, q.fromlen), q.type, q.name); - } - - domain_len = strlen(q.name) - strlen(topdomain); - if (domain_len >= 0 && !strcasecmp(q.name + domain_len, topdomain)) - inside_topdomain = 1; - /* require dot before topdomain */ - if (domain_len >= 1 && q.name[domain_len - 1] != '.') - inside_topdomain = 0; - - if (inside_topdomain) { - /* This is a query we can handle */ - - /* Handle A-type query for ns.topdomain, possibly caused - by our proper response to any NS request */ - if (domain_len == 3 && q.type == T_A && - (q.name[0] == 'n' || q.name[0] == 'N') && - (q.name[1] == 's' || q.name[1] == 'S') && - q.name[2] == '.') { - handle_a_request(dns_fd, &q, 0); - return 0; - } - - /* Handle A-type query for www.topdomain, for anyone that's - poking around */ - if (domain_len == 4 && q.type == T_A && - (q.name[0] == 'w' || q.name[0] == 'W') && - (q.name[1] == 'w' || q.name[1] == 'W') && - (q.name[2] == 'w' || q.name[2] == 'W') && - q.name[3] == '.') { - handle_a_request(dns_fd, &q, 1); - return 0; - } - - switch (q.type) { - case T_NULL: - case T_PRIVATE: - case T_CNAME: - case T_A: - case T_MX: - case T_SRV: - case T_TXT: - /* encoding is "transparent" here */ - handle_null_request(tun_fd, dns_fd, dns_fds, &q, domain_len); - break; - case T_NS: - handle_ns_request(dns_fd, &q); - break; - default: - break; - } - } else { - /* Forward query to other port ? */ - if (bind_fd) { - forward_query(bind_fd, &q); - } - } - return 0; -} - -static int -tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) -{ - struct timeval tv; - fd_set fds; - int i; - int userid; - time_t last_action = time(NULL); - - while (running) { - int maxfd; - tv.tv_sec = 10; /* doesn't really matter */ - tv.tv_usec = 0; - - /* Adjust timeout if there is anything to send realsoon. - Clients won't be sending new data until we send our ack, - so don't keep them waiting long. This only triggers at - final upstream fragments, which is about once per eight - requests during heavy upstream traffic. - 20msec: ~8 packs every 1/50sec = ~400 DNSreq/sec, - or ~1200bytes every 1/50sec = ~0.5 Mbit/sec upstream */ - for (userid = 0; userid < created_users; userid++) { - if (users[userid].active && !users[userid].disabled && - users[userid].last_pkt + 60 > time(NULL)) { - users[userid].q_sendrealsoon_new = 0; - if (users[userid].q_sendrealsoon.id != 0) { - tv.tv_sec = 0; - tv.tv_usec = 20000; - } - } - } - - FD_ZERO(&fds); - maxfd = 0; - - if (dns_fds->v4fd >= 0) { - FD_SET(dns_fds->v4fd, &fds); - maxfd = MAX(dns_fds->v4fd, maxfd); - } - if (dns_fds->v6fd >= 0) { - FD_SET(dns_fds->v6fd, &fds); - maxfd = MAX(dns_fds->v6fd, maxfd); - } - - if (bind_fd) { - /* wait for replies from real DNS */ - FD_SET(bind_fd, &fds); - maxfd = MAX(bind_fd, maxfd); - } - - /* Don't read from tun if no users can accept data anyway; - tun queue/TCP buffers are larger than our outpacket-queues */ - if(!all_users_waiting_to_send()) { - FD_SET(tun_fd, &fds); - maxfd = MAX(tun_fd, maxfd); - } - - i = select(maxfd + 1, &fds, NULL, NULL, &tv); - - if(i < 0) { - if (running) - warn("select"); - return 1; - } - - if (i==0) { - if (max_idle_time) { - /* only trigger the check if that's worth ( ie, no need to loop over if there - is something to send */ - if (last_action + max_idle_time < time(NULL)) { - for (userid = 0; userid < created_users; userid++) { - last_action = ( users[userid].last_pkt > last_action ) ? users[userid].last_pkt : last_action; - } - if (last_action + max_idle_time < time(NULL)) { - fprintf(stderr, "Idling since too long, shutting down...\n"); - running = 0; - } - } - } - } else { - if (FD_ISSET(tun_fd, &fds)) { - tunnel_tun(tun_fd, dns_fds); - } - if (FD_ISSET(dns_fds->v4fd, &fds)) { - tunnel_dns(tun_fd, dns_fds->v4fd, dns_fds, bind_fd); - } - if (FD_ISSET(dns_fds->v6fd, &fds)) { - tunnel_dns(tun_fd, dns_fds->v6fd, dns_fds, bind_fd); - } - if (FD_ISSET(bind_fd, &fds)) { - tunnel_bind(bind_fd, dns_fds); - } - } - - /* Send realsoon's if tun or dns didn't already */ - for (userid = 0; userid < created_users; userid++) - if (users[userid].active && !users[userid].disabled && - users[userid].last_pkt + 60 > time(NULL) && - users[userid].q_sendrealsoon.id != 0 && - users[userid].conn == CONN_DNS_NULL && - !users[userid].q_sendrealsoon_new) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); - send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } - } - - return 0; -} - -static void -handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid) -{ - unsigned long outlen; - char out[64*1024]; - int touser; - int ret; - - outlen = sizeof(out); - ret = uncompress((uint8_t*)out, &outlen, - (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len); - - if (ret == Z_OK) { - struct ip *hdr; - - hdr = (struct ip*) (out + 4); - touser = find_user_by_ip(hdr->ip_dst.s_addr); - - if (touser == -1) { - /* send the uncompressed packet to tun device */ - write_tun(tun_fd, out, outlen); - } else { - /* send the compressed(!) packet to other client */ - if (users[touser].conn == CONN_DNS_NULL) { - if (users[touser].outpacket.len == 0) { - start_new_outpacket(touser, - users[userid].inpacket.data, - users[userid].inpacket.len); - - /* Start sending immediately if query is waiting */ - if (users[touser].q_sendrealsoon.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[touser].q_sendrealsoon.from); - send_chunk_or_dataless(dns_fd, touser, &users[touser].q_sendrealsoon); - } else if (users[touser].q.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); - send_chunk_or_dataless(dns_fd, touser, &users[touser].q); - } -#ifdef OUTPACKETQ_LEN - } else { - save_to_outpacketq(touser, - users[userid].inpacket.data, - users[userid].inpacket.len); -#endif - } - } else{ /* CONN_RAW_UDP */ - int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); - send_raw(dns_fd, users[userid].inpacket.data, - users[userid].inpacket.len, touser, - RAW_HDR_CMD_DATA, &users[touser].q); - } - } - } else { - if (debug >= 1) - fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret); - } - - /* This packet is done */ - users[userid].inpacket.len = 0; - users[userid].inpacket.offset = 0; -} - -static void -handle_raw_login(char *packet, int len, struct query *q, int fd, int userid) -{ - char myhash[16]; - - if (len < 16) return; - - /* can't use check_authenticated_user_and_ip() since IP address will be different, - so duplicate here except IP address */ - if (userid < 0 || userid >= created_users) return; - if (!users[userid].active || users[userid].disabled) return; - if (!users[userid].authenticated) return; - if (users[userid].last_pkt + 60 < time(NULL)) return; - - if (debug >= 1) { - fprintf(stderr, "IN login raw, len %d, from user %d\n", - len, userid); - } - - /* User sends hash of seed + 1 */ - login_calculate(myhash, 16, password, users[userid].seed + 1); - if (memcmp(packet, myhash, 16) == 0) { - /* Update query and time info for user */ - users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); - - /* Store remote IP number */ - memcpy(&(users[userid].host), &(q->from), q->fromlen); - users[userid].hostlen = q->fromlen; - - /* Correct hash, reply with hash of seed - 1 */ - user_set_conn_type(userid, CONN_RAW_UDP); - login_calculate(myhash, 16, password, users[userid].seed - 1); - send_raw(fd, myhash, 16, userid, RAW_HDR_CMD_LOGIN, q); - - users[userid].authenticated_raw = 1; - } -} - -static void -handle_raw_data(char *packet, int len, struct query *q, struct dnsfd *dns_fds, int tun_fd, int userid) -{ - if (check_authenticated_user_and_ip(userid, q) != 0) { - return; - } - if (!users[userid].authenticated_raw) return; - - /* Update query and time info for user */ - users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); - - /* copy to packet buffer, update length */ - users[userid].inpacket.offset = 0; - memcpy(users[userid].inpacket.data, packet, len); - users[userid].inpacket.len = len; - - if (debug >= 1) { - fprintf(stderr, "IN pkt raw, total %d, from user %d\n", - users[userid].inpacket.len, userid); - } - - handle_full_packet(tun_fd, dns_fds, userid); -} - -static void -handle_raw_ping(struct query *q, int dns_fd, int userid) -{ - if (check_authenticated_user_and_ip(userid, q) != 0) { - return; - } - if (!users[userid].authenticated_raw) return; - - /* Update query and time info for user */ - users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); - - if (debug >= 1) { - fprintf(stderr, "IN ping raw, from user %d\n", userid); - } - - /* Send ping reply */ - send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, q); -} - -static int -raw_decode(char *packet, int len, struct query *q, int dns_fd, struct dnsfd *dns_fds, int tun_fd) -{ - int raw_user; - - /* minimum length */ - if (len < RAW_HDR_LEN) return 0; - /* should start with header */ - if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0; - - raw_user = RAW_HDR_GET_USR(packet); - switch (RAW_HDR_GET_CMD(packet)) { - case RAW_HDR_CMD_LOGIN: - /* Login challenge */ - handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, raw_user); - break; - case RAW_HDR_CMD_DATA: - /* Data packet */ - handle_raw_data(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fds, tun_fd, raw_user); - break; - case RAW_HDR_CMD_PING: - /* Keepalive packet */ - handle_raw_ping(q, dns_fd, raw_user); - break; - default: - warnx("Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); - break; - } - return 1; -} - -static int -read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) -/* FIXME: dns_fds and tun_fd are because of raw_decode() below */ -{ - struct sockaddr_storage from; - socklen_t addrlen; - char packet[64*1024]; - int r; -#ifndef WINDOWS32 - char control[CMSG_SPACE(sizeof (struct in6_pktinfo))]; - struct msghdr msg; - struct iovec iov; - struct cmsghdr *cmsg; - - addrlen = sizeof(struct sockaddr_storage); - iov.iov_base = packet; - iov.iov_len = sizeof(packet); - - msg.msg_name = (caddr_t) &from; - msg.msg_namelen = (unsigned) addrlen; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - msg.msg_flags = 0; - - r = recvmsg(fd, &msg, 0); -#else - addrlen = sizeof(struct sockaddr_storage); - r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen); -#endif /* !WINDOWS32 */ - - if (r > 0) { - memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen); - q->fromlen = addrlen; - - /* TODO do not handle raw packets here! */ - if (raw_decode(packet, r, q, fd, dns_fds, tun_fd)) { - return 0; - } - if (dns_decode(NULL, 0, q, QR_QUERY, packet, r) < 0) { - return 0; - } - -#ifndef WINDOWS32 - memset(&q->destination, 0, sizeof(struct sockaddr_storage)); - /* Read destination IP address */ - for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - - if (cmsg->cmsg_level == IPPROTO_IP && - cmsg->cmsg_type == DSTADDR_SOCKOPT) { - - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - addr->sin_family = AF_INET; - addr->sin_addr = *dstaddr(cmsg); - q->dest_len = sizeof(*addr); - break; - } - if (cmsg->cmsg_level == IPPROTO_IPV6 && - cmsg->cmsg_type == IPV6_PKTINFO) { - - struct in6_pktinfo *pktinfo; - struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; - pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsg); - addr->sin6_family = AF_INET6; - memcpy(&addr->sin6_addr, &pktinfo->ipi6_addr, sizeof(struct in6_addr)); - q->dest_len = sizeof(*addr); - break; - } - } -#endif - - return strlen(q->name); - } else if (r < 0) { - /* Error */ - warn("read dns"); - } - - return 0; -} - -static size_t -write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downenc) -/* Returns #bytes of data that were encoded */ -{ - static int td1 = 0; - static int td2 = 0; - size_t space; - char *b; - - /* Make a rotating topdomain to prevent filtering */ - td1+=3; - td2+=7; - if (td1>=26) td1-=26; - if (td2>=25) td2-=25; - - /* encode data,datalen to CNAME/MX answer - (adapted from build_hostname() in encoding.c) - */ - - space = MIN(0xFF, buflen) - 4 - 2; - /* -1 encoding type, -3 ".xy", -2 for safety */ - - memset(buf, 0, buflen); - - if (downenc == 'S') { - buf[0] = 'i'; - if (!b64->places_dots()) - space -= (space / 57); /* space for dots */ - b64->encode(buf+1, &space, data, datalen); - if (!b64->places_dots()) - inline_dotify(buf, buflen); - } else if (downenc == 'U') { - buf[0] = 'j'; - if (!b64u->places_dots()) - space -= (space / 57); /* space for dots */ - b64u->encode(buf+1, &space, data, datalen); - if (!b64u->places_dots()) - inline_dotify(buf, buflen); - } else if (downenc == 'V') { - buf[0] = 'k'; - if (!b128->places_dots()) - space -= (space / 57); /* space for dots */ - b128->encode(buf+1, &space, data, datalen); - if (!b128->places_dots()) - inline_dotify(buf, buflen); - } else { - buf[0] = 'h'; - if (!b32->places_dots()) - space -= (space / 57); /* space for dots */ - b32->encode(buf+1, &space, data, datalen); - if (!b32->places_dots()) - inline_dotify(buf, buflen); - } - - /* Add dot (if it wasn't there already) and topdomain */ - b = buf; - b += strlen(buf) - 1; - if (*b != '.') - *++b = '.'; - b++; - - *b = 'a' + td1; - b++; - *b = 'a' + td2; - b++; - *b = '\0'; - - return space; -} - -static void -write_dns(int fd, struct query *q, char *data, int datalen, char downenc) -{ - char buf[64*1024]; - int len = 0; - - if (q->type == T_CNAME || q->type == T_A) { - char cnamebuf[1024]; /* max 255 */ - - write_dns_nameenc(cnamebuf, sizeof(cnamebuf), - data, datalen, downenc); - - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, - sizeof(cnamebuf)); - } else if (q->type == T_MX || q->type == T_SRV) { - char mxbuf[64*1024]; - char *b = mxbuf; - int offset = 0; - int res; - - while (1) { - res = write_dns_nameenc(b, sizeof(mxbuf) - (b - mxbuf), - data + offset, - datalen - offset, downenc); - if (res < 1) { - /* nothing encoded */ - b++; /* for final \0 */ - break; - } - - b = b + strlen(b) + 1; - - offset += res; - if (offset >= datalen) - break; - } - - /* Add final \0 */ - *b = '\0'; - - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, mxbuf, - sizeof(mxbuf)); - } else if (q->type == T_TXT) { - /* TXT with base32 */ - char txtbuf[64*1024]; - size_t space = sizeof(txtbuf) - 1;; - - memset(txtbuf, 0, sizeof(txtbuf)); - - if (downenc == 'S') { - txtbuf[0] = 's'; /* plain base64(Sixty-four) */ - len = b64->encode(txtbuf+1, &space, data, datalen); - } - else if (downenc == 'U') { - txtbuf[0] = 'u'; /* Base64 with Underscore */ - len = b64u->encode(txtbuf+1, &space, data, datalen); - } - else if (downenc == 'V') { - txtbuf[0] = 'v'; /* Base128 */ - len = b128->encode(txtbuf+1, &space, data, datalen); - } - else if (downenc == 'R') { - txtbuf[0] = 'r'; /* Raw binary data */ - len = MIN(datalen, sizeof(txtbuf) - 1); - memcpy(txtbuf + 1, data, len); - } else { - txtbuf[0] = 't'; /* plain base32(Thirty-two) */ - len = b32->encode(txtbuf+1, &space, data, datalen); - } - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, txtbuf, len+1); - } else { - /* Normal NULL-record encode */ - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); - } - - if (len < 1) { - warnx("dns_encode doesn't fit"); - return; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes data\n", - format_addr(&q->from, q->fromlen), q->type, q->name, datalen); - } - - sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); + server_stop(); } static void print_usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] " - "[-4] [-6] [-c] [-s] [-f] [-D] [-u user] " - "[-t chrootdir] [-d device] [-m mtu] [-z context] " - "[-l ipv4 listen address] [-L ipv6 listen address] " - "[-p port] [-n external ip] [-b dnsport] " - "[-P password] [-F pidfile] [-i max idle time] " - "tunnel_ip[/netmask] topdomain\n", __progname); + fprintf(stderr, "Usage: %s [-v] [-h] [-4] [-6] [-c] [-s] [-f] [-D] " + "[-u user] [-t chrootdir] [-d device] [-m mtu] [-z context] " + "[-l ipv4 listen address] [-L ipv6 listen address] [-p port] " + "[-n external ip] [-b dnsport] [-P password] [-F pidfile] " + "[-i max idle time] tunnel_ip[/netmask] topdomain\n", __progname); } static void @@ -2343,7 +188,7 @@ help() { static void version() { fprintf(stderr, "iodine IP over DNS tunneling server\n"); - fprintf(stderr, "Git version: %s\n", GITREVISION); + fprintf(stderr, "Git version: %s; protocol version %08X\n", GITREVISION, PROTOCOL_VERSION); exit(0); } @@ -2419,25 +264,21 @@ main(int argc, char **argv) bind_fd = 0; mtu = 1130; /* Very many relays give fragsize 1150 or slightly higher for NULL; tun/zlib adds ~17 bytes. */ + dns4addr_len = 0; + dns6addr_len = 0; listen_ip4 = NULL; listen_ip6 = NULL; port = 53; - ns_ip = INADDR_ANY; ns_get_externalip = 0; addrfamily = AF_UNSPEC; - check_ip = 1; skipipconfig = 0; - debug = 0; - netmask = 27; pidfile = NULL; - - b32 = get_base32_encoder(); - b64 = get_base64_encoder(); - b64u = get_base64u_encoder(); - b128 = get_base128_encoder(); + srand(time(NULL)); retval = 0; + server_init(); + #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); #endif @@ -2450,10 +291,6 @@ main(int argc, char **argv) __progname++; #endif - memset(password, 0, sizeof(password)); - srand(time(NULL)); - fw_query_init(); - while ((choice = getopt(argc, argv, "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { switch(choice) { case '4': @@ -2593,17 +430,20 @@ main(int argc, char **argv) foreground = 1; } - dns4addr_len = get_addr(listen_ip4, port, AF_INET, AI_PASSIVE | AI_NUMERICHOST, &dns4addr); - if (dns4addr_len < 0) { - warnx("Bad IPv4 address to listen on."); - usage(); + if (addrfamily == AF_UNSPEC || addrfamily == AF_INET) { + dns4addr_len = get_addr(listen_ip4, port, AF_INET, AI_PASSIVE | AI_NUMERICHOST, &dns4addr); + if (dns4addr_len < 0) { + warnx("Bad IPv4 address to listen on."); + usage(); + } } - dns6addr_len = get_addr(listen_ip6, port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &dns6addr); - if (dns6addr_len < 0) { - warnx("Bad IPv6 address to listen on."); - usage(); + if (addrfamily == AF_UNSPEC || addrfamily == AF_INET6) { + dns6addr_len = get_addr(listen_ip6, port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &dns6addr); + if (dns6addr_len < 0) { + warnx("Bad IPv6 address to listen on."); + usage(); + } } - if(bind_enable) { in_addr_t dns_ip = ((struct sockaddr_in *) &dns4addr)->sin_addr.s_addr; if (bind_port < 1 || bind_port > 65535) { @@ -2767,7 +607,7 @@ main(int argc, char **argv) syslog(LOG_INFO, "started, listening on port %d", port); - tunnel(tun_fd, &dns_fds, bind_fd, max_idle_time); + server_tunnel(tun_fd, &dns_fds, bind_fd, max_idle_time); syslog(LOG_INFO, "stopping"); close_dns(bind_fd); diff --git a/src/osflags b/src/osflags old mode 100755 new mode 100644 diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..8d84ee6 --- /dev/null +++ b/src/server.c @@ -0,0 +1,1780 @@ +/* + * Copyright (c) 2006-2015 Erik Ekman , + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "version.h" + +#include "dns.h" +#include "encoding.h" +#include "base32.h" +#include "base64.h" +#include "base64u.h" +#include "base128.h" +#include "user.h" +#include "login.h" +#include "tun.h" +#include "fw_query.h" +#include "util.h" +#include "server.h" +#include "window.h" + +#ifdef HAVE_SYSTEMD +# include +#endif + +#ifdef WINDOWS32 +WORD req_version = MAKEWORD(2, 2); +WSADATA wsa_data; +#else +#include +#endif + +/* Global server variables */ +int running = 1; +char *topdomain; +char password[33]; +struct encoder *b32; +struct encoder *b64; +struct encoder *b64u; +struct encoder *b128; + +int check_ip; +int my_mtu; +in_addr_t my_ip; +int netmask; + +in_addr_t ns_ip; + +int bind_port; +int debug; + +void +server_init() +{ + running = 1; + ns_ip = INADDR_ANY; + netmask = 27; + debug = 0; + check_ip = 1; + memset(password, 0, sizeof(password)); + fw_query_init(); + b32 = get_base32_encoder(); + b64 = get_base64_encoder(); + b64u = get_base64u_encoder(); + b128 = get_base128_encoder(); +} + +void +server_stop() +{ + running = 0; +} + +static void +send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr_storage *from, socklen_t fromlen) +{ + char packet[buflen + RAW_HDR_LEN]; + int len = buflen; + + memcpy(packet, raw_header, RAW_HDR_LEN); + if (len) { + memcpy(&packet[RAW_HDR_LEN], buf, len); + } + + len += RAW_HDR_LEN; + packet[RAW_HDR_CMD] = cmd | (user & 0x0F); + + DEBUG(3, "TX-raw: client %s (user %d), cmd %d, %d bytes", + format_addr(from, fromlen), user, cmd, len); + + sendto(fd, packet, len, 0, (struct sockaddr *) from, fromlen); +} + +/* Ringbuffer Query Handling (qmem) and DNS Cache: + This is used to make the handling duplicates and query timeouts simpler + and all handled in one place. + Using this, lazy mode is possible with n queries (n <= windowsize) + + New queries are placed consecutively in the buffer, replacing any old + queries (already responded to) if length == QMEM_LEN. Old queries are kept + as a record for duplicate requests. If a dupe is found and USE_DNSCACHE is + defined, the previous answer is sent (if it exists), otherwise an invalid + response is sent. + + On the DNS cache: + This cache is implemented to better handle the aggressively impatient DNS + servers that very quickly re-send requests when we choose to not + immediately answer them in lazy mode. This cache works much better than + pruning(=dropping) the improper requests, since the DNS server will + actually get an answer instead of silence. + + Because of the CMC in both ping and upstream data, unwanted cache hits + are prevented. Due to the combination of CMC and varying sequence IDs, it + is extremely unlikely that any duplicate answers will be incorrectly sent + during a session (given QMEM_LEN is not very large). */ + +#define QMEM_DEBUG(l, u, ...) \ + if (debug >= l) {\ + TIMEPRINT("[QMEM u%d (%lu/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } + +static void +qmem_init(int userid) +/* initialize user QMEM and DNS cache (if enabled) */ +{ + memset(&users[userid].qmem, 0, sizeof(struct qmem_buffer)); + for (size_t i = 0; i < QMEM_LEN; i++) { + users[userid].qmem.queries[i].q.id = -1; + } +} + +static int +qmem_is_cached(int dns_fd, int userid, struct query *q) +/* Check if an answer for a particular query is cached in qmem + * If so, sends an "invalid" answer or one from DNS cache + * Returns 0 if new query (ie. not cached), 1 if cached (and then answered) */ +{ + struct qmem_buffer *buf; + struct query *pq; + char *data = "x"; + char dataenc = 'T'; + size_t len = 1; + int dnscache = 0; + buf = &users[userid].qmem; + + /* Check if this is a duplicate query */ + for (size_t p = buf->start; p != buf->end; p = (p + 1) % QMEM_LEN) { + pq = &buf->queries[p].q; + if (pq->id != q->id) + continue; + if (pq->type != q->type) + continue; + + if (strcasecmp(pq->name, q->name)) + continue; + + /* Aha! A match! */ + +#ifdef USE_DNSCACHE + /* Check if answer is in DNS cache */ + if (buf->queries[p].a.len) { + data = (char *)buf->queries[p].a.data; + len = buf->queries[p].a.len; + dataenc = users[userid].downenc; + dnscache = 1; + } +#endif + + QMEM_DEBUG(2, userid, "OUT from qmem for '%s', %s", q->name, + dnscache ? "answer from DNS cache" : "sending invalid response"); + write_dns(dns_fd, q, data, len, dataenc); + return 1; + } + return 0; +} + +static int +qmem_append(int userid, struct query *q) +/* Appends incoming query to the buffer. */ +{ + struct qmem_buffer *buf; + buf = &users[userid].qmem; + + if (buf->num_pending >= QMEM_LEN) { + /* this means we have QMEM_LEN *pending* queries; overwrite oldest one + * to prevent buildup of ancient queries */ + QMEM_DEBUG(2, userid, "Full of pending queries! Replacing old query %d with new %d.", + buf->queries[buf->start].q.id, q->id); + } + + if (buf->length < QMEM_LEN) { + buf->length++; + } else { + /* will replace oldest query (in buf->queries[buf->start]) */ + buf->start = (buf->start + 1) % QMEM_LEN; + } + + QMEM_DEBUG(5, userid, "add query ID %d, timeout %lu ms", q->id, timeval_to_ms(&users[userid].dns_timeout)); + + /* Copy query into buffer */ + memcpy(&buf->queries[buf->end].q, q, sizeof(struct query)); +#ifdef USE_DNSCACHE + buf->queries[buf->end].a.len = 0; +#endif + buf->end = (buf->end + 1) % QMEM_LEN; + buf->num_pending += 1; + return 1; +} + +static void +qmem_answered(int userid, uint8_t *data, size_t len) +/* Call when oldest/first/earliest query added has been answered */ +{ + struct qmem_buffer *buf; + size_t answered; + buf = &users[userid].qmem; + + if (buf->num_pending == 0) { + /* Most likely caused by bugs somewhere else. */ + QMEM_DEBUG(1, userid, "Query answered with 0 in qmem! Fix bugs."); + return; + } + answered = buf->start_pending; + buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; + buf->num_pending -= 1; + +#ifdef USE_DNSCACHE + /* Add answer to query entry */ + if (len && data) { + if (len > 4096) { + QMEM_DEBUG(1, userid, "got answer with length >4096!"); + } + memcpy(&buf->queries[answered].a.data, data, MIN(len, 4096)); + buf->queries[answered].a.len = len; + } +#endif + + QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[answered].q.id); +} + +struct query * +qmem_get_next_response(int userid) +/* Gets oldest query to be responded to (for lazy mode) or NULL if none available + * The query is NOT marked as "answered" since that is done later. */ +{ + struct qmem_buffer *buf; + struct query *q; + buf = &users[userid].qmem; + if (buf->length == 0 || buf->num_pending == 0) + return NULL; + q = &buf->queries[buf->start_pending].q; + QMEM_DEBUG(3, userid, "next response using cached query: ID %d", q->id); + return q; +} + +static struct timeval +qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) +/* Gets max interval before the next query has to be responded to + * Response(s) are sent automatically for queries if: + * - the query has timed out + * - the user has data to send or pending ACKs, and spare pending queries + * - the user has excess pending queries (>downstream window size) + * Returns largest safe time to wait before next timeout */ +{ + struct timeval now, timeout, soonest, tmp, age, nextresend; + soonest.tv_sec = 10; + soonest.tv_usec = 0; + int userid, qnum, nextuser = -1, immediate, resend = 0; + struct query *q = NULL, *nextq = NULL; + size_t sending, total, sent; + time_t age_ms; + struct tun_user *u; + + gettimeofday(&now, NULL); + for (userid = 0; userid < created_users; userid++) { + if (!user_active(userid)) + continue; + + u = &users[userid]; + + if (u->qmem.num_pending == 0) + continue; + + /* Keep track of how many fragments we can send */ + if (u->lazy) { + total = window_sending(u->outgoing, &nextresend); + if ((nextresend.tv_sec != 0 || nextresend.tv_usec != 0) + && u->qmem.num_pending >= 1) { + /* will use nextresend as max wait time if it is smallest + * and if user has spare queries */ + resend = 1; + soonest = nextresend; + } + + if (u->qmem.num_pending > u->outgoing->windowsize) { + /* calculate number of "excess" queries */ + total = MAX(total, u->qmem.num_pending - u->outgoing->windowsize); + } + } else { + /* User in immediate mode, must answer all pending queries */ + total = u->qmem.num_pending; + } + + sending = total; + sent = 0; + + qnum = u->qmem.start_pending; + for (; qnum != u->qmem.end; qnum = (qnum + 1) % QMEM_LEN) { + q = &u->qmem.queries[qnum].q; + + /* queries will always be in time order */ + timeradd(&q->time_recv, &u->dns_timeout, &timeout); + if (sending > 0 || !timercmp(&now, &timeout, <) || u->next_upstream_ack >= 0) { + /* respond to a query with ping/data if: + * - query has timed out (ping, or data if available) + * - user has pending data (always data) + * - user has pending ACK (either) */ + timersub(&now, &q->time_recv, &age); + age_ms = timeval_to_ms(&age); + + /* only consider "immediate" when age is negligible */ + immediate = llabs(age_ms) <= 10; + + QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old (%s), timeout %ld ms", + q->id, age_ms, immediate ? "immediate" : "lazy", timeval_to_ms(&u->dns_timeout)); + + sent++; + QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %lu of %lu + sending another %lu", + q->id, u->next_upstream_ack, sent, total, sending); + + send_data_or_ping(dns_fds, userid, q, 0, immediate); + + if (sending > 0) + sending--; + continue; + } + + timersub(&timeout, &now, &tmp); + if (timercmp(&tmp, &soonest, <)) { + /* the oldest non-timed-out query in the buffer will be the + * soonest to timeout for this user; we can skip the rest */ + soonest = tmp; + nextuser = userid; + nextq = q; + break; + } + } + } + + if (debug >= 5) { + time_t soonest_ms = timeval_to_ms(&soonest); + if (nextq && nextuser >= 0) { + QMEM_DEBUG(5, nextuser, "can wait for %lu ms, will send id %d", soonest_ms, nextq->id); + } else { + if (nextuser < 0) + nextuser = 0; + if (soonest_ms != 10000 && resend) { + /* only if resending some frags */ + QMEM_DEBUG(5, nextuser, "Resending some fragments") + } else { + QMEM_DEBUG(2, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); + } + } + } + + if (sendq) + *sendq = nextq; + if (touser) + *touser = nextuser; + + return soonest; +} + +static int +get_dns_fd(struct dnsfd *fds, struct sockaddr_storage *addr) +{ + if (addr->ss_family == AF_INET6) { + return fds->v6fd; + } + return fds->v4fd; +} + + +static void +forward_query(int bind_fd, struct query *q) +{ + char buf[64*1024]; + int len; + struct fw_query fwq; + struct sockaddr_in *myaddr; + in_addr_t newaddr; + + len = dns_encode(buf, sizeof(buf), q, QR_QUERY, q->name, strlen(q->name)); + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } + + /* Store sockaddr for q->id */ + memcpy(&(fwq.addr), &(q->from), q->fromlen); + fwq.addrlen = q->fromlen; + fwq.id = q->id; + fw_query_put(&fwq); + + newaddr = inet_addr("127.0.0.1"); + myaddr = (struct sockaddr_in *) &(q->from); + memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t)); + myaddr->sin_port = htons(bind_port); + + DEBUG(2, "TX: NS reply"); + + if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("forward query error"); + } +} + +static void +send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, struct query *q) +{ + char out[9]; + + switch (ack) { + case VERSION_ACK: + strncpy(out, "VACK", sizeof(out)); + break; + case VERSION_NACK: + strncpy(out, "VNAK", sizeof(out)); + break; + case VERSION_FULL: + strncpy(out, "VFUL", sizeof(out)); + break; + } + + out[4] = ((payload >> 24) & 0xff); + out[5] = ((payload >> 16) & 0xff); + out[6] = ((payload >> 8) & 0xff); + out[7] = ((payload) & 0xff); + out[8] = userid & 0xff; + + write_dns(fd, q, out, sizeof(out), users[userid].downenc); +} + +void +send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, + int ping, int immediate) +/* Sends current fragment to user, or a ping if no data available. + ping: 1=force send ping (even if data available), 0=only send if no data. + immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem */ +{ + uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; + size_t datalen, headerlen; + struct fragment *f; + struct frag_buffer *out, *in; + + in = users[userid].incoming; + out = users[userid].outgoing; + + window_tick(out); + + f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); + + /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ + if (!f) { + /* No data, may as well send data/ping header (with extra info) */ + ping = 1; + datalen = 0; + pkt[0] = 0; /* Pings don't need seq IDs unless they have data */ + pkt[1] = users[userid].next_upstream_ack & 0xFF; + pkt[2] = (users[userid].next_upstream_ack < 0 ? 0 : 1) << 3; + users[userid].next_upstream_ack = -1; + } else { + datalen = f->len; + pkt[0] = f->seqID & 0xFF; + pkt[1] = f->ack_other & 0xFF; + pkt[2] = ((f->ack_other < 0 ? 0 : 1) << 3) | ((f->compressed & 1) << 2) | (f->start << 1) | f->end; + headerlen = DOWNSTREAM_HDR; + } + + /* If this is being responded to immediately (ie. not from qmem) */ + pkt[2] |= (immediate & 1) << 5; + + if (ping) { + /* set ping flag and build extra header */ + pkt[2] |= 1 << 4; + pkt[3] = out->windowsize & 0xFF; + pkt[4] = in->windowsize & 0xFF; + pkt[5] = out->start_seq_id & 0xFF; + pkt[6] = in->start_seq_id & 0xFF; + headerlen = DOWNSTREAM_PING_HDR; + } + if (datalen + headerlen > sizeof(pkt)) { + /* Should never happen, or at least user should be warned about + * fragsize > MAX_FRAGLEN earlier on */ + warnx("send_frag_or_dataless: fragment too large to send! (%lu)", datalen); + return; + } + if (f) + memcpy(pkt + headerlen, f->data, datalen); + + write_dns(get_dns_fd(dns_fds, &q->from), q, (char *)pkt, + datalen + headerlen, users[userid].downenc); + + /* mark query as answered */ + qmem_answered(userid, pkt, datalen + headerlen); + window_tick(out); +} + +void +user_process_incoming_data(int tun_fd, struct dnsfd *dns_fds, int userid, int ack) +{ + uint8_t pkt[65536]; + size_t datalen; + int compressed = 0; + + window_ack(users[userid].outgoing, ack); + window_tick(users[userid].outgoing); + + datalen = window_reassemble_data(users[userid].incoming, pkt, sizeof(pkt), &compressed); + window_tick(users[userid].incoming); + + /* Update time info */ + users[userid].last_pkt = time(NULL); + + if (datalen > 0) { + /* Data reassembled successfully + cleared out of buffer */ + handle_full_packet(tun_fd, dns_fds, userid, pkt, datalen, compressed); + } +} + +static int +user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, + size_t len, int compressed) +/* Appends data to a user's outgoing queue and sends it (in raw mode only) */ +{ + size_t datalen; + int ret = 0; + uint8_t out[65536], *data; + + data = indata; + datalen = len; + + /* use compressed or uncompressed packet to match user settings */ + if (users[userid].down_compression && !compressed) { + datalen = sizeof(out); + compress2(out, &datalen, indata, len, 9); + data = out; + } else if (!users[userid].down_compression && compressed) { + datalen = sizeof(out); + ret = uncompress(out, &datalen, indata, len); + if (ret != Z_OK) { + DEBUG(1, "FAIL: Uncompress == %d: %lu bytes to user %d!", ret, len, userid); + return 0; + } + } + + compressed = users[userid].down_compression; + + if (users[userid].conn == CONN_DNS_NULL && data && datalen) { + /* append new data to user's outgoing queue; sent later in qmem_max_wait */ + ret = window_add_outgoing_data(users[userid].outgoing, data, datalen, compressed); + + } else if (data && datalen) { /* CONN_RAW_UDP */ + if (!compressed) + DEBUG(1, "Sending in RAW mode uncompressed to user %d!", userid); + int dns_fd = get_dns_fd(dns_fds, &users[userid].host); + send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA, + &users[userid].host, users[userid].hostlen); + ret = 1; + } + + return ret; +} + +static int +tunnel_bind(int bind_fd, struct dnsfd *dns_fds) +{ + char packet[64*1024]; + struct sockaddr_storage from; + socklen_t fromlen; + struct fw_query *query; + unsigned short id; + int dns_fd; + int r; + + fromlen = sizeof(struct sockaddr); + r = recvfrom(bind_fd, packet, sizeof(packet), 0, + (struct sockaddr*)&from, &fromlen); + + if (r <= 0) + return 0; + + id = dns_get_id(packet, r); + + DEBUG(3, "RX: Got response on query %u from DNS", (id & 0xFFFF)); + + /* Get sockaddr from id */ + fw_query_get(id, &query); + if (!query) { + DEBUG(2, "Lost sender of id %u, dropping reply", (id & 0xFFFF)); + return 0; + } + + DEBUG(3, "TX: client %s id %u, %d bytes", + format_addr(&query->addr, query->addrlen), (id & 0xffff), r); + + dns_fd = get_dns_fd(dns_fds, &query->addr); + if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), + query->addrlen) <= 0) { + warn("forward reply error"); + } + + return 0; +} + +static int +tunnel_tun(int tun_fd, struct dnsfd *dns_fds) +{ + struct ip *header; + static uint8_t in[64*1024]; + int userid; + int read; + + if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) + return 0; + + /* find target ip in packet, in is padded with 4 bytes TUN header */ + header = (struct ip*) (in + 4); + userid = find_user_by_ip(header->ip_dst.s_addr); + if (userid < 0) + return 0; + + DEBUG(3, "IN: %d byte pkt from tun to user %d; compression %d", + read, userid, users[userid].down_compression); + + return user_send_data(userid, dns_fds, in, read, 0); +} + +static int +tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) +{ + struct query q; + int read; + int domain_len; + int inside_topdomain = 0; + + if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) + return 0; + + DEBUG(3, "RX: client %s ID %5d, type %d, name %s", + format_addr(&q.from, q.fromlen), q.id, q.type, q.name); + + domain_len = strlen(q.name) - strlen(topdomain); + if (domain_len >= 0 && !strcasecmp(q.name + domain_len, topdomain)) + inside_topdomain = 1; + /* require dot before topdomain */ + if (domain_len >= 1 && q.name[domain_len - 1] != '.') + inside_topdomain = 0; + + if (inside_topdomain) { + /* This is a query we can handle */ + + /* Handle A-type query for ns.topdomain, possibly caused + by our proper response to any NS request */ + if (domain_len == 3 && q.type == T_A && + (q.name[0] == 'n' || q.name[0] == 'N') && + (q.name[1] == 's' || q.name[1] == 'S') && + q.name[2] == '.') { + handle_a_request(dns_fd, &q, 0); + return 0; + } + + /* Handle A-type query for www.topdomain, for anyone that's + poking around */ + if (domain_len == 4 && q.type == T_A && + (q.name[0] == 'w' || q.name[0] == 'W') && + (q.name[1] == 'w' || q.name[1] == 'W') && + (q.name[2] == 'w' || q.name[2] == 'W') && + q.name[3] == '.') { + handle_a_request(dns_fd, &q, 1); + return 0; + } + + switch (q.type) { + case T_NULL: + case T_PRIVATE: + case T_CNAME: + case T_A: + case T_MX: + case T_SRV: + case T_TXT: + /* encoding is "transparent" here */ + handle_null_request(tun_fd, dns_fd, dns_fds, &q, domain_len); + break; + case T_NS: + handle_ns_request(dns_fd, &q); + break; + default: + break; + } + } else { + /* Forward query to other port ? */ + DEBUG(2, "Requested domain outside our topdomain."); + if (bind_fd) { + forward_query(bind_fd, &q); + } + } + return 0; +} + +int +server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) +{ + struct timeval tv; + fd_set fds; + int i; + int userid; + struct query *answer_now = NULL; + time_t last_action = time(NULL); + + if (debug >= 5) + window_debug = debug - 3; + + while (running) { + int maxfd; + /* max wait time based on pending queries */ + tv = qmem_max_wait(dns_fds, &userid, &answer_now); + + FD_ZERO(&fds); + maxfd = 0; + + if (dns_fds->v4fd >= 0) { + FD_SET(dns_fds->v4fd, &fds); + maxfd = MAX(dns_fds->v4fd, maxfd); + } + if (dns_fds->v6fd >= 0) { + FD_SET(dns_fds->v6fd, &fds); + maxfd = MAX(dns_fds->v6fd, maxfd); + } + + if (bind_fd) { + /* wait for replies from real DNS */ + FD_SET(bind_fd, &fds); + maxfd = MAX(bind_fd, maxfd); + } + + /* Don't read from tun if all users have filled outpacket queues */ + if(!all_users_waiting_to_send()) { + FD_SET(tun_fd, &fds); + maxfd = MAX(tun_fd, maxfd); + } + + i = select(maxfd + 1, &fds, NULL, NULL, &tv); + + if(i < 0) { + if (running) + warn("select"); + return 1; + } + + if (i == 0) { + if (max_idle_time) { + /* only trigger the check if that's worth ( ie, no need to loop over if there + is something to send */ + if (difftime(time(NULL), last_action) > max_idle_time) { + for (userid = 0; userid < created_users; userid++) { + last_action = (users[userid].last_pkt > last_action) ? users[userid].last_pkt : last_action; + } + if (difftime(time(NULL), last_action) > max_idle_time) { + fprintf(stderr, "Server idle for too long, shutting down...\n"); + running = 0; + } + } + } + } else { + if (FD_ISSET(tun_fd, &fds)) { + tunnel_tun(tun_fd, dns_fds); + } + if (FD_ISSET(dns_fds->v4fd, &fds)) { + tunnel_dns(tun_fd, dns_fds->v4fd, dns_fds, bind_fd); + } + if (FD_ISSET(dns_fds->v6fd, &fds)) { + tunnel_dns(tun_fd, dns_fds->v6fd, dns_fds, bind_fd); + } + if (FD_ISSET(bind_fd, &fds)) { + tunnel_bind(bind_fd, dns_fds); + } + } + } + + return 0; +} + +void +handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len, int compressed) +{ + size_t rawlen; + uint8_t out[64*1024], *rawdata; + struct ip *hdr; + int touser; + int ret; + + /* Check if data needs to be uncompressed */ + if (compressed) { + rawlen = sizeof(out); + ret = uncompress(out, &rawlen, data, len); + rawdata = out; + } else { + rawlen = len; + rawdata = data; + ret = Z_OK; + } + + if (ret == Z_OK) { + hdr = (struct ip*) (out + 4); + touser = find_user_by_ip(hdr->ip_dst.s_addr); + DEBUG(2, "FULL PKT: %lu bytes from user %d (touser %d)", len, userid, touser); + if (touser == -1) { + /* send the uncompressed packet to tun device */ + write_tun(tun_fd, rawdata, rawlen); + } else { + /* don't re-compress if possible */ + if (users[touser].down_compression && compressed) { + user_send_data(touser, dns_fds, data, len, 1); + } else { + user_send_data(touser, dns_fds, rawdata, rawlen, 0); + } + } + } else { + DEBUG(2, "Discarded upstream data from user %d, uncompress() result: %d", userid, ret); + } +} + +static void +handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int userid) +{ + char myhash[16]; + + if (len < 16) { + DEBUG(2, "Invalid raw login packet: length %lu < 16 bytes!", len); + return; + } + + if (userid < 0 || userid >= created_users || + check_authenticated_user_and_ip(userid, q) != 0) { + DEBUG(2, "User %d not authenticated, ignoring raw login!", userid); + return; + } + + DEBUG(1, "RX-raw: login, len %lu, from user %d", len, userid); + + /* User sends hash of seed + 1 */ + login_calculate(myhash, 16, password, users[userid].seed + 1); + if (memcmp(packet, myhash, 16) == 0) { + /* Update time info for user */ + users[userid].last_pkt = time(NULL); + + /* Store remote IP number */ + memcpy(&(users[userid].host), &(q->from), q->fromlen); + users[userid].hostlen = q->fromlen; + + /* Correct hash, reply with hash of seed - 1 */ + user_set_conn_type(userid, CONN_RAW_UDP); + login_calculate(myhash, 16, password, users[userid].seed - 1); + send_raw(fd, (uint8_t *)myhash, 16, userid, RAW_HDR_CMD_LOGIN, &q->from, q->fromlen); + + users[userid].authenticated_raw = 1; + } +} + +static void +handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_fds, int tun_fd, int userid) +{ + if (check_authenticated_user_and_ip(userid, q) != 0) { + return; + } + if (!users[userid].authenticated_raw) return; + + /* Update time info for user */ + users[userid].last_pkt = time(NULL); + + /* copy to packet buffer, update length */ + + DEBUG(3, "RX-raw: full pkt raw, length %lu, from user %d", len, userid); + + handle_full_packet(tun_fd, dns_fds, userid, packet, len, 1); +} + +static void +handle_raw_ping(struct query *q, int dns_fd, int userid) +{ + if (check_authenticated_user_and_ip(userid, q) != 0) { + return; + } + if (!users[userid].authenticated_raw) return; + + /* Update time info for user */ + users[userid].last_pkt = time(NULL); + + DEBUG(3, "RX-raw: ping from user %d", userid); + + /* Send ping reply */ + send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, &q->from, q->fromlen); +} + +static int +raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsfd *dns_fds, int tun_fd) +{ + int raw_user; + uint8_t raw_cmd; + + /* minimum length */ + if (len < RAW_HDR_LEN) return 0; + /* should start with header */ + if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) + return 0; + + raw_cmd = RAW_HDR_GET_CMD(packet); + raw_user = RAW_HDR_GET_USR(packet); + + DEBUG(3, "RX-raw: client %s, user %d, raw command 0x%02X, length %lu", + format_addr(&q->from, q->fromlen), raw_user, raw_cmd, len); + + packet += RAW_HDR_LEN; + len -= RAW_HDR_LEN; + switch (raw_cmd) { + case RAW_HDR_CMD_LOGIN: + /* Login challenge */ + handle_raw_login(packet, len, q, dns_fd, raw_user); + break; + case RAW_HDR_CMD_DATA: + /* Data packet */ + handle_raw_data(packet, len, q, dns_fds, tun_fd, raw_user); + break; + case RAW_HDR_CMD_PING: + /* Keepalive packet */ + handle_raw_ping(q, dns_fd, raw_user); + break; + default: + DEBUG(1, "Unhandled raw command %02X from user %d", raw_cmd, raw_user); + break; + } + return 1; +} + +int +read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) +/* FIXME: dns_fds and tun_fd are because of raw_decode() below */ +{ + struct sockaddr_storage from; + socklen_t addrlen; + uint8_t packet[64*1024]; + int r; +#ifndef WINDOWS32 + char control[CMSG_SPACE(sizeof (struct in6_pktinfo))]; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + + addrlen = sizeof(struct sockaddr_storage); + iov.iov_base = packet; + iov.iov_len = sizeof(packet); + + msg.msg_name = (caddr_t) &from; + msg.msg_namelen = (unsigned) addrlen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + msg.msg_flags = 0; + + r = recvmsg(fd, &msg, 0); +#else + addrlen = sizeof(struct sockaddr_storage); + r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen); +#endif /* !WINDOWS32 */ + + if (r > 0) { + memcpy(&q->from, &from, addrlen); + q->fromlen = addrlen; + gettimeofday(&q->time_recv, NULL); + + /* TODO do not handle raw packets here! */ + if (raw_decode(packet, r, q, fd, dns_fds, tun_fd)) { + return 0; + } + if (dns_decode(NULL, 0, q, QR_QUERY, (char *)packet, r) < 0) { + return 0; + } + +#ifndef WINDOWS32 + memset(&q->destination, 0, sizeof(struct sockaddr_storage)); + /* Read destination IP address */ + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == DSTADDR_SOCKOPT) { + + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + addr->sin_family = AF_INET; + addr->sin_addr = *dstaddr(cmsg); + q->dest_len = sizeof(*addr); + break; + } + if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) { + + struct in6_pktinfo *pktinfo; + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; + pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsg); + addr->sin6_family = AF_INET6; + memcpy(&addr->sin6_addr, &pktinfo->ipi6_addr, sizeof(struct in6_addr)); + q->dest_len = sizeof(*addr); + break; + } + } +#endif + + return strlen(q->name); + } else if (r < 0) { + /* Error */ + warn("read dns"); + } + + return 0; +} + +static size_t +write_dns_nameenc(uint8_t *buf, size_t buflen, uint8_t *data, size_t datalen, char downenc) +/* Returns #bytes of data that were encoded */ +{ + static int td_cmc; + char td[3]; + struct encoder *enc; + + /* Make a rotating topdomain to prevent filtering, ie 10-bit CMC */ + td_cmc ++; + td_cmc &= 0x3FF; + + td[0] = b32_5to8(td_cmc & 0x1F); + td[1] = b32_5to8((td_cmc >> 5) & 0x1F); + td[2] = 0; + + /* encode data,datalen to CNAME/MX answer */ + if (downenc == 'S') { + buf[0] = 'i'; + enc = b64; + } else if (downenc == 'U') { + buf[0] = 'j'; + enc = b64u; + } else if (downenc == 'V') { + buf[0] = 'k'; + enc = b128; + } else { + buf[0] = 'h'; + enc = b32; + } + + return build_hostname(buf, buflen, data, datalen, td, enc, 0xFF, 1); +} + +void +write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) +{ + char buf[64*1024]; + int len = 0; + + if (q->type == T_CNAME || q->type == T_A) { + char cnamebuf[1024]; /* max 255 */ + + write_dns_nameenc((uint8_t *)cnamebuf, sizeof(cnamebuf), (uint8_t *)data, datalen, downenc); + + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, sizeof(cnamebuf)); + } else if (q->type == T_MX || q->type == T_SRV) { + char mxbuf[64*1024]; + char *b = mxbuf; + int offset = 0; + int res; + + while (1) { + res = write_dns_nameenc((uint8_t *)b, sizeof(mxbuf) - (b - mxbuf), + (uint8_t *)data + offset, datalen - offset, downenc); + if (res < 1) { + /* nothing encoded */ + b++; /* for final \0 */ + break; + } + + b = b + strlen(b) + 1; + + offset += res; + if (offset >= datalen) + break; + } + + /* Add final \0 */ + *b = '\0'; + + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, mxbuf, + sizeof(mxbuf)); + } else if (q->type == T_TXT) { + /* TXT with base32 */ + uint8_t txtbuf[64*1024]; + size_t space = sizeof(txtbuf) - 1;; + + memset(txtbuf, 0, sizeof(txtbuf)); + + if (downenc == 'S') { + txtbuf[0] = 's'; /* plain base64(Sixty-four) */ + len = b64->encode(txtbuf+1, &space, (uint8_t *)data, datalen); + } + else if (downenc == 'U') { + txtbuf[0] = 'u'; /* Base64 with Underscore */ + len = b64u->encode(txtbuf+1, &space, (uint8_t *)data, datalen); + } + else if (downenc == 'V') { + txtbuf[0] = 'v'; /* Base128 */ + len = b128->encode(txtbuf+1, &space, (uint8_t *)data, datalen); + } + else if (downenc == 'R') { + txtbuf[0] = 'r'; /* Raw binary data */ + len = MIN(datalen, sizeof(txtbuf) - 1); + memcpy(txtbuf + 1, data, len); + } else { + txtbuf[0] = 't'; /* plain base32(Thirty-two) */ + len = b32->encode(txtbuf+1, &space, (uint8_t *)data, datalen); + } + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, (char *)txtbuf, len+1); + } else { + /* Normal NULL-record encode */ + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); + } + + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } + + DEBUG(3, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'", + format_addr(&q->from, q->fromlen), q->id, datalen, q->type, q->name); + + sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); +} + +void +handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) +/* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ +{ + struct in_addr tempip; + uint8_t in[512]; + char logindata[16]; + uint8_t out[64*1024]; + static uint8_t unpacked[64*1024]; + char *tmp[2]; + int userid; + size_t read; + + userid = -1; + + /* Everything here needs at least two chars in the name */ + if (domain_len < 2) + return; + + memcpy(in, q->name, MIN(domain_len, sizeof(in))); + + DEBUG(3, "NULL request length %d/%lu, command '%c'", domain_len, sizeof(in), in[0]); + + if(in[0] == 'V' || in[0] == 'v') { /* Version request */ + uint32_t version = !PROTOCOL_VERSION; + + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); + /* Version greeting, compare and send ack/nak */ + if (read > 4) { + /* Received V + 32bits version (network byte order) */ + version = ntohl(*(uint32_t *) unpacked); + } /* if invalid pkt, just send VNAK */ + + if (version == PROTOCOL_VERSION) { + userid = find_available_user(); + if (userid >= 0) { + struct tun_user *u = &users[userid]; + u->seed = rand(); + /* Store remote IP number */ + memcpy(&(u->host), &(q->from), q->fromlen); + u->hostlen = q->fromlen; + + u->encoder = get_base32_encoder(); + + if (q->type == T_NULL || q->type == T_PRIVATE) { + u->downenc = 'R'; + u->downenc_bits = 8; + } else { + u->downenc = 'T'; + u->downenc_bits = 5; + } + u->down_compression = 1; + send_version_response(dns_fd, VERSION_ACK, u->seed, userid, q); + syslog(LOG_INFO, "Accepted version for user #%d from %s", + userid, format_addr(&q->from, q->fromlen)); + u->fragsize = 100; /* very safe */ + u->conn = CONN_DNS_NULL; + u->lazy = 0; + // TODO: client specified window size + u->outgoing->maxfraglen = u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR; + window_buffer_clear(u->outgoing); + window_buffer_clear(u->incoming); + u->next_upstream_ack = -1; + qmem_init(userid); + + DEBUG(1, "User %d connected with correct version from %s.", + userid, format_addr(&q->from, q->fromlen)); + } else { + /* No space for another user */ + send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); + syslog(LOG_INFO, "dropped user from %s, server full", + format_addr(&q->from, q->fromlen)); + } + } else { + send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q); + syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", + format_addr(&q->from, q->fromlen), version); + } + return; + } else if (in[0] == 'L' || in[0] == 'l') { /* Login request */ + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); + if (read < 17) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Login phase, handle auth */ + userid = unpacked[0]; + DEBUG(2, "Received login request for user %d from %s.", + userid, format_addr(&q->from, q->fromlen)); + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + syslog(LOG_WARNING, "dropped login request from user #%d from %s; expected source %s", + userid, format_addr(&q->from, q->fromlen), format_addr(&users[userid].host, users[userid].hostlen)); + return; + } else { + users[userid].last_pkt = time(NULL); + login_calculate(logindata, 16, password, users[userid].seed); + + if (read >= 18 && (memcmp(logindata, unpacked + 1, 16) == 0)) { + /* Store login ok */ + users[userid].authenticated = 1; + + /* Send ip/mtu/netmask info */ + tempip.s_addr = my_ip; + tmp[0] = strdup(inet_ntoa(tempip)); + tempip.s_addr = users[userid].tun_ip; + tmp[1] = strdup(inet_ntoa(tempip)); + + read = snprintf((char *)out, sizeof(out), "%s-%s-%d-%d", + tmp[0], tmp[1], my_mtu, netmask); + + write_dns(dns_fd, q, (char *)out, read, users[userid].downenc); + syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); + + free(tmp[1]); + free(tmp[0]); + } else { + write_dns(dns_fd, q, "LNAK", 4, 'T'); + syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password", + userid, format_addr(&q->from, q->fromlen)); + } + } + return; + } else if(in[0] == 'I' || in[0] == 'i') { /* IP address request */ + char reply[17]; + int length; + + userid = b32_8to5(in[1]); + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + reply[0] = 'I'; + if (q->from.ss_family == AF_INET) { + if (ns_ip != INADDR_ANY) { + /* If set, use assigned external ip (-n option) */ + memcpy(&reply[1], &ns_ip, sizeof(ns_ip)); + } else { + /* otherwise return destination ip from packet */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr)); + } + length = 1 + sizeof(struct in_addr); + } else { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; + memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr)); + length = 1 + sizeof(struct in6_addr); + } + + write_dns(dns_fd, q, reply, length, 'T'); + } else if(in[0] == 'Z' || in[0] == 'z') { /* Upstream codec check */ + /* Check for case conservation and chars not allowed according to RFC */ + + /* Reply with received hostname as data */ + /* No userid here, reply with lowest-grade downenc */ + write_dns(dns_fd, q, (char *)in, domain_len, 'T'); + return; + } else if(in[0] == 'S' || in[0] == 's') { /* Switch upstream codec */ + int codec; + struct encoder *enc; + if (domain_len < 3) { /* len at least 3, example: "S15" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + userid = b32_8to5(in[1]); + + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + codec = b32_8to5(in[2]); + + switch (codec) { + case 5: /* 5 bits per byte = base32 */ + enc = b32; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 6: /* 6 bits per byte = base64 */ + enc = b64; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ + enc = b64u; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 7: /* 7 bits per byte = base128 */ + enc = b128; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + break; + } + return; + } else if(in[0] == 'O' || in[0] == 'o') { /* Protocol options */ + int bits = 0; + int numopts; + char *opts; + + int tmp_lazy, tmp_downenc, tmp_comp; + if (domain_len < 7) { /* len at least 7, example: "oa1tcmc" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + userid = b32_8to5(in[1]); + + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + numopts = in[2] - '0'; + + if (domain_len < numopts + 6 || numopts == 0 || numopts > 9) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; /* invalid packet */ + } + + /* Temporary variables: don't change anything until all options parsed */ + tmp_lazy = users[userid].lazy; + tmp_comp = users[userid].down_compression; + tmp_downenc = users[userid].downenc; + + opts = (char *) in + 3; + + for (int i = 0; i < numopts; i++) { + switch (toupper(opts[i])) { + case 'T': + tmp_downenc = 'T'; + bits = 5; + break; + case 'S': + tmp_downenc = 'S'; + bits = 6; + break; + case 'U': + tmp_downenc = 'U'; + bits = 6; + break; + case 'V': + tmp_downenc = 'V'; + bits = 7; + break; + case 'R': + tmp_downenc = 'R'; + bits = 8; + break; + case 'L': + tmp_lazy = 1; + break; + case 'I': + tmp_lazy = 0; + break; + case 'C': + tmp_comp = 1; + break; + case 'D': + tmp_comp = 0; + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + return; + } + } + + /* Automatically switch to raw encoding if PRIVATE or NULL request */ + if ((q->type == T_NULL || q->type == T_PRIVATE) && !bits) { + users[userid].downenc = 'R'; + bits = 8; + DEBUG(2, "Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid); + } + if (bits) { + int f = users[userid].fragsize; + users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; + users[userid].downenc_bits = bits; + } + + DEBUG(1, "Options for user %d: down compression %d, data bits %d/maxlen %u (enc '%c'), lazy %d.", + userid, tmp_comp, bits, users[userid].outgoing->maxfraglen, tmp_downenc, tmp_lazy); + + /* Store any changes */ + users[userid].down_compression = tmp_comp; + users[userid].downenc = tmp_downenc; + users[userid].lazy = tmp_lazy; + + write_dns(dns_fd, q, opts, numopts, users[userid].downenc); + return; + } else if(in[0] == 'Y' || in[0] == 'y') { /* Downstream codec check */ + int i; + char *datap; + int datalen; + + if (domain_len < 6) { /* len at least 6, example: "YTxCMC" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + i = b32_8to5(in[2]); /* check variant */ + + switch (i) { + case 1: + datap = DOWNCODECCHECK1; + datalen = DOWNCODECCHECK1_LEN; + break; + default: + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + switch (toupper(in[1])) { + case 'T': + case 'S': + case 'U': + case 'V': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, toupper(in[1])); + return; + } + break; + case 'R': + if (q->type == T_NULL || q->type == T_TXT) { + write_dns(dns_fd, q, datap, datalen, 'R'); + return; + } + break; + } + + /* if still here, then codec not available */ + write_dns(dns_fd, q, "BADCODEC", 8, 'T'); + return; + + } else if(in[0] == 'R' || in[0] == 'r') { /* Downstream fragsize probe */ + int req_frag_size; + + if (domain_len < 16) { /* we'd better have some chars for data... */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Downstream fragsize probe packet */ + read = unpack_data(unpacked, sizeof(unpacked), in + 1, 5, b32); + + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + req_frag_size = ntohs(*(uint16_t *) (unpacked + 1)); + DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size); + + if (req_frag_size < 2 || req_frag_size > 2047) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + char buf[2048]; + int i; + unsigned int v = ((unsigned int) rand()) & 0xff; + + memset(buf, 0, sizeof(buf)); + buf[0] = (req_frag_size >> 8) & 0xff; + buf[1] = req_frag_size & 0xff; + /* make checkable pseudo-random sequence */ + buf[2] = 107; + for (i = 3; i < 2048; i++, v = (v + 107) & 0xff) + buf[i] = v; + write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); + } + return; + } else if(in[0] == 'N' || in[0] == 'n') { /* Downstream fragsize */ + int max_frag_size; + + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); + + if (read < 3) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Downstream fragsize packet */ + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + max_frag_size = ntohs(*(uint16_t *)(unpacked + 1)); + if (max_frag_size < 2) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + users[userid].fragsize = max_frag_size; + users[userid].outgoing->maxfraglen = (users[userid].downenc_bits * max_frag_size) / + 8 - DOWNSTREAM_PING_HDR; + write_dns(dns_fd, q, (char *) unpacked + 1, 2, users[userid].downenc); + + DEBUG(1, "Setting max downstream data length to %u bytes for user %d; %d bits (%c)", + users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); + } + return; + } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ + int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; + int respond, set_qtimeout, set_wtimeout; + unsigned qtimeout_ms, wtimeout_ms; + + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); + if (read < UPSTREAM_PING) { + DEBUG(1, "Invalid ping! Length %lu", read); + return; + } + + /* Check userid */ + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + /* Check if cached */ + if (qmem_is_cached(dns_fd, userid, q)) + return; + + dn_ack = ((unpacked[10] >> 2) & 1) ? unpacked[1] : -1; + up_winsize = unpacked[2]; + dn_winsize = unpacked[3]; + up_seq = unpacked[4]; + dn_seq = unpacked[5]; + + /* Query timeout and window frag timeout */ + qtimeout_ms = ntohs(*(uint16_t *) (unpacked + 6)); + wtimeout_ms = ntohs(*(uint16_t *) (unpacked + 8)); + + respond = unpacked[10] & 1; + set_qtimeout = (unpacked[10] >> 3) & 1; + set_wtimeout = (unpacked[10] >> 4) & 1; + + DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, %swtime %u ms, respond %d (flags %02X)", + userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, + set_qtimeout ? "SET " : "", qtimeout_ms, set_wtimeout ? "SET " : "", + wtimeout_ms, respond, unpacked[10]); + + if (set_qtimeout) { + /* update user's query timeout if timeout flag set */ + users[userid].dns_timeout = ms_to_timeval(qtimeout_ms); + + /* if timeout is 0, we do not enable lazy mode but it is effectively the same */ + int newlazy = !(qtimeout_ms == 0); + if (newlazy != users[userid].lazy) + DEBUG(2, "User %d: not setting lazymode to %d with timeout %u", + userid, newlazy, qtimeout_ms); + } + + if (set_wtimeout) { + /* update sending window fragment ACK timeout */ + users[userid].outgoing->timeout = ms_to_timeval(wtimeout_ms); + } + + qmem_append(userid, q); + + if (respond) { + /* ping handshake - set windowsizes etc, respond NOW using this query + * NOTE: not added to qmem */ + DEBUG(2, "PING HANDSHAKE set windowsizes (old/new) up: %d/%d, dn: %d/%d", + users[userid].outgoing->windowsize, dn_winsize, users[userid].incoming->windowsize, up_winsize); + users[userid].outgoing->windowsize = dn_winsize; + users[userid].incoming->windowsize = up_winsize; + send_data_or_ping(dns_fds, userid, q, 1, 1); + return; + } + + user_process_incoming_data(tun_fd, dns_fds, userid, dn_ack); + + /* if respond flag not set, query waits in qmem and is used later */ + } else if (isxdigit(in[0])) { /* Upstream data packet */ + int code = 0; + struct fragment f; + size_t len; + + /* Need 6 char header + >=1 char data */ + if (domain_len < UPSTREAM_HDR + 1) + return; + + if ((in[0] >= '0' && in[0] <= '9')) + code = in[0] - '0'; + if ((in[0] >= 'a' && in[0] <= 'f')) + code = in[0] - 'a' + 10; + if ((in[0] >= 'A' && in[0] <= 'F')) + code = in[0] - 'A' + 10; + + userid = code; + /* Check user and sending IP address */ + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal IP */ + } + + /* Check if cached */ + if (qmem_is_cached(dns_fd, userid, q)) { + /* if is cached, by this point it has already been answered */ + return; + } + + qmem_append(userid, q); + /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ + /* First byte (after userid) = CMC (ignored); skip 2 bytes */ + len = sizeof(unpacked); + read = b32->decode(unpacked, &len, in + 2, 5); + + f.seqID = unpacked[0]; + unpacked[2] >>= 4; /* Lower 4 bits are unused */ + f.ack_other = ((unpacked[2] >> 3) & 1) ? unpacked[1] : -1; + f.compressed = (unpacked[2] >> 2) & 1; + f.start = (unpacked[2] >> 1) & 1; + f.end = unpacked[2] & 1; + + /* Decode remainder of data with user encoding into fragment */ + f.len = unpack_data(f.data, MAX_FRAGSIZE, in + UPSTREAM_HDR, + domain_len - UPSTREAM_HDR, users[userid].encoder); + + DEBUG(3, "frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", + f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end); + + /* if already waiting for an ACK to be sent back upstream (on incoming buffer) */ + if (users[userid].next_upstream_ack >= 0) { + /* Shouldn't normally happen; will always be reset after sending a packet. */ + DEBUG(1, "[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); + } + + window_process_incoming_fragment(users[userid].incoming, &f); + users[userid].next_upstream_ack = f.seqID; + + user_process_incoming_data(tun_fd, dns_fds, userid, f.ack_other); + + /* Nothing to do. ACK for this fragment is sent later in qmem_max_wait, + * using an old query. This is left in qmem until needed/times out */ + } +} + + +void +handle_ns_request(int dns_fd, struct query *q) +/* Mostly identical to handle_a_request() below */ +{ + char buf[64*1024]; + int len; + + if (ns_ip != INADDR_ANY) { + /* If ns_ip set, overwrite destination addr with it. + * Destination addr will be sent as additional record (A, IN) */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); + } + + len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain); + if (len < 1) { + warnx("dns_encode_ns_response doesn't fit"); + return; + } + + DEBUG(2, "TX: NS reply client %s ID %5d, type %d, name %s, %d bytes", + format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("ns reply send error"); + } +} + +void +handle_a_request(int dns_fd, struct query *q, int fakeip) +/* Mostly identical to handle_ns_request() above */ +{ + char buf[64*1024]; + int len; + + if (fakeip) { + in_addr_t ip = inet_addr("127.0.0.1"); + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&addr->sin_addr, &ip, sizeof(ip)); + + } else if (ns_ip != INADDR_ANY) { + /* If ns_ip set, overwrite destination addr with it. + * Destination addr will be sent as additional record (A, IN) */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); + } + + len = dns_encode_a_response(buf, sizeof(buf), q); + if (len < 1) { + warnx("dns_encode_a_response doesn't fit"); + return; + } + + DEBUG(2, "TX: A reply client %s ID %5d, type %d, name %s, %d bytes", + format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("a reply send error"); + } +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..6183bf6 --- /dev/null +++ b/src/server.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2006-2015 Erik Ekman , + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __SERVER_H__ +#define __SERVER_H__ + +#ifdef WINDOWS32 +#include "windows.h" +#include +#else +#include +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#define _XPG4_2 +#include +#include +#include +#include +#include +#include +#include +#endif + +/* Max number of incoming queries to hold at one time (recommended to be same as windowsize) + * Memory = USERS * (sizeof(struct query_buffer) + sizeof(query) * QMEM_LEN) */ +#define QMEM_LEN 24 + +#define USE_DNSCACHE +/* QMEM entries contain additional space for DNS responses. + * Undefine to disable. */ + +/* Number of fragments in outgoing buffer. + * Mem usage: USERS * (MAX_FRAGLEN * OUTFRAGBUF_LEN + sizeof(struct window_buffer)) */ +#define OUTFRAGBUF_LEN 64 + +/* Number of fragments in incoming buffer; must be at least windowsize * 2 + * Minimum recommended = ((max packet size or MTU) / (max up fragsize)) * 2 + * ie. (1200 / 100) * 2 = 24 */ +#define INFRAGBUF_LEN 64 + +#define PASSWORD_ENV_VAR "IODINED_PASS" + +#if defined IP_RECVDSTADDR +# define DSTADDR_SOCKOPT IP_RECVDSTADDR +# define dstaddr(x) ((struct in_addr *) CMSG_DATA(x)) +#elif defined IP_PKTINFO +# define DSTADDR_SOCKOPT IP_PKTINFO +# define dstaddr(x) (&(((struct in_pktinfo *)(CMSG_DATA(x)))->ipi_addr)) +#endif + +#ifndef IPV6_RECVPKTINFO +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#endif + +#if !defined(BSD) && !defined(__GLIBC__) +static char *__progname; +#endif + +/* Struct with IPv4 and IPv6 file descriptors. + * Need to be passed on down to tunneling code since we can get a + * packet on one fd meant for a user on the other. + */ +struct dnsfd { + int v4fd; + int v6fd; +}; + +typedef enum { + VERSION_ACK, + VERSION_NACK, + VERSION_FULL +} version_ack_t; + +struct query_answer { + uint8_t data[4096]; + size_t len; +}; + +struct qmem_query { + struct query q; +#ifdef USE_DNSCACHE + struct query_answer a; +#endif +}; + +/* Struct used for QMEM + DNS cache */ +struct qmem_buffer { + struct qmem_query queries[QMEM_LEN]; + size_t start_pending; /* index of first "pending" query (ie. no response yet) */ + size_t start; /* index of first stored/pending query */ + size_t end; /* index of space after last stored/pending query */ + size_t length; /* number of stored queries */ + size_t num_pending; /* number of pending queries */ +}; + +extern char *topdomain; +extern char password[33]; +extern struct encoder *b32; +extern struct encoder *b64; +extern struct encoder *b64u; +extern struct encoder *b128; + +extern int check_ip; +extern int my_mtu; +extern in_addr_t my_ip; +extern int netmask; + +extern in_addr_t ns_ip; + +extern int bind_port; +extern int debug; + +void server_init(); +void server_stop(); +int server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time); + +int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); +void write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc); +void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len, int); +void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len); +void handle_ns_request(int dns_fd, struct query *q); +void handle_a_request(int dns_fd, struct query *q, int fakeip); + +void send_data_or_ping(struct dnsfd *, int, struct query *, int, int); + +#endif /* __SERVER_H__ */ diff --git a/src/tun.c b/src/tun.c index c05a591..f563320 100644 --- a/src/tun.c +++ b/src/tun.c @@ -466,7 +466,7 @@ close_tun(int tun_fd) #ifdef WINDOWS32 int -write_tun(int tun_fd, char *data, size_t len) +write_tun(int tun_fd, uint8_t *data, size_t len) { DWORD written; DWORD res; @@ -490,12 +490,12 @@ write_tun(int tun_fd, char *data, size_t len) } ssize_t -read_tun(int tun_fd, char *buf, size_t len) +read_tun(int tun_fd, uint8_t *buf, size_t len) { int bytes; memset(buf, 0, 4); - bytes = recv(tun_fd, buf + 4, len - 4, 0); + bytes = recv(tun_fd, (char *)buf + 4, len - 4, 0); if (bytes < 0) { return bytes; } else { @@ -504,7 +504,7 @@ read_tun(int tun_fd, char *buf, size_t len) } #else int -write_tun(int tun_fd, char *data, size_t len) +write_tun(int tun_fd, uint8_t *data, size_t len) { #if defined (FREEBSD) || defined (NETBSD) /* FreeBSD/NetBSD has no header */ @@ -545,7 +545,7 @@ write_tun(int tun_fd, char *data, size_t len) } ssize_t -read_tun(int tun_fd, char *buf, size_t len) +read_tun(int tun_fd, uint8_t *buf, size_t len) { #if defined (FREEBSD) || defined (NETBSD) /* FreeBSD/NetBSD has no header */ diff --git a/src/tun.h b/src/tun.h index 8982a9f..fda8abd 100644 --- a/src/tun.h +++ b/src/tun.h @@ -20,8 +20,8 @@ int open_tun(const char *); void close_tun(int); -int write_tun(int, char *, size_t); -ssize_t read_tun(int, char *, size_t); +int write_tun(int, uint8_t *, size_t); +ssize_t read_tun(int, uint8_t *, size_t); int tun_setip(const char *, const char *, int); int tun_setmtu(const unsigned); diff --git a/src/user.c b/src/user.c index ce1fa5a..6c4b69f 100644 --- a/src/user.c +++ b/src/user.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2014 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,9 +34,13 @@ #include "common.h" #include "encoding.h" #include "user.h" +#include "window.h" +#include "server.h" struct tun_user *users; unsigned usercount; +int created_users; + int init_users(in_addr_t my_ip, int netbits) @@ -60,6 +65,7 @@ init_users(in_addr_t my_ip, int netbits) maxusers = (1 << (32-netbits)) - 3; /* 3: Net addr, broadcast addr, iodined addr */ usercount = MIN(maxusers, USERS); + if (users) free(users); users = calloc(usercount, sizeof(struct tun_user)); for (i = 0; i < usercount; i++) { in_addr_t ip; @@ -72,13 +78,17 @@ init_users(in_addr_t my_ip, int netbits) snprintf(newip, sizeof(newip), "0.0.0.%d", i + skip + 1); ip = ipstart.s_addr + inet_addr(newip); } + if (debug >= 2) { + struct in_addr IP; + IP.s_addr = ip; + DEBUG(2, "User %d: IP %s", i, inet_ntoa(IP)); + } users[i].tun_ip = ip; net.s_addr = ip; - users[i].disabled = 0; - users[i].authenticated = 0; - users[i].authenticated_raw = 0; - users[i].active = 0; - /* Rest is reset on login ('V' packet) */ + + users[i].incoming = window_buffer_init(INFRAGBUF_LEN, 10, MAX_FRAGSIZE, WINDOW_RECVING); + users[i].outgoing = window_buffer_init(OUTFRAGBUF_LEN, 10, 100, WINDOW_SENDING); + /* Rest is reset on login ('V' packet) or already 0 */ } return usercount; @@ -95,75 +105,67 @@ users_get_first_ip() int find_user_by_ip(uint32_t ip) { - int ret; - int i; - - ret = -1; - for (i = 0; i < usercount; i++) { - if (users[i].active && - users[i].authenticated && - !users[i].disabled && - users[i].last_pkt + 60 > time(NULL) && - ip == users[i].tun_ip) { - ret = i; - break; + for (int i = 0; i < usercount; i++) { + if (user_active(i) && users[i].authenticated && ip == users[i].tun_ip) { + return i; } } - return ret; + return -1; +} + +int +user_sending(int user) +{ + return users[user].outgoing->numitems > 0; +} + +int +user_active(int i) +{ + return users[i].active && difftime(time(NULL), users[i].last_pkt) < 60; } int all_users_waiting_to_send() /* If this returns true, then reading from tun device is blocked. - So only return true when all clients have at least one packet in - the outpacket-queue, so that sending back-to-back is possible - without going through another select loop. -*/ + So only return true when all clients have insufficient space in + outgoing buffer, so that sending back-to-back is possible + without going through another select loop. */ { - time_t now; - int ret; - int i; - - ret = 1; - now = time(NULL); - for (i = 0; i < usercount; i++) { - if (users[i].active && !users[i].disabled && - users[i].last_pkt + 60 > now && - ((users[i].conn == CONN_RAW_UDP) || - ((users[i].conn == CONN_DNS_NULL) -#ifdef OUTPACKETQ_LEN - && users[i].outpacketq_filled < 1 -#else - && users[i].outpacket.len == 0 -#endif - ))) { - - ret = 0; - break; + int numactive = 0; + for (int i = 0; i < usercount; i++) { + if (user_active(i)) { + if (users[i].outgoing->length - users[i].outgoing->numitems > 8) + return 0; + numactive ++; } } - return ret; + + /* no users waiting if there are no users */ + if (numactive == 0) + return 0; + + return 1; } int find_available_user() { - int ret = -1; - int i; - for (i = 0; i < usercount; i++) { + for (int u = 0; u < usercount; u++) { /* Not used at all or not used in one minute */ - if ((!users[i].active || users[i].last_pkt + 60 < time(NULL)) && !users[i].disabled) { - users[i].active = 1; - users[i].authenticated = 0; - users[i].authenticated_raw = 0; - users[i].last_pkt = time(NULL); - users[i].fragsize = 4096; - users[i].conn = CONN_DNS_NULL; - ret = i; - break; + if (!user_active(u)) { + struct tun_user *user = &users[u]; + /* reset all stats */ + user->active = 1; + user->authenticated = 0; + user->authenticated_raw = 0; + user->last_pkt = time(NULL); + user->fragsize = MAX_FRAGSIZE; + user->conn = CONN_DNS_NULL; + return u; } } - return ret; + return -1; } void @@ -187,3 +189,56 @@ user_set_conn_type(int userid, enum connection c) users[userid].conn = c; } +/* This will not check that user has passed login challenge */ +int +check_user_and_ip(int userid, struct query *q) +{ + /* Note: duplicate in handle_raw_login() except IP-address check */ + + if (userid < 0 || userid >= created_users ) { + return 1; + } + if (!user_active(userid)) return 1; + + /* return early if IP checking is disabled */ + if (!check_ip) { + return 0; + } + + if (q->from.ss_family != users[userid].host.ss_family) { + return 1; + } + /* Check IPv4 */ + if (q->from.ss_family == AF_INET) { + struct sockaddr_in *expected, *received; + + expected = (struct sockaddr_in *) &(users[userid].host); + received = (struct sockaddr_in *) &(q->from); + return memcmp(&(expected->sin_addr), &(received->sin_addr), sizeof(struct in_addr)); + } + /* Check IPv6 */ + if (q->from.ss_family == AF_INET6) { + struct sockaddr_in6 *expected, *received; + + expected = (struct sockaddr_in6 *) &(users[userid].host); + received = (struct sockaddr_in6 *) &(q->from); + return memcmp(&(expected->sin6_addr), &(received->sin6_addr), sizeof(struct in6_addr)); + } + /* Unknown address family */ + return 1; +} + +int +check_authenticated_user_and_ip(int userid, struct query *q) +/* This checks that user has passed normal (non-raw) login challenge + * Returns 0 on success, 1 if user is not authenticated/IP is wrong */ +{ + int res = check_user_and_ip(userid, q); + if (res) + return res; + + if (!users[userid].authenticated) + return 1; + + return 0; +} diff --git a/src/user.h b/src/user.h index 7d2553a..1b19f38 100644 --- a/src/user.h +++ b/src/user.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2014 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,71 +19,47 @@ #ifndef __USER_H__ #define __USER_H__ +#include "window.h" +#include "server.h" + #define USERS 16 -#define OUTPACKETQ_LEN 4 /* Note: 16 users * 1 packet = 1MB */ -/* Undefine to have no queue for packets coming in from tun device, which may - lead to massive dropping in multi-user situations with high traffic. */ - -#define DNSCACHE_LEN 4 -/* Undefine to disable. Should be less than 18; also see comments in iodined.c */ - - -#define QMEMPING_LEN 30 -/* Max advisable: 64k/2 = 32000. Total mem usage: QMEMPING_LEN * USERS * 6 bytes */ - -#define QMEMDATA_LEN 15 -/* Max advisable: 36/2 = 18. Total mem usage: QMEMDATA_LEN * USERS * 6 bytes */ - struct tun_user { char id; int active; int authenticated; int authenticated_raw; - int disabled; time_t last_pkt; + struct timeval dns_timeout; int seed; in_addr_t tun_ip; struct sockaddr_storage host; socklen_t hostlen; - struct query q; - struct query q_sendrealsoon; - int q_sendrealsoon_new; - struct packet inpacket; - struct packet outpacket; - int outfragresent; + struct frag_buffer *incoming; + struct frag_buffer *outgoing; + int next_upstream_ack; struct encoder *encoder; char downenc; - int out_acked_seqno; - int out_acked_fragment; + int downenc_bits; + int down_compression; int fragsize; enum connection conn; int lazy; - unsigned char qmemping_cmc[QMEMPING_LEN * 4]; - unsigned short qmemping_type[QMEMPING_LEN]; - int qmemping_lastfilled; - unsigned char qmemdata_cmc[QMEMDATA_LEN * 4]; - unsigned short qmemdata_type[QMEMDATA_LEN]; - int qmemdata_lastfilled; -#ifdef OUTPACKETQ_LEN - struct packet outpacketq[OUTPACKETQ_LEN]; - int outpacketq_nexttouse; - int outpacketq_filled; -#endif -#ifdef DNSCACHE_LEN - struct query dnscache_q[DNSCACHE_LEN]; - char dnscache_answer[DNSCACHE_LEN][4096]; - int dnscache_answerlen[DNSCACHE_LEN]; - int dnscache_lastfilled; -#endif + struct qmem_buffer qmem; }; extern struct tun_user *users; +extern int created_users; + +int user_sending(int user); +int all_users_waiting_to_send(); +int user_active(int i); +int check_authenticated_user_and_ip(int userid, struct query *q); +int check_user_and_ip(int userid, struct query *q); int init_users(in_addr_t, int); const char* users_get_first_ip(); int find_user_by_ip(uint32_t); -int all_users_waiting_to_send(); int find_available_user(); void user_switch_codec(int userid, struct encoder *enc); void user_set_conn_type(int userid, enum connection c); diff --git a/src/util.c b/src/util.c index 5cbad72..3bd162c 100644 --- a/src/util.c +++ b/src/util.c @@ -18,6 +18,23 @@ #include #include "common.h" +time_t +timeval_to_ms(struct timeval *tv) +{ + time_t ms = tv->tv_sec * 1000; + ms += (tv->tv_usec + 500) / 1000; + return ms; +} + +struct timeval +ms_to_timeval(time_t ms) +{ + struct timeval tv; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms - tv.tv_sec * 1000) * 1000; + return tv; +} + char * get_resolvconf_addr() { @@ -43,7 +60,10 @@ get_resolvconf_addr() err(1, "/etc/resolv.conf"); while (feof(fp) == 0) { - fgets(buf, sizeof(buf), fp); + if (!fgets(buf, sizeof(buf), fp)) { + /* resolv.conf is empty (we got to EOF without reading anything yet */ + err(1, "/etc/resolv.conf is empty! Please specify a nameserver."); + } if (sscanf(buf, "nameserver %15s", addr) == 1) { rv = addr; diff --git a/src/util.h b/src/util.h index a0ee03b..e232531 100644 --- a/src/util.h +++ b/src/util.h @@ -18,7 +18,14 @@ #ifndef __UTIL_H__ #define __UTIL_H__ +#include +#include + char *get_resolvconf_addr(); void socket_setrtable(int fd, int rtable); +time_t timeval_to_ms(struct timeval *tv); + +struct timeval ms_to_timeval(time_t ms); + #endif diff --git a/src/version.h b/src/version.h index 5843a7a..440a7ba 100644 --- a/src/version.h +++ b/src/version.h @@ -20,7 +20,7 @@ /* This is the version of the network protocol It is usually equal to the latest iodine version number */ -#define PROTOCOL_VERSION 0x00000502 +#define PROTOCOL_VERSION 0x00000800 #endif /* _VERSION_H_ */ diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..b870b62 --- /dev/null +++ b/src/window.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#ifndef WINDOWS32 +#include +#endif +#include +#include + +#include "common.h" +#include "util.h" +#include "window.h" + +int window_debug = 0; + + +/* Window debugging macro */ +#ifdef DEBUG_BUILD +#define WDEBUG(...) if (window_debug) {\ + TIMEPRINT("[WINDOW-DEBUG] (%s:%d) ", __FILE__, __LINE__);\ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } +#else +#define WDEBUG(...) +#endif + +/* Window-specific macros */ +/* Gets index of fragment o fragments after window start */ +#define AFTER(w, o) ((w->window_start + o) % w->length) + +/* Check if fragment index a is within window_buffer *w */ +#define INWINDOW_INDEX(w, a) ((w->window_start < w->window_end) ? \ + (a >= w->window_start && a <= w->window_end) : \ + ((a >= w->window_start && a <= w->length - 1) || \ + (a >= 0 && a <= w->window_end))) + +/* Wrap index x to a value within the window buffer length */ +#define WRAP(x) ((x) % w->length) + + +struct frag_buffer * +window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir) +{ + struct frag_buffer *buf; + buf = calloc(1, sizeof(struct frag_buffer)); + if (!buf) { + errx(1, "Failed to allocate window buffer memory!"); + } + if (dir != WINDOW_RECVING && dir != WINDOW_SENDING) { + errx(1, "Invalid window direction!"); + } + if (fragsize > MAX_FRAGSIZE) { + errx(fragsize, "Fragsize too large! Please recompile with larger MAX_FRAGSIZE!"); + } + + buf->frags = calloc(length, sizeof(struct fragment)); + if (!buf->frags) { + errx(1, "Failed to allocate fragment buffer!"); + } + buf->length = length; + buf->windowsize = windowsize; + buf->maxfraglen = fragsize; + buf->window_end = AFTER(buf, windowsize); + buf->direction = dir; + buf->timeout.tv_sec = 5; + buf->timeout.tv_usec = 0; + + return buf; +} + +void +window_buffer_reset(struct frag_buffer *w) +{ + w->chunk_start = 0; + w->cur_seq_id = 0; + w->last_write = 0; + w->numitems = 0; + w->oos = 0; + w->resends = 0; + w->start_seq_id = 0; + w->window_start = 0; + w->window_end = AFTER(w, w->windowsize); +} + +void +window_buffer_resize(struct frag_buffer *w, size_t length) +{ + if (w->length == length) return; + if (w->numitems > 0) { + WDEBUG("Resizing window buffer with things still in it! This will cause problems!"); + } + if (w->frags) free(w->frags); + w->frags = calloc(length, sizeof(struct fragment)); + if (!w->frags) { + errx(1, "Failed to resize window buffer!"); + } + w->length = length; + window_buffer_reset(w); +} + +void +window_buffer_destroy(struct frag_buffer *w) +{ + if (!w) return; + if (w->frags) free(w->frags); + free(w); +} + +void +window_buffer_clear(struct frag_buffer *w) +{ + if (!w) return; + + memset(w->frags, 0, w->length * sizeof(struct fragment)); + window_buffer_reset(w); +} + +/* Returns number of available fragment slots (NOT BYTES) */ +size_t +window_buffer_available(struct frag_buffer *w) +{ + return w->length - w->numitems; +} + +/* Places a fragment in the window after the last one */ +int +window_append_fragment(struct frag_buffer *w, struct fragment *src) +{ + if (window_buffer_available(w) < 1) return 0; + memcpy(&w->frags[w->last_write], src, sizeof(struct fragment)); + w->last_write = WRAP(w->last_write + 1); + w->numitems ++; + return 1; +} + + +ssize_t +window_process_incoming_fragment(struct frag_buffer *w, struct fragment *f) +/* Handles fragment received from the sending side (RECV) + * Returns index of fragment in window or <0 if dropped + * The next ACK MUST be for this fragment */ +{ + /* Check if packet is in window */ + unsigned startid, endid, offset; + struct fragment *fd; + startid = w->start_seq_id; + endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; + offset = SEQ_OFFSET(startid, f->seqID); + + if (!INWINDOW_SEQ(startid, endid, f->seqID)) { + w->oos++; + if (offset > MIN(w->length - w->numitems, MAX_SEQ_ID / 2)) { + /* Only drop the fragment if it is ancient */ + WDEBUG("Dropping frag with seqID %u: not in window (%u-%u)", f->seqID, startid, endid); + return -1; + } else { + /* Save "new" fragments to avoid causing other end to advance + * when this fragment is ACK'd despite being dropped */ + WDEBUG("WARNING: Got future fragment (%u), offset %u from start %u (wsize %u).", + f->seqID, offset, startid, w->windowsize); + } + } + /* Place fragment into correct location in buffer */ + ssize_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); + WDEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", + f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); + + /* Check if fragment already received */ + fd = &w->frags[dest]; + if (fd->len != 0) { + WDEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); + if (f->seqID == fd->seqID) { + /* use retries as counter for dupes */ + fd->retries ++; + return -1; + } + } + + memcpy(fd, f, sizeof(struct fragment)); + w->numitems ++; + + fd->retries = 0; + fd->ack_other = -1; + + /* We assume this packet gets ACKed immediately on return of this function */ + fd->acks = 1; + + return dest; +} + +/* Perform wrapped iteration of statement with pos = (begin to end) wrapped at + * max, executing statement f for every value of pos. */ +#define ITER_FORWARD(begin, end, max, pos, f) { \ + if (end >= begin) \ + for (pos = begin; pos < end && pos < max; pos++) {f}\ + else {\ + for (pos = begin; pos < max; pos++) {f}\ + for (pos = 0; pos < end && pos < max; pos++) {f}\ + }\ + } + +/* Reassembles first complete sequence of fragments into data. (RECV) + * Returns length of data reassembled, or 0 if no data reassembled */ +size_t +window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression) +{ + size_t woffs, fraglen, datalen = 0; + uint8_t *dest; //, *fdata_start; + dest = data; + if (w->direction != WINDOW_RECVING) + return 0; + if (w->frags[w->chunk_start].start == 0 && w->numitems > 0) { + WDEBUG("chunk_start (%lu) pointing to non-start fragment (seq %u, len %lu)!", + w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); + return 0; + } + if (compression) *compression = 1; + + struct fragment *f; + size_t i; + unsigned curseq; + int end = 0; + curseq = w->frags[w->chunk_start].seqID; + for (i = 0; i < w->numitems; ++i) { + woffs = WRAP(w->chunk_start + i); + f = &w->frags[woffs]; + fraglen = f->len; + if (fraglen == 0 || f->seqID != curseq) { + WDEBUG("Missing next frag %u [%lu], got seq %u (%lu bytes) instead! Not reassembling!", + curseq, woffs, f->seqID, fraglen); + return 0; + } + + WDEBUG(" Fragment seq %u, data length %lu, data offset %lu, total len %lu, maxlen %lu", + f->seqID, fraglen, dest - data, datalen, maxlen); + memcpy(dest, f->data, MIN(fraglen, maxlen)); + dest += fraglen; + datalen += fraglen; + if (compression) { + *compression &= f->compressed & 1; + if (f->compressed != *compression) { + WDEBUG("Inconsistent compression flags in chunk. Will reassemble anyway!"); + } + } + if (fraglen > maxlen) { + WDEBUG("Data buffer too small! Reassembled %lu bytes.", datalen); + return 0; + } + + /* Move window along to avoid weird issues */ + window_tick(w); + + if (f->end == 1) { + WDEBUG("Found end of chunk! (seqID %u, chunk len %lu, datalen %lu)", f->seqID, i, datalen); + end = 1; + break; + } + + /* Move position counters and expected next seqID */ + maxlen -= fraglen; + curseq = (curseq + 1) % MAX_SEQ_ID; + } + + if (end == 0) { + /* no end of chunk found because the window buffer has no more frags + * meaning they haven't been received yet. */ + return 0; + } + + WDEBUG("Reassembled %lu bytes from %lu frags; %scompressed!", datalen, i + 1, *compression ? "" : "un"); + /* Clear all used fragments */ + size_t p; + ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, + memset(&w->frags[p], 0, sizeof(struct fragment)); + ); + w->chunk_start = WRAP(woffs + 1); + w->numitems -= i + 1; + return datalen; +} + +size_t +window_sending(struct frag_buffer *w, struct timeval *nextresend) +/* Returns number of fragments that can be sent immediately; effectively + the same as window_get_next_sending_fragment but without doing anything. + *nextresend is time before the next frag will be resent */ +{ + struct timeval age, now, oldest; + struct fragment *f; + size_t tosend = 0; + + oldest.tv_sec = 0; + oldest.tv_usec = 0; + + if (w->numitems == 0) + return 0; + + gettimeofday(&now, NULL); + + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[WRAP(w->window_start + i)]; + if (f->len == 0 || f->acks >= 1) continue; + + if (f->retries < 1 || f->lastsent.tv_sec == 0) { + /* Sending frag for first time + * Note: if retries==0 then lastsent MUST also be 0 */ + tosend++; + } else { + /* Frag has been sent before so lastsent is a valid timestamp */ + timersub(&now, &f->lastsent, &age); + + if (!timercmp(&age, &w->timeout, <)) { + /* ACK timeout: Frag will be resent */ + tosend++; + } else if (timercmp(&age, &oldest, >)) { + /* Hasn't timed out yet and is oldest so far */ + oldest = age; + } + } + } + + if (nextresend) { + /* nextresend = time before oldest fragment (not being sent now) + * will be re-sent = timeout - age */ + timersub(&w->timeout, &oldest, nextresend); + } + + return tosend; +} + +/* Returns next fragment to be sent or NULL if nothing (SEND) + * This also handles packet resends, timeouts etc. */ +struct fragment * +window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) +{ + struct timeval age, now; + struct fragment *f = NULL; + + if (*other_ack >= MAX_SEQ_ID || *other_ack < 0) + *other_ack = -1; + + gettimeofday(&now, NULL); + + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[WRAP(w->window_start + i)]; + if (f->acks >= 1 || f->len == 0) continue; + + timersub(&now, &f->lastsent, &age); + + if (f->retries >= 1 && !timercmp(&age, &w->timeout, <)) { + /* Resending fragment due to ACK timeout */ + WDEBUG("Retrying frag %u (%ld ms old/timeout %ld ms), retries: %u/total %u", + f->seqID, timeval_to_ms(&age), timeval_to_ms(&w->timeout), f->retries, w->resends); + w->resends ++; + goto found; + } else if (f->retries == 0 && f->len > 0) { + /* Fragment not sent */ + goto found; + } + } + if (f) + WDEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", + f->retries, f->seqID, f->len); + return NULL; + + found: + /* store other ACK into fragment for sending; ignore any previous values. + Don't resend ACKs because by the time we do, the other end will have + resent the corresponding fragment so may as well not cause trouble. */ + f->ack_other = *other_ack, *other_ack = -1; + f->start &= 1; + f->end &= 1; + f->retries++; + gettimeofday(&f->lastsent, NULL); + return f; +} + +/* Gets the seqid of next fragment to be ACK'd (RECV) */ +int +window_get_next_ack(struct frag_buffer *w) +{ + struct fragment *f; + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[WRAP(w->window_start + i)]; + if (f->len > 0 && f->acks <= 0) { + f->acks = 1; + return f->seqID; + } + } + return -1; +} + +/* Sets the fragment with seqid to be ACK'd (SEND) */ +void +window_ack(struct frag_buffer *w, int seqid) +{ + struct fragment *f; + if (seqid < 0 || seqid > MAX_SEQ_ID) return; + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[AFTER(w, i)]; + if (f->seqID == seqid && f->len > 0) { /* ACK first non-empty frag */ + if (f->acks > 0) + WDEBUG("DUPE ACK: %d ACKs for seqId %u", f->acks, seqid); + f->acks ++; + WDEBUG(" ACK frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); + break; + } + } +} + +/* Function to be called after all other processing has been done + * when anything happens (moves window etc) (SEND/RECV) */ +void +window_tick(struct frag_buffer *w) +{ + for (size_t i = 0; i < w->windowsize; i++) { + if (w->frags[w->window_start].acks >= 1) { +#ifdef DEBUG_BUILD + unsigned old_start_id = w->start_seq_id; +#endif + w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; + WDEBUG("moving window forwards; %lu-%lu (%u) to %lu-%lu (%u) len=%lu", + w->window_start, w->window_end, old_start_id, AFTER(w, 1), + AFTER(w, w->windowsize + 1), w->start_seq_id, w->length); + if (w->direction == WINDOW_SENDING) { + WDEBUG("Clearing old fragments in SENDING window."); + w->numitems --; /* Clear old fragments */ + memset(&w->frags[w->window_start], 0, sizeof(struct fragment)); + } + w->window_start = AFTER(w, 1); + + w->window_end = AFTER(w, w->windowsize); + } else break; + } +} + +/* Splits data into fragments and adds to the end of the window buffer for sending + * All fragment meta-data is created here (SEND) */ +int +window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int compressed) +{ + // Split data into thingies of <= fragsize + size_t n = ((len - 1) / w->maxfraglen) + 1; + if (!data || n == 0 || len == 0 || n > window_buffer_available(w)) { + WDEBUG("Failed to append fragment (buffer too small!)"); + return -1; + } + compressed &= 1; + size_t offset = 0; + struct fragment f; + WDEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); + for (size_t i = 0; i < n; i++) { + memset(&f, 0, sizeof(f)); + f.len = MIN(len - offset, w->maxfraglen); + memcpy(f.data, data + offset, f.len); + f.seqID = w->cur_seq_id; + f.start = (i == 0) ? 1 : 0; + f.end = (i == n - 1) ? 1 : 0; + f.compressed = compressed; + f.ack_other = -1; + window_append_fragment(w, &f); + w->cur_seq_id = (w->cur_seq_id + 1) % MAX_SEQ_ID; + WDEBUG(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); + offset += f.len; + } + return n; +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..6255ff1 --- /dev/null +++ b/src/window.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __WINDOW_H__ +#define __WINDOW_H__ + +/* Hard-coded sequence ID and fragment size limits + * These should match the limitations of the protocol. */ +#define MAX_SEQ_ID 256 +#define MAX_FRAGSIZE 4096 + +#define WINDOW_SENDING 1 +#define WINDOW_RECVING 0 + +/* Generic ring-buffer macros */ + +/* Distance (going forwards) between a and b in window of length l */ +#define DISTF(l, a, b) (((a > b) ? a-b : l-a+b-1) % l) + +/* Distance backwards between a and b in window of length l */ +#define DISTB(l, a, b) (((a < b) ? l-b+a-1 : a-b) % l) + +/* Check if sequence ID a is within sequence range start to end */ +#define INWINDOW_SEQ(start, end, a) ((start < end) ? \ + (a >= start && a <= end) : \ + ((a >= start && a <= MAX_SEQ_ID - 1) || \ + (a <= end))) + +/* Find the wrapped offset between sequence IDs start and a + * Note: the maximum possible offset is MAX_SEQ_ID - 1 */ +#define SEQ_OFFSET(start, a) ((a >= start) ? a - start : MAX_SEQ_ID - start + a) + +struct fragment { + size_t len; /* Length of fragment data (0 if fragment unused) */ + unsigned seqID; /* fragment sequence ID */ + int ack_other; /* other way ACK seqID (>=0) or unset (<0) */ + int compressed; /* compression flag */ + uint8_t start; /* start of chunk flag */ + uint8_t end; /* end of chunk flag */ + uint8_t data[MAX_FRAGSIZE]; /* fragment data */ + unsigned retries; /* number of times has been sent or dupes recv'd */ + struct timeval lastsent; /* timestamp of most recent send attempt */ + int acks; /* number of times packet has been ack'd */ +}; + +struct frag_buffer { + struct fragment *frags; /* pointer to array of data fragments */ + unsigned windowsize; /* Max number of fragments in flight */ + unsigned maxfraglen; /* Max outgoing fragment data size */ + size_t length; /* Length of buffer */ + size_t numitems; /* number of non-empty fragments stored in buffer */ + size_t window_start; /* Start of window (index) */ + size_t window_end; /* End of window (index) */ + size_t last_write; /* Last fragment appended (index) */ + size_t chunk_start; /* Start of current chunk of fragments (index) */ + unsigned cur_seq_id; /* Next unused sequence ID */ + unsigned start_seq_id; /* Start of window sequence ID */ + unsigned resends; /* number of fragments resent or number of dupes received */ + unsigned oos; /* Number of out-of-sequence fragments received */ + int direction; /* WINDOW_SENDING or WINDOW_RECVING */ + struct timeval timeout; /* Fragment ACK timeout before resend */ +}; + +extern int window_debug; + + + +/* Window function definitions. */ + + +/* Window buffer creation and housekeeping */ +struct frag_buffer *window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir); +void window_buffer_resize(struct frag_buffer *w, size_t length); +void window_buffer_destroy(struct frag_buffer *w); + +/* Clears fragments and resets window stats */ +void window_buffer_clear(struct frag_buffer *w); + +/* Resets window stats without clearing fragments */ +void window_buffer_reset(struct frag_buffer *w); + +/* Returns number of available fragment slots (NOT BYTES) */ +size_t window_buffer_available(struct frag_buffer *w); + +/* Places a fragment in the window after the last one */ +int window_append_fragment(struct frag_buffer *w, struct fragment *src); + +/* Handles fragment received from the sending side (RECV) */ +ssize_t window_process_incoming_fragment(struct frag_buffer *w, struct fragment *f); + +/* Reassembles first complete sequence of fragments into data. (RECV) + * Returns length of data reassembled, or 0 if no data reassembled */ +size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression); + +/* Returns number of fragments to be sent */ +size_t window_sending(struct frag_buffer *w, struct timeval *); + +/* Returns next fragment to be sent or NULL if nothing (SEND) */ +struct fragment *window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack); + +/* Gets the seqid of next fragment to be ACK'd (RECV) */ +int window_get_next_ack(struct frag_buffer *w); + +/* Sets the fragment with seqid to be ACK'd (SEND) */ +void window_ack(struct frag_buffer *w, int seqid); + +/* To be called after all other processing has been done + * when anything happens (moves window etc) (SEND/RECV) */ +void window_tick(struct frag_buffer *w); + +/* Splits data into fragments and adds to the end of the window buffer for sending + * All fragment meta-data is created here (SEND) */ +int window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int compressed); + +#endif /* __WINDOW_H__ */ diff --git a/src/windows.h b/src/windows.h index 96288d1..65fb619 100644 --- a/src/windows.h +++ b/src/windows.h @@ -20,11 +20,13 @@ typedef unsigned int in_addr_t; +#include #include #include #include #include #include +#include /* Missing from the mingw headers */ #ifndef DNS_TYPE_SRV @@ -74,7 +76,7 @@ typedef struct { } HEADER; struct ip - { +{ unsigned int ip_hl:4; /* header length */ unsigned int ip_v:4; /* version */ u_char ip_tos; /* type of service */ @@ -89,7 +91,99 @@ struct ip u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ - }; +}; + +#ifdef WINDOWS32 +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 +/* dummy syslog definition to prevent compile errors */ +#define syslog(...) /* TODO: implement (add to event log), move to common.c */ +#endif + +/* windows gettimeofday from https://gist.github.com/ugovaretto/5875385 */ +#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) + #define DELTA_EPOCH_IN_MICROSECS 116444736000000000Ui64 +#else + #define DELTA_EPOCH_IN_MICROSECS 116444736000000000ULL +#endif + +/* Convenience macros for operations on timevals. + NOTE: `timercmp' does not work for >= or <=. */ +#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) + +#ifndef timerclear +#define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#endif +#ifndef timercmp +#define timercmp(a, b, CMP) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_usec CMP (b)->tv_usec) : \ + ((a)->tv_sec CMP (b)->tv_sec)) +#endif +#define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) \ + { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) + +#if 0 +inline int +gettimeofday(struct timeval *tv, struct timezone *tz) +{ + FILETIME ft; + unsigned __int64 tmpres = 0; + int tzflag = 0; + + if (NULL != tv) + { + GetSystemTimeAsFileTime(&ft); + + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + + /* convert into microseconds */ + tmpres /= 10; + /* converting file time to unix epoch */ + tmpres -= DELTA_EPOCH_IN_MICROSECS; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + + if (NULL != tz) + { + if (!tzflag) + { + _tzset(); + tzflag++; + } + tz->tz_minuteswest = _timezone / 60; + tz->tz_dsttime = _daylight; + } + + return 0; +} +#endif DWORD WINAPI tun_reader(LPVOID arg); struct tun_data { diff --git a/tests/Makefile b/tests/Makefile index 03eed98..7977947 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ TEST = test -OBJS = test.o base32.o base64.o common.o read.o dns.o encoding.o login.o user.o fw_query.o -SRCOBJS = ../src/base32.o ../src/base64.o ../src/common.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o ../src/fw_query.o +OBJS = test.o base32.o base64.o common.o read.o dns.o encoding.o login.o user.o fw_query.o window.o +SRCOBJS = ../src/base32.o ../src/base64.o ../src/window.o ../src/common.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o ../src/fw_query.o ../src/util.o OS = `uname | tr "a-z" "A-Z"` diff --git a/tests/base32.c b/tests/base32.c index 4b460e2..9c9fdff 100644 --- a/tests/base32.c +++ b/tests/base32.c @@ -49,7 +49,7 @@ START_TEST(test_base32_encode) b32 = get_base32_encoder(); len = sizeof(buf); - val = b32->encode(buf, &len, testpairs[_i].a, strlen(testpairs[_i].a)); + val = b32->encode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].a, strlen(testpairs[_i].a)); fail_unless(val == strlen(testpairs[_i].b)); fail_unless(strcmp(buf, testpairs[_i].b) == 0, @@ -67,7 +67,7 @@ START_TEST(test_base32_decode) b32 = get_base32_encoder(); len = sizeof(buf); - val = b32->decode(buf, &len, testpairs[_i].b, strlen(testpairs[_i].b)); + val = b32->decode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].b, strlen(testpairs[_i].b)); fail_unless(val == strlen(testpairs[_i].a)); fail_unless(strcmp(buf, testpairs[_i].a) == 0, @@ -110,7 +110,7 @@ START_TEST(test_base32_blksize) } rawbuf[i] = 0; - val = b32->encode(encbuf, &enclen, rawbuf, rawlen); + val = b32->encode((uint8_t *)encbuf, &enclen, (uint8_t *)rawbuf, rawlen); fail_unless(rawlen == 5, "raw length was %d not 5", rawlen); fail_unless(enclen == 5, "encoded %d bytes, not 5", enclen); @@ -119,7 +119,7 @@ START_TEST(test_base32_blksize) memset(rawbuf, 0, rawlen + 16); enclen = val; - val = b32->decode(rawbuf, &rawlen, encbuf, enclen); + val = b32->decode((uint8_t *)rawbuf, &rawlen, (uint8_t *)encbuf, enclen); fail_unless(rawlen == 5, "raw length was %d not 5", rawlen); fail_unless(val == 5, "val was not 5 but %d", val); diff --git a/tests/base64.c b/tests/base64.c index bcefc4c..e0d828f 100644 --- a/tests/base64.c +++ b/tests/base64.c @@ -75,7 +75,7 @@ START_TEST(test_base64_encode) b64 = get_base64_encoder(); len = sizeof(buf); - val = b64->encode(buf, &len, testpairs[_i].a, strlen(testpairs[_i].a)); + val = b64->encode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].a, strlen(testpairs[_i].a)); fail_unless(val == strlen(testpairs[_i].b)); fail_unless(strcmp(buf, testpairs[_i].b) == 0, @@ -93,7 +93,7 @@ START_TEST(test_base64_decode) b64 = get_base64_encoder(); len = sizeof(buf); - val = b64->decode(buf, &len, testpairs[_i].b, strlen(testpairs[_i].b)); + val = b64->decode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].b, strlen(testpairs[_i].b)); fail_unless(val == strlen(testpairs[_i].a)); fail_unless(strcmp(buf, testpairs[_i].a) == 0, @@ -124,7 +124,7 @@ START_TEST(test_base64_blksize) } rawbuf[i] = 0; - val = b64->encode(encbuf, &enclen, rawbuf, rawlen); + val = b64->encode((uint8_t *)encbuf, &enclen, (uint8_t *)rawbuf, rawlen); fail_unless(rawlen == 3, "raw length was %d not 3", rawlen); fail_unless(enclen == 3, "encoded %d bytes, not 3", enclen); @@ -133,7 +133,7 @@ START_TEST(test_base64_blksize) memset(rawbuf, 0, rawlen + 16); enclen = val; - val = b64->decode(rawbuf, &rawlen, encbuf, enclen); + val = b64->decode((uint8_t *)rawbuf, &rawlen, (uint8_t *)encbuf, enclen); fail_unless(rawlen == 3, "raw length was %d not 3", rawlen); fail_unless(val == 3); diff --git a/tests/dns.c b/tests/dns.c index 1f484ab..206df1f 100644 --- a/tests/dns.c +++ b/tests/dns.c @@ -86,7 +86,7 @@ START_TEST(test_encode_query) enc = get_base32_encoder(); *d++ = 'A'; - enc->encode(d, &enclen, innerData, strlen(innerData)); + enc->encode((uint8_t *)d, &enclen, (uint8_t *)innerData, strlen(innerData)); d = resolv + strlen(resolv); if (*d != '.') { *d++ = '.'; @@ -123,7 +123,7 @@ START_TEST(test_decode_query) dns_decode(buf, sizeof(buf), &q, QR_QUERY, query_packet, len); domain = strstr(q.name, topdomain); len = sizeof(buf); - unpack_data(buf, len, &(q.name[1]), (int) (domain - q.name) - 1, enc); + unpack_data((uint8_t *)buf, len, (uint8_t *)(q.name + 1), (int) (domain - q.name) - 1, enc); fail_unless(strncmp(buf, innerData, strlen(innerData)) == 0, "Did not extract expected host: '%s'", buf); fail_unless(strlen(buf) == strlen(innerData), "Bad host length: %d, expected %d: '%s'", strlen(buf), strlen(innerData), buf); diff --git a/tests/encoding.c b/tests/encoding.c index 0d5f358..e736406 100644 --- a/tests/encoding.c +++ b/tests/encoding.c @@ -32,13 +32,13 @@ static struct tuple char *a; char *b; } dottests[] = { - { "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaa"}, - { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."}, - { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, - { "abc123", "abc123" }, + { "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaa"}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaa"}, + { "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, { NULL, NULL } }; @@ -50,7 +50,7 @@ START_TEST(test_inline_dotify) memset(temp, 0, sizeof(temp)); strcpy(temp, dottests[_i].a); b = temp; - inline_dotify(b, sizeof(temp)); + inline_dotify((uint8_t *)b, sizeof(temp)); fail_unless(strcmp(dottests[_i].b, temp) == 0, "'%s' != '%s'", temp, dottests[_i].b); @@ -65,7 +65,7 @@ START_TEST(test_inline_undotify) memset(temp, 0, sizeof(temp)); strcpy(temp, dottests[_i].b); b = temp; - inline_undotify(b, sizeof(temp)); + inline_undotify((uint8_t *)b, sizeof(temp)); fail_unless(strcmp(dottests[_i].a, temp) == 0, "'%s' != '%s'", temp, dottests[_i].a); @@ -76,7 +76,7 @@ START_TEST(test_build_hostname) { char data[256]; char buf[1024]; - char *topdomain = "a.c"; + char *topdomain = "iodine.test.example.com"; int buflen; int i; @@ -86,11 +86,16 @@ START_TEST(test_build_hostname) buflen = sizeof(buf); + for (int j = 0; j < 10; j++) /* dummy header length */ for (i = 1; i < sizeof(data); i++) { - int len = build_hostname(buf, buflen, data, i, topdomain, get_base32_encoder(), sizeof(buf)); + buf[j] = j + 'A'; + int len = build_hostname((uint8_t *)buf, buflen, (uint8_t *)data, i, topdomain, get_base32_encoder(), buflen, j); fail_if(len > i); + fail_if((strstr(buf, ".") - buf) > 63, "First label in encoded hostname >63 bytes!"); fail_if(strstr(buf, ".."), "Found double dots when encoding data len %d! buf: %s", i, buf); + fail_if(!strstr(buf, topdomain), "Didn't find topdomain in hostname!"); + fail_if(buf[j] == j, "Header has been changed during encode hostname!"); } } END_TEST diff --git a/tests/test.c b/tests/test.c index eda792b..73c04b5 100644 --- a/tests/test.c +++ b/tests/test.c @@ -23,6 +23,8 @@ #include "test.h" +int check_ip = 0; + int main() { @@ -60,6 +62,9 @@ main() test = test_fw_query_create_tests(); suite_add_tcase(iodine, test); + test = test_window_create_tests(); + suite_add_tcase(iodine, test); + runner = srunner_create(iodine); srunner_run_all(runner, CK_NORMAL); failed = srunner_ntests_failed(runner); diff --git a/tests/test.h b/tests/test.h index d3f7985..f1cc5fd 100644 --- a/tests/test.h +++ b/tests/test.h @@ -27,6 +27,7 @@ TCase *test_read_create_tests(); TCase *test_login_create_tests(); TCase *test_user_create_tests(); TCase *test_fw_query_create_tests(); +TCase *test_window_create_tests(); char *va_str(const char *, ...); diff --git a/tests/user.c b/tests/user.c index bdc189e..d8118fe 100644 --- a/tests/user.c +++ b/tests/user.c @@ -28,6 +28,8 @@ #include "user.h" #include "test.h" +int debug = 0; + START_TEST(test_init_users) { in_addr_t ip; @@ -39,11 +41,9 @@ START_TEST(test_init_users) count = init_users(ip, 27); for (i = 0; i < count; i++) { fail_unless(users[i].id == i); - fail_unless(users[i].q.id == 0); - fail_unless(users[i].inpacket.len == 0); - fail_unless(users[i].outpacket.len == 0); snprintf(givenip, sizeof(givenip), "127.0.0.%d", i + 2); fail_unless(users[i].tun_ip == inet_addr(givenip)); + fail_if(user_active(i), "user_active true for new users"); } } END_TEST @@ -87,25 +87,17 @@ START_TEST(test_all_users_waiting_to_send) ip = inet_addr("127.0.0.1"); init_users(ip, 27); - fail_unless(all_users_waiting_to_send() == 1); + fail_unless(all_users_waiting_to_send() == 0, "empty user list all waiting to send"); users[0].conn = CONN_DNS_NULL; users[0].active = 1; - fail_unless(all_users_waiting_to_send() == 1); + fail_unless(all_users_waiting_to_send() == 0, "single user with empty buffer waiting to send"); users[0].last_pkt = time(NULL); - users[0].outpacket.len = 0; - fail_unless(all_users_waiting_to_send() == 0); + fail_unless(all_users_waiting_to_send() == 0, "single active user with empty buffer waiting to send"); -#ifdef OUTPACKETQ_LEN - users[0].outpacketq_filled = 1; -#else - users[0].outpacket.len = 44; -#endif - - fail_unless(all_users_waiting_to_send() == 1); } END_TEST diff --git a/tests/window.c b/tests/window.c new file mode 100644 index 0000000..3e896a6 --- /dev/null +++ b/tests/window.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "window.h" +#include "test.h" + +struct frag_buffer *in, *out; +char origdata[1000] = ""; + +START_TEST(test_window_everything) +{ + in = window_buffer_init(1000, 10, 5, WINDOW_RECVING); + out = window_buffer_init(1000, 10, 5, WINDOW_SENDING); + for (unsigned i = 0; i < 20; i++) { + char c[100] = "0ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()-=`';\\|][{}/?~"; + c[0] += i; + window_add_outgoing_data(out, (uint8_t *)c, i + 1, 0); + strncat(origdata, c, i + 1); + //warnx(" OUT: %u available, current seq %u, new seq %u\n", window_buffer_available(out), i, out->cur_seq_id); + } +// printf("Original data: '%s' (%lu)\n", origdata, strlen(origdata)); +// warnx("Added data, fragmented into %lu frags, next seq %u.", out->numitems, out->cur_seq_id); + // "send" data + int a = -1; + for (; out->numitems > 0;) { + struct fragment *f = window_get_next_sending_fragment(out, &a); + if (!f) { +// warnx("Nothing to send."); + continue; + } + fail_if(window_process_incoming_fragment(in, f) < 0, "Dropped fragment!"); +// warnx("Received fragment with seqid %u, remaining space %lu.", f->seqID, window_buffer_available(in)); + window_tick(in); + window_ack(out, f->seqID); + window_tick(out); + fail_if(out->start_seq_id != in->start_seq_id, "in/out windows have different start IDs!"); + } +// warnx("Added %lu fragments, reassembling into data.", in->numitems); + uint8_t data[100]; + uint8_t newdata[1000]; + memset(newdata, 0, 1000); + unsigned i; + int c; + for (i = 0; i < 50; i++) { + memset(data, 0, 100); + size_t len = window_reassemble_data(in, data, 100, &c); + fail_if(c != 0, "Compression flag weird"); +// printf("Reassembled %lu bytes, num frags %lu: '", len, in->numitems); +// for (unsigned i = 0; i < len; i++) { +// printf("%c", data[i]); +// } +// printf("'\n"); + strncat((char *)newdata, (char *)data, len); + if (in->numitems <= 0) break; + } +// printf("New data: '%s' (%lu)\n", newdata, strlen((char *)newdata)); + fail_if(strcmp((char *)newdata, origdata), "Reassembled data didn't match original data."); +} +END_TEST + + +TCase * +test_window_create_tests() +{ + TCase *tc; + + tc = tcase_create("Windowing"); + tcase_add_test(tc, test_window_everything); + + return tc; +}