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
|
// 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"
"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
addr string = "[::1]:0"
vpnAddr string
cfgFile string
stateDir string
)
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")
}
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)
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)
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)
}
log.Println("DEBUG:", listenConfig)
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)
}
}
// obfsConn 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")
}
}
// a stock copy loop. let's not dwell too much on 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?
left.Close()
right.Close()
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
}
}
|