From 51ab983d4326b313da6469f33b39a8847bb076d3 Mon Sep 17 00:00:00 2001 From: alex tsai Date: Tue, 27 Oct 2020 22:17:50 +0800 Subject: [PATCH] Add vtun --- .gitignore | 17 +++++++ Dockerfile | 9 ++++ LICENSE | 20 ++++++++ README.md | 18 +++++++ cmd/vtun.go | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 9 ++++ go.sum | 46 ++++++++++++++++++ main.go | 27 +++++++++++ 8 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100755 LICENSE create mode 100644 README.md create mode 100644 cmd/vtun.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05e2b8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Mac OS X files +.DS_Store +# Binaries for programs and plugins +*.dll +*.so +*.dylib +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +logs/ +bin/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6d6e77c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.14.3-alpine + +WORKDIR /app +COPY . /app +ENV GO111MODULE=on +RUN go build -o ./bin/vtun ./main.go + +ENTRYPOINT ["./bin/vtun"] + diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..69f733f --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fc8ff0 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# vtun + +A simple p2p vpn. + +# Usage + +``` +Usage of ./vtun: + + -k string + Encrypt key (default "6da62287-979a-4eb4-a5ab-8b3d89da134b") + -l string + Local tun interface IP/MASK like 172.16.0.1/24 (default "172.16.0.1/24") + -p int + UDP port (default 2001) + -r string + Remote server external IP like 172.16.0.2 (default "172.16.0.2") +``` \ No newline at end of file diff --git a/cmd/vtun.go b/cmd/vtun.go new file mode 100644 index 0000000..ca196b7 --- /dev/null +++ b/cmd/vtun.go @@ -0,0 +1,135 @@ +package cmd + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "encoding/hex" + "fmt" + "log" + "net" + "os" + "os/exec" + "runtime" + + "github.com/songgao/water" +) + +const ( + BufferSize = 1500 + MTU = "1300" +) + +// New vtun +func New(local *string, remote *string, port *int, key *string) { + os := runtime.GOOS + if "linux" != os { + log.Fatal("Only support linux!") + return + } + hashKey := createHash(*key) + // create tun + config := water.Config{ + DeviceType: water.TAP, + } + config.Name = "vtun" + iface, err := water.New(config) + if err != nil { + log.Fatal(err) + } + if nil != err { + log.Fatalln("Unable to allocate TUN interface:", err) + } + log.Println("Interface allocated:", iface.Name()) + // config tun + configTun(local, iface) + // start udp listener + remoteAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%v", *remote, *port)) + if nil != err { + log.Fatalln("Unable to resolve remote addr:", err) + } + localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%v", *port)) + if nil != err { + log.Fatalln("Unable to get UDP socket:", err) + } + conn, err := net.ListenUDP("udp", localAddr) + if nil != err { + log.Fatalln("Unable to listen on UDP socket:", err) + } + defer conn.Close() + // read data from remote and write data to tun + go func() { + buf := make([]byte, BufferSize) + for { + n, _, err := conn.ReadFromUDP(buf) + if err != nil || n == 0 { + break + } + b := buf[:n] + // aes decrypt + decrypt(&b, hashKey) + iface.Write(b) + } + }() + // read data from tun and write to remote + packet := make([]byte, BufferSize) + for { + n, err := iface.Read(packet) + if err != nil || n == 0 { + break + } + b := packet[:n] + // aes encrypt + encrypt(&b, hashKey) + conn.WriteToUDP(b, remoteAddr) + } +} + +func configTun(local *string, iface *water.Interface) { + execCmd("/sbin/ip", "link", "set", "dev", iface.Name(), "mtu", MTU) + execCmd("/sbin/ip", "addr", "add", *local, "dev", iface.Name()) + execCmd("/sbin/ip", "link", "set", "dev", iface.Name(), "up") +} + +func execCmd(c string, args ...string) { + cmd := exec.Command(c, args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + err := cmd.Run() + if nil != err { + log.Fatalln("Error running /sbin/ip:", err) + } +} + +func createHash(key string) []byte { + hasher := md5.New() + hasher.Write([]byte(key)) + return []byte(hex.EncodeToString(hasher.Sum(nil))) +} + +func encrypt(data *[]byte, key []byte) { + block, err := aes.NewCipher(key) + if err != nil { + log.Println(err) + } + iv := key[:aes.BlockSize] + stream := cipher.NewCFBEncrypter(block, iv) + dest := make([]byte, len(*data)) + stream.XORKeyStream(dest, *data) + data = nil + data = &dest +} + +func decrypt(data *[]byte, key []byte) { + block, err := aes.NewCipher(key) + if err != nil { + log.Println(err) + } + iv := key[:aes.BlockSize] + stream := cipher.NewCFBDecrypter(block, iv) + dest := make([]byte, len(*data)) + stream.XORKeyStream(dest, *data) + data = nil + data = &dest +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ba9355b --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module vtun + +go 1.14 + +require ( + github.com/marten-seemann/quic-conn v0.0.0-20191204020628-6e719687462b + github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7033e6d --- /dev/null +++ b/go.sum @@ -0,0 +1,46 @@ +github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/lucas-clemente/quic-go v0.14.0 h1:xmWt9+sRgvAAXmi2S9lrikUtNUPwxeOy400LD+I3Qw0= +github.com/lucas-clemente/quic-go v0.14.0/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgVpFfizUfW5WMaqyU= +github.com/marten-seemann/chacha20 v0.2.0 h1:f40vqzzx+3GdOmzQoItkLX5WLvHgPgyYqFFIO5Gh4hQ= +github.com/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE= +github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= +github.com/marten-seemann/qtls v0.4.1 h1:YlT8QP3WCCvvok7MGEZkMldXbyqgr8oFg5/n8Gtbkks= +github.com/marten-seemann/qtls v0.4.1/go.mod h1:pxVXcHHw1pNIt8Qo0pwSYQEoZ8yYOOPXTCZLQQunvRc= +github.com/marten-seemann/quic-conn v0.0.0-20191204020628-6e719687462b h1:o3bbMw41/oEYaoqS5KugYhqAkO1QtqI+IPpi/SsGoME= +github.com/marten-seemann/quic-conn v0.0.0-20191204020628-6e719687462b/go.mod h1:s6F5Us7rXwvhuJH4/E5AcncOlo2PV5DrIV2cciSuKq8= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= +golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e8934c5 --- /dev/null +++ b/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "flag" + "log" + vtun "vtun/cmd" +) + +var ( + local = flag.String("l", "172.16.0.1/24", "Local tun interface IP/MASK like 172.16.0.1/24") + remote = flag.String("r", "172.16.0.2", "Remote server external IP like 172.16.0.2") + port = flag.Int("p", 2001, "UDP port") + key = flag.String("k", "6da62287-979a-4eb4-a5ab-8b3d89da134b", "Encrypt key") +) + +func main() { + flag.Parse() + if "" == *local { + flag.Usage() + log.Fatalln("local ip is not specified") + } + if "" == *remote { + flag.Usage() + log.Fatalln("remote ip is not specified") + } + vtun.New(local, remote, port, key) +}