From 7c8048e7c246f9a66643c4384cf09a9996829dd5 Mon Sep 17 00:00:00 2001 From: NNdroid <99177648+NNdroid@users.noreply.github.com> Date: Tue, 30 May 2023 14:12:39 +0800 Subject: [PATCH] add utls support add dtls support --- app/app.go | 16 +++++- dtls/dtlsclient.go | 108 +++++++++++++++++++++++++++++++++++++ dtls/dtlsserver.go | 105 ++++++++++++++++++++++++++++++++++++ go.mod | 9 +++- go.sum | 19 +++++++ grpc/grpcclient.go | 6 +-- tls/tlsclient.go | 6 +-- utls/sniffer.go | 131 +++++++++++++++++++++++++++++++++++++++++++++ utls/utlsclient.go | 104 +++++++++++++++++++++++++++++++++++ utls/utlsserver.go | 111 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 607 insertions(+), 8 deletions(-) create mode 100644 dtls/dtlsclient.go create mode 100644 dtls/dtlsserver.go create mode 100644 utls/sniffer.go create mode 100644 utls/utlsclient.go create mode 100644 utls/utlsserver.go diff --git a/app/app.go b/app/app.go index 2ef54c5..454fa30 100644 --- a/app/app.go +++ b/app/app.go @@ -1,8 +1,10 @@ package app import ( + "github.com/net-byte/vtun/dtls" "github.com/net-byte/vtun/kcp" "github.com/net-byte/vtun/quic" + "github.com/net-byte/vtun/utls" "log" "github.com/net-byte/vtun/common/cipher" @@ -94,9 +96,21 @@ func (app *App) StartApp() { case "kcp": if app.Config.ServerMode { kcp.StartServer(app.Iface, *app.Config) - }else { + } else { kcp.StartClient(app.Iface, *app.Config) } + case "utls": + if app.Config.ServerMode { + utls.StartServer(app.Iface, *app.Config) + } else { + utls.StartClient(app.Iface, *app.Config) + } + case "dtls": + if app.Config.ServerMode { + dtls.StartServer(app.Iface, *app.Config) + } else { + dtls.StartClient(app.Iface, *app.Config) + } default: if app.Config.ServerMode { udp.StartServer(app.Iface, *app.Config) diff --git a/dtls/dtlsclient.go b/dtls/dtlsclient.go new file mode 100644 index 0000000..f1c31b4 --- /dev/null +++ b/dtls/dtlsclient.go @@ -0,0 +1,108 @@ +package dtls + +import ( + "github.com/pion/dtls" + "log" + "net" + "time" + + "github.com/golang/snappy" + "github.com/net-byte/vtun/common/cache" + "github.com/net-byte/vtun/common/cipher" + "github.com/net-byte/vtun/common/config" + "github.com/net-byte/vtun/common/counter" + "github.com/net-byte/vtun/common/netutil" + "github.com/net-byte/water" +) + +// StartClient starts the tls client +func StartClient(iface *water.Interface, config config.Config) { + log.Println("vtun dtls client started") + go tunToTLS(config, iface) + tlsConfig := &dtls.Config{ + PSK: func(bytes []byte) ([]byte, error) { + return []byte{0x09, 0x46, 0x59, 0x02, 0x49}, nil + }, + PSKIdentityHint: []byte(config.Key), + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_PSK_WITH_AES_128_GCM_SHA256, dtls.TLS_PSK_WITH_AES_128_CCM_8}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + } + if config.TLSSni != "" { + tlsConfig.ServerName = config.TLSSni + } + for { + addr, err := net.ResolveUDPAddr("udp", config.ServerAddr) + if err != nil { + time.Sleep(3 * time.Second) + netutil.PrintErr(err, config.Verbose) + continue + } + conn, err := dtls.Dial("udp", addr, tlsConfig) + if err != nil { + time.Sleep(3 * time.Second) + netutil.PrintErr(err, config.Verbose) + continue + } + cache.GetCache().Set("dtlsconn", conn, 24*time.Hour) + tlsToTun(config, conn, iface) + cache.GetCache().Delete("dtlsconn") + } +} + +// tunToTLS sends packets from tun to tls +func tunToTLS(config config.Config, iface *water.Interface) { + packet := make([]byte, config.BufferSize) + for { + n, err := iface.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + if v, ok := cache.GetCache().Get("dtlsconn"); ok { + b := packet[:n] + if config.Obfs { + b = cipher.XOR(b) + } + if config.Compress { + b = snappy.Encode(nil, b) + } + tlsconn := v.(net.Conn) + _, err = tlsconn.Write(b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + continue + } + counter.IncrWrittenBytes(n) + } + } +} + +// tlsToTun sends packets from tls to tun +func tlsToTun(config config.Config, tlsconn net.Conn, iface *water.Interface) { + defer tlsconn.Close() + packet := make([]byte, config.BufferSize) + for { + n, err := tlsconn.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + b := packet[:n] + if config.Compress { + b, err = snappy.Decode(nil, b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + } + if config.Obfs { + b = cipher.XOR(b) + } + _, err = iface.Write(b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + counter.IncrReadBytes(n) + } +} diff --git a/dtls/dtlsserver.go b/dtls/dtlsserver.go new file mode 100644 index 0000000..082092a --- /dev/null +++ b/dtls/dtlsserver.go @@ -0,0 +1,105 @@ +package dtls + +import ( + "github.com/pion/dtls" + "log" + "net" + "time" + + "github.com/golang/snappy" + "github.com/net-byte/vtun/common/cache" + "github.com/net-byte/vtun/common/cipher" + "github.com/net-byte/vtun/common/config" + "github.com/net-byte/vtun/common/counter" + "github.com/net-byte/vtun/common/netutil" + "github.com/net-byte/water" +) + +// StartServer starts the tls server +func StartServer(iface *water.Interface, config config.Config) { + log.Printf("vtun dtls server started on %v", config.LocalAddr) + tlsConfig := &dtls.Config{ + PSK: func(bytes []byte) ([]byte, error) { + return []byte{0x09, 0x46, 0x59, 0x02, 0x49}, nil + }, + PSKIdentityHint: []byte(config.Key), + CipherSuites: []dtls.CipherSuiteID{dtls.TLS_PSK_WITH_AES_128_GCM_SHA256, dtls.TLS_PSK_WITH_AES_128_CCM_8}, + ExtendedMasterSecret: dtls.RequireExtendedMasterSecret, + } + addr, err := net.ResolveUDPAddr("udp", config.LocalAddr) + if err != nil { + log.Panic(err) + } + ln, err := dtls.Listen("udp", addr, tlsConfig) + if err != nil { + log.Panic(err) + } + // server -> client + go toClient(config, iface) + // client -> server + for { + conn, err := ln.Accept() + if err != nil { + continue + } + go toServer(config, conn, iface) + } +} + +// toClient sends packets from iface to tlsconn +func toClient(config config.Config, iface *water.Interface) { + packet := make([]byte, config.BufferSize) + for { + n, err := iface.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + continue + } + b := packet[:n] + if key := netutil.GetDstKey(b); key != "" { + if v, ok := cache.GetCache().Get(key); ok { + if config.Obfs { + b = cipher.XOR(b) + } + if config.Compress { + b = snappy.Encode(nil, b) + } + _, err := v.(net.Conn).Write(b) + if err != nil { + cache.GetCache().Delete(key) + continue + } + counter.IncrWrittenBytes(n) + } + } + } +} + +// toServer sends packets from tlsconn to iface +func toServer(config config.Config, tlsconn net.Conn, iface *water.Interface) { + defer tlsconn.Close() + packet := make([]byte, config.BufferSize) + for { + n, err := tlsconn.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + b := packet[:n] + if config.Compress { + b, err = snappy.Decode(nil, b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + } + if config.Obfs { + b = cipher.XOR(b) + } + if key := netutil.GetSrcKey(b); key != "" { + cache.GetCache().Set(key, tlsconn, 24*time.Hour) + iface.Write(b) + counter.IncrReadBytes(n) + } + } +} diff --git a/go.mod b/go.mod index 12bf869..1ca9749 100644 --- a/go.mod +++ b/go.mod @@ -10,26 +10,33 @@ require ( github.com/net-byte/water v0.0.9 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/quic-go/quic-go v0.32.0 + github.com/smallnest/chanx v1.1.0 github.com/xtaci/kcp-go v5.4.20+incompatible - golang.org/x/crypto v0.4.0 + golang.org/x/crypto v0.5.0 google.golang.org/grpc v1.46.2 google.golang.org/protobuf v1.28.0 ) require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/gaukas/godicttls v0.0.3 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/klauspost/reedsolomon v1.11.3 // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect + github.com/pion/dtls v1.5.4 // indirect + github.com/pion/logging v0.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/quic-go/qtls-go1-18 v0.2.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.0 // indirect github.com/quic-go/qtls-go1-20 v0.1.0 // indirect + github.com/refraction-networking/utls v1.3.2 // indirect github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect github.com/tjfoc/gmsm v1.4.1 // indirect diff --git a/go.sum b/go.sum index 27d8668..ef6ecf9 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -23,6 +25,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk= +github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -67,6 +71,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 h1:X3Xxno5Ji8idrNiUoFc7QyXpqhSYlDRYQmc7mlpMBzU= github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/reedsolomon v1.11.3 h1:rX9UNNvDhJ0Bq45y6uBy/eYehcjyz5faokTuZmu1Q9U= @@ -80,6 +86,11 @@ github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7 github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pion/dtls v1.5.4 h1:q8pXFMF7T+EAVO4auQU/ds+5yh5yOK6NiTN/4NQ0dB0= +github.com/pion/dtls v1.5.4/go.mod h1:eVHevf4AM8R9+Pxa29q4aiI2iIbfMWOW1WgEcSCGpHU= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -93,8 +104,13 @@ github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV5 github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA= github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= +github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8= +github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/smallnest/chanx v1.1.0 h1:2f/anv7jUuFqeOvCRy4ApmVixWcYUz1ya0vpVx6P7vQ= +github.com/smallnest/chanx v1.1.0/go.mod h1:zXoLoNTSzdRiD+XNNsfAj6/jY7ZfBnviL96tVr+tQ3E= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -112,10 +128,13 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= diff --git a/grpc/grpcclient.go b/grpc/grpcclient.go index 711339b..739d34b 100644 --- a/grpc/grpcclient.go +++ b/grpc/grpcclient.go @@ -23,13 +23,13 @@ import ( func StartClient(iface *water.Interface, config config.Config) { log.Println("vtun grpc client started") go tunToGrpc(config, iface) - tlsconfig := &tls.Config{ + tlsConfig := &tls.Config{ InsecureSkipVerify: config.TLSInsecureSkipVerify, } if config.TLSSni != "" { - tlsconfig.ServerName = config.TLSSni + tlsConfig.ServerName = config.TLSSni } - creds := credentials.NewTLS(tlsconfig) + creds := credentials.NewTLS(tlsConfig) for { conn, err := grpc.Dial(config.ServerAddr, grpc.WithBlock(), grpc.WithTransportCredentials(creds)) if err != nil { diff --git a/tls/tlsclient.go b/tls/tlsclient.go index 2ed11d7..64e1cda 100644 --- a/tls/tlsclient.go +++ b/tls/tlsclient.go @@ -19,7 +19,7 @@ import ( func StartClient(iface *water.Interface, config config.Config) { log.Println("vtun tls client started") go tunToTLS(config, iface) - tlsconfig := &tls.Config{ + tlsConfig := &tls.Config{ InsecureSkipVerify: config.TLSInsecureSkipVerify, MinVersion: tls.VersionTLS13, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, @@ -34,10 +34,10 @@ func StartClient(iface *water.Interface, config config.Config) { }, } if config.TLSSni != "" { - tlsconfig.ServerName = config.TLSSni + tlsConfig.ServerName = config.TLSSni } for { - conn, err := tls.Dial("tcp", config.ServerAddr, tlsconfig) + conn, err := tls.Dial("tcp", config.ServerAddr, tlsConfig) if err != nil { time.Sleep(3 * time.Second) netutil.PrintErr(err, config.Verbose) diff --git a/utls/sniffer.go b/utls/sniffer.go new file mode 100644 index 0000000..90d6456 --- /dev/null +++ b/utls/sniffer.go @@ -0,0 +1,131 @@ +package utls + +import ( + "bytes" + "errors" + "io" + "log" + "net" + + "github.com/net-byte/vtun/common/netutil" +) + +type SniffConn struct { + net.Conn + rout io.Reader + peeked, read bool + Type int + preData []byte + path string +} + +const ( + TypeHttp = iota + TypeHttp2 + TypeUnknown +) + +var ( + httpMethods = [...][]byte{ + []byte("GET"), + []byte("POST"), + []byte("HEAD"), + []byte("PUT"), + []byte("DELETE"), + []byte("OPTIONS"), + []byte("CONNECT"), + } + http2Header = []byte("PRI * HTTP/2.0") + sep = []byte(" ") +) + +func NewPeekPreDataConn(c net.Conn) *SniffConn { + s := &SniffConn{Conn: c, rout: c} + s.Type = s.sniff() + return s +} + +func (c *SniffConn) peekPreData(n int) ([]byte, error) { + if c.read { + return nil, errors.New("pre-data must be peek before read") + } + if c.peeked { + return nil, errors.New("can only peek once") + } + c.peeked = true + preDate := make([]byte, n) + n, err := c.Conn.Read(preDate) + return preDate[:n], err +} + +func (c *SniffConn) Read(p []byte) (int, error) { + if !c.read { + c.read = true + c.rout = io.MultiReader(bytes.NewReader(c.preData), c.Conn) + } + return c.rout.Read(p) +} + +func (c *SniffConn) sniff() int { + var err error + c.preData, err = c.peekPreData(64) + if err != nil && err != io.EOF { + return TypeUnknown + } + + if c.sniffHttp() { + return TypeHttp + } + + if c.sniffHttp2() { + return TypeHttp2 + } + + return TypeUnknown +} + +func (c *SniffConn) sniffHttp() bool { + preDataParts := bytes.Split(c.preData, sep) + + if len(preDataParts) < 2 { + return false + } + + for _, m := range httpMethods { + if bytes.Compare(preDataParts[0], m) == 0 { + c.path = string(preDataParts[1]) + return true + } + } + return false +} + +func (c *SniffConn) sniffHttp2() bool { + return len(c.preData) >= len(http2Header) && + bytes.Compare(c.preData[:len(http2Header)], http2Header) == 0 +} + +func (c *SniffConn) SetPath(path string) { + preDataParts := bytes.Split(c.preData, sep) + preDataParts[1] = []byte(path) + c.preData = bytes.Join(preDataParts, sep) + c.path = path +} + +func (c *SniffConn) GetPath() string { + return c.path +} + +func (c *SniffConn) Handle() bool { + write, err := c.Write(netutil.GetDefaultHttpResponse()) + if err != nil { + return false + } + defer func(c *SniffConn) { + err := c.Close() + if err != nil { + log.Printf("%x\n", err) + } + }(c) + return write > 0 +} diff --git a/utls/utlsclient.go b/utls/utlsclient.go new file mode 100644 index 0000000..406733d --- /dev/null +++ b/utls/utlsclient.go @@ -0,0 +1,104 @@ +package utls + +import ( + utls "github.com/refraction-networking/utls" + "log" + "net" + "time" + + "github.com/golang/snappy" + "github.com/net-byte/vtun/common/cache" + "github.com/net-byte/vtun/common/cipher" + "github.com/net-byte/vtun/common/config" + "github.com/net-byte/vtun/common/counter" + "github.com/net-byte/vtun/common/netutil" + "github.com/net-byte/water" +) + +// StartClient starts the tls client +func StartClient(iface *water.Interface, config config.Config) { + log.Println("vtun utls client started") + go tunToTLS(config, iface) + tlsconfig := &utls.Config{ + InsecureSkipVerify: config.TLSInsecureSkipVerify, + } + if config.TLSSni != "" { + tlsconfig.ServerName = config.TLSSni + } + for { + tcpConn, err := net.Dial("tcp", config.ServerAddr) + if err != nil { + time.Sleep(3 * time.Second) + netutil.PrintErr(err, config.Verbose) + continue + } + conn := utls.UClient(tcpConn, tlsconfig, utls.HelloRandomized) + err = conn.Handshake() + if err != nil { + time.Sleep(3 * time.Second) + netutil.PrintErr(err, config.Verbose) + continue + } + cache.GetCache().Set("utlsconn", conn, 24*time.Hour) + tlsToTun(config, conn, iface) + cache.GetCache().Delete("utlsconn") + } +} + +// tunToTLS sends packets from tun to tls +func tunToTLS(config config.Config, iface *water.Interface) { + packet := make([]byte, config.BufferSize) + for { + n, err := iface.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + if v, ok := cache.GetCache().Get("utlsconn"); ok { + b := packet[:n] + if config.Obfs { + b = cipher.XOR(b) + } + if config.Compress { + b = snappy.Encode(nil, b) + } + utlsconn := v.(*utls.UConn) + _, err = utlsconn.Write(b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + continue + } + counter.IncrWrittenBytes(n) + } + } +} + +// tlsToTun sends packets from tls to tun +func tlsToTun(config config.Config, utlsconn *utls.UConn, iface *water.Interface) { + defer utlsconn.Close() + packet := make([]byte, config.BufferSize) + for { + n, err := utlsconn.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + b := packet[:n] + if config.Compress { + b, err = snappy.Decode(nil, b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + } + if config.Obfs { + b = cipher.XOR(b) + } + _, err = iface.Write(b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + counter.IncrReadBytes(n) + } +} diff --git a/utls/utlsserver.go b/utls/utlsserver.go new file mode 100644 index 0000000..997064c --- /dev/null +++ b/utls/utlsserver.go @@ -0,0 +1,111 @@ +package utls + +import ( + "log" + "net" + "time" + + "github.com/golang/snappy" + "github.com/net-byte/vtun/common/cache" + "github.com/net-byte/vtun/common/cipher" + "github.com/net-byte/vtun/common/config" + "github.com/net-byte/vtun/common/counter" + "github.com/net-byte/vtun/common/netutil" + "github.com/net-byte/water" + utls "github.com/refraction-networking/utls" +) + +// StartServer starts the tls server +func StartServer(iface *water.Interface, config config.Config) { + log.Printf("vtun utls server started on %v", config.LocalAddr) + cert, err := utls.LoadX509KeyPair(config.TLSCertificateFilePath, config.TLSCertificateKeyFilePath) + if err != nil { + log.Panic(err) + } + tlsConfig := &utls.Config{ + Certificates: []utls.Certificate{cert}, + } + ln, err := utls.Listen("tcp", config.LocalAddr, tlsConfig) + if err != nil { + log.Panic(err) + } + // server -> client + go toClient(config, iface) + // client -> server + for { + conn, err := ln.Accept() + if err != nil { + continue + } + sniffConn := NewPeekPreDataConn(conn) + switch sniffConn.Type { + case TypeHttp: + if sniffConn.Handle() { + continue + } + case TypeHttp2: + if sniffConn.Handle() { + continue + } + } + go toServer(config, sniffConn, iface) + } +} + +// toClient sends packets from iface to tlsconn +func toClient(config config.Config, iface *water.Interface) { + packet := make([]byte, config.BufferSize) + for { + n, err := iface.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + continue + } + b := packet[:n] + if key := netutil.GetDstKey(b); key != "" { + if v, ok := cache.GetCache().Get(key); ok { + if config.Obfs { + b = cipher.XOR(b) + } + if config.Compress { + b = snappy.Encode(nil, b) + } + _, err := v.(net.Conn).Write(b) + if err != nil { + cache.GetCache().Delete(key) + continue + } + counter.IncrWrittenBytes(n) + } + } + } +} + +// toServer sends packets from tlsconn to iface +func toServer(config config.Config, tlsconn net.Conn, iface *water.Interface) { + defer tlsconn.Close() + packet := make([]byte, config.BufferSize) + for { + n, err := tlsconn.Read(packet) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + b := packet[:n] + if config.Compress { + b, err = snappy.Decode(nil, b) + if err != nil { + netutil.PrintErr(err, config.Verbose) + break + } + } + if config.Obfs { + b = cipher.XOR(b) + } + if key := netutil.GetSrcKey(b); key != "" { + cache.GetCache().Set(key, tlsconn, 24*time.Hour) + iface.Write(b) + counter.IncrReadBytes(n) + } + } +}