diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2022-02-17 23:39:16 +0100 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2022-03-18 22:07:58 +0100 |
commit | ed693a8e2f5f493655dc0810c281af2207537a96 (patch) | |
tree | 0c7e7b06631230864b608c7137353338e9270d4a /tunnel | |
parent | 75ce7ab5b8d2657822f28812826ecccfa08e88a1 (diff) |
WIP transparent proxy
Diffstat (limited to 'tunnel')
-rw-r--r-- | tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java | 20 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/api-android.go | 12 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/conntrack.go | 45 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/go.mod | 1 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/go.sum | 2 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/http-proxy.go | 278 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/nat-tun.go | 383 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/service.go | 17 | ||||
-rw-r--r-- | tunnel/tools/libwg-go/transparent.go | 31 |
9 files changed, 545 insertions, 244 deletions
diff --git a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java index cc22b548..5d31dc84 100644 --- a/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/tunnel/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -14,6 +14,7 @@ import android.net.ProxyInfo; import android.net.Uri; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.system.OsConstants; import android.util.Log; @@ -342,19 +343,24 @@ public final class GoBackend implements Backend { StreamObserver<ReverseResponse> responseObserver = new StreamObserver<ReverseResponse>() { @Override public void onNext(ReverseResponse resp) { + String pkg = ""; int uid = connectivityManager.getConnectionOwnerUid(resp.getUid().getProtocol(), toInetSocketAddress(resp.getUid().getLocal()), toInetSocketAddress(resp.getUid().getRemote())); - PackageManager pm = context.getPackageManager(); - String pkg = pm.getNameForUid(uid); - String[] pkgs = pm.getPackagesForUid(uid); - Log.i(TAG, "reverse onNext uid:" + uid + " package:" + pkg); - for (int i=0; i < pkgs.length; i++) { - Log.i(TAG, "getPackagesForUid() = " + pkgs[i]); + if (uid != Process.INVALID_UID) { + PackageManager pm = context.getPackageManager(); + pkg = pm.getNameForUid(uid); + String[] pkgs = pm.getPackagesForUid(uid); + Log.i(TAG, "reverse onNext uid:" + uid + " package:" + pkg); + for (int i=0; i < pkgs.length; i++) { + Log.i(TAG, "getPackagesForUid() = " + pkgs[i]); + } + } else { + Log.i(TAG, "Connection not found"); } ReverseRequest req = ReverseRequest.newBuilder() .setUid(GetConnectionOwnerUidResponse.newBuilder() .setUid(uid) - .setPackage(pkg) + .setPackage(pkg != null ? pkg: "") .build()) .build(); diff --git a/tunnel/tools/libwg-go/api-android.go b/tunnel/tools/libwg-go/api-android.go index 2ce9c49c..2dc5f53b 100644 --- a/tunnel/tools/libwg-go/api-android.go +++ b/tunnel/tools/libwg-go/api-android.go @@ -82,13 +82,23 @@ func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 { nativeTun, name, err := CreateUnmonitoredTUNFromFD(int(tunFd)) - tun, err := NewNatTun(nativeTun) + ct := NewConntrack() + service.http_proxy.SetConntrack(ct) + + tun, err := NewNatTun(nativeTun, ct) if err != nil { unix.Close(int(tunFd)) logger.Errorf("CreateUnmonitoredTUNFromFD: %v", err) return -1 } + if service != nil && service.http_proxy != nil { + tun.addTranslation(false, 80, int(service.http_proxy.addrPort.Port())) + tun.addTranslation(false, 443, int(service.http_proxy.tlsAddrPort.Port())) + tun.addTranslation(true, 80, int(service.http_proxy.addrPort.Port())) + tun.addTranslation(true, 443, int(service.http_proxy.tlsAddrPort.Port())) + } + logger.Verbosef("Attaching to interface %v", name) device := device.NewDevice(tun, conn.NewStdNetBind(), logger) diff --git a/tunnel/tools/libwg-go/conntrack.go b/tunnel/tools/libwg-go/conntrack.go new file mode 100644 index 00000000..788b1b80 --- /dev/null +++ b/tunnel/tools/libwg-go/conntrack.go @@ -0,0 +1,45 @@ +package main + +import ( + "sync" + + "golang.zx2c4.com/go118/netip" +) + +type connection struct { + src netip.AddrPort + dst netip.AddrPort +} + +func Connection(src, dst netip.AddrPort) connection { + return connection{ + src: src, + dst: dst, + } +} + +type Conntrack struct { + connections map[connection]connection + connectionsMutex sync.RWMutex +} + +func NewConntrack() *Conntrack { + return &Conntrack{ + connections: make(map[connection]connection), + connectionsMutex: sync.RWMutex{}, + } +} + +func (ct *Conntrack) addConnection(new, orig connection) { + ct.connectionsMutex.Lock() + ct.connections[new] = orig + ct.connectionsMutex.Unlock() +} + +func (ct *Conntrack) lookupConnection(new connection) (connection, bool) { + ct.connectionsMutex.RLock() + c, ok := ct.connections[new] + ct.connectionsMutex.RUnlock() + return c, ok +} + diff --git a/tunnel/tools/libwg-go/go.mod b/tunnel/tools/libwg-go/go.mod index 00105ab0..7df5aa4d 100644 --- a/tunnel/tools/libwg-go/go.mod +++ b/tunnel/tools/libwg-go/go.mod @@ -13,6 +13,7 @@ require ( require ( github.com/golang/protobuf v1.5.2 // indirect + github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b // indirect golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect golang.org/x/net v0.0.0-20211111083644-e5c967477495 // indirect golang.org/x/text v0.3.6 // indirect diff --git a/tunnel/tools/libwg-go/go.sum b/tunnel/tools/libwg-go/go.sum index 9039129d..b990a8c3 100644 --- a/tunnel/tools/libwg-go/go.sum +++ b/tunnel/tools/libwg-go/go.sum @@ -50,6 +50,8 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE= +github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= diff --git a/tunnel/tools/libwg-go/http-proxy.go b/tunnel/tools/libwg-go/http-proxy.go index 3c0bef47..54b56f79 100644 --- a/tunnel/tools/libwg-go/http-proxy.go +++ b/tunnel/tools/libwg-go/http-proxy.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/elazarl/goproxy" + "github.com/inconshreveable/go-vhost" "golang.zx2c4.com/go118/netip" "golang.zx2c4.com/wireguard/device" @@ -17,6 +18,10 @@ import ( ) const ( + APIPA_PREFIX = "169.254.0.0/16" +// ULA_PREFIX = "fdba:4b51:1606:a61d::/64" + ULA_PREFIX = "fdba:4b51:1606:0000::/64" + // Imported from Firefox' ProxyAutoConfig.cpp // https://searchfox.org/mozilla-central/source/netwerk/base/ProxyAutoConfig.cpp // This Source Code Form is subject to the terms of the Mozilla Public @@ -257,23 +262,27 @@ const ( ) type HttpProxy struct { + conntrack *Conntrack listener net.Listener + tlsListener net.Listener logger *device.Logger addrPort netip.AddrPort - uidRequest chan netip.AddrPort - uidResponse chan string + tlsAddrPort netip.AddrPort pacFileUrl *url.URL ctx *duktape.Context + defaultProxy *goproxy.ProxyHttpServer + uidRequest chan AddrPortPair + uidResponse chan string } -func NewHttpProxy(uidRequest chan netip.AddrPort, uidResponse chan string, logger *device.Logger, pacFileUrl *url.URL) *HttpProxy { +func NewHttpProxy(uidRequest chan AddrPortPair, uidResponse chan string, logger *device.Logger, pacFileUrl *url.URL) *HttpProxy { logger.Verbosef("NewHttpProxy") return &HttpProxy{ listener: nil, logger: logger, + pacFileUrl: pacFileUrl, uidRequest: uidRequest, uidResponse: uidResponse, - pacFileUrl: pacFileUrl, } } @@ -281,6 +290,10 @@ var ( ASCII_PAC_UTILS_NAMES = []string{"dnsDomainIs", "dnsDomainLevels", "isValidIpAddress", "convert_addr", "isInNet", "isPlainHostName", "isResolvable", "localHostOrDomainIs", "shExpMatch", "weekdayRange", "dateRange", "timeRange"} ) +func (p *HttpProxy) SetConntrack(ct *Conntrack) { + p.conntrack = ct +} + func (p *HttpProxy) GetAddrPort() netip.AddrPort { return p.addrPort } @@ -292,6 +305,15 @@ func newGoProxy(proxyUrl string) *goproxy.ProxyHttpServer { return url.Parse(proxyUrl) } proxy.ConnectDial = proxy.NewConnectDialToProxy(proxyUrl) + proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Host == "" { + fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0") + return + } + req.URL.Scheme = "http" + req.URL.Host = req.Host + proxy.ServeHTTP(w, req) + }) return proxy } @@ -419,34 +441,104 @@ func (p *HttpProxy) Start() (listen_port uint16, err error) { proxyMap["bbc.iplayer.android"] = newGoProxy("http://10.49.124.111:8888") proxyMap["no.nrk.tv"] = newGoProxy("http://10.49.124.115:8888") - defaultProxy := goproxy.NewProxyHttpServer() - defaultProxy.Verbose = true + // TODO: Debugging NATed tls proxy + // defaultProxy := goproxy.NewProxyHttpServer() + // defaultProxy.Verbose = true + p.defaultProxy = newGoProxy("http://10.49.32.1:8888") + proxyMap[""] = p.defaultProxy + + listen_port, err = p.startRegularProxy(pacFileBody, proxyMap) + if err != nil { + return + } + + err = p.startTlsProxy(pacFileBody, proxyMap) + if err != nil { + p.Stop() + } + return +} -// listener, err := net.Listen("tcp", "localhost:") - listener, err := net.Listen("tcp", "169.254.0.2:") +func (p *HttpProxy) startRegularProxy(pacFileBody string, proxyMap map[string]*goproxy.ProxyHttpServer) (listen_port uint16, err error) { + p.listener, err = net.Listen("tcp", "[::]:") if err != nil { return } - p.addrPort, err = netip.ParseAddrPort(listener.Addr().String()) + p.addrPort, err = netip.ParseAddrPort(p.listener.Addr().String()) if err != nil { return } listen_port = p.addrPort.Port() - handler := NewHttpHandler(defaultProxy, p.logger) + handler := NewHttpHandler(p, p.defaultProxy, pacFileBody, proxyMap, p.logger) + + go http.Serve(NewUidListener(p, p.listener, handler, p.logger), handler) + return +} - go http.Serve(NewUidListener(listener, handler, p.uidRequest, p.uidResponse, pacFileBody, proxyMap, p.logger), handler) +func (p *HttpProxy) startTlsProxy(pacFileBody string, proxyMap map[string]*goproxy.ProxyHttpServer) (err error) { + p.tlsListener, err = net.Listen("tcp", "[::]:") + if err != nil { + return + } - return + p.tlsAddrPort, err = netip.ParseAddrPort(p.tlsListener.Addr().String()) + if err != nil { + return + } + + handler := NewHttpHandler(p, p.defaultProxy, pacFileBody, proxyMap, p.logger) + + go func () { + for { + conn, err := p.tlsListener.Accept() + + if err != nil { + p.logger.Verbosef("Error accepting new connection - %v", err) + return + } + + go func(conn net.Conn) { + tlsConn, err := vhost.TLS(conn) + if err != nil { + p.logger.Verbosef("Error TLS connection - %v", err) + } + if tlsConn.Host() == "" { + p.logger.Verbosef("Cannot support non-SNI enabled clients") + return + } + handler.addConnToProxyMap(conn) + connectReq := &http.Request{ + Method: "CONNECT", + URL: &url.URL{ + Opaque: tlsConn.Host(), + Host: net.JoinHostPort(tlsConn.Host(), "443"), + }, + Host: tlsConn.Host(), + Header: make(http.Header), + // TODO fetch remote addr from NAT map + RemoteAddr: conn.RemoteAddr().String(), + } + resp := dumbResponseWriter{tlsConn} + handler.ServeHTTP(resp, connectReq) + }(conn) + } + }() + return } func (p *HttpProxy) Stop() { if p.listener != nil { p.logger.Verbosef("Close: %v", p.listener) p.listener.Close() - p.listener = nil +// p.listener = nil + } + if p.tlsListener != nil { + p.logger.Verbosef("Close: %v", p.tlsListener) + p.tlsListener.Close() +// p.tlsListener = nil } if p.ctx != nil { p.ctx.DestroyHeap() @@ -455,19 +547,82 @@ func (p *HttpProxy) Stop() { } type HttpHandler struct { + p *HttpProxy defaultProxy *goproxy.ProxyHttpServer logger *device.Logger remoteAddrPkgMap map[string]*goproxy.ProxyHttpServer + apipaPrefix netip.Prefix + ulaPrefix netip.Prefix + uidRequest chan AddrPortPair + uidResponse chan string + ctx *duktape.Context + proxyMap map[string]*goproxy.ProxyHttpServer } -func NewHttpHandler(defaultProxy *goproxy.ProxyHttpServer, logger *device.Logger) *HttpHandler{ - return &HttpHandler{ +func NewHttpHandler(p *HttpProxy, defaultProxy *goproxy.ProxyHttpServer, pacFileBody string, proxyMap map[string]*goproxy.ProxyHttpServer, logger *device.Logger) *HttpHandler{ + apipa, _ := netip.ParsePrefix(APIPA_PREFIX) + ula, _ := netip.ParsePrefix(ULA_PREFIX) + + h := &HttpHandler{ + p: p, defaultProxy: defaultProxy, logger: logger, remoteAddrPkgMap: make(map[string]*goproxy.ProxyHttpServer), + apipaPrefix: apipa, + ulaPrefix: ula, + uidRequest: p.uidRequest, + uidResponse: p.uidResponse, + proxyMap: proxyMap, + } + + if pacFileBody != "" { + h.ctx = newPacFileCtx(pacFileBody, logger) } + + return h } + +func (h *HttpHandler) addConnToProxyMap(c net.Conn) { + h.logger.Verbosef("Accept: %v -> %v", c.RemoteAddr().String(), c.LocalAddr().String()) + local, err := netip.ParseAddrPort(c.RemoteAddr().String()) + remote, err2 := netip.ParseAddrPort(c.LocalAddr().String()) + if err != nil || err2 != nil{ + // Skip + } else { + newConn := connection{src: local, dst: remote} + oldConn, ok := h.p.conntrack.lookupConnection(newConn) + + if ok { + local = oldConn.src + remote = oldConn.dst + h.logger.Verbosef("Before NAT: %v -> %v", local, remote) + } + + if !h.apipaPrefix.Contains(local.Addr()) && !h.ulaPrefix.Contains(local.Addr()) { + h.logger.Verbosef("uidRequest") + h.uidRequest <- AddrPortPair{local: local, remote: remote} + + // TODO add timeout? + select { + case pkg := <-h.uidResponse: + h.logger.Verbosef("uidResponse: '%v'", pkg) + + proxy, ok := h.proxyMap[pkg] + if !ok { + proxy = h.findProxyForPkg(pkg) + } + + if proxy != nil { + h.remoteAddrPkgMap[c.RemoteAddr().String()] = proxy + } + } + } else { + h.logger.Verbosef("uidRequest skipped, %v -> %v", local, remote) + } + } +} + func (h *HttpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { proxy, ok := h.remoteAddrPkgMap[req.RemoteAddr] if ok && proxy != nil { @@ -481,48 +636,21 @@ func (h *HttpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { proxy.ServeHTTP(rw, req) } else { - h.defaultProxy.ServeHTTP(rw, req) + h.defaultProxy.ServeHTTP(rw, req) } } -// UidListener -type UidListener struct { - l net.Listener - handler *HttpHandler - logger *device.Logger - uidRequest chan netip.AddrPort - uidResponse chan string - ctx *duktape.Context - proxyMap map[string]*goproxy.ProxyHttpServer -} - -func NewUidListener(listener net.Listener, handler *HttpHandler, uidRequest chan netip.AddrPort, uidResponse chan string, pacFileBody string, proxyMap map[string]*goproxy.ProxyHttpServer, logger *device.Logger) *UidListener{ - l := &UidListener{ - l: listener, - handler: handler, - logger: logger, - uidRequest: uidRequest, - uidResponse: uidResponse, - proxyMap: proxyMap, - } - - if pacFileBody != "" { - l.ctx = newPacFileCtx(pacFileBody, logger) - } - return l -} - -func (l *UidListener) findProxyForPkg(pkg string) *goproxy.ProxyHttpServer { - if l.ctx == nil { +func (h *HttpHandler) findProxyForPkg(pkg string) *goproxy.ProxyHttpServer { + if h.ctx == nil { return nil } find := func() (res string, err error) { - l.logger.Verbosef("Call FindProxyForPkg %v %v", pkg) - res, err = FindProxyForPkg(l.ctx, pkg, l.logger) - l.logger.Verbosef("FindProxyForPkg res %v %v", res, err) + h.logger.Verbosef("Call FindProxyForPkg %v %v", pkg) + res, err = FindProxyForPkg(h.ctx, pkg, h.logger) + h.logger.Verbosef("FindProxyForPkg res %v %v", res, err) if err != nil { - l.logger.Verbosef("FindProxyForPkg result is: %v stack:%v", res, l.ctx.GetTop()) + h.logger.Verbosef("FindProxyForPkg result is: %v stack:%v", res, h.ctx.GetTop()) return "", err } else { values := strings.Split(strings.Trim(res, " "), ";") @@ -542,38 +670,44 @@ func (l *UidListener) findProxyForPkg(pkg string) *goproxy.ProxyHttpServer { } proxy := newGoProxy("http://" + res) - l.proxyMap[res] = proxy + h.proxyMap[res] = proxy return proxy } -func (l *UidListener) Accept() (net.Conn, error) { - c, err := l.l.Accept() - if err != nil { - return c, err - } +type AddrPortPair struct { + local netip.AddrPort + remote netip.AddrPort +} - l.logger.Verbosef("Accept: %v", c.RemoteAddr().String()) - addr_port, err := netip.ParseAddrPort(c.RemoteAddr().String()) - if err == nil { - l.logger.Verbosef("uidRequest") - l.uidRequest <- addr_port +// UidListener +type UidListener struct { + p *HttpProxy + l net.Listener + handler *HttpHandler + logger *device.Logger +} - // TODO add timeout? - select { - case pkg := <-l.uidResponse: - l.logger.Verbosef("uidResponse: %v", pkg) +func NewUidListener(httpProxy *HttpProxy, listener net.Listener, handler *HttpHandler, logger *device.Logger) *UidListener{ + l := &UidListener{ + p: httpProxy, + l: listener, + handler: handler, + logger: logger, + } - proxy, ok := l.proxyMap[pkg] - if !ok { - proxy = l.findProxyForPkg(pkg) - } + return l +} - if proxy != nil { - l.handler.remoteAddrPkgMap[c.RemoteAddr().String()] = proxy - } - } +func (l *UidListener) Accept() (net.Conn, error) { + c, err := l.l.Accept() + if err != nil { + l.logger.Verbosef("Accept failed: %v", err) + return c, err } + + l.handler.addConnToProxyMap(c) + return c, nil } diff --git a/tunnel/tools/libwg-go/nat-tun.go b/tunnel/tools/libwg-go/nat-tun.go index abb3f9a5..529921d3 100644 --- a/tunnel/tools/libwg-go/nat-tun.go +++ b/tunnel/tools/libwg-go/nat-tun.go @@ -5,6 +5,8 @@ package main +// TODO debug IPv6 NAT + import ( "encoding/binary" "fmt" @@ -15,85 +17,87 @@ import ( "golang.zx2c4.com/wireguard/tun" ) -type connection struct { - src netip.AddrPort - dst netip.AddrPort -} - -func Connection(src, dst netip.AddrPort) connection { - return connection{ - src: src, - dst: dst, - } -} - -type natTun struct { - tun tun.Device - intAddr netip.Addr - extAddr netip.Addr +type natEntry struct { + // intAddr netip.Addr + // extAddr netip.Addr + ipVersion int + loop bool srcAddr netip.Addr + dstPort int proxyAddr netip.Addr proxyPort int - connections map[connection]connection } -func NewNatTun(t tun.Device) (dev tun.Device, err error) { - dev = nil - - extAddr, err := netip.ParseAddr("10.49.40.151") - if err != nil { - return - } - - intAddr, err := netip.ParseAddr("10.49.40.101") - if err != nil { - return - } - - proxyPort := 0 - var proxyAddr netip.Addr - var srcAddr netip.Addr - if service != nil && service.http_proxy != nil { - srcAddr, err = netip.ParseAddr("169.254.0.1") - if err != nil { - return - } - - proxyPort = int(service.http_proxy.addrPort.Port()) - proxyAddr, err = netip.ParseAddr("169.254.0.2") - if err != nil { - return - } - } else { - proxyPort = 8888 - proxyAddr, err = netip.ParseAddr("10.49.124.115") - if err != nil { - return - } - } +type natTun struct { + conntrack *Conntrack + tun tun.Device + translations []natEntry +} +func NewNatTun(t tun.Device, ct *Conntrack) (dev *natTun, err error) { dev = &natTun{ + conntrack: ct, tun: t, - intAddr: intAddr, - extAddr: extAddr, - srcAddr: srcAddr, - proxyAddr: proxyAddr, - proxyPort: proxyPort, - connections: make(map[connection]connection), + translations: make([]natEntry, 4), } err = nil return } -func (tun *natTun) addConnection(new, orig connection) { - // TODO use mutex - tun.connections[new] = orig -} +func (tun *natTun) addTranslation(ipv6 bool, dstPort int, proxyPort int) { + var ipVersion int + loop := true + var srcStr string + var proxyStr string + var srcAddr netip.Addr + + if ipv6 { + ipVersion = IPV6_VERSION + //srcStr = "fe80::1" + //proxyStr = "fe80::2" + srcStr = "fdba:4b51:1606:a61d::1" + proxyStr = "fdba:4b51:1606:a61d::2" + // loop = false + // proxyStr = "2001:470:de6f:2fff::f780" + // proxyPort = 8888 + } else { + ipVersion = IPV4_VERSION + srcStr = "169.254.0.1" + proxyStr = "169.254.0.2" + } + + if srcStr != "" { + var err error + srcAddr, err = netip.ParseAddr(srcStr) + if err != nil { + return + } + } + + proxyAddr, err := netip.ParseAddr(proxyStr) + if err != nil { + return + } + + // proxyPort = 8888 + // proxyAddr, err = netip.ParseAddr("10.49.124.115") + // if err != nil { + // return + // } + + translation := natEntry{ + // intAddr: intAddr, + // extAddr: extAddr, + ipVersion: ipVersion, + loop: loop, + srcAddr: srcAddr, + dstPort: dstPort, + proxyAddr: proxyAddr, + proxyPort: proxyPort, + } -func (tun *natTun) lookupConnection(new connection) (connection, bool) { - c, ok := tun.connections[new] - return c, ok + tun.translations = append(tun.translations, translation) } func (tun *natTun) Name() (string, error) { @@ -161,21 +165,41 @@ func putIPv4DstAddr(header []byte, addr netip.Addr) { copy(header[IPV4_HEADER_DST_ADDR:IPV4_HEADER_DST_ADDR+4], addr.AsSlice()) } +func getIPv6SrcAddr(header []byte) netip.Addr { + src, _ := netip.AddrFromSlice(header[IPV6_HEADER_SRC_ADDR:IPV6_HEADER_SRC_ADDR+16]) + return src +} + +func putIPv6SrcAddr(header []byte, addr netip.Addr) { + copy(header[IPV6_HEADER_SRC_ADDR:IPV6_HEADER_SRC_ADDR+16], addr.AsSlice()) +} + +func getIPv6DstAddr(header []byte) netip.Addr { + dst, _ := netip.AddrFromSlice(header[IPV6_HEADER_DST_ADDR:IPV6_HEADER_DST_ADDR+16]) + return dst +} + +func putIPv6DstAddr(header []byte, addr netip.Addr) { + copy(header[IPV6_HEADER_DST_ADDR:IPV6_HEADER_DST_ADDR+16], addr.AsSlice()) +} + func getSrcAddr(header []byte, version int) netip.Addr { if version == IPV4_VERSION { return getIPv4SrcAddr(header) + } else if version == IPV6_VERSION { + return getIPv6SrcAddr(header) } else { - // FIXME - return netip.IPv6Unspecified() - } + return netip.IPv6Unspecified() + } } func getDstAddr(header []byte, version int) netip.Addr { if version == IPV4_VERSION { return getIPv4DstAddr(header) + } else if version == IPV6_VERSION { + return getIPv6DstAddr(header) } else { - // FIXME - return netip.IPv6Unspecified() + return netip.IPv6Unspecified() } } @@ -226,52 +250,60 @@ func calculateChecksum(hc int, m, mPrim int) int { } func updateAddr(header []byte, version int, getFunc func (header []byte) netip.Addr, putFunc func (header []byte, addr netip.Addr), updateFunc func (netip.Addr) netip.Addr) { - if version == IPV4_VERSION { - addr := getFunc(header) - newAddr := updateFunc(addr) - - if newAddr != addr { - putFunc(header, newAddr) - // TODO reduce code duplication see updateSrcAddr - updateIPv4Checksum(header, func (checksum int) int { - addr4 := addr.As4() - newAddr4 := newAddr.As4() - checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[0:2])), int(binary.BigEndian.Uint16(newAddr4[0:2]))) - checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[2:4])), int(binary.BigEndian.Uint16(newAddr4[2:4]))) - return checksum - }) - updateTransportChecksum(header, version, func (checksum int) int { - if version == IPV4_VERSION && checksum == 0 { - return 0 - } else { - addr4 := addr.As4() - newAddr4 := newAddr.As4() - checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[0:2])), int(binary.BigEndian.Uint16(newAddr4[0:2]))) - checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[2:4])), int(binary.BigEndian.Uint16(newAddr4[2:4]))) - return checksum - } - }) - } - // } else if version == IPV6_VERSION { - // addr := getIPv6SrcAddr(header) - // putIPv6SrcAddr(header, updateFunc(addr)) + addr := getFunc(header) + newAddr := updateFunc(addr) + + if newAddr != addr { + putFunc(header, newAddr) + if version == IPV4_VERSION { + updateIPv4Checksum(header, func (checksum int) int { + addr4 := addr.As4() + newAddr4 := newAddr.As4() + checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[0:2])), int(binary.BigEndian.Uint16(newAddr4[0:2]))) + checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[2:4])), int(binary.BigEndian.Uint16(newAddr4[2:4]))) + return checksum + }) + } + updateTransportChecksum(header, version, func (checksum int) int { + if checksum == 0 { + // if version == IPV6_VERSION { + // // Log error + // } + return 0 + } else if version == IPV4_VERSION { + addr4 := addr.As4() + newAddr4 := newAddr.As4() + checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[0:2])), int(binary.BigEndian.Uint16(newAddr4[0:2]))) + checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr4[2:4])), int(binary.BigEndian.Uint16(newAddr4[2:4]))) + return checksum + } else if version == IPV6_VERSION { + addr6 := addr.As16() + newAddr6 := newAddr.As16() + for i := 0; i < 8; i++ { + checksum = calculateChecksum(checksum, int(binary.BigEndian.Uint16(addr6[i*2:(i+1)*2])), int(binary.BigEndian.Uint16(newAddr6[i*2:(i+1)*2]))) + } + return checksum + } else { + return checksum + } + }) } } func updateSrcAddr(header []byte, version int, updateFunc func (netip.Addr) netip.Addr ) { if version == IPV4_VERSION { updateAddr(header, version, getIPv4SrcAddr, putIPv4SrcAddr, updateFunc) - } + } else if version == IPV6_VERSION { + updateAddr(header, version, getIPv6SrcAddr, putIPv6SrcAddr, updateFunc) + } } func updateDstAddr(header []byte, version int, updateFunc func (netip.Addr) netip.Addr ) { if version == IPV4_VERSION { updateAddr(header, version, getIPv4DstAddr, putIPv4DstAddr, updateFunc) + } else if version == IPV6_VERSION { + updateAddr(header, version, getIPv6DstAddr, putIPv6DstAddr, updateFunc) } - - // } else if version == IPV6_VERSION { - // addr := getIPv6SrcAddr(header) - // putIPv6SrcAddr(header, updateFunc(addr)) } func updateDstPort(header []byte, version int, updateFunc func (int) int ) { @@ -285,7 +317,10 @@ func updateDstPort(header []byte, version int, updateFunc func (int) int ) { putUint16(transportPayload, TCP_HEADER_DST_PORT, newPort) updateTransportChecksum(header, version, func (checksum int) int { - if version == IPV4_VERSION && checksum == 0 { + if checksum == 0 { + // if version == IPV6_VERSION { + // // Log error + // } return 0 } else { checksum = calculateChecksum(checksum, int(port), int(newPort)) @@ -293,7 +328,6 @@ func updateDstPort(header []byte, version int, updateFunc func (int) int ) { } }) } - // } else if version == IPV6_VERSION { } } @@ -308,7 +342,10 @@ func updateSrcPort(header []byte, version int, updateFunc func (int) int ) { putUint16(transportPayload, TCP_HEADER_SRC_PORT, newPort) updateTransportChecksum(header, version, func (checksum int) int { - if version == IPV4_VERSION && checksum == 0 { + if checksum == 0 { + // if version == IPV6_VERSION { + // // Log error + // } return 0 } else { checksum = calculateChecksum(checksum, int(port), int(newPort)) @@ -316,7 +353,6 @@ func updateSrcPort(header []byte, version int, updateFunc func (int) int ) { } }) } - // } else if version == IPV6_VERSION { } } @@ -398,60 +434,95 @@ func (tun *natTun) Read(buf []byte, offset int) (int, error) { if err != nil { // Ignore bad packet - } else if version == IPV4_VERSION { - isProxy := false - - srcPort := getSrcPort(header, version) - origDstPort := 0 - newDstPort := 0 - var origSrcAddr netip.Addr - var newSrcAddr netip.Addr - var origDstAddr netip.Addr - var newDstAddr netip.Addr - - updateDstPort(header, version, func(port int) int { - // Transparent proxy HTTP and HTTPS - if port == 80 || port == 443 { - isProxy = true - origDstPort = port - newDstPort = tun.proxyPort - return newDstPort - } else { - return port - } - }) - if tun.srcAddr.IsValid() { - updateSrcAddr(header, version, func(addr netip.Addr) netip.Addr { - if isProxy { - origSrcAddr = addr - newSrcAddr = tun.srcAddr - return newSrcAddr - } else { - return addr - } - }) - } - updateDstAddr(header, version, func(addr netip.Addr) netip.Addr { - if isProxy { - origDstAddr = addr - newDstAddr = tun.proxyAddr - return newDstAddr - } else { - return addr - } - }) - - if isProxy { - if !newSrcAddr.IsValid() { + } else if version == IPV4_VERSION || version == IPV6_VERSION { + origDstPort := getDstPort(header, version) + + for _, entry := range(tun.translations) { + if version != entry.ipVersion { + continue + } + + if origDstPort != entry.dstPort { + continue + } + + srcPort := getSrcPort(header, version) + newDstPort := 0 + var origSrcAddr netip.Addr + var origDstAddr netip.Addr + var newSrcAddr netip.Addr + var newDstAddr netip.Addr + + updateDstPort(header, version, func(port int) int { + newDstPort = entry.proxyPort + return newDstPort + }) + + if entry.srcAddr.IsValid() { + updateSrcAddr(header, version, func(addr netip.Addr) netip.Addr { + origSrcAddr = addr + newSrcAddr = entry.srcAddr + return newSrcAddr + }) + } else { origSrcAddr = getSrcAddr(header, version) newSrcAddr = origSrcAddr - } + } + + updateDstAddr(header, version, func(addr netip.Addr) netip.Addr { + origDstAddr = addr + newDstAddr = entry.proxyAddr + return newDstAddr + }) + orig := Connection(netip.AddrPortFrom(origSrcAddr, uint16(srcPort)), netip.AddrPortFrom(origDstAddr, uint16(origDstPort))) new := Connection(netip.AddrPortFrom(newSrcAddr, uint16(srcPort)), netip.AddrPortFrom(newDstAddr, uint16(newDstPort))) - tun.addConnection(new, orig) - } + tun.conntrack.addConnection(new, orig) + + if !entry.loop { + return len, nil + } + + // Write back to tunnel + writeBuf := buf[:offset+len] + written, err := tun.Write(writeBuf, offset) + if err != nil { + return 0, err + } else if written != len { + return 0, fmt.Errorf("NAT buffer partly written %v != %v", written, len) + } else { + return 0, nil + } + } + + srcAddr := getSrcAddr(header, version) + srcPort := getSrcPort(header, version) + dstAddr := getDstAddr(header, version) + dstPort := getDstPort(header, version) + + src := netip.AddrPortFrom(srcAddr, uint16(srcPort)) + dst := netip.AddrPortFrom(dstAddr, uint16(dstPort)) + new := Connection(dst, src) + + orig, ok := tun.conntrack.lookupConnection(new) + if ok { + updateSrcAddr(header, version, func(netip.Addr) netip.Addr { return orig.dst.Addr() }) + updateSrcPort(header, version, func(int) int { return int(orig.dst.Port()) }) + updateDstAddr(header, version, func(netip.Addr) netip.Addr { return orig.src.Addr() }) + updateDstPort(header, version, func(int) int { return int(orig.src.Port()) }) + // Write back to tunnel + writeBuf := buf[:offset+len] + written, err := tun.Write(writeBuf, offset) + if err != nil { + return 0, err + } else if written != len { + return 0, fmt.Errorf("NAT rev buffer partly written %v != %v", written, len) + } else { + return 0, nil + } + } // protocol := int(header[9]) // if protocol == PROTO_TCP && len >= (IPV4_HEADER_LEN + TCP_HEADER_LEN) { @@ -491,7 +562,7 @@ func (tun *natTun) Write(buf []byte, offset int) (int, error) { dst := netip.AddrPortFrom(dstAddr, uint16(dstPort)) new := Connection(dst, src) - orig, ok := tun.lookupConnection(new) + orig, ok := tun.conntrack.lookupConnection(new) if ok { updateSrcAddr(header, version, func(netip.Addr) netip.Addr { return orig.dst.Addr() }) diff --git a/tunnel/tools/libwg-go/service.go b/tunnel/tools/libwg-go/service.go index c5303876..57c128de 100644 --- a/tunnel/tools/libwg-go/service.go +++ b/tunnel/tools/libwg-go/service.go @@ -13,7 +13,6 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" - "golang.zx2c4.com/go118/netip" gen "golang.zx2c4.com/wireguard/android/gen" "golang.zx2c4.com/wireguard/device" ) @@ -26,7 +25,7 @@ type LibwgServiceImpl struct { gen.UnimplementedLibwgServer logger *device.Logger http_proxy *HttpProxy - uidRequest chan netip.AddrPort + uidRequest chan AddrPortPair uidResponse chan string } @@ -36,7 +35,7 @@ var server *grpc.Server func NewLibwgService(logger *device.Logger) gen.LibwgServer { return &LibwgServiceImpl{ logger: logger, - uidRequest: make(chan netip.AddrPort), + uidRequest: make(chan AddrPortPair), uidResponse: make(chan string), } } @@ -167,22 +166,24 @@ func (e *LibwgServiceImpl) Reverse(stream gen.Libwg_ReverseServer) error { } select { - case addr_port := <-e.uidRequest: + case addrPortPair := <-e.uidRequest: + local := addrPortPair.local + remote := addrPortPair.remote r := &gen.ReverseResponse{ Request: &gen.ReverseResponse_Uid{ Uid: &gen.GetConnectionOwnerUidRequest{ Protocol: IPPROTO_TCP, Local: &gen.InetSocketAddress{ Address: &gen.InetAddress{ - Address: addr_port.Addr().AsSlice(), + Address: local.Addr().AsSlice(), }, - Port: uint32(addr_port.Port()), + Port: uint32(local.Port()), }, Remote: &gen.InetSocketAddress{ Address: &gen.InetAddress{ - Address: e.http_proxy.GetAddrPort().Addr().AsSlice(), + Address: remote.Addr().AsSlice(), }, - Port: uint32(e.http_proxy.GetAddrPort().Port()), + Port: uint32(remote.Port()), }, }, }, diff --git a/tunnel/tools/libwg-go/transparent.go b/tunnel/tools/libwg-go/transparent.go new file mode 100644 index 00000000..6dbe482c --- /dev/null +++ b/tunnel/tools/libwg-go/transparent.go @@ -0,0 +1,31 @@ +package main + +import ( + "bufio" + "bytes" + "net" + "net/http" +) + +type dumbResponseWriter struct { + net.Conn +} + +func (dumb dumbResponseWriter) Header() http.Header { + panic("Header() should not be called on this ResponseWriter") +} + +func (dumb dumbResponseWriter) Write(buf []byte) (int, error) { + if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) { + return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request + } + return dumb.Conn.Write(buf) +} + +func (dumb dumbResponseWriter) WriteHeader(code int) { + panic("WriteHeader() should not be called on this ResponseWriter") +} + +func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil +} |