├── .travis.yml ├── LICENSE ├── README.md ├── build-release.sh ├── client ├── config.go └── main.go ├── echo ├── config.go └── main.go └── server ├── config.go └── main.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.9.x 5 | - tip 6 | 7 | before_install: 8 | - go get -v ./... 9 | 10 | script: 11 | - go test -race -coverprofile=coverage.txt -covermode=atomic 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 the quictun authors gnolizuh. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quictun 2 | 3 | The simplest tunnel service based on QUIC. 4 | 5 | ### QuickStart 6 | 7 | Download precompiled [Releases](https://github.com/gnolizuh/quictun/releases). 8 | 9 | ``` 10 | QUIC Client: ./quictun-client-darwin-amd64 -l ":1935" -r "QUIC_SERVER_IP:6935" 11 | QUIC Server: ./quictun-server-darwin-amd64 -l ":6935" -t "TARGET_IP:1935" 12 | ``` 13 | 14 | The above commands will establish port forwarding for 1935/tcp as: 15 | 16 | > Application -> **QUIC Client(1935/tcp) -> QUIC Server(6935/udp)** -> Target Server(1935/tcp) 17 | 18 | Tunnels the original connection: 19 | 20 | > Application -> Target Server(1935/tcp) 21 | 22 | ### Install from source 23 | 24 | ``` 25 | $go get -u github.com/gnolizuh/quictun/client 26 | $go get -u github.com/gnolizuh/quictun/server 27 | ``` 28 | 29 | ### Benchmarks 30 | 31 | 32 | **
sshping** 33 |

34 | 35 | ```bash 36 | QUIC TCP 37 | ssh-Login-Time: 2.47 s 2.60 s 38 | Minimum-Latency: 58.3 ms 59.0 ms 39 | Median-Latency: 59.5 ms 59.8 ms 40 | Average-Latency: 60.1 ms 60.1 ms 41 | Average-Deviation: 3.32 ms 2.15 ms 42 | Maximum-Latency: 105 ms 96.2 ms 43 | Echo-Count: 1.00 kB 1.00 kB 44 | Upload-Size: 8.00 MB 8.00 MB 45 | Upload-Rate: 811 kB/s 810 kB/s 46 | Download-Size: 8.00 MB 8.00 MB 47 | Download-Rate: 2.45 MB/s 1.80 MB/s 48 | ``` 49 | 50 |

51 |
52 | 53 | **
iperf3** 54 |

55 | 56 | - **QUIC** 57 | 58 | ```bash 59 | [ ID] Interval Transfer Bitrate Retr Cwnd 60 | [ 5] 0.00-1.00 sec 11.2 MBytes 94.3 Mbits/sec 2 1.69 MBytes 61 | [ 5] 1.00-2.00 sec 1.25 MBytes 10.5 Mbits/sec 0 639 KBytes 62 | [ 5] 2.00-3.00 sec 0.00 Bytes 0.00 bits/sec 0 639 KBytes 63 | [ 5] 3.00-4.00 sec 1.25 MBytes 10.5 Mbits/sec 0 639 KBytes 64 | [ 5] 4.00-5.00 sec 1.25 MBytes 10.5 Mbits/sec 0 639 KBytes 65 | [ 5] 5.00-6.00 sec 0.00 Bytes 0.00 bits/sec 0 639 KBytes 66 | [ 5] 6.00-7.00 sec 1.25 MBytes 10.5 Mbits/sec 0 639 KBytes 67 | [ 5] 7.00-8.00 sec 1.25 MBytes 10.5 Mbits/sec 0 639 KBytes 68 | [ 5] 8.00-9.00 sec 0.00 Bytes 0.00 bits/sec 0 639 KBytes 69 | [ 5] 9.00-10.00 sec 1.25 MBytes 10.5 Mbits/sec 0 639 KBytes 70 | - - - - - - - - - - - - - - - - - - - - - - - - - 71 | [ ID] Interval Transfer Bitrate Retr 72 | [ 5] 0.00-10.00 sec 18.8 MBytes 15.7 Mbits/sec 2 sender 73 | [ 5] 0.00-12.29 sec 9.69 MBytes 6.61 Mbits/sec receive 74 | ``` 75 | - **TCP** 76 | ```bash 77 | [ ID] Interval Transfer Bitrate Retr Cwnd 78 | [ 5] 0.00-1.00 sec 10.0 MBytes 83.8 Mbits/sec 1 5.31 MBytes 79 | [ 5] 1.00-2.00 sec 1.25 MBytes 10.5 Mbits/sec 0 5.31 MBytes 80 | [ 5] 2.00-3.00 sec 1.25 MBytes 10.5 Mbits/sec 2 5.31 MBytes 81 | [ 5] 3.00-4.00 sec 0.00 Bytes 0.00 bits/sec 2 5.31 MBytes 82 | [ 5] 4.00-5.00 sec 1.25 MBytes 10.5 Mbits/sec 2 5.31 MBytes 83 | [ 5] 5.00-6.00 sec 1.25 MBytes 10.5 Mbits/sec 2 5.31 MBytes 84 | [ 5] 6.00-7.00 sec 0.00 Bytes 0.00 bits/sec 3 5.31 MBytes 85 | [ 5] 7.00-8.00 sec 1.25 MBytes 10.5 Mbits/sec 2 5.31 MBytes 86 | [ 5] 8.00-9.00 sec 1.25 MBytes 10.5 Mbits/sec 2 2.62 MBytes 87 | [ 5] 9.00-10.00 sec 0.00 Bytes 0.00 bits/sec 1 1.31 MBytes 88 | - - - - - - - - - - - - - - - - - - - - - - - - - 89 | [ ID] Interval Transfer Bitrate Retr 90 | [ 5] 0.00-10.00 sec 17.5 MBytes 14.7 Mbits/sec 17 sender 91 | [ 5] 0.00-10.57 sec 8.75 MBytes 6.95 Mbits/sec receiver 92 | ``` 93 | 94 |

95 |
96 | 97 | -------------------------------------------------------------------------------- /build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LDFLAGS="-s -w" 4 | GCFLAGS="" 5 | 6 | OSES=(linux darwin windows freebsd) 7 | ARCHS=(amd64 386) 8 | for os in ${OSES[@]}; do 9 | for arch in ${ARCHS[@]}; do 10 | suffix="" 11 | if [ "$os" == "windows" ] 12 | then 13 | suffix=".exe" 14 | fi 15 | env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o quictun_client_${os}_${arch}${suffix} github.com/gnolizuh/quictun/client 16 | env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o quictun_server_${os}_${arch}${suffix} github.com/gnolizuh/quictun/server 17 | tar -zcf quictun-${os}-${arch}.tar.gz quictun_client_${os}_${arch}${suffix} quictun_server_${os}_${arch}${suffix} 18 | done 19 | done 20 | -------------------------------------------------------------------------------- /client/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Config for client 4 | type Config struct { 5 | LocalAddr string `json:"localaddr"` 6 | RemoteAddr string `json:"remoteaddr"` 7 | Timeout int `json:"timeout"` 8 | Retry int `json:"retry"` 9 | Quiet bool `json:"quiet"` 10 | } 11 | -------------------------------------------------------------------------------- /client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "time" 6 | "log" 7 | "github.com/urfave/cli" 8 | "github.com/marten-seemann/quic-conn" 9 | "net" 10 | "io" 11 | "os" 12 | ) 13 | 14 | var ( 15 | VERSION = "1.0" 16 | ) 17 | 18 | func main() { 19 | myApp := cli.NewApp() 20 | myApp.Name = "quictun" 21 | myApp.Usage = "client of QUIC tunnel." 22 | myApp.Version = VERSION 23 | myApp.Flags = []cli.Flag{ 24 | cli.StringFlag{ 25 | Name: "localaddr, l", 26 | Value: ":1935", 27 | Usage: "client listen address", 28 | }, 29 | cli.StringFlag{ 30 | Name: "remoteaddr, r", 31 | Value: "127.0.0.1:6935", 32 | Usage: "quic server address", 33 | }, 34 | cli.IntFlag{ 35 | Name: "timeout", 36 | Value: 5, 37 | Usage: "max time of waiting a connection to complete", 38 | }, 39 | cli.IntFlag{ 40 | Name: "retry", 41 | Value: 10, 42 | Usage: "max retry time for quic server connect", 43 | }, 44 | cli.BoolFlag{ 45 | Name: "quiet", 46 | Usage: "to suppress the 'stream open/close' messages", 47 | }, 48 | } 49 | myApp.Action = func(c *cli.Context) error { 50 | config := Config{} 51 | config.LocalAddr = c.String("localaddr") 52 | config.RemoteAddr = c.String("remoteaddr") 53 | config.Timeout = c.Int("timeout") 54 | config.Retry = c.Int("retry") 55 | config.Quiet = c.Bool("quiet") 56 | 57 | log.SetFlags(log.LstdFlags | log.Lmicroseconds) 58 | 59 | // TODO: how to use TLS config? 60 | TLSConfig := func() *tls.Config { 61 | return &tls.Config{InsecureSkipVerify: true} 62 | } 63 | 64 | addr, err := net.ResolveTCPAddr("tcp", config.LocalAddr) 65 | if err != nil { 66 | log.Println(err) 67 | return err 68 | } 69 | 70 | listener, err := net.ListenTCP("tcp", addr) 71 | if err != nil { 72 | log.Println(err) 73 | return err 74 | } 75 | 76 | log.Println("version:", VERSION) 77 | log.Println("listening on:", listener.Addr()) 78 | log.Println("remote addr:", config.RemoteAddr) 79 | log.Println("timeout:", config.Timeout) 80 | log.Println("retry:", config.Retry) 81 | log.Println("quiet:", config.Quiet) 82 | 83 | // transfer data between p1(tcp side) and p2(quic side). 84 | transfer := func(p1 io.ReadWriteCloser) { 85 | if !config.Quiet { 86 | log.Println("stream opened") 87 | defer log.Println("stream closed") 88 | } 89 | defer p1.Close() 90 | 91 | max := config.Retry 92 | p2, err := quicconn.Dial(config.RemoteAddr, TLSConfig()) 93 | for err != nil { 94 | log.Println(err) 95 | if max <= 0 { 96 | p1.Close() 97 | return 98 | } else { 99 | time.Sleep(1 * time.Second) 100 | max-- 101 | p2, err = quicconn.Dial(config.RemoteAddr, TLSConfig()) 102 | } 103 | } 104 | defer p2.Close() 105 | 106 | p1die := make(chan struct{}) 107 | go func() { 108 | n, err := io.Copy(p1, p2) 109 | if err != nil { 110 | log.Println(err) 111 | } 112 | 113 | log.Printf("<- write %d bytes", n) 114 | 115 | close(p1die) 116 | }() 117 | 118 | p2die := make(chan struct{}) 119 | go func() { 120 | n, err := io.Copy(p2, p1) 121 | if err != nil { 122 | log.Println(err) 123 | } 124 | 125 | log.Printf("-> write %d bytes", n) 126 | 127 | close(p2die) 128 | }() 129 | 130 | select { 131 | case <-p1die: 132 | case <-p2die: 133 | } 134 | } 135 | 136 | for { 137 | if p1, err := listener.AcceptTCP(); err == nil { 138 | log.Printf("accpet tcp addr:%s\n", p1.RemoteAddr()) 139 | 140 | go transfer(p1) 141 | } else { 142 | log.Fatalln(err) 143 | } 144 | } 145 | } 146 | myApp.Run(os.Args) 147 | } 148 | -------------------------------------------------------------------------------- /echo/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Config for echo server 4 | type Config struct { 5 | Listen string `json:"listen"` 6 | Quiet bool `json:"quiet"` 7 | } -------------------------------------------------------------------------------- /echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/tls" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "math/big" 9 | "time" 10 | "log" 11 | "encoding/pem" 12 | "github.com/urfave/cli" 13 | "github.com/marten-seemann/quic-conn" 14 | "io" 15 | "os" 16 | ) 17 | 18 | var ( 19 | VERSION = "1.0" 20 | ) 21 | 22 | func main() { 23 | myApp := cli.NewApp() 24 | myApp.Name = "QUIC echo server" 25 | myApp.Usage = "Echo QUIC data immediately." 26 | myApp.Version = VERSION 27 | myApp.Flags = []cli.Flag{ 28 | cli.StringFlag{ 29 | Name: "listen, l", 30 | Value: ":6935", 31 | Usage: "server listen address", 32 | }, 33 | cli.BoolFlag{ 34 | Name: "quiet", 35 | Usage: "to suppress the 'stream open/close' messages", 36 | }, 37 | } 38 | myApp.Action = func(c *cli.Context) error { 39 | config := Config{} 40 | config.Listen = c.String("listen") 41 | config.Quiet = c.Bool("quiet") 42 | 43 | log.SetFlags(log.LstdFlags | log.Lmicroseconds) 44 | 45 | TLSConfig := func() *tls.Config { 46 | key, err := rsa.GenerateKey(rand.Reader, 2048) 47 | if err != nil { 48 | log.Println(err) 49 | return nil 50 | } 51 | 52 | template := x509.Certificate{ 53 | SerialNumber: big.NewInt(1), 54 | NotBefore: time.Now(), 55 | NotAfter: time.Now().Add(time.Hour), 56 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 57 | } 58 | 59 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 60 | if err != nil { 61 | log.Println(err) 62 | return nil 63 | } 64 | 65 | keyPEM := pem.EncodeToMemory(&pem.Block{ 66 | Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key), 67 | }) 68 | b := pem.Block{Type: "CERTIFICATE", Bytes: certDER} 69 | certPEM := pem.EncodeToMemory(&b) 70 | 71 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 72 | if err != nil { 73 | log.Println(err) 74 | return nil 75 | } 76 | 77 | return &tls.Config{ Certificates: []tls.Certificate{tlsCert} } 78 | } 79 | 80 | listener, err := quicconn.Listen("udp", config.Listen, TLSConfig()) 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | log.Println("version:", VERSION) 86 | log.Println("listening on:", listener.Addr()) 87 | log.Println("quiet:", config.Quiet) 88 | 89 | // echo data from read side to write side. 90 | echo := func(p1 io.ReadWriteCloser) { 91 | if !config.Quiet { 92 | log.Println("stream opened") 93 | defer log.Println("stream closed") 94 | } 95 | defer p1.Close() 96 | 97 | n, err := io.Copy(p1, p1) 98 | if err != nil { 99 | log.Println(err) 100 | } 101 | 102 | log.Printf("echo %d bytes", n) 103 | } 104 | 105 | for { 106 | if p1, err := listener.Accept(); err == nil { 107 | log.Printf("accpet quic addr:%s\n", p1.RemoteAddr()) 108 | 109 | go echo(p1) 110 | } else { 111 | log.Fatalln(err) 112 | } 113 | } 114 | } 115 | myApp.Run(os.Args) 116 | } 117 | -------------------------------------------------------------------------------- /server/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Config for server 4 | type Config struct { 5 | Listen string `json:"listen"` 6 | Target string `json:"target"` 7 | Timeout int `json:"timeout"` 8 | Retry int `json:"retry"` 9 | Quiet bool `json:"quiet"` 10 | } -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/tls" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "math/big" 9 | "time" 10 | "log" 11 | "encoding/pem" 12 | "github.com/urfave/cli" 13 | "github.com/marten-seemann/quic-conn" 14 | "net" 15 | "io" 16 | "os" 17 | ) 18 | 19 | var ( 20 | VERSION = "1.0" 21 | ) 22 | 23 | func main() { 24 | myApp := cli.NewApp() 25 | myApp.Name = "quictun" 26 | myApp.Usage = "server of QUIC tunnel." 27 | myApp.Version = VERSION 28 | myApp.Flags = []cli.Flag{ 29 | cli.StringFlag{ 30 | Name: "listen, l", 31 | Value: ":6935", 32 | Usage: "server listen address", 33 | }, 34 | cli.StringFlag{ 35 | Name: "target, t", 36 | Value: "127.0.0.1:1935", 37 | Usage: "target server address", 38 | }, 39 | cli.IntFlag{ 40 | Name: "timeout", 41 | Value: 5, 42 | Usage: "max time of waiting a connection to complete", 43 | }, 44 | cli.IntFlag{ 45 | Name: "retry", 46 | Value: 10, 47 | Usage: "max retry time for target connect", 48 | }, 49 | cli.BoolFlag{ 50 | Name: "quiet", 51 | Usage: "to suppress the 'stream open/close' messages", 52 | }, 53 | } 54 | myApp.Action = func(c *cli.Context) error { 55 | config := Config{} 56 | config.Listen = c.String("listen") 57 | config.Target = c.String("target") 58 | config.Timeout = c.Int("timeout") 59 | config.Retry = c.Int("retry") 60 | config.Quiet = c.Bool("quiet") 61 | 62 | log.SetFlags(log.LstdFlags | log.Lmicroseconds) 63 | 64 | // TODO: how to use TLS config? 65 | TLSConfig := func() *tls.Config { 66 | key, err := rsa.GenerateKey(rand.Reader, 2048) 67 | if err != nil { 68 | log.Println(err) 69 | return nil 70 | } 71 | 72 | template := x509.Certificate{ 73 | SerialNumber: big.NewInt(1), 74 | NotBefore: time.Now(), 75 | NotAfter: time.Now().Add(time.Hour), 76 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 77 | } 78 | 79 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 80 | if err != nil { 81 | log.Println(err) 82 | return nil 83 | } 84 | 85 | keyPEM := pem.EncodeToMemory(&pem.Block{ 86 | Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key), 87 | }) 88 | b := pem.Block{Type: "CERTIFICATE", Bytes: certDER} 89 | certPEM := pem.EncodeToMemory(&b) 90 | 91 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 92 | if err != nil { 93 | log.Println(err) 94 | return nil 95 | } 96 | 97 | return &tls.Config{ Certificates: []tls.Certificate{tlsCert} } 98 | } 99 | 100 | listener, err := quicconn.Listen("udp", config.Listen, TLSConfig()) 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | log.Println("version:", VERSION) 106 | log.Println("listening on:", listener.Addr()) 107 | log.Println("target:", config.Target) 108 | log.Println("timeout:", config.Timeout) 109 | log.Println("retry:", config.Retry) 110 | log.Println("quiet:", config.Quiet) 111 | 112 | // transfer data between p1(quic side) and p2(tcp side). 113 | transfer := func(p1 io.ReadWriteCloser) { 114 | if !config.Quiet { 115 | log.Println("stream opened") 116 | defer log.Println("stream closed") 117 | } 118 | defer p1.Close() 119 | 120 | max := config.Retry 121 | p2, err := net.DialTimeout("tcp", config.Target, time.Duration(config.Timeout) * time.Second) 122 | for err != nil { 123 | log.Println(err) 124 | if max <= 0 { 125 | p1.Close() 126 | return 127 | } else { 128 | time.Sleep(1 * time.Second) 129 | max-- 130 | p2, err = net.DialTimeout("tcp", config.Target, time.Duration(config.Timeout) * time.Second) 131 | } 132 | } 133 | defer p2.Close() 134 | 135 | p1die := make(chan struct{}) 136 | go func() { 137 | n, err := io.Copy(p1, p2) 138 | if err != nil { 139 | log.Println(err) 140 | } 141 | 142 | log.Printf("<- write %d bytes", n) 143 | 144 | close(p1die) 145 | }() 146 | 147 | p2die := make(chan struct{}) 148 | go func() { 149 | n, err := io.Copy(p2, p1) 150 | if err != nil { 151 | log.Println(err) 152 | } 153 | 154 | log.Printf("-> write %d bytes", n) 155 | 156 | close(p2die) 157 | }() 158 | 159 | select { 160 | case <-p1die: 161 | case <-p2die: 162 | } 163 | } 164 | 165 | for { 166 | if p1, err := listener.Accept(); err == nil { 167 | log.Printf("accpet quic addr:%s\n", p1.RemoteAddr()) 168 | 169 | go transfer(p1) 170 | } else { 171 | log.Fatalln(err) 172 | } 173 | } 174 | } 175 | myApp.Run(os.Args) 176 | } 177 | --------------------------------------------------------------------------------