Browse Source

Merge pull request #1 from shadowsocks/master

merge
pull/373/head
breakwa11 10 years ago
parent
commit
32fc0f3768
13 changed files with 462 additions and 104 deletions
  1. +9
    -0
      CHANGES
  2. +3
    -0
      README.md
  3. +70
    -15
      shadowsocks-csharp/Controller/Listener.cs
  4. +5
    -1
      shadowsocks-csharp/Controller/PACServer.cs
  5. +5
    -1
      shadowsocks-csharp/Controller/PortForwarder.cs
  6. +4
    -2
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  7. +133
    -57
      shadowsocks-csharp/Controller/TCPRelay.cs
  8. +199
    -0
      shadowsocks-csharp/Controller/UDPRelay.cs
  9. +16
    -13
      shadowsocks-csharp/Controller/UpdateChecker.cs
  10. +1
    -0
      shadowsocks-csharp/Data/cn.txt
  11. +4
    -0
      shadowsocks-csharp/Model/Configuration.cs
  12. +11
    -14
      shadowsocks-csharp/Properties/Resources.Designer.cs
  13. +2
    -1
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 9
- 0
CHANGES View File

@@ -1,3 +1,12 @@
2.4 2015-07-11
- Support UDP relay
- Support online PAC
- Migrate update checker to GitHub releases
- Other fixes

2.3.1 2015-03-06
- Support user rule

2.3 2015-01-25
- Use the same port for every profile
- Use the same port for HTTP/Socks5/PAC


+ 3
- 0
README.md View File

@@ -10,6 +10,7 @@ Shadowsocks for Windows
3. PAC mode and global mode
4. GFWList and user rules
5. Supports HTTP proxy
6. Supports UDP relay (see Usage)

#### Download

@@ -33,6 +34,8 @@ with any editor, Shadowsocks will notify browsers about the change automatically
6. You can also update the PAC file from GFWList. Note your modifications to the PAC
file will be lost. However you can put your rules in the user rule file for GFWList.
Don't forget to update from GFWList again after you've edited the user rule
7. For UDP, you need to use SocksCap or ProxyCap to force programs you want
to proxy to tunnel over Shadowsocks

### Develop



+ 70
- 15
shadowsocks-csharp/Controller/Listener.cs View File

@@ -12,12 +12,19 @@ namespace Shadowsocks.Controller
{
public interface Service
{
bool Handle(byte[] firstPacket, int length, Socket socket);
bool Handle(byte[] firstPacket, int length, Socket socket, object state);
}
public class UDPState
{
public byte[] buffer = new byte[4096];
public EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 1);
}
Configuration _config;
bool _shareOverLAN;
Socket _socket;
Socket _tcpSocket;
Socket _udpSocket;
IList<Service> _services;
public Listener(IList<Service> services)
@@ -51,8 +58,10 @@ namespace Shadowsocks.Controller
try
{
// Create a TCP/IP socket.
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_tcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
_tcpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
IPEndPoint localEndPoint = null;
if (_shareOverLAN)
{
@@ -64,29 +73,73 @@ namespace Shadowsocks.Controller
}
// Bind the socket to the local endpoint and listen for incoming connections.
_socket.Bind(localEndPoint);
_socket.Listen(1024);
_tcpSocket.Bind(localEndPoint);
_udpSocket.Bind(localEndPoint);
_tcpSocket.Listen(1024);
// Start an asynchronous socket to listen for connections.
Console.WriteLine("Shadowsocks started");
_socket.BeginAccept(
_tcpSocket.BeginAccept(
new AsyncCallback(AcceptCallback),
_socket);
_tcpSocket);
UDPState udpState = new UDPState();
_udpSocket.BeginReceiveFrom(udpState.buffer, 0, udpState.buffer.Length, 0, ref udpState.remoteEndPoint, new AsyncCallback(RecvFromCallback), udpState);
}
catch (SocketException)
{
_socket.Close();
_tcpSocket.Close();
throw;
}
}
public void Stop()
{
if (_socket != null)
if (_tcpSocket != null)
{
_tcpSocket.Close();
_tcpSocket = null;
}
if (_udpSocket != null)
{
_udpSocket.Close();
_udpSocket = null;
}
}
public void RecvFromCallback(IAsyncResult ar)
{
UDPState state = (UDPState)ar.AsyncState;
try
{
int bytesRead = _udpSocket.EndReceiveFrom(ar, ref state.remoteEndPoint);
foreach (Service service in _services)
{
if (service.Handle(state.buffer, bytesRead, _udpSocket, state))
{
break;
}
}
}
catch (ObjectDisposedException)
{
}
catch (Exception)
{
_socket.Close();
_socket = null;
}
finally
{
try
{
_udpSocket.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, 0, ref state.remoteEndPoint, new AsyncCallback(RecvFromCallback), state);
}
catch (ObjectDisposedException)
{
// do nothing
}
catch (Exception)
{
}
}
}
@@ -144,14 +197,16 @@ namespace Shadowsocks.Controller
int bytesRead = conn.EndReceive(ar);
foreach (Service service in _services)
{
if (service.Handle(buf, bytesRead, conn))
if (service.Handle(buf, bytesRead, conn, null))
{
return;
}
}
// no service found for this
// shouldn't happen
conn.Close();
if (conn.ProtocolType == ProtocolType.Tcp)
{
conn.Close();
}
}
catch (Exception e)
{


+ 5
- 1
shadowsocks-csharp/Controller/PACServer.cs View File

@@ -33,8 +33,12 @@ namespace Shadowsocks.Controller
this._config = config;
}
public bool Handle(byte[] firstPacket, int length, Socket socket)
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
try
{
string request = Encoding.UTF8.GetString(firstPacket, 0, length);


+ 5
- 1
shadowsocks-csharp/Controller/PortForwarder.cs View File

@@ -15,8 +15,12 @@ namespace Shadowsocks.Controller
this._targetPort = targetPort;
}
public bool Handle(byte[] firstPacket, int length, Socket socket)
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
new Handler().Start(firstPacket, length, socket, this._targetPort);
return true;
}


+ 4
- 2
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -250,9 +250,11 @@ namespace Shadowsocks.Controller
{
polipoRunner.Start(_config);
Local local = new Local(_config);
TCPRelay tcpRelay = new TCPRelay(_config);
UDPRelay udpRelay = new UDPRelay(_config);
List<Listener.Service> services = new List<Listener.Service>();
services.Add(local);
services.Add(tcpRelay);
services.Add(udpRelay);
services.Add(_pacServer);
services.Add(new PortForwarder(polipoRunner.RunningPort));
_listener = new Listener(services);


shadowsocks-csharp/Controller/Local.cs → shadowsocks-csharp/Controller/TCPRelay.cs View File

@@ -9,16 +9,20 @@ using Shadowsocks.Model;
namespace Shadowsocks.Controller
{
class Local : Listener.Service
class TCPRelay : Listener.Service
{
private Configuration _config;
public Local(Configuration config)
public TCPRelay(Configuration config)
{
this._config = config;
}
public bool Handle(byte[] firstPacket, int length, Socket socket)
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
if (length < 2 || firstPacket[0] != 5)
{
return false;
@@ -44,6 +48,7 @@ namespace Shadowsocks.Controller
public Socket remote;
public Socket connection;
private byte command;
private byte[] _firstPacket;
private int _firstPacketLength;
// Size of receive buffer.
@@ -70,32 +75,7 @@ namespace Shadowsocks.Controller
{
this._firstPacket = firstPacket;
this._firstPacketLength = length;
try
{
// TODO async resolving
IPAddress ipAddress;
bool parsed = IPAddress.TryParse(server.server, out ipAddress);
if (!parsed)
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server);
ipAddress = ipHostInfo.AddressList[0];
}
IPEndPoint remoteEP = new IPEndPoint(ipAddress, server.server_port);
remote = new Socket(ipAddress.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
// Connect to the remote endpoint.
remote.BeginConnect(remoteEP,
new AsyncCallback(ConnectCallback), null);
}
catch (Exception e)
{
Logging.LogUsefulException(e);
this.Close();
}
this.HandshakeReceive();
}
private void CheckClose()
@@ -149,28 +129,6 @@ namespace Shadowsocks.Controller
}
}
private void ConnectCallback(IAsyncResult ar)
{
if (closed)
{
return;
}
try
{
// Complete the connection.
remote.EndConnect(ar);
//Console.WriteLine("Socket connected to {0}",
// remote.RemoteEndPoint.ToString());
HandshakeReceive();
}
catch (Exception e)
{
Logging.LogUsefulException(e);
this.Close();
}
}
private void HandshakeReceive()
{
@@ -241,11 +199,19 @@ namespace Shadowsocks.Controller
try
{
int bytesRead = connection.EndReceive(ar);
if (bytesRead > 0)
if (bytesRead >= 3)
{
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(StartPipe), null);
command = connetionRecvBuffer[1];
if (command == 1)
{
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(StartConnect), null);
}
else if (command == 3)
{
HandleUDPAssociate();
}
}
else
{
@@ -260,8 +226,119 @@ namespace Shadowsocks.Controller
}
}
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;
if (endPoint.AddressFamily == AddressFamily.InterNetwork)
{
response[3] = 1;
}
else if (endPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
response[3] = 4;
}
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, 0, 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, 0,
new AsyncCallback(ReadAll), null);
}
else
{
int bytesRead = connection.EndReceive(ar);
if (bytesRead > 0)
{
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0,
new AsyncCallback(ReadAll), null);
}
else
{
this.Close();
}
}
}
catch (Exception e)
{
Logging.LogUsefulException(e);
this.Close();
}
}
private void StartConnect(IAsyncResult ar)
{
try
{
connection.EndSend(ar);
// TODO async resolving
IPAddress ipAddress;
bool parsed = IPAddress.TryParse(server.server, out ipAddress);
if (!parsed)
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server);
ipAddress = ipHostInfo.AddressList[0];
}
IPEndPoint remoteEP = new IPEndPoint(ipAddress, server.server_port);
remote = new Socket(ipAddress.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
// Connect to the remote endpoint.
remote.BeginConnect(remoteEP,
new AsyncCallback(ConnectCallback), null);
}
catch (Exception e)
{
Logging.LogUsefulException(e);
this.Close();
}
}
private void ConnectCallback(IAsyncResult ar)
{
if (closed)
{
return;
}
try
{
// Complete the connection.
remote.EndConnect(ar);
//Console.WriteLine("Socket connected to {0}",
// remote.RemoteEndPoint.ToString());
StartPipe();
}
catch (Exception e)
{
Logging.LogUsefulException(e);
this.Close();
}
}
private void StartPipe(IAsyncResult ar)
private void StartPipe()
{
if (closed)
{
@@ -269,7 +346,6 @@ namespace Shadowsocks.Controller
}
try
{
connection.EndReceive(ar);
remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0,
new AsyncCallback(PipeRemoteReceiveCallback), null);
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0,

+ 199
- 0
shadowsocks-csharp/Controller/UDPRelay.cs View File

@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Text;
using Shadowsocks.Encryption;
using Shadowsocks.Model;
using System.Net.Sockets;
using System.Net;
using System.Runtime.CompilerServices;
namespace Shadowsocks.Controller
{
class UDPRelay : Listener.Service
{
private Configuration _config;
private LRUCache<IPEndPoint, UDPHandler> _cache;
public UDPRelay(Configuration config)
{
this._config = config;
this._cache = new LRUCache<IPEndPoint, UDPHandler>(512); // todo: choose a smart number
}
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Udp)
{
return false;
}
if (length < 4)
{
return false;
}
Listener.UDPState udpState = (Listener.UDPState)state;
IPEndPoint remoteEndPoint = (IPEndPoint)udpState.remoteEndPoint;
UDPHandler handler = _cache.get(remoteEndPoint);
if (handler == null)
{
handler = new UDPHandler(socket, _config.GetCurrentServer(), remoteEndPoint);
_cache.add(remoteEndPoint, handler);
}
handler.Send(firstPacket, length);
handler.Receive();
return true;
}
public class UDPHandler
{
private Socket _local;
private Socket _remote;
private Server _server;
private byte[] _buffer = new byte[1500];
private IPEndPoint _localEndPoint;
private IPEndPoint _remoteEndPoint;
public UDPHandler(Socket local, Server server, IPEndPoint localEndPoint)
{
_local = local;
_server = server;
_localEndPoint = localEndPoint;
// TODO async resolving
IPAddress ipAddress;
bool parsed = IPAddress.TryParse(server.server, out ipAddress);
if (!parsed)
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server);
ipAddress = ipHostInfo.AddressList[0];
}
_remoteEndPoint = new IPEndPoint(ipAddress, server.server_port);
_remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
}
public void Send(byte[] data, int length)
{
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password);
byte[] dataIn = new byte[length - 3];
Array.Copy(data, 3, dataIn, 0, length - 3);
byte[] dataOut = new byte[length - 3 + 16];
int outlen;
encryptor.Encrypt(dataIn, dataIn.Length, dataOut, out outlen);
_remote.SendTo(dataOut, _remoteEndPoint);
}
public void Receive()
{
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
_remote.BeginReceiveFrom(_buffer, 0, _buffer.Length, 0, ref remoteEndPoint, new AsyncCallback(RecvFromCallback), null);
}
public void RecvFromCallback(IAsyncResult ar)
{
try
{
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
int bytesRead = _remote.EndReceiveFrom(ar, ref remoteEndPoint);
byte[] dataOut = new byte[bytesRead];
int outlen;
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password);
encryptor.Decrypt(_buffer, bytesRead, dataOut, out outlen);
byte[] sendBuf = new byte[outlen + 3];
Array.Copy(dataOut, 0, sendBuf, 3, outlen);
_local.SendTo(sendBuf, outlen + 3, 0, _localEndPoint);
Receive();
}
catch (ObjectDisposedException)
{
}
catch (Exception)
{
}
finally
{
}
}
public void Close()
{
try
{
_remote.Close();
}
catch (ObjectDisposedException)
{
}
catch (Exception)
{
}
finally
{
}
}
}
}
// cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054
class LRUCache<K, V> where V : UDPRelay.UDPHandler
{
private int capacity;
private Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>> cacheMap = new Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>>();
private LinkedList<LRUCacheItem<K, V>> lruList = new LinkedList<LRUCacheItem<K, V>>();
public LRUCache(int capacity)
{
this.capacity = capacity;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public V get(K key)
{
LinkedListNode<LRUCacheItem<K, V>> node;
if (cacheMap.TryGetValue(key, out node))
{
V value = node.Value.value;
lruList.Remove(node);
lruList.AddLast(node);
return value;
}
return default(V);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void add(K key, V val)
{
if (cacheMap.Count >= capacity)
{
RemoveFirst();
}
LRUCacheItem<K, V> cacheItem = new LRUCacheItem<K, V>(key, val);
LinkedListNode<LRUCacheItem<K, V>> node = new LinkedListNode<LRUCacheItem<K, V>>(cacheItem);
lruList.AddLast(node);
cacheMap.Add(key, node);
}
private void RemoveFirst()
{
// Remove from LRUPriority
LinkedListNode<LRUCacheItem<K, V>> node = lruList.First;
lruList.RemoveFirst();
// Remove from cache
cacheMap.Remove(node.Value.key);
node.Value.value.Close();
}
}
class LRUCacheItem<K, V>
{
public LRUCacheItem(K k, V v)
{
key = k;
value = v;
}
public K key;
public V value;
}
}

+ 16
- 13
shadowsocks-csharp/Controller/UpdateChecker.cs View File

@@ -6,24 +6,25 @@ using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using SimpleJson;
namespace Shadowsocks.Controller
{
public class UpdateChecker
{
private const string UpdateURL = "https://sourceforge.net/api/file/index/project-id/1817190/path/dist/mtime/desc/limit/10/rss";
private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-csharp/releases";
public string LatestVersionNumber;
public string LatestVersionURL;
public event EventHandler NewVersionFound;
public const string Version = "2.3.1";
public const string Version = "2.4";
public void CheckUpdate(Configuration config)
{
// TODO test failures
WebClient http = new WebClient();
http.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36");
http.Proxy = new WebProxy(IPAddress.Loopback.ToString(), config.localPort);
http.DownloadStringCompleted += http_DownloadStringCompleted;
http.DownloadStringAsync(new Uri(UpdateURL));
@@ -119,23 +120,25 @@ namespace Shadowsocks.Controller
{
string response = e.Result;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(response);
XmlNodeList elements = xmlDoc.GetElementsByTagName("media:content");
JsonArray result = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(e.Result);
List<string> versions = new List<string>();
foreach (XmlNode el in elements)
foreach (JsonObject release in result)
{
foreach (XmlAttribute attr in el.Attributes)
if ((bool)release["prerelease"])
{
continue;
}
foreach (JsonObject asset in (JsonArray)release["assets"])
{
if (attr.Name == "url")
string url = (string)asset["browser_download_url"];
if (IsNewVersion(url))
{
if (IsNewVersion(attr.Value))
{
versions.Add(attr.Value);
}
versions.Add(url);
}
}
}
if (versions.Count == 0)
{
return;


+ 1
- 0
shadowsocks-csharp/Data/cn.txt View File

@@ -58,6 +58,7 @@ Please add at least one server=请添加至少一个服务器
Server IP can not be blank=服务器 IP 不能为空
Password can not be blank=密码不能为空
Port out of range=端口超出范围
Port can't be 8123=端口不能为 8123
Shadowsocks {0} Update Found=Shadowsocks {0} 更新
Click here to download=点击这里下载
Shadowsocks is here=Shadowsocks 在这里


+ 4
- 0
shadowsocks-csharp/Model/Configuration.cs View File

@@ -118,6 +118,10 @@ namespace Shadowsocks.Model
{
throw new ArgumentException(I18N.GetString("Port out of range"));
}
if (port == 8123)
{
throw new ArgumentException(I18N.GetString("Port can't be 8123"));
}
}
private static void CheckPassword(string password)


+ 11
- 14
shadowsocks-csharp/Properties/Resources.Designer.cs View File

@@ -71,7 +71,12 @@ namespace Shadowsocks.Properties {
}
/// <summary>
/// Looks up a localized string similar to Shadowsocks=Shadowsocks
/// Looks up a localized string similar to # translation for Simplified Chinese
///
///Shadowsocks=Shadowsocks
///
///# Menu items
///
///Enable System Proxy=启用系统代理
///Mode=系统代理模式
///PAC=PAC 模式
@@ -80,24 +85,16 @@ namespace Shadowsocks.Properties {
///Edit Servers...=编辑服务器...
///Start on Boot=开机启动
///Allow Clients from LAN=允许来自局域网的连接
///Edit PAC File...=编辑 PAC 文件...
///Local PAC=使用本地 PAC
///Online PAC=使用在线 PAC
///Edit Local PAC File...=编辑本地 PAC 文件...
///Update Local PAC from GFWList=从 GFWList 更新本地 PAC
///Edit User Rule for GFWList...=编辑 GFWList 的用户规则...
///Show QRCode...=显示二维码...
///Scan QRCode from Screen...=扫描屏幕上的二维码...
///Show Logs...=显示日志...
///About...=关于...
///Quit=退出
///Edit Servers=编辑服务器
///&amp;Add=添加(&amp;A)
///&amp;Delete=删除(&amp;D)
///Server=服务器
///Server IP=服务器 IP
///Server Port=服务器端口
///Password=密码
///Encryption=加密
///Proxy Port=代理端口
///Remarks=备注
///OK= [rest of string was truncated]&quot;;.
///Quit=退出 [rest of string was truncated]&quot;;.
/// </summary>
internal static string cn {
get {


+ 2
- 1
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -131,6 +131,7 @@
<Compile Include="Controller\Listener.cs" />
<Compile Include="Controller\Logging.cs" />
<Compile Include="Controller\PortForwarder.cs" />
<Compile Include="Controller\UDPRelay.cs" />
<Compile Include="Controller\UpdateChecker.cs" />
<Compile Include="Encryption\EncryptorBase.cs" />
<Compile Include="Encryption\EncryptorFactory.cs" />
@@ -156,7 +157,7 @@
<Compile Include="View\ConfigForm.Designer.cs">
<DependentUpon>ConfigForm.cs</DependentUpon>
</Compile>
<Compile Include="Controller\Local.cs" />
<Compile Include="Controller\TCPRelay.cs" />
<Compile Include="Controller\PolipoRunner.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />


Loading…
Cancel
Save