diff --git a/README.md b/README.md index b1aebec..28c8624 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![GoVersion][10] [![GoDoc][1]][2] [![MIT licensed][3]][4] [![Go Report Card][5]][6] [![Downloads][7]][8] [![release][9]][8] -[1]: https://godoc.org/github.com/e1732a364fed/v2ray_simple?status.svg -[2]: https://godoc.org/github.com/e1732a364fed/v2ray_simple +[1]: https://pkg.go.dev/badge/github.com/e1732a364fed/v2ray_simple.svg +[2]: https://pkg.go.dev/github.com/e1732a364fed/v2ray_simple#section-readme [3]: https://img.shields.io/badge/license-MIT-blue.svg [4]: LICENSE [5]: https://goreportcard.com/badge/github.com/e1732a364fed/v2ray_simple diff --git a/advLayer/advLayer.go b/advLayer/advLayer.go index 8abca73..a7d7fd6 100644 --- a/advLayer/advLayer.go +++ b/advLayer/advLayer.go @@ -4,7 +4,6 @@ package advLayer import ( "bytes" "crypto/tls" - "errors" "fmt" "io" "net" @@ -13,10 +12,11 @@ import ( "github.com/e1732a364fed/v2ray_simple/utils" ) -var ErrPreviousFull = errors.New("previous conn full") +//var ErrPreviousFull = errors.New("previous conn full") var ProtocolsMap = make(map[string]Creator) +//为了避免黑客攻击,我们固定earlydata最大值为2048 var MaxEarlyDataLen = 2048 //for ws early data func PrintAllProtocolNames() { @@ -31,6 +31,9 @@ type Creator interface { //NewClientFromURL(url *url.URL) (Client, error) NewClientFromConf(conf *Conf) (Client, error) NewServerFromConf(conf *Conf) (Server, error) + + GetDefaultAlpn() (alpn string, mustUse bool) + PackageID() string } type Conf struct { @@ -45,12 +48,12 @@ type Conf struct { } type Client interface { - GetPath() string IsMux() bool //quic and grpc. if IsMux, then Client is a MuxClient, or it's a SingleClient - IsEarly() bool //is 0-rtt or not. - IsSuper() bool // quic handles transport layer dialing and tls layer handshake directly. + GetPath() string + IsEarly() bool //is 0-rtt or not. + } // ws (h1.1) @@ -68,6 +71,7 @@ type MuxClient interface { // If IsSuper, underlay should be nil; // // If not IsSuper and underlay == nil, it will return error if it can't find any extablished connection. + // Usually underlay is tls.Conn. GetCommonConn(underlay net.Conn) (conn any, err error) DialSubConn(underlay any) (net.Conn, error) @@ -76,11 +80,11 @@ type MuxClient interface { } type Server interface { + IsMux() bool //quic and grpc. if IsMux, then Server is a MuxServer, or it's a SingleServer + IsSuper() bool //quic + GetPath() string //for ws and grpc - IsMux() bool //quic and grpc. if IsMux, then Server is a MuxServer, or it's a SingleServer - - IsSuper() bool //quic } //ws diff --git a/advLayer/grpc/client.go b/advLayer/grpc/client.go index 7b053b8..25758ac 100644 --- a/advLayer/grpc/client.go +++ b/advLayer/grpc/client.go @@ -166,12 +166,14 @@ func (c *streamClient) multitun_withName(ctx context.Context, name string, opts type Client struct { ServerAddr netLayer.Addr Path string + ismulti bool } -func NewClient(addr netLayer.Addr, path string) (*Client, error) { +func NewClient(addr netLayer.Addr, path string, ismulti bool) (*Client, error) { return &Client{ ServerAddr: addr, Path: path, + ismulti: ismulti, }, nil } @@ -211,7 +213,7 @@ func (c *Client) DialSubConn(underlay any) (net.Conn, error) { return nil, utils.ErrNilParameter } if cc, ok := underlay.(ClientConn); ok { - return DialNewSubConn(c.Path, cc, &c.ServerAddr, false) + return DialNewSubConn(c.Path, cc, &c.ServerAddr, c.ismulti) } else { return nil, utils.ErrInvalidParameter } diff --git a/advLayer/grpc/grpc.go b/advLayer/grpc/grpc.go index fc1405d..507856b 100644 --- a/advLayer/grpc/grpc.go +++ b/advLayer/grpc/grpc.go @@ -24,7 +24,9 @@ https://github.com/v2fly/v2ray-core/pull/757 */ package grpc -import "github.com/e1732a364fed/v2ray_simple/advLayer" +import ( + "github.com/e1732a364fed/v2ray_simple/advLayer" +) func init() { advLayer.ProtocolsMap["grpc"] = Creator{} @@ -32,8 +34,27 @@ func init() { type Creator struct{} +func (Creator) PackageID() string { + return "grpc" +} + +func (Creator) GetDefaultAlpn() (alpn string, mustUse bool) { + // v2ray 和 xray 的grpc 因为没有自己处理tls,直接用grpc包处理的tls,而grpc包对alpn有严格要求, 要用h2. + return "h2", true +} + func (Creator) NewClientFromConf(conf *advLayer.Conf) (advLayer.Client, error) { - return NewClient(conf.Addr, conf.Path) + grpc_multi := false + if extra := conf.Extra; len(extra) > 0 { + if thing := extra["grpc_multi"]; thing != nil { + if use_multi, ok := thing.(bool); ok { + grpc_multi = use_multi + } + + } + } + + return NewClient(conf.Addr, conf.Path, grpc_multi) } func (Creator) NewServerFromConf(conf *advLayer.Conf) (advLayer.Server, error) { diff --git a/advLayer/grpcSimple/client.go b/advLayer/grpcSimple/client.go index ed1ed47..4e1870a 100644 --- a/advLayer/grpcSimple/client.go +++ b/advLayer/grpcSimple/client.go @@ -5,20 +5,19 @@ package grpcSimple import ( "bufio" - "context" "crypto/tls" "encoding/binary" "errors" - "fmt" "io" "net" "net/http" - "net/url" + "strings" "sync" "time" "github.com/e1732a364fed/v2ray_simple/utils" "go.uber.org/atomic" + "go.uber.org/zap" "golang.org/x/net/http2" ) @@ -26,9 +25,10 @@ var ( ErrInvalidLength = errors.New("invalid length") ) -var defaultHeader = http.Header{ +var defaultClientHeader = http.Header{ "content-type": []string{"application/grpc"}, - "user-agent": []string{"grpc-go/1.36.0"}, + "user-agent": []string{"grpc-go/1.41.0"}, + "Te": []string{"trailers"}, } //implements net.Conn @@ -44,7 +44,9 @@ type ClientConn struct { br *bufio.Reader // deadlines - deadline *time.Timer + timeouter + + client *Client } type Config struct { @@ -52,7 +54,7 @@ type Config struct { Host string } -func (g *ClientConn) initRequest() { +func (g *ClientConn) handshake() { response, err := g.transport.RoundTrip(g.request) if err != nil { g.err = err @@ -61,15 +63,33 @@ func (g *ClientConn) initRequest() { } if !g.close.Load() { + //log.Println("response headers", response.Header) + + if ct := response.Header.Get("Content-Type"); ct != "application/grpc" { + if ce := utils.CanLogWarn("GRPC Client got wrong Content-Type"); ce != nil { + ce.Write(zap.String("type", ct)) + } + + g.client.cachedTransport = nil + + response.Body.Close() + return + } + g.response = response g.br = bufio.NewReader(response.Body) } else { + + g.client.cachedTransport = nil + response.Body.Close() } } func (g *ClientConn) Read(b []byte) (n int, err error) { - g.once.Do(g.initRequest) + + g.once.Do(g.handshake) + if g.err != nil { return 0, g.err } @@ -133,6 +153,10 @@ func (g *ClientConn) Write(b []byte) (n int, err error) { if err == io.ErrClosedPipe && g.err != nil { err = g.err } + if err != nil { + g.client.dealErr(err) + + } return len(b), err } @@ -146,110 +170,101 @@ func (g *ClientConn) Close() error { return g.writer.Close() } -func (g *ClientConn) LocalAddr() net.Addr { return nil } -func (g *ClientConn) RemoteAddr() net.Addr { return nil } -func (g *ClientConn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) } -func (g *ClientConn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) } - -func (g *ClientConn) SetDeadline(t time.Time) error { - d := time.Until(t) - if g.deadline != nil { - g.deadline.Reset(d) - return nil - } - g.deadline = time.AfterFunc(d, func() { - g.Close() - }) - return nil -} - const tlsTimeout = time.Second * 5 -func NewHTTP2Client( - rawTCPConn net.Conn, - tlsConfig *tls.Config, -) *http2.Transport { +type Client struct { + Config - dialFunc := func(_, _ string, cfg *tls.Config) (net.Conn, error) { + curBaseConn net.Conn //一般为 tlsConn - cn := tls.Client(rawTCPConn, cfg) + theRequest http.Request - ctx, cancel := context.WithTimeout(context.Background(), tlsTimeout) - defer cancel() - if err := cn.HandshakeContext(ctx); err != nil { - rawTCPConn.Close() - return nil, err - } - state := cn.ConnectionState() - if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { - cn.Close() - return nil, utils.ErrInErr{ - ErrDesc: "grpcHardcore, http2: unexpected ALPN protocol", - ErrDetail: utils.ErrInvalidData, - Data: p, - } - } - return cn, nil - } + cachedTransport *http2.Transport - return &http2.Transport{ - DialTLS: dialFunc, - TLSClientConfig: tlsConfig, - AllowHTTP: false, - DisableCompression: true, - PingTimeout: 0, + path string +} + +func (g *Client) dealErr(err error) { + //use of closed connection + if strings.Contains(err.Error(), "use of closed") { + g.cachedTransport = nil } } -func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn, error) { - serviceName := "GunService" - if cfg.ServiceName != "" { - serviceName = cfg.ServiceName +func (c *Client) GetPath() string { + return c.ServiceName +} + +func (c *Client) IsSuper() bool { + return false +} + +func (c *Client) IsMux() bool { + return true +} + +func (c *Client) IsEarly() bool { + return false +} + +// 由于 本包应用了 http2包, 无法获取特定连接, 所以返回 underlay 本身 +func (c *Client) GetCommonConn(underlay net.Conn) (any, error) { + + if underlay == nil { + if c.cachedTransport != nil { + return c.cachedTransport, nil + } else { + return nil, nil + } + } else { + return underlay, nil + + } +} + +func (c *Client) ProcessWhenFull(underlay any) {} + +func (c *Client) DialSubConn(underlay any) (net.Conn, error) { + + if underlay == nil { + return nil, utils.ErrNilParameter + } + + var transport *http2.Transport + + if t, ok := underlay.(*http2.Transport); ok && t != nil { + transport = t + } else { + transport = &http2.Transport{ + DialTLS: func(_, _ string, cfg *tls.Config) (net.Conn, error) { + return underlay.(net.Conn), nil + }, + AllowHTTP: false, + DisableCompression: true, + PingTimeout: 0, + } + c.cachedTransport = transport } reader, writer := io.Pipe() - request := &http.Request{ - Method: http.MethodPost, - Body: reader, - URL: &url.URL{ - Scheme: "https", - Host: cfg.Host, - Path: fmt.Sprintf("/%s/Tun", serviceName), - // for unescape path - Opaque: fmt.Sprintf("//%s/%s/Tun", cfg.Host, serviceName), - }, - Proto: "HTTP/2", - ProtoMajor: 2, - ProtoMinor: 0, - Header: defaultHeader, - } + + request := c.theRequest + request.Body = reader conn := &ClientConn{ - request: request, + request: &request, transport: transport, writer: writer, close: atomic.NewBool(false), + client: c, + } + conn.timeouter = timeouter{ + closeFunc: func() { + conn.Close() + }, } - go conn.once.Do(conn.initRequest) + go conn.once.Do(conn.handshake) //necessary + return conn, nil } - -func GetNewClientStream(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) { - - transport := NewHTTP2Client(conn, tlsConfig) - return StreamGunWithTransport(transport, cfg) -} - -func GetNewClientStream_withTlsConn(conn net.Conn, cfg *Config) (net.Conn, error) { - - transport := &http2.Transport{ - DialTLS: func(_, _ string, cfg *tls.Config) (net.Conn, error) { - return conn, nil - }, - AllowHTTP: false, - DisableCompression: true, - PingTimeout: 0, - } - return StreamGunWithTransport(transport, cfg) -} diff --git a/advLayer/grpcSimple/grpc.go b/advLayer/grpcSimple/grpc.go deleted file mode 100644 index 90a42fb..0000000 --- a/advLayer/grpcSimple/grpc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Package grpcHardcore implements grpc tunnel without importing google.golang.org/grpc. -// -//Reference -// -// https://github.com/Dreamacro/clash/blob/master/transport/gun/gun.go, which is under MIT license -// -package grpcSimple diff --git a/advLayer/grpcSimple/grpcSimple.go b/advLayer/grpcSimple/grpcSimple.go new file mode 100644 index 0000000..c42ea79 --- /dev/null +++ b/advLayer/grpcSimple/grpcSimple.go @@ -0,0 +1,79 @@ +// Package grpcHardcore implements grpc tunnel without importing google.golang.org/grpc. +// +//Reference +// +// https://github.com/Dreamacro/clash/blob/master/transport/gun/gun.go, which is under MIT license +// +// 在 clash的客户端实现 的 基础上 继续用 golang的 http2包 实现了 grpc 的 基本服务端,并改进了 原代码。 +// +// grpcSimple包 比grpc包 小很多,替代grpc包的话,可以减小 4MB 左右的可执行文件大小。但是目前不支持 multiMode。 +// +// grpcSimple包 是很棒 很有用的 实现,未来可以添加 针对 grpc的ServiceName的回落。 +package grpcSimple + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/e1732a364fed/v2ray_simple/advLayer" +) + +func init() { + advLayer.ProtocolsMap["grpc"] = Creator{} +} + +type Creator struct{} + +func (Creator) PackageID() string { + return "grpcSimple" +} + +func (Creator) GetDefaultAlpn() (alpn string, mustUse bool) { + // v2ray 和 xray 的grpc 因为没有自己处理tls,直接用grpc包处理的tls,而grpc包对alpn有严格要求, 要用h2. + return "h2", true +} + +func (Creator) NewClientFromConf(conf *advLayer.Conf) (advLayer.Client, error) { + + serviceName := "GunService" + if conf.Path != "" { + serviceName = conf.Path + } + + c := &Client{ + Config: Config{ + ServiceName: serviceName, + Host: conf.Host, + }, + path: fmt.Sprintf("/%s/Tun", conf.Path), + } + + c.theRequest = http.Request{ + Method: http.MethodPost, + URL: &url.URL{ + Scheme: "https", + Host: c.Host, + Path: c.path, + // for unescape path + Opaque: fmt.Sprintf("//%s/%s/Tun", c.Host, c.ServiceName), + }, + Proto: "HTTP/2", + ProtoMajor: 2, + ProtoMinor: 0, + Header: defaultClientHeader, + } + return c, nil +} + +func (Creator) NewServerFromConf(conf *advLayer.Conf) (advLayer.Server, error) { + s := &Server{ + Config: Config{ + ServiceName: conf.Path, + Host: conf.Host, + }, + path: fmt.Sprintf("/%s/Tun", conf.Path), + } + + return s, nil +} diff --git a/advLayer/grpcSimple/server.go b/advLayer/grpcSimple/server.go index 697c643..d4cff99 100644 --- a/advLayer/grpcSimple/server.go +++ b/advLayer/grpcSimple/server.go @@ -1 +1,169 @@ package grpcSimple + +import ( + "bufio" + "encoding/binary" + "io" + "net" + "net/http" + "sync" + + "github.com/e1732a364fed/v2ray_simple/utils" + "go.uber.org/zap" + "golang.org/x/net/http2" +) + +type Server struct { + Config + + http2.Server + + path string +} + +func (s *Server) GetPath() string { + return s.ServiceName +} + +func (*Server) IsMux() bool { + return true +} + +func (*Server) IsSuper() bool { + return false +} +func (s *Server) StartHandle(underlay net.Conn, newSubConnChan chan net.Conn) { + go s.Server.ServeConn(underlay, &http2.ServeConnOpts{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, rq *http.Request) { + + //log.Println("request headers", rq.Header) + + //TODO: support fallback + + if rq.URL.Path != s.path { + if ce := utils.CanLogWarn("grpc Server got wrong path"); ce != nil { + ce.Write(zap.String("path", rq.URL.Path)) + } + + return + } + + if ct := rq.Header.Get("Content-Type"); ct != "application/grpc" { + if ce := utils.CanLogWarn("GRPC Server got wrong Content-Type"); ce != nil { + ce.Write(zap.String("type", ct)) + } + + return + } + + //https://dzone.com/articles/learning-about-the-headers-used-for-grpc-over-http + + headerMap := rw.Header() + headerMap.Add("Content-Type", "application/grpc") //necessary + rw.WriteHeader(http.StatusOK) + + cc := make(chan int) + sc := &ServerConn{ + br: bufio.NewReader(rq.Body), + Writer: rw, + Closer: rq.Body, + closeChan: cc, + } + + sc.timeouter = timeouter{ + closeFunc: func() { + sc.Close() + }, + } + newSubConnChan <- sc + <-cc //necessary + }), + }) +} + +type ServerConn struct { + io.Closer + io.Writer + + remain int + br *bufio.Reader + + once sync.Once + closeChan chan int + + timeouter +} + +func (g *ServerConn) Close() error { + g.once.Do(func() { + close(g.closeChan) + g.Closer.Close() + }) + return nil +} + +func (g *ServerConn) Read(b []byte) (n int, err error) { + + if g.remain > 0 { + + size := g.remain + if len(b) < size { + size = len(b) + } + + n, err = io.ReadFull(g.br, b[:size]) + g.remain -= n + return + } + + _, err = g.br.Discard(6) + if err != nil { + + return 0, err + } + + protobufPayloadLen, err := binary.ReadUvarint(g.br) + if err != nil { + return 0, ErrInvalidLength + } + + size := int(protobufPayloadLen) + if len(b) < size { + size = len(b) + } + + n, err = io.ReadFull(g.br, b[:size]) + if err != nil { + return + } + + remain := int(protobufPayloadLen) - n + if remain > 0 { + g.remain = remain + } + return n, nil +} + +func (g *ServerConn) Write(b []byte) (n int, err error) { + + protobufHeader := [binary.MaxVarintLen64 + 1]byte{0x0A} + varuintSize := binary.PutUvarint(protobufHeader[1:], uint64(len(b))) + grpcHeader := make([]byte, 5) + grpcPayloadLen := uint32(varuintSize + 1 + len(b)) + binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen) + + buf := utils.GetBuf() + defer utils.PutBuf(buf) + buf.Write(grpcHeader) + buf.Write(protobufHeader[:varuintSize+1]) + buf.Write(b) + + _, err = g.Writer.Write(buf.Bytes()) + + if err == nil { + g.Writer.(http.Flusher).Flush() //necessary + + } + + return len(b), err +} diff --git a/advLayer/grpcSimple/timeouter.go b/advLayer/grpcSimple/timeouter.go new file mode 100644 index 0000000..ff78640 --- /dev/null +++ b/advLayer/grpcSimple/timeouter.go @@ -0,0 +1,42 @@ +package grpcSimple + +import ( + "net" + "time" +) + +type timeouter struct { + deadline *time.Timer + + closeFunc func() +} + +func (g *timeouter) LocalAddr() net.Addr { return nil } +func (g *timeouter) RemoteAddr() net.Addr { return nil } +func (g *timeouter) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) } +func (g *timeouter) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) } + +func (g *timeouter) SetDeadline(t time.Time) error { + + var d time.Duration + + if g.deadline != nil { + + if t == (time.Time{}) { + g.deadline.Stop() + return nil + } + + g.deadline.Reset(d) + return nil + } else { + if t == (time.Time{}) { + return nil + } + d = time.Until(t) + + } + + g.deadline = time.AfterFunc(d, g.closeFunc) + return nil +} diff --git a/advLayer/quic/quic.go b/advLayer/quic/quic.go index 8dbb207..36f81bc 100644 --- a/advLayer/quic/quic.go +++ b/advLayer/quic/quic.go @@ -57,6 +57,7 @@ func CloseConn(baseC any) { } var ( + //h3 DefaultAlpnList = []string{"h3"} common_ListenConfig = quic.Config{ @@ -78,6 +79,14 @@ var ( type Creator struct{} +func (Creator) GetDefaultAlpn() (alpn string, mustUse bool) { + return "h3", false +} + +func (Creator) PackageID() string { + return "quic" +} + func (Creator) NewClientFromConf(conf *advLayer.Conf) (advLayer.Client, error) { var alpn []string if conf.TlsConf != nil { diff --git a/advLayer/ws/client.go b/advLayer/ws/client.go index 6236829..0ba1b77 100644 --- a/advLayer/ws/client.go +++ b/advLayer/ws/client.go @@ -15,9 +15,6 @@ import ( "github.com/gobwas/ws/wsutil" ) -//为了避免黑客攻击,我们固定earlydata最大值为2048 -const MaxEarlyDataLen = 2048 - //implements advLayer.Client type Client struct { requestURL *url.URL //因为调用gobwas/ws.Dialer.Upgrade 时要传入url,所以我们直接提供包装好的即可 diff --git a/advLayer/ws/server.go b/advLayer/ws/server.go index c829990..05a99c4 100644 --- a/advLayer/ws/server.go +++ b/advLayer/ws/server.go @@ -28,12 +28,13 @@ type Server struct { } // 这里默认: 传入的path必须 以 "/" 为前缀. 本函数 不对此进行任何检查. -func NewServer(path string, headers map[string][]string) *Server { +func NewServer(path string, headers map[string][]string, UseEarlyData bool) *Server { return &Server{ //upgrader: upgrader, - Thepath: path, - headers: headers, + Thepath: path, + headers: headers, + UseEarlyData: UseEarlyData, } } diff --git a/advLayer/ws/ws.go b/advLayer/ws/ws.go index 1b19418..d1c6214 100644 --- a/advLayer/ws/ws.go +++ b/advLayer/ws/ws.go @@ -48,5 +48,11 @@ func (Creator) NewClientFromConf(conf *advLayer.Conf) (advLayer.Client, error) { } func (Creator) NewServerFromConf(conf *advLayer.Conf) (advLayer.Server, error) { - return NewServer(conf.Path, conf.Headers), nil + return NewServer(conf.Path, conf.Headers, conf.IsEarly), nil +} +func (Creator) GetDefaultAlpn() (alpn string, mustUse bool) { + return +} +func (Creator) PackageID() string { + return "ws" } diff --git a/advLayer/ws/ws_test.go b/advLayer/ws/ws_test.go index 7ec9dd9..e1c882d 100644 --- a/advLayer/ws/ws_test.go +++ b/advLayer/ws/ws_test.go @@ -46,7 +46,7 @@ func TestWs(t *testing.T) { return } - s := ws.NewServer(wsPath, nil) + s := ws.NewServer(wsPath, nil, false) wsConn, err := s.Handshake(nil, conn) if err != nil { diff --git a/adv_grpc.go b/adv_grpc.go new file mode 100644 index 0000000..8a706b2 --- /dev/null +++ b/adv_grpc.go @@ -0,0 +1,8 @@ +//go:build grpc_full + +package v2ray_simple + +import _ "github.com/e1732a364fed/v2ray_simple/advLayer/grpc" + +//默认使用 grpcSimple,除非编译时 使用 grpc_full 这个 tag, 才会使用 grpc 包。 +// go build -tags=grpc_full diff --git a/adv_grpcSimple.go b/adv_grpcSimple.go new file mode 100644 index 0000000..0f94003 --- /dev/null +++ b/adv_grpcSimple.go @@ -0,0 +1,5 @@ +//go:build !grpc_full + +package v2ray_simple + +import _ "github.com/e1732a364fed/v2ray_simple/advLayer/grpcSimple" diff --git a/adv_quic.go b/adv_quic.go new file mode 100644 index 0000000..7c2f00e --- /dev/null +++ b/adv_quic.go @@ -0,0 +1,7 @@ +//go:build !noquic + +package v2ray_simple + +import _ "github.com/e1732a364fed/v2ray_simple/advLayer/quic" + +// 如果不引用 quic,go build 编译出的可执行文件 的大小 可以减小 2MB 。 diff --git a/cmd/verysimple/cli.go b/cmd/verysimple/cli.go index eb1d3cc..ca16461 100644 --- a/cmd/verysimple/cli.go +++ b/cmd/verysimple/cli.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/asaskevich/govalidator" - "github.com/e1732a364fed/v2ray_simple/advLayer/quic" "github.com/e1732a364fed/v2ray_simple/netLayer" "github.com/e1732a364fed/v2ray_simple/proxy" "github.com/e1732a364fed/v2ray_simple/proxy/trojan" @@ -80,41 +79,6 @@ func init() { }, }) - cliCmdList = append(cliCmdList, CliCmd{ - "调节hy手动挡", func() { - var arr = []string{"加速", "减速", "当前状态", "讲解"} - - Select := promptui.Select{ - Label: "请选择", - Items: arr, - } - - for { - i, result, err := Select.Run() - - if err != nil { - fmt.Printf("Prompt failed %v\n", err) - return - } - - fmt.Printf("你选择了 %s\n", result) - - switch i { - case 0: - quic.TheCustomRate -= 0.1 - fmt.Printf("调好了!当前rate %f\n", quic.TheCustomRate) - case 1: - quic.TheCustomRate += 0.1 - fmt.Printf("调好了!当前rate %f\n", quic.TheCustomRate) - case 2: - fmt.Printf("当前rate %f\n", quic.TheCustomRate) - case 3: - fmt.Printf("rate越小越加速, rate越大越减速. 最小0.2最大1.5。实际速度倍率为 1.5/rate \n") - } - } - }, - }) - } type CliCmd struct { diff --git a/cmd/verysimple/cli_quic.go b/cmd/verysimple/cli_quic.go new file mode 100644 index 0000000..094b664 --- /dev/null +++ b/cmd/verysimple/cli_quic.go @@ -0,0 +1,49 @@ +//go:build !noquic + +package main + +import ( + "fmt" + + "github.com/e1732a364fed/v2ray_simple/advLayer/quic" + "github.com/manifoldco/promptui" +) + +func init() { + + cliCmdList = append(cliCmdList, CliCmd{ + "调节hy手动挡", func() { + var arr = []string{"加速", "减速", "当前状态", "讲解"} + + Select := promptui.Select{ + Label: "请选择", + Items: arr, + } + + for { + i, result, err := Select.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("你选择了 %s\n", result) + + switch i { + case 0: + quic.TheCustomRate -= 0.1 + fmt.Printf("调好了!当前rate %f\n", quic.TheCustomRate) + case 1: + quic.TheCustomRate += 0.1 + fmt.Printf("调好了!当前rate %f\n", quic.TheCustomRate) + case 2: + fmt.Printf("当前rate %f\n", quic.TheCustomRate) + case 3: + fmt.Printf("rate越小越加速, rate越大越减速. 最小0.2最大1.5。实际速度倍率为 1.5/rate \n") + } + } + }, + }) + +} diff --git a/cmd/verysimple/version.go b/cmd/verysimple/version.go index 6917c06..9eb61e3 100644 --- a/cmd/verysimple/version.go +++ b/cmd/verysimple/version.go @@ -14,6 +14,7 @@ import ( "fmt" "runtime" + "github.com/e1732a364fed/v2ray_simple/advLayer" "github.com/e1732a364fed/v2ray_simple/netLayer" ) @@ -22,7 +23,13 @@ const delimiter = "===============================\n" var Version string = "[version_undefined]" //版本号可由 -ldflags "-X 'main.Version=v1.x.x'" 指定, 本项目的Makefile就是用这种方式确定版本号 func versionStr() string { - return fmt.Sprintf("verysimple %s, %s %s %s", Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) + //verysimple 可以用 noquic, grpc_full tag 来选择性加载 advLayer的一些包,所以需要注明编译使用了哪些包 + var advList []string + for _, c := range advLayer.ProtocolsMap { + advList = append(advList, c.PackageID()) + } + + return fmt.Sprintf("verysimple %s, %s %s %s, with advLayer packages: %v", Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, advList) } func printVersion_simple() { diff --git a/examples/trojan.client.toml b/examples/trojan.client.toml index 347db01..99349b4 100644 --- a/examples/trojan.client.toml +++ b/examples/trojan.client.toml @@ -5,7 +5,7 @@ port = 10800 [[dial]] -protocol = "trojans" # 还是,为了简便,直接加了尾缀s 表示使用tls. 虽然trojan强制tls, 但是我们很灵活, 自行可以选择是否开启tls. +protocol = "trojans" # 还是,为了简便,直接加了尾缀s 表示使用tls. 虽然trojan强制tls, 但是vs很灵活, 自行可以选择是否开启tls. uuid = "a684455c-b14f-11ea-bf0d-42010aaa0003" # trojan的"password",我们填写到uuid项里. 实际上trojan这个password不要求格式, 所以你可以乱写,甚至可以写成一个中文字符串, 不过我们作为示例就统一用 示例的uuid了 ip = "127.0.0.1" host = "your-domain-name.com" # trojan-go 的服务端要求指定一个sni 并与服务端的配置相匹配, 否则会trojan-go 会拒绝连接 @@ -13,6 +13,9 @@ port = 4434 insecure = true utls = true -# use_mux = true # 只需要客户端指明 use_mux 即可开启mux, 服务端自动适配. +#advancedLayer = "ws" +#path = "/ohmygod_verysimple_is_very_simple" +#early = true # websocket early data 功能 (即0-rtt) +#use_mux = true # 只需要客户端指明 use_mux 即可开启mux, 服务端自动适配. # 备注: trojan 也是一样可以应用 ws/grpc/quic 的,具体你只要参考对应示例文件即可,然后把 vlesss 改成 trojans 即可. \ No newline at end of file diff --git a/examples/trojan.server.toml b/examples/trojan.server.toml index 5cf2889..de07091 100644 --- a/examples/trojan.server.toml +++ b/examples/trojan.server.toml @@ -8,6 +8,9 @@ insecure = true fallback = ":80" cert = "cert.pem" key = "cert.key" +#advancedLayer = "ws" +#path = "/ohmygod_verysimple_is_very_simple" +#early = true [[dial]] protocol = "direct" diff --git a/go.mod b/go.mod index 0a3e4bb..b34d24c 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,10 @@ require ( github.com/refraction-networking/utls v1.0.0 github.com/xtaci/smux v1.5.16 github.com/yl2chen/cidranger v1.0.2 + go.uber.org/atomic v1.7.0 go.uber.org/zap v1.21.0 golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 gonum.org/v1/gonum v0.11.0 google.golang.org/grpc v1.45.0 @@ -37,11 +39,9 @@ require ( github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.4 // 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/text v0.3.7 // indirect golang.org/x/tools v0.1.9 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/main.go b/main.go index 0616634..d1b7bbf 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,6 @@ import ( "github.com/e1732a364fed/v2ray_simple/tlsLayer" "github.com/e1732a364fed/v2ray_simple/utils" - _ "github.com/e1732a364fed/v2ray_simple/advLayer/grpc" - _ "github.com/e1732a364fed/v2ray_simple/advLayer/quic" _ "github.com/e1732a364fed/v2ray_simple/advLayer/ws" _ "github.com/e1732a364fed/v2ray_simple/proxy/dokodemo" @@ -1030,6 +1028,8 @@ func dialClient(targetAddr netLayer.Addr, advLayerHandshakeStep: + //var firstPayloadAlreadyDealt bool + if adv != "" { switch adv { case "quic": @@ -1116,7 +1116,11 @@ advLayerHandshakeStep: //若配置了 MaxEarlyDataLen,则我们先读一段; edBuf := utils.GetPacket() edBuf = edBuf[:advLayer.MaxEarlyDataLen] + + wlc.SetReadDeadline(time.Now().Add(proxy.FirstPayloadTimeout)) n, e := wlc.Read(edBuf) + wlc.SetReadDeadline(time.Time{}) + if e != nil { if ce := utils.CanLogErr("failed to read ws early data"); ce != nil { ce.Write(zap.Error(e)) @@ -1124,7 +1128,11 @@ advLayerHandshakeStep: result = -1 return } - ed = edBuf[:n] + if n > 0 { + //firstPayloadAlreadyDealt = true + ed = edBuf[:n] + + } if ce := utils.CanLogDebug("will send early data"); ce != nil { ce.Write( @@ -1165,11 +1173,16 @@ advLayerHandshakeStep: //udp但是有innermux时 依然用handshake, 而不是 EstablishUDPChannel var firstPayload []byte - if !hasInnerMux { //如果有内层mux,要在dialInnerProxy函数里再读 + //读取firstPayload + if !hasInnerMux { + //如果有内层mux,要在dialInnerProxy函数里再读, 而不是在这里读 + firstPayload = utils.GetMTU() wlc.SetReadDeadline(time.Now().Add(proxy.FirstPayloadTimeout)) n, err := wlc.Read(firstPayload) + wlc.SetReadDeadline(time.Time{}) + if err != nil { if !errors.Is(err, os.ErrDeadlineExceeded) { @@ -1194,7 +1207,7 @@ advLayerHandshakeStep: } } - wlc.SetReadDeadline(time.Time{}) + firstPayload = firstPayload[:n] } diff --git a/netLayer/dial.go b/netLayer/dial.go index 6250cb2..a695f31 100644 --- a/netLayer/dial.go +++ b/netLayer/dial.go @@ -37,16 +37,8 @@ func (addr *Addr) Dial() (net.Conn, error) { tcp: - //dialer := &net.Dialer{ - // Timeout: time.Second * 16, - //} //本以为直接用 DialTCP 可以加速拨号,结果发现go官方包内部依然还是把地址转换回字符串再拨号 - //另外,为了为以后支持 tproxy、bindToDevice、SO_MARK 作准备,我们还是要选择性使用 net.Dialer. - - //fastopen 不予支持, 因为自己客户端在重重网关之下,不可能让层层网关都支持tcp fast open; - // 而自己的远程节点的话因为本来网速就很快, 也不需要fastopen,总之 因为木桶原理,慢的地方在我们层层网关, 所以fastopen 意义不大. - if addr.IP != nil { if addr.IP.To4() == nil { if !machineCanConnectToIpv6 { diff --git a/netLayer/netlayer.go b/netLayer/netlayer.go index a8e5f13..a0a70b4 100644 --- a/netLayer/netlayer.go +++ b/netLayer/netlayer.go @@ -1,9 +1,9 @@ /* Package netLayer contains definitions in network layer AND transport layer. -本包有 geoip, geosite, udp, readv, splice, relay, route, dns 等相关功能。 +本包有 geoip, geosite, route, udp, readv, splice, relay, dns, listen/dial/sockopt 等相关功能。 -以后如果要添加 kcp 或 raw socket 等底层协议时,或者要控制tcp/udp拨号的细节时,也要在此包里实现. +以后如果要添加 kcp 或 raw socket 等底层协议时,也要在此包里实现. */ package netLayer @@ -54,7 +54,12 @@ func HasIpv6Interface() bool { // According to godoc, If ip is not an IPv4 address, To4 returns nil. // This means it's ipv6 if ipnet.IP.To4() == nil { - utils.Debug("Has Ipv6Interface!") + + if ce := utils.CanLogDebug("Has Ipv6Interface!"); ce != nil { + ce.Write() + } else { + log.Println("Has Ipv6Interface!") + } return true } diff --git a/netLayer/readv.go b/netLayer/readv.go index fa273d3..26f95ae 100644 --- a/netLayer/readv.go +++ b/netLayer/readv.go @@ -154,6 +154,7 @@ func ReadBuffersFrom(c io.Reader, rawReadConn syscall.RawConn, mr utils.MultiRea return } +// if r!=0, then it means c can be used in readv. 1 means syscall.RawConn, 2 means utils.MultiReader func IsConnGoodForReadv(c net.Conn) (r int, rawReadConn syscall.RawConn, mr utils.MultiReader) { rawReadConn = GetRawConn(c) var ok bool diff --git a/netLayer/sockopt.go b/netLayer/sockopt.go index 0828d9c..6e903c7 100644 --- a/netLayer/sockopt.go +++ b/netLayer/sockopt.go @@ -10,6 +10,10 @@ type Sockopt struct { TProxy bool `toml:"tproxy"` Somark int `toml:"mark"` Device string `toml:"device"` + + //fastopen 不予支持, 因为自己客户端在重重网关之下,不可能让层层网关都支持tcp fast open; + // 而自己的远程节点的话因为本来网速就很快, 也不需要fastopen,总之 因为木桶原理,慢的地方在我们层层网关, 所以fastopen 意义不大. + } //net.TCPListener, net.UnixListener @@ -32,5 +36,3 @@ func SetSockOptForListener(tcplistener ListenerWithFile, sockopt *Sockopt, isudp defer fileDescriptorSource.Close() SetSockOpt(int(fileDescriptorSource.Fd()), sockopt, isudp, isipv6) } - -//SetSockOpt 是平台相关的. diff --git a/netLayer/sockopt_other.go b/netLayer/sockopt_other.go index a34b6b8..ba3ba8e 100644 --- a/netLayer/sockopt_other.go +++ b/netLayer/sockopt_other.go @@ -3,5 +3,6 @@ package netLayer +//SetSockOpt 是平台相关的. func SetSockOpt(fd int, sockopt *Sockopt, isudp bool, isipv6 bool) { } diff --git a/proxy/proxy.go b/proxy/proxy.go index 6881b25..d948e05 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -296,6 +296,7 @@ func (*ProxyCommonStruct) GetServerInnerMuxSession(wlc io.ReadWriteCloser) *smux zap.Error(err), ) } + return nil } return smuxSession } @@ -319,6 +320,7 @@ func (pcs *ProxyCommonStruct) GetClientInnerMuxSession(wrc io.ReadWriteCloser) * zap.Error(err), ) } + return nil } pcs.innermux = smuxSession return smuxSession @@ -429,8 +431,11 @@ func (s *ProxyCommonStruct) GetAdvServer() advLayer.Server { } func (s *ProxyCommonStruct) InitAdvLayer() { - if s.AdvancedL == "" { + switch s.AdvancedL { + case "": return + case "quic": + s.setNetwork("udp") } creator := advLayer.ProtocolsMap[s.AdvancedL] diff --git a/proxy/tlsConfig.go b/proxy/tlsConfig.go index bd8aa26..c791ecb 100644 --- a/proxy/tlsConfig.go +++ b/proxy/tlsConfig.go @@ -4,68 +4,57 @@ import ( "net" "net/url" - "github.com/e1732a364fed/v2ray_simple/httpLayer" + "github.com/e1732a364fed/v2ray_simple/advLayer" "github.com/e1732a364fed/v2ray_simple/tlsLayer" ) +func updateAlpnListByAdvLayer(com ProxyCommon, alpnList []string) (result []string) { + result = alpnList + + if adv := com.AdvancedLayer(); adv != "" { + if creator := advLayer.ProtocolsMap[adv]; creator != nil { + if alpn, must := creator.GetDefaultAlpn(); must { + has_alpn := false + + for _, a := range alpnList { + if a == alpn { + has_alpn = true + break + } + } + + if !has_alpn { + result = append([]string{alpn}, alpnList...) + } + } + } + } + + return +} + //use dc.Host, dc.Insecure, dc.Utls, dc.Alpn. func prepareTLS_forClient(com ProxyCommon, dc *DialConf) error { - alpnList := dc.Alpn + alpnList := updateAlpnListByAdvLayer(com, dc.Alpn) clic := com.getCommon() if clic == nil { return nil } - switch com.AdvancedLayer() { - case "quic": - clic.setNetwork("udp") - return nil - case "grpc": - has_h2 := false - for _, a := range alpnList { - if a == httpLayer.H2_Str { - has_h2 = true - break - } - } - if !has_h2 { - alpnList = append([]string{httpLayer.H2_Str}, alpnList...) - } - } clic.setTLS_Client(tlsLayer.NewClient(dc.Host, dc.Insecure, dc.Utls, alpnList)) return nil } //use lc.Host, lc.TLSCert, lc.TLSKey, lc.Insecure, lc.Alpn. func prepareTLS_forServer(com ProxyCommon, lc *ListenConf) error { - // 这里直接不检查 字符串就直接传给 tlsLayer.NewServer - // 所以要求 cert和 key 不在程序本身目录 的话,就要给出完整路径 serc := com.getCommon() if serc == nil { return nil } - alpnList := lc.Alpn - switch com.AdvancedLayer() { - case "quic": - - serc.setNetwork("udp") - return nil - - case "grpc": - has_h2 := false - for _, a := range alpnList { - if a == httpLayer.H2_Str { - has_h2 = true - break - } - } - if !has_h2 { - alpnList = append([]string{httpLayer.H2_Str}, alpnList...) - } - } + alpnList := updateAlpnListByAdvLayer(com, lc.Alpn) tlsserver, err := tlsLayer.NewServer(lc.Host, lc.TLSCert, lc.TLSKey, lc.Insecure, alpnList) if err == nil {