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..b0e135f --- /dev/null +++ b/doc/proto_00000800.txt @@ -0,0 +1,361 @@ +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 + 1 byte userid char (hex) + Rest encoded with base32: + 1 byte flags: (least to most significant bits) + 0: connect to remote TCP port (data pipe/ProxyCommand mode) + 1: use non-localhost remote IP + 2: remote IP is IPv6 + 3: use TCP-over-tun optimisation (drop extra packets) + 4: check forward connected status + 5-8: unused + 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) + 2 bytes remote TCP port (big endian) + (TCP port appears only when flags bit 0 is set) + 4-16 bytes remote IP address (4 bytes if IPv4 or 16 for IPv6) + (IP address included only if flags bits 0, 1 are set) + 2 bytes CMC +Server replies: + LNAK means either auth or options not accepted + flag [-x.x.x.x-y.y.y.y-mtu-netmask]|[error message] means accepted + (server ip, client ip, mtu, netmask bits) + flag can be one of: + I: Login success (followed by IP addresses in [...]) + C: TCP forward connected + W: TCP forward connection waiting + E: TCP connection error - followed by human readable message string +If the requested TCP forwarding options are not accepted by the server, the +response is simply LNAK. +If TCP forwarding is requested, server opens a connection to the specified host +and IP in the background and responds with a flag corresponding to the current +connection status. +The client repeats login requests with the check flag (bit 4) set to poll TCP +connection status, not resending the remote host address or setting any other +flags. Once the server responds with 'C' or 'E', the client either continues +the handshake or prints the error message and exits. + + +IP Request: (for where to try raw login) +Client sends: + First byte i or I + 1 byte userid char (hex) + CMC as 4 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 + 1 byte userid char (hex) + rest encoded in base32: + 1 byte meaning number of bits per encoded byte in new codec: + 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) + 2 bytes CMC +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 + + +Set Options: +Client sends: + First byte o or O + 1 byte userid char (hex) + rest encoded in base32: + 1 byte option flags and 2 bytes CMC: + 0 1 - 3 + +76543210+---+ + |0TSUVRCL|CMC| + +--------+---+ +Server sends: + Full name of encoding type used if successful (case insensitive). + 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 flags: + T: Downstream encoding Base32, for TXT/CNAME/A/MX (default) + S: Downstream encoding Base64, for TXT/CNAME/A/MX + U: Downstream encoding Base64u, for TXT/CNAME/A/MX + V: Downstream encoding Base128, for TXT/CNAME/A/MX + R: Downstream encoding Raw, for PRIVATE/TXT/NULL (assumed for + PRIVATE and NULL) + C: Downstream compression enabled (compressed before encoding) + L: Lazy mode enabled, 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. + 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 the encoding options; client must always use the + downstream encoding type indicated in every downstream DNS packet. + + +Probe downstream fragment size: +Client sends: + First byte r or R + 1 byte userid char (hex) + 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 + 1 byte userid char (hex) + Rest encoded with base32: + 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 |0EIPACFL|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 +E = TCP Forward error (data following is text string reason) +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. + +If the TCP forward error (E) flag is set, the TCP connection at the server is +closed and the client sends EOF to stdout and exits. + +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 + 1 byte userid char (hex) + Rest encoded with Base32: + 0 1...7 8 - 9 + +--------+---+76543210+---+ + |Dn SeqID|...|00DWTANR|CMC| + +--------+---+--------+---+ + 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: + D = disconnect remote TCP forward (client should then exit) + 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. + +If the bit corresponding to changing a particular value (ie. window timeout) is +not set, the value should be random. (note: this is disabled in debug mode). + +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 faster since the data is not encoded and transferred +on top of the DNS protocol. 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/Android.mk b/src/Android.mk index 81136e5..cfbad40 100644 --- a/src/Android.mk +++ b/src/Android.mk @@ -16,7 +16,7 @@ HEAD_COMMIT = `git rev-parse --short HEAD` include $(CLEAR_VARS) LOCAL_MODULE := iodine -LOCAL_SRC_FILES := tun.c dns.c read.c encoding.c login.c base32.c base64.c base64u.c base128.c md5.c common.c iodine.c client.c util.c +LOCAL_SRC_FILES := tun.c dns.c read.c encoding.c login.c base32.c base64.c base64u.c base128.c md5.c common.c iodine.c client.c window.c util.c LOCAL_CFLAGS := -c -DANDROID -DLINUX -DIFCONFIGPATH=\"/system/bin/\" -Wall -DGITREVISION=\"$(HEAD_COMMIT)\" LOCAL_LDLIBS := -lz diff --git a/src/Makefile b/src/Makefile index 04cef2e..7bfe8d4 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) @@ -35,11 +44,11 @@ base64u.o client.o iodined.o: base64u.h base64u.c: base64.c @echo Making $@ @echo '/* No use in editing, produced by Makefile! */' > $@ - @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/\([Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ base64u.h: base64.h @echo Making $@ @echo '/* No use in editing, produced by Makefile! */' > $@ - @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/\([Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ clean: @echo "Cleaning src/" diff --git a/src/base128.c b/src/base128.c index be7ba86..7255c0f 100644 --- a/src/base128.c +++ b/src/base128.c @@ -42,23 +42,25 @@ * 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 = +struct encoder base128_encoder = { "Base128", base128_encode, @@ -66,9 +68,13 @@ 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 *b128 = &base128_encoder; + struct encoder *get_base128_encoder() { @@ -81,18 +87,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 +127,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 +139,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 +219,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 +233,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 +245,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/base128.h b/src/base128.h index f4c55f6..9235e62 100644 --- a/src/base128.h +++ b/src/base128.h @@ -17,6 +17,9 @@ #ifndef __BASE128_H__ #define __BASE128_H__ +extern struct encoder base128_encoder; +extern struct encoder *b128; + struct encoder *get_base128_encoder(void); #endif diff --git a/src/base32.c b/src/base32.c index a058d38..86191ac 100644 --- a/src/base32.c +++ b/src/base32.c @@ -33,13 +33,16 @@ 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 = + +struct encoder base32_encoder = { "Base32", base32_encode, @@ -47,9 +50,13 @@ 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 *b32 = &base32_encoder; + struct encoder *get_base32_encoder() { @@ -62,18 +69,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 +125,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 +137,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 +215,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 +229,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/base32.h b/src/base32.h index 83ba784..3676564 100644 --- a/src/base32.h +++ b/src/base32.h @@ -18,6 +18,9 @@ #ifndef __BASE32_H__ #define __BASE32_H__ +extern struct encoder base32_encoder; +extern struct encoder *b32; + struct encoder *get_base32_encoder(void); int b32_5to8(int); diff --git a/src/base64.c b/src/base64.c index 7834731..570120b 100644 --- a/src/base64.c +++ b/src/base64.c @@ -33,13 +33,15 @@ 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 = +struct encoder base64_encoder = { "Base64", base64_encode, @@ -47,9 +49,13 @@ 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 *b64 = &base64_encoder; + struct encoder *get_base64_encoder() { @@ -62,18 +68,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 +108,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 +120,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 +168,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 +182,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/base64.h b/src/base64.h index 8ce4742..6ed5aaa 100644 --- a/src/base64.h +++ b/src/base64.h @@ -18,6 +18,9 @@ #ifndef __BASE64_H__ #define __BASE64_H__ +extern struct encoder base64_encoder; +extern struct encoder *b64; + struct encoder *get_base64_encoder(void); #endif diff --git a/src/client.c b/src/client.c index 38b573b..4ca5f7d 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,10 +25,11 @@ #include #include #include -#include #include #include +#include #include +#include #ifdef WINDOWS32 #include "windows.h" @@ -55,139 +57,28 @@ #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); - -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; -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; - -/* 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; -/* Base64 etc encoders for replies */ -static struct encoder *b64; -static struct encoder *b64u; -static struct encoder *b128; - -/* The encoder used for data packets - * Defaults to Base32, can be changed after handshake */ -static struct encoder *dataenc; - -/* The encoder to use for downstream data */ -static char downenc = ' '; - -/* set query type to send */ -static unsigned short do_qtype = T_UNSET; - -/* My connection mode */ -static enum connection conn; - -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; - -void -client_init() -{ - running = 1; - b32 = get_base32_encoder(); - b64 = get_base64_encoder(); - b64u = get_base64u_encoder(); - b128 = get_base128_encoder(); - dataenc = get_base32_encoder(); - rand_seed = ((unsigned int) rand()) & 0xFFFF; - send_ping_soon = 1; /* send ping immediately after startup */ - conn = CONN_DNS_NULL; - - chunkid = ((unsigned int) rand()) & 0xFFFF; - chunkid_prev = 0; - chunkid_prev2 = 0; - - outpkt.len = 0; - outpkt.seqno = 0; - outpkt.fragment = 0; - outchunkresent = 0; - inpkt.len = 0; - inpkt.seqno = 0; - inpkt.fragment = 0; -} - -void -client_stop() -{ - running = 0; -} - -enum connection -client_get_conn() -{ - return conn; -} - -void -client_set_nameserver(struct sockaddr_storage *addr, int addrlen) -{ - memcpy(&nameserv, addr, addrlen); - nameserv_len = addrlen; -} - -void -client_set_topdomain(const char *cp) -{ - topdomain = cp; -} - -void -client_set_password(const char *cp) -{ - password = cp; -} - int client_set_qtype(char *qtype) { if (!strcasecmp(qtype, "NULL")) - do_qtype = T_NULL; + this.do_qtype = T_NULL; else if (!strcasecmp(qtype, "PRIVATE")) - do_qtype = T_PRIVATE; + this.do_qtype = T_PRIVATE; else if (!strcasecmp(qtype, "CNAME")) - do_qtype = T_CNAME; + this.do_qtype = T_CNAME; else if (!strcasecmp(qtype, "A")) - do_qtype = T_A; + this.do_qtype = T_A; else if (!strcasecmp(qtype, "MX")) - do_qtype = T_MX; + this.do_qtype = T_MX; else if (!strcasecmp(qtype, "SRV")) - do_qtype = T_SRV; + this.do_qtype = T_SRV; else if (!strcasecmp(qtype, "TXT")) - do_qtype = T_TXT; - return (do_qtype == T_UNSET); + this.do_qtype = T_TXT; + return (this.do_qtype == T_UNSET); } char * @@ -195,120 +86,320 @@ client_get_qtype() { char *c = "UNDEFINED"; - if (do_qtype == T_NULL) c = "NULL"; - else if (do_qtype == T_PRIVATE) c = "PRIVATE"; - else if (do_qtype == T_CNAME) c = "CNAME"; - else if (do_qtype == T_A) c = "A"; - else if (do_qtype == T_MX) c = "MX"; - else if (do_qtype == T_SRV) c = "SRV"; - else if (do_qtype == T_TXT) c = "TXT"; + if (this.do_qtype == T_NULL) c = "NULL"; + else if (this.do_qtype == T_PRIVATE) c = "PRIVATE"; + else if (this.do_qtype == T_CNAME) c = "CNAME"; + else if (this.do_qtype == T_A) c = "A"; + else if (this.do_qtype == T_MX) c = "MX"; + else if (this.do_qtype == T_SRV) c = "SRV"; + else if (this.do_qtype == T_TXT) c = "TXT"; return c; } -void -client_set_downenc(char *encoding) +char +parse_encoding(char *encoding) { + char enc_char = 0; if (!strcasecmp(encoding, "base32")) - downenc = 'T'; + enc_char = 'T'; else if (!strcasecmp(encoding, "base64")) - downenc = 'S'; + enc_char = 'S'; else if (!strcasecmp(encoding, "base64u")) - downenc = 'U'; + enc_char = 'U'; else if (!strcasecmp(encoding, "base128")) - downenc = 'V'; + enc_char = 'V'; else if (!strcasecmp(encoding, "raw")) - downenc = 'R'; + enc_char = 'R'; + return enc_char; } void -client_set_selecttimeout(int select_timeout) +client_set_hostname_maxlen(size_t i) { - selecttimeout = select_timeout; -} - -void -client_set_lazymode(int lazy_mode) -{ - lazymode = lazy_mode; -} - -void -client_set_hostname_maxlen(int i) -{ - if (i <= 0xFF) - hostname_maxlen = i; + if (i <= 0xFF && i != this.hostname_maxlen) { + this.hostname_maxlen = i; + this.maxfragsize_up = get_raw_length_from_dns(this.hostname_maxlen - UPSTREAM_HDR, this.dataenc, this.topdomain); + if (this.outbuf) + this.outbuf->maxfraglen = this.maxfragsize_up; + } } const char * client_get_raw_addr() { - return format_addr(&raw_serv, raw_serv_len); + return format_addr(&this.raw_serv, this.raw_serv_len); +} + +void +client_rotate_nameserver() +{ + this.current_nameserver ++; + if (this.current_nameserver >= this.nameserv_addrs_len) + this.current_nameserver = 0; +} + +void +immediate_mode_defaults() +{ + this.send_interval_ms = MIN(this.rtt_total_ms / this.num_immediate, 1000); + this.max_timeout_ms = MAX(4 * this.rtt_total_ms / this.num_immediate, 5000); + this.server_timeout_ms = 0; +} + +/* Client-side query tracking for lazy mode */ + +/* Handy macro for printing this.stats with messages */ +#ifdef DEBUG_BUILD +#define QTRACK_DEBUG(l, ...) \ + if (this.debug >= l) {\ + TIMEPRINT("[QTRACK (%" L "u/%" L "u), ? %" L "u, TO %" L "u, S %" L "u/%" L "u] ", this.num_pending, PENDING_QUERIES_LENGTH, \ + this.num_untracked, this.num_timeouts, window_sending(this.outbuf, NULL), this.outbuf->numitems); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } +#else +#define QTRACK_DEBUG(...) +#endif + +static int +update_server_timeout(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 = (this.num_immediate == 0) ? 1 : this.rtt_total_ms / this.num_immediate; + if (rtt_ms >= this.max_timeout_ms && this.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", this.max_timeout_ms, rtt_ms); + } else { + /* bump up target timeout */ + this.max_timeout_ms = rtt_ms + 1000; + this.server_timeout_ms = 1000; + if (this.lazymode) + fprintf(stderr, "Adjusting server timeout to %ld ms, target interval %ld ms. Try -I%.1f next time with this network.\n", + this.server_timeout_ms, this.max_timeout_ms, this.max_timeout_ms / 1000.0); + + num_rtt_timeouts = 0; + } + } else { + /* Set server timeout based on target interval and RTT */ + this.server_timeout_ms = this.max_timeout_ms - rtt_ms; + if (this.server_timeout_ms <= 0) { + this.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 */ + this.downstream_timeout_ms = rtt_ms * 2; + this.outbuf->timeout = ms_to_timeval(this.downstream_timeout_ms); + + if (handshake) { + /* Send ping handshake to set server timeout */ + return send_ping(1, -1, 1, 0); + } + return -1; } static void -send_query(int fd, char *hostname) +check_pending_queries() +/* Updates pending queries list */ { - char packet[4096]; + this.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(this.max_timeout_ms + 1000); + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (this.pending_queries[i].time.tv_sec > 0 && this.pending_queries[i].id >= 0) { + timeradd(&this.pending_queries[i].time, &max_timeout, &qtimeout); + if (!timercmp(&qtimeout, &now, >)) { + /* Query has timed out, clear timestamp but leave ID */ + this.pending_queries[i].time.tv_sec = 0; + this.num_timeouts++; + } + this.num_pending++; + } + } +} + +static void +query_sent_now(int id) +{ + int i = 0, found = 0; + if (!this.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 (this.pending_queries[i].id < 0) { + found = 1; + break; + } + } + if (!found) { + for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (this.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 */ + this.pending_queries[i].id = id; + gettimeofday(&this.pending_queries[i].time, NULL); + this.num_pending ++; + QTRACK_DEBUG(4, "Adding query id %d into this.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 && this.pending_queries[i].id == id) { + if (this.num_pending > 0) + this.num_pending--; + + if (this.pending_queries[i].time.tv_sec == 0) { + if (this.num_timeouts > 0) { + /* If query has timed out but is still stored - just in case + * ID is kept on timeout in check_pending_queries() */ + this.num_timeouts --; + immediate = 0; + } else { + /* query is empty */ + continue; + } + } + + if (immediate || this.debug >= 4) { + timersub(&now, &this.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. */ + this.rtt_total_ms += rtt_ms; + this.num_immediate++; + + if (this.autodetect_server_timeout && this.lazymode) + update_server_timeout(0); + } + + /* Remove query info from buffer to mark it as answered */ + id = -1; + this.pending_queries[i].id = -1; + this.pending_queries[i].time.tv_sec = 0; + break; + } + } + if (id > 0) { + QTRACK_DEBUG(4, " got untracked response to id %d.", id); + this.num_untracked++; + } +} + +static int +send_query(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; - chunkid += 7727; - if (chunkid == 0) + DEBUG(3, "TX: pkt len %" L "u: hostname '%s'", strlen((char *)hostname), hostname); + + this.chunkid += 7727; + if (this.chunkid == 0) /* 0 is used as "no-query" in iodined.c */ - chunkid = 7727; + this.chunkid = rand() & 0xFF; - q.id = chunkid; - q.type = do_qtype; + q.id = this.chunkid; + q.type = this.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(this.dns_fd, packet, len, 0, (struct sockaddr*) &this.nameserv_addrs[this.current_nameserver], + sizeof(struct sockaddr_storage)); + + 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 this.connected + and if user hasn't specified server timeout/window timeout etc. */ - if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode) { - send_query_sendcnt++; + this.num_sent++; + if (this.send_query_sendcnt > 0 && this.send_query_sendcnt < 100 && + this.lazymode && this.connected && this.autodetect_server_timeout) { + this.send_query_sendcnt++; + + if ((this.send_query_sendcnt > this.windowsize_down && this.send_query_recvcnt <= 0) || + (this.send_query_sendcnt > 2 * this.windowsize_down && 4 * this.send_query_recvcnt < this.send_query_sendcnt)) { + if (this.max_timeout_ms > 500) { + this.max_timeout_ms -= 200; + double secs = (double) this.max_timeout_ms / 1000.0; + fprintf(stderr, "Receiving too few answers. Setting target timeout to %.1fs (-I%.1f)\n", secs, secs); - 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; /* 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."); - lazymode = 0; - selecttimeout = 1; - handshake_lazyoff(fd); + this.send_query_sendcnt = 0; + this.send_query_recvcnt = 0; + + } else if (this.lazymode) { + 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"); + this.lazymode = 0; + this.server_timeout_ms = 0; } + update_server_timeout(1); } } + return q.id; } static void -send_raw(int fd, char *buf, int buflen, int user, int cmd) +send_raw(uint8_t *buf, size_t buflen, int cmd) { char packet[4096]; int len; @@ -321,104 +412,143 @@ 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) | (this.userid & 0x0F); - sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv)); + sendto(this.dns_fd, packet, len, 0, (struct sockaddr*)&this.raw_serv, sizeof(this.raw_serv)); } static void -send_raw_data(int dns_fd) +send_raw_data(uint8_t *data, size_t datalen) { - send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); - outpkt.len = 0; + send_raw(data, datalen, RAW_HDR_CMD_DATA); } -static void -send_packet(int fd, char cmd, const char *data, const size_t datalen) +static int +send_packet(char cmd, const uint8_t *data, const size_t datalen) +/* Base32 encodes data and sends as single DNS query + * cmd becomes first byte of query, followed by hex userid, encoded + * data and 3 bytes base32 encoded CMC + * Returns ID of sent query */ { - char buf[4096]; + uint8_t buf[512], data_with_cmc[datalen + 2]; + + if (data) + memcpy(data_with_cmc, data, datalen); + *(uint16_t *) (data_with_cmc + datalen) = this.rand_seed; + this.rand_seed++; buf[0] = cmd; + buf[1] = this.userid_char; - build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain, - b32, hostname_maxlen); - send_query(fd, buf); + build_hostname(buf, sizeof(buf), data_with_cmc, datalen + 2, + this.topdomain, b32, this.hostname_maxlen, 2); + + return send_query(buf); } -static inline int -is_sending() +int +send_ping(int ping_response, int ack, int set_timeout, int disconnect) { - return (outpkt.len != 0); + this.num_pings++; + if (this.conn == CONN_DNS_NULL) { + uint8_t data[12]; + int id; + + /* Build ping header (see doc/proto_xxxxxxxx.txt) */ + data[0] = ack & 0xFF; + + if (this.outbuf && this.inbuf) { + data[1] = this.outbuf->windowsize & 0xff; /* Upstream window size */ + data[2] = this.inbuf->windowsize & 0xff; /* Downstream window size */ + data[3] = this.outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[4] = this.inbuf->start_seq_id & 0xff; /* Downstream window start */ + } + + *(uint16_t *) (data + 5) = htons(this.server_timeout_ms); + *(uint16_t *) (data + 7) = htons(this.downstream_timeout_ms); + + /* update server frag/lazy timeout, ack flag, respond with ping flag */ + data[9] = ((disconnect & 1) << 5) | ((set_timeout & 1) << 4) | + ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[10] = (this.rand_seed >> 8) & 0xff; + data[11] = (this.rand_seed >> 0) & 0xff; + this.rand_seed += 1; + + DEBUG(3, " SEND PING: %srespond %d, ack %d, %s(server %ld ms, downfrag %ld ms), flags %02X, wup %u, wdn %u", + disconnect ? "DISCONNECT! " : "", ping_response, ack, set_timeout ? "SET " : "", + this.server_timeout_ms, this.downstream_timeout_ms, + data[8], this.outbuf->windowsize, this.inbuf->windowsize); + + id = send_packet('p', data, sizeof(data)); + + /* Log query ID as being sent now */ + query_sent_now(id); + return id; + } else { + send_raw(NULL, 0, RAW_HDR_CMD_PING); + return -1; + } } static void -send_chunk(int fd) +send_next_frag() +/* 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"; + 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(this.outbuf, &this.next_downstream_ack); + if (!f) { + if (this.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(1, this.next_downstream_ack, 1, 0); + this.next_downstream_ack = -1; + window_tick(this.outbuf); + } + return; /* nothing to send */ + } /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ + buf[0] = this.userid_char; /* First byte is hex this.userid */ - buf[0] = userid_char; /* First byte is hex userid */ + buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ - 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 */ + /* 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 = ((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 */ + hdr[0] = f->seqID & 0xFF; + hdr[1] = f->ack_other & 0xFF; + hdr[2] = code << 4; /* Flags are in upper 4 bits - lower 4 unused */ - 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 */ + 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, this.topdomain, + this.dataenc, this.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 %" L "u, 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(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(this.outbuf); + this.num_frags_sent++; } static void @@ -426,14 +556,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 +601,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 +620,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 +629,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 +638,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 +647,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,8 +695,7 @@ 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(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. @@ -568,24 +703,24 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) */ { struct sockaddr_storage from; - char data[64*1024]; + uint8_t data[64*1024]; socklen_t addrlen; int r; addrlen = sizeof(from); - if ((r = recvfrom(dns_fd, data, sizeof(data), 0, + if ((r = recvfrom(this.dns_fd, data, sizeof(data), 0, (struct sockaddr*)&from, &addrlen)) < 0) { warn("recvfrom"); return -1; } - if (conn == CONN_DNS_NULL) { + if (this.conn == CONN_DNS_NULL) { int rv; if (r <= 0) /* 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 +750,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 +770,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) != this.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); + this.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) { - write_tun(tun_fd, buf, datalen); + if (uncompress(buf, &datalen, data + RAW_HDR_LEN, r) == Z_OK) { + write_tun(this.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(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,27 +824,29 @@ 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; tv.tv_usec = 0; FD_ZERO(&fds); - FD_SET(dns_fd, &fds); - r = select(dns_fd + 1, &fds, NULL, NULL, &tv); + FD_SET(this.dns_fd, &fds); + r = select(this.dns_fd + 1, &fds, NULL, NULL, &tv); if (r < 0) return -1; /* select error */ 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((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 != this.chunkid || qcmd != cmd) { + DEBUG(1, "Ignoring unfitting reply id %d starting with '%c'", q.id, q.name[0]); continue; } @@ -719,8 +862,8 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo (q.name[0] == 'Y' || q.name[0] == 'y' || q.name[0] == 'V' || q.name[0] == 'v')) { fprintf(stderr, "Got empty reply. This nameserver may not be resolving recursively, use another.\n"); - fprintf(stderr, "Try \"iodine [options] ns.%s %s\" first, it might just work.\n", - topdomain, topdomain); + fprintf(stderr, "Try \"iodine [options] %s ns.%s\" first, it might just work.\n", + this.topdomain, this.topdomain); return -2; } @@ -730,7 +873,7 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo mostly long after we've moved along to some other queries. However, some DNS relays, once they throw a SERVFAIL, will for several seconds apply it immediately to _any_ new query - for the same topdomain. When this happens, waiting a while + for the same this.topdomain. When this happens, waiting a while is the only option that works. */ if (rv < 0 && q.rcode == SERVFAIL) @@ -748,644 +891,690 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo return -1; } -static int -tunnel_tun(int tun_fd, int dns_fd) +int +parse_data(uint8_t *data, size_t len, fragment *f, int *immediate, int *ping) { - unsigned long outlen; - unsigned long inlen; - char out[64*1024]; - char in[64*1024]; + size_t headerlen = DOWNSTREAM_HDR; + memset(f, 0, sizeof(fragment)); + int error; + + 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; + if (ping) *ping = (data[2] >> 4) & 1; + error = (data[2] >> 6) & 1; + + if (immediate) + *immediate = (data[2] >> 5) & 1; + + if (ping && *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=%" L "u 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 error; /* return ping flag (if corresponding query was a ping) */ +} + +static ssize_t +tunnel_stdin() +{ + size_t datalen; + uint8_t out[64*1024]; + uint8_t in[64*1024]; + uint8_t *data; + ssize_t readlen; + + readlen = read(STDIN_FILENO, in, sizeof(in)); + DEBUG(4, " IN: %" L "d bytes on stdin, to be compressed: %d", readlen, this.compression_up); + if (readlen == 0) { + DEBUG(2, "EOF on stdin!"); + return -1; + } else if (readlen < 0) { + warnx("Error %d reading from stdin: %s", errno, strerror(errno)); + return -1; + } + + if (this.conn != CONN_DNS_NULL || this.compression_up) { + datalen = sizeof(out); + compress2(out, &datalen, in, readlen, 9); + data = out; + } else { + datalen = readlen; + data = in; + } + + if (this.conn == CONN_DNS_NULL) { + /* Check if outgoing buffer can hold data */ + if (window_buffer_available(this.outbuf) < (datalen / MAX_FRAGSIZE) + 1) { + DEBUG(1, " Outgoing buffer full (%" L "u/%" L "u), not adding data!", + this.outbuf->numitems, this.outbuf->length); + return -1; + } + + window_add_outgoing_data(this.outbuf, data, datalen, this.compression_up); + /* Don't send anything here to respect min. send interval */ + } else { + send_raw_data(data, datalen); + } + + return datalen; +} + +static int +tunnel_tun() +{ + 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) + if ((read = read_tun(this.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: %" L "u bytes on tunnel, to be compressed: %d", read, this.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) { - send_chunk(dns_fd); - - send_ping_soon = 0; + if (this.conn != CONN_DNS_NULL || this.compression_up) { + datalen = sizeof(out); + compress2(out, &datalen, in, read, 9); + data = out; } else { - send_raw_data(dns_fd); + datalen = read; + data = in; + } + + if (this.conn == CONN_DNS_NULL) { + /* Check if outgoing buffer can hold data */ + if (window_buffer_available(this.outbuf) < (read / MAX_FRAGSIZE) + 1) { + DEBUG(1, " Outgoing buffer full (%" L "u/%" L "u), not adding data!", + this.outbuf->numitems, this.outbuf->length); + return -1; + } + + window_add_outgoing_data(this.outbuf, data, datalen, this.compression_up); + /* Don't send anything here to respect min. send interval */ + } else { + send_raw_data(data, datalen); } return read; } static int -tunnel_dns(int tun_fd, int dns_fd) +tunnel_dns() { - 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; + fragment f; + int read, compressed, ping, immediate, error; - 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(cbuf, sizeof(cbuf), &q); - if (conn != CONN_DNS_NULL) + if (this.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 timeout, which means we won't send a proper ping for a while. So make select a bit faster, <1sec. */ if (q.name[0] != 'P' && q.name[0] != 'p' && - q.name[0] != userid_char && q.name[0] != userid_char2) { - send_ping_soon = 700; + q.name[0] != this.userid_char && q.name[0] != this.userid_char2) { + this.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) { + this.num_servfail++; + + if (this.lazymode) { + + if (this.send_query_recvcnt < 500 && this.num_servfail < 4) { + fprintf(stderr, "Hmm, that's %" L "d SERVFAILs. Your data should still go through...\n", this.num_servfail); + + } else if (this.send_query_recvcnt < 500 && this.num_servfail >= 10 && + this.autodetect_server_timeout && this.max_timeout_ms >= 500 && this.num_servfail % 5 == 0) { + + this.max_timeout_ms -= 200; + double target_timeout = (float) this.max_timeout_ms / 1000.0; + fprintf(stderr, "Too many SERVFAILs (%" L "d), reducing timeout to" + " %.1f secs. (use -I%.1f next time on this network)\n", + this.num_servfail, target_timeout, target_timeout); + + /* Reset query counts this.stats */ + this.send_query_sendcnt = 0; + this.send_query_recvcnt = 0; + update_server_timeout(1); + + } else if (this.send_query_recvcnt < 500 && this.num_servfail >= 40 && + this.autodetect_server_timeout && this.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(0, this.compression_down, this.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 + this.send_ping_soon = 900; - 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 */ - } + this.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)) { + this.num_badip++; + if (this.num_badip % 5 == 1) { + fprintf(stderr, "BADIP (%" L "d): 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", this.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); + this.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; + this.num_recv++; + + /* Mark query as received */ + got_response(q.id, immediate, 0); + + /* Decode the downstream data header and fragment-ify ready for processing */ + error = parse_data(cbuf, read, &f, &immediate, &ping); + + if ((this.debug >= 3 && ping) || (this.debug >= 2 && !ping)) + fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %" L "u, s%d e%d\n", + ping ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); + + + window_ack(this.outbuf, f.ack_other); + window_tick(this.outbuf); + + /* respond to TCP forwarding errors by shutting down */ + if (error && this.use_remote_forward) { + f.data[f.len] = 0; + warnx("server: TCP forwarding error: %s", f.data); + this.running = 0; + 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; + /* 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 (!ping) + DEBUG(1, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); + if (!this.lazymode) + this.send_ping_soon = 100; + else + this.send_ping_soon = 700; + return -1; } - while (read > 2) { - /* "if" with easy exit */ - - 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; - - 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; + /* Get next ACK if nothing already pending: if we get a new ack + * then we must send it immediately. */ + if (this.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, "this.next_downstream_ack NOT -1! (%d), %u resends, %u oos", this.next_downstream_ack, this.outbuf->resends, this.outbuf->oos); } - /* NOTE: buf[] was overwritten when down-packet complete */ + /* Downstream data traffic + ack data fragment */ + this.next_downstream_ack = f.seqID; + window_process_incoming_fragment(this.inbuf, &f); + this.num_frags_recv++; - /* 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(this.inbuf, cbuf, sizeof(cbuf), &compressed); + if (datalen > 0) { + if (compressed) { + buflen = sizeof(buf); + if ((ping = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { + DEBUG(1, "Uncompress failed (%d) for data len %" L "u: reassembled data corrupted or incomplete!", ping, 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; + } + + if (datalen) { + if (this.use_remote_forward) + write(STDOUT_FILENO, data, datalen); + else + write_tun(this.tun_fd, data, datalen); } - /* 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()). */ } - - /* 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(this.inbuf); return read; } int -client_tunnel(int tun_fd, int dns_fd) +client_tunnel() { - 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, maxfd; + time_t last_stats; + size_t sent_since_report, recv_since_report; + this.connected = 1; + + /* start counting now */ rv = 0; - lastdownstreamtime = time(NULL); - send_query_sendcnt = 0; /* start counting now */ + this.lastdownstreamtime = time(NULL); + last_stats = time(NULL); - while (running) { - tv.tv_sec = selecttimeout; - tv.tv_usec = 0; + /* reset connection statistics */ + this.num_badip = 0; + this.num_servfail = 0; + this.num_timeouts = 0; + this.send_query_recvcnt = 0; + this.send_query_sendcnt = 0; + this.num_sent = 0; + this.num_recv = 0; + this.num_frags_sent = 0; + this.num_frags_recv = 0; + this.num_pings = 0; - if (is_sending()) { - /* fast timeout for retransmits */ - tv.tv_sec = 1; - tv.tv_usec = 0; + sent_since_report = 0; + recv_since_report = 0; + + use_min_send = 0; + + if (this.debug >= 5) + window_debug = this.debug - 3; + + while (this.running) { + if (!use_min_send) + tv = ms_to_timeval(this.max_timeout_ms); + + /* TODO: detect DNS servers which drop frequent requests + * TODO: adjust number of pending queries based on current data rate */ + if (this.conn == CONN_DNS_NULL && !use_min_send) { + + /* Send a single query per loop */ + sending = window_sending(this.outbuf, &nextresend); + total = sending; + check_pending_queries(); + if (this.num_pending < this.windowsize_down && this.lazymode) + total = MAX(total, this.windowsize_down - this.num_pending); + else if (this.num_pending < 1 && !this.lazymode) + total = MAX(total, 1); + + /* Upstream traffic - this is where all ping/data queries are sent */ + if (sending > 0 || total > 0 || this.next_downstream_ack >= 0) { + + if (sending > 0) { + /* More to send - next fragment */ + send_next_frag(); + } else { + /* Send ping if we didn't send anything yet */ + send_ping(0, this.next_downstream_ack, (this.num_pings > 20 && this.num_pings % 50 == 0), 0); + this.next_downstream_ack = -1; + } + + sending--; + total--; + QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %" L "u, will send another %d", + this.lazymode ? this.windowsize_down : 1, total); + + if (sending > 0 || (total > 0 && this.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(this.min_send_interval_ms); + if (this.min_send_interval_ms) + use_min_send = 1; + tv.tv_usec += 1; + } else if (total > 0 && !this.lazymode) { + /* In immediate mode, use normal interval when needing + * to send non-data queries to probe server. */ + tv = ms_to_timeval(this.send_interval_ms); + } + + if (sending == 0 && !use_min_send) { + /* check next resend time when not sending any data */ + if (timercmp(&nextresend, &tv, <)) + tv = nextresend; + } + + this.send_ping_soon = 0; + } } - if (send_ping_soon) { + if (this.stats) { + if (difftime(time(NULL), last_stats) >= this.stats) { + /* print useful statistics report */ + fprintf(stderr, "\n============ iodine connection statistics (user %1d) ============\n", this.userid); + fprintf(stderr, " Queries sent: %8" L "u" ", answered: %8" L "u" ", SERVFAILs: %4" L "u\n", + this.num_sent, this.num_recv, this.num_servfail); + fprintf(stderr, " last %3d secs: %7" L "u" " (%4" L "u/s), replies: %7" L "u" " (%4" L "u/s)\n", + this.stats, this.num_sent - sent_since_report, (this.num_sent - sent_since_report) / this.stats, + this.num_recv - recv_since_report, (this.num_recv - recv_since_report) / this.stats); + fprintf(stderr, " num IP rejected: %4" L "u, untracked: %4" L "u, lazy mode: %1d\n", + this.num_badip, this.num_untracked, this.lazymode); + fprintf(stderr, " Min send: %5" L "d ms, Avg RTT: %5" L "d ms Timeout server: %4" L "d ms\n", + this.min_send_interval_ms, this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); + fprintf(stderr, " Queries immediate: %5" L "u, timed out: %4" L "u target: %4" L "d ms\n", + this.num_immediate, this.num_timeouts, this.max_timeout_ms); + if (this.conn == CONN_DNS_NULL) { + fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4" L "d ms\n", + this.outbuf->resends, this.inbuf->oos, this.downstream_timeout_ms); + fprintf(stderr, " TX fragments: %8" L "u" ", RX: %8" L "u" ", pings: %8" L "u" "\n\n", + this.num_frags_sent, this.num_frags_recv, this.num_pings); + } + /* update since-last-report this.stats */ + sent_since_report = this.num_sent; + recv_since_report = this.num_recv; + last_stats = time(NULL); + + } + } + + if (this.send_ping_soon && !use_min_send) { tv.tv_sec = 0; - tv.tv_usec = send_ping_soon * 1000; + tv.tv_usec = this.send_ping_soon * 1000; + this.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. */ - FD_SET(tun_fd, &fds); + maxfd = 0; + if (this.conn != CONN_DNS_NULL || window_buffer_available(this.outbuf) > 1) { + /* Fill up outgoing buffer with available data if it has enough space + * The windowing protocol manages data retransmits, timeouts etc. */ + if (this.use_remote_forward) { + FD_SET(STDIN_FILENO, &fds); + maxfd = MAX(STDIN_FILENO, maxfd); + } else { + FD_SET(this.tun_fd, &fds); + maxfd = MAX(this.tun_fd, maxfd); + } } - FD_SET(dns_fd, &fds); + FD_SET(this.dns_fd, &fds); + maxfd = MAX(this.dns_fd, maxfd); - i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); + DEBUG(4, "Waiting %ld ms before sending more... (min_send %d)", timeval_to_ms(&tv), use_min_send); - if (lastdownstreamtime + 60 < time(NULL)) { - warnx("No downstream data received in 60 seconds, shutting down."); - running = 0; + if (use_min_send) { + gettimeofday(&now, NULL); + } + + i = select(maxfd + 1, &fds, NULL, NULL, &tv); + + 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), this.lastdownstreamtime) > 60) { + fprintf(stderr, "No downstream data received in 60 seconds, shutting down.\n"); + this.running = 0; } - if (running == 0) + if (this.running == 0) 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) + if (!this.use_remote_forward && FD_ISSET(this.tun_fd, &fds)) { + if (tunnel_tun() <= 0) continue; /* Returns -1 on error OR when quickly dropping data in case of DNS congestion; we need to _not_ do tunnel_dns() then. - If chunk sent, sets send_ping_soon=0. */ + If chunk sent, sets this.send_ping_soon=0. */ } - if (FD_ISSET(dns_fd, &fds)) { - if (tunnel_dns(tun_fd, dns_fd) <= 0) - continue; + if (this.use_remote_forward && FD_ISSET(STDIN_FILENO, &fds)) { + if (tunnel_stdin() <= 0) { + fprintf(stderr, "server: closing remote TCP forward connection\n"); + /* send ping to disconnect, don't care if it comes back */ + send_ping(0, 0, 0, 1); + this.running = 0; + break; + } + } + + if (FD_ISSET(this.dns_fd, &fds)) { + tunnel_dns(); } } + if (this.running == 0) + break; } return rv; } static void -send_login(int fd, char *login, int len) -{ - char data[19]; - - memset(data, 0, sizeof(data)); - data[0] = userid; - memcpy(&data[1], login, MIN(len, 16)); - - data[17] = (rand_seed >> 8) & 0xff; - data[18] = (rand_seed >> 0) & 0xff; - - rand_seed++; - - send_packet(fd, 'l', data, sizeof(data)); -} - -static void -send_fragsize_probe(int fd, int fragsize) -{ - char probedata[256]; - char buf[4096]; - - /* - * build a large query domain which is random and maximum size, - * will also take up maximal 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() */ - - send_query(fd, buf); -} - -static void -send_set_downstream_fragsize(int fd, int fragsize) -{ - char data[5]; - - data[0] = userid; - data[1] = (fragsize & 0xff00) >> 8; - data[2] = (fragsize & 0x00ff); - data[3] = (rand_seed >> 8) & 0xff; - data[4] = (rand_seed >> 0) & 0xff; - - rand_seed++; - - send_packet(fd, 'n', data, sizeof(data)); -} - -static void -send_version(int fd, uint32_t version) -{ - char data[6]; - - data[0] = (version >> 24) & 0xff; - data[1] = (version >> 16) & 0xff; - data[2] = (version >> 8) & 0xff; - data[3] = (version >> 0) & 0xff; - - data[4] = (rand_seed >> 8) & 0xff; - data[5] = (rand_seed >> 0) & 0xff; - - rand_seed++; - - send_packet(fd, 'v', data, sizeof(data)); -} - -static void -send_ip_request(int fd, int userid) -{ - char buf[512] = "i____."; - buf[1] = b32_5to8(userid); - - buf[2] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[3] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[4] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; - - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); -} - -static void -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); -} - -static void -send_upenctest(int fd, char *s) +send_upenctest(char *s) /* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */ { - char buf[512] = "z___"; + char buf[512] = "zCMC"; + size_t buf_space = 10; + uint32_t cmc = rand(); - buf[1] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[2] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[3] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; + b32->encode((uint8_t *)buf + 1, &buf_space, (uint8_t *) &cmc, 4); - strncat(buf, s, 512); - strncat(buf, ".", 512); - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + /* Append test string without changing it */ + strncat(buf, ".", 512 - strlen(buf)); + strncat(buf, s, 512 - strlen(buf)); + strncat(buf, ".", 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); + send_query((uint8_t *)buf); } static void -send_downenctest(int fd, char downenc, int variant, char *s, int slen) -/* Note: content/handling of s is not defined yet. */ +send_downenctest(char downenc, int variant) { - char buf[512] = "y_____."; + char buf[512] = "yTaCMC."; + size_t buf_space = 4; buf[1] = tolower(downenc); buf[2] = b32_5to8(variant); - 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++; + /* add 3 bytes base32 CMC (random) */ + uint32_t cmc = rand(); - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + b32->encode((uint8_t *)buf + 3, &buf_space, (uint8_t *) &cmc, 2); + strncat(buf, ".", 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); + send_query((uint8_t *)buf); } static void -send_codec_switch(int fd, int userid, int bits) +send_version(uint32_t version) { - char buf[512] = "s_____."; - buf[1] = b32_5to8(userid); - buf[2] = b32_5to8(bits); + uint8_t data[8], buf[512]; - 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++; + *(uint32_t *) data = htonl(version); + *(uint32_t *) (data + 4) = (uint32_t) rand(); /* CMC */ - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); -} + buf[0] = 'v'; + build_hostname(buf, sizeof(buf), data, sizeof(data), + this.topdomain, b32, this.hostname_maxlen, 1); -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(buf); } static void -send_lazy_switch(int fd, int userid) +send_login(char *login, int len) +/* Send DNS login packet. See doc/proto_xxxxxxxx.txt for details */ { - char buf[512] = "o_____."; - buf[1] = b32_5to8(userid); + uint8_t flags = 0, data[100]; + int length = 17, addrlen = 0; + uint16_t port; - if (lazymode) - buf[2] = 'l'; - else - buf[2] = 'i'; + if (len != 16) + DEBUG(1, "Login calculated incorrect length hash! len=%d", len); - 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++; + memcpy(data + 1, login, 16); - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + /* if remote forward address is specified and not currently connecting */ + if (this.remote_forward_connected != 2 && + this.remote_forward_addr.ss_family != AF_UNSPEC) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &this.remote_forward_addr; + struct sockaddr_in *s = (struct sockaddr_in *) &this.remote_forward_addr; + + port = (this.remote_forward_addr.ss_family == AF_INET ? s->sin_port : s6->sin6_port); + + *(uint16_t *) (data + length) = port; + + flags |= 1; + length += 2; + /* set remote IP to be non-localhost if this.remote_forward_addr set */ + if (this.remote_forward_addr.ss_family == AF_INET && s->sin_addr.s_addr != INADDR_LOOPBACK) { + if (this.remote_forward_addr.ss_family == AF_INET6) { /* IPv6 address */ + addrlen = sizeof(s6); + flags |= 4; + memcpy(data + length, &s6->sin6_addr, addrlen); + } else { /* IPv4 address */ + flags |= 2; + addrlen = sizeof(s); + memcpy(data + length, &s->sin_addr, addrlen); + } + + length += addrlen; + } + DEBUG(2, "Sending TCP forward login request: port %hu, length %d, addrlen %d", + port, length, addrlen); + } else if (this.remote_forward_connected == 2) { + /* remote TCP forward connection in progress */ + DEBUG(2, "Sending TCP forward login/poll request to check connection status."); + flags |= (1 << 4); + } + + data[0] = flags; + + DEBUG(6, "Sending login request: length=%d, flags=0x%02x, hash=0x%016llx%016llx", + length, flags, *(unsigned long long *) (data + 1), *(unsigned long long *) (data + 9)); + + send_packet('l', data, length); +} + +static void +send_fragsize_probe(uint16_t fragsize) +{ + uint8_t data[256]; + + /* Probe downstream fragsize packet */ + + /* build a large query domain which is random and maximum size, + * will also take up maximum space in the return packet */ + memset(data, MAX(1, this.rand_seed & 0xff), sizeof(data)); + + *(uint16_t *) (data) = htons(fragsize); + this.rand_seed++; + + send_packet('r', data, sizeof(data)); +} + +static void +send_set_downstream_fragsize(uint16_t fragsize) +{ + uint8_t data[2]; + *(uint16_t *) data = htons(fragsize); + + send_packet('n', data, sizeof(data)); +} + +static void +send_ip_request() +{ + send_packet('i', NULL, 0); +} + +static void +send_raw_udp_login(int seed) +{ + char buf[16]; + login_calculate(buf, sizeof(buf), this.password, seed + 1); + + send_raw((uint8_t *) buf, sizeof(buf), RAW_HDR_CMD_LOGIN); +} + +static void +send_codec_switch(uint8_t bits) +{ + send_packet('s', &bits, 1); +} + +static void +send_server_options(int lazy, int compression, char denc) +{ + uint8_t optflags = 0; + + if (denc == 'T') /* Base32 */ + optflags |= 1 << 6; + else if (denc == 'S') /* Base64 */ + optflags |= 1 << 5; + else if (denc == 'U') /* Base64u */ + optflags |= 1 << 4; + else if (denc == 'V') /* Base128 */ + optflags |= 1 << 3; + else if (denc == 'R') /* Raw */ + optflags |= 1 << 2; + + optflags |= (compression & 1) << 1; + optflags |= lazy & 1; + + send_packet('o', &optflags, 1); } static int -handshake_version(int dns_fd, int *seed) +handshake_version(int *seed) { char hex[] = "0123456789abcdef"; char hex2[] = "0123456789ABCDEF"; @@ -1394,32 +1583,34 @@ handshake_version(int dns_fd, int *seed) int i; int read; - for (i = 0; running && i < 5; i++) { + for (i = 0; this.running && i < 5; i++) { - send_version(dns_fd, PROTOCOL_VERSION); + send_version(PROTOCOL_VERSION); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'v', 'V', i+1); + read = handshake_waitdns(in, sizeof(in), 'V', i+1); if (read >= 9) { - payload = (((in[4] & 0xff) << 24) | - ((in[5] & 0xff) << 16) | - ((in[6] & 0xff) << 8) | - ((in[7] & 0xff))); + payload = ntohl(*(uint32_t *) (in + 4)); - if (strncmp("VACK", in, 4) == 0) { + if (strncmp("VACK", (char *)in, 4) == 0) { + /* Payload is login challenge */ *seed = payload; - userid = in[8]; - userid_char = hex[userid & 15]; - userid_char2 = hex2[userid & 15]; + this.userid = in[8]; + this.userid_char = hex[this.userid & 15]; + this.userid_char2 = hex2[this.userid & 15]; + + DEBUG(2, "Login challenge: 0x%08x", *seed); fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", - PROTOCOL_VERSION, userid); + PROTOCOL_VERSION, this.userid); return 0; - } else if (strncmp("VNAK", in, 4) == 0) { + } else if (strncmp("VNAK", (char *)in, 4) == 0) { + /* Payload is server version */ 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) { + /* Payload is max number of users on server */ warnx("Server full, all %d slots are taken. Try again later", payload); return 1; } @@ -1433,55 +1624,104 @@ handshake_version(int dns_fd, int *seed) } static int -handshake_login(int dns_fd, int seed) +handshake_login(int seed) { - char in[4096]; - char login[16]; - char server[65]; - char client[65]; - int mtu; - int i; - int read; + char in[4096], login[16], server[65], client[65], flag; + int mtu, netmask, read, numwaiting = 0; - login_calculate(login, 16, password, seed); + login_calculate(login, 16, this.password, seed); - for (i=0; running && i<5 ;i++) { + for (int i = 0; this.running && i < 5; i++) { - send_login(dns_fd, login, 16); + send_login(login, 16); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'l', 'L', i+1); + read = handshake_waitdns(in, sizeof(in), 'L', i + 1); + in[MIN(read, sizeof(in))] = 0; /* Null terminate */ if (read > 0) { - int netmask; if (strncmp("LNAK", in, 4) == 0) { fprintf(stderr, "Bad password\n"); return 1; - } else if (sscanf(in, "%64[^-]-%64[^-]-%d-%d", - server, client, &mtu, &netmask) == 4) { - - server[64] = 0; - client[64] = 0; - if (tun_setip(client, server, netmask) == 0 && - tun_setmtu(mtu) == 0) { - - fprintf(stderr, "Server tunnel IP is %s\n", server); - return 0; - } else { - errx(4, "Failed to set IP and MTU"); - } - } else { - fprintf(stderr, "Received bad handshake\n"); + /* not reached */ } + flag = toupper(in[0]); + + switch (flag) { + case 'I': + if (sscanf(in, "%c-%64[^-]-%64[^-]-%d-%d", + &flag, server, client, &mtu, &netmask) == 5) { + + server[64] = 0; + client[64] = 0; + if (tun_setip(client, server, netmask) == 0 && + tun_setmtu(mtu) == 0) { + + fprintf(stderr, "Server tunnel IP is %s\n", server); + return 0; + } else { + errx(4, "Failed to set IP and MTU"); + } + } else { + goto bad_handshake; + } + break; + case 'C': + if (!this.use_remote_forward) { + goto bad_handshake; + } + + this.remote_forward_connected = 1; + fprintf(stderr, " done."); + return 0; + case 'W': + if (!this.use_remote_forward || this.remote_forward_connected == 1) { + goto bad_handshake; + } + + this.remote_forward_connected = 2; + + if (numwaiting == 0) + fprintf(stderr, "server: Opening Remote TCP forward.\n"); + else + fprintf(stderr, "%.*s", numwaiting, "..............."); + + numwaiting ++; + + /* wait a while before re-polling server, max 5 tries (14 seconds) */ + if (numwaiting > 1) + sleep(numwaiting); + + continue; + case 'E': + if (!this.use_remote_forward) + goto bad_handshake; + + char errormsg[100]; + strncpy(errormsg, in + 1, MIN(read, sizeof(errormsg))); + errormsg[99] = 0; + fprintf(stderr, "server: Remote TCP forward connection failed: %s\n", errormsg); + return 1; + default: + /* undefined flag */ + bad_handshake: + fprintf(stderr, "Received bad handshake: %.*s\n", read, in); + break; + } + } fprintf(stderr, "Retrying login...\n"); } - warnx("couldn't login to server"); + if (numwaiting != 0) + warnx("Remote TCP forward connection timed out after 5 tries."); + else + warnx("couldn't login to server"); + return 1; } static int -handshake_raw_udp(int dns_fd, int seed) +handshake_raw_udp(int seed) { struct timeval tv; char in[4096]; @@ -1491,33 +1731,33 @@ handshake_raw_udp(int dns_fd, int seed) int len; int got_addr; - memset(&raw_serv, 0, sizeof(raw_serv)); + memset(&this.raw_serv, 0, sizeof(this.raw_serv)); got_addr = 0; fprintf(stderr, "Testing raw UDP data to the server (skip with -r)"); - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { - send_ip_request(dns_fd, userid); + send_ip_request(); - len = handshake_waitdns(dns_fd, in, sizeof(in), 'i', 'I', i+1); + len = handshake_waitdns(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 *) &this.raw_serv; 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); + this.raw_serv_len = 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 *) &this.raw_serv; 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); + this.raw_serv_len = sizeof(struct sockaddr_in6); got_addr = 1; break; } @@ -1526,36 +1766,36 @@ handshake_raw_udp(int dns_fd, int seed) fflush(stderr); } fprintf(stderr, "\n"); - if (!running) + if (!this.running) return 0; if (!got_addr) { 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(&this.raw_serv, this.raw_serv_len)); fflush(stderr); /* do login against port 53 on remote server * based on the old seed. If reply received, * switch to raw udp mode */ - for (i=0; running && i<4 ;i++) { + for (i=0; this.running && i<4 ;i++) { tv.tv_sec = i + 1; tv.tv_usec = 0; - send_raw_udp_login(dns_fd, userid, seed); + send_raw_udp_login(seed); FD_ZERO(&fds); - FD_SET(dns_fd, &fds); + FD_SET(this.dns_fd, &fds); - r = select(dns_fd + 1, &fds, NULL, NULL, &tv); + r = select(this.dns_fd + 1, &fds, NULL, NULL, &tv); if(r > 0) { /* recv() needed for windows, dont change to read() */ - len = recv(dns_fd, in, sizeof(in), 0); + len = recv(this.dns_fd, in, sizeof(in), 0); if (len >= (16 + RAW_HDR_LEN)) { char hash[16]; - login_calculate(hash, 16, password, seed - 1); + login_calculate(hash, 16, this.password, seed - 1); if (memcmp(in, raw_header, RAW_HDR_IDENT_LEN) == 0 && RAW_HDR_GET_CMD(in) == RAW_HDR_CMD_LOGIN && memcmp(&in[RAW_HDR_LEN], hash, sizeof(hash)) == 0) { @@ -1574,7 +1814,7 @@ handshake_raw_udp(int dns_fd, int seed) } static int -handshake_upenctest(int dns_fd, char *s) +handshake_upenctest(char *s) /* NOTE: *s may be max 59 chars; must start with "aA" for case-swap check Returns: -1: case swap, no need for any further test: error printed; or Ctrl-C @@ -1585,16 +1825,14 @@ handshake_upenctest(int dns_fd, char *s) char in[4096]; unsigned char *uin = (unsigned char *) in; unsigned char *us = (unsigned char *) s; - int i; - int read; - int slen; + int i, read, slen; slen = strlen(s); - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { - send_upenctest(dns_fd, s); + send_upenctest(s); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'z', 'Z', i+1); + read = handshake_waitdns(in, sizeof(in), 'Z', i+1); if (read == -2) return 0; /* hard error */ @@ -1604,13 +1842,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"); @@ -1643,7 +1874,7 @@ handshake_upenctest(int dns_fd, char *s) fprintf(stderr, "Retrying upstream codec test...\n"); } - if (!running) + if (!this.running) return -1; /* timeout */ @@ -1651,7 +1882,7 @@ handshake_upenctest(int dns_fd, char *s) } static int -handshake_upenc_autodetect(int dns_fd) +handshake_upenc_autodetect() /* Returns: 0: keep Base32 1: Base64 is okay @@ -1681,7 +1912,7 @@ handshake_upenc_autodetect(int dns_fd) /* Try Base128, starting very gently to not draw attention */ while (1) { - res = handshake_upenctest(dns_fd, pat128a); + res = handshake_upenctest(pat128a); if (res < 0) { /* DNS swaps case, msg already printed; or Ctrl-C */ return 0; @@ -1690,7 +1921,7 @@ handshake_upenc_autodetect(int dns_fd) break; } - res = handshake_upenctest(dns_fd, pat128b); + res = handshake_upenctest(pat128b); if (res < 0) return 0; else if (res == 0) @@ -1698,19 +1929,19 @@ handshake_upenc_autodetect(int dns_fd) /* if this works, we can test the real stuff */ - res = handshake_upenctest(dns_fd, pat128c); + res = handshake_upenctest(pat128c); if (res < 0) return 0; else if (res == 0) break; - res = handshake_upenctest(dns_fd, pat128d); + res = handshake_upenctest(pat128d); if (res < 0) return 0; else if (res == 0) break; - res = handshake_upenctest(dns_fd, pat128e); + res = handshake_upenctest(pat128e); if (res < 0) return 0; else if (res == 0) @@ -1721,7 +1952,7 @@ handshake_upenc_autodetect(int dns_fd) } /* Try Base64 (with plus sign) */ - res = handshake_upenctest(dns_fd, pat64); + res = handshake_upenctest(pat64); if (res < 0) { /* DNS swaps case, msg already printed; or Ctrl-C */ return 0; @@ -1731,7 +1962,7 @@ handshake_upenc_autodetect(int dns_fd) } /* Try Base64u (with _u_nderscore) */ - res = handshake_upenctest(dns_fd, pat64u); + res = handshake_upenctest(pat64u); if (res < 0) { /* DNS swaps case, msg already printed; or Ctrl-C */ return 0; @@ -1746,7 +1977,7 @@ handshake_upenc_autodetect(int dns_fd) } static int -handshake_downenctest(int dns_fd, char trycodec) +handshake_downenctest(char trycodec) /* Returns: 0: not identical or error or timeout 1: identical string returned @@ -1758,11 +1989,11 @@ handshake_downenctest(int dns_fd, char trycodec) char *s = DOWNCODECCHECK1; int slen = DOWNCODECCHECK1_LEN; - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { - send_downenctest(dns_fd, trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + read = handshake_waitdns(in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -1790,40 +2021,40 @@ handshake_downenctest(int dns_fd, char trycodec) } static char -handshake_downenc_autodetect(int dns_fd) +handshake_downenc_autodetect() /* Returns codec char (or ' ' if no advanced codec works) */ { int base64ok = 0; int base64uok = 0; int base128ok = 0; - if (do_qtype == T_NULL || do_qtype == T_PRIVATE) { + if (this.do_qtype == T_NULL || this.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"); /* Try Base64 */ - if (handshake_downenctest(dns_fd, 'S')) + if (handshake_downenctest('S')) base64ok = 1; - else if (running && handshake_downenctest(dns_fd, 'U')) + else if (this.running && handshake_downenctest('U')) base64uok = 1; /* Try Base128 only if 64 gives us some perspective */ - if (running && (base64ok || base64uok)) { - if (handshake_downenctest(dns_fd, 'V')) + if (this.running && (base64ok || base64uok)) { + if (handshake_downenctest('V')) base128ok = 1; } /* If 128 works, then TXT may give us Raw as well */ - if (running && (base128ok && do_qtype == T_TXT)) { - if (handshake_downenctest(dns_fd, 'R')) + if (this.running && (base128ok && this.do_qtype == T_TXT)) { + if (handshake_downenctest('R')) return 'R'; } - if (!running) + if (!this.running) return ' '; if (base128ok) @@ -1838,7 +2069,7 @@ handshake_downenc_autodetect(int dns_fd) } static int -handshake_qtypetest(int dns_fd, int timeout) +handshake_qtypetest(int timeout) /* Returns: 0: doesn't work with this timeout 1: works properly @@ -1851,7 +2082,7 @@ handshake_qtypetest(int dns_fd, int timeout) int trycodec; int k; - if (do_qtype == T_NULL || do_qtype == T_PRIVATE) + if (this.do_qtype == T_NULL || this.do_qtype == T_PRIVATE) trycodec = 'R'; else trycodec = 'T'; @@ -1860,9 +2091,9 @@ handshake_qtypetest(int dns_fd, int timeout) byte values can be returned, which is needed for NULL/PRIVATE to work. */ - send_downenctest(dns_fd, trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', timeout); + read = handshake_waitdns(in, sizeof(in), 'Y', timeout); if (read != slen) return 0; /* incorrect */ @@ -1894,9 +2125,9 @@ handshake_qtype_numcvt(int num) } static int -handshake_qtype_autodetect(int dns_fd) +handshake_qtype_autodetect() /* Returns: - 0: okay, do_qtype set + 0: okay, this.do_qtype set 1: problem, program exit */ { @@ -1917,22 +2148,19 @@ handshake_qtype_autodetect(int dns_fd) to see if things will start working after a while. */ - for (timeout = 1; running && timeout <= 3; timeout++) { - for (qtypenum = 0; running && qtypenum < highestworking; qtypenum++) { - do_qtype = handshake_qtype_numcvt(qtypenum); - if (do_qtype == T_UNSET) + for (timeout = 1; this.running && timeout <= 3; timeout++) { + for (qtypenum = 0; this.running && qtypenum < highestworking; qtypenum++) { + this.do_qtype = handshake_qtype_numcvt(qtypenum); + if (this.do_qtype == T_UNSET) break; /* this round finished */ fprintf(stderr, "."); fflush(stderr); - if (handshake_qtypetest(dns_fd, timeout)) { + if (handshake_qtypetest(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 */ } @@ -1945,17 +2173,17 @@ handshake_qtype_autodetect(int dns_fd) fprintf(stderr, "\n"); - if (!running) { + if (!this.running) { warnx("Stopped while autodetecting DNS query type (try setting manually with -T)"); return 1; /* problem */ } /* finished */ - do_qtype = handshake_qtype_numcvt(highestworking); + this.do_qtype = handshake_qtype_numcvt(highestworking); - if (do_qtype == T_UNSET) { + if (this.do_qtype == T_UNSET) { /* also catches highestworking still 100 */ - warnx("No suitable DNS query type found. Are you connected to a network?"); + warnx("No suitable DNS query type found. Are you this.connected to a network?"); warnx("If you expect very long roundtrip delays, use -T explicitly."); warnx("(Also, connecting to an \"ancient\" version of iodined won't work.)"); return 1; /* problem */ @@ -1966,7 +2194,7 @@ handshake_qtype_autodetect(int dns_fd) } static int -handshake_edns0_check(int dns_fd) +handshake_edns0_check() /* Returns: 0: EDNS0 not supported; or Ctrl-C 1: EDNS0 works @@ -1979,16 +2207,16 @@ handshake_edns0_check(int dns_fd) int slen = DOWNCODECCHECK1_LEN; char trycodec; - if (do_qtype == T_NULL) + if (this.do_qtype == T_NULL) trycodec = 'R'; else trycodec = 'T'; - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { - send_downenctest(dns_fd, trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + read = handshake_waitdns(in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -2016,7 +2244,7 @@ handshake_edns0_check(int dns_fd) } static void -handshake_switch_codec(int dns_fd, int bits) +handshake_switch_codec(int bits) { char in[4096]; int i; @@ -2035,161 +2263,108 @@ handshake_switch_codec(int dns_fd, int bits) fprintf(stderr, "Switching upstream to codec %s\n", tempenc->name); - for (i=0; running && i<5 ;i++) { + for (i=0; this.running && i<5 ;i++) { - send_codec_switch(dns_fd, userid, bits); + send_codec_switch(bits); - read = handshake_waitdns(dns_fd, in, sizeof(in), 's', 'S', i+1); + read = handshake_waitdns(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; + this.dataenc = tempenc; + + /* Update outgoing buffer max (decoded) fragsize */ + this.maxfragsize_up = get_raw_length_from_dns(this.hostname_maxlen - UPSTREAM_HDR, this.dataenc, this.topdomain); return; } fprintf(stderr, "Retrying codec switch...\n"); } - if (!running) + if (!this.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); + fprintf(stderr, "Falling back to upstream codec %s\n", this.dataenc->name); } -static void -handshake_switch_downenc(int dns_fd) +void +handshake_switch_options(int lazy, int compression, char denc) { - char in[4096]; - int i; + char in[100]; int read; - char *dname; + char *dname, *comp_status, *lazy_status; + + 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; this.running && i < 5; i++) { - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); + send_server_options(lazy, compression, denc); + + read = handshake_waitdns(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; - } 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 the selected codec. "); - goto codec_revert; - } in[read] = 0; /* zero terminate */ - fprintf(stderr, "Server switched downstream to codec %s\n", in); - return; - } - fprintf(stderr, "Retrying codec switch...\n"); - } - if (!running) - return; - - fprintf(stderr, "No reply from server on codec switch. "); - -codec_revert: - fprintf(stderr, "Falling back to downstream codec Base32\n"); -} - -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; + 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 lazy mode. "); - goto codec_revert; - } else if (strncmp("Lazy", in, 4) == 0) { - fprintf(stderr, "Server switched to lazy mode\n"); - lazymode = 1; + fprintf(stderr, "Server rejected the selected options.\n"); + goto opt_revert; + } else if (strcasecmp(dname, in) == 0) { + fprintf(stderr, "Switched server options, using downsteam codec %s.\n", in); + this.lazymode = lazy; + this.compression_down = compression; + this.downenc = denc; return; + } else { + fprintf(stderr, "Got invalid response. "); } } - fprintf(stderr, "Retrying lazy mode switch...\n"); + fprintf(stderr, "Retrying options switch...\n"); } - if (!running) + if (!this.running) return; - fprintf(stderr, "No reply from server on lazy switch. "); + fprintf(stderr, "No reply from server on options switch.\n"); -codec_revert: - fprintf(stderr, "Falling back to legacy mode\n"); - lazymode = 0; - selecttimeout = 1; -} +opt_revert: + comp_status = this.compression_down ? "enabled" : "disabled"; + lazy_status = this.lazymode ? "lazy" : "immediate"; -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", + this.dataenc->name, lazy_status, comp_status); } static int @@ -2229,8 +2404,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; } @@ -2251,7 +2425,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) *max_fragsize = acked_fragsize; return 1; } else { - if (downenc != ' ' && downenc != 'T') { + if (this.downenc != ' ' && this.downenc != 'T') { fprintf(stderr, "%d corrupted at %d.. (Try -O Base32)\n", acked_fragsize, i); } else { fprintf(stderr, "%d corrupted at %d.. ", acked_fragsize, i); @@ -2266,9 +2440,9 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) static int -handshake_autoprobe_fragsize(int dns_fd) +handshake_autoprobe_fragsize() { - char in[4096]; + char in[MAX_FRAGSIZE]; int i; int read; int proposed_fragsize = 768; @@ -2276,14 +2450,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"); - while (running && range > 0 && (range >= 8 || max_fragsize < 300)) { + fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)"); + while (this.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++) { + for (i=0; this.running && i<3 ;i++) { - send_fragsize_probe(dns_fd, proposed_fragsize); + send_fragsize_probe(proposed_fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'r', 'R', 1); + read = handshake_waitdns(in, sizeof(in), 'R', 1); if (read > 0) { /* We got a reply */ @@ -2308,30 +2482,28 @@ handshake_autoprobe_fragsize(int dns_fd) proposed_fragsize -= range; } } - if (!running) { - fprintf(stderr, "\n"); - warnx("stopped while autodetecting fragment size (Try setting manually with -m)"); + if (!this.running) { + 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) { fprintf(stderr, "Note: this probably won't work well.\n"); fprintf(stderr, "Try setting -M to 200 or lower, or try other DNS types (-T option).\n"); } else if (max_fragsize < 202 && - (do_qtype == T_NULL || do_qtype == T_PRIVATE || do_qtype == T_TXT || - do_qtype == T_SRV || do_qtype == T_MX)) { + (this.do_qtype == T_NULL || this.do_qtype == T_PRIVATE || this.do_qtype == T_TXT || + this.do_qtype == T_SRV || this.do_qtype == T_MX)) { fprintf(stderr, "Note: this isn't very much.\n"); fprintf(stderr, "Try setting -M to 200 or lower, or try other DNS types (-T option).\n"); } @@ -2340,23 +2512,23 @@ handshake_autoprobe_fragsize(int dns_fd) } static void -handshake_set_fragsize(int dns_fd, int fragsize) +handshake_set_fragsize(int fragsize) { char in[4096]; int i; int read; fprintf(stderr, "Setting downstream fragment size to max %d...\n", fragsize); - for (i=0; running && i<5 ;i++) { + for (i=0; this.running && i<5 ;i++) { - send_set_downstream_fragsize(dns_fd, fragsize); + send_set_downstream_fragsize(fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'n', 'N', i+1); + read = handshake_waitdns(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"); @@ -2370,14 +2542,53 @@ handshake_set_fragsize(int dns_fd, int fragsize) fprintf(stderr, "Retrying set fragsize...\n"); } - if (!running) + if (!this.running) return; fprintf(stderr, "No reply from server when setting fragsize. Keeping default.\n"); } +static void +handshake_set_timeout() +{ + char in[4096]; + int read, id; + + fprintf(stderr, "Setting window sizes to %" L "u frags upstream, %" L "u frags downstream...\n", + this.windowsize_up, this.windowsize_down); + + fprintf(stderr, "Calculating round-trip time..."); + + /* Reset RTT stats */ + this.num_immediate = 0; + this.rtt_total_ms = 0; + + for (int i = 0; this.running && i < 5; i++) { + + id = this.autodetect_server_timeout ? + update_server_timeout(1) : send_ping(1, -1, 1, 0); + + read = handshake_waitdns(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"); + } + continue; + } + + } + if (!this.running) + return; + + fprintf(stderr, "\nDetermined round-trip time of %ld ms, using server timeout of %ld ms.\n", + this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); +} + int -client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize) +client_handshake() { int seed; int upcodec; @@ -2386,8 +2597,8 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz dnsc_use_edns0 = 0; /* qtype message printed in handshake function */ - if (do_qtype == T_UNSET) { - r = handshake_qtype_autodetect(dns_fd); + if (this.do_qtype == T_UNSET) { + r = handshake_qtype_autodetect(); if (r) { return r; } @@ -2395,76 +2606,94 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz fprintf(stderr, "Using DNS type %s queries\n", client_get_qtype()); - r = handshake_version(dns_fd, &seed); - if (r) { + if ((r = handshake_version(&seed))) { return r; } - r = handshake_login(dns_fd, seed); - if (r) { + if ((r = handshake_login(seed))) { return r; } - if (raw_mode && handshake_raw_udp(dns_fd, seed)) { - conn = CONN_RAW_UDP; - selecttimeout = 20; + if (this.raw_mode && handshake_raw_udp(seed)) { + this.conn = CONN_RAW_UDP; + this.max_timeout_ms = 10000; + this.compression_down = 1; + this.compression_up = 1; + if (this.use_remote_forward) + fprintf(stderr, "Warning: Remote TCP forwards over Raw (UDP) mode may be unreliable.\n" + " If forwarded connections are unstable, try using '-r' to force DNS tunnelling mode.\n"); } else { - if (raw_mode == 0) { + if (this.raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); } dnsc_use_edns0 = 1; - if (handshake_edns0_check(dns_fd) && running) { + if (handshake_edns0_check() && this.running) { fprintf(stderr, "Using EDNS0 extension\n"); - } else if (!running) { + } else if (!this.running) { return -1; } else { fprintf(stderr, "DNS relay does not support EDNS0 extension\n"); dnsc_use_edns0 = 0; } - upcodec = handshake_upenc_autodetect(dns_fd); - if (!running) + upcodec = handshake_upenc_autodetect(); + if (!this.running) return -1; - if (upcodec == 1) { - handshake_switch_codec(dns_fd, 6); - } else if (upcodec == 2) { - handshake_switch_codec(dns_fd, 26); - } else if (upcodec == 3) { - handshake_switch_codec(dns_fd, 7); + if (upcodec == 1) { /* Base64 */ + handshake_switch_codec(6); + } else if (upcodec == 2) { /* Base64u */ + handshake_switch_codec(26); + } else if (upcodec == 3) { /* Base128 */ + handshake_switch_codec(7); } - if (!running) + if (!this.running) return -1; - if (downenc == ' ') { - downenc = handshake_downenc_autodetect(dns_fd); + if (this.downenc == ' ') { + this.downenc = handshake_downenc_autodetect(); } - if (!running) + if (!this.running) return -1; - if (downenc != ' ') { - handshake_switch_downenc(dns_fd); - } - if (!running) + /* Set options for compression, this.lazymode and downstream codec */ + handshake_switch_options(this.lazymode, this.compression_down, this.downenc); + if (!this.running) return -1; - if (lazymode) { - handshake_try_lazy(dns_fd); - } - if (!running) - return -1; - - if (autodetect_frag_size) { - fragsize = handshake_autoprobe_fragsize(dns_fd); - if (!fragsize) { + if (this.autodetect_frag_size) { + this.max_downstream_frag_size = handshake_autoprobe_fragsize(); + if (this.max_downstream_frag_size > 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", this.max_downstream_frag_size, MAX_FRAGSIZE); + this.max_downstream_frag_size = MAX_FRAGSIZE; + } + if (!this.max_downstream_frag_size) { return 1; } } - handshake_set_fragsize(dns_fd, fragsize); - if (!running) + handshake_set_fragsize(this.max_downstream_frag_size); + if (!this.running) return -1; + + /* init windowing protocol */ + this.outbuf = window_buffer_init(64, this.windowsize_up, this.maxfragsize_up, WINDOW_SENDING); + this.outbuf->timeout = ms_to_timeval(this.downstream_timeout_ms); + /* Incoming buffer max fragsize doesn't matter */ + this.inbuf = window_buffer_init(64, this.windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); + + /* init query tracking */ + this.num_untracked = 0; + this.num_pending = 0; + this.pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) + this.pending_queries[i].id = -1; + + /* set server window/timeout parameters and calculate RTT */ + handshake_set_timeout(); } return 0; diff --git a/src/client.h b/src/client.h index c2493f1..eda47a0 100644 --- a/src/client.h +++ b/src/client.h @@ -18,23 +18,152 @@ #ifndef __CLIENT_H__ #define __CLIENT_H__ +#include "window.h" + +extern int debug; +extern int stats; + +#define PENDING_QUERIES_LENGTH (MAX(this.windowsize_up, this.windowsize_down) * 4) +#define INSTANCE this + +struct client_instance { + int max_downstream_frag_size; + int autodetect_frag_size; + int hostname_maxlen; + int raw_mode; + int foreground; + char password[33]; + + /* DNS nameserver info */ + char **nameserv_hosts; + size_t nameserv_hosts_len; + struct sockaddr_storage *nameserv_addrs; + size_t nameserv_addrs_len; + int current_nameserver; + struct sockaddr_storage raw_serv; + int raw_serv_len; + char *topdomain; + + /* Remote TCP forwarding stuff (for -R) */ + struct sockaddr_storage remote_forward_addr; + int use_remote_forward; /* 0 if no forwarding used */ + int remote_forward_connected; + + int tun_fd; + int dns_fd; + +#ifdef OPENBSD + int rtable; +#endif + int running; + + /* Output flags for debug and time between stats update */ + int debug; + int stats; + + uint16_t rand_seed; + + /* Current up/downstream window data */ + struct frag_buffer *outbuf; + struct frag_buffer *inbuf; + size_t windowsize_up; + size_t windowsize_down; + size_t maxfragsize_up; + + /* Next downstream seqID to be ACK'd (-1 if none pending) */ + int next_downstream_ack; + + /* Remembering queries we sent for tracking purposes */ + struct query_tuple *pending_queries; + size_t num_pending; + time_t max_timeout_ms; + time_t send_interval_ms; + time_t min_send_interval_ms; + + /* Server response timeout in ms and downstream window timeout */ + time_t server_timeout_ms; + time_t downstream_timeout_ms; + int autodetect_server_timeout; + + /* Cumulative Round-Trip-Time in ms */ + time_t rtt_total_ms; + size_t num_immediate; + + /* Connection statistics */ + size_t num_timeouts; + size_t num_untracked; + size_t num_servfail; + size_t num_badip; + size_t num_sent; + size_t num_recv; + size_t send_query_sendcnt; + size_t send_query_recvcnt; + size_t num_frags_sent; + size_t num_frags_recv; + size_t num_pings; + + /* My userid at the server */ + char userid; + char userid_char; /* used when sending (lowercase) */ + char userid_char2; /* also accepted when receiving (uppercase) */ + + uint16_t chunkid; + + /* Base32 encoder used for non-data packets and replies */ + struct encoder *b32; + /* Base64 etc encoders for replies */ + struct encoder *b64; + struct encoder *b64u; + struct encoder *b128; + + /* The encoder used for data packets + * Defaults to Base32, can be changed after handshake */ + struct encoder *dataenc; + + /* Upstream/downstream compression flags */ + int compression_up; + int compression_down; + + /* The encoder to use for downstream data */ + char downenc; + + /* set query type to send */ + uint16_t do_qtype; + + /* My connection mode */ + enum connection conn; + int connected; + + int lazymode; + long send_ping_soon; + time_t lastdownstreamtime; +}; + +struct query_tuple { + int id; /* DNS query / response ID */ + struct timeval time; /* time sent or 0 if cleared */ +}; + +extern struct client_instance this; + 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_set_topdomain(const char *cp); -void client_set_password(const char *cp); +void client_rotate_nameserver(); 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_lazymode(int lazy_mode); -void client_set_hostname_maxlen(int i); +char *format_qtype(); +char parse_encoding(char *encoding); +void client_set_hostname_maxlen(size_t i); -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 client_handshake(); +int client_tunnel(); + +int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate, int*); +int handshake_waitdns(char *buf, size_t buflen, char cmd, int timeout); +void handshake_switch_options(int lazy, int compression, char denc); +int send_ping(int ping_response, int ack, int timeout, int); #endif diff --git a/src/common.c b/src/common.c index ce6c2aa..4d70ab1 100644 --- a/src/common.c +++ b/src/common.c @@ -228,9 +228,15 @@ open_dns_from_host(char *host, int port, int addr_family, int flags) } void -close_dns(int fd) +close_socket(int fd) { + if (fd <= 0) + return; +#ifdef WINDOWS32 + closesocket(fd); +#else close(fd); +#endif } void @@ -282,7 +288,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 +318,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(); @@ -394,6 +403,77 @@ check_topdomain(char *str, char **errormsg) return 0; } +int +socket_set_blocking(int fd, int blocking) +{ + /* Set non-blocking socket mode */ +#ifdef WINDOWS32 + if (ioctlsocket(fd, FIONBIO, &blocking) != 0) { + return WSAGetLastError(); + } +#else + int flags; + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { + return flags; + } + + if (fcntl(fd, F_SETFL, !blocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))) == -1) + return errno; + +#endif + return 0; +} + +int +open_tcp_nonblocking(struct sockaddr_storage *addr, char **errormsg) +/* Open TCP connection to given address without blocking */ +{ + int fd, ret; + if ((fd = socket(addr->ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { + if (errormsg) + *errormsg = strerror(errno); + return -1; + } + + if ((ret = socket_set_blocking(fd, 0)) != 0) { + if (errormsg) + *errormsg = strerror(ret); + return -1; + } + + if ((ret = connect(fd, (struct sockaddr *)addr, sizeof(struct sockaddr_storage))) + == -1 && errno != EINPROGRESS) { + if (errormsg) + *errormsg = strerror(errno); + return -1; + } + + if (errormsg) + *errormsg = strerror(errno); + + return fd; +} + +int +check_tcp_error(int fd, char **error) +/* checks connected status of given socket. + * returns error code. 0 if connected or EINPROGRESS if connecting */ +{ + int errornum = 0; + socklen_t len = sizeof(int); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errornum, &len) != 0) { + if (error) + *error = "getsockopt failed."; + return -1; + } + + if (error) + *error = strerror(errornum); + + return errornum; +} + #if defined(WINDOWS32) || defined(ANDROID) #ifndef ANDROID int @@ -455,22 +535,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..81716f2 100644 --- a/src/common.h +++ b/src/common.h @@ -40,10 +40,37 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #include #include #include +#include #endif #define DNS_PORT 53 +#if _WIN32 || _WIN64 +#if _WIN64 +#define BITS_64 +#else +#define BITS_32 +#endif +#endif + +#if __GNUC__ +#if __x86_64__ || __ppc64__ +#define BITS_64 1 +#else +#define BITS_32 1 +#endif +#endif + +/* Determine appropriate format specifier for long int on 32/64 bit systems */ +#if BITS_64 +#define FMT_LONG "l" +#else +#define FMT_LONG "" +#endif + +/* For convenience and shortness */ +#define L FMT_LONG + #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif @@ -67,33 +94,57 @@ 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 (INSTANCE.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 (INSTANCE.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; }; enum connection { @@ -108,7 +159,10 @@ int get_addr(char *, int, int, int, struct sockaddr_storage *); int open_dns(struct sockaddr_storage *, size_t); int open_dns_opt(struct sockaddr_storage *sockaddr, size_t sockaddr_len, int v6only); int open_dns_from_host(char *host, int port, int addr_family, int flags); -void close_dns(int); +void close_socket(int); + +int open_tcp_nonblocking(struct sockaddr_storage *addr, char **error); +int check_tcp_error(int fd, char **error); void do_chroot(char *); void do_setcon(char *); @@ -119,6 +173,8 @@ void read_password(char*, size_t); int check_topdomain(char *, char **); +extern double difftime(time_t, time_t); + #if defined(WINDOWS32) || defined(ANDROID) #ifndef ANDROID int inet_aton(const char *cp, struct in_addr *inp); @@ -130,8 +186,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..6b206b9 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,29 @@ #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 +/* Don't push the limit with DNS servers: potentially unwanted behaviour + * if labels are all 63 chars long (DNS standard max label length) */ +#define DNS_MAXLABEL 59 + 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..3b79c9c 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -21,9 +21,10 @@ #include #include #include +#include #include #include -#include +#include #include #include @@ -34,16 +35,33 @@ #include #include #include +#include #endif #include "common.h" +#include "version.h" #include "tun.h" #include "client.h" #include "util.h" +#include "encoding.h" +#include "base32.h" #ifdef WINDOWS32 +#include "windows.h" WORD req_version = MAKEWORD(2, 2); WSADATA wsa_data; +#else +#include +#ifdef ANDROID +#include "android_dns.h" +#endif +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#include +#include +#include #endif #if !defined(BSD) && !defined(__GLIBC__) @@ -52,10 +70,139 @@ static char *__progname; #define PASSWORD_ENV_VAR "IODINE_PASS" +struct client_instance this; + +/* BEGIN PRESET DEFINITIONS */ + +/* static startup values - should not be changed in presets */ +#define PRESET_STATIC_VALUES \ + .conn = CONN_DNS_NULL, \ + .send_ping_soon = 1, \ + .maxfragsize_up = 100, \ + .next_downstream_ack = -1, \ + .num_immediate = 1, \ + .rtt_total_ms = 200, \ + .remote_forward_addr = {.ss_family = AF_UNSPEC} + +static struct client_instance preset_default = { + .raw_mode = 1, + .lazymode = 1, + .max_timeout_ms = 5000, + .send_interval_ms = 0, + .server_timeout_ms = 4000, + .downstream_timeout_ms = 2000, + .autodetect_server_timeout = 1, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = MAX_FRAGSIZE, + .compression_up = 1, + .compression_down = 1, + .windowsize_up = 8, + .windowsize_down = 8, + .hostname_maxlen = 0xFF, + .downenc = ' ', + .do_qtype = T_UNSET, + PRESET_STATIC_VALUES +}; + +static struct client_instance preset_original = { + .raw_mode = 0, + .lazymode = 1, + .max_timeout_ms = 4000, + .send_interval_ms = 0, + .server_timeout_ms = 3000, + .autodetect_server_timeout = 1, + .windowsize_down = 1, + .windowsize_up = 1, + .hostname_maxlen = 0xFF, + .downstream_timeout_ms = 4000, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = MAX_FRAGSIZE, + .compression_down = 1, + .compression_up = 0, + .downenc = ' ', + .do_qtype = T_UNSET, + PRESET_STATIC_VALUES +}; + +static struct client_instance preset_fast = { + .raw_mode = 0, + .lazymode = 1, + .max_timeout_ms = 3000, + .send_interval_ms = 0, + .server_timeout_ms = 2500, + .downstream_timeout_ms = 100, + .autodetect_server_timeout = 1, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = 1176, + .compression_up = 1, + .compression_down = 1, + .windowsize_up = 30, + .windowsize_down = 30, + .hostname_maxlen = 0xFF, + .downenc = ' ', + .do_qtype = T_UNSET, + PRESET_STATIC_VALUES +}; + +static struct client_instance preset_fallback = { + .raw_mode = 1, + .lazymode = 1, + .max_timeout_ms = 1000, + .send_interval_ms = 20, + .server_timeout_ms = 500, + .downstream_timeout_ms = 1000, + .autodetect_server_timeout = 1, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = 500, + .compression_up = 1, + .compression_down = 1, + .windowsize_up = 1, + .windowsize_down = 1, + .hostname_maxlen = 100, + .downenc = 'T', + .do_qtype = T_CNAME, + PRESET_STATIC_VALUES +}; + +#define NUM_CLIENT_PRESETS 4 + +static struct { + struct client_instance *preset_data; + char short_name; + char *desc; +} client_presets[NUM_CLIENT_PRESETS] = { + { + .preset_data = &preset_default, + .short_name = 'D', + .desc = "Defaults" + }, + { + .preset_data = &preset_original, + .short_name = '7', + .desc = "Imitate iodine 0.7" + }, + { + .preset_data = &preset_fast, + .short_name = 'F', + .desc = "Fast and low latency" + }, + { + .preset_data = &preset_fallback, + .short_name = 'M', + .desc = "Minimal DNS queries and short DNS timeouts" + } +}; + +/* END PRESET DEFINITIONS */ + static void sighandler(int sig) { - client_stop(); + this.running = 0; } #if defined(__GNUC__) || defined(__clang__) @@ -65,121 +212,181 @@ 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] [-Y preset] [-V sec] [-X port] [-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 port[,host] ] " + "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [...]]]\n", __progname); +} + +static void +usage() +{ + print_usage(); exit(2); } static void -help() { - extern char *__progname; +print_presets(int spaces) +{ +#define INDENT fprintf(stderr, "%*s", spaces, ""); + INDENT fprintf(stderr, "Available presets: (use -Y )\n"); + spaces += 2; + for (int i = 0; i < NUM_CLIENT_PRESETS; i++) { + INDENT fprintf(stderr, "'%c': %s\n", client_presets[i].short_name, client_presets[i].desc); + } +} +static void +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"); - 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"); + print_usage(); + fprintf(stderr, "\nOptions to try if connection doesn't work:\n"); + fprintf(stderr, " -T use DNS type: NULL, PRIVATE, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); + fprintf(stderr, " -O use specific downstream encoding for queries: Base32, Base64, Base64u,\n"); + fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\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, " -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 skip raw UDP mode attempt\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, " --nodrop disable TCP packet-dropping optimisations\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, " -f to keep running in foreground\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, " -v, --version print version info and exit\n"); + fprintf(stderr, " -h, --help print this help and exit\n"); + fprintf(stderr, " -V, --stats print connection statistics at given intervals (default: 5 sec)\n"); + fprintf(stderr, " -f keep running in foreground\n"); + fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); + fprintf(stderr, " -d set tunnel device name\n"); + fprintf(stderr, " -u drop privileges and run as specified user\n"); + fprintf(stderr, " -F write PID to specified file\n"); + fprintf(stderr, " -Y, --preset use a set of predefined options for DNS tunnel (can be overridden manually)\n"); + print_presets(6); + fprintf(stderr, " -R, --remote [host:]port skip tun device and forward data to/from\n"); + fprintf(stderr, " stdin/out, telling iodined to forward data to a remote port\n"); + fprintf(stderr, " locally or to a specific host (accessed by server). Implies --nodrop.\n"); + fprintf(stderr, " To specify an IPv6 address, host must be enclosed in square brackets.\n"); + fprintf(stderr, " Can be used with SSH ProxyCommand option. ('iodine -R 22 ...')\n"); + fprintf(stderr, " --chroot chroot to given directory\n"); + fprintf(stderr, " --context apply specified SELinux context after initialization\n"); + fprintf(stderr, " --rdomain use specified routing domain (OpenBSD only)\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); } +static int +parse_tcp_forward_option(char *optstr) +{ + char *remote_port_str, *remote_host_str; + int retval; + + if (strrchr(optstr, ':')) { + remote_port_str = strrchr(optstr, ':') + 1; + if (optstr[0] == '[') { + /* IPv6 address enclosed in square brackets */ + remote_host_str = optstr + 1; + /* replace closing bracket with null terminator */ + *strchr(remote_host_str, ']') = 0; + this.remote_forward_addr.ss_family = AF_INET6; + retval = inet_pton(AF_INET6, remote_host_str, + &((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_addr); + } else { + remote_host_str = optstr; + /* replace separator with null terminator */ + *strchr(remote_host_str, ':') = 0; + this.remote_forward_addr.ss_family = AF_INET; + retval = inet_aton(remote_host_str, + &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr); + } + } else { + /* no address specified (use server localhost IPv4), optstr is port */ + remote_port_str = optstr; + this.remote_forward_addr.ss_family = AF_INET; + ((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + retval = 1; + } + + if (!retval) { + warnx("Invalid remote forward address (-R)! Must be [host:]port,\n" + "where IPv6 addresses are enclosed in literal square brackets []."); + usage(); + /* not reached */ + } + + /* parse port */ + int port = atoi(remote_port_str); + if (port < 1 || port > 65535) { + fprintf(stderr, "Remote forward (-R) TCP port must be between 1 and 65535."); + usage(); + /* not reached */ + } + + if (this.remote_forward_addr.ss_family == AF_INET) { + /* set port as sockaddr_in (IPv4) */ + ((struct sockaddr_in *) &this.remote_forward_addr)->sin_port = htons(port); + } else { + /* set port in IPv6 sockaddr */ + ((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_port = htons(port); + } + return port; +} + int main(int argc, char **argv) { - char *nameserv_host; - char *topdomain; - char *errormsg; + char *errormsg = NULL; #ifndef WINDOWS32 - struct passwd *pw; -#endif - char *username; - char password[33]; - int foreground; - char *newroot; - char *context; - char *device; - char *pidfile; - int choice; - int tun_fd; - int dns_fd; - int max_downstream_frag_size; - int autodetect_frag_size; - int retval; - int raw_mode; - int lazymode; - int selecttimeout; - int hostname_maxlen; -#ifdef OPENBSD - int rtable = 0; + struct passwd *pw = NULL; #endif + int choice = -1; + int retval = 0; + + char *username = NULL; + char *newroot = NULL; + char *context = NULL; + char *device = NULL; + char *pidfile = NULL; + + int remote_forward_port = 0; + + char *nameserv_host = NULL; struct sockaddr_storage nameservaddr; - int nameservaddr_len; - int nameserv_family; - - nameserv_host = NULL; - topdomain = NULL; - errormsg = NULL; -#ifndef WINDOWS32 - pw = NULL; -#endif - username = NULL; - memset(password, 0, 33); - srand(time(NULL)); - foreground = 0; - newroot = NULL; - context = NULL; - device = NULL; - pidfile = NULL; - - autodetect_frag_size = 1; - max_downstream_frag_size = 3072; - retval = 0; - raw_mode = 1; - lazymode = 1; - selecttimeout = 4; - hostname_maxlen = 0xFF; - nameserv_family = AF_UNSPEC; + int nameservaddr_len = 0; + int nameserv_family = AF_UNSPEC; #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); #endif - srand((unsigned) time(NULL)); - client_init(); - #if !defined(BSD) && !defined(__GLIBC__) __progname = strrchr(argv[0], '/'); if (__progname == NULL) @@ -188,8 +395,76 @@ 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) { - switch(choice) { +#define OPT_RDOMAIN 0x80 +#define OPT_NODROP 0x81 + + /* each option has format: + * char *name, int has_arg, int *flag, int val */ + static struct option iodine_args[] = { + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"stats", optional_argument, 0, 'V'}, + {"context", required_argument, 0, 'z'}, + {"rdomain", required_argument, 0, OPT_RDOMAIN}, + {"chrootdir", required_argument, 0, 't'}, + {"preset", required_argument, 0, 'Y'}, + {"proxycommand", no_argument, 0, 'R'}, +// {"nodrop", no_argument, 0, OPT_NODROP}, + {"remote", required_argument, 0, 'R'}, + {NULL, 0, 0, 0} + }; + + /* Pre-parse command line to get preset + * This is so that all options override preset values regardless of order in command line */ + int optind_orig = optind, preset_id = -1; + + static char *iodine_args_short = "46vfDhrY:s:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:"; + + while ((choice = getopt_long(argc, argv, iodine_args_short, iodine_args, NULL))) { + /* Check if preset has been found yet so we don't process any other options */ + if (preset_id < 0) { + if (choice == -1) { + /* reached end of command line and no preset specified - use default */ + preset_id = 0; + } else if (choice == 'Y') { + /* find index of preset */ + if (optarg) { + for (int i = 0; i < NUM_CLIENT_PRESETS; i++) { + if (toupper(optarg[0]) == client_presets[i].short_name) { + preset_id = i; + break; + } + } + } + } else if (choice == '?') { + usage(); + /* Not reached */ + } else { + /* skip all other options until we find preset */ + continue; + } + + if (preset_id < 0) { + /* invalid preset or none specified */ + fprintf(stderr, "Invalid preset or none specified with -Y or --preset!\n"); + print_presets(2); + usage(); + /* not reached */ + } + + memcpy(&this, client_presets[preset_id].preset_data, sizeof(struct client_instance)); + + /* Reset optind to reparse command line */ + optind = optind_orig; + continue; + } else if (choice == -1) { + break; + } + + /* Once a preset is used, it is copied into memory. This way other + * options can override preset values regardless of order in command line */ + + switch (choice) { case '4': nameserv_family = AF_INET; break; @@ -200,15 +475,23 @@ main(int argc, char **argv) version(); /* NOTREACHED */ break; + case 'V': + this.stats = atoi(optarg); + if (this.stats < 0) + this.stats = 0; + break; case 'f': - foreground = 1; + this.foreground = 1; + break; + case 'D': + this.debug++; break; case 'h': help(); /* NOTREACHED */ break; case 'r': - raw_mode = 0; + this.raw_mode = 0; break; case 'u': username = optarg; @@ -220,27 +503,36 @@ main(int argc, char **argv) device = optarg; break; #ifdef OPENBSD - case 'R': + case OPT_RDOMAIN: rtable = atoi(optarg); break; #endif + case 'R': + /* Argument format: [host:]port */ + if (!optarg) break; + this.use_remote_forward = 1; + remote_forward_port = parse_tcp_forward_option(optarg); + break; + case OPT_NODROP: + // TODO implement TCP-over-tun optimisations + break; case 'P': - strncpy(password, optarg, sizeof(password)); - password[sizeof(password)-1] = 0; + strncpy(this.password, optarg, sizeof(this.password)); + this.password[sizeof(this.password)-1] = 0; /* XXX: find better way of cleaning up ps(1) */ memset(optarg, 0, strlen(optarg)); break; case 'm': - autodetect_frag_size = 0; - max_downstream_frag_size = atoi(optarg); + this.autodetect_frag_size = 0; + this.max_downstream_frag_size = atoi(optarg); break; case 'M': - hostname_maxlen = atoi(optarg); - if (hostname_maxlen > 255) - hostname_maxlen = 255; - if (hostname_maxlen < 10) - hostname_maxlen = 10; + this.hostname_maxlen = atoi(optarg); + if (this.hostname_maxlen > 255) + this.hostname_maxlen = 255; + if (this.hostname_maxlen < 10) + this.hostname_maxlen = 10; break; case 'z': context = optarg; @@ -252,104 +544,184 @@ 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 */ - client_set_downenc(optarg); + case 'O': + if ((this.downenc = parse_encoding(optarg)) == 0) + errx(6, "Invalid encoding type '%s'", optarg); break; case 'L': - lazymode = atoi(optarg); - if (lazymode > 1) - lazymode = 1; - if (lazymode < 0) - lazymode = 0; - if (!lazymode) - selecttimeout = 1; + this.lazymode = atoi(optarg); + if (this.lazymode > 1) + this.lazymode = 1; + if (this.lazymode < 0) + this.lazymode = 0; break; case 'I': - selecttimeout = atoi(optarg); - if (selecttimeout < 1) - selecttimeout = 1; + this.max_timeout_ms = strtod(optarg, NULL) * 1000; + if (this.autodetect_server_timeout) { + this.server_timeout_ms = this.max_timeout_ms / 2; + } break; + case 'i': + this.server_timeout_ms = strtod(optarg, NULL) * 1000; + this.autodetect_server_timeout = 0; + break; + case 'j': + this.downstream_timeout_ms = strtod(optarg, NULL) * 1000; + if (this.autodetect_server_timeout) { + this.autodetect_server_timeout = 0; + this.server_timeout_ms = 4000; + } + break; + case 's': + this.send_interval_ms = atoi(optarg); + if (this.send_interval_ms < 0) + this.send_interval_ms = 0; + case 'w': + this.windowsize_down = atoi(optarg); + break; + case 'W': + this.windowsize_up = atoi(optarg); + break; + case 'c': + this.compression_down = atoi(optarg) & 1; + break; + case 'C': + this.compression_up = atoi(optarg) & 1; + break; + case 'Y': + /* Already processed preset: ignore */ + continue; default: usage(); /* NOTREACHED */ } } + srand((unsigned) time(NULL)); + this.rand_seed = (uint16_t) rand(); + this.chunkid = (uint16_t) rand(); + this.running = 1; + check_superuser(usage); 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 (this.debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", this.debug); + fprintf(stderr, "Add more -D switches to set higher debug level.\n"); + this.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 */ - } - if (nameserv_host) { + + this.nameserv_hosts_len = argc - 1; + if (this.nameserv_hosts_len <= 0) + /* if no hosts specified, use resolv.conf */ + this.nameserv_hosts_len = 1; + + // Preallocate memory with expected number of hosts + this.nameserv_hosts = malloc(sizeof(char *) * this.nameserv_hosts_len); + this.nameserv_addrs = malloc(sizeof(struct sockaddr_storage) * this.nameserv_hosts_len); + + if (argc == 0) { + usage(); + /* NOT REACHED */ + } else if (argc == 1) { + this.nameserv_hosts[0] = get_resolvconf_addr(); + } else if (argc > 1) + for (int h = 0; h < this.nameserv_hosts_len; h++) + this.nameserv_hosts[h] = strdup(argv[h + 1]); + this.topdomain = strdup(argv[0]); + + for (int n = 0; n < this.nameserv_hosts_len; n++) { + nameserv_host = this.nameserv_hosts[n]; + if (!nameserv_host) { + errx(1, "Error processing nameserver hostnames!"); + } 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 { - warnx("No nameserver found - not connected to any network?\n"); + memcpy(&this.nameserv_addrs[n], &nameservaddr, sizeof(struct sockaddr_storage)); + this.nameserv_addrs_len ++; + nameserv_host = NULL; + } + + if (this.nameserv_addrs_len <= 0 || !this.nameserv_hosts[0]) { + warnx("No nameservers found - not connected to any network?"); + usage(); + } + + if (this.max_downstream_frag_size < 10 || this.max_downstream_frag_size > MAX_FRAGSIZE) { + warnx("Use a max frag size between 10 and %d bytes.", MAX_FRAGSIZE); usage(); /* NOTREACHED */ } - if(check_topdomain(topdomain, &errormsg)) { + if(check_topdomain(this.topdomain, &errormsg)) { warnx("Invalid topdomain: %s", errormsg); usage(); /* NOTREACHED */ } - client_set_selecttimeout(selecttimeout); - client_set_lazymode(lazymode); - client_set_topdomain(topdomain); - client_set_hostname_maxlen(hostname_maxlen); + int max_ws = MAX_SEQ_ID / 2; + if (this.windowsize_up < 1 || this.windowsize_down < 1 || + this.windowsize_up > max_ws || this.windowsize_down > max_ws) { + warnx("Window sizes (-w or -W) must be between 0 and %d!", max_ws); + usage(); + } + + if (this.max_timeout_ms < 100) { + warnx("Target interval (-I) must be greater than 0.1 seconds!"); + usage(); + } + + if ((this.server_timeout_ms < 100 || this.server_timeout_ms >= this.max_timeout_ms) + && !this.autodetect_server_timeout) { + warnx("Server timeout (-i) must be greater than 0.1 sec and less than target interval!"); + usage(); + } + + if (this.downstream_timeout_ms < 100) { + warnx("Downstream fragment timeout must be more than 0.1 sec to prevent excessive retransmits."); + usage(); + } + + if (!this.lazymode && this.max_timeout_ms > 1000) { + fprintf(stderr, "Warning: Target interval of >1 second in immediate mode will cause high latency.\n"); + } if (username != NULL) { #ifndef WINDOWS32 if ((pw = getpwnam(username)) == NULL) { - warnx("User %s does not exist!\n", username); + warnx("User %s does not exist!", username); usage(); /* NOTREACHED */ } +#else + warnx("Warning: Cannot switch user on Windows systems."); #endif } - if (strlen(password) == 0) { + if (strlen(this.password) == 0) { if (NULL != getenv(PASSWORD_ENV_VAR)) - snprintf(password, sizeof(password), "%s", getenv(PASSWORD_ENV_VAR)); + snprintf(this.password, sizeof(this.password), "%s", getenv(PASSWORD_ENV_VAR)); else - read_password(password, sizeof(password)); + read_password(this.password, sizeof(this.password)); } - client_set_password(password); - - if ((tun_fd = open_tun(device)) == -1) { - retval = 1; - goto cleanup1; + if (!this.use_remote_forward) { + if ((this.tun_fd = open_tun(device)) == -1) { + retval = 1; + goto cleanup; + } } - if ((dns_fd = open_dns_from_host(NULL, 0, nameservaddr.ss_family, AI_PASSIVE)) < 0) { + + if ((this.dns_fd = open_dns_from_host(NULL, 0, nameservaddr.ss_family, AI_PASSIVE)) < 0) { retval = 1; - goto cleanup2; + goto cleanup; } #ifdef OPENBSD if (rtable > 0) @@ -359,21 +731,28 @@ 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 ", this.topdomain); + for (int a = 0; a < this.nameserv_addrs_len; a++) + fprintf(stderr, "%s%s", format_addr(&this.nameserv_addrs[a], sizeof(struct sockaddr_storage)), + (a != this.nameserv_addrs_len - 1) ? ", " : ""); + fprintf(stderr, "\n"); - if (client_handshake(dns_fd, raw_mode, autodetect_frag_size, max_downstream_frag_size)) { + if (this.remote_forward_addr.ss_family != AF_UNSPEC) + fprintf(stderr, "Requesting TCP data forwarding from server to %s:%d\n", + format_addr(&this.remote_forward_addr, sizeof(struct sockaddr_storage)), remote_forward_port); + + if (client_handshake()) { retval = 1; - goto cleanup2; + goto cleanup; } - if (client_get_conn() == CONN_RAW_UDP) { - fprintf(stderr, "Sending raw traffic directly to %s\n", client_get_raw_addr()); + if (this.conn == CONN_RAW_UDP) { + fprintf(stderr, "Sending raw UDP traffic directly to %s\n", client_get_raw_addr()); } fprintf(stderr, "Connection setup complete, transmitting data.\n"); - if (foreground == 0) + if (this.foreground == 0) do_detach(); if (pidfile != NULL) @@ -397,12 +776,16 @@ main(int argc, char **argv) if (context != NULL) do_setcon(context); - client_tunnel(tun_fd, dns_fd); + client_tunnel(); -cleanup2: - close_dns(dns_fd); - close_tun(tun_fd); -cleanup1: +cleanup: + if (this.use_remote_forward) + close(STDOUT_FILENO); + close_socket(this.dns_fd); + close_socket(this.tun_fd); +#ifdef WINDOWS32 + WSACleanup(); +#endif return retval; } diff --git a/src/iodined.c b/src/iodined.c index 2e6f7bf..437641a 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 @@ -27,13 +28,16 @@ #include #include #include +#include #include "common.h" +#include "version.h" #ifdef WINDOWS32 #include "windows.h" #include #else +#include #include #ifdef DARWIN #define BIND_8_COMPAT @@ -60,6 +64,7 @@ #include "tun.h" #include "fw_query.h" #include "version.h" +#include "server.h" #ifdef HAVE_SYSTEMD # include @@ -68,66 +73,39 @@ #ifdef WINDOWS32 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) +#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, ...) { - if (addr->ss_family == AF_INET6) { - return fds->v6fd; - } - return fds->v4fd; + /* TODO: implement (add to event log), move to common.c */ + ; } +#endif + +/* Definition of main server instance */ +struct server_instance server; + +static struct server_instance preset_default = { + .check_ip = 1, + .netmask = 27, + .ns_ip = INADDR_ANY, + .mtu = 1130, /* Very many relays give fragsize 1150 or slightly + higher for NULL; tun/zlib adds ~17 bytes. */ + .port = 53, + .addrfamily = AF_UNSPEC, + + /* Mark both file descriptors as unused */ + .dns_fds.v4fd = -1, + .dns_fds.v6fd = -1 +}; /* Ask ipify.org webservice to get external ip */ static int @@ -185,2118 +163,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.running = 0; } 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 [options] [-v] [-h] [-4] [-6] [-c] [-s] [-f] [-D] " + "[-u user] [-d device] [-m mtu] " + "[-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 @@ -2309,31 +187,33 @@ static void help() { fprintf(stderr, "iodine IP over DNS tunneling server\n"); print_usage(); - fprintf(stderr, " -v to print version info and exit\n"); - fprintf(stderr, " -h to print this help and exit\n"); - fprintf(stderr, " -4 to listen only on IPv4\n"); - fprintf(stderr, " -6 to listen only on IPv6\n"); - fprintf(stderr, " -c to disable check of client IP/port on each request\n"); - fprintf(stderr, " -s to skip creating and configuring the tun device, " + fprintf(stderr, " -v, --version print version info and exit\n"); + fprintf(stderr, " -h, --help print this help and exit\n"); + fprintf(stderr, " -4 listen only on IPv4\n"); + fprintf(stderr, " -6 listen only on IPv6\n"); + fprintf(stderr, " -c, --noipcheck disable check of client IP/port on each request\n"); + fprintf(stderr, " -s, --notun skip creating and configuring the tun device, " "which then has to be created manually\n"); - fprintf(stderr, " -f to keep running in foreground\n"); - fprintf(stderr, " -D to increase debug level\n"); + fprintf(stderr, " -f to keep running in foreground\n"); + fprintf(stderr, " -D increase debug level\n"); fprintf(stderr, " (using -DD in UTF-8 terminal: \"LC_ALL=C luit iodined -DD ...\")\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, " -m mtu to set tunnel device mtu\n"); - fprintf(stderr, " -z context to apply SELinux context after initialization\n"); - fprintf(stderr, " -l IPv4 address to listen on for incoming dns traffic " + fprintf(stderr, " -u, --user drop privileges and run as user\n"); + fprintf(stderr, " --chrootdir chroot to directory after init\n"); + fprintf(stderr, " -d specify tunnel device name\n"); + fprintf(stderr, " -m, --mtu specify tunnel device mtu\n"); + fprintf(stderr, " --context apply SELinux context after initialization\n"); + fprintf(stderr, " -l, --listen4 IPv4 address to listen on for incoming dns traffic " "(default 0.0.0.0)\n"); - fprintf(stderr, " -L IPv6 address to listen on for incoming dns traffic " + fprintf(stderr, " -L, --listen6 IPv6 address to listen on for incoming dns traffic " "(default ::)\n"); - fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); - fprintf(stderr, " -n ip to respond with to NS queries\n"); - fprintf(stderr, " -b port to forward normal DNS queries to (on localhost)\n"); - fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); - fprintf(stderr, " -F pidfile to write pid to a file\n"); - fprintf(stderr, " -i maximum idle time before shutting down\n"); + fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); + fprintf(stderr, " -n, --nsip ip to respond with to NS queries\n"); + fprintf(stderr, " -b, --forwardto forward normal DNS queries to a UDP port on localhost\n"); + fprintf(stderr, " -A, --localforward allow TCP data pipe to local ports only (default: disabled)\n"); + fprintf(stderr, " -R, --remoteforward allow TCP data pipe to remote hosts (default: disabled)\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + fprintf(stderr, " -F, --pidfile write pid to a file\n"); + fprintf(stderr, " -i, --idlequit maximum idle time before shutting down\n"); fprintf(stderr, "tunnel_ip is the IP number of the local tunnel interface.\n"); fprintf(stderr, " /netmask sets the size of the tunnel network.\n"); fprintf(stderr, "topdomain is the FQDN that is delegated to this server.\n"); @@ -2343,7 +223,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); } @@ -2373,7 +253,7 @@ main(int argc, char **argv) char *listen_ip6; char *errormsg; #ifndef WINDOWS32 - struct passwd *pw; + struct passwd *pw = NULL; #endif int foreground; char *username; @@ -2381,60 +261,31 @@ main(int argc, char **argv) char *context; char *device; char *pidfile; - int addrfamily; - struct dnsfd dns_fds; - int tun_fd; - - /* settings for forwarding normal DNS to - * local real DNS server */ - int bind_fd; - int bind_enable; int choice; - int port; - int mtu; + int skipipconfig; char *netsize; int ns_get_externalip; int retval; - int max_idle_time = 0; - struct sockaddr_storage dns4addr; - int dns4addr_len; - struct sockaddr_storage dns6addr; - int dns6addr_len; + #ifdef HAVE_SYSTEMD int nb_fds; #endif -#ifndef WINDOWS32 - pw = NULL; -#endif errormsg = NULL; username = NULL; newroot = NULL; context = NULL; device = NULL; foreground = 0; - bind_enable = 0; - bind_fd = 0; - mtu = 1130; /* Very many relays give fragsize 1150 or slightly - higher for NULL; tun/zlib adds ~17 bytes. */ 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(); + ns_get_externalip = 0; + skipipconfig = 0; + pidfile = NULL; + srand(time(NULL)); retval = 0; @@ -2450,23 +301,48 @@ main(int argc, char **argv) __progname++; #endif - memset(password, 0, sizeof(password)); - srand(time(NULL)); - fw_query_init(); + // Load default values from preset + memcpy(&server, &preset_default, sizeof(struct server_instance)); - while ((choice = getopt(argc, argv, "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { + /* each option has format: + char *name, int has_arg, int *flag, int val */ + static struct option iodined_args[] = { + {"version", no_argument, 0, 'v'}, + {"noipcheck", no_argument, 0, 'c'}, + {"notun", no_argument, 0, 's'}, + {"user", required_argument, 0, 'u'}, + {"listen4", required_argument, 0, 'l'}, + {"listen6", required_argument, 0, 'L'}, + {"nsip", required_argument, 0, 'n'}, + {"mtu", required_argument, 0, 'm'}, + {"idlequit", required_argument, 0, 'i'}, + {"forwardto", required_argument, 0, 'b'}, + {"localforward", no_argument, 0, 'A'}, + {"remoteforward", no_argument, 0, 'R'}, + {"help", no_argument, 0, 'h'}, + {"context", required_argument, 0, 'z'}, + {"chrootdir", required_argument, 0, 't'}, + {"pidfile", required_argument, 0, 'F'}, + {NULL, 0, 0, 0} + }; + + static char *iodined_args_short = "46vcsfhDARu:t:d:m:l:L:p:n:b:P:z:F:i:"; + + server.running = 1; + + while ((choice = getopt_long(argc, argv, iodined_args_short, iodined_args, NULL)) != -1) { switch(choice) { case '4': - addrfamily = AF_INET; + server.addrfamily = AF_INET; break; case '6': - addrfamily = AF_INET6; + server.addrfamily = AF_INET6; break; case 'v': version(); break; case 'c': - check_ip = 0; + server.check_ip = 0; break; case 's': skipipconfig = 1; @@ -2478,7 +354,7 @@ main(int argc, char **argv) help(); break; case 'D': - debug++; + server.debug++; break; case 'u': username = optarg; @@ -2490,7 +366,7 @@ main(int argc, char **argv) device = optarg; break; case 'm': - mtu = atoi(optarg); + server.mtu = atoi(optarg); break; case 'l': listen_ip4 = optarg; @@ -2499,28 +375,35 @@ main(int argc, char **argv) listen_ip6 = optarg; break; case 'p': - port = atoi(optarg); + server.port = atoi(optarg); break; case 'n': if (optarg && strcmp("auto", optarg) == 0) { ns_get_externalip = 1; } else { - ns_ip = inet_addr(optarg); + server.ns_ip = inet_addr(optarg); } break; case 'b': - bind_enable = 1; - bind_port = atoi(optarg); + server.bind_enable = 1; + server.bind_port = atoi(optarg); + break; + case 'A': + server.allow_forward_local_port = 1; + break; + case 'R': + server.allow_forward_local_port = 1; + server.allow_forward_remote = 1; break; case 'F': pidfile = optarg; break; case 'i': - max_idle_time = atoi(optarg); + server.max_idle_time = atoi(optarg); break; case 'P': - strncpy(password, optarg, sizeof(password)); - password[sizeof(password)-1] = 0; + strncpy(server.password, optarg, sizeof(server.password)); + server.password[sizeof(server.password)-1] = 0; /* XXX: find better way of cleaning up ps(1) */ memset(optarg, 0, strlen(optarg)); @@ -2546,18 +429,18 @@ main(int argc, char **argv) if (netsize) { *netsize = 0; netsize++; - netmask = atoi(netsize); + server.netmask = atoi(netsize); } - my_ip = inet_addr(argv[0]); + server.my_ip = inet_addr(argv[0]); - if (my_ip == INADDR_NONE) { + if (server.my_ip == INADDR_NONE) { warnx("Bad IP address to use inside tunnel."); usage(); } - topdomain = strdup(argv[1]); - if(check_topdomain(topdomain, &errormsg)) { + server.topdomain = strdup(argv[1]); + if(check_topdomain(server.topdomain, &errormsg)) { warnx("Invalid topdomain: %s", errormsg); usage(); /* NOTREACHED */ @@ -2572,54 +455,59 @@ main(int argc, char **argv) #endif } - if (mtu <= 0) { + if (server.mtu <= 0) { warnx("Bad MTU given."); usage(); } - if(port < 1 || port > 65535) { + if(server.port < 1 || server.port > 65535) { warnx("Bad port number given."); usage(); } - if (port != 53) { + if (server.port != 53) { fprintf(stderr, "ALERT! Other dns servers expect you to run on port 53.\n"); - fprintf(stderr, "You must manually forward port 53 to port %d for things to work.\n", port); + fprintf(stderr, "You must manually forward port 53 to port %d for things to work.\n", server.port); } - if (debug) { - fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", debug); + if (server.debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", server.debug); fprintf(stderr, "Add more -D switches to set higher debug level.\n"); 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 (server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET) { + server.dns4addr_len = get_addr(listen_ip4, server.port, AF_INET, + AI_PASSIVE | AI_NUMERICHOST, &server.dns4addr); + if (server.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 (server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET6) { + server.dns6addr_len = get_addr(listen_ip6, server.port, AF_INET6, + AI_PASSIVE | AI_NUMERICHOST, &server.dns6addr); + if (server.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) { + if (server.bind_enable) { + in_addr_t dns_ip = ((struct sockaddr_in *) &server.dns4addr)->sin_addr.s_addr; + if (server.bind_port < 1 || server.bind_port > 65535) { warnx("Bad DNS server port number given."); usage(); /* NOTREACHED */ } /* Avoid forwarding loops */ - if (bind_port == port && (dns_ip == INADDR_ANY || dns_ip == htonl(0x7f000001L))) { - warnx("Forward port is same as listen port (%d), will create a loop!", bind_port); + if (server.bind_port == server.port && (dns_ip == INADDR_ANY || dns_ip == INADDR_LOOPBACK)) { + warnx("Forward port is same as listen port (%d), will create a loop!", server.bind_port); fprintf(stderr, "Use -l to set listen ip to avoid this.\n"); usage(); /* NOTREACHED */ } fprintf(stderr, "Requests for domains outside of %s will be forwarded to port %d\n", - topdomain, bind_port); + server.topdomain, server.bind_port); } if (ns_get_externalip) { @@ -2629,39 +517,35 @@ main(int argc, char **argv) fprintf(stderr, "Failed to get external IP via web service.\n"); exit(3); } - ns_ip = extip.s_addr; + server.ns_ip = extip.s_addr; fprintf(stderr, "Using %s as external IP.\n", inet_ntoa(extip)); } - if (ns_ip == INADDR_NONE) { + if (server.ns_ip == INADDR_NONE) { warnx("Bad IP address to return as nameserver."); usage(); } - if (netmask > 30 || netmask < 8) { - warnx("Bad netmask (%d bits). Use 8-30 bits.", netmask); + if (server.netmask > 30 || server.netmask < 8) { + warnx("Bad netmask (%d bits). Use 8-30 bits.", server.netmask); usage(); } - if (strlen(password) == 0) { + if (strlen(server.password) == 0) { if (NULL != getenv(PASSWORD_ENV_VAR)) - snprintf(password, sizeof(password), "%s", getenv(PASSWORD_ENV_VAR)); + snprintf(server.password, sizeof(server.password), "%s", getenv(PASSWORD_ENV_VAR)); else - read_password(password, sizeof(password)); + read_password(server.password, sizeof(server.password)); } - /* Mark both file descriptors as unused */ - dns_fds.v4fd = -1; - dns_fds.v6fd = -1; + created_users = init_users(server.my_ip, server.netmask); - created_users = init_users(my_ip, netmask); - - if ((tun_fd = open_tun(device)) == -1) { + if ((server.tun_fd = open_tun(device)) == -1) { /* nothing to clean up, just return */ return 1; } if (!skipipconfig) { const char *other_ip = users_get_first_ip(); - if (tun_setip(argv[0], other_ip, netmask) != 0 || tun_setmtu(mtu) != 0) { + if (tun_setip(argv[0], other_ip, server.netmask) != 0 || tun_setmtu(server.mtu) != 0) { retval = 1; free((void*) other_ip); goto cleanup; @@ -2677,15 +561,15 @@ main(int argc, char **argv) goto cleanup; } else if (nb_fds == 0) { #endif - if ((addrfamily == AF_UNSPEC || addrfamily == AF_INET) && - (dns_fds.v4fd = open_dns(&dns4addr, dns4addr_len)) < 0) { + if ((server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET) && + (server.dns_fds.v4fd = open_dns(&server.dns4addr, server.dns4addr_len)) < 0) { retval = 1; goto cleanup; } - if ((addrfamily == AF_UNSPEC || addrfamily == AF_INET6) && + if ((server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET6) && /* Set IPv6 socket to V6ONLY */ - (dns_fds.v6fd = open_dns_opt(&dns6addr, dns6addr_len, 1)) < 0) { + (server.dns_fds.v6fd = open_dns_opt(&server.dns6addr, server.dns6addr_len, 1)) < 0) { retval = 1; goto cleanup; @@ -2714,25 +598,23 @@ main(int argc, char **argv) #endif /* Setup dns file descriptors to get destination IP address */ - if (dns_fds.v4fd >= 0) - prepare_dns_fd(dns_fds.v4fd); - if (dns_fds.v6fd >= 0) - prepare_dns_fd(dns_fds.v6fd); + if (server.dns_fds.v4fd >= 0) + prepare_dns_fd(server.dns_fds.v4fd); + if (server.dns_fds.v6fd >= 0) + prepare_dns_fd(server.dns_fds.v6fd); - if (bind_enable) { - if ((bind_fd = open_dns_from_host(NULL, 0, AF_INET, 0)) < 0) { + if (server.bind_enable) { + if ((server.bind_fd = open_dns_from_host(NULL, 0, AF_INET, 0)) < 0) { retval = 1; goto cleanup; } } - my_mtu = mtu; - if (created_users < USERS) { fprintf(stderr, "Limiting to %d simultaneous users because of netmask /%d\n", - created_users, netmask); + created_users, server.netmask); } - fprintf(stderr, "Listening to dns for domain %s\n", topdomain); + fprintf(stderr, "Listening to dns for domain %s\n", server.topdomain); if (foreground == 0) do_detach(); @@ -2765,18 +647,20 @@ main(int argc, char **argv) if (context != NULL) do_setcon(context); - syslog(LOG_INFO, "started, listening on port %d", port); + syslog(LOG_INFO, "started, listening on port %d", server.port); - tunnel(tun_fd, &dns_fds, bind_fd, max_idle_time); + server_tunnel(); syslog(LOG_INFO, "stopping"); - close_dns(bind_fd); + close_socket(server.bind_fd); cleanup: - if (dns_fds.v6fd >= 0) - close_dns(dns_fds.v6fd); - if (dns_fds.v4fd >= 0) - close_dns(dns_fds.v4fd); - close_tun(tun_fd); + close_socket(server.dns_fds.v6fd); + close_socket(server.dns_fds.v4fd); + close_socket(server.tun_fd); +#ifdef WINDOWS32 + WSACleanup(); +#endif + /* TODO close user TCP forward sockets */ return retval; } 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..5c4bc88 --- /dev/null +++ b/src/server.c @@ -0,0 +1,2008 @@ +/* + * 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 + +#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 + +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 (server.debug >= l) {\ + TIMEPRINT("[QMEM u%d (%" L "u/%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 %" L "u 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(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 %" L "u of %" L "u + sending another %" L "u", + q->id, u->next_upstream_ack, sent, total, sending); + + send_data_or_ping(userid, q, 0, immediate, NULL); + + 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 (server.debug >= 5) { + time_t soonest_ms = timeval_to_ms(&soonest); + if (nextq && nextuser >= 0) { + QMEM_DEBUG(5, nextuser, "can wait for %" L "u 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 %" L "u 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(server.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; + } + + *(uint32_t *) (out + 4) = htonl(payload); + out[8] = userid & 0xff; + + write_dns(fd, q, out, sizeof(out), users[userid].downenc); +} + +void +send_data_or_ping(int userid, struct query *q, int ping, int immediate, char *tcperror) +/* 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 + disconnect: whether to tell user that TCP socket is closed (NULL if OK or pointer to error message) */ +{ + uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; + size_t datalen, headerlen; + fragment *f = NULL; + struct frag_buffer *out, *in; + + in = users[userid].incoming; + out = users[userid].outgoing; + + window_tick(out); + + if (!tcperror) { + f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); + } else { + /* construct fake fragment containing error message. */ + fragment fr; + f = &fr; + memset(f, 0, sizeof(fragment)); + f->ack_other = -1; + f->len = strlen(tcperror); + memcpy(f->data, tcperror, f->len); + f->data[f->len] = 0; + f->start = 1; + f->end = 1; + DEBUG(2, "Sending ping with TCP forward disconnect; error: %s", f->data); + } + + /* 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 (tcperror) + pkt[2] |= (1 << 6); + + 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! (%" L "u)", datalen); + return; + } + if (f) + memcpy(pkt + headerlen, f->data, datalen); + + write_dns(get_dns_fd(&server.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 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(userid, pkt, datalen, compressed); + } +} + +static int +user_send_data(int userid, 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: %" L "u 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(&server.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 +user_send_tcp_disconnect(int userid, struct query *q, char *errormsg) +/* tell user that TCP socket has been disconnected */ +{ + users[userid].remote_forward_connected = -1; + close_socket(users[userid].remote_tcp_fd); + if (q == NULL) + q = qmem_get_next_response(userid); + if (q != NULL) { + send_data_or_ping(userid, q, 1, 0, errormsg); + users[userid].active = 0; + return 1; + } + users[userid].active = 0; + return 0; +} + +static int +tunnel_bind() +{ + 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(server.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(&server.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 ssize_t +tunnel_tcp(int userid) +{ + ssize_t len; + uint8_t buf[64*1024]; + char *errormsg = NULL; + + if (users[userid].remote_forward_connected != 1) { + DEBUG(2, "tunnel_tcp: user %d TCP socket not connected!", userid); + return 0; + } + + len = read(users[userid].remote_tcp_fd, buf, sizeof(buf)); + + DEBUG(5, "read %ld bytes on TCP", len); + if (len == 0) { + DEBUG(1, "EOF on TCP forward for user %d; closing connection.", userid); + errormsg = "Connection closed by remote host."; + user_send_tcp_disconnect(userid, NULL, errormsg); + return -1; + } else if (len < 0) { + errormsg = strerror(errno); + DEBUG(1, "Error %d on TCP forward for user %d: %s", errno, userid, errormsg); + user_send_tcp_disconnect(userid, NULL, errormsg); + return -1; + } + + user_send_data(userid, buf, (size_t) len, 0); + return len; +} + +static int +tunnel_tun() +{ + struct ip *header; + static uint8_t in[64*1024]; + int userid; + int read; + + if ((read = read_tun(server.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, in, read, 0); +} + +static int +tunnel_dns(int dns_fd) +{ + struct query q; + int read; + int domain_len; + int inside_topdomain = 0; + + if ((read = read_dns(dns_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(server.topdomain); + if (domain_len >= 0 && !strcasecmp(q.name + domain_len, server.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(dns_fd, &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 (server.bind_fd) { + forward_query(server.bind_fd, &q); + } + } + return 0; +} + +int +server_tunnel() +{ + struct timeval tv; + fd_set read_fds, write_fds; + int i; + int userid; + struct query *answer_now = NULL; + time_t last_action = time(NULL); + + if (server.debug >= 5) + window_debug = server.debug - 4; + + while (server.running) { + int maxfd; + /* max wait time based on pending queries */ + tv = qmem_max_wait(&userid, &answer_now); + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + maxfd = 0; + + if (server.dns_fds.v4fd >= 0) { + FD_SET(server.dns_fds.v4fd, &read_fds); + maxfd = MAX(server.dns_fds.v4fd, maxfd); + } + if (server.dns_fds.v6fd >= 0) { + FD_SET(server.dns_fds.v6fd, &read_fds); + maxfd = MAX(server.dns_fds.v6fd, maxfd); + } + + if (server.bind_fd) { + /* wait for replies from real DNS */ + FD_SET(server.bind_fd, &read_fds); + maxfd = MAX(server.bind_fd, maxfd); + } + + /* Don't read from tun if all users have filled outpacket queues */ + if(!all_users_waiting_to_send()) { + FD_SET(server.tun_fd, &read_fds); + maxfd = MAX(server.tun_fd, maxfd); + } + + /* add connected user TCP forward FDs to read set */ + maxfd = MAX(set_user_tcp_fds(&read_fds, 1), maxfd); + + /* add connectING user TCP FDs to write set */ + maxfd = MAX(set_user_tcp_fds(&write_fds, 2), maxfd); + + i = select(maxfd + 1, &read_fds, &write_fds, NULL, &tv); + + if(i < 0) { + if (server.running) + warn("select"); + return 1; + } + + if (i == 0) { + if (server.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) > server.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) > server.max_idle_time) { + fprintf(stderr, "Server idle for too long, shutting down...\n"); + server.running = 0; + } + } + } + } else { + if (FD_ISSET(server.tun_fd, &read_fds)) { + tunnel_tun(); + } + + for (userid = 0; userid < created_users; userid++) { + if (FD_ISSET(users[userid].remote_tcp_fd, &read_fds) && users[userid].remoteforward_addr_len > 0) { + DEBUG(4, "tunnel_tcp called for user %d", userid); + tunnel_tcp(userid); + } else if (users[userid].remote_forward_connected == 2 && + FD_ISSET(users[userid].remote_tcp_fd, &write_fds)) { + DEBUG(2, "User %d TCP socket now writable (connection established)", userid); + users[userid].remote_forward_connected = 1; + } + } + + if (FD_ISSET(server.dns_fds.v4fd, &read_fds)) { + tunnel_dns(server.dns_fds.v4fd); + } + if (FD_ISSET(server.dns_fds.v6fd, &read_fds)) { + tunnel_dns(server.dns_fds.v6fd); + } + + if (FD_ISSET(server.bind_fd, &read_fds)) { + tunnel_bind(); + } + } + } + + return 0; +} + +void +handle_full_packet(int userid, uint8_t *data, size_t len, int compressed) +{ + size_t rawlen; + uint8_t out[64*1024], *rawdata; + struct ip *hdr; + int touser = -1; + 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) { + if (users[userid].remoteforward_addr_len == 0) { + hdr = (struct ip*) (out + 4); + touser = find_user_by_ip(hdr->ip_dst.s_addr); + DEBUG(2, "FULL PKT: %" L "u bytes from user %d (touser %d)", len, userid, touser); + if (touser == -1) { + /* send the uncompressed packet to tun device */ + write_tun(server.tun_fd, rawdata, rawlen); + } else { + /* don't re-compress if possible */ + if (users[touser].down_compression && compressed) { + user_send_data(touser, data, len, 1); + } else { + user_send_data(touser, rawdata, rawlen, 0); + } + } + } else { + /* Write full pkt to user's remote forward TCP stream */ + if ((ret = write(users[userid].remote_tcp_fd, rawdata, rawlen)) != rawlen) { + DEBUG(2, "Write error %d on TCP socket for user %d: %s", errno, userid, strerror(errno)); + } + } + + } 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 %" L "u < 16 bytes!", len); + return; + } + + if (userid < 0 || userid >= created_users || + check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { + DEBUG(2, "User %d not authenticated, ignoring raw login!", userid); + return; + } + + DEBUG(1, "RX-raw: login, len %" L "u, from user %d", len, userid); + + /* User sends hash of seed + 1 */ + login_calculate(myhash, 16, server.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, server.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, int userid) +{ + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 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 %" L "u, from user %d", len, userid); + + handle_full_packet(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, server.check_ip) != 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) +{ + 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 %" L "u", + 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, 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 query *q) +{ + 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)) { + 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, %" L "u 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); +} + +#define CHECK_LEN(l, x) \ + if (l < x) { \ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); \ + return; \ + } + +void +handle_dns_version(int dns_fd, struct query *q, uint8_t *domain, int domain_len) +{ + uint8_t unpacked[512]; + uint32_t version = !PROTOCOL_VERSION; + int userid, read; + + read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)domain + 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) { + 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; + } + + userid = find_available_user(); + if (userid < 0) { + /* 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)); + return; + } + + /* Reset user options to safe defaults */ + 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->remote_forward_connected = 0; + u->remoteforward_addr_len = 0; + u->remote_tcp_fd = 0; + u->remoteforward_addr.ss_family = AF_UNSPEC; + u->fragsize = 100; /* very safe */ + u->conn = CONN_DNS_NULL; + u->encoder = get_base32_encoder(); + u->down_compression = 1; + u->lazy = 0; + u->next_upstream_ack = -1; + u->outgoing->maxfraglen = u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR; + window_buffer_clear(u->outgoing); + window_buffer_clear(u->incoming); + qmem_init(userid); + + if (q->type == T_NULL || q->type == T_PRIVATE) { + u->downenc = 'R'; + u->downenc_bits = 8; + } else { + u->downenc = 'T'; + u->downenc_bits = 5; + } + + 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)); + + DEBUG(1, "User %d connected with correct version from %s.", + userid, format_addr(&q->from, q->fromlen)); + DEBUG(3, "User %d has login challenge 0x%08x", userid, u->seed); +} + +void +handle_dns_downstream_codec_check(int dns_fd, struct query *q, uint8_t *domain, int domain_len) +{ + char *datap; + int datalen, i, codec; + + i = b32_8to5(domain[2]); /* check variant: second char in b32 */ + + if (i == 1) { + datap = DOWNCODECCHECK1; + datalen = DOWNCODECCHECK1_LEN; + } else { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* codec to test: first char raw */ + codec = toupper(domain[1]); + switch (codec) { + 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, codec); + 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'); +} + +void +handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid) +{ + uint8_t unpacked[512], flags; + char logindata[16], *tmp[2], out[512], *reason = NULL; + char *errormsg = NULL, fromaddr[100]; + struct in_addr tempip; + char remote_tcp, remote_isnt_localhost, use_ipv6, poll_status; //, drop_packets; + int length = 17, read, addrlen, login_ok = 1; + uint16_t port; + struct tun_user *u = &users[userid]; + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &u->remoteforward_addr; + struct sockaddr_in *addr = (struct sockaddr_in *) &u->remoteforward_addr; + + read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *) domain + 2, domain_len - 2, b32); + + /* Decode flags and calculate min. length */ + flags = unpacked[0]; + remote_tcp = flags & 1; + remote_isnt_localhost = (flags & 2) >> 1; + use_ipv6 = (flags & 4) >> 2; + //drop_packets = (flags & 8) >> 3; /* currently unimplemented */ + poll_status = (flags & 0x10) >> 4; + addrlen = (remote_tcp && remote_isnt_localhost) ? (use_ipv6 ? 16 : 4) : 0; + + length += (remote_tcp ? 2 : 0) + addrlen; + + /* There should be no extra data if only polling forwarding status */ + if (poll_status) { + length = 17; + } + + CHECK_LEN(read, length); + + strncpy(fromaddr, format_addr(&q->from, q->fromlen), 100); + + DEBUG(2, "Received login request for user %d from %s", + userid, fromaddr); + + DEBUG(6, "Login: length=%d, flags=0x%02x, seed=0x%08x, hash=0x%016llx%016llx", + length, flags, u->seed, *(unsigned long long *) (unpacked + 1), + *(unsigned long long *) (unpacked + 9)); + + if (check_user_and_ip(userid, q, server.check_ip) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + syslog(LOG_WARNING, "rejected login request from user #%d from %s; expected source %s", + userid, fromaddr, format_addr(&u->host, u->hostlen)); + DEBUG(1, "Rejected login request from user %d: BADIP", userid); + return; + } + + /* Check remote host/port options */ + if ((addrlen > 0 && !server.allow_forward_remote) || + (remote_tcp && !server.allow_forward_local_port)) { + login_ok = 0; + reason = "requested bad TCP forward options"; + } + + u->last_pkt = time(NULL); + login_calculate(logindata, 16, server.password, u->seed); + + if (memcmp(logindata, unpacked + 1, 16) != 0) { + login_ok = 0; + reason = "bad password"; + } + + if (remote_tcp) { + port = ntohs(*(uint16_t *) (unpacked + 17)); + if (addrlen > 0) { + if (use_ipv6) { + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(port); + u->remoteforward_addr_len = sizeof(*addr6); + memcpy(&addr6->sin6_addr, unpacked + 19, MIN(sizeof(*addr6), addrlen)); + } else { + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + u->remoteforward_addr_len = sizeof(*addr); + memcpy(&addr->sin_addr, unpacked + 19, MIN(sizeof(*addr), addrlen)); + } + + DEBUG(1, "User %d requested TCP connection to %s:%hu, %s.", userid, + format_addr(&u->remoteforward_addr, u->remoteforward_addr_len), + port, login_ok ? "allowed" : "rejected"); + } else { + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + DEBUG(1, "User %d requested TCP connection to localhost:%hu, %s.", userid, + port, login_ok ? "allowed" : "rejected"); + } + } + + if (poll_status && login_ok) { + if (addrlen > 0 || (flags ^ 0x10)) { + login_ok = 0; + reason = "invalid flags"; + } + } + + if (!login_ok) { + write_dns(dns_fd, q, "LNAK", 4, 'T'); + if (--u->authenticated >= 0) + u->authenticated = -1; + int tries = abs(u->authenticated); + DEBUG(1, "rejected login from user %d (%s), tries: %d, reason: %s", + userid, fromaddr, tries, reason); + syslog(LOG_WARNING, "rejected login request from user #%d from %s, %s; incorrect attempts: %d", + userid, fromaddr, reason, tries); + return; + } + + /* Store user auth OK, count number of logins */ + u->authenticated++; + if (u->authenticated > 1 && !poll_status) + syslog(LOG_WARNING, "duplicate login request from user #%d from %s", + userid, fromaddr); + + if (remote_tcp) { + int tcp_fd; + + DEBUG(1, "User %d connected from %s, starting TCP connection to %s.", userid, + fromaddr, format_addr(&u->remoteforward_addr, sizeof(struct sockaddr_storage))); + syslog(LOG_NOTICE, "accepted password from user #%d, connecting TCP forward", userid); + + /* Open socket and connect to TCP forward host:port */ + tcp_fd = open_tcp_nonblocking(&u->remoteforward_addr, &errormsg); + if (tcp_fd < 0) { + if (!errormsg) + errormsg = "Error opening socket."; + goto tcp_forward_error; + } + + /* connection in progress */ + out[0] = 'W'; + read = 1; + write_dns(dns_fd, q, out, read + 1, u->downenc); + u->remote_tcp_fd = tcp_fd; + u->remote_forward_connected = 2; /* connecting */ + return; + } else if (poll_status) { + /* Check TCP forward connection status and update user data */ + int retval; + + /* Check for connection errors */ + if ((retval = check_tcp_error(u->remote_tcp_fd, &errormsg)) != 0) { + /* if unacceptable error, tell user */ + if (retval != EINPROGRESS) + goto tcp_forward_error; + } + + if (retval == EINPROGRESS) + u->remote_forward_connected = 2; + + read = 1; + out[1] = 0; + + /* check user TCP forward status flag, which is updated in server_tunnel + * when the file descriptor becomes writable (ie, connection established */ + if (u->remote_forward_connected == 1) { + out[0] = 'C'; + DEBUG(2, "User %d TCP forward connection established: %s", userid, errormsg); + } else if (u->remote_forward_connected == 2) { + out[0] = 'W'; + DEBUG(3, "User %d TCP connection in progress: %s", userid, errormsg); + } + + write_dns(dns_fd, q, out, read + 1, u->downenc); + return; + } else { + out[0] = 'I'; + + /* Send ip/mtu/netmask info */ + tempip.s_addr = server.my_ip; + tmp[0] = strdup(inet_ntoa(tempip)); + tempip.s_addr = u->tun_ip; + tmp[1] = strdup(inet_ntoa(tempip)); + + read = snprintf(out + 1, sizeof(out) - 1, "-%s-%s-%d-%d", + tmp[0], tmp[1], server.mtu, server.netmask); + + DEBUG(1, "User %d connected from %s, tun_ip %s.", userid, + fromaddr, tmp[1]); + syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); + + free(tmp[1]); + free(tmp[0]); + write_dns(dns_fd, q, out, read + 1, u->downenc); + return; + } +tcp_forward_error: + DEBUG(1, "Failed to connect TCP forward for user %d: %s", userid, errormsg); + out[0] = 'E'; + strncpy(out + 1, errormsg, sizeof(out) - 1); + read = strlen(out); + write_dns(dns_fd, q, out, read + 1, u->downenc); +} + +void +handle_dns_ip_request(int dns_fd, struct query *q, int userid) +{ + char reply[17]; + int length; + reply[0] = 'I'; + if (q->from.ss_family == AF_INET) { + if (server.ns_ip != INADDR_ANY) { + /* If set, use assigned external ip (-n option) */ + memcpy(&reply[1], &server.ns_ip, sizeof(server.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'); +} + +void +handle_dns_upstream_codec_switch(int dns_fd, struct query *q, int userid, + uint8_t *unpacked, size_t read) +{ + int codec; + struct encoder *enc; + + codec = unpacked[0]; + + 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; + } +} + +void +handle_dns_set_options(int dns_fd, struct query *q, int userid, + uint8_t *unpacked, size_t read) +{ + uint8_t bits = 0; + char *encname = "BADCODEC"; + + int tmp_lazy, tmp_downenc, tmp_comp; + + /* 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; + + switch (unpacked[0] & 0x7C) { + case (1 << 6): /* Base32 */ + tmp_downenc = 'T'; + encname = "Base32"; + bits = 5; + break; + case (1 << 5): /* Base64 */ + tmp_downenc = 'S'; + encname = "Base64"; + bits = 6; + break; + case (1 << 4): /* Base64u */ + tmp_downenc = 'U'; + encname = "Base64u"; + bits = 26; + break; + case (1 << 3): /* Base128 */ + tmp_downenc = 'V'; + encname = "Base128"; + bits = 7; + break; + case (1 << 2): /* Raw */ + tmp_downenc = 'R'; + encname = "Raw"; + bits = 8; + break; + default: /* Invalid (More than 1 encoding bit set) */ + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + return; + } + + tmp_comp = (unpacked[0] & 2) >> 1; /* compression flag */ + tmp_lazy = (unpacked[0] & 1); /* lazy mode flag */ + + /* 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, encname, strlen(encname), users[userid].downenc); +} + +void +handle_dns_fragsize_probe(int dns_fd, struct query *q, int userid, + uint8_t *unpacked, size_t read) +/* Downstream fragsize probe packet */ +{ + int req_frag_size; + + req_frag_size = ntohs(*(uint16_t *) unpacked); + DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size); + + if (req_frag_size < 2 || req_frag_size > MAX_FRAGSIZE) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + char buf[MAX_FRAGSIZE]; + 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 < MAX_FRAGSIZE; i++, v = (v + 107) & 0xff) + buf[i] = v; + write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); + } +} + +void +handle_dns_set_fragsize(int dns_fd, struct query *q, int userid, + uint8_t *unpacked, size_t read) + /* Downstream fragsize packet */ +{ + int max_frag_size; + max_frag_size = ntohs(*(uint16_t *)unpacked); + + if (max_frag_size < 2 || max_frag_size > MAX_FRAGSIZE) { + 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, 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); + } +} + +void +handle_dns_ping(int dns_fd, struct query *q, int userid, + uint8_t *unpacked, size_t read) +{ + int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; + int respond, set_qtimeout, set_wtimeout, tcp_disconnect; + unsigned qtimeout_ms, wtimeout_ms; + + CHECK_LEN(read, UPSTREAM_PING); + + /* Check if query is cached */ + if (qmem_is_cached(dns_fd, userid, q)) + return; + + /* Unpack flags/options from ping header */ + dn_ack = ((unpacked[9] >> 2) & 1) ? unpacked[0] : -1; + up_winsize = unpacked[1]; + dn_winsize = unpacked[2]; + up_seq = unpacked[3]; + dn_seq = unpacked[4]; + + /* Query timeout and window frag timeout */ + qtimeout_ms = ntohs(*(uint16_t *) (unpacked + 5)); + wtimeout_ms = ntohs(*(uint16_t *) (unpacked + 7)); + respond = unpacked[9] & 1; + set_qtimeout = (unpacked[9] >> 3) & 1; + set_wtimeout = (unpacked[9] >> 4) & 1; + tcp_disconnect = (unpacked[9] >> 5) & 1; + + DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, " + "%swtime %u ms, respond %d, tcp_close %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, tcp_disconnect, unpacked[9]); + + if (tcp_disconnect) { + /* close user's TCP forward connection and mark user as inactive */ + if (users[userid].remoteforward_addr_len == 0) { + DEBUG(1, "User %d attempted TCP disconnect but didn't request TCP forwarding!", userid); + } else { + DEBUG(1, "User %d closed remote TCP forward", userid); + close_socket(users[userid].remote_tcp_fd); + users[userid].active = 0; + } + } + + 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 changing 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: still added to qmem (for cache) even though responded to immediately */ + 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(userid, q, 1, 1, NULL); + return; + } + + /* if respond flag not set, query waits in qmem and is used later */ + user_process_incoming_data(userid, dn_ack); +} + +void +handle_dns_data(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid) +{ + uint8_t unpacked[20]; + static fragment f; + size_t len; + + /* Need 6 char header + >=1 char data */ + CHECK_LEN(domain_len, UPSTREAM_HDR + 1); + + /* 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); + b32->decode(unpacked, &len, (uint8_t *)domain + 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, (uint8_t *)domain + 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(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_null_request(int dns_fd, struct query *q, int domain_len) +/* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ +{ + char cmd, userchar; + int userid = -1; + uint8_t in[QUERY_NAME_SIZE + 1]; + + /* Everything here needs at least 5 chars in the name: + * cmd, userid and more data or at least 3 bytes CMC */ + if (domain_len < 5) + return; + + /* Duplicate domain name to prevent changing original query */ + memcpy(in, q->name, QUERY_NAME_SIZE + 1); + in[QUERY_NAME_SIZE] = 0; /* null terminate */ + + cmd = toupper(in[0]); + DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(in), cmd); + + /* Commands that do not care about userid: also these need to be backwards + * compatible with older versions of iodine (at least down to 00000502) */ + if (cmd == 'V') { /* Version check - before userid is assigned*/ + handle_dns_version(dns_fd, q, in, domain_len); + return; + } + else if (cmd == 'Z') { /* Upstream codec check - user independent */ + /* Reply with received hostname as data (encoded in base32) */ + write_dns(dns_fd, q, (char *)in, domain_len, 'T'); + return; + } + else if (cmd == 'Y') { /* Downstream codec check - user independent */ + handle_dns_downstream_codec_check(dns_fd, q, in, domain_len); + return; + } + + /* Get userid from query (always 2nd byte in hex except for data packets) */ + if (isxdigit(cmd)) { + /* Upstream data packet - first byte is userid in hex */ + userchar = cmd; + cmd = 'd'; /* flag for data packet - not part of protocol */ + } else { + userchar = toupper(in[1]); + } + + if (isxdigit(userchar)) { + userid = (userchar >= 'A' && userchar <= 'F') ? + (userchar - 'A' + 10) : (userchar - '0'); + } else { + /* Invalid user ID or bad DNS query */ + write_dns(dns_fd, q, "BADLEN", 5, 'T'); + } + + /* Login request - after version check successful, do not check auth yet */ + if (cmd == 'L') { + handle_dns_login(dns_fd, q, in, domain_len, userid); + return; + } + + /* Check user IP and authentication status */ + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; + } + + if (cmd == 'd') { /* Upstream data packet */ + handle_dns_data(dns_fd, q, in, domain_len, userid); + return; + } else if (cmd == 'I') { /* IP request packet - no base32 data */ + handle_dns_ip_request(dns_fd, q, userid); + } + + /* Following commands have everything after cmd and userid in base32 + * All bytes that are not valid base32 are decoded to 0 */ + + uint8_t unpacked[512]; + size_t raw_len; + raw_len = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)in + 2, domain_len - 2, b32); + if (raw_len < 3) /* always at least 3 bytes after decoding at least 5 bytes */ + return; /* Just in case. */ + + switch (cmd) { + case 'S': + handle_dns_upstream_codec_switch(dns_fd, q, userid, unpacked, raw_len); + break; + case 'O': + handle_dns_set_options(dns_fd, q, userid, unpacked, raw_len); + break; + case 'R': + handle_dns_fragsize_probe(dns_fd, q, userid, unpacked, raw_len); + break; + case 'N': + handle_dns_set_fragsize(dns_fd, q, userid, unpacked, raw_len); + break; + case 'P': + handle_dns_ping(dns_fd, q, userid, unpacked, raw_len); + break; + default: + DEBUG(2, "Invalid DNS query! cmd = %c, hostname = '%*s'", + cmd, domain_len, in); + } +} + +void +handle_ns_request(int dns_fd, struct query *q) +/* Mostly identical to handle_a_request() below */ +{ + char buf[64*1024]; + int len; + + if (server.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, &server.ns_ip, sizeof(server.ns_ip)); + } + + len = dns_encode_ns_response(buf, sizeof(buf), q, server.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 (server.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, &server.ns_ip, sizeof(server.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..4e9e8f9 --- /dev/null +++ b/src/server.h @@ -0,0 +1,163 @@ +/* + * 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 32 + +#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" + +#define INSTANCE server + +#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; +}; + +struct server_instance { + /* Global server variables */ + int running; + char *topdomain; + char password[33]; + int check_ip; + int my_mtu; + in_addr_t my_ip; + int netmask; + in_addr_t ns_ip; + int bind_port; + int debug; + + int addrfamily; + struct dnsfd dns_fds; + int tun_fd; + int port; + int mtu; + int max_idle_time; + struct sockaddr_storage dns4addr; + int dns4addr_len; + struct sockaddr_storage dns6addr; + int dns6addr_len; + + int allow_forward_local_port; + int allow_forward_remote; + + /* settings for forwarding normal DNS to + * local real DNS server */ + int bind_fd; + int bind_enable; +}; + +extern struct server_instance server; + +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 */ +}; + +void server_init(); +void server_stop(); +int server_tunnel(); + +int read_dns(int fd, struct query *q); +void write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc); +void handle_full_packet(int userid, uint8_t *data, size_t len, int); +void handle_null_request(int dns_fd, 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(int, struct query *, int, int, char*); + +#endif /* __SERVER_H__ */ diff --git a/src/tun.c b/src/tun.c index c05a591..b8ed005 100644 --- a/src/tun.c +++ b/src/tun.c @@ -457,16 +457,9 @@ open_tun(const char *tun_device) #endif -void -close_tun(int tun_fd) -{ - if (tun_fd >= 0) - close(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 +483,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 +497,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 +538,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..9606356 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,12 @@ #include "common.h" #include "encoding.h" #include "user.h" +#include "window.h" struct tun_user *users; unsigned usercount; +int created_users; + int init_users(in_addr_t my_ip, int netbits) @@ -60,6 +64,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; @@ -74,11 +79,10 @@ init_users(in_addr_t my_ip, int netbits) } 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 +99,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 +183,72 @@ 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, int check_ip) +{ + /* 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, int check_ip) +/* 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, check_ip); + if (res) + return res; + + if (!(users[userid].authenticated >= 1)) + return 1; + + return 0; +} + +int +set_user_tcp_fds(fd_set *fds, int conn_status) +/* Add TCP forward FDs to fd_set for users with given connection status; returns largest FD added */ +{ + int max_fd = 0; + for (int userid = 0; userid < created_users; userid ++) { + if (user_active(userid) && users[userid].remoteforward_addr_len > 0 + && users[userid].remote_forward_connected == conn_status) { + FD_SET(users[userid].remote_tcp_fd, fds); + max_fd = MAX(max_fd, users[userid].remote_tcp_fd); + } + } + return max_fd; +} + diff --git a/src/user.h b/src/user.h index 7d2553a..192cebd 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,73 +19,54 @@ #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 sockaddr_storage remoteforward_addr; + socklen_t remoteforward_addr_len; /* 0 if no remote forwarding enabled */ + int remote_tcp_fd; + int remote_forward_connected; /* 0 if not connected, -1 if error or 1 if OK */ + 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_ip); +int check_user_and_ip(int userid, struct query *q, int check_ip); 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); +int set_user_tcp_fds(fd_set *fds, int); #endif 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..a3bd346 --- /dev/null +++ b/src/window.c @@ -0,0 +1,453 @@ +/* + * 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; + +struct frag_buffer * +window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir) +{ + struct frag_buffer *buf; + buf = calloc(sizeof(struct frag_buffer), 1); + 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(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(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(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, fragment *src) +{ + if (window_buffer_available(w) < 1) return 0; + memcpy(&w->frags[w->last_write], src, sizeof(fragment)); + w->last_write = WRAP(w->last_write + 1); + w->numitems ++; + return 1; +} + + +ssize_t +window_process_incoming_fragment(struct frag_buffer *w, 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; + 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[%" L "u + %u = %" L "u]", + 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(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; +} + +/* 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 (%" L "u) pointing to non-start fragment (seq %u, len %" L "u)!", + w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); + return 0; + } + if (compression) *compression = 1; + + 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 [%" L "u], got seq %u (%" L "u bytes) instead! Not reassembling!", + curseq, woffs, f->seqID, fraglen); + return 0; + } + + WDEBUG(" Fragment seq %u, data length %" L "u, data offset %" L "u, total len %" L "u, maxlen %" L "u", + 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 %" L "u 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 %" L "u, datalen %" L "u)", 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 %" L "u bytes from %" L "u 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(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; + fragment *f; + size_t tosend = 0; + + oldest.tv_sec = 0; + oldest.tv_usec = 0; + + if (w->numitems == 0) { + if (nextresend) { + nextresend->tv_sec = 0; + nextresend->tv_usec = 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. */ +fragment * +window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) +{ + struct timeval age, now; + 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 %" L "u)", + 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) +{ + 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) +{ + 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 %" L "u, 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; %" L "u-%" L "u (%u) to %" L "u-%" L "u (%u) len=%" L "u", + 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(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; + static fragment f; + WDEBUG("add data len %" L "u, %" L "u 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 %" L "u, seqID %u, s %u, end %u, dOffs %" L "u", 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..fc412f7 --- /dev/null +++ b/src/window.h @@ -0,0 +1,156 @@ +/* + * 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 + +/* Window function definitions. */ +#define WINDOW_SENDING 1 +#define WINDOW_RECVING 0 + +typedef 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 */ +} fragment; + +struct frag_buffer { + 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 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 + +/* Gets index of fragment o fragments after window start */ +#define AFTER(w, o) ((w->window_start + o) % w->length) + +/* 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 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))) + +/* 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) + +/* Wrap index x to a value within the window buffer length */ +#define WRAP(x) ((x) % w->length) + +/* 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}\ + }\ + } + +/* 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, fragment *src); + +/* Handles fragment received from the sending side (RECV) */ +ssize_t window_process_incoming_fragment(struct frag_buffer *w, 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) */ +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..909e5f6 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,79 @@ struct ip u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ - }; +}; + +/* 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..70b348c 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", + "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaa"}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaa"}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaa"}, + { "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVW.XYZ" }, { 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..6920d33 --- /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;) { + 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; +}