Merge pull request #53 from tobyxdd/wip-xplus-obfs

Better obfs
This commit is contained in:
Toby 2021-04-19 20:54:05 -07:00 committed by GitHub
commit f396cae604
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 173 additions and 14 deletions

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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 {

52
pkg/obfs/xplus.go Normal file
View file

@ -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
}

31
pkg/obfs/xplus_test.go Normal file
View file

@ -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)
}
})
}
}

21
pkg/utils/df_linux.go Normal file
View file

@ -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
}

10
pkg/utils/df_stub.go Normal file
View file

@ -0,0 +1,10 @@
// +build !linux,!windows
package utils
import "net"
func SetDontFragment(conn *net.UDPConn) error {
// Not implemented
return nil
}

23
pkg/utils/df_windows.go Normal file
View file

@ -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
}