-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathmain.go
244 lines (223 loc) · 8.47 KB
/
main.go
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
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
// udptunnel is a daemon that sets up a point-to-point virtual private network
// between two hosts:
// * A client host that may be behind an obtrusive NAT that frequently drops
// TCP connections, but happens to pass UDP traffic reliably.
// * A server host that is internet-accessible.
//
// This only supports Linux.
//
// Example Setup
//
// The udptunnel is setup by running it on two different hosts, one in a server
// configuration, and the other in client configuration. The difference between
// a server or client is determined by the NetworkAddress field in the
// configuration. If the address has an empty host portion, then the daemon
// operates in server mode. Otherwise, the daemon operates in client mode and
// will use the host to dial the server.
//
// Example server config:
// {"TunnelAddress": "10.0.0.1", "NetworkAddress": ":8000", "AllowedPorts": [22]}
// Example client config:
// {"TunnelAddress": "10.0.0.2", "NetworkAddress": "example.com:8000", "AllowedPorts": [22]}
//
// See the TunnelConfig struct for more details.
//
// Security Considerations
//
// TUN traffic is sent ad-verbatim between the two endpoints via unencrypted
// UDP traffic. The intended use case is to run a secure protocol (like SSH;
// see github.com/dsnet/sshtunnel) on top of this simple VPN. In order to
// prevent attackers from connecting to other locally binded sockets on the
// endpoints, a simple port filter is built-in to restrict IP traffic to only
// the specified ports. Users of udptunnel should also setup iptable rules as
// a secondary measure to restrict malicious traffic.
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/signal"
"path"
"runtime"
"syscall"
"time"
"github.com/dsnet/golib/jsonfmt"
)
// Version of the udptunnel binary. May be set by linker when building.
var version string
type TunnelConfig struct {
// LogFile is where the tunnel daemon directs its output log.
// If the path is empty, then the server outputs to os.Stderr.
LogFile string `json:",omitempty"`
// TunnelDevice is the name of the TUN device.
//
// This field is optional on Linux, and ignored on Darwin.
// The default value is some device name assigned by the operating system.
TunnelDevice string
// TunnelAddress is the private IPv4 address for the local endpoint.
// The client and server should have different IP addresses both in the
// 255.255.255.0 subnet mask.
//
// This field is required on both Linux and Darwin.
// Recommended values are 10.0.0.1 and 10.0.0.2 for the server and client.
TunnelAddress string
// TunnelPeerAddress is the private IPv4 address for the remote endpoint.
// The client and server should have different IP addresses both in the
// 255.255.255.0 subnet mask.
//
// This field is ignored on Linux, and required on Darwin.
// On Darwin, the recommended value is 10.0.0.1 or 10.0.0.2,
// depending on which value does not conflict with the local TunnelAddress.
TunnelPeerAddress string
// NetworkAddress is the public host and port for UDP traffic.
// If the host portion is empty, then the daemon is operating in
// server mode and will bind on the specified port (e.g., ":8000").
// Otherwise, the daemon is operating in client mode and will use the
// specified host to communicate with the server (e.g., "example.com:8000").
NetworkAddress string
// AllowedPorts is a list of allowed UDP and TCP ports.
// Since udptunnel sends traffic as unencrypted messages,
// only protocols that provide application-layer security (like SSH)
// should be used.
//
// The set of allowed ports must match on both the client and server.
AllowedPorts []uint16
// HeartbeatInterval is the amount of time in seconds without any
// outbound traffic to wait before the tunnel client will send a heartbeat
// message to the server. In the event that the client's address changed,
// this informs the server of the new client address.
//
// This field only applies to the client.
// The default value is 30.
HeartbeatInterval *uint
// PacketMagic is used to generate a sequence of bytes that is prepended to
// every TUN packet sent over UDP. Only inbound messages carrying the
// magic sequence will be accepted. This mechanism is used as a trivial way
// to protect against denial-of-service attacks by ensuring the server only
// responds to remote IP addresses that are validated.
//
// This validation mechanism is only intended to protect against adversaries
// with the ability to create arbitrary spoof UDP packets. It does not
// protect against man-in-the-middle (MITM) attacks since any attacker with
// the ability to intercept traffic already has the capability to block
// communication between the client and server.
//
// This value must match on both the client and server.
PacketMagic string `json:",omitempty"`
}
func loadConfig(conf string) (tunn tunnel, logger *log.Logger, closer func() error) {
var logBuf bytes.Buffer
logger = log.New(io.MultiWriter(os.Stderr, &logBuf), "", log.Ldate|log.Ltime|log.Lshortfile)
var hash string
if b, _ := ioutil.ReadFile(os.Args[0]); len(b) > 0 {
hash = fmt.Sprintf("%x", sha256.Sum256(b))
}
// Load configuration file.
var config TunnelConfig
c, err := ioutil.ReadFile(conf)
if err != nil {
logger.Fatalf("unable to read config: %v", err)
}
if c, err = jsonfmt.Format(c, jsonfmt.Standardize()); err != nil {
logger.Fatalf("unable to parse config: %v", err)
}
if err := json.Unmarshal(c, &config); err != nil {
logger.Fatalf("unable to decode config: %v", err)
}
if config.TunnelAddress == "" {
logger.Fatal("required TunnelAddress field must be specified")
}
if config.TunnelPeerAddress == "" && runtime.GOOS == "darwin" {
logger.Fatal("required TunnelPeerAddress field must be specified on darwin")
}
if config.TunnelAddress == config.TunnelPeerAddress {
logger.Fatal("TunnelAddress and TunnelPeerAddress must not conflict")
}
if config.HeartbeatInterval == nil {
config.HeartbeatInterval = new(uint)
*config.HeartbeatInterval = 30
}
host, _, _ := net.SplitHostPort(config.NetworkAddress)
serverMode := host == ""
// Print the configuration.
var b bytes.Buffer
enc := json.NewEncoder(&b)
enc.SetEscapeHTML(false)
enc.SetIndent("", "\t")
enc.Encode(struct {
TunnelConfig
BinaryVersion string `json:",omitempty"`
BinarySHA256 string `json:",omitempty"`
}{config, version, hash})
logger.Printf("loaded config:\n%s", b.String())
// Setup the log output.
if config.LogFile == "" {
logger.SetOutput(os.Stderr)
closer = func() error { return nil }
} else {
f, err := os.OpenFile(config.LogFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
if err != nil {
logger.Fatalf("error opening log file: %v", err)
}
f.Write(logBuf.Bytes()) // Write log output prior to this point
logger.Printf("suppress stderr logging (redirected to %s)", f.Name())
logger.SetOutput(f)
closer = f.Close
}
if _, _, err := net.SplitHostPort(config.NetworkAddress); err != nil {
logger.Fatalf("invalid network address: %v", err)
}
if net.ParseIP(config.TunnelAddress).To4() == nil {
logger.Fatalf("private tunnel address must be valid IPv4 address")
}
if len(config.AllowedPorts) == 0 {
logger.Fatalf("no allowed ports specified")
}
tunn = tunnel{
server: serverMode,
tunDevName: config.TunnelDevice,
tunLocalAddr: config.TunnelAddress,
tunRemoteAddr: config.TunnelPeerAddress,
netAddr: config.NetworkAddress,
ports: config.AllowedPorts,
magic: config.PacketMagic,
beatInterval: time.Second * time.Duration(*config.HeartbeatInterval),
log: logger,
}
return tunn, logger, closer
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, "\t%s CONFIG_PATH\n", os.Args[0])
os.Exit(1)
}
tunn, logger, closer := loadConfig(os.Args[1])
defer closer()
// Setup signal handler to initiate shutdown.
ctx, cancel := context.WithCancel(context.Background())
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
logger.Printf("received %v - initiating shutdown", <-sigc)
cancel()
}()
// Start the VPN tunnel.
if tunn.server {
logger.Printf("%s starting in server mode", path.Base(os.Args[0]))
} else {
logger.Printf("%s starting in client mode", path.Base(os.Args[0]))
}
defer logger.Printf("%s shutdown", path.Base(os.Args[0]))
tunn.run(ctx)
}