Pages

Showing posts with label sods. Show all posts
Showing posts with label sods. Show all posts

Sunday, July 4, 2010

DNS Programming with Erlang

I have this strange fascination with DNS. By which I mean loathing. Yet somehow I've already written 3 small DNS servers in Erlang:
  • emdns: An unfinished multicast DNS server with unspecified yet no doubt awesome features. Someday I'll finish it. Maybe.
  • spood: A strange, little program; a spoofing DNS proxy that will send out your DNS requests from somebody else's IP address and sniff the responses. Maybe (if you're somewhat sketchy) you could use it to hide your DNS lookups. Maybe, you could use it to ramp up your DNS requests on networks that throttle them down. Not that I would do any of that.
  • seds: a DNS server that tunnels TCP/IP. I'm typing this blog over a DNS tunnel right now, stress testing it (with my blazing fast ASCII input) and trying to make seds crash. Also stress testing my patience.
Since the programmatic interfaces to DNS in Erlang are mostly undocumented, I thought I'd go over them briefly. So I'll remember how to use them if I ever finish emdns. I figured out how they worked mainly by reading the source and dumping DNS packets to see the record structures. The DNS parsing functions are kept in lib/kernel/src/inet_dns.erl. Pretty much the only functions that you will need from this module are encode/1 and decode/1. The tricky part is passing in the appropriate data structures.
  • decode/1 takes a binary and returns a #dns_rec{} record or {error, fmt} if the DNS payload cannot be decoded
  • encode/1 as you might expect, does the inverse, taking an appropriate record and returning a binary
The record structure is defined in lib/kernel/src/inet_dns.hrl.
-record(dns_rec,
    {
        header,       %% dns_header record
        qdlist = [],  %% list of question entries
        anlist = [],  %% list of answer entries
        nslist = [],  %% list of authority entries
        arlist = []   %% list of resource entries
    }).
  • The DNS header is another record:
    -record(dns_header,
        {
         id = 0,       %% ushort query identification number
         %% byte F0
         qr = 0,       %% :1   response flag
         opcode = 0,   %% :4   purpose of message
         aa = 0,       %% :1   authoritive answer
         tc = 0,       %% :1   truncated message
         rd = 0,       %% :1   recursion desired
         %% byte F1
         ra = 0,       %% :1   recursion available
         pr = 0,       %% :1   primary server required (non standard)
                       %% :2   unused bits
         rcode = 0     %% :4   response code
        }).
    
    While the defaults are initialized to small integers, inet_dns replaces them with atoms. So, the 1 bit values are either the atoms 'true' or 'false' and the opcode is set to an atom, for example, 'query'. Both integers and the atom representations are usually accepted by the functions though.

  • qdlist is a list of DNS query records:
    -record(dns_query,
        {
         domain,     %% query domain
         type,        %% query type
         class      %% query class
         }).
    
    • domain is a string representing the domain name, e.g., "foo.bar.example.com"
    • type is an atom describing the DNS type: a, cname, txt, null, srv, ns, ...
    • class will most commonly be 'in' (Internet), though multicast DNS uses "cache flush" (32769) for some operations

Making a valid Erlang DNS query would look something like:
-module(dns).
-compile(export_all).

-include_lib("kernel/src/inet_dns.hrl").

q(Domain, NS) ->
    Query = inet_dns:encode(
        #dns_rec{
            header = #dns_header{
                id = crypto:rand_uniform(1,16#FFFF),
                opcode = 'query',
                rd = true
            },
            qdlist = [#dns_query{
                domain = Domain,
                type = a,
                class = in
            }]
        }),
    {ok, Socket} = gen_udp:open(0, [binary, {active, false}]),
    gen_udp:send(Socket, NS, 53, Query),
    {ok, {NS, 53, Reply}} = gen_udp:recv(Socket, 65535),
    inet_dns:decode(Reply).
I enabled recursion because the request will be going through the one of the public Google nameservers (8.8.8.8) instead of going directly through the authoritative nameserver.

Testing the results:
$ erl
Erlang R14A (erts-5.8) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8  (abort with ^G)
1> {ok, Q} = dns:q("listincomprehension.com", {8,8,8,8}).
{ok,{dns_rec,{dns_header,7296,true,'query',false,false,
                         true,true,false,0},
             [{dns_query,"listincomprehension.com",a,in}],
             [{dns_rr,"listincomprehension.com",a,in,0,656,
                      {216,239,32,21},                      undefined,[],false},
              {dns_rr,"listincomprehension.com",a,in,0,656,
                      {216,239,34,21},
                      undefined,[],false},
              {dns_rr,"listincomprehension.com",a,in,0,656,
                      {216,239,36,21},
                      undefined,[],false},
              {dns_rr,"listincomprehension.com",a,in,0,656,
                      {216,239,38,21},
                      undefined,[],false}],
             [],[]}}
2> rr("/usr/local/lib/erlang/lib/kernel-2.14/src/inet_dns.hrl").
[dns_header,dns_query,dns_rec,dns_rr,dns_rr_opt]
3> Q.
#dns_rec{header = #dns_header{id = 7296,qr = true,
                              opcode = 'query',aa = false,tc = false,rd = true,ra = true,
                              pr = false,rcode = 0},
         qdlist = [#dns_query{domain = "listincomprehension.com",
                              type = a,class = in}],
         anlist = [#dns_rr{domain = "listincomprehension.com",
                           type = a,class = in,cnt = 0,ttl = 656,
                           data = {216,239,32,21},
                           tm = undefined,bm = [],func = false},
                   #dns_rr{domain = "listincomprehension.com",type = a,
                           class = in,cnt = 0,ttl = 656,
                           data = {216,239,34,21},
                           tm = undefined,bm = [],func = false},
                   #dns_rr{domain = "listincomprehension.com",type = a,
                           class = in,cnt = 0,ttl = 656,
                           data = {216,239,36,21},
                           tm = undefined,bm = [],func = false},
                   #dns_rr{domain = "listincomprehension.com",type = a,
                           class = in,cnt = 0,ttl = 656,
                           data = {216,239,38,21},
                           tm = undefined,bm = [],func = false}],
         nslist = [],arlist = []}
The records are displayed as tuples. You can pretty print the records by using the shell rr() command to include the header file wherever it is on your system.

The query returned the same packet we sent with some changes to the header:
  • The response flag (qr) is set to true
  • The recursion available flag (ra) is also set to true
The answer to our query is a list bound to the anlist record atom. The #dns_rr{} record looks like:
-record(dns_rr,
    {
     domain = "",   %% resource domain
     type = any,    %% resource type
     class = in,    %% reource class
     cnt = 0,       %% access count
     ttl = 0,       %% time to live
     data = [],     %% raw data
      %%  
     tm,            %% creation time
         bm = [],       %% Bitmap storing domain character case information.
         func = false   %% Optional function calculating the data field.
    }).
The data field is interesting. Although it's initialized as an empty list, the data structure bound to it depends on the DNS record type. For example, from the ones I remember:
  • A: tuple representing the IP address
  • TXT: a list of strings
  • NULL: a binary
  • CNAME: a domain name string appropriately "labelled" (canonicalized by the "."'s), e.g., "ghs.google.com". inet_dns takes care of breaking the domain name into the appropriate, compressed domain name -- a weird form where the "."'s are replaced by nulls and each component is prefaced by a length or a pointer redirecting to another field (hence the compression).

Pattern Matching

The cool thing is that, since the DNS records are nested records, its very easy to pattern match on the results. Modifying the example above:
-module(dns1).
-compile(export_all).

-include_lib("kernel/src/inet_dns.hrl").

q(Type, Domain, NS) ->
    Query = inet_dns:encode(
        #dns_rec{
            header = #dns_header{
                id = crypto:rand_uniform(1,16#FFFF),
                opcode = 'query',
                rd = true
            },
            qdlist = [#dns_query{
                    domain = Domain,
                    type = Type,
                    class = in
                }]
        }),
    {ok, Socket} = gen_udp:open(0, [binary, {active, true}]),
    gen_udp:send(Socket, NS, 53, Query),
    loop(Socket, Type, Domain, NS).


loop(Socket, Type, Domain, NS) ->
    receive
        {udp, Socket, NS, _, Packet} ->
            {ok, Response} = inet_dns:decode(Packet),
            match(Type, Domain, Response)
    end.

match(a, Domain, #dns_rec{
        header = #dns_header{
            qr = true,
            opcode = 'query'
        },
        qdlist = [#dns_query{
                domain = Domain,
                type = a,
                class = in
            }],
        anlist = [#dns_rr{
                domain = Domain,
                type = a,
                class = in,
                data = {IP1, IP2, IP3, IP4}
            }|_]}) ->
    {a, Domain, {IP1,IP2,IP3,IP4}};
match(cname, Domain, #dns_rec{
        header = #dns_header{
            qr = true,
            opcode = 'query'
        },
        qdlist = [#dns_query{
                domain = Domain,
                type = cname,
                class = in
            }],
        anlist = [#dns_rr{
                domain = Domain,
                type = cname,
                class = in,
                data = Data
            }|_]}) ->
    {cname, Domain, Data}.

And the results:

$ erl
Erlang R14A (erts-5.8) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8 (abort with ^G)
1> dns1:q(cname, "blog.listincomprehension.com", {8,8,8,8}).
{cname,"blog.listincomprehension.com","ghs.google.com"}

Thursday, June 24, 2010

Fun with Raw Sockets in Erlang: A Spoofing DNS Proxy

UDP Header

UDP headers are specified as:
  • Source Port:16
  • Destination Port:16
  • Length:16
  • Checksum:16

  • The Source Port is a 2 byte value representing the originating port
  • The Destination Port is the 2 byte value specifying the target port
  • The Length is the size of the UDP header and packet in bytes. The size of the UDP header is 8 bytes.
  • The Checksum algorithm is the same as for TCP, involving the creation of an IP pseuduoheader.
    If the length of the UDP packet is an odd number of bytes, the packet is zero padded with an additional byte only for checksumming purposes.
The equivalent UDP header in Erlang is:
<<SourcePort:16,
DestinationPort:16,
Length:16,
Checksum:16>>
The pseudo-header used for checksumming is:
  • Source Address:32
  • Destination Address:32
  • Zero:8
  • Protocol:8
  • UDP Packet Length:16
  • UDP Header and Payload1:8
  • ...
  • UDP Header and PayloadN:8
  • optional padding:8
  • The Source Address from the IP header
  • The Destination Address from the IP header
  • The Protocol for UDP is 17
  • The UDP Packet Length in bytes, for both the UDP header and payload. The length of the IP pseudo-header is not included.
  • The UDP Header and Payload is the full UDP packet
  • If the UDP packet length is odd, an additional zero'ed byte is included for checksumming purposes. The extra byte is not used in the length computation or sent with the packet.
  • The length of the UDP packet is included in both the IP pseudo-header and the UDP header.
  • The checksum field of the UDP header is set to 0.
In Erlang, the pseudo-header is represented as:
<<SA1:8,SA2:8,SA3:8,SA4:8,  % bytes representing the IP source address
DA1:8,DA2:8,DA3:8,DA4:8,    % bytes representing the IP destination address
0:8,                        % Zero
6:8,                        % Protocol: TCP
UDPlen:16                % UDP packet size in bytes

SourcePort:16,
DestinationPort:16,
UDPlen:16,
0:16,
Data/binary,
0:UDPpad>>                  % UDPpad may be 0 or 8 bits

A Spoofing DNS Proxy

What?

spood is a spoofing DNS proxy with a vaguely obscene name. spood works by accepting DNS queries on localhost and then spoofing the source IP address of the DNS request using the Linux PF_PACKET interface. DNS replies are sniffed off the network and returned to localhost.

Why?

Maybe for using with IP over DNS tunnels?

How?

spood works with procket and epcap.

This Will Probably Be the First Page Returned For Searches For "Erlang Promiscuity"

While spood can run by spoofing its own IP address, it's more fun running it on a hubbed or public wireless network. To allow spood to sniff the network, I added support for setsockopt() to procket as an additional NIF. Promiscuous mode, under Linux, can be activated/deactivated globally by using an ioctl() or per application by using setsockopt(). To enable promiscuous mode, the application needs to call:
setsockopt(socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq))
Though obtaining a PF_PACKET socket requires root privileges, performing the setsockopt() call on the socket does not. mreq is a struct packet_mreq:
struct packet_mreq {
    int            mr_ifindex;    /* interface index */
    unsigned short mr_type;       /* action */
    unsigned short mr_alen;       /* address length */
    unsigned char  mr_address[8]; /* physical layer address */
};
  • mr_ifindex is the interface index returned by doing an ioctl() in host endian format
  • mr_type is set to PACKET_MR_PROMISC in host endian format
  • the reminder of the struct is zero'ed
The Erlang version looks like:
-define(SOL_PACKET, 263).
-define(PACKET_ADD_MEMBERSHIP, 1).
-define(PACKET_DROP_MEMBERSHIP, 2).
-define(PACKET_MR_PROMISC, 1).

promiscuous(Socket, Ifindex) ->
    procket:setsockopt(Socket, ?SOL_PACKET, ?PACKET_ADD_MEMBERSHIP, <<
        Ifindex:32/native,              % mr_ifindex: interface index
        ?PACKET_MR_PROMISC:16/native,   % mr_type: action
        0:16,                           % mr_alen: address length
        0:64                            % mr_address[8]:  physical layer address
        >>).

Sniffing Packets

Sniffing packets involves running procket:recvfrom/2 in a loop. Erlang's pattern matching makes filtering the packets simple. One trick is retrieving the default nameservers in Erlang.
{ok, PL} = inet_parse:resolv(
    proplists:get_value(resolv_conf, inet_db:get_rc(), "/etc/resolv.conf")),
NS = proplists:get_value(nameserver, PL).
inet_db:get_rc() will return the path to the system resolv.conf file. inet_parse has an undocumented function to parse resolv.conf and return the attributes as list of key/value pairs.

Spoofing Packets

Spoofing packets is done by constructing a packet consisting of the Ethernet, IP and UDP header and payload.
dns_query(SourcePort, Data, #state{
    shost = {SM1,SM2,SM3,SM4,SM5,SM6},
    dhost = {DM1,DM2,DM3,DM4,DM5,DM6},
    saddr = {SA1,SA2,SA3,SA4},
    daddr = {DA1,DA2,DA3,DA4}
    }) ->

    Id = 1,
    TTL = 64,

    UDPlen = 8 + byte_size(Data),
    IPlen = 20 + UDPlen,

    IPsum = epcap_net:makesum(
        <<
        % IPv4 header
        4:4, 5:4, 0:8, IPlen:16,
        Id:16, 0:1, 1:1, 0:1,
        0:13, TTL:8, 17:8, 0:16,
        SA1:8, SA2:8, SA3:8, SA4:8,
        DA1:8, DA2:8, DA3:8, DA4:8
        >>
    ),

    UDPpad = case UDPlen rem 2 of
        0 -> 0;
        1 -> 8
    end,

    UDPsum = epcap_net:makesum(
        <<
        SA1:8,SA2:8,SA3:8,SA4:8,
        DA1:8,DA2:8,DA3:8,DA4:8,
        0:8,
        17:8,
        UDPlen:16,

        SourcePort:16,
        53:16,
        UDPlen:16,
        0:16,
        Data/binary,
        0:UDPpad
        >>),

    <<
    % Ethernet header
    DM1:8,DM2:8,DM3:8,DM4:8,DM5:8,DM6:8,
    SM1:8,SM2:8,SM3:8,SM4:8,SM5:8,SM6:8,
    16#08, 16#00,

    % IPv4 header
    4:4, 5:4, 0:8, IPlen:16,
    Id:16, 0:1, 1:1, 0:1,
    0:13, TTL:8, 17:8, IPsum:16,
    SA1:8, SA2:8, SA3:8, SA4:8,
    DA1:8, DA2:8, DA3:8, DA4:8,

    % UDP header
    SourcePort:16,
    53:16,
    UDPlen:16,
    UDPsum:16,
    Data/binary
    >>.

Running spood

Setup isn't all automatic yet (but see the README, maybe this has changed). After everything is compiled, find the MAC and IP address of your client and name server. Then run:
erl -pa ebin deps/*/ebin
1> spood:start("eth0",
    {{16#00,16#aa,16#bb,16#cc,16#dd,16#ee}, {list, [{192,168,100,100}, {192,168,100,101}]}},
    {{16#00,16#11,16#22,16#33,16#44,16#55}, {192,168,100,1}}).
Where:
  • The first argument is your interface device name
  • The second argument is a 2-tuple composed of your source MAC address and a representation of what should be used for your client IP address. Unless you're ARP spoofing or have published the ARP entries yourself, the IP's should be of clients on the network.

    The second argument can be a tuple or a string representing an IP or a tuple consisting of the keyword "list" followed a list of IP addresses. The source IP for each query will be randomly chosen from the list.

  • The third argument is the name server MAC and IP address
Then test it:
$ nslookup
> server 127.0.0.1
Default server: 127.0.0.1
Address: 127.0.0.1#53
> www.google.com
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
www.google.com  canonical name = www.l.google.com.
Name:   www.l.google.com
Address: 173.194.33.104
If you happen to be running the sods client, you can use the DNS proxy by using the "-r" option:
sdt -r 127.0.0.1 sshdns.s.example.com
Update: Well, I've tested spood in the wild now and made a few changes. By default, spood will discover the IP addresses on your network and add them to the list of source addresses to spoof. spood now takes a proplist as an argument. However, if no argument is passed, spood will try to figure out your network by:
  • guessing which interface device to use
  • finding the MAC and IP address assigned to the device
  • looking up the MAC address of the name server in the ARP cache
The arguments to spood:start/1 is a proplist consisting of:
  • {dev, string() | undefined}
  • {srcmac, tuple()}
  • {dstmac, tuple()}
  • {saddr, tuple() | string() | discover | {discover, list()} | {list, list()}}
  • {nameserver, tuple() | undefined}

Or call spood:start() to use the defaults.

Wednesday, April 28, 2010

DNS Tunnelling on Android

I have a Motorola Milestone, its somewhat awesome, but what would make it awesome-er is ... tunnelling TCP/IP over DNS! Any device that can support covert, high latency, text based network protocols should. And rooting the phone isn't required.

It turns out it is suprisingly easy to get simple C code running on an Android phone, if you don't mind statically compiling and don't have many dependencies. The results are sort of bloated though.

You can download a precompiled sods client (sdt) for Android (the binary will also work on Nokia tablets running Maemo) from here. I compiled sdt using scratchbox. You'll need a sods server and a domain set up, of course (here are instructions).

Copy sdt to your phone. I moved it to the sd card.

If you don't have ConnectBot installed, get it from the Android market now. Start up ConnectBot and create a new local host. This will start a shell on the phone.

Change to the ConnectBot data directory; we'll use it to hold the sods client.
cd /data/data/org.connectbot
Create a new directory or use the files directory (I have no idea what will happen to these directories when ConnectBot is upgraded).
mkdir bin
cd bin
Copy sdt and change the permissions:
cp /sdcard/tmp/sdt .
chmod 744 sdt
./sdt -h
Now create a new local host to run sdt. Do a long press on the new entry and select "Edit Host".
Choose the "Post-login automation" item. This will let us run commands when the shell is started.
The flags to sdt can be manually specified or wrapped in a shell script.
Here is an example shell script (called sd):
#!/system/bin/sh

PORT=22220

S=${1:-0}
PORT=$((PORT+$S))

T=${2:-TXT}

NS1=`getprop net.dns1`
NS=${3:-$NS1}
./sdt -t $T -r $NS -p $PORT -s $S -vvvv x.a.example.com x.b.example.com x.n.example.com
Create a new ssh host, connecting to localhost and whatever port sdt has been configured to listen on (using the -p flag).

Test it out. In the ConnectBot host list, select the sdt host.
Hit the back button, then select the ssh host.
If everything works out, a glorious, if slow, ssh session over DNS should be the reward.
After login, run GNU screen (in case the connection drops). I usually run console apps, but ConnectBot supports port forwarding for those times you need to run send data from other apps over the tunnel (forwarding through a proxy like ziproxy works well).
centerim: IRC over SSH over TCP/IP over DNS
Web browsing over DNS on a phone using w3m

Update: 2011-03-13

I've switched phones to an HTC Desire Z (running Android 2.2) and one of my first tasks was to get sods running on it.
  1. I was getting permission errors copying from the sdcard to the connectbot directory so I resorted to using "cat":
    
    cd /data/data/org.connectbot/bin
    cat /sdcard/tmp/sdt > sdt
    chmod 755 sdt
    ./sdt -h
    
  2. If you are having problems connecting to the sods server, make sure another sdt process is not running. To do this, run "ps" and "kill" from the ConnectBot shell or kill the ConnectBot application.

Tuesday, January 26, 2010

SoDS and TXT Records

Sometimes the sods client (sdt) will return an error "Invalid base64 encoded packet". If run with the default options, sdt will use TXT records and it's likely that someone, in between you and the sods server, is re-writing the TXT records.

In this particular case, it might be the DNS hosting service that I used for the test domain (GoDaddy) inserting an SPF record. Thanks a bunch for that.

But I've seen hotel networks where TXT records are MITM'ed, for some sort of nefarious Active Directory scheme.

Anyway, the fix is to run sdt with the "-t" flag set to use NULL or CNAME records ("-t null" or "-t cname").

Monday, January 4, 2010

SoDS and Domain Names

As an experiment in further obscuring traffic, I made a small change to the sods client and server to take multiple domain names from the command line. The maximum number of domains is hard coded to 256, just because.

The domain names can either be subdomains, if you have only one domain (e.g., sshdns.p1.example.com sshdns.p2.example.com ...) or unique domains (e.g., sshdns.p.example1.com sshdns.p.example2.com sshdns.p1.example2.com).

The sods server needs to be started with the same list of domains (e.g., p.example1.com p.example2.com  p1.example2.com) or the DNS requests will be rejected. If you want to disable this behaviour, start the sods server with the domain name set to "any". (The sods server checks the domain name to prevent it from answering to DNS scans, otherwise it wouldn't be too stealthy. Of course, if someone knows your domain name, they can easily scan for sods, spoof requests, etc.).

I wonder if DNS servers ever throttle by domain. It would make more sense to restrict queries by client IP address, though even this could be worked around on a non-switched network, like most public wifi internet access, by ARP'ing fake MAC/IP addresses, sending out UDP packets from these IP's and sniffing the responses. On a switched network, the same effect is achievable in tandem with ettercap. Maybe a future feature to think about.

Wednesday, December 30, 2009

SoDS; Care and Feeding of,

SoDS provides a covert method of bypassing firewall restrictions, obscuring traffic analysis and hiding your data by tunnelling it over DNS. SoDS was intended to be small, resource efficient, reasonably portable with few dependencies (at least on Unix) as well as taking basic precautions against misuse.

Test Ride


To get a feel for sods, its simple to try out:
git clone git://github.com/msantos/sods.git
cd sods/sods
./configure
make
cd ../sdt
./configure
make

Depending on your OS, you might need to adjust the Makefiles.

To test sods, you'll need to have an SSH server available somewhere and root privs on your test host. Start up the socket server:
$ sudo ./sods -d /tmp -L localhost:22 -vvv a.example.com

In another window, open an SSH session:
$ ssh -o ProxyCommand="./sdt -r 127.0.0.1 sshdns.a.example.com" 127.0.0.100

Real World SoDS


To use sods, you'll need a few things:
  1. A publicly addressable IP
  2. Something on which to run sods. The server doesn't need to be dedicated or high powered.
  3. A registered domain name
  4. A DNS server to host your domain

Well, that may seem to be waaaayyy too much bother, but it might not be as much work as it seems.

Publicly Available IP Address


You need an IP address that is reachable from the Internet or control of a device that can forward packets to your sods server.

The IP addresses the ISP assigns you tend to fall into 3 groups:
  1. the IP address is static and never (or rarely) changes
  2. the IP address changes infrequently, but the reverse lookup for the address is static
  3. the IP address and, consequently, the reverse address, changes frequently

You can work with any of these.

The IP address must be able to receive UDP packets on port 53 and send packets from port 53. If your ISP blocks port 53 (DNS), then you'll have to look around for another ISP, use a hosted service or maybe piggyback off of a friend.

A SoDS Server


You'll need sods to be running any time you might need it. SoDS doesn't require much in the way of resources, so I suggest using a low powered server that you can leave running all the time. The SoDS server might be the same device that you use for your gateway.

I use a Linksys WRT54gl as my home router and sods server. But any small, lower power device will do: I've also used a Linksys NSLU2, and, more recently, a Sheeva Plug.

A Registered Domain Name


If you're even thinking about setting this up, you probably already own a domain or two.

Another DNS Server


Finally, you will need another DNS server to delegate your domain name. This could be a DNS server you run on another IP address, a service provided by your registrar or one of the many free DNS services on the internet (there are a bunch, any of them should work, google for them).

Assuming your domain name is example.com with an IP address of 10.10.10.10, here is what you would have to set up.
  • If you have a static IP address (or one that doesn't change too frequently to be inconvenient), delegate a name server for a subdomain (in this example, "a"):
    example.com.    IN  A   10.10.10.10
    a               IN  NS  example.com.
    

  • Some ISP's will change your IP, but maintain a consistent reverse DNS lookup for your IP address (something like a DHCP name). You can use this by setting a CNAME.

    For example, if your IP address always resolves to:
    macaddr-00-00-aa-bb-cc-dd.home.isp.com
    

    You could set up your DNS entry as follows:
    a.example.com.  IN  CNAME   macaddr-00-00-aa-bb-cc-dd.home.isp.com.
    

  • Finally, if your ISP gives you a dynamic IP address, you can use one of the dynamic DNS services. Many of these services are free and will provide a consistent reverse DNS entry that you can exploit as with the previous example.

Set Up Your SoDS Server


Set up your sods server and point it to whatever SSH or tcp servers you're interested in. These servers don't have to be publicly exposed; they could be running somewhere behind your firewall.

SoDS is configured from the command line, but its easy to wrap it in a script for system startup. Here is an example for OpenWRT:
/etc/default/sods:
OPTIONS="-D -L ssh.server1:22 -L ssh.server2:22 -L 65.55.21.250:22"
DOMAIN="a.example.com"

/etc/init.d/sods:
#!/bin/sh /etc/rc.common
# Copyright (C) 2006 OpenWrt.org
START=50

BIN=sods
DEFAULT=/etc/default/$BIN
CHROOT_D=/var/chroot/sods

start() {
include /lib/network
scan_interfaces
config_load /var/state/network
config_get ipaddr wan ipaddr

[ -f $DEFAULT ] && . $DEFAULT
[ ! -d $CHROOT_D ] && mkdir -p $CHROOT_D

$BIN $OPTIONS -i $ipaddr $DOMAIN
}

stop() {
killall sods
}

Accessing Your DNS Tunnel


So next time you are enjoying a venti americano in your local coffee shop, you may think to pause from chatting with the cute barista to fire up your laptop or other mobile device and login to IRC. We're geeks, it happens.
$ ssh -o ProxyCommand="./sdt sshdns.a.example.com" 127.0.0.100

Since UDP is innately unreliable, for best results, wrap the ssh command in a script that reconnects on failure. On the server side, if you are using ssh, running everything within a GNU screen session provides seamless access to your console, in case your connection drops. See the example scripts for more details, but basically, all you need to do is use a named GNU screen session on your ssh server:
$ while [ "1" ]; do ssh -t -C -o ProxyCommand="./sdt sshdns.a.example.com" 127.0.0.100 "screen -drR sshdns"; done

SoDS Client


  • sdt will try not to overwhelm the local DNS server and will back off. Some DNS servers throttle chatty clients.

    To add some reliability to the protocol, both the client and server track sent packets and will re-send if they are lost.
  • If you are having problems connecting, run with multiple "-v" switches to see what's going on. It'll fill your screen with debug messages but will eventually stop. To control the number of debug messages displayed, use the "-V" switch (it defaults to 100).
  • You can bounce your connection off of other DNS servers, beside your local DNS server, if they support recursion by using the "-r" flag. To see which servers sdt knows about, run:
    sdt -v -h
    

    To use a particular recursive server, choose it by name:
    sdt -r google
    

    To pick a random server, run:
    sdt -r random
    
  • sods supports tunnelling using TXT, CNAME and NULL records. The default is TXT records. I've found that some ISP's re-write TXT records and many DNS servers in the wild do not support NULL records at all. Using CNAME's seems to work with most networks.

    NULL records have the lowest encoding overhead and the specification allows for a large packet size, reducing overhead further. Since sods does not support TCP, large packets aren't supported.
  • If you are on a mobile device, you probably want to conserve your battery. sdt polls for data at regular intervals by sending a request to the local DNS server to flush any pending data from your SoDS server.

    You can control this polling (without much effect on latency) by using the "-b" option. Every time data is received polling ramps up and slowly decays as no new data is recieved.

    If you happen to be on linux, powertop is a great tool for fine tuning the polling intervals.
  • Files can be transferred securely over DNS by using sftp, see the "sdftp" script in the examples. If you're brave, you can also run a fuse filesystem using sshfs.

Thursday, November 26, 2009

sods: A Socket over DNS Tunneling Service


So, once -- almost 2 years ago now! -- I planned on taking about a month's vacation to travel around Vietnam, Cambodia and Thailand. Roaming cell phone data plans are expensive and unreliable, and I wanted a way of quickly reading emails, browsing those important web sites (what if Linus had announced this was finally the year of the Linux desktop? what if I missed out on LOLCATS?) and checking up on my servers.

I expected wired and wireless access to be available pretty much everywhere I went, and, I was right, it was ubiquitous, though pricy. For internet access, hotels in Phnom Penh charged about $20/day, in a country where the average teacher makes around $80/month. Even for a tourist the price is a bit steep for a few minutes to chat with your friends.

Well, the alternative was figuring out a way of tunneling IP over whatever protocols these "walled garden" ISP's allowed. In the past, data could be hidden in the payload of a ping packet. Another option was piggy backing on the domain name system. Most public internet services will assign a new client an IP address, then re-direct whatever domain their browser has requested to a gateway page, where they can sign up for access. There have been many clients and tutorials that exploit this to let you tunnel your data across the internet.

But the clients I found were too complicated to setup or required specific platforms or had strange dependencies. What would be ideal would be a client that:
  • required very little in the way of additional software
  • ran completely in user space and was small and portable enough to run on a Nokia n770
  • supported different DNS message formats to get around DNS access controls
  • took basic security precautions, like using chroot and dropping privileges
  • was fast enough to minimize lag and frustration
OzymanDNS, while awesome, had a number of dependencies and required a threaded Perl -- way too complicated to install on an embedded device.

So I decided to see if I could come up with something that worked better. sods is a small client and server written in C that will tunnel ssh connections over DNS. If I remember correctly, it was at least 4 times as fast as the Perl version, but such is its awesomeness, I wouldn't be surprised if it were 10 or 20 times faster. Let me emphasize this: this client may even approach dial up line speeds.

Of course, being written in C, it has many other advantages as well: lower latency, lower memory usage, efficient use of CPU and tweaks for power saving, segmentation faults, memory leaks, the possibility of amusing buffer overflows that entail an enjoyable evening cleaning up root'ed servers. But you may just get a discount on Viagara from all of that spam you've been sending out!

I'll discuss how to set up sods in another post. If you want to download and play with sods, it can be found on github.