修订代码, 默认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
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移除不Activesession, 并试图返回一个 最佳的可用于新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
}

View File

@@ -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
@@ -21,7 +22,7 @@ type sessionState struct {
type StreamConn struct {
quic.Stream
laddr, raddr net.Addr
relatedSessionState *sessionState
relatedConnState *connState
isclosed bool
}
@@ -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)

View File

@@ -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() {
@@ -41,7 +35,7 @@ const (
common_maxidletimeout = time.Second * 45
common_HandshakeIdleTimeout = time.Second * 8
common_ConnectionIDLength = 12
server_maxStreamCountInOneSession = 4 //一个session中 stream越多, 性能越低, 因此我们这里限制为4
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
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(&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) {
}
}
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,9 +140,22 @@ func mainFunc() (result int) {
netLayer.Prepare()
fmt.Printf("Log Level:%d\n", utils.LogLevel)
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()
var defaultInServer proxy.Server
@@ -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,6 +321,12 @@ func mainFunc() (result int) {
utils.Info("Program got close signal.")
cleanup()
}
return
}
func cleanup() {
//在程序ctrl+C关闭时, 会主动Close所有的监听端口. 主要是被报告windows有时退出程序之后, 端口还是处于占用状态.
// 用下面代码以试图解决端口占用问题.
@@ -306,7 +339,4 @@ func mainFunc() (result int) {
for _, tm := range TproxyList {
tm.Stop()
}
}
return
}

2
doc.go
View File

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

View File

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

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

View File

@@ -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,8 +878,10 @@ func dialClient(targetAddr netLayer.Addr,
// 而其它代理的话, realTargetAddr会被设成实际配置的代理的地址
realTargetAddr = targetAddr
/*
if ce := utils.CanLogDebug("request isn't the appointed domain"); ce != nil {
if uniqueTestDomain != "" && uniqueTestDomain != targetAddr.Name {
ce.Write(
@@ -891,6 +893,7 @@ func dialClient(targetAddr netLayer.Addr,
}
}
*/
if ce := utils.CanLogInfo("Request"); ce != nil {

View File

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

View File

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

View File

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

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
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
import (

View File

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

View File

@@ -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格式
// tomlhttps://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

View File

@@ -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的拨号是否使用了多信道方式

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()
}
if theLen := len(words); theLen == 0 {
getWordListFailed = true
result = GenerateRandomString()
} else {
result = words[rand.Int()%theLen]
}
}()
babbler := babble.NewBabbler()
babbler.Count = 1
result = babbler.Babble()
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 (
"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)