├── .github └── workflows │ ├── go.yml │ └── golangci-lint.yml ├── README.md ├── client.ini ├── main.go ├── server.ini ├── vendor └── vendor.json ├── vpn ├── client.go ├── connection.go ├── data.go ├── init.go ├── interface.go ├── ippool.go ├── server.go └── utils │ ├── config.go │ └── loging.go └── ws-vpn.iml /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | permissions: 10 | contents: read 11 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 12 | # pull-requests: read 13 | jobs: 14 | golangci: 15 | name: lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/setup-go@v3 19 | with: 20 | go-version: 1.17 21 | - uses: actions/checkout@v3 22 | - name: golangci-lint 23 | uses: golangci/golangci-lint-action@v3 24 | with: 25 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 26 | version: v1.29 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ws-vpn 2 | A VPN implementation over websockets. This is the client/server implementation 3 | of a layer-2 software switch able to route packets over websockets connections. 4 | The ws-vpn is built on top of Linux's tun/tap device. 5 | 6 | ## Configuration 7 | 8 | There are two config files to distinguish between client and server. 9 | 10 | To start server execute the following command: 11 | 12 | ``` 13 | ws-vpn --config server.ini 14 | ``` 15 | 16 | client: 17 | 18 | ``` 19 | ws-vpn --config client.ini 20 | ``` 21 | 22 | ### Download 23 | 24 | You can get updated release from: https://github.com/zreigz/ws-vpn/releases 25 | 26 | ### Build and Install 27 | 28 | Building ws-vpn needs Go 1.1 or higher. 29 | 30 | ws-vpn is a go-gettable package: 31 | 32 | ``` 33 | go get github.com/zreigz/ws-vpn 34 | ``` 35 | 36 | ### Network forwarding 37 | On the server the IP forwarding is needed. First we need to be sure that IP forwarding is enabled. 38 | Very often this is disabled by default. This is done by running the following command line as root: 39 | ``` 40 | # sysctl -w net.ipv4.ip_forward=1 41 | # iptables -t nat -A POSTROUTING -j MASQUERADE 42 | ``` 43 | 44 | So, lets look at the iptables rules required for this to work. 45 | ``` 46 | # Allow TUN interface connections to VPN server 47 | iptables -A INPUT -i tun0 -j ACCEPT 48 | 49 | # Allow TUN interface connections to be forwarded through other interfaces 50 | iptables -A FORWARD -i tun0 -j ACCEPT 51 | 52 | iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE 53 | 54 | iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT 55 | 56 | iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /client.ini: -------------------------------------------------------------------------------- 1 | [default] 2 | # server or client 3 | mode = client 4 | 5 | [client] 6 | server = 104.199.15.195 7 | # server port 8 | port = 80 9 | # MTU 10 | mtu = 1400 11 | redirectGateway = true -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | 19 | package main 20 | 21 | import ( 22 | "flag" 23 | "os" 24 | "runtime" 25 | 26 | . "github.com/zreigz/ws-vpn/vpn/utils" 27 | client "github.com/zreigz/ws-vpn/vpn" 28 | server "github.com/zreigz/ws-vpn/vpn" 29 | ) 30 | 31 | var debug bool 32 | var cfgFile string 33 | 34 | func main() { 35 | flag.BoolVar(&debug, "debug", false, "Provide debug info") 36 | flag.StringVar(&cfgFile, "config", "", "configfile") 37 | flag.Parse() 38 | 39 | InitLogger(debug) 40 | logger := GetLogger() 41 | 42 | checkerr := func(err error) { 43 | if err != nil { 44 | logger.Error(err.Error()) 45 | os.Exit(1) 46 | } 47 | } 48 | 49 | if cfgFile == "" { 50 | cfgFile = flag.Arg(0) 51 | } 52 | 53 | logger.Info("using config file: ", cfgFile) 54 | 55 | icfg, err := ParseConfig(cfgFile) 56 | logger.Debug(icfg) 57 | checkerr(err) 58 | 59 | maxProcs := runtime.GOMAXPROCS(0) 60 | if maxProcs < 2 { 61 | runtime.GOMAXPROCS(2) 62 | } 63 | 64 | switch cfg := icfg.(type) { 65 | case ServerConfig: 66 | err := server.NewServer(cfg) 67 | checkerr(err) 68 | case ClientConfig: 69 | err := client.NewClient(cfg) 70 | checkerr(err) 71 | default: 72 | logger.Error("Invalid config file") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server.ini: -------------------------------------------------------------------------------- 1 | [default] 2 | # server or client 3 | mode = server 4 | 5 | [server] 6 | # port to listen 7 | port = 8080 8 | # server addr 9 | vpnaddr = 10.1.1.1/24 10 | mtu = 1400 11 | # allow communication between clients 12 | interconnection = false -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "path": "-v", 7 | "revision": "" 8 | }, 9 | { 10 | "path": "all", 11 | "revision": "" 12 | }, 13 | { 14 | "checksumSHA1": "ajAqUByI39Sfm99F/ZNOguPP3Mk=", 15 | "path": "github.com/gorilla/websocket", 16 | "revision": "c3e18be99d19e6b3e8f1559eea2c161a665c4b6b", 17 | "revisionTime": "2019-08-25T01:20:11Z" 18 | }, 19 | { 20 | "checksumSHA1": "BoXdUBWB8UnSlFlbnuTQaPqfCGk=", 21 | "path": "github.com/op/go-logging", 22 | "revision": "970db520ece77730c7e4724c61121037378659d9", 23 | "revisionTime": "2016-03-15T20:05:05Z" 24 | }, 25 | { 26 | "checksumSHA1": "TXpUFvKVEIHH5//3sbRVv6wgSUU=", 27 | "path": "github.com/scalingdata/gcfg", 28 | "revision": "37aabad69cfd3d20b8390d902a8b10e245c615ff", 29 | "revisionTime": "2014-07-29T18:38:56Z" 30 | }, 31 | { 32 | "checksumSHA1": "LV3NB8wXu26KZwc6pAStcxE+ESQ=", 33 | "path": "github.com/scalingdata/gcfg/scanner", 34 | "revision": "37aabad69cfd3d20b8390d902a8b10e245c615ff", 35 | "revisionTime": "2014-07-29T18:38:56Z" 36 | }, 37 | { 38 | "checksumSHA1": "ExoT8iEbM4K8c4b3mQglfuPkxzw=", 39 | "path": "github.com/scalingdata/gcfg/token", 40 | "revision": "37aabad69cfd3d20b8390d902a8b10e245c615ff", 41 | "revisionTime": "2014-07-29T18:38:56Z" 42 | }, 43 | { 44 | "checksumSHA1": "hmKm+qy874Gta477MI+wlzxG2JM=", 45 | "path": "github.com/scalingdata/gcfg/types", 46 | "revision": "37aabad69cfd3d20b8390d902a8b10e245c615ff", 47 | "revisionTime": "2014-07-29T18:38:56Z" 48 | }, 49 | { 50 | "checksumSHA1": "fJhbYaAmKx1WfLtKcN9vfWrfbLU=", 51 | "path": "github.com/songgao/water", 52 | "revision": "fd331bda3f4bbc9aad07ccd4bd2abaa1e363a852", 53 | "revisionTime": "2019-07-25T07:32:26Z" 54 | }, 55 | { 56 | "checksumSHA1": "4rZ8D9GLDvy+6S940nMs75DvyfA=", 57 | "path": "golang.org/x/net/bpf", 58 | "revision": "24e19bdeb0f2d062d8e2640d50a7aaf2a7f80e7a", 59 | "revisionTime": "2019-06-07T04:56:05Z" 60 | }, 61 | { 62 | "checksumSHA1": "CHpYrf4HcmHB60gnTNxgjGgY53w=", 63 | "path": "golang.org/x/net/internal/iana", 64 | "revision": "24e19bdeb0f2d062d8e2640d50a7aaf2a7f80e7a", 65 | "revisionTime": "2019-06-07T04:56:05Z" 66 | }, 67 | { 68 | "checksumSHA1": "4vBBc8EXFsyNS8lJ4g8ljXUlYqo=", 69 | "path": "golang.org/x/net/internal/socket", 70 | "revision": "24e19bdeb0f2d062d8e2640d50a7aaf2a7f80e7a", 71 | "revisionTime": "2019-06-07T04:56:05Z" 72 | }, 73 | { 74 | "checksumSHA1": "BeKuwferCpRcN+C4oEQGm/pdTAU=", 75 | "path": "golang.org/x/net/ipv4", 76 | "revision": "24e19bdeb0f2d062d8e2640d50a7aaf2a7f80e7a", 77 | "revisionTime": "2019-06-07T04:56:05Z" 78 | }, 79 | { 80 | "checksumSHA1": "vng7KQUjr7YzuxySU+KIh7Ki7mA=", 81 | "path": "golang.org/x/sys/unix", 82 | "revision": "c3b328c6e5a763a2bfc82a48b4bd855037210934", 83 | "revisionTime": "2019-09-13T12:12:07Z" 84 | }, 85 | { 86 | "checksumSHA1": "U4rR1I0MXcvJz3zSxTp3hb3Y0I0=", 87 | "path": "golang.org/x/sys/windows", 88 | "revision": "c3b328c6e5a763a2bfc82a48b4bd855037210934", 89 | "revisionTime": "2019-09-13T12:12:07Z" 90 | }, 91 | { 92 | "checksumSHA1": "FCBHX83YaM1LmNHtSM30BKmbJQY=", 93 | "path": "golang.org/x/sys/windows/registry", 94 | "revision": "c3b328c6e5a763a2bfc82a48b4bd855037210934", 95 | "revisionTime": "2019-09-13T12:12:07Z" 96 | } 97 | ], 98 | "rootPath": "github.com/zreigz/ws-vpn" 99 | } 100 | -------------------------------------------------------------------------------- /vpn/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package vpn 19 | 20 | import ( 21 | "errors" 22 | "github.com/songgao/water" 23 | "net" 24 | 25 | . "github.com/zreigz/ws-vpn/vpn/utils" 26 | "net/url" 27 | "os" 28 | "os/signal" 29 | "syscall" 30 | 31 | "encoding/json" 32 | "github.com/gorilla/websocket" 33 | 34 | "time" 35 | 36 | "fmt" 37 | ) 38 | 39 | type Client struct { 40 | // config 41 | cfg ClientConfig 42 | // interface 43 | iface *water.Interface 44 | // ip addr 45 | ip net.IP 46 | 47 | toIface chan []byte 48 | 49 | ws *websocket.Conn 50 | 51 | data chan *Data 52 | 53 | state int 54 | 55 | routes []string 56 | } 57 | 58 | var net_gateway, net_nic string 59 | 60 | func NewClient(cfg ClientConfig) error { 61 | var err error 62 | 63 | if cfg.MTU != 0 { 64 | MTU = cfg.MTU 65 | } 66 | 67 | client := new(Client) 68 | client.cfg = cfg 69 | 70 | client.toIface = make(chan []byte, 100) 71 | client.data = make(chan *Data, 100) 72 | client.routes = make([]string, 0, 1024) 73 | 74 | go client.cleanUp() 75 | 76 | iface, err := newTun("") 77 | if err != nil { 78 | return err 79 | } 80 | client.iface = iface 81 | 82 | net_gateway, net_nic, err = getNetGateway() 83 | logger.Debug("Net Gateway: ", net_gateway, net_nic) 84 | if err != nil { 85 | logger.Error("Net gateway error") 86 | return err 87 | } 88 | srvDest := cfg.Server + "/32" 89 | addRoute(srvDest, net_gateway, net_nic) 90 | client.routes = append(client.routes, srvDest) 91 | 92 | srvAdr := fmt.Sprintf("%s:%d", cfg.Server, cfg.Port) 93 | u := url.URL{Scheme: "ws", Host: srvAdr, Path: "/ws"} 94 | logger.Debug("Connecting to ", u.String()) 95 | 96 | ticker := time.NewTicker(time.Second * 4) 97 | defer ticker.Stop() 98 | 99 | var connection *websocket.Conn 100 | 101 | for ok := true; ok; ok = (connection == nil) { 102 | select { 103 | case <-ticker.C: 104 | connection, _, err = websocket.DefaultDialer.Dial(u.String(), nil) 105 | if err != nil { 106 | logger.Info("Dial: ", err) 107 | } else { 108 | ticker.Stop() 109 | } 110 | break 111 | } 112 | } 113 | 114 | client.ws = connection 115 | 116 | defer connection.Close() 117 | 118 | client.state = STATE_INIT 119 | 120 | client.ws.SetReadLimit(maxMessageSize) 121 | client.ws.SetReadDeadline(time.Now().Add(pongWait)) 122 | client.ws.SetPongHandler(func(string) error { 123 | client.ws.SetReadDeadline(time.Now().Add(pongWait)) 124 | logger.Debug("Pong received") 125 | return nil 126 | }) 127 | 128 | go client.writePump() 129 | 130 | // Initialize connection with master 131 | client.data <- &Data{ 132 | ConnectionState: STATE_CONNECT, 133 | } 134 | 135 | for { 136 | messageType, r, err := connection.ReadMessage() 137 | if err != nil { 138 | logger.Error("Read error:", err) 139 | delRoute("0.0.0.0/1") 140 | delRoute("128.0.0.0/1") 141 | for _, dest := range client.routes { 142 | delRoute(dest) 143 | } 144 | break 145 | } else { 146 | 147 | if messageType == websocket.TextMessage { 148 | client.dispatcher(r) 149 | } 150 | 151 | } 152 | 153 | } 154 | return errors.New("Not expected to exit") 155 | } 156 | 157 | func (clt *Client) dispatcher(p []byte) { 158 | logger.Debug("Dispatcher: ", clt.state) 159 | switch clt.state { 160 | case STATE_INIT: 161 | logger.Debug("STATE_INIT") 162 | var message Data 163 | if err := json.Unmarshal(p, &message); err != nil { 164 | clt.ws.Close() 165 | close(clt.data) 166 | logger.Panic(err) 167 | } 168 | if message.ConnectionState == STATE_CONNECT { 169 | 170 | ipStr := string(message.Payload) 171 | ip, subnet, _ := net.ParseCIDR(ipStr) 172 | setTunIP(clt.iface, ip, subnet) 173 | if clt.cfg.RedirectGateway { 174 | err := redirectGateway(clt.iface.Name(), tun_peer.String()) 175 | if err != nil { 176 | logger.Error("Redirect gateway error", err.Error()) 177 | } 178 | } 179 | 180 | clt.state = STATE_CONNECTED 181 | clt.handleInterface() 182 | } 183 | case STATE_CONNECTED: 184 | clt.toIface <- p 185 | 186 | } 187 | } 188 | 189 | func (clt *Client) handleInterface() { 190 | // network packet to interface 191 | go func() { 192 | for { 193 | hp := <-clt.toIface 194 | _, err := clt.iface.Write(hp) 195 | if err != nil { 196 | logger.Error(err.Error()) 197 | return 198 | } 199 | logger.Debug("Write to interface") 200 | } 201 | }() 202 | 203 | go func() { 204 | packet := make([]byte, IFACE_BUFSIZE) 205 | for { 206 | plen, err := clt.iface.Read(packet) 207 | if err != nil { 208 | logger.Error(err) 209 | break 210 | } 211 | clt.data <- &Data{ 212 | ConnectionState: STATE_CONNECTED, 213 | Payload: packet[:plen], 214 | } 215 | 216 | } 217 | }() 218 | } 219 | 220 | func (clt *Client) writePump() { 221 | 222 | ticker := time.NewTicker(pingPeriod) 223 | 224 | defer func() { 225 | ticker.Stop() 226 | clt.ws.Close() 227 | }() 228 | 229 | for { 230 | select { 231 | case message, ok := <-clt.data: 232 | if !ok { 233 | clt.write(websocket.CloseMessage, &Data{}) 234 | return 235 | } 236 | if err := clt.write(websocket.TextMessage, message); err != nil { 237 | logger.Error("writePump error", err) 238 | } 239 | case <-ticker.C: 240 | if err := clt.ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil { 241 | logger.Error("Send ping error", err) 242 | } 243 | } 244 | } 245 | } 246 | 247 | func (clt *Client) write(mt int, message *Data) error { 248 | 249 | if message.ConnectionState == STATE_CONNECTED { 250 | return clt.ws.WriteMessage(mt, message.Payload) 251 | } else { 252 | s, err := json.Marshal(message) 253 | if err != nil { 254 | logger.Panic(err) 255 | } 256 | return clt.ws.WriteMessage(mt, s) 257 | } 258 | 259 | } 260 | 261 | func (clt *Client) cleanUp() { 262 | c := make(chan os.Signal, 1) 263 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 264 | <-c 265 | logger.Info("Cleaning Up") 266 | delRoute("0.0.0.0/1") 267 | delRoute("128.0.0.0/1") 268 | for _, dest := range clt.routes { 269 | delRoute(dest) 270 | } 271 | 272 | os.Exit(0) 273 | } 274 | -------------------------------------------------------------------------------- /vpn/connection.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package vpn 19 | 20 | import ( 21 | "encoding/json" 22 | "github.com/gorilla/websocket" 23 | "io" 24 | "net" 25 | "time" 26 | ) 27 | 28 | const ( 29 | writeWait = 10 * time.Second 30 | pongWait = 60 * time.Second 31 | pingPeriod = (pongWait) / 2 32 | maxMessageSize = 1024 * 1024 33 | ) 34 | 35 | type connection struct { 36 | id int 37 | ws *websocket.Conn 38 | server *VpnServer 39 | data chan *Data 40 | state int 41 | ipAddress *net.IPNet 42 | } 43 | 44 | var upgrader = websocket.Upgrader{ 45 | ReadBufferSize: maxMessageSize, 46 | WriteBufferSize: maxMessageSize, 47 | } 48 | 49 | var maxId int = 0 50 | 51 | func NewConnection(ws *websocket.Conn, server *VpnServer) *connection { 52 | 53 | logger.Debug("New connection created") 54 | 55 | if ws == nil { 56 | panic("ws cannot be nil") 57 | } 58 | 59 | if server == nil { 60 | panic("server cannot be nil") 61 | } 62 | 63 | maxId++ 64 | data := make(chan *Data) 65 | 66 | c := &connection{maxId, ws, server, data, STATE_INIT, nil} 67 | go c.writePump() 68 | go c.readPump() 69 | 70 | return c 71 | } 72 | 73 | func (c *connection) readPump() { 74 | defer func() { 75 | c.server.unregister <- c 76 | c.ws.Close() 77 | }() 78 | 79 | c.ws.SetReadLimit(maxMessageSize) 80 | c.ws.SetPingHandler(func(string) error { 81 | logger.Debug("Ping received") 82 | if err := c.ws.WriteControl(websocket.PongMessage, []byte{}, time.Now().Add(writeWait)); err != nil { 83 | logger.Error("Send ping error", err) 84 | } 85 | return nil 86 | }) 87 | 88 | for { 89 | messageType, r, err := c.ws.ReadMessage() 90 | if err == io.EOF { 91 | c.cleanUp() 92 | break 93 | } else if err != nil { 94 | logger.Info(err) 95 | c.cleanUp() 96 | break 97 | } else { 98 | 99 | if messageType == websocket.TextMessage { 100 | c.dispatcher(r) 101 | } 102 | } 103 | } 104 | } 105 | 106 | func (c *connection) writePump() { 107 | 108 | defer func() { 109 | 110 | c.ws.Close() 111 | }() 112 | 113 | for { 114 | if c != nil { 115 | select { 116 | case message, ok := <-c.data: 117 | // Thread can be still active after close connection 118 | if message != nil { 119 | logger.Debug("writePump data len: ", len(message.Payload)) 120 | if !ok { 121 | c.write(websocket.CloseMessage, &Data{}) 122 | return 123 | } 124 | if err := c.write(websocket.TextMessage, message); err != nil { 125 | logger.Error("writePump error", err) 126 | } 127 | } else { 128 | break 129 | } 130 | 131 | } 132 | } else { 133 | break 134 | } 135 | } 136 | } 137 | 138 | func (c *connection) write(mt int, message *Data) error { 139 | 140 | c.ws.SetWriteDeadline(time.Now().Add(writeWait)) 141 | 142 | if message.ConnectionState == STATE_CONNECTED { 143 | err := c.ws.WriteMessage(mt, message.Payload) 144 | if err != nil { 145 | return err 146 | } 147 | } else { 148 | s, err := json.Marshal(message) 149 | if err != nil { 150 | logger.Panic(err) 151 | return err 152 | } 153 | 154 | err = c.ws.WriteMessage(mt, s) 155 | if err != nil { 156 | return err 157 | } 158 | } 159 | return nil 160 | } 161 | 162 | func (c *connection) dispatcher(p []byte) { 163 | logger.Debug("Dispatcher: ", c.state) 164 | switch c.state { 165 | case STATE_INIT: 166 | logger.Debug("STATE_INIT") 167 | var message Data 168 | if err := json.Unmarshal(p, &message); err != nil { 169 | logger.Panic(err) 170 | } 171 | if message.ConnectionState == STATE_CONNECT { 172 | d := new(Data) 173 | d.ConnectionState = STATE_CONNECT 174 | cltIP, err := c.server.ippool.next() 175 | if err != nil { 176 | c.cleanUp() 177 | logger.Error(err) 178 | } 179 | logger.Debug("Next IP from ippool", cltIP) 180 | c.ipAddress = cltIP 181 | d.Payload = []byte(cltIP.String()) 182 | c.state = STATE_CONNECTED 183 | c.server.register <- c 184 | c.data <- d 185 | 186 | } 187 | case STATE_CONNECTED: 188 | logger.Debug("STATE_CONNECTED") 189 | c.server.toIface <- p 190 | } 191 | } 192 | 193 | func (c *connection) cleanUp() { 194 | c.server.unregister <- c 195 | c.ws.Close() 196 | } 197 | -------------------------------------------------------------------------------- /vpn/data.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package vpn 19 | 20 | type Data struct { 21 | ConnectionState int `json:"connectionState"` 22 | Payload []byte `json:"payload"` 23 | } 24 | -------------------------------------------------------------------------------- /vpn/init.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package vpn 19 | 20 | import loging "github.com/zreigz/ws-vpn/vpn/utils" 21 | 22 | var logger = loging.GetLogger() 23 | 24 | var MTU = 1400 25 | 26 | const ( 27 | IFACE_BUFSIZE = 2000 28 | ) 29 | 30 | const ( 31 | STATE_INIT = 0 32 | 33 | STATE_CONNECT = 1 34 | 35 | STATE_CONNECTED = 2 36 | ) 37 | -------------------------------------------------------------------------------- /vpn/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package vpn 19 | 20 | import ( 21 | "bufio" 22 | "bytes" 23 | "errors" 24 | "fmt" 25 | "net" 26 | "os" 27 | "os/exec" 28 | "strconv" 29 | "strings" 30 | 31 | "github.com/songgao/water" 32 | ) 33 | 34 | var invalidAddr = errors.New("Invalid device ip address") 35 | 36 | var tun_peer net.IP 37 | 38 | func newTun(name string) (iface *water.Interface, err error) { 39 | 40 | iface, err = water.New(water.Config{}) 41 | if err != nil { 42 | return nil, err 43 | } 44 | logger.Info("interface %v created", iface.Name()) 45 | 46 | sargs := fmt.Sprintf("link set dev %s up mtu %d qlen 100", iface.Name(), MTU) 47 | args := strings.Split(sargs, " ") 48 | cmd := exec.Command("ip", args...) 49 | logger.Info("ip %s", sargs) 50 | err = cmd.Run() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return iface, nil 56 | } 57 | 58 | func setTunIP(iface *water.Interface, ip net.IP, subnet *net.IPNet) (err error) { 59 | ip = ip.To4() 60 | logger.Debug("IP address ", ip) 61 | if ip[3]%2 == 0 { 62 | return invalidAddr 63 | } 64 | 65 | peer := net.IP(make([]byte, 4)) 66 | copy([]byte(peer), []byte(ip)) 67 | peer[3]++ 68 | tun_peer = peer 69 | 70 | sargs := fmt.Sprintf("addr add dev %s local %s peer %s", iface.Name(), ip, peer) 71 | args := strings.Split(sargs, " ") 72 | cmd := exec.Command("ip", args...) 73 | logger.Info("ip ", sargs) 74 | err = cmd.Run() 75 | if err != nil { 76 | return err 77 | } 78 | 79 | sargs = fmt.Sprintf("route add %s via %s dev %s", subnet, peer, iface.Name()) 80 | args = strings.Split(sargs, " ") 81 | cmd = exec.Command("ip", args...) 82 | logger.Info("ip ", sargs) 83 | err = cmd.Run() 84 | return err 85 | } 86 | 87 | // return net gateway (default route) and nic 88 | func getNetGateway() (gw, dev string, err error) { 89 | 90 | file, err := os.Open("/proc/net/route") 91 | if err != nil { 92 | return "", "", err 93 | } 94 | 95 | defer file.Close() 96 | rd := bufio.NewReader(file) 97 | 98 | s2byte := func(s string) byte { 99 | b, _ := strconv.ParseUint(s, 16, 8) 100 | return byte(b) 101 | } 102 | 103 | for { 104 | line, isPrefix, err := rd.ReadLine() 105 | 106 | if err != nil { 107 | logger.Error(err.Error()) 108 | return "", "", err 109 | } 110 | if isPrefix { 111 | return "", "", errors.New("Line Too Long!") 112 | } 113 | buf := bytes.NewBuffer(line) 114 | scanner := bufio.NewScanner(buf) 115 | scanner.Split(bufio.ScanWords) 116 | tokens := make([]string, 0, 8) 117 | 118 | for scanner.Scan() { 119 | tokens = append(tokens, scanner.Text()) 120 | } 121 | 122 | iface := tokens[0] 123 | dest := tokens[1] 124 | gw := tokens[2] 125 | mask := tokens[7] 126 | 127 | if bytes.Equal([]byte(dest), []byte("00000000")) && 128 | bytes.Equal([]byte(mask), []byte("00000000")) { 129 | a := s2byte(gw[6:8]) 130 | b := s2byte(gw[4:6]) 131 | c := s2byte(gw[2:4]) 132 | d := s2byte(gw[0:2]) 133 | 134 | ip := net.IPv4(a, b, c, d) 135 | 136 | return ip.String(), iface, nil 137 | } 138 | 139 | } 140 | return "", "", errors.New("No default gateway found") 141 | } 142 | 143 | // add route 144 | func addRoute(dest, nextHop, iface string) { 145 | 146 | scmd := fmt.Sprintf("ip -4 r a %s via %s dev %s", dest, nextHop, iface) 147 | cmd := exec.Command("bash", "-c", scmd) 148 | logger.Info(scmd) 149 | err := cmd.Run() 150 | 151 | if err != nil { 152 | logger.Warning(err.Error()) 153 | } 154 | 155 | } 156 | 157 | // delete route 158 | func delRoute(dest string) { 159 | sargs := fmt.Sprintf("-4 route del %s", dest) 160 | args := strings.Split(sargs, " ") 161 | cmd := exec.Command("ip", args...) 162 | logger.Info("ip %s", sargs) 163 | err := cmd.Run() 164 | 165 | if err != nil { 166 | logger.Warning(err.Error()) 167 | } 168 | } 169 | 170 | // redirect default gateway 171 | func redirectGateway(iface, gw string) error { 172 | subnets := []string{"0.0.0.0/1", "128.0.0.0/1"} 173 | logger.Info("Redirecting Gateway") 174 | for _, subnet := range subnets { 175 | sargs := fmt.Sprintf("-4 route add %s via %s dev %s", subnet, gw, iface) 176 | args := strings.Split(sargs, " ") 177 | cmd := exec.Command("ip", args...) 178 | logger.Info("ip %s", sargs) 179 | err := cmd.Run() 180 | 181 | if err != nil { 182 | return err 183 | } 184 | } 185 | return nil 186 | } 187 | -------------------------------------------------------------------------------- /vpn/ippool.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package vpn 19 | 20 | import ( 21 | "errors" 22 | "net" 23 | "sync/atomic" 24 | ) 25 | 26 | type VpnIpPool struct { 27 | subnet *net.IPNet 28 | pool [127]int32 29 | } 30 | 31 | var poolFull = errors.New("IP Pool Full") 32 | 33 | func (p *VpnIpPool) next() (*net.IPNet, error) { 34 | found := false 35 | var i int 36 | for i = 3; i < 255; i += 2 { 37 | if atomic.CompareAndSwapInt32(&p.pool[i], 0, 1) { 38 | found = true 39 | break 40 | } 41 | } 42 | if !found { 43 | return nil, poolFull 44 | } 45 | 46 | ipnet := &net.IPNet{ 47 | make([]byte, 4), 48 | make([]byte, 4), 49 | } 50 | copy([]byte(ipnet.IP), []byte(p.subnet.IP)) 51 | copy([]byte(ipnet.Mask), []byte(p.subnet.Mask)) 52 | ipnet.IP[3] = byte(i) 53 | return ipnet, nil 54 | } 55 | 56 | func (p *VpnIpPool) relase(ip net.IP) { 57 | defer func() { 58 | if err := recover(); err != nil { 59 | logger.Error("%v", err) 60 | } 61 | }() 62 | 63 | logger.Debug("releasing ip: ", ip) 64 | i := ip[3] 65 | p.pool[i] = 0 66 | } 67 | -------------------------------------------------------------------------------- /vpn/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | 19 | package vpn 20 | 21 | import ( 22 | "net" 23 | 24 | "github.com/songgao/water" 25 | . "github.com/zreigz/ws-vpn/vpn/utils" 26 | 27 | "fmt" 28 | "net/http" 29 | "os" 30 | "os/signal" 31 | "syscall" 32 | 33 | "golang.org/x/net/ipv4" 34 | ) 35 | 36 | type VpnServer struct { 37 | // config 38 | cfg ServerConfig 39 | // interface 40 | iface *water.Interface 41 | // subnet 42 | ipnet *net.IPNet 43 | // IP Pool 44 | ippool *VpnIpPool 45 | // client peers, key is the mac address, value is a HopPeer record 46 | 47 | // Registered clients 48 | clients map[string]*connection 49 | 50 | // Register requests 51 | register chan *connection 52 | 53 | // Unregister requests 54 | unregister chan *connection 55 | 56 | outData *Data 57 | 58 | inData chan *Data 59 | 60 | toIface chan []byte 61 | } 62 | 63 | func NewServer(cfg ServerConfig) error { 64 | var err error 65 | 66 | if cfg.MTU != 0 { 67 | MTU = cfg.MTU 68 | } 69 | 70 | vpnServer := new(VpnServer) 71 | 72 | vpnServer.cfg = cfg 73 | 74 | vpnServer.ippool = new(VpnIpPool) 75 | 76 | iface, err := newTun("") 77 | if err != nil { 78 | return err 79 | } 80 | vpnServer.iface = iface 81 | ip, subnet, err := net.ParseCIDR(cfg.VpnAddr) 82 | err = setTunIP(iface, ip, subnet) 83 | if err != nil { 84 | return err 85 | } 86 | vpnServer.ipnet = &net.IPNet{ip, subnet.Mask} 87 | vpnServer.ippool.subnet = subnet 88 | 89 | go vpnServer.cleanUp() 90 | 91 | go vpnServer.run() 92 | 93 | vpnServer.register = make(chan *connection) 94 | vpnServer.unregister = make(chan *connection) 95 | vpnServer.clients = make(map[string]*connection) 96 | vpnServer.inData = make(chan *Data, 100) 97 | vpnServer.toIface = make(chan []byte, 100) 98 | 99 | vpnServer.handleInterface() 100 | 101 | http.HandleFunc("/ws", vpnServer.serveWs) 102 | 103 | adr := fmt.Sprintf(":%d", vpnServer.cfg.Port) 104 | err = http.ListenAndServe(adr, nil) 105 | if err != nil { 106 | logger.Panic("ListenAndServe: " + err.Error()) 107 | } 108 | 109 | return nil 110 | 111 | } 112 | 113 | func (srv *VpnServer) serveWs(w http.ResponseWriter, r *http.Request) { 114 | if r.Method != "GET" { 115 | http.Error(w, "Method not allowed", 405) 116 | return 117 | } 118 | 119 | ws, err := upgrader.Upgrade(w, r, nil) 120 | if err != nil { 121 | logger.Error(err) 122 | return 123 | } 124 | 125 | NewConnection(ws, srv) 126 | 127 | } 128 | 129 | func (srv *VpnServer) run() { 130 | for { 131 | select { 132 | case c := <-srv.register: 133 | logger.Info("Connection registered:", c.ipAddress.IP.String()) 134 | srv.clients[c.ipAddress.IP.String()] = c 135 | break 136 | 137 | case c := <-srv.unregister: 138 | clientIP := c.ipAddress.IP.String() 139 | _, ok := srv.clients[clientIP] 140 | if ok { 141 | delete(srv.clients, clientIP) 142 | close(c.data) 143 | if c.ipAddress != nil { 144 | srv.ippool.relase(c.ipAddress.IP) 145 | } 146 | logger.Info("Connection removed:", c.ipAddress.IP) 147 | logger.Info("Number active clients:", len(srv.clients)) 148 | } 149 | break 150 | 151 | } 152 | } 153 | } 154 | 155 | func (srv *VpnServer) handleInterface() { 156 | // network packet to interface 157 | go func() { 158 | for { 159 | hp := <-srv.toIface 160 | logger.Debug("Write to interface") 161 | _, err := srv.iface.Write(hp) 162 | if err != nil { 163 | logger.Error(err.Error()) 164 | return 165 | } 166 | 167 | } 168 | }() 169 | 170 | go func() { 171 | packet := make([]byte, IFACE_BUFSIZE) 172 | for { 173 | plen, err := srv.iface.Read(packet) 174 | if err != nil { 175 | logger.Error(err) 176 | break 177 | } 178 | header, _ := ipv4.ParseHeader(packet[:plen]) 179 | logger.Debug("Try sending: ", header) 180 | clientIP := header.Dst.String() 181 | client, ok := srv.clients[clientIP] 182 | if ok { 183 | if !srv.cfg.Interconnection { 184 | if srv.isConnectionBetweenClients(header) { 185 | logger.Info("Drop connection betwenn ", header.Src, header.Dst) 186 | continue 187 | } 188 | } 189 | 190 | logger.Debug("Sending to client: ", client.ipAddress) 191 | client.data <- &Data{ 192 | ConnectionState: STATE_CONNECTED, 193 | Payload: packet[:plen], 194 | } 195 | 196 | } else { 197 | logger.Warning("Client not found ", clientIP) 198 | } 199 | 200 | } 201 | }() 202 | } 203 | 204 | func (srv *VpnServer) isConnectionBetweenClients(header *ipv4.Header) bool { 205 | 206 | if (header.Src.String() != header.Dst.String() && header.Src.String() != srv.ipnet.IP.String() && srv.ippool.subnet.Contains(header.Dst)) { 207 | return true 208 | } 209 | 210 | return false 211 | } 212 | 213 | func (srv *VpnServer) cleanUp() { 214 | 215 | c := make(chan os.Signal, 1) 216 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 217 | <-c 218 | logger.Debug("clean up") 219 | for key, client := range srv.clients { 220 | client.ws.Close() 221 | delete(srv.clients, key) 222 | } 223 | 224 | os.Exit(0) 225 | } 226 | -------------------------------------------------------------------------------- /vpn/utils/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package utils 19 | 20 | import ( 21 | "errors" 22 | 23 | "github.com/scalingdata/gcfg" 24 | ) 25 | 26 | // Server Config 27 | type ServerConfig struct { 28 | Port int 29 | ListenAddr string 30 | VpnAddr string 31 | MTU int 32 | Interconnection bool 33 | } 34 | 35 | // Client Config 36 | type ClientConfig struct { 37 | Server string 38 | Port int 39 | MTU int 40 | RedirectGateway bool 41 | } 42 | 43 | type VpnConfig struct { 44 | Default struct { 45 | Mode string 46 | } 47 | Server ServerConfig 48 | Client ClientConfig 49 | } 50 | 51 | func ParseConfig(filename string) (interface{}, error) { 52 | cfg := new(VpnConfig) 53 | err := gcfg.ReadFileInto(cfg, filename) 54 | if err != nil { 55 | return nil, err 56 | } 57 | switch cfg.Default.Mode { 58 | case "server": 59 | return cfg.Server, nil 60 | case "client": 61 | return cfg.Client, nil 62 | default: 63 | return nil, errors.New("Wrong config data") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vpn/utils/loging.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | * 15 | * Author: Lukasz Zajaczkowski 16 | * 17 | */ 18 | package utils 19 | 20 | import ( 21 | "os" 22 | 23 | "github.com/op/go-logging" 24 | ) 25 | 26 | var Logger = logging.MustGetLogger("ws-vpn") 27 | 28 | func InitLogger(debug bool) { 29 | fmt_string := "\r%{color}[%{time:06-01-02 15:04:05}][%{level:.6s}]%{color:reset} %{message}" 30 | format := logging.MustStringFormatter(fmt_string) 31 | logging.SetFormatter(format) 32 | logging.SetBackend(logging.NewLogBackend(os.Stdout, "", 0)) 33 | 34 | if debug { 35 | logging.SetLevel(logging.DEBUG, "ws-vpn") 36 | } else { 37 | logging.SetLevel(logging.INFO, "ws-vpn") 38 | } 39 | } 40 | 41 | func GetLogger() *logging.Logger { 42 | return Logger 43 | } 44 | -------------------------------------------------------------------------------- /ws-vpn.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------