summaryrefslogtreecommitdiff
path: root/server/main.go
blob: 3ade4dc7913c56b3e83d2f8ac743d03d058ab4a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// The obfsproxy command creates a SOCKS5 obfuscating proxy.
package main

import (
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/signal"

	"0xacab.org/leap/obfsvpn"
	pt "git.torproject.org/pluggable-transports/goptlib.git"
)

const transportName = "obfs4"

type Config struct {
	NodeID     string `json:"node-id"`
	PrivateKey string `json:"private-key"`
	PublicKey  string `json:"public-key"`
	DRBGSeed   string `json:"drbg-seed"`
	IatMode    int    `json:"iat-mode"`
}

func main() {
	// Setup logging.
	logger := log.New(os.Stderr, "", log.LstdFlags)
	debug := log.New(io.Discard, "DEBUG ", log.LstdFlags)

	// Setup command line flags.
	var (
		verbose  bool
		vpnAddr  string
		cfgFile  string
		stateDir string
		addr     = "[::1]:0"
	)
	flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flags.BoolVar(&verbose, "v", verbose, "Enable verbose logging")
	flags.StringVar(&addr, "addr", addr, "The address to listen on for client connections")
	flags.StringVar(&vpnAddr, "vpn", vpnAddr, "The address of the OpenVPN server to connect to")
	flags.StringVar(&cfgFile, "c", cfgFile, "The JSON config file to load")
	flags.StringVar(&stateDir, "state", stateDir, "A directory in which to store bridge state")
	err := flags.Parse(os.Args[1:])
	if err != nil {
		logger.Fatalf("error parsing flags: %v", err)
	}

	if vpnAddr == "" {
		flags.PrintDefaults()
		logger.Fatal("must specify -vpn")
	}

	// TODO this needs to be configurable if we switch to UDP mode for OpenVPN.
	tcpVPNAddr, err := net.ResolveTCPAddr("tcp", vpnAddr)
	log.Println("target:", tcpVPNAddr)
	if err != nil {
		logger.Fatalf("error resolving VPN address: %v", err)
	}

	var cfg Config

	fd, err := os.Open(cfgFile) //#nosec G304
	log.Println("opening:", cfgFile)
	if err != nil {
		logger.Fatalf("error opening config file: %v", err)
	}
	err = json.NewDecoder(fd).Decode(&cfg)
	if err != nil {
		logger.Fatalf("error decoding config: %v", err)
	}

	// Configure logging.
	if verbose {
		debug.SetOutput(os.Stderr)
	}

	log.Println("config:", cfg)

	// Setup graceful shutdown.
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)

	// TODO pass a "mode" ? (kcp)
	listenConfig, err := obfsvpn.NewListenConfig(
		cfg.NodeID, cfg.PrivateKey, cfg.PublicKey,
		cfg.DRBGSeed,
		stateDir,
	)
	if err != nil {
		logger.Fatalf("Error creating listener from config: %v", err)
	}

	logger.Printf("DEBUG: %v", listenConfig)

	// TODO: pass kcp mode
	ln, err := listenConfig.Listen(ctx, "tcp", addr)

	if err != nil {
		logger.Fatalf("error binding to %s: %v", addr, err)
	}

	go func() {
		<-ctx.Done()
		// Stop releases the signal handling and falls back to the default behavior,
		// so sending another interrupt will immediately terminate.
		stop()
		logger.Printf("shutting down…")
		err := ln.Close()
		if err != nil {
			logger.Printf("error closing listener: %v", err)
		}
	}()

	info := &pt.ServerInfo{
		OrAddr: tcpVPNAddr,
	}

	logger.Printf("Listening on %s…", ln.Addr())

	for {
		conn, err := ln.Accept()
		if err != nil {
			debug.Printf("error accepting connection: %v", err)
			return
		}
		debug.Printf("accepted connection %v…", conn)
		go proxyConn(ctx, info, conn, logger, debug)
	}
}

// proxyConn is a connection to the obfs4 client that we have accepted. we will dial to the remote contained in info
func proxyConn(ctx context.Context, info *pt.ServerInfo, obfsConn net.Conn, logger, debug *log.Logger) {
	defer func() {
		err := obfsConn.Close()
		if err != nil {
			debug.Printf("Error closing connection: %v", err)
		}
	}()

	// FIXME scrub ips in other than debug mode!
	log.Println("Dialing:", info.OrAddr)
	log.Println("Obfs4 client:", obfsConn.RemoteAddr().String())

	/*
		in the case of Tor, pt.DialOr returns a *net.TCPConn after dialing info.OrAddr.
		in the vpn case (or any transparent proxy really), we do use
		the pt.DialOr method to simply get a dialer to our upstream VPN remote.

		keeping this terminology is a bit stupid and slightly confusing, instead
		we could get the clearConn just by doing:

		s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr)
		if err != nil {
			return nil, err
		}

		that is precisely what the code in ptlib is doing.

		We also aspire at being a generic PT at some point, so perhaps it's better to keep the usage
		of DialOr?

		Maybe not, and so we don't need to keep the confusing info struct.
	*/

	// we'll refer to the connection to the usptream node as "clearConn", as opposed to the obfuscated conn.
	// but for sure openvpn or whatever protocol you wrap has its own layer of encryption :)

	clearConn, err := pt.DialOr(info, obfsConn.RemoteAddr().String(), transportName)

	if err != nil {
		logger.Printf("error dialing remote: %v", err)
		return
	}

	if err = CopyLoop(clearConn, obfsConn); err != nil {
		debug.Printf("%s - closed connection: %s", "obfsvpn", err.Error())
	} else {
		debug.Printf("%s - closed connection", "obfsvpn")
	}
}

// CopyLoop is a standard copy loop. We don't care too much who's client and
// who's server
func CopyLoop(left net.Conn, right net.Conn) error {

	fmt.Println("--> Entering copy loop.")

	if left == nil {
		fmt.Fprintln(os.Stderr, "--> Copy loop has a nil connection (left).")
		return errors.New("copy loop has a nil connection (left)")
	}

	if right == nil {
		fmt.Fprintln(os.Stderr, "--> Copy loop has a nil connection (right).")
		return errors.New("copy loop has a nil connection (right)")
	}

	// Note: right is always the pt connection.
	lockL := make(chan bool)
	lockR := make(chan bool)
	errChan := make(chan error)

	go CopyLeftToRight(left, right, lockL, errChan)
	go CopyRightToLeft(left, right, lockR, errChan)

	leftUp := true
	rightUp := true

	var copyErr error

	for leftUp || rightUp {
		select {
		case <-lockL:
			leftUp = false
		case <-lockR:
			rightUp = false
		case copyErr = <-errChan:
			log.Println("Error while copying")
		}
	}

	// XXX better to defer?
	err := left.Close()
	if err != nil {
		fmt.Fprintln(os.Stderr, "error closing left connection: ", err.Error())
	}
	err = right.Close()
	if err != nil {
		fmt.Fprintln(os.Stderr, "error closing right connection: ", err.Error())
	}

	return copyErr
}

// TODO check for data races

func CopyLeftToRight(l net.Conn, r net.Conn, ll chan bool, errChan chan error) {
	_, e := io.Copy(r, l)
	ll <- true
	if e != nil {
		errChan <- e
	}
}

func CopyRightToLeft(l net.Conn, r net.Conn, lr chan bool, errChan chan error) {
	_, e := io.Copy(l, r)
	lr <- true
	if e != nil {
		errChan <- e
	}
}