From e9162c84b2cc00ab96fb632cf5c10d16ca93d5fb Mon Sep 17 00:00:00 2001 From: howmp Date: Thu, 17 Oct 2024 18:55:28 +0800 Subject: [PATCH] add multi grsc and grsu --- README.md | 13 ++- client.go | 15 ++-- client_test.go | 4 +- cmd/common.go | 20 ++++- cmd/grsc/main.go | 2 +- cmd/grss/gen.go | 32 +++++++- cmd/grss/serv.go | 161 ++++++++++++++++++++----------------- cmd/grsu/main.go | 10 ++- example/testclient/main.go | 2 +- example/testserver/main.go | 2 +- server.go | 3 +- 11 files changed, 164 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 56796b2..5672be3 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ grs是一个反向socks5代理,其中grss和grsc和grsu是通过REALITY协议通 若SNIAddr或ServerAddr不指定,则尝试加载已有配置文件 +默认生成3个不同id文件名的客户端,可通过`-c`参数指定 + ```txt Usage: grss [OPTIONS] gen [gen-OPTIONS] [SNIAddr] [ServerAddr] @@ -40,6 +42,7 @@ Help Options: -f=[chrome|firefox|safari|ios|android|edge|360|qq] client finger print (default: chrome) -e= expire second (default: 30) -o= server config output path (default: config.json) + -c= client count (default: 3) --dir= client output directory (default: .) [gen command arguments] @@ -66,14 +69,20 @@ Help Options: ### 启动客户端 -`grsc` +`grscX` + +**X表示id** ### 启动用户端 -`grsu` +`grsu -id 0` + +**这里id参数对应了grsc的id,不同id会连接不同的grsc** ```txt Usage of grsu: + -i uint + id -l string socks5 listen address (default "127.0.0.1:61080") ``` diff --git a/client.go b/client.go index aff9f92..afe17ed 100644 --- a/client.go +++ b/client.go @@ -23,9 +23,10 @@ type ClientConfig struct { SNI string `json:"sni_name"` PublicKeyECDH string `json:"public_key_ecdh"` PublicKeyVerify string `json:"public_key_verify"` - FingerPrint string `json:"finger_print,omitempty"` - ExpireSecond uint32 `json:"expire_second,omitempty"` - Debug bool `json:"debug,omitempty"` + FingerPrint string `json:"finger_print"` + ExpireSecond uint32 `json:"expire_second"` + Debug bool `json:"debug"` + OverlayData byte `json:"overlay_data"` fingerPrint *utls.ClientHelloID // 客户端的TLS指纹 publicKeyECDH *ecdh.PublicKey // 用于密钥协商 @@ -133,7 +134,7 @@ func UnmarshalClientConfig(configData []byte) (*ClientConfig, error) { return &config, nil } -func NewClient(ctx context.Context, config *ClientConfig, overlayData byte) (net.Conn, error) { +func NewClient(ctx context.Context, config *ClientConfig) (net.Conn, error) { if err := config.Validate(); err != nil { return nil, err } @@ -213,10 +214,10 @@ func NewClient(ctx context.Context, config *ClientConfig, overlayData byte) (net is12 := state.Version == versionTLS12 if is12 { // 进行我们私有握手,客户端发送附加数据,服务端回复64字节签名数据 - logger.Debugf("overlayData: %x", overlayData) + logger.Debugf("overlayData: %x", config.OverlayData) // record数据前缀模仿seq data := generateRandomData(seqNumerOne[:]) - data[len(data)-1] = overlayData + data[len(data)-1] = config.OverlayData record := newTLSRecord(recordTypeApplicationData, versionTLS12, data) if _, err := record.writeTo(uconn.GetUnderlyingConn()); err != nil { uconn.Close() @@ -247,7 +248,7 @@ func NewClient(ctx context.Context, config *ClientConfig, overlayData byte) (net } // 服务端回复验证通过 logger.Debugln("verify ok") - return newWarpConn(uconn.GetUnderlyingConn(), aead, overlayData, seqNumerOne), nil + return newWarpConn(uconn.GetUnderlyingConn(), aead, config.OverlayData, seqNumerOne), nil } uconn.Close() return nil, ErrVerifyFailed diff --git a/client_test.go b/client_test.go index 19c346f..be4d1aa 100644 --- a/client_test.go +++ b/client_test.go @@ -36,7 +36,7 @@ func TestClient(t *testing.T) { } t.Log(string(d)) - _, err = reality.NewClient(context.Background(), config, 0) + _, err = reality.NewClient(context.Background(), config) if err == nil { t.Fatal("should error") } @@ -48,7 +48,7 @@ func TestClientConfig(t *testing.T) { if err != nil { t.Fatal(err) } - config := configServer.ToClientConfig() + config := configServer.ToClientConfig(0) configData, err := config.Marshal() if err != nil { t.Fatal(err) diff --git a/cmd/common.go b/cmd/common.go index a28eabd..f4a4045 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -1,8 +1,20 @@ package cmd -const ( - OverlayGRSC = byte(0x95) - OverlayGRSU = byte(0x27) -) +func NewShortID(isGRSC bool, id byte) byte { + if id > 0x80 { + panic("id should be less than 128") + } + if isGRSC { + return id + } + return 0x80 | id +} + +func ParseShortID(shortID byte) (isGRSC bool, id byte) { + if shortID >= 0x80 { + return false, shortID & 0x7f + } + return true, shortID +} var ConfigDataPlaceholder = []byte{0xff, 0xff, 'g', 'r', 's', 'c', 'o', 'n', 'f', 'i', 'g', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} diff --git a/cmd/grsc/main.go b/cmd/grsc/main.go index 350e3a2..0e2e9b3 100644 --- a/cmd/grsc/main.go +++ b/cmd/grsc/main.go @@ -45,7 +45,7 @@ type client struct { func (c *client) serve() error { c.logger.Infoln("try connect to server") - client, err := reality.NewClient(context.Background(), c.config, cmd.OverlayGRSC) + client, err := reality.NewClient(context.Background(), c.config) if err != nil { return err } diff --git a/cmd/grss/gen.go b/cmd/grss/gen.go index 3667cb1..0f501d4 100644 --- a/cmd/grss/gen.go +++ b/cmd/grss/gen.go @@ -4,8 +4,10 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "os" "path/filepath" + "strings" utls "github.com/refraction-networking/utls" @@ -19,6 +21,7 @@ type gen struct { FingerPrint string `short:"f" default:"chrome" description:"client finger print" choice:"chrome" choice:"firefox" choice:"safari" choice:"ios" choice:"android" choice:"edge" choice:"360" choice:"qq"` ExpireSecond uint32 `short:"e" default:"30" description:"expire second"` ConfigPath string `short:"o" default:"config.json" description:"server config output path"` + ClientCount byte `short:"c" default:"3" description:"client count"` ClientOutputDir string `long:"dir" default:"." description:"client output directory"` Positional struct { SNIAddr string `description:"tls server address, e.g. example.com:443"` @@ -32,6 +35,11 @@ func (c *gen) Execute(args []string) error { c.logger = reality.GetLogger(c.Debug) var config *reality.ServerConfig var err error + if c.ClientCount > 128 { + return errors.New("client count must less than 128") + } else if c.ClientCount == 0 { + c.ClientCount = 1 + } if c.Positional.SNIAddr == "" || c.Positional.ServerAddr == "" { c.logger.Infof("try loading config, path %s", c.ConfigPath) config, err = loadConfig(c.ConfigPath) @@ -52,7 +60,7 @@ func (c *gen) Execute(args []string) error { if err := c.check(); err != nil { return err } - return c.genClient(config.ToClientConfig()) + return c.genClient(config.ToClientConfig(0)) } @@ -118,6 +126,28 @@ func (c *gen) genClient(clientConfig *reality.ClientConfig) error { } for _, name := range AssetNames() { + if strings.HasPrefix(name, "grsc") { + // 根据客户端数量生成多个客户端 + for i := 0; i < int(c.ClientCount); i++ { + path := filepath.Join(c.ClientOutputDir, fmt.Sprintf("grsc%d%s", i, name[4:])) + clientConfig.OverlayData = cmd.NewShortID(true, byte(i)) + clientConfigData, err := clientConfig.Marshal() + if err != nil { + return err + } + + ClientBin, err := replaceClientTemplate(MustAsset(name), clientConfigData) + if err != nil { + return err + } + + if err := os.WriteFile(path, ClientBin, 0755); err != nil { + return err + } + c.logger.Infof("generated %s", path) + } + continue + } path := filepath.Join(c.ClientOutputDir, name) ClientBin, err := replaceClientTemplate(MustAsset(name), configData) if err != nil { diff --git a/cmd/grss/serv.go b/cmd/grss/serv.go index f3ffb90..643ebba 100644 --- a/cmd/grss/serv.go +++ b/cmd/grss/serv.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "io" "net" @@ -27,19 +26,81 @@ func (s *serv) Execute(args []string) error { return nil } +type sessionManager struct { + logger logrus.FieldLogger + sessions [128]*yamux.Session + sessionsLock [128]sync.Mutex +} + +func (s *sessionManager) createSession(conn net.Conn, id byte) { + if s.isSessionOpen(id) { + s.logger.Errorf("client(id:%d) session already open, close %s", id, conn.RemoteAddr()) + conn.Close() + return + } + s.sessionsLock[id].Lock() + defer s.sessionsLock[id].Unlock() + session, err := yamux.Server(conn, nil) + if err != nil { + s.logger.Error(err) + conn.Close() + } + go s.checkSession(id, session) + s.sessions[id] = session + s.logger.Infof("client(id:%d) session opened %s", id, conn.RemoteAddr()) +} + +func (s *sessionManager) isSessionOpen(id byte) bool { + s.sessionsLock[id].Lock() + defer s.sessionsLock[id].Unlock() + session := s.sessions[id] + if session != nil { + return !session.IsClosed() + } + return false +} + +func (s *sessionManager) openClientSessionStream(id byte) (*yamux.Stream, error) { + s.sessionsLock[id].Lock() + defer s.sessionsLock[id].Unlock() + session := s.sessions[id] + if session != nil { + stream, err := session.OpenStream() + if err != nil { + session.Close() + s.sessions[id] = nil + return nil, err + } + return stream, nil + } + return nil, fmt.Errorf("client(id:%d) session not open", id) +} + +func (s *sessionManager) checkSession(id byte, session *yamux.Session) { + <-session.CloseChan() + s.logger.Infof("client session closed %s", session.RemoteAddr()) + s.sessionsLock[id].Lock() + defer s.sessionsLock[id].Unlock() + if s.sessions[id] == session { + s.sessions[id] = nil + } +} + // Server 反向socks5代理服务端 type Server struct { - config *reality.ServerConfig - logger logrus.FieldLogger - session *yamux.Session - sessionLock *sync.Mutex + config *reality.ServerConfig + logger logrus.FieldLogger + sm *sessionManager } func NewServer(config *reality.ServerConfig) *Server { + logger := reality.GetLogger(config.Debug) return &Server{ - config: config, - logger: reality.GetLogger(config.Debug), - sessionLock: &sync.Mutex{}, + config: config, + logger: logger, + sm: &sessionManager{ + logger: logger, + }, } } @@ -63,66 +124,48 @@ func (s *Server) Serve() { } if o, ok := conn.(reality.OverlayData); ok { - overlayData := o.OverlayData() + isGRSC, id := cmd.ParseShortID(o.OverlayData()) + if isGRSC { + s.logger.Infof("accept client(id:%d) %s", id, conn.RemoteAddr()) - if overlayData == cmd.OverlayGRSC { - s.logger.Infof("accept client %s", conn.RemoteAddr()) - go s.handleClient(conn) + go s.sm.createSession(conn, id) continue - } else if overlayData == cmd.OverlayGRSU { - s.logger.Infof("accept user %s", conn.RemoteAddr()) - go s.handleUser(conn) + } else { + s.logger.Infof("accept user(id:%d) %s", id, conn.RemoteAddr()) + go s.handleUser(conn, id) continue } } - s.logger.Warnf("accept %s, but overlay wrong", conn.RemoteAddr()) + s.logger.Warnf("accept %s, but no overlay data", conn.RemoteAddr()) conn.Close() } } -func (s *Server) handleClient(conn net.Conn) { - if s.isSessionOpen() { - s.logger.Errorf("client session already open, close %s", conn.RemoteAddr()) - conn.Close() - return - } - s.sessionLock.Lock() - defer s.sessionLock.Unlock() - session, err := yamux.Server(conn, nil) - if err != nil { - s.logger.Error(err) - conn.Close() - } - go s.checkSession(session) - s.session = session - s.logger.Infof("session opened %s", conn.RemoteAddr()) -} - -func (s *Server) handleUser(conn net.Conn) { +func (s *Server) handleUser(conn net.Conn, id byte) { defer conn.Close() session, err := yamux.Client(conn, nil) if err != nil { - s.logger.Errorf("yamux: %v", err) + s.logger.Errorf("user(id:%d) yamux: %v", id, err) return } defer session.Close() for { stream, err := session.Accept() if err != nil { - s.logger.Errorf("user session accept: %v", err) + s.logger.Errorf("user(id:%d) session accept: %v", id, err) return } - s.logger.Infof("user stream accept %s", stream.RemoteAddr()) - go s.handleUserStream(stream) + s.logger.Infof("user(id:%d) stream accept %s", id, stream.RemoteAddr()) + go s.handleUserStream(stream, id) } } -func (s *Server) handleUserStream(stream net.Conn) { +func (s *Server) handleUserStream(stream net.Conn, id byte) { defer stream.Close() - conn, err := s.openClientSessionStream() + conn, err := s.sm.openClientSessionStream(id) if err != nil { - s.logger.Errorf("open client session stream: %v", err) + s.logger.Errorf("open client(id:%d) session stream: %v", id, err) return } defer conn.Close() @@ -130,37 +173,3 @@ func (s *Server) handleUserStream(stream net.Conn) { io.Copy(stream, conn) } - -func (s *Server) isSessionOpen() bool { - s.sessionLock.Lock() - defer s.sessionLock.Unlock() - if s.session != nil { - return !s.session.IsClosed() - } - return false -} - -func (s *Server) openClientSessionStream() (*yamux.Stream, error) { - s.sessionLock.Lock() - defer s.sessionLock.Unlock() - if s.session != nil { - stream, err := s.session.OpenStream() - if err != nil { - s.session.Close() - s.session = nil - return nil, err - } - return stream, nil - } - return nil, errors.New("client session not open") -} - -func (s *Server) checkSession(session *yamux.Session) { - <-session.CloseChan() - s.logger.Infof("client session closed %s", session.RemoteAddr()) - s.sessionLock.Lock() - defer s.sessionLock.Unlock() - if s.session == session { - s.session = nil - } -} diff --git a/cmd/grsu/main.go b/cmd/grsu/main.go index a11245e..2032421 100644 --- a/cmd/grsu/main.go +++ b/cmd/grsu/main.go @@ -38,7 +38,7 @@ func (s *serverSession) connectForever() { } func (s *serverSession) connect() { logger := s.logger - client, err := reality.NewClient(context.Background(), s.config, cmd.OverlayGRSU) + client, err := reality.NewClient(context.Background(), s.config) if err != nil { logger.Errorf("connect server: %v", err) return @@ -76,10 +76,12 @@ func main() { println(err.Error()) return } - - addr := flag.String("l", "127.0.0.1:61080", "socks5 listen address") - flag.Parse() logger := reality.GetLogger(config.Debug) + addr := flag.String("l", "127.0.0.1:61080", "socks5 listen address") + id := flag.Uint("i", 0, "id") + flag.Parse() + logger.Infof("server addr: %s, sni: %s, id: %d", config.ServerAddr, config.SNI, byte(*id)) + config.OverlayData = cmd.NewShortID(false, byte(*id)) l, err := net.Listen("tcp", *addr) if err != nil { logger.Panic(err) diff --git a/example/testclient/main.go b/example/testclient/main.go index 7f79496..3c9cb2c 100644 --- a/example/testclient/main.go +++ b/example/testclient/main.go @@ -27,7 +27,7 @@ func main() { logger.Panic(err) } - client, err := reality.NewClient(context.Background(), &config, 0) + client, err := reality.NewClient(context.Background(), &config) if err != nil { logger.Panic(err) } diff --git a/example/testserver/main.go b/example/testserver/main.go index fb9d1f5..8d88acf 100644 --- a/example/testserver/main.go +++ b/example/testserver/main.go @@ -22,7 +22,7 @@ func main() { log.Panic(err) } config.Debug = true - jsonData, err := json.MarshalIndent(config.ToClientConfig(), "", " ") + jsonData, err := json.MarshalIndent(config.ToClientConfig(0), "", " ") if err != nil { log.Panic(err) } diff --git a/server.go b/server.go index 4226311..23e99ed 100644 --- a/server.go +++ b/server.go @@ -106,7 +106,7 @@ func (c *ServerConfig) SNIHost() string { func (c *ServerConfig) SNIPort() string { return c.sniPort } -func (s *ServerConfig) ToClientConfig() *ClientConfig { +func (s *ServerConfig) ToClientConfig(overlayData byte) *ClientConfig { return &ClientConfig{ SNI: s.sniHost, @@ -116,6 +116,7 @@ func (s *ServerConfig) ToClientConfig() *ClientConfig { ExpireSecond: s.ExpireSecond, Debug: s.Debug, FingerPrint: s.ClientFingerPrint, + OverlayData: overlayData, } }