You've already forked v2ray-core
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ce02fa857 | ||
|
|
21e7e8ad2d | ||
|
|
73c3d9b9e1 | ||
|
|
b33995399b | ||
|
|
c598e229a9 | ||
|
|
184560883f | ||
|
|
9c45cb031a | ||
|
|
13e595e4cb | ||
|
|
3747e45978 | ||
|
|
d77ba76ccf | ||
|
|
8c682d32e6 | ||
|
|
08f85fc9b7 | ||
|
|
1995594b98 | ||
|
|
e83eec9479 | ||
|
|
d77fdbd719 | ||
|
|
9e4902d17a | ||
|
|
685c0f3c05 | ||
|
|
02c3f144e2 | ||
|
|
fc14b9346c | ||
|
|
51c2a2b880 | ||
|
|
a78dbe7133 | ||
|
|
64103229d6 | ||
|
|
c138004bf9 | ||
|
|
6ecb18268e | ||
|
|
ef790375ee | ||
|
|
5604ff175b | ||
|
|
70ad3d0174 | ||
|
|
99f32f1dbf | ||
|
|
33768175c1 | ||
|
|
10d733263d | ||
|
|
8ce7ee1cda | ||
|
|
bb442b4f83 | ||
|
|
3fbae6795a | ||
|
|
c59dcc309c | ||
|
|
019d8266bc | ||
|
|
dc5fbf8e60 | ||
|
|
db23bdfc20 | ||
|
|
53eff7bb3e | ||
|
|
3f0f8f005d | ||
|
|
f30841019d | ||
|
|
ec83281d18 | ||
|
|
338300248c | ||
|
|
0527d40f21 | ||
|
|
787df1ab9b | ||
|
|
4874cd54a4 | ||
|
|
4320c14efd | ||
|
|
dfdea480ff | ||
|
|
bd35793cf8 | ||
|
|
67f41cb3ba | ||
|
|
e57089637a | ||
|
|
26b6c06c9e | ||
|
|
0ce10e1f88 | ||
|
|
79ffd818b2 | ||
|
|
e2bd6abb04 | ||
|
|
3e2cd914b3 | ||
|
|
a51d64a102 | ||
|
|
5aa448753d | ||
|
|
186aadf1db | ||
|
|
313aec4097 | ||
|
|
e4b6d5e0f0 | ||
|
|
402e7c09c3 | ||
|
|
145d78b271 |
@@ -9,6 +9,7 @@ before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
script:
|
||||
- go test github.com/v2ray/v2ray-core/...
|
||||
- ./testing/coverage/coverall
|
||||
|
||||
env:
|
||||
@@ -20,7 +21,13 @@ deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "Nbno1ibFpq0DC6y0mkN6NFTbDQinRAEQo2Jl5lvsbiaX9m4CK59xG8XOcmZ3mqqdeioj6m9X5BVe2UtCZjmXmnyPq0FYNFHAPTfd2VrcOpi0fWLD9ndTYAsqjxrqPGK62XVjPbG8U2pGdq2yCOb0qQ9DlahDhVIMzQ/GYuRDps25fPGftepdPwlrwOfYCIvAHoi3GEozuS67WNEx/YtmZTce/2Qebdmwf44hUNaQibeXEspu9wQcp9M/i7aTuX/tUbn7rVCQ4TfoIJU1C1qKedUw0QYMjqP08Wn6UjwwhbblsJKFv8Kc0nx+i2K9CvvLyaBBIVY2Ggxj4Niy+X4VbyWWzdKx+0FXlmKh3U/hJdMZU3OrnwliBns2X3tCHSq7/+7ynLs3UZ4wCkCl7rQYucJ2mlvGquohQV6MX2vjl73y+pYljf+PKYbSDBb7iUOjllisRD+HmDmGQl81BvdEpN/K/BlfsTwHoHxsALSTjRljSngH51SUvjnaVbi/JolJ3lomTr1vCHl4WUiQVLnh7O2Cg7QLNMNieoblnh9yDQG9wC4xuzOJ/AaxRd/pRZMeIooQ1zsHOe+47C1YAep88b+9xtRqVMozAjk/XgNzWjuT2R8G7lZDq0mL9VajD/NUjM3Yrswoft9A3DRmR+lIFJR+fJb8tNf+k1OczE4lesg="
|
||||
file: "$GOPATH/bin/v2ray.zip"
|
||||
file:
|
||||
- "$GOPATH/bin/v2ray-macos.zip"
|
||||
- "$GOPATH/bin/v2ray-windows-64.zip"
|
||||
- "$GOPATH/bin/v2ray-windows-32.zip"
|
||||
- "$GOPATH/bin/v2ray-linux-64.zip"
|
||||
- "$GOPATH/bin/v2ray-linux-32.zip"
|
||||
- "$GOPATH/bin/v2ray-armv6.zip"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
|
||||
@@ -14,13 +14,14 @@ V2Ray 是一个翻墙工具包,用于简化和复用其它翻墙工具,加
|
||||
## 主要特点
|
||||
* 多对多服务器支持,负载均衡
|
||||
* 支持多用户
|
||||
* 开放协议支持,兼容 ShadowSocks 和 GoAgent
|
||||
* 开放协议支持,兼容 Shadowsocks 和 GoAgent
|
||||
|
||||
## 使用说明
|
||||
* [简明教程](https://github.com/V2Ray/v2ray-core/blob/master/spec/guide.md)
|
||||
* [建议或意见](https://github.com/v2ray/v2ray-core/issues)
|
||||
* [Issue 指引](https://github.com/V2Ray/v2ray-core/blob/master/spec/issue.md)
|
||||
* [当前状态](https://github.com/V2Ray/v2ray-core/blob/master/spec/status.md)
|
||||
* [错误信息](https://github.com/V2Ray/v2ray-core/blob/master/spec/errors.md)
|
||||
|
||||
## 开发人员相关
|
||||
* [概要设计](https://github.com/V2Ray/v2ray-core/blob/master/spec/design.md)
|
||||
@@ -28,7 +29,8 @@ V2Ray 是一个翻墙工具包,用于简化和复用其它翻墙工具,加
|
||||
* [Help Wanted](https://github.com/v2ray/v2ray-core/labels/help%20wanted):所有被标记为“Help Wanted”的 Issue 都接受 Pull Request,如果你对本项目感兴趣并想做点贡献,请挑选其中之一完善之,不甚感激。
|
||||
|
||||
## 联系方式
|
||||
私下联系:love@v2ray.com
|
||||
* Twitter:https://twitter.com/projectv2ray
|
||||
* 私下联系:love@v2ray.com
|
||||
|
||||
## 捐赠
|
||||
目前 V2Ray 还在早期开发阶段,暂时没什么可用性,也谈不上捐赠。如果你执意想捐赠,请发送 Amazon Gift Card 至 donate@v2ray.com
|
||||
|
||||
111
common/collect/timed_map.go
Normal file
111
common/collect/timed_map.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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()
|
||||
}
|
||||
48
common/collect/timed_map_test.go
Normal file
48
common/collect/timed_map_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
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")
|
||||
}
|
||||
119
common/errors/errors.go
Normal file
119
common/errors/errors.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func HasCode(err error, code int) bool {
|
||||
if errWithCode, ok := err.(ErrorWithCode); ok {
|
||||
return errWithCode.Code() == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ErrorWithCode interface {
|
||||
error
|
||||
Code() int
|
||||
}
|
||||
|
||||
type ErrorCode int
|
||||
|
||||
func (code ErrorCode) Code() int {
|
||||
return int(code)
|
||||
}
|
||||
|
||||
func (code ErrorCode) Prefix() string {
|
||||
return fmt.Sprintf("[Error 0x%04X] ", code.Code())
|
||||
}
|
||||
|
||||
type AuthenticationError struct {
|
||||
ErrorCode
|
||||
AuthDetail interface{}
|
||||
}
|
||||
|
||||
func NewAuthenticationError(detail interface{}) AuthenticationError {
|
||||
return AuthenticationError{
|
||||
ErrorCode: 1,
|
||||
AuthDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
func (err AuthenticationError) Error() string {
|
||||
return fmt.Sprintf("%sInvalid auth %v", err.Prefix(), err.AuthDetail)
|
||||
}
|
||||
|
||||
type ProtocolVersionError struct {
|
||||
ErrorCode
|
||||
Version int
|
||||
}
|
||||
|
||||
func NewProtocolVersionError(version int) ProtocolVersionError {
|
||||
return ProtocolVersionError{
|
||||
ErrorCode: 2,
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
func (err ProtocolVersionError) Error() string {
|
||||
return fmt.Sprintf("%sInvalid version %d", err.Prefix(), err.Version)
|
||||
}
|
||||
|
||||
type CorruptedPacketError struct {
|
||||
ErrorCode
|
||||
}
|
||||
|
||||
var corruptedPacketErrorInstance = CorruptedPacketError{ErrorCode: 3}
|
||||
|
||||
func NewCorruptedPacketError() CorruptedPacketError {
|
||||
return corruptedPacketErrorInstance
|
||||
}
|
||||
|
||||
func (err CorruptedPacketError) Error() string {
|
||||
return err.Prefix() + "Corrupted packet."
|
||||
}
|
||||
|
||||
type IPFormatError struct {
|
||||
ErrorCode
|
||||
IP []byte
|
||||
}
|
||||
|
||||
func NewIPFormatError(ip []byte) IPFormatError {
|
||||
return IPFormatError{
|
||||
ErrorCode: 4,
|
||||
IP: ip,
|
||||
}
|
||||
}
|
||||
|
||||
func (err IPFormatError) Error() string {
|
||||
return fmt.Sprintf("%sInvalid IP %v", err.Prefix(), err.IP)
|
||||
}
|
||||
|
||||
type ConfigurationError struct {
|
||||
ErrorCode
|
||||
}
|
||||
|
||||
var configurationErrorInstance = ConfigurationError{ErrorCode: 5}
|
||||
|
||||
func NewConfigurationError() ConfigurationError {
|
||||
return configurationErrorInstance
|
||||
}
|
||||
|
||||
func (r ConfigurationError) Error() string {
|
||||
return r.Prefix() + "Invalid configuration."
|
||||
}
|
||||
|
||||
type InvalidOperationError struct {
|
||||
ErrorCode
|
||||
Operation string
|
||||
}
|
||||
|
||||
func NewInvalidOperationError(operation string) InvalidOperationError {
|
||||
return InvalidOperationError{
|
||||
ErrorCode: 6,
|
||||
Operation: operation,
|
||||
}
|
||||
}
|
||||
|
||||
func (r InvalidOperationError) Error() string {
|
||||
return r.Prefix() + "Invalid operation: " + r.Operation
|
||||
}
|
||||
23
common/errors/errors_test.go
Normal file
23
common/errors/errors_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package errors_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
"github.com/v2ray/v2ray-core/testing/unit"
|
||||
)
|
||||
|
||||
type MockError struct {
|
||||
errors.ErrorCode
|
||||
}
|
||||
|
||||
func (err MockError) Error() string {
|
||||
return "This is a fake error."
|
||||
}
|
||||
|
||||
func TestHasCode(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
err := MockError{ErrorCode: 101}
|
||||
assert.Error(err).HasCode(101)
|
||||
}
|
||||
@@ -3,17 +3,15 @@ package io
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"io"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
)
|
||||
|
||||
// CryptionReader is a general purpose reader that applies
|
||||
// block cipher on top of a regular reader.
|
||||
// CryptionReader is a general purpose reader that applies a stream cipher on top of a regular reader.
|
||||
type CryptionReader struct {
|
||||
stream cipher.Stream
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// NewCryptionReader creates a new CryptionReader instance from given stream cipher and reader.
|
||||
func NewCryptionReader(stream cipher.Stream, reader io.Reader) *CryptionReader {
|
||||
return &CryptionReader{
|
||||
stream: stream,
|
||||
@@ -21,26 +19,22 @@ func NewCryptionReader(stream cipher.Stream, reader io.Reader) *CryptionReader {
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads blocks from underlying reader, the length of blocks must be
|
||||
// a multiply of BlockSize()
|
||||
// Read reads blocks from underlying reader, and crypt it. The content of blocks is modified in place.
|
||||
func (reader CryptionReader) Read(blocks []byte) (int, error) {
|
||||
nBytes, err := reader.reader.Read(blocks)
|
||||
if nBytes > 0 {
|
||||
reader.stream.XORKeyStream(blocks[:nBytes], blocks[:nBytes])
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
log.Error("Error reading blocks: %v", err)
|
||||
}
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
// Cryption writer is a general purpose of byte stream writer that applies
|
||||
// block cipher on top of a regular writer.
|
||||
// Cryption writer is a general purpose of byte stream writer that applies a stream cipher on top of a regular writer.
|
||||
type CryptionWriter struct {
|
||||
stream cipher.Stream
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewCryptionWriter creates a new CryptionWriter from given stream cipher and writer.
|
||||
func NewCryptionWriter(stream cipher.Stream, writer io.Writer) *CryptionWriter {
|
||||
return &CryptionWriter{
|
||||
stream: stream,
|
||||
@@ -48,12 +42,12 @@ func NewCryptionWriter(stream cipher.Stream, writer io.Writer) *CryptionWriter {
|
||||
}
|
||||
}
|
||||
|
||||
// Crypt crypts the content of blocks without writing them into the underlying writer.
|
||||
func (writer CryptionWriter) Crypt(blocks []byte) {
|
||||
writer.stream.XORKeyStream(blocks, blocks)
|
||||
}
|
||||
|
||||
// Write writes the give blocks to underlying writer. The length of the blocks
|
||||
// must be a multiply of BlockSize()
|
||||
// Write crypts the content of blocks in place, and then writes the give blocks to underlying writer.
|
||||
func (writer CryptionWriter) Write(blocks []byte) (int, error) {
|
||||
writer.Crypt(blocks)
|
||||
return writer.writer.Write(blocks)
|
||||
|
||||
@@ -31,7 +31,7 @@ func writeLog(level LogLevel, prefix, format string, v ...interface{}) string {
|
||||
} else {
|
||||
data = fmt.Sprintf(format, v...)
|
||||
}
|
||||
log.Print(prefix + data)
|
||||
log.Println(prefix + data)
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -4,22 +4,26 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
)
|
||||
|
||||
// Address represents a network address to be communicated with. It may be an IP address or domain
|
||||
// address, not both. This interface doesn't resolve IP address for a given domain.
|
||||
type Address interface {
|
||||
IP() net.IP
|
||||
Domain() string
|
||||
Port() uint16
|
||||
PortBytes() []byte
|
||||
IP() net.IP // IP of this Address
|
||||
Domain() string // Domain of this Address
|
||||
Port() uint16 // Port of this Address
|
||||
PortBytes() []byte // Port in bytes, network byte order
|
||||
|
||||
IsIPv4() bool
|
||||
IsIPv6() bool
|
||||
IsDomain() bool
|
||||
IsIPv4() bool // True if this Address is an IPv4 address
|
||||
IsIPv6() bool // True if this Address is an IPv6 address
|
||||
IsDomain() bool // True if this Address is an domain address
|
||||
|
||||
String() string
|
||||
String() string // String representation of this Address
|
||||
}
|
||||
|
||||
// IPAddress creates an Address with given IP and port.
|
||||
func IPAddress(ip []byte, port uint16) Address {
|
||||
switch len(ip) {
|
||||
case net.IPv4len:
|
||||
@@ -30,13 +34,20 @@ func IPAddress(ip []byte, port uint16) Address {
|
||||
case net.IPv6len:
|
||||
return IPv6Address{
|
||||
PortAddress: PortAddress{port: port},
|
||||
ip: [16]byte{ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]},
|
||||
ip: [16]byte{
|
||||
ip[0], ip[1], ip[2], ip[3],
|
||||
ip[4], ip[5], ip[6], ip[7],
|
||||
ip[8], ip[9], ip[10], ip[11],
|
||||
ip[12], ip[13], ip[14], ip[15],
|
||||
},
|
||||
}
|
||||
default:
|
||||
panic(log.Error("Unknown IP format: %v", ip))
|
||||
log.Error(errors.NewIPFormatError(ip).Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DomainAddress creates an Address with given domain and port.
|
||||
func DomainAddress(domain string, port uint16) Address {
|
||||
return DomainAddressImpl{
|
||||
domain: domain,
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package net
|
||||
|
||||
// Destination represents a network destination including address and protocol (tcp / udp).
|
||||
type Destination interface {
|
||||
Network() string
|
||||
Address() Address
|
||||
String() string
|
||||
Network() string // Protocol of communication (tcp / udp)
|
||||
Address() Address // Address of destination
|
||||
String() string // String representation of the destination
|
||||
|
||||
IsTCP() bool
|
||||
IsUDP() bool
|
||||
IsTCP() bool // True if destination is reachable via TCP
|
||||
IsUDP() bool // True if destination is reachable via UDP
|
||||
}
|
||||
|
||||
// NewTCPDestination creates a TCP destination with given address
|
||||
func NewTCPDestination(address Address) Destination {
|
||||
return TCPDestination{address: address}
|
||||
}
|
||||
|
||||
// NewUDPDestination creates a UDP destination with given address
|
||||
func NewUDPDestination(address Address) Destination {
|
||||
return UDPDestination{address: address}
|
||||
}
|
||||
|
||||
53
common/net/packet.go
Normal file
53
common/net/packet.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package net
|
||||
|
||||
type Packet interface {
|
||||
Destination() Destination
|
||||
Chunk() []byte // First chunk of this commnunication
|
||||
MoreChunks() bool
|
||||
}
|
||||
|
||||
func NewTCPPacket(dest Destination) *TCPPacket {
|
||||
return &TCPPacket{
|
||||
basePacket: basePacket{destination: dest},
|
||||
}
|
||||
}
|
||||
|
||||
func NewUDPPacket(dest Destination, data []byte) *UDPPacket {
|
||||
return &UDPPacket{
|
||||
basePacket: basePacket{destination: dest},
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type basePacket struct {
|
||||
destination Destination
|
||||
}
|
||||
|
||||
func (base basePacket) Destination() Destination {
|
||||
return base.destination
|
||||
}
|
||||
|
||||
type TCPPacket struct {
|
||||
basePacket
|
||||
}
|
||||
|
||||
func (packet *TCPPacket) Chunk() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (packet *TCPPacket) MoreChunks() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type UDPPacket struct {
|
||||
basePacket
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (packet *UDPPacket) Chunk() []byte {
|
||||
return packet.data
|
||||
}
|
||||
|
||||
func (packet *UDPPacket) MoreChunks() bool {
|
||||
return false
|
||||
}
|
||||
30
common/net/timed_io.go
Normal file
30
common/net/timed_io.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
emptyTime time.Time
|
||||
)
|
||||
|
||||
type TimeOutReader struct {
|
||||
timeout int
|
||||
connection net.Conn
|
||||
}
|
||||
|
||||
func NewTimeOutReader(timeout int, connection net.Conn) *TimeOutReader {
|
||||
return &TimeOutReader{
|
||||
timeout: timeout,
|
||||
connection: connection,
|
||||
}
|
||||
}
|
||||
|
||||
func (reader *TimeOutReader) Read(p []byte) (n int, err error) {
|
||||
deadline := time.Duration(reader.timeout) * time.Second
|
||||
reader.connection.SetReadDeadline(time.Now().Add(deadline))
|
||||
n, err = reader.connection.Read(p)
|
||||
reader.connection.SetReadDeadline(emptyTime)
|
||||
return
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
bufferSize = 32 * 1024
|
||||
bufferSize = 4 * 1024
|
||||
)
|
||||
|
||||
// ReaderToChan dumps all content from a given reader to a chan by constantly reading it until EOF.
|
||||
func ReaderToChan(stream chan<- []byte, reader io.Reader) error {
|
||||
for {
|
||||
buffer := make([]byte, bufferSize)
|
||||
@@ -21,6 +22,7 @@ func ReaderToChan(stream chan<- []byte, reader io.Reader) error {
|
||||
}
|
||||
}
|
||||
|
||||
// ChanToWriter dumps all content from a given chan to a writer until the chan is closed.
|
||||
func ChanToWriter(writer io.Writer, stream <-chan []byte) error {
|
||||
for buffer := range stream {
|
||||
_, err := writer.Write(buffer)
|
||||
|
||||
2
core.go
2
core.go
@@ -2,7 +2,7 @@
|
||||
package core
|
||||
|
||||
const (
|
||||
Version = "0.5"
|
||||
Version = "0.6"
|
||||
Codename = "Post Apocalypse"
|
||||
Intro = "A stable and unbreakable connection for everyone."
|
||||
)
|
||||
|
||||
10
point.go
10
point.go
@@ -64,7 +64,8 @@ type InboundConnectionHandler interface {
|
||||
}
|
||||
|
||||
type OutboundConnectionHandlerFactory interface {
|
||||
Create(VP *Point, config []byte, dest v2net.Destination) (OutboundConnectionHandler, error)
|
||||
Initialize(config []byte) error
|
||||
Create(VP *Point, firstPacket v2net.Packet) (OutboundConnectionHandler, error)
|
||||
}
|
||||
|
||||
type OutboundConnectionHandler interface {
|
||||
@@ -77,6 +78,9 @@ func (vp *Point) Start() error {
|
||||
if vp.port <= 0 {
|
||||
return log.Error("Invalid port %d", vp.port)
|
||||
}
|
||||
|
||||
vp.ochFactory.Initialize(vp.ochConfig)
|
||||
|
||||
inboundConnectionHandler, err := vp.ichFactory.Create(vp, vp.ichConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -85,10 +89,10 @@ func (vp *Point) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vp *Point) NewInboundConnectionAccepted(destination v2net.Destination) InboundRay {
|
||||
func (p *Point) DispatchToOutbound(packet v2net.Packet) InboundRay {
|
||||
ray := NewRay()
|
||||
// TODO: handle error
|
||||
och, _ := vp.ochFactory.Create(vp, vp.ochConfig, destination)
|
||||
och, _ := p.ochFactory.Create(p, packet)
|
||||
_ = och.Start(ray)
|
||||
return ray
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package freedom
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/v2ray/v2ray-core"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
@@ -9,50 +10,64 @@ import (
|
||||
)
|
||||
|
||||
type FreedomConnection struct {
|
||||
dest v2net.Destination
|
||||
packet v2net.Packet
|
||||
}
|
||||
|
||||
func NewFreedomConnection(dest v2net.Destination) *FreedomConnection {
|
||||
func NewFreedomConnection(firstPacket v2net.Packet) *FreedomConnection {
|
||||
return &FreedomConnection{
|
||||
dest: dest,
|
||||
packet: firstPacket,
|
||||
}
|
||||
}
|
||||
|
||||
func (vconn *FreedomConnection) Start(ray core.OutboundRay) error {
|
||||
input := ray.OutboundInput()
|
||||
output := ray.OutboundOutput()
|
||||
conn, err := net.Dial(vconn.dest.Network(), vconn.dest.Address().String())
|
||||
log.Info("Freedom: Opening connection to %s", vconn.dest.String())
|
||||
conn, err := net.Dial(vconn.packet.Destination().Network(), vconn.packet.Destination().Address().String())
|
||||
log.Info("Freedom: Opening connection to %s", vconn.packet.Destination().String())
|
||||
if err != nil {
|
||||
close(output)
|
||||
return log.Error("Freedom: Failed to open connection: %s : %v", vconn.dest.String(), err)
|
||||
if ray != nil {
|
||||
close(ray.OutboundOutput())
|
||||
}
|
||||
return log.Error("Freedom: Failed to open connection: %s : %v", vconn.packet.Destination().String(), err)
|
||||
}
|
||||
|
||||
readFinish := make(chan bool)
|
||||
writeFinish := make(chan bool)
|
||||
if chunk := vconn.packet.Chunk(); chunk != nil {
|
||||
conn.Write(chunk)
|
||||
}
|
||||
|
||||
if !vconn.packet.MoreChunks() {
|
||||
if ray != nil {
|
||||
close(ray.OutboundOutput())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
input := ray.OutboundInput()
|
||||
output := ray.OutboundOutput()
|
||||
var readMutex, writeMutex sync.Mutex
|
||||
readMutex.Lock()
|
||||
writeMutex.Lock()
|
||||
|
||||
go dumpInput(conn, input, &writeMutex)
|
||||
go dumpOutput(conn, output, &readMutex)
|
||||
|
||||
go func() {
|
||||
writeMutex.Lock()
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.CloseWrite()
|
||||
}
|
||||
readMutex.Lock()
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
go vconn.DumpInput(conn, input, writeFinish)
|
||||
go vconn.DumpOutput(conn, output, readFinish)
|
||||
go vconn.CloseConn(conn, readFinish, writeFinish)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vconn *FreedomConnection) DumpInput(conn net.Conn, input <-chan []byte, finish chan<- bool) {
|
||||
func dumpInput(conn net.Conn, input <-chan []byte, finish *sync.Mutex) {
|
||||
v2net.ChanToWriter(conn, input)
|
||||
finish <- true
|
||||
finish.Unlock()
|
||||
}
|
||||
|
||||
func (vconn *FreedomConnection) DumpOutput(conn net.Conn, output chan<- []byte, finish chan<- bool) {
|
||||
func dumpOutput(conn net.Conn, output chan<- []byte, finish *sync.Mutex) {
|
||||
v2net.ReaderToChan(output, conn)
|
||||
finish.Unlock()
|
||||
close(output)
|
||||
finish <- true
|
||||
}
|
||||
|
||||
func (vconn *FreedomConnection) CloseConn(conn net.Conn, readFinish <-chan bool, writeFinish <-chan bool) {
|
||||
<-writeFinish
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.CloseWrite()
|
||||
}
|
||||
<-readFinish
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
90
proxy/freedom/freedom_test.go
Normal file
90
proxy/freedom/freedom_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package freedom
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
|
||||
"github.com/v2ray/v2ray-core"
|
||||
_ "github.com/v2ray/v2ray-core/proxy/socks"
|
||||
"github.com/v2ray/v2ray-core/testing/mocks"
|
||||
"github.com/v2ray/v2ray-core/testing/unit"
|
||||
)
|
||||
|
||||
func TestSocksTcpConnect(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
port := 48274
|
||||
|
||||
data2Send := "Data to be sent to remote"
|
||||
data2Return := "Data to be returned to local"
|
||||
|
||||
var serverReady sync.Mutex
|
||||
serverReady.Lock()
|
||||
|
||||
go func() {
|
||||
listener, err := net.ListenTCP("tcp", &net.TCPAddr{
|
||||
IP: []byte{0, 0, 0, 0},
|
||||
Port: port,
|
||||
Zone: "",
|
||||
})
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
serverReady.Unlock()
|
||||
conn, err := listener.Accept()
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
nBytes, err := conn.Read(buffer)
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
if string(buffer[:nBytes]) == data2Send {
|
||||
_, err = conn.Write([]byte(data2Return))
|
||||
assert.Error(err).IsNil()
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
pointPort := uint16(38724)
|
||||
config := mocks.Config{
|
||||
PortValue: pointPort,
|
||||
InboundConfigValue: &mocks.ConnectionConfig{
|
||||
ProtocolValue: "socks",
|
||||
ContentValue: []byte("{\"auth\": \"noauth\"}"),
|
||||
},
|
||||
OutboundConfigValue: &mocks.ConnectionConfig{
|
||||
ProtocolValue: "freedom",
|
||||
ContentValue: nil,
|
||||
},
|
||||
}
|
||||
|
||||
point, err := core.NewPoint(&config)
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
err = point.Start()
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
socks5Client, err := proxy.SOCKS5("tcp", "127.0.0.1:38724", nil, proxy.Direct)
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
serverReady.Lock()
|
||||
|
||||
targetServer := "127.0.0.1:48274"
|
||||
conn, err := socks5Client.Dial("tcp", targetServer)
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
conn.Write([]byte(data2Send))
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.CloseWrite()
|
||||
}
|
||||
|
||||
dataReturned, err := ioutil.ReadAll(conn)
|
||||
assert.Error(err).IsNil()
|
||||
conn.Close()
|
||||
|
||||
assert.Bytes(dataReturned).Equals([]byte(data2Return))
|
||||
}
|
||||
@@ -8,8 +8,12 @@ import (
|
||||
type FreedomFactory struct {
|
||||
}
|
||||
|
||||
func (factory FreedomFactory) Create(vp *core.Point, config []byte, dest v2net.Destination) (core.OutboundConnectionHandler, error) {
|
||||
return NewFreedomConnection(dest), nil
|
||||
func (factory FreedomFactory) Initialize(config []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (factory FreedomFactory) Create(vp *core.Point, firstPacket v2net.Packet) (core.OutboundConnectionHandler, error) {
|
||||
return NewFreedomConnection(firstPacket), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
JsonAuthMethodNoAuth = "noauth"
|
||||
JsonAuthMethodUserPass = "password"
|
||||
)
|
||||
|
||||
type SocksConfig struct {
|
||||
AuthMethod string `json:"auth"`
|
||||
Username string `json:"user"`
|
||||
Password string `json:"pass"`
|
||||
}
|
||||
|
||||
func loadConfig(rawConfig []byte) (SocksConfig, error) {
|
||||
config := SocksConfig{}
|
||||
err := json.Unmarshal(rawConfig, &config)
|
||||
return config, err
|
||||
}
|
||||
30
proxy/socks/config/json/config.go
Normal file
30
proxy/socks/config/json/config.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthMethodNoAuth = "noauth"
|
||||
AuthMethodUserPass = "password"
|
||||
)
|
||||
|
||||
type SocksConfig struct {
|
||||
AuthMethod string `json:"auth"`
|
||||
Username string `json:"user"`
|
||||
Password string `json:"pass"`
|
||||
}
|
||||
|
||||
func (config SocksConfig) IsNoAuth() bool {
|
||||
return config.AuthMethod == AuthMethodNoAuth
|
||||
}
|
||||
|
||||
func (config SocksConfig) IsPassword() bool {
|
||||
return config.AuthMethod == AuthMethodUserPass
|
||||
}
|
||||
|
||||
func Load(rawConfig []byte) (SocksConfig, error) {
|
||||
config := SocksConfig{}
|
||||
err := json.Unmarshal(rawConfig, &config)
|
||||
return config, err
|
||||
}
|
||||
@@ -2,10 +2,9 @@ package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
)
|
||||
@@ -23,10 +22,6 @@ const (
|
||||
Socks4RequestRejected = byte(91)
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorSocksVersion4 = errors.New("Using SOCKS version 4.")
|
||||
)
|
||||
|
||||
// Authentication request header of Socks5 protocol
|
||||
type Socks5AuthenticationRequest struct {
|
||||
version byte
|
||||
@@ -34,13 +29,6 @@ type Socks5AuthenticationRequest struct {
|
||||
authMethods [256]byte
|
||||
}
|
||||
|
||||
type Socks4AuthenticationRequest struct {
|
||||
Version byte
|
||||
Command byte
|
||||
Port uint16
|
||||
IP [4]byte
|
||||
}
|
||||
|
||||
func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
|
||||
for i := 0; i < int(request.nMethods); i++ {
|
||||
if request.authMethods[i] == method {
|
||||
@@ -54,11 +42,11 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, aut
|
||||
buffer := make([]byte, 256)
|
||||
nBytes, err := reader.Read(buffer)
|
||||
if err != nil {
|
||||
log.Error("Failed to read socks authentication: %v", err)
|
||||
return
|
||||
}
|
||||
if nBytes < 2 {
|
||||
err = fmt.Errorf("Expected 2 bytes read, but actaully %d bytes read", nBytes)
|
||||
log.Info("Socks expected 2 bytes read, but only %d bytes read", nBytes)
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,24 +55,26 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, aut
|
||||
auth4.Command = buffer[1]
|
||||
auth4.Port = binary.BigEndian.Uint16(buffer[2:4])
|
||||
copy(auth4.IP[:], buffer[4:8])
|
||||
err = ErrorSocksVersion4
|
||||
err = NewSocksVersion4Error()
|
||||
return
|
||||
}
|
||||
|
||||
auth.version = buffer[0]
|
||||
if auth.version != socksVersion {
|
||||
err = fmt.Errorf("Unknown SOCKS version %d", auth.version)
|
||||
err = errors.NewProtocolVersionError(int(auth.version))
|
||||
return
|
||||
}
|
||||
|
||||
auth.nMethods = buffer[1]
|
||||
if auth.nMethods <= 0 {
|
||||
err = fmt.Errorf("Zero length of authentication methods")
|
||||
log.Info("Zero length of authentication methods")
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
|
||||
if nBytes-2 != int(auth.nMethods) {
|
||||
err = fmt.Errorf("Unmatching number of auth methods, expecting %d, but got %d", auth.nMethods, nBytes)
|
||||
log.Info("Unmatching number of auth methods, expecting %d, but got %d", auth.nMethods, nBytes)
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
copy(auth.authMethods[:], buffer[2:nBytes])
|
||||
@@ -96,12 +86,6 @@ type Socks5AuthenticationResponse struct {
|
||||
authMethod byte
|
||||
}
|
||||
|
||||
type Socks4AuthenticationResponse struct {
|
||||
result byte
|
||||
port uint16
|
||||
ip []byte
|
||||
}
|
||||
|
||||
func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
|
||||
return &Socks5AuthenticationResponse{
|
||||
version: socksVersion,
|
||||
@@ -109,29 +93,11 @@ func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
func NewSocks4AuthenticationResponse(result byte, port uint16, ip []byte) *Socks4AuthenticationResponse {
|
||||
return &Socks4AuthenticationResponse{
|
||||
result: result,
|
||||
port: port,
|
||||
ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
func WriteAuthentication(writer io.Writer, r *Socks5AuthenticationResponse) error {
|
||||
_, err := writer.Write([]byte{r.version, r.authMethod})
|
||||
return err
|
||||
}
|
||||
|
||||
func WriteSocks4AuthenticationResponse(writer io.Writer, r *Socks4AuthenticationResponse) error {
|
||||
buffer := make([]byte, 8)
|
||||
// buffer[0] is always 0
|
||||
buffer[1] = r.result
|
||||
binary.BigEndian.PutUint16(buffer[2:4], r.port)
|
||||
copy(buffer[4:], r.ip)
|
||||
_, err := writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
type Socks5UserPassRequest struct {
|
||||
version byte
|
||||
username string
|
||||
@@ -142,6 +108,10 @@ func (request Socks5UserPassRequest) IsValid(username string, password string) b
|
||||
return request.username == username && request.password == password
|
||||
}
|
||||
|
||||
func (request Socks5UserPassRequest) AuthDetail() string {
|
||||
return request.username + ":" + request.password
|
||||
}
|
||||
|
||||
func ReadUserPassRequest(reader io.Reader) (request Socks5UserPassRequest, err error) {
|
||||
buffer := make([]byte, 256)
|
||||
_, err = reader.Read(buffer[0:2])
|
||||
@@ -207,14 +177,13 @@ type Socks5Request struct {
|
||||
}
|
||||
|
||||
func ReadRequest(reader io.Reader) (request *Socks5Request, err error) {
|
||||
|
||||
buffer := make([]byte, 4)
|
||||
nBytes, err := reader.Read(buffer)
|
||||
buffer := make([]byte, 256)
|
||||
nBytes, err := reader.Read(buffer[:4])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if nBytes < len(buffer) {
|
||||
err = fmt.Errorf("Unable to read request.")
|
||||
if nBytes < 4 {
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
request = &Socks5Request{
|
||||
@@ -230,11 +199,10 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) {
|
||||
return
|
||||
}
|
||||
if nBytes != 4 {
|
||||
err = fmt.Errorf("Unable to read IPv4 address.")
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
case AddrTypeDomain:
|
||||
buffer = make([]byte, 256)
|
||||
nBytes, err = reader.Read(buffer[0:1])
|
||||
if err != nil {
|
||||
return
|
||||
@@ -246,7 +214,8 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) {
|
||||
}
|
||||
|
||||
if nBytes != int(domainLength) {
|
||||
err = fmt.Errorf("Unable to read domain with %d bytes, expecting %d bytes", nBytes, domainLength)
|
||||
log.Info("Unable to read domain with %d bytes, expecting %d bytes", nBytes, domainLength)
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
request.Domain = string(buffer[:domainLength])
|
||||
@@ -256,21 +225,21 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) {
|
||||
return
|
||||
}
|
||||
if nBytes != 16 {
|
||||
err = fmt.Errorf("Unable to read IPv4 address.")
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("Unexpected address type %d", request.AddrType)
|
||||
log.Info("Unexpected address type %d", request.AddrType)
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
|
||||
buffer = make([]byte, 2)
|
||||
nBytes, err = reader.Read(buffer)
|
||||
nBytes, err = reader.Read(buffer[:2])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if nBytes != 2 {
|
||||
err = fmt.Errorf("Unable to read port.")
|
||||
err = errors.NewCorruptedPacketError()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -351,9 +320,7 @@ func (r *Socks5Response) toBytes() []byte {
|
||||
case 0x04:
|
||||
buffer = append(buffer, r.IPv6[:]...)
|
||||
}
|
||||
portBuffer := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(portBuffer, r.Port)
|
||||
buffer = append(buffer, portBuffer...)
|
||||
buffer = append(buffer, byte(r.Port>>8), byte(r.Port))
|
||||
return buffer
|
||||
}
|
||||
|
||||
|
||||
51
proxy/socks/protocol/socks4.go
Normal file
51
proxy/socks/protocol/socks4.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
)
|
||||
|
||||
type SocksVersion4Error struct {
|
||||
errors.ErrorCode
|
||||
}
|
||||
|
||||
var socksVersion4ErrorInstance = SocksVersion4Error{ErrorCode: 1000}
|
||||
|
||||
func NewSocksVersion4Error() SocksVersion4Error {
|
||||
return socksVersion4ErrorInstance
|
||||
}
|
||||
|
||||
func (err SocksVersion4Error) Error() string {
|
||||
return err.Prefix() + "Request is socks version 4."
|
||||
}
|
||||
|
||||
type Socks4AuthenticationRequest struct {
|
||||
Version byte
|
||||
Command byte
|
||||
Port uint16
|
||||
IP [4]byte
|
||||
}
|
||||
|
||||
type Socks4AuthenticationResponse struct {
|
||||
result byte
|
||||
port uint16
|
||||
ip []byte
|
||||
}
|
||||
|
||||
func NewSocks4AuthenticationResponse(result byte, port uint16, ip []byte) *Socks4AuthenticationResponse {
|
||||
return &Socks4AuthenticationResponse{
|
||||
result: result,
|
||||
port: port,
|
||||
ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Socks4AuthenticationResponse) ToBytes(buffer []byte) []byte {
|
||||
if buffer == nil {
|
||||
buffer = make([]byte, 8)
|
||||
}
|
||||
buffer[1] = r.result
|
||||
buffer[2] = byte(r.port >> 8)
|
||||
buffer[3] = byte(r.port)
|
||||
copy(buffer[4:], r.ip)
|
||||
return buffer
|
||||
}
|
||||
37
proxy/socks/protocol/socks4_test.go
Normal file
37
proxy/socks/protocol/socks4_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/v2ray/v2ray-core/testing/unit"
|
||||
)
|
||||
|
||||
func TestSocks4AuthenticationRequestRead(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
rawRequest := []byte{
|
||||
0x04, // version
|
||||
0x01, // command
|
||||
0x00, 0x35,
|
||||
0x72, 0x72, 0x72, 0x72,
|
||||
}
|
||||
_, request4, err := ReadAuthentication(bytes.NewReader(rawRequest))
|
||||
assert.Error(err).HasCode(1000)
|
||||
assert.Byte(request4.Version).Named("Version").Equals(0x04)
|
||||
assert.Byte(request4.Command).Named("Command").Equals(0x01)
|
||||
assert.Uint16(request4.Port).Named("Port").Equals(53)
|
||||
assert.Bytes(request4.IP[:]).Named("IP").Equals([]byte{0x72, 0x72, 0x72, 0x72})
|
||||
}
|
||||
|
||||
func TestSocks4AuthenticationResponseToBytes(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
response := &Socks4AuthenticationResponse{
|
||||
result: byte(0x10),
|
||||
port: 443,
|
||||
ip: []byte{1, 2, 3, 4},
|
||||
}
|
||||
responseBytes := response.ToBytes(nil)
|
||||
assert.Bytes(responseBytes).Equals([]byte{0x00, 0x10, 0x01, 0xBB, 0x01, 0x02, 0x03, 0x04})
|
||||
}
|
||||
@@ -37,23 +37,6 @@ func TestAuthenticationRequestRead(t *testing.T) {
|
||||
assert.Byte(request.authMethods[0]).Named("Auth Method").Equals(0x02)
|
||||
}
|
||||
|
||||
func TestAuthentication4RequestRead(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
rawRequest := []byte{
|
||||
0x04, // version
|
||||
0x01, // command
|
||||
0x00, 0x35,
|
||||
0x72, 0x72, 0x72, 0x72,
|
||||
}
|
||||
_, request4, err := ReadAuthentication(bytes.NewReader(rawRequest))
|
||||
assert.Error(err).Equals(ErrorSocksVersion4)
|
||||
assert.Byte(request4.Version).Named("Version").Equals(0x04)
|
||||
assert.Byte(request4.Command).Named("Command").Equals(0x01)
|
||||
assert.Uint16(request4.Port).Named("Port").Equals(53)
|
||||
assert.Bytes(request4.IP[:]).Named("IP").Equals([]byte{0x72, 0x72, 0x72, 0x72})
|
||||
}
|
||||
|
||||
func TestAuthenticationResponseWrite(t *testing.T) {
|
||||
assert := unit.Assert(t)
|
||||
|
||||
|
||||
@@ -1,19 +1,59 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"io"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorUnknownAddressType = errors.New("Unknown Address Type.")
|
||||
)
|
||||
|
||||
type Socks5UDPRequest struct {
|
||||
fragment byte
|
||||
address v2net.Address
|
||||
data []byte
|
||||
Fragment byte
|
||||
Address v2net.Address
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func ReadUDPRequest(reader io.Reader) (request Socks5UDPRequest, err error) {
|
||||
//buf := make([]byte, 4 * 1024) // Regular UDP packet size is 1500 bytes.
|
||||
func (request *Socks5UDPRequest) Destination() v2net.Destination {
|
||||
return v2net.NewUDPDestination(request.Address)
|
||||
}
|
||||
|
||||
func ReadUDPRequest(packet []byte) (request Socks5UDPRequest, err error) {
|
||||
// packet[0] and packet[1] are reserved
|
||||
request.Fragment = packet[2]
|
||||
|
||||
addrType := packet[3]
|
||||
var dataBegin int
|
||||
|
||||
switch addrType {
|
||||
case AddrTypeIPv4:
|
||||
ip := packet[4:8]
|
||||
port := binary.BigEndian.Uint16(packet[8:10])
|
||||
request.Address = v2net.IPAddress(ip, port)
|
||||
dataBegin = 10
|
||||
case AddrTypeIPv6:
|
||||
ip := packet[4:20]
|
||||
port := binary.BigEndian.Uint16(packet[20:22])
|
||||
request.Address = v2net.IPAddress(ip, port)
|
||||
dataBegin = 22
|
||||
case AddrTypeDomain:
|
||||
domainLength := int(packet[4])
|
||||
domain := string(packet[5 : 5+domainLength])
|
||||
port := binary.BigEndian.Uint16(packet[5+domainLength : 5+domainLength+2])
|
||||
request.Address = v2net.DomainAddress(domain, port)
|
||||
dataBegin = 5 + domainLength + 2
|
||||
default:
|
||||
log.Warning("Unknown address type %d", addrType)
|
||||
err = ErrorUnknownAddressType
|
||||
return
|
||||
}
|
||||
|
||||
request.Data = make([]byte, len(packet)-dataBegin)
|
||||
copy(request.Data, packet[dataBegin:])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
_ "bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/v2ray/v2ray-core"
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
protocol "github.com/v2ray/v2ray-core/proxy/socks/protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorAuthenticationFailed = errors.New("None of the authentication methods is allowed.")
|
||||
ErrorCommandNotSupported = errors.New("Client requested an unsupported command.")
|
||||
ErrorInvalidUser = errors.New("Invalid username or password.")
|
||||
jsonconfig "github.com/v2ray/v2ray-core/proxy/socks/config/json"
|
||||
"github.com/v2ray/v2ray-core/proxy/socks/protocol"
|
||||
)
|
||||
|
||||
// SocksServer is a SOCKS 5 proxy server
|
||||
type SocksServer struct {
|
||||
accepting bool
|
||||
vPoint *core.Point
|
||||
config SocksConfig
|
||||
config jsonconfig.SocksConfig
|
||||
}
|
||||
|
||||
func NewSocksServer(vp *core.Point, rawConfig []byte) *SocksServer {
|
||||
config, err := loadConfig(rawConfig)
|
||||
config, err := jsonconfig.Load(rawConfig)
|
||||
if err != nil {
|
||||
panic(log.Error("Unable to load socks config: %v", err))
|
||||
log.Error("Unable to load socks config: %v", err)
|
||||
panic(errors.NewConfigurationError())
|
||||
}
|
||||
return &SocksServer{
|
||||
vPoint: vp,
|
||||
@@ -40,10 +36,9 @@ func NewSocksServer(vp *core.Point, rawConfig []byte) *SocksServer {
|
||||
func (server *SocksServer) Listen(port uint16) error {
|
||||
listener, err := net.Listen("tcp", ":"+strconv.Itoa(int(port)))
|
||||
if err != nil {
|
||||
log.Error("Error on listening port %d: %v", port, err)
|
||||
log.Error("Socks failed to listen on port %d: %v", port, err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Working on tcp:%d", port)
|
||||
server.accepting = true
|
||||
go server.AcceptConnections(listener)
|
||||
return nil
|
||||
@@ -53,7 +48,8 @@ func (server *SocksServer) AcceptConnections(listener net.Listener) {
|
||||
for server.accepting {
|
||||
connection, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Error("Error on accepting socks connection: %v", err)
|
||||
log.Error("Socks failed to accept new connection %v", err)
|
||||
return
|
||||
}
|
||||
go server.HandleConnection(connection)
|
||||
}
|
||||
@@ -62,33 +58,33 @@ func (server *SocksServer) AcceptConnections(listener net.Listener) {
|
||||
func (server *SocksServer) HandleConnection(connection net.Conn) error {
|
||||
defer connection.Close()
|
||||
|
||||
reader := connection.(io.Reader)
|
||||
reader := v2net.NewTimeOutReader(4, connection)
|
||||
|
||||
auth, auth4, err := protocol.ReadAuthentication(reader)
|
||||
if err != nil && err != protocol.ErrorSocksVersion4 {
|
||||
log.Error("Error on reading authentication: %v", err)
|
||||
if err != nil && !errors.HasCode(err, 1000) {
|
||||
log.Error("Socks failed to read authentication: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var dest v2net.Destination
|
||||
|
||||
// TODO refactor this part
|
||||
if err == protocol.ErrorSocksVersion4 {
|
||||
if errors.HasCode(err, 1000) {
|
||||
result := protocol.Socks4RequestGranted
|
||||
if auth4.Command == protocol.CmdBind {
|
||||
result = protocol.Socks4RequestRejected
|
||||
}
|
||||
socks4Response := protocol.NewSocks4AuthenticationResponse(result, auth4.Port, auth4.IP[:])
|
||||
protocol.WriteSocks4AuthenticationResponse(connection, socks4Response)
|
||||
connection.Write(socks4Response.ToBytes(nil))
|
||||
|
||||
if result == protocol.Socks4RequestRejected {
|
||||
return ErrorCommandNotSupported
|
||||
return errors.NewInvalidOperationError("Socks4 command " + strconv.Itoa(int(auth4.Command)))
|
||||
}
|
||||
|
||||
dest = v2net.NewTCPDestination(v2net.IPAddress(auth4.IP[:], auth4.Port))
|
||||
} else {
|
||||
expectedAuthMethod := protocol.AuthNotRequired
|
||||
if server.config.AuthMethod == JsonAuthMethodUserPass {
|
||||
if server.config.IsPassword() {
|
||||
expectedAuthMethod = protocol.AuthUserPass
|
||||
}
|
||||
|
||||
@@ -96,23 +92,23 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
|
||||
authResponse := protocol.NewAuthenticationResponse(protocol.AuthNoMatchingMethod)
|
||||
err = protocol.WriteAuthentication(connection, authResponse)
|
||||
if err != nil {
|
||||
log.Error("Error on socksio write authentication: %v", err)
|
||||
log.Error("Socks failed to write authentication: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Warning("Client doesn't support allowed any auth methods.")
|
||||
return ErrorAuthenticationFailed
|
||||
log.Warning("Socks client doesn't support allowed any auth methods.")
|
||||
return errors.NewInvalidOperationError("Unsupported auth methods.")
|
||||
}
|
||||
|
||||
authResponse := protocol.NewAuthenticationResponse(expectedAuthMethod)
|
||||
err = protocol.WriteAuthentication(connection, authResponse)
|
||||
if err != nil {
|
||||
log.Error("Error on socksio write authentication: %v", err)
|
||||
log.Error("Socks failed to write authentication: %v", err)
|
||||
return err
|
||||
}
|
||||
if server.config.AuthMethod == JsonAuthMethodUserPass {
|
||||
if server.config.IsPassword() {
|
||||
upRequest, err := protocol.ReadUserPassRequest(reader)
|
||||
if err != nil {
|
||||
log.Error("Failed to read username and password: %v", err)
|
||||
log.Error("Socks failed to read username and password: %v", err)
|
||||
return err
|
||||
}
|
||||
status := byte(0)
|
||||
@@ -122,17 +118,19 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
|
||||
upResponse := protocol.NewSocks5UserPassResponse(status)
|
||||
err = protocol.WriteUserPassResponse(connection, upResponse)
|
||||
if err != nil {
|
||||
log.Error("Error on socksio write user pass response: %v", err)
|
||||
log.Error("Socks failed to write user pass response: %v", err)
|
||||
return err
|
||||
}
|
||||
if status != byte(0) {
|
||||
return ErrorInvalidUser
|
||||
err = errors.NewAuthenticationError(upRequest.AuthDetail())
|
||||
log.Warning(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
request, err := protocol.ReadRequest(reader)
|
||||
if err != nil {
|
||||
log.Error("Error on reading socks request: %v", err)
|
||||
log.Error("Socks failed to read request: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -143,11 +141,11 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
|
||||
response.Error = protocol.ErrorCommandNotSupported
|
||||
err = protocol.WriteResponse(connection, response)
|
||||
if err != nil {
|
||||
log.Error("Error on socksio write response: %v", err)
|
||||
log.Error("Socks failed to write response: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Warning("Unsupported socks command %d", request.Command)
|
||||
return ErrorCommandNotSupported
|
||||
return errors.NewInvalidOperationError("Socks command " + strconv.Itoa(int(request.Command)))
|
||||
}
|
||||
|
||||
response.Error = protocol.ErrorSuccess
|
||||
@@ -163,35 +161,34 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
|
||||
}
|
||||
err = protocol.WriteResponse(connection, response)
|
||||
if err != nil {
|
||||
log.Error("Error on socksio write response: %v", err)
|
||||
log.Error("Socks failed to write response: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dest = request.Destination()
|
||||
}
|
||||
|
||||
ray := server.vPoint.NewInboundConnectionAccepted(dest)
|
||||
ray := server.vPoint.DispatchToOutbound(v2net.NewTCPPacket(dest))
|
||||
input := ray.InboundInput()
|
||||
output := ray.InboundOutput()
|
||||
readFinish := make(chan bool)
|
||||
writeFinish := make(chan bool)
|
||||
var readFinish, writeFinish sync.Mutex
|
||||
readFinish.Lock()
|
||||
writeFinish.Lock()
|
||||
|
||||
go server.dumpInput(reader, input, readFinish)
|
||||
go server.dumpOutput(connection, output, writeFinish)
|
||||
<-writeFinish
|
||||
go dumpInput(reader, input, &readFinish)
|
||||
go dumpOutput(connection, output, &writeFinish)
|
||||
writeFinish.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *SocksServer) dumpInput(reader io.Reader, input chan<- []byte, finish chan<- bool) {
|
||||
func dumpInput(reader io.Reader, input chan<- []byte, finish *sync.Mutex) {
|
||||
v2net.ReaderToChan(input, reader)
|
||||
finish.Unlock()
|
||||
close(input)
|
||||
log.Debug("Socks input closed")
|
||||
finish <- true
|
||||
}
|
||||
|
||||
func (server *SocksServer) dumpOutput(writer io.Writer, output <-chan []byte, finish chan<- bool) {
|
||||
func dumpOutput(writer io.Writer, output <-chan []byte, finish *sync.Mutex) {
|
||||
v2net.ChanToWriter(writer, output)
|
||||
log.Debug("Socks output closed")
|
||||
finish <- true
|
||||
finish.Unlock()
|
||||
}
|
||||
|
||||
52
proxy/socks/udp.go
Normal file
52
proxy/socks/udp.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
"github.com/v2ray/v2ray-core/proxy/socks/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
bufferSize = 2 * 1024
|
||||
)
|
||||
|
||||
func (server *SocksServer) ListenUDP(port uint16) error {
|
||||
addr := &net.UDPAddr{
|
||||
IP: net.IP{0, 0, 0, 0},
|
||||
Port: int(port),
|
||||
Zone: "",
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
log.Error("Socks failed to listen UDP on port %d: %v", port, err)
|
||||
return err
|
||||
}
|
||||
|
||||
go server.AcceptPackets(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *SocksServer) AcceptPackets(conn *net.UDPConn) error {
|
||||
for {
|
||||
buffer := make([]byte, 0, bufferSize)
|
||||
nBytes, _, err := conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
log.Error("Socks failed to read UDP packets: %v", err)
|
||||
return err
|
||||
}
|
||||
request, err := protocol.ReadUDPRequest(buffer[:nBytes])
|
||||
if err != nil {
|
||||
log.Error("Socks failed to parse UDP request: %v", err)
|
||||
return err
|
||||
}
|
||||
if request.Fragment != 0 {
|
||||
// TODO handle fragments
|
||||
continue
|
||||
}
|
||||
|
||||
udpPacket := v2net.NewUDPPacket(request.Destination(), request.Data)
|
||||
server.vPoint.DispatchToOutbound(udpPacket)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/v2ray/v2ray-core/proxy/vmess/protocol/user"
|
||||
)
|
||||
|
||||
// VMessUser is an authenticated user account in VMess configuration.
|
||||
type VMessUser struct {
|
||||
Id string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
@@ -21,6 +22,7 @@ func (u *VMessUser) ToUser() (user.User, error) {
|
||||
}, err
|
||||
}
|
||||
|
||||
// VMessInboundConfig is
|
||||
type VMessInboundConfig struct {
|
||||
AllowedClients []VMessUser `json:"clients"`
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ const (
|
||||
// The ID of en entity, in the form of an UUID.
|
||||
type ID struct {
|
||||
String string
|
||||
Bytes []byte
|
||||
cmdKey []byte
|
||||
Bytes [IDBytesLen]byte
|
||||
cmdKey [IDBytesLen]byte
|
||||
}
|
||||
|
||||
func NewID(id string) (ID, error) {
|
||||
@@ -25,27 +25,25 @@ func NewID(id string) (ID, error) {
|
||||
}
|
||||
|
||||
md5hash := md5.New()
|
||||
md5hash.Write(idBytes)
|
||||
md5hash.Write(idBytes[:])
|
||||
md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21"))
|
||||
cmdKey := md5.Sum(nil)
|
||||
|
||||
return ID{
|
||||
String: id,
|
||||
Bytes: idBytes,
|
||||
cmdKey: cmdKey[:],
|
||||
cmdKey: cmdKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v ID) CmdKey() []byte {
|
||||
return v.cmdKey
|
||||
return v.cmdKey[:]
|
||||
}
|
||||
|
||||
var byteGroups = []int{8, 4, 4, 4, 12}
|
||||
|
||||
// TODO: leverage a full functional UUID library
|
||||
func UUIDToID(uuid string) (v []byte, err error) {
|
||||
v = make([]byte, 16)
|
||||
|
||||
func UUIDToID(uuid string) (v [IDBytesLen]byte, err error) {
|
||||
text := []byte(uuid)
|
||||
if len(text) < 32 {
|
||||
err = log.Error("uuid: invalid UUID string: %s", text)
|
||||
|
||||
@@ -13,5 +13,5 @@ func TestUUIDToID(t *testing.T) {
|
||||
expectedBytes := []byte{0x24, 0x18, 0xd0, 0x87, 0x64, 0x8d, 0x49, 0x90, 0x86, 0xe8, 0x19, 0xdc, 0xa1, 0xd0, 0x06, 0xd3}
|
||||
|
||||
actualBytes, _ := NewID(uuid)
|
||||
assert.Bytes(actualBytes.Bytes).Named("UUID").Equals(expectedBytes)
|
||||
assert.Bytes(actualBytes.Bytes[:]).Named("UUID").Equals(expectedBytes)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/collect"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
)
|
||||
|
||||
@@ -19,8 +19,7 @@ type UserSet interface {
|
||||
|
||||
type TimedUserSet struct {
|
||||
validUserIds []ID
|
||||
userHashes map[string]indexTimePair
|
||||
hash2Remove hashEntrySet
|
||||
userHash *collect.TimedStringMap
|
||||
}
|
||||
|
||||
type indexTimePair struct {
|
||||
@@ -28,58 +27,21 @@ type indexTimePair struct {
|
||||
timeSec int64
|
||||
}
|
||||
|
||||
type hashEntry struct {
|
||||
hash string
|
||||
timeSec int64
|
||||
}
|
||||
|
||||
type hashEntrySet []*hashEntry
|
||||
|
||||
func (set hashEntrySet) Len() int {
|
||||
return len(set)
|
||||
}
|
||||
|
||||
func (set hashEntrySet) Less(i, j int) bool {
|
||||
return set[i].timeSec < set[j].timeSec
|
||||
}
|
||||
|
||||
func (set hashEntrySet) Swap(i, j int) {
|
||||
tmp := set[i]
|
||||
set[i] = set[j]
|
||||
set[j] = tmp
|
||||
}
|
||||
|
||||
func (set *hashEntrySet) Push(value interface{}) {
|
||||
entry := value.(*hashEntry)
|
||||
*set = append(*set, entry)
|
||||
}
|
||||
|
||||
func (set *hashEntrySet) Pop() interface{} {
|
||||
old := *set
|
||||
n := len(old)
|
||||
v := old[n-1]
|
||||
*set = old[:n-1]
|
||||
return v
|
||||
}
|
||||
|
||||
func NewTimedUserSet() UserSet {
|
||||
vuSet := new(TimedUserSet)
|
||||
vuSet.validUserIds = make([]ID, 0, 16)
|
||||
vuSet.userHashes = make(map[string]indexTimePair)
|
||||
vuSet.hash2Remove = make(hashEntrySet, 0, cacheDurationSec*10)
|
||||
|
||||
go vuSet.updateUserHash(time.Tick(updateIntervalSec * time.Second))
|
||||
return vuSet
|
||||
tus := &TimedUserSet{
|
||||
validUserIds: make([]ID, 0, 16),
|
||||
userHash: collect.NewTimedStringMap(updateIntervalSec),
|
||||
}
|
||||
go tus.updateUserHash(time.Tick(updateIntervalSec * time.Second))
|
||||
return tus
|
||||
}
|
||||
|
||||
func (us *TimedUserSet) generateNewHashes(lastSec, nowSec int64, idx int, id ID) {
|
||||
idHash := NewTimeHash(HMACHash{})
|
||||
for lastSec < nowSec+cacheDurationSec {
|
||||
|
||||
idHash := idHash.Hash(id.Bytes, lastSec)
|
||||
idHash := idHash.Hash(id.Bytes[:], lastSec)
|
||||
log.Debug("Valid User Hash: %v", idHash)
|
||||
heap.Push(&us.hash2Remove, &hashEntry{string(idHash), lastSec})
|
||||
us.userHashes[string(idHash)] = indexTimePair{idx, lastSec}
|
||||
us.userHash.Set(string(idHash), indexTimePair{idx, lastSec}, lastSec+2*cacheDurationSec)
|
||||
lastSec++
|
||||
}
|
||||
}
|
||||
@@ -87,24 +49,14 @@ func (us *TimedUserSet) generateNewHashes(lastSec, nowSec int64, idx int, id ID)
|
||||
func (us *TimedUserSet) updateUserHash(tick <-chan time.Time) {
|
||||
now := time.Now().UTC()
|
||||
lastSec := now.Unix()
|
||||
lastSec2Remove := now.Unix()
|
||||
|
||||
for {
|
||||
now := <-tick
|
||||
nowSec := now.UTC().Unix()
|
||||
|
||||
remove2Sec := nowSec - cacheDurationSec
|
||||
if remove2Sec > lastSec2Remove {
|
||||
for lastSec2Remove+1 < remove2Sec {
|
||||
front := heap.Pop(&us.hash2Remove)
|
||||
entry := front.(*hashEntry)
|
||||
lastSec2Remove = entry.timeSec
|
||||
delete(us.userHashes, entry.hash)
|
||||
}
|
||||
}
|
||||
for idx, id := range us.validUserIds {
|
||||
us.generateNewHashes(lastSec, nowSec, idx, id)
|
||||
}
|
||||
lastSec = nowSec
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +73,9 @@ func (us *TimedUserSet) AddUser(user User) error {
|
||||
}
|
||||
|
||||
func (us TimedUserSet) GetUser(userHash []byte) (*ID, int64, bool) {
|
||||
pair, found := us.userHashes[string(userHash)]
|
||||
rawPair, found := us.userHash.Get(string(userHash))
|
||||
if found {
|
||||
pair := rawPair.(indexTimePair)
|
||||
return &us.validUserIds[pair.index], pair.timeSec, true
|
||||
}
|
||||
return nil, 0, false
|
||||
|
||||
@@ -4,14 +4,12 @@ package protocol
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
v2io "github.com/v2ray/v2ray-core/common/io"
|
||||
"github.com/v2ray/v2ray-core/common/log"
|
||||
v2net "github.com/v2ray/v2ray-core/common/net"
|
||||
@@ -31,15 +29,9 @@ const (
|
||||
blockSize = 16
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorInvalidUser = errors.New("Invalid User")
|
||||
ErrorInvalidVerion = errors.New("Invalid Version")
|
||||
)
|
||||
|
||||
// VMessRequest implements the request message of VMess protocol. It only contains
|
||||
// the header of a request message. The data part will be handled by conection
|
||||
// handler directly, in favor of data streaming.
|
||||
|
||||
// VMessRequest implements the request message of VMess protocol. It only contains the header of a
|
||||
// request message. The data part will be handled by conection handler directly, in favor of data
|
||||
// streaming.
|
||||
type VMessRequest struct {
|
||||
Version byte
|
||||
UserId user.ID
|
||||
@@ -50,6 +42,7 @@ type VMessRequest struct {
|
||||
Address v2net.Address
|
||||
}
|
||||
|
||||
// Destination is the final destination of this request.
|
||||
func (request *VMessRequest) Destination() v2net.Destination {
|
||||
if request.Command == CmdTCP {
|
||||
return v2net.NewTCPDestination(request.Address)
|
||||
@@ -58,16 +51,19 @@ func (request *VMessRequest) Destination() v2net.Destination {
|
||||
}
|
||||
}
|
||||
|
||||
// VMessRequestReader is a parser to read VMessRequest from a byte stream.
|
||||
type VMessRequestReader struct {
|
||||
vUserSet user.UserSet
|
||||
}
|
||||
|
||||
// NewVMessRequestReader creates a new VMessRequestReader with a given UserSet
|
||||
func NewVMessRequestReader(vUserSet user.UserSet) *VMessRequestReader {
|
||||
return &VMessRequestReader{
|
||||
vUserSet: vUserSet,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads a VMessRequest from a byte stream.
|
||||
func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) {
|
||||
buffer := make([]byte, 256)
|
||||
|
||||
@@ -80,7 +76,7 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) {
|
||||
|
||||
userId, timeSec, valid := r.vUserSet.GetUser(buffer[:nBytes])
|
||||
if !valid {
|
||||
return nil, ErrorInvalidUser
|
||||
return nil, errors.NewAuthenticationError(buffer[:nBytes])
|
||||
}
|
||||
|
||||
aesCipher, err := aes.NewCipher(userId.CmdKey())
|
||||
@@ -94,24 +90,11 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nBytes, err = decryptor.Read(buffer[0:1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
randomLength := buffer[0]
|
||||
if randomLength <= 0 || randomLength > 32 {
|
||||
return nil, fmt.Errorf("Unexpected random length %d", randomLength)
|
||||
}
|
||||
_, err = decryptor.Read(buffer[:randomLength])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nBytes, err = decryptor.Read(buffer[0:1])
|
||||
nBytes, err = decryptor.Read(buffer[:41])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bufferLen := nBytes
|
||||
|
||||
request := &VMessRequest{
|
||||
UserId: *userId,
|
||||
@@ -119,97 +102,76 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) {
|
||||
}
|
||||
|
||||
if request.Version != Version {
|
||||
log.Error("Unknown VMess version %d", request.Version)
|
||||
return nil, ErrorInvalidVerion
|
||||
return nil, errors.NewProtocolVersionError(int(request.Version))
|
||||
}
|
||||
|
||||
// TODO: check number of bytes returned
|
||||
_, err = decryptor.Read(request.RequestIV[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = decryptor.Read(request.RequestKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = decryptor.Read(request.ResponseHeader[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = decryptor.Read(buffer[0:1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Command = buffer[0]
|
||||
copy(request.RequestIV[:], buffer[1:17]) // 16 bytes
|
||||
copy(request.RequestKey[:], buffer[17:33]) // 16 bytes
|
||||
copy(request.ResponseHeader[:], buffer[33:37]) // 4 bytes
|
||||
request.Command = buffer[37]
|
||||
|
||||
_, err = decryptor.Read(buffer[0:2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port := binary.BigEndian.Uint16(buffer[0:2])
|
||||
port := binary.BigEndian.Uint16(buffer[38:40])
|
||||
|
||||
_, err = decryptor.Read(buffer[0:1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch buffer[0] {
|
||||
switch buffer[40] {
|
||||
case addrTypeIPv4:
|
||||
_, err = decryptor.Read(buffer[1:5])
|
||||
_, err = decryptor.Read(buffer[41:45]) // 4 bytes
|
||||
bufferLen += 4
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Address = v2net.IPAddress(buffer[1:5], port)
|
||||
request.Address = v2net.IPAddress(buffer[41:45], port)
|
||||
case addrTypeIPv6:
|
||||
_, err = decryptor.Read(buffer[1:17])
|
||||
_, err = decryptor.Read(buffer[41:57]) // 16 bytes
|
||||
bufferLen += 16
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Address = v2net.IPAddress(buffer[1:17], port)
|
||||
request.Address = v2net.IPAddress(buffer[41:57], port)
|
||||
case addrTypeDomain:
|
||||
_, err = decryptor.Read(buffer[1:2])
|
||||
_, err = decryptor.Read(buffer[41:42])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainLength := buffer[1]
|
||||
_, err = decryptor.Read(buffer[2 : 2+domainLength])
|
||||
domainLength := int(buffer[41])
|
||||
_, err = decryptor.Read(buffer[42 : 42+domainLength])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Address = v2net.DomainAddress(string(buffer[2:2+domainLength]), port)
|
||||
bufferLen += 1 + domainLength
|
||||
request.Address = v2net.DomainAddress(string(buffer[42:42+domainLength]), port)
|
||||
}
|
||||
_, err = decryptor.Read(buffer[0:1])
|
||||
|
||||
_, err = decryptor.Read(buffer[bufferLen : bufferLen+4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
randomLength = buffer[0]
|
||||
_, err = decryptor.Read(buffer[:randomLength])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
fnv1a := fnv.New32a()
|
||||
fnv1a.Write(buffer[:bufferLen])
|
||||
actualHash := fnv1a.Sum32()
|
||||
expectedHash := binary.BigEndian.Uint32(buffer[bufferLen : bufferLen+4])
|
||||
|
||||
if actualHash != expectedHash {
|
||||
return nil, errors.NewCorruptedPacketError()
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (request *VMessRequest) ToBytes(idHash user.CounterHash, randomRangeInt64 user.RandomInt64InRange) ([]byte, error) {
|
||||
buffer := make([]byte, 0, 300)
|
||||
// ToBytes returns a VMessRequest in the form of byte array.
|
||||
func (request *VMessRequest) ToBytes(idHash user.CounterHash, randomRangeInt64 user.RandomInt64InRange, buffer []byte) ([]byte, error) {
|
||||
if buffer == nil {
|
||||
buffer = make([]byte, 0, 300)
|
||||
}
|
||||
|
||||
counter := randomRangeInt64(time.Now().UTC().Unix(), 30)
|
||||
hash := idHash.Hash(request.UserId.Bytes, counter)
|
||||
hash := idHash.Hash(request.UserId.Bytes[:], counter)
|
||||
|
||||
log.Debug("Writing userhash: %v", hash)
|
||||
buffer = append(buffer, hash...)
|
||||
|
||||
encryptionBegin := len(buffer)
|
||||
|
||||
randomLength := mrand.Intn(32) + 1
|
||||
randomContent := make([]byte, randomLength)
|
||||
_, err := rand.Read(randomContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer = append(buffer, byte(randomLength))
|
||||
buffer = append(buffer, randomContent...)
|
||||
|
||||
buffer = append(buffer, request.Version)
|
||||
buffer = append(buffer, request.RequestIV[:]...)
|
||||
buffer = append(buffer, request.RequestKey[:]...)
|
||||
@@ -230,16 +192,18 @@ func (request *VMessRequest) ToBytes(idHash user.CounterHash, randomRangeInt64 u
|
||||
buffer = append(buffer, []byte(request.Address.Domain())...)
|
||||
}
|
||||
|
||||
paddingLength := mrand.Intn(32) + 1
|
||||
paddingBuffer := make([]byte, paddingLength)
|
||||
_, err = rand.Read(paddingBuffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer = append(buffer, byte(paddingLength))
|
||||
buffer = append(buffer, paddingBuffer...)
|
||||
encryptionEnd := len(buffer)
|
||||
|
||||
fnv1a := fnv.New32a()
|
||||
fnv1a.Write(buffer[encryptionBegin:encryptionEnd])
|
||||
|
||||
fnvHash := fnv1a.Sum32()
|
||||
buffer = append(buffer, byte(fnvHash>>24))
|
||||
buffer = append(buffer, byte(fnvHash>>16))
|
||||
buffer = append(buffer, byte(fnvHash>>8))
|
||||
buffer = append(buffer, byte(fnvHash))
|
||||
encryptionEnd += 4
|
||||
|
||||
aesCipher, err := aes.NewCipher(request.UserId.CmdKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -250,10 +214,14 @@ func (request *VMessRequest) ToBytes(idHash user.CounterHash, randomRangeInt64 u
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// VMessResponse is the header of a TCP response in VMess format.
|
||||
type VMessResponse [4]byte
|
||||
|
||||
// NewVMessResponse creates a VMessResponse from a given VMessRequest.
|
||||
func NewVMessResponse(request *VMessRequest) *VMessResponse {
|
||||
response := new(VMessResponse)
|
||||
copy(response[:], request.ResponseHeader[:])
|
||||
return response
|
||||
return &VMessResponse{
|
||||
request.ResponseHeader[0],
|
||||
request.ResponseHeader[1],
|
||||
request.ResponseHeader[2],
|
||||
request.ResponseHeader[3]}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestVMessSerialization(t *testing.T) {
|
||||
request.Address = v2net.DomainAddress("v2ray.com", 80)
|
||||
|
||||
mockTime := int64(1823730)
|
||||
buffer, err := request.ToBytes(user.NewTimeHash(user.HMACHash{}), func(base int64, delta int) int64 { return mockTime })
|
||||
buffer, err := request.ToBytes(user.NewTimeHash(user.HMACHash{}), func(base int64, delta int) int64 { return mockTime }, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -85,6 +85,6 @@ func BenchmarkVMessRequestWriting(b *testing.B) {
|
||||
request.Address = v2net.DomainAddress("v2ray.com", 80)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
request.ToBytes(user.NewTimeHash(user.HMACHash{}), user.GenerateRandomInt64InRange)
|
||||
request.ToBytes(user.NewTimeHash(user.HMACHash{}), user.GenerateRandomInt64InRange, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestVMessInAndOut(t *testing.T) {
|
||||
},
|
||||
OutboundConfigValue: &mocks.ConnectionConfig{
|
||||
ProtocolValue: "vmess",
|
||||
ContentValue: []byte("{\"vnext\":[{\"address\": \"127.0.0.1\", \"port\": 13829, \"users\":[{\"id\": \"ad937d9d-6e23-4a5a-ba23-bce5092a7c51\"}]}]}"),
|
||||
ContentValue: []byte("{\"vnext\":[{\"address\": \"127.0.0.1\", \"network\": \"tcp\", \"port\": 13829, \"users\":[{\"id\": \"ad937d9d-6e23-4a5a-ba23-bce5092a7c51\"}]}]}"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestVMessInAndOut(t *testing.T) {
|
||||
assert.Error(err).IsNil()
|
||||
|
||||
dest := v2net.NewTCPDestination(v2net.IPAddress([]byte{1, 2, 3, 4}, 80))
|
||||
ich.Communicate(dest)
|
||||
ich.Communicate(v2net.NewTCPPacket(dest))
|
||||
assert.Bytes([]byte(data2Send)).Equals(och.Data2Send.Bytes())
|
||||
assert.Bytes(ich.DataReturned.Bytes()).Equals(och.Data2Return)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/v2ray/v2ray-core"
|
||||
v2io "github.com/v2ray/v2ray-core/common/io"
|
||||
@@ -14,6 +16,14 @@ import (
|
||||
"github.com/v2ray/v2ray-core/proxy/vmess/protocol/user"
|
||||
)
|
||||
|
||||
const (
|
||||
requestReadTimeOut = 4 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
zeroTime time.Time
|
||||
)
|
||||
|
||||
type VMessInboundHandler struct {
|
||||
vPoint *core.Point
|
||||
clients user.UserSet
|
||||
@@ -52,23 +62,24 @@ func (handler *VMessInboundHandler) AcceptConnections(listener net.Listener) err
|
||||
func (handler *VMessInboundHandler) HandleConnection(connection net.Conn) error {
|
||||
defer connection.Close()
|
||||
|
||||
reader := protocol.NewVMessRequestReader(handler.clients)
|
||||
connReader := v2net.NewTimeOutReader(4, connection)
|
||||
requestReader := protocol.NewVMessRequestReader(handler.clients)
|
||||
|
||||
request, err := reader.Read(connection)
|
||||
request, err := requestReader.Read(connReader)
|
||||
if err != nil {
|
||||
log.Warning("VMessIn: Invalid request from (%s): %v", connection.RemoteAddr().String(), err)
|
||||
return err
|
||||
}
|
||||
log.Debug("VMessIn: Received request for %s", request.Address.String())
|
||||
|
||||
ray := handler.vPoint.NewInboundConnectionAccepted(request.Destination())
|
||||
ray := handler.vPoint.DispatchToOutbound(v2net.NewTCPPacket(request.Destination()))
|
||||
input := ray.InboundInput()
|
||||
output := ray.InboundOutput()
|
||||
var readFinish, writeFinish sync.Mutex
|
||||
readFinish.Lock()
|
||||
writeFinish.Lock()
|
||||
|
||||
readFinish := make(chan bool)
|
||||
writeFinish := make(chan bool)
|
||||
|
||||
go handleInput(request, connection, input, readFinish)
|
||||
go handleInput(request, connReader, input, &readFinish)
|
||||
|
||||
responseKey := md5.Sum(request.RequestKey[:])
|
||||
responseIV := md5.Sum(request.RequestIV[:])
|
||||
@@ -80,27 +91,27 @@ func (handler *VMessInboundHandler) HandleConnection(connection net.Conn) error
|
||||
}
|
||||
|
||||
// Optimize for small response packet
|
||||
buffer := make([]byte, 0, 1024)
|
||||
buffer := make([]byte, 0, 2*1024)
|
||||
buffer = append(buffer, response[:]...)
|
||||
|
||||
if data, open := <-output; open {
|
||||
buffer = append(buffer, data...)
|
||||
responseWriter.Write(buffer)
|
||||
go handleOutput(request, responseWriter, output, writeFinish)
|
||||
<-writeFinish
|
||||
go handleOutput(request, responseWriter, output, &writeFinish)
|
||||
writeFinish.Lock()
|
||||
}
|
||||
|
||||
if tcpConn, ok := connection.(*net.TCPConn); ok {
|
||||
tcpConn.CloseWrite()
|
||||
}
|
||||
<-readFinish
|
||||
readFinish.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleInput(request *protocol.VMessRequest, reader io.Reader, input chan<- []byte, finish chan<- bool) {
|
||||
func handleInput(request *protocol.VMessRequest, reader io.Reader, input chan<- []byte, finish *sync.Mutex) {
|
||||
defer close(input)
|
||||
defer close(finish)
|
||||
defer finish.Unlock()
|
||||
|
||||
requestReader, err := v2io.NewAesDecryptReader(request.RequestKey[:], request.RequestIV[:], reader)
|
||||
if err != nil {
|
||||
@@ -111,9 +122,9 @@ func handleInput(request *protocol.VMessRequest, reader io.Reader, input chan<-
|
||||
v2net.ReaderToChan(input, requestReader)
|
||||
}
|
||||
|
||||
func handleOutput(request *protocol.VMessRequest, writer io.Writer, output <-chan []byte, finish chan<- bool) {
|
||||
func handleOutput(request *protocol.VMessRequest, writer io.Writer, output <-chan []byte, finish *sync.Mutex) {
|
||||
v2net.ChanToWriter(writer, output)
|
||||
close(finish)
|
||||
finish.Unlock()
|
||||
}
|
||||
|
||||
type VMessInboundHandlerFactory struct {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/rand"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/v2ray/v2ray-core"
|
||||
v2io "github.com/v2ray/v2ray-core/common/io"
|
||||
@@ -15,6 +16,10 @@ import (
|
||||
"github.com/v2ray/v2ray-core/proxy/vmess/protocol/user"
|
||||
)
|
||||
|
||||
const (
|
||||
InfoTimeNotSync = "Please check the User ID in your vmess configuration, and make sure the time on your local and remote server are in sync."
|
||||
)
|
||||
|
||||
// VNext is the next Point server in the connection chain.
|
||||
type VNextServer struct {
|
||||
Destination v2net.Destination // Address of VNext server
|
||||
@@ -23,14 +28,14 @@ type VNextServer struct {
|
||||
|
||||
type VMessOutboundHandler struct {
|
||||
vPoint *core.Point
|
||||
dest v2net.Destination
|
||||
packet v2net.Packet
|
||||
vNextList []VNextServer
|
||||
}
|
||||
|
||||
func NewVMessOutboundHandler(vp *core.Point, vNextList []VNextServer, dest v2net.Destination) *VMessOutboundHandler {
|
||||
func NewVMessOutboundHandler(vp *core.Point, vNextList []VNextServer, firstPacket v2net.Packet) *VMessOutboundHandler {
|
||||
return &VMessOutboundHandler{
|
||||
vPoint: vp,
|
||||
dest: dest,
|
||||
packet: firstPacket,
|
||||
vNextList: vNextList,
|
||||
}
|
||||
}
|
||||
@@ -40,13 +45,20 @@ func (handler *VMessOutboundHandler) pickVNext() (v2net.Destination, user.User)
|
||||
if vNextLen == 0 {
|
||||
panic("VMessOut: Zero vNext is configured.")
|
||||
}
|
||||
vNextIndex := mrand.Intn(vNextLen)
|
||||
vNextIndex := 0
|
||||
if vNextLen > 1 {
|
||||
vNextIndex = mrand.Intn(vNextLen)
|
||||
}
|
||||
|
||||
vNext := handler.vNextList[vNextIndex]
|
||||
vNextUserLen := len(vNext.Users)
|
||||
if vNextUserLen == 0 {
|
||||
panic("VMessOut: Zero User account.")
|
||||
}
|
||||
vNextUserIndex := mrand.Intn(vNextUserLen)
|
||||
vNextUserIndex := 0
|
||||
if vNextUserLen > 1 {
|
||||
vNextUserIndex = mrand.Intn(vNextUserLen)
|
||||
}
|
||||
vNextUser := vNext.Users[vNextUserIndex]
|
||||
return vNext.Destination, vNextUser
|
||||
}
|
||||
@@ -55,58 +67,72 @@ func (handler *VMessOutboundHandler) Start(ray core.OutboundRay) error {
|
||||
vNextAddress, vNextUser := handler.pickVNext()
|
||||
|
||||
command := protocol.CmdTCP
|
||||
if handler.dest.IsUDP() {
|
||||
if handler.packet.Destination().IsUDP() {
|
||||
command = protocol.CmdUDP
|
||||
}
|
||||
request := &protocol.VMessRequest{
|
||||
Version: protocol.Version,
|
||||
UserId: vNextUser.Id,
|
||||
Command: command,
|
||||
Address: handler.dest.Address(),
|
||||
Address: handler.packet.Destination().Address(),
|
||||
}
|
||||
rand.Read(request.RequestIV[:])
|
||||
rand.Read(request.RequestKey[:])
|
||||
rand.Read(request.ResponseHeader[:])
|
||||
|
||||
go startCommunicate(request, vNextAddress, ray)
|
||||
go startCommunicate(request, vNextAddress, ray, handler.packet)
|
||||
return nil
|
||||
}
|
||||
|
||||
func startCommunicate(request *protocol.VMessRequest, dest v2net.Destination, ray core.OutboundRay) error {
|
||||
input := ray.OutboundInput()
|
||||
output := ray.OutboundOutput()
|
||||
|
||||
func startCommunicate(request *protocol.VMessRequest, dest v2net.Destination, ray core.OutboundRay, firstPacket v2net.Packet) error {
|
||||
conn, err := net.DialTCP(dest.Network(), nil, &net.TCPAddr{dest.Address().IP(), int(dest.Address().Port()), ""})
|
||||
if err != nil {
|
||||
log.Error("Failed to open tcp (%s): %v", dest.String(), err)
|
||||
close(output)
|
||||
if ray != nil {
|
||||
close(ray.OutboundOutput())
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Info("VMessOut: Tunneling request for %s", request.Address.String())
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
requestFinish := make(chan bool)
|
||||
responseFinish := make(chan bool)
|
||||
if chunk := firstPacket.Chunk(); chunk != nil {
|
||||
conn.Write(chunk)
|
||||
}
|
||||
|
||||
go handleRequest(conn, request, input, requestFinish)
|
||||
go handleResponse(conn, request, output, responseFinish)
|
||||
if !firstPacket.MoreChunks() {
|
||||
if ray != nil {
|
||||
close(ray.OutboundOutput())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
<-requestFinish
|
||||
input := ray.OutboundInput()
|
||||
output := ray.OutboundOutput()
|
||||
var requestFinish, responseFinish sync.Mutex
|
||||
requestFinish.Lock()
|
||||
responseFinish.Lock()
|
||||
|
||||
go handleRequest(conn, request, input, &requestFinish)
|
||||
go handleResponse(conn, request, output, &responseFinish)
|
||||
|
||||
requestFinish.Lock()
|
||||
conn.CloseWrite()
|
||||
<-responseFinish
|
||||
responseFinish.Lock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleRequest(conn *net.TCPConn, request *protocol.VMessRequest, input <-chan []byte, finish chan<- bool) {
|
||||
defer close(finish)
|
||||
func handleRequest(conn *net.TCPConn, request *protocol.VMessRequest, input <-chan []byte, finish *sync.Mutex) {
|
||||
defer finish.Unlock()
|
||||
encryptRequestWriter, err := v2io.NewAesEncryptWriter(request.RequestKey[:], request.RequestIV[:], conn)
|
||||
if err != nil {
|
||||
log.Error("VMessOut: Failed to create encrypt writer: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
buffer, err := request.ToBytes(user.NewTimeHash(user.HMACHash{}), user.GenerateRandomInt64InRange)
|
||||
buffer := make([]byte, 0, 2*1024)
|
||||
buffer, err = request.ToBytes(user.NewTimeHash(user.HMACHash{}), user.GenerateRandomInt64InRange, buffer)
|
||||
if err != nil {
|
||||
log.Error("VMessOut: Failed to serialize VMess request: %v", err)
|
||||
return
|
||||
@@ -129,8 +155,8 @@ func handleRequest(conn *net.TCPConn, request *protocol.VMessRequest, input <-ch
|
||||
return
|
||||
}
|
||||
|
||||
func handleResponse(conn *net.TCPConn, request *protocol.VMessRequest, output chan<- []byte, finish chan<- bool) {
|
||||
defer close(finish)
|
||||
func handleResponse(conn *net.TCPConn, request *protocol.VMessRequest, output chan<- []byte, finish *sync.Mutex) {
|
||||
defer finish.Unlock()
|
||||
defer close(output)
|
||||
responseKey := md5.Sum(request.RequestKey[:])
|
||||
responseIV := md5.Sum(request.RequestIV[:])
|
||||
@@ -142,9 +168,9 @@ func handleResponse(conn *net.TCPConn, request *protocol.VMessRequest, output ch
|
||||
}
|
||||
|
||||
response := protocol.VMessResponse{}
|
||||
nBytes, err := decryptResponseReader.Read(response[:])
|
||||
_, err = decryptResponseReader.Read(response[:])
|
||||
if err != nil {
|
||||
log.Error("VMessOut: Failed to read VMess response (%d bytes): %v", nBytes, err)
|
||||
//log.Error("VMessOut: Failed to read VMess response (%d bytes): %v", nBytes, err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(response[:], request.ResponseHeader[:]) {
|
||||
@@ -157,18 +183,25 @@ func handleResponse(conn *net.TCPConn, request *protocol.VMessRequest, output ch
|
||||
}
|
||||
|
||||
type VMessOutboundHandlerFactory struct {
|
||||
servers []VNextServer
|
||||
}
|
||||
|
||||
func (factory *VMessOutboundHandlerFactory) Create(vp *core.Point, rawConfig []byte, destination v2net.Destination) (core.OutboundConnectionHandler, error) {
|
||||
func (factory *VMessOutboundHandlerFactory) Initialize(rawConfig []byte) error {
|
||||
config, err := loadOutboundConfig(rawConfig)
|
||||
if err != nil {
|
||||
panic(log.Error("Failed to load VMess outbound config: %v", err))
|
||||
return err
|
||||
}
|
||||
servers := make([]VNextServer, 0, len(config.VNextList))
|
||||
for _, server := range config.VNextList {
|
||||
servers = append(servers, server.ToVNextServer())
|
||||
}
|
||||
return NewVMessOutboundHandler(vp, servers, destination), nil
|
||||
factory.servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func (factory *VMessOutboundHandlerFactory) Create(vp *core.Point, firstPacket v2net.Packet) (core.OutboundConnectionHandler, error) {
|
||||
return NewVMessOutboundHandler(vp, factory.servers, firstPacket), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
16
ray.go
16
ray.go
@@ -4,13 +4,14 @@ const (
|
||||
bufferSize = 16
|
||||
)
|
||||
|
||||
// Ray is an internal tranport channel bewteen inbound and outbound connection.
|
||||
type Ray struct {
|
||||
Input chan []byte
|
||||
Output chan []byte
|
||||
}
|
||||
|
||||
func NewRay() Ray {
|
||||
return Ray{
|
||||
func NewRay() *Ray {
|
||||
return &Ray{
|
||||
Input: make(chan []byte, bufferSize),
|
||||
Output: make(chan []byte, bufferSize),
|
||||
}
|
||||
@@ -26,18 +27,21 @@ type InboundRay interface {
|
||||
InboundOutput() <-chan []byte
|
||||
}
|
||||
|
||||
func (ray Ray) OutboundInput() <-chan []byte {
|
||||
func (ray *Ray) OutboundInput() <-chan []byte {
|
||||
return ray.Input
|
||||
}
|
||||
|
||||
func (ray Ray) OutboundOutput() chan<- []byte {
|
||||
func (ray *Ray) OutboundOutput() chan<- []byte {
|
||||
return ray.Output
|
||||
}
|
||||
|
||||
func (ray Ray) InboundInput() chan<- []byte {
|
||||
func (ray *Ray) InboundInput() chan<- []byte {
|
||||
return ray.Input
|
||||
}
|
||||
|
||||
func (ray Ray) InboundOutput() <-chan []byte {
|
||||
func (ray *Ray) InboundOutput() <-chan []byte {
|
||||
return ray.Output
|
||||
}
|
||||
|
||||
type UDPRay struct {
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"port": 27183,
|
||||
"users": [
|
||||
{"id": "ad937d9d-6e23-4a5a-ba23-bce5092a7c51"}
|
||||
]
|
||||
],
|
||||
"network": "tcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,34 +2,38 @@
|
||||
|
||||
VERSION=$(sed -n 's/.*Version.*=.*\"\([^"]*\)\".*/\1/p' $GOPATH/src/github.com/v2ray/v2ray-core/core.go)
|
||||
|
||||
REL_PATH=$GOPATH/bin/$VERSION
|
||||
if [ -d "$REL_PATH" ]; then
|
||||
rm -rf "$REL_PATH"
|
||||
fi
|
||||
|
||||
mkdir -p $REL_PATH
|
||||
mkdir -p $REL_PATH/config
|
||||
|
||||
cp -R $GOPATH/src/github.com/v2ray/v2ray-core/release/config/* $REL_PATH/config/
|
||||
BIN_PATH=$GOPATH/bin
|
||||
mkdir -p $BIN_PATH
|
||||
|
||||
function build {
|
||||
local GOOS=$1
|
||||
local GOARCH=$2
|
||||
local EXT=$3
|
||||
local SUFFIX=$3
|
||||
local EXT=$4
|
||||
|
||||
local REL_PATH=$BIN_PATH/v2ray_${VERSION}${SUFFIX}
|
||||
local TARGET=$REL_PATH/v2ray${EXT}
|
||||
GOOS=${GOOS} GOARCH=${GOARCH} go build -o ${TARGET} -compiler gc github.com/v2ray/v2ray-core/release/server
|
||||
if [ -d "$REL_PATH" ]; then
|
||||
rm -rf "$REL_PATH"
|
||||
fi
|
||||
mkdir -p $REL_PATH/config
|
||||
cp -R $GOPATH/src/github.com/v2ray/v2ray-core/release/config/* $REL_PATH/config/
|
||||
GOOS=${GOOS} GOARCH=${GOARCH} go build -o ${TARGET} -compiler gc -ldflags "-s" github.com/v2ray/v2ray-core/release/server
|
||||
|
||||
ZIP_FILE=$BIN_PATH/v2ray${SUFFIX}.zip
|
||||
if [ -f $ZIP_FILE ]; then
|
||||
rm -f $ZIP_FILE
|
||||
fi
|
||||
|
||||
pushd $BIN_PATH
|
||||
zip -r $ZIP_FILE ./v2ray_${VERSION}${SUFFIX}/*
|
||||
popd
|
||||
}
|
||||
|
||||
build "darwin" "amd64" "-macos"
|
||||
build "windows" "amd64" "-windows-64.exe"
|
||||
build "linux" "amd64" "-linux-64"
|
||||
build "linux" "386" "-linux-32"
|
||||
build "darwin" "amd64" "-macos" "-macos"
|
||||
build "windows" "amd64" "-windows-64" "-windows-64.exe"
|
||||
build "windows" "amd64" "-windows-32" "-windows-32.exe"
|
||||
build "linux" "amd64" "-linux-64" "-linux-64"
|
||||
build "linux" "386" "-linux-32" "-linux-32"
|
||||
build "linux" "arm" "-armv6" "-armv6"
|
||||
|
||||
ZIP_FILE=$GOPATH/bin/v2ray.zip
|
||||
if [ -f $ZIP_FILE ]; then
|
||||
rm -f $ZIP_FILE
|
||||
fi
|
||||
|
||||
pushd $GOPATH/bin/
|
||||
zip -r $ZIP_FILE ./$VERSION/*
|
||||
popd
|
||||
|
||||
@@ -8,39 +8,31 @@
|
||||
## 架构
|
||||
|
||||
### 术语
|
||||
* Point:一个 V2Ray 服务器称为 VPoint
|
||||
* Set:本机上的一组 VPoint
|
||||
* SuperSet:多机环境中的多个 VSet
|
||||
* Source:用户所使用的需要翻墙的软件,比如浏览器
|
||||
* End:用户需要访问的网站
|
||||
* User:一个受到 VPoint 认证的帐号
|
||||
* [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md):全局唯一的 ID,类似于 UUID
|
||||
|
||||
* Point:一个 V2Ray 服务器称为 Point Server
|
||||
* Set:一组 Point Server,包含多个 Point 进程,由一个 Master 进程统一管理。
|
||||
* SuperSet:多机环境中的多个 Set
|
||||
|
||||
### 工作流程
|
||||
VPoint 可提收来自 VSource 或其它 VPoint 的请求,并将请求转发至配置中的下一个 VPoint(或 VSet 或 VSuperSet) 或目标网站,然后将所得到的应答回复给请求来源。
|
||||
VPoint 采用白名单机制,只接受已认证帐号的请求。
|
||||
Point 可接收来自用户或其它 Point 的请求,并将请求转发至配置中的下一个 Point(或 Set 或 SuperSet) 或目标网站,然后将所得到的应答回复给请求来源。
|
||||
Point 采用白名单机制,只接受已认证帐号的请求。
|
||||
|
||||
### 通信协议
|
||||
* VPoint 之间默认使用自有 VMess 协议,或第三方自定义协议。
|
||||
* VPoint 和客户端之间可使用以下协议:
|
||||
* Point 之间默认使用自有 VMess 协议,或第三方自定义协议。
|
||||
* Point 和客户端之间可使用以下协议:
|
||||
* HTTP Proxy
|
||||
* SOCKS 5 Proxy
|
||||
* SOCKS Proxy
|
||||
* PPTP / L2TP / SSTP 等 VPN 隧道
|
||||
* 其它自定义协议
|
||||
* VPoint 和目标网站之间使用以下协议:
|
||||
* Point 和目标网站之间使用以下协议:
|
||||
* HTTP / HTTPS
|
||||
* UDP (DNS)
|
||||
|
||||
#### VMess
|
||||
VMess 为 V2Ray 的原生协议,设计用于两个 VPoint 之间的通信。[详细设计](https://github.com/V2Ray/v2ray-core/blob/master/spec/vmess.md)
|
||||
|
||||
### User
|
||||
* 每个 User 有一个 ID
|
||||
VMess 为 V2Ray 的原生协议,设计用于两个 Point 之间的通信。[详细设计](https://github.com/V2Ray/v2ray-core/blob/master/spec/vmess.md)
|
||||
|
||||
### Point
|
||||
* 每个 Point 有一个 ID,运行时生成
|
||||
* 每个 Point 可使用独立的配置文件,或从 VSet 继承
|
||||
* 每个 Point 可使用独立的配置文件,或从 Set 继承
|
||||
* 一个 Point 监听主机上的一个特定端口(可配置),用于接收和发送数据
|
||||
* 一个 Point 运行于一个独立的进程,可设定其使用的系统帐户
|
||||
|
||||
@@ -53,36 +45,11 @@ TODO
|
||||
## Point 详细设计
|
||||
一个 Point 包含五个部分:
|
||||
* 配置文件处理:读取和解析配置文件
|
||||
* 输入:负责与客户端建立连接(如 TCP),接收客户端的消息
|
||||
* 控制中心:负责处理事件
|
||||
* 加密解密
|
||||
* Point 负载均衡
|
||||
* Point 进程间通信
|
||||
* 输出:负责向客户端发送消息
|
||||
* 输入(Inbound):负责与客户端建立连接(如 TCP),接收客户端的消息
|
||||
* 输出(Outbound):负责向客户端发送消息
|
||||
|
||||
### 配置文件
|
||||
配置文件使用 JSON / ProtoBuf 兼容格式,定义 TODO
|
||||
|
||||
### 加密
|
||||
TODO
|
||||
|
||||
### 任务处理
|
||||
TODO
|
||||
|
||||
### 控制中心
|
||||
控制中心响应以下事件:
|
||||
|
||||
**INIT**
|
||||
* 输入:用户 ID
|
||||
* 输出:如果用户 ID 有效:"OK",否则关闭连接
|
||||
|
||||
**MSG**
|
||||
* 输入:VMess 消息
|
||||
* 输出:对应的响应消息
|
||||
|
||||
**END**
|
||||
* 输入:用户 ID
|
||||
* 输出:如果用户 ID 有效:关闭连接
|
||||
配置文件使用 JSON / ProtoBuf 兼容格式
|
||||
|
||||
## 编程语言
|
||||
暂定为 golang。
|
||||
|
||||
37
spec/errors.md
Normal file
37
spec/errors.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 错误信息
|
||||
|
||||
## 简介
|
||||
在日志中可以看到 [Error XXXXXX] 的信息,其中 XXXXXX 表示错误代码,已知的错误代码和解释如下:
|
||||
|
||||
|
||||
## 0x0001 Authentication Error
|
||||
* 原因:未认证用户。
|
||||
* 解决:请检查客户端和服务器的用户数据。
|
||||
|
||||
## 0x0002 Protocol Version Error
|
||||
* 原因:客户端使用了不正确的协议
|
||||
* 解决:
|
||||
* 如果错误信息为 Invalid version 67 (或 71、80),则表示你的浏览器使用了 HTTP 代理,而 V2Ray 只接受 Socks 代理。
|
||||
* 请检查客户端配置。
|
||||
|
||||
## 0x0003 Corrupted Packet Error
|
||||
* 原因:网络数据损坏
|
||||
* 解决:极有可能你的网络连接被劫持,请更换网络线路或 IP。
|
||||
|
||||
|
||||
## 0x0004 IP Format Error
|
||||
* 原因:不正确的 IP 地址
|
||||
* 解决:请检查客户端软件,如浏览器的配置
|
||||
|
||||
## 0x0005 Configuration Error
|
||||
* 原因:配置文件不能正常读取
|
||||
* 解决:请检查配置文件是否存在,权限是否合适,内容是否正常
|
||||
|
||||
## 0x0006 Invalid Operation Error
|
||||
* 原因:不正确的操作
|
||||
|
||||
|
||||
## 0x03E8 Socks Version 4
|
||||
* 原因:客户端使用了 SOCKS 4 协议
|
||||
* 解决:升级客户端软件
|
||||
|
||||
@@ -92,7 +92,10 @@ Point Server B
|
||||
|
||||
./server --config="vpoint_vmess_freedom.json 的绝对路径"
|
||||
|
||||
## 测试服务器可用性:
|
||||
## 测试服务器可用性
|
||||
|
||||
curl -v --socks5-hostname 127.0.0.1:1080 https://www.google.com/
|
||||
|
||||
## 调试
|
||||
|
||||
使用过程中遇到任何问题,请参考[错误信息](https://github.com/V2Ray/v2ray-core/blob/master/spec/errors.md)。
|
||||
|
||||
@@ -12,10 +12,18 @@
|
||||
3. go get github.com/v2ray/v2ray-core
|
||||
4. go build github.com/v2ray/v2ray-core/release/server
|
||||
|
||||
### archlinux 编译源文件
|
||||
1. 安装 Git: sudo pacman -S git
|
||||
2. 安装 golang:sudo pacman -S go
|
||||
1. export GOPATH=$HOME/work
|
||||
3. go get github.com/v2ray/v2ray-core
|
||||
4. go build -o $GOPATH/bin/v2ray -compiler gc github.com/v2ray/v2ray-core/release/server
|
||||
|
||||
### Debian / Ubuntu
|
||||
sudo bash <(curl -s https://raw.githubusercontent.com/v2ray/v2ray-core/master/release/install.sh)
|
||||
|
||||
此脚本会自动安装 git 和 golan 1.5 (如果系统上没有的话),然后把 v2ray 编译到 $GOPATH/bin/v2ray,新装的 golang 会把 GOPATH 设定到 /v2ray。
|
||||
|
||||
|
||||
## 配置和运行
|
||||
[链接](https://github.com/V2Ray/v2ray-core/blob/master/spec/guide.md)
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
* 问题描述:任何的错误信息,不正常的行为等
|
||||
* 日志文件:如果有的话
|
||||
|
||||
如对软件使用有作何问题也请发到这个类别
|
||||
如对软件使用有任何问题也请发到这个类别。
|
||||
|
||||
请在一个 Issue 中只描述一个问题,如果你遇到多个问题,请分别创建不同的 Issue,以方便讨论和解决。如果合在一起发,将有很高的机率被标记为 Chat 而降低解决问题的优先级。
|
||||
|
||||
### Chat
|
||||
聊天或其它相关性不强的内容。标记为 Chat 的 Issue 将在最后回复 7 天后关闭。
|
||||
@@ -23,6 +25,9 @@
|
||||
有关新特性的建议,如果是针对现有代码的修改,请详细描述您的建议。
|
||||
|
||||
## 以下分类仅供管理员使用
|
||||
### Announcement
|
||||
新版本发布、最新动态等公告内容。
|
||||
|
||||
### Help Wanted
|
||||
标记为 Help Wanted 的 Issue 接受 Pull Request,一般为新特性的实现,如果您对其中某一个感兴趣,欢迎供献代码。
|
||||
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
|
||||
## 数据请求
|
||||
认证部分:
|
||||
* 16 字节:基于时间的 hash(用户 ID),见下文
|
||||
* 16 字节:基于时间的 hash(用户 [ID](https://github.com/V2Ray/v2ray-core/blob/master/spec/id.md)),见下文
|
||||
|
||||
指令部分:
|
||||
* 1 字节:随机填充长度 M (0 < M <= 32)
|
||||
* M 字节:随机填充内容
|
||||
* 1 字节:版本号,目前为 0x1
|
||||
* 16 字节:请求数据 IV
|
||||
* 16 字节:请求数据 Key
|
||||
@@ -26,8 +24,7 @@
|
||||
* 4 字节:IPv4
|
||||
* 1 字节长度 + 域名
|
||||
* 16 字节:IPv6
|
||||
* 1 字节:随机填充长度 M2 (0 < M2 <= 32)
|
||||
* M2 字节:随机填充内容
|
||||
* 4 字节:指令部分前面所有内容的 FNV1a hash
|
||||
|
||||
数据部分
|
||||
* N 字节:请求数据
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
FAIL=0
|
||||
|
||||
function join { local IFS="$1"; shift; echo "$*"; }
|
||||
|
||||
function test_package {
|
||||
DIR="github.com/v2ray/v2ray-core/$1"
|
||||
DEP=$2
|
||||
IFS=',' read -ra DEPS <<< "$DEP"
|
||||
DEPS=("${DEPS[@]/#/github.com/v2ray/v2ray-core/}")
|
||||
DEP=$(join , "${DEPS[@]}")
|
||||
DEP=$(go list -f '{{ join .Deps "\n" }}' $DIR | grep v2ray | tr '\n' ',')
|
||||
DEP=${DEP}$DIR
|
||||
go test -coverprofile=coversingle.out -coverpkg=$DEP $DIR || FAIL=1
|
||||
if [ -f coversingle.out ]; then
|
||||
cat coversingle.out | grep -v "mode: set" >> coverall.out
|
||||
@@ -19,13 +15,12 @@ function test_package {
|
||||
|
||||
touch coverall.out
|
||||
|
||||
test_package "common/net" "common/net"
|
||||
test_package "config/json" "config/json"
|
||||
test_package "proxy/socks" "proxy/socks,proxy/socks/protocol"
|
||||
test_package "proxy/socks/protocol" "proxy/socks/protocol"
|
||||
test_package "proxy/vmess" "common/io,common/net,proxy/vmess,proxy/vmess/protocol,proxy/vmess/protocol/user"
|
||||
test_package "proxy/vmess/protocol" "proxy/vmess/protocol,proxy/vmess/protocol/user"
|
||||
test_package "proxy/vmess/protocol/user" "proxy/vmess/protocol/user"
|
||||
for DIR in $(find * -type d -not -path "*.git*"); do
|
||||
TEST_FILES=($DIR/*_test.go)
|
||||
if [ -f ${TEST_FILES[0]} ]; then
|
||||
test_package $DIR
|
||||
fi
|
||||
done
|
||||
|
||||
cat coverall.out | sort -t: -k1 > coverallsorted.out
|
||||
echo "mode: set" | cat - coverallsorted.out > coverall.out
|
||||
|
||||
@@ -19,8 +19,8 @@ func (handler *InboundConnectionHandler) Listen(port uint16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *InboundConnectionHandler) Communicate(dest v2net.Destination) error {
|
||||
ray := handler.Server.NewInboundConnectionAccepted(dest)
|
||||
func (handler *InboundConnectionHandler) Communicate(packet v2net.Packet) error {
|
||||
ray := handler.Server.DispatchToOutbound(packet)
|
||||
|
||||
input := ray.InboundInput()
|
||||
output := ray.InboundOutput()
|
||||
|
||||
@@ -32,7 +32,11 @@ func (handler *OutboundConnectionHandler) Start(ray core.OutboundRay) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *OutboundConnectionHandler) Create(point *core.Point, config []byte, dest v2net.Destination) (core.OutboundConnectionHandler, error) {
|
||||
handler.Destination = dest
|
||||
func (handler *OutboundConnectionHandler) Initialize(config []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *OutboundConnectionHandler) Create(point *core.Point, packet v2net.Packet) (core.OutboundConnectionHandler, error) {
|
||||
handler.Destination = packet.Destination()
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/v2ray/v2ray-core/common/errors"
|
||||
)
|
||||
|
||||
type ErrorSubject struct {
|
||||
*Subject
|
||||
value error
|
||||
@@ -36,3 +42,9 @@ func (subject *ErrorSubject) IsNil() {
|
||||
subject.FailWithMessage("Not true that " + subject.DisplayString() + " is nil.")
|
||||
}
|
||||
}
|
||||
|
||||
func (subject *ErrorSubject) HasCode(code int) {
|
||||
if !errors.HasCode(subject.value, code) {
|
||||
subject.FailWithMessage(fmt.Sprintf("Not ture that %s has error code 0x%04X.", subject.DisplayString(), code))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Subject struct {
|
||||
assert *Assertion
|
||||
name string
|
||||
@@ -12,8 +19,43 @@ func NewSubject(assert *Assertion) *Subject {
|
||||
}
|
||||
}
|
||||
|
||||
// decorate prefixes the string with the file and line of the call site
|
||||
// and inserts the final newline if needed and indentation tabs for formatting.
|
||||
func decorate(s string) string {
|
||||
_, file, line, ok := runtime.Caller(4) // decorate + log + public function.
|
||||
if ok {
|
||||
// Truncate file name at last file name separator.
|
||||
if index := strings.LastIndex(file, "/"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
}
|
||||
} else {
|
||||
file = "???"
|
||||
line = 1
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
// Every line is indented at least one tab.
|
||||
buf.WriteString(" ")
|
||||
fmt.Fprintf(buf, "%s:%d: ", file, line)
|
||||
lines := strings.Split(s, "\n")
|
||||
if l := len(lines); l > 1 && lines[l-1] == "" {
|
||||
lines = lines[:l-1]
|
||||
}
|
||||
for i, line := range lines {
|
||||
if i > 0 {
|
||||
// Second and subsequent lines are indented an extra tab.
|
||||
buf.WriteString("\n\t\t")
|
||||
}
|
||||
buf.WriteString(line)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (subject *Subject) FailWithMessage(message string) {
|
||||
subject.assert.t.Error(message)
|
||||
fmt.Println(decorate(message))
|
||||
subject.assert.t.Fail()
|
||||
}
|
||||
|
||||
func (subject *Subject) Named(name string) {
|
||||
|
||||
Reference in New Issue
Block a user