Skip to content

Commit

Permalink
Add tls stream support;
Browse files Browse the repository at this point in the history
  • Loading branch information
selcarpa committed Jun 22, 2023
1 parent 0cd1e48 commit 767d71a
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 27 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ dependencies {
implementation("io.netty:netty-all:4.1.93.Final")
// implementation("io.jpower.kcp:kcp-netty:1.5.0")

//ssl server support
implementation("org.bouncycastle:bcpkix-jdk18on:1.75")

//kotlin-logging
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
implementation("ch.qos.logback:logback-classic:1.4.7")
Expand Down
1 change: 1 addition & 0 deletions configurations/wss+trojan/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# WSS+TROJAN
8 changes: 4 additions & 4 deletions configurations/wss+trojan/client/trojanOut.json5
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"inbounds": [
{
port: 14270,
port: 14270,//local port
protocol: "socks5"
},
{
port: 14272,
port: 14272,//local port
protocol: "http"
}
],
Expand All @@ -15,8 +15,8 @@
"outboundStreamBy": {
"type": "wss",
"wsOutboundSetting": {
"port": 443,
"host": "example.org",
"port": 443,//caddy default https port
"host": "example.org",//same as caddyfile domain
"path": "/surfer"
}
},
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/model/config/Configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ data class HttpOutboundSetting(val auth: Auth?,val port:Int,val host:String)
@Serializable
data class TcpOutboundSetting(val port: Int, val host: String)
@Serializable
data class InboundStreamBy(val type: String, val wsInboundSetting: WsInboundSetting)
data class InboundStreamBy(val type: String, val wsInboundSetting: WsInboundSetting?,val tlsInboundSetting: TlsInboundSetting? )
@Serializable
data class TlsInboundSetting(val keyCertChainFile:String, val keyFile:String, val password: String?)
@Serializable
data class WsOutboundSetting(val path: String, val port: Int, val host: String)
@Serializable
Expand Down
43 changes: 39 additions & 4 deletions src/main/kotlin/netty/ProxyChannelInitializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@ import io.netty.handler.codec.http.HttpObjectAggregator
import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler
import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.stream.ChunkedWriteHandler
import io.netty.handler.timeout.IdleStateHandler
import io.netty.util.concurrent.FutureListener
import io.netty.util.concurrent.Promise
import model.config.ConfigurationSettings.Companion.Configuration
import model.config.Inbound
import model.config.TlsInboundSetting
import model.config.WsInboundSetting
import model.protocol.Protocol
import mu.KotlinLogging
import protocol.TrojanInboundHandler
import stream.SslActiveHandler
import stream.WebsocketDuplexHandler
import java.io.File
import java.util.function.Function
import java.util.stream.Collectors

Expand All @@ -40,7 +46,6 @@ class ProxyChannelInitializer : ChannelInitializer<NioSocketChannel>() {
//todo: set idle timeout, and close channel
ch.pipeline().addFirst(IdleStateHandler(300, 300, 300))
ch.pipeline().addFirst(IdleCloseHandler())
//todo refactor to strategy pattern
if (inbound != null) {
when (Protocol.valueOfOrNull(inbound.protocol)) {
Protocol.HTTP -> {
Expand Down Expand Up @@ -95,7 +100,17 @@ class ProxyChannelInitializer : ChannelInitializer<NioSocketChannel>() {
}
})

initWebsocketInbound(ch, inbound.inboundStreamBy.wsInboundSetting.path, handleShakePromise)
initWebsocketInbound(ch, inbound.inboundStreamBy.wsInboundSetting!!, handleShakePromise)
}
Protocol.TLS->{
val handleShakePromise = ch.eventLoop().next().newPromise<Channel>()
handleShakePromise.addListener(FutureListener { future ->
if (future.isSuccess) {
future.get().pipeline().addLast(TrojanInboundHandler(inbound))
}
})

initTlsInbound(ch, inbound.inboundStreamBy.tlsInboundSetting!!, handleShakePromise)
}

else -> {
Expand All @@ -106,16 +121,36 @@ class ProxyChannelInitializer : ChannelInitializer<NioSocketChannel>() {

}

private fun initTlsInbound(
ch: NioSocketChannel,
tlsInboundSetting: TlsInboundSetting,
handleShakePromise: Promise<Channel>
) {
val sslCtx: SslContext = if (tlsInboundSetting.password != null) {
SslContextBuilder.forServer(
File(tlsInboundSetting.keyCertChainFile),
File(tlsInboundSetting.keyFile),
tlsInboundSetting.password
).build()
} else {
SslContextBuilder.forServer(File(tlsInboundSetting.keyCertChainFile), File(tlsInboundSetting.keyFile)).build()
}
ch.pipeline().addLast(
sslCtx.newHandler(ch.alloc()),
SslActiveHandler(handleShakePromise)
)
}

private fun initWebsocketInbound(
ch: NioSocketChannel,
path: String,
wsInboundSetting: WsInboundSetting,
handleShakePromise: Promise<Channel>
) {
ch.pipeline().addLast(
ChunkedWriteHandler(),
HttpServerCodec(),
HttpObjectAggregator(Int.MAX_VALUE),
WebSocketServerProtocolHandler(path),
WebSocketServerProtocolHandler(wsInboundSetting.path),
WebsocketDuplexHandler(handleShakePromise)
)
}
Expand Down
23 changes: 23 additions & 0 deletions src/main/kotlin/stream/SslActiveHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package stream

import io.netty.channel.Channel
import io.netty.channel.ChannelDuplexHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.ssl.SslCompletionEvent
import io.netty.util.concurrent.Promise
import mu.KotlinLogging

/**
* ssl activator for client connected, when ssl handshake complete, we can activate other operation
*/
class SslActiveHandler(private val promise: Promise<Channel>) : ChannelDuplexHandler() {
private val logger = KotlinLogging.logger {}
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any?) {
if (evt is SslCompletionEvent) {
logger.trace { "SslCompletionEvent: $evt" }
promise.setSuccess(ctx.channel())
ctx.channel().pipeline().remove(this)
}
ctx.fireUserEventTriggered(evt)
}
}
20 changes: 2 additions & 18 deletions src/main/kotlin/stream/Surfer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import io.netty.handler.logging.LoggingHandler
import io.netty.handler.proxy.HttpProxyHandler
import io.netty.handler.proxy.ProxyConnectionEvent
import io.netty.handler.proxy.Socks5ProxyHandler
import io.netty.handler.ssl.SslCompletionEvent
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.util.InsecureTrustManagerFactory
Expand Down Expand Up @@ -189,9 +188,8 @@ private fun tlsStream(
connect(
eventLoopGroup, {
mutableListOf(
HandlerPair(SslActiveHandler(promise)),
HandlerPair(sslCtx.newHandler(it.alloc(), tcpOutboundSetting.host, tcpOutboundSetting.port))

HandlerPair(sslCtx.newHandler(it.alloc(), tcpOutboundSetting.host, tcpOutboundSetting.port)),
HandlerPair(ChannelActiveHandler(promise)),
)
}, odor
)
Expand Down Expand Up @@ -424,20 +422,6 @@ class ChannelActiveHandler(private val promise: Promise<Channel>) : ChannelDuple
}
}

/**
* ssl activator for client connected, when ssl handshake complete, we can activate other operation
*/
class SslActiveHandler(private val promise: Promise<Channel>) : ChannelDuplexHandler() {
private val logger = KotlinLogging.logger {}
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any?) {
if (evt is SslCompletionEvent) {
logger.trace { "SslCompletionEvent: $evt" }
promise.setSuccess(ctx.channel())
}
ctx.fireUserEventTriggered(evt)
}
}


/**
* relay from client channel to server
Expand Down

0 comments on commit 767d71a

Please sign in to comment.