Skip to content

Commit

Permalink
ipn/socks5: resolve endpoint w namespace-aware dns
Browse files Browse the repository at this point in the history
  • Loading branch information
ignoramous committed Oct 28, 2023
1 parent 2a9c6f2 commit 2ea4501
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 29 deletions.
20 changes: 10 additions & 10 deletions intra/ipn/wg/multihost.go → intra/ipn/multihost/multihost.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package wg
package multihost

import (
"net/netip"
Expand All @@ -14,35 +14,35 @@ import (
"github.com/celzero/firestack/intra/log"
)

// Multihost is a list of hostnames and/or ip addresses for one endpoint.
type Multihost struct {
// MH is a list of hostnames and/or ip addresses for one endpoint.
type MH struct {
names []string
addrs []netip.Addr
}

func (h *Multihost) Names() []string {
func (h *MH) Names() []string {
return h.names
}

func (h *Multihost) Addrs() []netip.Addr {
func (h *MH) Addrs() []netip.Addr {
return h.addrs
}

func (h *Multihost) Len() int {
func (h *MH) Len() int {
// names may exist without addrs and vice versa
return max(len(h.addrs), len(h.names))
}

func (h *Multihost) addrlen() int {
func (h *MH) addrlen() int {
return len(h.addrs)
}

func (h *Multihost) Refresh() int {
func (h *MH) Refresh() int {
h.With(h.names)
return h.Len()
}

func (h *Multihost) With(domainsOrIps []string) {
func (h *MH) With(domainsOrIps []string) {
h.names = make([]string, 0)
h.addrs = make([]netip.Addr, 0)
for _, dip := range domainsOrIps {
Expand All @@ -60,7 +60,7 @@ func (h *Multihost) With(domainsOrIps []string) {
}
}

func (h *Multihost) EqualAddrs(other *Multihost) bool {
func (h *MH) EqualAddrs(other *MH) bool {
if (other == nil) || (h.addrlen() != other.addrlen()) {
return false
}
Expand Down
47 changes: 33 additions & 14 deletions intra/ipn/socks5.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@
package ipn

import (
"errors"
"net"
"net/http"
"net/netip"
"strconv"

"github.com/celzero/firestack/intra/core"
"github.com/celzero/firestack/intra/dialers"
"github.com/celzero/firestack/intra/ipn/multihost"
"github.com/celzero/firestack/intra/log"
"github.com/celzero/firestack/intra/protect"
"github.com/celzero/firestack/intra/settings"
tx "github.com/txthinking/socks5"
"golang.org/x/net/proxy"
)

type socks5 struct {
proxydialer *tx.Client
id string
opts *settings.ProxyOptions
rd *protect.RDial
hc *http.Client
status int
dialers []proxy.Dialer // outbound dialers connecting unto upstream proxy
id string // unique identifier
opts *settings.ProxyOptions // connect options
rd *protect.RDial // this transport as a dialer
hc *http.Client // this transport as a http client
status int // status of this transport
}

type socks5tcpconn struct {
Expand Down Expand Up @@ -80,28 +85,42 @@ func NewSocks5Proxy(id string, ctl protect.Controller, po *settings.ProxyOptions

// replace with a network namespace aware dialer
tx.Dial = protect.MakeNsRDial(id, ctl)

portnumber, _ := strconv.Atoi(po.Port)
mh := new(multihost.MH)
mh.With([]string{po.Host}) // resolves if ip is name

var clients []proxy.Dialer
// x.net.proxy doesn't yet support udp
// github.com/golang/net/blob/62affa334/internal/socks/socks.go#L233
// if po.Auth.User and po.Auth.Password are empty strings, the upstream
// socks5 server may throw err when dialing with golang/net/x/proxy;
// although, txthinking/socks5 deals gracefully with empty auth strings
// fproxy, err = proxy.SOCKS5("udp", po.IPPort, po.Auth, proxy.Direct)
fproxy, err := tx.NewClient(po.IPPort, po.Auth.User, po.Auth.Password, tcptimeoutsec, udptimeoutsec)
for _, ip := range mh.Addrs() {
ipport := netip.AddrPortFrom(ip, uint16(portnumber))
c, cerr := tx.NewClient(ipport.String(), po.Auth.User, po.Auth.Password, tcptimeoutsec, udptimeoutsec)
if cerr != nil {
err = errors.Join(err, cerr)
} else {
clients = append(clients, c)
}
}

if err != nil {
log.W("proxy: err creating socks5(%v): %v", po, err)
if len(clients) == 0 && err != nil {
log.W("proxy: err creating socks5 for %v (opts: %v): %v", mh, po, err)
return nil, err
}

h := &socks5{
proxydialer: fproxy,
id: id,
opts: po,
dialers: clients,
id: id,
opts: po,
}
h.rd = newRDial(h)
h.hc = newHTTP1Client(h.rd)

log.D("proxy: socks5: created %s with opts(%s)", h.ID(), po)
log.D("proxy: socks5: created %s with clients(%d), opts(%s)", h.ID(), len(clients), po)

return h, nil
}
Expand All @@ -113,7 +132,7 @@ func (h *socks5) Dial(network, addr string) (c protect.Conn, err error) {

// todo: tx.Client can only dial in to ip:port and not host:port even for server addr
// tx.Client.Dial does not support dialing into client addr as hostnames
if c, err = dialers.ProxyDial(h.proxydialer, network, addr); err == nil {
if c, err = dialers.ProxyDials(h.dialers, network, addr); err == nil {
// github.com/txthinking/socks5/blob/39268fae/client.go#L15
if uc, ok := c.(*tx.Client); ok {
if uc.UDPConn != nil { // a udp conn will always have an embedded tcp conn
Expand Down
4 changes: 3 additions & 1 deletion intra/ipn/wg/wgconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"syscall"
"time"

"github.com/celzero/firestack/intra/ipn/multihost"

"github.com/celzero/firestack/intra/log"
"github.com/celzero/firestack/intra/protect"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -58,7 +60,7 @@ var (
)

func (*StdNetBind) ParseEndpoint(s string) (conn.Endpoint, error) {
d := new(Multihost)
d := new(multihost.MH)
host, portstr, err := net.SplitHostPort(s)
if err != nil {
log.E("wg: bind: not a valid endpoint in(%s); err: %v", s, err)
Expand Down
9 changes: 5 additions & 4 deletions intra/ipn/wgproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"syscall"

"github.com/celzero/firestack/intra/core"
"github.com/celzero/firestack/intra/ipn/multihost"
"github.com/celzero/firestack/intra/ipn/wg"
"github.com/celzero/firestack/intra/log"
"github.com/celzero/firestack/intra/protect"
Expand Down Expand Up @@ -69,7 +70,7 @@ type wgtun struct {
incomingPacket chan *buffer.View // pipes ep writes to wg
events chan tun.Event // wg specific tun (interface) events
mtu int // mtu of this interface
dns *wg.Multihost // dns resolver for this interface
dns *multihost.MH // dns resolver for this interface
reqbarrier *core.Barrier // request barrier for dns lookups
hasV4, hasV6 bool // interface has ipv4/ipv6 routes?
}
Expand Down Expand Up @@ -194,11 +195,11 @@ func wglogger(id string) *device.Logger {
return logger
}

func wgIfConfigOf(txtptr *string) (ifaddrs []netip.Prefix, dnsh *wg.Multihost, mtu int, err error) {
func wgIfConfigOf(txtptr *string) (ifaddrs []netip.Prefix, dnsh *multihost.MH, mtu int, err error) {
txt := *txtptr
pcfg := strings.Builder{}
r := bufio.NewScanner(strings.NewReader(txt))
dnsh = new(wg.Multihost)
dnsh = new(multihost.MH)
for r.Scan() {
line := r.Text()
if len(line) <= 0 {
Expand Down Expand Up @@ -336,7 +337,7 @@ func NewWgProxy(id string, ctl protect.Controller, cfg string) (WgProxy, error)
}

// ref: github.com/WireGuard/wireguard-go/blob/469159ecf7/tun/netstack/tun.go#L54
func makeWgTun(id string, ifaddrs []netip.Prefix, dnsm *wg.Multihost, mtu int) (*wgtun, error) {
func makeWgTun(id string, ifaddrs []netip.Prefix, dnsm *multihost.MH, mtu int) (*wgtun, error) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
Expand Down

0 comments on commit 2ea4501

Please sign in to comment.