github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/socksProxy.go (about)

     1  /*
     2   * Copyright (c) 2015, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package psiphon
    21  
    22  import (
    23  	"net"
    24  	"strings"
    25  	"sync"
    26  
    27  	socks "github.com/Psiphon-Labs/goptlib"
    28  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    29  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    30  )
    31  
    32  // SocksProxy is a SOCKS server that accepts local host connections
    33  // and, for each connection, establishes a port forward through
    34  // the tunnel SSH client and relays traffic through the port
    35  // forward.
    36  type SocksProxy struct {
    37  	config                 *Config
    38  	tunneler               Tunneler
    39  	listener               *socks.SocksListener
    40  	serveWaitGroup         *sync.WaitGroup
    41  	openConns              *common.Conns
    42  	stopListeningBroadcast chan struct{}
    43  }
    44  
    45  var _SOCKS_PROXY_TYPE = "SOCKS"
    46  
    47  // NewSocksProxy initializes a new SOCKS server. It begins listening for
    48  // connections, starts a goroutine that runs an accept loop, and returns
    49  // leaving the accept loop running.
    50  func NewSocksProxy(
    51  	config *Config,
    52  	tunneler Tunneler,
    53  	listenIP string) (proxy *SocksProxy, err error) {
    54  
    55  	listener, portInUse, err := makeLocalProxyListener(
    56  		listenIP, config.LocalSocksProxyPort)
    57  	if err != nil {
    58  		if portInUse {
    59  			NoticeSocksProxyPortInUse(config.LocalSocksProxyPort)
    60  		}
    61  		return nil, errors.Trace(err)
    62  	}
    63  	proxy = &SocksProxy{
    64  		config:                 config,
    65  		tunneler:               tunneler,
    66  		listener:               socks.NewSocksListener(listener),
    67  		serveWaitGroup:         new(sync.WaitGroup),
    68  		openConns:              common.NewConns(),
    69  		stopListeningBroadcast: make(chan struct{}),
    70  	}
    71  	proxy.serveWaitGroup.Add(1)
    72  	go proxy.serve()
    73  	NoticeListeningSocksProxyPort(proxy.listener.Addr().(*net.TCPAddr).Port)
    74  	return proxy, nil
    75  }
    76  
    77  // Close terminates the listener and waits for the accept loop
    78  // goroutine to complete.
    79  func (proxy *SocksProxy) Close() {
    80  	close(proxy.stopListeningBroadcast)
    81  	proxy.listener.Close()
    82  	proxy.serveWaitGroup.Wait()
    83  	proxy.openConns.CloseAll()
    84  }
    85  
    86  func (proxy *SocksProxy) socksConnectionHandler(localConn *socks.SocksConn) (err error) {
    87  	defer localConn.Close()
    88  	defer proxy.openConns.Remove(localConn)
    89  
    90  	proxy.openConns.Add(localConn)
    91  
    92  	// Using downstreamConn so localConn.Close() will be called when remoteConn.Close() is called.
    93  	// This ensures that the downstream client (e.g., web browser) doesn't keep waiting on the
    94  	// open connection for data which will never arrive.
    95  	remoteConn, err := proxy.tunneler.Dial(localConn.Req.Target, localConn)
    96  
    97  	if err != nil {
    98  		reason := byte(socks.SocksRepGeneralFailure)
    99  
   100  		// "ssh: rejected" is the prefix of ssh.OpenChannelError
   101  		// TODO: retain error type and check for ssh.OpenChannelError
   102  		if strings.Contains(err.Error(), "ssh: rejected") {
   103  			reason = byte(socks.SocksRepConnectionRefused)
   104  		}
   105  
   106  		_ = localConn.RejectReason(reason)
   107  		return errors.Trace(err)
   108  	}
   109  
   110  	defer remoteConn.Close()
   111  
   112  	err = localConn.Grant(&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})
   113  	if err != nil {
   114  		return errors.Trace(err)
   115  	}
   116  
   117  	LocalProxyRelay(proxy.config, _SOCKS_PROXY_TYPE, localConn, remoteConn)
   118  
   119  	return nil
   120  }
   121  
   122  func (proxy *SocksProxy) serve() {
   123  	defer proxy.listener.Close()
   124  	defer proxy.serveWaitGroup.Done()
   125  loop:
   126  	for {
   127  		// Note: will be interrupted by listener.Close() call made by proxy.Close()
   128  		socksConnection, err := proxy.listener.AcceptSocks()
   129  		// Can't check for the exact error that Close() will cause in Accept(),
   130  		// (see: https://code.google.com/p/go/issues/detail?id=4373). So using an
   131  		// explicit stop signal to stop gracefully.
   132  		select {
   133  		case <-proxy.stopListeningBroadcast:
   134  			break loop
   135  		default:
   136  		}
   137  		if err != nil {
   138  			NoticeWarning("SOCKS proxy accept error: %s", err)
   139  			if e, ok := err.(net.Error); ok && e.Temporary() {
   140  				// Temporary error, keep running
   141  				continue
   142  			}
   143  			// Fatal error, stop the proxy
   144  			proxy.tunneler.SignalComponentFailure()
   145  			break loop
   146  		}
   147  		go func() {
   148  			err := proxy.socksConnectionHandler(socksConnection)
   149  			if err != nil {
   150  				NoticeLocalProxyError(_SOCKS_PROXY_TYPE, errors.Trace(err))
   151  			}
   152  		}()
   153  	}
   154  	NoticeInfo("SOCKS proxy stopped")
   155  }