Compare commits

...

62 Commits
v0.5 ... v0.6

Author SHA1 Message Date
V2 Ray
0ce02fa857 Merge pull request #27 from helinb/patch-1
增加archlinux安装方法
2015-09-27 18:42:40 +02:00
helinb
21e7e8ad2d Update install.md 2015-09-27 20:20:17 +08:00
helinb
73c3d9b9e1 增加archlinux安装方法 2015-09-27 20:02:28 +08:00
V2Ray
b33995399b Remove uniq -u 2015-09-26 23:38:57 +02:00
V2Ray
c598e229a9 fix for travis env 2015-09-26 23:36:25 +02:00
V2Ray
184560883f fully automated coverall script 2015-09-26 23:33:08 +02:00
V2Ray
9c45cb031a Remove magic number 2015-09-26 22:32:45 +02:00
V2Ray
13e595e4cb Move socks config into a sparate folder 2015-09-25 21:00:51 +02:00
V2Ray
3747e45978 test cases for socks4 2015-09-25 20:48:55 +02:00
V2Ray
d77ba76ccf Update error handling in socks proxy 2015-09-25 17:59:45 +02:00
V2Ray
8c682d32e6 coverall 2015-09-25 00:18:26 +02:00
V2Ray
08f85fc9b7 Unify error checking by introducing error codes 2015-09-25 00:17:44 +02:00
V2Ray
1995594b98 Error code check 2015-09-24 22:24:00 +02:00
V2Ray
e83eec9479 Print out the correct point of error 2015-09-24 21:46:25 +02:00
V2Ray
d77fdbd719 Explictly wait for server ready 2015-09-24 18:08:55 +02:00
V2Ray
9e4902d17a typo 2015-09-24 18:04:42 +02:00
V2Ray
685c0f3c05 Update coverall script 2015-09-24 18:02:10 +02:00
V2Ray
02c3f144e2 test case for freedom connection 2015-09-24 18:01:02 +02:00
V2Ray
fc14b9346c Remove keepalive settings 2015-09-24 15:10:02 +02:00
V2Ray
51c2a2b880 build break 2015-09-24 14:55:21 +02:00
V2Ray
a78dbe7133 Close connection more aggressively. 2015-09-24 14:51:19 +02:00
V2Ray
64103229d6 Update format 2015-09-24 13:04:38 +02:00
V2Ray
c138004bf9 First attempt to regulate errors. 2015-09-24 12:54:10 +02:00
V2Ray
6ecb18268e Use array instead of slice 2015-09-23 22:17:25 +02:00
V2Ray
ef790375ee Update test 2015-09-23 22:02:55 +02:00
V2Ray
5604ff175b Update mutex usage in timed_map 2015-09-23 21:31:06 +02:00
V2Ray
70ad3d0174 Fix time usage in timed_map_test 2015-09-23 20:24:30 +02:00
V2Ray
99f32f1dbf More coverage 2015-09-23 20:22:09 +02:00
V2Ray
33768175c1 Update travis 2015-09-23 20:18:23 +02:00
V2Ray
10d733263d Test case for TimedStringMap 2015-09-23 20:14:12 +02:00
V2Ray
8ce7ee1cda Initial version of TimedStringMap 2015-09-23 18:19:05 +02:00
V2Ray
bb442b4f83 Fix usage of Mutex 2015-09-23 17:13:50 +02:00
V2Ray
3fbae6795a Switch to Mutex for better readability 2015-09-23 14:14:53 +02:00
V2Ray
c59dcc309c Update VMess protocol to remove unnecessary data 2015-09-23 00:29:10 +02:00
V2Ray
019d8266bc Basic implementation of socks udp listener. 2015-09-22 23:50:05 +02:00
V2Ray
dc5fbf8e60 Upload all files for release 2015-09-22 23:45:56 +02:00
V2Ray
db23bdfc20 reduce binary size by stripping debug information 2015-09-22 23:43:39 +02:00
V2Ray
53eff7bb3e format code 2015-09-22 18:43:30 +02:00
V2Ray
3f0f8f005d still need this goroutine 2015-09-22 18:43:12 +02:00
V2Ray
f30841019d simplify code 2015-09-22 18:31:06 +02:00
V2Ray
ec83281d18 Parse config just once 2015-09-22 18:11:55 +02:00
V2Ray
338300248c Target oriented building and windows x86 and arm support. 2015-09-22 17:19:28 +02:00
V2Ray
0527d40f21 change address back to 127.0.0.1 2015-09-22 15:12:02 +02:00
V2Ray
787df1ab9b Reduce memory allocation in vmess. 2015-09-22 14:50:34 +02:00
V2Ray
4874cd54a4 Introduce Packet to unify TCP and UDP communication 2015-09-22 14:45:03 +02:00
V2Ray
4320c14efd In practice 32k buffer is never fully used. 2015-09-22 13:25:17 +02:00
V2Ray
dfdea480ff Update issue guide 2015-09-22 12:57:08 +02:00
V2Ray
bd35793cf8 Don't random pick if there is only one vnext or user 2015-09-22 00:28:52 +02:00
V2Ray
67f41cb3ba Update doc 2015-09-22 00:08:27 +02:00
V2Ray
e57089637a udp packet format in socks5 2015-09-21 20:56:07 +02:00
V2Ray
26b6c06c9e format code 2015-09-21 19:57:30 +02:00
V2Ray
0ce10e1f88 More docs 2015-09-21 19:56:58 +02:00
V2Ray
79ffd818b2 Add network field in config file. 2015-09-21 17:52:58 +02:00
V2Ray
e2bd6abb04 format code 2015-09-21 17:48:08 +02:00
V2Ray
3e2cd914b3 Prevent DoS attack 2015-09-21 17:37:06 +02:00
V2Ray
a51d64a102 More information when vmess auth fails. 2015-09-21 17:28:48 +02:00
V2 Ray
5aa448753d Update README.md 2015-09-21 15:07:48 +02:00
V2 Ray
186aadf1db Update README.md 2015-09-21 15:07:28 +02:00
V2 Ray
313aec4097 more docs 2015-09-21 10:15:25 +00:00
V2 Ray
e4b6d5e0f0 Doc for address.go 2015-09-21 11:51:18 +02:00
V2 Ray
402e7c09c3 Doc for encryption.go 2015-09-21 11:44:53 +02:00
V2Ray
145d78b271 refactor code 2015-09-20 21:21:55 +02:00
50 changed files with 1224 additions and 529 deletions

View File

@@ -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

View File

@@ -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
* Twitterhttps://twitter.com/projectv2ray
* 私下联系love@v2ray.com
## 捐赠
目前 V2Ray 还在早期开发阶段,暂时没什么可用性,也谈不上捐赠。如果你执意想捐赠,请发送 Amazon Gift Card 至 donate@v2ray.com

111
common/collect/timed_map.go Normal file
View 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()
}

View 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
View 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
}

View 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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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
View 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
View 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
}

View File

@@ -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)

View File

@@ -2,7 +2,7 @@
package core
const (
Version = "0.5"
Version = "0.6"
Codename = "Post Apocalypse"
Intro = "A stable and unbreakable connection for everyone."
)

View File

@@ -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
}

View File

@@ -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()
}

View 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))
}

View File

@@ -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() {

View File

@@ -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
}

View 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
}

View File

@@ -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
}

View 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
}

View 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})
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
View 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)
}
}

View File

@@ -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"`
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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]}
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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
View File

@@ -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 {
}

View File

@@ -5,7 +5,8 @@
"port": 27183,
"users": [
{"id": "ad937d9d-6e23-4a5a-ba23-bce5092a7c51"}
]
],
"network": "tcp"
}
]
}

View File

@@ -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

View File

@@ -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 的请求,并将请求转发至配置中的下一个 VPointVSet 或 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
View 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 协议
* 解决:升级客户端软件

View File

@@ -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)。

View File

@@ -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. 安装 golangsudo 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)

View File

@@ -14,7 +14,9 @@
* 问题描述:任何的错误信息,不正常的行为等
* 日志文件:如果有的话
如对软件使用有何问题也请发到这个类别
如对软件使用有何问题也请发到这个类别
请在一个 Issue 中只描述一个问题,如果你遇到多个问题,请分别创建不同的 Issue以方便讨论和解决。如果合在一起发将有很高的机率被标记为 Chat 而降低解决问题的优先级。
### Chat
聊天或其它相关性不强的内容。标记为 Chat 的 Issue 将在最后回复 7 天后关闭。
@@ -23,6 +25,9 @@
有关新特性的建议,如果是针对现有代码的修改,请详细描述您的建议。
## 以下分类仅供管理员使用
### Announcement
新版本发布、最新动态等公告内容。
### Help Wanted
标记为 Help Wanted 的 Issue 接受 Pull Request一般为新特性的实现如果您对其中某一个感兴趣欢迎供献代码。

View File

@@ -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 字节:请求数据

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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) {