修订代码, 默认loglevel 改为 Log_info.

对一般用户而言,还是需要使用Info等级 来了解一下 一般的 日志情况,等到使用熟练之后,且确认运行没有错误后, 可以自行调为 warning 来提升性能

发现 bubble包 还自己引入了 命令行参数,这十分不可取,所以我们还是直接使用其代码。

将其它包中 的 命令行参数 统一 移动 到 cmd/verysimple 中;tls lazy 特性因为还在 调试阶段,所以 命令行参数 仍然放到 v2ray_simple 包中。
This commit is contained in:
e1732a364fed
2022-04-26 13:22:18 +08:00
parent 9d5b553e01
commit f28f0d0bee
27 changed files with 460 additions and 415 deletions

View File

@@ -23,8 +23,8 @@ type Client struct {
useHysteria, hysteria_manual, early bool useHysteria, hysteria_manual, early bool
maxbyteCount int maxbyteCount int
clientconns map[[16]byte]*sessionState clientconns map[[16]byte]*connState
sessionMapMutex sync.RWMutex connMapMutex sync.RWMutex
} }
func NewClient(addr *netLayer.Addr, alpnList []string, host string, insecure bool, useHysteria bool, maxbyteCount int, hysteria_manual, early bool) *Client { func NewClient(addr *netLayer.Addr, alpnList []string, host string, insecure bool, useHysteria bool, maxbyteCount int, hysteria_manual, early bool) *Client {
@@ -42,8 +42,8 @@ func NewClient(addr *netLayer.Addr, alpnList []string, host string, insecure boo
} }
} }
//trimSessions移除不Activesession, 并试图返回一个 最佳的可用于新stream的session //trimBadConns removes non-Active sessions, 并试图返回一个 最佳的可用于新stream的session
func (c *Client) trimSessions(ss map[[16]byte]*sessionState) (s *sessionState) { func (c *Client) trimBadConns(ss map[[16]byte]*connState) (s *connState) {
minSessionNum := 10000 minSessionNum := 10000
for id, thisState := range ss { for id, thisState := range ss {
if isActive(thisState) { if isActive(thisState) {
@@ -84,24 +84,24 @@ func (c *Client) DialCommonConn(openBecausePreviousFull bool, previous any) any
if !openBecausePreviousFull { if !openBecausePreviousFull {
c.sessionMapMutex.Lock() c.connMapMutex.Lock()
var theSession *sessionState var theState *connState
if len(c.clientconns) > 0 { if len(c.clientconns) > 0 {
theSession = c.trimSessions(c.clientconns) theState = c.trimBadConns(c.clientconns)
} }
if len(c.clientconns) > 0 { if len(c.clientconns) > 0 {
c.sessionMapMutex.Unlock() c.connMapMutex.Unlock()
if theSession != nil { if theState != nil {
return theSession return theState
} }
} else { } else {
c.clientconns = make(map[[16]byte]*sessionState) c.clientconns = make(map[[16]byte]*connState)
c.sessionMapMutex.Unlock() c.connMapMutex.Unlock()
} }
} else if previous != nil && c.knownServerMaxStreamCount == 0 { } else if previous != nil && c.knownServerMaxStreamCount == 0 {
ps, ok := previous.(*sessionState) ps, ok := previous.(*connState)
if !ok { if !ok {
if ce := utils.CanLogDebug("QUIC: 'previous' parameter was given but with wrong type "); ce != nil { if ce := utils.CanLogDebug("QUIC: 'previous' parameter was given but with wrong type "); ce != nil {
ce.Write(zap.String("type", reflect.TypeOf(previous).String())) ce.Write(zap.String("type", reflect.TypeOf(previous).String()))
@@ -154,16 +154,16 @@ func (c *Client) DialCommonConn(openBecausePreviousFull bool, previous any) any
id := utils.GenerateUUID() id := utils.GenerateUUID()
var result = &sessionState{Connection: conn, id: id} var result = &connState{Connection: conn, id: id}
c.sessionMapMutex.Lock() c.connMapMutex.Lock()
c.clientconns[id] = result c.clientconns[id] = result
c.sessionMapMutex.Unlock() c.connMapMutex.Unlock()
return result return result
} }
func (c *Client) DialSubConn(thing any) (net.Conn, error) { func (c *Client) DialSubConn(thing any) (net.Conn, error) {
theState, ok := thing.(*sessionState) theState, ok := thing.(*connState)
if !ok { if !ok {
return nil, utils.ErrNilOrWrongParameter return nil, utils.ErrNilOrWrongParameter
} }
@@ -176,5 +176,5 @@ func (c *Client) DialSubConn(thing any) (net.Conn, error) {
atomic.AddInt32(&theState.openedStreamCount, 1) atomic.AddInt32(&theState.openedStreamCount, 1)
return StreamConn{Stream: stream, laddr: theState.LocalAddr(), raddr: theState.RemoteAddr(), relatedSessionState: theState}, nil return StreamConn{Stream: stream, laddr: theState.LocalAddr(), raddr: theState.RemoteAddr(), relatedConnState: theState}, nil
} }

View File

@@ -7,8 +7,9 @@ import (
"github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go"
) )
//用于 跟踪 一个 session 中 所开启的 stream的数量 // 对 quic.Connection 的一个包装。
type sessionState struct { //用于 跟踪 一个 session 中 所开启的 stream的数量.
type connState struct {
quic.Connection quic.Connection
id [16]byte id [16]byte
@@ -20,9 +21,9 @@ type sessionState struct {
// 因为它是通过 StreamID 来识别连接. 不过session是有的。 // 因为它是通过 StreamID 来识别连接. 不过session是有的。
type StreamConn struct { type StreamConn struct {
quic.Stream quic.Stream
laddr, raddr net.Addr laddr, raddr net.Addr
relatedSessionState *sessionState relatedConnState *connState
isclosed bool isclosed bool
} }
func (sc StreamConn) LocalAddr() net.Addr { func (sc StreamConn) LocalAddr() net.Addr {
@@ -42,7 +43,7 @@ func (sc StreamConn) Close() error {
sc.isclosed = true sc.isclosed = true
sc.CancelRead(quic.StreamErrorCode(quic.ConnectionRefused)) sc.CancelRead(quic.StreamErrorCode(quic.ConnectionRefused))
sc.CancelWrite(quic.StreamErrorCode(quic.ConnectionRefused)) sc.CancelWrite(quic.StreamErrorCode(quic.ConnectionRefused))
if rss := sc.relatedSessionState; rss != nil { if rss := sc.relatedConnState; rss != nil {
atomic.AddInt32(&rss.openedStreamCount, -1) atomic.AddInt32(&rss.openedStreamCount, -1)

View File

@@ -1,22 +1,16 @@
//Package quic defines functions to listen and dial quic, with some customizable congestion settings. //Package quic defines functions to listen and dial quic, with some customizable congestion settings.
// //
// 这里我们使用 hysteria的 brutal阻控. // 这里我们 还选择性 使用 hysteria的 brutal阻控.
// 见 https://github.com/tobyxdd/quic-go 中 toby的 *-mod 分支, 里面会多一个 congestion 文件夹. // 见 https://github.com/tobyxdd/quic-go 中 toby的 *-mod 分支, 里面会多一个 congestion 文件夹.
package quic package quic
import ( import (
"context"
"crypto/tls"
"log" "log"
"net"
"reflect" "reflect"
"time" "time"
"github.com/e1732a364fed/v2ray_simple/advLayer" "github.com/e1732a364fed/v2ray_simple/advLayer"
"github.com/e1732a364fed/v2ray_simple/utils"
"github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/congestion"
"go.uber.org/zap"
) )
func init() { func init() {
@@ -38,10 +32,10 @@ const (
//100mbps //100mbps
Default_hysteriaMaxByteCount = 1024 * 1024 / 8 * 100 Default_hysteriaMaxByteCount = 1024 * 1024 / 8 * 100
common_maxidletimeout = time.Second * 45 common_maxidletimeout = time.Second * 45
common_HandshakeIdleTimeout = time.Second * 8 common_HandshakeIdleTimeout = time.Second * 8
common_ConnectionIDLength = 12 common_ConnectionIDLength = 12
server_maxStreamCountInOneSession = 4 //一个session中 stream越多, 性能越低, 因此我们这里限制为4 server_maxStreamCountInOneConn = 4 //一个 Connection 中 stream越多, 性能越低, 因此我们这里限制为4
) )
func isActive(s quic.Connection) bool { func isActive(s quic.Connection) bool {
@@ -69,7 +63,7 @@ var (
ConnectionIDLength: common_ConnectionIDLength, ConnectionIDLength: common_ConnectionIDLength,
HandshakeIdleTimeout: common_HandshakeIdleTimeout, HandshakeIdleTimeout: common_HandshakeIdleTimeout,
MaxIdleTimeout: common_maxidletimeout, MaxIdleTimeout: common_maxidletimeout,
MaxIncomingStreams: server_maxStreamCountInOneSession, MaxIncomingStreams: server_maxStreamCountInOneConn,
MaxIncomingUniStreams: -1, MaxIncomingUniStreams: -1,
KeepAlive: true, KeepAlive: true,
} }
@@ -81,126 +75,3 @@ var (
KeepAlive: true, KeepAlive: true,
} }
) )
func ListenInitialLayers(addr string, tlsConf tls.Config, useHysteria bool, hysteriaMaxByteCount int, hysteria_manual, early bool, customMaxStreamCountInOneSession int64) (newConnChan chan net.Conn, baseConn any) {
thisConfig := common_ListenConfig
if customMaxStreamCountInOneSession > 0 {
thisConfig.MaxIncomingStreams = customMaxStreamCountInOneSession
}
var listener quic.Listener
var elistener quic.EarlyListener
var err error
if early {
utils.Info("quic Listen Early")
elistener, err = quic.ListenAddrEarly(addr, &tlsConf, &thisConfig)
} else {
listener, err = quic.ListenAddr(addr, &tlsConf, &thisConfig)
}
if err != nil {
if ce := utils.CanLogErr("quic listen"); ce != nil {
ce.Write(zap.Error(err))
}
return
}
if useHysteria {
if hysteriaMaxByteCount <= 0 {
hysteriaMaxByteCount = Default_hysteriaMaxByteCount
}
}
newConnChan = make(chan net.Conn, 10)
if early {
go loopAcceptEarly(elistener, newConnChan, useHysteria, hysteria_manual, hysteriaMaxByteCount)
} else {
go loopAccept(listener, newConnChan, useHysteria, hysteria_manual, hysteriaMaxByteCount)
}
return
}
//阻塞
func loopAccept(l quic.Listener, theChan chan net.Conn, useHysteria bool, hysteria_manual bool, hysteriaMaxByteCount int) {
for {
conn, err := l.Accept(context.Background())
if err != nil {
if ce := utils.CanLogErr("quic session accept"); ce != nil {
ce.Write(zap.Error(err))
}
//close(theChan) //不应关闭chan因为listen虽然不好使但是也许现存的stream还是好使的...
return
}
if useHysteria {
configHyForConn(conn, hysteria_manual, hysteriaMaxByteCount)
}
dealNewSession(conn, theChan)
}
}
//阻塞
func loopAcceptEarly(el quic.EarlyListener, theChan chan net.Conn, useHysteria bool, hysteria_manual bool, hysteriaMaxByteCount int) {
for {
conn, err := el.Accept(context.Background())
if err != nil {
if ce := utils.CanLogErr("quic session accept"); ce != nil {
ce.Write(zap.Error(err))
}
return
}
if useHysteria {
configHyForConn(conn, hysteria_manual, hysteriaMaxByteCount)
}
dealNewSession(conn, theChan)
}
}
func configHyForConn(conn quic.Connection, hysteria_manual bool, hysteriaMaxByteCount int) {
if hysteria_manual {
bs := NewBrutalSender_M(congestion.ByteCount(hysteriaMaxByteCount))
conn.SetCongestionControl(bs)
} else {
bs := NewBrutalSender(congestion.ByteCount(hysteriaMaxByteCount))
conn.SetCongestionControl(bs)
}
}
//非阻塞
func dealNewSession(session quic.Connection, theChan chan net.Conn) {
go func() {
for {
stream, err := session.AcceptStream(context.Background())
if err != nil {
if ce := utils.CanLogDebug("quic stream accept failed"); ce != nil {
//只要某个连接idle时间一长超过了idleTimeout服务端就会出现此错误:
// timeout: no recent network activity即 quic.IdleTimeoutError
//这不能说是错误, 而是quic的udp特性所致所以放到debug 输出中.
//这也同时说明, keep alive功能并不会更新 idle的最后期限.
//我们为了性能不必将该err转成 net.Error然后判断是否是timeout
//如果要排错那就开启debug日志即可.
ce.Write(zap.Error(err))
}
break
}
theChan <- StreamConn{stream, session.LocalAddr(), session.RemoteAddr(), nil, false}
}
}()
}

132
advLayer/quic/server.go Normal file
View File

@@ -0,0 +1,132 @@
package quic
import (
"context"
"crypto/tls"
"net"
"github.com/e1732a364fed/v2ray_simple/utils"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/congestion"
"go.uber.org/zap"
)
func ListenInitialLayers(addr string, tlsConf tls.Config, useHysteria bool, hysteriaMaxByteCount int, hysteria_manual, early bool, customMaxStreamCountInOneConn int64) (newConnChan chan net.Conn, baseConn any) {
thisConfig := common_ListenConfig
if customMaxStreamCountInOneConn > 0 {
thisConfig.MaxIncomingStreams = customMaxStreamCountInOneConn
}
var listener quic.Listener
var elistener quic.EarlyListener
var err error
if early {
utils.Info("quic Listen Early")
elistener, err = quic.ListenAddrEarly(addr, &tlsConf, &thisConfig)
} else {
listener, err = quic.ListenAddr(addr, &tlsConf, &thisConfig)
}
if err != nil {
if ce := utils.CanLogErr("quic listen"); ce != nil {
ce.Write(zap.Error(err))
}
return
}
if useHysteria {
if hysteriaMaxByteCount <= 0 {
hysteriaMaxByteCount = Default_hysteriaMaxByteCount
}
}
newConnChan = make(chan net.Conn, 10)
if early {
go loopAcceptEarly(elistener, newConnChan, useHysteria, hysteria_manual, hysteriaMaxByteCount)
} else {
go loopAccept(listener, newConnChan, useHysteria, hysteria_manual, hysteriaMaxByteCount)
}
return
}
//阻塞
func loopAccept(l quic.Listener, theChan chan net.Conn, useHysteria bool, hysteria_manual bool, hysteriaMaxByteCount int) {
for {
conn, err := l.Accept(context.Background())
if err != nil {
if ce := utils.CanLogErr("quic accept failed"); ce != nil {
ce.Write(zap.Error(err))
}
//close(theChan) //不应关闭chan因为listen虽然不好使但是也许现存的stream还是好使的...
return
}
if useHysteria {
configHyForConn(conn, hysteria_manual, hysteriaMaxByteCount)
}
go dealNewConn(conn, theChan)
}
}
//阻塞
func loopAcceptEarly(el quic.EarlyListener, theChan chan net.Conn, useHysteria bool, hysteria_manual bool, hysteriaMaxByteCount int) {
for {
conn, err := el.Accept(context.Background())
if err != nil {
if ce := utils.CanLogErr("quic early accept failed"); ce != nil {
ce.Write(zap.Error(err))
}
return
}
if useHysteria {
configHyForConn(conn, hysteria_manual, hysteriaMaxByteCount)
}
go dealNewConn(conn, theChan)
}
}
func configHyForConn(conn quic.Connection, hysteria_manual bool, hysteriaMaxByteCount int) {
if hysteria_manual {
bs := NewBrutalSender_M(congestion.ByteCount(hysteriaMaxByteCount))
conn.SetCongestionControl(bs)
} else {
bs := NewBrutalSender(congestion.ByteCount(hysteriaMaxByteCount))
conn.SetCongestionControl(bs)
}
}
//阻塞
func dealNewConn(conn quic.Connection, theChan chan net.Conn) {
for {
stream, err := conn.AcceptStream(context.Background())
if err != nil {
if ce := utils.CanLogDebug("quic stream accept failed"); ce != nil {
//只要某个连接idle时间一长超过了idleTimeout服务端就会出现此错误:
// timeout: no recent network activity即 quic.IdleTimeoutError
//这不能说是错误, 而是quic的udp特性所致所以放到debug 输出中.
//这也同时说明, keep alive功能并不会更新 idle的最后期限.
//我们为了性能不必将该err转成 net.Error然后判断是否是timeout
//如果要排错开启debug日志即可.
ce.Write(zap.Error(err))
}
break
}
theChan <- StreamConn{stream, conn.LocalAddr(), conn.RemoteAddr(), nil, false}
}
}

View File

@@ -53,6 +53,16 @@ func init() {
flag.StringVar(&listenURL, "L", "", "listen URL (i.e. the listen part in config file), only enbled when config file is not provided.") flag.StringVar(&listenURL, "L", "", "listen URL (i.e. the listen part in config file), only enbled when config file is not provided.")
flag.StringVar(&dialURL, "D", "", "dial URL (i.e. the dial part in config file), only enbled when config file is not provided.") flag.StringVar(&dialURL, "D", "", "dial URL (i.e. the dial part in config file), only enbled when config file is not provided.")
//other packages
flag.IntVar(&utils.LogLevel, "ll", utils.DefaultLL, "log level,0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal")
flag.StringVar(&utils.LogOutFileName, "lf", "vs_log", "output file for log; If empty, no log file will be used.")
flag.BoolVar(&netLayer.UseReadv, "readv", netLayer.DefaultReadvOption, "toggle the use of 'readv' syscall")
flag.StringVar(&netLayer.GeoipFileName, "geoip", "GeoLite2-Country.mmdb", "geoip maxmind file name")
} }
func main() { func main() {
@@ -69,6 +79,8 @@ func mainFunc() (result int) {
} }
result = -3 result = -3
cleanup()
} }
}() }()
@@ -91,7 +103,9 @@ func mainFunc() (result int) {
} }
} }
utils.ShouldLogToFile = true if utils.LogOutFileName != "" {
utils.ShouldLogToFile = true
}
utils.InitLog() utils.InitLog()
@@ -103,7 +117,7 @@ func mainFunc() (result int) {
} }
if startMProf { if startMProf {
//若不使用 NoShutdownHook, 我们ctrl+c退出时不会产生 pprof文件 //若不使用 NoShutdownHook, 我们ctrl+c退出时不会产生 pprof文件
p := profile.Start(profile.MemProfile, profile.MemProfileRate(1), profile.NoShutdownHook) p := profile.Start(profile.MemProfile, profile.MemProfileRate(1), profile.NoShutdownHook)
defer p.Stop() defer p.Stop()
@@ -126,8 +140,21 @@ func mainFunc() (result int) {
netLayer.Prepare() netLayer.Prepare()
fmt.Printf("Log Level:%d\n", utils.LogLevel) fmt.Printf("Log Level:%d\n", utils.LogLevel)
fmt.Printf("UseReadv:%t\n", netLayer.UseReadv)
fmt.Printf("tls_lazy_encrypt:%t\n", vs.Tls_lazy_encrypt) if ce := utils.CanLogInfo("Options"); ce != nil {
ce.Write(
zap.String("Log Level", utils.LogLevelStr(utils.LogLevel)),
zap.Bool("UseReadv", netLayer.UseReadv),
zap.Bool("tls_lazy_encrypt", vs.Tls_lazy_encrypt),
)
} else {
fmt.Printf("UseReadv:%t\n", netLayer.UseReadv)
fmt.Printf("tls_lazy_encrypt:%t\n", vs.Tls_lazy_encrypt)
}
runPreCommands() runPreCommands()
@@ -138,7 +165,7 @@ func mainFunc() (result int) {
RoutingEnv.MainFallback = mainFallback RoutingEnv.MainFallback = mainFallback
} }
//load inServers and vs.RoutePolicy //load inServers and RoutingEnv
switch mode { switch mode {
case proxy.SimpleMode: case proxy.SimpleMode:
var hase bool var hase bool
@@ -294,19 +321,22 @@ func mainFunc() (result int) {
utils.Info("Program got close signal.") utils.Info("Program got close signal.")
//在程序ctrl+C关闭时, 会主动Close所有的监听端口. 主要是被报告windows有时退出程序之后, 端口还是处于占用状态. cleanup()
// 用下面代码以试图解决端口占用问题.
for _, listener := range ListenerArray {
if listener != nil {
listener.Close()
}
}
for _, tm := range TproxyList {
tm.Stop()
}
} }
return return
} }
func cleanup() {
//在程序ctrl+C关闭时, 会主动Close所有的监听端口. 主要是被报告windows有时退出程序之后, 端口还是处于占用状态.
// 用下面代码以试图解决端口占用问题.
for _, listener := range ListenerArray {
if listener != nil {
listener.Close()
}
}
for _, tm := range TproxyList {
tm.Stop()
}
}

2
doc.go
View File

@@ -1,11 +1,11 @@
/* /*
Package v2ray_simple provides a way to set up a proxy. Package v2ray_simple provides a way to set up a proxy.
Structure 本项目结构 Structure 本项目结构
utils -> netLayer-> tlsLayer -> httpLayer -> advLayer -> proxy -> v2ray_simple -> cmd/verysimple utils -> netLayer-> tlsLayer -> httpLayer -> advLayer -> proxy -> v2ray_simple -> cmd/verysimple
根项目 v2ray_simple 仅研究实际转发过程.
Chain Chain

View File

@@ -3,7 +3,7 @@
[app] # app 项是可选的 [app] # app 项是可选的
# 日志等级,默认为2. 0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal, # 日志等级,默认为1. 0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal,
# 推荐开发时用0, 测试时使用1, 日常使用时 使用2或3; 显然日志越少越快, 设为6或者更大值的话性能是最好的. # 推荐开发时用0, 测试时使用1, 日常使用时 使用2或3; 显然日志越少越快, 设为6或者更大值的话性能是最好的.
#loglevel = 1 #loglevel = 1

8
go.mod
View File

@@ -5,10 +5,12 @@ go 1.18
require ( require (
github.com/BurntSushi/toml v1.0.0 github.com/BurntSushi/toml v1.0.0
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/biter777/countries v1.3.4
github.com/gobwas/ws v1.1.0 github.com/gobwas/ws v1.1.0
github.com/lucas-clemente/quic-go v0.0.0-00010101000000-000000000000 github.com/lucas-clemente/quic-go v0.0.0-00010101000000-000000000000
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/miekg/dns v1.1.47 github.com/miekg/dns v1.1.47
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/oschwald/maxminddb-golang v1.8.0 github.com/oschwald/maxminddb-golang v1.8.0
github.com/pkg/profile v1.6.0 github.com/pkg/profile v1.6.0
github.com/refraction-networking/utls v1.0.0 github.com/refraction-networking/utls v1.0.0
@@ -16,13 +18,13 @@ require (
github.com/yl2chen/cidranger v1.0.2 github.com/yl2chen/cidranger v1.0.2
go.uber.org/zap v1.21.0 go.uber.org/zap v1.21.0
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
gonum.org/v1/gonum v0.11.0 gonum.org/v1/gonum v0.11.0
google.golang.org/grpc v1.45.0 google.golang.org/grpc v1.45.0
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
) )
require ( require (
github.com/biter777/countries v1.3.4 // indirect
github.com/cheekybits/genny v1.0.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
@@ -33,20 +35,18 @@ require (
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
github.com/nxadm/tail v1.4.8 // indirect github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect github.com/onsi/ginkgo v1.16.4 // indirect
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.9 // indirect golang.org/x/tools v0.1.9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
) )

4
go.sum
View File

@@ -202,8 +202,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 h1:j8whCiEmvLCXI3scVn+YnklCU8mwJ9ZJ4/DGAKqQbRE=
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1/go.mod h1:O5hBrCGqzfb+8WyY8ico2AyQau7XQwAfEQeEQ5/5V9E=
github.com/tobyxdd/quic-go v0.27.1-0.20220414074155-271e5f3ac478 h1:/6nptMH0dGAsaE4/ai70AyOmhRlPBy+lAcvw3x8T0m4= github.com/tobyxdd/quic-go v0.27.1-0.20220414074155-271e5f3ac478 h1:/6nptMH0dGAsaE4/ai70AyOmhRlPBy+lAcvw3x8T0m4=
github.com/tobyxdd/quic-go v0.27.1-0.20220414074155-271e5f3ac478/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= github.com/tobyxdd/quic-go v0.27.1-0.20220414074155-271e5f3ac478/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
@@ -381,6 +379,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

27
main.go
View File

@@ -48,7 +48,7 @@ var (
Tls_lazy_secure bool Tls_lazy_secure bool
//有时需要测试到单一网站的流量,此时为了避免其它干扰,可声明 一下 该域名,然后程序里会进行过滤 //有时需要测试到单一网站的流量,此时为了避免其它干扰,可声明 一下 该域名,然后程序里会进行过滤
uniqueTestDomain string //uniqueTestDomain string
) )
func init() { func init() {
@@ -56,12 +56,12 @@ func init() {
flag.BoolVar(&Tls_lazy_encrypt, "lazy", false, "tls lazy encrypt (splice)") flag.BoolVar(&Tls_lazy_encrypt, "lazy", false, "tls lazy encrypt (splice)")
flag.BoolVar(&Tls_lazy_secure, "ls", false, "tls lazy secure, use special techs to ensure the tls lazy encrypt data can't be detected. Only valid at client end.") flag.BoolVar(&Tls_lazy_secure, "ls", false, "tls lazy secure, use special techs to ensure the tls lazy encrypt data can't be detected. Only valid at client end.")
flag.StringVar(&uniqueTestDomain, "td", "", "test a single domain, like www.domain.com. Only valid when loglevel=0") //flag.StringVar(&uniqueTestDomain, "td", "", "test a single domain, like www.domain.com. Only valid when loglevel=0")
} }
//非阻塞. 可以 直接使用 ListenSer 函数 来手动开启新的转发流程。 //非阻塞. 可以 直接使用 ListenSer 函数 来手动开启新的转发流程。
// 若 not_temporary 为 false, 则不会使用 RoutingEnv进行路由或回落 // 若 env 为 nil, 则不会 进行路由或回落
func ListenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client, env *proxy.RoutingEnv) (thisListener net.Listener) { func ListenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client, env *proxy.RoutingEnv) (thisListener net.Listener) {
var err error var err error
@@ -878,19 +878,22 @@ func dialClient(targetAddr netLayer.Addr,
// 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址 // 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址
realTargetAddr = targetAddr realTargetAddr = targetAddr
if ce := utils.CanLogDebug("request isn't the appointed domain"); ce != nil { /*
if ce := utils.CanLogDebug("request isn't the appointed domain"); ce != nil {
if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
ce.Write( if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
zap.String("request", targetAddr.String()),
zap.String("uniqueTestDomain", uniqueTestDomain),
)
result = -1
return
ce.Write(
zap.String("request", targetAddr.String()),
zap.String("uniqueTestDomain", uniqueTestDomain),
)
result = -1
return
}
} }
} */
if ce := utils.CanLogInfo("Request"); ce != nil { if ce := utils.CanLogInfo("Request"); ce != nil {

View File

@@ -2,7 +2,6 @@ package netLayer
import ( import (
_ "embed" _ "embed"
"flag"
"log" "log"
"net" "net"
"os" "os"
@@ -16,14 +15,9 @@ var (
the_geoipdb *maxminddb.Reader the_geoipdb *maxminddb.Reader
embedGeoip bool embedGeoip bool
GeoipFileName string //若运行程序指定了 geoip 参数则该值为给定值否则默认会被init为 GeoLite2-Country.mmdb GeoipFileName string
) )
func init() {
flag.StringVar(&GeoipFileName, "geoip", "GeoLite2-Country.mmdb", "geoip maxmind file name")
}
func HasEmbedGeoip() bool { func HasEmbedGeoip() bool {
return embedGeoip return embedGeoip
} }
@@ -42,7 +36,7 @@ func LoadMaxmindGeoipFile(fn string) {
if fn == "" { if fn == "" {
fn = GeoipFileName fn = GeoipFileName
} }
if fn == "" { //因为 GeoipFileName 是有变量,所以可能会被设成"", 不排除脑残 if fn == "" { //因为 GeoipFileName 是有变量,所以可能会被设成""
return return
} }
bs, e := os.ReadFile(fn) bs, e := os.ReadFile(fn)

View File

@@ -10,12 +10,15 @@ import (
/* go test -bench "CheckMMDB_country" . -v /* go test -bench "CheckMMDB_country" . -v
BenchmarkCheckMMDB_country-8 3631854 315.3 ns/op BenchmarkCheckMMDB_country-8 3631854 315.3 ns/op
总之一次mmdb查询比map查询慢了十倍多 (见 utils/container_test.go.bak) 总之一次mmdb查询比map查询慢了十倍多 (见 以前代码的 utils/container_test.go.bak, 新代码已经删掉了,可以找老 tag 找到。)
有必要设置一个 国别-ip 的map缓存; 不过这种纳秒级别的优化就无所谓了; 也不好说,谁知道客户端的cpu有多垃圾 有必要设置一个 国别-ip 的map缓存; 不过这种纳秒级别的优化就无所谓了; 也不好说,谁知道客户端的cpu有多垃圾
*/ */
func BenchmarkCheckMMDB_country(b *testing.B) { func BenchmarkCheckMMDB_country(b *testing.B) {
GeoipFileName = "GeoLite2-Country.mmdb"
b.StopTimer() b.StopTimer()
b.ResetTimer() b.ResetTimer()
LoadMaxmindGeoipFile(utils.GetFilePath("../" + GeoipFileName)) LoadMaxmindGeoipFile(utils.GetFilePath("../" + GeoipFileName))

View File

@@ -1,7 +1,6 @@
package netLayer package netLayer
import ( import (
"flag"
"io" "io"
"net" "net"
"sync" "sync"
@@ -29,7 +28,6 @@ var (
) )
func init() { func init() {
flag.BoolVar(&UseReadv, "readv", DefaultReadvOption, "toggle the use of 'readv' syscall")
readvPool = sync.Pool{ readvPool = sync.Pool{
New: newReadvMem, New: newReadvMem,

View File

@@ -1,3 +1,97 @@
/*
Package tproxy implements tproxy.
透明代理只能用于linux。
About TProxy 关于透明代理
透明代理原理
https://www.kernel.org/doc/html/latest/networking/tproxy.html
golang 示例
https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_tcp.go
c 语言 示例
https://github.com/FarFetchd/simple_tproxy_example/blob/master/tproxy_captive_portal.c
关键点在于
1. 要使用 syscall.IP_TRANSPARENT 监听
2. 监听到的 连接 的 localAddr实际上是 真实的目标地址, 而不是我们监听的地址;
我们在本包里要做的事情就是 模仿 上面的 golang示例,
但是上面的go示例有一个特点, 它是直接利用客户端自己的地址+reuse端口的方法去拨号实际地址的,而我们不需要那样做。
而且, udp 的过程更加特殊。
总之,这种情况完全不适配 proxy.Server 的接口, 应该单独拿出来, 属于网络层的特殊情况.
另外就是偶然发现trojan-go也是使用的 上面的示例的代码。
同时trojan-go还使用了
https://github.com/cybozu-go/transocks/blob/master/original_dst_linux.go
Iptables
iptables配置教程
https://toutyrater.github.io/app/tproxy.html
下面把该教程的重要部分搬过来。
ip rule add fwmark 1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
iptables -t mangle -N V2RAY
iptables -t mangle -A V2RAY -d 127.0.0.1/32 -j RETURN
iptables -t mangle -A V2RAY -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A V2RAY -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A V2RAY -d 192.168.0.0/16 -p tcp -j RETURN
iptables -t mangle -A V2RAY -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A V2RAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A V2RAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A PREROUTING -j V2RAY
iptables -t mangle -N V2RAY_MASK
iptables -t mangle -A V2RAY_MASK -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A V2RAY_MASK -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A V2RAY_MASK -d 192.168.0.0/16 -p tcp -j RETURN
iptables -t mangle -A V2RAY_MASK -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A V2RAY_MASK -j RETURN -m mark --mark 0xff
iptables -t mangle -A V2RAY_MASK -p udp -j MARK --set-mark 1
iptables -t mangle -A V2RAY_MASK -p tcp -j MARK --set-mark 1
iptables -t mangle -A OUTPUT -j V2RAY_MASK
Persistant iptables
单独设置iptables重启后会消失. 下面是持久化方法
mkdir -p /etc/iptables && iptables-save > /etc/iptables/rules.v4
vi /etc/systemd/system/tproxyrule.service
[Unit]
Description=Tproxy rule
After=network.target
Wants=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip rule add fwmark 1 table 100 ; /sbin/ip route add local 0.0.0.0/0 dev lo table 100 ; /sbin/iptables-restore /etc/iptables/rules.v4
[Install]
WantedBy=multi-user.target
systemctl enable tproxyrule
*/
package tproxy package tproxy
import ( import (

View File

@@ -1,97 +1,3 @@
/*
Package tproxy implements tproxy.
透明代理只能用于linux。
About TProxy 关于透明代理
透明代理原理
https://www.kernel.org/doc/html/latest/networking/tproxy.html
golang 示例
https://github.com/LiamHaworth/go-tproxy/blob/master/tproxy_tcp.go
c 语言 示例
https://github.com/FarFetchd/simple_tproxy_example/blob/master/tproxy_captive_portal.c
关键点在于
1. 要使用 syscall.IP_TRANSPARENT 监听
2. 监听到的 连接 的 localAddr实际上是 真实的目标地址, 而不是我们监听的地址;
我们在本包里要做的事情就是 模仿 上面的 golang示例,
但是上面的go示例有一个特点, 它是直接利用客户端自己的地址+reuse端口的方法去拨号实际地址的,而我们不需要那样做。
而且, udp 的过程更加特殊。
总之,这种情况完全不适配 proxy.Server 的接口, 应该单独拿出来, 属于网络层的特殊情况.
另外就是偶然发现trojan-go也是使用的 上面的示例的代码。
同时trojan-go还使用了
https://github.com/cybozu-go/transocks/blob/master/original_dst_linux.go
Iptables
iptables配置教程
https://toutyrater.github.io/app/tproxy.html
下面把该教程的重要部分搬过来。
ip rule add fwmark 1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
iptables -t mangle -N V2RAY
iptables -t mangle -A V2RAY -d 127.0.0.1/32 -j RETURN
iptables -t mangle -A V2RAY -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A V2RAY -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A V2RAY -d 192.168.0.0/16 -p tcp -j RETURN
iptables -t mangle -A V2RAY -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A V2RAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A V2RAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A PREROUTING -j V2RAY
iptables -t mangle -N V2RAY_MASK
iptables -t mangle -A V2RAY_MASK -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A V2RAY_MASK -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A V2RAY_MASK -d 192.168.0.0/16 -p tcp -j RETURN
iptables -t mangle -A V2RAY_MASK -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A V2RAY_MASK -j RETURN -m mark --mark 0xff
iptables -t mangle -A V2RAY_MASK -p udp -j MARK --set-mark 1
iptables -t mangle -A V2RAY_MASK -p tcp -j MARK --set-mark 1
iptables -t mangle -A OUTPUT -j V2RAY_MASK
Persistant iptables
单独设置iptables重启后会消失. 下面是持久化方法
mkdir -p /etc/iptables && iptables-save > /etc/iptables/rules.v4
vi /etc/systemd/system/tproxyrule.service
[Unit]
Description=Tproxy rule
After=network.target
Wants=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip rule add fwmark 1 table 100 ; /sbin/ip route add local 0.0.0.0/0 dev lo table 100 ; /sbin/iptables-restore /etc/iptables/rules.v4
[Install]
WantedBy=multi-user.target
systemctl enable tproxyrule
*/
package tproxy package tproxy
import ( import (

View File

@@ -3,6 +3,8 @@ package proxy
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"log"
"net/url"
"os" "os"
"github.com/e1732a364fed/v2ray_simple/httpLayer" "github.com/e1732a364fed/v2ray_simple/httpLayer"
@@ -51,3 +53,50 @@ func LoadSimpleConfigFromStr(str string) (config SimpleConf, hasE bool, E utils.
} }
return return
} }
func loadSimpleConf_byFile(fpath string) (simpleConf SimpleConf, mainFallback *httpLayer.ClassicFallback, err error) {
//默认认为所有其他后缀的都是json格式因为有时会用 server.json.vless 这种写法
// 默认所有json格式的文件都为 极简模式
var hasE bool
simpleConf, hasE, err = LoadSimpleConfigFile(fpath)
if hasE {
log.Printf("can not load simple config file: %s\n", err)
return
}
if simpleConf.Fallbacks != nil {
mainFallback = httpLayer.NewClassicFallbackFromConfList(simpleConf.Fallbacks)
}
if simpleConf.Client_ThatDialRemote_Url == "" {
simpleConf.Client_ThatDialRemote_Url = "direct://"
}
return
}
func loadSimpleConf_byUrl(listenURL, dialURL string) (simpleConf SimpleConf, err error) {
_, err = url.Parse(listenURL)
if err != nil {
log.Printf("listenURL given but invalid %s %s\n", listenURL, err)
return
}
simpleConf = SimpleConf{
Server_ThatListenPort_Url: listenURL,
}
if dialURL != "" {
_, err = url.Parse(dialURL)
if err != nil {
log.Printf("dialURL given but invalid %s %s\n", dialURL, err)
return
}
simpleConf.Client_ThatDialRemote_Url = dialURL
}
return
}

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"log" "log"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@@ -27,9 +26,9 @@ type AppConf struct {
UDP_timeout *int `toml:"udp_timeout"` UDP_timeout *int `toml:"udp_timeout"`
} }
//标准配置。默认使用toml格式 //标准配置使用toml格式
// tomlhttps://toml.io/cn/ // tomlhttps://toml.io/cn/
// english: https://toml.io/en/ // English: https://toml.io/en/
type StandardConf struct { type StandardConf struct {
App *AppConf `toml:"app"` App *AppConf `toml:"app"`
DnsConf *netLayer.DnsConf `toml:"dns"` DnsConf *netLayer.DnsConf `toml:"dns"`
@@ -43,7 +42,6 @@ type StandardConf struct {
func LoadTomlConfStr(str string) (c StandardConf, err error) { func LoadTomlConfStr(str string) (c StandardConf, err error) {
_, err = toml.Decode(str, &c) _, err = toml.Decode(str, &c)
return return
} }
@@ -59,7 +57,8 @@ func LoadTomlConfFile(fileNamePath string) (StandardConf, error) {
} }
// 先检查configFileName是否存在存在就尝试加载文件到 standardConf 或者 simpleConf否则尝试 -L参数 // 先检查configFileName是否存在存在就尝试加载文件到 standardConf 或者 simpleConf否则尝试 listenURL, dialURL 参数.
// 若 返回的是 simpleConf, 则还可能返回 mainFallback.
func LoadConfig(configFileName, listenURL, dialURL string) (standardConf StandardConf, simpleConf SimpleConf, confMode int, mainFallback *httpLayer.ClassicFallback, err error) { func LoadConfig(configFileName, listenURL, dialURL string) (standardConf StandardConf, simpleConf SimpleConf, confMode int, mainFallback *httpLayer.ClassicFallback, err error) {
fpath := utils.GetFilePath(configFileName) fpath := utils.GetFilePath(configFileName)
@@ -113,54 +112,6 @@ func LoadConfig(configFileName, listenURL, dialURL string) (standardConf Standar
return return
} }
func loadSimpleConf_byFile(fpath string) (simpleConf SimpleConf, mainFallback *httpLayer.ClassicFallback, err error) {
//默认认为所有其他后缀的都是json格式因为有时会用 server.json.vless 这种写法
// 默认所有json格式的文件都为 极简模式
var hasE bool
simpleConf, hasE, err = LoadSimpleConfigFile(fpath)
if hasE {
log.Printf("can not load simple config file: %s\n", err)
return
}
if simpleConf.Fallbacks != nil {
mainFallback = httpLayer.NewClassicFallbackFromConfList(simpleConf.Fallbacks)
}
//ConfMode = 0
if simpleConf.Client_ThatDialRemote_Url == "" {
simpleConf.Client_ThatDialRemote_Url = "direct://"
}
return
}
func loadSimpleConf_byUrl(listenURL, dialURL string) (simpleConf SimpleConf, err error) {
_, err = url.Parse(listenURL)
if err != nil {
log.Printf("listenURL given but invalid %s %s\n", listenURL, err)
return
}
simpleConf = SimpleConf{
Server_ThatListenPort_Url: listenURL,
}
if dialURL != "" {
_, err = url.Parse(dialURL)
if err != nil {
log.Printf("dialURL given but invalid %s %s\n", dialURL, err)
return
}
simpleConf.Client_ThatDialRemote_Url = dialURL
}
return
}
type RoutingEnv struct { type RoutingEnv struct {
RoutePolicy *netLayer.RoutePolicy //used in passToOutClient RoutePolicy *netLayer.RoutePolicy //used in passToOutClient
MainFallback *httpLayer.ClassicFallback //used in checkFallback in passToOutClient MainFallback *httpLayer.ClassicFallback //used in checkFallback in passToOutClient

View File

@@ -57,7 +57,7 @@ type Client interface {
//规定, firstPayload 由 utils.GetMTU或者 GetPacket获取, 所以写入之后可以用 utils.PutBytes 放回 //规定, firstPayload 由 utils.GetMTU或者 GetPacket获取, 所以写入之后可以用 utils.PutBytes 放回
Handshake(underlay net.Conn, firstPayload []byte, target netLayer.Addr) (wrappedConn io.ReadWriteCloser, err error) Handshake(underlay net.Conn, firstPayload []byte, target netLayer.Addr) (wrappedConn io.ReadWriteCloser, err error)
//建立一个通道, 然后在这个通道上 不断地申请发送到 各个远程udp地址 的连接。 //建立一个通道, 然后在这个通道上 不断地申请发送到 各个远程udp地址 的连接。理论上target可为空值。
EstablishUDPChannel(underlay net.Conn, target netLayer.Addr) (netLayer.MsgConn, error) EstablishUDPChannel(underlay net.Conn, target netLayer.Addr) (netLayer.MsgConn, error)
//udp的拨号是否使用了多信道方式 //udp的拨号是否使用了多信道方式

View File

@@ -14,19 +14,19 @@ import (
) )
func TestTCP_vless(t *testing.T) { func TestTCP_vless(t *testing.T) {
testTCP("vless", 0, "tcp", false, t) testTCP(t, "vless", 0, "tcp", false)
} }
func TestTCP_trojan(t *testing.T) { func TestTCP_trojan(t *testing.T) {
testTCP("trojan", 0, "tcp", false, t) testTCP(t, "trojan", 0, "tcp", false)
} }
func TestTCP_trojan_mux(t *testing.T) { func TestTCP_trojan_mux(t *testing.T) {
testTCP("trojan", 0, "tcp", true, t) testTCP(t, "trojan", 0, "tcp", true)
} }
//tcp测试我们直接使用http请求来测试 //tcp测试我们直接使用http请求来测试
func testTCP(protocol string, version int, network string, innermux bool, t *testing.T) { func testTCP(t *testing.T, protocol string, version int, network string, innermux bool) {
utils.LogLevel = utils.Log_debug utils.LogLevel = utils.Log_debug
utils.InitLog() utils.InitLog()
@@ -133,14 +133,14 @@ protocol = "direct"
}, },
} }
tryGetHttp(client, "http://captive.apple.com", t) tryGetHttp(t, client, "http://captive.apple.com")
tryGetHttp(client, "http://www.msftconnecttest.com/connecttest.txt", t) tryGetHttp(t, client, "http://www.msftconnecttest.com/connecttest.txt")
//联通性测试 可参考 https://imldy.cn/posts/99d42f85/ //联通性测试 可参考 https://imldy.cn/posts/99d42f85/
// 用这种 captive 测试 不容易遇到 网站无法在 某些地区 如 github action 所在的地区 访问 或者卡顿等情况. // 用这种 captive 测试 不容易遇到 网站无法在 某些地区 如 github action 所在的地区 访问 或者卡顿等情况.
} }
func tryGetHttp(client *http.Client, path string, t *testing.T) { func tryGetHttp(t *testing.T, client *http.Client, path string) {
t.Log("start dial", path) t.Log("start dial", path)
resp, err := client.Get(path) resp, err := client.Get(path)
if err != nil { if err != nil {

View File

@@ -28,17 +28,18 @@ func canLazyEncryptClient(outClient proxy.Client) bool {
return outClient.IsUseTLS() && canNetwork_tlsLazy(outClient.Network()) && outClient.AdvancedLayer() == "" return outClient.IsUseTLS() && canNetwork_tlsLazy(outClient.Network()) && outClient.AdvancedLayer() == ""
} }
func canNetwork_tlsLazy(nw string) bool { func canNetwork_tlsLazy(n string) bool {
switch nw { switch n {
case "", "tcp", "tcp4", "tcp6", "unix": case "", "tcp", "tcp4", "tcp6", "unix":
return true return true
} }
return false return false
} }
// tryTlsLazyRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn也就是裸奔转发 // tryTlsLazyRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn也就是裸奔转发.
// 如果在linux上则和 xtls的splice 含义相同. 在其他系统时与xtls-direct含义相同。 // 如果在linux上则和 xtls的splice 含义相同. 在其他系统时与xtls-direct含义相同。
// 我们内部先 使用 DetectConn进行过滤分析然后再判断进化为splice 或者退化为普通拷贝 // 我们内部先 使用 DetectConn进行过滤分析然后再判断进化为splice 或者退化为普通拷贝.
//
// 第一个参数仅用于 tls_lazy_secure // 第一个参数仅用于 tls_lazy_secure
func tryTlsLazyRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_server proxy.UserServer, targetAddr netLayer.Addr, wrc, wlc io.ReadWriteCloser, localConn net.Conn, isclient bool, theRecorder *tlsLayer.Recorder) { func tryTlsLazyRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_server proxy.UserServer, targetAddr netLayer.Addr, wrc, wlc io.ReadWriteCloser, localConn net.Conn, isclient bool, theRecorder *tlsLayer.Recorder) {
if ce := utils.CanLogDebug("trying tls lazy copy"); ce != nil { if ce := utils.CanLogDebug("trying tls lazy copy"); ce != nil {
@@ -362,7 +363,9 @@ func tryTlsLazyRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, prox
// //
//这是因为, 触发上一段类似代码的原因是tls 0-rtt而 tls 0-rtt 总是由 客户端发起的 //这是因为, 触发上一段类似代码的原因是tls 0-rtt而 tls 0-rtt 总是由 客户端发起的
log.Println("有问题, nextI > len(bs)", nextI, len(bs)) log.Println("有问题, nextI > len(bs)", nextI, len(bs))
//os.Exit(-1) //这里就暂时不要退出程序了,毕竟理论上有可能由一些黑客来触发这里。
//这里不应 用 os.Exit 退出程序,理论上有可能由一些黑客来触发这里, 直接退出转发过程即可。
localConn.Close() localConn.Close()
rawWRC.Close() rawWRC.Close()
return return

View File

@@ -9,6 +9,8 @@ import (
"github.com/e1732a364fed/v2ray_simple/utils" "github.com/e1732a364fed/v2ray_simple/utils"
) )
//监听透明代理, 返回一个 值 用于 关闭.
func ListenTproxy(addr string) (tm *tproxy.Machine) { func ListenTproxy(addr string) (tm *tproxy.Machine) {
utils.Info("Start running Tproxy") utils.Info("Start running Tproxy")
@@ -16,7 +18,7 @@ func ListenTproxy(addr string) (tm *tproxy.Machine) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
//tproxy因为比较特殊, 不属于 proxy.Server, 需要独特的转发过程去处理. //因为 tproxy比较特殊, 不属于 proxy.Server, 所以 需要 独立的 转发过程去处理.
lis, err := startLoopTCP(ad) lis, err := startLoopTCP(ad)
if err != nil { if err != nil {
if ce := utils.CanLogErr("TProxy startLoopTCP failed"); ce != nil { if ce := utils.CanLogErr("TProxy startLoopTCP failed"); ce != nil {

View File

@@ -8,7 +8,7 @@ import (
"github.com/e1732a364fed/v2ray_simple/utils" "github.com/e1732a364fed/v2ray_simple/utils"
) )
func ListenTproxy(addr string) (tm *tproxy.Machine) { func ListenTproxy(addr string) (_ *tproxy.Machine) {
utils.Warn("Tproxy not possible on non-linux device") utils.Warn("Tproxy not possible on non-linux device")
return return
} }

View File

@@ -13,45 +13,45 @@ import (
) )
func TestUDP_vless(t *testing.T) { func TestUDP_vless(t *testing.T) {
testUDP("vless", 0, "tcp", false, false, false, t) testUDP(t, "vless", 0, "tcp", false, false, false)
} }
//v0 没有fullcone //v0 没有fullcone
func TestUDP_vless_v1(t *testing.T) { func TestUDP_vless_v1(t *testing.T) {
testUDP("vless", 1, "tcp", false, false, false, t) testUDP(t, "vless", 1, "tcp", false, false, false)
} }
func TestUDP_vless_v1_fullcone(t *testing.T) { func TestUDP_vless_v1_fullcone(t *testing.T) {
testUDP("vless", 1, "tcp", false, true, false, t) testUDP(t, "vless", 1, "tcp", false, true, false)
} }
func TestUDP_vless_v1_udpMulti(t *testing.T) { func TestUDP_vless_v1_udpMulti(t *testing.T) {
testUDP("vless", 1, "tcp", true, false, false, t) testUDP(t, "vless", 1, "tcp", true, false, false)
} }
func TestUDP_vless_v1_udpMulti_fullcone(t *testing.T) { func TestUDP_vless_v1_udpMulti_fullcone(t *testing.T) {
testUDP("vless", 1, "tcp", true, true, false, t) testUDP(t, "vless", 1, "tcp", true, true, false)
} }
func TestUDP_trojan(t *testing.T) { func TestUDP_trojan(t *testing.T) {
testUDP("trojan", 0, "tcp", false, false, false, t) testUDP(t, "trojan", 0, "tcp", false, false, false)
} }
func TestUDP_trojan_mux(t *testing.T) { func TestUDP_trojan_mux(t *testing.T) {
testUDP("trojan", 0, "tcp", false, false, true, t) testUDP(t, "trojan", 0, "tcp", false, false, true)
} }
func TestUDP_trojan_fullcone(t *testing.T) { func TestUDP_trojan_fullcone(t *testing.T) {
testUDP("trojan", 0, "tcp", false, true, false, t) testUDP(t, "trojan", 0, "tcp", false, true, false)
} }
func TestUDP_trojan_through_udp(t *testing.T) { func TestUDP_trojan_through_udp(t *testing.T) {
testUDP("trojan", 0, "udp", false, false, false, t) testUDP(t, "trojan", 0, "udp", false, false, false)
} }
//udp测试我们直接使用dns请求来测试. //udp测试我们直接使用dns请求来测试.
func testUDP(protocol string, version int, network string, multi bool, fullcone bool, mux bool, t *testing.T) { func testUDP(t *testing.T, protocol string, version int, network string, multi bool, fullcone bool, mux bool) {
utils.LogLevel = utils.Log_debug utils.LogLevel = utils.Log_debug
utils.InitLog() utils.InitLog()

View File

@@ -1,7 +1,6 @@
package utils package utils
import ( import (
"flag"
"fmt" "fmt"
"os" "os"
@@ -15,12 +14,12 @@ const (
Log_info Log_info
Log_warning Log_warning
Log_error //error一般用于输出一些 连接错误或者客户端协议错误之类的, 但不致命 Log_error //error一般用于输出一些 连接错误或者客户端协议错误之类的, 但不致命
log_dpanic Log_dpanic
log_panic Log_panic
Log_fatal Log_fatal
log_off //不支持不打印致命输出。既然致命我们一定要尸检然后查看病因 log_off //不支持不打印致命输出。既然致命 那么我们一定要尸检然后查看病因
DefaultLL = Log_warning DefaultLL = Log_info
) )
// LogLevel 值越小越唠叨, 废话越多值越大打印的越少见log_开头的常量; // LogLevel 值越小越唠叨, 废话越多值越大打印的越少见log_开头的常量;
@@ -37,13 +36,6 @@ var (
ShouldLogToFile bool ShouldLogToFile bool
) )
func init() {
flag.IntVar(&LogLevel, "ll", DefaultLL, "log level,0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal")
flag.StringVar(&LogOutFileName, "lf", "vs_log", "output file for log; If empty, no log file will be used.")
}
func LogLevelStrList() (sl []string) { func LogLevelStrList() (sl []string) {
sl = make([]string, 0, log_off) sl = make([]string, 0, log_off)
for i := 0; i < log_off; i++ { for i := 0; i < log_off; i++ {
@@ -107,7 +99,7 @@ func InitLog() {
if ShouldLogToFile && LogOutFileName != "" { if ShouldLogToFile && LogOutFileName != "" {
jsonConf := zap.NewProductionEncoderConfig() jsonConf := zap.NewProductionEncoderConfig()
jsonConf.EncodeTime = zapcore.TimeEncoderOfLayout("060102 150405.000") //用一种比较简短的方式输出时间,年月日 时分秒.毫秒。 年只需输出后两位数字即可, 不管Y2K问题, 80年后要是还没实现网络自由那这个世界完蛋了. jsonConf.EncodeTime = zapcore.TimeEncoderOfLayout("060102 150405.000") //用一种比较简短的方式输出时间,年月日 时分秒.毫秒。 年只需输出后两位数字即可, 不管Y2K问题
jsonConf.LevelKey = "L" jsonConf.LevelKey = "L"
jsonConf.TimeKey = "T" jsonConf.TimeKey = "T"
jsonConf.MessageKey = "M" jsonConf.MessageKey = "M"

View File

@@ -3,8 +3,15 @@ package utils
import ( import (
"math/rand" "math/rand"
"strings" "strings"
"time"
) )
func init() {
//保证我们随机种子每次都不一样, 这样可以保证go test中使用随机端口时, 不同的test会使用不同的端口, 防止端口冲突
// 因为我们所有的包应该都引用了 utils包, 所以可以保证这一点.
rand.Seed(time.Now().Unix())
}
//6-11 字节的字符串 //6-11 字节的字符串
func GenerateRandomString() string { func GenerateRandomString() string {

View File

@@ -4,25 +4,42 @@
package utils package utils
import ( import (
"github.com/tjarratt/babble" "io/ioutil"
"go.uber.org/zap" "math/rand"
"os"
"strings"
) )
var words []string
var getWordListFailed bool
func GetRandomWord() (result string) { func GetRandomWord() (result string) {
//babbler包 在 系统中 没有 /usr/share/dict/words 且不是windows 时会panic
defer func() {
if r := recover(); r != nil { if len(words) == 0 && !getWordListFailed {
if ce := CanLogErr("getRandomWord babble panic"); ce != nil { words = readAvailableDictionary()
ce.Write(zap.Any("err:", r)) }
}
result = GenerateRandomString() if theLen := len(words); theLen == 0 {
} getWordListFailed = true
}() result = GenerateRandomString()
babbler := babble.NewBabbler() } else {
babbler.Count = 1 result = words[rand.Int()%theLen]
result = babbler.Babble() }
return return
} }
func readAvailableDictionary() (words []string) {
file, err := os.Open("/usr/share/dict/words")
if err != nil {
return
}
bytes, err := ioutil.ReadAll(file)
if err != nil {
return
}
words = strings.Split(string(bytes), "\n")
return
}

View File

@@ -3,19 +3,11 @@ package utils
import ( import (
"flag" "flag"
"math/rand"
"strings" "strings"
"time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
func init() {
//保证我们随机种子每次都不一样, 这样可以保证go test中使用随机端口时, 不同的test会使用不同的端口, 防止端口冲突
// 因为我们所有的包应该都引用了 utils包, 所以可以保证这一点.
rand.Seed(time.Now().Unix())
}
// bufio.Reader 和 bytes.Buffer 都实现了 ByteReader // bufio.Reader 和 bytes.Buffer 都实现了 ByteReader
type ByteReader interface { type ByteReader interface {
ReadByte() (byte, error) ReadByte() (byte, error)