summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoratanarjuat <atanarjuat@example.com>2022-05-30 19:14:02 +0200
committeratanarjuat <atanarjuat@example.com>2022-05-30 19:14:02 +0200
commitae8664bec8a34bc758184e5c72141e26a1c960da (patch)
treed40154885facfbba68799f1e1846c2cfb380f3b6
parent50c5fdc8a15f37d506292b02eef992e83752152b (diff)
quic dialerfeat/quic
-rw-r--r--Makefile4
-rw-r--r--client/client.go16
-rw-r--r--cmd/client/main.go9
-rw-r--r--go.mod6
-rw-r--r--go.sum20
-rw-r--r--quicwrapper/dialer.go228
-rw-r--r--server/Makefile3
7 files changed, 282 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index aa104f2..3d30a53 100644
--- a/Makefile
+++ b/Makefile
@@ -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()
}
diff --git a/go.mod b/go.mod
index 51ce1a8..4482bd5 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 632bab7..2706599 100644
--- a/go.sum
+++ b/go.sum
@@ -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: