Update On Tue Aug 12 20:40:50 CEST 2025

This commit is contained in:
github-action[bot]
2025-08-12 20:40:51 +02:00
parent 7a3097a4a2
commit 94f030fce0
58 changed files with 1108 additions and 854 deletions

View File

@@ -3,14 +3,11 @@ package outbound
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/metacubex/mihomo/common/convert"
N "github.com/metacubex/mihomo/common/net"
@@ -456,41 +453,11 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option,
}
if s := strings.SplitN(option.Encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
var i int
i, err = strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
var b []byte
b, err = base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
if len(b) == encryption.MLKEM768ClientLength {
v.encryption = &encryption.ClientInstance{}
if err = v.encryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
} else {
return nil, fmt.Errorf("invaild vless encryption value: %s", option.Encryption)
}
v.encryption, err = encryption.NewClient(option.Encryption)
if err != nil {
return nil, err
}
if v.encryption != nil {
if option.Flow != "" {
return nil, errors.New(`vless "encryption" doesn't support "flow" yet`)
}

View File

@@ -10,11 +10,13 @@ import (
"net"
"net/http"
"net/netip"
"strconv"
"sync"
"testing"
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
@@ -30,7 +32,7 @@ import (
)
var httpPath = "/inbound_test"
var httpData = make([]byte, 10240)
var httpData = make([]byte, 2*pool.RelayBufferSize)
var remoteAddr = netip.MustParseAddr("1.2.3.4")
var userUUID = utils.NewUUIDV4().String()
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
@@ -134,14 +136,21 @@ func NewHttpTestTunnel() *TestTunnel {
r := chi.NewRouter()
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
render.Data(w, r, httpData)
query := r.URL.Query()
size, err := strconv.Atoi(query.Get("size"))
if err != nil {
render.Status(r, http.StatusBadRequest)
render.PlainText(w, r, err.Error())
return
}
render.Data(w, r, httpData[:size])
})
h2Server := &http2.Server{}
server := http.Server{Handler: r}
_ = http2.ConfigureServer(&server, h2Server)
go server.Serve(ln)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), nil)
if !assert.NoError(t, err) {
return
}
@@ -200,7 +209,7 @@ func NewHttpTestTunnel() *TestTunnel {
if !assert.NoError(t, err) {
return
}
assert.Equal(t, httpData, data)
assert.Equal(t, httpData[:size], data)
}
tunnel := &TestTunnel{
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
@@ -241,27 +250,30 @@ func NewHttpTestTunnel() *TestTunnel {
},
CloseFn: ln.Close,
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
// Sequential testing for debugging
t.Run("Sequential", func(t *testing.T) {
testFn(t, proxy, "http")
testFn(t, proxy, "https")
testFn(t, proxy, "http", len(httpData))
testFn(t, proxy, "https", len(httpData))
})
// Concurrent testing to detect stress
t.Run("Concurrent", func(t *testing.T) {
wg := sync.WaitGroup{}
const num = 50
for i := 0; i < num; i++ {
num := len(httpData) / 1024
for i := 1; i <= num; i++ {
i := i
wg.Add(1)
go func() {
testFn(t, proxy, "https")
testFn(t, proxy, "https", i*1024)
defer wg.Done()
}()
}
for i := 0; i < num; i++ {
for i := 1; i <= num; i++ {
i := i
wg.Add(1)
go func() {
testFn(t, proxy, "http")
testFn(t, proxy, "http", i*1024)
defer wg.Done()
}()
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.

View File

@@ -2,15 +2,11 @@ package sing_vless
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"unsafe"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -88,42 +84,11 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl = &Listener{config: config, service: service}
if s := strings.SplitN(config.Decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
var i int
i, err = strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
var b []byte
b, err = base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
if len(b) == encryption.MLKEM768SeedLength {
sl.decryption = &encryption.ServerInstance{}
if err = sl.decryption.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
} else {
return nil, fmt.Errorf("invaild vless decryption value: %s", config.Decryption)
}
sl.decryption, err = encryption.NewServer(config.Decryption)
if err != nil {
return nil, err
}
if sl.decryption != nil {
defer func() { // decryption must be closed to avoid the goroutine leak
if err != nil {
_ = sl.decryption.Close()

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
@@ -13,7 +12,6 @@ import (
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
"golang.org/x/sys/cpu"
)
@@ -30,19 +28,20 @@ var (
var ClientCipher byte
func init() {
if !HasAESGCMHardwareSupport {
if HasAESGCMHardwareSupport {
ClientCipher = 1
}
}
type ClientInstance struct {
sync.RWMutex
eKeyNfs *mlkem.EncapsulationKey768
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
nfsEKey *mlkem.EncapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
}
type ClientConn struct {
@@ -58,19 +57,22 @@ type ClientConn struct {
peerCache []byte
}
func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
i.xor = xor
func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
if xor > 0 {
i.nfsEKeyBytes = nfsEKeyBytes
i.xor = xor
}
i.minutes = minutes
return
}
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.eKeyNfs == nil {
if i.nfsEKey == nil {
return nil, errors.New("uninitialized")
}
if i.xor == 1 {
conn = NewXorConn(conn, i.eKeyNfs.Bytes())
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
c := &ClientConn{Conn: conn}
@@ -86,18 +88,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
i.RUnlock()
}
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
seed := make([]byte, 64)
rand.Read(seed)
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
padding := randBetween(100, 1000)
pfsDKeySeed := make([]byte, 64)
rand.Read(pfsDKeySeed)
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
paddingLen := randBetween(100, 1000)
clientHello := make([]byte, 1088+1184+1+5+padding)
copy(clientHello, encapsulatedNfsKey)
copy(clientHello[1088:], eKeyPfs)
clientHello[2272] = ClientCipher
encodeHeader(clientHello[2273:], int(padding))
clientHello := make([]byte, 1+1184+1088+5+paddingLen)
clientHello[0] = ClientCipher
copy(clientHello[1:], pfsEKeyBytes)
copy(clientHello[1185:], encapsulatedNfsKey)
EncodeHeader(clientHello[2273:], int(paddingLen))
rand.Read(clientHello[2278:])
if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err
@@ -111,17 +114,15 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
encapsulatedPfsKey := peerServerHello[:1088]
c.ticket = peerServerHello[1088:]
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
if err != nil {
return nil, err
}
c.baseKey = append(nfsKey, pfsKey...)
c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
nonce := make([]byte, 12)
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
nonce := [12]byte{ClientCipher}
VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more messages
return nil, errors.New("invalid server")
}
@@ -141,32 +142,39 @@ func (c *ClientConn) Write(b []byte) (int, error) {
return 0, nil
}
var data []byte
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key)
c.aead = newAead(ClientCipher, key)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
encodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in server's Read()
}
n += len(b)
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.ticket)
copy(data[21:], c.random)
EncodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
}
return len(b), nil
}
func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
func (c *ClientConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
@@ -177,11 +185,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
peerPaddingLen, _ := DecodeHeader(peerHeader)
if peerPaddingLen == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
return 0, err
}
}
@@ -196,11 +204,9 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
return 0, err
}
if c.random == nil {
return 0, errors.New("can not Read() first")
return 0, errors.New("empty c.random")
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
c.peerAead = newAead(ClientCipher, peerKey)
c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandom, c.random)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
@@ -211,7 +217,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
peerLength, err := DecodeHeader(peerHeader) // 17~17000
if err != nil {
if c.instance != nil {
c.instance.Lock()
@@ -228,10 +234,17 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
dst = b[:len(dst)] // avoids another copy()
}
var peerAead cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAead = NewAead(ClientCipher, c.baseKey, peerData, peerHeader)
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if peerAead != nil {
c.peerAead = peerAead
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, err
}

View File

@@ -1,17 +1,22 @@
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"math/big"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
func encodeHeader(b []byte, l int) {
var MaxNonce = bytes.Repeat([]byte{255}, 12)
func EncodeHeader(b []byte, l int) {
b[0] = 23
b[1] = 3
b[2] = 3
@@ -19,10 +24,10 @@ func encodeHeader(b []byte, l int) {
b[4] = byte(l)
}
func decodeHeader(b []byte) (int, error) {
func DecodeHeader(b []byte) (int, error) {
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
l := int(b[3])<<8 | int(b[4])
if l < 17 || l > 17000 { // TODO
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
}
return l, nil
@@ -30,29 +35,24 @@ func decodeHeader(b []byte) (int, error) {
return 0, errors.New("invalid record's header")
}
func newAead(c byte, k []byte) cipher.AEAD {
switch c {
case 0:
if block, err := aes.NewCipher(k); err == nil {
aead, _ := cipher.NewGCM(block)
return aead
}
case 1:
aead, _ := chacha20poly1305.New(k)
return aead
func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
key := make([]byte, 32)
hkdf.New(sha256.New, secret, salt, info).Read(key)
if c&1 == 1 {
block, _ := aes.NewCipher(key)
aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(key)
}
return nil
return
}
func increaseNonce(nonce []byte) {
func IncreaseNonce(nonce []byte) {
for i := 0; i < 12; i++ {
nonce[11-i]++
if nonce[11-i] != 0 {
break
}
if i == 11 {
// TODO
}
}
}

View File

@@ -2,4 +2,7 @@
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
// https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e
// https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80
// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da
// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6
// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35
package encryption

View File

@@ -0,0 +1,99 @@
package encryption
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
)
// NewClient new client from encryption string
// maybe return a nil *ClientInstance without any error, that means don't need to encrypt
func NewClient(encryption string) (*ClientInstance, error) {
switch encryption {
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
return nil, nil
}
if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
i, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
b, err := base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
if len(b) == MLKEM768ClientLength {
client := &ClientInstance{}
if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
return client, nil
} else {
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
}
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
}
// NewServer new server from decryption string
// maybe return a nil *ServerInstance without any error, that means don't need to decrypt
func NewServer(decryption string) (*ServerInstance, error) {
switch decryption {
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
return nil, nil
}
if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
var minutes uint32
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
i, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
minutes = uint32(i)
}
var xor uint32
switch s[1] {
case "vless":
case "aes128xor":
xor = 1
default:
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
b, err := base64.RawURLEncoding.DecodeString(s[3])
if err != nil {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
if len(b) == MLKEM768SeedLength {
server := &ServerInstance{}
if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
}
return server, nil
} else {
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}
}
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"io"
"net"
@@ -12,7 +11,6 @@ import (
"time"
"github.com/metacubex/utls/mlkem"
"golang.org/x/crypto/hkdf"
)
type ServerSession struct {
@@ -24,11 +22,12 @@ type ServerSession struct {
type ServerInstance struct {
sync.RWMutex
dKeyNfs *mlkem.DecapsulationKey768
xor uint32
minutes time.Duration
sessions map[[21]byte]*ServerSession
stop bool
nfsDKey *mlkem.DecapsulationKey768
nfsEKeyBytes []byte
xor uint32
minutes time.Duration
sessions map[[21]byte]*ServerSession
closed bool
}
type ServerConn struct {
@@ -44,9 +43,12 @@ type ServerConn struct {
nonce []byte
}
func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) {
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
i.xor = xor
func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) {
i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed)
if xor > 0 {
i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes()
i.xor = xor
}
if minutes > 0 {
i.minutes = minutes
i.sessions = make(map[[21]byte]*ServerSession)
@@ -55,12 +57,13 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
time.Sleep(time.Minute)
now := time.Now()
i.Lock()
if i.stop {
if i.closed {
i.Unlock()
return
}
for index, session := range i.sessions {
for ticket, session := range i.sessions {
if now.After(session.expire) {
delete(i.sessions, index)
delete(i.sessions, ticket)
}
}
i.Unlock()
@@ -72,17 +75,17 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat
func (i *ServerInstance) Close() (err error) {
i.Lock()
defer i.Unlock()
i.stop = true
i.closed = true
i.Unlock()
return
}
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.dKeyNfs == nil {
if i.nfsDKey == nil {
return nil, errors.New("uninitialized")
}
if i.xor == 1 {
conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes())
if i.xor > 0 {
conn = NewXorConn(conn, i.nfsEKeyBytes)
}
c := &ServerConn{Conn: conn}
@@ -109,50 +112,49 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return nil, err
}
if l, _ := decodeHeader(peerHeader); l != 0 {
c.Conn.Write(make([]byte, randBetween(100, 1000))) // make client do new handshake
if l, _ := DecodeHeader(peerHeader); l != 0 {
noise := make([]byte, randBetween(100, 1000))
rand.Read(noise)
c.Conn.Write(noise) // make client do new handshake
return nil, errors.New("invalid ticket")
}
peerClientHello := make([]byte, 1088+1184+1)
peerClientHello := make([]byte, 1+1184+1088)
copy(peerClientHello, peerTicketHello)
copy(peerClientHello[53:], peerHeader)
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
return nil, err
}
encapsulatedNfsKey := peerClientHello[:1088]
eKeyPfsData := peerClientHello[1088:2272]
c.cipher = peerClientHello[2272]
if c.cipher != 0 && c.cipher != 1 {
return nil, errors.New("invalid cipher")
}
c.cipher = peerClientHello[0]
pfsEKeyBytes := peerClientHello[1:1185]
encapsulatedNfsKey := peerClientHello[1185:2273]
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
if err != nil {
return nil, err
}
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
if err != nil {
return nil, err
}
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
c.baseKey = append(nfsKey, pfsKey...)
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
nonce := make([]byte, 12)
c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
nonce := [12]byte{c.cipher}
c.ticket = NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes)
padding := randBetween(100, 1000)
paddingLen := randBetween(100, 1000)
serverHello := make([]byte, 1088+21+5+padding)
serverHello := make([]byte, 1088+21+5+paddingLen)
copy(serverHello, encapsulatedPfsKey)
copy(serverHello[1088:], c.ticket)
encodeHeader(serverHello[1109:], int(padding))
EncodeHeader(serverHello[1109:], int(paddingLen))
rand.Read(serverHello[1114:])
if _, err := c.Conn.Write(serverHello); err != nil {
return nil, err
}
// we can send more padding if needed
if i.minutes > 0 {
i.Lock()
@@ -178,11 +180,11 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
peerPaddingLen, _ := DecodeHeader(peerHeader)
if peerPaddingLen == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil {
return 0, err
}
}
@@ -199,9 +201,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return 0, err
}
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.ticket).Read(peerKey)
c.peerAead = newAead(c.cipher, peerKey)
c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
@@ -212,7 +212,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
peerLength, err := DecodeHeader(peerHeader) // 17~17000
if err != nil {
return 0, err
}
@@ -222,10 +222,17 @@ func (c *ServerConn) Read(b []byte) (int, error) {
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
dst = b[:len(dst)] // avoids another copy()
}
var peerAead cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAead = NewAead(c.cipher, c.baseKey, peerData, peerHeader)
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if peerAead != nil {
c.peerAead = peerAead
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, errors.New("error")
}
@@ -236,31 +243,39 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return len(dst), nil
}
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
func (c *ServerConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
var data []byte
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("can not Write() first")
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in client's Read()
}
n += len(b)
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("empty c.peerRandom")
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
c.aead = NewAead(c.cipher, c.baseKey, data[:32], c.peerRandom)
c.nonce = make([]byte, 12)
EncodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAead(c.cipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
c.aead = newAead(c.cipher, key)
c.nonce = make([]byte, 12)
encodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
return len(b), nil
}

View File

@@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) {
return 0, err
}
if iv != nil {
b = b[16:]
b = b[16:] // for len(b)
}
return len(b), nil
}