add multi grsc and grsu

This commit is contained in:
howmp 2024-10-17 18:55:28 +08:00
parent 9bb9c6d084
commit e9162c84b2
11 changed files with 164 additions and 100 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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