mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2024-04-21 12:21:34 +00:00
add utls anti-fingerprinting
This commit is contained in:
+11
-9
@@ -36,15 +36,17 @@ func (s *ClientAPIService) QueryStats(ctx context.Context, req *StatsRequest) (*
|
||||
}
|
||||
|
||||
func (s *ClientAPIService) calcSpeed() {
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
sent, recv := s.meter.Query("")
|
||||
s.uploadSpeed = sent - s.lastSent
|
||||
s.downloadSpeed = recv - s.lastRecv
|
||||
s.lastSent = sent
|
||||
s.lastRecv = recv
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
sent, recv := s.meter.Query("")
|
||||
s.uploadSpeed = sent - s.lastSent
|
||||
s.downloadSpeed = recv - s.lastRecv
|
||||
s.lastSent = sent
|
||||
s.lastRecv = recv
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -48,4 +48,6 @@ for name in trojan-go-*;do
|
||||
zip $name.zip client.json server.json trojan-go.service geoip.dat geosite.dat $name
|
||||
sha1sum $name.zip > $name.zip.sha1
|
||||
rm $name
|
||||
done
|
||||
done
|
||||
|
||||
rm *.json *.service *.dat
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
type RunType string
|
||||
@@ -31,7 +32,9 @@ type TLSConfig struct {
|
||||
FallbackHost string `json:"fallback_addr"`
|
||||
FallbackPort int `json:"fallback_port"`
|
||||
ReuseSession bool `json:"reuse_session"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
|
||||
ClientHelloID utls.ClientHelloID
|
||||
FallbackAddress *common.Address
|
||||
CertPool *x509.CertPool
|
||||
KeyPair []tls.Certificate
|
||||
|
||||
+25
-2
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
@@ -46,7 +47,11 @@ func loadCommonConfig(config *GlobalConfig) error {
|
||||
config.TargetAddress = common.NewAddress(config.TargetHost, config.TargetPort, "tcp")
|
||||
|
||||
if config.TLS.FallbackPort != 0 {
|
||||
config.TLS.FallbackAddress = common.NewAddress(config.RemoteHost, config.TLS.FallbackPort, "tcp")
|
||||
if config.TLS.FallbackHost == "" {
|
||||
config.TLS.FallbackAddress = common.NewAddress(config.RemoteHost, config.TLS.FallbackPort, "tcp")
|
||||
} else {
|
||||
config.TLS.FallbackAddress = common.NewAddress(config.TLS.FallbackHost, config.TLS.FallbackPort, "tcp")
|
||||
}
|
||||
}
|
||||
|
||||
//api settings
|
||||
@@ -84,7 +89,7 @@ func loadCommonConfig(config *GlobalConfig) error {
|
||||
for _, c := range supportedSuites {
|
||||
list += c.Name + ":"
|
||||
}
|
||||
log.Warn(list[0 : len(list)-1])
|
||||
log.Warn(list[:len(list)-1])
|
||||
config.TLS.CipherSuites = nil
|
||||
}
|
||||
} else {
|
||||
@@ -192,6 +197,24 @@ func loadClientConfig(config *GlobalConfig) error {
|
||||
}
|
||||
|
||||
//tls settings
|
||||
if config.TLS.Fingerprint != "" && config.TLS.Fingerprint != "auto" {
|
||||
table := map[string]utls.ClientHelloID{
|
||||
"chrome": utls.HelloChrome_Auto,
|
||||
"firefox": utls.HelloFirefox_Auto,
|
||||
"ios": utls.HelloIOS_Auto,
|
||||
"randomized": utls.HelloRandomized,
|
||||
"randomized_alpn": utls.HelloRandomizedALPN,
|
||||
"randomized_no_alpn": utls.HelloRandomizedNoALPN,
|
||||
}
|
||||
id, found := table[config.TLS.Fingerprint]
|
||||
if found {
|
||||
log.Debug("tls fingerprint loaded:", id.Str())
|
||||
config.TLS.ClientHelloID = id
|
||||
} else {
|
||||
log.Warn("invalid tls fingerprint:", config.TLS.Fingerprint, ", using default fingerprint")
|
||||
}
|
||||
}
|
||||
|
||||
if config.TLS.SNI == "" {
|
||||
log.Warn("SNI is unspecified, using remote_addr as SNI")
|
||||
config.TLS.SNI = config.RemoteHost
|
||||
|
||||
@@ -7,6 +7,7 @@ require (
|
||||
github.com/go-acme/lego/v3 v3.5.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/golang/protobuf v1.4.0
|
||||
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57
|
||||
github.com/smartystreets/goconvey v1.6.4
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
|
||||
github.com/xtaci/smux v1.5.12
|
||||
|
||||
@@ -229,6 +229,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57 h1:SL1K0QAuC1b54KoY1pjPWe6kSlsFHwK9/oC960fKrTY=
|
||||
github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build linux
|
||||
// +build linux darwin
|
||||
|
||||
package tproxy
|
||||
|
||||
|
||||
+151
-16
@@ -3,30 +3,118 @@ package client
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/common"
|
||||
"github.com/p4gefau1t/trojan-go/conf"
|
||||
"github.com/p4gefau1t/trojan-go/log"
|
||||
"github.com/p4gefau1t/trojan-go/protocol/trojan"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
type Roller struct {
|
||||
HelloIDs []utls.ClientHelloID
|
||||
HelloIDMu sync.Mutex
|
||||
WorkingHelloID *utls.ClientHelloID
|
||||
TCPDialTimeout time.Duration
|
||||
TLSHandshakeTimeout time.Duration
|
||||
TLSConfig *utls.Config
|
||||
}
|
||||
|
||||
// NewRoller creates Roller object with default range of HelloIDs to cycle through until a
|
||||
// working/unblocked one is found.
|
||||
func NewRoller(config *utls.Config) *Roller {
|
||||
tcpDialTimeoutInc := rand.Intn(14)
|
||||
tcpDialTimeoutInc = 7 + tcpDialTimeoutInc
|
||||
|
||||
tlsHandshakeTimeoutInc := rand.Intn(20)
|
||||
tlsHandshakeTimeoutInc = 11 + tlsHandshakeTimeoutInc
|
||||
|
||||
return &Roller{
|
||||
HelloIDs: []utls.ClientHelloID{
|
||||
utls.HelloChrome_Auto,
|
||||
utls.HelloFirefox_Auto,
|
||||
utls.HelloIOS_Auto,
|
||||
utls.HelloRandomized,
|
||||
},
|
||||
TCPDialTimeout: time.Second * time.Duration(tcpDialTimeoutInc),
|
||||
TLSHandshakeTimeout: time.Second * time.Duration(tlsHandshakeTimeoutInc),
|
||||
TLSConfig: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Roller) Dial(network, addr, serverName string) (*utls.UConn, error) {
|
||||
helloIDs := make([]utls.ClientHelloID, len(c.HelloIDs))
|
||||
copy(helloIDs, c.HelloIDs)
|
||||
rand.Shuffle(len(c.HelloIDs), func(i, j int) {
|
||||
helloIDs[i], helloIDs[j] = helloIDs[j], helloIDs[i]
|
||||
})
|
||||
|
||||
c.HelloIDMu.Lock()
|
||||
workingHelloID := c.WorkingHelloID // keep using same helloID, if it works
|
||||
c.HelloIDMu.Unlock()
|
||||
if workingHelloID != nil {
|
||||
helloIDFound := false
|
||||
for i, ID := range helloIDs {
|
||||
if ID == *workingHelloID {
|
||||
helloIDs[i] = helloIDs[0]
|
||||
helloIDs[0] = *workingHelloID // push working hello ID first
|
||||
helloIDFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !helloIDFound {
|
||||
helloIDs = append([]utls.ClientHelloID{*workingHelloID}, helloIDs...)
|
||||
}
|
||||
}
|
||||
|
||||
var tcpConn net.Conn
|
||||
var err error
|
||||
for _, helloID := range helloIDs {
|
||||
tcpConn, err = net.DialTimeout(network, addr, c.TCPDialTimeout)
|
||||
if err != nil {
|
||||
return nil, err // on tcp Dial failure return with error right away
|
||||
}
|
||||
|
||||
client := utls.UClient(tcpConn, c.TLSConfig, helloID)
|
||||
client.SetSNI(serverName)
|
||||
client.SetDeadline(time.Now().Add(c.TLSHandshakeTimeout))
|
||||
err = client.Handshake()
|
||||
client.SetDeadline(time.Time{}) // unset timeout
|
||||
if err != nil {
|
||||
log.Debug("hello id", helloID.Str(), "failed, err:", err)
|
||||
continue // on tls Dial error keep trying HelloIDs
|
||||
}
|
||||
|
||||
log.Debug("found avaliable hello id:", helloID.Str())
|
||||
c.HelloIDMu.Lock()
|
||||
c.WorkingHelloID = &client.ClientHelloID
|
||||
c.HelloIDMu.Unlock()
|
||||
return client, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type TLSManager struct {
|
||||
TransportManager
|
||||
|
||||
tlsConfig *tls.Config
|
||||
config *conf.GlobalConfig
|
||||
utlsConfig *utls.Config
|
||||
tlsConfig *tls.Config
|
||||
autoClientHelloID *utls.ClientHelloID
|
||||
config *conf.GlobalConfig
|
||||
roller *Roller
|
||||
}
|
||||
|
||||
func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
|
||||
network := "tcp"
|
||||
if m.config.TCP.PreferIPV4 {
|
||||
network = "tcp4"
|
||||
func (m *TLSManager) printConnInfo(conn net.Conn) {
|
||||
if m.config.LogLevel != 0 {
|
||||
return
|
||||
}
|
||||
tlsConn, err := tls.Dial(network, m.config.RemoteAddress.String(), m.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, common.NewError("cannot dial to the remote server").Base(err)
|
||||
}
|
||||
if m.config.LogLevel == 0 {
|
||||
switch conn.(type) {
|
||||
case *tls.Conn:
|
||||
tlsConn := conn.(*tls.Conn)
|
||||
state := tlsConn.ConnectionState()
|
||||
chain := state.VerifiedChains
|
||||
log.Debug("tls handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume)
|
||||
@@ -35,19 +123,63 @@ func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
|
||||
log.Debug("subject:", chain[i][j].Subject, ", issuer:", chain[i][j].Issuer)
|
||||
}
|
||||
}
|
||||
case *utls.UConn:
|
||||
tlsConn := conn.(*utls.UConn)
|
||||
state := tlsConn.ConnectionState()
|
||||
chain := state.VerifiedChains
|
||||
log.Debug("tls handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume)
|
||||
for i := range chain {
|
||||
for j := range chain[i] {
|
||||
log.Debug("subject:", chain[i][j].Subject, ", issuer:", chain[i][j].Issuer)
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(conn)
|
||||
}
|
||||
var conn io.ReadWriteCloser = tlsConn
|
||||
}
|
||||
|
||||
func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) {
|
||||
network := "tcp"
|
||||
if m.config.TCP.PreferIPV4 {
|
||||
network = "tcp4"
|
||||
}
|
||||
var tlsConn net.Conn
|
||||
var err error
|
||||
if m.config.TLS.Fingerprint == "auto" {
|
||||
//use utls roller
|
||||
tlsConn, err = m.roller.Dial(network, m.config.RemoteAddress.String(), m.config.TLS.SNI)
|
||||
} else if m.config.TLS.ClientHelloID.IsSet() {
|
||||
//use utls fixed fingerprint
|
||||
log.Debug("using fingerprint", m.config.TLS.ClientHelloID.Str())
|
||||
var conn net.Conn
|
||||
conn, err = net.Dial(network, m.config.RemoteAddress.String())
|
||||
tlsConn = utls.UClient(conn, m.utlsConfig, m.config.TLS.ClientHelloID)
|
||||
} else {
|
||||
//normal golang tls
|
||||
tlsConn, err = tls.Dial(network, m.config.RemoteAddress.String(), m.tlsConfig)
|
||||
}
|
||||
m.printConnInfo(tlsConn)
|
||||
if err != nil {
|
||||
return nil, common.NewError("cannot dial to the remote server").Base(err)
|
||||
}
|
||||
var transport io.ReadWriteCloser = tlsConn
|
||||
if m.config.Websocket.Enabled {
|
||||
ws, err := trojan.NewOutboundWebosocket(tlsConn, m.config)
|
||||
if err != nil {
|
||||
return nil, common.NewError("failed to start websocket connection").Base(err)
|
||||
}
|
||||
conn = ws
|
||||
transport = ws
|
||||
}
|
||||
return conn, nil
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
func NewTLSManager(config *conf.GlobalConfig) *TLSManager {
|
||||
utlsConfig := &utls.Config{
|
||||
RootCAs: config.TLS.CertPool,
|
||||
ServerName: config.TLS.SNI,
|
||||
InsecureSkipVerify: !config.TLS.Verify,
|
||||
SessionTicketsDisabled: !config.TLS.SessionTicket,
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
CipherSuites: config.TLS.CipherSuites,
|
||||
RootCAs: config.TLS.CertPool,
|
||||
@@ -56,11 +188,14 @@ func NewTLSManager(config *conf.GlobalConfig) *TLSManager {
|
||||
SessionTicketsDisabled: !config.TLS.SessionTicket,
|
||||
}
|
||||
if config.TLS.ReuseSession {
|
||||
utlsConfig.ClientSessionCache = utls.NewLRUClientSessionCache(-1)
|
||||
tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(-1)
|
||||
}
|
||||
m := &TLSManager{
|
||||
config: config,
|
||||
tlsConfig: tlsConfig,
|
||||
config: config,
|
||||
utlsConfig: utlsConfig,
|
||||
tlsConfig: tlsConfig,
|
||||
roller: NewRoller(utlsConfig),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package sockopt
|
||||
+50
-4
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -98,6 +99,7 @@ func getTLSConfig() conf.TLSConfig {
|
||||
ReuseSession: true,
|
||||
SessionTicket: true,
|
||||
FallbackAddress: common.NewAddress("127.0.0.1", 10080, "tcp"),
|
||||
Fingerprint: "auto",
|
||||
}
|
||||
return c
|
||||
}
|
||||
@@ -194,7 +196,6 @@ func RunServer(ctx context.Context, config *conf.GlobalConfig) {
|
||||
}
|
||||
|
||||
func CheckClientServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) {
|
||||
time.Sleep(time.Second)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go RunEchoTCPServer(ctx)
|
||||
go RunServer(ctx, serverConfig)
|
||||
@@ -216,8 +217,8 @@ func CheckClientServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConf
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
cancel()
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
func CheckForwardServer(t *testing.T, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) {
|
||||
@@ -267,17 +268,51 @@ func SingleThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalCo
|
||||
common.Must(err)
|
||||
conn, err := dialer.Dial("tcp", "127.0.0.1:5000")
|
||||
common.Must(err)
|
||||
mbytes := 512
|
||||
mbytes := 2048
|
||||
payload := GeneratePayload(1024 * 1024 * mbytes)
|
||||
t1 := time.Now()
|
||||
conn.Write(payload)
|
||||
t2 := time.Now()
|
||||
speed := float64(mbytes) / t2.Sub(t1).Seconds()
|
||||
b.Log("Single thread link speed:", speed*8/1024, "Gbps")
|
||||
b.Log("single-thread link speed:", speed*8/1024, "Gbps")
|
||||
conn.Close()
|
||||
cancel()
|
||||
}
|
||||
|
||||
func MultiThreadSpeedTestClientServer(b *testing.B, clientConfig *conf.GlobalConfig, serverConfig *conf.GlobalConfig) {
|
||||
time.Sleep(time.Second)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go RunBlackHoleTCPServer(ctx)
|
||||
go RunServer(ctx, serverConfig)
|
||||
go RunClient(ctx, clientConfig)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:4444", nil, nil)
|
||||
common.Must(err)
|
||||
mbytes := 2048
|
||||
threads := 16
|
||||
payload := GeneratePayload(1024 * 1024 * mbytes / threads)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(threads)
|
||||
t1 := time.Now()
|
||||
for i := 0; i < threads; i++ {
|
||||
go func() {
|
||||
conn, err := dialer.Dial("tcp", "127.0.0.1:5000")
|
||||
common.Must(err)
|
||||
common.Must2(conn.Write(payload))
|
||||
wg.Done()
|
||||
conn.Close()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
t2 := time.Now()
|
||||
speed := float64(mbytes) / t2.Sub(t1).Seconds()
|
||||
|
||||
b.Log("multi-thread link speed:", speed*8/1024, "Gbps")
|
||||
cancel()
|
||||
}
|
||||
|
||||
func TestIt(t *testing.T) {
|
||||
clientConfig := getBasicClientConfig()
|
||||
serverConfig := getBasicServerConfig()
|
||||
@@ -315,24 +350,28 @@ func BenchmarkNormal(b *testing.B) {
|
||||
clientConfig := getBasicClientConfig()
|
||||
serverConfig := getBasicServerConfig()
|
||||
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
}
|
||||
|
||||
func BenchmarkMux(b *testing.B) {
|
||||
clientConfig := addMuxConfig(getBasicClientConfig())
|
||||
serverConfig := getBasicServerConfig()
|
||||
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
}
|
||||
|
||||
func BenchmarkWebsocket(b *testing.B) {
|
||||
clientConfig := addWsConfig(getBasicClientConfig())
|
||||
serverConfig := addWsConfig(getBasicServerConfig())
|
||||
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
}
|
||||
|
||||
func BenchmarkMuxWebsocket(b *testing.B) {
|
||||
clientConfig := addMuxConfig(addWsConfig(getBasicClientConfig()))
|
||||
serverConfig := addWsConfig(getBasicServerConfig())
|
||||
SingleThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
MultiThreadSpeedTestClientServer(b, clientConfig, serverConfig)
|
||||
}
|
||||
|
||||
func TestWebsocketShadow(t *testing.T) {
|
||||
@@ -410,3 +449,10 @@ func TestShadow(t *testing.T) {
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
|
||||
func TestAutoClientID(t *testing.T) {
|
||||
serverConfig := getBasicServerConfig()
|
||||
clientConfig := getBasicClientConfig()
|
||||
clientConfig.TLS.Fingerprint = "auto"
|
||||
CheckClientServer(t, clientConfig, serverConfig)
|
||||
}
|
||||
|
||||
+5
-1
@@ -69,7 +69,10 @@ func RunBlackHoleTCPServer(ctx context.Context) {
|
||||
common.Must(err)
|
||||
go func() {
|
||||
for {
|
||||
conn, _ := listener.Accept()
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
io.Copy(ioutil.Discard, conn)
|
||||
conn.Close()
|
||||
@@ -77,6 +80,7 @@ func RunBlackHoleTCPServer(ctx context.Context) {
|
||||
}
|
||||
}()
|
||||
<-ctx.Done()
|
||||
listener.Close()
|
||||
}
|
||||
|
||||
func RunHelloHTTPServer(ctx context.Context) {
|
||||
|
||||
Reference in New Issue
Block a user