mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-21 16:54:29 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa58459817 | ||
|
|
8d0deebf86 | ||
|
|
0396b34eff | ||
|
|
83147cf3a7 | ||
|
|
8d0a7cf856 |
@@ -26,20 +26,20 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
|||||||
panic("Unsupported network type for process lookup.")
|
panic("Unsupported network type for process lookup.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var procFiles []string
|
var procFile string
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
if net.ParseIP(srcIP).To4() != nil {
|
if net.ParseIP(srcIP).To4() != nil {
|
||||||
procFiles = []string{"/proc/net/tcp"}
|
procFile = "/proc/net/tcp"
|
||||||
} else {
|
} else {
|
||||||
procFiles = []string{"/proc/net/tcp6"}
|
procFile = "/proc/net/tcp6"
|
||||||
}
|
}
|
||||||
case "udp":
|
case "udp":
|
||||||
if net.ParseIP(srcIP).To4() != nil {
|
if net.ParseIP(srcIP).To4() != nil {
|
||||||
procFiles = []string{"/proc/net/udp", "/proc/net/udp6"}
|
procFile = "/proc/net/udp"
|
||||||
} else {
|
} else {
|
||||||
procFiles = []string{"/proc/net/udp6", "/proc/net/udp"}
|
procFile = "/proc/net/udp6"
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic("Unsupported network type for process lookup.")
|
panic("Unsupported network type for process lookup.")
|
||||||
@@ -50,17 +50,12 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
|
|||||||
return 0, "", "", errors.New("failed to format address: ", err)
|
return 0, "", "", errors.New("failed to format address: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
portFallback := ""
|
inode, err := findInodeInFile(procFile, targetHexAddr)
|
||||||
if network == "udp" {
|
|
||||||
portFallback = fmt.Sprintf(":%04X", srcPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
inode, err := findInodeInFiles(procFiles, targetHexAddr, portFallback)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", "", errors.New("could not search in ", strings.Join(procFiles, " and ")).Base(err)
|
return 0, "", "", errors.New("could not search in ", procFile).Base(err)
|
||||||
}
|
}
|
||||||
if inode == "" {
|
if inode == "" {
|
||||||
return 0, "", "", errors.New("connection for ", srcIP, ":", srcPort, " not found in ", strings.Join(procFiles, " or "))
|
return 0, "", "", errors.New("connection for ", srcIP, ":", srcPort, " not found in ", procFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
pidStr, err := findPidByInode(inode)
|
pidStr, err := findPidByInode(inode)
|
||||||
@@ -107,43 +102,31 @@ func formatLittleEndianString(addr net.IP, port Port) (string, error) {
|
|||||||
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
|
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findInodeInFiles(filePaths []string, targetHexAddr, portFallback string) (string, error) {
|
func findInodeInFile(filePath, targetHexAddr string) (string, error) {
|
||||||
onlyMatchedPort := ""
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
for i, filePath := range filePaths {
|
scanner := bufio.NewScanner(file)
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
for scanner.Scan() {
|
||||||
if i > 0 && os.IsNotExist(err) {
|
line := scanner.Text()
|
||||||
continue
|
fields := strings.Fields(line)
|
||||||
}
|
|
||||||
return "", err
|
if len(fields) < 10 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
localAddress := fields[1]
|
||||||
for scanner.Scan() {
|
if localAddress == targetHexAddr {
|
||||||
fields := strings.Fields(scanner.Text())
|
inode := fields[9]
|
||||||
if len(fields) < 10 {
|
return inode, nil
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
localAddress := fields[1]
|
|
||||||
if localAddress == targetHexAddr {
|
|
||||||
return fields[9], nil
|
|
||||||
}
|
|
||||||
if portFallback != "" && strings.HasSuffix(localAddress, portFallback) {
|
|
||||||
wildcardIP := strings.TrimSuffix(localAddress, portFallback)
|
|
||||||
if (len(wildcardIP) == 8 || len(wildcardIP) == 32) && strings.Trim(wildcardIP, "0") == "" {
|
|
||||||
onlyMatchedPort = fields[9]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return onlyMatchedPort, nil
|
return "", scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func findPidByInode(inode string) (string, error) {
|
func findPidByInode(inode string) (string, error) {
|
||||||
|
|||||||
@@ -53,21 +53,23 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Inbound, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
udpCodec, err := NewUDPServerCodec(method, psk, 500*time.Second)
|
user := &protocol.MemoryUser{
|
||||||
|
Email: config.Email,
|
||||||
|
Level: uint32(config.Level),
|
||||||
|
}
|
||||||
|
|
||||||
|
udpCodec, err := NewUDPServerCodec(method, psk, user, 500*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
v := core.MustFromContext(ctx)
|
v := core.MustFromContext(ctx)
|
||||||
return &Inbound{
|
return &Inbound{
|
||||||
networks: networks,
|
networks: networks,
|
||||||
method: method,
|
method: method,
|
||||||
psk: psk,
|
psk: psk,
|
||||||
saltFilter: antireplay.NewMapFilter[[32]byte](60),
|
saltFilter: antireplay.NewMapFilter[[32]byte](60),
|
||||||
user: &protocol.MemoryUser{
|
user: user,
|
||||||
Email: config.Email,
|
|
||||||
Level: uint32(config.Level),
|
|
||||||
},
|
|
||||||
udpCodec: udpCodec,
|
udpCodec: udpCodec,
|
||||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -155,20 +157,12 @@ func (i *Inbound) processUDP(ctx context.Context, conn stat.Connection, dispatch
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range mb {
|
for _, b := range mb {
|
||||||
decoded, err := i.udpCodec.DecodePacket(b.Bytes())
|
sessionItem, decoded, err := i.udpCodec.DecodePacket(b.Bytes())
|
||||||
b.Release()
|
b.Release()
|
||||||
if err != nil || decoded.HeaderType != HeaderTypeClient {
|
if err != nil || decoded.HeaderType != HeaderTypeClient {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionItem := i.udpCodec.GetSession(decoded.SessionID)
|
|
||||||
if sessionItem.User == nil {
|
|
||||||
sessionItem.Lock()
|
|
||||||
if sessionItem.User == nil {
|
|
||||||
sessionItem.User = i.user
|
|
||||||
}
|
|
||||||
sessionItem.Unlock()
|
|
||||||
}
|
|
||||||
link := sessionItem.link.Load()
|
link := sessionItem.link.Load()
|
||||||
if link != nil {
|
if link != nil {
|
||||||
sessionItem.UpdateConn(conn)
|
sessionItem.UpdateConn(conn)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package shadowsocks_2022
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/binary"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -41,8 +39,7 @@ type MultiUserInbound struct {
|
|||||||
usersByEmail *utils.TypedSyncMap[string, *protocol.MemoryUser]
|
usersByEmail *utils.TypedSyncMap[string, *protocol.MemoryUser]
|
||||||
userCount atomic.Int64
|
userCount atomic.Int64
|
||||||
saltFilter *antireplay.ReplayFilter[[32]byte]
|
saltFilter *antireplay.ReplayFilter[[32]byte]
|
||||||
udpSessions *UDPSessionManager
|
udpCodec *UDPMultiServerCodec
|
||||||
udpMasterCipher cipher.Block
|
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,22 +65,21 @@ func NewMultiServer(ctx context.Context, config *MultiUserServerConfig) (*MultiU
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
masterBlock, err := method.NewBlock(masterPSK)
|
udpCodec, err := NewUDPMultiServerCodec(method, masterPSK, 500*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
v := core.MustFromContext(ctx)
|
v := core.MustFromContext(ctx)
|
||||||
i := &MultiUserInbound{
|
i := &MultiUserInbound{
|
||||||
networks: networks,
|
networks: networks,
|
||||||
method: method,
|
method: method,
|
||||||
masterPSK: masterPSK,
|
masterPSK: masterPSK,
|
||||||
usersByHash: utils.NewTypedSyncMap[[AESBlockSize]byte, *protocol.MemoryUser](),
|
usersByHash: utils.NewTypedSyncMap[[AESBlockSize]byte, *protocol.MemoryUser](),
|
||||||
usersByEmail: utils.NewTypedSyncMap[string, *protocol.MemoryUser](),
|
usersByEmail: utils.NewTypedSyncMap[string, *protocol.MemoryUser](),
|
||||||
saltFilter: antireplay.NewMapFilter[[32]byte](60),
|
saltFilter: antireplay.NewMapFilter[[32]byte](60),
|
||||||
udpSessions: NewUDPSessionManager(500 * time.Second),
|
udpCodec: udpCodec,
|
||||||
udpMasterCipher: masterBlock,
|
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, user := range config.Users {
|
for idx, user := range config.Users {
|
||||||
@@ -281,68 +277,26 @@ func (i *MultiUserInbound) processUDP(ctx context.Context, conn stat.Connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range mb {
|
for _, b := range mb {
|
||||||
// In multi-user UDP:
|
sessionItem, decoded, user, err := i.udpCodec.DecodePacket(b.Bytes(), func(hash [AESBlockSize]byte) (*protocol.MemoryUser, []byte, bool) {
|
||||||
// Packet header is 16 bytes: Encrypted(SessionID + PacketID)
|
u, ok := i.usersByHash.Load(hash)
|
||||||
// Followed by 16 bytes EIH
|
|
||||||
packetBytes := b.Bytes()
|
|
||||||
if len(packetBytes) < 32+1+8+2 {
|
|
||||||
b.Release()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawHeader [16]byte
|
|
||||||
i.udpMasterCipher.Decrypt(rawHeader[:], packetBytes[:16])
|
|
||||||
|
|
||||||
sessionID := binary.BigEndian.Uint64(rawHeader[:8])
|
|
||||||
packetID := binary.BigEndian.Uint64(rawHeader[8:16])
|
|
||||||
|
|
||||||
sessionItem := i.udpSessions.GetOrCreate(sessionID)
|
|
||||||
|
|
||||||
if !sessionItem.CheckPacketID(packetID) {
|
|
||||||
b.Release()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var userPSK []byte
|
|
||||||
var currentUser *protocol.MemoryUser
|
|
||||||
sessionItem.Lock()
|
|
||||||
currentUser = sessionItem.User
|
|
||||||
userPSK = sessionItem.UserPSK
|
|
||||||
sessionItem.Unlock()
|
|
||||||
|
|
||||||
if currentUser == nil {
|
|
||||||
// Decrypt EIH
|
|
||||||
decryptedHash := DecryptUDPEIH(i.udpMasterCipher, rawHeader[:], packetBytes[16:32])
|
|
||||||
|
|
||||||
user, ok := i.usersByHash.Load(decryptedHash)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Release()
|
return nil, nil, false
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
currentUser = user
|
return u, u.Account.(*MemoryAccount).Key, true
|
||||||
userPSK = user.Account.(*MemoryAccount).Key
|
})
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := sessionItem.DecryptAESPayload(i.method, userPSK, sessionID, packetID, rawHeader[:], packetBytes[32:])
|
|
||||||
b.Release()
|
b.Release()
|
||||||
if err != nil {
|
if err != nil || decoded.HeaderType != HeaderTypeClient {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionItem.Lock()
|
|
||||||
if sessionItem.User == nil {
|
|
||||||
sessionItem.User = currentUser
|
|
||||||
sessionItem.UserPSK = userPSK
|
|
||||||
}
|
|
||||||
sessionItem.Unlock()
|
|
||||||
|
|
||||||
link := sessionItem.link.Load()
|
link := sessionItem.link.Load()
|
||||||
if link != nil {
|
if link != nil {
|
||||||
sessionItem.UpdateConn(conn)
|
sessionItem.UpdateConn(conn)
|
||||||
} else {
|
} else {
|
||||||
|
userPSK := user.Account.(*MemoryAccount).Key
|
||||||
var err error
|
var err error
|
||||||
link, err = sessionItem.EnsureLink(ctx, conn, decoded.Destination, dispatcher, i.policyManager, func(replyDest net.Destination, payload []byte) ([]byte, error) {
|
link, err = sessionItem.EnsureLink(ctx, conn, decoded.Destination, dispatcher, i.policyManager, func(replyDest net.Destination, payload []byte) ([]byte, error) {
|
||||||
return i.encodeServerUDPPacket(sessionID, userPSK, replyDest, payload)
|
return i.udpCodec.EncodeServerPacket(decoded.SessionID, userPSK, replyDest, payload)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -356,7 +310,3 @@ func (i *MultiUserInbound) processUDP(ctx context.Context, conn stat.Connection,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *MultiUserInbound) encodeServerUDPPacket(clientSessionID uint64, userPSK []byte, dest net.Destination, payload []byte) ([]byte, error) {
|
|
||||||
return i.udpSessions.EncodeServerPacket(i.method, userPSK, clientSessionID, dest, payload)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -243,16 +243,14 @@ func (i *RelayInbound) processUDP(ctx context.Context, conn stat.Connection, dis
|
|||||||
dest.Network = net.Network_UDP
|
dest.Network = net.Network_UDP
|
||||||
|
|
||||||
sessionItem := i.udpSessions.GetOrCreate(sessionID)
|
sessionItem := i.udpSessions.GetOrCreate(sessionID)
|
||||||
|
sessionItem.Lock()
|
||||||
if sessionItem.User == nil {
|
if sessionItem.User == nil {
|
||||||
sessionItem.Lock()
|
sessionItem.User = &protocol.MemoryUser{
|
||||||
if sessionItem.User == nil {
|
Email: targetDest.email,
|
||||||
sessionItem.User = &protocol.MemoryUser{
|
Level: targetDest.level,
|
||||||
Email: targetDest.email,
|
|
||||||
Level: targetDest.level,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sessionItem.Unlock()
|
|
||||||
}
|
}
|
||||||
|
sessionItem.Unlock()
|
||||||
link := sessionItem.link.Load()
|
link := sessionItem.link.Load()
|
||||||
if link != nil {
|
if link != nil {
|
||||||
sessionItem.UpdateConn(conn)
|
sessionItem.UpdateConn(conn)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type Outbound struct {
|
|||||||
method *CipherMethod
|
method *CipherMethod
|
||||||
pskList [][]byte
|
pskList [][]byte
|
||||||
finalPSK []byte
|
finalPSK []byte
|
||||||
udpCodec *UDPPacketCodec
|
udpCodec *UDPClientCodec
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +50,9 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
finalPSK := pskList[len(pskList)-1]
|
finalPSK := pskList[len(pskList)-1]
|
||||||
udpCodec, err := NewUDPPacketCodec(method, pskList)
|
udpCodec, err := NewUDPClientCodec(method, pskList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to create udp packet codec").Base(err)
|
return nil, errors.New("failed to create udp client codec").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
v := core.MustFromContext(ctx)
|
v := core.MustFromContext(ctx)
|
||||||
|
|||||||
+327
-127
@@ -7,33 +7,78 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
mrand "math/rand/v2"
|
mrand "math/rand/v2"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UDPCodec struct {
|
type UDPClientCodec struct {
|
||||||
method *CipherMethod
|
method *CipherMethod
|
||||||
pskList [][]byte
|
pskList [][]byte
|
||||||
psk []byte
|
psk []byte
|
||||||
blockCipher cipher.Block
|
blockCipher cipher.Block
|
||||||
blockCiphers []cipher.Block
|
blockCiphers []cipher.Block
|
||||||
chachaCipher cipher.AEAD
|
chachaCipher cipher.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPClientCodec(method *CipherMethod, pskList [][]byte) (*UDPClientCodec, error) {
|
||||||
|
if len(pskList) == 0 {
|
||||||
|
return nil, errors.New("empty psk list")
|
||||||
|
}
|
||||||
|
if method.IsChaCha && len(pskList) > 1 {
|
||||||
|
return nil, errors.New("multi-key is not supported for chacha20-poly1305")
|
||||||
|
}
|
||||||
|
finalPSK := pskList[len(pskList)-1]
|
||||||
|
c := &UDPClientCodec{
|
||||||
|
method: method,
|
||||||
|
pskList: pskList,
|
||||||
|
psk: finalPSK,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if method.IsChaCha {
|
||||||
|
c.chachaCipher, err = method.NewUDPCipher(finalPSK)
|
||||||
|
} else {
|
||||||
|
c.blockCipher, err = method.NewBlock(finalPSK)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pskList) > 1 {
|
||||||
|
c.blockCiphers = make([]cipher.Block, len(pskList))
|
||||||
|
for i, psk := range pskList {
|
||||||
|
c.blockCiphers[i], err = method.NewBlock(psk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPPacketCodec(method *CipherMethod, pskList [][]byte) (*UDPClientCodec, error) {
|
||||||
|
return NewUDPClientCodec(method, pskList)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPServerCodec struct {
|
||||||
|
method *CipherMethod
|
||||||
|
psk []byte
|
||||||
|
user *protocol.MemoryUser
|
||||||
|
blockCipher cipher.Block
|
||||||
|
chachaCipher cipher.AEAD
|
||||||
sessions *UDPSessionManager
|
sessions *UDPSessionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
func NewUDPServerCodec(method *CipherMethod, psk []byte, user *protocol.MemoryUser, sessionTimeout time.Duration) (*UDPServerCodec, error) {
|
||||||
UDPPacketCodec = UDPCodec
|
c := &UDPServerCodec{
|
||||||
UDPServerCodec = UDPCodec
|
method: method,
|
||||||
)
|
psk: psk,
|
||||||
|
user: user,
|
||||||
func newUDPCodec(method *CipherMethod, psk []byte) (*UDPCodec, error) {
|
sessions: NewUDPSessionManager(sessionTimeout),
|
||||||
c := &UDPCodec{
|
|
||||||
method: method,
|
|
||||||
psk: psk,
|
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if method.IsChaCha {
|
if method.IsChaCha {
|
||||||
@@ -47,46 +92,129 @@ func newUDPCodec(method *CipherMethod, psk []byte) (*UDPCodec, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPPacketCodec(method *CipherMethod, pskList [][]byte) (*UDPCodec, error) {
|
func (c *UDPServerCodec) Sessions() *UDPSessionManager {
|
||||||
if method.IsChaCha && len(pskList) > 1 {
|
|
||||||
return nil, errors.New("multi-key is not supported for chacha20-poly1305")
|
|
||||||
}
|
|
||||||
finalPSK := pskList[len(pskList)-1]
|
|
||||||
c, err := newUDPCodec(method, finalPSK)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.pskList = pskList
|
|
||||||
if len(pskList) > 1 {
|
|
||||||
c.blockCiphers = make([]cipher.Block, len(pskList))
|
|
||||||
for i, psk := range pskList {
|
|
||||||
c.blockCiphers[i], err = method.NewBlock(psk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUDPServerCodec(method *CipherMethod, psk []byte, sessionTimeout time.Duration) (*UDPCodec, error) {
|
|
||||||
c, err := newUDPCodec(method, psk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.sessions = NewUDPSessionManager(sessionTimeout)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPCodec) Sessions() *UDPSessionManager {
|
|
||||||
return c.sessions
|
return c.sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPCodec) GetSession(sessionID uint64) *ServerUDPSession {
|
func (c *UDPServerCodec) GetSession(sessionID uint64) *ServerUDPSession {
|
||||||
if c.sessions == nil {
|
if c.sessions == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.sessions.GetOrCreate(sessionID)
|
return c.sessions.Get(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPMultiServerCodec struct {
|
||||||
|
method *CipherMethod
|
||||||
|
masterPSK []byte
|
||||||
|
masterBlock cipher.Block
|
||||||
|
sessions *UDPSessionManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPMultiServerCodec(method *CipherMethod, masterPSK []byte, sessionTimeout time.Duration) (*UDPMultiServerCodec, error) {
|
||||||
|
if method.IsChaCha {
|
||||||
|
return nil, errors.New("multi-user is not supported for chacha20-poly1305")
|
||||||
|
}
|
||||||
|
masterBlock, err := method.NewBlock(masterPSK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &UDPMultiServerCodec{
|
||||||
|
method: method,
|
||||||
|
masterPSK: masterPSK,
|
||||||
|
masterBlock: masterBlock,
|
||||||
|
sessions: NewUDPSessionManager(sessionTimeout),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UDPMultiServerCodec) Sessions() *UDPSessionManager {
|
||||||
|
return c.sessions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UDPMultiServerCodec) GetSession(sessionID uint64) *ServerUDPSession {
|
||||||
|
if c.sessions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.sessions.Get(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserLookupFunc func(hash [AESBlockSize]byte) (*protocol.MemoryUser, []byte, bool)
|
||||||
|
|
||||||
|
func (c *UDPMultiServerCodec) DecodePacket(data []byte, userLookup UserLookupFunc) (*ServerUDPSession, DecodedUDPPacket, *protocol.MemoryUser, error) {
|
||||||
|
if len(data) < 2*AESBlockSize+AEADTagSize+RequestHeaderFixedChunkLength {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, ErrPacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawHeader [16]byte
|
||||||
|
c.masterBlock.Decrypt(rawHeader[:], data[:16])
|
||||||
|
|
||||||
|
sessionID := binary.BigEndian.Uint64(rawHeader[:8])
|
||||||
|
packetID := binary.BigEndian.Uint64(rawHeader[8:16])
|
||||||
|
|
||||||
|
sessionItem := c.sessions.Get(sessionID)
|
||||||
|
var currentUser *protocol.MemoryUser
|
||||||
|
var userPSK []byte
|
||||||
|
var bodyAead cipher.AEAD
|
||||||
|
|
||||||
|
if sessionItem != nil {
|
||||||
|
sessionItem.Lock()
|
||||||
|
currentUser = sessionItem.User
|
||||||
|
userPSK = sessionItem.UserPSK
|
||||||
|
bodyAead = sessionItem.clientBodyCipher
|
||||||
|
sessionItem.Unlock()
|
||||||
|
|
||||||
|
if currentUser != nil {
|
||||||
|
if !sessionItem.CheckPacketID(packetID) {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentUser == nil {
|
||||||
|
decryptedHash := DecryptUDPEIH(c.masterBlock, rawHeader[:], data[16:32])
|
||||||
|
user, psk, ok := userLookup(decryptedHash)
|
||||||
|
if !ok {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, errors.New("user not found")
|
||||||
|
}
|
||||||
|
currentUser = user
|
||||||
|
userPSK = psk
|
||||||
|
}
|
||||||
|
|
||||||
|
if bodyAead == nil {
|
||||||
|
bodyKey := DeriveSessionSubKey(userPSK, rawHeader[:8], c.method.KeySaltLength)
|
||||||
|
var err error
|
||||||
|
bodyAead, err = c.method.NewAEAD(bodyKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyNonce := rawHeader[4:16]
|
||||||
|
bodyCipher := data[32:]
|
||||||
|
bodyPlain, err := bodyAead.Open(nil, bodyNonce, bodyCipher, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, errors.New("failed to decrypt aes udp body").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := parsePlainUDPPacket(sessionID, packetID, bodyPlain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, err
|
||||||
|
}
|
||||||
|
if decoded.HeaderType != HeaderTypeClient {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, ErrBadHeaderType
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionItem == nil {
|
||||||
|
sessionItem = c.sessions.GetOrCreate(sessionID)
|
||||||
|
}
|
||||||
|
if !sessionItem.Commit(packetID, bodyAead, currentUser, userPSK) {
|
||||||
|
return nil, DecodedUDPPacket{}, nil, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionItem, decoded, currentUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UDPMultiServerCodec) EncodeServerPacket(clientSessionID uint64, userPSK []byte, dest net.Destination, payload []byte) ([]byte, error) {
|
||||||
|
return c.sessions.EncodeServerPacket(c.method, userPSK, clientSessionID, dest, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecodedUDPPacket struct {
|
type DecodedUDPPacket struct {
|
||||||
@@ -193,44 +321,42 @@ func parsePlainUDPPacket(sessionID, packetID uint64, bodyPlain []byte) (DecodedU
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPCodec) DecodePacket(data []byte) (DecodedUDPPacket, error) {
|
func (c *UDPServerCodec) DecodePacket(data []byte) (*ServerUDPSession, DecodedUDPPacket, error) {
|
||||||
if len(data) < PacketMinimalHeaderSize {
|
if len(data) < PacketMinimalHeaderSize {
|
||||||
return DecodedUDPPacket{}, ErrPacketTooShort
|
return nil, DecodedUDPPacket{}, ErrPacketTooShort
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.method.IsChaCha {
|
if c.method.IsChaCha {
|
||||||
if len(data) < PacketNonceSize+AEADTagSize {
|
if len(data) < PacketNonceSize+AEADTagSize {
|
||||||
return DecodedUDPPacket{}, ErrPacketTooShort
|
return nil, DecodedUDPPacket{}, ErrPacketTooShort
|
||||||
}
|
}
|
||||||
nonce := data[:PacketNonceSize]
|
nonce := data[:PacketNonceSize]
|
||||||
ciphertext := data[PacketNonceSize:]
|
ciphertext := data[PacketNonceSize:]
|
||||||
plain, err := c.chachaCipher.Open(nil, nonce, ciphertext, nil)
|
plain, err := c.chachaCipher.Open(nil, nonce, ciphertext, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, errors.New("failed to decrypt chacha udp packet").Base(err)
|
return nil, DecodedUDPPacket{}, errors.New("failed to decrypt chacha udp packet").Base(err)
|
||||||
}
|
}
|
||||||
if len(plain) < 16+1+8+2 {
|
if len(plain) < 16+1+8+2 {
|
||||||
return DecodedUDPPacket{}, ErrPacketTooShort
|
return nil, DecodedUDPPacket{}, ErrPacketTooShort
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionID := binary.BigEndian.Uint64(plain[:8])
|
sessionID := binary.BigEndian.Uint64(plain[:8])
|
||||||
packetID := binary.BigEndian.Uint64(plain[8:16])
|
packetID := binary.BigEndian.Uint64(plain[8:16])
|
||||||
|
|
||||||
sessionItem := c.sessions.GetOrCreate(sessionID)
|
|
||||||
if !sessionItem.CheckPacketID(packetID) {
|
|
||||||
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := parsePlainUDPPacket(sessionID, packetID, plain[16:])
|
decoded, err := parsePlainUDPPacket(sessionID, packetID, plain[16:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, err
|
return nil, DecodedUDPPacket{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if decoded.HeaderType != HeaderTypeClient {
|
if decoded.HeaderType != HeaderTypeClient {
|
||||||
return DecodedUDPPacket{}, ErrBadHeaderType
|
return nil, DecodedUDPPacket{}, ErrBadHeaderType
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionItem.AddPacketID(packetID)
|
sessionItem := c.sessions.GetOrCreate(sessionID)
|
||||||
return decoded, nil
|
if !sessionItem.Commit(packetID, nil, c.user, c.psk) {
|
||||||
|
return nil, DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
return sessionItem, decoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AES mode
|
// AES mode
|
||||||
@@ -239,49 +365,48 @@ func (c *UDPCodec) DecodePacket(data []byte) (DecodedUDPPacket, error) {
|
|||||||
sessionID := binary.BigEndian.Uint64(rawHeader[:8])
|
sessionID := binary.BigEndian.Uint64(rawHeader[:8])
|
||||||
packetID := binary.BigEndian.Uint64(rawHeader[8:16])
|
packetID := binary.BigEndian.Uint64(rawHeader[8:16])
|
||||||
|
|
||||||
sessionItem := c.sessions.GetOrCreate(sessionID)
|
sessionItem := c.sessions.Get(sessionID)
|
||||||
if !sessionItem.CheckPacketID(packetID) {
|
var bodyAead cipher.AEAD
|
||||||
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
if sessionItem != nil {
|
||||||
|
if !sessionItem.CheckPacketID(packetID) {
|
||||||
|
return nil, DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
sessionItem.Lock()
|
||||||
|
bodyAead = sessionItem.clientBodyCipher
|
||||||
|
sessionItem.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionItem.DecryptAESPayload(c.method, c.psk, sessionID, packetID, rawHeader[:], data[16:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerUDPSession) DecryptAESPayload(method *CipherMethod, psk []byte, sessionID, packetID uint64, rawHeader, bodyCipher []byte) (DecodedUDPPacket, error) {
|
|
||||||
bodyAead := s.clientBodyCipher
|
|
||||||
isNewCipher := false
|
|
||||||
if bodyAead == nil {
|
if bodyAead == nil {
|
||||||
bodyKey := DeriveSessionSubKey(psk, rawHeader[:8], method.KeySaltLength)
|
bodyKey := DeriveSessionSubKey(c.psk, rawHeader[:8], c.method.KeySaltLength)
|
||||||
var err error
|
var err error
|
||||||
bodyAead, err = method.NewAEAD(bodyKey)
|
bodyAead, err = c.method.NewAEAD(bodyKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, err
|
return nil, DecodedUDPPacket{}, err
|
||||||
}
|
}
|
||||||
isNewCipher = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyNonce := rawHeader[4:16]
|
bodyNonce := rawHeader[4:16]
|
||||||
|
bodyCipher := data[16:]
|
||||||
bodyPlain, err := bodyAead.Open(nil, bodyNonce, bodyCipher, nil)
|
bodyPlain, err := bodyAead.Open(nil, bodyNonce, bodyCipher, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, errors.New("failed to decrypt aes udp body").Base(err)
|
return nil, DecodedUDPPacket{}, errors.New("failed to decrypt aes udp body").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
decoded, err := parsePlainUDPPacket(sessionID, packetID, bodyPlain)
|
decoded, err := parsePlainUDPPacket(sessionID, packetID, bodyPlain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, err
|
return nil, DecodedUDPPacket{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if decoded.HeaderType != HeaderTypeClient {
|
if decoded.HeaderType != HeaderTypeClient {
|
||||||
return DecodedUDPPacket{}, ErrBadHeaderType
|
return nil, DecodedUDPPacket{}, ErrBadHeaderType
|
||||||
}
|
}
|
||||||
|
|
||||||
s.AddPacketID(packetID)
|
if sessionItem == nil {
|
||||||
|
sessionItem = c.sessions.GetOrCreate(sessionID)
|
||||||
if isNewCipher {
|
|
||||||
s.clientBodyCipher = bodyAead
|
|
||||||
}
|
}
|
||||||
|
if !sessionItem.Commit(packetID, bodyAead, c.user, c.psk) {
|
||||||
return decoded, nil
|
return nil, DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
return sessionItem, decoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerUDPSession) EnsureServerState(method *CipherMethod, psk []byte) error {
|
func (s *ServerUDPSession) EnsureServerState(method *CipherMethod, psk []byte) error {
|
||||||
@@ -303,6 +428,9 @@ func (s *ServerUDPSession) EnsureServerState(method *CipherMethod, psk []byte) e
|
|||||||
if method.IsChaCha {
|
if method.IsChaCha {
|
||||||
var err error
|
var err error
|
||||||
s.serverChaCha, err = method.NewUDPCipher(psk)
|
s.serverChaCha, err = method.NewUDPCipher(psk)
|
||||||
|
if err != nil {
|
||||||
|
s.ServerSessionID = 0
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,11 +515,12 @@ func (s *ServerUDPSession) EncodeServerPacket(method *CipherMethod, clientSessio
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPCodec) EncodeServerPacket(clientSessionID uint64, dest net.Destination, payload []byte) ([]byte, error) {
|
func (c *UDPServerCodec) EncodeServerPacket(clientSessionID uint64, dest net.Destination, payload []byte) ([]byte, error) {
|
||||||
return c.sessions.EncodeServerPacket(c.method, c.psk, clientSessionID, dest, payload)
|
return c.sessions.EncodeServerPacket(c.method, c.psk, clientSessionID, dest, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverSessionState struct {
|
type serverSessionState struct {
|
||||||
|
sync.Mutex
|
||||||
sessionID uint64
|
sessionID uint64
|
||||||
window *SlidingWindow
|
window *SlidingWindow
|
||||||
cipher cipher.AEAD
|
cipher cipher.AEAD
|
||||||
@@ -399,21 +528,20 @@ type serverSessionState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *serverSessionState) check(packetID uint64) bool {
|
func (st *serverSessionState) check(packetID uint64) bool {
|
||||||
if st.window == nil {
|
st.Lock()
|
||||||
st.window = new(SlidingWindow)
|
defer st.Unlock()
|
||||||
}
|
|
||||||
return st.window.Check(packetID)
|
return st.window.Check(packetID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *serverSessionState) add(packetID uint64) {
|
func (st *serverSessionState) commit(packetID uint64) bool {
|
||||||
if st.window == nil {
|
st.Lock()
|
||||||
st.window = new(SlidingWindow)
|
defer st.Unlock()
|
||||||
}
|
return st.window.CheckAndAdd(packetID)
|
||||||
st.window.Add(packetID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientUDPSession struct {
|
type ClientUDPSession struct {
|
||||||
codec *UDPCodec
|
mu sync.Mutex
|
||||||
|
codec *UDPClientCodec
|
||||||
clientSessionID uint64
|
clientSessionID uint64
|
||||||
nextPacketID atomic.Uint64
|
nextPacketID atomic.Uint64
|
||||||
clientBodyCipher cipher.AEAD
|
clientBodyCipher cipher.AEAD
|
||||||
@@ -421,7 +549,7 @@ type ClientUDPSession struct {
|
|||||||
old atomic.Pointer[serverSessionState]
|
old atomic.Pointer[serverSessionState]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UDPCodec) NewClientSession() (*ClientUDPSession, error) {
|
func (c *UDPClientCodec) NewClientSession() (*ClientUDPSession, error) {
|
||||||
var sessID [8]byte
|
var sessID [8]byte
|
||||||
if _, err := io.ReadFull(rand.Reader, sessID[:]); err != nil {
|
if _, err := io.ReadFull(rand.Reader, sessID[:]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -446,7 +574,7 @@ func (c *UDPCodec) NewClientSession() (*ClientUDPSession, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientUDPSession) getServerSession(sessionID uint64, now int64) (*serverSessionState, error) {
|
func (s *ClientUDPSession) getOrTransitionServerSession(sessionID uint64, cipher cipher.AEAD, now int64) (*serverSessionState, error) {
|
||||||
cur := s.current.Load()
|
cur := s.current.Load()
|
||||||
if cur != nil && cur.sessionID == sessionID {
|
if cur != nil && cur.sessionID == sessionID {
|
||||||
return cur, nil
|
return cur, nil
|
||||||
@@ -455,39 +583,42 @@ func (s *ClientUDPSession) getServerSession(sessionID uint64, now int64) (*serve
|
|||||||
old := s.old.Load()
|
old := s.old.Load()
|
||||||
if old != nil && old.sessionID == sessionID {
|
if old != nil && old.sessionID == sessionID {
|
||||||
if now-old.lastSeen.Load() > 60 {
|
if now-old.lastSeen.Load() > 60 {
|
||||||
s.old.CompareAndSwap(old, nil)
|
|
||||||
return nil, errors.New("old server session expired")
|
return nil, errors.New("old server session expired")
|
||||||
}
|
}
|
||||||
return old, nil
|
return old, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New server session:
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
cur = s.current.Load()
|
||||||
|
if cur != nil && cur.sessionID == sessionID {
|
||||||
|
return cur, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
old = s.old.Load()
|
||||||
|
if old != nil && old.sessionID == sessionID {
|
||||||
|
if now-old.lastSeen.Load() > 60 {
|
||||||
|
return nil, errors.New("old server session expired")
|
||||||
|
}
|
||||||
|
return old, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Spec §3.2.4: reject newer server sessions when the last packet received from the old session is less than 1 minute old.
|
// Spec §3.2.4: reject newer server sessions when the last packet received from the old session is less than 1 minute old.
|
||||||
if old != nil && now-old.lastSeen.Load() < 60 {
|
if old != nil && now-old.lastSeen.Load() < 60 {
|
||||||
return nil, errors.New("newer server session rejected: old session is less than 1 minute old")
|
return nil, errors.New("newer server session rejected: old session is less than 1 minute old")
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodyAead cipher.AEAD
|
|
||||||
if !s.codec.method.IsChaCha {
|
|
||||||
var sessBytes [8]byte
|
|
||||||
binary.BigEndian.PutUint64(sessBytes[:], sessionID)
|
|
||||||
bodyKey := DeriveSessionSubKey(s.codec.psk, sessBytes[:], s.codec.method.KeySaltLength)
|
|
||||||
var err error
|
|
||||||
bodyAead, err = s.codec.method.NewAEAD(bodyKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := &serverSessionState{
|
newState := &serverSessionState{
|
||||||
sessionID: sessionID,
|
sessionID: sessionID,
|
||||||
cipher: bodyAead,
|
window: new(SlidingWindow),
|
||||||
|
cipher: cipher,
|
||||||
}
|
}
|
||||||
newState.lastSeen.Store(now)
|
newState.lastSeen.Store(now)
|
||||||
|
|
||||||
if cur == nil {
|
if cur == nil {
|
||||||
s.current.CompareAndSwap(nil, newState)
|
s.current.Store(newState)
|
||||||
return s.current.Load(), nil
|
return newState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.old.Store(cur)
|
s.old.Store(cur)
|
||||||
@@ -637,15 +768,6 @@ func (s *ClientUDPSession) DecodePacket(data []byte) (DecodedUDPPacket, error) {
|
|||||||
sessionID := binary.BigEndian.Uint64(plain[:8])
|
sessionID := binary.BigEndian.Uint64(plain[:8])
|
||||||
packetID := binary.BigEndian.Uint64(plain[8:16])
|
packetID := binary.BigEndian.Uint64(plain[8:16])
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
st, err := s.getServerSession(sessionID, now)
|
|
||||||
if err != nil {
|
|
||||||
return DecodedUDPPacket{}, err
|
|
||||||
}
|
|
||||||
if !st.check(packetID) {
|
|
||||||
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := parsePlainUDPPacket(sessionID, packetID, plain[16:])
|
decoded, err := parsePlainUDPPacket(sessionID, packetID, plain[16:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, err
|
return DecodedUDPPacket{}, err
|
||||||
@@ -658,7 +780,16 @@ func (s *ClientUDPSession) DecodePacket(data []byte) (DecodedUDPPacket, error) {
|
|||||||
return DecodedUDPPacket{}, errors.New("client session ID mismatch")
|
return DecodedUDPPacket{}, errors.New("client session ID mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
st.add(packetID)
|
now := time.Now().Unix()
|
||||||
|
st, err := s.getOrTransitionServerSession(sessionID, nil, now)
|
||||||
|
if err != nil {
|
||||||
|
return DecodedUDPPacket{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically commit after all validations succeed
|
||||||
|
if !st.commit(packetID) {
|
||||||
|
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
st.lastSeen.Store(now)
|
st.lastSeen.Store(now)
|
||||||
|
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
@@ -671,18 +802,79 @@ func (s *ClientUDPSession) DecodePacket(data []byte) (DecodedUDPPacket, error) {
|
|||||||
packetID := binary.BigEndian.Uint64(rawHeader[8:16])
|
packetID := binary.BigEndian.Uint64(rawHeader[8:16])
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
st, err := s.getServerSession(sessionID, now)
|
|
||||||
|
// 1. Fast path for known active session
|
||||||
|
cur := s.current.Load()
|
||||||
|
var st *serverSessionState
|
||||||
|
var bodyAead cipher.AEAD
|
||||||
|
|
||||||
|
if cur != nil && cur.sessionID == sessionID {
|
||||||
|
st = cur
|
||||||
|
bodyAead = cur.cipher
|
||||||
|
} else {
|
||||||
|
old := s.old.Load()
|
||||||
|
if old != nil && old.sessionID == sessionID {
|
||||||
|
if now-old.lastSeen.Load() > 60 {
|
||||||
|
return DecodedUDPPacket{}, errors.New("old server session expired")
|
||||||
|
}
|
||||||
|
st = old
|
||||||
|
bodyAead = old.cipher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if st != nil {
|
||||||
|
if !st.check(packetID) {
|
||||||
|
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
bodyNonce := rawHeader[4:16]
|
||||||
|
bodyCipher := data[16:]
|
||||||
|
bodyPlain, err := bodyAead.Open(nil, bodyNonce, bodyCipher, nil)
|
||||||
|
if err != nil {
|
||||||
|
return DecodedUDPPacket{}, errors.New("failed to decrypt aes udp body").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := parsePlainUDPPacket(sessionID, packetID, bodyPlain)
|
||||||
|
if err != nil {
|
||||||
|
return DecodedUDPPacket{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if decoded.HeaderType != HeaderTypeServer {
|
||||||
|
return DecodedUDPPacket{}, ErrBadHeaderType
|
||||||
|
}
|
||||||
|
if decoded.ClientSessionID != s.clientSessionID {
|
||||||
|
return DecodedUDPPacket{}, errors.New("client session ID mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !st.commit(packetID) {
|
||||||
|
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
|
st.lastSeen.Store(now)
|
||||||
|
|
||||||
|
return decoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Unknown session: Trial path without mutating s.current or s.old yet!
|
||||||
|
// Re-check cur in case another worker concurrently transitioned to this sessionID
|
||||||
|
cur = s.current.Load()
|
||||||
|
old := s.old.Load()
|
||||||
|
if (cur == nil || cur.sessionID != sessionID) && (old == nil || old.sessionID != sessionID) {
|
||||||
|
// Spec §3.2.4: reject newer server sessions when the last packet received from the old session is less than 1 minute old.
|
||||||
|
if old != nil && now-old.lastSeen.Load() < 60 {
|
||||||
|
return DecodedUDPPacket{}, errors.New("newer server session rejected: old session is less than 1 minute old")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessBytes [8]byte
|
||||||
|
binary.BigEndian.PutUint64(sessBytes[:], sessionID)
|
||||||
|
bodyKey := DeriveSessionSubKey(s.codec.psk, sessBytes[:], s.codec.method.KeySaltLength)
|
||||||
|
trialAead, err := s.codec.method.NewAEAD(bodyKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, err
|
return DecodedUDPPacket{}, err
|
||||||
}
|
}
|
||||||
if !st.check(packetID) {
|
|
||||||
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
|
||||||
}
|
|
||||||
bodyAead := st.cipher
|
|
||||||
|
|
||||||
bodyNonce := rawHeader[4:16]
|
bodyNonce := rawHeader[4:16]
|
||||||
bodyCipher := data[16:]
|
bodyCipher := data[16:]
|
||||||
bodyPlain, err := bodyAead.Open(nil, bodyNonce, bodyCipher, nil)
|
bodyPlain, err := trialAead.Open(nil, bodyNonce, bodyCipher, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DecodedUDPPacket{}, errors.New("failed to decrypt aes udp body").Base(err)
|
return DecodedUDPPacket{}, errors.New("failed to decrypt aes udp body").Base(err)
|
||||||
}
|
}
|
||||||
@@ -699,7 +891,15 @@ func (s *ClientUDPSession) DecodePacket(data []byte) (DecodedUDPPacket, error) {
|
|||||||
return DecodedUDPPacket{}, errors.New("client session ID mismatch")
|
return DecodedUDPPacket{}, errors.New("client session ID mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
st.add(packetID)
|
// Payload authenticated! Safely commit and transition session under lock
|
||||||
|
st, err = s.getOrTransitionServerSession(sessionID, trialAead, now)
|
||||||
|
if err != nil {
|
||||||
|
return DecodedUDPPacket{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !st.commit(packetID) {
|
||||||
|
return DecodedUDPPacket{}, ErrPacketIdNotUnique
|
||||||
|
}
|
||||||
st.lastSeen.Store(now)
|
st.lastSeen.Store(now)
|
||||||
|
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ func TestRelayUDPSessionStabilityAndDispatch(t *testing.T) {
|
|||||||
// Verify downstream destination can decode both packets
|
// Verify downstream destination can decode both packets
|
||||||
method, err := GetCipherMethod(MethodAES128GCM)
|
method, err := GetCipherMethod(MethodAES128GCM)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
destCodec, err := NewUDPServerCodec(method, destKey, 300*time.Second)
|
destCodec, err := NewUDPServerCodec(method, destKey, nil, 300*time.Second)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -202,7 +202,8 @@ func TestRelayUDPSessionStabilityAndDispatch(t *testing.T) {
|
|||||||
t.Fatalf("expected 2 received packets at destination, got %d", len(pkts))
|
t.Fatalf("expected 2 received packets at destination, got %d", len(pkts))
|
||||||
}
|
}
|
||||||
|
|
||||||
dec1, err := destCodec.DecodePacket(pkts[0])
|
dec1, dec2 := DecodedUDPPacket{}, DecodedUDPPacket{}
|
||||||
|
_, dec1, err = destCodec.DecodePacket(pkts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("dest failed to decode packet 1: %v", err)
|
t.Fatalf("dest failed to decode packet 1: %v", err)
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,7 @@ func TestRelayUDPSessionStabilityAndDispatch(t *testing.T) {
|
|||||||
t.Fatalf("dec1 mismatch: sess=%x, pktID=%d, payload=%s", dec1.SessionID, dec1.PacketID, string(dec1.Payload))
|
t.Fatalf("dec1 mismatch: sess=%x, pktID=%d, payload=%s", dec1.SessionID, dec1.PacketID, string(dec1.Payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
dec2, err := destCodec.DecodePacket(pkts[1])
|
_, dec2, err = destCodec.DecodePacket(pkts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("dest failed to decode packet 2: %v", err)
|
t.Fatalf("dest failed to decode packet 2: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,19 +100,27 @@ type ServerUDPSession struct {
|
|||||||
func (s *ServerUDPSession) CheckPacketID(packetID uint64) bool {
|
func (s *ServerUDPSession) CheckPacketID(packetID uint64) bool {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
if s.Window == nil {
|
|
||||||
s.Window = new(SlidingWindow)
|
|
||||||
}
|
|
||||||
return s.Window.Check(packetID)
|
return s.Window.Check(packetID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerUDPSession) AddPacketID(packetID uint64) {
|
func (s *ServerUDPSession) Commit(packetID uint64, bodyAead cipher.AEAD, user *protocol.MemoryUser, userPSK []byte) bool {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
if s.Window == nil {
|
if s.User != nil && user != nil && s.User != user {
|
||||||
s.Window = new(SlidingWindow)
|
return false
|
||||||
}
|
}
|
||||||
s.Window.Add(packetID)
|
if !s.Window.CheckAndAdd(packetID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s.clientBodyCipher == nil && bodyAead != nil {
|
||||||
|
s.clientBodyCipher = bodyAead
|
||||||
|
}
|
||||||
|
if s.User == nil && user != nil {
|
||||||
|
s.User = user
|
||||||
|
s.UserPSK = userPSK
|
||||||
|
}
|
||||||
|
s.LastActive.Store(time.Now().Unix())
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDPSessionManager struct {
|
type UDPSessionManager struct {
|
||||||
@@ -128,6 +136,13 @@ func NewUDPSessionManager(timeout time.Duration) *UDPSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *UDPSessionManager) Get(sessionID uint64) *ServerUDPSession {
|
||||||
|
if s, ok := m.sessions.Load(sessionID); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *UDPSessionManager) GetOrCreate(sessionID uint64) *ServerUDPSession {
|
func (m *UDPSessionManager) GetOrCreate(sessionID uint64) *ServerUDPSession {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if s, ok := m.sessions.Load(sessionID); ok {
|
if s, ok := m.sessions.Load(sessionID); ok {
|
||||||
@@ -137,6 +152,7 @@ func (m *UDPSessionManager) GetOrCreate(sessionID uint64) *ServerUDPSession {
|
|||||||
|
|
||||||
s := &ServerUDPSession{
|
s := &ServerUDPSession{
|
||||||
SessionID: sessionID,
|
SessionID: sessionID,
|
||||||
|
Window: new(SlidingWindow),
|
||||||
manager: m,
|
manager: m,
|
||||||
}
|
}
|
||||||
s.LastActive.Store(now)
|
s.LastActive.Store(now)
|
||||||
|
|||||||
@@ -63,10 +63,16 @@ func (s *ServerUDPSession) EnsureLink(
|
|||||||
|
|
||||||
s.currentConn.Store(conn)
|
s.currentConn.Store(conn)
|
||||||
|
|
||||||
sessCtx, cancel := context.WithCancel(ctx)
|
// UDP Session might travel across multiple connections
|
||||||
|
// don't bind its lifetime to a parent context
|
||||||
|
sessCtx, cancel := context.WithCancel(context.WithoutCancel(ctx))
|
||||||
inbound := session.InboundFromContext(sessCtx)
|
inbound := session.InboundFromContext(sessCtx)
|
||||||
if inbound != nil && s.User != nil {
|
if inbound != nil {
|
||||||
inbound.User = s.User
|
newInbound := *inbound
|
||||||
|
if s.User != nil {
|
||||||
|
newInbound.User = s.User
|
||||||
|
}
|
||||||
|
sessCtx = session.ContextWithInbound(sessCtx, &newInbound)
|
||||||
}
|
}
|
||||||
var email string
|
var email string
|
||||||
var level uint32
|
var level uint32
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ func TestUDPCodec(t *testing.T) {
|
|||||||
|
|
||||||
clientCodec, err := NewUDPPacketCodec(method, [][]byte{psk})
|
clientCodec, err := NewUDPPacketCodec(method, [][]byte{psk})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
serverCodec, err := NewUDPServerCodec(method, psk, time.Minute)
|
serverCodec, err := NewUDPServerCodec(method, psk, nil, time.Minute)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
session, err := clientCodec.NewClientSession()
|
session, err := clientCodec.NewClientSession()
|
||||||
@@ -277,7 +277,7 @@ func TestUDPCodec(t *testing.T) {
|
|||||||
rawCopy := make([]byte, pktBuf.Len())
|
rawCopy := make([]byte, pktBuf.Len())
|
||||||
copy(rawCopy, pktBuf.Bytes())
|
copy(rawCopy, pktBuf.Bytes())
|
||||||
|
|
||||||
decoded, err := serverCodec.DecodePacket(pktBuf.Bytes())
|
_, decoded, err := serverCodec.DecodePacket(pktBuf.Bytes())
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if decoded.HeaderType != HeaderTypeClient {
|
if decoded.HeaderType != HeaderTypeClient {
|
||||||
@@ -291,7 +291,7 @@ func TestUDPCodec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replay same packet wire bytes should fail with ErrPacketIdNotUnique
|
// Replay same packet wire bytes should fail with ErrPacketIdNotUnique
|
||||||
_, err = serverCodec.DecodePacket(rawCopy)
|
_, _, err = serverCodec.DecodePacket(rawCopy)
|
||||||
if err != ErrPacketIdNotUnique {
|
if err != ErrPacketIdNotUnique {
|
||||||
t.Fatalf("expected ErrPacketIdNotUnique on replay, got: %v", err)
|
t.Fatalf("expected ErrPacketIdNotUnique on replay, got: %v", err)
|
||||||
}
|
}
|
||||||
@@ -427,7 +427,7 @@ func TestClientUDPSessionMultiDestination(t *testing.T) {
|
|||||||
|
|
||||||
clientCodec, err := NewUDPPacketCodec(method, [][]byte{rawKey})
|
clientCodec, err := NewUDPPacketCodec(method, [][]byte{rawKey})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
serverCodec, err := NewUDPServerCodec(method, rawKey, time.Minute)
|
serverCodec, err := NewUDPServerCodec(method, rawKey, nil, time.Minute)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
session, err := clientCodec.NewClientSession()
|
session, err := clientCodec.NewClientSession()
|
||||||
@@ -448,9 +448,9 @@ func TestClientUDPSessionMultiDestination(t *testing.T) {
|
|||||||
defer pkt2.Release()
|
defer pkt2.Release()
|
||||||
|
|
||||||
// Server decodes both
|
// Server decodes both
|
||||||
dec1, err := serverCodec.DecodePacket(pkt1.Bytes())
|
_, dec1, err := serverCodec.DecodePacket(pkt1.Bytes())
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
dec2, err := serverCodec.DecodePacket(pkt2.Bytes())
|
_, dec2, err := serverCodec.DecodePacket(pkt2.Bytes())
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if dec1.SessionID != session.ClientSessionID() || dec2.SessionID != session.ClientSessionID() {
|
if dec1.SessionID != session.ClientSessionID() || dec2.SessionID != session.ClientSessionID() {
|
||||||
|
|||||||
Reference in New Issue
Block a user