celzero / firestack

Userspace wireguard and network monitor
https://rethinkdns.com/app
Mozilla Public License 2.0
87 stars 15 forks source link

Help integrating DNS proxy into Orbot #13

Closed n8fr8 closed 2 years ago

n8fr8 commented 2 years ago

We are moving Orbot to go-tun2socks (and perhaps eventually firestack), but are having trouble with proxying the DNS to the Tor localhost:5400 service.

Work is happening here: https://github.com/guardianproject/orbot/tree/dev_gotun2socks

and integration with go-tun2socks is here (due to the fact we can only have one golib in the app): https://github.com/n8fr8/IPtProxy/commits/master

Any tips, insights, help would be appreciated, and we do also have funding to support paid assistance. Contact me @n8fr8 on irc, matrix, twitter, etc, or nathan@guardianproject.info email.

n8fr8 commented 2 years ago

(also want to talk about long term firestack plans and/or integration of tor-android into firestack/rethink)

n8fr8 commented 2 years ago

Currently thinking we need to integrate this code: https://github.com/celzero/firestack/blob/rdns/intra/dnsproxy/upstream.go

into the current UDP handler: https://github.com/n8fr8/IPtProxy/commit/76d5ece5ef80fe3a231e66122055138bc9871a28

ignoramous commented 2 years ago

Hi Nathan,

(also want to talk about long term firestack plans and/or integration of tor-android into firestack/rethink)

You've caught us at the wrong time: We're midway through an now-paused rewrite (to be resumed soon), and are leaning towards deprecating go-tun2socks (as upstream has been archived in favour of leaf which a rust-lang wrapper on tun2socks, but sure enough, there indeed are bugs in the golang impl that would now linger forever).

Most likely we move to netstack (#3) or drop user-space tcp/ip altogether and transition to being a stateless firewall that operates on individual packets (gopacket) than on protocols: That is, not transparent-proxy anything whatsoever. Or may be, operate firestack in two modes, stateful and stateless... I'd imagine that to be painful to maintain but worth a shot nevertheless (since the stateful bits are already in place).

Any tips, insights, help would be appreciated, and we do also have funding to support paid assistance. Contact me @n8fr8 on irc, matrix, twitter, etc, or nathan@guardianproject.info email.

Sure thing! I'll take a look at IPtProxy, and get back in a day or so.

n8fr8 commented 2 years ago

Thanks! We love Leaf to and are already using it on iOS with our new Orbot for iOS there. It is working well. However, on Android, it created a huge library dependency on top of our existing go IPtProxy library size, so we opted to try go-tun2socks.

Anyhow, thanks for whatever advice you can provide, and still want to talk about how we can support your important work in any way.

ignoramous commented 2 years ago

(also want to talk about long term firestack plans and/or integration of tor-android into firestack/rethink)

Hi Nathan, lets catch up over a Signal session or something? I've sent you an email.

Any tips, insights

So, from the handler, all you need to do is proxy the request as-is to whatever udp endpoint is capable of handling DNS (in this case, I assume its localhost:5400?). Firestack's dnsproxy:upstream.go and intra:udp.go is unnecessary for Orbot because unlike Firestack, Orbot only needs to forward ALL udp packets to the loopback, which is relatively straight-forward.

I haven't tested this with Orbot's code to know whether it would work, but I'd wager that this generic 'proxying' handler would work:

// adapted from github.com/Jigsaw-Code/outline-go-tun2socks/blob/11e6e39/shadowsocks/udp.go
package proxy

import (
    "errors"
    "fmt"
    "net"
    "sync"
    "time"

    "github.com/eycorsican/go-tun2socks/core"
)

type udpHandler struct {
    sync.Mutex

    timeout        time.Duration
        // maps redirected core.UDPConn to a local proxy-conn that pipes data to upstream
    udpConns       map[core.UDPConn]*net.UDPConn
        // upstream is the target udp server
        // app -> tun -> lwip -> this-handler -> upstream
        // core.UDPConn <--> proxy-conn <--> upstream
    upstream       *net.UDPAddr
}

// constructor
func NewUDPHandler(target string, timeout time.Duration) core.UDPConnHandler {
    return &udpHandler{
        timeout:        timeout,
        udpConns:       make(map[core.UDPConn]*net.UDPConn),
                // target is localhost:5400
        upstream:       net.ResolveUDPAddr("udp", target),
    }
}

// drain proxy-conn (connected to upstream) into core.UDPConn (connected to android/app/tun)
func (h *udpHandler) fetchUDPInput(conn core.UDPConn, pc *net.UDPConn) {
    buf := core.NewBytes(core.BufSize)

    defer func() {
        h.Close(conn)
        core.FreeBytes(buf)
    }()

    for {
                // extend hole-punch deadline
        pc.SetDeadline(time.Now().Add(h.timeout))

                // reads data from pc into buf upto n bytes
        n, who, err := pc.ReadFrom(buf)

        if err != nil { // break
            return
        }

        // write n bytes of buf to downstream conn (tun) with spoofed source, who
                // who is usually upstream
        _, err = conn.WriteFrom(buf[:n], who)

        if err != nil { // break
            return
        }
    }
}

// Connect spoofs core.UDPConn's connection to target via a proxy-conn to upstream
func (h *udpHandler) Connect(conn core.UDPConn, target *net.UDPAddr) error {
        // create an unconnected udp socket, this is our proxy-conn
    bindAddr := &net.UDPAddr{IP: nil, Port: 0}
    pc, err := net.ListenUDP("udp", bindAddr)

    if err != nil {
        return err
    }

    h.Lock()
        // assign this proxy-conn to this incoming core.UDPConn from android/app (tun)
    h.udpConns[conn] = pc
    h.Unlock()

        // drain proxy-conn (pc) and pipe it to core.UDPConn (tun)
    go h.fetchUDPInput(conn, pc)

    return nil
}

// ReceiveTo receives data from core.UDPConn and sends it to upstream via
// proxy-conn instead of sending it to addr
func (h *udpHandler) ReceiveTo(conn core.UDPConn, data []byte, addr *net.UDPAddr) error {
    h.Lock()
    pc, ok := h.udpConns[conn]
    h.Unlock()

    if ok {
        _, err := pc.WriteTo(data, h.upstream)
        return err // err could be nil
    } else {
        return errors.New("proxy-conn missing")
    }
}

// remove the udp hole-punch mapping between
// core.UDPConn (tun) <-> proxy-conn (this-handler) <-> upstream (localhost:5400)
func (h *udpHandler) Close(conn core.UDPConn) {
    conn.Close()

    h.Lock()
    defer h.Unlock()

    if pc, ok := h.udpConns[conn]; ok {
        pc.Close()
        delete(h.udpConns, conn)
    }
}

I would have tested the above with Orbot, but then I came across this commit https://github.com/guardianproject/orbot/commit/416107699fe35b7371c51ac008f9ce598f8d6b1d and figured there's another way you've found to make it all work (: Sorry I slept on this task for a bit more than I should have. If not, let me know in case we should pair program integrate this thing over jitsi/signal session or something.

Just a note: In my reading of the code, it looks like Outline's shadowsocks implementation (1, 2) is more similar to Orbot's integration with eycorsican/go-tun2socks than Intra's (or RethinkDNS' for that matter, both of which focus on proxying DNS over multiple protocols a bit too much, complicating otherwise simpler code).

(also want to talk about long term firestack plans and/or integration of tor-android into firestack/rethink)

Sent you an email from mzat celzero dot com

into the current UDP handler: https://github.com/n8fr8/IPtProxy/commit/76d5ece5ef80fe3a231e66122055138bc9871a28

(I am not sure if this is already known to the Orbot/Tor developers but, I will err on the side of caution and spell it out anyway): This UDP handler responds with a truncated DNS answer for ALL queries. Android netd would then retry those same queries over TCP. This means, DNS can be handled just as Orbot (go-tun2socks) would handle any TCP request, and no special handling for UDP is required.

ignoramous commented 2 years ago

@n8fr8

Closing this issue since Orbot DNS is all up and working. Feel free to reach out over email or Signal, I am here ready to volunteer in any which way for Orbot.

Btw, RethinkDNS has moved to gvisor/netstack, and it has been working wonderfully well: https://github.com/celzero/firestack/tree/n2