Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion dialer/hysteria2/hysteria2.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type Hysteria2 struct {
User string
Password string
Server string
Obfs string
ObfsPass string
Insecure bool
Sni string
PinSHA256 string
Expand Down Expand Up @@ -81,6 +83,12 @@ func (s *Hysteria2) Dialer(option *dialer.ExtraOption, nextDialer netproxy.Diale
}
}
}
if s.Obfs != "" {
feature1.ObfuscationConfig = client.ObfuscationConfig{
Obfuscation: s.Obfs,
ObfuscationKey: []byte(s.ObfsPass),
}
}
header.Feature1 = feature1

if s.PinSHA256 != "" {
Expand Down Expand Up @@ -120,7 +128,6 @@ func normalizeCertHash(hash string) string {

// ref: https://v2.hysteria.network/zh/docs/developers/URI-Scheme/
func ParseHysteria2URL(link string) (*Hysteria2, error) {
// TODO: support salamander obfuscation
u, err := url.Parse(link)
if err != nil {
return nil, err
Expand Down Expand Up @@ -148,6 +155,8 @@ func ParseHysteria2URL(link string) (*Hysteria2, error) {
Name: u.Fragment,
User: u.User.Username(),
Server: u.Host,
Obfs: q.Get("obfs"),
ObfsPass: q.Get("obfs-password"),
Insecure: insecure,
Sni: q.Get("sni"),
PinSHA256: q.Get("pinSHA256"),
Expand All @@ -169,6 +178,12 @@ func (s *Hysteria2) ExportToURL() string {
t.User = url.UserPassword(s.User, s.Password)
}
q := t.Query()
if s.Obfs != "" {
q.Set("obfs", s.Obfs)
}
if s.ObfsPass != "" {
q.Set("obfs-password", s.ObfsPass)
}
if s.Insecure {
q.Set("insecure", "1")
}
Expand Down
9 changes: 9 additions & 0 deletions protocol/hysteria2/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ func (c *clientImpl) connect(ctx context.Context) (*HandshakeInfo, error) {
if err != nil {
return nil, err
}

switch c.config.ObfuscationConfig.Obfuscation {
case "salamander":
pktConn = NewSalamanderPacketConn(pktConn, c.config.ObfuscationConfig.ObfuscationKey)
case "", "plain":
default:
return nil, errors.New("unknown obfuscation: " + c.config.ObfuscationConfig.Obfuscation)
}
Comment thread
AkinoKaede marked this conversation as resolved.

// Convert config to TLS config & QUIC config
tlsConfig := &tls.Config{
ServerName: c.config.TLSConfig.ServerName,
Expand Down
22 changes: 14 additions & 8 deletions protocol/hysteria2/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ const (
)

type Config struct {
ConnFactory ConnFactory
ServerAddr net.Addr
Auth string
TLSConfig TLSConfig
QUICConfig QUICConfig
BandwidthConfig BandwidthConfig
UDPHopInterval time.Duration
FastOpen bool
ConnFactory ConnFactory
ServerAddr net.Addr
Auth string
TLSConfig TLSConfig
QUICConfig QUICConfig
BandwidthConfig BandwidthConfig
UDPHopInterval time.Duration
ObfuscationConfig ObfuscationConfig
FastOpen bool

filled bool // whether the fields have been verified and filled
}
Expand Down Expand Up @@ -114,3 +115,8 @@ type BandwidthConfig struct {
MaxTx uint64
MaxRx uint64
}

type ObfuscationConfig struct {
Obfuscation string
ObfuscationKey []byte
}
86 changes: 86 additions & 0 deletions protocol/hysteria2/client/salamander.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package client

import (
"crypto/rand"
"crypto/subtle"
"net"
"time"

"golang.org/x/crypto/blake2b"
)

type SalamanderPacketConn struct {
Connection net.PacketConn
Key []byte
}

func NewSalamanderPacketConn(conn net.PacketConn, key []byte) SalamanderPacketConn {
return SalamanderPacketConn{
Connection: conn,
Key: key,
}
}

func (s SalamanderPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
packet := make([]byte, len(p)+8)

n, addr, err = s.Connection.ReadFrom(packet)

if err != nil {
return
}

if n <= 8 {
return 0, addr, nil
}

s.Process(p, packet[8:n], packet[:8])

n -= 8

return
}

func (s SalamanderPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
packet := make([]byte, len(p)+8)

rand.Read(packet[:8])

s.Process(packet[8:], p, packet[:8])

n, err = s.Connection.WriteTo(packet, addr)

if err == nil {
n -= 8
}

return
}

func (s SalamanderPacketConn) Process(dst, src, salt []byte) {
hash := blake2b.Sum256(append(s.Key, salt...))

for i := 0; i < len(src); i += 32 {
subtle.XORBytes(dst[i:], src[i:], hash[:])
}
}

func (s SalamanderPacketConn) Close() error {
return s.Connection.Close()
}

func (s SalamanderPacketConn) LocalAddr() net.Addr {
return s.Connection.LocalAddr()
}

func (s SalamanderPacketConn) SetDeadline(t time.Time) error {
return s.Connection.SetDeadline(t)
}

func (s SalamanderPacketConn) SetReadDeadline(t time.Time) error {
return s.Connection.SetReadDeadline(t)
}

func (s SalamanderPacketConn) SetWriteDeadline(t time.Time) error {
return s.Connection.SetWriteDeadline(t)
}
6 changes: 4 additions & 2 deletions protocol/hysteria2/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ type Dialer struct {
}

type Feature1 struct {
BandwidthConfig client.BandwidthConfig
UDPHopInterval time.Duration
BandwidthConfig client.BandwidthConfig
UDPHopInterval time.Duration
ObfuscationConfig client.ObfuscationConfig
}

func NewDialer(nextDialer netproxy.Dialer, header protocol.Header) (netproxy.Dialer, error) {
Expand Down Expand Up @@ -54,6 +55,7 @@ func NewDialer(nextDialer netproxy.Dialer, header protocol.Header) (netproxy.Dia
if feature := header.Feature1; feature != nil {
config.BandwidthConfig = feature.(*Feature1).BandwidthConfig
config.UDPHopInterval = feature.(*Feature1).UDPHopInterval
config.ObfuscationConfig = feature.(*Feature1).ObfuscationConfig
}

var err error
Expand Down