Skip to content

Commit

Permalink
Merge branch 'master' into staging-client
Browse files Browse the repository at this point in the history
  • Loading branch information
rod-hynes committed Nov 16, 2021
2 parents ba370d8 + 75d665b commit 893ec1b
Show file tree
Hide file tree
Showing 477 changed files with 51,730 additions and 124,683 deletions.
64 changes: 20 additions & 44 deletions psiphon/LookupIP.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build android || linux || darwin
// +build android linux darwin

/*
Expand Down Expand Up @@ -25,7 +26,7 @@ import (
"context"
std_errors "errors"
"net"
"os"
"strconv"
"syscall"

"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
Expand Down Expand Up @@ -111,53 +112,28 @@ func bindLookupIP(
}
}

var ipv4 [4]byte
var ipv6 [16]byte
var domain int

// Get address type (IPv4 or IPv6)
if ipAddr.To4() != nil {
copy(ipv4[:], ipAddr.To4())
domain = syscall.AF_INET
} else if ipAddr.To16() != nil {
copy(ipv6[:], ipAddr.To16())
domain = syscall.AF_INET6
} else {
return nil, errors.TraceNew("invalid IP address for DNS server")
}

socketFd, err := syscall.Socket(domain, syscall.SOCK_DGRAM, 0)
if err != nil {
return nil, errors.Trace(err)
}
dialer := &net.Dialer{
Control: func(_, _ string, c syscall.RawConn) error {
var controlErr error
err := c.Control(func(fd uintptr) {

_, err = config.DeviceBinder.BindToDevice(socketFd)
if err != nil {
syscall.Close(socketFd)
return nil, errors.Tracef("BindToDevice failed with %s", err)
}
socketFD := int(fd)

// Connect socket to the server's IP address
// Note: no timeout or interrupt for this connect, as it's a datagram socket
if domain == syscall.AF_INET {
sockAddr := syscall.SockaddrInet4{Addr: ipv4, Port: DNS_PORT}
err = syscall.Connect(socketFd, &sockAddr)
} else if domain == syscall.AF_INET6 {
sockAddr := syscall.SockaddrInet6{Addr: ipv6, Port: DNS_PORT}
err = syscall.Connect(socketFd, &sockAddr)
}
if err != nil {
syscall.Close(socketFd)
return nil, errors.Trace(err)
_, err := config.DeviceBinder.BindToDevice(socketFD)
if err != nil {
controlErr = errors.Tracef("BindToDevice failed: %s", err)
return
}
})
if controlErr != nil {
return errors.Trace(controlErr)
}
return errors.Trace(err)
},
}

// Convert the syscall socket to a net.Conn, for use in the dns package
// This code block is from:
// https://github.com/golang/go/issues/6966

file := os.NewFile(uintptr(socketFd), "")
netConn, err := net.FileConn(file) // net.FileConn() dups socketFd
file.Close() // file.Close() closes socketFd
netConn, err := dialer.DialContext(
ctx, "udp", net.JoinHostPort(ipAddr.String(), strconv.Itoa(DNS_PORT)))
if err != nil {
return nil, errors.Trace(err)
}
Expand Down
218 changes: 30 additions & 188 deletions psiphon/TCPConn_bind.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows

/*
Expand Down Expand Up @@ -25,30 +26,16 @@ import (
"context"
"math/rand"
"net"
"os"
"strconv"
"syscall"

"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
"github.com/creack/goselect"
)

// tcpDial is the platform-specific part of DialTCP
//
// To implement socket device binding, the lower-level syscall APIs are used.
// The sequence of syscalls in this implementation are taken from:
// https://github.com/golang/go/issues/6966
// (originally: https://code.google.com/p/go/issues/detail?id=6966)
//
// TODO: use https://golang.org/pkg/net/#Dialer.Control, introduced in Go 1.11?
func tcpDial(ctx context.Context, addr string, config *DialConfig) (net.Conn, error) {

// Get the remote IP and port, resolving a domain name if necessary
host, strPort, err := net.SplitHostPort(addr)
if err != nil {
return nil, errors.Trace(err)
}
port, err := strconv.Atoi(strPort)
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down Expand Up @@ -100,190 +87,45 @@ func tcpDial(ctx context.Context, addr string, config *DialConfig) (net.Conn, er

for _, index := range permutedIndexes {

// Get address type (IPv4 or IPv6)

var ipv4 [4]byte
var ipv6 [16]byte
var domain int
var sockAddr syscall.Sockaddr

ipAddr := ipAddrs[index]
if ipAddr != nil && ipAddr.To4() != nil {
copy(ipv4[:], ipAddr.To4())
domain = syscall.AF_INET
} else if ipAddr != nil && ipAddr.To16() != nil {
copy(ipv6[:], ipAddr.To16())
domain = syscall.AF_INET6
} else {
lastErr = errors.TraceNew("invalid IP address")
continue
}
if domain == syscall.AF_INET {
sockAddr = &syscall.SockaddrInet4{Addr: ipv4, Port: port}
} else if domain == syscall.AF_INET6 {
sockAddr = &syscall.SockaddrInet6{Addr: ipv6, Port: port}
}
dialer := &net.Dialer{
Control: func(_, _ string, c syscall.RawConn) error {
var controlErr error
err := c.Control(func(fd uintptr) {

// Create a socket and bind to device, when configured to do so
socketFD := int(fd)

socketFD, err := syscall.Socket(domain, syscall.SOCK_STREAM, 0)
if err != nil {
lastErr = errors.Trace(err)
continue
}

syscall.CloseOnExec(socketFD)

setAdditionalSocketOptions(socketFD)

if config.BPFProgramInstructions != nil {
err = setSocketBPF(config.BPFProgramInstructions, socketFD)
if err != nil {
syscall.Close(socketFD)
lastErr = errors.Trace(err)
continue
}
}

if config.DeviceBinder != nil {
_, err = config.DeviceBinder.BindToDevice(socketFD)
if err != nil {
syscall.Close(socketFD)
lastErr = errors.Tracef("BindToDevice failed with %s", err)
continue
}
}

// Connect socket to the server's IP address

err = syscall.SetNonblock(socketFD, true)
if err != nil {
syscall.Close(socketFD)
lastErr = errors.Trace(err)
continue
}

err = syscall.Connect(socketFD, sockAddr)
if err != nil {
if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINPROGRESS {
syscall.Close(socketFD)
lastErr = errors.Trace(err)
continue
}
}

// Use a control pipe to interrupt if the dial context is done (timeout or
// interrupted) before the TCP connection is established.

var controlFDs [2]int
err = syscall.Pipe(controlFDs[:])
if err != nil {
syscall.Close(socketFD)
lastErr = errors.Trace(err)
continue

}
setAdditionalSocketOptions(socketFD)

for _, controlFD := range controlFDs {
syscall.CloseOnExec(controlFD)
err = syscall.SetNonblock(controlFD, true)
if err != nil {
break
}
}

if err != nil {
syscall.Close(socketFD)
lastErr = errors.Trace(err)
continue
}

resultChannel := make(chan error)

go func() {

readSet := goselect.FDSet{}
readSet.Set(uintptr(controlFDs[0]))
writeSet := goselect.FDSet{}
writeSet.Set(uintptr(socketFD))

max := socketFD
if controlFDs[0] > max {
max = controlFDs[0]
}

err := goselect.Select(max+1, &readSet, &writeSet, nil, -1)

if err == nil && !writeSet.IsSet(uintptr(socketFD)) {
err = errors.TraceNew("interrupted")
}

resultChannel <- err
}()

done := false
select {
case err = <-resultChannel:
case <-ctx.Done():
err = ctx.Err()
// Interrupt the goroutine
// TODO: if this Write fails, abandon the goroutine instead of hanging?
var b [1]byte
syscall.Write(controlFDs[1], b[:])
<-resultChannel
done = true
}

syscall.Close(controlFDs[0])
syscall.Close(controlFDs[1])

if err != nil {
syscall.Close(socketFD)

if done {
// Skip retry as dial context has timed out of been canceled.
return nil, errors.Trace(err)
}

lastErr = errors.Trace(err)
continue
}
if config.BPFProgramInstructions != nil {
err := setSocketBPF(config.BPFProgramInstructions, socketFD)
if err != nil {
controlErr = errors.Tracef("setSocketBPF failed: %s", err)
return
}
}

err = syscall.SetNonblock(socketFD, false)
if err != nil {
syscall.Close(socketFD)
lastErr = errors.Trace(err)
continue
if config.DeviceBinder != nil {
_, err := config.DeviceBinder.BindToDevice(socketFD)
if err != nil {
controlErr = errors.Tracef("BindToDevice failed: %s", err)
return
}
}
})
if controlErr != nil {
return errors.Trace(controlErr)
}
return errors.Trace(err)
},
}

// Convert the socket fd to a net.Conn
// This code block is from:
// https://github.com/golang/go/issues/6966

file := os.NewFile(uintptr(socketFD), "")
conn, err := net.FileConn(file) // net.FileConn() dups socketFD
file.Close() // file.Close() closes socketFD
conn, err := dialer.DialContext(
ctx, "tcp", net.JoinHostPort(ipAddrs[index].String(), port))
if err != nil {
lastErr = errors.Trace(err)
continue
}

// Handle the case where net.FileConn produces a Conn where RemoteAddr
// unexpectedly returns nil: https://github.com/golang/go/issues/23022.
//
// The net.Conn interface indicates that RemoteAddr returns a usable value,
// and code such as crypto/tls, in loadSession/clientSessionCacheKey, uses
// the RemoteAddr return value without a nil check, resulting in an panic.
//
// As the most likely explanation for this net.FileConn condition is
// getpeername returning ENOTCONN due to the socket connection closing
// during the net.FileConn execution, we choose to abort this dial rather
// than try to mask the RemoteAddr issue.
if conn.RemoteAddr() == nil {
conn.Close()
return nil, errors.TraceNew("RemoteAddr returns nil")
}

return &TCPConn{Conn: conn}, nil
}

Expand Down
8 changes: 5 additions & 3 deletions psiphon/common/buildinfo/buildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ func (bi *BuildInfo) ToMap() map[string]interface{} {

// GetBuildInfo returns an instance of the BuildInfo struct
func GetBuildInfo() *BuildInfo {
if strings.TrimSpace(dependencies) == "" {
dependencies = "{}"

deps := strings.TrimSpace(dependencies)
if deps == "" {
deps = "{}"
}

buildInfo := BuildInfo{
Expand All @@ -94,7 +96,7 @@ func GetBuildInfo() *BuildInfo {
BuildRev: strings.TrimSpace(buildRev),
GoVersion: strings.TrimSpace(goVersion),
GomobileVersion: strings.TrimSpace(gomobileVersion),
Dependencies: json.RawMessage(strings.TrimSpace(dependencies)),
Dependencies: json.RawMessage(deps),
ValuesRev: values.GetRevision(),
}

Expand Down
4 changes: 2 additions & 2 deletions psiphon/common/obfuscator/passthrough_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ func TestTLSPassthrough(t *testing.T) {
timeDiff = -timeDiff
}

if timeDiff.Microseconds() > 100 {
t.Fatalf("unexpected elapsed time difference")
if timeDiff.Microseconds() > 500 {
t.Fatalf("unexpected elapsed time difference: %v", timeDiff)
}

// test: invalid message length and elapsed time
Expand Down
7 changes: 7 additions & 0 deletions psiphon/common/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ func QUICVersionIsObfuscated(version string) bool {
version == QUIC_VERSION_DECOY_V1
}

func QUICVersionUsesPathMTUDiscovery(version string) bool {
return version != QUIC_VERSION_GQUIC39 &&
version != QUIC_VERSION_GQUIC43 &&
version != QUIC_VERSION_GQUIC44 &&
version != QUIC_VERSION_OBFUSCATED
}

type QUICVersions []string

func (versions QUICVersions) Validate() error {
Expand Down
Loading

0 comments on commit 893ec1b

Please sign in to comment.