wip: obfs rework, needs further testing & profiling, wechat & faketcp not available yet

This commit is contained in:
tobyxdd 2022-12-16 23:21:15 -08:00
parent a5647379b1
commit dc78d4b528
12 changed files with 113 additions and 158 deletions

View file

@ -31,11 +31,11 @@ import (
)
var clientPacketConnFuncFactoryMap = map[string]pktconns.ClientPacketConnFuncFactory{
"": pktconns.NewClientUDPConnFunc,
"udp": pktconns.NewClientUDPConnFunc,
"wechat": pktconns.NewClientWeChatConnFunc,
"wechat-video": pktconns.NewClientWeChatConnFunc,
"faketcp": pktconns.NewClientFakeTCPConnFunc,
"": pktconns.NewClientUDPConnFunc,
"udp": pktconns.NewClientUDPConnFunc,
// "wechat": pktconns.NewClientWeChatConnFunc,
// "wechat-video": pktconns.NewClientWeChatConnFunc,
// "faketcp": pktconns.NewClientFakeTCPConnFunc,
}
func client(config *clientConfig) {

View file

@ -25,11 +25,11 @@ import (
)
var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFactory{
"": pktconns.NewServerUDPConnFunc,
"udp": pktconns.NewServerUDPConnFunc,
"wechat": pktconns.NewServerWeChatConnFunc,
"wechat-video": pktconns.NewServerWeChatConnFunc,
"faketcp": pktconns.NewServerFakeTCPConnFunc,
"": pktconns.NewServerUDPConnFunc,
"udp": pktconns.NewServerUDPConnFunc,
// "wechat": pktconns.NewServerWeChatConnFunc,
// "wechat-video": pktconns.NewServerWeChatConnFunc,
// "faketcp": pktconns.NewServerFakeTCPConnFunc,
}
func server(config *serverConfig) {

View file

@ -29,12 +29,10 @@ require (
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/coreos/go-iptables v0.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@ -92,7 +90,7 @@ require (
replace github.com/apernet/hysteria/core => ../core/
replace github.com/lucas-clemente/quic-go => github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c
replace github.com/lucas-clemente/quic-go => github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b
replace github.com/LiamHaworth/go-tproxy => github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88

View file

@ -51,8 +51,8 @@ github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88 h1:YNsl7PMiU9x/0
github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88/go.mod h1:uxH+nFzlJug5OHjPYmzKwvVVb9wOToeGuLNVeerwWtc=
github.com/apernet/goproxy v0.0.0-20221124043924-155acfaf278f h1:v3Bn97M5KWzdVajNphf3PxoHdsRF/RzBVovIsH/DEvY=
github.com/apernet/goproxy v0.0.0-20221124043924-155acfaf278f/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c h1:YoKqNVd+1knWdkYD/VjDNgEV+5FDAXTylFWAZV9PAa0=
github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b h1:Z49X7P2v8otyir1SAcAbbEzVZNn+AiYwInpf0dbhBJI=
github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b/go.mod h1:NVsR0x0u2DwoSL69OKbkbiF2vljWREImECy9jD04+R0=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -72,8 +72,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -155,8 +153,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=

View file

@ -43,4 +43,4 @@ require (
google.golang.org/protobuf v1.28.1 // indirect
)
replace github.com/lucas-clemente/quic-go => github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c
replace github.com/lucas-clemente/quic-go => github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b

View file

@ -38,8 +38,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c h1:YoKqNVd+1knWdkYD/VjDNgEV+5FDAXTylFWAZV9PAa0=
github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b h1:Z49X7P2v8otyir1SAcAbbEzVZNn+AiYwInpf0dbhBJI=
github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b/go.mod h1:NVsR0x0u2DwoSL69OKbkbiF2vljWREImECy9jD04+R0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View file

@ -5,10 +5,8 @@ import (
"strings"
"time"
"github.com/apernet/hysteria/core/pktconns/faketcp"
"github.com/apernet/hysteria/core/pktconns/obfs"
"github.com/apernet/hysteria/core/pktconns/udp"
"github.com/apernet/hysteria/core/pktconns/wechat"
)
type (
@ -54,6 +52,7 @@ func NewClientUDPConnFunc(obfsPassword string, hopInterval time.Duration) Client
}
}
/*
func NewClientWeChatConnFunc(obfsPassword string, hopInterval time.Duration) ClientPacketConnFunc {
if obfsPassword == "" {
return func(server string) (net.PacketConn, net.Addr, error) {
@ -108,6 +107,7 @@ func NewClientFakeTCPConnFunc(obfsPassword string, hopInterval time.Duration) Cl
}
}
}
*/
func NewServerUDPConnFunc(obfsPassword string) ServerPacketConnFunc {
if obfsPassword == "" {
@ -134,6 +134,7 @@ func NewServerUDPConnFunc(obfsPassword string) ServerPacketConnFunc {
}
}
/*
func NewServerWeChatConnFunc(obfsPassword string) ServerPacketConnFunc {
if obfsPassword == "" {
return func(listen string) (net.PacketConn, error) {
@ -179,6 +180,7 @@ func NewServerFakeTCPConnFunc(obfsPassword string) ServerPacketConnFunc {
}
}
}
*/
func isMultiPortAddr(addr string) bool {
_, portStr, err := net.SplitHostPort(addr)

View file

@ -5,54 +5,73 @@ import (
"math/rand"
"sync"
"time"
"github.com/lucas-clemente/quic-go"
)
type Obfuscator interface {
Deobfuscate(in []byte, out []byte) int
Obfuscate(in []byte, out []byte) int
}
const xpSaltLen = 16
const (
xpSaltLen = 16
udpBufferSize = 4096
)
// XPlusObfuscator obfuscates payload using one-time keys generated from hashing a pre-shared key and random salt.
// Packet format: [salt][obfuscated payload]
type XPlusObfuscator struct {
Key []byte
RandSrc *rand.Rand
lk sync.Mutex
key []byte
randSrc *rand.Rand
randLk sync.Mutex
bufPool sync.Pool
saltPool sync.Pool
}
func NewXPlusObfuscator(key []byte) *XPlusObfuscator {
func NewXPlusObfuscator(key []byte) quic.Obfuscator {
return &XPlusObfuscator{
Key: key,
RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
key: key,
randSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
bufPool: sync.Pool{New: func() interface{} { return make([]byte, udpBufferSize) }},
saltPool: sync.Pool{New: func() interface{} { return make([]byte, xpSaltLen) }},
}
}
func (x *XPlusObfuscator) Deobfuscate(in []byte, out []byte) int {
outLen := len(in) - xpSaltLen
if outLen <= 0 || len(out) < outLen {
return 0
func (x *XPlusObfuscator) Obfuscate(data []byte, scat bool) ([][]byte, func()) {
if scat {
salt := x.saltPool.Get().([]byte)
x.randLk.Lock()
_, _ = x.randSrc.Read(salt)
x.randLk.Unlock()
key := sha256.Sum256(append(x.key, salt...))
buf := x.bufPool.Get().([]byte)
for i, c := range data {
buf[i] = c ^ key[i%sha256.Size]
}
payload := buf[:len(data)]
return [][]byte{salt, payload}, func() {
x.saltPool.Put(salt)
x.bufPool.Put(buf)
}
} else {
buf := x.bufPool.Get().([]byte)
x.randLk.Lock()
_, _ = x.randSrc.Read(buf[:xpSaltLen])
x.randLk.Unlock()
key := sha256.Sum256(append(x.key, buf[:xpSaltLen]...))
for i, c := range data {
buf[i+xpSaltLen] = c ^ key[i%sha256.Size]
}
payload := buf[:xpSaltLen+len(data)]
return [][]byte{payload}, func() {
x.bufPool.Put(buf)
}
}
key := sha256.Sum256(append(x.Key, in[:xpSaltLen]...))
for i, c := range in[xpSaltLen:] {
out[i] = c ^ key[i%sha256.Size]
}
return outLen
}
func (x *XPlusObfuscator) Obfuscate(in []byte, out []byte) int {
outLen := len(in) + xpSaltLen
if len(out) < outLen {
func (x *XPlusObfuscator) Deobfuscate(data []byte) int {
if len(data) <= xpSaltLen {
return 0
}
x.lk.Lock()
_, _ = x.RandSrc.Read(out[:xpSaltLen])
x.lk.Unlock()
key := sha256.Sum256(append(x.Key, out[:xpSaltLen]...))
for i, c := range in {
out[i+xpSaltLen] = c ^ key[i%sha256.Size]
key := sha256.Sum256(append(x.key, data[:xpSaltLen]...))
for i, c := range data[xpSaltLen:] {
data[i] = c ^ key[i%sha256.Size]
}
return outLen
return len(data) - xpSaltLen
}

View file

@ -18,13 +18,35 @@ func TestXPlusObfuscator(t *testing.T) {
"And by opposing end them. To die—to sleep,\nNo more; and by a sleep to say we end")},
{name: "empty", p: []byte("")},
}
// Non-scat
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := make([]byte, 10240)
n := x.Obfuscate(tt.p, buf)
n2 := x.Deobfuscate(buf[:n], buf[n:])
if !bytes.Equal(tt.p, buf[n:n+n2]) {
t.Errorf("Inconsistent deobfuscate result: got %v, want %v", buf[n:n+n2], tt.p)
bb, _ := x.Obfuscate(tt.p, false)
if len(bb) != 1 {
t.Errorf("Incorrect number of buffers returned: %d", len(bb))
}
n := x.Deobfuscate(bb[0])
if !bytes.Equal(tt.p, bb[0][:n]) {
t.Errorf("Inconsistent deobfuscation: %s", string(bb[0][:n]))
}
})
}
// Scat
for _, tt := range tests {
t.Run("scat-"+tt.name, func(t *testing.T) {
bb, _ := x.Obfuscate(tt.p, true)
if len(bb) != 2 {
t.Errorf("Incorrect number of buffers returned: %d", len(bb))
}
if len(bb[0]) != xpSaltLen || len(bb[1]) != len(tt.p) {
t.Errorf("Incorrect buffer length: %d, %d", len(bb[0]), len(bb[1]))
}
var data []byte
data = append(data, bb[0]...)
data = append(data, bb[1]...)
n := x.Deobfuscate(data)
if !bytes.Equal(tt.p, data[:n]) {
t.Errorf("Inconsistent deobfuscation: %s", string(bb[0][:n]))
}
})
}

View file

@ -10,7 +10,7 @@ import (
"syscall"
"time"
"github.com/apernet/hysteria/core/pktconns/obfs"
"github.com/lucas-clemente/quic-go"
)
const (
@ -24,7 +24,7 @@ type ObfsUDPHopClientPacketConn struct {
serverAddrs []net.Addr
hopInterval time.Duration
obfs obfs.Obfuscator
quic.Obfuscator
connMutex sync.RWMutex
prevConn net.PacketConn
@ -57,7 +57,7 @@ type udpPacket struct {
addr net.Addr
}
func NewObfsUDPHopClientPacketConn(server string, hopInterval time.Duration, obfs obfs.Obfuscator) (*ObfsUDPHopClientPacketConn, net.Addr, error) {
func NewObfsUDPHopClientPacketConn(server string, hopInterval time.Duration, obfs quic.Obfuscator) (*ObfsUDPHopClientPacketConn, net.Addr, error) {
host, ports, err := parseAddr(server)
if err != nil {
return nil, nil, err
@ -79,7 +79,7 @@ func NewObfsUDPHopClientPacketConn(server string, hopInterval time.Duration, obf
serverAddr: &hopAddr,
serverAddrs: serverAddrs,
hopInterval: hopInterval,
obfs: obfs,
Obfuscator: obfs,
addrIndex: rand.Intn(len(serverAddrs)),
recvQueue: make(chan *udpPacket, packetQueueSize),
closeChan: make(chan struct{}),
@ -155,11 +155,7 @@ func (c *ObfsUDPHopClientPacketConn) hop() {
_ = c.prevConn.Close() // recvRoutine will exit on error
}
c.prevConn = c.currentConn
if c.obfs != nil {
c.currentConn = NewObfsUDPConn(newConn, c.obfs)
} else {
c.currentConn = newConn
}
c.currentConn = newConn
// Set buffer sizes if previously set
if c.readBufferSize > 0 {
_ = trySetPacketConnReadBuffer(c.currentConn, c.readBufferSize)

View file

@ -2,99 +2,20 @@ package udp
import (
"net"
"os"
"sync"
"syscall"
"time"
"github.com/apernet/hysteria/core/pktconns/obfs"
"github.com/lucas-clemente/quic-go"
)
const udpBufferSize = 4096
type ObfsUDPPacketConn struct {
orig *net.UDPConn
obfs obfs.Obfuscator
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
type ObfsUDPConn struct {
net.UDPConn
quic.Obfuscator
}
func NewObfsUDPConn(orig *net.UDPConn, obfs obfs.Obfuscator) *ObfsUDPPacketConn {
return &ObfsUDPPacketConn{
orig: orig,
obfs: obfs,
readBuf: make([]byte, udpBufferSize),
writeBuf: make([]byte, udpBufferSize),
func NewObfsUDPConn(udpConn *net.UDPConn, obfs quic.Obfuscator) *ObfsUDPConn {
return &ObfsUDPConn{
UDPConn: *udpConn,
Obfuscator: obfs,
}
}
func (c *ObfsUDPPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
for {
c.readMutex.Lock()
n, addr, err := c.orig.ReadFrom(c.readBuf)
if n <= 0 {
c.readMutex.Unlock()
return 0, addr, err
}
newN := c.obfs.Deobfuscate(c.readBuf[:n], p)
c.readMutex.Unlock()
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
}
}
}
func (c *ObfsUDPPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.writeMutex.Lock()
bn := c.obfs.Obfuscate(p, c.writeBuf)
_, err = c.orig.WriteTo(c.writeBuf[:bn], addr)
c.writeMutex.Unlock()
if err != nil {
return 0, err
} else {
return len(p), nil
}
}
func (c *ObfsUDPPacketConn) Close() error {
return c.orig.Close()
}
func (c *ObfsUDPPacketConn) LocalAddr() net.Addr {
return c.orig.LocalAddr()
}
func (c *ObfsUDPPacketConn) SetDeadline(t time.Time) error {
return c.orig.SetDeadline(t)
}
func (c *ObfsUDPPacketConn) SetReadDeadline(t time.Time) error {
return c.orig.SetReadDeadline(t)
}
func (c *ObfsUDPPacketConn) SetWriteDeadline(t time.Time) error {
return c.orig.SetWriteDeadline(t)
}
func (c *ObfsUDPPacketConn) SetReadBuffer(bytes int) error {
return c.orig.SetReadBuffer(bytes)
}
func (c *ObfsUDPPacketConn) SetWriteBuffer(bytes int) error {
return c.orig.SetWriteBuffer(bytes)
}
func (c *ObfsUDPPacketConn) SyscallConn() (syscall.RawConn, error) {
return c.orig.SyscallConn()
}
func (c *ObfsUDPPacketConn) File() (f *os.File, err error) {
return c.orig.File()
}

View file

@ -6,7 +6,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apernet/quic-go v0.31.1-0.20221208013043-a01b50646f2c/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b h1:Z49X7P2v8otyir1SAcAbbEzVZNn+AiYwInpf0dbhBJI=
github.com/apernet/quic-go v0.31.1-0.20221217071728-fd43ba23387b/go.mod h1:NVsR0x0u2DwoSL69OKbkbiF2vljWREImECy9jD04+R0=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=