浏览代码

Remove VMess UDP

V2Ray 10 年之前
父节点
当前提交
cd42e5551c
共有 6 个文件被更改,包括 73 次插入223 次删除
  1. 5 0
      proxy/vmess/config.go
  2. 0 140
      proxy/vmess/protocol/udp.go
  3. 0 41
      proxy/vmess/protocol/udp_test.go
  4. 46 6
      proxy/vmess/vmessin_udp.go
  5. 20 11
      proxy/vmess/vmessout.go
  6. 2 25
      spec/vmess.md

+ 5 - 0
proxy/vmess/config.go

@@ -3,6 +3,7 @@ package vmess
 import (
 	"encoding/json"
 	"net"
+	"strings"
 
 	"github.com/v2ray/v2ray-core/common/log"
 	v2net "github.com/v2ray/v2ray-core/common/net"
@@ -40,6 +41,10 @@ type VNextConfig struct {
 	Network string      `json:"network"`
 }
 
+func (config VNextConfig) HasNetwork(network string) bool {
+	return strings.Contains(config.Network, network)
+}
+
 func (config VNextConfig) ToVNextServer() VNextServer {
 	users := make([]user.User, 0, len(config.Users))
 	for _, user := range config.Users {

+ 0 - 140
proxy/vmess/protocol/udp.go

@@ -1,140 +0,0 @@
-package protocol
-
-import (
-	"crypto/aes"
-	"crypto/cipher"
-	"encoding/binary"
-	"hash/fnv"
-	"time"
-
-	"github.com/v2ray/v2ray-core/common/errors"
-	"github.com/v2ray/v2ray-core/common/log"
-	v2net "github.com/v2ray/v2ray-core/common/net"
-	"github.com/v2ray/v2ray-core/proxy/vmess/protocol/user"
-)
-
-type VMessUDP struct {
-	user    user.ID
-	version byte
-	address v2net.Address
-	data    []byte
-}
-
-func (message *VMessUDP) ToPacket() v2net.Packet {
-	dest := v2net.NewUDPDestination(message.address)
-	return v2net.NewPacket(dest, message.data, false)
-}
-
-func ReadVMessUDP(buffer []byte, userset user.UserSet) (*VMessUDP, error) {
-	userHash := buffer[:user.IDBytesLen]
-	userId, timeSec, valid := userset.GetUser(userHash)
-	if !valid {
-		return nil, errors.NewAuthenticationError(userHash)
-	}
-
-	buffer = buffer[user.IDBytesLen:]
-	aesCipher, err := aes.NewCipher(userId.CmdKey())
-	if err != nil {
-		return nil, err
-	}
-	aesStream := cipher.NewCFBDecrypter(aesCipher, user.Int64Hash(timeSec))
-	aesStream.XORKeyStream(buffer, buffer)
-
-	fnvHash := binary.BigEndian.Uint32(buffer[:4])
-	fnv1a := fnv.New32a()
-	fnv1a.Write(buffer[4:])
-	fnvHashActual := fnv1a.Sum32()
-
-	if fnvHash != fnvHashActual {
-		log.Warning("Unexpected fhv hash %d, should be %d", fnvHashActual, fnvHash)
-		return nil, errors.NewCorruptedPacketError()
-	}
-
-	buffer = buffer[4:]
-
-	vmess := &VMessUDP{
-		user:    *userId,
-		version: buffer[0],
-	}
-
-	// buffer[1] is reserved
-
-	port := binary.BigEndian.Uint16(buffer[2:4])
-	addrType := buffer[4]
-	var address v2net.Address
-	switch addrType {
-	case addrTypeIPv4:
-		address = v2net.IPAddress(buffer[5:9], port)
-		buffer = buffer[9:]
-	case addrTypeIPv6:
-		address = v2net.IPAddress(buffer[5:21], port)
-		buffer = buffer[21:]
-	case addrTypeDomain:
-		domainLength := buffer[5]
-		domain := string(buffer[6 : 6+domainLength])
-		address = v2net.DomainAddress(domain, port)
-		buffer = buffer[6+domainLength:]
-	default:
-		log.Warning("Unexpected address type %d", addrType)
-		return nil, errors.NewCorruptedPacketError()
-	}
-
-	vmess.address = address
-	vmess.data = buffer
-
-	return vmess, nil
-}
-
-func (vmess *VMessUDP) ToBytes(idHash user.CounterHash, randomRangeInt64 user.RandomInt64InRange, buffer []byte) []byte {
-	if buffer == nil {
-		buffer = make([]byte, 0, 2*1024)
-	}
-
-	counter := randomRangeInt64(time.Now().UTC().Unix(), 30)
-	hash := idHash.Hash(vmess.user.Bytes[:], counter)
-
-	buffer = append(buffer, hash...)
-	encryptBegin := 16
-
-	// Placeholder for fnv1a hash
-	buffer = append(buffer, byte(0), byte(0), byte(0), byte(0))
-	fnvHash := 16
-	fnvHashBegin := 20
-
-	buffer = append(buffer, vmess.version)
-	buffer = append(buffer, byte(0x00))
-	buffer = append(buffer, vmess.address.PortBytes()...)
-	switch {
-	case vmess.address.IsIPv4():
-		buffer = append(buffer, addrTypeIPv4)
-		buffer = append(buffer, vmess.address.IP()...)
-	case vmess.address.IsIPv6():
-		buffer = append(buffer, addrTypeIPv6)
-		buffer = append(buffer, vmess.address.IP()...)
-	case vmess.address.IsDomain():
-		buffer = append(buffer, addrTypeDomain)
-		buffer = append(buffer, byte(len(vmess.address.Domain())))
-		buffer = append(buffer, []byte(vmess.address.Domain())...)
-	}
-
-	buffer = append(buffer, vmess.data...)
-
-	fnv1a := fnv.New32a()
-	fnv1a.Write(buffer[fnvHashBegin:])
-	fnvHashValue := fnv1a.Sum32()
-
-	buffer[fnvHash] = byte(fnvHashValue >> 24)
-	buffer[fnvHash+1] = byte(fnvHashValue >> 16)
-	buffer[fnvHash+2] = byte(fnvHashValue >> 8)
-	buffer[fnvHash+3] = byte(fnvHashValue)
-
-	aesCipher, err := aes.NewCipher(vmess.user.CmdKey())
-	if err != nil {
-		log.Error("VMess failed to create AES cipher: %v", err)
-		return nil
-	}
-	aesStream := cipher.NewCFBEncrypter(aesCipher, user.Int64Hash(counter))
-	aesStream.XORKeyStream(buffer[encryptBegin:], buffer[encryptBegin:])
-
-	return buffer
-}

+ 0 - 41
proxy/vmess/protocol/udp_test.go

@@ -1,41 +0,0 @@
-package protocol
-
-import (
-	"testing"
-
-	v2net "github.com/v2ray/v2ray-core/common/net"
-	"github.com/v2ray/v2ray-core/proxy/vmess/protocol/user"
-	"github.com/v2ray/v2ray-core/testing/mocks"
-	"github.com/v2ray/v2ray-core/testing/unit"
-)
-
-func TestVMessUDPReadWrite(t *testing.T) {
-	assert := unit.Assert(t)
-
-	userId, err := user.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51")
-	assert.Error(err).IsNil()
-
-	userSet := mocks.MockUserSet{[]user.ID{}, make(map[string]int), make(map[string]int64)}
-	userSet.AddUser(user.User{userId})
-
-	message := &VMessUDP{
-		user:    userId,
-		version: byte(0x01),
-		address: v2net.DomainAddress("v2ray.com", 8372),
-		data:    []byte("An UDP message."),
-	}
-
-	mockTime := int64(1823730)
-	buffer := message.ToBytes(user.NewTimeHash(user.HMACHash{}), func(base int64, delta int) int64 { return mockTime }, nil)
-
-	userSet.UserHashes[string(buffer[:16])] = 0
-	userSet.Timestamps[string(buffer[:16])] = mockTime
-
-	messageRestored, err := ReadVMessUDP(buffer, &userSet)
-	assert.Error(err).IsNil()
-
-	assert.String(messageRestored.user.String).Equals(message.user.String)
-	assert.Byte(messageRestored.version).Equals(message.version)
-	assert.String(messageRestored.address.String()).Equals(message.address.String())
-	assert.Bytes(messageRestored.data).Equals(message.data)
-}

+ 46 - 6
proxy/vmess/vmessin_udp.go

@@ -1,8 +1,11 @@
 package vmess
 
 import (
+	"bytes"
+	"crypto/md5"
 	"net"
 
+	v2io "github.com/v2ray/v2ray-core/common/io"
 	"github.com/v2ray/v2ray-core/common/log"
 	v2net "github.com/v2ray/v2ray-core/common/net"
 	"github.com/v2ray/v2ray-core/proxy/vmess/protocol"
@@ -36,22 +39,59 @@ func (handler *VMessInboundHandler) AcceptPackets(conn *net.UDPConn) error {
 			log.Error("VMessIn failed to read UDP packets: %v", err)
 			return err
 		}
-		request, err := protocol.ReadVMessUDP(buffer[:nBytes], handler.clients)
+
+		reader := bytes.NewReader(buffer[:nBytes])
+		requestReader := protocol.NewVMessRequestReader(handler.clients)
+
+		request, err := requestReader.Read(reader)
+		if err != nil {
+			log.Warning("VMessIn: Invalid request from (%s): %v", addr.String(), err)
+			return err
+		}
+
+		cryptReader, err := v2io.NewAesDecryptReader(request.RequestKey[:], request.RequestIV[:], reader)
+		if err != nil {
+			log.Error("VMessIn: Failed to create decrypt reader: %v", err)
+			return err
+		}
+
+		data := make([]byte, bufferSize)
+		nBytes, err = cryptReader.Read(data)
 		if err != nil {
-			log.Error("VMessIn failed to parse UDP request: %v", err)
+			log.Warning("VMessIn: Unable to decrypt data: %v", err)
 			return err
 		}
 
-		udpPacket := request.ToPacket()
-		go handler.handlePacket(conn, udpPacket, addr)
+		packet := v2net.NewPacket(request.Destination(), data, false)
+		go handler.handlePacket(conn, request, packet, addr)
 	}
 }
 
-func (handler *VMessInboundHandler) handlePacket(conn *net.UDPConn, packet v2net.Packet, clientAddr *net.UDPAddr) {
+func (handler *VMessInboundHandler) handlePacket(conn *net.UDPConn, request *protocol.VMessRequest, packet v2net.Packet, clientAddr *net.UDPAddr) {
 	ray := handler.vPoint.DispatchToOutbound(packet)
 	close(ray.InboundInput())
 
+	responseKey := md5.Sum(request.RequestKey[:])
+	responseIV := md5.Sum(request.RequestIV[:])
+
+	buffer := bytes.NewBuffer(make([]byte, 0, bufferSize))
+
+	response := protocol.NewVMessResponse(request)
+	responseWriter, err := v2io.NewAesEncryptWriter(responseKey[:], responseIV[:], buffer)
+	if err != nil {
+		log.Error("VMessIn: Failed to create encrypt writer: %v", err)
+		return
+	}
+	responseWriter.Write(response[:])
+
+	hasData := false
+
 	if data, ok := <-ray.InboundOutput(); ok {
-		conn.WriteToUDP(data, clientAddr)
+		hasData = true
+		responseWriter.Write(data)
+	}
+
+	if hasData {
+		conn.WriteToUDP(buffer.Bytes(), clientAddr)
 	}
 }

+ 20 - 11
proxy/vmess/vmessout.go

@@ -27,12 +27,13 @@ type VNextServer struct {
 }
 
 type VMessOutboundHandler struct {
-	vPoint    *core.Point
-	packet    v2net.Packet
-	vNextList []VNextServer
+	vPoint       *core.Point
+	packet       v2net.Packet
+	vNextList    []VNextServer
+	vNextListUDP []VNextServer
 }
 
-func NewVMessOutboundHandler(vp *core.Point, vNextList []VNextServer, firstPacket v2net.Packet) *VMessOutboundHandler {
+func NewVMessOutboundHandler(vp *core.Point, vNextList, vNextListUDP []VNextServer, firstPacket v2net.Packet) *VMessOutboundHandler {
 	return &VMessOutboundHandler{
 		vPoint:    vp,
 		packet:    firstPacket,
@@ -40,8 +41,8 @@ func NewVMessOutboundHandler(vp *core.Point, vNextList []VNextServer, firstPacke
 	}
 }
 
-func (handler *VMessOutboundHandler) pickVNext() (v2net.Destination, user.User) {
-	vNextLen := len(handler.vNextList)
+func pickVNext(serverList []VNextServer) (v2net.Destination, user.User) {
+	vNextLen := len(serverList)
 	if vNextLen == 0 {
 		panic("VMessOut: Zero vNext is configured.")
 	}
@@ -50,7 +51,7 @@ func (handler *VMessOutboundHandler) pickVNext() (v2net.Destination, user.User)
 		vNextIndex = mrand.Intn(vNextLen)
 	}
 
-	vNext := handler.vNextList[vNextIndex]
+	vNext := serverList[vNextIndex]
 	vNextUserLen := len(vNext.Users)
 	if vNextUserLen == 0 {
 		panic("VMessOut: Zero User account.")
@@ -64,7 +65,7 @@ func (handler *VMessOutboundHandler) pickVNext() (v2net.Destination, user.User)
 }
 
 func (handler *VMessOutboundHandler) Start(ray core.OutboundRay) error {
-	vNextAddress, vNextUser := handler.pickVNext()
+	vNextAddress, vNextUser := pickVNext(handler.vNextList)
 
 	command := protocol.CmdTCP
 	if handler.packet.Destination().IsUDP() {
@@ -180,7 +181,8 @@ func handleResponse(conn *net.TCPConn, request *protocol.VMessRequest, output ch
 }
 
 type VMessOutboundHandlerFactory struct {
-	servers []VNextServer
+	servers    []VNextServer
+	udpServers []VNextServer
 }
 
 func (factory *VMessOutboundHandlerFactory) Initialize(rawConfig []byte) error {
@@ -190,15 +192,22 @@ func (factory *VMessOutboundHandlerFactory) Initialize(rawConfig []byte) error {
 		return err
 	}
 	servers := make([]VNextServer, 0, len(config.VNextList))
+	udpServers := make([]VNextServer, 0, len(config.VNextList))
 	for _, server := range config.VNextList {
-		servers = append(servers, server.ToVNextServer())
+		if server.HasNetwork("tcp") {
+			servers = append(servers, server.ToVNextServer())
+		}
+		if server.HasNetwork("udp") {
+			udpServers = append(udpServers, server.ToVNextServer())
+		}
 	}
 	factory.servers = servers
+	factory.udpServers = udpServers
 	return nil
 }
 
 func (factory *VMessOutboundHandlerFactory) Create(vp *core.Point, firstPacket v2net.Packet) (core.OutboundConnectionHandler, error) {
-	return NewVMessOutboundHandler(vp, factory.servers, firstPacket), nil
+	return NewVMessOutboundHandler(vp, factory.servers, factory.udpServers, firstPacket), nil
 }
 
 func init() {

+ 2 - 25
spec/vmess.md

@@ -2,7 +2,7 @@
 ## 摘要
 * 版本:1
 
-## TCP
+## 格式
 ### 数据请求
 认证部分:
 * 16 字节:基于时间的 hash(用户 [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md)),见下文
@@ -43,32 +43,9 @@
 
 其中数据部分使用 AES-128-CFB 加密,IV 为 md5(请求数据 IV),Key 为 md5(请求数据 Key)
 
-## UDP
-UDP 数据包为对称设计,即请求和响应的格式一样
-
-* 16 字节:基于时间的 hash(用户 [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md)),见下文
-* 4 字节:余下所有内容的 FNV1a hash
-* 1 字节:版本号,目前为 0x1
-* 1 字节:保留,暂为 0x00
-* 2 字节:目标端口
-* 1 字节:目标类型
-  * 0x01:IPv4
-  * 0x02:域名
-  * 0x03:IPv6
-* 目标地址:
-  * 4 字节:IPv4
-  * 1 字节长度 + 域名
-  * 16 字节:IPv6
-* N 字节:请求数据
-
-其中除了 hash 之外的部分经过 AES-128-CFB 加密:
-* Key:md5(用户 ID + '22f01806-5ef0-4e88-95ab-b57f1c7a4a40')
-* IV:md5(X + X + X + X),X = []byte(UserHash 生成的时间) (8 字节, Big Endian)
-
-
 ## 基于时间的用户 ID Hash
 
 * H = MD5
 * K = 用户 ID (16 字节)
 * M = UTC 时间,精确到秒,取值为当前时间的前后 30 秒随机值(8 字节, Big Endian)
-* Hash = HMAC(H, K, M)
+* Hash = HMAC(H, K, M)