mirror of https://github.com/v2ray/v2ray-core
Remove VMess UDP
parent
820da78f92
commit
cd42e5551c
|
@ -3,6 +3,7 @@ package vmess
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/v2ray/v2ray-core/common/log"
|
"github.com/v2ray/v2ray-core/common/log"
|
||||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||||
|
@ -40,6 +41,10 @@ type VNextConfig struct {
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (config VNextConfig) HasNetwork(network string) bool {
|
||||||
|
return strings.Contains(config.Network, network)
|
||||||
|
}
|
||||||
|
|
||||||
func (config VNextConfig) ToVNextServer() VNextServer {
|
func (config VNextConfig) ToVNextServer() VNextServer {
|
||||||
users := make([]user.User, 0, len(config.Users))
|
users := make([]user.User, 0, len(config.Users))
|
||||||
for _, user := range config.Users {
|
for _, user := range config.Users {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,8 +1,11 @@
|
||||||
package vmess
|
package vmess
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
v2io "github.com/v2ray/v2ray-core/common/io"
|
||||||
"github.com/v2ray/v2ray-core/common/log"
|
"github.com/v2ray/v2ray-core/common/log"
|
||||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||||
"github.com/v2ray/v2ray-core/proxy/vmess/protocol"
|
"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)
|
log.Error("VMessIn failed to read UDP packets: %v", err)
|
||||||
return 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 {
|
if err != nil {
|
||||||
log.Error("VMessIn failed to parse UDP request: %v", err)
|
log.Warning("VMessIn: Invalid request from (%s): %v", addr.String(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
udpPacket := request.ToPacket()
|
cryptReader, err := v2io.NewAesDecryptReader(request.RequestKey[:], request.RequestIV[:], reader)
|
||||||
go handler.handlePacket(conn, udpPacket, addr)
|
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.Warning("VMessIn: Unable to decrypt data: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
ray := handler.vPoint.DispatchToOutbound(packet)
|
||||||
close(ray.InboundInput())
|
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 {
|
if data, ok := <-ray.InboundOutput(); ok {
|
||||||
conn.WriteToUDP(data, clientAddr)
|
hasData = true
|
||||||
|
responseWriter.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasData {
|
||||||
|
conn.WriteToUDP(buffer.Bytes(), clientAddr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,13 @@ type VNextServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type VMessOutboundHandler struct {
|
type VMessOutboundHandler struct {
|
||||||
vPoint *core.Point
|
vPoint *core.Point
|
||||||
packet v2net.Packet
|
packet v2net.Packet
|
||||||
vNextList []VNextServer
|
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{
|
return &VMessOutboundHandler{
|
||||||
vPoint: vp,
|
vPoint: vp,
|
||||||
packet: firstPacket,
|
packet: firstPacket,
|
||||||
|
@ -40,8 +41,8 @@ func NewVMessOutboundHandler(vp *core.Point, vNextList []VNextServer, firstPacke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *VMessOutboundHandler) pickVNext() (v2net.Destination, user.User) {
|
func pickVNext(serverList []VNextServer) (v2net.Destination, user.User) {
|
||||||
vNextLen := len(handler.vNextList)
|
vNextLen := len(serverList)
|
||||||
if vNextLen == 0 {
|
if vNextLen == 0 {
|
||||||
panic("VMessOut: Zero vNext is configured.")
|
panic("VMessOut: Zero vNext is configured.")
|
||||||
}
|
}
|
||||||
|
@ -50,7 +51,7 @@ func (handler *VMessOutboundHandler) pickVNext() (v2net.Destination, user.User)
|
||||||
vNextIndex = mrand.Intn(vNextLen)
|
vNextIndex = mrand.Intn(vNextLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
vNext := handler.vNextList[vNextIndex]
|
vNext := serverList[vNextIndex]
|
||||||
vNextUserLen := len(vNext.Users)
|
vNextUserLen := len(vNext.Users)
|
||||||
if vNextUserLen == 0 {
|
if vNextUserLen == 0 {
|
||||||
panic("VMessOut: Zero User account.")
|
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 {
|
func (handler *VMessOutboundHandler) Start(ray core.OutboundRay) error {
|
||||||
vNextAddress, vNextUser := handler.pickVNext()
|
vNextAddress, vNextUser := pickVNext(handler.vNextList)
|
||||||
|
|
||||||
command := protocol.CmdTCP
|
command := protocol.CmdTCP
|
||||||
if handler.packet.Destination().IsUDP() {
|
if handler.packet.Destination().IsUDP() {
|
||||||
|
@ -180,7 +181,8 @@ func handleResponse(conn *net.TCPConn, request *protocol.VMessRequest, output ch
|
||||||
}
|
}
|
||||||
|
|
||||||
type VMessOutboundHandlerFactory struct {
|
type VMessOutboundHandlerFactory struct {
|
||||||
servers []VNextServer
|
servers []VNextServer
|
||||||
|
udpServers []VNextServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *VMessOutboundHandlerFactory) Initialize(rawConfig []byte) error {
|
func (factory *VMessOutboundHandlerFactory) Initialize(rawConfig []byte) error {
|
||||||
|
@ -190,15 +192,22 @@ func (factory *VMessOutboundHandlerFactory) Initialize(rawConfig []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
servers := make([]VNextServer, 0, len(config.VNextList))
|
servers := make([]VNextServer, 0, len(config.VNextList))
|
||||||
|
udpServers := make([]VNextServer, 0, len(config.VNextList))
|
||||||
for _, server := range 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.servers = servers
|
||||||
|
factory.udpServers = udpServers
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *VMessOutboundHandlerFactory) Create(vp *core.Point, firstPacket v2net.Packet) (core.OutboundConnectionHandler, error) {
|
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() {
|
func init() {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
## 摘要
|
## 摘要
|
||||||
* 版本:1
|
* 版本:1
|
||||||
|
|
||||||
## TCP
|
## 格式
|
||||||
### 数据请求
|
### 数据请求
|
||||||
认证部分:
|
认证部分:
|
||||||
* 16 字节:基于时间的 hash(用户 [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md)),见下文
|
* 16 字节:基于时间的 hash(用户 [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md)),见下文
|
||||||
|
@ -43,29 +43,6 @@
|
||||||
|
|
||||||
其中数据部分使用 AES-128-CFB 加密,IV 为 md5(请求数据 IV),Key 为 md5(请求数据 Key)
|
其中数据部分使用 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
|
## 基于时间的用户 ID Hash
|
||||||
|
|
||||||
* H = MD5
|
* H = MD5
|
||||||
|
|
Loading…
Reference in New Issue