Update On Tue Dec 31 19:31:58 CET 2024

This commit is contained in:
github-action[bot]
2024-12-31 19:31:59 +01:00
parent 24d7dba717
commit a3e7f86215
576 changed files with 22919 additions and 15080 deletions

View File

@@ -1,100 +0,0 @@
root = true
# EditorConfig: http://EditorConfig.org
# VS extension: https://marketplace.visualstudio.com/items?itemName = MadsKristensen.EditorConfig
# 4 space indentation
[*]
charset = utf-8
indent_style = space
indent_size = 4
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_style_expression_bodied_methods = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_naming_rule.private_constants_rule.severity = suggestion
dotnet_naming_rule.private_constants_rule.style = all_upper_style
dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
dotnet_naming_rule.private_static_readonly_rule.severity = suggestion
dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
dotnet_naming_style.all_upper_style.capitalization = all_upper
dotnet_naming_style.all_upper_style.word_separator = _
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_style.lower_camel_case_style.required_prefix = _
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
dotnet_style_qualification_for_event = false:warning
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = true:suggestion
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = true:suggestion
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = false:none
csharp_style_pattern_matching_over_as_with_null_check = false:none
csharp_style_inlined_variable_declaration = false:none
csharp_style_throw_expression = false:none
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_members_in_anonymous_types = false
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Suggest more modern language features when available
dotnet_style_object_initializer = true:silent
dotnet_style_collection_initializer = true:silent
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
[*.{appxmanifest,asax,ascx,aspx,axml,build,config,cs,cshtml,csproj,css,dbml,discomap,dtd,htm,html,js,json,jsproj,jsx,lsproj,master,njsproj,nuspec,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,ts,tsx,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
indent_style = space
indent_size = 4
tab_width = 4

View File

@@ -1,129 +0,0 @@
name: Build
on:
push:
branches-ignore:
- 'v4'
- 'rm'
paths-ignore:
- 'README.md'
- 'LICENSE.txt'
pull_request:
branches-ignore:
- 'v4'
- 'rm'
paths-ignore:
- 'README.md'
- 'LICENSE.txt'
jobs:
build:
name: Build
strategy:
matrix:
os: [ubuntu-20.04, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: Restore dependencies
if: matrix.os == 'windows-latest'
run: dotnet restore
- name: Build
if: matrix.os == 'windows-latest'
run: dotnet build --no-restore
- name: Test
if: matrix.os == 'windows-latest'
run: dotnet test --no-build --verbosity normal
# Publish CLI
- name: Define MSBuild properties
run: echo "MSBUILD_PROPS=-p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=link -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:InvariantGlobalization=true" >> $GITHUB_ENV
- name: Publish CLI framework-dependent
run: |
dotnet publish Shadowsocks.CLI -c Release
- name: Publish CLI self-contained for Linux ARM64
if: matrix.os == 'ubuntu-20.04'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r linux-arm64 --self-contained
- name: Publish CLI self-contained for Linux x64
if: matrix.os == 'ubuntu-20.04'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r linux-x64 --self-contained
- name: Publish CLI self-contained for Windows ARM64
if: matrix.os == 'windows-latest'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r win-arm64 --self-contained
- name: Publish CLI self-contained for Windows x64
if: matrix.os == 'windows-latest'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r win-x64 --self-contained
# Publish WPF
- name: Publish WPF framework-dependent
if: matrix.os == 'windows-latest'
run: dotnet publish Shadowsocks.WPF -c Release --no-restore
# - name: Publish WPF self-contained for Windows ARM64
# if: matrix.os == 'windows-latest'
# run: dotnet publish Shadowsocks.WPF -c Release -r win-arm64 --self-contained
- name: Publish WPF self-contained for Windows x64
if: matrix.os == 'windows-latest'
run: dotnet publish Shadowsocks.WPF -c Release -r win-x64 --self-contained
# Upload CLI
- name: Upload CLI artifacts for Linux ARM64
if: matrix.os == 'ubuntu-20.04'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-cli-${{ github.sha }}-linux-arm64
path: Shadowsocks.CLI/bin/Release/net5.0/linux-arm64/publish/
- name: Upload CLI artifacts for Linux x64
if: matrix.os == 'ubuntu-20.04'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-cli-${{ github.sha }}-linux-x64
path: Shadowsocks.CLI/bin/Release/net5.0/linux-x64/publish/
- name: Upload CLI artifacts for Linux framework-dependent
if: matrix.os == 'ubuntu-20.04'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-cli-${{ github.sha }}-linux
path: Shadowsocks.CLI/bin/Release/net5.0/publish/
- name: Upload CLI artifacts for Windows ARM64
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-cli-${{ github.sha }}-windows-arm64
path: Shadowsocks.CLI/bin/Release/net5.0/win-arm64/publish/
- name: Upload CLI artifacts for Windows x64
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-cli-${{ github.sha }}-windows-x64
path: Shadowsocks.CLI/bin/Release/net5.0/win-x64/publish/
- name: Upload CLI artifacts for Windows framework-dependent
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-cli-${{ github.sha }}-windows
path: Shadowsocks.CLI/bin/Release/net5.0/publish/
# Upload WPF
# - name: Upload WPF artifacts for Windows ARM64
# if: matrix.os == 'windows-latest'
# uses: actions/upload-artifact@v2
# with:
# name: shadowsocks-wpf-${{ github.sha }}-windows-arm64
# path: Shadowsocks.WPF/bin/Release/net5.0-windows10.0.19041.0/win-arm64/publish/
- name: Upload WPF artifacts for Windows x64
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-wpf-${{ github.sha }}-windows-x64
path: Shadowsocks.WPF/bin/Release/net5.0-windows10.0.19041.0/win-x64/publish/
- name: Upload WPF artifacts for Windows framework-dependent
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: shadowsocks-wpf-${{ github.sha }}-windows
path: Shadowsocks.WPF/bin/Release/net5.0-windows10.0.19041.0/publish/

View File

@@ -1,132 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
publish_upload:
name: Publish and upload
strategy:
matrix:
os: [ubuntu-20.04, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: Restore dependencies
if: matrix.os == 'windows-latest'
run: dotnet restore
- name: Build
if: matrix.os == 'windows-latest'
run: dotnet build --no-restore
- name: Test
if: matrix.os == 'windows-latest'
run: dotnet test --no-build --verbosity normal
# Publish CLI
- name: Define MSBuild properties
run: echo "MSBUILD_PROPS=-p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=link -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:InvariantGlobalization=true" >> $GITHUB_ENV
- name: Publish CLI framework-dependent
run: |
dotnet publish Shadowsocks.CLI -c Release
- name: Publish CLI self-contained for Linux ARM64
if: matrix.os == 'ubuntu-20.04'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r linux-arm64 --self-contained
- name: Publish CLI self-contained for Linux x64
if: matrix.os == 'ubuntu-20.04'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r linux-x64 --self-contained
- name: Publish CLI self-contained for Windows ARM64
if: matrix.os == 'windows-latest'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r win-arm64 --self-contained
- name: Publish CLI self-contained for Windows x64
if: matrix.os == 'windows-latest'
run: |
dotnet publish Shadowsocks.CLI -c Release $MSBUILD_PROPS -r win-x64 --self-contained
# Publish WPF
- name: Publish WPF framework-dependent
if: matrix.os == 'windows-latest'
run: dotnet publish Shadowsocks.WPF -c Release --no-restore
# - name: Publish WPF self-contained for Windows ARM64
# if: matrix.os == 'windows-latest'
# run: dotnet publish Shadowsocks.WPF -c Release -r win-arm64 --self-contained
- name: Publish WPF self-contained for Windows x64
if: matrix.os == 'windows-latest'
run: dotnet publish Shadowsocks.WPF -c Release -r win-x64 --self-contained
# Get version
- name: Get version
id: get_version
shell: bash
run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3)
# Package
- name: Package for Linux
if: matrix.os == 'ubuntu-20.04'
env:
ZSTD_CLEVEL: 19
ZSTD_NBTHREADS: 2
run: |
# Shadowsocks.CLI
cd Shadowsocks.CLI/bin/Release/net5.0/publish
tar -acf ../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-linux.tar.zst .
cd ../linux-arm64/publish
tar -acf ../../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-linux-arm64.tar.zst .
cd ../../linux-x64/publish
tar -acf ../../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-linux-x64.tar.zst .
- name: Package for Windows
if: matrix.os == 'windows-latest'
run: |
# WPF
cd Shadowsocks.WPF/bin/Release/net5.0-windows10.0.19041.0/publish
7z a -tzip -mx=9 -mfb=128 ../shadowsocks-wpf-${{ steps.get_version.outputs.VERSION }}-windows.zip .
7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../shadowsocks-wpf-${{ steps.get_version.outputs.VERSION }}-windows.7z .
# cd ../win-arm64/publish
# 7z a -tzip -mx=9 -mfb=128 ../../shadowsocks-wpf-${{ steps.get_version.outputs.VERSION }}-windows-arm64.zip .
# 7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../../shadowsocks-wpf-${{ steps.get_version.outputs.VERSION }}-windows-arm64.7z .
cd ../../win-x64/publish
7z a -tzip -mx=9 -mfb=128 ../../shadowsocks-wpf-${{ steps.get_version.outputs.VERSION }}-windows-x64.zip .
7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../../shadowsocks-wpf-${{ steps.get_version.outputs.VERSION }}-windows-x64.7z .
# CLI
cd ../../../../../Shadowsocks.CLI/bin/Release/net5.0/publish
7z a -tzip -mx=9 -mfb=128 ../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-windows.zip .
7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-windows.7z .
cd ../win-arm64/publish
7z a -tzip -mx=9 -mfb=128 ../../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-windows-arm64.zip .
7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-windows-arm64.7z .
cd ../../win-x64/publish
7z a -tzip -mx=9 -mfb=128 ../../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-windows-x64.zip .
7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../../shadowsocks-cli-${{ steps.get_version.outputs.VERSION }}-windows-x64.7z .
# Release
- name: Upload release assets for Linux
uses: svenstaro/upload-release-action@v2
if: matrix.os == 'ubuntu-20.04'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: Shadowsocks.CLI/bin/Release/net5.0/*.tar.zst
tag: ${{ github.ref }}
file_glob: true
prerelease: true
- name: Upload CLI release assets for Windows
if: matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: Shadowsocks.CLI/bin/Release/net5.0/shadowsocks-wpf-*
tag: ${{ github.ref }}
file_glob: true
prerelease: true
- name: Upload WPF release assets for Windows
if: matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: Shadowsocks.WPF/bin/Release/net5.0-windows10.0.19041.0/shadowsocks-wpf-*
tag: ${{ github.ref }}
file_glob: true
prerelease: true

View File

@@ -182,7 +182,7 @@ publish/
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
# *.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to

View File

@@ -1,3 +1,16 @@
4.4.1.0 2022-02-08
- Add plain/none ciphers
4.4.0.0 2021-01-01
- Security: remove infrastructure of stream ciphers (#3048)
- Show warning message when importing from deprecated legacy ss:// links.
- Other minor bug fixes and improvements
4.3.3.0 2020-12-07
- PAC: Add option for custom sha256sum URL of custom geosite source (#3026)
- Update to .NET Framework 4.8
- Other minor bug fixes and improvements
4.3.2.0 2020-11-05
- PAC: direct connection for private IP ranges by @studentmain (#3008)
- Remove duplicate startup entries (#3012)

View File

@@ -624,7 +624,7 @@ copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
Copyright (C) 2015 clowwindy <clowwindy42@gmail.com>
Copyright (C) 2021 Shadowsocks Project <https://shadowsocks.org>
Copyright (C) 2020 Shadowsocks Project <https://shadowsocks.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -986,6 +986,42 @@ consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
mbed TLS
--------
https://tls.mbed.org
License: https://raw.githubusercontent.com/ARMmbed/mbedtls/master/LICENSE
Newtonsoft.Json
----------
https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/LICENSE.md
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ZXing
-----
@@ -1002,3 +1038,21 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
libsodium
---------
Copyright (c) 2013-2015
Frank Denis <j at pureftpd dot org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,15 @@
OpenSSL library guide for VS2017
# Read NOTES.WIN and NOTES.PERL
# use Visual Studio native tools command prompt
# use activeperl, install NASM assembler
ppm install dmake
# Win32 x86
set PATH=D:\NASM-32;%PATH%
perl Configure VC-WIN32 --release --prefix=C:\Users\home\Downloads\openssl-1.1.0g\x86-build --openssldir=C:\Users\home\Downloads\openssl-1.1.0g\x86-install
nmake
nmake test
# to rebuild
nmake distclean

View File

@@ -1,24 +1,37 @@
<img src="Shadowsocks.WPF/Resources/ssw128.png" alt="[logo]" width="48"/> Shadowsocks for Windows
<img src="shadowsocks-csharp/Resources/ssw128.png" alt="[logo]" width="48"/> Shadowsocks for Windows
=======================
[![Build](https://github.com/shadowsocks/shadowsocks-windows/workflows/Build/badge.svg)](https://github.com/shadowsocks/shadowsocks-windows/actions?query=workflow%3ABuild)
[![Release](https://github.com/shadowsocks/shadowsocks-windows/workflows/Release/badge.svg)](https://github.com/shadowsocks/shadowsocks-windows/actions?query=workflow%3ARelease)
[![Build Status]][Appveyor]
[中文说明]
## Features
- Connect to Shadowsocks servers.
- Automatically set system proxy.
- SIP002 URL scheme.
- SIP003 plugins.
- SIP008 online configuration delivery.
1. System proxy configuration
2. PAC mode and global mode
3. [GeoSite] and user rules
4. Supports HTTP proxy
5. Supports server auto switching
6. Supports UDP relay (see Usage)
7. Supports plugins
## Downloads
Download from [releases](https://github.com/shadowsocks/shadowsocks-windows/releases).
Download the latest release from [release page].
## Usage
## Requirements
- 🚀
.NET Framework 4.8 or higher, Microsoft [Visual C++ 2015 Redistributable] (x86) .
## Basics
1. Find Shadowsocks icon in the notification tray
2. You can add multiple servers in servers menu
3. Select `Enable System Proxy` menu to enable system proxy. Please disable other
proxy addons in your browser, or set them to use system proxy
4. You can also configure your browser proxy manually if you don't want to enable
system proxy. Set Socks5 or HTTP proxy to 127.0.0.1:1080. You can change this
port in `Servers -> Edit Servers`
## PAC
@@ -40,43 +53,118 @@ Download from [releases](https://github.com/shadowsocks/shadowsocks-windows/rele
- To define your own PAC rules, it's recommended to use the `user-rule.txt` file.
- You can also modify `pac.txt` directly. But your modifications won't persist after updating geosite from the upstream.
For Windows10 Store and related applications, please execute the following command under Admin privilege:
```
netsh winhttp import proxy source=ie
```
## Server Auto Switching
1. Load balance: choosing server randomly
2. High availability: choosing the best server (low latency and packet loss)
3. Choose By Total Package Loss: ping and choose. Please also enable
`Availability Statistics` in the menu if you want to use this
4. Write your own strategy by implement IStrategy interface and send us a pull request!
## UDP
For UDP, you need to use SocksCap or ProxyCap to force programs you want
to be proxied to tunnel over Shadowsocks
## Multiple Instances
If you want to manage multiple servers using other tools like SwitchyOmega,
you can start multiple Shadowsocks instances. To avoid configuration conflicts,
copy Shadowsocks to a new directory and choose a different local port.
## Plugins
If you would like to connect to server via a plugin, please set the plugin's
path (relative or absolute) on Edit Servers form.
_Note_: Forward Proxy will not be used while a plugin is enabled.
Details:
[Working with non SIP003 standard Plugin].
## Global hotkeys
Hotkeys could be registered automatically on startup.
If you are using multiple instances of Shadowsocks,
you must set different key combination for each instance.
### How to input?
1. Put focus in the corresponding textbox.
2. Press the key combination that you want to use.
3. Release all keys when you think it is ready.
4. Your input appears in the textbox.
### How to change?
1. Put focus in the corresponding textbox.
2. Press BackSpace key to clear content.
3. Re-input new key combination.
### How to deactivate?
1. Clear content in the textbox that you want to deactivate,
if you want to deactivate all, please clear all textboxes.
2. Press OK button to confirm.
### Meaning of label color
- Green: This key combination is not occupied by other programs and register successfully.
- Yellow: This key combination is occupied by other programs and you have to change to another one.
- Transparent without color: The initial status.
## Server Configuration
Please visit [Servers] for more information.
## Experimental
[Experimental Features]
## Development
- IDE: Visual Studio 2019
- Language: C# 9.0
- SDK: .NET 5
### Build
1. Clone the repository recursively.
```bash
$ git clone --recursive https://github.com/shadowsocks/shadowsocks-windows.git
```
2. Open the repository in VS2019, switch to the _Release_ configuration, and build the solution.
### Contribute
`PR welcome`
You can use the [Source Browser](https://ss-windows.cube64128.xyz/) to review code online.
1. Visual Studio 2019 & .NET Framework 4.8 SDK are required.
2. It is recommended to share your idea on the Issue Board before you start to work,
especially for feature development.
## License
Shadowsocks-windows is licensed under the [GPLv3](LICENSE.txt) license.
[GPLv3]
## Open Source Components / Libraries
```
BouncyCastle.NetCore (MIT) https://github.com/chrishaly/bc-csharp
Caseless.Fody (MIT) https://github.com/Fody/Caseless
Costura.Fody (MIT) https://github.com/Fody/Costura
Fody (MIT) https://github.com/Fody/Fody
GlobalHotKey (GPLv3) https://github.com/kirmir/GlobalHotKey
MdXaml (MIT) https://github.com/whistyun/MdXaml
Newtonsoft.Json (MIT) https://www.newtonsoft.com/json
Privoxy (GPLv2) https://www.privoxy.org
ReactiveUI.WPF (MIT) https://github.com/reactiveui/ReactiveUI
ReactiveUI.Events.WPF (MIT) https://github.com/reactiveui/ReactiveUI
ReactiveUI.Fody (MIT) https://github.com/reactiveui/ReactiveUI
ReactiveUI.Validation (MIT) https://github.com/reactiveui/ReactiveUI.Validation
WPFLocalizationExtension (MS-PL) https://github.com/XAMLMarkupExtensions/WPFLocalizationExtension/
ZXing.Net (Apache 2.0) https://github.com/micjahn/ZXing.Net
libsscrypto (GPLv2) https://github.com/shadowsocks/libsscrypto
Privoxy (GPLv2) https://www.privoxy.org
Sysproxy () https://github.com/Noisyfox/sysproxy
```
[Appveyor]: https://ci.appveyor.com/project/celeron533/shadowsocks-windows
[Build Status]: https://ci.appveyor.com/api/projects/status/tfw57q6eecippsl5/branch/master?svg=true
[release page]: https://github.com/shadowsocks/shadowsocks-csharp/releases
[GeoSite]: https://github.com/v2fly/domain-list-community
[Servers]: https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#linux--server-side
[中文说明]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Shadowsocks-Windows-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E
[Visual C++ 2015 Redistributable]: https://www.microsoft.com/en-us/download/details.aspx?id=53840
[GPLv3]: https://github.com/shadowsocks/shadowsocks-windows/blob/master/LICENSE.txt
[Working with non SIP003 standard Plugin]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Working-with-non-SIP003-standard-Plugin
[Experimental Features]: https://github.com/shadowsocks/shadowsocks-windows/wiki/Experimental

View File

@@ -1,10 +0,0 @@
namespace Shadowsocks.CLI
{
public enum Backend
{
SsRust,
V2Ray,
Legacy,
Pipelines,
}
}

View File

@@ -1,57 +0,0 @@
using Shadowsocks.Models;
using Shadowsocks.Net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.CLI.Client
{
public class Legacy
{
private TCPListener? _tcpListener;
private UDPListener? _udpListener;
public void Start(string listenSocks, string serverAddress, int serverPort, string method, string password, string? plugin, string? pluginOpts, string? pluginArgs)
{
var localEP = IPEndPoint.Parse(listenSocks);
var server = new Server()
{
Host = serverAddress,
Port = serverPort,
Method = method,
Password = password,
Plugin = plugin,
PluginOpts = pluginOpts,
};
if (!string.IsNullOrEmpty(plugin) && !string.IsNullOrEmpty(pluginArgs))
{
var processStartInfo = new ProcessStartInfo(plugin, pluginArgs);
server.PluginArgs = processStartInfo.ArgumentList.ToList();
}
var tcpRelay = new TCPRelay(server);
_tcpListener = new TCPListener(localEP, new List<IStreamService>()
{
tcpRelay,
});
_tcpListener.Start();
var udpRelay = new UDPRelay(server);
_udpListener = new UDPListener(localEP, new List<IDatagramService>()
{
udpRelay,
});
_udpListener.Start();
}
public void Stop()
{
_tcpListener?.Stop();
_udpListener?.Stop();
}
}
}

View File

@@ -1,29 +0,0 @@
using Shadowsocks.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.CLI.Client
{
public class Pipelines
{
private TcpPipeListener? _tcpPipeListener;
public Task Start(string listenSocks, string serverAddress, int serverPort, string method, string? password, string? key, string? plugin, string? pluginOpts, string? pluginArgs)
{
// TODO
var localEP = IPEndPoint.Parse(listenSocks);
var remoteEp = new DnsEndPoint(serverAddress, serverPort);
byte[]? mainKey = null;
if (!string.IsNullOrEmpty(key))
mainKey = Encoding.UTF8.GetBytes(key);
_tcpPipeListener = new(localEP);
return _tcpPipeListener.Start(localEP, remoteEp, method, password, mainKey);
}
public void Stop() => _tcpPipeListener?.Stop();
}
}

View File

@@ -1,208 +0,0 @@
using Shadowsocks.Interop.Utils;
using Shadowsocks.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.CLI
{
public class ConfigConverter
{
/// <summary>
/// Gets or sets whether to prefix group name to server names.
/// </summary>
public bool PrefixGroupName { get; set; }
/// <summary>
/// Gets or sets the list of servers that are not in any groups.
/// </summary>
public List<Server> Servers { get; set; } = new();
public ConfigConverter(bool prefixGroupName = false) => PrefixGroupName = prefixGroupName;
/// <summary>
/// Collects servers from ss:// links or SIP008 delivery links.
/// </summary>
/// <param name="uris">URLs to collect servers from.</param>
/// <param name="cancellationToken">A token that may be used to cancel the asynchronous operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task FromUrls(IEnumerable<Uri> uris, CancellationToken cancellationToken = default)
{
var sip008Links = new List<Uri>();
foreach (var uri in uris)
{
switch (uri.Scheme)
{
case "ss":
{
if (Server.TryParse(uri, out var server))
Servers.Add(server);
break;
}
case "https":
sip008Links.Add(uri);
break;
}
}
if (sip008Links.Count > 0)
{
var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30.0)
};
var tasks = sip008Links.Select(async x => await httpClient.GetFromJsonAsync<Group>(x, JsonHelper.snakeCaseJsonDeserializerOptions, cancellationToken))
.ToList();
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
var group = await finishedTask;
if (group != null)
Servers.AddRange(group.Servers);
tasks.Remove(finishedTask);
}
}
}
/// <summary>
/// Collects servers from SIP008 JSON files.
/// </summary>
/// <param name="paths">JSON file paths.</param>
/// <param name="cancellationToken">A token that may be used to cancel the read operation.</param>
/// <returns>A task that represents the asynchronous read operation.</returns>
public async Task FromSip008Json(IEnumerable<string> paths, CancellationToken cancellationToken = default)
{
foreach (var path in paths)
{
using var jsonFile = new FileStream(path, FileMode.Open);
var group = await JsonSerializer.DeserializeAsync<Group>(jsonFile, JsonHelper.snakeCaseJsonDeserializerOptions, cancellationToken);
if (group != null)
{
if (PrefixGroupName && !string.IsNullOrEmpty(group.Name))
group.Servers.ForEach(x => x.Name = $"{group.Name} - {x.Name}");
Servers.AddRange(group.Servers);
}
}
}
/// <summary>
/// Collects servers from outbounds in V2Ray JSON files.
/// </summary>
/// <param name="paths">JSON file paths.</param>
/// <param name="cancellationToken">A token that may be used to cancel the read operation.</param>
/// <returns>A task that represents the asynchronous read operation.</returns>
public async Task FromV2rayJson(IEnumerable<string> paths, CancellationToken cancellationToken = default)
{
foreach (var path in paths)
{
using var jsonFile = new FileStream(path, FileMode.Open);
var v2rayConfig = await JsonSerializer.DeserializeAsync<Interop.V2Ray.Config>(jsonFile, JsonHelper.camelCaseJsonDeserializerOptions, cancellationToken);
if (v2rayConfig?.Outbounds != null)
{
foreach (var outbound in v2rayConfig.Outbounds)
{
if (outbound.Protocol == "shadowsocks"
&& outbound.Settings is JsonElement jsonElement)
{
var jsonText = jsonElement.GetRawText();
var ssConfig = JsonSerializer.Deserialize<Interop.V2Ray.Protocols.Shadowsocks.OutboundConfigurationObject>(jsonText, JsonHelper.camelCaseJsonDeserializerOptions);
if (ssConfig != null)
foreach (var ssServer in ssConfig.Servers)
{
var server = new Server
{
Name = outbound.Tag,
Host = ssServer.Address,
Port = ssServer.Port,
Method = ssServer.Method,
Password = ssServer.Password
};
Servers.Add(server);
}
}
}
}
}
}
/// <summary>
/// Converts saved servers to ss:// URLs.
/// </summary>
/// <returns>A list of ss:// URLs.</returns>
public List<Uri> ToUrls()
{
var urls = new List<Uri>();
foreach (var server in Servers)
urls.Add(server.ToUrl());
return urls;
}
/// <summary>
/// Converts saved servers to SIP008 JSON.
/// </summary>
/// <param name="path">JSON file path.</param>
/// <param name="cancellationToken">A token that may be used to cancel the write operation.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task ToSip008Json(string path, CancellationToken cancellationToken = default)
{
var group = new Group();
group.Servers.AddRange(Servers);
var fullPath = Path.GetFullPath(path);
var directoryPath = Path.GetDirectoryName(fullPath) ?? throw new ArgumentException("Invalid path", nameof(path));
Directory.CreateDirectory(directoryPath);
using var jsonFile = new FileStream(fullPath, FileMode.Create);
return JsonSerializer.SerializeAsync(jsonFile, group, JsonHelper.snakeCaseJsonSerializerOptions, cancellationToken);
}
/// <summary>
/// Converts saved servers to V2Ray outbounds.
/// </summary>
/// <param name="path">JSON file path.</param>
/// <param name="prefixGroupName">Whether to prefix group name to server names.</param>
/// <param name="cancellationToken">A token that may be used to cancel the write operation.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public Task ToV2rayJson(string path, CancellationToken cancellationToken = default)
{
var v2rayConfig = new Interop.V2Ray.Config
{
Outbounds = new()
};
foreach (var server in Servers)
{
var ssOutbound = Interop.V2Ray.OutboundObject.GetShadowsocks(server);
v2rayConfig.Outbounds.Add(ssOutbound);
}
// enforce outbound tag uniqueness
var serversWithDuplicateTags = v2rayConfig.Outbounds.GroupBy(x => x.Tag)
.Where(x => x.Count() > 1);
foreach (var serversWithSameTag in serversWithDuplicateTags)
{
var duplicates = serversWithSameTag.ToList();
for (var i = 0; i < duplicates.Count; i++)
{
duplicates[i].Tag = $"{duplicates[i].Tag} {i}";
}
}
var fullPath = Path.GetFullPath(path);
var directoryPath = Path.GetDirectoryName(fullPath) ?? throw new ArgumentException("Invalid path", nameof(path));
Directory.CreateDirectory(directoryPath);
using var jsonFile = new FileStream(fullPath, FileMode.Create);
return JsonSerializer.SerializeAsync(jsonFile, v2rayConfig, JsonHelper.camelCaseJsonSerializerOptions, cancellationToken);
}
}
}

View File

@@ -1,175 +0,0 @@
using Shadowsocks.Models;
using Splat;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.CLI
{
internal class Program
{
private static Task<int> Main(string[] args)
{
var clientCommand = new Command("client", "Shadowsocks client.");
clientCommand.AddAlias("c");
clientCommand.AddOption(new Option<Backend>("--backend", "Shadowsocks backend to use. Available backends: shadowsocks-rust, v2ray, legacy, pipelines."));
clientCommand.AddOption(new Option<string?>("--listen", "Address and port to listen on for both SOCKS5 and HTTP proxy."));
clientCommand.AddOption(new Option<string?>("--listen-socks", "Address and port to listen on for SOCKS5 proxy."));
clientCommand.AddOption(new Option<string?>("--listen-http", "Address and port to listen on for HTTP proxy."));
clientCommand.AddOption(new Option<string>("--server-address", "Address of the remote Shadowsocks server to connect to."));
clientCommand.AddOption(new Option<int>("--server-port", "Port of the remote Shadowsocks server to connect to."));
clientCommand.AddOption(new Option<string>("--method", "Encryption method to use for remote Shadowsocks server."));
clientCommand.AddOption(new Option<string?>("--password", "Password to use for remote Shadowsocks server."));
clientCommand.AddOption(new Option<string?>("--key", "Encryption key (NOT password!) to use for remote Shadowsocks server."));
clientCommand.AddOption(new Option<string?>("--plugin", "Plugin binary path."));
clientCommand.AddOption(new Option<string?>("--plugin-opts", "Plugin options."));
clientCommand.AddOption(new Option<string?>("--plugin-args", "Plugin startup arguments."));
clientCommand.Handler = CommandHandler.Create(
async (Backend backend, string? listen, string? listenSocks, string? listenHttp, string serverAddress, int serverPort, string method, string? password, string? key, string? plugin, string? pluginOpts, string? pluginArgs, CancellationToken cancellationToken) =>
{
Locator.CurrentMutable.RegisterConstant<ConsoleLogger>(new());
if (string.IsNullOrEmpty(listenSocks))
{
LogHost.Default.Error("You must specify SOCKS5 listen address and port.");
return;
}
Client.Legacy? legacyClient = null;
Client.Pipelines? pipelinesClient = null;
switch (backend)
{
case Backend.SsRust:
LogHost.Default.Error("Not implemented.");
break;
case Backend.V2Ray:
LogHost.Default.Error("Not implemented.");
break;
case Backend.Legacy:
if (!string.IsNullOrEmpty(password))
{
legacyClient = new();
legacyClient.Start(listenSocks, serverAddress, serverPort, method, password, plugin, pluginOpts, pluginArgs);
}
else
LogHost.Default.Error("The legacy backend requires password.");
break;
case Backend.Pipelines:
pipelinesClient = new();
await pipelinesClient.Start(listenSocks, serverAddress, serverPort, method, password, key, plugin, pluginOpts, pluginArgs);
break;
default:
LogHost.Default.Error("Not implemented.");
break;
}
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromHours(1.00), cancellationToken);
Console.WriteLine("An hour has passed.");
}
switch (backend)
{
case Backend.SsRust:
LogHost.Default.Error("Not implemented.");
break;
case Backend.V2Ray:
LogHost.Default.Error("Not implemented.");
break;
case Backend.Legacy:
legacyClient?.Stop();
break;
case Backend.Pipelines:
pipelinesClient?.Stop();
break;
default:
LogHost.Default.Error("Not implemented.");
break;
}
});
var serverCommand = new Command("server", "Shadowsocks server.");
serverCommand.AddAlias("s");
serverCommand.Handler = CommandHandler.Create(
() =>
{
Console.WriteLine("Not implemented.");
});
var convertConfigCommand = new Command("convert-config", "Convert between different config formats. Supported formats: SIP002 links, SIP008 delivery JSON, and V2Ray JSON (outbound only).");
convertConfigCommand.AddOption(new Option<string[]?>("--from-urls", "URL conversion sources. Multiple URLs are supported. Supported protocols are ss:// and https://."));
convertConfigCommand.AddOption(new Option<string[]?>("--from-sip008-json", "SIP008 JSON conversion sources. Multiple JSON files are supported."));
convertConfigCommand.AddOption(new Option<string[]?>("--from-v2ray-json", "V2Ray JSON conversion sources. Multiple JSON files are supported."));
convertConfigCommand.AddOption(new Option<bool>("--prefix-group-name", "Whether to prefix group name to server names after conversion."));
convertConfigCommand.AddOption(new Option<bool>("--to-urls", "Convert to ss:// links and print."));
convertConfigCommand.AddOption(new Option<string?>("--to-sip008-json", "Convert to SIP008 JSON and save to the specified path."));
convertConfigCommand.AddOption(new Option<string?>("--to-v2ray-json", "Convert to V2Ray JSON and save to the specified path."));
convertConfigCommand.Handler = CommandHandler.Create(
async (string[]? fromUrls, string[]? fromSip008Json, string[]? fromV2rayJson, bool prefixGroupName, bool toUrls, string? toSip008Json, string? toV2rayJson, CancellationToken cancellationToken) =>
{
var configConverter = new ConfigConverter(prefixGroupName);
try
{
if (fromUrls != null)
{
var uris = new List<Uri>();
foreach (var url in fromUrls)
{
if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
uris.Add(uri);
else
Console.WriteLine($"Invalid URL: {url}");
}
await configConverter.FromUrls(uris, cancellationToken);
}
if (fromSip008Json != null)
await configConverter.FromSip008Json(fromSip008Json, cancellationToken);
if (fromV2rayJson != null)
await configConverter.FromV2rayJson(fromV2rayJson, cancellationToken);
if (toUrls)
{
var uris = configConverter.ToUrls();
foreach (var uri in uris)
Console.WriteLine(uri.AbsoluteUri);
}
if (!string.IsNullOrEmpty(toSip008Json))
await configConverter.ToSip008Json(toSip008Json, cancellationToken);
if (!string.IsNullOrEmpty(toV2rayJson))
await configConverter.ToV2rayJson(toV2rayJson, cancellationToken);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
});
var utilitiesCommand = new Command("utilities", "Shadowsocks-related utilities.")
{
convertConfigCommand,
};
utilitiesCommand.AddAlias("u");
utilitiesCommand.AddAlias("util");
utilitiesCommand.AddAlias("utils");
var rootCommand = new RootCommand("CLI for Shadowsocks server and client implementation in C#.")
{
clientCommand,
serverCommand,
utilitiesCommand,
};
Console.OutputEncoding = Encoding.UTF8;
return rootCommand.InvokeAsync(args);
}
}
}

View File

@@ -1,41 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<AssemblyName>sscli</AssemblyName>
<PackageId>Shadowsocks.CLI</PackageId>
<Authors>Clowwindy &amp; The Community</Authors>
<Product>Shadowsocks CLI</Product>
<Description>CLI for Shadowsocks server and client implementation in C#.</Description>
<Copyright>© 2021 Clowwindy &amp; The Community</Copyright>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageProjectUrl>https://github.com/shadowsocks/shadowsocks-windows</PackageProjectUrl>
<RepositoryUrl>https://github.com/shadowsocks/shadowsocks-windows</RepositoryUrl>
<RepositoryType>Public</RepositoryType>
<PackageIcon>ssw128.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="..\LICENSE.txt">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<None Include="..\Shadowsocks.WPF\Resources\ssw128.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21223.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shadowsocks.Interop\Shadowsocks.Interop.csproj" />
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" />
<ProjectReference Include="..\Shadowsocks.Protocol\Shadowsocks.Protocol.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.Interop.Settings
{
public class InteropSettings
{
public string SsRustPath { get; set; }
public string V2RayCorePath { get; set; }
public InteropSettings()
{
SsRustPath = "";
V2RayCorePath = "";
}
}
}

View File

@@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,131 +0,0 @@
using Shadowsocks.Models;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Shadowsocks.Interop.SsRust
{
public class Config : IGroup<Server>
{
/// <inheritdoc/>
public int Version { get; set; }
/// <inheritdoc/>
public List<Server> Servers { get; set; }
/// <summary>
/// Gets or sets the listening address.
/// </summary>
public string LocalAddress { get; set; }
/// <summary>
/// Gets or sets the listening port.
/// </summary>
public int LocalPort { get; set; }
/// <inheritdoc cref="Server.Host"/>
[JsonPropertyName("server")]
public string? Host { get; set; }
/// <inheritdoc cref="Server.Port"/>
[JsonPropertyName("server_port")]
public int Port { get; set; }
/// <inheritdoc cref="Server.Password"/>
public string? Password { get; set; }
/// <inheritdoc cref="Server.Method"/>
public string? Method { get; set; }
/// <inheritdoc cref="Server.Plugin"/>
public string? Plugin { get; set; }
/// <inheritdoc cref="Server.PluginOpts"/>
public string? PluginOpts { get; set; }
/// <inheritdoc cref="Server.PluginArgs"/>
public List<string>? PluginArgs { get; set; }
/// <summary>
/// Gets or sets the timeout for UDP associations in seconds.
/// Defaults to 300 seconds (5 minutes).
/// </summary>
public int? UdpTimeout { get; set; }
/// <summary>
/// Gets or sets the maximum number of UDP associations.
/// Defaults to 0 (unlimited).
/// </summary>
public int UdpMaxAssociations { get; set; }
/// <summary>
/// Gets or sets the server manager address.
/// </summary>
public string? ManagerAddress { get; set; }
/// <summary>
/// Gets or sets the server manager port.
/// </summary>
public int ManagerPort { get; set; }
/// <summary>
/// Gets or sets the DNS server used to resolve hostnames.
/// </summary>
public string? Dns { get; set; }
/// <summary>
/// Gets or sets the mode.
/// Defaults to tcp_only.
/// Can also be tcp_and_udp or udp_only.
/// </summary>
public string Mode { get; set; }
/// <summary>
/// Gets or sets TCP_NODELAY.
/// Defaults to false.
/// </summary>
public bool NoDelay { get; set; }
/// <summary>
/// Gets or sets the soft and hard limit of file descriptors.
/// </summary>
public int Nofile { get; set; }
/// <summary>
/// Gets or sets whether IPv6 addresses take precedence over IPv4 addresses for resolved hostnames.
/// Defaults to false.
/// </summary>
public bool Ipv6First { get; set; }
public Config()
{
Version = 1;
Servers = new();
LocalAddress = "";
LocalPort = 1080;
Mode = "tcp_only";
}
/// <summary>
/// Gets the default configuration for Linux.
/// </summary>
public static Config DefaultLinux => new()
{
LocalAddress = "::1",
Mode = "tcp_and_udp",
NoDelay = true,
Nofile = 32768,
Ipv6First = true,
};
/// <summary>
/// Gets the default configuration for Windows.
/// </summary>
public static Config DefaultWindows => new()
{
LocalAddress = "::1",
Mode = "tcp_and_udp",
NoDelay = true,
Ipv6First = true,
};
}
}

View File

@@ -1,42 +0,0 @@
using Shadowsocks.Models;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Shadowsocks.Interop.Utils
{
public static class JsonHelper
{
public static readonly JsonSerializerOptions camelCaseJsonSerializerOptions = new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
public static readonly JsonSerializerOptions snakeCaseJsonSerializerOptions = new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy(),
WriteIndented = true,
};
public static readonly JsonSerializerOptions camelCaseJsonDeserializerOptions = new JsonSerializerOptions()
{
AllowTrailingCommas = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReadCommentHandling = JsonCommentHandling.Skip,
WriteIndented = true,
};
public static readonly JsonSerializerOptions snakeCaseJsonDeserializerOptions = new JsonSerializerOptions()
{
AllowTrailingCommas = true,
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy(),
ReadCommentHandling = JsonCommentHandling.Skip,
WriteIndented = true,
};
}
}

View File

@@ -1,37 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray
{
public class ApiObject
{
/// <summary>
/// Gets or sets the outbound tag for the API.
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Gets or sets the list of API services to enable.
/// </summary>
public List<string> Services { get; set; }
public ApiObject()
{
Tag = "";
Services = new();
}
/// <summary>
/// Gets the default API object.
/// </summary>
public static ApiObject Default => new()
{
Tag = "api",
Services = new()
{
"HandlerService",
"LoggerService",
"StatsService",
},
};
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray
{
public class Config
{
public LogObject? Log { get; set; }
public ApiObject? Api { get; set; }
public DnsObject? Dns { get; set; }
public RoutingObject? Routing { get; set; }
public PolicyObject? Policy { get; set; }
public List<InboundObject>? Inbounds { get; set; }
public List<OutboundObject>? Outbounds { get; set; }
public TransportObject? Transport { get; set; }
public StatsObject? Stats { get; set; }
public ReverseObject? Reverse { get; set; }
/// <summary>
/// Gets the default configuration.
/// </summary>
public static Config Default => new()
{
Log = new(),
Api = ApiObject.Default,
Dns = new(),
Routing = new(),
Policy = PolicyObject.Default,
Inbounds = new(),
Outbounds = new(),
Stats = new(),
};
}
}

View File

@@ -1,45 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Dns
{
public class ServerObject
{
/// <summary>
/// Gets or sets the DNS server address.
/// Supports UDP and DoH.
/// </summary>
public string Address { get; set; }
/// <summary>
/// Gets or sets the DNS server port.
/// Defaults to 53.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the client IP
/// to include in DNS queries.
/// </summary>
public string? ClientIp { get; set; }
/// <summary>
/// Gets or sets the list of domains
/// that prefers this DNS server.
/// </summary>
public List<string> Domains { get; set; }
/// <summary>
/// Gets or sets the ranges of IP addresses
/// this DNS server is expected to return.
/// </summary>
public List<string> ExpectIPs { get; set; }
public ServerObject()
{
Address = "";
Port = 53;
Domains = new();
ExpectIPs = new();
}
}
}

View File

@@ -1,43 +0,0 @@
using Shadowsocks.Interop.V2Ray.Dns;
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray
{
public class DnsObject
{
/// <summary>
/// Gets or sets the dictionary storing hosts.
/// The key is the hostname.
/// The value can either be a hostname or an IP address.
/// </summary>
public Dictionary<string, string> Hosts { get; set; }
/// <summary>
/// Gets or sets the list of DNS servers.
/// A DNS server can either be a <see cref="ServerObject"/> or a string.
/// </summary>
public List<object> Servers { get; set; }
/// <summary>
/// Gets or sets the client IP used when sending requests to DNS server.
/// </summary>
public string? ClientIp { get; set; }
/// <summary>
/// Gets or sets whether to disable internal DNS cache.
/// Defaults to false, or DNS cache is enabled.
/// </summary>
public bool DisableCache { get; set; }
/// <summary>
/// Gets or sets the inbound tag for DNS traffic.
/// </summary>
public string? Tag { get; set; }
public DnsObject()
{
Hosts = new();
Servers = new();
}
}
}

View File

@@ -1,15 +0,0 @@
namespace Shadowsocks.Interop.V2Ray
{
public class FakeDnsObject
{
/// <summary>
/// Gets or sets the IP pool CIDR.
/// </summary>
public string IpPool { get; set; } = "198.18.0.0/15";
/// <summary>
/// Gets or sets the IP pool size.
/// </summary>
public long PoolSize { get; set; } = 65535L;
}
}

View File

@@ -1,35 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Inbound
{
public class AllocateObject
{
/// <summary>
/// Gets or sets the port allocation strategy.
/// Defaults to "always".
/// Available values: "always" | "random"
/// </summary>
public string Strategy { get; set; }
/// <summary>
/// Gets or sets the random port refreshing interval in minutes.
/// Defaults to 5 minutes.
/// </summary>
public int? Refresh { get; set; }
/// <summary>
/// Gets or sets the number of random ports.
/// Defaults to 3.
/// </summary>
public int? Concurrency { get; set; }
public AllocateObject()
{
Strategy = "always";
}
public static AllocateObject Default => new()
{
Refresh = 5,
Concurrency = 3,
};
}
}

View File

@@ -1,58 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Inbound
{
public class SniffingObject
{
/// <summary>
/// Gets or sets whether to enable sniffing.
/// Defaults to true (enabled).
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the list of protocols that destination override is enabled.
/// </summary>
public List<string> DestOverride { get; set; }
/// <summary>
/// Gets or sets whether the target address is sniffed
/// solely based on metadata.
/// Defaults to false.
/// Change to true to use FakeDNS.
/// </summary>
public bool MetadataOnly { get; set; }
public SniffingObject()
{
Enabled = true;
DestOverride = new()
{
"http",
"tls",
};
}
public static SniffingObject Default => new()
{
Enabled = false,
DestOverride = new()
{
"http",
"tls",
},
};
public static SniffingObject DefaultFakeDns => new()
{
Enabled = true,
DestOverride = new()
{
"http",
"tls",
"fakedns",
},
MetadataOnly = true,
};
}
}

View File

@@ -1,42 +0,0 @@
using Shadowsocks.Interop.V2Ray.Inbound;
using Shadowsocks.Interop.V2Ray.Transport;
namespace Shadowsocks.Interop.V2Ray
{
public class InboundObject
{
public string Tag { get; set; }
public string? Listen { get; set; }
public object? Port { get; set; }
public string Protocol { get; set; }
public object? Settings { get; set; }
public StreamSettingsObject? StreamSettings { get; set; }
public SniffingObject? Sniffing { get; set; }
public AllocateObject? Allocate { get; set; }
public InboundObject()
{
Tag = "";
Protocol = "";
}
public static InboundObject DefaultLocalSocks => new()
{
Tag = "socks-in",
Listen = "127.0.0.1",
Port = 1080,
Protocol = "socks",
Settings = Protocols.Socks.InboundConfigurationObject.Default,
Sniffing = SniffingObject.Default,
};
public static InboundObject DefaultLocalHttp => new()
{
Tag = "http-in",
Listen = "127.0.0.1",
Port = 8080,
Protocol = "http",
Sniffing = SniffingObject.Default,
};
}
}

View File

@@ -1,31 +0,0 @@
namespace Shadowsocks.Interop.V2Ray
{
public class LogObject
{
/// <summary>
/// Gets or sets the path to the access log file.
/// Defaults to empty, which prints to stdout.
/// </summary>
public string Access { get; set; }
/// <summary>
/// Gets or sets the path to the error log file.
/// Defaults to empty, which prints to stdout.
/// </summary>
public string Error { get; set; }
/// <summary>
/// Gets or sets the log level.
/// Defaults to warning.
/// Available values: "debug" | "info" | "warning" | "error" | "none"
/// </summary>
public string Loglevel { get; set; }
public LogObject()
{
Access = "";
Error = "";
Loglevel = "warning";
}
}
}

View File

@@ -1,25 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Outbound
{
public class MuxObject
{
/// <summary>
/// Gets or sets whether to enable mux.
/// Defaults to false (disabled).
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the concurrency for a single TCP connection when using mux.
/// Defaults to 8.
/// Range: [1, 1024].
/// Set to -1 to disable the mux module.
/// </summary>
public int Concurrency { get; set; }
public MuxObject()
{
Enabled = false;
Concurrency = 8;
}
}
}

View File

@@ -1,30 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Outbound
{
public class ProxySettingsObject
{
/// <summary>
/// Gets or sets the tag of the outbound
/// used as the proxy.
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Gets or sets whether to keep the protocol
/// itself's transport layer intact.
/// Defaults to false, or only proxy internal TCP traffic.
/// Set to true to proxy the protocol.
/// The tag will act as a forward proxy.
/// </summary>
public bool TransportLayer { get; set; }
public ProxySettingsObject()
{
Tag = "";
}
public static ProxySettingsObject Default => new()
{
TransportLayer = true,
};
}
}

View File

@@ -1,88 +0,0 @@
using Shadowsocks.Interop.V2Ray.Outbound;
using Shadowsocks.Interop.V2Ray.Transport;
using Shadowsocks.Models;
using System;
using System.Net;
namespace Shadowsocks.Interop.V2Ray
{
public class OutboundObject
{
public string Tag { get; set; }
public string? SendThrough { get; set; }
public string Protocol { get; set; }
public object? Settings { get; set; }
public StreamSettingsObject? StreamSettings { get; set; }
public ProxySettingsObject? ProxySettings { get; set; }
public MuxObject? Mux { get; set; }
public OutboundObject()
{
Tag = "";
Protocol = "";
}
/// <summary>
/// Gets the <see cref="OutboundObject"/> for the SOCKS server.
/// </summary>
/// <param name="name">SOCKS server name. Used as outbound tag.</param>
/// <param name="socksEndPoint">The SOCKS server.</param>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
public static OutboundObject GetSocks(string name, DnsEndPoint socksEndPoint, string username = "", string password = "") => new()
{
Tag = name,
Protocol = "socks",
Settings = new Protocols.Socks.OutboundConfigurationObject(socksEndPoint, username, password),
};
/// <summary>
/// Gets the <see cref="OutboundObject"/> for the Shadowsocks server.
/// Plugins are not supported.
/// </summary>
/// <param name="server"></param>
/// <returns></returns>
public static OutboundObject GetShadowsocks(IServer server)
{
if (!string.IsNullOrEmpty(server.Plugin))
throw new InvalidOperationException("V2Ray doesn't support SIP003 plugins.");
return new()
{
Tag = server.Name,
Protocol = "shadowsocks",
Settings = new Protocols.Shadowsocks.OutboundConfigurationObject(server.Host, server.Port, server.Method, server.Password),
};
}
/// <summary>
/// Gets the <see cref="OutboundObject"/> for the Trojan server.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="password"></param>
/// <returns></returns>
public static OutboundObject GetTrojan(string name, string address, int port, string password) => new()
{
Tag = name,
Protocol = "trojan",
Settings = new Protocols.Trojan.OutboundConfigurationObject(address, port, password),
};
/// <summary>
/// Gets the <see cref="OutboundObject"/> for the VMess server.
/// </summary>
/// <param name="name"></param>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="id"></param>
/// <returns></returns>
public static OutboundObject GetVMess(string name, string address, int port, string id) => new()
{
Tag = name,
Protocol = "vmess",
Settings = new Protocols.VMess.OutboundConfigurationObject(address, port, id),
};
}
}

View File

@@ -1,24 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Policy
{
public class LevelPolicyObject
{
public int? Handshake { get; set; }
public int? ConnIdle { get; set; }
public int? UplinkOnly { get; set; }
public int? DownlinkOnly { get; set; }
public bool? StatsUserUplink { get; set; }
public bool? StatsUserDownlink { get; set; }
public int? BufferSize { get; set; }
public static LevelPolicyObject Default => new()
{
Handshake = 4,
ConnIdle = 300,
UplinkOnly = 2,
DownlinkOnly = 5,
StatsUserUplink = false,
StatsUserDownlink = false,
BufferSize = 512,
};
}
}

View File

@@ -1,18 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Policy
{
public class SystemPolicyObject
{
public bool StatsInboundUplink { get; set; }
public bool StatsInboundDownlink { get; set; }
public bool StatsOutboundUplink { get; set; }
public bool StatsOutboundDownlink { get; set; }
public static SystemPolicyObject Default => new()
{
StatsInboundUplink = true,
StatsInboundDownlink = true,
StatsOutboundUplink = true,
StatsOutboundDownlink = true,
};
}
}

View File

@@ -1,20 +0,0 @@
using Shadowsocks.Interop.V2Ray.Policy;
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray
{
public class PolicyObject
{
public Dictionary<string, LevelPolicyObject>? Levels { get; set; }
public SystemPolicyObject? System { get; set; }
/// <summary>
/// Gets the default policy object.
/// </summary>
public static PolicyObject Default => new()
{
Levels = new(),
System = SystemPolicyObject.Default,
};
}
}

View File

@@ -1,14 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols
{
public class AccountObject
{
public string User { get; set; }
public string Pass { get; set; }
public AccountObject()
{
User = "";
Pass = "";
}
}
}

View File

@@ -1,14 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols.Freedom
{
public class OutboundConfigurationObject
{
public string DomainStrategy { get; set; }
public string? Redirect { get; set; }
public int? UserLevel { get; set; }
public OutboundConfigurationObject()
{
DomainStrategy = "AsIs";
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Text.Json.Serialization;
namespace Shadowsocks.Interop.V2Ray.Protocols.Shadowsocks
{
public class InboundConfigurationObject
{
public string? Email { get; set; }
public string Method { get; set; }
public string Password { get; set; }
public int? Level { get; set; }
public string Network { get; set; }
public InboundConfigurationObject()
{
Method = "chacha20-ietf-poly1305";
Password = Guid.NewGuid().ToString();
Network = "tcp,udp";
}
}
}

View File

@@ -1,22 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.Shadowsocks
{
public class OutboundConfigurationObject
{
public List<ServerObject> Servers { get; set; }
public OutboundConfigurationObject()
{
Servers = new();
}
public OutboundConfigurationObject(string address, int port, string method, string password)
{
Servers = new()
{
new(address, port, method, password),
};
}
}
}

View File

@@ -1,35 +0,0 @@
using System.Text.Json.Serialization;
namespace Shadowsocks.Interop.V2Ray.Protocols.Shadowsocks
{
public class ServerObject
{
public string? Email { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Method { get; set; }
public string Password { get; set; }
public int? Level { get; set; }
public ServerObject()
{
Address = "";
Port = 8388;
Method = "chacha20-ietf-poly1305";
Password = "";
}
public ServerObject(string address, int port, string method, string password)
{
Address = address;
Port = port;
Method = method;
Password = password;
}
}
}

View File

@@ -1,19 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.Socks
{
public class InboundConfigurationObject
{
public string? Auth { get; set; }
public List<AccountObject>? Accounts { get; set; }
public bool? Udp { get; set; }
public string? Ip { get; set; }
public int? UserLevel { get; set; }
public static InboundConfigurationObject Default => new()
{
Udp = true,
Ip = "127.0.0.1",
};
}
}

View File

@@ -1,23 +0,0 @@
using System.Collections.Generic;
using System.Net;
namespace Shadowsocks.Interop.V2Ray.Protocols.Socks
{
public class OutboundConfigurationObject
{
public List<ServerObject> Servers { get; set; }
public OutboundConfigurationObject()
{
Servers = new();
}
public OutboundConfigurationObject(DnsEndPoint socksEndPoint, string username = "", string password = "")
{
Servers = new()
{
new(socksEndPoint, username, password),
};
}
}
}

View File

@@ -1,38 +0,0 @@
using System.Collections.Generic;
using System.Net;
namespace Shadowsocks.Interop.V2Ray.Protocols.Socks
{
public class ServerObject
{
public string Address { get; set; }
public int Port { get; set; }
public List<UserObject>? Users { get; set; }
public ServerObject()
{
Address = "";
Port = 0;
}
public ServerObject(DnsEndPoint socksEndPoint, string? username = null, string? password = null)
{
Address = socksEndPoint.Host;
Port = socksEndPoint.Port;
Users = new();
var hasCredential = !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password);
if (hasCredential)
{
var user = new UserObject()
{
User = username!, // null check already performed at line 23.
Pass = password!,
};
Users = new()
{
user,
};
}
}
}
}

View File

@@ -1,14 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols.Trojan
{
public class ClientObject
{
public string Password { get; set; }
public string? Email { get; set; }
public int? Level { get; set; }
public ClientObject()
{
Password = "";
}
}
}

View File

@@ -1,22 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols.Trojan
{
public class FallbackObject
{
public string? Alpn { get; set; }
public string? Path { get; set; }
public object Dest { get; set; }
public int? Xver { get; set; }
public FallbackObject()
{
Dest = 0;
}
public static FallbackObject Default => new()
{
Alpn = "",
Path = "",
Xver = 0,
};
}
}

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.Trojan
{
public class InboundConfigurationObject
{
public List<ClientObject> Clients { get; set; }
public List<FallbackObject> Fallbacks { get; set; }
public InboundConfigurationObject()
{
Clients = new();
Fallbacks = new();
}
}
}

View File

@@ -1,22 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.Trojan
{
public class OutboundConfigurationObject
{
public List<ServerObject> Servers { get; set; }
public OutboundConfigurationObject()
{
Servers = new();
}
public OutboundConfigurationObject(string address, int port, string password)
{
Servers = new()
{
new(address, port, password),
};
}
}
}

View File

@@ -1,25 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols.Trojan
{
public class ServerObject
{
public string Address { get; set; }
public int Port { get; set; }
public string Password { get; set; }
public string? Email { get; set; }
public int? Level { get; set; }
public ServerObject()
{
Address = "";
Port = 0;
Password = "";
}
public ServerObject(string address, int port, string password)
{
Address = address;
Port = port;
Password = password;
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols
{
public class UserObject : AccountObject
{
public int Level { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Protocols.VMess
{
public class DetourObject
{
public string To { get; set; }
public DetourObject()
{
To = "";
}
}
}

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.VMess
{
public class InboundConfigurationObject
{
public List<UserObject> Clients { get; set; }
public UserObject? Default { get; set; }
public DetourObject? Detour { get; set; }
public InboundConfigurationObject()
{
Clients = new();
}
}
}

View File

@@ -1,22 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.VMess
{
public class OutboundConfigurationObject
{
public List<ServerObject> Vnext { get; set; }
public OutboundConfigurationObject()
{
Vnext = new();
}
public OutboundConfigurationObject(string address, int port, string id)
{
Vnext = new()
{
new(address, port, id),
};
}
}
}

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Protocols.VMess
{
public class ServerObject
{
public string Address { get; set; }
public int Port { get; set; }
public List<UserObject> Users { get; set; }
public ServerObject()
{
Address = "";
Port = 0;
Users = new();
}
public ServerObject(string address, int port, string id)
{
Address = address;
Port = port;
Users = new()
{
new(id),
};
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace Shadowsocks.Interop.V2Ray.Protocols.VMess
{
/// <summary>
/// The user object for VMess AEAD.
/// </summary>
public class UserObject
{
public string Id { get; set; }
public string? Email { get; set; }
public int Level { get; set; }
public UserObject(string id = "")
{
Id = id;
}
public static UserObject Default => new()
{
Id = Guid.NewGuid().ToString(),
};
}
}

View File

@@ -1,21 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Reverse
{
public class BridgeObject
{
/// <summary>
/// Gets or sets the inbound tag for the bridge.
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Gets or sets the domain name for the bridge.
/// Can be omitted.
/// </summary>
public string? Domain { get; set; }
public BridgeObject()
{
Tag = "";
}
}
}

View File

@@ -1,21 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Reverse
{
public class PortalObject
{
/// <summary>
/// Gets or sets the outbound tag for the portal.
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Gets or sets the domain name for the portal.
/// </summary>
public string Domain { get; set; }
public PortalObject()
{
Tag = "";
Domain = "";
}
}
}

View File

@@ -1,17 +0,0 @@
using Shadowsocks.Interop.V2Ray.Reverse;
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray
{
public class ReverseObject
{
public List<BridgeObject> Bridges { get; set; }
public List<PortalObject> Portals { get; set; }
public ReverseObject()
{
Bridges = new();
Portals = new();
}
}
}

View File

@@ -1,24 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Routing
{
public class BalancerObject
{
/// <summary>
/// Gets or sets the outbound tag for the load balancer.
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Gets or sets a list of outbound tags
/// to include in the load balancer.
/// </summary>
public List<string> Selector { get; set; }
public BalancerObject()
{
Tag = "";
Selector = new();
}
}
}

View File

@@ -1,38 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Shadowsocks.Interop.V2Ray.Routing
{
public class RuleObject
{
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string Type { get; set; }
public List<string>? Domain { get; set; }
public List<string>? Ip { get; set; }
public object? Port { get; set; }
public object? SourcePort { get; set; }
public string? Network { get; set; }
public List<string>? Source { get; set; }
public List<string>? User { get; set; }
public List<string>? InboundTag { get; set; }
public List<string>? Protocol { get; set; }
public string? Attrs { get; set; }
public string? OutboundTag { get; set; }
public string? BalancerTag { get; set; }
public RuleObject()
{
Type = "field";
}
public static RuleObject DefaultOutbound => new()
{
OutboundTag = "",
};
public static RuleObject DefaultBalancer => new()
{
BalancerTag = "",
};
}
}

View File

@@ -1,52 +0,0 @@
using Shadowsocks.Interop.V2Ray.Routing;
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray
{
public class RoutingObject
{
/// <summary>
/// Gets or sets the domain strategy used for routing.
/// Default value: AsIs.
/// Available values: "AsIs" | "IPIfNonMatch" | "IPOnDemand"
/// </summary>
public string DomainStrategy { get; set; }
/// <summary>
/// Gets or sets the domain matcher used for routing.
/// Default value: "linear".
/// Available values: "linear" | "mph"
/// </summary>
public string DomainMatcher { get; set; }
/// <summary>
/// Gets or sets the list of routing rules.
/// </summary>
public List<RuleObject> Rules { get; set; }
/// <summary>
/// Gets or sets the list of load balancers.
/// </summary>
public List<BalancerObject>? Balancers { get; set; }
public RoutingObject()
{
DomainStrategy = "AsIs";
DomainMatcher = "linear";
Rules = new();
}
public static RoutingObject Default => new()
{
DomainStrategy = "IPOnDemand",
DomainMatcher = "mph",
};
public static RoutingObject DefaultBalancers => new()
{
DomainStrategy = "IPOnDemand",
DomainMatcher = "mph",
Balancers = new(),
};
}
}

View File

@@ -1,6 +0,0 @@
namespace Shadowsocks.Interop.V2Ray
{
public class StatsObject
{
}
}

View File

@@ -1,30 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class CertificateObject
{
public string Usage { get; set; }
public string? CertificateFile { get; set; }
public string? KeyFile { get; set; }
public List<string>? Certificate { get; set; }
public List<string>? Key { get; set; }
public CertificateObject()
{
Usage = "encipherment";
}
public static CertificateObject DefaultFromFile => new()
{
CertificateFile = "",
KeyFile = "",
};
public static CertificateObject DefaultEmbedded => new()
{
Certificate = new(),
Key = new(),
};
}
}

View File

@@ -1,29 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class DomainSocketObject
{
/// <summary>
/// Gets or sets the path to the unix domain socket file.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets whether the domain socket is abstract.
/// Defaults to false.
/// </summary>
public bool Abstract { get; set; }
/// <summary>
/// Gets or sets whether padding is used.
/// Defaults to false.
/// </summary>
public bool Padding { get; set; }
public DomainSocketObject()
{
Path = "";
Abstract = false;
Padding = false;
}
}
}

View File

@@ -1,23 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Transport.Header
{
public class HeaderObject
{
/// <summary>
/// Gets or sets the header type.
/// Defaults to none.
/// Available values:
/// none
/// srtp
/// utp
/// wechat-video
/// dtls
/// wireguard
/// </summary>
public string Type { get; set; }
public HeaderObject()
{
Type = "none";
}
}
}

View File

@@ -1,47 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Transport.Header.Http
{
public class HttpRequestObject
{
public string Version { get; set; }
public string Method { get; set; }
public List<string> Path { get; set; }
public Dictionary<string, List<string>> Headers { get; set; }
public HttpRequestObject()
{
Version = "1.1";
Method = "GET";
Path = new()
{
"/",
};
Headers = new()
{
["Host"] = new()
{
"www.baidu.com",
"www.bing.com",
},
["User-Agent"] = new()
{
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46",
},
["Accept-Encoding"] = new()
{
"gzip, deflate",
},
["Connection"] = new()
{
"keep-alive",
},
["Pragma"] = new()
{
"no-cache",
},
};
}
}
}

View File

@@ -1,44 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Transport.Header.Http
{
public class HttpResponseObject
{
public string Version { get; set; }
public string Status { get; set; }
public string Reason { get; set; }
public Dictionary<string, List<string>> Headers { get; set; }
public HttpResponseObject()
{
Version = "1.1";
Status = "200";
Reason = "OK";
Headers = new()
{
["Content-Type"] = new()
{
"application/octet-stream",
"video/mpeg",
},
["Transfer-Encoding"] = new()
{
"chunked",
},
["Connection"] = new()
{
"keep-alive",
},
["Pragma"] = new()
{
"no-cache",
},
["Cache-Control"] = new()
{
"private",
"no-cache",
},
};
}
}
}

View File

@@ -1,18 +0,0 @@
using Shadowsocks.Interop.V2Ray.Transport.Header.Http;
namespace Shadowsocks.Interop.V2Ray.Transport.Header
{
public class HttpHeaderObject : HeaderObject
{
public HttpRequestObject request { get; set; }
public HttpResponseObject response { get; set; }
public HttpHeaderObject()
{
Type = "http";
request = new();
response = new();
}
}
}

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class HttpObject
{
public List<string> Host { get; set; }
public string Path { get; set; }
public HttpObject()
{
Host = new();
Path = "/";
}
}
}

View File

@@ -1,30 +0,0 @@
using Shadowsocks.Interop.V2Ray.Transport.Header;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class KcpObject
{
public int Mtu { get; set; }
public int Tti { get; set; }
public int UplinkCapacity { get; set; }
public int DownlinkCapacity { get; set; }
public bool Congestion { get; set; }
public int ReadBufferSize { get; set; }
public int WriteBufferSize { get; set; }
public HeaderObject Header { get; set; }
public string Seed { get; set; }
public KcpObject()
{
Mtu = 1350;
Tti = 50;
UplinkCapacity = 5;
DownlinkCapacity = 20;
Congestion = false;
ReadBufferSize = 2;
WriteBufferSize = 2;
Header = new();
Seed = "";
}
}
}

View File

@@ -1,31 +0,0 @@
using Shadowsocks.Interop.V2Ray.Transport.Header;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class QuicObject
{
/// <summary>
/// Gets or sets the encryption method.
/// Defaults to "none" (no encryption).
/// Available values: "none" | "aes-128-gcm" | "chacha20-poly1305"
/// </summary>
public string Security { get; set; }
/// <summary>
/// Gets or sets the encryption key.
/// </summary>
public string Key { get; set; }
/// <summary>
/// Gets or sets the header options.
/// </summary>
public HeaderObject Header { get; set; }
public QuicObject()
{
Security = "none";
Key = "";
Header = new();
}
}
}

View File

@@ -1,14 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class SockoptObject
{
public int Mark { get; set; }
public bool TcpFastOpen { get; set; }
public string? Tproxy { get; set; }
public static SockoptObject DefaultLinux => new()
{
Tproxy = "off",
};
}
}

View File

@@ -1,29 +0,0 @@
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class StreamSettingsObject : TransportObject
{
/// <summary>
/// Gets or sets the transport protocol type.
/// Defaults to "tcp".
/// Available values: "tcp" | "kcp" | "ws" | "http" | "domainsocket" | "quic"
/// </summary>
public string? Network { get; set; }
/// <summary>
/// Gets or sets the transport encryption type.
/// Defaults to "none" (no encryption).
/// Available values: "none" | "tls"
/// </summary>
public string? Security { get; set; }
public TlsObject? TlsSettings { get; set; }
public SockoptObject? Sockopt { get; set; }
public static StreamSettingsObject DefaultWsTls => new()
{
Network = "ws",
Security = "tls",
TlsSettings = new(),
};
}
}

View File

@@ -1,28 +0,0 @@
using Shadowsocks.Interop.V2Ray.Transport.Header;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class TcpObject
{
/// <summary>
/// Gets or sets whether to use PROXY protocol.
/// </summary>
public bool AcceptProxyProtocol { get; set; }
/// <summary>
/// Gets or sets the header options.
/// </summary>
public object Header { get; set; }
public TcpObject()
{
AcceptProxyProtocol = false;
Header = new HeaderObject();
}
public static TcpObject DefaultHttp => new()
{
Header = new HttpHeaderObject(),
};
}
}

View File

@@ -1,13 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class TlsObject
{
public string? ServerName { get; set; }
public bool AllowInsecure { get; set; }
public List<string>? Alpn { get; set; }
public List<CertificateObject>? Certificates { get; set; }
public bool DisableSystemRoot { get; set; }
}
}

View File

@@ -1,31 +0,0 @@
using System.Collections.Generic;
namespace Shadowsocks.Interop.V2Ray.Transport
{
public class WebSocketObject
{
/// <summary>
/// Gets or sets whether to use PROXY protocol.
/// </summary>
public bool AcceptProxyProtocol { get; set; }
/// <summary>
/// Gets or sets the HTTP query path.
/// Defaults to "/".
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets HTTP header key-value pairs.
/// Defaults to empty.
/// </summary>
public Dictionary<string, string> Headers { get; set; }
public WebSocketObject()
{
AcceptProxyProtocol = false;
Path = "/";
Headers = new();
}
}
}

View File

@@ -1,14 +0,0 @@
using Shadowsocks.Interop.V2Ray.Transport;
namespace Shadowsocks.Interop.V2Ray
{
public class TransportObject
{
public TcpObject? TcpSettings { get; set; }
public KcpObject? KcpSettings { get; set; }
public WebSocketObject? WsSettings { get; set; }
public HttpObject? HttpSettings { get; set; }
public QuicObject? QuicSettings { get; set; }
public DomainSocketObject? DsSettings { get; set; }
}
}

View File

@@ -1,222 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.Net
{
// cache first packet for duty-chain pattern listener
public class CachedNetworkStream : Stream
{
// 256 byte first packet buffer should enough for 99.999...% situation
// socks5: 0x05 0x....
// http-pac: GET /pac HTTP/1.1
// http-proxy: /[a-z]+ .+ HTTP\/1\.[01]/i
public const int MaxCache = 256;
public Socket Socket { get; private set; }
private readonly Stream s;
private byte[] cache = new byte[MaxCache];
private long cachePtr = 0;
private long readPtr = 0;
public CachedNetworkStream(Socket socket)
{
s = new NetworkStream(socket);
Socket = socket;
}
/// <summary>
/// Only for test purpose
/// </summary>
/// <param name="stream"></param>
public CachedNetworkStream(Stream stream)
{
s = stream;
}
public override bool CanRead => s.CanRead;
// we haven't run out of cache
public override bool CanSeek => cachePtr == readPtr;
public override bool CanWrite => s.CanWrite;
public override long Length => s.Length;
public override long Position { get => readPtr; set => Seek(value, SeekOrigin.Begin); }
public override void Flush()
{
s.Flush();
}
//public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
//{
// var endPtr = buffer.Length + readPtr; // expected ptr after operation
// var uncachedLen = Math.Max(endPtr - cachePtr, 0); // how many data from socket
// var cachedLen = buffer.Length - uncachedLen; // how many data from cache
// var emptyCacheLen = MaxCache - cachePtr; // how many cache remain
// int readLen = 0;
// var cachedMem = buffer[..(int)cachedLen];
// var uncachedMem = buffer[(int)cachedLen..];
// if (cachedLen > 0)
// {
// cache[(int)readPtr..(int)(readPtr + cachedLen)].CopyTo(cachedMem);
// readPtr += cachedLen;
// readLen += (int)cachedLen;
// }
// if (uncachedLen > 0)
// {
// int readStreamLen = await s.ReadAsync(cachedMem, cancellationToken);
// int lengthToCache = (int)Math.Min(emptyCacheLen, readStreamLen); // how many data need to cache
// if (lengthToCache > 0)
// {
// uncachedMem[0..lengthToCache].CopyTo(cache[(int)cachePtr..]);
// cachePtr += lengthToCache;
// }
// readPtr += readStreamLen;
// readLen += readStreamLen;
// }
// return readLen;
//}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(byte[] buffer, int offset, int count)
{
Span<byte> span = buffer.AsSpan(offset, count);
return Read(span);
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public override int Read(Span<byte> buffer)
{
// how many data from socket
// r: readPtr, c: cachePtr, e: endPtr
// ptr 0 r c e
// cached ####+++++
// read ++++
// ptr 0 c r e
// cached #####
// read +++++
var endPtr = buffer.Length + readPtr; // expected ptr after operation
var uncachedLen = Math.Max(endPtr - Math.Max(cachePtr, readPtr), 0);
var cachedLen = buffer.Length - uncachedLen; // how many data from cache
var emptyCacheLen = MaxCache - cachePtr; // how many cache remain
int readLen = 0;
Span<byte> cachedSpan = buffer[..(int)cachedLen];
Span<byte> uncachedSpan = buffer[(int)cachedLen..];
if (cachedLen > 0)
{
cache[(int)readPtr..(int)(readPtr + cachedLen)].CopyTo(cachedSpan);
readPtr += cachedLen;
readLen += (int)cachedLen;
}
if (uncachedLen > 0)
{
int readStreamLen = s.Read(uncachedSpan);
// how many data need to cache
int lengthToCache = (int)Math.Min(emptyCacheLen, readStreamLen);
if (lengthToCache > 0)
{
uncachedSpan[0..lengthToCache].ToArray().CopyTo(cache, cachePtr);
cachePtr += lengthToCache;
}
readPtr += readStreamLen;
readLen += readStreamLen;
}
return readLen;
}
/// <summary>
/// Read first block, will never read into non-cache range
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public int ReadFirstBlock(Span<byte> buffer)
{
Seek(0, SeekOrigin.Begin);
int len = Math.Min(MaxCache, buffer.Length);
return Read(buffer[0..len]);
}
/// <summary>
/// Seek position, only support seek to cached range when we haven't read into non-cache range
/// </summary>
/// <param name="offset"></param>
/// <param name="origin">Set it to System.IO.SeekOrigin.Begin, otherwise it will throw System.NotSupportedException</param>
/// <exception cref="IOException"></exception>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
/// <returns></returns>
public override long Seek(long offset, SeekOrigin origin)
{
if (!CanSeek) throw new NotSupportedException("Non cache data has been read");
if (origin != SeekOrigin.Begin) throw new NotSupportedException("We don't know network stream's length");
if (offset < 0 || offset > cachePtr) throw new NotSupportedException("Can't seek to uncached position");
readPtr = offset;
return Position;
}
/// <summary>
/// Useless
/// </summary>
/// <param name="value"></param>
/// <exception cref="NotSupportedException"></exception>
public override void SetLength(long value)
{
s.SetLength(value);
}
/// <summary>
/// Write to underly stream
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return s.WriteAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
/// Write to underly stream
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
public override void Write(byte[] buffer, int offset, int count)
{
s.Write(buffer, offset, count);
}
protected override void Dispose(bool disposing)
{
s.Dispose();
base.Dispose(disposing);
}
}
}

View File

@@ -1,313 +0,0 @@
using CryptoBase;
using Shadowsocks.Net.Crypto.Exception;
using Shadowsocks.Net.Crypto.Stream;
using Splat;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
namespace Shadowsocks.Net.Crypto.AEAD
{
public abstract class AEADCrypto : CryptoBase, IEnableLogger
{
// We are using the same saltLen and keyLen
private const string Info = "ss-subkey";
private static readonly byte[] InfoBytes = Encoding.ASCII.GetBytes(Info);
// every connection should create its own buffer
private readonly byte[] buffer = new byte[65536];
private int bufPtr = 0;
public const int ChunkLengthBytes = 2;
public const uint ChunkLengthMask = 0x3FFFu;
protected CipherFamily cipherFamily;
protected CipherInfo CipherInfo;
protected static byte[] masterKey = Array.Empty<byte>();
protected byte[] sessionKey = Array.Empty<byte>();
protected int keyLen;
protected int saltLen;
protected int tagLen;
protected int nonceLen;
protected byte[] salt;
protected byte[] nonce;
// Is first packet
protected bool saltReady;
// Is first chunk(tcp request)
protected bool tcpRequestSent;
// [len(2)][lentag][data][datatag]
private int ChunkOverhead => tagLen * 2 + 2;
public AEADCrypto(string method, string password)
: base(method, password)
{
CipherInfo = GetCiphers()[method.ToLower()];
cipherFamily = CipherInfo.Type;
AEADCipherParameter parameter = (AEADCipherParameter)CipherInfo.CipherParameter;
keyLen = parameter.KeySize;
saltLen = parameter.SaltSize;
tagLen = parameter.TagSize;
nonceLen = parameter.NonceSize;
InitKey(password);
salt = new byte[saltLen];
// Initialize all-zero nonce for each connection
nonce = new byte[nonceLen];
this.Log().Debug($"masterkey {instanceId} {masterKey} {keyLen}");
this.Log().Debug($"nonce {instanceId} {nonce} {keyLen}");
}
protected abstract Dictionary<string, CipherInfo> GetCiphers();
protected void InitKey(string password)
{
byte[] passbuf = Encoding.UTF8.GetBytes(password);
// init master key
if (masterKey == null)
{
masterKey = new byte[keyLen];
}
if (masterKey.Length != keyLen)
{
Array.Resize(ref masterKey, keyLen);
}
StreamCrypto.LegacyDeriveKey(passbuf, masterKey, keyLen);
// init session key
sessionKey = new byte[keyLen];
}
public virtual void InitCipher(byte[] salt, bool isEncrypt)
{
this.salt = new byte[saltLen];
Array.Copy(salt, this.salt, saltLen);
HKDF.DeriveKey(HashAlgorithmName.SHA1, masterKey, sessionKey, salt, InfoBytes);
this.Log().Debug($"salt {instanceId}", salt, saltLen);
this.Log().Debug($"sessionkey {instanceId}", sessionKey, keyLen);
}
public abstract int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher);
public abstract int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher);
#region TCP
[MethodImpl(MethodImplOptions.Synchronized)]
public override int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
// push data
Span<byte> tmp = buffer.AsSpan(0, plain.Length + bufPtr);
plain.CopyTo(tmp.Slice(bufPtr));
int outlength = 0;
if (!saltReady)
{
saltReady = true;
// Generate salt
byte[] saltBytes = RNG.GetBytes(saltLen);
InitCipher(saltBytes, true);
saltBytes.CopyTo(cipher);
outlength = saltLen;
}
while (true)
{
// calculate next chunk size
int bufSize = tmp.Length;
if (bufSize <= 0)
{
return outlength;
}
int chunklength = (int)Math.Min(bufSize, ChunkLengthMask);
// read next chunk
int encChunkLength = ChunkEncrypt(tmp.Slice(0, chunklength), cipher.Slice(outlength));
tmp = tmp.Slice(chunklength);
outlength += encChunkLength;
// check if we have enough space for outbuf
// if not, keep buf for next run, at this condition, buffer is not empty
if (outlength + ChunkOverhead > cipher.Length)
{
this.Log().Debug("enc outbuf almost full, giving up");
// write rest data to head of shared buffer
tmp.CopyTo(buffer);
bufPtr = tmp.Length;
return outlength;
}
// check if buffer empty
bufSize = tmp.Length;
if (bufSize <= 0)
{
this.Log().Debug("No more data to encrypt, leaving");
return outlength;
}
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
int outlength = 0;
// drop all into buffer
Span<byte> tmp = buffer.AsSpan(0, cipher.Length + bufPtr);
cipher.CopyTo(tmp.Slice(bufPtr));
int bufSize = tmp.Length;
this.Log().Debug($"{instanceId} decrypt tcp, read salt: {!saltReady}");
if (!saltReady)
{
// check if we get the leading salt
if (bufSize <= saltLen)
{
// need more, write back cache
tmp.CopyTo(buffer);
bufPtr = tmp.Length;
return outlength;
}
saltReady = true;
byte[] salt = tmp.Slice(0, saltLen).ToArray();
tmp = tmp.Slice(saltLen);
InitCipher(salt, false);
}
// handle chunks
while (true)
{
bufSize = tmp.Length;
// check if we have any data
if (bufSize <= 0)
{
this.Log().Debug("No data in buffer");
return outlength;
}
// first get chunk length
if (bufSize <= ChunkLengthBytes + tagLen)
{
// so we only have chunk length and its tag?
// wait more
this.Log().Debug($"{instanceId} not enough data to decrypt chunk. write {tmp.Length} byte back to buffer.");
tmp.CopyTo(buffer);
bufPtr = tmp.Length;
return outlength;
}
this.Log().Debug($"{instanceId} try decrypt to offset {outlength}");
int len = ChunkDecrypt(plain.Slice(outlength), tmp);
if (len <= 0)
{
this.Log().Debug($"{instanceId} no chunk decrypted, write {tmp.Length} byte back to buffer.");
// no chunk decrypted
tmp.CopyTo(buffer);
bufPtr = tmp.Length;
return outlength;
}
this.Log().Debug($"{instanceId} decrypted {len} to offset {outlength}");
// drop decrypted data
tmp = tmp.Slice(ChunkLengthBytes + tagLen + len + tagLen);
outlength += len;
// logger.Debug("aead dec outlength " + outlength);
if (outlength + ChunkOverhead > cipher.Length)
{
this.Log().Debug($"{instanceId} output almost full, write {tmp.Length} byte back to buffer.");
tmp.CopyTo(buffer);
bufPtr = tmp.Length;
return outlength;
}
bufSize = tmp.Length;
// check if we already done all of them
if (bufSize <= 0)
{
bufPtr = 0;
this.Log().Debug($"{instanceId} no data in buffer, already all done");
return outlength;
}
}
}
#endregion
#region UDP
[MethodImpl(MethodImplOptions.Synchronized)]
public override int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
RNG.GetSpan(cipher.Slice(0, saltLen));
InitCipher(cipher.Slice(0, saltLen).ToArray(), true);
return saltLen + CipherEncrypt(plain, cipher.Slice(saltLen));
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
InitCipher(cipher.Slice(0, saltLen).ToArray(), false);
return CipherDecrypt(plain, cipher.Slice(saltLen));
}
#endregion
[MethodImpl(MethodImplOptions.Synchronized)]
private int ChunkEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
if (plain.Length > ChunkLengthMask)
{
this.Log().Error("enc chunk too big");
throw new CryptoErrorException();
}
byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plain.Length));
int cipherLenSize = CipherEncrypt(lenbuf, cipher);
nonce.Increment();
int cipherDataSize = CipherEncrypt(plain, cipher.Slice(cipherLenSize));
nonce.Increment();
return cipherLenSize + cipherDataSize;
}
[MethodImpl(MethodImplOptions.Synchronized)]
private int ChunkDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
// try to dec chunk len
byte[] chunkLengthByte = new byte[ChunkLengthBytes];
CipherDecrypt(chunkLengthByte, cipher.Slice(0, ChunkLengthBytes + tagLen));
ushort chunkLength = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(chunkLengthByte, 0));
if (chunkLength > ChunkLengthMask)
{
// we get invalid chunk
this.Log().Error($"{instanceId} Invalid chunk length: {chunkLength}");
throw new CryptoErrorException();
}
// logger.Debug("Get the real chunk len:" + chunkLength);
int bufSize = cipher.Length;
if (bufSize < ChunkLengthBytes + tagLen /* we haven't remove them */+ chunkLength + tagLen)
{
this.Log().Debug($"{instanceId} need {ChunkLengthBytes + tagLen + chunkLength + tagLen}, but have {cipher.Length}");
return 0;
}
nonce.Increment();
// we have enough data to decrypt one chunk
// drop chunk len and its tag from buffer
int len = CipherDecrypt(plain, cipher.Slice(ChunkLengthBytes + tagLen, chunkLength + tagLen));
nonce.Increment();
this.Log().Debug($"{instanceId} decrypted {len} byte chunk used {ChunkLengthBytes + tagLen + chunkLength + tagLen} from {cipher.Length}");
return len;
}
}
}

View File

@@ -1,72 +0,0 @@
#nullable enable
using CryptoBase;
using CryptoBase.Abstractions.SymmetricCryptos;
using System;
using System.Collections.Generic;
namespace Shadowsocks.Net.Crypto.AEAD
{
public class AEADCryptoBaseCrypto : AEADCrypto
{
private IAEADCrypto? _crypto;
public AEADCryptoBaseCrypto(string method, string password) : base(method, password)
{
}
#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new()
{
{ "aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm) },
{ "aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm) },
{ "aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm) },
{ "chacha20-ietf-poly1305", new CipherInfo("chacha20-ietf-poly1305", 32, 32, 12, 16, CipherFamily.Chacha20Poly1305) },
{ "xchacha20-ietf-poly1305", new CipherInfo("xchacha20-ietf-poly1305", 32, 32, 24, 16, CipherFamily.XChacha20Poly1305) },
};
protected override Dictionary<string, CipherInfo> GetCiphers()
{
return _ciphers;
}
public static Dictionary<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}
#endregion
public override void InitCipher(byte[] salt, bool isEncrypt)
{
base.InitCipher(salt, isEncrypt);
_crypto?.Dispose();
_crypto = cipherFamily switch
{
CipherFamily.AesGcm => AEADCryptoCreate.AesGcm(sessionKey),
CipherFamily.Chacha20Poly1305 => AEADCryptoCreate.ChaCha20Poly1305(sessionKey),
CipherFamily.XChacha20Poly1305 => AEADCryptoCreate.XChaCha20Poly1305(sessionKey),
_ => throw new NotSupportedException()
};
}
public override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
_crypto!.Encrypt(nonce, plain, cipher.Slice(0, plain.Length), cipher.Slice(plain.Length, tagLen));
return plain.Length + tagLen;
}
public override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
var clen = cipher.Length - tagLen;
var ciphertxt = cipher.Slice(0, clen);
var tag = cipher.Slice(clen);
_crypto!.Decrypt(nonce, ciphertxt, tag, plain.Slice(0, clen));
return clen;
}
public override void Dispose()
{
_crypto?.Dispose();
}
}
}

View File

@@ -1,111 +0,0 @@
namespace Shadowsocks.Net.Crypto
{
public enum CipherFamily
{
Plain,
Table,
AesGcm,
AesCfb,
AesCtr,
Chacha20,
Chacha20Poly1305,
XChacha20Poly1305,
Rc4,
Rc4Md5,
}
public enum CipherStandardState
{
InUse,
Deprecated, // popup warning when updated
Hidden, // enabled by hidden flag in config file
Unstable, // not in standard list or wip, only gui info
}
public class CipherParameter
{
public int KeySize;
}
public class StreamCipherParameter : CipherParameter
{
public int IvSize;
public override string ToString()
{
return $"stream (key:{KeySize * 8}, iv:{IvSize * 8})";
}
}
public class AEADCipherParameter : CipherParameter
{
public int SaltSize;
public int TagSize;
public int NonceSize;
public override string ToString()
{
return $"aead (key:{KeySize * 8}, salt:{SaltSize * 8}, tag:{TagSize * 8}, nonce:{NonceSize * 8})";
}
}
public class CipherInfo
{
public string Name;
public CipherFamily Type;
public CipherParameter CipherParameter;
public CipherStandardState StandardState = CipherStandardState.InUse;
#region Stream ciphers
public CipherInfo(string name, int keySize, int ivSize, CipherFamily type, CipherStandardState state = CipherStandardState.Hidden)
{
Type = type;
Name = name;
StandardState = state;
CipherParameter = new StreamCipherParameter
{
KeySize = keySize,
IvSize = ivSize,
};
}
#endregion
#region AEAD ciphers
public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type, CipherStandardState state = CipherStandardState.InUse)
{
Type = type;
Name = name;
StandardState = state;
CipherParameter = new AEADCipherParameter
{
KeySize = keySize,
SaltSize = saltSize,
NonceSize = nonceSize,
TagSize = tagSize,
};
}
#endregion
public override string ToString()
{
// TODO:
// return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString(StandardState.ToString().ToLower())})";
return "";
}
public string ToString(bool verbose)
{
if (!verbose)
{
return ToString();
}
return $"{Name} {StandardState} {CipherParameter}";
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
namespace Shadowsocks.Net.Crypto
{
public abstract class CryptoBase : ICrypto
{
private static int _currentId = 0;
public const int MaxInputSize = 32768;
public const int MAX_DOMAIN_LEN = 255;
public const int ADDR_PORT_LEN = 2;
public const int ADDR_ATYP_LEN = 1;
public const int ATYP_IPv4 = 0x01;
public const int ATYP_DOMAIN = 0x03;
public const int ATYP_IPv6 = 0x04;
public const int MD5Length = 16;
// for debugging only, give it a number to trace data stream
public readonly int instanceId;
protected CryptoBase(string method, string password)
{
instanceId = _currentId;
_currentId++;
Method = method;
Password = password;
}
protected string Method;
protected string Password;
public override string ToString()
{
return $"{instanceId}({Method},{Password})";
}
public abstract int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher);
public abstract int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher);
public abstract int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher);
public abstract int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher);
public int AddressBufferLength { get; set; } = -1;
public abstract void Dispose();
}
}

View File

@@ -1,97 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Shadowsocks.Net.Crypto.AEAD;
using Shadowsocks.Net.Crypto.Stream;
namespace Shadowsocks.Net.Crypto
{
public static class CryptoFactory
{
public static string DefaultCipher = "chacha20-ietf-poly1305";
private static readonly Dictionary<string, Type> _registeredEncryptors = new Dictionary<string, Type>();
private static readonly Dictionary<string, CipherInfo> ciphers = new Dictionary<string, CipherInfo>();
private static readonly Type[] ConstructorTypes = { typeof(string), typeof(string) };
static CryptoFactory()
{
foreach (var method in StreamPlainNativeCrypto.SupportedCiphers())
{
if (!_registeredEncryptors.ContainsKey(method.Key))
{
ciphers.Add(method.Key, method.Value);
_registeredEncryptors.Add(method.Key, typeof(StreamPlainNativeCrypto));
}
}
foreach (var method in StreamCryptoBaseCrypto.SupportedCiphers())
{
if (!_registeredEncryptors.ContainsKey(method.Key))
{
ciphers.Add(method.Key, method.Value);
_registeredEncryptors.Add(method.Key, typeof(StreamCryptoBaseCrypto));
}
}
foreach (var method in AEADCryptoBaseCrypto.SupportedCiphers())
{
if (!_registeredEncryptors.ContainsKey(method.Key))
{
ciphers.Add(method.Key, method.Value);
_registeredEncryptors.Add(method.Key, typeof(AEADCryptoBaseCrypto));
}
}
}
public static ICrypto GetEncryptor(string method, string password)
{
if (string.IsNullOrEmpty(method))
{
// todo
//method = IoCManager.Container.Resolve<IDefaultCrypto>().GetDefaultMethod();
}
method = method.ToLowerInvariant();
bool ok = _registeredEncryptors.TryGetValue(method, out Type t);
if (!ok)
{
t = _registeredEncryptors[DefaultCipher];
}
ConstructorInfo c = t?.GetConstructor(ConstructorTypes) ??
throw new TypeLoadException("can't load constructor");
if (c == null) throw new System.Exception("Invalid ctor");
ICrypto result = (ICrypto)c.Invoke(new object[] { method, password });
return result;
}
public static string DumpRegisteredEncryptor()
{
var sb = new StringBuilder();
sb.Append(Environment.NewLine);
sb.AppendLine("-------------------------");
sb.AppendLine("Registered Encryptor Info");
foreach (var encryptor in _registeredEncryptors)
{
sb.AppendLine($"{ciphers[encryptor.Key].ToString(true)} => {encryptor.Value.Name}");
}
// use ----- instead of =======, so when user paste it to Github, it won't became title
sb.AppendLine("-------------------------");
return sb.ToString();
}
public static CipherInfo GetCipherInfo(string name)
{
// TODO: Replace cipher when required not exist
return ciphers[name];
}
public static IEnumerable<CipherInfo> ListAvaliableCiphers()
{
return ciphers.Values;
}
}
}

View File

@@ -1,14 +0,0 @@
using CryptoBase.Digests.MD5;
namespace Shadowsocks.Net.Crypto
{
public static class CryptoUtils
{
public static byte[] MD5(byte[] b)
{
var hash = new byte[CryptoBase.MD5Length];
MD5Utils.Default(b, hash);
return hash;
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
namespace Shadowsocks.Net.Crypto
{
public interface ICrypto : IDisposable
{
int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher);
int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher);
int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher);
int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher);
}
}

View File

@@ -1,50 +0,0 @@
using System;
using System.Security.Cryptography;
namespace Shadowsocks.Net.Crypto
{
public static class RNG
{
private static RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
public static void Reload()
{
_rng.Dispose();
_rng = new RNGCryptoServiceProvider();
}
public static void GetSpan(Span<byte> span)
{
_rng.GetBytes(span);
}
public static Span<byte> GetSpan(int length)
{
Span<byte> span = new byte[length];
_rng.GetBytes(span);
return span;
}
public static byte[] GetBytes(int length)
{
byte[] buf = new byte[length];
_rng.GetBytes(buf);
return buf;
}
public static void GetBytes(byte[] buf, int len)
{
try
{
_rng.GetBytes(buf, 0, len);
}
catch
{
// the backup way
byte[] tmp = new byte[len];
_rng.GetBytes(tmp);
Buffer.BlockCopy(tmp, 0, buf, 0, len);
}
}
}
}

View File

@@ -1,178 +0,0 @@
using Splat;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
namespace Shadowsocks.Net.Crypto.Stream
{
public abstract class StreamCrypto : CryptoBase, IEnableLogger
{
// shared by TCP decrypt UDP encrypt and decrypt
protected static byte[] sharedBuffer = new byte[65536];
// Is first packet
protected bool ivReady;
protected CipherFamily cipherFamily;
protected CipherInfo CipherInfo;
// long-time master key
protected static byte[] key = Array.Empty<byte>();
protected byte[] iv = Array.Empty<byte>();
protected int keyLen;
protected int ivLen;
public StreamCrypto(string method, string password)
: base(method, password)
{
CipherInfo = GetCiphers()[method.ToLower()];
cipherFamily = CipherInfo.Type;
StreamCipherParameter parameter = (StreamCipherParameter)CipherInfo.CipherParameter;
keyLen = parameter.KeySize;
ivLen = parameter.IvSize;
InitKey(password);
this.Log().Debug($"key {instanceId} {key} {keyLen}");
}
protected abstract Dictionary<string, CipherInfo> GetCiphers();
private void InitKey(string password)
{
byte[] passbuf = Encoding.UTF8.GetBytes(password);
key ??= new byte[keyLen];
if (key.Length != keyLen)
{
Array.Resize(ref key, keyLen);
}
LegacyDeriveKey(passbuf, key, keyLen);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LegacyDeriveKey(byte[] password, byte[] key, int keylen)
{
byte[] result = new byte[password.Length + MD5Length];
int i = 0;
byte[] md5sum = Array.Empty<byte>();
while (i < keylen)
{
if (i == 0)
{
md5sum = CryptoUtils.MD5(password);
}
else
{
Array.Copy(md5sum, 0, result, 0, MD5Length);
Array.Copy(password, 0, result, MD5Length, password.Length);
md5sum = CryptoUtils.MD5(result);
}
Array.Copy(md5sum, 0, key, i, Math.Min(MD5Length, keylen - i));
i += MD5Length;
}
}
protected virtual void InitCipher(byte[] iv, bool isEncrypt)
{
if (ivLen == 0)
{
return;
}
this.iv = new byte[ivLen];
Array.Copy(iv, this.iv, ivLen);
}
protected abstract int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher);
protected abstract int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher);
#region TCP
[MethodImpl(MethodImplOptions.Synchronized)]
public override int Encrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
int cipherOffset = 0;
this.Log().Debug($"{instanceId} encrypt TCP, generate iv: {!ivReady}");
if (!ivReady)
{
// Generate IV
byte[] ivBytes = RNG.GetBytes(ivLen);
InitCipher(ivBytes, true);
ivBytes.CopyTo(cipher);
cipherOffset = ivLen;
cipher = cipher.Slice(cipherOffset);
ivReady = true;
}
int clen = CipherEncrypt(plain, cipher);
this.Log().Debug($"plain {instanceId} {Convert.ToBase64String(plain)}");
this.Log().Debug($"cipher {instanceId} {Convert.ToBase64String(cipher.Slice(0, clen))}");
this.Log().Debug($"iv {instanceId} {iv} {ivLen}");
return clen + cipherOffset;
}
private int recieveCtr = 0;
[MethodImpl(MethodImplOptions.Synchronized)]
public override int Decrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
this.Log().Debug($"{instanceId} decrypt TCP, read iv: {!ivReady}");
int cipherOffset = 0;
// is first packet, need read iv
if (!ivReady)
{
// push to buffer in case of not enough data
cipher.CopyTo(sharedBuffer.AsSpan(recieveCtr));
recieveCtr += cipher.Length;
// not enough data for read iv, return 0 byte data
if (recieveCtr <= ivLen)
{
return 0;
}
// start decryption
ivReady = true;
if (ivLen > 0)
{
// read iv
byte[] iv = sharedBuffer.AsSpan(0, ivLen).ToArray();
InitCipher(iv, false);
}
else
{
InitCipher(Array.Empty<byte>(), false);
}
cipherOffset += ivLen;
}
// read all data from buffer
int len = CipherDecrypt(plain, cipher.Slice(cipherOffset));
this.Log().Debug($"cipher {instanceId} {Convert.ToBase64String(cipher.Slice(cipherOffset))}");
this.Log().Debug($"plain {instanceId} {Convert.ToBase64String(plain.Slice(0, len))}");
this.Log().Debug($"iv {instanceId} {iv} {ivLen}");
return len;
}
#endregion
#region UDP
[MethodImpl(MethodImplOptions.Synchronized)]
public override int EncryptUDP(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
byte[] iv = RNG.GetBytes(ivLen);
iv.CopyTo(cipher);
InitCipher(iv, true);
return ivLen + CipherEncrypt(plain, cipher.Slice(ivLen));
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override int DecryptUDP(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
InitCipher(cipher.Slice(0, ivLen).ToArray(), false);
return CipherDecrypt(plain, cipher.Slice(ivLen));
}
#endregion
}
}

View File

@@ -1,89 +0,0 @@
#nullable enable
using CryptoBase;
using CryptoBase.Abstractions.SymmetricCryptos;
using CryptoBase.Digests.MD5;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Shadowsocks.Net.Crypto.Stream
{
public class StreamCryptoBaseCrypto : StreamCrypto
{
private IStreamCrypto? _crypto;
public StreamCryptoBaseCrypto(string method, string password) : base(method, password)
{
}
protected override void InitCipher(byte[] iv, bool isEncrypt)
{
base.InitCipher(iv, isEncrypt);
_crypto?.Dispose();
if (cipherFamily == CipherFamily.Rc4Md5)
{
Span<byte> temp = stackalloc byte[keyLen + ivLen];
var realKey = new byte[MD5Length];
key.CopyTo(temp);
iv.CopyTo(temp.Slice(keyLen));
MD5Utils.Fast440(temp, realKey);
_crypto = StreamCryptoCreate.Rc4(realKey);
return;
}
_crypto = cipherFamily switch
{
CipherFamily.AesCfb => StreamCryptoCreate.AesCfb(isEncrypt, key, iv),
CipherFamily.Chacha20 => StreamCryptoCreate.ChaCha20(key, iv),
CipherFamily.Rc4 => StreamCryptoCreate.Rc4(key),
_ => throw new NotSupportedException()
};
}
protected override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
return CipherUpdate(plain, cipher);
}
protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
return CipherUpdate(cipher, plain);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output)
{
_crypto!.Update(input, output);
return input.Length;
}
#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new()
{
{ "aes-128-cfb", new CipherInfo("aes-128-cfb", 16, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) },
{ "aes-192-cfb", new CipherInfo("aes-192-cfb", 24, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) },
{ "aes-256-cfb", new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable) },
{ "chacha20-ietf", new CipherInfo("chacha20-ietf", 32, 12, CipherFamily.Chacha20) },
{ "rc4", new CipherInfo("rc4", 16, 0, CipherFamily.Rc4) },
{ "rc4-md5", new CipherInfo("rc4-md5", 16, 16, CipherFamily.Rc4Md5) },
};
public static Dictionary<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}
protected override Dictionary<string, CipherInfo> GetCiphers()
{
return _ciphers;
}
#endregion
public override void Dispose()
{
_crypto?.Dispose();
}
}
}

View File

@@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
namespace Shadowsocks.Net.Crypto.Stream
{
public class StreamPlainNativeCrypto : StreamCrypto
{
public StreamPlainNativeCrypto(string method, string password) : base(method, password)
{
}
protected override int CipherDecrypt(Span<byte> plain, ReadOnlySpan<byte> cipher)
{
cipher.CopyTo(plain);
return cipher.Length;
}
protected override int CipherEncrypt(ReadOnlySpan<byte> plain, Span<byte> cipher)
{
plain.CopyTo(cipher);
return plain.Length;
}
#region Cipher Info
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo>
{
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) },
{"none", new CipherInfo("none", 0, 0, CipherFamily.Plain) },
};
public static Dictionary<string, CipherInfo> SupportedCiphers()
{
return _ciphers;
}
protected override Dictionary<string, CipherInfo> GetCiphers()
{
return _ciphers;
}
#endregion
public override void Dispose() { }
}
}

View File

@@ -1,20 +0,0 @@
using Shadowsocks.Net.Crypto.AEAD;
namespace Shadowsocks.Net.Crypto
{
public static class TCPParameter
{
// each recv size.
public const int RecvSize = 2048;
// overhead of one chunk, reserved for AEAD ciphers
// /* two tags */
public const int ChunkOverheadSize = 16 * 2 + AEADCrypto.ChunkLengthBytes;
// max chunk size
public const uint MaxChunkSize = AEADCrypto.ChunkLengthMask + AEADCrypto.ChunkLengthBytes + 16 * 2;
// In general, the ciphertext length, we should take overhead into account
public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */;
}
}

View File

@@ -1,72 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.Net.Proxy
{
public class DirectConnect : IProxy
{
private class FakeAsyncResult : IAsyncResult
{
public FakeAsyncResult(object state)
{
AsyncState = state;
}
public bool IsCompleted { get; } = true;
public WaitHandle AsyncWaitHandle { get; } = null;
public object AsyncState { get; }
public bool CompletedSynchronously { get; } = true;
}
private class FakeEndPoint : EndPoint
{
public override AddressFamily AddressFamily { get; } = AddressFamily.Unspecified;
public override string ToString()
{
return "null proxy";
}
}
private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
public EndPoint ProxyEndPoint { get; } = new FakeEndPoint();
public EndPoint DestEndPoint { get; private set; }
public void Shutdown(SocketShutdown how)
{
_remote.Shutdown(how);
}
public void Close()
{
_remote.Dispose();
}
public Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default)
{
return Task.CompletedTask;
}
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default)
{
DestEndPoint = destEndPoint;
await _remote.ConnectAsync(destEndPoint);
}
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
{
return await _remote.SendAsync(buffer, SocketFlags.None, token);
}
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token);
}
}
}

View File

@@ -1,113 +0,0 @@
using Splat;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.Net.Proxy
{
public class HttpProxy : IProxy, IEnableLogger
{
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
public EndPoint ProxyEndPoint { get; private set; }
public EndPoint DestEndPoint { get; private set; }
private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
private const string HTTP_CRLF = "\r\n";
private const string HTTP_CONNECT_TEMPLATE =
"CONNECT {0} HTTP/1.1" + HTTP_CRLF +
"Host: {0}" + HTTP_CRLF +
"Proxy-Connection: keep-alive" + HTTP_CRLF +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF +
"{1}" + // Proxy-Authorization if any
"" + HTTP_CRLF; // End with an empty line
private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF;
public void Shutdown(SocketShutdown how)
{
_remote.Shutdown(how);
}
public void Close()
{
_remote.Dispose();
}
private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled);
private int _respondLineCount = 0;
private bool _established = false;
private bool OnLineRead(string line, object state)
{
this.Log().Debug(line);
if (_respondLineCount == 0)
{
var m = HttpRespondHeaderRegex.Match(line);
if (m.Success)
{
var resultCode = m.Groups[2].Value;
if ("200" != resultCode)
{
return true;
}
_established = true;
}
}
else
{
if (string.IsNullOrEmpty(line))
{
return true;
}
}
_respondLineCount++;
return false;
}
private NetworkCredential auth;
public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default)
{
ProxyEndPoint = remoteEP;
this.auth = auth;
await _remote.ConnectAsync(remoteEP);
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
}
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default)
{
DestEndPoint = destEndPoint;
String authInfo = "";
if (auth != null)
{
string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password));
authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey);
}
string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo);
var b = Encoding.UTF8.GetBytes(request);
await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token);
// start line read
LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null);
await reader.Finished;
}
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
{
return await _remote.SendAsync(buffer, SocketFlags.None, token);
}
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token);
}
}
}

View File

@@ -1,29 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.Net.Proxy
{
public interface IProxy
{
EndPoint LocalEndPoint { get; }
EndPoint ProxyEndPoint { get; }
EndPoint DestEndPoint { get; }
Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default);
Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default);
Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default);
Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default);
void Shutdown(SocketShutdown how);
void Close();
}
}

View File

@@ -1,127 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.Net.Proxy
{
public class Socks5Proxy : IProxy
{
private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
private const int Socks5PktMaxSize = 4 + 16 + 2;
private readonly byte[] _receiveBuffer = new byte[Socks5PktMaxSize];
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
public EndPoint ProxyEndPoint { get; private set; }
public EndPoint DestEndPoint { get; private set; }
public void Shutdown(SocketShutdown how)
{
_remote.Shutdown(how);
}
public void Close()
{
_remote.Dispose();
}
public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default)
{
ProxyEndPoint = remoteEP;
await _remote.ConnectAsync(remoteEP);
await _remote.SendAsync(new byte[] { 5, 1, 0 }, SocketFlags.None);
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 2), SocketFlags.None) != 2)
{
throw new Exception("Proxy handshake failed");
}
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0)
{
throw new Exception("Proxy handshake failed");
}
}
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default)
{
// TODO: support SOCKS5 auth
DestEndPoint = destEndPoint;
byte[] request;
byte atyp;
int port;
if (destEndPoint is DnsEndPoint dep)
{
// is a domain name, we will leave it to server
atyp = 3; // DOMAINNAME
var enc = Encoding.UTF8;
var hostByteCount = enc.GetByteCount(dep.Host);
request = new byte[4 + 1/*length byte*/ + hostByteCount + 2];
request[4] = (byte)hostByteCount;
enc.GetBytes(dep.Host, 0, dep.Host.Length, request, 5);
port = dep.Port;
}
else
{
switch (DestEndPoint.AddressFamily)
{
case AddressFamily.InterNetwork:
request = new byte[4 + 4 + 2];
atyp = 1; // IP V4 address
break;
case AddressFamily.InterNetworkV6:
request = new byte[4 + 16 + 2];
atyp = 4; // IP V6 address
break;
default:
throw new Exception("Proxy request failed");
}
port = ((IPEndPoint)DestEndPoint).Port;
var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes();
Array.Copy(addr, 0, request, 4, request.Length - 4 - 2);
}
request[0] = 5;
request[1] = 1;
request[2] = 0;
request[3] = atyp;
request[^2] = (byte)((port >> 8) & 0xff);
request[^1] = (byte)(port & 0xff);
await _remote.SendAsync(request, SocketFlags.None, token);
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 4), SocketFlags.None, token) != 4)
{
throw new Exception("Proxy request failed");
};
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0)
{
throw new Exception("Proxy request failed");
}
var addrLen = _receiveBuffer[3] switch
{
1 => 6,
4 => 18,
_ => throw new NotImplementedException(),
};
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, addrLen), SocketFlags.None, token) != addrLen)
{
throw new Exception("Proxy request failed");
}
}
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
{
return await _remote.SendAsync(buffer, SocketFlags.None, token);
}
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token);
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.Net.Settings
{
public class ForwardProxySettings
{
public bool NoProxy { get; set; }
public bool UseSocks5Proxy { get; set; }
public bool UseHttpProxy { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public ForwardProxySettings()
{
NoProxy = true;
UseSocks5Proxy = false;
UseHttpProxy = false;
Address = "";
Port = 1088;
Username = "";
Password = "";
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Shadowsocks.Net.Settings
{
public class NetSettings
{
public bool EnableSocks5 { get; set; }
public bool EnableHttp { get; set; }
public string Socks5ListeningAddress { get; set; }
public string HttpListeningAddress { get; set; }
public int Socks5ListeningPort { get; set; }
public int HttpListeningPort { get; set; }
public ForwardProxySettings ForwardProxy { get; set; }
public NetSettings()
{
EnableSocks5 = true;
EnableHttp = true;
Socks5ListeningAddress = "::1";
HttpListeningAddress = "::1";
Socks5ListeningPort = 1080;
HttpListeningPort = 1080;
ForwardProxy = new ForwardProxySettings();
}
}
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CryptoBase" Version="1.2.2" />
<PackageReference Include="Splat" Version="11.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,176 +0,0 @@
using Splat;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace Shadowsocks.Net
{
public interface IStreamService
{
[Obsolete]
bool Handle(byte[] firstPacket, int length, Socket socket, object state);
public abstract bool Handle(CachedNetworkStream stream, object state);
void Stop();
}
public abstract class StreamService : IStreamService
{
[Obsolete]
public abstract bool Handle(byte[] firstPacket, int length, Socket socket, object state);
public abstract bool Handle(CachedNetworkStream stream, object state);
public virtual void Stop() { }
}
public class TCPListener : IEnableLogger
{
public class UDPState
{
public UDPState(Socket s)
{
socket = s;
remoteEndPoint = new IPEndPoint(s.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, 0);
}
public Socket socket;
public byte[] buffer = new byte[4096];
public EndPoint remoteEndPoint;
}
IPEndPoint _localEndPoint;
Socket _tcpSocket;
IEnumerable<IStreamService> _services;
public TCPListener(IPEndPoint localEndPoint, IEnumerable<IStreamService> services)
{
_localEndPoint = localEndPoint;
_services = services;
}
private bool CheckIfPortInUse(int port)
{
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
return ipProperties.GetActiveTcpListeners().Any(endPoint => endPoint.Port == port);
}
public void Start()
{
if (CheckIfPortInUse(_localEndPoint.Port))
throw new Exception($"Port {_localEndPoint.Port} already in use");
try
{
// Create a TCP/IP socket.
_tcpSocket = new Socket(_localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_tcpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_tcpSocket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
// Bind the socket to the local endpoint and listen for incoming connections.
_tcpSocket.Bind(_localEndPoint);
_tcpSocket.Listen(1024);
// Start an asynchronous socket to listen for connections.
this.Log().Info($"Shadowsocks started TCP");
this.Log().Debug(Crypto.CryptoFactory.DumpRegisteredEncryptor());
_tcpSocket.BeginAccept(new AsyncCallback(AcceptCallback), _tcpSocket);
}
catch (SocketException)
{
_tcpSocket.Close();
throw;
}
}
public void Stop()
{
_tcpSocket?.Close();
foreach (IStreamService s in _services)
{
s.Stop();
}
}
public void AcceptCallback(IAsyncResult ar)
{
Socket listener = (Socket)ar.AsyncState;
try
{
Socket conn = listener.EndAccept(ar);
byte[] buf = new byte[4096];
object[] state = new object[] {
conn,
buf
};
conn.BeginReceive(buf, 0, buf.Length, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (ObjectDisposedException)
{
}
catch (Exception e)
{
this.Log().Error(e, "");
}
finally
{
try
{
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
}
catch (ObjectDisposedException)
{
// do nothing
}
catch (Exception e)
{
this.Log().Error(e, "");
}
}
}
private void ReceiveCallback(IAsyncResult ar)
{
object[] state = (object[])ar.AsyncState;
Socket conn = (Socket)state[0];
byte[] buf = (byte[])state[1];
try
{
int bytesRead = conn.EndReceive(ar);
if (bytesRead <= 0)
{
goto Shutdown;
}
foreach (IStreamService service in _services)
{
if (service.Handle(buf, bytesRead, conn, null))
{
return;
}
}
Shutdown:
// no service found for this
if (conn.ProtocolType == ProtocolType.Tcp)
{
conn.Close();
}
}
catch (Exception e)
{
this.Log().Error(e, "");
conn.Close();
}
}
}
}

View File

@@ -1,601 +0,0 @@
using Splat;
using Shadowsocks.Net.Crypto;
using Shadowsocks.Net.Crypto.AEAD;
using Shadowsocks.Net.Proxy;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Shadowsocks.Net.Crypto.CryptoBase;
using Shadowsocks.Models;
namespace Shadowsocks.Net
{
public class TCPRelay : StreamService, IEnableLogger
{
public event EventHandler<SSTCPConnectedEventArgs> OnConnected;
public event EventHandler<SSTransmitEventArgs> OnInbound;
public event EventHandler<SSTransmitEventArgs> OnOutbound;
public event EventHandler<SSRelayEventArgs> OnFailed;
private Server _server;
private DateTime _lastSweepTime;
public ISet<TCPHandler> Handlers { get; set; }
public TCPRelay(Server server)
{
_server = server;
Handlers = new HashSet<TCPHandler>();
_lastSweepTime = DateTime.Now;
}
public override bool Handle(CachedNetworkStream stream, object state)
{
byte[] fp = new byte[256];
int len = stream.ReadFirstBlock(fp);
var socket = stream.Socket;
if (socket.ProtocolType != ProtocolType.Tcp
|| (len < 2 || fp[0] != 5))
return false;
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
TCPHandler handler = new TCPHandler(_server, socket);
IList<TCPHandler> handlersToClose = new List<TCPHandler>();
lock (Handlers)
{
Handlers.Add(handler);
DateTime now = DateTime.Now;
if (now - _lastSweepTime > TimeSpan.FromSeconds(1))
{
_lastSweepTime = now;
foreach (TCPHandler handler1 in Handlers)
if (now - handler1.lastActivity > TimeSpan.FromSeconds(900))
handlersToClose.Add(handler1);
}
}
foreach (TCPHandler handler1 in handlersToClose)
{
this.Log().Debug("Closing timed out TCP connection.");
handler1.Close();
}
/*
* Start after we put it into Handlers set. Otherwise if it failed in handler.Start()
* then it will call handler.Close() before we add it into the set.
* Then the handler will never release until the next Handle call. Sometimes it will
* cause odd problems (especially during memory profiling).
*/
// handler.Start(fp, len);
_ = handler.StartAsync(fp, len);
return true;
// return Handle(fp, len, stream.Socket, state);
}
[Obsolete]
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp
|| (length < 2 || firstPacket[0] != 5))
{
return false;
}
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
TCPHandler handler = new TCPHandler(_server, socket);
handler.OnConnected += OnConnected;
handler.OnInbound += OnInbound;
handler.OnOutbound += OnOutbound;
handler.OnFailed += OnFailed;
handler.OnClosed += (h, arg) =>
{
lock (Handlers)
{
Handlers.Remove(handler);
}
};
IList<TCPHandler> handlersToClose = new List<TCPHandler>();
lock (Handlers)
{
Handlers.Add(handler);
DateTime now = DateTime.Now;
if (now - _lastSweepTime > TimeSpan.FromSeconds(1))
{
_lastSweepTime = now;
foreach (TCPHandler handler1 in Handlers)
{
if (now - handler1.lastActivity > TimeSpan.FromSeconds(900))
{
handlersToClose.Add(handler1);
}
}
}
}
foreach (TCPHandler handler1 in handlersToClose)
{
this.Log().Debug("Closing timed out TCP connection.");
handler1.Close();
}
/*
* Start after we put it into Handlers set. Otherwise if it failed in handler.Start()
* then it will call handler.Close() before we add it into the set.
* Then the handler will never release until the next Handle call. Sometimes it will
* cause odd problems (especially during memory profiling).
*/
// handler.Start(firstPacket, length);
_ = handler.StartAsync(firstPacket, length);
return true;
}
public override void Stop()
{
List<TCPHandler> handlersToClose = new List<TCPHandler>();
lock (Handlers)
{
handlersToClose.AddRange(Handlers);
}
handlersToClose.ForEach(h => h.Close());
}
}
public class SSRelayEventArgs : EventArgs
{
public readonly Server server;
public SSRelayEventArgs(Server server)
{
this.server = server;
}
}
public class SSTransmitEventArgs : SSRelayEventArgs
{
public readonly long length;
public SSTransmitEventArgs(Server server, long length) : base(server)
{
this.length = length;
}
}
public class SSTCPConnectedEventArgs : SSRelayEventArgs
{
public readonly TimeSpan latency;
public SSTCPConnectedEventArgs(Server server, TimeSpan latency) : base(server)
{
this.latency = latency;
}
}
public class TCPHandler : IEnableLogger
{
public event EventHandler<SSTCPConnectedEventArgs> OnConnected;
public event EventHandler<SSTransmitEventArgs> OnInbound;
public event EventHandler<SSTransmitEventArgs> OnOutbound;
public event EventHandler<SSRelayEventArgs> OnClosed;
public event EventHandler<SSRelayEventArgs> OnFailed;
private readonly int _serverTimeout;
private readonly int _proxyTimeout;
private readonly MemoryPool<byte> pool = MemoryPool<byte>.Shared;
// each recv size.
public const int RecvSize = 16384;
// overhead of one chunk, reserved for AEAD ciphers
public const int ChunkOverheadSize = 100;//16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes;
// In general, the ciphertext length, we should take overhead into account
public const int SendSize = 32768;
public DateTime lastActivity;
// TODO: forward proxy
//private readonly ForwardProxyConfig _config;
private readonly Server _server;
private readonly Socket _connection;
private IProxy _remote;
private ICrypto encryptor;
// workaround
private ICrypto decryptor;
private byte[] _firstPacket;
private int _firstPacketLength;
private const int CMD_CONNECT = 0x01;
private const int CMD_BIND = 0x02;
private const int CMD_UDP_ASSOC = 0x03;
private bool _closed = false;
// instance-based lock without static
private readonly object _encryptionLock = new object();
private readonly object _decryptionLock = new object();
private readonly object _closeConnLock = new object();
// TODO: decouple controller
public TCPHandler(Server server, Socket socket)
{
_server = server;
_connection = socket;
_proxyTimeout = 5000;
_serverTimeout = 5000;
lastActivity = DateTime.Now;
}
public void CreateRemote(EndPoint destination)
{
if (_server == null || _server.Host == "")
{
throw new ArgumentException("No server configured");
}
encryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password);
decryptor = CryptoFactory.GetEncryptor(_server.Method, _server.Password);
}
public async Task StartAsync(byte[] firstPacket, int length)
{
_firstPacket = firstPacket;
_firstPacketLength = length;
(int cmd, EndPoint dst) = await Socks5Handshake();
if (cmd == CMD_CONNECT)
{
await ConnectRemote(dst);
await SendAddress(dst);
await Forward();
}
else if (cmd == CMD_UDP_ASSOC)
{
await DrainConnection();
}
}
private void ErrorClose(Exception e)
{
this.Log().Error(e, "");
Close();
}
public void Close()
{
lock (_closeConnLock)
{
if (_closed)
{
return;
}
_closed = true;
}
OnClosed?.Invoke(this, new SSRelayEventArgs(_server));
try
{
_connection.Shutdown(SocketShutdown.Both);
_connection.Close();
encryptor?.Dispose();
decryptor?.Dispose();
}
catch (Exception e)
{
this.Log().Error(e, "");
}
}
async Task<(int cmd, EndPoint destination)> Socks5Handshake()
{
// not so strict here
// 5 2 1 2 should return 5 255
// 5 1 0 5 / 1 0 1 127 0 0 1 0 80 will cause handshake fail
int bytesRead = _firstPacketLength;
if (bytesRead <= 1)
{
Close();
return (0, default);
}
byte[] response = { 5, 0 };
if (_firstPacket[0] != 5)
{
// reject socks 4
response = new byte[] { 0, 91 };
this.Log().Error("socks5 protocol error");
}
await _connection.SendAsync(response, SocketFlags.None);
using var bufOwner = pool.Rent(512);
var buf = bufOwner.Memory;
if (await _connection.ReceiveAsync(buf.Slice(0, 5), SocketFlags.None) != 5)
{
Close();
return (0, default);
}
var cmd = buf.Span[1];
EndPoint dst = default;
switch (cmd)
{
case CMD_CONNECT:
await _connection.SendAsync(new byte[] { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, SocketFlags.None);
dst = await ReadAddress(buf);
// start forward
break;
case CMD_UDP_ASSOC:
dst = await ReadAddress(buf);
await SendUdpAssociate();
// drain
break;
default:
Close();
break;
}
return (cmd, dst);
}
async Task DrainConnection()
{
if (_closed)
{
return;
}
using var b = pool.Rent(512);
try
{
int l;
do
{
l = await _connection.ReceiveAsync(b.Memory, SocketFlags.None);
}
while (l > 0);
Close();
}
catch (Exception e)
{
ErrorClose(e);
}
}
private async Task<EndPoint> ReadAddress(Memory<byte> buf)
{
var atyp = buf.Span[3];
var maybeDomainLength = buf.Span[4];
buf.Span[0] = atyp;
buf.Span[1] = maybeDomainLength;
int toRead = atyp switch
{
ATYP_IPv4 => 4,
ATYP_IPv6 => 16,
ATYP_DOMAIN => maybeDomainLength + 1,
_ => throw new NotSupportedException(),
} + 2 - 1;
await _connection.ReceiveAsync(buf.Slice(2, toRead), SocketFlags.None);
return GetSocks5EndPoint(buf.ToArray());
}
private int ReadPort(byte[] arr, long offset)
{
return (arr[offset] << 8) + arr[offset + 1];
}
private EndPoint GetSocks5EndPoint(byte[] buf)
{
int maybeDomainLength = buf[1] + 2;
return (buf[0]) switch
{
ATYP_IPv4 => new IPEndPoint(new IPAddress(buf[1..5]), ReadPort(buf, 5)),
ATYP_IPv6 => new IPEndPoint(new IPAddress(buf[1..17]), ReadPort(buf, 17)),
ATYP_DOMAIN => new DnsEndPoint(Encoding.ASCII.GetString(buf[2..maybeDomainLength]), ReadPort(buf, maybeDomainLength)),
_ => throw new NotSupportedException(),
};
}
private async Task SendUdpAssociate()
{
IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint;
byte[] address = endPoint.Address.GetAddressBytes();
int port = endPoint.Port;
byte[] response = new byte[4 + address.Length + ADDR_PORT_LEN];
response[0] = 5;
switch (endPoint.AddressFamily)
{
case AddressFamily.InterNetwork:
response[3] = ATYP_IPv4;
break;
case AddressFamily.InterNetworkV6:
response[3] = ATYP_IPv6;
break;
}
address.CopyTo(response, 4);
response[^1] = (byte)(port & 0xFF);
response[^2] = (byte)((port >> 8) & 0xFF);
await _connection.SendAsync(response, SocketFlags.None);
}
private async Task ConnectRemote(EndPoint destination)
{
CreateRemote(destination);
IProxy remote;
EndPoint proxyEP = null;
EndPoint serverEP = new DnsEndPoint(_server.Host, _server.Port);
EndPoint pluginEP = null; // TODO: plugin local end point
remote = new DirectConnect(); // TODO: forward proxy
NetworkCredential auth = null;
/*if (_config.useAuth)
{
auth = new NetworkCredential(_config.authUser, _config.authPwd);
}
if (pluginEP != null)
{
serverEP = pluginEP;
remote = new DirectConnect();
}
else if (_config.useProxy)
{
remote = _config.proxyType switch
{
ForwardProxyConfig.PROXY_SOCKS5 => new Socks5Proxy(),
ForwardProxyConfig.PROXY_HTTP => new HttpProxy(),
_ => throw new NotSupportedException("Unknown forward proxy."),
};
proxyEP = new DnsEndPoint(_config.proxyServer, _config.proxyPort);
}
else
{
remote = new DirectConnect();
}*/
CancellationTokenSource cancelProxy = new CancellationTokenSource(_proxyTimeout * 1000);
await remote.ConnectProxyAsync(proxyEP, auth, cancelProxy.Token);
_remote = remote;
if (!(remote is DirectConnect))
{
this.Log().Debug($"Socket connected to proxy {remote.ProxyEndPoint}");
}
var _startConnectTime = DateTime.Now;
CancellationTokenSource cancelServer = new CancellationTokenSource(_serverTimeout * 1000);
await remote.ConnectRemoteAsync(serverEP, cancelServer.Token);
this.Log().Debug($"Socket connected to ss server: {_server}");
TimeSpan latency = DateTime.Now - _startConnectTime;
OnConnected?.Invoke(this, new SSTCPConnectedEventArgs(_server, latency));
}
private async Task SendAddress(EndPoint dest)
{
byte[] dstByte = GetSocks5EndPointByte(dest);
using var t = pool.Rent(512);
try
{
int addrlen = encryptor.Encrypt(dstByte, t.Memory.Span);
await _remote.SendAsync(t.Memory.Slice(0, addrlen));
}
catch (Exception e)
{
ErrorClose(e);
}
}
private byte[] GetSocks5EndPointByte(EndPoint dest)
{
if (dest is DnsEndPoint d)
{
byte[] r = new byte[d.Host.Length + 4];
r[0] = 3;
r[1] = (byte)d.Host.Length;
Encoding.ASCII.GetBytes(d.Host, r.AsSpan(2));
r[^2] = (byte)(d.Port / 256);
r[^1] = (byte)(d.Port % 256);
return r;
}
else if (dest is IPEndPoint i)
{
if (i.AddressFamily == AddressFamily.InterNetwork)
{
byte[] r = new byte[7];
r[0] = 1;
i.Address.GetAddressBytes().CopyTo(r, 1);
r[^2] = (byte)(i.Port / 256);
r[^1] = (byte)(i.Port % 256);
return r;
}
else if (i.AddressFamily == AddressFamily.InterNetworkV6)
{
byte[] r = new byte[19];
r[0] = 1;
i.Address.GetAddressBytes().CopyTo(r, 1);
r[^2] = (byte)(i.Port / 256);
r[^1] = (byte)(i.Port % 256);
return r;
}
}
throw new NotImplementedException();
}
private async Task Forward()
{
try
{
await Task.WhenAll(ForwardInbound(), ForwardOutbound());
Close();
}
catch (Exception e)
{
ErrorClose(e);
}
}
private async Task ForwardInbound()
{
using var cipherOwner = pool.Rent(RecvSize);
using var plainOwner = pool.Rent(SendSize);
var plain = plainOwner.Memory;
var cipher = cipherOwner.Memory;
try
{
while (true)
{
int len = await _remote.ReceiveAsync(cipher);
if (len == 0) break;
int plen = decryptor.Decrypt(plain.Span, cipher.Span.Slice(0, len));
if (plen == 0) continue;
int len2 = await _connection.SendAsync(plain.Slice(0, plen), SocketFlags.None);
if (len2 == 0) break;
OnInbound?.Invoke(this, new SSTransmitEventArgs(_server, plen));
}
}
catch (Exception e)
{
ErrorClose(e);
}
}
private async Task ForwardOutbound()
{
using var plainOwner = pool.Rent(RecvSize);
using var cipherOwner = pool.Rent(SendSize);
var plain = plainOwner.Memory;
var cipher = cipherOwner.Memory;
while (true)
{
int len = await _connection.ReceiveAsync(plain, SocketFlags.None);
if (len == 0) break;
int clen = encryptor.Encrypt(plain.Span.Slice(0, len), cipher.Span);
int len2 = await _remote.SendAsync(cipher.Slice(0, clen));
if (len2 == 0) break;
OnOutbound?.Invoke(this, new SSTransmitEventArgs(_server, len));
}
_remote.Shutdown(SocketShutdown.Send);
}
}
}

View File

@@ -1,107 +0,0 @@
using Splat;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Shadowsocks.Net
{
public interface IDatagramService
{
public abstract Task< bool> Handle(Memory<byte> packet, Socket socket, EndPoint client);
void Stop();
}
public abstract class DatagramService : IDatagramService
{
public abstract Task<bool> Handle(Memory<byte> packet, Socket socket, EndPoint client);
public virtual void Stop() { }
}
public class UDPListener : IEnableLogger
{
public class UDPState
{
public UDPState(Socket s)
{
socket = s;
remoteEndPoint = new IPEndPoint(s.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, 0);
}
public Socket socket;
public byte[] buffer = new byte[4096];
public EndPoint remoteEndPoint;
}
IPEndPoint _localEndPoint;
Socket _udpSocket;
IEnumerable<IDatagramService> _services;
CancellationTokenSource tokenSource = new CancellationTokenSource();
public UDPListener(IPEndPoint localEndPoint, IEnumerable<IDatagramService> services)
{
_localEndPoint = localEndPoint;
_services = services;
}
private bool CheckIfPortInUse(int port)
{
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
return ipProperties.GetActiveUdpListeners().Any(endPoint => endPoint.Port == port);
}
public void Start()
{
if (CheckIfPortInUse(_localEndPoint.Port))
throw new Exception($"Port {_localEndPoint.Port} already in use");
// Create a TCP/IP socket.
_udpSocket = new Socket(_localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_udpSocket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
// Bind the socket to the local endpoint and listen for incoming connections.
_udpSocket.Bind(_localEndPoint);
// Start an asynchronous socket to listen for connections.
this.Log().Info($"Shadowsocks started UDP");
this.Log().Debug(Crypto.CryptoFactory.DumpRegisteredEncryptor());
UDPState udpState = new UDPState(_udpSocket);
// _udpSocket.BeginReceiveFrom(udpState.buffer, 0, udpState.buffer.Length, 0, ref udpState.remoteEndPoint, new AsyncCallback(RecvFromCallback), udpState);
Task.Run(() => WorkLoop(tokenSource.Token));
}
private async Task WorkLoop(CancellationToken token)
{
byte[] buffer = new byte[4096];
EndPoint remote = new IPEndPoint(_udpSocket.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, 0);
while (!token.IsCancellationRequested)
{
var result = await _udpSocket.ReceiveFromAsync(buffer, SocketFlags.None, remote);
var len = result.ReceivedBytes;
foreach (IDatagramService service in _services)
{
if (await service.Handle(new Memory<byte>(buffer)[..len], _udpSocket, result.RemoteEndPoint))
{
break;
}
}
}
}
public void Stop()
{
tokenSource.Cancel();
_udpSocket?.Close();
foreach (var s in _services)
{
s.Stop();
}
}
}
}

View File

@@ -1,89 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Shadowsocks.PAC
{
/// <summary>
/// Settings used for PAC.
/// </summary>
public class PACSettings
{
public PACSettings()
{
PACDefaultToDirect = false;
PACServerEnableSecret = true;
RegeneratePacOnVersionUpdate = true;
CustomPACUrl = "";
CustomGeositeUrl = "";
CustomGeositeSha256SumUrl = "";
GeositeDirectGroups = new List<string>()
{
"private",
"cn",
"geolocation-!cn@cn",
};
GeositeProxiedGroups = new List<string>()
{
"geolocation-!cn",
};
}
/// <summary>
/// Controls whether direct connection is used for
/// hostnames not matched by blocking rules
/// or matched by exception rules.
/// Defaults to false, or whitelist mode,
/// where hostnames matching the above conditions
/// are connected to via proxy.
/// Enable it to use blacklist mode.
/// </summary>
public bool PACDefaultToDirect { get; set; }
/// <summary>
/// Controls whether the PAC server uses a secret
/// to protect access to the PAC URL.
/// Defaults to true.
/// </summary>
public bool PACServerEnableSecret { get; set; }
/// <summary>
/// Controls whether `pac.txt` should be regenerated
/// when shadowsocks-windows is updated.
/// Defaults to true, so new changes can be applied.
/// Change it to false if you want to manage `pac.txt`
/// yourself.
/// </summary>
public bool RegeneratePacOnVersionUpdate { get; set; }
/// <summary>
/// Specifies a custom PAC URL.
/// Leave empty to use local PAC.
/// </summary>
public string CustomPACUrl { get; set; }
/// <summary>
/// Specifies a custom Geosite database URL.
/// Leave empty to use the default source.
/// </summary>
public string CustomGeositeUrl { get; set; }
/// <summary>
/// Specifies the custom Geosite database's corresponding SHA256 checksum download URL.
/// Leave empty to disable checksum verification for your custom Geosite database.
/// </summary>
public string CustomGeositeSha256SumUrl { get; set; }
/// <summary>
/// A list of Geosite groups
/// that we use direct connection for.
/// </summary>
public List<string> GeositeDirectGroups { get; set; }
/// <summary>
/// A list of Geosite groups
/// that we always connect to via proxy.
/// </summary>
public List<string> GeositeProxiedGroups { get; set; }
}
}

View File

@@ -1,112 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Shadowsocks.PAC.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Shadowsocks.PAC.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to /* eslint-disable */
///// Was generated by gfwlist2pac in precise mode
///// https://github.com/clowwindy/gfwlist2pac
///
///// 2019-10-06: More &apos;javascript&apos; way to interaction with main program
///// 2019-02-08: Updated to support shadowsocks-windows user rules.
///
///var proxy = __PROXY__;
///var userrules = [];
///var rules = [];
///
///// convert to abp grammar
///var re = /^(@@)?\|\|.*?[^\^]$/;
///for (var i = 0; i &lt; __RULES__.length; i++) {
/// var s = __RULES__[i];
/// if (s.match(re)) s += &quot;^&quot;;
/// rules.push(s);
///}
///
/// [rest of string was truncated]&quot;;.
/// </summary>
internal static string abp {
get {
return ResourceManager.GetString("abp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] dlc {
get {
object obj = ResourceManager.GetObject("dlc", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized string similar to ! Put user rules line by line in this file.
///! See https://adblockplus.org/en/filter-cheatsheet
///.
/// </summary>
internal static string user_rule {
get {
return ResourceManager.GetString("user_rule", resourceCulture);
}
}
}
}

View File

@@ -1,41 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\abp.js" />
<None Remove="Resources\dlc.dat" />
<None Remove="Resources\user-rule.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" />
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" />
<ProjectReference Include="..\Shadowsocks.Protobuf\Shadowsocks.Protobuf.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\abp.js" />
<Resource Include="Resources\dlc.dat" />
<Resource Include="Resources\user-rule.txt" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More