Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes

This commit is contained in:
Shtorm
2026-06-04 01:47:50 +03:00
parent 9b3da79c32
commit 195a33379d
164 changed files with 16665 additions and 1332 deletions

227
transport/openvpn/cipher.go Normal file
View File

@@ -0,0 +1,227 @@
package openvpn
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"errors"
"hash"
"golang.org/x/crypto/chacha20poly1305"
)
const (
AESGCMTagSize = 16
AESGCMIVSize = 12
CBCIVSize = aes.BlockSize
)
type DataCipher interface {
Encrypt(header []byte, packetID uint32, payload []byte) ([]byte, error)
Decrypt(packet []byte, headerSize int) ([]byte, error)
}
type AEADDataCipher struct {
send cipher.AEAD
recv cipher.AEAD
sendImplicitIV [AESGCMIVSize]byte
recvImplicitIV [AESGCMIVSize]byte
}
func NewAEADCipher(keys *KeyMaterial, cipherName string) (*AEADDataCipher, error) {
var send, recv cipher.AEAD
var err error
if cipherName == CipherCHACHA20POLY {
send, err = chacha20poly1305.New(keys.SendCipherKey)
if err != nil {
return nil, err
}
recv, err = chacha20poly1305.New(keys.RecvCipherKey)
if err != nil {
return nil, err
}
} else {
sendBlock, err := aes.NewCipher(keys.SendCipherKey)
if err != nil {
return nil, err
}
recvBlock, err := aes.NewCipher(keys.RecvCipherKey)
if err != nil {
return nil, err
}
send, err = cipher.NewGCMWithTagSize(sendBlock, AESGCMTagSize)
if err != nil {
return nil, err
}
recv, err = cipher.NewGCMWithTagSize(recvBlock, AESGCMTagSize)
if err != nil {
return nil, err
}
}
if len(keys.SendHMACKey) < AESGCMIVSize-4 || len(keys.RecvHMACKey) < AESGCMIVSize-4 {
return nil, errors.New("openvpn implicit IV keys are too short")
}
g := &AEADDataCipher{send: send, recv: recv}
copy(g.sendImplicitIV[4:], keys.SendHMACKey[:AESGCMIVSize-4])
copy(g.recvImplicitIV[4:], keys.RecvHMACKey[:AESGCMIVSize-4])
return g, nil
}
func (g *AEADDataCipher) Encrypt(header []byte, packetID uint32, payload []byte) ([]byte, error) {
var pidBytes [4]byte
binary.BigEndian.PutUint32(pidBytes[:], packetID)
nonce := g.nonce(packetID, g.sendImplicitIV)
ad := append(header, pidBytes[:]...)
sealed := g.send.Seal(nil, nonce[:], payload, ad)
out := make([]byte, 0, len(header)+4+len(sealed))
out = append(out, header...)
out = append(out, pidBytes[:]...)
out = append(out, sealed[len(sealed)-AESGCMTagSize:]...)
out = append(out, sealed[:len(sealed)-AESGCMTagSize]...)
return out, nil
}
func (g *AEADDataCipher) Decrypt(packet []byte, headerSize int) ([]byte, error) {
if len(packet) < headerSize+4+AESGCMTagSize+1 {
return nil, errors.New("openvpn gcm data packet too short")
}
header := packet[:headerSize]
pidBytes := packet[headerSize : headerSize+4]
tag := packet[headerSize+4 : headerSize+4+AESGCMTagSize]
ciphertext := packet[headerSize+4+AESGCMTagSize:]
combined := append(ciphertext, tag...)
ad := append(header, pidBytes...)
nonce := g.nonce(binary.BigEndian.Uint32(pidBytes), g.recvImplicitIV)
return g.recv.Open(nil, nonce[:], combined, ad)
}
func (g *AEADDataCipher) nonce(packetID uint32, implicit [AESGCMIVSize]byte) [AESGCMIVSize]byte {
nonce := implicit
binary.BigEndian.PutUint32(nonce[:4], binary.BigEndian.Uint32(nonce[:4])^packetID)
return nonce
}
type CBCDataCipher struct {
sendBlock cipher.Block
recvBlock cipher.Block
sendHMAC []byte
recvHMAC []byte
newHash func() hash.Hash
hmacSize int
}
func NewCBCCipher(keys *KeyMaterial, auth string) (*CBCDataCipher, error) {
sendBlock, err := aes.NewCipher(keys.SendCipherKey)
if err != nil {
return nil, err
}
recvBlock, err := aes.NewCipher(keys.RecvCipherKey)
if err != nil {
return nil, err
}
var newHash func() hash.Hash
var hmacSize int
switch auth {
case AuthSHA256:
newHash = sha256.New
hmacSize = sha256.Size
case AuthSHA384:
newHash = sha512.New384
hmacSize = 48
case AuthSHA512:
newHash = sha512.New
hmacSize = sha512.Size
default:
newHash = sha1.New
hmacSize = sha1.Size
}
return &CBCDataCipher{
sendBlock: sendBlock,
recvBlock: recvBlock,
sendHMAC: cloneBytes(keys.SendHMACKey[:hmacSize]),
recvHMAC: cloneBytes(keys.RecvHMACKey[:hmacSize]),
newHash: newHash,
hmacSize: hmacSize,
}, nil
}
func (c *CBCDataCipher) Encrypt(header []byte, packetID uint32, payload []byte) ([]byte, error) {
var pidBytes [4]byte
binary.BigEndian.PutUint32(pidBytes[:], packetID)
plain := append(pidBytes[:], payload...)
padLen := aes.BlockSize - (len(plain) % aes.BlockSize)
for i := 0; i < padLen; i++ {
plain = append(plain, byte(padLen))
}
iv := make([]byte, CBCIVSize)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
ct := make([]byte, len(plain))
cipher.NewCBCEncrypter(c.sendBlock, iv).CryptBlocks(ct, plain)
mac := hmac.New(c.newHash, c.sendHMAC)
mac.Write(iv)
mac.Write(ct)
tag := mac.Sum(nil)
out := make([]byte, 0, len(header)+c.hmacSize+CBCIVSize+len(ct))
out = append(out, header...)
out = append(out, tag...)
out = append(out, iv...)
out = append(out, ct...)
return out, nil
}
func (c *CBCDataCipher) Decrypt(packet []byte, headerSize int) ([]byte, error) {
minSize := headerSize + c.hmacSize + CBCIVSize + aes.BlockSize
if len(packet) < minSize {
return nil, errors.New("openvpn cbc data packet too short")
}
tag := packet[headerSize : headerSize+c.hmacSize]
iv := packet[headerSize+c.hmacSize : headerSize+c.hmacSize+CBCIVSize]
ct := packet[headerSize+c.hmacSize+CBCIVSize:]
if len(ct)%aes.BlockSize != 0 {
return nil, errors.New("openvpn cbc ciphertext not block-aligned")
}
mac := hmac.New(c.newHash, c.recvHMAC)
mac.Write(iv)
mac.Write(ct)
if !hmac.Equal(tag, mac.Sum(nil)) {
return nil, errors.New("openvpn cbc hmac verification failed")
}
plain := make([]byte, len(ct))
cipher.NewCBCDecrypter(c.recvBlock, iv).CryptBlocks(plain, ct)
padLen := int(plain[len(plain)-1])
if padLen < 1 || padLen > aes.BlockSize {
return nil, errors.New("openvpn cbc invalid padding")
}
plain = plain[:len(plain)-padLen]
if len(plain) < 4 {
return nil, errors.New("openvpn cbc payload too short")
}
return plain[4:], nil
}
func CipherKeyLength(cipher string) int {
switch cipher {
case CipherAES128GCM, CipherAES128CBC:
return 16
case CipherAES192GCM, CipherAES192CBC:
return 24
default:
return 32
}
}
func IsAEAD(cipher string) bool {
switch cipher {
case CipherAES128GCM, CipherAES192GCM, CipherAES256GCM, CipherCHACHA20POLY:
return true
default:
return false
}
}

280
transport/openvpn/client.go Normal file
View File

@@ -0,0 +1,280 @@
package openvpn
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/sagernet/sing/common/tls"
)
const defaultHandshakeTimeout = 30 * time.Second
type Client struct {
config *ClientConfig
tlsConfig tls.Config
mux *PacketMux
control *ControlChannel
tlsConn tls.Conn
data *DataChannel
push *PushReply
cancel context.CancelFunc
}
func NewClient(config *ClientConfig, io PacketIO, tlsConfig tls.Config) (*Client, error) {
if config == nil {
return nil, errors.New("nil openvpn client config")
}
if io == nil {
return nil, errors.New("nil openvpn packet io")
}
if tlsConfig == nil {
return nil, errors.New("nil openvpn tls config")
}
var crypt ControlCrypt
var err error
if config.TLSAuthKey != nil {
crypt, err = NewTLSAuth(config.TLSAuthKey, config.KeyDirection, config.Auth)
if err != nil {
return nil, err
}
} else if config.TLSCryptKey != nil {
crypt, err = NewTLSCrypt(config.TLSCryptKey, true)
if err != nil {
return nil, err
}
}
local, err := NewSessionID()
if err != nil {
return nil, err
}
runCtx, cancel := context.WithCancel(context.Background())
mux := NewPacketMux(io)
go mux.Run(runCtx)
return &Client{
config: config,
tlsConfig: tlsConfig,
mux: mux,
control: NewControlChannel(mux, crypt, local),
cancel: cancel,
}, nil
}
func (c *Client) Handshake(ctx context.Context) (*PushReply, error) {
if c == nil {
return nil, errors.New("nil openvpn client")
}
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, defaultHandshakeTimeout)
defer cancel()
}
if c.config.TLSCryptV2WKc != nil {
if err := c.sendResetV3(ctx); err != nil {
return nil, fmt.Errorf("send hard reset v3: %w", err)
}
} else {
if err := c.control.SendReset(ctx); err != nil {
return nil, fmt.Errorf("send hard reset: %w", err)
}
}
if err := c.waitServerReset(ctx); err != nil {
return nil, err
}
controlConn := NewControlConn(c.control)
tlsConn, err := c.tlsConfig.Client(controlConn)
if err != nil {
return nil, fmt.Errorf("openvpn tls client: %w", err)
}
c.tlsConn = tlsConn
if err := c.tlsConn.HandshakeContext(ctx); err != nil {
return nil, fmt.Errorf("openvpn tls handshake: %w", err)
}
clientRecord, err := NewClientKeyMethod2Record(
InstallScriptOptionsString(c.config.Proto, c.config.Cipher, c.config.Auth),
InstallScriptPeerInfo(c.config.Cipher),
strings.TrimSpace(c.config.Username),
c.config.Password,
)
if err != nil {
return nil, err
}
clientBytes, err := clientRecord.MarshalClient()
if err != nil {
return nil, err
}
if _, err := c.tlsConn.Write(clientBytes); err != nil {
return nil, fmt.Errorf("write key method 2 client record: %w", err)
}
serverRecord, err := c.readServerKeyMethod(ctx)
if err != nil {
return nil, err
}
sources := clientRecord.Sources
sources.Server = serverRecord.Sources.Server
keys, err := DeriveClientKeyMaterial(sources, c.control.LocalSessionID(), c.control.RemoteSessionID(), 32)
if err != nil {
return nil, fmt.Errorf("derive data channel keys: %w", err)
}
if _, err := c.tlsConn.Write([]byte(PushRequest + "\x00")); err != nil {
return nil, fmt.Errorf("write push request: %w", err)
}
push, err := c.readPushReply(ctx)
if err != nil {
return nil, err
}
c.push = push
dataCipher := c.config.Cipher
if push.Cipher != "" {
dataCipher = push.Cipher
}
if dataCipher == "" {
return nil, errors.New("openvpn server did not negotiate a cipher and no cipher configured")
}
keyLen := CipherKeyLength(dataCipher)
keys.SendCipherKey = keys.SendCipherKey[:keyLen]
keys.RecvCipherKey = keys.RecvCipherKey[:keyLen]
var cipher DataCipher
if IsAEAD(dataCipher) {
cipher, err = NewAEADCipher(keys, dataCipher)
} else {
cipher, err = NewCBCCipher(keys, c.config.Auth)
}
if err != nil {
return nil, err
}
c.data = NewDataChannel(cipher, push.PeerID, push.CompLZO)
return push, nil
}
func (c *Client) WriteIPPacket(ctx context.Context, packet []byte) error {
if c.data == nil {
return errors.New("openvpn data channel is not ready")
}
encrypted, err := c.data.Encrypt(packet)
if err != nil {
return err
}
return c.mux.WritePacket(ctx, encrypted)
}
func (c *Client) ReadIPPacket(ctx context.Context) ([]byte, error) {
if c.data == nil {
return nil, errors.New("openvpn data channel is not ready")
}
for {
packet, err := c.mux.ReadDataPacket(ctx)
if err != nil {
return nil, err
}
plain, err := c.data.Decrypt(packet)
if err != nil {
continue
}
return plain, nil
}
}
func (c *Client) Close() error {
if c.cancel != nil {
c.cancel()
}
if c.tlsConn != nil {
_ = c.tlsConn.Close()
}
if c.mux != nil {
return c.mux.Close()
}
return nil
}
func (c *Client) waitServerReset(ctx context.Context) error {
for {
packet, err := c.control.Read(ctx)
if err != nil {
return fmt.Errorf("read hard reset response: %w", err)
}
switch packet.Opcode {
case PControlHardResetServerV2:
return c.control.SendAck(ctx)
case PControlHardResetServerV1:
return fmt.Errorf("openvpn server replied with unsupported key method 1 reset")
}
}
}
func (c *Client) readServerKeyMethod(ctx context.Context) (*KeyMethod2Record, error) {
var buf []byte
tmp := make([]byte, 4096)
for {
n, err := c.tlsConn.Read(tmp)
if err != nil {
return nil, fmt.Errorf("read key method 2 server record: %w", err)
}
buf = append(buf, tmp[:n]...)
record, err := ParseServerKeyMethod2Record(buf)
if err == nil {
return record, nil
}
if !strings.Contains(err.Error(), "truncated") && !errors.Is(err, ioStringEOF) {
return nil, err
}
}
}
func (c *Client) readPushReply(ctx context.Context) (*PushReply, error) {
var buf []byte
tmp := make([]byte, 4096)
for {
n, err := c.tlsConn.Read(tmp)
if err != nil {
if errors.Is(err, io.EOF) && len(buf) > 0 {
break
}
return nil, fmt.Errorf("read push reply: %w", err)
}
buf = append(buf, tmp[:n]...)
if bytes.Contains(buf, []byte("\x00")) || strings.Contains(string(buf), "PUSH_REPLY") {
msg := string(buf)
if idx := strings.IndexByte(msg, 0); idx >= 0 {
msg = msg[:idx]
}
if reply, err := ParsePushReply(msg); err == nil {
return reply, nil
}
}
}
return nil, ctx.Err()
}
func (c *Client) sendResetV3(ctx context.Context) error {
c.control.mu.Lock()
messageID := c.control.sendMessage
c.control.sendMessage++
packet := &ControlPacket{
Opcode: PControlHardResetClientV3,
KeyID: c.control.keyID,
LocalSession: c.control.local,
MessageID: messageID,
}
c.control.pending[messageID] = packet
c.control.mu.Unlock()
encoded, err := c.control.encodeAndWrap(ctx, packet)
if err != nil {
return err
}
encoded = append(encoded, c.config.TLSCryptV2WKc...)
return c.control.io.WritePacket(ctx, encoded)
}
var _ net.Conn = (*ControlConn)(nil)

175
transport/openvpn/config.go Normal file
View File

@@ -0,0 +1,175 @@
package openvpn
import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strings"
)
const (
ProtoUDP = "udp"
ProtoTCP = "tcp"
CipherAES128GCM = "AES-128-GCM"
CipherAES192GCM = "AES-192-GCM"
CipherAES256GCM = "AES-256-GCM"
CipherAES128CBC = "AES-128-CBC"
CipherAES192CBC = "AES-192-CBC"
CipherAES256CBC = "AES-256-CBC"
CipherCHACHA20POLY = "CHACHA20-POLY1305"
AuthSHA1 = "SHA1"
AuthSHA256 = "SHA256"
AuthSHA384 = "SHA384"
AuthSHA512 = "SHA512"
)
type ClientConfig struct {
Proto string
Cipher string
Auth string
Username string
Password string
KeyDirection int
TLSCrypt []byte
TLSCryptV2 bool
TLSCryptKey []byte
TLSCryptV2WKc []byte
TLSAuthKey []byte
}
func (c *ClientConfig) Prepare() error {
if c == nil {
return errors.New("nil openvpn client config")
}
c.Proto = normalizeProto(c.Proto)
c.Cipher = strings.ToUpper(strings.TrimSpace(c.Cipher))
if c.Auth == "" {
c.Auth = AuthSHA1
}
c.Auth = strings.ToUpper(strings.TrimSpace(c.Auth))
if c.Proto != ProtoUDP && c.Proto != ProtoTCP {
return fmt.Errorf("unsupported openvpn proto %q: only udp and tcp are supported", c.Proto)
}
if c.Cipher != "" && !isValidCipher(c.Cipher) {
return fmt.Errorf("unsupported openvpn cipher %q", c.Cipher)
}
if !isValidAuth(c.Auth) {
return fmt.Errorf("unsupported openvpn auth %q", c.Auth)
}
if c.TLSCryptV2 {
kc, wkc, err := decodeTLSCryptV2Key(c.TLSCrypt)
if err != nil {
return fmt.Errorf("parse tls-crypt-v2 key: %w", err)
}
c.TLSCryptKey = kc
c.TLSCryptV2WKc = wkc
return nil
}
if len(strings.TrimSpace(string(c.TLSCrypt))) == 0 {
return nil
}
key, err := decodeStaticKey(c.TLSCrypt)
if err != nil {
return fmt.Errorf("parse tls key: %w", err)
}
if c.KeyDirection >= 0 {
c.TLSAuthKey = key
} else {
c.TLSCryptKey = key
}
return nil
}
func normalizeProto(proto string) string {
switch strings.ToLower(strings.TrimSpace(proto)) {
case "", "udp", "udp4":
return ProtoUDP
case "tcp", "tcp-client", "tcp4", "tcp4-client":
return ProtoTCP
default:
return strings.ToLower(strings.TrimSpace(proto))
}
}
func isValidCipher(cipher string) bool {
switch cipher {
case CipherAES128GCM, CipherAES192GCM, CipherAES256GCM,
CipherAES128CBC, CipherAES192CBC, CipherAES256CBC,
CipherCHACHA20POLY:
return true
}
return false
}
func isValidAuth(auth string) bool {
switch auth {
case AuthSHA1, AuthSHA256, AuthSHA384, AuthSHA512:
return true
}
return false
}
func decodeStaticKey(block []byte) ([]byte, error) {
var hexLines []string
for _, raw := range strings.Split(string(block), "\n") {
line := strings.TrimSpace(raw)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if strings.HasPrefix(line, "-----BEGIN OpenVPN Static key") || strings.HasPrefix(line, "-----END OpenVPN Static key") {
continue
}
hexLines = append(hexLines, line)
}
encoded := strings.Join(hexLines, "")
key, err := hex.DecodeString(encoded)
if err != nil {
return nil, err
}
if len(key) != 256 {
return nil, fmt.Errorf("invalid static key length %d, expected 256 bytes", len(key))
}
return key, nil
}
func decodeTLSCryptV2Key(block []byte) (kc []byte, wkc []byte, err error) {
data, err := decodePEM(block, "OpenVPN tls-crypt-v2 client key")
if err != nil {
return nil, nil, err
}
if len(data) < 256 {
return nil, nil, fmt.Errorf("tls-crypt-v2 key too short: %d bytes", len(data))
}
return data[:256], data[256:], nil
}
func decodePEM(block []byte, expectedHeader string) ([]byte, error) {
lines := strings.Split(string(block), "\n")
var b64 strings.Builder
inBlock := false
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.Contains(line, "BEGIN") && strings.Contains(line, expectedHeader) {
inBlock = true
continue
}
if strings.Contains(line, "END") && strings.Contains(line, expectedHeader) {
break
}
if inBlock {
b64.WriteString(line)
}
}
if b64.Len() == 0 {
return nil, fmt.Errorf("no %s block found", expectedHeader)
}
return base64Decode(b64.String())
}
func base64Decode(s string) ([]byte, error) {
return base64.StdEncoding.DecodeString(s)
}

View File

@@ -0,0 +1,475 @@
package openvpn
import (
"context"
"errors"
"fmt"
"io"
"net"
"sync"
"time"
)
type PacketIO interface {
ReadPacket(ctx context.Context) ([]byte, error)
WritePacket(ctx context.Context, packet []byte) error
Close() error
LocalAddr() net.Addr
RemoteAddr() net.Addr
}
type ControlChannel struct {
io PacketIO
encode func(*ControlPacket, uint32, uint32) ([]byte, error)
decode func([]byte) (*ControlPacket, uint32, uint32, error)
clock func() time.Time
keyID uint8
local SessionID
remote SessionID
mu sync.Mutex
sendPacketID uint32
sendMessage uint32
ackPending []uint32
pending map[uint32]*ControlPacket
readDeadline time.Time
writeDeadline time.Time
}
func NewControlChannel(io PacketIO, crypt ControlCrypt, local SessionID) *ControlChannel {
ch := &ControlChannel{
io: io,
clock: time.Now,
local: local,
pending: make(map[uint32]*ControlPacket),
}
if crypt != nil {
ch.encode = func(p *ControlPacket, pid uint32, t uint32) ([]byte, error) {
return EncodeControlPacketCrypt(*p, crypt, pid, t)
}
ch.decode = func(pkt []byte) (*ControlPacket, uint32, uint32, error) {
return DecodeControlPacketCrypt(crypt, pkt)
}
} else {
ch.encode = func(p *ControlPacket, _ uint32, _ uint32) ([]byte, error) {
return EncodeControlPacket(*p)
}
ch.decode = func(pkt []byte) (*ControlPacket, uint32, uint32, error) {
cp, err := DecodeControlPacket(pkt)
return cp, 0, 0, err
}
}
return ch
}
func (c *ControlChannel) LocalSessionID() SessionID {
return c.local
}
func (c *ControlChannel) RemoteSessionID() SessionID {
c.mu.Lock()
defer c.mu.Unlock()
return c.remote
}
func (c *ControlChannel) SetRemoteSessionID(id SessionID) {
c.mu.Lock()
c.remote = id
c.mu.Unlock()
}
func (c *ControlChannel) SendReset(ctx context.Context) error {
_, err := c.Send(ctx, PControlHardResetClientV2, nil)
return err
}
func (c *ControlChannel) Send(ctx context.Context, opcode Opcode, payload []byte) (uint32, error) {
if !opcode.HasMessageID() {
return 0, fmt.Errorf("opcode %s cannot carry a reliable message", opcode)
}
c.mu.Lock()
messageID := c.sendMessage
c.sendMessage++
packet := &ControlPacket{
Opcode: opcode,
KeyID: c.keyID,
LocalSession: c.local,
AckIDs: append([]uint32(nil), c.ackPending...),
AckRemoteSession: c.remote,
MessageID: messageID,
Payload: cloneBytes(payload),
}
c.ackPending = nil
c.pending[messageID] = packet
c.mu.Unlock()
if err := c.writeControlPacket(ctx, packet); err != nil {
return 0, err
}
return messageID, nil
}
func (c *ControlChannel) SendAck(ctx context.Context) error {
c.mu.Lock()
if len(c.ackPending) == 0 {
c.mu.Unlock()
return nil
}
packet := &ControlPacket{
Opcode: PAckV1,
KeyID: c.keyID,
LocalSession: c.local,
AckIDs: append([]uint32(nil), c.ackPending...),
AckRemoteSession: c.remote,
}
c.ackPending = nil
c.mu.Unlock()
return c.writeControlPacket(ctx, packet)
}
func (c *ControlChannel) Read(ctx context.Context) (*ControlPacket, error) {
for {
packet, err := c.readControlPacket(ctx)
if err != nil {
return nil, err
}
c.mu.Lock()
if c.remote == (SessionID{}) && packet.LocalSession != c.local {
c.remote = packet.LocalSession
}
for _, ackID := range packet.AckIDs {
delete(c.pending, ackID)
}
if packet.Opcode.HasMessageID() {
c.ackPending = appendAck(c.ackPending, packet.MessageID)
}
c.mu.Unlock()
if packet.Opcode == PAckV1 {
continue
}
return packet, nil
}
}
func (c *ControlChannel) PendingMessages() int {
c.mu.Lock()
defer c.mu.Unlock()
return len(c.pending)
}
func (c *ControlChannel) RetransmitPending(ctx context.Context) error {
c.mu.Lock()
packets := make([]*ControlPacket, 0, len(c.pending))
for _, packet := range c.pending {
cp := *packet
cp.AckIDs = append([]uint32(nil), c.ackPending...)
cp.AckRemoteSession = c.remote
packets = append(packets, &cp)
}
c.ackPending = nil
c.mu.Unlock()
for _, packet := range packets {
if err := c.writeControlPacket(ctx, packet); err != nil {
return err
}
}
return nil
}
func (c *ControlChannel) writeControlPacket(ctx context.Context, packet *ControlPacket) error {
c.mu.Lock()
c.sendPacketID++
packetID := c.sendPacketID
unixTime := uint32(c.clock().Unix())
deadline := c.writeDeadline
c.mu.Unlock()
if !deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, deadline)
defer cancel()
}
encoded, err := c.encode(packet, packetID, unixTime)
if err != nil {
return err
}
return c.io.WritePacket(ctx, encoded)
}
func (c *ControlChannel) encodeAndWrap(ctx context.Context, packet *ControlPacket) ([]byte, error) {
c.mu.Lock()
c.sendPacketID++
packetID := c.sendPacketID
unixTime := uint32(c.clock().Unix())
c.mu.Unlock()
return c.encode(packet, packetID, unixTime)
}
func (c *ControlChannel) readControlPacket(ctx context.Context) (*ControlPacket, error) {
c.mu.Lock()
deadline := c.readDeadline
c.mu.Unlock()
if !deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, deadline)
defer cancel()
}
raw, err := c.io.ReadPacket(ctx)
if err != nil {
return nil, err
}
packet, _, _, err := c.decode(raw)
return packet, err
}
func (c *ControlChannel) SetDeadline(t time.Time) error {
c.mu.Lock()
c.readDeadline = t
c.writeDeadline = t
c.mu.Unlock()
return nil
}
func (c *ControlChannel) SetReadDeadline(t time.Time) error {
c.mu.Lock()
c.readDeadline = t
c.mu.Unlock()
return nil
}
func (c *ControlChannel) SetWriteDeadline(t time.Time) error {
c.mu.Lock()
c.writeDeadline = t
c.mu.Unlock()
return nil
}
func appendAck(acks []uint32, ack uint32) []uint32 {
for _, existing := range acks {
if existing == ack {
return acks
}
}
return append(acks, ack)
}
type ControlConn struct {
channel *ControlChannel
readBuf []byte
closed bool
mu sync.Mutex
}
func NewControlConn(channel *ControlChannel) *ControlConn {
return &ControlConn{channel: channel}
}
func (c *ControlConn) Read(b []byte) (int, error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return 0, net.ErrClosed
}
if len(c.readBuf) > 0 {
n := copy(b, c.readBuf)
c.readBuf = c.readBuf[n:]
c.mu.Unlock()
return n, nil
}
c.mu.Unlock()
for {
packet, err := c.channel.Read(context.Background())
if err != nil {
return 0, err
}
if packet.Opcode != PControlV1 {
if err := c.channel.SendAck(context.Background()); err != nil {
return 0, err
}
continue
}
if err := c.channel.SendAck(context.Background()); err != nil {
return 0, err
}
if len(packet.Payload) == 0 {
continue
}
n := copy(b, packet.Payload)
if n < len(packet.Payload) {
c.mu.Lock()
c.readBuf = append(c.readBuf, packet.Payload[n:]...)
c.mu.Unlock()
}
return n, nil
}
}
func (c *ControlConn) Write(b []byte) (int, error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return 0, net.ErrClosed
}
c.mu.Unlock()
if _, err := c.channel.Send(context.Background(), PControlV1, b); err != nil {
return 0, err
}
return len(b), nil
}
func (c *ControlConn) Close() error {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return nil
}
c.closed = true
c.mu.Unlock()
return c.channel.io.Close()
}
func (c *ControlConn) LocalAddr() net.Addr {
return c.channel.io.LocalAddr()
}
func (c *ControlConn) RemoteAddr() net.Addr {
return c.channel.io.RemoteAddr()
}
func (c *ControlConn) SetDeadline(t time.Time) error {
return c.channel.SetDeadline(t)
}
func (c *ControlConn) SetReadDeadline(t time.Time) error {
return c.channel.SetReadDeadline(t)
}
func (c *ControlConn) SetWriteDeadline(t time.Time) error {
return c.channel.SetWriteDeadline(t)
}
type streamPacketIO struct {
conn net.Conn
}
type datagramPacketIO struct {
conn net.Conn
}
func NewDatagramPacketIO(conn net.Conn) PacketIO {
return &datagramPacketIO{conn: conn}
}
func (d *datagramPacketIO) ReadPacket(ctx context.Context) ([]byte, error) {
done := make(chan struct{})
var (
packet []byte
err error
)
go func() {
defer close(done)
buf := make([]byte, 64*1024)
var n int
n, err = d.conn.Read(buf)
if err == nil {
packet = cloneBytes(buf[:n])
}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
return packet, err
}
}
func (d *datagramPacketIO) WritePacket(ctx context.Context, packet []byte) error {
done := make(chan error, 1)
go func() {
_, err := d.conn.Write(packet)
done <- err
}()
select {
case <-ctx.Done():
return ctx.Err()
case err := <-done:
return err
}
}
func (d *datagramPacketIO) Close() error {
return d.conn.Close()
}
func (d *datagramPacketIO) LocalAddr() net.Addr {
return d.conn.LocalAddr()
}
func (d *datagramPacketIO) RemoteAddr() net.Addr {
return d.conn.RemoteAddr()
}
func NewTCPPacketIO(conn net.Conn) PacketIO {
return &streamPacketIO{conn: conn}
}
func (s *streamPacketIO) ReadPacket(ctx context.Context) ([]byte, error) {
done := make(chan struct{})
var (
packet []byte
err error
)
go func() {
defer close(done)
var lenBuf [2]byte
if _, err = io.ReadFull(s.conn, lenBuf[:]); err != nil {
return
}
size := int(lenBuf[0])<<8 | int(lenBuf[1])
if size == 0 {
err = errors.New("empty openvpn tcp packet")
return
}
packet = make([]byte, size)
_, err = io.ReadFull(s.conn, packet)
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
return packet, err
}
}
func (s *streamPacketIO) WritePacket(ctx context.Context, packet []byte) error {
if len(packet) > 0xffff {
return fmt.Errorf("openvpn tcp packet too large: %d", len(packet))
}
done := make(chan error, 1)
go func() {
frame := make([]byte, 2+len(packet))
frame[0] = byte(len(packet) >> 8)
frame[1] = byte(len(packet))
copy(frame[2:], packet)
_, err := s.conn.Write(frame)
done <- err
}()
select {
case <-ctx.Done():
return ctx.Err()
case err := <-done:
return err
}
}
func (s *streamPacketIO) Close() error {
return s.conn.Close()
}
func (s *streamPacketIO) LocalAddr() net.Addr {
return s.conn.LocalAddr()
}
func (s *streamPacketIO) RemoteAddr() net.Addr {
return s.conn.RemoteAddr()
}

91
transport/openvpn/data.go Normal file
View File

@@ -0,0 +1,91 @@
package openvpn
import (
"errors"
"fmt"
"sync"
)
const (
PeerIDUnset uint32 = 0xffffff
)
type DataChannel struct {
cipher DataCipher
keyID uint8
peerID uint32
compLZO bool
mu sync.Mutex
sendPacketID uint32
}
func NewDataChannel(cipher DataCipher, peerID uint32, compLZO bool) *DataChannel {
return &DataChannel{
cipher: cipher,
peerID: peerID,
compLZO: compLZO,
}
}
func (d *DataChannel) Encrypt(packet []byte) ([]byte, error) {
if d.compLZO {
p := make([]byte, 1+len(packet))
p[0] = 0xFA
copy(p[1:], packet)
packet = p
}
d.mu.Lock()
d.sendPacketID++
packetID := d.sendPacketID
d.mu.Unlock()
return d.cipher.Encrypt(d.dataHeader(), packetID, packet)
}
func (d *DataChannel) Decrypt(packet []byte) ([]byte, error) {
if len(packet) < 1 {
return nil, errors.New("empty openvpn data packet")
}
opcode, _ := parseOpcodeKeyID(packet[0])
headerSize := 1
if opcode == PDataV2 {
headerSize = 4
}
plain, err := d.cipher.Decrypt(packet, headerSize)
if err != nil {
return nil, err
}
if d.compLZO {
if len(plain) < 1 {
return nil, errors.New("openvpn comp-lzo packet too short")
}
if plain[0] != 0xFA {
return nil, fmt.Errorf("openvpn compressed packet not supported (byte: 0x%02x)", plain[0])
}
plain = plain[1:]
}
return plain, nil
}
func (d *DataChannel) dataHeader() []byte {
if d.peerID != PeerIDUnset {
return []byte{
opcodeKeyID(PDataV2, d.keyID),
byte(d.peerID >> 16),
byte(d.peerID >> 8),
byte(d.peerID),
}
}
return []byte{opcodeKeyID(PDataV1, d.keyID)}
}
func ParsePeerID(options string) uint32 {
for _, field := range splitPushOptions(options) {
if len(field) > len("peer-id ") && field[:len("peer-id ")] == "peer-id " {
var id uint32
if _, err := fmt.Sscanf(field, "peer-id %d", &id); err == nil && id <= PeerIDUnset {
return id
}
}
}
return PeerIDUnset
}

View File

@@ -0,0 +1,33 @@
package openvpn
import (
"context"
"net/netip"
"time"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
wgTun "github.com/sagernet/wireguard-go/tun"
)
type Device interface {
wgTun.Device
N.Dialer
Start() error
}
type DeviceOptions struct {
Context context.Context
Logger logger.ContextLogger
Handler tun.Handler
UDPTimeout time.Duration
CreateDialer func(interfaceName string) N.Dialer
Name string
MTU uint32
Address []netip.Prefix
}
func NewDevice(options DeviceOptions) (Device, error) {
return newStackDevice(options)
}

View File

@@ -0,0 +1,309 @@
//go:build with_gvisor
package openvpn
import (
"context"
"net"
"net/netip"
"os"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/transport/wireguard"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
wgTun "github.com/sagernet/wireguard-go/tun"
)
type stackDevice struct {
ctx context.Context
logger log.ContextLogger
stack *stack.Stack
mtu uint32
events chan wgTun.Event
wgTun.Device
outbound chan *stack.PacketBuffer
packetOutbound chan *buf.Buffer
done chan struct{}
dispatcher stack.NetworkDispatcher
inet4Address netip.Addr
inet6Address netip.Addr
}
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
tunDevice := &stackDevice{
ctx: options.Context,
logger: options.Logger,
mtu: options.MTU,
events: make(chan wgTun.Event, 1),
outbound: make(chan *stack.PacketBuffer, 256),
packetOutbound: make(chan *buf.Buffer, 256),
done: make(chan struct{}),
}
ipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true)
if err != nil {
return nil, err
}
var (
inet4Address netip.Addr
inet6Address netip.Addr
)
for _, prefix := range options.Address {
addr := tun.AddressFromAddr(prefix.Addr())
protoAddr := tcpip.ProtocolAddress{
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: prefix.Bits(),
},
}
if prefix.Addr().Is4() {
inet4Address = prefix.Addr()
tunDevice.inet4Address = inet4Address
protoAddr.Protocol = ipv4.ProtocolNumber
} else {
inet6Address = prefix.Addr()
tunDevice.inet6Address = inet6Address
protoAddr.Protocol = ipv6.ProtocolNumber
}
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
if gErr != nil {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
}
}
tunDevice.stack = ipStack
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
}
return tunDevice, nil
}
func (w *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
addr := tcpip.FullAddress{
NIC: tun.DefaultNIC,
Port: destination.Port,
Addr: tun.AddressFromAddr(destination.Addr),
}
bind := tcpip.FullAddress{
NIC: tun.DefaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
if !w.inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(w.inet4Address)
} else {
if !w.inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(w.inet6Address)
}
switch N.NetworkName(network) {
case N.NetworkTCP:
tcpConn, err := wireguard.DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)
if err != nil {
return nil, err
}
return tcpConn, nil
case N.NetworkUDP:
udpConn, err := gonet.DialUDP(w.stack, &bind, &addr, networkProtocol)
if err != nil {
return nil, err
}
return udpConn, nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
bind := tcpip.FullAddress{
NIC: tun.DefaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(w.inet4Address)
} else {
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(w.inet6Address)
}
udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol)
if err != nil {
return nil, err
}
return udpConn, nil
}
func (w *stackDevice) Start() error {
w.events <- wgTun.EventUp
return nil
}
func (w *stackDevice) File() *os.File {
return nil
}
func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
select {
case packet, ok := <-w.outbound:
if !ok {
return 0, os.ErrClosed
}
defer packet.DecRef()
var copyN int
/*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) {
copyN += copy(bufs[0][offset+copyN:], view.AsSlice())
})*/
for _, view := range packet.AsSlices() {
copyN += copy(bufs[0][offset+copyN:], view)
}
sizes[0] = copyN
return 1, nil
case packet := <-w.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
case <-w.done:
return 0, os.ErrClosed
}
}
func (w *stackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
for _, b := range bufs {
b = b[offset:]
if len(b) == 0 {
continue
}
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(b) {
case header.IPv4Version:
networkProtocol = header.IPv4ProtocolNumber
case header.IPv6Version:
networkProtocol = header.IPv6ProtocolNumber
}
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(b),
})
w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)
packetBuffer.DecRef()
count++
}
return
}
func (w *stackDevice) Flush() error {
return nil
}
func (w *stackDevice) MTU() (int, error) {
return int(w.mtu), nil
}
func (w *stackDevice) Name() (string, error) {
return "sing-box", nil
}
func (w *stackDevice) Events() <-chan wgTun.Event {
return w.events
}
func (w *stackDevice) Close() error {
close(w.done)
close(w.events)
w.stack.Close()
for _, endpoint := range w.stack.CleanupEndpoints() {
endpoint.Abort()
}
w.stack.Wait()
return nil
}
func (w *stackDevice) BatchSize() int {
return 1
}
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
type wireEndpoint stackDevice
func (ep *wireEndpoint) MTU() uint32 {
return ep.mtu
}
func (ep *wireEndpoint) SetMTU(mtu uint32) {
}
func (ep *wireEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (ep *wireEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
}
func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityRXChecksumOffload
}
func (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
ep.dispatcher = dispatcher
}
func (ep *wireEndpoint) IsAttached() bool {
return ep.dispatcher != nil
}
func (ep *wireEndpoint) Wait() {
}
func (ep *wireEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (ep *wireEndpoint) AddHeader(buffer *stack.PacketBuffer) {
}
func (ep *wireEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
return true
}
func (ep *wireEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {
for _, packetBuffer := range list.AsSlice() {
packetBuffer.IncRef()
select {
case <-ep.done:
return 0, &tcpip.ErrClosedForSend{}
case ep.outbound <- packetBuffer:
}
}
return list.Len(), nil
}
func (ep *wireEndpoint) Close() {
}
func (ep *wireEndpoint) SetOnCloseAction(f func()) {
}

View File

@@ -0,0 +1,13 @@
//go:build !with_gvisor
package openvpn
import "github.com/sagernet/sing-tun"
func newStackDevice(options DeviceOptions) (Device, error) {
return nil, tun.ErrGVisorNotIncluded
}
func newSystemStackDevice(options DeviceOptions) (Device, error) {
return nil, tun.ErrGVisorNotIncluded
}

View File

@@ -0,0 +1,250 @@
package openvpn
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"encoding/binary"
"errors"
"fmt"
"hash"
)
const (
KeyMethod2 = 2
keySourcePreMasterSize = 48
keySourceRandomSize = 32
maxCipherKeyLength = 64
maxHMACKeyLength = 64
keyBlockSize = 2 * (maxCipherKeyLength + maxHMACKeyLength)
keyExpansionID = "OpenVPN"
)
type KeySource struct {
PreMaster [keySourcePreMasterSize]byte
Random1 [keySourceRandomSize]byte
Random2 [keySourceRandomSize]byte
}
type KeySource2 struct {
Client KeySource
Server KeySource
}
type KeyMaterial struct {
SendCipherKey []byte
SendHMACKey []byte
RecvCipherKey []byte
RecvHMACKey []byte
}
type KeyMethod2Record struct {
Sources KeySource2
Options string
Username string
Password string
PeerInfo string
}
func NewClientKeyMethod2Record(options, peerInfo, username, password string) (*KeyMethod2Record, error) {
var record KeyMethod2Record
if _, err := rand.Read(record.Sources.Client.PreMaster[:]); err != nil {
return nil, err
}
if _, err := rand.Read(record.Sources.Client.Random1[:]); err != nil {
return nil, err
}
if _, err := rand.Read(record.Sources.Client.Random2[:]); err != nil {
return nil, err
}
record.Options = options
record.PeerInfo = peerInfo
record.Username = username
record.Password = password
return &record, nil
}
func (r *KeyMethod2Record) MarshalClient() ([]byte, error) {
if r == nil {
return nil, errors.New("nil key method 2 record")
}
out := make([]byte, 0, 4+1+keySourcePreMasterSize+keySourceRandomSize*2+len(r.Options)+16)
out = binary.BigEndian.AppendUint32(out, 0)
out = append(out, KeyMethod2)
out = append(out, r.Sources.Client.PreMaster[:]...)
out = append(out, r.Sources.Client.Random1[:]...)
out = append(out, r.Sources.Client.Random2[:]...)
out = appendOpenVPNString(out, r.Options)
out = appendOpenVPNString(out, r.Username)
out = appendOpenVPNString(out, r.Password)
out = appendOpenVPNString(out, r.PeerInfo)
return out, nil
}
func ParseServerKeyMethod2Record(packet []byte) (*KeyMethod2Record, error) {
if len(packet) < 4+1+keySourceRandomSize*2 {
return nil, errors.New("key method 2 packet too short")
}
if binary.BigEndian.Uint32(packet[:4]) != 0 {
return nil, errors.New("invalid key method 2 prefix")
}
if packet[4]&0x0f != KeyMethod2 {
return nil, fmt.Errorf("unsupported key method %d", packet[4])
}
offset := 5
record := &KeyMethod2Record{}
copy(record.Sources.Server.Random1[:], packet[offset:offset+keySourceRandomSize])
offset += keySourceRandomSize
copy(record.Sources.Server.Random2[:], packet[offset:offset+keySourceRandomSize])
offset += keySourceRandomSize
var err error
record.Options, offset, err = readOpenVPNString(packet, offset)
if err != nil {
return nil, fmt.Errorf("read options: %w", err)
}
record.Username, offset, _ = readOpenVPNString(packet, offset)
record.Password, offset, _ = readOpenVPNString(packet, offset)
record.PeerInfo, _, _ = readOpenVPNString(packet, offset)
return record, nil
}
func DeriveClientKeyMaterial(sources KeySource2, clientSession, serverSession SessionID, cipherKeyLen int) (*KeyMaterial, error) {
if cipherKeyLen != 16 && cipherKeyLen != 32 {
return nil, fmt.Errorf("unsupported data cipher key length %d", cipherKeyLen)
}
var master [48]byte
if err := openvpnPRF(
sources.Client.PreMaster[:],
keyExpansionID+" master secret",
sources.Client.Random1[:],
sources.Server.Random1[:],
nil,
nil,
master[:],
); err != nil {
return nil, err
}
keyBlock := make([]byte, keyBlockSize)
if err := openvpnPRF(
master[:],
keyExpansionID+" key expansion",
sources.Client.Random2[:],
sources.Server.Random2[:],
clientSession[:],
serverSession[:],
keyBlock,
); err != nil {
return nil, err
}
clientToServer := keyBlock[:maxCipherKeyLength+maxHMACKeyLength]
serverToClient := keyBlock[maxCipherKeyLength+maxHMACKeyLength:]
return &KeyMaterial{
SendCipherKey: cloneBytes(clientToServer[:cipherKeyLen]),
SendHMACKey: cloneBytes(clientToServer[maxCipherKeyLength : maxCipherKeyLength+maxHMACKeyLength]),
RecvCipherKey: cloneBytes(serverToClient[:cipherKeyLen]),
RecvHMACKey: cloneBytes(serverToClient[maxCipherKeyLength : maxCipherKeyLength+maxHMACKeyLength]),
}, nil
}
func InstallScriptOptionsString(proto, cipher, auth string) string {
protoName := "UDPv4"
if proto == ProtoTCP {
protoName = "TCPv4_CLIENT"
}
keysize := "128"
switch cipher {
case CipherAES192GCM, CipherAES192CBC:
keysize = "192"
case CipherAES256GCM, CipherAES256CBC, CipherCHACHA20POLY:
keysize = "256"
}
return fmt.Sprintf("V4,dev-type tun,link-mtu 1550,tun-mtu 1500,proto %s,cipher %s,auth %s,keysize %s,key-method 2,tls-client", protoName, cipher, auth, keysize)
}
func InstallScriptPeerInfo(cipher string) string {
if cipher != "" {
return "IV_VER=sing-box-openvpn\nIV_PROTO=6\nIV_CIPHERS=" + cipher + "\n"
}
return "IV_VER=sing-box-openvpn\nIV_PROTO=6\nIV_CIPHERS=AES-256-GCM:AES-192-GCM:AES-128-GCM:AES-256-CBC:AES-192-CBC:AES-128-CBC:CHACHA20-POLY1305\n"
}
func appendOpenVPNString(out []byte, s string) []byte {
if s == "" {
return binary.BigEndian.AppendUint16(out, 0)
}
if len(s)+1 > 0xffff {
s = s[:0xfffe]
}
out = binary.BigEndian.AppendUint16(out, uint16(len(s)+1))
out = append(out, s...)
out = append(out, 0)
return out
}
func readOpenVPNString(packet []byte, offset int) (string, int, error) {
if offset+2 > len(packet) {
return "", offset, ioStringEOF
}
size := int(binary.BigEndian.Uint16(packet[offset : offset+2]))
offset += 2
if size == 0 {
return "", offset, nil
}
if offset+size > len(packet) {
return "", offset, ioStringEOF
}
raw := packet[offset : offset+size]
offset += size
if raw[len(raw)-1] == 0 {
raw = raw[:len(raw)-1]
}
return string(raw), offset, nil
}
var ioStringEOF = errors.New("openvpn string truncated")
func openvpnPRF(secret []byte, label string, clientSeed, serverSeed, clientSession, serverSession []byte, out []byte) error {
seed := make([]byte, 0, len(label)+len(clientSeed)+len(serverSeed)+len(clientSession)+len(serverSession))
seed = append(seed, label...)
seed = append(seed, clientSeed...)
seed = append(seed, serverSeed...)
seed = append(seed, clientSession...)
seed = append(seed, serverSession...)
split := (len(secret) + 1) / 2
s1 := secret[:split]
s2 := secret[len(secret)-split:]
md5Out := pHash(md5.New, s1, seed, len(out))
sha1Out := pHash(sha1.New, s2, seed, len(out))
for i := range out {
out[i] = md5Out[i] ^ sha1Out[i]
}
return nil
}
func pHash(newHash func() hash.Hash, secret, seed []byte, size int) []byte {
out := make([]byte, 0, size)
a := hmacSum(newHash, secret, seed)
for len(out) < size {
chunkInput := make([]byte, 0, len(a)+len(seed))
chunkInput = append(chunkInput, a...)
chunkInput = append(chunkInput, seed...)
out = append(out, hmacSum(newHash, secret, chunkInput)...)
a = hmacSum(newHash, secret, a)
}
return out[:size]
}
func hmacSum(newHash func() hash.Hash, key, data []byte) []byte {
mac := hmac.New(newHash, key)
_, _ = mac.Write(data)
return mac.Sum(nil)
}

92
transport/openvpn/mux.go Normal file
View File

@@ -0,0 +1,92 @@
package openvpn
import (
"context"
"net"
"sync"
)
type PacketMux struct {
io PacketIO
control chan []byte
data chan []byte
done chan struct{}
once sync.Once
}
func NewPacketMux(io PacketIO) *PacketMux {
return &PacketMux{
io: io,
control: make(chan []byte, 64),
data: make(chan []byte, 256),
done: make(chan struct{}),
}
}
func (m *PacketMux) Run(ctx context.Context) {
defer m.Close()
for ctx.Err() == nil {
packet, err := m.io.ReadPacket(ctx)
if err != nil {
return
}
if len(packet) == 0 {
continue
}
opcode, _ := parseOpcodeKeyID(packet[0])
ch := m.data
if opcode.IsControl() {
ch = m.control
}
select {
case ch <- packet:
case <-ctx.Done():
return
case <-m.done:
return
}
}
}
func (m *PacketMux) ReadPacket(ctx context.Context) ([]byte, error) {
select {
case packet := <-m.control:
return packet, nil
case <-ctx.Done():
return nil, ctx.Err()
case <-m.done:
return nil, net.ErrClosed
}
}
func (m *PacketMux) ReadDataPacket(ctx context.Context) ([]byte, error) {
select {
case packet := <-m.data:
return packet, nil
case <-ctx.Done():
return nil, ctx.Err()
case <-m.done:
return nil, net.ErrClosed
}
}
func (m *PacketMux) WritePacket(ctx context.Context, packet []byte) error {
return m.io.WritePacket(ctx, packet)
}
func (m *PacketMux) Close() error {
m.once.Do(func() {
close(m.done)
_ = m.io.Close()
})
return nil
}
func (m *PacketMux) LocalAddr() net.Addr {
return m.io.LocalAddr()
}
func (m *PacketMux) RemoteAddr() net.Addr {
return m.io.RemoteAddr()
}

254
transport/openvpn/packet.go Normal file
View File

@@ -0,0 +1,254 @@
package openvpn
import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
)
type ControlCrypt interface {
Wrap(header []byte, packetID uint32, unixTime uint32, plaintext []byte) ([]byte, error)
Unwrap(packet []byte) (header []byte, packetID uint32, unixTime uint32, plaintext []byte, err error)
}
const (
KeyIDMask = 0x07
OpcodeShift = 3
PControlHardResetClientV1 Opcode = 1
PControlHardResetServerV1 Opcode = 2
PControlSoftResetV1 Opcode = 3
PControlV1 Opcode = 4
PAckV1 Opcode = 5
PDataV1 Opcode = 6
PControlHardResetClientV2 Opcode = 7
PControlHardResetServerV2 Opcode = 8
PDataV2 Opcode = 9
PControlHardResetClientV3 Opcode = 10
PControlWKCV1 Opcode = 11
SessionIDSize = 8
)
type Opcode uint8
func (o Opcode) String() string {
switch o {
case PControlHardResetClientV1:
return "P_CONTROL_HARD_RESET_CLIENT_V1"
case PControlHardResetServerV1:
return "P_CONTROL_HARD_RESET_SERVER_V1"
case PControlSoftResetV1:
return "P_CONTROL_SOFT_RESET_V1"
case PControlV1:
return "P_CONTROL_V1"
case PAckV1:
return "P_ACK_V1"
case PDataV1:
return "P_DATA_V1"
case PControlHardResetClientV2:
return "P_CONTROL_HARD_RESET_CLIENT_V2"
case PControlHardResetServerV2:
return "P_CONTROL_HARD_RESET_SERVER_V2"
case PDataV2:
return "P_DATA_V2"
case PControlHardResetClientV3:
return "P_CONTROL_HARD_RESET_CLIENT_V3"
case PControlWKCV1:
return "P_CONTROL_WKC_V1"
default:
return "P_UNKNOWN"
}
}
func (o Opcode) IsControl() bool {
switch o {
case PControlHardResetClientV1, PControlHardResetServerV1, PControlSoftResetV1, PControlV1,
PAckV1, PControlHardResetClientV2, PControlHardResetServerV2, PControlHardResetClientV3, PControlWKCV1:
return true
default:
return false
}
}
func (o Opcode) HasMessageID() bool {
return o.IsControl() && o != PAckV1
}
type SessionID [SessionIDSize]byte
func NewSessionID() (SessionID, error) {
var id SessionID
_, err := rand.Read(id[:])
return id, err
}
type ControlPacket struct {
Opcode Opcode
KeyID uint8
LocalSession SessionID
AckIDs []uint32
AckRemoteSession SessionID
MessageID uint32
Payload []byte
}
func opcodeKeyID(opcode Opcode, keyID uint8) byte {
return byte(opcode)<<OpcodeShift | (keyID & KeyIDMask)
}
func parseOpcodeKeyID(b byte) (Opcode, uint8) {
return Opcode(b >> OpcodeShift), b & KeyIDMask
}
func EncodeControlPlain(p ControlPacket) ([]byte, error) {
if !p.Opcode.IsControl() {
return nil, fmt.Errorf("opcode %s is not a control opcode", p.Opcode)
}
if len(p.AckIDs) > 255 {
return nil, fmt.Errorf("too many ack ids: %d", len(p.AckIDs))
}
size := 1 + len(p.AckIDs)*4
if len(p.AckIDs) > 0 {
size += SessionIDSize
}
if p.Opcode.HasMessageID() {
size += 4 + len(p.Payload)
}
out := make([]byte, 0, size)
out = append(out, byte(len(p.AckIDs)))
for _, id := range p.AckIDs {
var b [4]byte
binary.BigEndian.PutUint32(b[:], id)
out = append(out, b[:]...)
}
if len(p.AckIDs) > 0 {
out = append(out, p.AckRemoteSession[:]...)
}
if p.Opcode.HasMessageID() {
var b [4]byte
binary.BigEndian.PutUint32(b[:], p.MessageID)
out = append(out, b[:]...)
out = append(out, p.Payload...)
}
return out, nil
}
func EncodeControlPacket(p ControlPacket) ([]byte, error) {
plain, err := EncodeControlPlain(p)
if err != nil {
return nil, err
}
header := make([]byte, TLSCryptHeaderSize)
header[0] = opcodeKeyID(p.Opcode, p.KeyID)
copy(header[1:], p.LocalSession[:])
out := make([]byte, 0, len(header)+len(plain))
out = append(out, header...)
out = append(out, plain...)
return out, nil
}
func EncodeControlPacketCrypt(p ControlPacket, crypt ControlCrypt, packetID uint32, unixTime uint32) ([]byte, error) {
plain, err := EncodeControlPlain(p)
if err != nil {
return nil, err
}
header := make([]byte, TLSCryptHeaderSize)
header[0] = opcodeKeyID(p.Opcode, p.KeyID)
copy(header[1:], p.LocalSession[:])
return crypt.Wrap(header, packetID, unixTime, plain)
}
func DecodeControlPacket(packet []byte) (*ControlPacket, error) {
if len(packet) < TLSCryptHeaderSize {
return nil, errors.New("control packet too short")
}
opcode, keyID := parseOpcodeKeyID(packet[0])
if !opcode.IsControl() {
return nil, fmt.Errorf("opcode %s is not a control opcode", opcode)
}
var local SessionID
copy(local[:], packet[1:])
plain := packet[TLSCryptHeaderSize:]
ackIDs, ackRemote, messageID, payload, err := DecodeControlPlain(opcode, plain)
if err != nil {
return nil, err
}
return &ControlPacket{
Opcode: opcode,
KeyID: keyID,
LocalSession: local,
AckIDs: ackIDs,
AckRemoteSession: ackRemote,
MessageID: messageID,
Payload: payload,
}, nil
}
func DecodeControlPacketCrypt(crypt ControlCrypt, packet []byte) (*ControlPacket, uint32, uint32, error) {
header, packetID, unixTime, plain, err := crypt.Unwrap(packet)
if err != nil {
return nil, 0, 0, err
}
if len(header) != TLSCryptHeaderSize {
return nil, 0, 0, fmt.Errorf("invalid control header length %d", len(header))
}
opcode, keyID := parseOpcodeKeyID(header[0])
if !opcode.IsControl() {
return nil, 0, 0, fmt.Errorf("opcode %s is not a control opcode", opcode)
}
var local SessionID
copy(local[:], header[1:])
ackIDs, ackRemote, messageID, payload, err := DecodeControlPlain(opcode, plain)
if err != nil {
return nil, 0, 0, err
}
return &ControlPacket{
Opcode: opcode,
KeyID: keyID,
LocalSession: local,
AckIDs: ackIDs,
AckRemoteSession: ackRemote,
MessageID: messageID,
Payload: payload,
}, packetID, unixTime, nil
}
func DecodeControlPlain(opcode Opcode, plain []byte) (ackIDs []uint32, ackRemote SessionID, messageID uint32, payload []byte, err error) {
if len(plain) < 1 {
return nil, SessionID{}, 0, nil, errors.New("control payload too short")
}
ackLen := int(plain[0])
offset := 1
if len(plain) < offset+ackLen*4 {
return nil, SessionID{}, 0, nil, errors.New("control ack array truncated")
}
ackIDs = make([]uint32, ackLen)
for i := 0; i < ackLen; i++ {
ackIDs[i] = binary.BigEndian.Uint32(plain[offset : offset+4])
offset += 4
}
if ackLen > 0 {
if len(plain) < offset+SessionIDSize {
return nil, SessionID{}, 0, nil, errors.New("control ack remote session truncated")
}
copy(ackRemote[:], plain[offset:offset+SessionIDSize])
offset += SessionIDSize
}
if opcode.HasMessageID() {
if len(plain) < offset+4 {
return nil, SessionID{}, 0, nil, errors.New("control message id truncated")
}
messageID = binary.BigEndian.Uint32(plain[offset : offset+4])
offset += 4
payload = cloneBytes(plain[offset:])
} else if len(plain) != offset {
return nil, SessionID{}, 0, nil, errors.New("ack packet has trailing payload")
}
return ackIDs, ackRemote, messageID, payload, nil
}

139
transport/openvpn/push.go Normal file
View File

@@ -0,0 +1,139 @@
package openvpn
import (
"fmt"
"net/netip"
"strconv"
"strings"
)
const PushRequest = "PUSH_REQUEST"
type PushReply struct {
Raw string
Prefixes []netip.Prefix
DNS []netip.Addr
PeerID uint32
Cipher string
Ping uint32
MTU uint32
CompLZO bool
Redirect bool
BlockIPv6 bool
}
func ParsePushReply(message string) (*PushReply, error) {
message = strings.TrimRight(message, "\x00")
if !strings.HasPrefix(message, "PUSH_REPLY") {
return nil, fmt.Errorf("unexpected openvpn push message %q", message)
}
reply := &PushReply{
Raw: message,
PeerID: PeerIDUnset,
}
for _, option := range splitPushOptions(message) {
fields := strings.Fields(option)
if len(fields) == 0 {
continue
}
switch fields[0] {
case "ifconfig":
if len(fields) >= 3 {
prefix, err := parseIPv4Ifconfig(fields[1], fields[2])
if err != nil {
return nil, err
}
reply.Prefixes = append(reply.Prefixes, prefix)
}
case "ifconfig-ipv6":
if len(fields) >= 2 {
prefix, err := netip.ParsePrefix(fields[1])
if err != nil {
return nil, fmt.Errorf("parse pushed ipv6 address %q: %w", fields[1], err)
}
reply.Prefixes = append(reply.Prefixes, prefix)
}
case "dhcp-option":
if len(fields) >= 3 && fields[1] == "DNS" {
if addr, err := netip.ParseAddr(fields[2]); err == nil {
reply.DNS = append(reply.DNS, addr)
}
}
case "peer-id":
if len(fields) >= 2 {
id, err := strconv.ParseUint(fields[1], 10, 24)
if err != nil {
return nil, fmt.Errorf("parse pushed peer-id %q: %w", fields[1], err)
}
reply.PeerID = uint32(id)
}
case "redirect-gateway":
reply.Redirect = true
case "block-ipv6":
reply.BlockIPv6 = true
case "cipher":
if len(fields) >= 2 {
reply.Cipher = fields[1]
}
case "ping":
if len(fields) >= 2 {
if v, err := strconv.ParseUint(fields[1], 10, 32); err == nil {
reply.Ping = uint32(v)
}
}
case "tun-mtu":
if len(fields) >= 2 {
if v, err := strconv.ParseUint(fields[1], 10, 32); err == nil {
reply.MTU = uint32(v)
}
}
case "comp-lzo":
reply.CompLZO = true
}
}
if len(reply.Prefixes) == 0 {
return nil, fmt.Errorf("openvpn push reply missing ifconfig address")
}
return reply, nil
}
func splitPushOptions(message string) []string {
message = strings.TrimRight(message, "\x00")
parts := strings.Split(message, ",")
if len(parts) > 0 && parts[0] == "PUSH_REPLY" {
parts = parts[1:]
}
out := parts[:0]
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
out = append(out, part)
}
}
return out
}
func parseIPv4Ifconfig(address, mask string) (netip.Prefix, error) {
addr, err := netip.ParseAddr(address)
if err != nil {
return netip.Prefix{}, fmt.Errorf("parse pushed ipv4 address %q: %w", address, err)
}
maskAddr, err := netip.ParseAddr(mask)
if err != nil {
return netip.Prefix{}, fmt.Errorf("parse pushed ipv4 mask %q: %w", mask, err)
}
if !addr.Is4() || !maskAddr.Is4() {
return netip.Prefix{}, fmt.Errorf("openvpn ifconfig requires ipv4 address and mask")
}
maskBytes := maskAddr.As4()
ones := 0
for _, b := range maskBytes {
for i := 7; i >= 0; i-- {
if b&(1<<i) == 0 {
return netip.PrefixFrom(addr, ones), nil
}
ones++
}
}
return netip.PrefixFrom(addr, ones), nil
}

View File

@@ -0,0 +1,100 @@
package openvpn
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"errors"
"fmt"
"hash"
)
type TLSAuth struct {
sendHMACKey []byte
recvHMACKey []byte
newHash func() hash.Hash
hmacSize int
}
func NewTLSAuth(staticKey []byte, keyDirection int, auth string) (*TLSAuth, error) {
if len(staticKey) != staticKeySize {
return nil, fmt.Errorf("invalid tls-auth static key length %d, expected %d", len(staticKey), staticKeySize)
}
key0 := staticKey[:keySlotSize]
key1 := staticKey[keySlotSize:]
var sendSlot, recvSlot []byte
if keyDirection == 1 {
sendSlot = key1
recvSlot = key0
} else {
sendSlot = key0
recvSlot = key1
}
var newHash func() hash.Hash
var hmacSize int
switch auth {
case AuthSHA256:
newHash = sha256.New
hmacSize = sha256.Size
case AuthSHA384:
newHash = sha512.New384
hmacSize = 48
case AuthSHA512:
newHash = sha512.New
hmacSize = sha512.Size
default:
newHash = sha1.New
hmacSize = sha1.Size
}
return &TLSAuth{
sendHMACKey: cloneBytes(sendSlot[64 : 64+hmacSize]),
recvHMACKey: cloneBytes(recvSlot[64 : 64+hmacSize]),
newHash: newHash,
hmacSize: hmacSize,
}, nil
}
func (a *TLSAuth) Wrap(header []byte, packetID uint32, unixTime uint32, plaintext []byte) ([]byte, error) {
if len(header) != TLSCryptHeaderSize {
return nil, fmt.Errorf("invalid tls-auth header length %d, expected %d", len(header), TLSCryptHeaderSize)
}
var pid [TLSCryptPIDSize]byte
binary.BigEndian.PutUint32(pid[:4], packetID)
binary.BigEndian.PutUint32(pid[4:], unixTime)
mac := hmac.New(a.newHash, a.sendHMACKey)
mac.Write(pid[:])
mac.Write(header)
mac.Write(plaintext)
tag := mac.Sum(nil)
out := make([]byte, 0, len(header)+a.hmacSize+TLSCryptPIDSize+len(plaintext))
out = append(out, header...)
out = append(out, tag...)
out = append(out, pid[:]...)
out = append(out, plaintext...)
return out, nil
}
func (a *TLSAuth) Unwrap(packet []byte) (header []byte, packetID uint32, unixTime uint32, plaintext []byte, err error) {
minLen := TLSCryptHeaderSize + a.hmacSize + TLSCryptPIDSize
if len(packet) < minLen {
return nil, 0, 0, nil, errors.New("tls-auth packet too short")
}
header = cloneBytes(packet[:TLSCryptHeaderSize])
tag := packet[TLSCryptHeaderSize : TLSCryptHeaderSize+a.hmacSize]
pidStart := TLSCryptHeaderSize + a.hmacSize
pid := packet[pidStart : pidStart+TLSCryptPIDSize]
plaintext = cloneBytes(packet[pidStart+TLSCryptPIDSize:])
mac := hmac.New(a.newHash, a.recvHMACKey)
mac.Write(pid)
mac.Write(header)
mac.Write(plaintext)
tagCheck := mac.Sum(nil)
if !hmac.Equal(tag, tagCheck) {
return nil, 0, 0, nil, errors.New("tls-auth authentication failed")
}
packetID = binary.BigEndian.Uint32(pid[:4])
unixTime = binary.BigEndian.Uint32(pid[4:])
return header, packetID, unixTime, plaintext, nil
}

View File

@@ -0,0 +1,128 @@
package openvpn
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
)
const (
TLSCryptHeaderSize = 1 + 8
TLSCryptPIDSize = 4 + 4
TLSCryptTagSize = sha256.Size
staticKeySize = 256
keySlotSize = 128
cipherKeySize = 32
hmacKeySize = 32
)
type TLSCrypt struct {
encryptCipherKey []byte
encryptHMACKey []byte
decryptCipherKey []byte
decryptHMACKey []byte
}
func NewTLSCrypt(staticKey []byte, client bool) (*TLSCrypt, error) {
if len(staticKey) != staticKeySize {
return nil, fmt.Errorf("invalid tls-crypt static key length %d, expected %d", len(staticKey), staticKeySize)
}
key0 := staticKey[:keySlotSize]
key1 := staticKey[keySlotSize:]
encrypt := key0
decrypt := key1
if client {
encrypt = key1
decrypt = key0
}
return &TLSCrypt{
encryptCipherKey: cloneBytes(encrypt[:cipherKeySize]),
encryptHMACKey: cloneBytes(encrypt[64 : 64+hmacKeySize]),
decryptCipherKey: cloneBytes(decrypt[:cipherKeySize]),
decryptHMACKey: cloneBytes(decrypt[64 : 64+hmacKeySize]),
}, nil
}
func (c *TLSCrypt) Wrap(header []byte, packetID uint32, unixTime uint32, plaintext []byte) ([]byte, error) {
if len(header) != TLSCryptHeaderSize {
return nil, fmt.Errorf("invalid tls-crypt header length %d, expected %d", len(header), TLSCryptHeaderSize)
}
ad := make([]byte, 0, len(header)+TLSCryptPIDSize)
ad = append(ad, header...)
var pid [TLSCryptPIDSize]byte
binary.BigEndian.PutUint32(pid[:4], packetID)
binary.BigEndian.PutUint32(pid[4:], unixTime)
ad = append(ad, pid[:]...)
tag := c.hmac(c.encryptHMACKey, ad, plaintext)
ciphertext, err := aes256ctr(c.encryptCipherKey, tag[:aes.BlockSize], plaintext)
if err != nil {
return nil, err
}
out := make([]byte, 0, len(ad)+len(tag)+len(ciphertext))
out = append(out, ad...)
out = append(out, tag...)
out = append(out, ciphertext...)
return out, nil
}
func (c *TLSCrypt) Unwrap(packet []byte) (header []byte, packetID uint32, unixTime uint32, plaintext []byte, err error) {
if len(packet) < TLSCryptHeaderSize+TLSCryptPIDSize+TLSCryptTagSize {
return nil, 0, 0, nil, errors.New("tls-crypt packet too short")
}
header = cloneBytes(packet[:TLSCryptHeaderSize])
adEnd := TLSCryptHeaderSize + TLSCryptPIDSize
tagEnd := adEnd + TLSCryptTagSize
ad := packet[:adEnd]
tag := packet[adEnd:tagEnd]
ciphertext := packet[tagEnd:]
plaintext, err = aes256ctr(c.decryptCipherKey, tag[:aes.BlockSize], ciphertext)
if err != nil {
return nil, 0, 0, nil, err
}
tagCheck := c.hmac(c.decryptHMACKey, ad, plaintext)
if !hmac.Equal(tag, tagCheck) {
return nil, 0, 0, nil, errors.New("tls-crypt authentication failed")
}
packetID = binary.BigEndian.Uint32(packet[TLSCryptHeaderSize : TLSCryptHeaderSize+4])
unixTime = binary.BigEndian.Uint32(packet[TLSCryptHeaderSize+4 : adEnd])
return header, packetID, unixTime, plaintext, nil
}
func (c *TLSCrypt) hmac(key []byte, parts ...[]byte) []byte {
mac := hmac.New(sha256.New, key)
for _, part := range parts {
_, _ = mac.Write(part)
}
return mac.Sum(nil)
}
func aes256ctr(key, iv, in []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
out := cloneBytes(in)
cipher.NewCTR(block, iv).XORKeyStream(out, out)
return out, nil
}
func cloneBytes(in []byte) []byte {
out := make([]byte, len(in))
copy(out, in)
return out
}

284
transport/openvpn/tunnel.go Normal file
View File

@@ -0,0 +1,284 @@
package openvpn
import (
"bytes"
"context"
"fmt"
"net"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/tls"
)
type TunnelOptions struct {
Dialer N.Dialer
Servers []option.ServerOptions
TLSConfig tls.Config
Config *ClientConfig
UDPTimeout time.Duration
ReconnectDelay time.Duration
PingInterval time.Duration
}
type Tunnel struct {
ctx context.Context
logger logger.ContextLogger
options TunnelOptions
device Device
client *Client
mtu uint32
serverIndex int
await chan struct{}
mu sync.Mutex
}
func NewTunnel(ctx context.Context, logger logger.ContextLogger, options TunnelOptions) (*Tunnel, error) {
if options.ReconnectDelay == 0 {
options.ReconnectDelay = 5 * time.Second
}
return &Tunnel{
ctx: ctx,
logger: logger,
options: options,
await: make(chan struct{}),
}, nil
}
func (t *Tunnel) Start() error {
go func() {
defer close(t.await)
client, err := t.getClient()
if err != nil {
t.logger.Error("OpenVPN connect: ", err)
return
}
t.mtu = 1500
if client.push.MTU > 0 {
t.mtu = client.push.MTU
}
deviceOptions := DeviceOptions{
Context: t.ctx,
Logger: t.logger,
UDPTimeout: t.options.UDPTimeout,
MTU: t.mtu,
Address: client.push.Prefixes,
}
device, err := NewDevice(deviceOptions)
if err != nil {
client.Close()
t.logger.Error("create OpenVPN device: ", err)
return
}
t.device = device
if err := device.Start(); err != nil {
client.Close()
t.logger.Error("start OpenVPN device: ", err)
return
}
t.maintainTunnel()
}()
return nil
}
func (t *Tunnel) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if !destination.Addr.IsValid() {
return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination")
}
return t.device.DialContext(ctx, network, destination)
}
func (t *Tunnel) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.Addr.IsValid() {
return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination")
}
return t.device.ListenPacket(ctx, destination)
}
func (t *Tunnel) Close() error {
t.mu.Lock()
defer t.mu.Unlock()
if t.client != nil {
t.client.Close()
t.client = nil
}
if t.device != nil {
return t.device.Close()
}
return nil
}
func (t *Tunnel) isTunnelInitialized(ctx context.Context) error {
select {
case <-t.await:
case <-ctx.Done():
return ctx.Err()
}
if t.device == nil {
return E.New("endpoint not initialized")
}
return nil
}
func (t *Tunnel) maintainTunnel() {
go func() {
bufs := make([][]byte, 1)
bufs[0] = make([]byte, t.mtu)
sizes := make([]int, 1)
for t.ctx.Err() == nil {
_, err := t.device.Read(bufs, sizes, 0)
if err != nil {
if t.ctx.Err() != nil {
return
}
continue
}
client, err := t.getClient()
if err != nil {
return
}
if err := client.WriteIPPacket(t.ctx, bufs[0][:sizes[0]]); err != nil {
if t.ctx.Err() != nil {
return
}
}
}
}()
go func() {
for t.ctx.Err() == nil {
client, err := t.getClient()
if err != nil {
return
}
packet, err := client.ReadIPPacket(t.ctx)
if err != nil {
if t.ctx.Err() != nil {
return
}
if ok := t.closeClient(client); ok {
t.logger.ErrorContext(t.ctx, fmt.Errorf("connection lost: %v", err))
}
continue
}
if bytes.Equal(packet, pingPayload) {
continue
}
if _, err := t.device.Write([][]byte{packet}, 0); err != nil {
if t.ctx.Err() != nil {
return
}
}
}
}()
pingInterval := t.options.PingInterval
if pingInterval == 0 && t.client != nil && t.client.push.Ping > 0 {
pingInterval = time.Duration(t.client.push.Ping) * time.Second
}
if pingInterval > 0 {
go func() {
ticker := time.NewTicker(pingInterval)
defer ticker.Stop()
for {
select {
case <-t.ctx.Done():
return
case <-ticker.C:
client, err := t.getClient()
if err != nil {
return
}
client.WriteIPPacket(t.ctx, pingPayload)
}
}
}()
}
<-t.ctx.Done()
}
func (t *Tunnel) getClient() (*Client, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.ctx.Err() != nil {
return nil, t.ctx.Err()
}
if t.client != nil {
return t.client, nil
}
timer := time.NewTimer(0)
defer timer.Stop()
for {
t.logger.InfoContext(t.ctx, "connecting to OpenVPN server")
client, err := t.connect()
if err != nil {
t.logger.ErrorContext(t.ctx, fmt.Errorf("connect failed: %v", err))
timer.Reset(t.options.ReconnectDelay)
select {
case <-t.ctx.Done():
return nil, t.ctx.Err()
case <-timer.C:
}
continue
}
t.client = client
t.logger.InfoContext(t.ctx, "connected to OpenVPN server")
return client, nil
}
}
func (t *Tunnel) closeClient(client *Client) bool {
t.mu.Lock()
defer t.mu.Unlock()
if client == t.client {
t.client.Close()
t.client = nil
return true
}
return false
}
func (t *Tunnel) connect() (*Client, error) {
config := t.options.Config
server := t.options.Servers[t.serverIndex].Build()
t.serverIndex = (t.serverIndex + 1) % len(t.options.Servers)
connectCtx, cancel := context.WithTimeout(t.ctx, t.options.ReconnectDelay)
defer cancel()
var conn net.Conn
var err error
if config.Proto == ProtoTCP {
conn, err = t.options.Dialer.DialContext(connectCtx, N.NetworkTCP, server)
} else {
conn, err = t.options.Dialer.DialContext(connectCtx, N.NetworkUDP, server)
}
if err != nil {
return nil, fmt.Errorf("dial openvpn server: %w", err)
}
var packetIO PacketIO
if config.Proto == ProtoTCP {
packetIO = NewTCPPacketIO(conn)
} else {
packetIO = NewDatagramPacketIO(conn)
}
client, err := NewClient(config, packetIO, t.options.TLSConfig)
if err != nil {
conn.Close()
return nil, err
}
_, err = client.Handshake(connectCtx)
if err != nil {
client.Close()
return nil, fmt.Errorf("openvpn handshake: %w", err)
}
return client, nil
}
var pingPayload = []byte{
0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48,
}