├── .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 |
--------------------------------------------------------------------------------