├── .gitignore ├── Makefile ├── .gitmodules ├── rateLimiter.go ├── README.md ├── hub.go ├── wsproxy.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /.gopath/ 2 | /bin/ 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mkdir -p bin .gopath 3 | if [ ! -L .gopath/src ]; then ln -s "$(CURDIR)/vendor" .gopath/src; fi 4 | GOBIN="$(CURDIR)/bin/" GOPATH="$(CURDIR)/.gopath" go install 5 | 6 | all: build 7 | 8 | .PHONY: all build 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/golang.org/x/net"] 2 | path = vendor/golang.org/x/net 3 | url = https://github.com/golang/net 4 | [submodule "vendor/github.com/songgao/water"] 5 | path = vendor/github.com/songgao/water 6 | url = https://github.com/songgao/water.git 7 | [submodule "vendor/github.com/ogier/pflag"] 8 | path = vendor/github.com/ogier/pflag 9 | url = https://github.com/ogier/pflag 10 | -------------------------------------------------------------------------------- /rateLimiter.go: -------------------------------------------------------------------------------- 1 | /* go-websockproxy - https://github.com/gdm85/go-websockproxy 2 | Copyright (C) 2016 gdm85 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | package main 19 | 20 | import ( 21 | "strconv" 22 | "strings" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | func parseBandwidth(s string) (int64, error) { 28 | if s == "" { 29 | return 0, nil 30 | } 31 | if strings.HasSuffix(s, "kbps") { 32 | s = strings.TrimSpace(s[:len(s)-4]) 33 | n, err := strconv.ParseInt(s, 10, 64) 34 | if err != nil { 35 | return 0, err 36 | } 37 | return n * 1000, nil 38 | } 39 | if strings.HasSuffix(s, "mbps") { 40 | s = strings.TrimSpace(s[:len(s)-4]) 41 | n, err := strconv.ParseInt(s, 10, 64) 42 | if err != nil { 43 | return 0, err 44 | } 45 | return n * 1000 * 1000, nil 46 | } 47 | if strings.HasSuffix(s, "kbit") { 48 | s = strings.TrimSpace(s[:len(s)-4]) 49 | n, err := strconv.ParseInt(s, 10, 64) 50 | if err != nil { 51 | return 0, err 52 | } 53 | return n * 125, nil 54 | } 55 | if strings.HasSuffix(s, "mbit") { 56 | s = strings.TrimSpace(s[:len(s)-4]) 57 | n, err := strconv.ParseInt(s, 10, 64) 58 | if err != nil { 59 | return 0, err 60 | } 61 | return n * 1000 * 125, nil 62 | } 63 | if strings.HasSuffix(s, "bps") { 64 | s = strings.TrimSpace(s[:len(s)-4]) 65 | } 66 | n, err := strconv.ParseInt(s, 10, 64) 67 | if err != nil { 68 | return 0, err 69 | } 70 | return n, nil 71 | } 72 | 73 | /* BandwidthAllowance is a simple rate-limiting device. 74 | Bandwidths or rates can be specified in: 75 | * kbps - Kilobytes per second 76 | * mbps - Megabytes per second 77 | * kbit - Kilobits per second 78 | * mbit - Megabits per second 79 | * bps or a bare number - Bytes per second */ 80 | type BandwidthAllowance struct { 81 | sync.Mutex 82 | lastCheck time.Time 83 | allowance, rate int64 84 | } 85 | 86 | // DoThrottle returns true if the payload of specified size needs to be throttled (dropped). 87 | func (ba BandwidthAllowance) DoThrottle(size int) bool { 88 | if ba.rate == 0 { 89 | return false 90 | } 91 | ba.Lock() 92 | now := time.Now() 93 | timePassed := now.Sub(ba.lastCheck) 94 | 95 | ba.lastCheck = now 96 | ba.allowance += int64(timePassed.Seconds() * float64(ba.rate)) 97 | 98 | if ba.allowance > ba.rate { 99 | ba.allowance = ba.rate // throttle 100 | } 101 | 102 | if ba.allowance > 1.0 { 103 | ba.allowance -= int64(size) 104 | ba.Unlock() 105 | return true 106 | } 107 | 108 | ba.Unlock() 109 | return false 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-websockproxy 2 | 3 | This is a Go port of [websockproxy](https://github.com/benjamincburns/websockproxy) licensed under [GNU/GPLv2](./LICENSE). 4 | It is a websockets server that allows network support in [jor1k](https://github.com/s-macke/jor1k) OR1K emulators running Linux. 5 | 6 | Pull requests are welcome. 7 | 8 | ## Features 9 | - [x] client authentication 10 | - [x] secure websockets (TLS a.k.a. `wss://`) 11 | - [x] MAC prefix whitelisting 12 | - [x] download/upload rate limiting 13 | - [x] serving a directory with static files 14 | - [ ] re-attaching persistent TAP interfaces (would be handy for non-root usage) 15 | 16 | ``` 17 | Usage of bin/go-websockproxy: 18 | --auth-key string 19 | accept TAP traffic via websockets only if authorized with this key; by default is disabled (accepts any traffic) 20 | --cert-file string 21 | certificate for listening on TLS connections; by default TLS is disabled 22 | --key-file string 23 | key file for listening on TLS connections; by default TLS is disabled 24 | --listen-address string 25 | address to listen on for incoming websocket connections; URI is '/wstap' (default ":8000") 26 | --log-level string 27 | one of 'debug', 'info', 'warning', 'error' (default "warning") 28 | --mac-prefix string 29 | accept websockets traffic only with MACs starting with the specified prefix (default is disabled) 30 | --max-download-bandwidth string 31 | max upload bandwidth per client; leave empty for unlimited 32 | --max-upload-bandwidth string 33 | max upload bandwidth per client; leave empty for unlimited 34 | --static-directory string 35 | static files directory to serve at '/'; disabled by default 36 | --tap-ipv4 string 37 | IPv4 address for the TAP interface; used only when interface is created (default "10.3.0.1/16") 38 | ``` 39 | 40 | go-websockproxy would by default be accessible at `wss://localhost:8000/wstap`. 41 | 42 | In order to use features like AUTH key, query-specified relay URL and MAC address whitelisting, give a peek to [author's fork of jor1k](https://github.com/gdm85/jor1k/). 43 | 44 | # Building 45 | 46 | Repository's submodules should be initialised: 47 | ``` 48 | git submodule update --init --recursive 49 | ``` 50 | 51 | Build with: 52 | 53 | ``` 54 | make 55 | ``` 56 | 57 | Then you can run: 58 | ``` 59 | bin/go-websockproxy 60 | ``` 61 | 62 | # Example usage (as root) 63 | 64 | A simple command-line: 65 | ``` 66 | bin/go-websockproxy --listen-address=:8080 --tap-ipv4=10.5.0.1/16 --static-directory=jor1k --max-download-bandwidth=50kbps --max-upload-bandwidth=50kbps --log-level=info 67 | ``` 68 | 69 | A more complex command-line: 70 | ``` 71 | bin/go-websockproxy --cert-file=mycert.pem --key-file=mycert.key --mac-prefix="00:15 --auth-key="yoursecrethere" --max-download-bandwidth=50kbps --max-upload-bandwidth=50kbps --log-level=debug" 72 | ``` 73 | 74 | Once go-websockproxy is started, you may want to start a DHCP server as in: 75 | ``` 76 | dnsmasq -d --bind-interfaces --listen-address=10.3.0.1 --dhcp-range=10.3.0.50,10.3.0.200,12h --dhcp-option=option:router,10.3.0.1 --dhcp-option=option:dns-server,10.3.0.1 --log-dhcp 77 | ``` 78 | 79 | Additionally, IPv4 forwarding should be enabled if you want to allow the jor1k clients to connect to regular internet. 80 | 81 | To quickly generate a TLS certificate + key pair: https://golang.org/src/crypto/tls/generate_cert.go 82 | 83 | # License 84 | 85 | [GNU/GPLv2](./LICENSE) 86 | 87 | # Thanks 88 | 89 | Thanks to Benjamin Burns for the python version upon which I based this one and to Sebastian Macke for the great jor1k. 90 | -------------------------------------------------------------------------------- /hub.go: -------------------------------------------------------------------------------- 1 | /* go-websockproxy - https://github.com/gdm85/go-websockproxy 2 | Copyright (C) 2016 gdm85 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | package main 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "net" 24 | "strings" 25 | "sync" 26 | "time" 27 | 28 | "github.com/songgao/water/waterutil" 29 | "golang.org/x/net/websocket" 30 | ) 31 | 32 | const ( 33 | defaultFrameBufferSize = 100 34 | ) 35 | 36 | // Client is a websocket client managed by a Hub. 37 | type Client struct { 38 | upload, download BandwidthAllowance 39 | remoteAddress string 40 | ws *websocket.Conn 41 | authorized bool 42 | 43 | frameReceiver chan ([]byte) 44 | terminator chan (bool) 45 | 46 | mac net.HardwareAddr 47 | } 48 | 49 | // Hub is a websocket clients manager. 50 | type Hub struct { 51 | sync.Mutex 52 | clients map[*websocket.Conn]*Client 53 | clientsByMAC map[string]*Client 54 | } 55 | 56 | // RateLimiter is an interface to limit upload and/or download bandwidths. 57 | type RateLimiter interface { 58 | UploadThrottle(frameLen int) bool 59 | DownloadThrottle(frameLen int) bool 60 | } 61 | 62 | // Add will add a client to the hub and initialize its frames delivery and eventual bandwidth limiting features. 63 | func (h *Hub) Add(ws *websocket.Conn) *Client { 64 | h.Lock() 65 | c := &Client{ 66 | remoteAddress: ws.Request().RemoteAddr, 67 | ws: ws, 68 | authorized: authKey == "", // pre-authorize all clients when authorization is disabled 69 | frameReceiver: make(chan []byte, defaultFrameBufferSize), 70 | terminator: make(chan bool), 71 | } 72 | if uploadBandwidth != 0 { 73 | c.upload.rate = uploadBandwidth 74 | c.upload.allowance = uploadBandwidth 75 | c.upload.lastCheck = time.Now() 76 | } 77 | if downloadBandwidth != 0 { 78 | c.download.rate = downloadBandwidth 79 | c.download.allowance = downloadBandwidth 80 | c.download.lastCheck = time.Now() 81 | } 82 | 83 | h.clients[ws] = c 84 | h.Unlock() 85 | 86 | go func() { 87 | err := c.deliverFrames() 88 | if err != nil { 89 | ErrorPrintf("client %v: dropping client because of error during send: %v", c, err) 90 | h.Remove(c) 91 | } 92 | }() 93 | 94 | return c 95 | } 96 | 97 | // deliverFrames delivers the frames buffered in the receiving channel. 98 | func (c *Client) deliverFrames() error { 99 | for { 100 | select { 101 | case <-c.terminator: 102 | DebugPrintf("client %v: terminated delivery of received frames (%d pending)", len(c.frameReceiver)) 103 | return nil 104 | case frame := <-c.frameReceiver: 105 | if c.DownloadThrottle(len(frame)) { 106 | WarningPrintf("client %v, frame %v: discarding because of download rate limiting", c, frame) 107 | } else { 108 | err := websocket.Message.Send(c.ws, frame) 109 | if err != nil { 110 | return err 111 | } 112 | DebugPrintf("client %v, frame %v: sent", c, frame) 113 | } 114 | } 115 | } 116 | } 117 | 118 | // UploadThrottle returns true if the payload should be throttled. 119 | func (c *Client) UploadThrottle(frameLen int) bool { 120 | return c.upload.DoThrottle(frameLen) 121 | } 122 | 123 | // DownloadThrottle returns true if the payload should be throttled. 124 | func (c *Client) DownloadThrottle(frameLen int) bool { 125 | return c.download.DoThrottle(frameLen) 126 | } 127 | 128 | // String returns a human-readable descriptive text of the client. 129 | func (c *Client) String() string { 130 | return fmt.Sprintf("{remote=%s mac=%v authorized=%v pendingFrames=%d}", c.remoteAddress, c.mac, c.authorized, len(c.frameReceiver)) 131 | } 132 | 133 | // Remove will remove the client from the hub and terminate its delivery goroutine. 134 | func (h *Hub) Remove(c *Client) { 135 | h.Lock() 136 | if _, ok := h.clients[c.ws]; ok { 137 | // stop delivery of messages 138 | c.terminator <- true 139 | 140 | delete(h.clients, c.ws) 141 | if c.mac.String() != "" { 142 | delete(h.clientsByMAC, c.mac.String()) 143 | } 144 | DebugPrintf("deleted client %v", c) 145 | } 146 | h.Unlock() 147 | } 148 | 149 | // Clear will remove all clients and terminate their delivery goroutines. 150 | func (h *Hub) Clear() { 151 | h.Lock() 152 | for _, c := range h.clients { 153 | // stop delivery of messages 154 | c.terminator <- true 155 | DebugPrintf("deleted client %v", c) 156 | } 157 | h.clients = map[*websocket.Conn]*Client{} 158 | h.clientsByMAC = map[string]*Client{} 159 | h.Unlock() 160 | } 161 | 162 | // NewHub returns an initialized hub. 163 | func NewHub() *Hub { 164 | h := &Hub{} 165 | h.clients = map[*websocket.Conn]*Client{} 166 | h.clientsByMAC = map[string]*Client{} 167 | return h 168 | } 169 | 170 | // HandleSpecialFrame handles a special frame; currently only AUTH is supported, PING could be added here. 171 | func (c *Client) HandleSpecialFrame(payload []byte) (skipFrame, flagAsBad bool, e error) { 172 | if len(payload) < 8 { 173 | skipFrame = true 174 | e = fmt.Errorf("too short special frame payload (%d bytes)", len(payload)) 175 | return 176 | } 177 | 178 | prefix := string(payload[:5]) 179 | switch prefix { 180 | case "AUTH ": 181 | DebugPrintf("received auth frame: %q", string(payload)) 182 | if authKey == "" { 183 | e = errors.New("ignoring AUTH frame (authorization disabled on server side)") 184 | skipFrame = true 185 | return 186 | } 187 | if c.authorized { 188 | skipFrame = true 189 | e = errors.New("client already authorized, ignoring AUTH") 190 | return 191 | } 192 | key := string(payload[5:]) 193 | if key == authKey { 194 | c.authorized = true 195 | InfoPrintf("AUTH key accepted") 196 | skipFrame = true 197 | return 198 | } 199 | // failure to authorize 200 | e = errors.New("AUTH key not accepted") 201 | skipFrame = true 202 | // do not close the connection but put it in an idle loop 203 | flagAsBad = true 204 | return 205 | } 206 | e = errors.New("invalid special frame: " + prefix) 207 | skipFrame = true 208 | return 209 | } 210 | 211 | // CanSourceMac returns true if the client is misbehaving and should be blocked, and an error if client is not allowed to source frames from the specified MAC address. 212 | func (h *Hub) CanSourceMAC(c *Client, mac net.HardwareAddr) (bool, error) { 213 | h.Lock() 214 | src := mac.String() 215 | 216 | existingClient, ok := h.clientsByMAC[src] 217 | if !ok { 218 | /// 219 | /// first time client sends a ethernet frame, associate with this MAC 220 | /// 221 | 222 | // make sure this is a valid MAC address 223 | if src == "ff:ff:ff:ff:ff:ff" || strings.HasPrefix(src, "33:33") || strings.HasPrefix(src, "01:00:5e") { 224 | h.Unlock() 225 | return true, errors.New("MAC address is invalid") 226 | } 227 | 228 | // if MAC prefix whitelisting is enabled, validate against it 229 | if macPrefix != "" && !strings.HasPrefix(src, macPrefix) { 230 | h.Unlock() 231 | return true, errors.New("MAC address will not be accepted") 232 | } 233 | 234 | c.mac = mac 235 | h.clientsByMAC[src] = c 236 | InfoPrintf("client %v: now associated with MAC %s", c, src) 237 | h.Unlock() 238 | return false, nil 239 | } 240 | 241 | if c != existingClient { 242 | // very bad thing: the client is trying to send with the MAC of another client :( 243 | h.Unlock() 244 | return true, fmt.Errorf("client %v tried to send traffic as client %v", c, existingClient) 245 | } 246 | 247 | // AOK - client can send with this MAC 248 | h.Unlock() 249 | return false, nil 250 | } 251 | 252 | // SwitchFrame switches a frame to either broadcast addresses or local websocket clients; returns true if frame was handled and an error in case of delivery errors. 253 | // based on https://github.com/benjamincburns/websockproxy/blob/master/switchedrelay.py 254 | func (h *Hub) SwitchFrame(source RateLimiter, frame []byte) (bool, error) { 255 | h.Lock() 256 | defer h.Unlock() 257 | 258 | dst := waterutil.MACDestination(frame) 259 | if waterutil.IsBroadcast(dst) && waterutil.IsIPv4Multicast(dst) { 260 | // broadcast message to all known peers 261 | for _, peer := range h.clientsByMAC { 262 | peer.Download(frame) 263 | } 264 | if source != nil { 265 | // finally broadcast on TAP interface itself 266 | if source.UploadThrottle(len(frame)) { 267 | WarningPrintf("client %v, frame %v: discarding because of upload rate limiting", source, frame) 268 | } else { 269 | _, err := tap.Write(frame) 270 | if err != nil { 271 | return false, err 272 | } 273 | } 274 | } 275 | return true, nil 276 | } 277 | 278 | // send to a specific peer 279 | if peer, ok := h.clientsByMAC[dst.String()]; ok { 280 | peer.Download(frame) 281 | return true, nil 282 | } 283 | if source != nil { 284 | // send on TAP interface itself 285 | if source.UploadThrottle(len(frame)) { 286 | WarningPrintf("client %v, frame %v: discarding because of upload rate limiting", source, frame) 287 | } else { 288 | _, err := tap.Write(frame) 289 | if err != nil { 290 | return false, err 291 | } 292 | } 293 | return true, nil 294 | } 295 | return false, nil 296 | } 297 | 298 | // Download queues a frame for receipt into the websocket stream of a specific client; the call is non-blocking. 299 | func (c *Client) Download(frame []byte) { 300 | go func() { c.frameReceiver <- frame }() 301 | DebugPrintf("client %v, frame %v: queued for receipt", c, frame) 302 | } 303 | -------------------------------------------------------------------------------- /wsproxy.go: -------------------------------------------------------------------------------- 1 | /* go-websockproxy - https://github.com/gdm85/go-websockproxy 2 | Copyright (C) 2016 gdm85 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "os" 25 | "os/exec" 26 | 27 | flag "github.com/ogier/pflag" 28 | "github.com/songgao/water" 29 | "github.com/songgao/water/waterutil" 30 | "golang.org/x/net/websocket" 31 | ) 32 | 33 | // Frame is a TAP ethernet frame (byte array). 34 | type Frame []byte 35 | 36 | // String returns a human-readable description for the TAP frame, with eventual IPv4 packet decoding if frame contains an IPv4 payload. 37 | func (f Frame) String() string { 38 | if waterutil.MACEthertype(f) == waterutil.IPv4 { 39 | p := waterutil.MACPayload(f) 40 | return fmt.Sprintf("{%d bytes [%s](%s) -> [%s](%s) TTL=%d}", len(f), waterutil.MACSource(f), waterutil.IPv4Source(p), waterutil.MACDestination(f), waterutil.IPv4Destination(p), waterutil.IPv4TTL(p)) 41 | } 42 | return fmt.Sprintf("{%d bytes [%s] -> [%s]}", len(f), waterutil.MACSource(f), waterutil.MACDestination(f)) 43 | } 44 | 45 | // websocketHandler is the main websocekt connections handling entrypoint. 46 | func websocketHandler(ws *websocket.Conn) { 47 | var flaggedAsBad bool 48 | client := hub.Add(ws) 49 | for { 50 | var frame []byte 51 | err := websocket.Message.Receive(ws, &frame) 52 | if err != nil { 53 | if err == io.EOF { 54 | // EOF is considered normal for a websocket closing the connection 55 | hub.Remove(client) 56 | return 57 | } 58 | WarningPrintf("client %v: dropping after read error: %v", client, err) 59 | hub.Remove(client) 60 | return 61 | } 62 | 63 | if flaggedAsBad { 64 | // discard all frames of this connection, but keep it open to mitigate many reconnections 65 | DebugPrintf("frame %v sent to /dev/null", frame) 66 | continue 67 | } 68 | 69 | /// 70 | /// a new frame is available 71 | /// 72 | 73 | if len(frame) < 12 { 74 | // this frame can't possibly be good 75 | WarningPrintf("client %v: skipping too short frame (%d bytes)", client, len(frame)) 76 | continue 77 | } 78 | 79 | // special frames have an invalid source MAC made of 0s 80 | if string(frame[:6]) == "\x00\x00\x00\x00\x00\x00" { 81 | skipFrame, flagAsBad, err := client.HandleSpecialFrame(frame[6:]) 82 | if err != nil { 83 | WarningPrintf("client %v, frame %v: %v", client, Frame(frame), err) 84 | } 85 | if flagAsBad { 86 | flaggedAsBad = true 87 | continue 88 | } 89 | if skipFrame { 90 | continue 91 | } 92 | } 93 | 94 | // discard frames of clients that are not authorized 95 | if !client.authorized { 96 | WarningPrintf("client %v, frame %v: discarding unauthorized", client, Frame(frame)) 97 | if len(frame) < 60 { 98 | WarningPrintf("discarded: %s", string(frame)) 99 | } 100 | continue 101 | } 102 | 103 | /// 104 | /// not a special frame, parse as a normal TAP frame 105 | /// 106 | 107 | // check if client can send this frame with its source MAC 108 | flagAsBad, err := hub.CanSourceMAC(client, waterutil.MACSource(frame)) 109 | if err != nil { 110 | WarningPrintf("client %v, frame %v: %v", client, Frame(frame), err) 111 | if flagAsBad { 112 | flaggedAsBad = true 113 | } 114 | continue 115 | } 116 | 117 | switched, err := hub.SwitchFrame(client, frame) 118 | if err != nil { 119 | ErrorPrintf("client %v, frame %v: dropping client because of TAP switch error: %v", client, Frame(frame), err) 120 | hub.Remove(client) 121 | return 122 | } 123 | 124 | if !switched { 125 | DebugPrintf("client %v, frame %v: frame could not be switched") 126 | } 127 | } 128 | } 129 | 130 | func readTAPTraffic() error { 131 | frame := make([]byte, 1500+18) 132 | for { 133 | n, err := tap.Read(frame) 134 | if err != nil { 135 | return err 136 | } 137 | if n < 12 { 138 | WarningPrintf("discarding invalid frame with size of %d bytes read from TAP interface", n) 139 | continue 140 | } 141 | 142 | switched, err := hub.SwitchFrame(nil, frame) 143 | if err != nil { 144 | return err 145 | } 146 | 147 | if !switched { 148 | DebugPrintf("frame %v: could not switch from TAP interface", Frame(frame)) 149 | } 150 | } 151 | } 152 | 153 | type PrintFunc func(string, ...interface{}) 154 | 155 | // ErrorPrintf prints a (formatted) error to standard error. 156 | func ErrorPrintf(format string, a ...interface{}) { 157 | fmt.Fprintf(os.Stderr, "ERROR: "+format+"\n", a...) 158 | } 159 | func warningPrintf(format string, a ...interface{}) { 160 | fmt.Fprintf(os.Stderr, "WARNING: "+format+"\n", a...) 161 | } 162 | func debugPrintf(format string, a ...interface{}) { 163 | fmt.Printf("DEBUG: "+format+"\n", a...) 164 | } 165 | func infoPrintf(format string, a ...interface{}) { 166 | fmt.Printf(format+"\n", a...) 167 | } 168 | func dummyPrintf(format string, a ...interface{}) { 169 | // do nothing 170 | } 171 | 172 | var ( 173 | hub = NewHub() // clients management hub 174 | tap *water.Interface // TAP interface 175 | // set of functions to provide CLI logging output 176 | DebugPrintf, InfoPrintf, WarningPrintf PrintFunc 177 | 178 | uploadBandwidth, downloadBandwidth int64 179 | // CLI options follow: 180 | logLevel string 181 | listenAddress string 182 | staticDirectory string 183 | maxUploadBandwidth string 184 | maxDownloadBandwidth string 185 | tapName string // re-using an existing TAP is not yet supported 186 | tapIPv4 string 187 | authKey string 188 | macPrefix string 189 | certFile string 190 | keyFile string 191 | ) 192 | 193 | func init() { 194 | flag.StringVar(&tapIPv4, "tap-ipv4", "10.3.0.1/16", "IPv4 address for the TAP interface; used only when interface is created") 195 | flag.StringVar(&maxUploadBandwidth, "max-upload-bandwidth", "", "max upload bandwidth per client; leave empty for unlimited") 196 | flag.StringVar(&maxDownloadBandwidth, "max-download-bandwidth", "", "max upload bandwidth per client; leave empty for unlimited") 197 | flag.StringVar(&listenAddress, "listen-address", ":8000", "address to listen on for incoming websocket connections; URI is '/wstap'") 198 | flag.StringVar(&staticDirectory, "static-directory", "", "static files directory to serve at '/'; disabled by default") 199 | flag.StringVar(&logLevel, "log-level", "warning", "one of 'debug', 'info', 'warning', 'error'") 200 | flag.StringVar(&authKey, "auth-key", "", "accept TAP traffic via websockets only if authorized with this key; by default is disabled (accepts any traffic)") 201 | flag.StringVar(&macPrefix, "mac-prefix", "", "accept websockets traffic only with MACs starting with the specified prefix (default is disabled)") 202 | flag.StringVar(&certFile, "cert-file", "", "certificate for listening on TLS connections; by default TLS is disabled") 203 | flag.StringVar(&keyFile, "key-file", "", "key file for listening on TLS connections; by default TLS is disabled") 204 | } 205 | 206 | func main() { 207 | flag.Parse() 208 | 209 | switch logLevel { 210 | case "debug": 211 | DebugPrintf = debugPrintf 212 | InfoPrintf = infoPrintf 213 | WarningPrintf = warningPrintf 214 | case "info": 215 | DebugPrintf = dummyPrintf 216 | InfoPrintf = infoPrintf 217 | WarningPrintf = warningPrintf 218 | case "warning": 219 | DebugPrintf = dummyPrintf 220 | InfoPrintf = dummyPrintf 221 | WarningPrintf = warningPrintf 222 | case "error": 223 | DebugPrintf = dummyPrintf 224 | InfoPrintf = dummyPrintf 225 | WarningPrintf = dummyPrintf 226 | default: 227 | ErrorPrintf("invalid log level specified") 228 | os.Exit(1) 229 | } 230 | 231 | if (certFile != "" && keyFile == "") || (keyFile != "" && certFile == "") { 232 | ErrorPrintf("both certificate and key file should be specified in order to enable TLS connections") 233 | os.Exit(2) 234 | } 235 | 236 | var err error 237 | uploadBandwidth, err = parseBandwidth(maxUploadBandwidth) 238 | if err != nil { 239 | ErrorPrintf("invalid upload bandwidth specified: %v", err) 240 | os.Exit(3) 241 | } 242 | downloadBandwidth, err = parseBandwidth(maxDownloadBandwidth) 243 | if err != nil { 244 | ErrorPrintf("invalid download bandwidth specified: %v", err) 245 | os.Exit(4) 246 | } 247 | 248 | tap, err = water.NewTAP(tapName) 249 | if err != nil { 250 | ErrorPrintf("creating TAP interface: %v", err) 251 | os.Exit(5) 252 | } 253 | if err := exec.Command("ip", "link", "set", tap.Name(), "up").Run(); err != nil { 254 | ErrorPrintf("bringing TAP interface up: %v", err) 255 | } 256 | if err := exec.Command("ip", "addr", "add", tapIPv4, "brd", "+", "dev", tap.Name()).Run(); err != nil { 257 | ErrorPrintf("configuring TAP interface IPv4: %v", err) 258 | } 259 | InfoPrintf("device %s is up with IPv4 %s", tap.Name(), tapIPv4) 260 | 261 | if staticDirectory != "" { 262 | http.Handle("/", http.FileServer(http.Dir(staticDirectory))) 263 | } 264 | http.Handle("/wstap", websocket.Handler(websocketHandler)) 265 | 266 | InfoPrintf("listening on %s", listenAddress) 267 | 268 | mainFlow := make(chan error, 2) 269 | 270 | go func() { 271 | if keyFile == "" { 272 | mainFlow <- http.ListenAndServe(listenAddress, nil) 273 | } else { 274 | mainFlow <- http.ListenAndServeTLS(listenAddress, certFile, keyFile, nil) 275 | } 276 | }() 277 | 278 | go func() { 279 | // start a polling goroutine that reads and switches frames from the TAP interface 280 | mainFlow <- readTAPTraffic() 281 | }() 282 | 283 | err = <-mainFlow 284 | if err != nil { 285 | ErrorPrintf("%v", err) 286 | hub.Clear() 287 | os.Exit(7) 288 | } else { 289 | hub.Clear() 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------