package main // Pure-Go VMess (AEAD) server, part of the in-process Xray emulator. // // This implements the modern "VMess AEAD" protocol (alterId = 0) exactly as // spoken by current Xray/v2ray clients: AEAD-authenticated request header, // AES-128-GCM / ChaCha20-Poly1305 chunked body with SHAKE-masked lengths, // optional global padding and authenticated length, and the AEAD response // header + body. Byte offsets, KDF labels and orderings follow the v2fly/xray // reference (proxy/vmess/{aead,encoding}). Legacy MD5-auth VMess (alterId > 0) // is intentionally not supported. // // TCP and UDP commands are served. Mux is intentionally not supported in the // native emulator yet. import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/md5" "crypto/rand" "crypto/sha256" "encoding/binary" "errors" "fmt" "hash" "hash/crc32" "hash/fnv" "io" "log" "net" "sync" "time" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/sha3" ) // ---------- constants ---------- const vmessCmdKeyMagic = "c48619fe-8f02-49e0-b9e9-edf763e17e21" const ( kdfSaltVMessAEADKDF = "VMess AEAD KDF" kdfLabelAuthIDEncryptionKey = "AES Auth ID Encryption" kdfLabelReqHeaderLenKey = "VMess Header AEAD Key_Length" kdfLabelReqHeaderLenIV = "VMess Header AEAD Nonce_Length" kdfLabelReqHeaderKey = "VMess Header AEAD Key" kdfLabelReqHeaderIV = "VMess Header AEAD Nonce" kdfLabelRespHeaderLenKey = "AEAD Resp Header Len Key" kdfLabelRespHeaderLenIV = "AEAD Resp Header Len IV" kdfLabelRespHeaderKey = "AEAD Resp Header Key" kdfLabelRespHeaderIV = "AEAD Resp Header IV" kdfLabelAuthLen = "auth_len" ) // VMess request option flags (header byte 34). const ( vmessOptChunkStream = 0x01 vmessOptChunkMasking = 0x04 vmessOptGlobalPadding = 0x08 vmessOptAuthenticatedLength = 0x10 ) // VMess security types (low nibble of header byte 35). const ( vmessSecAES128GCM = 3 vmessSecChaCha20Poly1305 = 4 vmessSecNone = 5 ) // VMess commands (header byte 37). const ( vmessCmdTCP = 1 vmessCmdUDP = 2 vmessCmdMux = 3 ) const vmessTimeWindowSeconds = 120 // ---------- KDF ("VMess AEAD KDF", nested HMAC-SHA256) ---------- type hmacCreator struct { parent *hmacCreator value []byte } func newHMAC(f func() hash.Hash, key []byte) hash.Hash { return hmac.New(f, key) } func (h *hmacCreator) create() hash.Hash { if h.parent == nil { return newHMAC(sha256.New, h.value) } return newHMAC(h.parent.create, h.value) } func vmessKDF(key []byte, path ...string) []byte { c := &hmacCreator{value: []byte(kdfSaltVMessAEADKDF)} for _, p := range path { c = &hmacCreator{value: []byte(p), parent: c} } h := c.create() h.Write(key) return h.Sum(nil) } func vmessKDF16(key []byte, path ...string) []byte { return vmessKDF(key, path...)[:16] } // ---------- command key / crypto helpers ---------- func vmessCmdKey(uuid [16]byte) [16]byte { h := md5.New() h.Write(uuid[:]) h.Write([]byte(vmessCmdKeyMagic)) var out [16]byte copy(out[:], h.Sum(nil)) return out } func newAESGCM(key []byte) cipher.AEAD { block, err := aes.NewCipher(key) if err != nil { panic(err) // only happens on wrong key length — a programmer error } gcm, err := cipher.NewGCM(block) if err != nil { panic(err) } return gcm } // vmessChaChaKey expands a 16-byte key into the 32-byte ChaCha20 key VMess uses: // MD5(key) || MD5(MD5(key)). func vmessChaChaKey(key []byte) []byte { h1 := md5.Sum(key) h2 := md5.Sum(h1[:]) out := make([]byte, 32) copy(out[0:16], h1[:]) copy(out[16:32], h2[:]) return out } func absInt64(v int64) int64 { if v < 0 { return -v } return v } func shakeNext(s sha3.ShakeHash) uint16 { var b [2]byte _, _ = s.Read(b[:]) return binary.BigEndian.Uint16(b[:]) } // ---------- auth ID matching ---------- // matchVMess tries every VMess client's auth-ID cipher against the 16-byte // auth ID, returning the client whose key decrypts to a CRC-valid, in-window // timestamp. This is O(clients) AES blocks per connection. func (ib *nativeInbound) matchVMess(authid [16]byte, now int64) *nativeXrayClient { ib.clientMu.RLock() defer ib.clientMu.RUnlock() for _, c := range ib.clientsByID { if c.authIDCipher == nil { continue } var dec [16]byte c.authIDCipher.Decrypt(dec[:], authid[:]) if crc32.ChecksumIEEE(dec[0:12]) != binary.BigEndian.Uint32(dec[12:16]) { continue } t := int64(binary.BigEndian.Uint64(dec[0:8])) if t < 0 || absInt64(t-now) > vmessTimeWindowSeconds { continue } return c } return nil } // ---------- request header ---------- type vmessRequest struct { bodyIV [16]byte bodyKey [16]byte respV byte option byte security byte command byte host string port uint16 } // openVMessHeader reads and decrypts the AEAD request header from r, given the // user's command key and the already-read 16-byte auth ID. r must be positioned // immediately after the auth ID. func openVMessHeader(cmdKey [16]byte, authid [16]byte, r io.Reader) ([]byte, error) { var lenBlock [18]byte // 2-byte length + 16-byte tag if _, err := io.ReadFull(r, lenBlock[:]); err != nil { return nil, err } var connNonce [8]byte if _, err := io.ReadFull(r, connNonce[:]); err != nil { return nil, err } aid := string(authid[:]) cn := string(connNonce[:]) lenGCM := newAESGCM(vmessKDF16(cmdKey[:], kdfLabelReqHeaderLenKey, aid, cn)) lenNonce := vmessKDF(cmdKey[:], kdfLabelReqHeaderLenIV, aid, cn)[:12] lenPlain, err := lenGCM.Open(nil, lenNonce, lenBlock[:], authid[:]) if err != nil { return nil, fmt.Errorf("vmess: header length decrypt: %w", err) } headerLen := int(binary.BigEndian.Uint16(lenPlain)) if headerLen < 38 || headerLen > 512 { return nil, fmt.Errorf("vmess: implausible header length %d", headerLen) } payload := make([]byte, headerLen+16) if _, err := io.ReadFull(r, payload); err != nil { return nil, err } payGCM := newAESGCM(vmessKDF16(cmdKey[:], kdfLabelReqHeaderKey, aid, cn)) payNonce := vmessKDF(cmdKey[:], kdfLabelReqHeaderIV, aid, cn)[:12] header, err := payGCM.Open(nil, payNonce, payload, authid[:]) if err != nil { return nil, fmt.Errorf("vmess: header payload decrypt: %w", err) } return header, nil } // parseVMessHeader parses the decrypted request header plaintext. func parseVMessHeader(h []byte) (vmessRequest, error) { var req vmessRequest if len(h) < 40 { return req, errors.New("vmess: header too short") } if h[0] != 1 { return req, fmt.Errorf("vmess: unsupported version %d", h[0]) } copy(req.bodyIV[:], h[1:17]) copy(req.bodyKey[:], h[17:33]) req.respV = h[33] req.option = h[34] req.security = h[35] & 0x0f paddingLen := int(h[35] >> 4) req.command = h[37] req.port = binary.BigEndian.Uint16(h[38:40]) host, next, err := parseVMessAddress(h, 40) if err != nil { return req, err } req.host = host if next+paddingLen+4 != len(h) { return req, fmt.Errorf("vmess: header length mismatch (addr end %d + pad %d + 4 != %d)", next, paddingLen, len(h)) } f := fnv.New32a() f.Write(h[:len(h)-4]) if binary.BigEndian.Uint32(h[len(h)-4:]) != f.Sum32() { return req, errors.New("vmess: header checksum mismatch") } return req, nil } func parseVMessAddress(h []byte, off int) (host string, next int, err error) { if off >= len(h) { return "", 0, io.ErrUnexpectedEOF } atyp := h[off] off++ switch atyp { case atypIPv4: if off+4 > len(h) { return "", 0, io.ErrUnexpectedEOF } host = net.IP(h[off : off+4]).String() off += 4 case atypIPv6: if off+16 > len(h) { return "", 0, io.ErrUnexpectedEOF } host = net.IP(h[off : off+16]).String() off += 16 case atypDomain: if off >= len(h) { return "", 0, io.ErrUnexpectedEOF } l := int(h[off]) off++ if off+l > len(h) { return "", 0, io.ErrUnexpectedEOF } host = string(h[off : off+l]) off += l default: return "", 0, fmt.Errorf("vmess: unknown address type %d", atyp) } return host, off, nil } // ---------- response header ---------- func writeVMessResponseHeader(w io.Writer, respBodyKey, respBodyIV [16]byte, respV byte) error { header := []byte{respV, 0, 0, 0} // V echo, option 0, command 0, command-data-length 0 lenGCM := newAESGCM(vmessKDF16(respBodyKey[:], kdfLabelRespHeaderLenKey)) lenNonce := vmessKDF(respBodyIV[:], kdfLabelRespHeaderLenIV)[:12] var lenPlain [2]byte binary.BigEndian.PutUint16(lenPlain[:], uint16(len(header))) lenSealed := lenGCM.Seal(nil, lenNonce, lenPlain[:], nil) // AAD nil payGCM := newAESGCM(vmessKDF16(respBodyKey[:], kdfLabelRespHeaderKey)) payNonce := vmessKDF(respBodyIV[:], kdfLabelRespHeaderIV)[:12] paySealed := payGCM.Seal(nil, payNonce, header, nil) // AAD nil out := make([]byte, 0, len(lenSealed)+len(paySealed)) out = append(out, lenSealed...) out = append(out, paySealed...) _, err := w.Write(out) return err } // ---------- authenticated-length size parser ---------- // vmessAuthLen encodes/decodes the 2-byte chunk length as an AEAD-sealed field // (option AuthenticatedLength). It always derives its key from the *request* // body key/IV, in both directions, per the reference. type vmessAuthLen struct { aead cipher.AEAD count uint16 ivTail [10]byte nonce [12]byte } func newVMessAuthLen(reqBodyKey, reqBodyIV [16]byte, chacha bool) *vmessAuthLen { keyMat := vmessKDF16(reqBodyKey[:], kdfLabelAuthLen) var aead cipher.AEAD if chacha { a, _ := chacha20poly1305.New(vmessChaChaKey(keyMat)) aead = a } else { aead = newAESGCM(keyMat) } al := &vmessAuthLen{aead: aead} copy(al.ivTail[:], reqBodyIV[2:12]) return al } func (al *vmessAuthLen) nextNonce() []byte { binary.BigEndian.PutUint16(al.nonce[0:2], al.count) copy(al.nonce[2:12], al.ivTail[:]) al.count++ return al.nonce[:12] } func (al *vmessAuthLen) decode(r io.Reader) (int, error) { var buf [18]byte if _, err := io.ReadFull(r, buf[:]); err != nil { return 0, err } plain, err := al.aead.Open(nil, al.nextNonce(), buf[:], nil) if err != nil { return 0, fmt.Errorf("vmess: auth-len decrypt: %w", err) } return int(binary.BigEndian.Uint16(plain)) + 16, nil } func (al *vmessAuthLen) encode(out *bytes.Buffer, size int) { var lenPlain [2]byte binary.BigEndian.PutUint16(lenPlain[:], uint16(size-16)) out.Write(al.aead.Seal(nil, al.nextNonce(), lenPlain[:], nil)) } // ---------- body chunk reader/writer ---------- const vmessMaxChunk = 64*1024 + 64 type vmessChunkReader struct { r io.Reader aead cipher.AEAD // nil for security "none" overhead int ivTail [10]byte count uint16 nonce [12]byte shake sha3.ShakeHash // non-nil when chunk masking is enabled authLen *vmessAuthLen // non-nil when authenticated length is enabled globalPad bool leftover []byte eof bool } func (cr *vmessChunkReader) nextNonce() []byte { binary.BigEndian.PutUint16(cr.nonce[0:2], cr.count) copy(cr.nonce[2:12], cr.ivTail[:]) cr.count++ return cr.nonce[:12] } func (cr *vmessChunkReader) readChunk() ([]byte, error) { // Padding length is always drawn from the SHAKE stream before the size. pad := 0 if cr.shake != nil && cr.globalPad { pad = int(shakeNext(cr.shake) % 64) } var size int switch { case cr.authLen != nil: s, err := cr.authLen.decode(cr.r) if err != nil { return nil, err } size = s case cr.shake != nil: var b [2]byte if _, err := io.ReadFull(cr.r, b[:]); err != nil { return nil, err } size = int(shakeNext(cr.shake) ^ binary.BigEndian.Uint16(b[:])) default: var b [2]byte if _, err := io.ReadFull(cr.r, b[:]); err != nil { return nil, err } size = int(binary.BigEndian.Uint16(b[:])) } // size == overhead + pad means an empty (terminating) chunk. if size == cr.overhead+pad { return nil, io.EOF } if size < cr.overhead+pad || size > vmessMaxChunk { return nil, fmt.Errorf("vmess: bad chunk size %d", size) } data := make([]byte, size) if _, err := io.ReadFull(cr.r, data); err != nil { return nil, err } sealed := data[:size-pad] // trailing pad bytes are clear-text, discarded if cr.aead == nil { return sealed, nil } plain, err := cr.aead.Open(nil, cr.nextNonce(), sealed, nil) if err != nil { return nil, fmt.Errorf("vmess: body decrypt: %w", err) } return plain, nil } func (cr *vmessChunkReader) Read(p []byte) (int, error) { for len(cr.leftover) == 0 { if cr.eof { return 0, io.EOF } chunk, err := cr.readChunk() if err == io.EOF { cr.eof = true return 0, io.EOF } if err != nil { return 0, err } cr.leftover = chunk } n := copy(p, cr.leftover) cr.leftover = cr.leftover[n:] return n, nil } type vmessChunkWriter struct { w io.Writer aead cipher.AEAD overhead int ivTail [10]byte count uint16 nonce [12]byte shake sha3.ShakeHash authLen *vmessAuthLen globalPad bool } func (cw *vmessChunkWriter) nextNonce() []byte { binary.BigEndian.PutUint16(cw.nonce[0:2], cw.count) copy(cw.nonce[2:12], cw.ivTail[:]) cw.count++ return cw.nonce[:12] } func (cw *vmessChunkWriter) writeChunk(p []byte) error { var out bytes.Buffer pad := 0 if cw.shake != nil && cw.globalPad { pad = int(shakeNext(cw.shake) % 64) } size := len(p) + cw.overhead + pad switch { case cw.authLen != nil: cw.authLen.encode(&out, size) case cw.shake != nil: var b [2]byte binary.BigEndian.PutUint16(b[:], shakeNext(cw.shake)^uint16(size)) out.Write(b[:]) default: var b [2]byte binary.BigEndian.PutUint16(b[:], uint16(size)) out.Write(b[:]) } if cw.aead != nil { out.Write(cw.aead.Seal(nil, cw.nextNonce(), p, nil)) } else { out.Write(p) } if pad > 0 { padBytes := make([]byte, pad) _, _ = rand.Read(padBytes) out.Write(padBytes) } _, err := cw.w.Write(out.Bytes()) return err } // ---------- vmessConn: net.Conn view of a decoded VMess session ---------- type nativeVMessStream interface { net.Conn ReadPacket() ([]byte, error) WritePacket([]byte) error } type vmessConn struct { net.Conn reader *vmessChunkReader writer *vmessChunkWriter terminateOnClose bool closeOnce sync.Once } func (c *vmessConn) Read(p []byte) (int, error) { return c.reader.Read(p) } // ReadPacket returns exactly one decrypted VMess body chunk. UDP-over-VMess uses // one VMess chunk per UDP datagram, so packet handling must bypass the streamy // Read method that can merge/split chunks. func (c *vmessConn) ReadPacket() ([]byte, error) { return c.reader.readChunk() } func (c *vmessConn) WritePacket(p []byte) error { return c.writer.writeChunk(p) } func (c *vmessConn) Write(p []byte) (int, error) { // Bound each chunk well under the uint16 length field. const maxChunk = 16 * 1024 total := 0 for len(p) > 0 { n := len(p) if n > maxChunk { n = maxChunk } if err := c.writer.writeChunk(p[:n]); err != nil { return total, err } total += n p = p[n:] } return total, nil } func (c *vmessConn) Close() error { c.closeOnce.Do(func() { if c.terminateOnClose { _ = c.writer.writeChunk(nil) // terminating empty chunk } }) return c.Conn.Close() } // vmessRawConn is used for VMess security=none when the client did not request // ChunkStream. Xray's own server returns a raw reader/writer in that exact case; // treating the following TLS ClientHello/HTTP bytes as a VMess chunk length makes // real clients authenticate but then pass no data. type vmessRawConn struct { net.Conn } func (c *vmessRawConn) ReadPacket() ([]byte, error) { buf := make([]byte, 64*1024) n, err := c.Conn.Read(buf) if n > 0 { return buf[:n], nil } return nil, err } func (c *vmessRawConn) WritePacket(p []byte) error { _, err := c.Conn.Write(p) return err } func newVMessConn(stream net.Conn, req vmessRequest, respBodyKey, respBodyIV [16]byte) (nativeVMessStream, error) { chunkMask := req.option&vmessOptChunkMasking != 0 globalPad := req.option&vmessOptGlobalPadding != 0 authLen := req.option&vmessOptAuthenticatedLength != 0 chacha := req.security == vmessSecChaCha20Poly1305 if req.security == vmessSecNone && req.option&vmessOptChunkStream == 0 { return &vmessRawConn{Conn: stream}, nil } var readAEAD, writeAEAD cipher.AEAD overhead := 16 switch req.security { case vmessSecAES128GCM: readAEAD = newAESGCM(req.bodyKey[:]) writeAEAD = newAESGCM(respBodyKey[:]) case vmessSecChaCha20Poly1305: ra, _ := chacha20poly1305.New(vmessChaChaKey(req.bodyKey[:])) wa, _ := chacha20poly1305.New(vmessChaChaKey(respBodyKey[:])) readAEAD, writeAEAD = ra, wa case vmessSecNone: overhead = 0 default: return nil, fmt.Errorf("vmess: unsupported security %d", req.security) } cr := &vmessChunkReader{r: stream, aead: readAEAD, overhead: overhead, globalPad: globalPad} copy(cr.ivTail[:], req.bodyIV[2:12]) cw := &vmessChunkWriter{w: stream, aead: writeAEAD, overhead: overhead, globalPad: globalPad} copy(cw.ivTail[:], respBodyIV[2:12]) if chunkMask { rs := sha3.NewShake128() rs.Write(req.bodyIV[:]) cr.shake = rs ws := sha3.NewShake128() ws.Write(respBodyIV[:]) cw.shake = ws } if authLen { cr.authLen = newVMessAuthLen(req.bodyKey, req.bodyIV, chacha) cw.authLen = newVMessAuthLen(req.bodyKey, req.bodyIV, chacha) } return &vmessConn{Conn: stream, reader: cr, writer: cw, terminateOnClose: req.option&vmessOptChunkStream != 0 || req.security != vmessSecNone}, nil } // ---------- handler ---------- func (ib *nativeInbound) handleVMess(stream net.Conn, remote net.Addr) { _ = stream.SetReadDeadline(time.Now().Add(30 * time.Second)) var authid [16]byte if _, err := io.ReadFull(stream, authid[:]); err != nil { return } client := ib.matchVMess(authid, time.Now().Unix()) if client == nil { log.Printf("native xray: inbound %q rejected unknown/expired VMess auth id from %s", ib.tag, remote) return } header, err := openVMessHeader(client.cmdKey, authid, stream) if err != nil { log.Printf("native xray: inbound %q VMess header open failed from %s: %v", ib.tag, remote, err) return } req, err := parseVMessHeader(header) if err != nil { log.Printf("native xray: inbound %q VMess header parse failed from %s: %v", ib.tag, remote, err) return } _ = stream.SetReadDeadline(time.Time{}) if req.command != vmessCmdTCP && req.command != vmessCmdUDP { log.Printf("native xray: inbound %q VMess command %d not supported yet", ib.tag, req.command) return } respBodyKey := sha256.Sum256(req.bodyKey[:]) respBodyIV := sha256.Sum256(req.bodyIV[:]) var rk, riv [16]byte copy(rk[:], respBodyKey[:16]) copy(riv[:], respBodyIV[:16]) if err := writeVMessResponseHeader(stream, rk, riv, req.respV); err != nil { return } vc, err := newVMessConn(stream, req, rk, riv) if err != nil { log.Printf("native xray: inbound %q VMess codec: %v", ib.tag, err) return } switch req.command { case vmessCmdTCP: backend, target, err := ib.nativeDialTCP(req.host, req.port) if err != nil { log.Printf("native xray: inbound %q VMess TCP dial %s failed: %v", ib.tag, target, err) return } log.Printf("native xray: vmess/tcp user=%s src=%s -> %s (inbound %q)", client.email, backend.LocalAddr(), target, ib.tag) nativeTunnel(vc, backend, client.uuid, client.email, ib.upLimiter(), ib.downLimiter()) case vmessCmdUDP: backend, target, err := ib.nativeDialUDP(req.host, req.port) if err != nil { log.Printf("native xray: inbound %q VMess UDP dial %s failed: %v", ib.tag, target, err) return } log.Printf("native xray: vmess/udp user=%s src=%s -> %s (inbound %q)", client.email, backend.LocalAddr(), target, ib.tag) nativeVMessUDPTunnel(vc, backend, client.uuid, client.email, ib.upLimiter(), ib.downLimiter()) } }