Does FreeBSD have the ability to properly forward UDP traffic ?

Eygene Ryabinkin rea at freebsd.org
Mon Jun 9 10:08:55 UTC 2014


Good day.

Fri, Jun 06, 2014 at 11:25:33PM -0700, None Secure via freebsd-net wrote:
> I would like very much to use sshuttle for an informal VPN.
> 
> However, sshuttle sets up a lot of complexity in order to route DNS
> requests over the ssh tunnel ... it uses divert rules for dns
> traffic, and I don't think they even tested it because it fails to
> start or utilize natd.

sshuttle doesn't need natd, divert(4) sockets are in use.  It is the
mechanism that is used to forward packets from, say, ipfw to the
userland and back to ipfw.  You have divert rule in ipfw that puts
a packet to the divert(4) socket that is identified by it's numeric
port (that has nothing to do with the TCP/UDP ports, it is just an
identified for different divert(4) listeners).  User-space application
listent to this socket and can read and write packets, thus consuming
input ones and generating output ones.  Sshuttle listens to the divert
socket:
{{{
    if dnsport:
        divertsock = socket.socket(socket.AF_INET, socket.SOCK_RAW,
                                   IPPROTO_DIVERT)
        divertsock.bind(('0.0.0.0', port)) # IP field is ignored

        nslist = resolvconf_nameservers()
        for ip in nslist:
            # relabel and then catch outgoing DNS requests
            ipfw('add', sport, 'divert', sport,
                 'udp',
                 'from', 'any', 'to', '%s/32' % ip, '53',
                 'not', 'ipttl', '42')
        # relabel DNS responses
        ipfw('add', sport, 'divert', sport,
             'udp',
             'from', 'any', str(dnsport), 'to', 'any',
             'not', 'ipttl', '42')

        def do_wait():
            while 1:
                r,w,x = select.select([sys.stdin, divertsock], [], [])
                if divertsock in r:
                    _handle_diversion(divertsock, dnsport)
                if sys.stdin in r:
                    return
    else:
        do_wait = None

    return do_wait

def _handle_diversion(divertsock, dnsport):
    p,tag = divertsock.recvfrom(4096)
    src,dst = _udp_unpack(p)
    debug3('got diverted packet from %r to %r\n' % (src, dst))
    if dst[1] == 53:
        # outgoing DNS
        debug3('...packet is a DNS request.\n')
        _real_dns_server[0] = dst
        dst = ('127.0.0.1', dnsport)
    elif src[1] == dnsport:
        if islocal(src[0]):
            debug3('...packet is a DNS response.\n')
            src = _real_dns_server[0]
    else:
        log('weird?! unexpected divert from %r to %r\n' % (src, dst))
        assert(0)
    newp = _udp_repack(p, src, dst)
    divertsock.sendto(newp, tag)
}}}
so it doesn't need natd at all.

> The stated reason by sshuttle project is that you can't just forward
> UDP traffic properly with BSD, like you can with linux - they say it
> doesn't keep track of port numbers or connections properly.

Are you referring to the following comment from
  https://github.com/apenwarr/sshuttle/blob/master/firewall.py
{{{
    # This part is much crazier than it is on Linux, because MacOS (at least
    # 10.6, and probably other versions, and maybe FreeBSD too) doesn't
    # correctly fixup the dstip/dstport for UDP packets when it puts them
    # through a 'fwd' rule. It also doesn't fixup the srcip/srcport in the
    # response packet. In Linux iptables, all that happens magically for us,
    # so we just redirect the packets and relax.
    #
    # On MacOS, we have to fix the ports ourselves. For that, we use a
    # 'divert' socket, which receives raw packets and lets us mangle them.
    #
    # Here's how it works. Let's say the local DNS server is 1.1.1.1:53,
    # and the remote DNS server is 2.2.2.2:53, and the local transproxy port
    # is 10.0.0.1:12300, and a client machine is making a request from
    # 10.0.0.5:9999. We see a packet like this:
    # 10.0.0.5:9999 -> 1.1.1.1:53
    # Since the destip:port matches one of our local nameservers, it will
    # match a 'fwd' rule, thus grabbing it on the local machine. However,
    # the local kernel will then see a packet addressed to *:53 and
    # not know what to do with it; there's nobody listening on port 53. Thus,
    # we divert it, rewriting it into this:
    # 10.0.0.5:9999 -> 10.0.0.1:12300
    # This gets proxied out to the server, which sends it to 2.2.2.2:53,
    # and the answer comes back, and the proxy sends it back out like this:
    # 10.0.0.1:12300 -> 10.0.0.5:9999
    # But that's wrong! The original machine expected an answer from
    # 1.1.1.1:53, so we have to divert the *answer* and rewrite it:
    # 1.1.1.1:53 -> 10.0.0.5:9999
    #
    # See? Easy stuff.
}}}

ipfw(8) 'fwd' rule isn't touching the internals of the packet, it
really just forwards it "as is".  This has nothing to do with UDP,
'fwd' is just intended to be used for transparent proxying, not for
"real" forwarding that implies rewriting packet contents.

sshuttle folks say about UDP just because of the following property of
SSH tunnelling: when you're doing it, you have something that listens
to localhost:N on TCP.  So you can forward packets to localhost:N
without any particular problems, they will be consumed by the proper
application (SSH) and be forwarded to the remote end.

But for DNS packets sshuttle does the following: it just forwards them
to the local host and some local port where sshuttle's own DNS proxy
lives.  So, in reality, there is no forwarding or tunnelling of
DNS/UDP requests in the normal sense: sshuttle wants to proxy them
via itself.  And for this it has to mangle the packets via divert
sockets on FreeBSD.

What I don't quite understand is that why sshuttle needs this dance
of diverting and can't just consume incoming DNS packet like SSH's
tunneling port will, reinterepret it and put the answer back with
the proper src/dst fields inside the UDP packet.  But probably there's
some explanation: I'll try to think of it later.

> Or is it possible to properly forward UDP traffic with ipfw rules,
> and not use natd/divert ?

What is your real problem with sshuttle?  If you concerned that no
natd(8) is spawned, then you shouldn't worry about that.  If something
doesn't work for you, please, be specific, what doesn't work, which
FreeBSD and sshuttle versions you're using and what is your
configuration.

Hope that helps.
-- 
Eygene Ryabinkin                                        ,,,^..^,,,
[ Life's unfair - but root password helps!           | codelabs.ru ]
[ 82FE 06BC D497 C0DE 49EC  4FF0 16AF 9EAE 8152 ECFB | freebsd.org ]
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 358 bytes
Desc: not available
URL: <http://lists.freebsd.org/pipermail/freebsd-net/attachments/20140609/2df259eb/attachment.sig>


More information about the freebsd-net mailing list