Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spawn multiple gost instances in a single command with -- #713

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Features
* [Routing control](https://v2.gost.run/en/bypass/)
* DNS [resolver](https://v2.gost.run/resolver/) and [proxy](https://v2.gost.run/dns/)
* [TUN/TAP device](https://v2.gost.run/en/tuntap/)
* [Multi-Instance](#Multi-Instance)

Wiki: [v2.gost.run](https://v2.gost.run/en/)

Expand Down Expand Up @@ -418,3 +419,47 @@ gost -L=:8080 -F="http2://:443?ca=ca.pem"
```

Certificate Pinning is contributed by [@sheerun](https://github.com/sheerun).

Multi-Instance
------

Run multiple gost instances with different rules and configuration files by separating each with `--`

#### Reverse SOCKS5 over SSH tunnel
```bash
# Server
gost -L forward+ssh://:2222

# Client
gost -L socks5://127.0.0.1:1111 -- -L rtcp://127.0.0.1:3333/127.0.0.1:1111 -F forward+ssh://<server-ip>:2222

# Test from Server
curl -s -L -x socks5://127.0.0.1:3333 https://example.com
```

#### Multiple port-forwarding through different proxies
```bash
gost -- -L tcp://:2222/192.168.1.9:22 -F forward+ssh://172.25.10.3:22 -F forward+ssh://70.9.17.2:22 \
-- -L tcp://:8080/10.10.10.10:80 -F forward+tls://90.33.2.11:443 \
-- -L udp://:5353/192.10.16.8:53 -F socks5://189.155.221.25:1080
```

#### Multiple configuration files
```bash
gost -C tls.json -- -C hyper-proxy.json -- -C reverse-nc.json -- -C happy-vpn.json
```

#### A mix of everything
```bash
gost -L rudp://:5353/192.168.1.1:53?ttl=60s -F socks5://172.24.10.1:1080 -- \
-C my-proxy.json -- \
-L redirect://:1234 -F 1.2.3.4:1080 -- \
-L udp://:5353 -C forward-servers.json -- \
-L :8080 -F http://localhost:8080?ip=192.168.1.2:8081,192.168.1.3:8082 \
-F socks5://localhost:1080?ip=172.20.1.1:1080,172.20.1.2:1081 -- \
-L socks5://localhost:1080 -- \
-L :2020 -F kcp://10.16.1.10:8388?peer=peer1.txt \
-F http2://12.20.1.3:443?peer=peer2.txt
```

Multi-Instance was contributed by [@caribpa](https://github.com/caribpa).
8 changes: 4 additions & 4 deletions cmd/gost/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ type baseConfig struct {
Debug bool
}

func parseBaseConfig(s string) (*baseConfig, error) {
func parseBaseConfig(s string, baseCfg *baseConfig) error {
file, err := os.Open(s)
if err != nil {
return nil, err
return err
}
defer file.Close()

if err := json.NewDecoder(file).Decode(baseCfg); err != nil {
return nil, err
return err
}

return baseCfg, nil
return nil
}

var (
Expand Down
194 changes: 125 additions & 69 deletions cmd/gost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"net/http"
"os"
"sync"
"strings"
"runtime"

_ "net/http/pprof"
Expand All @@ -16,58 +18,148 @@ import (
)

var (
configureFile string
baseCfg = &baseConfig{}
pprofAddr string
pprofEnabled = os.Getenv("PROFILING") != ""
)

func init() {
gost.SetLogger(&gost.LogLogger{})

// TODO - Generate different certificates for each worker
generateTLSCertificate()
}

func main() {
var wg sync.WaitGroup
wg.Add(1) // Gost must exit if any of the workers exit

// Split os.Args using -- and create a worker with each slice
args := strings.Split(" " + strings.Join(os.Args[1:], " ") + " ", " -- ")
if strings.Join(args, "") == "" {
// Fix to show gost help if the resulting array is empty
args[0] = " "
}
for wid, wargs := range args {
if wargs != "" {
go worker(wid, wargs, &wg)
}
}
wg.Wait()
}

func worker(id int, args string, wg *sync.WaitGroup) {
defer wg.Done()

var (
printVersion bool
configureFile string
baseCfg = &baseConfig{}
pprofAddr string
)

flag.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain")
flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)")
flag.IntVar(&baseCfg.route.Mark, "M", 0, "Specify out connection mark")
flag.StringVar(&configureFile, "C", "", "configure file")
flag.StringVar(&baseCfg.route.Interface, "I", "", "Interface to bind")
flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log")
flag.BoolVar(&printVersion, "V", false, "print version")
if pprofEnabled {
flag.StringVar(&pprofAddr, "P", ":6060", "profiling HTTP server address")
}
flag.Parse()
init := func () error {
var printVersion bool

wf := flag.NewFlagSet(os.Args[0], flag.ExitOnError)

wf.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain")
wf.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)")
wf.StringVar(&configureFile, "C", "", "configure file")
wf.BoolVar(&baseCfg.Debug, "D", false, "enable debug log")
wf.BoolVar(&printVersion, "V", false, "print version")

if pprofEnabled {
// Every worker uses a different profiling server by default
wf.StringVar(&pprofAddr, "P", fmt.Sprintf(":606%d", id), "profiling HTTP server address")
}

wf.Parse(strings.Fields(args))

if printVersion {
fmt.Fprintf(os.Stdout, "gost %s (%s %s/%s)\n", gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
os.Exit(0)
} else if wf.NFlag() == 0 {
wf.Usage()
os.Exit(0)
} else if configureFile != "" {
err := parseBaseConfig(configureFile, baseCfg)
if err != nil {
return err
}
}

if printVersion {
fmt.Fprintf(os.Stdout, "gost %s (%s %s/%s)\n",
gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
os.Exit(0)
if baseCfg.route.ServeNodes.String() == "[]" {
configErrMsg := ""
if configureFile != "" {
configErrMsg = " or ServeNodes inside config file (-C)"
}
fmt.Fprintf(os.Stderr, "\n[!] Error: Missing -L flag%s\n\n", configErrMsg)
wf.Usage()
os.Exit(1)
}

return nil
}

if configureFile != "" {
_, err := parseBaseConfig(configureFile)
start := func () error {
// TODO - Make debug worker independent
if ! gost.Debug {
gost.Debug = baseCfg.Debug
}

var routers []router
rts, err := baseCfg.route.GenRouters()
if err != nil {
log.Log(err)
os.Exit(1)
return err
}
routers = append(routers, rts...)

for _, route := range baseCfg.Routes {
rts, err := route.GenRouters()
if err != nil {
return err
}
routers = append(routers, rts...)
}

if len(routers) == 0 {
return errors.New("invalid config")
}
for i := range routers {
go routers[i].Serve()
}

return nil
}
if flag.NFlag() == 0 {
flag.PrintDefaults()
os.Exit(0)

main := func () error {
if pprofEnabled {
go func() {
log.Log("profiling server on", pprofAddr)
log.Log(http.ListenAndServe(pprofAddr, nil))
}()
}

err := start()
return err
}
}

func main() {
if pprofEnabled {
go func() {
log.Log("profiling server on", pprofAddr)
log.Log(http.ListenAndServe(pprofAddr, nil))
}()
if err := init(); err != nil {
log.Log(err)
return
}
if err := main(); err != nil {
log.Log(err)
return
}

// Allow local functions to be garbage-collected
init = nil
main = nil
start = nil

select {}
}

func generateTLSCertificate() {
// NOTE: as of 2.6, you can use custom cert/key files to initialize the default certificate.
tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile, "")
if err != nil {
Expand All @@ -83,41 +175,5 @@ func main() {
} else {
log.Log("load TLS certificate files OK")
}

gost.DefaultTLSConfig = tlsConfig

if err := start(); err != nil {
log.Log(err)
os.Exit(1)
}

select {}
}

func start() error {
gost.Debug = baseCfg.Debug

var routers []router
rts, err := baseCfg.route.GenRouters()
if err != nil {
return err
}
routers = append(routers, rts...)

for _, route := range baseCfg.Routes {
rts, err := route.GenRouters()
if err != nil {
return err
}
routers = append(routers, rts...)
}

if len(routers) == 0 {
return errors.New("invalid config")
}
for i := range routers {
go routers[i].Serve()
}

return nil
}