Skip to content

Commit

Permalink
Bypassing disallowed ports in SOCKS proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
oxtoacart committed May 26, 2016
1 parent 0197842 commit e33b07f
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 50 deletions.
55 changes: 47 additions & 8 deletions src/github.com/getlantern/flashlight/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import (
"net"
"net/http"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/armon/go-socks5"
"github.com/getlantern/balancer"
"github.com/getlantern/detour"
"github.com/getlantern/eventual"
"github.com/getlantern/golog"
"github.com/getlantern/protected"
)

const (
Expand Down Expand Up @@ -139,14 +140,11 @@ func (client *Client) ListenAndServeSOCKS5(requestedAddr string) error {

conf := &socks5.Config{
Dial: func(network, addr string) (net.Conn, error) {
bal, ok := client.bal.Get(1 * time.Minute)
if !ok {
return nil, fmt.Errorf("Unable to get balancer")
port, err := client.portForAddress(addr)
if err != nil {
return nil, err
}
// Using protocol "connect" will cause the balancer to issue an HTTP
// CONNECT request to the upstream proxy and return the resulting channel
// as a connection.
return bal.(*balancer.Balancer).Dial("connect", addr)
return client.dialCONNECT(addr, port)
},
}
server, err := socks5.New(conf)
Expand Down Expand Up @@ -219,6 +217,47 @@ func (client *Client) proxiedDialer(orig func(network, addr string) (net.Conn, e
}
}

func (client *Client) dialCONNECT(addr string, port int) (net.Conn, error) {
// Establish outbound connection
if client.shouldSendToProxy(port) {
log.Tracef("Proxying CONNECT request for %v", addr)
d := client.proxiedDialer(func(network, addr string) (net.Conn, error) {
// UGLY HACK ALERT! In this case, we know we need to send a CONNECT request
// to the chained server. We need to send that request from chained/dialer.go
// though because only it knows about the authentication token to use.
// We signal it to send the CONNECT here using the network transport argument
// that is effectively always "tcp" in the end, but we look for this
// special "transport" in the dialer and send a CONNECT request in that
// case.
return client.getBalancer().Dial("connect", addr)
})
return d("tcp", addr)
}
log.Tracef("Port not allowed, bypassing proxy and sending CONNECT request directly to %v", addr)
return protected.Dial("tcp", addr, 1*time.Minute)
}

func (client *Client) shouldSendToProxy(port int) bool {
for _, proxiedPort := range client.cfg().ProxiedCONNECTPorts {
if port == proxiedPort {
return true
}
}
return false
}

func (client *Client) portForAddress(addr string) (int, error) {
_, portString, err := net.SplitHostPort(addr)
if err != nil {
return 0, fmt.Errorf("Unable to determine port for address %v: %v", addr, err)
}
port, err := strconv.Atoi(portString)
if err != nil {
return 0, fmt.Errorf("Unable to parse port %v for address %v: %v", addr, port, err)
}
return port, nil
}

func isLanternSpecialDomain(addr string) bool {
return strings.Index(addr, lanternSpecialDomainWithColon) == 0
}
Expand Down
38 changes: 3 additions & 35 deletions src/github.com/getlantern/flashlight/client/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,14 @@ func (client *Client) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
// connection and starts piping the data over a new net.Conn obtained from the
// given dial function.
func (client *Client) intercept(resp http.ResponseWriter, req *http.Request) {

if req.Method != httpConnectMethod {
panic("Intercept used for non-CONNECT request!")
}

addr := hostIncludingPort(req, 443)
_, portString, err := net.SplitHostPort(addr)
port, err := client.portForAddress(addr)
if err != nil {
respondBadGateway(resp, fmt.Sprintf("Unable to determine port for address %v: %v", addr, err))
return
}
port, err := strconv.Atoi(portString)
if err != nil {
respondBadGateway(resp, fmt.Sprintf("Unable to parse port %v for address %v: %v", addr, port, err))
respondBadGateway(resp, err.Error())
return
}

Expand Down Expand Up @@ -89,33 +83,7 @@ func (client *Client) intercept(resp http.ResponseWriter, req *http.Request) {
return
}

sendToProxy := false
for _, proxiedPort := range client.cfg().ProxiedCONNECTPorts {
if port == proxiedPort {
sendToProxy = true
break
}
}

// Establish outbound connection
if sendToProxy {
log.Tracef("Proxying CONNECT request for %v", addr)
d := client.proxiedDialer(func(network, addr string) (net.Conn, error) {
// UGLY HACK ALERT! In this case, we know we need to send a CONNECT request
// to the chained server. We need to send that request from chained/dialer.go
// though because only it knows about the authentication token to use.
// We signal it to send the CONNECT here using the network transport argument
// that is effectively always "tcp" in the end, but we look for this
// special "transport" in the dialer and send a CONNECT request in that
// case.
return client.getBalancer().Dial("connect", addr)
})
connOut, err = d("tcp", addr)
} else {
log.Tracef("Port not allowed, bypassing proxy and sending CONNECT request directly to %v", addr)
connOut, err = net.Dial("tcp", addr)
}

connOut, err = client.dialCONNECT(addr, port)
if err != nil {
log.Debugf("Could not dial %v", err)
respondBadGatewayHijacked(clientConn, req)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions src/github.com/getlantern/lantern-mobile/.idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 20 additions & 4 deletions src/github.com/getlantern/protected/protected.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,25 @@ var (
log = golog.LoggerFor("lantern-android.protected")
currentProtect Protect
currentDnsServer string
mutex sync.RWMutex
)

func Configure(protect Protect, dnsServer string) {
mutex.Lock()
currentProtect = protect
if dnsServer != "" {
currentDnsServer = dnsServer
} else {
dnsServer = defaultDnsServer
}
mutex.Unlock()
}

// Resolve resolves the given address using a DNS lookup on a UDP socket
// protected by the currnet Protector.
// protected by the current Protector.
func Resolve(addr string) (*net.TCPAddr, error) {
protect, dnsServer := getCurrent()

host, port, err := SplitHostPort(addr)
if err != nil {
return nil, err
Expand All @@ -74,12 +79,12 @@ func Resolve(addr string) (*net.TCPAddr, error) {
// Here we protect the underlying socket from the
// VPN connection by passing the file descriptor
// back to Java for exclusion
err = currentProtect(socketFd)
err = protect(socketFd)
if err != nil {
return nil, fmt.Errorf("Could not bind socket to system device: %v", err)
}

IPAddr = net.ParseIP(currentDnsServer)
IPAddr = net.ParseIP(dnsServer)
if IPAddr == nil {
return nil, errors.New("invalid IP address")
}
Expand Down Expand Up @@ -127,6 +132,11 @@ func Resolve(addr string) (*net.TCPAddr, error) {
// specified system device (this is primarily
// used for Android VpnService routing functionality)
func Dial(network, addr string, timeout time.Duration) (net.Conn, error) {
protect, _ := getCurrent()
if protect == nil {
return net.DialTimeout(network, addr, timeout)
}

host, port, err := SplitHostPort(addr)
if err != nil {
return nil, err
Expand Down Expand Up @@ -154,7 +164,7 @@ func Dial(network, addr string, timeout time.Duration) (net.Conn, error) {
defer conn.cleanup()

// Actually protect the underlying socket here
err = currentProtect(conn.socketFd)
err = protect(conn.socketFd)
if err != nil {
return nil, fmt.Errorf("Could not bind socket to system device: %v", err)
}
Expand Down Expand Up @@ -269,3 +279,9 @@ func SplitHostPort(addr string) (string, int, error) {
}
return host, port, nil
}

func getCurrent() (Protect, string) {
mutex.RLock()
defer mutex.RUnlock()
return currentProtect, currentDnsServer
}

0 comments on commit e33b07f

Please sign in to comment.