diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml
index 35bd305a..95d88f54 100644
--- a/.github/workflows/debug.yml
+++ b/.github/workflows/debug.yml
@@ -13,34 +13,6 @@ on:
- dev
jobs:
- core:
- name: Native Build (Core)
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
- - name: Fetch Status
- run: git submodule status 'library/core/*' > core_status
- - name: Core Cache
- id: cache
- uses: actions/cache@v2
- with:
- path: |
- app/libs/core-release.aar
- key: ${{ hashFiles('core_status') }}
- - name: Gradle cache
- uses: actions/cache@v2
- if: steps.cache.outputs.cache-hit != 'true'
- with:
- path: ~/.gradle
- key: native-${{ hashFiles('**/*.gradle') }}
- - name: Native Build
- if: steps.cache.outputs.cache-hit != 'true'
- run: |
- echo "sdk.dir=${ANDROID_HOME}" > local.properties
- echo "ndk.dir=${ANDROID_HOME}/ndk/21.4.7075529" >> local.properties
- ./run init action library
- ./run lib core
v2ray:
name: Native Build (V2Ray)
runs-on: ubuntu-latest
@@ -252,7 +224,6 @@ jobs:
name: Gradle Build
runs-on: ubuntu-latest
needs:
- - core
- v2ray
- shadowsocks
- shadowsocksr
@@ -261,16 +232,9 @@ jobs:
uses: actions/checkout@v2
- name: Fetch Status
run: |
- git submodule status 'library/core/*' > core_status
git submodule status 'library/shadowsocks/*' > shadowsocks_status
git submodule status 'library/shadowsocksr/*' > shadowsocksr_status
git submodule status library/v2ray > v2ray_status
- - name: Core Cache
- uses: actions/cache@v2
- with:
- path: |
- app/libs/core-release.aar
- key: ${{ hashFiles('core_status') }}
- name: V2Ray Cache
uses: actions/cache@v2
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7e604709..ca3e1354 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,35 +25,6 @@ jobs:
permission: "write"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- core:
- name: Native Build (Core)
- runs-on: ubuntu-latest
- needs: check
- steps:
- - name: Checkout
- uses: actions/checkout@v2
- - name: Fetch Status
- run: git submodule status 'library/core/*' > core_status
- - name: Core Cache
- id: cache
- uses: actions/cache@v2
- with:
- path: |
- app/libs/core-release.aar
- key: ${{ hashFiles('core_status') }}
- - name: Gradle cache
- uses: actions/cache@v2
- if: steps.cache.outputs.cache-hit != 'true'
- with:
- path: ~/.gradle
- key: native-${{ hashFiles('**/*.gradle') }}
- - name: Native Build
- if: steps.cache.outputs.cache-hit != 'true'
- run: |
- echo "sdk.dir=${ANDROID_HOME}" > local.properties
- echo "ndk.dir=${ANDROID_HOME}/ndk/21.4.7075529" >> local.properties
- ./run init action library
- ./run lib core
v2ray:
name: Native Build (V2Ray)
runs-on: ubuntu-latest
@@ -152,7 +123,6 @@ jobs:
name: Gradle Build
runs-on: ubuntu-latest
needs:
- - core
- v2ray
- shadowsocks
- shadowsocksr
@@ -161,16 +131,9 @@ jobs:
uses: actions/checkout@v2
- name: Fetch Status
run: |
- git submodule status 'library/core/*' > core_status
git submodule status 'library/shadowsocks/*' > shadowsocks_status
git submodule status 'library/shadowsocksr/*' > shadowsocksr_status
git submodule status library/v2ray > v2ray_status
- - name: Core Cache
- uses: actions/cache@v2
- with:
- path: |
- app/libs/core-release.aar
- key: ${{ hashFiles('core_status') }}
- name: V2Ray Cache
uses: actions/cache@v2
with:
@@ -281,7 +244,6 @@ jobs:
if: github.event.inputs.play != 'y'
runs-on: ubuntu-latest
needs:
- - core
- v2ray
- shadowsocks
- shadowsocksr
@@ -290,16 +252,9 @@ jobs:
uses: actions/checkout@v2
- name: Fetch Status
run: |
- git submodule status 'library/core/*' > core_status
git submodule status 'library/shadowsocks/*' > shadowsocks_status
git submodule status 'library/shadowsocksr/*' > shadowsocksr_status
git submodule status library/v2ray > v2ray_status
- - name: Core Cache
- uses: actions/cache@v2
- with:
- path: |
- app/libs/core-release.aar
- key: ${{ hashFiles('core_status') }}
- name: V2Ray Cache
uses: actions/cache@v2
with:
diff --git a/.gitmodules b/.gitmodules
index a90d2cb2..a4dc6022 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -58,3 +58,9 @@
[submodule "plugin/trojan/src/main/cpp/trojan"]
path = plugin/trojan/src/main/cpp/trojan
url = https://github.com/trojan-gfw/trojan
+[submodule "external/netty"]
+ path = external/netty
+ url = https://github.com/netty/netty
+[submodule "external/netty-jni-util"]
+ path = external/netty-jni-util
+ url = https://github.com/netty/netty-jni-util
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index c6670684..1659114b 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -64,6 +64,7 @@
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 8e0535fe..94346c27 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -8,6 +8,8 @@
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 443b0ecc..776485a3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -31,6 +31,8 @@ dependencies {
implementation(fileTree("libs"))
+ implementation(project(":library:core"))
+ implementation(project(":library:epoll"))
compileOnly(project(":library:include"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")
@@ -90,6 +92,7 @@ dependencies {
implementation(project(":library:proto-stub"))
implementation("io.grpc:grpc-okhttp:1.39.0")
+
implementation("io.netty:netty-all:4.1.66.Final")
implementation("org.slf4j:slf4j-simple:1.7.32")
implementation("com.github.seancfoley:ipaddress:5.3.3")
diff --git a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
index f4a95d84..a5f0fbe2 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
@@ -40,6 +40,7 @@ import go.Seq
import io.nekohasekai.sagernet.bg.SagerConnection
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.database.SagerDatabase
+import io.nekohasekai.sagernet.ktx.Logs
import io.nekohasekai.sagernet.ktx.app
import io.nekohasekai.sagernet.ktx.checkMT
import io.nekohasekai.sagernet.ktx.runOnMainDispatcher
@@ -110,6 +111,7 @@ class SagerNet : Application(),
}
companion object {
+
var started = false
lateinit var application: SagerNet
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt
index 7008e431..a56d3b33 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt
@@ -30,6 +30,9 @@ import io.nekohasekai.sagernet.bg.VpnService
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.fmt.AbstractBean
import io.nekohasekai.sagernet.fmt.LOCALHOST
+import io.netty.buffer.ByteBuf
+import io.netty.buffer.Unpooled
+import io.netty.buffer.UnpooledDirectByteBuf
import okhttp3.ConnectionSpec
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
@@ -46,7 +49,11 @@ val okHttpClient = OkHttpClient.Builder()
private lateinit var proxyClient: OkHttpClient
fun createProxyClient(): OkHttpClient {
- if (!SagerNet.started) return okHttpClient
+ if (!SagerNet.started) {
+ if (DataStore.startedProfile == 0L) {
+ return okHttpClient
+ }
+ }
if (!::proxyClient.isInitialized) {
proxyClient = okHttpClient.newBuilder().proxy(requireProxy()).build()
@@ -109,4 +116,11 @@ fun mkPort(): Int {
val port = socket.localPort
socket.close()
return port
+}
+
+fun ByteBuf.toByteArray(): ByteArray {
+ if (this is UnpooledDirectByteBuf) {
+ return Unpooled.copiedBuffer(this).array()
+ }
+ return array()
}
\ No newline at end of file
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt
index dbb7189d..1f17fc3f 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt
@@ -62,6 +62,7 @@ import java.io.FileDescriptor
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.Socket
+import java.nio.channels.FileChannel
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
diff --git a/app/src/main/java/io/nekohasekai/sagernet/tun/TcpForwarder.kt b/app/src/main/java/io/nekohasekai/sagernet/tun/TcpForwarder.kt
index 84d955d2..647841ea 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/tun/TcpForwarder.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/tun/TcpForwarder.kt
@@ -37,7 +37,7 @@ import io.nekohasekai.sagernet.utils.PackageCache
import io.netty.bootstrap.Bootstrap
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.*
-import io.netty.channel.socket.nio.NioServerSocketChannel
+import io.netty.channel.socket.ServerSocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.proxy.Socks5ProxyHandler
import okhttp3.internal.connection.RouteSelector.Companion.socketHost
@@ -198,8 +198,8 @@ class TcpForwarder(val tun: TunThread) {
if (isDns) {
// direct for dns
- channelFeature = Bootstrap().group(tun.outboundLoop)
- .channel(NioSocketChannel::class.java)
+ channelFeature = Bootstrap().group(tun.eventLoop)
+ .channel(tun.socketChannelClazz)
.handler(object : ChannelInitializer() {
override fun initChannel(channel: Channel) {
channel.pipeline().addLast(ChannelForwardAdapter(ctx.channel()))
@@ -216,8 +216,8 @@ class TcpForwarder(val tun: TunThread) {
})
} else {
val socksPort = tun.uidMap[session.uid] ?: tun.socksPort
- channelFeature = Bootstrap().group(tun.outboundLoop)
- .channel(NioSocketChannel::class.java)
+ channelFeature = Bootstrap().group(tun.eventLoop)
+ .channel(tun.socketChannelClazz)
.handler(object : ChannelInitializer() {
override fun initChannel(channel: Channel) {
channel.pipeline().addFirst(
@@ -286,12 +286,12 @@ class TcpForwarder(val tun: TunThread) {
private lateinit var channelFuture: ChannelFuture
val forwardServerPort by lazy {
- (channelFuture.sync().channel() as NioServerSocketChannel).localAddress().port.toShort()
+ (channelFuture.sync().channel() as ServerSocketChannel).localAddress().port.toShort()
}
fun start() {
- channelFuture = ServerBootstrap().group(tun.serverLoop, tun.outboundLoop)
- .channel(NioServerSocketChannel::class.java)
+ channelFuture = ServerBootstrap().group(tun.eventLoop)
+ .channel(tun.serverSocketChannelClazz)
.childHandler(object : ChannelInitializer() {
override fun initChannel(channel: Channel) {
channel.pipeline().addLast(Forwarder())
diff --git a/app/src/main/java/io/nekohasekai/sagernet/tun/TunThread.kt b/app/src/main/java/io/nekohasekai/sagernet/tun/TunThread.kt
index c76a3dba..55c824a9 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/tun/TunThread.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/tun/TunThread.kt
@@ -32,7 +32,12 @@ import io.nekohasekai.sagernet.tun.ip.ipv4.ICMPHeader
import io.nekohasekai.sagernet.tun.ip.ipv4.IPv4Header
import io.nekohasekai.sagernet.tun.ip.ipv6.ICMPv6Header
import io.nekohasekai.sagernet.tun.ip.ipv6.IPv6Header
+import io.netty.channel.epoll.*
import io.netty.channel.nio.NioEventLoopGroup
+import io.netty.channel.socket.ServerSocketChannel
+import io.netty.channel.socket.nio.NioDatagramChannel
+import io.netty.channel.socket.nio.NioServerSocketChannel
+import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler
import kotlinx.coroutines.*
@@ -48,8 +53,10 @@ class TunThread(val service: VpnService) : Thread("TUN Thread") {
val closed get() = service.data.proxy?.closed == true
val loggingHandler = LoggingHandler(LogLevel.TRACE)
- val serverLoop = NioEventLoopGroup()
- val outboundLoop = NioEventLoopGroup()
+ val eventLoop = if (Epoll.isAvailable()) EpollEventLoopGroup() else NioEventLoopGroup()
+ val serverSocketChannelClazz: Class = if (Epoll.isAvailable()) EpollServerSocketChannel::class.java else NioServerSocketChannel::class.java
+ val socketChannelClazz = if (Epoll.isAvailable()) EpollSocketChannel::class.java else NioSocketChannel::class.java
+ val datagramChannelClazz = if (Epoll.isAvailable()) EpollDatagramChannel::class.java else NioDatagramChannel::class.java
val tcpForwarder = TcpForwarder(this)
val udpForwarder = UdpForwarder(this)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/tun/UdpForwarder.kt b/app/src/main/java/io/nekohasekai/sagernet/tun/UdpForwarder.kt
index 9e5559c4..a3e15719 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/tun/UdpForwarder.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/tun/UdpForwarder.kt
@@ -28,6 +28,7 @@ import io.nekohasekai.sagernet.bg.VpnService
import io.nekohasekai.sagernet.fmt.LOCALHOST
import io.nekohasekai.sagernet.ktx.LAUNCH_DELAY
import io.nekohasekai.sagernet.ktx.Logs
+import io.nekohasekai.sagernet.ktx.toByteArray
import io.nekohasekai.sagernet.tun.ip.IPHeader
import io.nekohasekai.sagernet.tun.ip.UDPHeader
import io.nekohasekai.sagernet.tun.ip.ipv4.IPv4Header
@@ -41,7 +42,6 @@ import io.netty.bootstrap.Bootstrap
import io.netty.buffer.Unpooled
import io.netty.channel.*
import io.netty.channel.socket.DatagramPacket
-import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.handler.codec.socksx.v5.Socks5AddressType
import io.netty.util.concurrent.DefaultPromise
import io.netty.util.concurrent.Future
@@ -179,13 +179,13 @@ class UdpForwarder(val tun: TunThread) {
fun connect() {
if (calIsDns()) {
isDns = true
- Bootstrap().group(tun.outboundLoop)
- .channel(NioDatagramChannel::class.java)
+ Bootstrap().group(tun.eventLoop)
+ .channel(tun.datagramChannelClazz)
.handler(this)
.connect(LOCALHOST, tun.dnsPort)
} else {
- Bootstrap().group(tun.outboundLoop)
- .channel(NioDatagramChannel::class.java)
+ Bootstrap().group(tun.eventLoop)
+ .channel(tun.datagramChannelClazz)
.handler(object : ChannelInitializer() {
override fun initChannel(channel: Channel) {
channel.pipeline().apply {
@@ -217,8 +217,8 @@ class UdpForwarder(val tun: TunThread) {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
when (msg) {
- is Socks5UdpMessage -> sendResponse(msg.data().array())
- is DatagramPacket -> sendResponse(msg.content().array())
+ is Socks5UdpMessage -> sendResponse(msg.data().toByteArray())
+ is DatagramPacket -> sendResponse(msg.content().toByteArray())
}
}
diff --git a/bin/init/action/library.sh b/bin/init/action/library.sh
index 2c146cbc..4f6e127a 100755
--- a/bin/init/action/library.sh
+++ b/bin/init/action/library.sh
@@ -1,3 +1,4 @@
#!/bin/bash
+git submodule update --init 'library/core/*'
git submodule update --init 'external/*'
diff --git a/bin/lib/core.sh b/bin/lib/core.sh
deleted file mode 100755
index 17afa900..00000000
--- a/bin/lib/core.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-source "bin/init/env.sh"
-
-git submodule update --init library/core/src/main/jni/*
-rm -rf library/core/build/outputs/aar
-./gradlew :library:core:assembleRelease || exit 1
-mkdir -p app/libs
-cp library/core/build/outputs/aar/* app/libs
diff --git a/buildSrc/src/main/kotlin/Helpers.kt b/buildSrc/src/main/kotlin/Helpers.kt
index b2c997eb..9b3b62a3 100644
--- a/buildSrc/src/main/kotlin/Helpers.kt
+++ b/buildSrc/src/main/kotlin/Helpers.kt
@@ -132,9 +132,9 @@ fun Project.setupCommon() {
applicationVariants.forEach { variant ->
variant.outputs.forEach {
it as BaseVariantOutputImpl
- it.outputFileName =
- it.outputFileName.replace("app", "${project.name}-" + variant.versionName)
- .replace("-release", "").replace("-oss", "")
+ it.outputFileName = it.outputFileName.replace(
+ "app", "${project.name}-" + variant.versionName
+ ).replace("-release", "").replace("-oss", "")
}
}
}
@@ -175,6 +175,27 @@ fun Project.setupNdkLibrary() {
}
}
+fun Project.setupCMakeLibrary() {
+ setupCommon()
+ setupNdk()
+ android.apply {
+ defaultConfig {
+ externalNativeBuild.cmake {
+ val targetAbi = requireTargetAbi()
+ if (targetAbi.isNotBlank()) {
+ abiFilters(targetAbi)
+ } else {
+ abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
+ }
+ arguments("-j${Runtime.getRuntime().availableProcessors()}")
+ }
+ }
+
+ externalNativeBuild.cmake.path("src/main/cpp/CMakeLists.txt")
+ }
+}
+
+
fun Project.setupPlay() {
val serviceAccountCredentialsFile = rootProject.file("service_account_credentials.json")
if (serviceAccountCredentialsFile.isFile) {
@@ -381,9 +402,9 @@ fun Project.setupPlugin(projectName: String) {
outputs.all {
this as BaseVariantOutputImpl
- outputFileName =
- outputFileName.replace(project.name, "${project.name}-plugin-$versionName")
- .replace("-release", "").replace("-oss", "")
+ outputFileName = outputFileName.replace(
+ project.name, "${project.name}-plugin-$versionName"
+ ).replace("-release", "").replace("-oss", "")
}
}
@@ -460,9 +481,9 @@ fun Project.setupApp() {
applicationVariants.all {
outputs.all {
this as BaseVariantOutputImpl
- outputFileName =
- outputFileName.replace(project.name, "SN-$versionName").replace("-release", "")
- .replace("-oss", "")
+ outputFileName = outputFileName.replace(project.name, "SN-$versionName")
+ .replace("-release", "")
+ .replace("-oss", "")
}
}
diff --git a/external/netty b/external/netty
new file mode 160000
index 00000000..e43d0d99
--- /dev/null
+++ b/external/netty
@@ -0,0 +1 @@
+Subproject commit e43d0d99f1beec78cd00be3018f7afeef944e5f2
diff --git a/external/netty-jni-util b/external/netty-jni-util
new file mode 160000
index 00000000..c66b7493
--- /dev/null
+++ b/external/netty-jni-util
@@ -0,0 +1 @@
+Subproject commit c66b7493ca1cdc6e2b6364e88c0845ca2f61f6f8
diff --git a/library/epoll/build.gradle.kts b/library/epoll/build.gradle.kts
new file mode 100644
index 00000000..36bf8cb6
--- /dev/null
+++ b/library/epoll/build.gradle.kts
@@ -0,0 +1,5 @@
+plugins {
+ id("com.android.library")
+}
+
+setupCMakeLibrary()
\ No newline at end of file
diff --git a/library/epoll/src/main/AndroidManifest.xml b/library/epoll/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..b279fb43
--- /dev/null
+++ b/library/epoll/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/library/epoll/src/main/cpp/CMakeLists.txt b/library/epoll/src/main/cpp/CMakeLists.txt
new file mode 100644
index 00000000..0831ea68
--- /dev/null
+++ b/library/epoll/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+set(PROJECT_PATH "${CMAKE_SOURCE_DIR}/../../../../..")
+set(UTILS_PATH "${PROJECT_PATH}/external/netty-jni-util/src/main/c")
+file(GLOB UTILS_SOURCES "${UTILS_PATH}/*.c")
+file(GLOB UTILS_HEADERS "${UTILS_PATH}/*.h")
+add_library(netty-jni-utils
+ STATIC
+ ${UTILS_SOURCES}
+ ${UTILS_HEADERS})
+
+# common
+set(COMMON_PATH "${PROJECT_PATH}/external/netty/transport-native-unix-common/src/main/c/")
+file(GLOB COMMON_SOURCES "${COMMON_PATH}/*.c")
+file(GLOB COMMON_HEADERS "${COMMON_PATH}/*.h")
+add_library(netty-common
+ STATIC
+ ${COMMON_SOURCES}
+ ${COMMON_HEADERS})
+target_include_directories(netty-common
+ PRIVATE
+ ${UTILS_PATH})
+target_link_libraries(netty-common netty-jni-utils)
+
+# epoll
+set(EPOLL_PATH "${PROJECT_PATH}/external/netty/transport-native-epoll/src/main/c/")
+file(GLOB EPOLL_SOURCES "${EPOLL_PATH}/*.c")
+file(GLOB EPOLL_HEADERS "${EPOLL_PATH}/*.h")
+add_library(netty_transport_native_epoll
+ SHARED
+ ${EPOLL_SOURCES}
+ ${COMMON_HEADERS})
+target_include_directories(netty_transport_native_epoll
+ PRIVATE
+ ${UTILS_PATH}
+ ${COMMON_PATH})
+target_link_libraries(netty_transport_native_epoll netty-common)
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 501472f2..113a933f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,4 +1,5 @@
include(":library:core")
+include(":library:epoll")
include(":library:include")
include(":library:shadowsocks")
include(":library:shadowsocksr")