mirror of
https://github.com/bolucat/Archive.git
synced 2025-12-24 13:28:37 +08:00
Update On Tue Dec 31 19:31:58 CET 2024
This commit is contained in:
@@ -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
|
||||
129
shadowsocks-windows/.github/workflows/build.yml
vendored
129
shadowsocks-windows/.github/workflows/build.yml
vendored
@@ -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/
|
||||
132
shadowsocks-windows/.github/workflows/release.yml
vendored
132
shadowsocks-windows/.github/workflows/release.yml
vendored
@@ -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
|
||||
2
shadowsocks-windows/.gitignore
vendored
2
shadowsocks-windows/.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
15
shadowsocks-windows/OPENSSL-GUIDE
Normal file
15
shadowsocks-windows/OPENSSL-GUIDE
Normal 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
|
||||
@@ -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
|
||||
=======================
|
||||
|
||||
[](https://github.com/shadowsocks/shadowsocks-windows/actions?query=workflow%3ABuild)
|
||||
[](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
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Shadowsocks.CLI
|
||||
{
|
||||
public enum Backend
|
||||
{
|
||||
SsRust,
|
||||
V2Ray,
|
||||
Legacy,
|
||||
Pipelines,
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 & The Community</Authors>
|
||||
<Product>Shadowsocks CLI</Product>
|
||||
<Description>CLI for Shadowsocks server and client implementation in C#.</Description>
|
||||
<Copyright>© 2021 Clowwindy & 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>
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Shadowsocks.Interop.V2Ray.Protocols
|
||||
{
|
||||
public class UserObject : AccountObject
|
||||
{
|
||||
public int Level { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Shadowsocks.Interop.V2Ray.Protocols.VMess
|
||||
{
|
||||
public class DetourObject
|
||||
{
|
||||
public string To { get; set; }
|
||||
|
||||
public DetourObject()
|
||||
{
|
||||
To = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Shadowsocks.Interop.V2Ray
|
||||
{
|
||||
public class StatsObject
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() { }
|
||||
}
|
||||
}
|
||||
@@ -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 */;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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 'javascript' 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 < __RULES__.length; i++) {
|
||||
/// var s = __RULES__[i];
|
||||
/// if (s.match(re)) s += "^";
|
||||
/// rules.push(s);
|
||||
///}
|
||||
///
|
||||
/// [rest of string was truncated]";.
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user