├── LICENSE ├── README.md ├── cmd └── utun │ └── main.go ├── go.mod ├── go.sum ├── utun.go └── utun_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 taoso 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # utun 2 | 3 | 之前搞过 [dtun](https://github.com/taoso/dtun/),基于 DTLS 实现加密隧道。在实践 4 | 中发现,我的路由器性能太弱,而且不支持 AES 硬件加速,隧道流量大的时候系统负载会 5 | 很高。鉴于此,我放弃 DTLS 加密,转而使用普通的 XOR 实现简单混淆。虽然加密强度变 6 | 弱,但考虑到应用导本身还会使用 TLS 加密,所以通过牺牲 IP 层加密强度换取性能也算 7 | 是赚钱的买卖。 8 | 9 | 详细使用说明请参考我的博客文章 10 | 11 | ## 安装 12 | 13 | ``` 14 | go install github.com/taoso/utun/cmd/utun 15 | ``` 16 | 17 | ## 服务端 18 | 19 | ```bash 20 | utun -listen :4430 -key XXXX 21 | ``` 22 | 23 | ## 客户端 24 | 25 | ```bash 26 | utun -connect example.net:4430 -key XXXX 27 | ``` 28 | 29 | 无论是服务端还是客户端,都需要自己为 tun 设备设置 IP 地址。utun 本身不维护任何 30 | 状态。 31 | -------------------------------------------------------------------------------- /cmd/utun/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net" 9 | "net/http" 10 | 11 | _ "net/http/pprof" 12 | 13 | "github.com/songgao/water" 14 | "github.com/taoso/utun" 15 | ) 16 | 17 | func main() { 18 | var listen string 19 | var connect string 20 | var skey string 21 | var pprof string 22 | 23 | flag.StringVar(&listen, "listen", "", "listen address") 24 | flag.StringVar(&connect, "connect", "", "connect address") 25 | flag.StringVar(&skey, "key", "", "shared xor key") 26 | flag.StringVar(&pprof, "pprof", "", "pprof address") 27 | 28 | flag.Parse() 29 | 30 | if skey == "" { 31 | log.Fatal("shared xor key can not be empty") 32 | } 33 | key, err := hex.DecodeString(skey) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | cfg := water.Config{DeviceType: water.TUN} 39 | tun, err := water.New(cfg) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | defer tun.Close() 44 | 45 | fmt.Println("tun:", tun.Name()) 46 | 47 | if pprof != "" { 48 | go http.ListenAndServe(pprof, nil) 49 | } 50 | 51 | if listen != "" { 52 | c, err := net.ListenPacket("udp", listen) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | defer c.Close() 57 | 58 | utun.Server(tun, c, key) 59 | } else if connect != "" { 60 | c, err := net.Dial("udp", connect) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | defer c.Close() 65 | 66 | utun.Client(tun, c, key) 67 | } else { 68 | log.Fatal("you must set either listen or connect") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/taoso/utun 2 | 3 | go 1.21 4 | 5 | require github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 6 | 7 | require golang.org/x/sys v0.16.0 // indirect 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= 2 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= 3 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 4 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 5 | -------------------------------------------------------------------------------- /utun.go: -------------------------------------------------------------------------------- 1 | package utun 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "sync/atomic" 8 | ) 9 | 10 | type PacketConn interface { 11 | ReadFrom(p []byte) (n int, addr net.Addr, err error) 12 | WriteTo(p []byte, addr net.Addr) (n int, err error) 13 | } 14 | 15 | func Server(tun io.ReadWriter, c PacketConn, key []byte) { 16 | var cAddr atomic.Value 17 | 18 | go func() { 19 | buf := make([]byte, 1500) 20 | for { 21 | n, err := tun.Read(buf) 22 | if err != nil { 23 | log.Println("tun read err:", err) 24 | } 25 | 26 | if n == 0 { 27 | continue 28 | } 29 | 30 | b := buf[:n] 31 | 32 | if a := cAddr.Load(); a != nil { 33 | xor(b, key) 34 | _, err := c.WriteTo(b, a.(net.Addr)) 35 | if err != nil { 36 | log.Println("WriteTo err:", err) 37 | cAddr.Store(nil) 38 | } 39 | } 40 | } 41 | }() 42 | 43 | for { 44 | buf := make([]byte, 1500) 45 | n, addr, err := c.ReadFrom(buf) 46 | if err != nil { 47 | log.Println("ReadFrom err:", err) 48 | } 49 | 50 | if n == 0 { 51 | continue 52 | } 53 | 54 | b := buf[:n] 55 | 56 | xor(b, key) 57 | 58 | cAddr.Store(addr) 59 | 60 | if _, err := tun.Write(b); err != nil { 61 | log.Println("tun write err:", err) 62 | } 63 | } 64 | } 65 | 66 | func Client(tun, conn io.ReadWriter, key []byte) { 67 | go func() { 68 | buf := make([]byte, 1500) 69 | for { 70 | n, err := tun.Read(buf) 71 | 72 | if err != nil { 73 | log.Println("tun read err:", err) 74 | } 75 | 76 | if n == 0 { 77 | continue 78 | } 79 | 80 | b := buf[:n] 81 | 82 | xor(b, key) 83 | 84 | if _, err := conn.Write(b); err != nil { 85 | log.Println("UDP write err:", err) 86 | } 87 | } 88 | }() 89 | 90 | buf := make([]byte, 1500) 91 | for { 92 | n, err := conn.Read(buf) 93 | if err != nil { 94 | log.Println("UDP read err:", err) 95 | } 96 | 97 | if n == 0 { 98 | continue 99 | } 100 | 101 | b := buf[:n] 102 | xor(b, key) 103 | 104 | if _, err := tun.Write(b); err != nil { 105 | log.Println("tun write err:", err) 106 | } 107 | } 108 | } 109 | 110 | func xor(data, key []byte) { 111 | if len(key) == 0 { 112 | return 113 | } 114 | j := 0 115 | for i := 0; i < len(data); i++ { 116 | data[i] ^= key[j] 117 | j += 1 118 | if j >= len(key) { 119 | j = 0 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /utun_test.go: -------------------------------------------------------------------------------- 1 | package utun 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestXor(t *testing.T) { 9 | cases := []struct { 10 | data []byte 11 | key []byte 12 | result []byte 13 | }{ 14 | { 15 | data: []byte{0x11, 0x22, 0x33, 0x44, 0x55}, 16 | key: []byte{0xaa, 0xbb}, 17 | result: []byte{ 18 | 0x11 ^ 0xaa, 19 | 0x22 ^ 0xbb, 20 | 0x33 ^ 0xaa, 21 | 0x44 ^ 0xbb, 22 | 0x55 ^ 0xaa, 23 | }, 24 | }, 25 | { 26 | data: []byte{}, 27 | key: []byte{0xaa, 0xbb}, 28 | result: []byte{}, 29 | }, 30 | { 31 | data: []byte{0x11}, 32 | key: []byte{}, 33 | result: []byte{0x11}, 34 | }, 35 | } 36 | 37 | for i, c := range cases { 38 | xor(c.data, c.key) 39 | 40 | if !bytes.Equal(c.data, c.result) { 41 | t.Fatal("invalid result", i, c.data) 42 | } 43 | } 44 | } 45 | --------------------------------------------------------------------------------