diff --git a/README.md b/README.md index 54e77b0..1add902 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A simple VPN written in golang. * VPN over tcp * VPN over udp * VPN over websocket +* VPN over tls # Usage @@ -23,6 +24,12 @@ Usage of ./vtun: tun interface cidr (default "172.16.0.10/24") -c6 string tun interface ipv6 cidr (default "fced:9999::9999/64") + -certificate string + tls certificate file path + -privatekey string + tls certificate key file path + -sni string + tls handshake sni -dn string device name -g client global mode diff --git a/README_CN.md b/README_CN.md index 402c60f..bd2133c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -13,6 +13,7 @@ * 支持tcp * 支持udp * 支持websocket +* 支持tls # 用法 @@ -23,6 +24,12 @@ Usage of ./vtun: tun interface cidr (default "172.16.0.10/24") -c6 string tun interface ipv6 cidr (default "fced:9999::9999/64") + -certificate string + tls certificate file path + -privatekey string + tls certificate key file path + -sni string + tls handshake sni -dn string device name -g client global mode diff --git a/common/config/config.go b/common/config/config.go index 5c32170..2ab3579 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -1,20 +1,23 @@ package config type Config struct { - DeviceName string - LocalAddr string - ServerAddr string - IntranetServerIP string - IntranetServerIPv6 string - CIDR string - CIDRv6 string - Key string - Protocol string - WebSocketPath string - ServerMode bool - GlobalMode bool - Obfs bool - MTU int - Timeout int - LocalGateway string + DeviceName string + LocalAddr string + ServerAddr string + IntranetServerIP string + IntranetServerIPv6 string + CIDR string + CIDRv6 string + Key string + Protocol string + WebSocketPath string + ServerMode bool + GlobalMode bool + Obfs bool + MTU int + Timeout int + LocalGateway string + TLSCertificateFilePath string + TLSCertificateKeyFilePath string + TLSSni string } diff --git a/main.go b/main.go index 061e9ce..ad66a33 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/net-byte/vtun/tcp" "github.com/net-byte/vtun/tun" "github.com/net-byte/vtun/udp" + "github.com/net-byte/vtun/tls" "github.com/net-byte/vtun/ws" ) @@ -29,12 +30,15 @@ func main() { flag.StringVar(&config.IntranetServerIP, "sip", "172.16.0.1", "intranet server ip") flag.StringVar(&config.IntranetServerIPv6, "sip6", "fced:9999::1", "intranet server ipv6") flag.StringVar(&config.Key, "k", "freedom@2022", "key") - flag.StringVar(&config.Protocol, "p", "wss", "protocol tcp/udp/ws/wss") + flag.StringVar(&config.Protocol, "p", "wss", "protocol tcp/udp/ws/tls/wss") flag.StringVar(&config.WebSocketPath, "path", "/freedom", "websocket path") flag.BoolVar(&config.ServerMode, "S", false, "server mode") flag.BoolVar(&config.GlobalMode, "g", false, "client global mode") flag.BoolVar(&config.Obfs, "obfs", false, "enable data obfuscation") flag.IntVar(&config.Timeout, "t", 30, "dial timeout in seconds") + flag.StringVar(&config.TLSCertificateFilePath, "certificate", "", "tls certificate file path") + flag.StringVar(&config.TLSCertificateKeyFilePath, "privatekey", "", "tls certificate key file path") + flag.StringVar(&config.TLSSni, "sni", "", "tls handshake sni") flag.Parse() initConfig(&config) go startApp(config) @@ -78,6 +82,12 @@ func startApp(config config.Config) { } else { ws.StartClient(config) } + case "tls": + if config.ServerMode { + tls.StartServer(config) + } else { + tls.StartClient(config) + } default: if config.ServerMode { ws.StartServer(config) diff --git a/tls/tlsclient.go b/tls/tlsclient.go new file mode 100644 index 0000000..7a4deaa --- /dev/null +++ b/tls/tlsclient.go @@ -0,0 +1,80 @@ +package tls + +import ( + "crypto/tls" + "io" + "log" + "net" + "time" + + "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/tun" + "github.com/songgao/water" +) + +// Start tls client +func StartClient(config config.Config) { + log.Printf("vtun tls client started on %v", config.LocalAddr) + iface := tun.CreateTun(config) + go tunToTLS(config, iface) + for { + tlsconfig := &tls.Config{ + //InsecureSkipVerify: true, + } + if config.TLSSni != "" { + tlsconfig.ServerName = config.TLSSni + } + conn, err := tls.Dial("tcp", config.ServerAddr, tlsconfig) + if err != nil { + time.Sleep(3 * time.Second) + continue + } + cache.GetCache().Set("tlsconn", conn, 24 * time.Hour) + tlsToTun(config, conn, iface) + cache.GetCache().Delete("tlsconn") + } +} + +func tunToTLS(config config.Config, iface *water.Interface) { + packet := make([]byte, config.MTU) + for { + n, err := iface.Read(packet) + if err != nil || n == 0 { + continue + } + if v, ok := cache.GetCache().Get("tlsconn"); ok { + b := packet[:n] + if config.Obfs { + packet = cipher.XOR(packet) + } + tlsconn := v.(net.Conn) + tlsconn.SetWriteDeadline(time.Now().Add(time.Duration(config.Timeout) * time.Second)) + _, err = tlsconn.Write(b) + if err != nil { + continue + } + } + } +} + +func tlsToTun(config config.Config, tlsconn net.Conn, iface *water.Interface) { + defer tlsconn.Close() + packet := make([]byte, config.MTU) + for { + tlsconn.SetReadDeadline(time.Now().Add(time.Duration(config.Timeout) * time.Second)) + n, err := tlsconn.Read(packet) + if err != nil || err == io.EOF { + break + } + b := packet[:n] + if config.Obfs { + b = cipher.XOR(b) + } + _, err = iface.Write(b) + if err != nil { + break + } + } +} diff --git a/tls/tlsserver.go b/tls/tlsserver.go new file mode 100644 index 0000000..5871f2a --- /dev/null +++ b/tls/tlsserver.go @@ -0,0 +1,94 @@ +package tls + +import ( + "crypto/tls" + "log" + "net" + "io" + "time" + + "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/netutil" + "github.com/net-byte/vtun/tun" + "github.com/songgao/water" +) + +//Start tls server +func StartServer(config config.Config) { + log.Printf("vtun tls server started on %v", config.LocalAddr) + iface := tun.CreateTun(config) + cert, err := tls.LoadX509KeyPair(config.TLSCertificateFilePath, config.TLSCertificateKeyFilePath) + if err != nil { + log.Println(err) + return + } + tlsconfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + } + ln, err := tls.Listen("tcp", config.LocalAddr, tlsconfig) + if err != nil { + log.Println(err) + return + } + // server -> client + go toClient(config, iface) + // client -> server + for { + conn, err := ln.Accept() + if err != nil { + continue + } + go toServer(config, conn, iface) + } +} + +func toClient(config config.Config, iface *water.Interface) { + packet := make([]byte, config.MTU) + for { + n, err := iface.Read(packet) + if err != nil || err == io.EOF || n == 0 { + 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) + } + v.(net.Conn).Write(b) + } + } + } +} + +// todo fallback to http +func toServer(config config.Config, tlsconn net.Conn, iface *water.Interface) { + defer tlsconn.Close() + packet := make([]byte, config.MTU) + for { + tlsconn.SetReadDeadline(time.Now().Add(time.Duration(config.Timeout) * time.Second)) + n, err := tlsconn.Read(packet) + if err != nil || err == io.EOF { + break + } + b := packet[:n] + if config.Obfs { + b = cipher.XOR(b) + } + if key := netutil.GetSrcKey(b); key != "" { + cache.GetCache().Set(key, tlsconn, 10*time.Minute) + iface.Write(b) + } + } +}