You've already forked v2ray-core
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28f87f2fae | ||
|
|
544b99b1a6 | ||
|
|
05b83508f8 | ||
|
|
259d772f73 | ||
|
|
ba3f6108b8 | ||
|
|
b5fee0def0 | ||
|
|
8f0cb97e89 |
@@ -1,111 +0,0 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timedQueueEntry struct {
|
||||
timeSec int64
|
||||
value interface{}
|
||||
}
|
||||
|
||||
type timedQueue []*timedQueueEntry
|
||||
|
||||
func (queue timedQueue) Len() int {
|
||||
return len(queue)
|
||||
}
|
||||
|
||||
func (queue timedQueue) Less(i, j int) bool {
|
||||
return queue[i].timeSec < queue[j].timeSec
|
||||
}
|
||||
|
||||
func (queue timedQueue) Swap(i, j int) {
|
||||
tmp := queue[i]
|
||||
queue[i] = queue[j]
|
||||
queue[j] = tmp
|
||||
}
|
||||
|
||||
func (queue *timedQueue) Push(value interface{}) {
|
||||
entry := value.(*timedQueueEntry)
|
||||
*queue = append(*queue, entry)
|
||||
}
|
||||
|
||||
func (queue *timedQueue) Pop() interface{} {
|
||||
old := *queue
|
||||
n := len(old)
|
||||
v := old[n-1]
|
||||
*queue = old[:n-1]
|
||||
return v
|
||||
}
|
||||
|
||||
type TimedStringMap struct {
|
||||
timedQueue
|
||||
queueMutex sync.Mutex
|
||||
dataMutext sync.RWMutex
|
||||
data map[string]interface{}
|
||||
interval int
|
||||
}
|
||||
|
||||
func NewTimedStringMap(updateInterval int) *TimedStringMap {
|
||||
m := &TimedStringMap{
|
||||
timedQueue: make([]*timedQueueEntry, 0, 1024),
|
||||
queueMutex: sync.Mutex{},
|
||||
dataMutext: sync.RWMutex{},
|
||||
data: make(map[string]interface{}, 1024),
|
||||
interval: updateInterval,
|
||||
}
|
||||
m.initialize()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *TimedStringMap) initialize() {
|
||||
go m.cleanup(time.Tick(time.Duration(m.interval) * time.Second))
|
||||
}
|
||||
|
||||
func (m *TimedStringMap) cleanup(tick <-chan time.Time) {
|
||||
for {
|
||||
now := <-tick
|
||||
nowSec := now.UTC().Unix()
|
||||
if m.timedQueue.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
for m.timedQueue.Len() > 0 {
|
||||
entry := m.timedQueue[0]
|
||||
if entry.timeSec > nowSec {
|
||||
break
|
||||
}
|
||||
m.queueMutex.Lock()
|
||||
entry = heap.Pop(&m.timedQueue).(*timedQueueEntry)
|
||||
m.queueMutex.Unlock()
|
||||
m.Remove(entry.value.(string))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TimedStringMap) Get(key string) (interface{}, bool) {
|
||||
m.dataMutext.RLock()
|
||||
value, ok := m.data[key]
|
||||
m.dataMutext.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (m *TimedStringMap) Set(key string, value interface{}, time2Delete int64) {
|
||||
m.dataMutext.Lock()
|
||||
m.data[key] = value
|
||||
m.dataMutext.Unlock()
|
||||
|
||||
m.queueMutex.Lock()
|
||||
heap.Push(&m.timedQueue, &timedQueueEntry{
|
||||
timeSec: time2Delete,
|
||||
value: key,
|
||||
})
|
||||
m.queueMutex.Unlock()
|
||||
}
|
||||
|
||||
func (m *TimedStringMap) Remove(key string) {
|
||||
m.dataMutext.Lock()
|
||||
delete(m.data, key)
|
||||
m.dataMutext.Unlock()
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core/testing/unit"
|
||||
)
|
||||
|
||||
func TestTimedStringMap(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
nowSec := time.Now().UTC().Unix()
|
||||
m := NewTimedStringMap(2)
|
||||
m.Set("Key1", "Value1", nowSec)
|
||||
m.Set("Key2", "Value2", nowSec+5)
|
||||
|
||||
v1, ok := m.Get("Key1")
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.String(v1.(string)).Equals("Value1")
|
||||
|
||||
v2, ok := m.Get("Key2")
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.String(v2.(string)).Equals("Value2")
|
||||
|
||||
tick := time.Tick(4 * time.Second)
|
||||
<-tick
|
||||
|
||||
v1, ok = m.Get("Key1")
|
||||
assert.Bool(ok).IsFalse()
|
||||
|
||||
v2, ok = m.Get("Key2")
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.String(v2.(string)).Equals("Value2")
|
||||
|
||||
<-tick
|
||||
v2, ok = m.Get("Key2")
|
||||
assert.Bool(ok).IsFalse()
|
||||
|
||||
<-tick
|
||||
v2, ok = m.Get("Key2")
|
||||
assert.Bool(ok).IsFalse()
|
||||
|
||||
m.Set("Key1", "Value1", time.Now().UTC().Unix()+10)
|
||||
v1, ok = m.Get("Key1")
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.String(v1.(string)).Equals("Value1")
|
||||
}
|
||||
89
common/collect/timed_queue.go
Normal file
89
common/collect/timed_queue.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timedQueueEntry struct {
|
||||
timeSec int64
|
||||
value interface{}
|
||||
}
|
||||
|
||||
type timedQueueImpl []*timedQueueEntry
|
||||
|
||||
func (queue timedQueueImpl) Len() int {
|
||||
return len(queue)
|
||||
}
|
||||
|
||||
func (queue timedQueueImpl) Less(i, j int) bool {
|
||||
return queue[i].timeSec < queue[j].timeSec
|
||||
}
|
||||
|
||||
func (queue timedQueueImpl) Swap(i, j int) {
|
||||
tmp := queue[i]
|
||||
queue[i] = queue[j]
|
||||
queue[j] = tmp
|
||||
}
|
||||
|
||||
func (queue *timedQueueImpl) Push(value interface{}) {
|
||||
entry := value.(*timedQueueEntry)
|
||||
*queue = append(*queue, entry)
|
||||
}
|
||||
|
||||
func (queue *timedQueueImpl) Pop() interface{} {
|
||||
old := *queue
|
||||
n := len(old)
|
||||
v := old[n-1]
|
||||
*queue = old[:n-1]
|
||||
return v
|
||||
}
|
||||
|
||||
type TimedQueue struct {
|
||||
queue timedQueueImpl
|
||||
access sync.Mutex
|
||||
removed chan interface{}
|
||||
}
|
||||
|
||||
func NewTimedQueue(updateInterval int) *TimedQueue {
|
||||
queue := &TimedQueue{
|
||||
queue: make([]*timedQueueEntry, 0, 256),
|
||||
removed: make(chan interface{}, 16),
|
||||
access: sync.Mutex{},
|
||||
}
|
||||
go queue.cleanup(time.Tick(time.Duration(updateInterval) * time.Second))
|
||||
return queue
|
||||
}
|
||||
|
||||
func (queue *TimedQueue) Add(value interface{}, time2Remove int64) {
|
||||
queue.access.Lock()
|
||||
heap.Push(&queue.queue, &timedQueueEntry{
|
||||
timeSec: time2Remove,
|
||||
value: value,
|
||||
})
|
||||
queue.access.Unlock()
|
||||
}
|
||||
|
||||
func (queue *TimedQueue) RemovedEntries() <-chan interface{} {
|
||||
return queue.removed
|
||||
}
|
||||
|
||||
func (queue *TimedQueue) cleanup(tick <-chan time.Time) {
|
||||
for {
|
||||
now := <-tick
|
||||
if queue.queue.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
nowSec := now.UTC().Unix()
|
||||
entry := queue.queue[0]
|
||||
if entry.timeSec > nowSec {
|
||||
continue
|
||||
}
|
||||
queue.access.Lock()
|
||||
entry = heap.Pop(&queue.queue).(*timedQueueEntry)
|
||||
queue.access.Unlock()
|
||||
|
||||
queue.removed <- entry.value
|
||||
}
|
||||
}
|
||||
60
common/collect/timed_queue_test.go
Normal file
60
common/collect/timed_queue_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core/testing/unit"
|
||||
)
|
||||
|
||||
func TestTimedQueue(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
removed := make(map[string]bool)
|
||||
|
||||
nowSec := time.Now().UTC().Unix()
|
||||
q := NewTimedQueue(2)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
entry := <-q.RemovedEntries()
|
||||
removed[entry.(string)] = true
|
||||
}
|
||||
}()
|
||||
|
||||
q.Add("Value1", nowSec)
|
||||
q.Add("Value2", nowSec+5)
|
||||
|
||||
v1, ok := removed["Value1"]
|
||||
assert.Bool(ok).IsFalse()
|
||||
|
||||
v2, ok := removed["Value2"]
|
||||
assert.Bool(ok).IsFalse()
|
||||
|
||||
tick := time.Tick(4 * time.Second)
|
||||
<-tick
|
||||
|
||||
v1, ok = removed["Value1"]
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.Bool(v1).IsTrue()
|
||||
removed["Value1"] = false
|
||||
|
||||
v2, ok = removed["Value2"]
|
||||
assert.Bool(ok).IsFalse()
|
||||
|
||||
<-tick
|
||||
v2, ok = removed["Value2"]
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.Bool(v2).IsTrue()
|
||||
removed["Value2"] = false
|
||||
|
||||
<-tick
|
||||
assert.Bool(removed["Values"]).IsFalse()
|
||||
|
||||
q.Add("Value1", time.Now().UTC().Unix()+10)
|
||||
|
||||
<-tick
|
||||
v1, ok = removed["Value1"]
|
||||
assert.Bool(ok).IsTrue()
|
||||
assert.Bool(v1).IsFalse()
|
||||
}
|
||||
@@ -12,10 +12,11 @@ func NewTCPPacket(dest Destination) *TCPPacket {
|
||||
}
|
||||
}
|
||||
|
||||
func NewUDPPacket(dest Destination, data []byte) *UDPPacket {
|
||||
func NewUDPPacket(dest Destination, data []byte, token uint16) *UDPPacket {
|
||||
return &UDPPacket{
|
||||
basePacket: basePacket{destination: dest},
|
||||
data: data,
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +42,12 @@ func (packet *TCPPacket) MoreChunks() bool {
|
||||
|
||||
type UDPPacket struct {
|
||||
basePacket
|
||||
data []byte
|
||||
data []byte
|
||||
token uint16
|
||||
}
|
||||
|
||||
func (packet *UDPPacket) Token() uint16 {
|
||||
return packet.token
|
||||
}
|
||||
|
||||
func (packet *UDPPacket) Chunk() []byte {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/collect"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/proxy/socks/protocol"
|
||||
@@ -12,6 +16,63 @@ const (
|
||||
bufferSize = 2 * 1024
|
||||
)
|
||||
|
||||
type portMap struct {
|
||||
access sync.Mutex
|
||||
data map[uint16]*net.UDPAddr
|
||||
removedPorts *collect.TimedQueue
|
||||
}
|
||||
|
||||
func newPortMap() *portMap {
|
||||
m := &portMap{
|
||||
access: sync.Mutex{},
|
||||
data: make(map[uint16]*net.UDPAddr),
|
||||
removedPorts: collect.NewTimedQueue(1),
|
||||
}
|
||||
go m.removePorts(m.removedPorts.RemovedEntries())
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *portMap) assignAddressToken(addr *net.UDPAddr) uint16 {
|
||||
for {
|
||||
token := uint16(rand.Intn(math.MaxUint16))
|
||||
if _, used := m.data[token]; !used {
|
||||
m.access.Lock()
|
||||
if _, used = m.data[token]; !used {
|
||||
m.data[token] = addr
|
||||
m.access.Unlock()
|
||||
return token
|
||||
}
|
||||
m.access.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *portMap) removePorts(removedPorts <-chan interface{}) {
|
||||
for {
|
||||
rawToken := <-removedPorts
|
||||
m.access.Lock()
|
||||
delete(m.data, rawToken.(uint16))
|
||||
m.access.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *portMap) popPort(token uint16) *net.UDPAddr {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
addr, exists := m.data[token]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
delete(m.data, token)
|
||||
return addr
|
||||
}
|
||||
|
||||
var (
|
||||
ports = newPortMap()
|
||||
|
||||
udpConn *net.UDPConn
|
||||
)
|
||||
|
||||
func (server *SocksServer) ListenUDP(port uint16) error {
|
||||
addr := &net.UDPAddr{
|
||||
IP: net.IP{0, 0, 0, 0},
|
||||
@@ -25,13 +86,14 @@ func (server *SocksServer) ListenUDP(port uint16) error {
|
||||
}
|
||||
|
||||
go server.AcceptPackets(conn)
|
||||
udpConn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *SocksServer) AcceptPackets(conn *net.UDPConn) error {
|
||||
for {
|
||||
buffer := make([]byte, 0, bufferSize)
|
||||
nBytes, _, err := conn.ReadFromUDP(buffer)
|
||||
nBytes, addr, err := conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
log.Error("Socks failed to read UDP packets: %v", err)
|
||||
return err
|
||||
@@ -46,7 +108,20 @@ func (server *SocksServer) AcceptPackets(conn *net.UDPConn) error {
|
||||
continue
|
||||
}
|
||||
|
||||
udpPacket := v2net.NewUDPPacket(request.Destination(), request.Data)
|
||||
token := ports.assignAddressToken(addr)
|
||||
|
||||
udpPacket := v2net.NewUDPPacket(request.Destination(), request.Data, token)
|
||||
server.vPoint.DispatchToOutbound(udpPacket)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *SocksServer) Dispatch(packet v2net.Packet) {
|
||||
if udpPacket, ok := packet.(*v2net.UDPPacket); ok {
|
||||
token := udpPacket.Token()
|
||||
addr := ports.popPort(token)
|
||||
if udpConn != nil {
|
||||
udpConn.WriteToUDP(udpPacket.Chunk(), addr)
|
||||
}
|
||||
}
|
||||
// We don't expect TCP Packets here
|
||||
}
|
||||
|
||||
138
proxy/vmess/protocol/udp.go
Normal file
138
proxy/vmess/protocol/udp.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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
|
||||
token uint16
|
||||
address v2net.Address
|
||||
data []byte
|
||||
}
|
||||
|
||||
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],
|
||||
token: binary.BigEndian.Uint16(buffer[1:3]),
|
||||
}
|
||||
|
||||
// buffer[3] is reserved
|
||||
|
||||
port := binary.BigEndian.Uint16(buffer[4:6])
|
||||
addrType := buffer[6]
|
||||
var address v2net.Address
|
||||
switch addrType {
|
||||
case addrTypeIPv4:
|
||||
address = v2net.IPAddress(buffer[7:11], port)
|
||||
buffer = buffer[11:]
|
||||
case addrTypeIPv6:
|
||||
address = v2net.IPAddress(buffer[7:23], port)
|
||||
buffer = buffer[23:]
|
||||
case addrTypeDomain:
|
||||
domainLength := buffer[7]
|
||||
domain := string(buffer[8 : 8+domainLength])
|
||||
address = v2net.DomainAddress(domain, port)
|
||||
buffer = buffer[8+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(vmess.token>>8), byte(vmess.token))
|
||||
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
|
||||
}
|
||||
43
proxy/vmess/protocol/udp_test.go
Normal file
43
proxy/vmess/protocol/udp_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
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),
|
||||
token: 1234,
|
||||
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.Uint16(messageRestored.token).Equals(message.token)
|
||||
assert.String(messageRestored.address.String()).Equals(message.address.String())
|
||||
assert.Bytes(messageRestored.data).Equals(message.data)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/collect"
|
||||
@@ -18,8 +19,10 @@ type UserSet interface {
|
||||
}
|
||||
|
||||
type TimedUserSet struct {
|
||||
validUserIds []ID
|
||||
userHash *collect.TimedStringMap
|
||||
validUserIds []ID
|
||||
userHash map[string]indexTimePair
|
||||
userHashDeleteQueue *collect.TimedQueue
|
||||
access sync.RWMutex
|
||||
}
|
||||
|
||||
type indexTimePair struct {
|
||||
@@ -29,19 +32,34 @@ type indexTimePair struct {
|
||||
|
||||
func NewTimedUserSet() UserSet {
|
||||
tus := &TimedUserSet{
|
||||
validUserIds: make([]ID, 0, 16),
|
||||
userHash: collect.NewTimedStringMap(updateIntervalSec),
|
||||
validUserIds: make([]ID, 0, 16),
|
||||
userHash: make(map[string]indexTimePair, 512),
|
||||
userHashDeleteQueue: collect.NewTimedQueue(updateIntervalSec),
|
||||
access: sync.RWMutex{},
|
||||
}
|
||||
go tus.updateUserHash(time.Tick(updateIntervalSec * time.Second))
|
||||
go tus.removeEntries(tus.userHashDeleteQueue.RemovedEntries())
|
||||
return tus
|
||||
}
|
||||
|
||||
func (us *TimedUserSet) removeEntries(entries <-chan interface{}) {
|
||||
for {
|
||||
entry := <-entries
|
||||
us.access.Lock()
|
||||
delete(us.userHash, entry.(string))
|
||||
us.access.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (us *TimedUserSet) generateNewHashes(lastSec, nowSec int64, idx int, id ID) {
|
||||
idHash := NewTimeHash(HMACHash{})
|
||||
for lastSec < nowSec+cacheDurationSec {
|
||||
idHash := idHash.Hash(id.Bytes[:], lastSec)
|
||||
log.Debug("Valid User Hash: %v", idHash)
|
||||
us.userHash.Set(string(idHash), indexTimePair{idx, lastSec}, lastSec+2*cacheDurationSec)
|
||||
us.access.Lock()
|
||||
us.userHash[string(idHash)] = indexTimePair{idx, lastSec}
|
||||
us.access.Unlock()
|
||||
us.userHashDeleteQueue.Add(string(idHash), lastSec+2*cacheDurationSec)
|
||||
lastSec++
|
||||
}
|
||||
}
|
||||
@@ -73,9 +91,10 @@ func (us *TimedUserSet) AddUser(user User) error {
|
||||
}
|
||||
|
||||
func (us TimedUserSet) GetUser(userHash []byte) (*ID, int64, bool) {
|
||||
rawPair, found := us.userHash.Get(string(userHash))
|
||||
defer us.access.RUnlock()
|
||||
us.access.RLock()
|
||||
pair, found := us.userHash[string(userHash)]
|
||||
if found {
|
||||
pair := rawPair.(indexTimePair)
|
||||
return &us.validUserIds[pair.index], pair.timeSec, true
|
||||
}
|
||||
return nil, 0, false
|
||||
|
||||
@@ -32,8 +32,7 @@ function build {
|
||||
|
||||
build "darwin" "amd64" "-macos" "-macos"
|
||||
build "windows" "amd64" "-windows-64" "-windows-64.exe"
|
||||
build "windows" "amd64" "-windows-32" "-windows-32.exe"
|
||||
build "windows" "386" "-windows-32" "-windows-32.exe"
|
||||
build "linux" "amd64" "-linux-64" "-linux-64"
|
||||
build "linux" "386" "-linux-32" "-linux-32"
|
||||
build "linux" "arm" "-armv6" "-armv6"
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# V2Ray 安装方式
|
||||
|
||||
目前 V2Ray 还在早期测试阶段,暂不提供预编译的运行文件。请使用下面的方式下载源文件并编译。
|
||||
## 预编译程序
|
||||
发布于 [Release](https://github.com/v2ray/v2ray-core/releases) 中,每周更新,[更新周期见此](https://github.com/V2Ray/v2ray-core/blob/master/spec/roadmap.md)。
|
||||
|
||||
## 编译源文件
|
||||
|
||||
大概流程,请根据实际情况修改
|
||||
|
||||
1. 安装 Git: sudo apt-get install git -y
|
||||
2. 安装 golang:
|
||||
1. curl -o go_latest.tar.gz https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
|
||||
@@ -12,7 +16,7 @@
|
||||
3. go get github.com/v2ray/v2ray-core
|
||||
4. go build github.com/v2ray/v2ray-core/release/server
|
||||
|
||||
### archlinux 编译源文件
|
||||
### Arch Linux
|
||||
1. 安装 Git: sudo pacman -S git
|
||||
2. 安装 golang:sudo pacman -S go
|
||||
1. export GOPATH=$HOME/work
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
## 摘要
|
||||
* 版本:1
|
||||
|
||||
## 数据请求
|
||||
## TCP
|
||||
### 数据请求
|
||||
认证部分:
|
||||
* 16 字节:基于时间的 hash(用户 [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md)),见下文
|
||||
|
||||
@@ -35,13 +36,37 @@
|
||||
|
||||
数据部分使用 AES-128-CFB 加密,Key 和 IV 在请求数据中
|
||||
|
||||
## 数据应答
|
||||
### 数据应答
|
||||
数据部分
|
||||
* 4 字节:认证信息 V
|
||||
* N 字节:应答数据
|
||||
|
||||
其中数据部分使用 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
|
||||
* 2 字节:Token,用于区分数据包
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user