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 }