diff options
author | atanarjuat <atanarjuat@example.com> | 2022-05-30 19:14:02 +0200 |
---|---|---|
committer | atanarjuat <atanarjuat@example.com> | 2022-05-30 19:14:02 +0200 |
commit | ae8664bec8a34bc758184e5c72141e26a1c960da (patch) | |
tree | d40154885facfbba68799f1e1846c2cfb380f3b6 | |
parent | 50c5fdc8a15f37d506292b02eef992e83752152b (diff) |
quic dialerfeat/quic
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | client/client.go | 16 | ||||
-rw-r--r-- | cmd/client/main.go | 9 | ||||
-rw-r--r-- | go.mod | 6 | ||||
-rw-r--r-- | go.sum | 20 | ||||
-rw-r--r-- | quicwrapper/dialer.go | 228 | ||||
-rw-r--r-- | server/Makefile | 3 |
7 files changed, 282 insertions, 4 deletions
@@ -15,6 +15,10 @@ run-client: run-client-kcp: KCP=1 ./obfsvpn-client -c ${OBFS4_CERT} +run-client-quic: + sudo sysctl -w net.core.rmem_max=2500000 + QUIC=1 ./obfsvpn-client -c ${OBFS4_CERT} + run-openvpn: ./scripts/run-openvpn-client.sh diff --git a/client/client.go b/client/client.go index c0a42e0..371cf88 100644 --- a/client/client.go +++ b/client/client.go @@ -1,25 +1,30 @@ package client import ( + "crypto/tls" "fmt" "log" "net" "0xacab.org/leap/obfsvpn" + "0xacab.org/leap/obfsvpn/quicwrapper" "github.com/kalikaneko/socks5" + "github.com/lucas-clemente/quic-go" "github.com/xtaci/kcp-go" ) type Client struct { kcp bool + quic bool socksAddr string obfs4Cert string } -func NewClient(kcp bool, socksAddr, obfs4Cert string) *Client { +func NewClient(kcp, quic bool, socksAddr, obfs4Cert string) *Client { return &Client{ kcp: kcp, + quic: quic, socksAddr: socksAddr, obfs4Cert: obfs4Cert, } @@ -42,6 +47,15 @@ func (c *Client) Start() bool { log.Printf("Dialing kcp://%s\n", address) return kcp.Dial(address) } + } else if c.quic { + dialer.DialFunc = func(network, address string) (net.Conn, error) { + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, // TODO proper pinning + NextProtos: []string{"quic-echo-example"}, // XXX what is this??? + } + c := quicwrapper.NewClient(address, tlsConfig, &quic.Config{}, nil) + return c.Dial() + } } server.Dial = dialer.Dial diff --git a/cmd/client/main.go b/cmd/client/main.go index 15f33d5..4125a12 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -42,14 +42,19 @@ func main() { debug.SetOutput(os.Stderr) } - kcpTransport := false + var ( + kcpTransport = false + quicTransport = false + ) // TODO make this configurable via a Config struct // TODO make sure we're disabling all the crypto options for KCP if os.Getenv("KCP") == "1" { kcpTransport = true + } else if os.Getenv("QUIC") == "1" { + quicTransport = true } socksAddr := net.JoinHostPort(socksHost, socksPort) - c := client.NewClient(kcpTransport, socksAddr, obfs4Cert) + c := client.NewClient(kcpTransport, quicTransport, socksAddr, obfs4Cert) c.Start() } @@ -14,13 +14,18 @@ require ( gitlab.com/yawning/obfs4.git v0.0.0-20210511220700-e330d1b7024b ) +require github.com/getlantern/netx v0.0.0-20211206143627-7ccfeb739cbd + require ( github.com/cheekybits/genny v1.0.0 // indirect github.com/dchest/siphash v1.2.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect github.com/getlantern/errors v1.0.1 // indirect + github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f // indirect + github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect + github.com/getlantern/iptool v0.0.0-20210721034953-519bf8ce0147 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -32,6 +37,7 @@ require ( github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.4 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect @@ -44,14 +44,31 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= github.com/getlantern/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw= github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= +github.com/getlantern/fdcount v0.0.0-20190912142506-f89afd7367c4 h1:JdD4XSaT6/j6InM7MT1E4WRvzR8gurxfq53A3ML3B/Q= +github.com/getlantern/fdcount v0.0.0-20190912142506-f89afd7367c4/go.mod h1:XZwE+iIlAgr64OFbXKFNCllBwV4wEipPx8Hlo2gZdbM= +github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f h1:wsVt3P/boVKkPFEZkWxgNgRq/+mD7sWHc17+Vw2PgH8= +github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA= +github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o= github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU= github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM= +github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA= github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2yaur9Qk3rHYD414j3Q1rl7+L0AylxrE= github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A= +github.com/getlantern/iptool v0.0.0-20210721034953-519bf8ce0147 h1:/4ibPEIbC7c786Ec5Z8QqTti8MAjjTp/LmfuF6frVDM= +github.com/getlantern/iptool v0.0.0-20210721034953-519bf8ce0147/go.mod h1:hfspzdRcvJ130tpTPL53/L92gG0pFtvQ6ln35ppwhHE= +github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848 h1:2MhMMVBTnaHrst6HyWFDhwQCaJ05PZuOv1bE2gN8WFY= +github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848/go.mod h1:+F5GJ7qGpQ03DBtcOEyQpM30ix4BLswdaojecFtsdy8= +github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7 h1:03J6Cb42EG06lHgpOFGm5BOax4qFqlSbSeKO2RGrj2g= +github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7/go.mod h1:GfzwugvtH7YcmNIrHHizeyImsgEdyL88YkdnK28B14c= +github.com/getlantern/netx v0.0.0-20211206143627-7ccfeb739cbd h1:z5IehLDMqMwJ0oeFIaMHhySRU8r1lRMh7WQ0Wn0LioA= +github.com/getlantern/netx v0.0.0-20211206143627-7ccfeb739cbd/go.mod h1:WEXF4pfIfnHBUAKwLa4DW7kcEINtG6wjUkbL2btwXZQ= +github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= +github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/getlantern/ops v0.0.0-20220418195917-45286e0140f6 h1:8DN68g9BZ8TS0TUQCvQB8R1lhAc60weDFPU++37RcvM= github.com/getlantern/ops v0.0.0-20220418195917-45286e0140f6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -63,6 +80,7 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= @@ -165,6 +183,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/quicwrapper/dialer.go b/quicwrapper/dialer.go new file mode 100644 index 0000000..554bb14 --- /dev/null +++ b/quicwrapper/dialer.go @@ -0,0 +1,228 @@ +package quicwrapper + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "net" + "sync" + + "github.com/apex/log" + "github.com/getlantern/netx" + quic "github.com/lucas-clemente/quic-go" +) + +// a QuicDialFn is a function that may be used to establish a new QUIC Session +type QuicDialFn func(ctx context.Context, addr string, tlsConf *tls.Config, config *quic.Config) (quic.Connection, error) +type UDPDialFn func(addr string) (net.PacketConn, *net.UDPAddr, error) + +var ( + DialWithNetx QuicDialFn = newDialerWithUDPDialer(DialUDPNetx) + DialWithoutNetx QuicDialFn = quic.DialAddrContext + defaultQuicDial QuicDialFn = DialWithNetx +) + +type wrappedSession struct { + quic.Connection + conn net.PacketConn +} + +func (w wrappedSession) CloseWithError(code quic.ApplicationErrorCode, mesg string) error { + err := w.Connection.CloseWithError(code, mesg) + err2 := w.conn.Close() + if err == nil { + err = err2 + } + return err +} + +// Creates a new QuicDialFn that uses the UDPDialFn given to +// create the underlying net.PacketConn +func newDialerWithUDPDialer(dial UDPDialFn) QuicDialFn { + return func(ctx context.Context, addr string, tlsConf *tls.Config, config *quic.Config) (quic.Connection, error) { + udpConn, udpAddr, err := dial(addr) + if err != nil { + return nil, err + } + ses, err := quic.DialContext(ctx, udpConn, udpAddr, addr, tlsConf, config) + if err != nil { + udpConn.Close() + return nil, err + } + return wrappedSession{ses, udpConn}, nil + } +} + +// DialUDPNetx is a UDPDialFn that resolves addresses and obtains +// the net.PacketConn using the netx package. +func DialUDPNetx(addr string) (net.PacketConn, *net.UDPAddr, error) { + udpAddr, err := netx.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, nil, err + } + udpConn, err := netx.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + if err != nil { + return nil, nil, err + } + return udpConn, udpAddr, nil +} + +// NewClient returns a client that creates multiplexed +// QUIC connections in a single Session with the given address using +// the provided configuration. +// +// The Session is created using the +// QuicDialFn given, but is not established until +// the first call to Dial(), DialContext() or Connect() +// +// if dial is nil, the default quic dialer is used +func NewClient(addr string, tlsConf *tls.Config, config *Config, dial QuicDialFn) *Client { + return NewClientWithPinnedCert(addr, tlsConf, config, dial, nil) +} + +// NewClientWithPinnedCert returns a new client configured +// as with NewClient, but accepting only a specific given +// certificate. If the certificate presented by the connected +// server does match the given certificate, the connection is +// rejected. This check is performed regardless of tls.Config +// settings (ie even if InsecureSkipVerify is true) +// +// If a nil certificate is given, the check is not performed and +// any valid certificate according the tls.Config given is accepted +// (equivalent to NewClient behavior) +func NewClientWithPinnedCert(addr string, tlsConf *tls.Config, config *Config, dial QuicDialFn, cert *x509.Certificate) *Client { + if dial == nil { + dial = defaultQuicDial + } + + tlsConf = defaultNextProtos(tlsConf, DefaultClientProtos) + + return &Client{ + session: nil, + address: addr, + tlsConf: tlsConf, + config: config, + dial: dial, + pinnedCert: cert, + } + +} + +type Client struct { + session quic.Connection + muSession sync.Mutex + address string + tlsConf *tls.Config + pinnedCert *x509.Certificate + config *Config + dial QuicDialFn +} + +// DialContext creates a new multiplexed QUIC connection to the +// server configured in the client. The given Context governs +// cancellation / timeout. If initial handshaking is performed, +// the operation is additionally governed by HandshakeTimeout +// value given in the client Config. +func (c *Client) DialContext(ctx context.Context) (*Conn, error) { + session, err := c.getOrCreateSession(ctx) + if err != nil { + return nil, fmt.Errorf("connecting session: %w", err) + } + stream, err := session.OpenStreamSync(ctx) + if err != nil { + if ne, ok := err.(net.Error); ok && !ne.Temporary() { + // start over again when seeing unrecoverable error. + c.clearSession(err.Error()) + } + return nil, fmt.Errorf("establishing stream: %w", err) + } + return newConn(stream, session, nil), nil +} + +// Dial creates a new multiplexed QUIC connection to the +// server configured for the client. +func (c *Client) Dial() (*Conn, error) { + return c.DialContext(context.Background()) +} + +// Connect requests immediate handshaking regardless of +// whether any specific Dial has been initiated. It is +// called lazily on the first Dial if not otherwise +// called. +// +// This can serve to pre-establish a multiplexed +// session, but will also initiate idle timeout +// tracking, keepalives etc. Returns any error +// encountered during handshake. +// +// This may safely be called concurrently with Dial. +// The handshake is guaranteed to be completed when the +// call returns to any caller. +func (c *Client) Connect(ctx context.Context) error { + _, err := c.getOrCreateSession(ctx) + return err +} + +func (c *Client) getOrCreateSession(ctx context.Context) (quic.Connection, error) { + c.muSession.Lock() + defer c.muSession.Unlock() + if c.session == nil { + session, err := c.dial(ctx, c.address, c.tlsConf, c.config) + if err != nil { + return nil, err + } + if c.pinnedCert != nil { + if err = c.verifyPinnedCert(session); err != nil { + session.CloseWithError(0, "") + return nil, err + } + } + c.session = session + } + return c.session, nil +} + +func (c *Client) verifyPinnedCert(session quic.Connection) error { + certs := session.ConnectionState().TLS.PeerCertificates + if len(certs) == 0 { + return fmt.Errorf("Server did not present any certificates!") + } + + serverCert := certs[0] + if !serverCert.Equal(c.pinnedCert) { + received := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Headers: nil, + Bytes: serverCert.Raw, + }) + + expected := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Headers: nil, + Bytes: c.pinnedCert.Raw, + }) + + return fmt.Errorf("Server's certificate didn't match expected! Server had\n%v\nbut expected:\n%v", received, expected) + } + return nil +} + +// closes the session established by this client +// (and all multiplexed connections) +func (c *Client) Close() error { + c.clearSession("client closed") + return nil +} + +func (c *Client) clearSession(reason string) { + c.muSession.Lock() + s := c.session + c.session = nil + c.muSession.Unlock() + if s != nil { + log.Debugf("Closing quic session (%v)", reason) + s.CloseWithError(0, "") + } +} diff --git a/server/Makefile b/server/Makefile index ac4f901..c7569d7 100644 --- a/server/Makefile +++ b/server/Makefile @@ -2,7 +2,7 @@ RHOST=163.172.126.44:443 LHOST="10.0.0.209:443" build: - go build + CGO_ENABLED=0 go build run: sudo ./server -addr ${LHOST} -vpn ${RHOST} -state test_data -c test_data/obfs4.json @@ -11,6 +11,7 @@ run-kcp: sudo KCP=1 ./server -addr ${LHOST} -vpn ${RHOST} -state test_data -c test_data/obfs4.json run-quic: + sudo sysctl -w net.core.rmem_max=2500000 sudo QUIC=1 ./server -addr ${LHOST} -vpn ${RHOST} -state test_data -c test_data/obfs4.json stop: |