Newer
Older
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* This file is based off goptlib's dummy-[client,server].go files.
*/
// obfs4 pluggable transport. Works only as a managed proxy.
//
// Client usage (in torrc):
// UseBridges 1
// Bridge obfs4 X.X.X.X:YYYY <fingerprint> public-key=<Base64 Bridge public key> node-id=<Base64 Bridge Node ID>
// ClientTransportPlugin obfs4 exec obfs4proxy
//
// Server usage (in torrc):
// BridgeRelay 1
// ORPort 9001
// ExtORPort 6669
// ServerTransportPlugin obfs4 exec obfs4proxy
// ServerTransportOptions obfs4 private-key=<Base64 Bridge private key> node-id=<Base64 Node ID> drbg-seed=<Base64 DRBG seed>
//
// Because the pluggable transport requires arguments, obfs4proxy requires
// tor-0.2.5.x to be useful.
package main
import (
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"path"
"sync"
"syscall"
"github.com/yawning/obfs4"
"github.com/yawning/obfs4/ntor"
)
const (
obfs4Method = "obfs4"
obfs4LogFile = "obfs4proxy.log"
)
var ptListeners []net.Listener
// When a connection handler starts, +1 is written to this channel; when it
// ends, -1 is written.
var handlerChan = make(chan int)
func logAndRecover() {
if err := recover(); err != nil {
log.Println("[ERROR] Panic:", err)
}
}
func copyLoop(a, b net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer b.Close()
defer a.Close()
_, err := io.Copy(b, a)
if err != nil {
log.Printf("[WARN] Connection closed: %s", err)
}
defer a.Close()
defer b.Close()
_, err := io.Copy(a, b)
if err != nil {
log.Printf("[WARN] Connection closed: %s", err)
}
}()
wg.Wait()
}
func serverHandler(conn *obfs4.Obfs4Conn, info *pt.ServerInfo) error {
defer conn.Close()
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
// Handshake with the client.
err := conn.ServerHandshake()
log.Printf("[WARN] server: Handshake failed: %s", err)
return err
}
or, err := pt.DialOr(info, conn.RemoteAddr().String(), obfs4Method)
if err != nil {
log.Printf("[ERROR] server: DialOr failed: %s", err)
return err
}
defer or.Close()
copyLoop(or, conn)
return nil
}
func serverAcceptLoop(ln *obfs4.Obfs4Listener, info *pt.ServerInfo) error {
defer ln.Close()
for {
conn, err := ln.AcceptObfs4()
if err != nil {
if e, ok := err.(net.Error); ok && !e.Temporary() {
return err
}
continue
}
go serverHandler(conn, info)
}
}
func serverSetup() bool {
launch := false
var err error
ptServerInfo, err := pt.ServerSetup([]string{obfs4Method})
if err != nil {
return launch
}
for _, bindaddr := range ptServerInfo.Bindaddrs {
switch bindaddr.MethodName {
case obfs4Method:
// Handle the mandetory arguments.
privateKey, ok := bindaddr.Options.Get("private-key")
if !ok {
pt.SmethodError(bindaddr.MethodName, "needs a private-key option")
break
}
nodeID, ok := bindaddr.Options.Get("node-id")
if !ok {
pt.SmethodError(bindaddr.MethodName, "needs a node-id option")
seed, ok := bindaddr.Options.Get("drbg-seed")
if !ok {
pt.SmethodError(bindaddr.MethodName, "needs a drbg-seed option")
break
}
// Initialize the listener.
ln, err := obfs4.ListenObfs4("tcp", bindaddr.Addr.String(), nodeID,
privateKey, seed)
if err != nil {
pt.SmethodError(bindaddr.MethodName, err.Error())
break
}
// Report the SMETHOD including the parameters.
args := pt.Args{}
args.Add("node-id", nodeID)
args.Add("public-key", ln.PublicKey())
go serverAcceptLoop(ln, &ptServerInfo)
pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args)
ptListeners = append(ptListeners, ln)
launch = true
default:
pt.SmethodError(bindaddr.MethodName, "no such method")
}
}
pt.SmethodsDone()
return launch
}
func clientHandler(conn *pt.SocksConn) error {
defer conn.Close()
// Extract the peer's node ID and public key.
nodeID, ok := conn.Req.Args.Get("node-id")
if !ok {
log.Printf("[ERROR] client: missing node-id argument")
conn.Reject()
return nil
}
publicKey, ok := conn.Req.Args.Get("public-key")
if !ok {
log.Printf("[ERROR] client: missing public-key argument")
conn.Reject()
return nil
}
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
remote, err := obfs4.DialObfs4("tcp", conn.Req.Target, nodeID, publicKey)
log.Printf("[ERROR] client: Handshake failed: %s", err)
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
conn.Reject()
return err
}
defer remote.Close()
err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
if err != nil {
return err
}
copyLoop(conn, remote)
return nil
}
func clientAcceptLoop(ln *pt.SocksListener) error {
defer ln.Close()
for {
conn, err := ln.AcceptSocks()
if err != nil {
if e, ok := err.(net.Error); ok && !e.Temporary() {
return err
}
continue
}
go clientHandler(conn)
}
}
func clientSetup() bool {
launch := false
ptClientInfo, err := pt.ClientSetup([]string{obfs4Method})
if err != nil {
log.Fatal(err)
return launch
}
for _, methodName := range ptClientInfo.MethodNames {
switch methodName {
case obfs4Method:
ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
if err != nil {
pt.CmethodError(methodName, err.Error())
break
}
go clientAcceptLoop(ln)
pt.Cmethod(methodName, ln.Version(), ln.Addr())
ptListeners = append(ptListeners, ln)
launch = true
default:
pt.CmethodError(methodName, "no such method")
}
}
pt.CmethodsDone()
return launch
}
func ptIsClient() bool {
env := os.Getenv("TOR_PT_CLIENT_TRANSPORTS")
return env != ""
}
func ptIsServer() bool {
env := os.Getenv("TOR_PT_SERVER_TRANSPORTS")
return env != ""
}
func ptGetStateDir() (dir string, err error) {
dir = os.Getenv("TOR_PT_STATE_LOCATION")
err = os.MkdirAll(dir, 0755)
log.Fatalf("[ERROR] Failed to create path: %s", err)
func (d discardWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func ptInitializeLogging(enable bool) {
if enable {
dir, err := ptGetStateDir()
if err != nil || dir == "" {
return
}
f, err := os.OpenFile(path.Join(dir, obfs4LogFile), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatalf("[ERROR] Failed to open log file: %s", err)
}
log.SetOutput(f)
} else {
var d discardWriter
log.SetOutput(d)
}
}
func generateServerParams(id string) {
rawID, err := hex.DecodeString(id)
if err != nil {
fmt.Println("Failed to hex decode id:", err)
return
}
parsedID, err := ntor.NewNodeID(rawID)
if err != nil {
fmt.Println("Failed to parse id:", err)
return
}
fmt.Println("Generated node-id:", parsedID.Base64())
keypair, err := ntor.NewKeypair(false)
if err != nil {
fmt.Println("Failed to generate keypair:", err)
return
}
seed, err := obfs4.NewDrbgSeed()
if err != nil {
fmt.Println("Failed to generate DRBG seed:", err)
return
}
fmt.Println("Generated private-key:", keypair.Private().Base64())
fmt.Println("Generated public-key:", keypair.Public().Base64())
fmt.Println("Generated drbg-seed:", seed.Base64())
fmt.Println()
fmt.Println("Client config: ")
fmt.Printf(" Bridge obfs4 <IP Address:Port> %s node-id=%s public-key=%s\n",
id, parsedID.Base64(), keypair.Public().Base64())
fmt.Println()
fmt.Println("Server config:")
fmt.Printf(" ServerTransportOptions obfs4 node-id=%s private-key=%s drbg-seed=%s\n",
parsedID.Base64(), keypair.Private().Base64(), seed.Base64())
}
func main() {
// Some command line args.
genParams := flag.String("genServerParams", "", "Generate server params given a bridge fingerprint.")
doLogging := flag.Bool("enableLogging", false, "Log to TOR_PT_STATE_LOCATION/obfs4proxy.log")
flag.Parse()
if *genParams != "" {
generateServerParams(*genParams)
os.Exit(0)
}
// Initialize pt logging.
// Go through the pt protocol and initialize client or server mode.
launched := false
if ptIsClient() {
launched = clientSetup()
} else if ptIsServer() {
launched = serverSetup()
}
if !launched {
log.Fatal("[ERROR] obfs4proxy must be run as a managed transport or server")
log.Println("[INFO] obfs4proxy - Launched and listening")
defer func() {
log.Println("[INFO] obfs4proxy - Terminated")
}()
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
// Handle termination notification.
numHandlers := 0
var sig os.Signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// wait for first signal
sig = nil
for sig == nil {
select {
case n := <-handlerChan:
numHandlers += n
case sig = <-sigChan:
}
}
for _, ln := range ptListeners {
ln.Close()
}
if sig == syscall.SIGTERM {
return
}
// wait for second signal or no more handlers
sig = nil
for sig == nil && numHandlers != 0 {
select {
case n := <-handlerChan:
numHandlers += n
case sig = <-sigChan:
}
}
}
/* vim :set ts=4 sw=4 sts=4 noet : */