Skip to content

Commit

Permalink
Reusable peer lookup/dial logic
Browse files Browse the repository at this point in the history
  • Loading branch information
neilalexander committed Nov 17, 2024
1 parent 75d2080 commit 42873be
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 124 deletions.
7 changes: 7 additions & 0 deletions cmd/yggdrasil/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,16 @@ func main() {

// Set up the Yggdrasil node itself.
{
iprange := net.IPNet{
IP: net.ParseIP("200::"),
Mask: net.CIDRMask(7, 128),
}
options := []core.SetupOption{
core.NodeInfo(cfg.NodeInfo),
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
core.PeerFilter(func(ip net.IP) bool {
return !iprange.Contains(ip)
}),
}
for _, addr := range cfg.Listen {
options = append(options, core.ListenAddress(addr))
Expand Down
10 changes: 9 additions & 1 deletion contrib/mobile/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,15 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
}
// Set up the Yggdrasil node itself.
{
options := []core.SetupOption{}
iprange := net.IPNet{
IP: net.ParseIP("200::"),
Mask: net.CIDRMask(7, 128),
}
options := []core.SetupOption{
core.PeerFilter(func(ip net.IP) bool {
return !iprange.Contains(ip)
}),
}
for _, peer := range m.config.Peers {
options = append(options, core.Peer{URI: peer})
}
Expand Down
1 change: 1 addition & 0 deletions src/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Core struct {
tls *tls.Config // immutable after startup
//_peers map[Peer]*linkInfo // configurable after startup
_listeners map[ListenAddress]struct{} // configurable after startup
peerFilter func(ip net.IP) bool // immutable after startup
nodeinfo NodeInfo // immutable after startup
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
Expand Down
38 changes: 38 additions & 0 deletions src/core/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ const ErrLinkPasswordInvalid = linkError("invalid password supplied")
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid")
const ErrLinkSNINotSupported = linkError("SNI not supported on this link type")
const ErrLinkNoSuitableIPs = linkError("no suitable remote IPs")

func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
var retErr error
Expand Down Expand Up @@ -653,6 +654,43 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s
return err
}

func (l *links) findSuitableIP(url *url.URL, fn func(hostname string, ip net.IP, port int) (net.Conn, error)) (net.Conn, error) {
host, p, err := net.SplitHostPort(url.Host)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(p)
if err != nil {
return nil, err
}
resp, err := net.LookupIP(host)
if err != nil {
return nil, err
}
var _ips [64]net.IP
ips := _ips[:0]
for _, ip := range resp {
if l.core.config.peerFilter != nil && !l.core.config.peerFilter(ip) {
continue
}
ips = append(ips, ip)
}
if len(ips) == 0 {
return nil, ErrLinkNoSuitableIPs
}
for _, ip := range ips {
var conn net.Conn
if conn, err = fn(host, ip, port); err != nil {
url := *url
url.RawQuery = ""
l.core.log.Debugln("Dialling", url.Redacted(), "reported error:", err)
continue
}
return conn, nil
}
return nil, err
}

func urlForLinkInfo(u url.URL) url.URL {
u.RawQuery = ""
return u
Expand Down
29 changes: 17 additions & 12 deletions src/core/link_quic.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,23 @@ func (l *links) newLinkQUIC() *linkQUIC {
}

func (l *linkQUIC) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
qc, err := quic.DialAddr(ctx, url.Host, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
qs, err := qc.OpenStreamSync(ctx)
if err != nil {
return nil, err
}
return &linkQUICStream{
Connection: qc,
Stream: qs,
}, nil
tlsconfig := l.tlsconfig.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsconfig.ServerName = hostname
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
qc, err := quic.DialAddr(ctx, hostport, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
qs, err := qc.OpenStreamSync(ctx)
if err != nil {
return nil, err
}
return &linkQUICStream{
Connection: qc,
Stream: qs,
}, nil
})
}

func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
Expand Down
46 changes: 28 additions & 18 deletions src/core/link_socks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,41 @@ func (l *links) newLinkSOCKS() *linkSOCKS {
}

func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
if url.Scheme != "sockstls" && options.tlsSNI != "" {
return nil, ErrLinkSNINotSupported
}
var proxyAuth *proxy.Auth
if url.User != nil && url.User.Username() != "" {
proxyAuth = &proxy.Auth{
User: url.User.Username(),
}
proxyAuth.Password, _ = url.User.Password()
}
dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("failed to configure proxy")
}
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
conn, err := dialer.Dial("tcp", pathtokens[0])
if err != nil {
return nil, fmt.Errorf("failed to dial: %w", err)
}
if url.Scheme == "sockstls" {
tlsconfig := l.tls.config.Clone()
tlsconfig.ServerName = options.tlsSNI
conn = tls.Client(conn, tlsconfig)
}
return conn, nil
tlsconfig := l.tls.config.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
dialer, err := l.tcp.dialerFor(&net.TCPAddr{
IP: ip,
Port: port,
}, info.sintf)
if err != nil {
return nil, err
}
proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer)
if err != nil {
return nil, err
}
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
conn, err := proxy.Dial("tcp", pathtokens[0])
if err != nil {
return nil, err
}
if url.Scheme == "sockstls" {
tlsconfig.ServerName = hostname
if sni := options.tlsSNI; sni != "" {
tlsconfig.ServerName = sni
}
conn = tls.Client(conn, tlsconfig)
}
return conn, nil
})
}

func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
Expand Down
54 changes: 6 additions & 48 deletions src/core/link_tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"net"
"net/url"
"strconv"
"time"

"github.com/Arceliar/phony"
Expand Down Expand Up @@ -34,59 +33,18 @@ type tcpDialer struct {
addr *net.TCPAddr
}

func (l *linkTCP) dialersFor(url *url.URL, info linkInfo) ([]*tcpDialer, error) {
host, p, err := net.SplitHostPort(url.Host)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(p)
if err != nil {
return nil, err
}
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
dialers := make([]*tcpDialer, 0, len(ips))
for _, ip := range ips {
func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.dialerFor(addr, info.sintf)
if err != nil {
continue
}
dialers = append(dialers, &tcpDialer{
info: info,
dialer: dialer,
addr: addr,
})
}
return dialers, nil
}

func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
if options.tlsSNI != "" {
return nil, ErrLinkSNINotSupported
}
dialers, err := l.dialersFor(url, info)
if err != nil {
return nil, err
}
if len(dialers) == 0 {
return nil, nil
}
for _, d := range dialers {
var conn net.Conn
conn, err = d.dialer.DialContext(ctx, "tcp", d.addr.String())
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
l.core.log.Warnf("Failed to connect to %s: %s", d.addr, err)
continue
return nil, err
}
return conn, nil
}
return nil, err
return dialer.DialContext(ctx, "tcp", addr.String())
})
}

func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
Expand Down
36 changes: 17 additions & 19 deletions src/core/link_tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,26 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
}

func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
dialers, err := l.tcp.dialersFor(url, info)
if err != nil {
return nil, err
}
if len(dialers) == 0 {
return nil, nil
}
for _, d := range dialers {
tlsconfig := l.config.Clone()
tlsconfig.ServerName = options.tlsSNI
tlsdialer := &tls.Dialer{
NetDialer: d.dialer,
Config: tlsconfig,
tlsconfig := l.config.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsconfig.ServerName = hostname
if sni := options.tlsSNI; sni != "" {
tlsconfig.ServerName = sni
}
var conn net.Conn
conn, err = tlsdialer.DialContext(ctx, "tcp", d.addr.String())
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
continue
return nil, err
}
return conn, nil
}
return nil, err
tlsdialer := &tls.Dialer{
NetDialer: dialer,
Config: tlsconfig,
}
return tlsdialer.DialContext(ctx, "tcp", addr.String())
})
}

func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
Expand Down
3 changes: 0 additions & 3 deletions src/core/link_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ func (l *links) newLinkUNIX() *linkUNIX {
}

func (l *linkUNIX) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
if options.tlsSNI != "" {
return nil, ErrLinkSNINotSupported
}
addr, err := net.ResolveUnixAddr("unix", url.Path)
if err != nil {
return nil, err
Expand Down
40 changes: 29 additions & 11 deletions src/core/link_ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package core

import (
"context"
"fmt"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -87,18 +88,35 @@ func (l *links) newLinkWS() *linkWS {
}

func (l *linkWS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
if options.tlsSNI != "" {
return nil, ErrLinkSNINotSupported
}
wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{
Subprotocols: []string{"ygg-ws"},
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
u := *url
u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
return nil, err
}
wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{
HTTPClient: &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: dialer.Dial,
DialContext: dialer.DialContext,
},
},
Subprotocols: []string{"ygg-ws"},
Host: hostname,
})
if err != nil {
return nil, err
}
return &linkWSConn{
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
}, nil
})
if err != nil {
return nil, err
}
return &linkWSConn{
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
}, nil
}

func (l *linkWS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
Expand Down
Loading

0 comments on commit 42873be

Please sign in to comment.