Skip to content

Commit

Permalink
mvp (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
mosajjal authored Feb 6, 2024
1 parent f5d54b9 commit 232b23d
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 95 deletions.
2 changes: 2 additions & 0 deletions cmd/sniproxy/config.defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ general:
bind_prometheus:
# Interface used for outbound TLS connections. uses OS prefered one if empty
interface:
# Preferred ip version for outgoing connections. default is 0 (random choice)
prefered_version:
# Public IPv4 of the server, reply address of DNS A queries
public_ipv4:
# Public IPv6 of the server, reply address of DNS AAAA queries
Expand Down
9 changes: 7 additions & 2 deletions cmd/sniproxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"net"
"net/http"
"net/netip"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -221,6 +222,8 @@ func main() {
c.BindPrometheus = generalConfig.String("prometheus")
c.AllowConnToLocal = generalConfig.Bool("allow_conn_to_local")

c.PreferredVersion = uint(generalConfig.Int("preferred_version"))

var err error
c.Acl, err = acl.StartACLs(&logger, k)
if err != nil {
Expand Down Expand Up @@ -284,8 +287,10 @@ func main() {
if err != nil {
logger.Error().Msg(err.Error())
}
c.SourceAddr = net.ParseIP(addrs[0].String())

// TODO: split ipv4 and ipv6 to different lists
for _, addr := range addrs {
c.SourceAddr = append(c.SourceAddr, netip.MustParseAddr(addr.String()))
}
}

if c.UpstreamSOCKS5 != "" {
Expand Down
29 changes: 14 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,50 @@ go 1.21

require (
github.com/deathowl/go-metrics-prometheus v0.0.0-20221009205350-f2a1482ba35b
github.com/folbricht/routedns v0.1.21-0.20231021081103-822f708e128c
github.com/folbricht/routedns v0.1.51
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/gorilla/handlers v1.5.1
github.com/gorilla/handlers v1.5.2
github.com/knadh/koanf v1.5.0
github.com/m13253/dns-over-https/v2 v2.3.4
github.com/miekg/dns v1.1.56
github.com/miekg/dns v1.1.58
github.com/mosajjal/doqd v0.0.0-20230911082614-66fb2db2687f
github.com/oschwald/maxminddb-golang v1.12.0
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_golang v1.18.0
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/rs/zerolog v1.31.0
github.com/spf13/cobra v1.7.0
github.com/spf13/cobra v1.8.0
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
github.com/yl2chen/cidranger v1.0.2
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.17.0
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/net v0.20.0
inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9
)

require (
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
github.com/google/pprof v0.0.0-20240125082051-42cd04596328 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc // indirect
github.com/jtacoma/uritemplates v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/dtls/v2 v2.2.9 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/common v0.46.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
Expand Down
106 changes: 51 additions & 55 deletions go.sum

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions pkg/conf.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package sniproxy

import (
"net"
"net/netip"

"github.com/mosajjal/sniproxy/v2/pkg/acl"
"github.com/rcrowley/go-metrics"
Expand All @@ -28,9 +28,11 @@ type Config struct {

Acl []acl.ACL `yaml:"-"`

DnsClient DNSClient `yaml:"-"`
Dialer proxy.Dialer `yaml:"-"`
SourceAddr net.IP `yaml:"-"`
DnsClient DNSClient `yaml:"-"`
Dialer proxy.Dialer `yaml:"-"`
// list of interface source IPs; used to rotate source IPs when initializing connections
SourceAddr []netip.Addr `yaml:"-"`
PreferredVersion uint `yaml:"preferred_version"` // "4" or "6" for outbound connections

// metrics
RecievedHTTP metrics.Counter `yaml:"-"`
Expand Down
112 changes: 100 additions & 12 deletions pkg/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package sniproxy
import (
"crypto/tls"
"fmt"
"math/rand"
"net"
"net/netip"
"net/url"
"strings"
"sync"
Expand All @@ -27,6 +29,33 @@ type DNSClient struct {

var dnsLock sync.RWMutex

// pickSrcAddr picks a random source address from the list of configured source addresses.
// version specifies the IP version to pick, 4 or 6. If 0, any version is picked.
func (c *Config) pickSrcAddr(version uint) net.IP {
if len(c.SourceAddr) == 0 {
return nil
}
if version == 0 {
version = uint(rand.Intn(2) + 4)
}

// shuffle the list of source addresses. TODO: potentially a better way to do this
for i := range c.SourceAddr {
j := rand.Intn(i + 1)
c.SourceAddr[i], c.SourceAddr[j] = c.SourceAddr[j], c.SourceAddr[i]
}

for _, ip := range c.SourceAddr {
if ip.Is4() && version == 4 {
return ip.AsSlice()
}
if ip.Is6() && version == 6 {
return ip.AsSlice()
}
}
return nil
}

func (dnsc *DNSClient) PerformExternalAQuery(fqdn string, QType uint16) ([]dns.RR, error) {
if !strings.HasSuffix(fqdn, ".") {
fqdn = fqdn + "."
Expand Down Expand Up @@ -87,26 +116,62 @@ func processQuestion(c *Config, l zerolog.Logger, q dns.Question, decision acl.D
return []dns.RR{}, nil
}

func (dnsc DNSClient) lookupDomain4(domain string) (net.IP, error) {
// lookupDomain looks up a domain name and returns the IP address.
// version specifies the IP version to lookup, 4 or 6. If 0, any version is picked.
func (dnsc DNSClient) lookupDomain(domain string, version uint) (netip.Addr, error) {
if version == 0 {
version = uint(rand.Intn(2) + 4)
}
if version == 4 {
return dnsc.lookupDomain4(domain)
}
if version == 6 {
return dnsc.lookupDomain6(domain)
}
return netip.IPv4Unspecified(), fmt.Errorf("invalid version")
}

func (dnsc DNSClient) lookupDomain4(domain string) (netip.Addr, error) {
if !strings.HasSuffix(domain, ".") {
domain = domain + "."
}
rAddrDNS, err := dnsc.PerformExternalAQuery(domain, dns.TypeA)
if err != nil {
return nil, err
return netip.IPv4Unspecified(), err
}
if len(rAddrDNS) > 0 {
if rAddrDNS[0].Header().Rrtype == dns.TypeCNAME {
return dnsc.lookupDomain4(rAddrDNS[0].(*dns.CNAME).Target)
}
if rAddrDNS[0].Header().Rrtype == dns.TypeA {
return rAddrDNS[0].(*dns.A).A, nil
return netip.AddrFrom4([4]byte(rAddrDNS[0].(*dns.A).A.To4())), nil
}
} else {
return nil, fmt.Errorf("[DNS] Empty DNS response for %s", domain)
return netip.IPv4Unspecified(), fmt.Errorf("[DNS] Empty DNS response for %s", domain)
}
return nil, fmt.Errorf("[DNS] Unknown type %s", dns.TypeToString[rAddrDNS[0].Header().Rrtype])
return netip.IPv4Unspecified(), fmt.Errorf("[DNS] Unknown type %s", dns.TypeToString[rAddrDNS[0].Header().Rrtype])
}
func (dnsc DNSClient) lookupDomain6(domain string) (netip.Addr, error) {
if !strings.HasSuffix(domain, ".") {
domain = domain + "."
}
rAddrDNS, err := dnsc.PerformExternalAQuery(domain, dns.TypeAAAA)
if err != nil {
return netip.IPv6Unspecified(), err
}
if len(rAddrDNS) > 0 {
if rAddrDNS[0].Header().Header().Rrtype == dns.TypeCNAME {
return dnsc.lookupDomain6(rAddrDNS[0].(*dns.CNAME).Target)
}
if rAddrDNS[0].Header().Rrtype == dns.TypeAAAA {
return netip.AddrFrom16([16]byte(rAddrDNS[0].(*dns.AAAA).AAAA.To16())), nil
}
} else {
return netip.IPv6Unspecified(), fmt.Errorf("[DNS] Empty DNS response for %s", domain)
}
return netip.IPv6Unspecified(), fmt.Errorf("[DNS] Unknown type %s", dns.TypeToString[rAddrDNS[0].Header().Rrtype])
}

func handleDNS(c *Config, l zerolog.Logger) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
Expand Down Expand Up @@ -276,8 +341,15 @@ func NewDNSClient(C *Config, uri string, skipVerify bool, proxy string) (*DNSCli
}
Address := rdns.AddressWithDefault(host, port)

var ldarr net.IP
if parsedURL.Scheme == "udp6" {
ldarr = C.pickSrcAddr(6)
} else {
ldarr = C.pickSrcAddr(4)
}

opt := rdns.DNSClientOptions{
LocalAddr: C.SourceAddr,
LocalAddr: ldarr,
UDPSize: 1300,
Dialer: *dialer,
}
Expand All @@ -293,9 +365,17 @@ func NewDNSClient(C *Config, uri string, skipVerify bool, proxy string) (*DNSCli
host = parsedURL.Host
port = "53"
}

var ldarr net.IP
if parsedURL.Scheme == "tcp6" {
ldarr = C.pickSrcAddr(6)
} else {
ldarr = C.pickSrcAddr(4)
}

Address := rdns.AddressWithDefault(host, port)
opt := rdns.DNSClientOptions{
LocalAddr: C.SourceAddr,
LocalAddr: ldarr,
UDPSize: 1300,
Dialer: *dialer,
}
Expand All @@ -309,11 +389,19 @@ func NewDNSClient(C *Config, uri string, skipVerify bool, proxy string) (*DNSCli
if err != nil {
return nil, err
}
var ldarr net.IP
bootstrapAddr := "1.1.1.1"
if parsedURL.Scheme == "tls6" || parsedURL.Scheme == "tcp-tls6" {
ldarr = C.pickSrcAddr(6)
bootstrapAddr = "2606:4700:4700::1111"
} else {
ldarr = C.pickSrcAddr(4)
}

opt := rdns.DoTClientOptions{
TLSConfig: tlsConfig,
BootstrapAddr: "1.1.1.1", //TODO: make this configurable
LocalAddr: C.SourceAddr,
BootstrapAddr: bootstrapAddr, //TODO: make this configurable
LocalAddr: ldarr,
Dialer: *dialer,
}
id, err := rdns.NewDoTClient("id", parsedURL.Host, opt)
Expand All @@ -329,11 +417,11 @@ func NewDNSClient(C *Config, uri string, skipVerify bool, proxy string) (*DNSCli

transport := "tcp"
opt := rdns.DoHClientOptions{
Method: "POST", // TODO: support POST
Method: "POST", // TODO: support anything other than POST
TLSConfig: tlsConfig,
BootstrapAddr: "1.1.1.1", //TODO: make this configurable
Transport: transport,
LocalAddr: C.SourceAddr,
LocalAddr: C.pickSrcAddr(4), //TODO:support IPv6
Dialer: *dialer,
}
id, err := rdns.NewDoHClient("id", parsedURL.String(), opt)
Expand All @@ -350,7 +438,7 @@ func NewDNSClient(C *Config, uri string, skipVerify bool, proxy string) (*DNSCli

opt := rdns.DoQClientOptions{
TLSConfig: tlsConfig,
LocalAddr: C.SourceAddr,
LocalAddr: C.pickSrcAddr(4), //TODO:support IPv6
// Dialer: *dialer, // BUG: not yet supported
}
id, err := rdns.NewDoQClient("id", parsedURL.Host, opt)
Expand Down
5 changes: 3 additions & 2 deletions pkg/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ func TestDNSClient_lookupDomain4(t *testing.T) {
wantErr bool
}{
{client: dnsc, name: "test1", domain: "ident.me", want: net.IPv4(49, 12, 234, 183), wantErr: false},
{client: dnsc, name: "test1", domain: "ifconfig.me", want: net.IPv4(34, 160, 111, 145), wantErr: false},
{client: dnsc, name: "test1", domain: "ifconfig.me", want: net.IPv4(34, 117, 118, 44), wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.client.lookupDomain4(tt.domain)
gotTmp, err := tt.client.lookupDomain4(tt.domain)
got := net.IP(gotTmp.AsSlice())
if (err != nil) != tt.wantErr {
t.Errorf("DNSClient.lookupDomain4() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
Loading

0 comments on commit 232b23d

Please sign in to comment.