使用WCF來實現一個ShadowSocks客戶端(四)

實現ShadowSocks客戶端

在具體說實現之前,還有個問題要描述一下,就是我在不使用WCF Session管理的情況下在實現了IDuplexChannel後爲何還要實現IDuplexSessionChannel,而且IDuplexSessionChannel的實現僅僅是繼承了IDuplexChannel實現,這樣做的目的是什麼。

    class CustomTcpSocketSessionChannel : CustomTcpSocketChannel, IDuplexSessionChannel
    {
        internal CustomTcpSocketSessionChannel(Socket socket, ChannelManagerBase channelManager, MessageEncoderFactory factory, BindingContext context)
            : base(socket, channelManager, factory, context)
        { }

        public IDuplexSession Session
        {
            get
            {
                return null;
            }
        }
     }

本來我是隻想用IDuplexChannel作爲自定義Channel實現的,就是爲了簡單,但是用IDuplexChannel實現的Channel在實際使用中有個很大問題,就是當有多個客戶端連接到服務端時,服務端對客戶端發送的請求進行回調時,所有回調都只回調到第一個連接上服務端的客戶端,這是個非常困惑的問題,目前也不清楚具體是什麼原因導致。但是如果是作爲IDuplexSessionChannel實現,就像上面貼的用原有IDuplexChannel實現類僅僅實現該接口,就算讓該接口的成員直接返回null也沒關係,這個問題就解決了,不知道WCF內部是怎麼回事,但換個接口的確就解決了問題。

開始說一說怎麼用之前定義的那麼多東西來實現ShadowSocks了。
先說說理由,爲什麼要做ShadowSocks客戶端,原因就是爲了測試之前做的東西管不管用,而選擇ShadowSocks是因爲ShadowSocks協議簡單,實現起來方便,測試起來也非常簡單。

Shadowsocks客戶端本身其實就是轉發socks5協議,只不過在轉發的時候,除了協議頭3個字節外,其他所有數據與shadowsocks服務端通訊時都是加密的,加密解密模塊是直接用shadowsocks csharp客戶端的,真正要做的就是數據轉發。

發送加密的數據到ss服務器並從服務器接收加密數據這塊是非常簡單的,就是一個WCFTcpClient:

    internal class ProxyClient : WCFTcpClient
    {
        private string sid;
        public event Action<string, byte[]> DataReceived = delegate { };


        public ProxyClient(string uri, string sessionId) : base(uri)
        {
            sid = sessionId;
        }

        protected override void OnConnect(ISocketChannel session)
        {
        }

        protected override void OnData(string sessionId, byte[] data)
        {
            DataReceived(SessionId, data);
        }

        protected override void OnDisconnect(ISocketChannel session)
        {
        }

        public override void Invoke(byte[] data)
        {
            base.Invoke(data);
        }

        public string SessionId
        {
            get { return sid; }
        }
    }

僞socks5代理也就是轉發服務這塊,主要就是處理協議頭這塊

        protected override void OnData(string sessionId, byte[] data)
        {
            HandShakeState state;
            if (!states.TryGetValue(sessionId, out state))
                throw new InvalidOperationException();

            switch (state)
            {
                case HandShakeState.First:
                    states[sessionId] = HandShakeState.Second;
                    Handshake(sessionId, data);
                    break;

                case HandShakeState.Second:
                    states[sessionId] = HandShakeState.Final;
                    HandshakeSecond(sessionId, data);
                    break;

                case HandShakeState.Final:
                    Pass(sessionId, data);
                    break;
            }
        }

分成3次握手,第一次是響應瀏覽器等的socks5協議請求

        private void Handshake(string sessionId, byte[] data)
        {
            if (data.Length <= 1)
            {
                Close();
                return;
            }

            byte[] response = { 5, 0 };
            if (data[0] != 5)
            {
                // reject socks 4
                response = new byte[] { 0, 91 };
            }

            Callback(sessionId, response);
        }

第二次是跳過前3個字節也就是版本信息及服務請求之類的,把真正有用的信息加密轉發給服務器,注意到這裏拒絕掉了3(UDP)請求,因爲沒實現。

        private void HandshakeSecond(string sessionId, byte[] data)
        {
            if (data.Length < 3)
            {
                Close();
                return;
            }
            if (data.Length > 3)
            {
                var extraHeader = new byte[data.Length - 3];
                Array.Copy(data, 3, extraHeader, 0, data.Length - 3);
                extraHeaders[sessionId] = extraHeader;
            }            

            var command = data[1];
            if (command == 1)
            {
                var proxy = NewProxy(sessionId);
                proxy.Open();

                byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
                Callback(response);
            }
            else if (command == 3)
            {
                throw new NotSupportedException();
            }
        }

一旦前2次成功,後面就正常的把從ss服務器接到的數據解密返回給瀏覽器,把從瀏覽器收到的數據加密返回給ss服務器

        private void Pass(string sessionId, byte[] data)
        {
            int size;
            byte[] buffer1, buffer2;

            lock (encryptBuffer)
            {
                buffer2 = data;
                byte[] extraHeader;
                if (extraHeaders.TryRemove(sessionId, out extraHeader))
                {
                    buffer2 = new byte[extraHeader.Length + data.Length];
                    Array.Copy(extraHeader, buffer2, extraHeader.Length);
                    Array.Copy(data, 0, buffer2, extraHeader.Length, data.Length);
                }
                encryptor.Encrypt(buffer2, buffer2.Length, encryptBuffer, out size);
                buffer1 = Truncate(encryptBuffer, size);
            }

            var proxy = GetProxy(sessionId);
            proxy.Invoke(buffer1);
        }

        private byte[] Truncate(byte[] array, int size)
        {
            var data = new byte[size];

            Array.Copy(array, data, size);
            return data;
        }

        private void Proxy_DataReceived(string sessionId, byte[] data)
        {
            int size;
            byte[] buffer;

            lock (decryptBuffer)
            {
                encryptor.Decrypt(data, data.Length, decryptBuffer, out size);
                buffer = Truncate(decryptBuffer, size);
            }

            Callback(sessionId, buffer);
        }

就這樣一個簡單的Shadowsocks客戶端lib就做好了,然後只要做個客戶端創建ProxyServer,並Open就算大功告成了。

最後由於WCF架構非常複雜,我這些個實現可能並不符合WCF實施規範,而且還有可能存在很多錯誤實施,所有這個東西也僅作參考,不過這個測試用的shadowsocks還是能正常工作的,當然它作爲一個網絡程序還差非常多嚴格測試,目前還只能算是測試參考用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章