From b80db1fc19da7472d984d9fa444908c313c621f6 Mon Sep 17 00:00:00 2001 From: Toby Date: Mon, 19 Apr 2021 20:52:50 -0700 Subject: [PATCH] XPlus obfs & don't frag --- cmd/client.go | 2 +- cmd/server.go | 2 +- pkg/core/client.go | 7 ++++++ pkg/core/obfs.go | 24 ++++++++++++------- pkg/core/server.go | 7 ++++++ pkg/obfs/xor.go | 8 +++---- pkg/obfs/xplus.go | 52 +++++++++++++++++++++++++++++++++++++++++ pkg/obfs/xplus_test.go | 31 ++++++++++++++++++++++++ pkg/utils/df_linux.go | 21 +++++++++++++++++ pkg/utils/df_stub.go | 10 ++++++++ pkg/utils/df_windows.go | 23 ++++++++++++++++++ 11 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 pkg/obfs/xplus.go create mode 100644 pkg/obfs/xplus_test.go create mode 100644 pkg/utils/df_linux.go create mode 100644 pkg/utils/df_stub.go create mode 100644 pkg/utils/df_windows.go diff --git a/cmd/client.go b/cmd/client.go index b08e5fa..9ea3da5 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -68,7 +68,7 @@ func client(config *clientConfig) { // Obfuscator var obfuscator core.Obfuscator if len(config.Obfs) > 0 { - obfuscator = obfs.XORObfuscator(config.Obfs) + obfuscator = obfs.NewXPlusObfuscator([]byte(config.Obfs)) } // ACL var aclEngine *acl.Engine diff --git a/cmd/server.go b/cmd/server.go index 69b3093..95ab1ad 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -99,7 +99,7 @@ func server(config *serverConfig) { // Obfuscator var obfuscator core.Obfuscator if len(config.Obfs) > 0 { - obfuscator = obfs.XORObfuscator(config.Obfs) + obfuscator = obfs.NewXPlusObfuscator([]byte(config.Obfs)) } // ACL var aclEngine *acl.Engine diff --git a/pkg/core/client.go b/pkg/core/client.go index 604583d..68a0ea1 100644 --- a/pkg/core/client.go +++ b/pkg/core/client.go @@ -9,6 +9,7 @@ import ( "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/congestion" "github.com/lunixbochs/struc" + "github.com/tobyxdd/hysteria/pkg/utils" "net" "strconv" "sync" @@ -66,6 +67,10 @@ func (c *Client) connectToServer() error { if err != nil { return err } + if err := utils.SetDontFragment(udpConn); err != nil { + _ = udpConn.Close() + return err + } var qs quic.Session if c.obfuscator != nil { // Wrap PacketConn with obfuscator @@ -74,11 +79,13 @@ func (c *Client) connectToServer() error { Obfuscator: c.obfuscator, }, serverUDPAddr, c.serverAddr, c.tlsConfig, c.quicConfig) if err != nil { + _ = udpConn.Close() return err } } else { qs, err = quic.Dial(udpConn, serverUDPAddr, c.serverAddr, c.tlsConfig, c.quicConfig) if err != nil { + _ = udpConn.Close() return err } } diff --git a/pkg/core/obfs.go b/pkg/core/obfs.go index 68ff95c..45523c3 100644 --- a/pkg/core/obfs.go +++ b/pkg/core/obfs.go @@ -8,7 +8,7 @@ import ( ) type Obfuscator interface { - Deobfuscate(buf []byte, n int) int + Deobfuscate(in []byte, out []byte) int Obfuscate(p []byte) []byte } @@ -21,13 +21,21 @@ func (c *obfsUDPConn) SyscallConn() (syscall.RawConn, error) { return c.Orig.SyscallConn() } -func (c *obfsUDPConn) 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 *obfsUDPConn) ReadFrom(p []byte) (int, net.Addr, error) { + buf := make([]byte, udpBufferSize) + for { + n, addr, err := c.Orig.ReadFrom(buf) + if n <= 0 { + return 0, addr, err + } + newN := c.Obfuscator.Deobfuscate(buf[:n], p) + if newN > 0 { + // Valid packet + return newN, addr, err + } else if err != nil { + // Not valid and Orig.ReadFrom had some error + return 0, addr, err + } } } diff --git a/pkg/core/server.go b/pkg/core/server.go index 57475c6..e61a556 100644 --- a/pkg/core/server.go +++ b/pkg/core/server.go @@ -9,6 +9,7 @@ import ( "github.com/lunixbochs/struc" "github.com/prometheus/client_golang/prometheus" "github.com/tobyxdd/hysteria/pkg/acl" + "github.com/tobyxdd/hysteria/pkg/utils" "net" ) @@ -47,6 +48,10 @@ func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config, if err != nil { return nil, err } + if err := utils.SetDontFragment(udpConn); err != nil { + _ = udpConn.Close() + return nil, err + } var listener quic.Listener if obfuscator != nil { // Wrap PacketConn with obfuscator @@ -55,11 +60,13 @@ func NewServer(addr string, tlsConfig *tls.Config, quicConfig *quic.Config, Obfuscator: obfuscator, }, tlsConfig, quicConfig) if err != nil { + _ = udpConn.Close() return nil, err } } else { listener, err = quic.Listen(udpConn, tlsConfig, quicConfig) if err != nil { + _ = udpConn.Close() return nil, err } } diff --git a/pkg/obfs/xor.go b/pkg/obfs/xor.go index 37c232b..1cee94e 100644 --- a/pkg/obfs/xor.go +++ b/pkg/obfs/xor.go @@ -2,12 +2,12 @@ package obfs type XORObfuscator []byte -func (x XORObfuscator) Deobfuscate(buf []byte, n int) int { +func (x XORObfuscator) Deobfuscate(in []byte, out []byte) int { l := len(x) - for i := range buf { - buf[i] ^= x[i%l] + for i := range in { + out[i] = in[i] ^ x[i%l] } - return n + return len(in) } func (x XORObfuscator) Obfuscate(p []byte) []byte { diff --git a/pkg/obfs/xplus.go b/pkg/obfs/xplus.go new file mode 100644 index 0000000..a836411 --- /dev/null +++ b/pkg/obfs/xplus.go @@ -0,0 +1,52 @@ +package obfs + +import ( + "crypto/sha256" + "math/rand" + "sync" + "time" +) + +// [salt(16)][obfuscated payload] + +type XPlusObfuscator struct { + Key []byte + RandSrc *rand.Rand + + lk sync.Mutex +} + +func NewXPlusObfuscator(key []byte) *XPlusObfuscator { + return &XPlusObfuscator{ + Key: key, + RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +func (x *XPlusObfuscator) Deobfuscate(in []byte, out []byte) int { + pLen := len(in) - 16 + if pLen <= 0 || len(out) < pLen { + // Invalid + return 0 + } + key := sha256.Sum256(append(x.Key, in[:16]...)) + // Deobfuscate the payload + for i, c := range in[16:] { + out[i] = c ^ key[i%sha256.Size] + } + return pLen +} + +func (x *XPlusObfuscator) Obfuscate(p []byte) []byte { + pLen := len(p) + buf := make([]byte, 16+pLen) + x.lk.Lock() + _, _ = x.RandSrc.Read(buf[:16]) // salt + x.lk.Unlock() + // Obfuscate the payload + key := sha256.Sum256(append(x.Key, buf[:16]...)) + for i, c := range p { + buf[i+16] = c ^ key[i%sha256.Size] + } + return buf +} diff --git a/pkg/obfs/xplus_test.go b/pkg/obfs/xplus_test.go new file mode 100644 index 0000000..baff4c2 --- /dev/null +++ b/pkg/obfs/xplus_test.go @@ -0,0 +1,31 @@ +package obfs + +import ( + "bytes" + "testing" +) + +func TestXPlusObfuscator(t *testing.T) { + x := NewXPlusObfuscator([]byte("Vaundy")) + tests := []struct { + name string + p []byte + }{ + {name: "1", p: []byte("HelloWorld")}, + {name: "2", p: []byte("Regret is just a horrible attempt at time travel that ends with you feeling like crap")}, + {name: "3", p: []byte("To be, or not to be, that is the question:\nWhether 'tis nobler in the mind to suffer\n" + + "The slings and arrows of outrageous fortune,\nOr to take arms against a sea of troubles\n" + + "And by opposing end them. To die—to sleep,\nNo more; and by a sleep to say we end")}, + {name: "empty", p: []byte("")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bs := x.Obfuscate(tt.p) + outBs := make([]byte, len(bs)) + n := x.Deobfuscate(bs, outBs) + if !bytes.Equal(tt.p, outBs[:n]) { + t.Errorf("Inconsistent deobfuscate result: got %v, want %v", outBs[:n], tt.p) + } + }) + } +} diff --git a/pkg/utils/df_linux.go b/pkg/utils/df_linux.go new file mode 100644 index 0000000..e3f87fd --- /dev/null +++ b/pkg/utils/df_linux.go @@ -0,0 +1,21 @@ +package utils + +import ( + "net" + "syscall" +) + +func SetDontFragment(conn *net.UDPConn) error { + rawConn, err := conn.SyscallConn() + if err != nil { + return err + } + var err1, err2 error + err1 = rawConn.Control(func(fd uintptr) { + err2 = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_PROBE) + }) + if err1 != nil { + return err1 + } + return err2 +} diff --git a/pkg/utils/df_stub.go b/pkg/utils/df_stub.go new file mode 100644 index 0000000..b48158a --- /dev/null +++ b/pkg/utils/df_stub.go @@ -0,0 +1,10 @@ +// +build !linux,!windows + +package utils + +import "net" + +func SetDontFragment(conn *net.UDPConn) error { + // Not implemented + return nil +} diff --git a/pkg/utils/df_windows.go b/pkg/utils/df_windows.go new file mode 100644 index 0000000..e054d99 --- /dev/null +++ b/pkg/utils/df_windows.go @@ -0,0 +1,23 @@ +package utils + +import ( + "net" + "syscall" +) + +func SetDontFragment(conn *net.UDPConn) error { + rawConn, err := conn.SyscallConn() + if err != nil { + return err + } + var err1, err2 error + err1 = rawConn.Control(func(fd uintptr) { + // https://docs.microsoft.com/en-us/troubleshoot/windows/win32/header-library-requirement-socket-ipproto-ip + // #define IP_DONTFRAGMENT 14 /* don't fragment IP datagrams */ + err2 = syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, 14, 1) + }) + if err1 != nil { + return err1 + } + return err2 +}