實現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還是能正常工作的,當然它作爲一個網絡程序還差非常多嚴格測試,目前還只能算是測試參考用。