diff --git a/cmd/proxy_client.go b/cmd/proxy_client.go index 85ee4b6..834dae9 100644 --- a/cmd/proxy_client.go +++ b/cmd/proxy_client.go @@ -7,6 +7,7 @@ import ( "github.com/lucas-clemente/quic-go/congestion" hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/obfs" "github.com/tobyxdd/hysteria/pkg/socks5" "io/ioutil" "log" @@ -52,11 +53,16 @@ func proxyClient(args []string) { quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow } + var obfuscator core.Obfuscator + if len(config.Obfs) > 0 { + obfuscator = obfs.XORObfuscator(config.Obfs) + } + client, err := core.NewClient(config.ServerAddr, config.Username, config.Password, tlsConfig, quicConfig, uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) - }) + }, obfuscator) if err != nil { log.Fatalln("Client initialization failed:", err) } diff --git a/cmd/proxy_config.go b/cmd/proxy_config.go index 6820925..3807641 100644 --- a/cmd/proxy_config.go +++ b/cmd/proxy_config.go @@ -16,6 +16,7 @@ type proxyClientConfig struct { DownMbps int `json:"down_mbps" desc:"Download speed in Mbps"` ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"` ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"` + Obfs string `json:"obfs" desc:"Obfuscation key"` } func (c *proxyClientConfig) Check() error { @@ -48,6 +49,7 @@ type proxyServerConfig struct { ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"` ReceiveWindowClient uint64 `json:"recv_window_client" desc:"Max receive window size per client"` MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"` + Obfs string `json:"obfs" desc:"Obfuscation key"` } func (c *proxyServerConfig) Check() error { diff --git a/cmd/proxy_server.go b/cmd/proxy_server.go index 7a5591a..2a15632 100644 --- a/cmd/proxy_server.go +++ b/cmd/proxy_server.go @@ -7,6 +7,7 @@ import ( "github.com/lucas-clemente/quic-go/congestion" hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/obfs" "io" "log" "net" @@ -55,11 +56,17 @@ func proxyServer(args []string) { log.Println("WARNING: No authentication configured. This server can be used by anyone!") } + var obfuscator core.Obfuscator + if len(config.Obfs) > 0 { + obfuscator = obfs.XORObfuscator(config.Obfs) + } + server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig, uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) }, + obfuscator, func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) { if len(config.AuthFile) == 0 { log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n", diff --git a/cmd/relay_client.go b/cmd/relay_client.go index 9eac229..1256fff 100644 --- a/cmd/relay_client.go +++ b/cmd/relay_client.go @@ -8,6 +8,7 @@ import ( "github.com/tobyxdd/hysteria/internal/utils" hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/obfs" "io/ioutil" "log" "net" @@ -60,11 +61,16 @@ func relayClient(args []string) { quicConfig.MaxReceiveConnectionFlowControlWindow = DefaultMaxReceiveConnectionFlowControlWindow } + var obfuscator core.Obfuscator + if len(config.Obfs) > 0 { + obfuscator = obfs.XORObfuscator(config.Obfs) + } + client, err := core.NewClient(config.ServerAddr, config.Name, "", tlsConfig, quicConfig, uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) - }) + }, obfuscator) if err != nil { log.Fatalln("Client initialization failed:", err) } diff --git a/cmd/relay_config.go b/cmd/relay_config.go index bb74af6..cc97421 100644 --- a/cmd/relay_config.go +++ b/cmd/relay_config.go @@ -14,6 +14,7 @@ type relayClientConfig struct { DownMbps int `json:"down_mbps" desc:"Download speed in Mbps"` ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"` ReceiveWindow uint64 `json:"recv_window" desc:"Max receive window size"` + Obfs string `json:"obfs" desc:"Obfuscation key"` } func (c *relayClientConfig) Check() error { @@ -43,6 +44,7 @@ type relayServerConfig struct { ReceiveWindowConn uint64 `json:"recv_window_conn" desc:"Max receive window size per connection"` ReceiveWindowClient uint64 `json:"recv_window_client" desc:"Max receive window size per client"` MaxConnClient int `json:"max_conn_client" desc:"Max simultaneous connections allowed per client"` + Obfs string `json:"obfs" desc:"Obfuscation key"` } func (c *relayServerConfig) Check() error { diff --git a/cmd/relay_server.go b/cmd/relay_server.go index 65bf7b0..a0b9874 100644 --- a/cmd/relay_server.go +++ b/cmd/relay_server.go @@ -6,6 +6,7 @@ import ( "github.com/lucas-clemente/quic-go/congestion" hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/obfs" "io" "log" "net" @@ -48,11 +49,17 @@ func relayServer(args []string) { quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams } + var obfuscator core.Obfuscator + if len(config.Obfs) > 0 { + obfuscator = obfs.XORObfuscator(config.Obfs) + } + server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig, uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps, func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) }, + obfuscator, func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) { // No authentication logic in relay, just log username and speed log.Printf("%s (%s) connected, negotiated speed (Mbps): Up %d / Down %d\n", diff --git a/internal/core/client.go b/internal/core/client.go index 2e4587d..9ed7046 100644 --- a/internal/core/client.go +++ b/internal/core/client.go @@ -29,10 +29,11 @@ type Client struct { quicConfig *quic.Config sendBPS, recvBPS uint64 congestionFactory CongestionFactory + obfuscator Obfuscator } func NewClient(serverAddr string, username string, password string, tlsConfig *tls.Config, quicConfig *quic.Config, - sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory) (*Client, error) { + sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory, obfuscator Obfuscator) (*Client, error) { c := &Client{ serverAddr: serverAddr, username: username, @@ -42,6 +43,7 @@ func NewClient(serverAddr string, username string, password string, tlsConfig *t sendBPS: sendBPS, recvBPS: recvBPS, congestionFactory: congestionFactory, + obfuscator: obfuscator, } if err := c.connectToServer(); err != nil { return nil, err @@ -97,7 +99,22 @@ func (c *Client) Close() error { } func (c *Client) connectToServer() error { - qs, err := quic.DialAddr(c.serverAddr, c.tlsConfig, c.quicConfig) + serverUDPAddr, err := net.ResolveUDPAddr("udp", c.serverAddr) + if err != nil { + return err + } + packetConn, err := net.ListenPacket("udp", "") + if err != nil { + return err + } + if c.obfuscator != nil { + // Wrap PacketConn with obfuscator + packetConn = &obfsPacketConn{ + Orig: packetConn, + Obfuscator: c.obfuscator, + } + } + qs, err := quic.Dial(packetConn, serverUDPAddr, c.serverAddr, c.tlsConfig, c.quicConfig) if err != nil { return err } diff --git a/internal/core/obfs.go b/internal/core/obfs.go new file mode 100644 index 0000000..8454dcf --- /dev/null +++ b/internal/core/obfs.go @@ -0,0 +1,56 @@ +package core + +import ( + "net" + "time" +) + +type Obfuscator interface { + Deobfuscate(buf []byte, n int) int + Obfuscate(p []byte) []byte +} + +type obfsPacketConn struct { + Orig net.PacketConn + Obfuscator Obfuscator +} + +func (c *obfsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + oldN, addr, err := c.Orig.ReadFrom(p) + if oldN > 0 { + newN := c.Obfuscator.Deobfuscate(p, oldN) + return newN, addr, err + } else { + return 0, addr, err + } +} + +func (c *obfsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + np := c.Obfuscator.Obfuscate(p) + _, err = c.Orig.WriteTo(np, addr) + if err != nil { + return 0, err + } else { + return len(p), nil + } +} + +func (c *obfsPacketConn) Close() error { + return c.Orig.Close() +} + +func (c *obfsPacketConn) LocalAddr() net.Addr { + return c.Orig.LocalAddr() +} + +func (c *obfsPacketConn) SetDeadline(t time.Time) error { + return c.Orig.SetDeadline(t) +} + +func (c *obfsPacketConn) SetReadDeadline(t time.Time) error { + return c.Orig.SetReadDeadline(t) +} + +func (c *obfsPacketConn) SetWriteDeadline(t time.Time) error { + return c.Orig.SetWriteDeadline(t) +} diff --git a/internal/core/server.go b/internal/core/server.go index 479cb6b..ee1667c 100644 --- a/internal/core/server.go +++ b/internal/core/server.go @@ -32,11 +32,23 @@ type Server struct { func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config, sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory, + obfuscator Obfuscator, clientAuthFunc ClientAuthFunc, clientDisconnectedFunc ClientDisconnectedFunc, handleRequestFunc HandleRequestFunc, requestClosedFunc RequestClosedFunc) (*Server, error) { - listener, err := quic.ListenAddr(addr, tlsConfig, quicConfig) + packetConn, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + if obfuscator != nil { + // Wrap PacketConn with obfuscator + packetConn = &obfsPacketConn{ + Orig: packetConn, + Obfuscator: obfuscator, + } + } + listener, err := quic.Listen(packetConn, tlsConfig, quicConfig) if err != nil { return nil, err } diff --git a/pkg/core/interface.go b/pkg/core/interface.go index dea0b23..38225e4 100644 --- a/pkg/core/interface.go +++ b/pkg/core/interface.go @@ -25,6 +25,7 @@ const ( ) type CongestionFactory core.CongestionFactory +type Obfuscator core.Obfuscator type ClientAuthFunc func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (AuthResult, string) type ClientDisconnectedFunc core.ClientDisconnectedFunc type HandleRequestFunc func(addr net.Addr, username string, id int, packet bool, reqAddr string) (ConnectResult, string, io.ReadWriteCloser) @@ -38,11 +39,13 @@ type Server interface { func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config, sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory, + obfuscator Obfuscator, clientAuthFunc ClientAuthFunc, clientDisconnectedFunc ClientDisconnectedFunc, handleRequestFunc HandleRequestFunc, requestClosedFunc RequestClosedFunc) (Server, error) { return core.NewServer(addr, tlsConfig, quicConfig, sendBPS, recvBPS, core.CongestionFactory(congestionFactory), + core.Obfuscator(obfuscator), func(addr net.Addr, username string, password string, sSend uint64, sRecv uint64) (core.AuthResult, string) { r, msg := clientAuthFunc(addr, username, password, sSend, sRecv) return core.AuthResult(r), msg @@ -65,7 +68,7 @@ type Client interface { func NewClient(serverAddr string, username string, password string, tlsConfig *tls.Config, quicConfig *quic.Config, sendBPS uint64, recvBPS uint64, - congestionFactory CongestionFactory) (Client, error) { + congestionFactory CongestionFactory, obfuscator Obfuscator) (Client, error) { return core.NewClient(serverAddr, username, password, tlsConfig, quicConfig, sendBPS, recvBPS, - core.CongestionFactory(congestionFactory)) + core.CongestionFactory(congestionFactory), core.Obfuscator(obfuscator)) } diff --git a/pkg/obfs/xor.go b/pkg/obfs/xor.go new file mode 100644 index 0000000..37c232b --- /dev/null +++ b/pkg/obfs/xor.go @@ -0,0 +1,20 @@ +package obfs + +type XORObfuscator []byte + +func (x XORObfuscator) Deobfuscate(buf []byte, n int) int { + l := len(x) + for i := range buf { + buf[i] ^= x[i%l] + } + return n +} + +func (x XORObfuscator) Obfuscate(p []byte) []byte { + np := make([]byte, len(p)) + l := len(x) + for i := range p { + np[i] = p[i] ^ x[i%l] + } + return np +}