mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-10-05 08:46:59 +08:00
修订代码, 默认loglevel 改为 Log_info.
对一般用户而言,还是需要使用Info等级 来了解一下 一般的 日志情况,等到使用熟练之后,且确认运行没有错误后, 可以自行调为 warning 来提升性能 发现 bubble包 还自己引入了 命令行参数,这十分不可取,所以我们还是直接使用其代码。 将其它包中 的 命令行参数 统一 移动 到 cmd/verysimple 中;tls lazy 特性因为还在 调试阶段,所以 命令行参数 仍然放到 v2ray_simple 包中。
This commit is contained in:
@@ -23,8 +23,8 @@ type Client struct {
|
||||
useHysteria, hysteria_manual, early bool
|
||||
maxbyteCount int
|
||||
|
||||
clientconns map[[16]byte]*sessionState
|
||||
sessionMapMutex sync.RWMutex
|
||||
clientconns map[[16]byte]*connState
|
||||
connMapMutex sync.RWMutex
|
||||
}
|
||||
|
||||
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移除不Active的session, 并试图返回一个 最佳的可用于新stream的session
|
||||
func (c *Client) trimSessions(ss map[[16]byte]*sessionState) (s *sessionState) {
|
||||
//trimBadConns removes non-Active sessions, 并试图返回一个 最佳的可用于新stream的session
|
||||
func (c *Client) trimBadConns(ss map[[16]byte]*connState) (s *connState) {
|
||||
minSessionNum := 10000
|
||||
for id, thisState := range ss {
|
||||
if isActive(thisState) {
|
||||
@@ -84,24 +84,24 @@ func (c *Client) DialCommonConn(openBecausePreviousFull bool, previous any) any
|
||||
|
||||
if !openBecausePreviousFull {
|
||||
|
||||
c.sessionMapMutex.Lock()
|
||||
var theSession *sessionState
|
||||
c.connMapMutex.Lock()
|
||||
var theState *connState
|
||||
if len(c.clientconns) > 0 {
|
||||
theSession = c.trimSessions(c.clientconns)
|
||||
theState = c.trimBadConns(c.clientconns)
|
||||
}
|
||||
if len(c.clientconns) > 0 {
|
||||
c.sessionMapMutex.Unlock()
|
||||
if theSession != nil {
|
||||
return theSession
|
||||
c.connMapMutex.Unlock()
|
||||
if theState != nil {
|
||||
return theState
|
||||
|
||||
}
|
||||
} else {
|
||||
c.clientconns = make(map[[16]byte]*sessionState)
|
||||
c.sessionMapMutex.Unlock()
|
||||
c.clientconns = make(map[[16]byte]*connState)
|
||||
c.connMapMutex.Unlock()
|
||||
}
|
||||
} else if previous != nil && c.knownServerMaxStreamCount == 0 {
|
||||
|
||||
ps, ok := previous.(*sessionState)
|
||||
ps, ok := previous.(*connState)
|
||||
if !ok {
|
||||
if ce := utils.CanLogDebug("QUIC: 'previous' parameter was given but with wrong type "); ce != nil {
|
||||
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()
|
||||
|
||||
var result = &sessionState{Connection: conn, id: id}
|
||||
c.sessionMapMutex.Lock()
|
||||
var result = &connState{Connection: conn, id: id}
|
||||
c.connMapMutex.Lock()
|
||||
c.clientconns[id] = result
|
||||
c.sessionMapMutex.Unlock()
|
||||
c.connMapMutex.Unlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Client) DialSubConn(thing any) (net.Conn, error) {
|
||||
theState, ok := thing.(*sessionState)
|
||||
theState, ok := thing.(*connState)
|
||||
if !ok {
|
||||
return nil, utils.ErrNilOrWrongParameter
|
||||
}
|
||||
@@ -176,5 +176,5 @@ func (c *Client) DialSubConn(thing any) (net.Conn, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
@@ -7,8 +7,9 @@ import (
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
//用于 跟踪 一个 session 中 所开启的 stream的数量
|
||||
type sessionState struct {
|
||||
// 对 quic.Connection 的一个包装。
|
||||
//用于 跟踪 一个 session 中 所开启的 stream的数量.
|
||||
type connState struct {
|
||||
quic.Connection
|
||||
id [16]byte
|
||||
|
||||
@@ -20,9 +21,9 @@ type sessionState struct {
|
||||
// 因为它是通过 StreamID 来识别连接. 不过session是有的。
|
||||
type StreamConn struct {
|
||||
quic.Stream
|
||||
laddr, raddr net.Addr
|
||||
relatedSessionState *sessionState
|
||||
isclosed bool
|
||||
laddr, raddr net.Addr
|
||||
relatedConnState *connState
|
||||
isclosed bool
|
||||
}
|
||||
|
||||
func (sc StreamConn) LocalAddr() net.Addr {
|
||||
@@ -42,7 +43,7 @@ func (sc StreamConn) Close() error {
|
||||
sc.isclosed = true
|
||||
sc.CancelRead(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)
|
||||
|
||||
|
@@ -1,22 +1,16 @@
|
||||
//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 文件夹.
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/e1732a364fed/v2ray_simple/advLayer"
|
||||
"github.com/e1732a364fed/v2ray_simple/utils"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,10 +32,10 @@ const (
|
||||
//100mbps
|
||||
Default_hysteriaMaxByteCount = 1024 * 1024 / 8 * 100
|
||||
|
||||
common_maxidletimeout = time.Second * 45
|
||||
common_HandshakeIdleTimeout = time.Second * 8
|
||||
common_ConnectionIDLength = 12
|
||||
server_maxStreamCountInOneSession = 4 //一个session中 stream越多, 性能越低, 因此我们这里限制为4
|
||||
common_maxidletimeout = time.Second * 45
|
||||
common_HandshakeIdleTimeout = time.Second * 8
|
||||
common_ConnectionIDLength = 12
|
||||
server_maxStreamCountInOneConn = 4 //一个 Connection 中 stream越多, 性能越低, 因此我们这里限制为4
|
||||
)
|
||||
|
||||
func isActive(s quic.Connection) bool {
|
||||
@@ -69,7 +63,7 @@ var (
|
||||
ConnectionIDLength: common_ConnectionIDLength,
|
||||
HandshakeIdleTimeout: common_HandshakeIdleTimeout,
|
||||
MaxIdleTimeout: common_maxidletimeout,
|
||||
MaxIncomingStreams: server_maxStreamCountInOneSession,
|
||||
MaxIncomingStreams: server_maxStreamCountInOneConn,
|
||||
MaxIncomingUniStreams: -1,
|
||||
KeepAlive: true,
|
||||
}
|
||||
@@ -81,126 +75,3 @@ var (
|
||||
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
132
advLayer/quic/server.go
Normal 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}
|
||||
}
|
||||
}
|
@@ -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(&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() {
|
||||
@@ -69,6 +79,8 @@ func mainFunc() (result int) {
|
||||
}
|
||||
|
||||
result = -3
|
||||
|
||||
cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -91,7 +103,9 @@ func mainFunc() (result int) {
|
||||
}
|
||||
}
|
||||
|
||||
utils.ShouldLogToFile = true
|
||||
if utils.LogOutFileName != "" {
|
||||
utils.ShouldLogToFile = true
|
||||
}
|
||||
|
||||
utils.InitLog()
|
||||
|
||||
@@ -103,7 +117,7 @@ func mainFunc() (result int) {
|
||||
|
||||
}
|
||||
if startMProf {
|
||||
//若不使用 NoShutdownHook, 我们ctrl+c退出时不会产生 pprof文件
|
||||
//若不使用 NoShutdownHook, 则 我们ctrl+c退出时不会产生 pprof文件
|
||||
p := profile.Start(profile.MemProfile, profile.MemProfileRate(1), profile.NoShutdownHook)
|
||||
|
||||
defer p.Stop()
|
||||
@@ -126,8 +140,21 @@ func mainFunc() (result int) {
|
||||
netLayer.Prepare()
|
||||
|
||||
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()
|
||||
|
||||
@@ -138,7 +165,7 @@ func mainFunc() (result int) {
|
||||
RoutingEnv.MainFallback = mainFallback
|
||||
}
|
||||
|
||||
//load inServers and vs.RoutePolicy
|
||||
//load inServers and RoutingEnv
|
||||
switch mode {
|
||||
case proxy.SimpleMode:
|
||||
var hase bool
|
||||
@@ -294,19 +321,22 @@ func mainFunc() (result int) {
|
||||
|
||||
utils.Info("Program got close signal.")
|
||||
|
||||
//在程序ctrl+C关闭时, 会主动Close所有的监听端口. 主要是被报告windows有时退出程序之后, 端口还是处于占用状态.
|
||||
// 用下面代码以试图解决端口占用问题.
|
||||
|
||||
for _, listener := range ListenerArray {
|
||||
if listener != nil {
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
for _, tm := range TproxyList {
|
||||
tm.Stop()
|
||||
}
|
||||
|
||||
cleanup()
|
||||
}
|
||||
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
2
doc.go
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
Package v2ray_simple provides a way to set up a proxy.
|
||||
|
||||
|
||||
Structure 本项目结构
|
||||
|
||||
utils -> netLayer-> tlsLayer -> httpLayer -> advLayer -> proxy -> v2ray_simple -> cmd/verysimple
|
||||
|
||||
根项目 v2ray_simple 仅研究实际转发过程.
|
||||
|
||||
Chain
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
[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或者更大值的话性能是最好的.
|
||||
#loglevel = 1
|
||||
|
||||
|
8
go.mod
8
go.mod
@@ -5,10 +5,12 @@ go 1.18
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.0.0
|
||||
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/lucas-clemente/quic-go v0.0.0-00010101000000-000000000000
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
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/pkg/profile v1.6.0
|
||||
github.com/refraction-networking/utls v1.0.0
|
||||
@@ -16,13 +18,13 @@ require (
|
||||
github.com/yl2chen/cidranger v1.0.2
|
||||
go.uber.org/zap v1.21.0
|
||||
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
|
||||
google.golang.org/grpc v1.45.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/biter777/countries v1.3.4 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // 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-17 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/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/multierr v1.6.0 // 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/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/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // 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
|
||||
)
|
||||
|
||||
|
4
go.sum
4
go.sum
@@ -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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
27
main.go
27
main.go
@@ -48,7 +48,7 @@ var (
|
||||
Tls_lazy_secure bool
|
||||
|
||||
//有时需要测试到单一网站的流量,此时为了避免其它干扰,可声明 一下 该域名,然后程序里会进行过滤
|
||||
uniqueTestDomain string
|
||||
//uniqueTestDomain string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -56,12 +56,12 @@ func init() {
|
||||
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.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 函数 来手动开启新的转发流程。
|
||||
// 若 not_temporary 为 false, 则不会使用 RoutingEnv进行路由或回落
|
||||
// 若 env 为 nil, 则不会 进行路由或回落
|
||||
func ListenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client, env *proxy.RoutingEnv) (thisListener net.Listener) {
|
||||
|
||||
var err error
|
||||
@@ -878,19 +878,22 @@ func dialClient(targetAddr netLayer.Addr,
|
||||
// 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址
|
||||
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(
|
||||
zap.String("request", targetAddr.String()),
|
||||
zap.String("uniqueTestDomain", uniqueTestDomain),
|
||||
)
|
||||
result = -1
|
||||
return
|
||||
if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
|
||||
|
||||
ce.Write(
|
||||
zap.String("request", targetAddr.String()),
|
||||
zap.String("uniqueTestDomain", uniqueTestDomain),
|
||||
)
|
||||
result = -1
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if ce := utils.CanLogInfo("Request"); ce != nil {
|
||||
|
||||
|
@@ -2,7 +2,6 @@ package netLayer
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
@@ -16,14 +15,9 @@ var (
|
||||
the_geoipdb *maxminddb.Reader
|
||||
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 {
|
||||
return embedGeoip
|
||||
}
|
||||
@@ -42,7 +36,7 @@ func LoadMaxmindGeoipFile(fn string) {
|
||||
if fn == "" {
|
||||
fn = GeoipFileName
|
||||
}
|
||||
if fn == "" { //因为 GeoipFileName 是共有变量,所以可能会被设成"", 不排除脑残
|
||||
if fn == "" { //因为 GeoipFileName 是公有变量,所以可能会被设成""
|
||||
return
|
||||
}
|
||||
bs, e := os.ReadFile(fn)
|
||||
|
@@ -10,12 +10,15 @@ import (
|
||||
/* go test -bench "CheckMMDB_country" . -v
|
||||
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有多垃圾
|
||||
*/
|
||||
|
||||
func BenchmarkCheckMMDB_country(b *testing.B) {
|
||||
|
||||
GeoipFileName = "GeoLite2-Country.mmdb"
|
||||
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
LoadMaxmindGeoipFile(utils.GetFilePath("../" + GeoipFileName))
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package netLayer
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
@@ -29,7 +28,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&UseReadv, "readv", DefaultReadvOption, "toggle the use of 'readv' syscall")
|
||||
|
||||
readvPool = sync.Pool{
|
||||
New: newReadvMem,
|
||||
|
@@ -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
|
||||
|
||||
import (
|
||||
|
@@ -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
|
||||
|
||||
import (
|
||||
|
@@ -3,6 +3,8 @@ package proxy
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/e1732a364fed/v2ray_simple/httpLayer"
|
||||
@@ -51,3 +53,50 @@ func LoadSimpleConfigFromStr(str string) (config SimpleConf, hasE bool, E utils.
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -27,9 +26,9 @@ type AppConf struct {
|
||||
UDP_timeout *int `toml:"udp_timeout"`
|
||||
}
|
||||
|
||||
//标准配置。默认使用toml格式
|
||||
//标准配置,使用toml格式。
|
||||
// toml:https://toml.io/cn/
|
||||
// english: https://toml.io/en/
|
||||
// English: https://toml.io/en/
|
||||
type StandardConf struct {
|
||||
App *AppConf `toml:"app"`
|
||||
DnsConf *netLayer.DnsConf `toml:"dns"`
|
||||
@@ -43,7 +42,6 @@ type StandardConf struct {
|
||||
|
||||
func LoadTomlConfStr(str string) (c StandardConf, err error) {
|
||||
_, err = toml.Decode(str, &c)
|
||||
|
||||
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) {
|
||||
|
||||
fpath := utils.GetFilePath(configFileName)
|
||||
@@ -113,54 +112,6 @@ func LoadConfig(configFileName, listenURL, dialURL string) (standardConf Standar
|
||||
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 {
|
||||
RoutePolicy *netLayer.RoutePolicy //used in passToOutClient
|
||||
MainFallback *httpLayer.ClassicFallback //used in checkFallback in passToOutClient
|
||||
|
@@ -57,7 +57,7 @@ type Client interface {
|
||||
//规定, firstPayload 由 utils.GetMTU或者 GetPacket获取, 所以写入之后可以用 utils.PutBytes 放回
|
||||
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)
|
||||
|
||||
//udp的拨号是否使用了多信道方式
|
||||
|
14
tcp_test.go
14
tcp_test.go
@@ -14,19 +14,19 @@ import (
|
||||
)
|
||||
|
||||
func TestTCP_vless(t *testing.T) {
|
||||
testTCP("vless", 0, "tcp", false, t)
|
||||
testTCP(t, "vless", 0, "tcp", false)
|
||||
}
|
||||
|
||||
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) {
|
||||
testTCP("trojan", 0, "tcp", true, t)
|
||||
testTCP(t, "trojan", 0, "tcp", true)
|
||||
}
|
||||
|
||||
//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.InitLog()
|
||||
|
||||
@@ -133,14 +133,14 @@ protocol = "direct"
|
||||
},
|
||||
}
|
||||
|
||||
tryGetHttp(client, "http://captive.apple.com", t)
|
||||
tryGetHttp(client, "http://www.msftconnecttest.com/connecttest.txt", t)
|
||||
tryGetHttp(t, client, "http://captive.apple.com")
|
||||
tryGetHttp(t, client, "http://www.msftconnecttest.com/connecttest.txt")
|
||||
|
||||
//联通性测试 可参考 https://imldy.cn/posts/99d42f85/
|
||||
// 用这种 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)
|
||||
resp, err := client.Get(path)
|
||||
if err != nil {
|
||||
|
13
tls_lazy.go
13
tls_lazy.go
@@ -28,17 +28,18 @@ func canLazyEncryptClient(outClient proxy.Client) bool {
|
||||
return outClient.IsUseTLS() && canNetwork_tlsLazy(outClient.Network()) && outClient.AdvancedLayer() == ""
|
||||
}
|
||||
|
||||
func canNetwork_tlsLazy(nw string) bool {
|
||||
switch nw {
|
||||
func canNetwork_tlsLazy(n string) bool {
|
||||
switch n {
|
||||
case "", "tcp", "tcp4", "tcp6", "unix":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tryTlsLazyRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn,也就是裸奔转发
|
||||
// tryTlsLazyRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn,也就是裸奔转发.
|
||||
// 如果在linux上,则和 xtls的splice 含义相同. 在其他系统时,与xtls-direct含义相同。
|
||||
// 我们内部先 使用 DetectConn进行过滤分析,然后再判断进化为splice 或者退化为普通拷贝
|
||||
// 我们内部先 使用 DetectConn进行过滤分析,然后再判断进化为splice 或者退化为普通拷贝.
|
||||
//
|
||||
// 第一个参数仅用于 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) {
|
||||
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 总是由 客户端发起的
|
||||
log.Println("有问题, nextI > len(bs)", nextI, len(bs))
|
||||
//os.Exit(-1) //这里就暂时不要退出程序了,毕竟理论上有可能由一些黑客来触发这里。
|
||||
|
||||
//这里不应 用 os.Exit 退出程序,理论上有可能由一些黑客来触发这里, 直接退出转发过程即可。
|
||||
|
||||
localConn.Close()
|
||||
rawWRC.Close()
|
||||
return
|
||||
|
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/e1732a364fed/v2ray_simple/utils"
|
||||
)
|
||||
|
||||
|
||||
//监听透明代理, 返回一个 值 用于 关闭.
|
||||
func ListenTproxy(addr string) (tm *tproxy.Machine) {
|
||||
utils.Info("Start running Tproxy")
|
||||
|
||||
@@ -16,7 +18,7 @@ func ListenTproxy(addr string) (tm *tproxy.Machine) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//tproxy因为比较特殊, 不属于 proxy.Server, 需要独特的转发过程去处理.
|
||||
//因为 tproxy比较特殊, 不属于 proxy.Server, 所以 需要 独立的 转发过程去处理.
|
||||
lis, err := startLoopTCP(ad)
|
||||
if err != nil {
|
||||
if ce := utils.CanLogErr("TProxy startLoopTCP failed"); ce != nil {
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"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")
|
||||
return
|
||||
}
|
||||
|
20
udp_test.go
20
udp_test.go
@@ -13,45 +13,45 @@ import (
|
||||
)
|
||||
|
||||
func TestUDP_vless(t *testing.T) {
|
||||
testUDP("vless", 0, "tcp", false, false, false, t)
|
||||
testUDP(t, "vless", 0, "tcp", false, false, false)
|
||||
}
|
||||
|
||||
//v0 没有fullcone
|
||||
|
||||
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) {
|
||||
testUDP("vless", 1, "tcp", false, true, false, t)
|
||||
testUDP(t, "vless", 1, "tcp", false, true, false)
|
||||
}
|
||||
|
||||
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) {
|
||||
testUDP("vless", 1, "tcp", true, true, false, t)
|
||||
testUDP(t, "vless", 1, "tcp", true, true, false)
|
||||
}
|
||||
|
||||
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) {
|
||||
testUDP("trojan", 0, "tcp", false, false, true, t)
|
||||
testUDP(t, "trojan", 0, "tcp", false, false, true)
|
||||
}
|
||||
|
||||
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) {
|
||||
testUDP("trojan", 0, "udp", false, false, false, t)
|
||||
testUDP(t, "trojan", 0, "udp", false, false, false)
|
||||
}
|
||||
|
||||
//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.InitLog()
|
||||
|
||||
|
18
utils/log.go
18
utils/log.go
@@ -1,7 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -15,12 +14,12 @@ const (
|
||||
Log_info
|
||||
Log_warning
|
||||
Log_error //error一般用于输出一些 连接错误或者客户端协议错误之类的, 但不致命
|
||||
log_dpanic
|
||||
log_panic
|
||||
Log_dpanic
|
||||
Log_panic
|
||||
Log_fatal
|
||||
log_off //不支持不打印致命输出。既然致命我们一定要尸检然后查看病因啊
|
||||
log_off //不支持不打印致命输出。既然致命 那么我们一定要尸检然后查看病因
|
||||
|
||||
DefaultLL = Log_warning
|
||||
DefaultLL = Log_info
|
||||
)
|
||||
|
||||
// LogLevel 值越小越唠叨, 废话越多,值越大打印的越少,见log_开头的常量;
|
||||
@@ -37,13 +36,6 @@ var (
|
||||
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) {
|
||||
sl = make([]string, 0, log_off)
|
||||
for i := 0; i < log_off; i++ {
|
||||
@@ -107,7 +99,7 @@ func InitLog() {
|
||||
|
||||
if ShouldLogToFile && LogOutFileName != "" {
|
||||
jsonConf := zap.NewProductionEncoderConfig()
|
||||
jsonConf.EncodeTime = zapcore.TimeEncoderOfLayout("060102 150405.000") //用一种比较简短的方式输出时间,年月日 时分秒.毫秒。 年只需输出后两位数字即可, 不管Y2K问题, 80年后要是还没实现网络自由那这个世界完蛋了.
|
||||
jsonConf.EncodeTime = zapcore.TimeEncoderOfLayout("060102 150405.000") //用一种比较简短的方式输出时间,年月日 时分秒.毫秒。 年只需输出后两位数字即可, 不管Y2K问题
|
||||
jsonConf.LevelKey = "L"
|
||||
jsonConf.TimeKey = "T"
|
||||
jsonConf.MessageKey = "M"
|
||||
|
@@ -3,8 +3,15 @@ package utils
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//保证我们随机种子每次都不一样, 这样可以保证go test中使用随机端口时, 不同的test会使用不同的端口, 防止端口冲突
|
||||
// 因为我们所有的包应该都引用了 utils包, 所以可以保证这一点.
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
//6-11 字节的字符串
|
||||
func GenerateRandomString() string {
|
||||
|
@@ -4,25 +4,42 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/tjarratt/babble"
|
||||
"go.uber.org/zap"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var words []string
|
||||
var getWordListFailed bool
|
||||
|
||||
func GetRandomWord() (result string) {
|
||||
//babbler包 在 系统中 没有 /usr/share/dict/words 且不是windows 时,会panic
|
||||
defer func() {
|
||||
|
||||
if r := recover(); r != nil {
|
||||
if ce := CanLogErr("getRandomWord babble panic"); ce != nil {
|
||||
ce.Write(zap.Any("err:", r))
|
||||
}
|
||||
if len(words) == 0 && !getWordListFailed {
|
||||
words = readAvailableDictionary()
|
||||
}
|
||||
|
||||
result = GenerateRandomString()
|
||||
}
|
||||
}()
|
||||
babbler := babble.NewBabbler()
|
||||
babbler.Count = 1
|
||||
result = babbler.Babble()
|
||||
if theLen := len(words); theLen == 0 {
|
||||
getWordListFailed = true
|
||||
result = GenerateRandomString()
|
||||
} else {
|
||||
result = words[rand.Int()%theLen]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@@ -3,19 +3,11 @@ package utils
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//保证我们随机种子每次都不一样, 这样可以保证go test中使用随机端口时, 不同的test会使用不同的端口, 防止端口冲突
|
||||
// 因为我们所有的包应该都引用了 utils包, 所以可以保证这一点.
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
// bufio.Reader 和 bytes.Buffer 都实现了 ByteReader
|
||||
type ByteReader interface {
|
||||
ReadByte() (byte, error)
|
||||
|
Reference in New Issue
Block a user