/Shadowsocks-Net

✈ A light-weight, cross-platform, extensible Shadowsocks developed in C# (.NET Core).

Primary LanguageC#OtherNOASSERTION

Overview

Shadowsocks-Net is a cross-platform version of Shadowsocks developed in C# (.NET Core).

中文

Version

Shadowsocks-Net plans to release multiple versions, the feature comparison is shown in the table below.

Completion
of
development
Version ss-local ss-remote Local
HTTP1
Obfuscation URL/IP
filtering
Server
scheduling
strategy
GUI
90% Minimal-
cross-platform
10% Windows
1% Linux

The Minimal vesion is available for testing now, supported encryption algorithms:

aes-256-gcm, aes-192-gcm, aes-128-gcm, chacha20-ietf-poly1305, xchacha20-ietf-poly1305

Development Instructions

Schematic diagram of architecture

arch

Shadowsocks-Net encapsulates the network programming part simply so that the upper layer can focus on the socks5 protocol. Since Shadowsocks mainly implements socks5 protocol, the upper layer code is now very thin. Socks5 generally only do two things: 1. negotiation, 2. forwarding. Shadowsocks-Net tries to make developing Shadowsocks in C# more enjoyable and easier.

Master branch is a classical implementation of Shadowsocks. The pluggable-tunnel branch has a slightly different architecture, which gives flexibility to integrate multiplexed tunnels.


Steps to add encryption algorithm

  1. Implement the unified encryption interface IShadowsocksAeadCipher or IShadowsocksStreamCipher
class MyCipher : IShadowsocksAeadCipher
{
    //implementation
}
  1. Mark with Cipher attribute
[Cipher("my-cipher-name")]
class MyCipher : IShadowsocksAeadCipher
{
    //implementation
}

MyCipher is recognized now.


Obfuscation support

Obfuscation is similar to encryption, in Shadowsocks-Net, it works as a filter. The logic of obfuscation can be more complicated than encryption. But as the other parts have been encapsulated, now only need to focus on reading and writing the network stream, and implement a ClientFilter.

public interface IClientReaderFilter
{
    ClientFilterResult OnReading(ClientFilterContext filterContext);
}
public interface IClientWriterFilter
{
    ClientFilterResult OnWriting(ClientFilterContext filterContext);        
}

Encryption, obfuscation and UDP encapsulation are all implemented by filters in Shadowsocks-Net. Filters are pluggable. Therefore, filters can also be used to interpret custom protocols.

The following filter inserts four bytes 0x12, 0x34, 0xAB, 0xCD into the beginning of the data each time before sending, and correspondingly skips the first four bytes when receiving:

class TestClientFilter : ClientFilter
{
    public override ClientFilterResult OnWriting(ClientFilterContext ctx)
    {
        byte[] data = ctx.Memory.ToArray();
        byte[] newData = new byte[data.Length + 4];
        newData[0] = 0x12;
        newData[1] = 0x34;
        newData[2] = 0xAB;
        newData[3] = 0xCD;
        Array.Copy(data, 0, newData, 4, data.Length);
        return new ClientFilterResult(ctx.Client, newData, ...);
    }

    public override ClientFilterResult OnReading(ClientFilterContext ctx)
    {
        byte[] data = ctx.Memory.ToArray();
        byte[] newData = data.Skip(4).ToArray();
        return new ClientFilterResult(ctx.Client, newData, ...);
    }
}

Steps to create filter
  1. Choose the proper Category and Priority, which determine the order of the filter in the filter chain. The framework has preseted several categories:
    public enum ClientFilterCategory
    {
        Obfuscation = 1,
        Cipher = 2,
        Encapsulation = 3,
        Custom = 4
    }
  1. Inherit ClientFilter
    public abstract class ClientFilter
    {
        public abstract ClientFilterResult OnReading(ClientFilterContext filterContext);
        public abstract ClientFilterResult OnWriting(ClientFilterContext filterContext);
    }
  1. Add filter to pipe
    DuplexPipe.AddFilter(IClient client, IClientFilter filter);

A typical case: UdpEncapsulationFilter.cs.

Support for protocols other than TCP or UDP

As interfaces have already been abstracted by design, other communication protocols can now also be integrated. Just implement IClient and IServer, no need to change other parts.

IClient and IServer are also simple:

public partial interface IClient : IPeer
{        
    ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
    ValueTask<int> WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default);
    
    IPEndPoint LocalEndPoint { get; }
    void Close();
    event EventHandler<ClientEventArgs> Closing;
}
public interface IServer : IPeer
{
    void Listen();
    void StopListen();
}
public interface IServer<TClient> : IServer
    where TClient : IClient
{
    Task<TClient> Accept();       
}

For instance, in order to integrate the KCP protocol, we implemented KcpClient and KcpServer, then Shadowsocks-Net uses KCP as the transport layer protocol.

Compile

Environment and dependence

Visual Studio 2019 Community, .NET Framework 4.6 (temporarily used to design winform), .NET Standard 2.1 & .NET Core 3.1.

How to compile

Simply build the entire solution in Visual Studio.
This project is currently written 100% in C#, the core is .NET Standard 2.1 class library.


Roadmap

Task list

  • ☑ Core rewrite
  • ☐ Windows end
  • ☐ Unified filter rule
  • ☐ Target IP, domain check
  • ☐ Linux version

Usage

Similar to using other versions of Shadowsocks.
The Minimal version has been tested on Windows and Debian10 x64, the parameters are configured through configuration file.

Take an example on Windows:
On server side, edit config.json, run shadowsocks-net-remote.exe:

{
  //"server_host": null,
  "server_port": 6666,
  "use_ipv6": false,
  "timeout": 5,
  "password": "password1",
  "method": "aes-128-gcm"
}

On local side, edit servers.json and app-config.json, run shadowsocks-net-local.exe:

[
  {
    "remarks": "Test Server",
    "server": "10.10.10.102",
    "server_port": 6666,
    "password": "password1",
    "method": "aes-128-gcm",
    "obfs": null,
    "category": null
  }
]
{
 "Socks5Proxy": {
    "Port": 2080,
    "UseIPv6Address": false,
    "UseLoopbackAddress": true
  },
  "HttpProxy": {
    "Port": 8080,
    "UseIPv6Address": false,
    "UseLoopbackAddress": true
  }
}

Resemble on Linux. Installing .NET Core Portal.

Contribute

There is still a lot of code waiting to be added.




Local HTTP: Transform socks5 into HTTP.