Files
regolith/internal/out/out.go

144 lines
3.0 KiB
Go

package out
import (
"context"
"log"
"net"
"net/url"
"sync"
"golang.org/x/net/proxy"
"edgaru089.ink/go/regolith/internal/util"
)
// Configures upstream TCP connections.
//
// Does not support HTTP proxies as upstream. They're
// just a pain to use besides CONNECT commands.
//
// Upstreams are specified as 'direct' for direct dialing
// by the Go net package, or 'socks5://user:pass@host:port'
// for SOCKS5 upstreams.
//
// Numeric addresses are always dialed directly.
type Config struct {
Default string // Default URI
Proxied map[string][]string // Proxy URI to hostname array mapping
}
// Dialer implements x/net/proxy.Dialer from the Config given to it.
type Dialer struct {
lock sync.RWMutex
hostnames map[string]string
uris map[string]interface {
proxy.Dialer
proxy.ContextDialer
}
def interface {
proxy.Dialer
proxy.ContextDialer
}
}
var _ proxy.ContextDialer = &Dialer{} // *Dialer implements x/net/proxy.ContextDialer
var _ proxy.Dialer = &Dialer{} // *Dialer implements x/net/proxy.Dialer
// New creates a new Dialer from a Config.
//
// You can also &out.Dialer{} and then call Load on it.
func New(c Config) (d *Dialer) {
d = &Dialer{}
d.Load(c)
return
}
// Load loads/reloads the Dialer struct.
func (d *Dialer) Load(c Config) {
d.lock.Lock()
defer d.lock.Unlock()
if len(c.Default) == 0 {
c.Default = "direct"
}
log.Print("default dialer ", c.Default)
if d.hostnames == nil {
d.hostnames = make(map[string]string)
d.uris = make(map[string]interface {
proxy.Dialer
proxy.ContextDialer
})
} else {
clear(d.hostnames)
clear(d.uris)
}
for uri, hosts := range c.Proxied {
d.uris[uri] = nil
for _, host := range hosts {
d.hostnames[host] = uri
log.Printf("dialer( %s ) => %s", host, uri)
}
}
d.uris[c.Default] = nil
// construct the Dialers
for uri := range d.uris {
if uri == "direct" {
d.uris[uri] = proxy.Direct
} else {
url, err := url.Parse(uri)
if err != nil {
log.Print("URI parse error: ", err)
d.uris[uri] = proxy.Direct
continue
}
dx, err := proxy.FromURL(url, proxy.Direct)
if err != nil {
log.Print("Proxy.FromURL error: ", err)
d.uris[uri] = proxy.Direct
continue
}
var ok bool
if d.uris[uri], ok = dx.(interface {
proxy.ContextDialer
proxy.Dialer
}); !ok {
log.Print("Proxy.FromURL unable to cast to proxy.Dialer+ContextDialer")
d.uris[uri] = proxy.Direct
continue
}
}
}
d.def = d.uris[c.Default]
}
func (d *Dialer) findDialer(host string) interface {
proxy.Dialer
proxy.ContextDialer
} {
d.lock.RLock()
defer d.lock.RUnlock()
if dx, ok := d.uris[d.hostnames[host]]; ok {
return dx
} else {
return d.def
}
}
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
host, _ := util.SplitHostPort(address)
return d.findDialer(host).Dial(network, address)
}
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
host, _ := util.SplitHostPort(address)
return d.findDialer(host).DialContext(ctx, network, address)
}