using System; using System.Linq; using System.Net; using System.Net.Sockets; using Shadowsocks.Model; using Shadowsocks.Util.Sockets; namespace Shadowsocks.Controller.Service { class Socks5HandlerFactory : ITCPHandlerFactory { public bool CanHandle(byte[] firstPacket, int length) { return length >= 2 && firstPacket[0] == 5; } public TCPHandler NewHandler(ShadowsocksController controller, Configuration config, TCPRelay tcprelay, Socket socket) { return new Socks5Handler(controller, config, tcprelay, socket); } } class Socks5Handler : TCPHandler { private byte _command; private int _firstPacketLength; public Socks5Handler(ShadowsocksController controller, Configuration config, TCPRelay tcprelay, Socket socket) : base(controller, config, tcprelay, socket, false) { } public override void StartHandshake(byte[] firstPacket, int length) { if (Closed) return; try { int bytesRead = length; if (bytesRead > 1) { byte[] response = { 5, 0 }; if (firstPacket[0] != 5) { // reject socks 4 response = new byte[] { 0, 91 }; Logging.Error("socks 5 protocol error"); } Connection.BeginSend(response, 0, response.Length, SocketFlags.None, HandshakeSendCallback, null); } else Close(); } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } private void HandshakeSendCallback(IAsyncResult ar) { if (Closed) return; try { Connection.EndSend(ar); // +-----+-----+-------+------+----------+----------+ // | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | // +-----+-----+-------+------+----------+----------+ // | 1 | 1 | X'00' | 1 | Variable | 2 | // +-----+-----+-------+------+----------+----------+ // Skip first 3 bytes, and read 2 more bytes to analysis the address. // 2 more bytes is designed if address is domain then we don't need to read once more to get the addr length. // TODO validate Connection.BeginReceive(ConnetionRecvBuffer, 0, 3 + 2, SocketFlags.None, handshakeReceive2Callback, null); } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } private void handshakeReceive2Callback(IAsyncResult ar) { if (Closed) return; try { int bytesRead = Connection.EndReceive(ar); if (bytesRead >= 5) { _command = ConnetionRecvBuffer[1]; if (_command != 1 && _command != 3) { Logging.Debug("Unsupported CMD=" + _command); Close(); } else { int atyp = ConnetionRecvBuffer[3]; switch (atyp) { case 1: // IPv4 address, 4 bytes ReadAddress(4 + 2 - 1); break; case 3: // domain name, length + str int len = ConnetionRecvBuffer[4]; ReadAddress(len + 2); break; case 4: // IPv6 address, 16 bytes ReadAddress(16 + 2 - 1); break; default: Logging.Debug("Unsupported ATYP=" + atyp); Close(); break; } } } else { Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); Close(); } } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } private void ReadAddress(int bytesRemain) { Array.Copy(ConnetionRecvBuffer, 3, ConnetionRecvBuffer, 0, 2); // Read the remain address bytes Connection.BeginReceive(ConnetionRecvBuffer, 2, RecvSize - 2, SocketFlags.None, OnAddressFullyRead, bytesRemain); } private void OnAddressFullyRead(IAsyncResult ar) { if (Closed) return; try { int bytesRead = Connection.EndReceive(ar); int bytesRemain = (int)ar.AsyncState; if (bytesRead >= bytesRemain) { _firstPacketLength = bytesRead + 2; int atyp = ConnetionRecvBuffer[0]; string dst_addr = "Unknown"; int dst_port = -1; switch (atyp) { case 1: // IPv4 address, 4 bytes dst_addr = new IPAddress(ConnetionRecvBuffer.Skip(1).Take(4).ToArray()).ToString(); dst_port = (ConnetionRecvBuffer[5] << 8) + ConnetionRecvBuffer[6]; break; case 3: // domain name, length + str int len = ConnetionRecvBuffer[1]; dst_addr = System.Text.Encoding.UTF8.GetString(ConnetionRecvBuffer, 2, len); dst_port = (ConnetionRecvBuffer[len + 2] << 8) + ConnetionRecvBuffer[len + 3]; break; case 4: // IPv6 address, 16 bytes dst_addr = $"[{new IPAddress(ConnetionRecvBuffer.Skip(1).Take(16).ToArray())}]"; dst_port = (ConnetionRecvBuffer[17] << 8) + ConnetionRecvBuffer[18]; break; } if (Config.isVerboseLogging) { Logging.Info($"connect to {dst_addr}:{dst_port}"); } var destEndPoint = SocketUtil.GetEndPoint(dst_addr, dst_port); if (_command == 1) { byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; Connection.BeginSend(response, 0, response.Length, SocketFlags.None, ResponseCallback, destEndPoint); } else if (_command == 3) { HandleUDPAssociate(); } } else { Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.OnAddressFullyRead()"); Close(); } } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } private void HandleUDPAssociate() { IPEndPoint endPoint = (IPEndPoint)Connection.LocalEndPoint; byte[] address = endPoint.Address.GetAddressBytes(); int port = endPoint.Port; byte[] response = new byte[4 + address.Length + 2]; response[0] = 5; switch (endPoint.AddressFamily) { case AddressFamily.InterNetwork: response[3] = 1; break; case AddressFamily.InterNetworkV6: response[3] = 4; break; } address.CopyTo(response, 4); response[response.Length - 1] = (byte)(port & 0xFF); response[response.Length - 2] = (byte)((port >> 8) & 0xFF); Connection.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(ReadAll), true); } private void ReadAll(IAsyncResult ar) { if (Closed) return; try { if (ar.AsyncState != null) { Connection.EndSend(ar); Connection.BeginReceive(ConnetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null); } else { int bytesRead = Connection.EndReceive(ar); if (bytesRead > 0) { Connection.BeginReceive(ConnetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null); } else Close(); } } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } private void ResponseCallback(IAsyncResult ar) { try { Connection.EndSend(ar); StartConnect((EndPoint) ar.AsyncState); } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } protected override void OnServerConnected(AsyncSession session) { BeginSendToServer(_firstPacketLength, session, FirstPackageSendCallback); } private void FirstPackageSendCallback(IAsyncResult ar) { if (Closed) return; try { var session = EndSendToServer(ar); StartPipe(session); } catch (Exception e) { Logging.LogUsefulException(e); Close(); } } } }