├── .gitignore ├── assets └── sytemd_miniature.service ├── dockerfile ├── scripts └── client │ └── osx_setup_interfaces.sh ├── go.mod ├── cmd ├── client │ └── main.go ├── server │ └── main.go └── gui │ ├── ref.go │ └── main.go ├── internal ├── miniature │ ├── metrics.go │ ├── compressor.go │ ├── linux.go │ ├── unix.go │ ├── http.go │ ├── pool.go │ ├── client.go │ └── server.go ├── cryptography │ ├── codec.go │ └── ca.go └── common │ ├── tplink.go │ ├── helpers.go │ └── ifce.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .mod 2 | *.sum 3 | *.code-workspace 4 | *.crt 5 | *.pem -------------------------------------------------------------------------------- /assets/sytemd_miniature.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Miniature 3 | 4 | [Service] 5 | ExecStart=/usr/local/bin/miniature run -config=/etc/miniature/config.yml 6 | Restart=always 7 | 8 | [Install] 9 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.5-alpine 2 | RUN apk update 3 | RUN apk upgrade 4 | RUN apk add git 5 | RUN apk add iptables 6 | RUN apk add bash 7 | RUN apk add curl 8 | RUN apk add net-tools 9 | RUN apk add tcpdump 10 | RUN apk add tshark 11 | RUN mkdir /miniature 12 | COPY . /miniature 13 | WORKDIR /miniature 14 | RUN export GO111MODULE=on 15 | -------------------------------------------------------------------------------- /scripts/client/osx_setup_interfaces.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Disable IPv6 on all interfaces. 3 | 4 | DNS=$1 5 | IFS=$'\n' 6 | net=`networksetup -listallnetworkservices | grep -v asterisk` 7 | for i in $net 8 | do 9 | networksetup -setv6off "$i" 10 | echo "IPv6 for $i is Off" 11 | echo "Setting dns for $i" 12 | networksetup -setdnsservers "$i" empty 13 | networksetup -setdnsservers "$i" "$DNS" 14 | sudo killall -HUP mDNSResponder 15 | echo "$i Configured successfully" 16 | done 17 | 18 | exit 0 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/devgenie/miniature 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/aead/ecdh v0.2.0 7 | github.com/gizak/termui/v3 v3.1.0 // indirect 8 | github.com/go-chi/chi v1.5.4 // indirect 9 | github.com/google/uuid v1.2.0 // indirect 10 | github.com/lithammer/shortuuid/v3 v3.0.5 11 | github.com/pierrec/lz4 v2.6.0+incompatible 12 | github.com/pierrec/lz4/v4 v4.1.3 13 | github.com/robfig/cron v1.2.0 14 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 15 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect 16 | golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d 17 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect 18 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 19 | ) 20 | -------------------------------------------------------------------------------- /cmd/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "flag" 6 | "log" 7 | 8 | "github.com/aead/ecdh" 9 | utilities "github.com/devgenie/miniature/internal/common" 10 | "github.com/devgenie/miniature/internal/miniature" 11 | ) 12 | 13 | func init() { 14 | gob.Register(ecdh.Point{}) 15 | } 16 | 17 | func main() { 18 | configFile := flag.String("config", "/etc/miniature/config.yml", "Client configuration file") 19 | flag.Parse() 20 | 21 | if *configFile != "" { 22 | clientConfig := new(miniature.ClientConfig) 23 | err := utilities.FileToYaml(*configFile, clientConfig) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | vpnClient := new(miniature.Client) 28 | err = vpnClient.Run(*clientConfig) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | } else { 33 | log.Fatal("Please provide path to config file") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/miniature/metrics.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | //Metrics handles server metrics 8 | type Metrics struct { 9 | TimeStarted int64 10 | GatewayInterfaceBytesIn int 11 | UDPTunnelBytesIn int 12 | UDPTunnelBytesOut int 13 | TotalBytesCompressed int 14 | ConnectionsIn int 15 | ConnectionsOut int 16 | mutex *sync.Mutex 17 | } 18 | 19 | func initMetrics() *Metrics { 20 | metrics := new(Metrics) 21 | metrics.mutex = new(sync.Mutex) 22 | return metrics 23 | } 24 | 25 | // Update updates metrics 26 | func (metrics *Metrics) Update(clientBytesRead int, bytesUncompressed int, bytesCompressed int, bytesRecieved int) { 27 | metrics.mutex.Lock() 28 | metrics.GatewayInterfaceBytesIn += bytesRecieved 29 | metrics.TotalBytesCompressed += bytesCompressed 30 | metrics.UDPTunnelBytesOut += bytesUncompressed 31 | metrics.UDPTunnelBytesIn += clientBytesRead 32 | if clientBytesRead > 0 { 33 | metrics.ConnectionsIn++ 34 | } else { 35 | metrics.ConnectionsOut++ 36 | } 37 | metrics.mutex.Unlock() 38 | } 39 | -------------------------------------------------------------------------------- /internal/miniature/compressor.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/pierrec/lz4" 7 | ) 8 | 9 | // Compress compresses bytes passed to it and returns a compressed byte array 10 | func Compress(data []byte) (compressed []byte, err error) { 11 | buff := make([]byte, 100*len(data)) 12 | n, err := lz4.CompressBlockHC(data, buff, 1) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | // if n >= len(data) || n == 0 { 18 | // fmt.Println(n) 19 | // err = errors.New("Data cannot be compressed") 20 | // return nil, err 21 | // } 22 | 23 | compressedData := buff[:n] 24 | return compressedData, nil 25 | } 26 | 27 | // Decompress decompresses bytes passed to it and returns a decompressed byte array 28 | func Decompress(data []byte) (decompressedData []byte, err error) { 29 | buff := make([]byte, 100*len(data)) 30 | n, err := lz4.UncompressBlock(data, buff) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | if n == 0 { 36 | err = errors.New("Failed to decompress data") 37 | return nil, err 38 | } 39 | 40 | decompressedData = buff[:n] 41 | return decompressedData, nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/cryptography/codec.go: -------------------------------------------------------------------------------- 1 | package cryptography 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "io" 8 | "log" 9 | ) 10 | 11 | // Encrypt encrypts the plain text data 12 | func Encrypt(secret []byte, plainTextData []byte) (EncryptedData []byte, err error) { 13 | block, err := aes.NewCipher(secret) 14 | if err != nil { 15 | log.Println(err) 16 | return nil, err 17 | } 18 | 19 | nonce := make([]byte, 12) 20 | if _, err := io.ReadFull(rand.Reader, nonce); err != nil { 21 | return nil, err 22 | } 23 | 24 | aesgcm, err := cipher.NewGCM(block) 25 | if err != nil { 26 | return nil, err 27 | } 28 | EncryptedData = aesgcm.Seal(nil, nonce, plainTextData, nil) 29 | EncryptedData = append(EncryptedData, nonce...) 30 | return EncryptedData, nil 31 | } 32 | 33 | // Decrypt decrypts the plain text data 34 | func Decrypt(secret []byte, encryptedData []byte) (plainText []byte, err error) { 35 | nonce := encryptedData[len(encryptedData)-12:] 36 | encryptedData = encryptedData[:len(encryptedData)-12] 37 | block, err := aes.NewCipher(secret) 38 | if err != nil { 39 | log.Println(err) 40 | return nil, err 41 | } 42 | aesgcm, err := cipher.NewGCM(block) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | plainText, err = aesgcm.Open(nil, nonce, encryptedData, nil) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return plainText, nil 52 | } 53 | -------------------------------------------------------------------------------- /internal/miniature/linux.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/devgenie/miniature/internal/common" 7 | ) 8 | 9 | // SetUpLinuxClient sets up IP tables on linux 10 | func SetUpLinuxClient(defaultGWIface string, defaultGWAddr string, tunnelIface string, tunnelIP string, serverIP string) error { 11 | commands := [][]string{ 12 | {"-F", "-t", "nat"}, // Flush the same for the NAT table // Accept all output packets from "interface" in the INPUT chain 13 | {"-t", "nat", "-A", "POSTROUTING", "-o", tunnelIface, "-j", "SNAT", "--to-source", tunnelIP}, // It says what it does ;) 14 | {"-P", "FORWARD", "ACCEPT"}, 15 | } 16 | 17 | // Setup IPTables 18 | for _, command := range commands { 19 | cmd := strings.Join(command, " ") 20 | err := common.RunCommand("iptables", cmd) 21 | if err != nil { 22 | return err 23 | } 24 | } 25 | 26 | // Setup routes 27 | routes := []common.Route{ 28 | {Destination: "0.0.0.0/0", NextHop: tunnelIP, GWInterface: tunnelIface}, 29 | {Destination: "128.0.0.0/1", NextHop: tunnelIP, GWInterface: tunnelIface}, 30 | {Destination: serverIP, NextHop: defaultGWAddr, GWInterface: defaultGWIface}, 31 | } 32 | for _, route := range routes { 33 | common.DeleteRoute(route.Destination) 34 | err := common.AddRoute(route) 35 | if err != nil { 36 | return nil 37 | } 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/miniature/unix.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/devgenie/miniature/internal/common" 10 | utilities "github.com/devgenie/miniature/internal/common" 11 | ) 12 | 13 | // SetDarwinClient sets up IP tables on Darwin hosts 14 | func SetDarwinClient(defaultGWIface string, defaultGWAddr string, tunnelIface string, tunnelIP string, serverIP string, dnsServer string) error { 15 | command := fmt.Sprintf("nat on %s from %s to any -> (%s) \n", defaultGWIface, tunnelIP, defaultGWIface) 16 | tmpFile, err := ioutil.TempFile(os.TempDir(), "minature-") 17 | defer os.Remove(tmpFile.Name()) 18 | defer tmpFile.Close() 19 | pfctl := []byte(command) 20 | _, err = tmpFile.Write(pfctl) 21 | if err != nil { 22 | log.Fatal("Failed to write to temporary file", err) 23 | return err 24 | } 25 | fmt.Println(command) 26 | 27 | command = fmt.Sprintf("-f %s", tmpFile.Name()) 28 | err = utilities.RunCommand("pfctl", command) 29 | if err != nil { 30 | tmpFile.Close() 31 | return err 32 | } 33 | 34 | // Setup routes 35 | routes := []common.Route{ 36 | {Destination: "0.0.0.0/0", NextHop: tunnelIP, GWInterface: tunnelIface}, 37 | {Destination: "128.0.0.0/1", NextHop: tunnelIP, GWInterface: tunnelIface}, 38 | {Destination: serverIP, NextHop: defaultGWAddr, GWInterface: defaultGWIface}, 39 | } 40 | for _, route := range routes { 41 | common.DeleteRoute(route.Destination) 42 | err := common.AddRoute(route) 43 | if err != nil { 44 | return nil 45 | } 46 | } 47 | // Disable ipv6 on osx 48 | command = fmt.Sprintf("./scripts/client/osx_setup_interfaces.sh %s", dnsServer) 49 | err = utilities.RunCommand("/bin/sh", command) 50 | if err != nil { 51 | tmpFile.Close() 52 | return err 53 | } 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/common/tplink.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "log" 7 | "net" 8 | ) 9 | 10 | const ( 11 | // HANDSHAKE Initiates handshake 12 | HANDSHAKE byte = 0x01 13 | // HANDSHAKE_ACCEPTED accepts handshake 14 | HANDSHAKE_ACCEPTED byte = 0x02 15 | // CLIENT_CONFIGURATION sends client configuration 16 | CLIENT_CONFIGURATION byte = 0x03 17 | // SESSION_REQUEST requests for session 18 | SESSION_REQUEST byte = 0x04 19 | // SESSION_ACCEPTED accepts a session 20 | SESSION_ACCEPTED byte = 0x05 21 | // HEARTBEAT sends heartbeat 22 | HEARTBEAT byte = 0x06 23 | // SESSION sends session data 24 | SESSION byte = 0x07 25 | // FRAGMENT_SIZE size of each fragmented packet 26 | FRAGMENT_SIZE int = 400 27 | ) 28 | 29 | // Addr represents a HTTP address 30 | type Addr struct { 31 | IPAddr net.IP 32 | Network net.IPNet 33 | Gateway net.IP 34 | } 35 | 36 | // Packet is a structure representing a packet 37 | type Packet struct { 38 | Flag byte 39 | Src string 40 | Nonce []byte 41 | Payload []byte 42 | } 43 | 44 | // Decode recieves data as a byte array and decodes it to the passed dataStructure 45 | func Decode(dataStructure interface{}, data []byte) error { 46 | buffer := bytes.NewBuffer(data) 47 | decoder := gob.NewDecoder(buffer) 48 | err := decoder.Decode(dataStructure) 49 | 50 | if err != nil { 51 | log.Println(err) 52 | return err 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // Encode recieves a dataStructure to encode and returns an encoded byte array 59 | func Encode(dataStructure interface{}) (encoded []byte, err error) { 60 | var buffer bytes.Buffer 61 | encoder := gob.NewEncoder(&buffer) 62 | err = encoder.Encode(dataStructure) 63 | 64 | if err != nil { 65 | log.Printf("Error : %s", err) 66 | return nil, err 67 | } 68 | 69 | return buffer.Bytes(), nil 70 | } 71 | 72 | // Fragment fragments packets over 1472 bytes 73 | func Fragment(data []byte) [][]byte { 74 | packets := make([][]byte, 0, len(data)/FRAGMENT_SIZE+1) 75 | var packet []byte 76 | if len(data) > 1400 { 77 | for len(data) >= FRAGMENT_SIZE { 78 | packet, data = data[:FRAGMENT_SIZE], data[FRAGMENT_SIZE:] 79 | packets = append(packets, packet) 80 | } 81 | } else { 82 | packets = append(packets, data) 83 | } 84 | return packets 85 | } 86 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | 8 | utilities "github.com/devgenie/miniature/internal/common" 9 | "github.com/devgenie/miniature/internal/miniature" 10 | ) 11 | 12 | func startServer(serverConfig string) { 13 | config := new(miniature.ServerConfig) 14 | if serverConfig != "" { 15 | err := utilities.FileToYaml(serverConfig, config) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | } else { 20 | config.CertificatesDirectory = "/etc/miniature/certs" 21 | config.Network = "10.2.0.0/24" 22 | config.ListeningPort = 4321 23 | } 24 | 25 | server := new(miniature.Server) 26 | server.Run(*config) 27 | } 28 | 29 | func main() { 30 | runFlag := flag.NewFlagSet("run", flag.ExitOnError) 31 | serverConfigFlag := runFlag.String("config", "/etc/miniature/config.yml", "Server configuration file") 32 | 33 | clientConfig := flag.NewFlagSet("newclient", flag.ExitOnError) 34 | configFile := clientConfig.String("config", "/etc/miniature/config.yml", "Server Configuration File") 35 | 36 | if len(os.Args) > 1 { 37 | switch os.Args[1] { 38 | case "newclient": 39 | serverConfig := new(miniature.ServerConfig) 40 | if len(os.Args) == 3 { 41 | err := clientConfig.Parse(os.Args[2:]) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | serverConfigYamlPath := *configFile 46 | err = utilities.FileToYaml(serverConfigYamlPath, serverConfig) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | } else { 51 | serverConfig.CertificatesDirectory = "/etc/miniature/certs" 52 | serverConfig.Network = "10.2.0.0/24" 53 | } 54 | server := new(miniature.Server) 55 | server.Config = *serverConfig 56 | config, err := server.CreateClientConfig() 57 | if err != nil { 58 | log.Fatal(err) 59 | break 60 | } 61 | log.Println(config) 62 | case "run": 63 | startServer(*serverConfigFlag) 64 | if len(os.Args) == 3 { 65 | log.Println("Running Server") 66 | err := runFlag.Parse(os.Args[2:]) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | startServer(*serverConfigFlag) 71 | } else { 72 | log.Println("Running server with default settings") 73 | startServer("") 74 | } 75 | default: 76 | flag.Usage() 77 | } 78 | } else { 79 | log.Println("Running server with default config") 80 | startServer("") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /internal/common/helpers.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net" 9 | "os" 10 | "os/exec" 11 | "strings" 12 | 13 | "github.com/robfig/cron" 14 | "gopkg.in/yaml.v3" 15 | ) 16 | 17 | // RunCommand is a wrapper to easily run linux commands 18 | func RunCommand(command, arguments string) error { 19 | log.Printf("Issuing command: %s %s \n", command, arguments) 20 | commandArguments := strings.Split(arguments, " ") 21 | cmd := exec.Command(command, commandArguments...) 22 | err := cmd.Run() 23 | if err != nil { 24 | fmt.Println("Error running ", command, arguments) 25 | } 26 | return err 27 | } 28 | 29 | // RunCron runs cron jobs 30 | func RunCron(name string, cronString string, cronFunc func()) { 31 | cronjob := cron.New() 32 | err := cronjob.AddFunc(cronString, cronFunc) 33 | if err != nil { 34 | log.Printf("An error occured setting up %s, err: %s \n", name, err) 35 | return 36 | } 37 | fmt.Printf("Starting %s \n", name) 38 | cronjob.Start() 39 | entry := cronjob.Entries() 40 | fmt.Printf("Cron scheduled to run on %s \n", entry[0].Next) 41 | } 42 | 43 | // FileToYaml unmarshals files to the data stucture specified 44 | func FileToYaml(filepath string, dataStruct interface{}) error { 45 | file, err := os.Open(filepath) 46 | if err != nil { 47 | file.Close() 48 | return err 49 | } 50 | 51 | fileData, err := ioutil.ReadAll(file) 52 | if err != nil { 53 | file.Close() 54 | return err 55 | } 56 | 57 | err = yaml.Unmarshal(fileData, dataStruct) 58 | if err != nil { 59 | file.Close() 60 | return err 61 | } 62 | return file.Close() 63 | } 64 | 65 | // GetPublicIP gets the public ip of the host 66 | func GetPublicIP(interfaceName string) (ipaddress string, err error) { 67 | interfaceByname, err := net.InterfaceByName(interfaceName) 68 | if err != nil { 69 | return "", err 70 | } 71 | 72 | var publicIPAddress string 73 | ipAddresses, err := interfaceByname.Addrs() 74 | if err != nil { 75 | return "", err 76 | } 77 | for _, ipAddr := range ipAddresses { 78 | addr := ipAddr.(*net.IPNet) 79 | if !addr.IP.IsLoopback() { 80 | publicIPAddress = addr.IP.String() 81 | break 82 | } 83 | } 84 | 85 | if len(strings.TrimSpace(publicIPAddress)) > 0 { 86 | fmt.Println(publicIPAddress) 87 | return publicIPAddress, nil 88 | } 89 | return "", errors.New("Could not find a public IP address") 90 | } 91 | -------------------------------------------------------------------------------- /internal/miniature/http.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/go-chi/chi" 10 | "github.com/go-chi/chi/middleware" 11 | ) 12 | 13 | // HTTPServer ... 14 | type HTTPServer struct { 15 | server *Server 16 | } 17 | 18 | type Stats struct { 19 | TimeElapsed string `json:"TimeElapsed"` 20 | GatewayInterfaceBytesIn int `json:"GatewayInterfaceBytesIn"` 21 | UDPTunnelBytesIn int `json:"UDPTunnelBytesIn"` 22 | UDPTunnelBytesOut int `json:"UDPTunnelBytesOut"` 23 | TotalBytesCompressed int `json:"TotalBytesCompressed"` 24 | ConnectionsIn int `json:"ConnectionsIn"` 25 | ConnectionsOut int `json:"ConnectionsOut"` 26 | Peers int `json:"Peers"` 27 | AvailableSlots int `json:"AvailableSlots"` 28 | } 29 | 30 | func startHTTPServer(miniatureServer *Server) error { 31 | defer miniatureServer.waiter.Done() 32 | router := chi.NewRouter() 33 | router.Use(middleware.Recoverer) 34 | 35 | httpServer := new(HTTPServer) 36 | httpServer.server = miniatureServer 37 | 38 | router.Get("/stats", httpServer.handleStats) 39 | router.Post("/client", httpServer.createClientConfig) 40 | 41 | log.Println("Server started at 8080") 42 | err := http.ListenAndServe("127.0.0.1:8080", router) 43 | return err 44 | } 45 | 46 | func (httpServer *HTTPServer) handleStats(w http.ResponseWriter, r *http.Request) { 47 | if r.Method == "GET" { 48 | serverStats := new(Stats) 49 | serverStats.ConnectionsIn = httpServer.server.metrics.ConnectionsIn 50 | serverStats.ConnectionsOut = httpServer.server.metrics.ConnectionsOut 51 | serverStats.TotalBytesCompressed = (httpServer.server.metrics.UDPTunnelBytesOut - httpServer.server.metrics.TotalBytesCompressed) 52 | serverStats.UDPTunnelBytesOut = httpServer.server.metrics.UDPTunnelBytesOut 53 | serverStats.UDPTunnelBytesIn = httpServer.server.metrics.UDPTunnelBytesIn 54 | serverStats.GatewayInterfaceBytesIn = httpServer.server.metrics.GatewayInterfaceBytesIn 55 | serverStats.Peers = httpServer.server.connectionPool.ConnectedPeersCount() 56 | serverStats.AvailableSlots = httpServer.server.connectionPool.AvailableAddressesCount() 57 | timeStarted := time.Unix(0, httpServer.server.metrics.TimeStarted) 58 | serverStats.TimeElapsed = time.Since(timeStarted).String() 59 | jsonResponse, _ := json.Marshal(serverStats) 60 | w.Write(jsonResponse) 61 | } else { 62 | w.WriteHeader(http.StatusMethodNotAllowed) 63 | } 64 | } 65 | 66 | func (httpServer *HTTPServer) createClientConfig(w http.ResponseWriter, r *http.Request) { 67 | if r.Method == "POST" { 68 | clientConfig, err := httpServer.server.CreateClientConfig() 69 | if err != nil { 70 | w.WriteHeader(http.StatusInternalServerError) 71 | } 72 | w.Write([]byte(clientConfig)) 73 | } else { 74 | w.WriteHeader(http.StatusMethodNotAllowed) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /cmd/gui/ref.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "time" 7 | 8 | ui "github.com/gizak/termui/v3" 9 | "github.com/gizak/termui/v3/widgets" 10 | ) 11 | 12 | func maind() { 13 | if err := ui.Init(); err != nil { 14 | log.Fatalf("failed to initialize termui: %v", err) 15 | } 16 | defer ui.Close() 17 | 18 | sinFloat64 := (func() []float64 { 19 | n := 400 20 | data := make([]float64, n) 21 | for i := range data { 22 | data[i] = 1 + math.Sin(float64(i)/5) 23 | } 24 | return data 25 | })() 26 | 27 | sl := widgets.NewSparkline() 28 | sl.Data = sinFloat64[:100] 29 | sl.LineColor = ui.ColorCyan 30 | sl.TitleStyle.Fg = ui.ColorWhite 31 | 32 | slg := widgets.NewSparklineGroup(sl) 33 | slg.Title = "Sparkline" 34 | 35 | lc := widgets.NewPlot() 36 | lc.Title = "braille-mode Line Chart" 37 | lc.Data = append(lc.Data, sinFloat64) 38 | lc.AxesColor = ui.ColorWhite 39 | lc.LineColors[0] = ui.ColorYellow 40 | 41 | gs := make([]*widgets.Gauge, 3) 42 | for i := range gs { 43 | gs[i] = widgets.NewGauge() 44 | gs[i].Percent = i * 10 45 | gs[i].BarColor = ui.ColorRed 46 | } 47 | 48 | ls := widgets.NewList() 49 | ls.Rows = []string{ 50 | "[1] Downloading File 1", 51 | "", 52 | "", 53 | "", 54 | "[2] Downloading File 2", 55 | "", 56 | "", 57 | "", 58 | "[3] Uploading File 3", 59 | } 60 | ls.Border = false 61 | 62 | p := widgets.NewParagraph() 63 | p.Text = "<> This row has 3 columns\n<- Widgets can be stacked up like left side\n<- Stacked widgets are treated as a single widget" 64 | p.Title = "Demonstration" 65 | 66 | grid := ui.NewGrid() 67 | termWidth, termHeight := ui.TerminalDimensions() 68 | grid.SetRect(0, 0, termWidth, termHeight) 69 | 70 | grid.Set( 71 | ui.NewRow(1.0/2, 72 | ui.NewCol(1.0/2, slg), 73 | ui.NewCol(1.0/2, lc), 74 | ), 75 | ui.NewRow(1.0/2, 76 | ui.NewCol(1.0/4, ls), 77 | ui.NewCol(1.0/4, 78 | ui.NewRow(.9/3, gs[0]), 79 | ui.NewRow(.9/3, gs[1]), 80 | ui.NewRow(1.2/3, gs[2]), 81 | ), 82 | ui.NewCol(1.0/2, p), 83 | ), 84 | ) 85 | 86 | ui.Render(grid) 87 | 88 | tickerCount := 1 89 | uiEvents := ui.PollEvents() 90 | ticker := time.NewTicker(time.Second).C 91 | for { 92 | select { 93 | case e := <-uiEvents: 94 | switch e.ID { 95 | case "q", "": 96 | return 97 | case "": 98 | payload := e.Payload.(ui.Resize) 99 | grid.SetRect(0, 0, payload.Width, payload.Height) 100 | ui.Clear() 101 | ui.Render(grid) 102 | } 103 | case <-ticker: 104 | if tickerCount == 100 { 105 | return 106 | } 107 | for _, g := range gs { 108 | g.Percent = (g.Percent + 3) % 100 109 | } 110 | slg.Sparklines[0].Data = sinFloat64[tickerCount : tickerCount+100] 111 | lc.Data[0] = sinFloat64[2*tickerCount:] 112 | ui.Render(grid) 113 | tickerCount++ 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /internal/miniature/pool.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // Pool is a pool of IP addresses 11 | type Pool struct { 12 | Peers map[string]*Peer 13 | Reserve []string 14 | NetworkAddress string 15 | Mutex *sync.Mutex 16 | peerTimeOut float64 17 | } 18 | 19 | // InitNodePool creates an empty nodepool and populates it with IP addresses 20 | func InitNodePool(IPAddr string, network net.IPNet) *Pool { 21 | ipaddr := net.ParseIP(IPAddr) 22 | nodePool := new(Pool) 23 | nodePool.Mutex = new(sync.Mutex) 24 | nodePool.Peers = make(map[string]*Peer) 25 | nodePool.peerTimeOut = float64(300) 26 | log.Println(nodePool.peerTimeOut) 27 | 28 | for addr := ipaddr.Mask(network.Mask); network.Contains(ipaddr); constructIP(ipaddr) { 29 | if !ipaddr.Equal(addr) { 30 | nodePool.Reserve = append(nodePool.Reserve, ipaddr.String()) 31 | } else { 32 | log.Printf("Skipping the interface id %s \n", addr.String()) 33 | } 34 | } 35 | // pop out the first and last values from the generated IP pool 36 | // first value is the subnet address 37 | // last address is the broadcast address 38 | nodePool.NetworkAddress = nodePool.Reserve[0] 39 | nodePool.Reserve = nodePool.Reserve[1 : len(nodePool.Reserve)-1] 40 | 41 | // start a timer to periodically remove dead peers and add them back to the reserve 42 | ticker := time.NewTicker(500 * time.Millisecond) 43 | done := make(chan bool) 44 | go func() { 45 | for { 46 | select { 47 | case <-done: 48 | return 49 | case <-ticker.C: 50 | nodePool.cleanupExpired() 51 | } 52 | } 53 | }() 54 | return nodePool 55 | } 56 | 57 | func (pool *Pool) cleanupExpired() { 58 | pool.Mutex.Lock() 59 | 60 | for k, v := range pool.Peers { 61 | timeSinceHeartBeat := time.Since(v.LastHeartbeat) 62 | elapsedTime := timeSinceHeartBeat.Seconds() 63 | if elapsedTime > pool.peerTimeOut { 64 | log.Printf("%s has been quiet for %g. Removing after %g timeout \n", k, elapsedTime, pool.peerTimeOut) 65 | v.Addr = nil 66 | v = nil 67 | delete(pool.Peers, k) 68 | pool.Reserve = append(pool.Reserve, k) 69 | } 70 | } 71 | pool.Mutex.Unlock() 72 | } 73 | 74 | // GetPeer returns a peer corresponding to 75 | func (pool *Pool) GetPeer(ipAddress string) *Peer { 76 | pool.Mutex.Lock() 77 | peer := pool.Peers[ipAddress] 78 | pool.Mutex.Unlock() 79 | return peer 80 | } 81 | 82 | // Update updates the peer 83 | func (pool *Pool) Update(ipAddress string, peer Peer) { 84 | pool.Mutex.Lock() 85 | oldPeer := pool.Peers[ipAddress] 86 | oldPeer.LastHeartbeat = peer.LastHeartbeat 87 | pool.Mutex.Unlock() 88 | } 89 | 90 | // NewPeer assigns an IP address to a peer and records the UDP address to be used to contact 91 | // the client 92 | func (pool *Pool) NewPeer() *Peer { 93 | var peer *Peer 94 | peer = nil 95 | pool.Mutex.Lock() 96 | if len(pool.Reserve) > 0 { 97 | peer = new(Peer) 98 | ipAddress := pool.Reserve[0] 99 | peer.IP = ipAddress 100 | peer.LastHeartbeat = time.Now() 101 | pool.Peers[ipAddress] = peer 102 | pool.Reserve = append(pool.Reserve[:0], pool.Reserve[1:]...) 103 | } 104 | pool.Mutex.Unlock() 105 | return peer 106 | } 107 | 108 | // ConnectedPeersCount is the number of peers connected to the server 109 | func (pool *Pool) ConnectedPeersCount() int { 110 | return len(pool.Peers) 111 | } 112 | 113 | // AvailableAddressesCount is the number of available ip addresses to be leased to peers 114 | func (pool *Pool) AvailableAddressesCount() int { 115 | return len(pool.Reserve) 116 | } 117 | 118 | func constructIP(ip net.IP) { 119 | for octet := len(ip) - 1; octet >= 0; octet-- { 120 | ip[octet]++ 121 | if ip[octet] > 0 { 122 | break 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /cmd/gui/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/devgenie/miniature/internal/miniature" 12 | ui "github.com/gizak/termui/v3" 13 | "github.com/gizak/termui/v3/widgets" 14 | ) 15 | 16 | func main() { 17 | if err := ui.Init(); err != nil { 18 | log.Fatal("Failed to initialize UI:", err) 19 | } 20 | defer ui.Close() 21 | termWidth, termHeight := ui.TerminalDimensions() 22 | title := widgets.NewParagraph() 23 | title.Text = "Welcome to Miniatureby DevGenie, press q to quit" 24 | title.TextStyle.Modifier = ui.ModifierBold 25 | title.WrapText = true 26 | title.TextStyle.Fg = ui.ColorGreen 27 | title.BorderStyle.Fg = ui.ColorCyan 28 | title.PaddingBottom = 1 29 | title.PaddingTop = 1 30 | title.PaddingRight = 1 31 | title.PaddingLeft = 1 32 | 33 | 34 | systemStats := widgets.NewList() 35 | systemStats.BorderStyle.Fg = ui.ColorCyan 36 | systemStats.Title = "System" 37 | systemStats.TitleStyle.Fg = ui.ColorGreen 38 | 39 | peerStats := widgets.NewList() 40 | peerStats.BorderStyle.Fg = ui.ColorCyan 41 | peerStats.Title = "Peers" 42 | peerStats.TitleStyle.Fg = ui.ColorGreen 43 | 44 | networkStats := widgets.NewList() 45 | networkStats.BorderStyle.Fg = ui.ColorCyan 46 | networkStats.Title = "Network" 47 | networkStats.TitleStyle.Fg = ui.ColorGreen 48 | 49 | networkData := widgets.NewSparkline() 50 | networkData.Title = "Peers connected: 0" 51 | networkData.LineColor = ui.ColorCyan 52 | networkData.Data = make([]float64, 1) 53 | networkData.TitleStyle.Modifier = ui.ModifierBold 54 | networkData.TitleStyle.Fg = ui.ColorGreen 55 | 56 | sparklineGroup := widgets.NewSparklineGroup(networkData) 57 | 58 | grid := ui.NewGrid() 59 | grid.SetRect(0, 0, termWidth, termHeight) 60 | 61 | grid.Set( 62 | ui.NewRow(0.4/6,title), 63 | ui.NewRow(5.6/6, 64 | ui.NewCol(1.0/4, 65 | ui.NewRow(2.0/6,systemStats), 66 | ui.NewRow(2.0/6,peerStats), 67 | ui.NewRow(2.0/6,networkStats), 68 | ), 69 | ui.NewCol(3.0/4, sparklineGroup), 70 | ), 71 | ) 72 | 73 | ui.Render(grid) 74 | tickerCount := 1 75 | uiEvents := ui.PollEvents() 76 | ticker := time.NewTicker(time.Second).C 77 | for { 78 | select { 79 | case e := <-uiEvents: 80 | switch e.ID { 81 | case "q", "": 82 | return 83 | case "": 84 | payload := e.Payload.(ui.Resize) 85 | grid.SetRect(0, 0, payload.Width, payload.Height) 86 | ui.Clear() 87 | ui.Render(grid) 88 | 89 | } 90 | case <-ticker: 91 | usageStats := callStats() 92 | generalStatsRows := []string{fmt.Sprintf("Available: %d", usageStats.AvailableSlots), 93 | fmt.Sprintf("Total: %d", usageStats.AvailableSlots), 94 | fmt.Sprintf("Connected: %d", usageStats.Peers), 95 | fmt.Sprintf("Bytes in: %d", usageStats.ConnectionsIn), 96 | fmt.Sprintf("Bytes out: %d", usageStats.ConnectionsOut), 97 | } 98 | 99 | systemStatsRows := []string{fmt.Sprintf("Running time: %s", usageStats.TimeElapsed), 100 | } 101 | networkData.Data = append(networkData.Data, float64(usageStats.Peers)) 102 | peerStats.Rows = generalStatsRows 103 | systemStats.Rows = systemStatsRows 104 | ui.Render(grid) 105 | tickerCount++ 106 | networkData.Title = fmt.Sprintf("Peers Connected: %d %s", tickerCount, termWidth) 107 | } 108 | } 109 | } 110 | 111 | func callStats() miniature.Stats { 112 | client := &http.Client{} 113 | req, err := http.NewRequest("GET", "http://localhost:8080/stats", nil) 114 | if err != nil { 115 | fmt.Print(err.Error()) 116 | } 117 | req.Header.Add("Accept", "application/json") 118 | req.Header.Add("Content-Type", "application/json") 119 | resp, err := client.Do(req) 120 | if err != nil { 121 | fmt.Print(err.Error()) 122 | } 123 | defer resp.Body.Close() 124 | bodyBytes, err := ioutil.ReadAll(resp.Body) 125 | if err != nil { 126 | fmt.Print(err.Error()) 127 | } 128 | var responseObject miniature.Stats 129 | json.Unmarshal(bodyBytes, &responseObject) 130 | return responseObject 131 | } -------------------------------------------------------------------------------- /internal/cryptography/ca.go: -------------------------------------------------------------------------------- 1 | package cryptography 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "errors" 11 | "math" 12 | "math/big" 13 | mathrand "math/rand" 14 | "net" 15 | "time" 16 | ) 17 | 18 | // Cert represents a certificate details 19 | type Cert struct { 20 | IsCA bool 21 | Country string 22 | Organization string 23 | OrganizationalUnit string 24 | Locality string 25 | Province string 26 | StreetAddress string 27 | PostalCode string 28 | CommonName string 29 | IPAddress string 30 | ExpiryDate string 31 | } 32 | 33 | // GenerateTemplate creates a certificate template 34 | func (cert *Cert) GenerateTemplate(privateKey *rsa.PrivateKey) (certificateTemplate *x509.Certificate) { 35 | mathrand.Seed(time.Now().UnixNano()) 36 | randomInteger := mathrand.Intn(math.MaxInt64) 37 | randomInteger64 := int64(randomInteger) 38 | subjectKeyID := HashBigInt(privateKey.N) 39 | 40 | ipAddress := net.ParseIP(cert.IPAddress) 41 | 42 | template := &x509.Certificate{ 43 | IsCA: cert.IsCA, 44 | SubjectKeyId: subjectKeyID, 45 | SerialNumber: big.NewInt(randomInteger64), 46 | Subject: pkix.Name{ 47 | Country: []string{cert.Country}, 48 | Organization: []string{cert.Organization}, 49 | OrganizationalUnit: []string{cert.OrganizationalUnit}, 50 | Locality: []string{cert.Locality}, 51 | Province: []string{cert.Province}, 52 | StreetAddress: []string{cert.StreetAddress}, 53 | PostalCode: []string{cert.PostalCode}, 54 | CommonName: cert.CommonName, 55 | }, 56 | NotBefore: time.Now(), 57 | NotAfter: time.Now().AddDate(5, 5, 5), 58 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 59 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 60 | BasicConstraintsValid: true, 61 | IPAddresses: []net.IP{ipAddress}, 62 | } 63 | 64 | return template 65 | } 66 | 67 | // GenerateCA generates a certificate authority 68 | func (cert *Cert) GenerateCA() (privatekey *rsa.PrivateKey, publickey *rsa.PublicKey, certificate []byte, err error) { 69 | privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 70 | if err != nil { 71 | return nil, nil, nil, err 72 | } 73 | 74 | publicKey := &privateKey.PublicKey 75 | caTemplate := cert.GenerateTemplate(privateKey) 76 | caCert, err := cert.generateCert(caTemplate, caTemplate, privateKey, publicKey) 77 | if err != nil { 78 | return nil, nil, nil, err 79 | } 80 | 81 | return privateKey, publicKey, caCert, nil 82 | } 83 | 84 | // GenerateClientCertificate generates client certificate from a certificate template, parent template and private key 85 | func (cert *Cert) GenerateClientCertificate(caTemplate *x509.Certificate, parentTemplate *x509.Certificate, caPrivateKey *rsa.PrivateKey) (privatekey *rsa.PrivateKey, publickey *rsa.PublicKey, certificate []byte, err error) { 86 | privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 87 | if err != nil { 88 | return nil, nil, nil, err 89 | } 90 | 91 | publicKey := &privateKey.PublicKey 92 | clientCert, err := cert.generateCert(caTemplate, parentTemplate, caPrivateKey, publicKey) 93 | if err != nil { 94 | return nil, nil, nil, err 95 | } 96 | 97 | return privateKey, publicKey, clientCert, nil 98 | } 99 | 100 | func (cert *Cert) generateCert(caTemplate *x509.Certificate, parentTemplate *x509.Certificate, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey) (certificate []byte, err error) { 101 | certBody, err := x509.CreateCertificate(rand.Reader, caTemplate, parentTemplate, publicKey, privateKey) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return certBody, nil 106 | } 107 | 108 | // VerifyCertificate recieves a certificate and veriufies it against the root certificate 109 | // returning an error on failure and nil on success 110 | func VerifyCertificate(rootPEM []byte, certPEM []byte) error { 111 | roots, _ := x509.SystemCertPool() 112 | if roots == nil { 113 | roots = x509.NewCertPool() 114 | } 115 | ok := roots.AppendCertsFromPEM(rootPEM) 116 | if !ok { 117 | return errors.New("Failed to parse root certificate") 118 | } 119 | 120 | certBlock, _ := pem.Decode(certPEM) 121 | if certBlock == nil { 122 | return errors.New("Failed to parse certificate PEM ") 123 | } 124 | 125 | cert, err := x509.ParseCertificate(certBlock.Bytes) 126 | if err != nil { 127 | return errors.New("Failed to parse certificate with error: " + err.Error()) 128 | } 129 | 130 | opts := x509.VerifyOptions{ 131 | Roots: roots, 132 | Intermediates: x509.NewCertPool(), 133 | } 134 | _, err = cert.Verify(opts) 135 | if err != nil { 136 | return errors.New("Failed to verify certificate with error: " + err.Error()) 137 | } 138 | 139 | return nil 140 | } 141 | 142 | // HashBigInt creates a random serial number 143 | func HashBigInt(bigInt *big.Int) []byte { 144 | hash := sha1.New() 145 | _, err := hash.Write(bigInt.Bytes()) 146 | if err != nil { 147 | return nil 148 | } 149 | return hash.Sum(nil) 150 | } 151 | -------------------------------------------------------------------------------- /internal/common/ifce.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "errors" 7 | "os/exec" 8 | 9 | "fmt" 10 | "log" 11 | "net" 12 | "os" 13 | "runtime" 14 | "strings" 15 | 16 | "github.com/songgao/water" 17 | ) 18 | 19 | // Tun represents a Tun interface 20 | type Tun struct { 21 | Ifce *water.Interface 22 | Name string 23 | IP net.IP 24 | Remote net.IP 25 | SubnetMask net.IPMask 26 | Mtu int 27 | } 28 | 29 | // Route represens an ip route 30 | type Route struct { 31 | Destination string 32 | NextHop string 33 | GWInterface string 34 | } 35 | 36 | // NewInterface creates a new Tun interface 37 | func NewInterface() (*Tun, error) { 38 | config := water.Config{ 39 | DeviceType: water.TUN, 40 | } 41 | 42 | tunInterface, err := water.New(config) 43 | if err != nil { 44 | return nil, err 45 | } 46 | ifce := new(Tun) 47 | ifce.Ifce = tunInterface 48 | return ifce, nil 49 | } 50 | 51 | // Configure configures the Tun interface 52 | func (tun *Tun) Configure(ifceAddr net.IP, remote net.IP, mtu int) error { 53 | ipaddr := ifceAddr.String() 54 | if runtime.GOOS == "linux" { 55 | command := fmt.Sprintf("link set dev %s mtu %d", tun.Ifce.Name(), mtu) 56 | err := RunCommand("ip", command) 57 | if err != nil { 58 | log.Fatalf("Error configuring interface %s, message: %s \n", tun.Ifce.Name(), err) 59 | return err 60 | } 61 | 62 | tun.Mtu = mtu 63 | command = fmt.Sprintf("add add dev %s local %s peer %s", tun.Ifce.Name(), ipaddr, remote.String()) 64 | err = RunCommand("ip", command) 65 | if err != nil { 66 | log.Fatalf("Error configuring interface %s, message: %s \n", tun.Ifce.Name(), err) 67 | return err 68 | } 69 | 70 | command = fmt.Sprintf("link set dev %s up", tun.Ifce.Name()) 71 | err = RunCommand("ip", command) 72 | if err != nil { 73 | log.Printf("Error configuring interface %s, message: %s \n", tun.Ifce.Name(), err) 74 | return err 75 | } 76 | } else if runtime.GOOS == "darwin" { 77 | command := fmt.Sprintf("%s inet %s %s up", tun.Ifce.Name(), ipaddr, remote.String()) 78 | fmt.Println(command) 79 | if err := RunCommand("ifconfig", command); err != nil { 80 | log.Fatalln("Unable to setup interface:", err) 81 | return err 82 | } 83 | } 84 | 85 | tun.IP = ifceAddr 86 | return nil 87 | } 88 | 89 | // GetDefaultGateway returns the default gateway on the host 90 | func GetDefaultGateway() (ifaceName string, gateway string, err error) { 91 | if runtime.GOOS == "linux" { 92 | routeFile, err := os.Open("/proc/net/route") 93 | if err != nil { 94 | log.Println("Error reading /proc/net/route") 95 | return "", "", err 96 | } 97 | defer routeFile.Close() 98 | 99 | scanner := bufio.NewScanner(routeFile) 100 | 101 | lastline := 0 102 | for scanner.Scan() { 103 | lastline++ 104 | if lastline == 2 { 105 | currentLine := scanner.Text() 106 | splitEntries := strings.Split(currentLine, "\t") 107 | ifaceName = splitEntries[0] 108 | gateway = splitEntries[2] 109 | break 110 | } 111 | } 112 | 113 | // reverse gateway address 114 | decodedGateway, err := hex.DecodeString(gateway) 115 | 116 | if len(decodedGateway) != net.IPv4len { 117 | log.Println("Error : This is not a valid IP address") 118 | return "", "", fmt.Errorf("Invalid IPv4 address") 119 | } 120 | ipAddr := net.IP([]byte{decodedGateway[3], decodedGateway[2], decodedGateway[1], decodedGateway[0]}) 121 | gateway = ipAddr.String() 122 | return ifaceName, gateway, nil 123 | } else if runtime.GOOS == "darwin" { 124 | //route get default 125 | routeCmd := exec.Command("/sbin/route", "-n", "get", "0.0.0.0") 126 | output, err := routeCmd.CombinedOutput() 127 | if err != nil { 128 | return ifaceName, gateway, err 129 | } 130 | 131 | lines := strings.Split(string(output), "\n") 132 | var iface, ip string 133 | for _, line := range lines { 134 | fields := strings.Fields(line) 135 | if len(fields) >= 2 && fields[0] == "gateway:" { 136 | ip = fields[1] 137 | } 138 | if len(fields) >= 2 && fields[0] == "interface:" { 139 | iface = fields[1] 140 | } 141 | } 142 | if len(iface) > 0 && len(ip) > 0 { 143 | return iface, ip, nil 144 | } 145 | return iface, ip, nil 146 | } 147 | return " ", "", nil 148 | } 149 | 150 | // AddRoute ... 151 | func AddRoute(route Route) error { 152 | switch runtime.GOOS { 153 | case "linux": 154 | command := fmt.Sprintf("route add %s via %s", route.Destination, route.NextHop) 155 | err := RunCommand("ip", command) 156 | return err 157 | case "darwin": 158 | command := fmt.Sprintf("-n add -net %s %s", route.Destination, route.NextHop) 159 | err := RunCommand("route", command) 160 | return err 161 | default: 162 | err := errors.New("Operating system not supported") 163 | return err 164 | } 165 | } 166 | 167 | // DeleteRoute deletes a route to a destination network 168 | func DeleteRoute(route string) error { 169 | switch runtime.GOOS { 170 | case "linux": 171 | command := fmt.Sprintf("route delete %s", route) 172 | err := RunCommand("ip", command) 173 | return err 174 | case "darwin": 175 | command := fmt.Sprintf("delete %s", route) 176 | err := RunCommand("route", command) 177 | return err 178 | default: 179 | err := errors.New("Operating system not supported") 180 | return err 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Miniature ## 2 | 3 | Miniature is an vpn server and client written in go. Miniature uses songao's water library under the hood. 4 | 5 | **Setup** 6 | 7 | ``` 8 | git clone https://github.com/devGenie/miniature 9 | 10 | cd miniature 11 | 12 | export GO111MODULE=on 13 | 14 | go mod init github.com/devgenie/miniature 15 | 16 | ``` 17 | 18 | To build and run the VPN server 19 | 20 | ``` 21 | go build ./cmd/server 22 | 23 | ./server run -config=/etc/miniature/config.yml 24 | ``` 25 | 26 | `-config` is the path to the VPN server's configuration file, the VPN server's configuration file looks like; 27 | 28 | ``` 29 | certificatesdirectory: /etc/miniature/certs 30 | 31 | network: 10.2.0.0/24 32 | 33 | listeningport: 4321 34 | 35 | publicip: 172.18.0.2 36 | 37 | dnsresolvers: 38 | - 1.1.1.1 39 | ``` 40 | 41 | You can also start the server using `./server run`. This will use the default path to the configuration file (`/etc/miniature/config.yml`) 42 | 43 | To create a client configuration file 44 | `./server newclient --config=/etc/miniature/config.yml` 45 | 46 | `-config` is the path to the server configuration file, you can also create the client configuration file using `./server newclient`, this uses the server's default path to the configuration file, in this case which is `/etc/miniature/config.yml` 47 | 48 | 49 | To build and run the VPN client: 50 | 51 | ``` 52 | go build ./cmd/client 53 | 54 | ./client -config=/etc/miniature/config.yml 55 | ``` 56 | 57 | `-config` is used to specify the path to the client configuration file. If this command line switch is not provided, the client will use the default path which is `/etc/miniature/config.yml`. The config file looks like an example below: 58 | 59 | ``` 60 | serveraddress: 172.2.2.2 61 | listeningport: 4321 62 | certificate: | 63 | -----BEGIN CERTIFICATE----- 64 | MIIDwDCCAqigAwIBAgIIEt8f19aYOP4wDQYJKoZIhvcNAQELBQAwcDEPMA0GA1UE 65 | BhMGVWdhbmRhMQkwBwYDVQQIEwAxCTAHBgNVBAcTADEJMAcGA1UECRMAMQkwBwYD 66 | VQQREwAxEjAQBgNVBAoTCUdlbmllTGFiczEJMAcGA1UECxMAMRIwEAYDVQQDEwlH 67 | ZW5pZUxhYnMwHhcNMTkwNzAyMjMyMjAwWhcNMjQxMjA3MjMyMjAwWjBwMQ8wDQYD 68 | VQQGEwZVZ2FuZGExCTAHBgNVBAgTADEJMAcGA1UEBxMAMQkwBwYDVQQJEwAxCTAH 69 | BgNVBBETADESMBAGA1UEChMJR2VuaWVMYWJzMQkwBwYDVQQLEwAxEjAQBgNVBAMT 70 | CUdlbmllTGFiczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKsjiB4T 71 | IZb5muzLVRCWf3Z1f7kub4l9/psyLL6FyOfdjvdbOP+fc1XxFd40G2fROFCAiZOw 72 | 2SFg/HLxDJt/RqX38e40Uto+RjUAj67k+B59A4JIP52+tqv4N9J1Q1IoQEKotQIB 73 | Ej6Ug5evKp2cQ7Ui731IvGzTwuacYoU6UkU+1rfw4L0SdAC1hjQ6S11WzitcRNTu 74 | aCx6tj+F+C/bvTwcneHmJjHbOT135jWyjLKSZzv1zNP3C8fDdj6/auTsCW7kSIyt 75 | G8e3c0/tpmP6YG5TeYyVOysPMnfcqJPDnJPrWIztOxYmhv1etPXR0wxZp83i6Rhe 76 | jHqyi9A2tPQiL0kCAwEAAaNeMFwwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQG 77 | CCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQAxYvP 78 | p4gWbBUK7RxG6dTdQ922qTANBgkqhkiG9w0BAQsFAAOCAQEArSBO+rMyoAWkCiBu 79 | 6RGdYy80KoCVKF3wNL8fEiXvMXZcnlyxF1GGyKTEWTVlelzMvauvNdhbtDEWKGqt 80 | UD3euOV+S6+/JNbHLIOlcj4N4pZRlSw8iTf9MPb7dGu/h4StXbIwSFgkVwyeiHWD 81 | vFaP1djY/6Ng1QDfaGN1fe/iFACvEpJAdiizq16eee3/y2ywFzZEqtk5mNoXSvHI 82 | MS9dGE1YxIYJtPeqw2ZsTtRIa+1XsCiUp0nqRya9bK1eJFmO7oYFKZnSQ89JnNeN 83 | 5eVwLDYsbrfU14kWHf9e2S3LXYqGROVSyIgVsMSyjcZJ1ipLFl9xqg3AY5O0yHRq 84 | y4ylDg== 85 | -----END CERTIFICATE----- 86 | privatekey: | 87 | -----BEGIN RSA PRIVATE KEY----- 88 | MIIEpAIBAAKCAQEAqyOIHhMhlvma7MtVEJZ/dnV/uS5viX3+mzIsvoXI592O91s4 89 | /59zVfEV3jQbZ9E4UICJk7DZIWD8cvEMm39Gpffx7jRS2j5GNQCPruT4Hn0Dgkg/ 90 | nb62q/g30nVDUihAQqi1AgESPpSDl68qnZxDtSLvfUi8bNPC5pxihTpSRT7Wt/Dg 91 | vRJ0ALWGNDpLXVbOK1xE1O5oLHq2P4X4L9u9PByd4eYmMds5PXfmNbKMspJnO/XM 92 | 0/cLx8N2Pr9q5OwJbuRIjK0bx7dzT+2mY/pgblN5jJU7Kw8yd9yok8Ock+tYjO07 93 | FiaG/V609dHTDFmnzeLpGF6MerKL0Da09CIvSQIDAQABAoIBAEJBM0VRasOkRpI9 94 | 9eTCHv6hZp0umQfFu3gh6Kip6qm5YMvqiRqNhH1VJH4t9h4vJXolCR4gbS86+QEW 95 | ySa6E4PVhdgOcbUEPvHuEbJH+rby9xTNG7PaTaYuJo5Xz4RTCO3Fmq339DQ+EuP6 96 | cKksAhpyN/1s12XaZa4aBRpHBerAUq8N01rYWgJ1uH/7ILKtaZMg/tBbUHqqPd0G 97 | oWcub/zbAmGmU3MqZMtY8VG1DsDQ8nlGsFdJnyHX9NFisPOiP3ytk1kBuIHN3yQT 98 | S3AG1FWu/PkYqZtho+dl4MI/osMmRoZLY62zDMASByeqQ5bO619UQ5TPl0XXDnkz 99 | tuplecECgYEAwwdym/yaykcjv34A8sgkSvPMr4TCsUkPZNAoijZWjuXlD9iMDKhK 100 | ITFiZtOHxdS9yNmWr7KUB6t3Amw7BAVRUU+9prYGi3069BVnMZP5PGa0xM8/UgqW 101 | KtrljHDydWGYq/9vNCFNDVg5CW7uBZEc2jZtL+fuKtbLcMpoG/5aWo0CgYEA4KQZ 102 | BdMd0HEL1W0EBjs2/WuElfxSnMvSZAxRYMJFDudma3tw5EnHvbSG3E32oLU7YYpG 103 | emvpL9NB2fVkiN99ylWqciXdjuxsv0y+POvpCuFyXVH2g5T/g7+TYU+SV/aKRcBd 104 | wpOYl8MbLzPlgVpHZUe2l48XGv8sHfdgaj4udq0CgYARm7GITdU34BZlKp4xTUqh 105 | jcN0MVtWoE8Ifha6688C1dTJinaSifsvZgMJX53JibyczrBhKpFc4+k5ycXGRiii 106 | W722uIZ8v5C8CtanTkHZZzh48HE6GgSW1+6TsHrjiC09kjFbFoqbYtS7ek15KTHe 107 | rb1L7ve83Gm/xDaEGIHV3QKBgQDax5bDMHhB8Ec5JgIcW4FT0GoBdQu0P2F5JPIA 108 | jVOaj00VctRg0WZh4LbTSm7e14KsnXHEeuJRPKtOrgqqrxcgfswQfcZJEwNaUFCa 109 | npuJiEXMky3FutAbLPJJfKinWKoUAqSOAxdC/ra0AxQLJbSQ9AXll2tGVKxPxwQ0 110 | lLjFxQKBgQCUkn1Y2yad4fLb+prcurtuIwBSqpXt/eX/SmT87b0G50VPR0vZQzNw 111 | v7tHAZear2HgMdM8s4c2h6Ye+hBDssEqg9TP6JrXcmXUOG8UST4w3PF8DPtJH/Vr 112 | bKLdmSN6GJJcT7lcwtXYNA6/ygkuMzySfBPLItkHQ+yPI9b2P8YKGA== 113 | -----END RSA PRIVATE KEY----- 114 | ``` 115 | 116 | To generate a config file like the one above, run `./server newclient --config=/etc/miniature/config.yml` 117 | 118 | **Note** 119 | 120 | At the moment, the VPN server runs on only linux, plans are to port it to windows sometime. At the moment, it is not possible to port it to OSX because of the limitations in configuring the tun interfaces 121 | 122 | The client has only been tested only on linux at the moment.Plans are to port it to both osx and windows in the future. 123 | 124 | **Note** 125 | 126 | Development has been done on Linux, if you don"t have a linux machine, you can use docker containers to run a dev environment. Right now, it is not possible to develop the server on OSX. This has not been tested on windows yet. 127 | 128 | The docker containers have to be run in privelege mode to make this work as expected. 129 | 130 | ``` 131 | docker network create miniature 132 | 133 | docker build -t miniature . 134 | 135 | docker run -dit --mount type=bind,source="$(pwd)",target=/miniature --privileged --name miniature-server --network miniature miniature 136 | 137 | //mount the current working directory to so that the changes made in you code editor are available inside the docker containers 138 | 139 | docker run -dit --mount type=bind,source="$(pwd)",target=/miniature --privileged --name miniature-client1 --network miniature miniature 140 | 141 | docker run -dit --mount type=bind,source="$(pwd)",target=/miniature --privileged --name miniature-client2 --network miniature miniature 142 | ``` 143 | 144 | **Todo** 145 | 146 | - [x] Encryption/ Decryption 147 | 148 | - [x] Authentication 149 | 150 | - [x] Compression using LZO 151 | 152 | - [x] Data Fragmentation/ Defragmentation 153 | 154 | - [x] DNS Forwarding -------------------------------------------------------------------------------- /internal/miniature/client.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "crypto/elliptic" 5 | "crypto/rand" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net" 12 | "os" 13 | "os/signal" 14 | "runtime" 15 | "strconv" 16 | "sync" 17 | 18 | "github.com/aead/ecdh" 19 | "github.com/devgenie/miniature/internal/common" 20 | utilities "github.com/devgenie/miniature/internal/common" 21 | codec "github.com/devgenie/miniature/internal/cryptography" 22 | "golang.org/x/net/ipv4" 23 | ) 24 | 25 | // DefaultGateway represents the default gateway of the client. 26 | type DefaultGateway struct { 27 | Interface string 28 | GatewayIP string 29 | } 30 | 31 | // Client represents a client connecting to the VPN server 32 | type Client struct { 33 | ifce *utilities.Tun 34 | serverAddr *net.UDPAddr 35 | conn *net.UDPConn 36 | waiter sync.WaitGroup 37 | config ClientConfig 38 | secret []byte 39 | resolveFile string 40 | defaultGateway DefaultGateway 41 | diconnectionCount int 42 | } 43 | 44 | // ClientConfig holds the client configuration loaded from the yml configuration file 45 | type ClientConfig struct { 46 | // Address of the VPN server 47 | ServerAddress string 48 | // Port which the server is listening at 49 | ListeningPort int 50 | // Client's public key issued by the server 51 | Certificate string 52 | // Client's private key issued by the server 53 | PrivateKey string 54 | // The servers public key 55 | CACert string 56 | } 57 | 58 | // Run starts the vpn client passing the ClientConfig as a parameter 59 | func (client *Client) Run(config ClientConfig) error { 60 | client.config = config 61 | if err := client.AuthenticateUser(); err != nil { 62 | log.Println(err) 63 | return err 64 | } 65 | 66 | if err := client.listen(client.config.ServerAddress, 67 | strconv.Itoa(client.config.ListeningPort)); err != nil { 68 | client.conn.Close() 69 | return err 70 | } 71 | defer client.conn.Close() 72 | 73 | client.diconnectionCount = 0 74 | c := make(chan os.Signal) 75 | signal.Notify(c, os.Interrupt) 76 | signal.Notify(c, os.Kill) 77 | go func() { 78 | select { 79 | case sig := <-c: 80 | log.Printf("Recieved %s signal, running some house keeping", sig) 81 | client.CleanUp() 82 | os.Exit(0) 83 | } 84 | }() 85 | 86 | client.waiter.Add(2) 87 | go client.handleIncomingConnections() 88 | go client.handleOutgoingConnections() 89 | client.waiter.Wait() 90 | 91 | return nil 92 | } 93 | 94 | // AuthenticateUser authenticates user with the vpn server 95 | func (client *Client) AuthenticateUser() error { 96 | caCert := []byte(client.config.CACert) 97 | certPool := x509.NewCertPool() 98 | ok := certPool.AppendCertsFromPEM(caCert) 99 | if !ok { 100 | log.Println("Failed to Parse certificate") 101 | } 102 | 103 | cert, err := tls.X509KeyPair([]byte(client.config.Certificate), []byte(client.config.PrivateKey)) 104 | if err != nil { 105 | panic(err) 106 | } 107 | conf := &tls.Config{ 108 | RootCAs: certPool, 109 | Certificates: []tls.Certificate{cert}, 110 | } 111 | 112 | serverAddress := fmt.Sprintf("%s:%d", client.config.ServerAddress, 443) 113 | conn, err := tls.Dial("tcp", serverAddress, conf) 114 | if err != nil { 115 | return err 116 | } 117 | defer conn.Close() 118 | 119 | p256 := ecdh.Generic(elliptic.P256()) 120 | clientPrivatekey, clientPublicKey, err := p256.GenerateKey(rand.Reader) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | err = p256.Check(clientPublicKey) 126 | if err != nil { 127 | log.Println("Client's public key is not on curve") 128 | } 129 | 130 | publicKeyBytes, err := utilities.Encode(clientPublicKey) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | packet := utilities.Packet{Flag: utilities.HANDSHAKE, Payload: publicKeyBytes} 136 | encodedData, err := utilities.Encode(&packet) 137 | if err != nil { 138 | return err 139 | } 140 | log.Println("Sending handshake to VPN server") 141 | _, err = conn.Write(encodedData) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | for { 147 | buf := make([]byte, 1380) 148 | _, err := conn.Read(buf) 149 | if err != nil { 150 | return err 151 | } 152 | packetReply := new(utilities.Packet) 153 | err = utilities.Decode(packetReply, buf) 154 | if err != nil { 155 | log.Println(err) 156 | return err 157 | } 158 | 159 | if packetReply.Flag == utilities.HANDSHAKE_ACCEPTED { 160 | log.Println("Server Handshake accepted, configuring client interfaces") 161 | conn.Close() 162 | handshakePacket := new(HandshakePacket) 163 | err := utilities.Decode(handshakePacket, packetReply.Payload) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | err = p256.Check(handshakePacket.ServerPublic) 169 | if err != nil { 170 | return err 171 | } 172 | client.secret = p256.ComputeSecret(clientPrivatekey, handshakePacket.ServerPublic) 173 | 174 | ifce, err := utilities.NewInterface() 175 | if err != nil { 176 | return err 177 | } 178 | 179 | if err = ifce.Configure(handshakePacket.ClientIP.IPAddr, 180 | handshakePacket.ClientIP.Gateway, 181 | 1500); err != nil { 182 | return err 183 | } 184 | 185 | log.Println("Client has been leased ", handshakePacket.ClientIP.IPAddr) 186 | client.ifce = ifce 187 | client.ifce.Mtu = 1500 188 | client.ifce.IP = handshakePacket.ClientIP.IPAddr 189 | gwIfce, gwIP, err := utilities.GetDefaultGateway() 190 | if err != nil { 191 | fmt.Println(err) 192 | return err 193 | } 194 | 195 | client.defaultGateway.GatewayIP = gwIP 196 | client.defaultGateway.Interface = gwIfce 197 | 198 | if runtime.GOOS == "linux" { 199 | err := SetUpLinuxClient(client.defaultGateway.Interface, client.defaultGateway.GatewayIP, client.ifce.Ifce.Name(), client.ifce.IP.String(), client.config.ServerAddress) 200 | if err != nil { 201 | return err 202 | } 203 | } else if runtime.GOOS == "darwin" { 204 | err := SetDarwinClient(client.defaultGateway.Interface, client.defaultGateway.GatewayIP, client.ifce.Ifce.Name(), client.ifce.IP.String(), client.config.ServerAddress, handshakePacket.DNSResolvers[0]) 205 | if err != nil { 206 | return err 207 | } 208 | } 209 | err = client.setUpDNS(handshakePacket.DNSResolvers) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | client.StartHeartBeat() 215 | break 216 | } 217 | } 218 | return nil 219 | } 220 | 221 | func (client *Client) listen(server, port string) error { 222 | serverAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%s", server, port)) 223 | if err != nil { 224 | log.Println("Failed to establish connection with the server") 225 | return err 226 | } 227 | client.serverAddr = serverAddr 228 | 229 | conn, err := net.DialUDP("udp4", nil, serverAddr) 230 | if err != nil { 231 | fmt.Println(err) 232 | log.Printf("Failed to connect to %s", server) 233 | return err 234 | } 235 | client.conn = conn 236 | return nil 237 | } 238 | 239 | func (client *Client) handleIncomingConnections() { 240 | defer client.waiter.Done() 241 | defer client.conn.Close() 242 | for { 243 | inputBytes := make([]byte, client.ifce.Mtu) 244 | if client.diconnectionCount < 3 { 245 | length, _, err := client.conn.ReadFromUDP(inputBytes) 246 | if err != nil || length == 0 { 247 | log.Printf("Error : %s \n", err) 248 | client.diconnectionCount++ 249 | continue 250 | } 251 | 252 | decompressedPacket, err := Decompress(inputBytes[:length]) 253 | if err != nil { 254 | log.Println("Error decompressing", length) 255 | log.Println(err) 256 | continue 257 | } 258 | 259 | flag := decompressedPacket[len(decompressedPacket)-1] 260 | decompressedPacket = decompressedPacket[:len(decompressedPacket)-1] 261 | decryptedPayload, err := codec.Decrypt(client.secret, decompressedPacket) 262 | if err != nil { 263 | log.Printf("Error decrypting data from the server \t Error : %s \n", err) 264 | continue 265 | } 266 | 267 | if flag == utilities.SESSION { 268 | go client.writeToIfce(decryptedPayload) 269 | } else { 270 | log.Println("Expected headers not found") 271 | } 272 | } else { 273 | if err := client.AuthenticateUser(); err != nil { 274 | log.Println(err) 275 | return 276 | } 277 | } 278 | } 279 | } 280 | 281 | func (client *Client) writeToIfce(packet []byte) { 282 | _, err := client.ifce.Ifce.Write(packet) 283 | if err != nil { 284 | fmt.Println(err) 285 | return 286 | } 287 | } 288 | 289 | func (client *Client) handleOutgoingConnections() { 290 | defer client.waiter.Done() 291 | log.Println("Handling outgoing connection") 292 | 293 | for { 294 | buffer := make([]byte, 1300) 295 | length, err := client.ifce.Ifce.Read(buffer) 296 | if err != nil { 297 | log.Println("Error reading interface:", err) 298 | continue 299 | } 300 | go func(data []byte, length int) { 301 | if length > -4 { 302 | _, err := ipv4.ParseHeader(data) 303 | if err != nil { 304 | log.Println("Error parsing header", err) 305 | return 306 | } 307 | 308 | encryptedData, err := codec.Encrypt(client.secret, buffer[:length]) 309 | if err != nil { 310 | log.Println("Error encrypting", err) 311 | return 312 | } 313 | 314 | clintIP := client.ifce.IP[len(client.ifce.IP)-4:] 315 | encryptedData = append(encryptedData, clintIP...) 316 | encryptedData = append(encryptedData, utilities.SESSION) 317 | compressedPacket, err := Compress(encryptedData) 318 | if err != nil { 319 | log.Println("Error compressing:", err) 320 | return 321 | } 322 | // log.Printf("Sending %d bytes to %s \n", len(compressedPacket), header.Dst) 323 | // log.Printf("Version %d, Protocol %d \n", header.Version, header.Protocol) 324 | 325 | _, err = client.conn.Write(compressedPacket) 326 | if err != nil { 327 | fmt.Println("Failed to write to tunnel", err) 328 | } 329 | } 330 | }(buffer[:length], length) 331 | } 332 | } 333 | 334 | // StartHeartBeat creates a cron that sends th clients heartbeat to the server 335 | func (client *Client) StartHeartBeat() { 336 | utilities.RunCron("Heartbeat", "0 0/1 * * * *", client.HeartBeat) 337 | } 338 | 339 | // HeartBeat sends the client's heartbeat to the server 340 | func (client *Client) HeartBeat() { 341 | peer := new(Peer) 342 | peer.IP = client.ifce.IP.String() 343 | encodedPeer, err := utilities.Encode(&peer) 344 | if err != nil { 345 | log.Printf("An error occured while encording peer data \t Error : %s \n", err) 346 | return 347 | } 348 | 349 | encryptedData, err := codec.Encrypt(client.secret, encodedPeer) 350 | if err != nil { 351 | log.Println(err) 352 | return 353 | } 354 | 355 | clintIP := client.ifce.IP[len(client.ifce.IP)-4:] 356 | encryptedData = append(encryptedData, clintIP...) 357 | encryptedData = append(encryptedData, utilities.HEARTBEAT) 358 | compressedPacket, err := Compress(encryptedData) 359 | if err != nil { 360 | log.Println("Error compressing", err) 361 | return 362 | } 363 | 364 | log.Printf("Sending pulse to server at %s \n", client.serverAddr.String()) 365 | 366 | _, err = client.conn.Write(compressedPacket) 367 | if err != nil { 368 | fmt.Println(err) 369 | return 370 | } 371 | } 372 | 373 | func (client *Client) setUpDNS(resolvers []string) error { 374 | oldResolveFileContents, err := ioutil.ReadFile("/etc/resolv.conf") 375 | if err != nil { 376 | return err 377 | } 378 | client.resolveFile = string(oldResolveFileContents) 379 | content := "# Generated by miniature \n" 380 | for _, resolver := range resolvers { 381 | content += fmt.Sprintf("nameserver %s\n", resolver) 382 | } 383 | return ioutil.WriteFile("/etc/resolv.conf", []byte(content), 0644) 384 | } 385 | 386 | // ResetDNS resets the resolv.conf file to the one before the vpn client was started 387 | func (client *Client) ResetDNS() error { 388 | err := ioutil.WriteFile("/etc/resolv.conf", []byte(client.resolveFile), 0644) 389 | if err != nil { 390 | log.Println("Failed to restore /etc/resolv.conf file, restore the file manually by copying the contents below and pasting them into /etc/resolve.conf file") 391 | fmt.Println(client.resolveFile) 392 | } 393 | return err 394 | } 395 | 396 | // CleanUp cleans up the client after a shutdown 397 | func (client *Client) CleanUp() { 398 | client.ResetDNS() 399 | 400 | if runtime.GOOS == "darwin" { 401 | command := fmt.Sprintf("-f %s", "/etc/pf.conf") 402 | err := utilities.RunCommand("pfctl", command) 403 | if err != nil { 404 | log.Println(err) 405 | } 406 | 407 | routes := []common.Route{ 408 | {Destination: "0.0.0.0/0", NextHop: client.defaultGateway.GatewayIP, GWInterface: client.defaultGateway.GatewayIP}, 409 | } 410 | for _, route := range routes { 411 | common.DeleteRoute(route.Destination) 412 | err := common.AddRoute(route) 413 | if err != nil { 414 | log.Println(err) 415 | } 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /internal/miniature/server.go: -------------------------------------------------------------------------------- 1 | package miniature 2 | 3 | import ( 4 | "bufio" 5 | "crypto" 6 | "crypto/elliptic" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/tls" 10 | "crypto/x509" 11 | "encoding/gob" 12 | "encoding/pem" 13 | "fmt" 14 | "io/ioutil" 15 | "log" 16 | "net" 17 | "os" 18 | "sync" 19 | "time" 20 | 21 | "golang.org/x/net/ipv4" 22 | "gopkg.in/yaml.v3" 23 | 24 | "github.com/aead/ecdh" 25 | utilities "github.com/devgenie/miniature/internal/common" 26 | "github.com/devgenie/miniature/internal/cryptography" 27 | codec "github.com/devgenie/miniature/internal/cryptography" 28 | ) 29 | 30 | // Peer represents a client connected to the VPN server 31 | type Peer struct { 32 | //IP address of a client connected to the ser ver 33 | IP string 34 | // UDP object to communicate back to the peer 35 | Addr *net.UDPAddr 36 | // Time since the last hearbeat was got from the client 37 | LastHeartbeat time.Time 38 | // Server's private key for the peer 39 | ServerSecret []byte 40 | } 41 | 42 | // HandshakePacket represents a handshake packet 43 | type HandshakePacket struct { 44 | // IP address to be assigned to the client 45 | ClientIP utilities.Addr 46 | // The public key of the server to be used for encryption 47 | ServerPublic crypto.PublicKey 48 | DNSResolvers []string 49 | } 50 | 51 | // Server represents attributes of the VPN server 52 | type Server struct { 53 | tunInterface *utilities.Tun 54 | gatewayIfce string 55 | network *net.IPNet 56 | socket *net.UDPConn 57 | Config ServerConfig 58 | connectionPool *Pool 59 | waiter sync.WaitGroup 60 | metrics *Metrics 61 | } 62 | 63 | // ServerConfig holds VPN server configurations 64 | // These configurations are read from a yaml file 65 | type ServerConfig struct { 66 | CertificatesDirectory string 67 | Network string 68 | ListeningPort int 69 | PublicIP string 70 | DNSResolvers []string 71 | Metadata struct { 72 | Country string `yaml:"Country"` 73 | Organization string `yaml:"Organization"` 74 | Unit string `yaml:"Unit"` 75 | Locality string `yaml:"Locality"` 76 | Province string `yaml:"Province"` 77 | StreetAddress string `yaml:"StreetAddress"` 78 | PostalCode string `yaml:"PostalCode"` 79 | CommonName string `yaml:"CommonName"` 80 | } 81 | } 82 | 83 | // Run starts the VPN server by passing a configuration object 84 | // The configuration object contains attributes needed to run the server 85 | func (server *Server) Run(config ServerConfig) { 86 | server.Config = config 87 | ifce, err := utilities.NewInterface() 88 | if err != nil { 89 | log.Print("Failed to create interface") 90 | return 91 | } 92 | 93 | _, network, err := net.ParseCIDR(config.Network) 94 | if err != nil { 95 | log.Println(err) 96 | log.Println("Failed to parse cidre") 97 | return 98 | } 99 | 100 | log.Printf("Generating IP address for %s network space \n", network) 101 | server.connectionPool = InitNodePool(network.IP.String(), *network) 102 | log.Printf("Generated %v ip addresses \n", server.connectionPool.AvailableAddressesCount()) 103 | 104 | ip := net.ParseIP(server.connectionPool.NetworkAddress) 105 | fmt.Println("TunIP", server.connectionPool.NetworkAddress) 106 | err = ifce.Configure(ip, ip, 1300) 107 | if err != nil { 108 | log.Printf("Error: %s \n", err) 109 | return 110 | } 111 | 112 | // route client traffic through tun interface 113 | command := fmt.Sprintf("route add %s dev %s", network.String(), ifce.Ifce.Name()) 114 | err = utilities.RunCommand("ip", command) 115 | if err != nil { 116 | return 117 | } 118 | 119 | gatewayIfce, _, err := utilities.GetDefaultGateway() 120 | if err != nil { 121 | return 122 | } 123 | 124 | command = fmt.Sprintf("-A FORWARD -i %s -o %s -m state --state RELATED,ESTABLISHED -j ACCEPT", ifce.Ifce.Name(), gatewayIfce) 125 | err = utilities.RunCommand("iptables", command) 126 | if err != nil { 127 | return 128 | } 129 | 130 | command = fmt.Sprintf("-A FORWARD -i %s -o %s -j ACCEPT", gatewayIfce, ifce.Ifce.Name()) 131 | err = utilities.RunCommand("iptables", command) 132 | if err != nil { 133 | return 134 | } 135 | 136 | command = fmt.Sprintf("-t nat -A POSTROUTING -o %s -j MASQUERADE", gatewayIfce) 137 | err = utilities.RunCommand("iptables", command) 138 | if err != nil { 139 | return 140 | } 141 | 142 | command = fmt.Sprintf("-t filter -I OUTPUT -o %s -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu", gatewayIfce) 143 | err = utilities.RunCommand("iptables", command) 144 | if err != nil { 145 | return 146 | } 147 | 148 | command = fmt.Sprintf("-t filter -I FORWARD -o %s -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu", gatewayIfce) 149 | err = utilities.RunCommand("iptables", command) 150 | if err != nil { 151 | return 152 | } 153 | 154 | server.tunInterface = ifce 155 | server.network = network 156 | server.gatewayIfce = gatewayIfce 157 | 158 | log.Printf("The CIDR of this network is %s \n", server.network) 159 | log.Printf("Tun interface assigned ip address %s \n", server.tunInterface.IP) 160 | 161 | certExists := true 162 | _, err = os.Stat(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "ca.crt")) 163 | if err != nil { 164 | certExists = false 165 | } 166 | 167 | _, err = os.Stat(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "privatekey.pem")) 168 | if err != nil { 169 | certExists = false 170 | } 171 | 172 | _, err = os.Stat(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "publickey.pem")) 173 | if err != nil { 174 | certExists = false 175 | } 176 | 177 | if !certExists { 178 | log.Println("Could not find one or more certificate files, creating fresh ones") 179 | err = server.createCA() 180 | if err != nil { 181 | log.Println("Failed to create certificate files") 182 | return 183 | } 184 | } else { 185 | serverCertsExist := true 186 | _, err = os.Stat(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "server.crt")) 187 | if err != nil { 188 | serverCertsExist = false 189 | } 190 | 191 | _, err = os.Stat(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "server.pem")) 192 | if err != nil { 193 | serverCertsExist = false 194 | } 195 | 196 | if !serverCertsExist { 197 | log.Println("Could not find one or more server certificate files, creating fresh ones") 198 | err = server.generateServerCerts() 199 | if err != nil { 200 | log.Println("Failed to create server certificate files") 201 | return 202 | } 203 | } 204 | } 205 | 206 | lstnAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("0.0.0.0:%v", server.Config.ListeningPort)) 207 | if err != nil { 208 | log.Fatalln("Unable to listen on UDP socket:", err) 209 | } 210 | 211 | lstnConn, err := net.ListenUDP("udp4", lstnAddr) 212 | if err != nil { 213 | log.Fatalln("Unable to listen on UDP socket111:", err) 214 | } 215 | 216 | server.socket = lstnConn 217 | defer lstnConn.Close() 218 | 219 | server.metrics = initMetrics() 220 | server.metrics.TimeStarted = time.Now().UnixNano() 221 | 222 | server.waiter.Add(5) 223 | go server.listenTLS() 224 | go server.listenAndServe() 225 | go server.readIfce() 226 | go startHTTPServer(server) 227 | 228 | server.waiter.Wait() 229 | } 230 | 231 | // CreateClientConfig creates a client configuration and parses it into yaml format 232 | // Upon successfully creating the client configuration yaml file, 233 | // a string representing the configuration and a nil error message is returned 234 | func (server *Server) CreateClientConfig() (yamlConfiguration string, errorMessage error) { 235 | // get default gateway and add the public IP address to configuration 236 | certPath := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "ca.crt") 237 | privatekeyPath := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "privatekey.pem") 238 | privateKeyBytes, certBytes, err := server.generateCerts(certPath, privatekeyPath) 239 | if err != nil { 240 | return "", err 241 | } 242 | 243 | caCertBytes, err := ioutil.ReadFile(certPath) 244 | if err != nil { 245 | return "", err 246 | } 247 | 248 | clientConfig := new(ClientConfig) 249 | clientConfig.ServerAddress = server.Config.PublicIP 250 | clientConfig.ListeningPort = server.Config.ListeningPort 251 | clientConfig.PrivateKey = string(privateKeyBytes) 252 | clientConfig.Certificate = string(certBytes) 253 | clientConfig.CACert = string(caCertBytes) 254 | 255 | configFile, err := yaml.Marshal(clientConfig) 256 | if err != nil { 257 | return "", err 258 | } 259 | return string(configFile), nil 260 | } 261 | 262 | func (server *Server) createCA() error { 263 | _, err := os.Stat(server.Config.CertificatesDirectory) 264 | if os.IsNotExist(err) { 265 | err = os.MkdirAll(server.Config.CertificatesDirectory, 0700) 266 | if err != nil { 267 | return err 268 | } 269 | } 270 | 271 | cert := new(cryptography.Cert) 272 | cert.IsCA = true 273 | cert.Country = server.Config.Metadata.Country 274 | cert.Organization = server.Config.Metadata.Organization 275 | cert.OrganizationalUnit = server.Config.Metadata.Unit 276 | cert.CommonName = server.Config.Metadata.CommonName 277 | cert.Locality = server.Config.Metadata.Locality 278 | cert.Province = server.Config.Metadata.Province 279 | cert.StreetAddress = server.Config.Metadata.StreetAddress 280 | cert.PostalCode = server.Config.Metadata.PostalCode 281 | cert.IPAddress = server.Config.PublicIP 282 | privateKey, publicKey, caCert, err := cert.GenerateCA() 283 | if err != nil { 284 | return err 285 | } 286 | 287 | // save server's certificate as a pem encoded crt file 288 | certificateFile, err := os.Create(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "ca.crt")) 289 | if err != nil { 290 | log.Println("Failed to save certificate file") 291 | return err 292 | } 293 | 294 | certificatePem := &pem.Block{ 295 | Type: "CERTIFICATE", 296 | Bytes: caCert, 297 | } 298 | err = pem.Encode(certificateFile, certificatePem) 299 | if err != nil { 300 | return err 301 | } 302 | 303 | // save public key as pem encoded file 304 | publicKeyFile, err := os.Create(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "publickey.pem")) 305 | if err != nil { 306 | log.Println("failed to save public key") 307 | return err 308 | } 309 | 310 | publicKeyANS1, err := x509.MarshalPKIXPublicKey(publicKey) 311 | if err != nil { 312 | log.Println(err) 313 | return err 314 | } 315 | publicKeyPem := &pem.Block{ 316 | Type: "RSA PUBLIC KEY", 317 | Bytes: publicKeyANS1, 318 | } 319 | 320 | err = pem.Encode(publicKeyFile, publicKeyPem) 321 | if err != nil { 322 | log.Println("Failed to save private key") 323 | return err 324 | } 325 | 326 | // save private key as pem encoded file 327 | privateKeyFile, err := os.Create(fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "privatekey.pem")) 328 | if err != nil { 329 | log.Println("Failed to save private key") 330 | return err 331 | } 332 | 333 | privateKeyPem := &pem.Block{ 334 | Type: "RSA PRIVATE KEY", 335 | Bytes: x509.MarshalPKCS1PrivateKey(privateKey), 336 | } 337 | err = pem.Encode(privateKeyFile, privateKeyPem) 338 | if err != nil { 339 | return err 340 | } 341 | 342 | log.Println("Successfully created certificate files") 343 | err = server.generateServerCerts() 344 | if err != nil { 345 | return err 346 | } 347 | return nil 348 | } 349 | 350 | func (server *Server) generateServerCerts() error { 351 | certPath := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "ca.crt") 352 | privatekeyPath := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "privatekey.pem") 353 | privateKeyBytes, certBytes, err := server.generateCerts(certPath, privatekeyPath) 354 | 355 | if err != nil { 356 | return err 357 | } 358 | 359 | serverCertFile := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "server.crt") 360 | serverPrivatekeyPath := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "server.pem") 361 | err = ioutil.WriteFile(serverCertFile, certBytes, 0644) 362 | if err != nil { 363 | log.Println("Failed to write Certificate file") 364 | return err 365 | } 366 | 367 | err = ioutil.WriteFile(serverPrivatekeyPath, privateKeyBytes, 0644) 368 | if err != nil { 369 | log.Println("Failed to write private key") 370 | return err 371 | } 372 | 373 | return nil 374 | } 375 | 376 | func (server *Server) generateCerts(certPath string, privatekeyPath string) (privateKey []byte, cert []byte, err error) { 377 | serverCertificate, err := tls.LoadX509KeyPair(certPath, privatekeyPath) 378 | if err != nil { 379 | return nil, nil, err 380 | } 381 | 382 | ca, err := x509.ParseCertificate(serverCertificate.Certificate[0]) 383 | if err != nil { 384 | return nil, nil, err 385 | } 386 | clientCertTemplate := *ca 387 | clientCertTemplate.IsCA = false 388 | 389 | privateKeyFile, err := os.Open(privatekeyPath) 390 | if err != nil { 391 | return nil, nil, err 392 | } 393 | defer privateKeyFile.Close() 394 | 395 | fileInfo, err := privateKeyFile.Stat() 396 | if err != nil { 397 | return nil, nil, err 398 | } 399 | filesize := fileInfo.Size() 400 | pemBytes := make([]byte, filesize) 401 | buffer := bufio.NewReader(privateKeyFile) 402 | _, err = buffer.Read(pemBytes) 403 | if err != nil { 404 | return nil, nil, err 405 | } 406 | 407 | pemdata, _ := pem.Decode([]byte(pemBytes)) 408 | 409 | caPrivateKey, err := x509.ParsePKCS1PrivateKey(pemdata.Bytes) 410 | if err != nil { 411 | return nil, nil, err 412 | } 413 | 414 | clientCert := new(cryptography.Cert) 415 | clientPrivateKey, _, cert, err := clientCert.GenerateClientCertificate(&clientCertTemplate, ca, caPrivateKey) 416 | if err != nil { 417 | log.Println(err) 418 | } 419 | 420 | certpem := &pem.Block{ 421 | Type: "CERTIFICATE", 422 | Bytes: cert, 423 | } 424 | certBytes := pem.EncodeToMemory(certpem) 425 | 426 | privateKeyPem := &pem.Block{ 427 | Type: "RSA PRIVATE KEY", 428 | Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey), 429 | } 430 | 431 | privateKeyBytes := pem.EncodeToMemory(privateKeyPem) 432 | return privateKeyBytes, certBytes, err 433 | } 434 | 435 | func (server *Server) listenTLS() { 436 | defer server.waiter.Done() 437 | caFile := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "ca.crt") 438 | crtFile := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "server.crt") 439 | privateKey := fmt.Sprintf("%s/%s", server.Config.CertificatesDirectory, "server.pem") 440 | 441 | certPem, err := ioutil.ReadFile(caFile) 442 | if err != nil { 443 | log.Println(err) 444 | } 445 | 446 | roots := x509.NewCertPool() 447 | ok := roots.AppendCertsFromPEM(certPem) 448 | if !ok { 449 | log.Println("Failed to parse certificate") 450 | } 451 | 452 | cert, err := tls.LoadX509KeyPair(crtFile, privateKey) 453 | if err != nil { 454 | log.Println(err) 455 | } 456 | 457 | tlsConfig := &tls.Config{ 458 | Certificates: []tls.Certificate{cert}, 459 | ClientAuth: tls.RequireAndVerifyClientCert, 460 | ClientCAs: roots, 461 | } 462 | 463 | listeningConn, err := tls.Listen("tcp", ":443", tlsConfig) 464 | if err != nil { 465 | log.Println(err) 466 | return 467 | } 468 | 469 | defer listeningConn.Close() 470 | gob.Register(ecdh.Point{}) 471 | for { 472 | conn, err := listeningConn.Accept() 473 | if err != nil { 474 | log.Println(err) 475 | continue 476 | } 477 | go server.handleTLS(conn) 478 | } 479 | } 480 | 481 | func (server *Server) handleTLS(conn net.Conn) { 482 | log.Println("Handling tls") 483 | defer conn.Close() 484 | buffer := make([]byte, 512) 485 | for { 486 | _, err := conn.Read(buffer) 487 | if err != nil { 488 | log.Println(err) 489 | break 490 | } 491 | packet := new(utilities.Packet) 492 | err = utilities.Decode(packet, buffer) 493 | if err != nil { 494 | log.Println(err) 495 | break 496 | } 497 | if packet.Flag == utilities.HANDSHAKE { 498 | err = server.handleHandshake(conn, packet.Payload) 499 | if err != nil { 500 | fmt.Println(err) 501 | break 502 | } 503 | } 504 | } 505 | } 506 | 507 | func (server *Server) handleHandshake(conn net.Conn, payload []byte) error { 508 | log.Println("Initiating Handshake") 509 | log.Println("Generating private key for this user session") 510 | 511 | gob.Register(rsa.PublicKey{}) 512 | clientPublicKey := new(ecdh.Point) 513 | err := utilities.Decode(clientPublicKey, payload) 514 | if err != nil { 515 | log.Println("Failed to decode client public key") 516 | return err 517 | } 518 | 519 | serverKEX := ecdh.Generic(elliptic.P256()) 520 | serverPrivateKey, serverPublicKey, err := serverKEX.GenerateKey(rand.Reader) 521 | if err != nil { 522 | log.Println(err) 523 | return err 524 | } 525 | peer := server.connectionPool.NewPeer() 526 | clientIPv4 := net.ParseIP(peer.IP) 527 | clientIP := utilities.Addr{IPAddr: clientIPv4, Network: *server.network, Gateway: server.tunInterface.IP} 528 | 529 | handshakePacket := new(HandshakePacket) 530 | handshakePacket.ClientIP = clientIP 531 | handshakePacket.ServerPublic = serverPublicKey 532 | handshakePacket.DNSResolvers = server.Config.DNSResolvers 533 | fmt.Println("DNS resolvers", server.Config.DNSResolvers) 534 | 535 | handshakePacketBytes, err := utilities.Encode(handshakePacket) 536 | if err != nil { 537 | log.Println(err) 538 | return err 539 | } 540 | packetData := utilities.Packet{Flag: utilities.HANDSHAKE_ACCEPTED, Payload: handshakePacketBytes} 541 | encodedPacket, err := utilities.Encode(packetData) 542 | if err != nil { 543 | log.Printf("Error encoding packet \t Error : %s \n", err) 544 | return err 545 | } 546 | _, err = conn.Write(encodedPacket) 547 | if err != nil { 548 | return err 549 | } 550 | 551 | peer.ServerSecret = serverKEX.ComputeSecret(serverPrivateKey, clientPublicKey) 552 | return nil 553 | } 554 | 555 | func (server *Server) listenAndServe() { 556 | defer server.waiter.Done() 557 | 558 | for { 559 | inputBytes := make([]byte, 1483) 560 | length, clientConn, err := server.socket.ReadFromUDP(inputBytes) 561 | go server.metrics.Update(length, 0, 0, 0) 562 | if err != nil || length == 0 { 563 | log.Println("Error: ", err) 564 | continue 565 | } 566 | go func(data []byte) { 567 | decompressedData, err := Decompress(data) 568 | if err != nil { 569 | log.Println("Failed to decompress data: ", err) 570 | return 571 | } 572 | 573 | headerData := decompressedData[len(decompressedData)-5:] 574 | decompressedData = decompressedData[:len(decompressedData)-5] 575 | srcIP := net.IP(headerData[:4]) 576 | headerFlag := headerData[4] 577 | peer := server.connectionPool.GetPeer(srcIP.String()) 578 | if peer == nil { 579 | return 580 | } 581 | 582 | peer.Addr = clientConn 583 | decryptedPayload, err := codec.Decrypt(peer.ServerSecret, decompressedData) 584 | if err != nil { 585 | log.Println("Failed to decrypt data") 586 | return 587 | } 588 | 589 | switch headerFlag { 590 | case utilities.HEARTBEAT: 591 | server.handleHeartbeat(decryptedPayload) 592 | case utilities.SESSION: 593 | server.handleConnection(peer, decryptedPayload) 594 | default: 595 | log.Println("Expected headers not found") 596 | } 597 | }(inputBytes[:length]) 598 | } 599 | } 600 | 601 | func (server *Server) handleConnection(peer *Peer, packet []byte) { 602 | server.connectionPool.Update(peer.IP, *peer) 603 | _, err := server.tunInterface.Ifce.Write(packet) 604 | if err != nil { 605 | fmt.Println(err) 606 | return 607 | } 608 | } 609 | 610 | func (server *Server) handleHeartbeat(packet []byte) { 611 | peer := new(Peer) 612 | err := utilities.Decode(peer, packet) 613 | if err != nil { 614 | log.Println("Error decoding peer data", err) 615 | } 616 | oldPeer := server.connectionPool.GetPeer(peer.IP) 617 | peer.LastHeartbeat = time.Now() 618 | peer.Addr = oldPeer.Addr 619 | peer.ServerSecret = oldPeer.ServerSecret 620 | server.connectionPool.Update(oldPeer.IP, *peer) 621 | } 622 | 623 | func (server *Server) readIfce() { 624 | defer server.waiter.Done() 625 | log.Println("Handling outgoing connection") 626 | for { 627 | buffer := make([]byte, server.tunInterface.Mtu) 628 | length, err := server.tunInterface.Ifce.Read(buffer) 629 | if err != nil { 630 | log.Println(err) 631 | continue 632 | } 633 | 634 | go func(data []byte, length int) { 635 | if length > -4 { 636 | header, err := ipv4.ParseHeader(data) 637 | if err != nil { 638 | log.Println(err) 639 | return 640 | } 641 | peer := server.connectionPool.GetPeer(header.Dst.String()) 642 | if peer != nil { 643 | encryptedData, err := codec.Encrypt(peer.ServerSecret, buffer[:length]) 644 | sendPacket := append(encryptedData, utilities.SESSION) 645 | if err != nil { 646 | log.Printf("An error occured while trying to encode this packet \t Error : %s \n", err) 647 | return 648 | } 649 | 650 | compressedPacket, err := Compress(sendPacket) 651 | if err != nil { 652 | log.Println(err) 653 | return 654 | } 655 | 656 | _, err = server.socket.WriteTo(compressedPacket, peer.Addr) 657 | if err != nil { 658 | fmt.Println(err) 659 | return 660 | } 661 | go server.metrics.Update(0, len(sendPacket), len(compressedPacket), length) 662 | } 663 | return 664 | } 665 | }(buffer[:length], length) 666 | } 667 | } 668 | --------------------------------------------------------------------------------