Compare commits

..

No commits in common. "master" and "v3.0.0-beta.5" have entirely different histories.

49 changed files with 1792 additions and 2349 deletions

89
.github/workflows/buildx.yaml vendored 100644
View File

@ -0,0 +1,89 @@
# ref: https://docs.docker.com/ci-cd/github-actions/
# https://blog.oddbit.com/post/2020-09-25-building-multi-architecture-im/
name: Docker
on:
push:
branches:
- master
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Prepare
id: prepare
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_IMAGE }}
VERSION=latest
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/s390x
# https://github.com/crazy-max/ghaction-docker-buildx
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
with:
version: latest
- name: Environment
run: |
echo home=$HOME
echo git_ref=$GITHUB_REF
echo git_sha=$GITHUB_SHA
echo version=${{ steps.prepare.outputs.version }}
echo image=${{ steps.prepare.outputs.docker_image }}
echo platforms=${{ steps.prepare.outputs.docker_platforms }}
echo avail_platforms=${{ steps.buildx.outputs.platforms }}
# https://github.com/actions/checkout
- name: Checkout
uses: actions/checkout@v2
- name: Docker Buildx (no push)
run: |
docker buildx bake \
--set ${{ github.event.repository.name }}.platform=${{ steps.prepare.outputs.docker_platforms }} \
--set ${{ github.event.repository.name }}.output=type=image,push=false \
--set ${{ github.event.repository.name }}.tags="${{ steps.prepare.outputs.tags }}" \
--file docker-compose.yaml
- name: Docker Login
if: success()
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Docker Buildx (push)
if: success()
run: |
docker buildx bake \
--set ${{ github.event.repository.name }}.platform=${{ steps.prepare.outputs.docker_platforms }} \
--set ${{ github.event.repository.name }}.output=type=image,push=true \
--set ${{ github.event.repository.name }}.tags="${{ steps.prepare.outputs.tags }}" \
--file docker-compose.yaml
- name: Clear
if: always()
run: |
rm -f ${HOME}/.docker/config.json

View File

@ -1,69 +0,0 @@
# ref: https://docs.docker.com/ci-cd/github-actions/
# https://blog.oddbit.com/post/2020-09-25-building-multi-architecture-im/
name: docker
on:
push:
branches:
- master
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Prepare
id: prepare
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_IMAGE }}
VERSION=latest
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
MAJOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[1]}'`
MINOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[2]}'`
TAGS="$TAGS,${DOCKER_IMAGE}:${MAJOR_VERSION},${DOCKER_IMAGE}:${MAJOR_VERSION}.${MINOR_VERSION},${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "docker_image=${DOCKER_IMAGE}" >> $GITHUB_OUTPUT
echo "docker_platforms=linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/s390x,linux/riscv64" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Environment
run: |
echo home=$HOME
echo git_ref=$GITHUB_REF
echo git_sha=$GITHUB_SHA
echo image=${{ steps.prepare.outputs.docker_image }}
echo tags=${{ steps.prepare.outputs.tags }}
echo platforms=${{ steps.prepare.outputs.docker_platforms }}
echo avail_platforms=${{ steps.buildx.outputs.platforms }}
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Buildx and push
uses: docker/build-push-action@v6
with:
platforms: ${{ steps.prepare.outputs.docker_platforms }}
push: true
tags: ${{ steps.prepare.outputs.tags }}

View File

@ -1,42 +0,0 @@
name: goreleaser
on:
push:
# run only against tags
tags:
- 'v*'
permissions:
contents: write
# packages: write
# issues: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: '1.26'
cache: true
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
# More assembly might be required: Docker logins, GPG, etc. It all depends
# on your needs.
- uses: goreleaser/goreleaser-action@v6
with:
# either 'goreleaser' (default) or 'goreleaser-pro':
distribution: goreleaser
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro'
# distribution:
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}

View File

@ -1,61 +0,0 @@
name: Trigger nightly build
on:
schedule:
# * is a special character in YAML, so you have to quote this string
- cron: '00 15 * * *'
workflow_dispatch:
jobs:
check_date:
runs-on: ubuntu-latest
name: Check latest commit
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@v4
- name: print latest_commit
run: echo ${{ github.sha }}
- id: should_run
continue-on-error: true
name: check latest commit is less than a day
if: ${{ github.event_name == 'schedule' }}
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
trigger-nightly:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
name: Push tag for nightly build
runs-on: ubuntu-latest
steps:
-
name: 'Checkout'
uses: actions/checkout@v4
with:
token: ${{ secrets.NIGHTLY_BUILD_GH_TOKEN }}
fetch-depth: 0
-
name: 'Push new tag'
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
# A previous release was created using a lightweight tag
# git describe by default includes only annotated tags
# git describe --tags includes lightweight tags as well
DESCRIBE=`git tag -l --sort=-v:refname | grep -v nightly | head -n 1`
MAJOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[1]}'`
MINOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[2]}'`
PATCH_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[3]}'`
PATCH_VERSION="$((${PATCH_VERSION} + 1))"
TAG="${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-nightly.$(date +'%Y%m%d')"
git tag -a $TAG -m "$TAG: nightly build"
git push origin $TAG
- name: 'Clean up nightly releases'
uses: dev-drprasad/delete-older-releases@v0.3.3
with:
keep_latest: 2
delete_tags: true
delete_tag_pattern: nightly
env:
GITHUB_TOKEN: ${{ secrets.NIGHTLY_BUILD_GH_TOKEN }}

5
.gitignore vendored
View File

@ -32,10 +32,7 @@ _testmain.go
*.bak *.bak
cmd/gost/gost cmd/gost/gost
gost
snap snap
*.pem *.pem
/*.yaml *.yaml
*.txt
dist/

View File

@ -1,72 +0,0 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
# - go generate ./...
builds:
- env:
- CGO_ENABLED=0
main: ./cmd/gost
targets:
- darwin_amd64
- darwin_arm64
- linux_386
- linux_amd64
- linux_amd64_v3
- linux_arm_5
- linux_arm_6
- linux_arm_7
- linux_arm64
- linux_mips_softfloat
- linux_mips_hardfloat
- linux_mipsle_softfloat
- linux_mipsle_hardfloat
- linux_mips64
- linux_mips64le
- linux_s390x
- linux_riscv64
- linux_loong64
- freebsd_386
- freebsd_amd64
- windows_386
- windows_amd64
- windows_amd64_v3
- windows_arm64
- android_arm64
ldflags:
- "-s -w -X 'main.version={{ .Tag }}'"
upx:
- # UPX compression. Disabled by default (see #863): upx --best/--lzma/--brute
# adds ~3s startup time on low-spec systems (linux/arm). Release builds can
# opt in via GORELEASER_UPX=true environment variable.
enabled: false
archives:
- format: tar.gz
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
release:
prerelease: auto
mode: keep-existing
# The lines beneath this are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

View File

@ -1,66 +0,0 @@
# CLAUDE.md — gost/
CLI binary entry point for GOST (GO Simple Tunnel). This module compiles the `gost` command.
## Build & Run
```bash
cd gost && go build ./cmd/gost/...
# Cross-compile
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" ./cmd/gost/...
# Build all platforms
make all
# Run
./gost -L "http://:8080" -F "socks5://:1080"
./gost -C gost.yml
```
## Structure
| File | Purpose |
|------|---------|
| `cmd/gost/main.go` | CLI entry point: flag parsing, multi-worker mode (`--` separator), program launch via `svc.Run` |
| `cmd/gost/program.go` | Service lifecycle (`Init`/`Start`/`Stop`), config loading, API/metrics/profiling servers, SIGHUP reload |
| `cmd/gost/register.go` | Blank imports for all built-in handlers, listeners, dialers, connectors — triggers `init()` registration |
| `cmd/gost/version.go` | Version string (`3.3.0`) |
## CLI Flags
| Flag | Usage |
|------|-------|
| `-L` | Inline service definition (repeatable) |
| `-F` | Inline chain node definition (repeatable) |
| `-C` | Path to config file (YAML/JSON) |
| `-D` / `-DD` | Debug / trace logging |
| `-api` | API service address (e.g. `:8080`) |
| `-metrics` | Prometheus metrics address |
| `-O` | Output merged config as yaml or json, then exit |
| `-V` | Print version and exit |
## Multi-Worker Mode
Arguments separated by ` -- ` spawn multiple worker processes via `exec.CommandContext`. Each worker runs as a child process with `_GOST_ID` set. Any worker's exit cancels all others. This is triggered in `init()` before flag parsing.
## Service Lifecycle
- `Init` → calls `parser.Init()` with all CLI/config inputs
- `Start``parser.Parse()``loader.Load()``p.run()` (starts services, API, metrics, profiling)
- `Stop` → cancels reload context, closes all services
- SIGHUP → `reloadConfig()` re-parses and re-runs without restarting the process
## Key Dependencies
- `github.com/go-gost/core` — interface definitions
- `github.com/go-gost/x` — all implementations, config, registry
- `github.com/judwhite/go-svc` — OS service framework (handles daemon/SIGHUP/SIGTERM)
## Registration Pattern
All components register via `init()` side-effects in their packages. `cmd/gost/register.go` triggers them with blank imports. When adding a new built-in handler/listener/dialer/connector, add a blank import here.
## Tests
Tests are in `tests/e2e/` — integration tests using the built binary. No unit tests. Run with `go test ./tests/e2e/...`.

View File

@ -1,37 +1,28 @@
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1 AS xx FROM --platform=$BUILDPLATFORM golang:1.19-alpine as builder
FROM --platform=$BUILDPLATFORM golang:1.26-alpine3.23 AS builder # Convert TARGETPLATFORM to GOARCH format
# https://github.com/tonistiigi/xx
# UPX compression disabled by default (see #863): upx --best adds ~3s startup COPY --from=tonistiigi/xx:golang / /
# time on low-spec systems (linux/arm). Builds can opt in by uncommenting the
# upx install line and adding upx --best to the build command below.
# RUN apk add --no-cache upx || echo "upx not found"
COPY --from=xx / /
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN xx-info env RUN apk add --no-cache musl-dev git gcc
ENV CGO_ENABLED=0 ADD . /src
ENV XX_VERIFY_STATIC=1 WORKDIR /src
WORKDIR /app ENV GO111MODULE=on
COPY . . RUN cd cmd/gost && go env && go build
RUN cd cmd/gost && \ FROM alpine:latest
xx-go build -ldflags "-s -w" && \
xx-verify gost
FROM alpine:3.23 # add iptables for tun/tap
RUN apk add --no-cache iptables
# add iptables/nftables for tun/tap
RUN apk add --no-cache iptables nftables
WORKDIR /bin/ WORKDIR /bin/
COPY --from=builder /app/cmd/gost/gost . COPY --from=builder /src/cmd/gost/gost .
ENTRYPOINT ["/bin/gost"] ENTRYPOINT ["/bin/gost"]

View File

@ -9,7 +9,6 @@ PLATFORM_LIST = \
darwin-arm64 \ darwin-arm64 \
linux-386 \ linux-386 \
linux-amd64 \ linux-amd64 \
linux-amd64v3 \
linux-armv5 \ linux-armv5 \
linux-armv6 \ linux-armv6 \
linux-armv7 \ linux-armv7 \
@ -28,7 +27,6 @@ PLATFORM_LIST = \
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \ windows-386 \
windows-amd64 \ windows-amd64 \
windows-amd64v3 \
windows-arm64 windows-arm64
all: linux-amd64 darwin-amd64 darwin-arm64 windows-amd64 # Most used all: linux-amd64 darwin-amd64 darwin-arm64 windows-amd64 # Most used
@ -45,9 +43,6 @@ linux-386:
linux-amd64: linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
linux-amd64v3:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
linux-armv5: linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
@ -96,9 +91,6 @@ windows-386:
windows-amd64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES) GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
windows-amd64v3:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
windows-arm64: windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES) GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)

View File

@ -2,69 +2,40 @@
### GO语言实现的安全隧道 ### GO语言实现的安全隧道
[![zh](https://img.shields.io/badge/Chinese%20README-green)](README.md) [![en](https://img.shields.io/badge/English%20README-gray)](README_en.md) [English README](README_en.md)
## 功能特性 ## 功能特性
- [x] [多端口监听](https://gost.run/getting-started/quick-start/) - [x] 多端口监听
- [x] [多级转发链](https://gost.run/concepts/chain/) - [x] 多级转发链
- [x] [多协议支持](https://gost.run/tutorials/protocols/overview/) - [x] 多协议支持
- [x] [TCP/UDP端口转发](https://gost.run/tutorials/port-forwarding/) - [x] TCP/UDP端口转发
- [x] [反向代理](https://gost.run/tutorials/reverse-proxy/)和[隧道](https://gost.run/tutorials/reverse-proxy-tunnel/) - [x] TCP/UDP透明代理
- [x] [TCP/UDP透明代理](https://gost.run/tutorials/redirect/) - [x] DNS解析和代理
- [x] DNS[解析](https://gost.run/concepts/resolver/)和[代理](https://gost.run/tutorials/dns/) - [x] TUN/TAP设备
- [x] [TUN/TAP设备](https://gost.run/tutorials/tuntap/)与[TUN2SOCKS](https://gost.run/tutorials/tungo/) - [x] 负载均衡
- [x] [负载均衡](https://gost.run/concepts/selector/) - [x] 路由控制
- [x] [路由控制](https://gost.run/concepts/bypass/) - [x] 准入控制
- [x] [准入控制](https://gost.run/concepts/admission/) - [x] 动态配置
- [x] [限速限流](https://gost.run/concepts/limiter/) - [x] Prometheus监控指标
- [x] [插件系统](https://gost.run/concepts/plugin/) - [x] Web API
- [x] [Prometheus监控指标](https://gost.run/tutorials/metrics/) - [ ] Web UI
- [x] [动态配置](https://gost.run/tutorials/api/config/)
- [x] [Web API](https://gost.run/tutorials/api/overview/)
- [x] [GUI](https://github.com/go-gost/gostctl)/[WebUI](https://github.com/go-gost/gost-ui)
## 概览 Wiki站点[https://gost.run](https://gost.run)
![Overview](https://gost.run/images/overview.png) Telegram讨论群[https://t.me/gogost](https://t.me/gogost)
GOST作为隧道有三种主要使用方式。 Google讨论组[https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
### 正向代理 旧版入口:[v2.gost.run](https://v2.gost.run)
作为代理服务访问网络,可以组合使用多种协议组成转发链进行转发。
![Proxy](https://gost.run/images/proxy.png)
### 端口转发
将一个服务的端口映射到另外一个服务的端口,同样可以组合使用多种协议组成转发链进行转发。
![Forward](https://gost.run/images/forward.png)
### 反向代理
利用隧道和内网穿透将内网服务暴露到公网访问。
![Reverse Proxy](https://gost.run/images/reverse-proxy.png)
## 下载安装 ## 下载安装
### 二进制文件 ### 二进制文件
[https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases) [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
### 安装脚本
```bash
# 安装最新版本 [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --install
```
```bash
# 选择要安装的版本
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh)
```
### 源码编译 ### 源码编译
``` ```
@ -76,31 +47,9 @@ go build
### Docker ### Docker
``` ```
docker run --rm gogost/gost -V docker pull gogost/gost
``` ```
## 工具
### GUI
[go-gost/gostctl](https://github.com/go-gost/gostctl)
### WebUI
[go-gost/gost-ui](https://github.com/go-gost/gost-ui)
### Shadowsocks Android插件 ### Shadowsocks Android插件
[hamid-nazari/ShadowsocksGostPlugin](https://github.com/hamid-nazari/ShadowsocksGostPlugin) [xausky/ShadowsocksGostPlugin](https://github.com/xausky/ShadowsocksGostPlugin)
## 帮助与支持
Wiki站点[https://gost.run](https://gost.run)
YouTube: [https://www.youtube.com/@gost-tunnel](https://www.youtube.com/@gost-tunnel)
Telegram[https://t.me/gogost](https://t.me/gogost)
Google讨论组[https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
旧版入口:[v2.gost.run](https://v2.gost.run)

View File

@ -2,69 +2,38 @@
### A simple security tunnel written in golang ### A simple security tunnel written in golang
[![en](https://img.shields.io/badge/English%20README-green)](README_en.md) [![zh](https://img.shields.io/badge/Chinese%20README-gray)](README.md)
## Features ## Features
- [x] [Listening on multiple ports](https://gost.run/en/getting-started/quick-start/) - [x] Listening on multiple ports
- [x] [Multi-level forwarding chain](https://gost.run/en/concepts/chain/) - [x] Multi-level forwarding chain.
- [x] Rich protocol - [x] Rich protocol
- [x] [TCP/UDP port forwarding](https://gost.run/en/tutorials/port-forwarding/) - [x] TCP/UDP port forwarding
- [x] [Reverse Proxy](https://gost.run/en/tutorials/reverse-proxy/) and [Tunnel](https://gost.run/en/tutorials/reverse-proxy-tunnel/) - [x] TCP/UDP transparent proxy
- [x] [TCP/UDP transparent proxy](https://gost.run/en/tutorials/redirect/) - [x] DNS resolver and proxy
- [x] DNS [resolver](https://gost.run/en/concepts/resolver/) and [proxy](https://gost.run/en/tutorials/dns/) - [x] TUN/TAP device
- [x] [TUN/TAP device](https://gost.run/en/tutorials/tuntap/) and [TUN2SOCKS](https://gost.run/en/tutorials/tungo/) - [x] Load balancing
- [x] [Load balancing](https://gost.run/en/concepts/selector/) - [x] Routing control
- [x] [Routing control](https://gost.run/en/concepts/bypass/) - [x] Admission control
- [x] [Admission control](https://gost.run/en/concepts/limiter/) - [x] Dynamic configuration
- [x] [Bandwidth/Rate Limiter](https://gost.run/en/concepts/limiter/) - [x] Prometheus metrics
- [x] [Plugin System](https://gost.run/en/concepts/plugin/) - [x] Web API
- [x] [Prometheus metrics](https://gost.run/en/tutorials/metrics/) - [ ] Web UI
- [x] [Dynamic configuration](https://gost.run/en/tutorials/api/config/)
- [x] [Web API](https://gost.run/en/tutorials/api/overview/)
- [x] [GUI](https://github.com/go-gost/gostctl)/[WebUI](https://github.com/go-gost/gost-ui)
## Overview Wiki: [https://gost.run](https://gost.run/en/)
![Overview](https://gost.run/images/overview.png) Telegram: [https://t.me/gogost](https://t.me/gogost)
There are three main ways to use GOST as a tunnel. Google group: [https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
### Proxy Legacy version: [v2.gost.run](https://v2.gost.run/en/)
As a proxy service to access the network, multiple protocols can be used in combination to form a forwarding chain for traffic forwarding.
![Proxy](https://gost.run/images/proxy.png)
### Port Forwarding
Mapping the port of one service to the port of another service, you can also use a combination of multiple protocols to form a forwarding chain for traffic forwarding.
![Forward](https://gost.run/images/forward.png)
### Reverse Proxy
Use tunnel and intranet penetration to expose local services behind NAT or firewall to public network for access.
![Reverse Proxy](https://gost.run/images/reverse-proxy.png)
## Installation ## Installation
### Binary files ### Binary files
[https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases) [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
### install script
```bash
# install latest from [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --install
```
```bash
# select version for install
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh)
```
### From source ### From source
``` ```
@ -76,31 +45,9 @@ go build
### Docker ### Docker
``` ```
docker run --rm gogost/gost -V docker pull gogost/gost
``` ```
## Tools
### GUI
[go-gost/gostctl](https://github.com/go-gost/gostctl)
### WebUI
[go-gost/gost-ui](https://github.com/go-gost/gost-ui)
### Shadowsocks Android ### Shadowsocks Android
[hamid-nazari/ShadowsocksGostPlugin](https://github.com/hamid-nazari/ShadowsocksGostPlugin) [xausky/ShadowsocksGostPlugin](https://github.com/xausky/ShadowsocksGostPlugin)
## Support
Wiki: [https://gost.run](https://gost.run/en/)
YouTube: [https://www.youtube.com/@gost-tunnel](https://www.youtube.com/@gost-tunnel)
Telegram: [https://t.me/gogost](https://t.me/gogost)
Google group: [https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
Legacy version: [v2.gost.run](https://v2.gost.run/en/)

636
cmd/gost/cmd.go 100644
View File

@ -0,0 +1,636 @@
package main
import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"os"
"strconv"
"strings"
"time"
mdutil "github.com/go-gost/core/metadata/util"
"github.com/go-gost/x/config"
"github.com/go-gost/x/limiter/conn"
"github.com/go-gost/x/limiter/traffic"
mdx "github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
var (
ErrInvalidCmd = errors.New("invalid cmd")
ErrInvalidNode = errors.New("invalid node")
)
type stringList []string
func (l *stringList) String() string {
return fmt.Sprintf("%s", *l)
}
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}
func buildConfigFromCmd(services, nodes stringList) (*config.Config, error) {
cfg := &config.Config{}
if v := os.Getenv("GOST_PROFILING"); v != "" {
cfg.Profiling = &config.ProfilingConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_METRICS"); v != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" {
cfg.Log = &config.LogConfig{
Level: v,
}
}
if v := os.Getenv("GOST_API"); v != "" {
cfg.API = &config.APIConfig{
Addr: v,
}
}
var chain *config.ChainConfig
if len(nodes) > 0 {
chain = &config.ChainConfig{
Name: "chain-0",
}
cfg.Chains = append(cfg.Chains, chain)
}
for i, node := range nodes {
url, err := normCmd(node)
if err != nil {
return nil, err
}
nodeConfig, err := buildNodeConfig(url)
if err != nil {
return nil, err
}
nodeConfig.Name = "node-0"
var nodes []*config.NodeConfig
for _, host := range strings.Split(nodeConfig.Addr, ",") {
if host == "" {
continue
}
nodeCfg := &config.NodeConfig{}
*nodeCfg = *nodeConfig
nodeCfg.Name = fmt.Sprintf("node-%d", len(nodes))
nodeCfg.Addr = host
nodes = append(nodes, nodeCfg)
}
mc := nodeConfig.Connector.Metadata
md := mdx.NewMetadata(mc)
hopConfig := &config.HopConfig{
Name: fmt.Sprintf("hop-%d", i),
Selector: parseSelector(mc),
Nodes: nodes,
}
if v := mdutil.GetString(md, "bypass"); v != "" {
bypassCfg := &config.BypassConfig{
Name: fmt.Sprintf("bypass-%d", len(cfg.Bypasses)),
}
if v[0] == '~' {
bypassCfg.Whitelist = true
v = v[1:]
}
for _, s := range strings.Split(v, ",") {
if s == "" {
continue
}
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
}
hopConfig.Bypass = bypassCfg.Name
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
delete(mc, "bypass")
}
if v := mdutil.GetString(md, "resolver"); v != "" {
resolverCfg := &config.ResolverConfig{
Name: fmt.Sprintf("resolver-%d", len(cfg.Resolvers)),
}
for _, rs := range strings.Split(v, ",") {
if rs == "" {
continue
}
resolverCfg.Nameservers = append(
resolverCfg.Nameservers,
&config.NameserverConfig{
Addr: rs,
},
)
}
hopConfig.Resolver = resolverCfg.Name
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
delete(mc, "resolver")
}
if v := mdutil.GetString(md, "hosts"); v != "" {
hostsCfg := &config.HostsConfig{
Name: fmt.Sprintf("hosts-%d", len(cfg.Hosts)),
}
for _, s := range strings.Split(v, ",") {
ss := strings.SplitN(s, ":", 2)
if len(ss) != 2 {
continue
}
hostsCfg.Mappings = append(
hostsCfg.Mappings,
&config.HostMappingConfig{
Hostname: ss[0],
IP: ss[1],
},
)
}
hopConfig.Hosts = hostsCfg.Name
cfg.Hosts = append(cfg.Hosts, hostsCfg)
delete(mc, "hosts")
}
if v := mdutil.GetString(md, "interface"); v != "" {
hopConfig.Interface = v
delete(mc, "interface")
}
if v := mdutil.GetInt(md, "so_mark"); v > 0 {
hopConfig.SockOpts = &config.SockOptsConfig{
Mark: v,
}
delete(mc, "so_mark")
}
chain.Hops = append(chain.Hops, hopConfig)
}
for i, svc := range services {
url, err := normCmd(svc)
if err != nil {
return nil, err
}
service, err := buildServiceConfig(url)
if err != nil {
return nil, err
}
service.Name = fmt.Sprintf("service-%d", i)
if chain != nil {
if service.Listener.Type == "rtcp" || service.Listener.Type == "rudp" {
service.Listener.Chain = chain.Name
} else {
service.Handler.Chain = chain.Name
}
}
cfg.Services = append(cfg.Services, service)
mh := service.Handler.Metadata
md := mdx.NewMetadata(mh)
if v := mdutil.GetInt(md, "retries"); v > 0 {
service.Handler.Retries = v
delete(mh, "retries")
}
if v := mdutil.GetString(md, "admission"); v != "" {
admCfg := &config.AdmissionConfig{
Name: fmt.Sprintf("admission-%d", len(cfg.Admissions)),
}
if v[0] == '~' {
admCfg.Whitelist = true
v = v[1:]
}
for _, s := range strings.Split(v, ",") {
if s == "" {
continue
}
admCfg.Matchers = append(admCfg.Matchers, s)
}
service.Admission = admCfg.Name
cfg.Admissions = append(cfg.Admissions, admCfg)
delete(mh, "admission")
}
if v := mdutil.GetString(md, "bypass"); v != "" {
bypassCfg := &config.BypassConfig{
Name: fmt.Sprintf("bypass-%d", len(cfg.Bypasses)),
}
if v[0] == '~' {
bypassCfg.Whitelist = true
v = v[1:]
}
for _, s := range strings.Split(v, ",") {
if s == "" {
continue
}
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
}
service.Bypass = bypassCfg.Name
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
delete(mh, "bypass")
}
if v := mdutil.GetString(md, "resolver"); v != "" {
resolverCfg := &config.ResolverConfig{
Name: fmt.Sprintf("resolver-%d", len(cfg.Resolvers)),
}
for _, rs := range strings.Split(v, ",") {
if rs == "" {
continue
}
resolverCfg.Nameservers = append(
resolverCfg.Nameservers,
&config.NameserverConfig{
Addr: rs,
Prefer: mdutil.GetString(md, "prefer"),
},
)
}
service.Resolver = resolverCfg.Name
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
delete(mh, "resolver")
}
if v := mdutil.GetString(md, "hosts"); v != "" {
hostsCfg := &config.HostsConfig{
Name: fmt.Sprintf("hosts-%d", len(cfg.Hosts)),
}
for _, s := range strings.Split(v, ",") {
ss := strings.SplitN(s, ":", 2)
if len(ss) != 2 {
continue
}
hostsCfg.Mappings = append(
hostsCfg.Mappings,
&config.HostMappingConfig{
Hostname: ss[0],
IP: ss[1],
},
)
}
service.Hosts = hostsCfg.Name
cfg.Hosts = append(cfg.Hosts, hostsCfg)
delete(mh, "hosts")
}
in := mdutil.GetString(md, "limiter.in")
out := mdutil.GetString(md, "limiter.out")
cin := mdutil.GetString(md, "limiter.conn.in")
cout := mdutil.GetString(md, "limiter.conn.out")
if in != "" || cin != "" {
limiter := &config.LimiterConfig{
Name: fmt.Sprintf("limiter-%d", len(cfg.Limiters)),
}
if in != "" {
limiter.Limits = append(limiter.Limits,
fmt.Sprintf("%s %s %s", traffic.GlobalLimitKey, in, out))
}
if cin != "" {
limiter.Limits = append(limiter.Limits,
fmt.Sprintf("%s %s %s", traffic.ConnLimitKey, cin, cout))
}
service.Limiter = limiter.Name
cfg.Limiters = append(cfg.Limiters, limiter)
delete(mh, "limiter.in")
delete(mh, "limiter.out")
delete(mh, "limiter.conn.in")
delete(mh, "limiter.conn.out")
}
if climit := mdutil.GetInt(md, "climiter"); climit > 0 {
limiter := &config.LimiterConfig{
Name: fmt.Sprintf("climiter-%d", len(cfg.CLimiters)),
Limits: []string{fmt.Sprintf("%s %d", conn.GlobalLimitKey, climit)},
}
service.CLimiter = limiter.Name
cfg.CLimiters = append(cfg.CLimiters, limiter)
delete(mh, "climiter")
}
if rlimit := mdutil.GetFloat(md, "rlimiter"); rlimit > 0 {
limiter := &config.LimiterConfig{
Name: fmt.Sprintf("rlimiter-%d", len(cfg.RLimiters)),
Limits: []string{fmt.Sprintf("%s %s", conn.GlobalLimitKey, strconv.FormatFloat(rlimit, 'f', -1, 64))},
}
service.RLimiter = limiter.Name
cfg.RLimiters = append(cfg.RLimiters, limiter)
delete(mh, "rlimiter")
}
}
return cfg, nil
}
func buildServiceConfig(url *url.URL) (*config.ServiceConfig, error) {
var handler, listener string
schemes := strings.Split(url.Scheme, "+")
if len(schemes) == 1 {
handler = schemes[0]
listener = schemes[0]
}
if len(schemes) == 2 {
handler = schemes[0]
listener = schemes[1]
}
svc := &config.ServiceConfig{
Addr: url.Host,
}
if h := registry.HandlerRegistry().Get(handler); h == nil {
handler = "auto"
}
if ln := registry.ListenerRegistry().Get(listener); ln == nil {
listener = "tcp"
if handler == "ssu" {
listener = "udp"
}
}
// forward mode
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" {
svc.Forwarder = &config.ForwarderConfig{
// Targets: strings.Split(remotes, ","),
}
for i, addr := range strings.Split(remotes, ",") {
svc.Forwarder.Nodes = append(svc.Forwarder.Nodes,
&config.NodeConfig{
Name: fmt.Sprintf("target-%d", i),
Addr: addr,
})
}
if handler != "relay" {
if listener == "tcp" || listener == "udp" ||
listener == "rtcp" || listener == "rudp" ||
listener == "tun" || listener == "tap" ||
listener == "dns" {
handler = listener
} else {
handler = "forward"
}
}
}
var auth *config.AuthConfig
if url.User != nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
}
auth.Password, _ = url.User.Password()
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := mdx.NewMetadata(m)
if sa := mdutil.GetString(md, "auth"); sa != "" {
au, err := parseAuthFromCmd(sa)
if err != nil {
return nil, err
}
auth = au
}
delete(m, "auth")
tlsConfig := &config.TLSConfig{
CertFile: mdutil.GetString(md, "certFile"),
KeyFile: mdutil.GetString(md, "keyFile"),
CAFile: mdutil.GetString(md, "caFile"),
}
if tlsConfig.CertFile == "" {
tlsConfig.CertFile = mdutil.GetString(md, "cert")
}
if tlsConfig.KeyFile == "" {
tlsConfig.KeyFile = mdutil.GetString(md, "key")
}
if tlsConfig.CAFile == "" {
tlsConfig.CAFile = mdutil.GetString(md, "ca")
}
delete(m, "certFile")
delete(m, "cert")
delete(m, "keyFile")
delete(m, "key")
delete(m, "caFile")
delete(m, "ca")
if tlsConfig.CertFile == "" {
tlsConfig = nil
}
if v := mdutil.GetString(md, "dns"); v != "" {
md.Set("dns", strings.Split(v, ","))
}
if svc.Forwarder != nil {
svc.Forwarder.Selector = parseSelector(m)
}
svc.Handler = &config.HandlerConfig{
Type: handler,
Auth: auth,
Metadata: m,
}
svc.Listener = &config.ListenerConfig{
Type: listener,
TLS: tlsConfig,
Metadata: m,
}
svc.Metadata = m
if svc.Listener.Type == "ssh" || svc.Listener.Type == "sshd" {
svc.Handler.Auth = nil
svc.Listener.Auth = auth
}
return svc, nil
}
func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
var connector, dialer string
schemes := strings.Split(url.Scheme, "+")
if len(schemes) == 1 {
connector = schemes[0]
dialer = schemes[0]
}
if len(schemes) == 2 {
connector = schemes[0]
dialer = schemes[1]
}
node := &config.NodeConfig{
Addr: url.Host,
}
if c := registry.ConnectorRegistry().Get(connector); c == nil {
connector = "http"
}
if d := registry.DialerRegistry().Get(dialer); d == nil {
dialer = "tcp"
if connector == "ssu" {
dialer = "udp"
}
}
var auth *config.AuthConfig
if url.User != nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
}
auth.Password, _ = url.User.Password()
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := mdx.NewMetadata(m)
if sauth := mdutil.GetString(md, "auth"); sauth != "" && auth == nil {
au, err := parseAuthFromCmd(sauth)
if err != nil {
return nil, err
}
auth = au
}
delete(m, "auth")
tlsConfig := &config.TLSConfig{
CertFile: mdutil.GetString(md, "certFile"),
KeyFile: mdutil.GetString(md, "keyFile"),
CAFile: mdutil.GetString(md, "caFile"),
Secure: mdutil.GetBool(md, "secure"),
ServerName: mdutil.GetString(md, "serverName"),
}
if tlsConfig.ServerName == "" {
tlsConfig.ServerName = url.Hostname()
}
if tlsConfig.CertFile == "" {
tlsConfig.CertFile = mdutil.GetString(md, "cert")
}
if tlsConfig.KeyFile == "" {
tlsConfig.KeyFile = mdutil.GetString(md, "key")
}
if tlsConfig.CAFile == "" {
tlsConfig.CAFile = mdutil.GetString(md, "ca")
}
delete(m, "certFile")
delete(m, "cert")
delete(m, "keyFile")
delete(m, "key")
delete(m, "caFile")
delete(m, "ca")
delete(m, "secure")
delete(m, "serverName")
if !tlsConfig.Secure && tlsConfig.CertFile == "" && tlsConfig.CAFile == "" {
tlsConfig = nil
}
node.Connector = &config.ConnectorConfig{
Type: connector,
Auth: auth,
Metadata: m,
}
node.Dialer = &config.DialerConfig{
Type: dialer,
TLS: tlsConfig,
Metadata: m,
}
if node.Dialer.Type == "ssh" || node.Dialer.Type == "sshd" {
node.Connector.Auth = nil
node.Dialer.Auth = auth
}
return node, nil
}
func normCmd(s string) (*url.URL, error) {
s = strings.TrimSpace(s)
if s == "" {
return nil, ErrInvalidCmd
}
if s[0] == ':' || !strings.Contains(s, "://") {
s = "auto://" + s
}
url, err := url.Parse(s)
if err != nil {
return nil, err
}
if url.Scheme == "https" {
url.Scheme = "http+tls"
}
return url, nil
}
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
v, err := base64.StdEncoding.DecodeString(sa)
if err != nil {
return nil, err
}
cs := string(v)
n := strings.IndexByte(cs, ':')
if n < 0 {
return &config.AuthConfig{
Username: cs,
}, nil
}
return &config.AuthConfig{
Username: cs[:n],
Password: cs[n+1:],
}, nil
}
func parseSelector(m map[string]any) *config.SelectorConfig {
md := mdx.NewMetadata(m)
strategy := mdutil.GetString(md, "strategy")
maxFails := mdutil.GetInt(md, "maxFails")
if maxFails == 0 {
maxFails = mdutil.GetInt(md, "max_fails")
}
failTimeout := mdutil.GetDuration(md, "failTimeout")
if failTimeout == 0 {
failTimeout = mdutil.GetDuration(md, "fail_timeout")
}
if strategy == "" && maxFails <= 0 && failTimeout <= 0 {
return nil
}
if strategy == "" {
strategy = "round"
}
if maxFails <= 0 {
maxFails = 1
}
if failTimeout <= 0 {
failTimeout = 30 * time.Second
}
delete(m, "strategy")
delete(m, "maxFails")
delete(m, "max_fails")
delete(m, "failTimeout")
delete(m, "fail_timeout")
return &config.SelectorConfig{
Strategy: strategy,
MaxFails: maxFails,
FailTimeout: failTimeout,
}
}

172
cmd/gost/config.go 100644
View File

@ -0,0 +1,172 @@
package main
import (
"io"
"os"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/service"
"github.com/go-gost/x/api"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
xlogger "github.com/go-gost/x/logger"
metrics "github.com/go-gost/x/metrics/service"
"github.com/go-gost/x/registry"
)
func buildService(cfg *config.Config) (services []service.Service) {
if cfg == nil {
return
}
for _, autherCfg := range cfg.Authers {
if auther := parsing.ParseAuther(autherCfg); auther != nil {
if err := registry.AutherRegistry().Register(autherCfg.Name, auther); err != nil {
log.Fatal(err)
}
}
}
for _, admissionCfg := range cfg.Admissions {
if adm := parsing.ParseAdmission(admissionCfg); adm != nil {
if err := registry.AdmissionRegistry().Register(admissionCfg.Name, adm); err != nil {
log.Fatal(err)
}
}
}
for _, bypassCfg := range cfg.Bypasses {
if bp := parsing.ParseBypass(bypassCfg); bp != nil {
if err := registry.BypassRegistry().Register(bypassCfg.Name, bp); err != nil {
log.Fatal(err)
}
}
}
for _, resolverCfg := range cfg.Resolvers {
r, err := parsing.ParseResolver(resolverCfg)
if err != nil {
log.Fatal(err)
}
if r != nil {
if err := registry.ResolverRegistry().Register(resolverCfg.Name, r); err != nil {
log.Fatal(err)
}
}
}
for _, hostsCfg := range cfg.Hosts {
if h := parsing.ParseHosts(hostsCfg); h != nil {
if err := registry.HostsRegistry().Register(hostsCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, recorderCfg := range cfg.Recorders {
if h := parsing.ParseRecorder(recorderCfg); h != nil {
if err := registry.RecorderRegistry().Register(recorderCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, limiterCfg := range cfg.Limiters {
if h := parsing.ParseTrafficLimiter(limiterCfg); h != nil {
if err := registry.TrafficLimiterRegistry().Register(limiterCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, limiterCfg := range cfg.CLimiters {
if h := parsing.ParseConnLimiter(limiterCfg); h != nil {
if err := registry.ConnLimiterRegistry().Register(limiterCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, limiterCfg := range cfg.RLimiters {
if h := parsing.ParseRateLimiter(limiterCfg); h != nil {
if err := registry.RateLimiterRegistry().Register(limiterCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, chainCfg := range cfg.Chains {
c, err := parsing.ParseChain(chainCfg)
if err != nil {
log.Fatal(err)
}
if c != nil {
if err := registry.ChainRegistry().Register(chainCfg.Name, c); err != nil {
log.Fatal(err)
}
}
}
for _, svcCfg := range cfg.Services {
svc, err := parsing.ParseService(svcCfg)
if err != nil {
log.Fatal(err)
}
if svc != nil {
if err := registry.ServiceRegistry().Register(svcCfg.Name, svc); err != nil {
log.Fatal(err)
}
}
services = append(services, svc)
}
return
}
func logFromConfig(cfg *config.LogConfig) logger.Logger {
if cfg == nil {
cfg = &config.LogConfig{}
}
opts := []xlogger.LoggerOption{
xlogger.FormatLoggerOption(logger.LogFormat(cfg.Format)),
xlogger.LevelLoggerOption(logger.LogLevel(cfg.Level)),
}
var out io.Writer = os.Stderr
switch cfg.Output {
case "none", "null":
return xlogger.Nop()
case "stdout":
out = os.Stdout
case "stderr", "":
out = os.Stderr
default:
f, err := os.OpenFile(cfg.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Warn(err)
} else {
out = f
}
}
opts = append(opts, xlogger.OutputLoggerOption(out))
return xlogger.NewLogger(opts...)
}
func buildAPIService(cfg *config.APIConfig) (service.Service, error) {
auther := parsing.ParseAutherFromAuth(cfg.Auth)
if cfg.Auther != "" {
auther = registry.AutherRegistry().Get(cfg.Auther)
}
return api.NewService(
cfg.Addr,
api.PathPrefixOption(cfg.PathPrefix),
api.AccessLogOption(cfg.AccessLog),
api.AutherOption(auther),
)
}
func buildMetricsService(cfg *config.MetricsConfig) (service.Service, error) {
return metrics.NewService(
cfg.Addr,
metrics.PathOption(cfg.Path),
)
}

View File

@ -1,97 +1,42 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"log" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/exec"
"runtime" "runtime"
"strings"
"sync"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/metrics"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
xlogger "github.com/go-gost/x/logger" xlogger "github.com/go-gost/x/logger"
"github.com/judwhite/go-svc" xmetrics "github.com/go-gost/x/metrics"
) )
type stringList []string
func (l *stringList) String() string {
return fmt.Sprintf("%s", *l)
}
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}
var ( var (
log logger.Logger
cfgFile string cfgFile string
outputFormat string outputFormat string
services stringList services stringList
nodes stringList nodes stringList
debug bool debug bool
trace bool
apiAddr string apiAddr string
metricsAddr string metricsAddr string
) )
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
args := strings.Join(os.Args[1:], " ")
if strings.Contains(args, " -- ") {
var (
wg sync.WaitGroup
ret int
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for wid, wargs := range strings.Split(" "+args+" ", " -- ") {
wg.Add(1)
go func(wid int, wargs string) {
defer wg.Done()
defer cancel()
worker(wid, strings.Split(wargs, " "), &ctx, &ret)
}(wid, strings.TrimSpace(wargs))
}
wg.Wait()
os.Exit(ret)
}
}
func worker(id int, args []string, ctx *context.Context, ret *int) {
cmd := exec.CommandContext(*ctx, os.Args[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id))
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
if cmd.ProcessState.Exited() {
*ret = cmd.ProcessState.ExitCode()
}
}
func init() { func init() {
var printVersion bool var printVersion bool
flag.Var(&services, "L", "service list") flag.Var(&services, "L", "service list")
flag.Var(&nodes, "F", "chain node list") flag.Var(&nodes, "F", "chain node list")
flag.StringVar(&cfgFile, "C", "", "configuration file") flag.StringVar(&cfgFile, "C", "", "configure file")
flag.BoolVar(&printVersion, "V", false, "print version") flag.BoolVar(&printVersion, "V", false, "print version")
flag.StringVar(&outputFormat, "O", "", "output format, one of yaml|json format") flag.StringVar(&outputFormat, "O", "", "output format, one of yaml|json format")
flag.BoolVar(&debug, "D", false, "debug mode") flag.BoolVar(&debug, "D", false, "debug mode")
flag.BoolVar(&trace, "DD", false, "trace mode")
flag.StringVar(&apiAddr, "api", "", "api service address") flag.StringVar(&apiAddr, "api", "", "api service address")
flag.StringVar(&metricsAddr, "metrics", "", "metrics service address") flag.StringVar(&metricsAddr, "metrics", "", "metrics service address")
flag.Parse() flag.Parse()
@ -101,15 +46,108 @@ func init() {
version, runtime.Version(), runtime.GOOS, runtime.GOARCH) version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
os.Exit(0) os.Exit(0)
} }
log = xlogger.NewLogger()
logger.SetDefault(log)
} }
func main() { func main() {
log := xlogger.NewLogger() cfg := &config.Config{}
var err error
if len(services) > 0 || apiAddr != "" {
cfg, err = buildConfigFromCmd(services, nodes)
if err != nil {
log.Fatal(err)
}
if debug && cfg != nil {
if cfg.Log == nil {
cfg.Log = &config.LogConfig{}
}
cfg.Log.Level = string(logger.DebugLevel)
}
if apiAddr != "" {
cfg.API = &config.APIConfig{
Addr: apiAddr,
}
}
if metricsAddr != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: metricsAddr,
}
}
} else {
if cfgFile != "" {
err = cfg.ReadFile(cfgFile)
} else {
err = cfg.Load()
}
if err != nil {
log.Fatal(err)
}
}
log = logFromConfig(cfg.Log)
logger.SetDefault(log) logger.SetDefault(log)
p := &program{} if outputFormat != "" {
if err := cfg.Write(os.Stdout, outputFormat); err != nil {
if err := svc.Run(p); err != nil { log.Fatal(err)
logger.Default().Fatal(err) }
os.Exit(0)
} }
if cfg.Profiling != nil {
go func() {
addr := cfg.Profiling.Addr
if addr == "" {
addr = ":6060"
}
log.Info("profiling server on ", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}()
}
if cfg.API != nil {
s, err := buildAPIService(cfg.API)
if err != nil {
log.Fatal(err)
}
defer s.Close()
go func() {
log.Info("api service on ", s.Addr())
log.Fatal(s.Serve())
}()
}
if cfg.Metrics != nil {
metrics.Init(xmetrics.NewMetrics())
if cfg.Metrics.Addr != "" {
s, err := buildMetricsService(cfg.Metrics)
if err != nil {
log.Fatal(err)
}
go func() {
defer s.Close()
log.Info("metrics service on ", s.Addr())
log.Fatal(s.Serve())
}()
}
}
parsing.BuildDefaultTLSConfig(cfg.TLS)
services := buildService(cfg)
for _, svc := range services {
svc := svc
go func() {
svc.Serve()
svc.Close()
}()
}
config.SetGlobal(cfg)
select {}
} }

View File

@ -1,274 +0,0 @@
package main
import (
"context"
"errors"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/service"
api_service "github.com/go-gost/x/api/service"
xauth "github.com/go-gost/x/auth"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/loader"
auth_parser "github.com/go-gost/x/config/parsing/auth"
"github.com/go-gost/x/config/parsing/parser"
xmetrics "github.com/go-gost/x/metrics"
metrics "github.com/go-gost/x/metrics/service"
"github.com/go-gost/x/registry"
"github.com/judwhite/go-svc"
)
type program struct {
srvApi service.Service
srvMetrics service.Service
srvProfiling *http.Server
cancel context.CancelFunc
}
func (p *program) Init(env svc.Environment) error {
parser.Init(parser.Args{
CfgFile: cfgFile,
Services: services,
Nodes: nodes,
Debug: debug,
Trace: trace,
ApiAddr: apiAddr,
MetricsAddr: metricsAddr,
})
return nil
}
func (p *program) Start() error {
cfg, err := parser.Parse()
if err != nil {
return err
}
if outputFormat != "" {
if err := cfg.Write(os.Stdout, outputFormat); err != nil {
return err
}
os.Exit(0)
}
config.Set(cfg)
if err := loader.Load(cfg); err != nil {
return err
}
if err := p.run(cfg); err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
go p.reload(ctx)
return nil
}
func (p *program) run(cfg *config.Config) error {
for _, svc := range registry.ServiceRegistry().GetAll() {
svc := svc
go func() {
svc.Serve()
}()
}
if p.srvApi != nil {
p.srvApi.Close()
p.srvApi = nil
}
if cfg.API != nil {
s, err := buildApiService(cfg.API)
if err != nil {
return err
}
p.srvApi = s
go func() {
defer s.Close()
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@api"})
log.Info("listening on ", s.Addr())
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}()
}
if p.srvMetrics != nil {
p.srvMetrics.Close()
p.srvMetrics = nil
}
if cfg.Metrics != nil && cfg.Metrics.Addr != "" {
s, err := buildMetricsService(cfg.Metrics)
if err != nil {
return err
}
p.srvMetrics = s
xmetrics.Enable(true)
go func() {
defer s.Close()
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@metrics"})
log.Info("listening on ", s.Addr())
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}()
}
if p.srvProfiling != nil {
p.srvProfiling.Close()
p.srvProfiling = nil
}
if cfg.Profiling != nil {
addr := cfg.Profiling.Addr
if addr == "" {
addr = ":6060"
}
s := &http.Server{
Addr: addr,
}
p.srvProfiling = s
go func() {
defer s.Close()
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@profiling"})
log.Info("listening on ", addr)
if err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}()
}
return nil
}
func (p *program) Stop() error {
if p.cancel != nil {
p.cancel()
}
for name, srv := range registry.ServiceRegistry().GetAll() {
srv.Close()
logger.Default().Debugf("service %s shutdown", name)
}
if p.srvApi != nil {
p.srvApi.Close()
logger.Default().Debug("service @api shutdown")
}
if p.srvMetrics != nil {
p.srvMetrics.Close()
logger.Default().Debug("service @metrics shutdown")
}
if p.srvProfiling != nil {
p.srvProfiling.Close()
logger.Default().Debug("service @profiling shutdown")
}
return nil
}
func (p *program) reload(ctx context.Context) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
for {
select {
case <-c:
if err := p.reloadConfig(); err != nil {
logger.Default().Error(err)
} else {
logger.Default().Info("config reloaded")
}
case <-ctx.Done():
return
}
}
}
func (p *program) reloadConfig() error {
cfg, err := parser.Parse()
if err != nil {
return err
}
config.Set(cfg)
if err := loader.Load(cfg); err != nil {
return err
}
if err := p.run(cfg); err != nil {
return err
}
return nil
}
func buildApiService(cfg *config.APIConfig) (service.Service, error) {
var authers []auth.Authenticator
if auther := auth_parser.ParseAutherFromAuth(cfg.Auth); auther != nil {
authers = append(authers, auther)
}
if cfg.Auther != "" {
authers = append(authers, registry.AutherRegistry().Get(cfg.Auther))
}
var auther auth.Authenticator
if len(authers) > 0 {
auther = xauth.AuthenticatorGroup(authers...)
}
network := "tcp"
addr := cfg.Addr
if strings.HasPrefix(addr, "unix://") {
network = "unix"
addr = strings.TrimPrefix(addr, "unix://")
}
return api_service.NewService(
network, addr,
api_service.PathPrefixOption(cfg.PathPrefix),
api_service.AccessLogOption(cfg.AccessLog),
api_service.AutherOption(auther),
)
}
func buildMetricsService(cfg *config.MetricsConfig) (service.Service, error) {
auther := auth_parser.ParseAutherFromAuth(cfg.Auth)
if cfg.Auther != "" {
auther = registry.AutherRegistry().Get(cfg.Auther)
}
network := "tcp"
addr := cfg.Addr
if strings.HasPrefix(addr, "unix://") {
network = "unix"
addr = strings.TrimPrefix(addr, "unix://")
}
return metrics.NewService(
network, addr,
metrics.PathOption(cfg.Path),
metrics.AutherOption(auther),
)
}

View File

@ -6,66 +6,46 @@ import (
_ "github.com/go-gost/x/connector/forward" _ "github.com/go-gost/x/connector/forward"
_ "github.com/go-gost/x/connector/http" _ "github.com/go-gost/x/connector/http"
_ "github.com/go-gost/x/connector/http2" _ "github.com/go-gost/x/connector/http2"
_ "github.com/go-gost/x/connector/masque"
_ "github.com/go-gost/x/connector/relay" _ "github.com/go-gost/x/connector/relay"
_ "github.com/go-gost/x/connector/router"
_ "github.com/go-gost/x/connector/serial"
_ "github.com/go-gost/x/connector/sni" _ "github.com/go-gost/x/connector/sni"
_ "github.com/go-gost/x/connector/socks/v4" _ "github.com/go-gost/x/connector/socks/v4"
_ "github.com/go-gost/x/connector/socks/v5" _ "github.com/go-gost/x/connector/socks/v5"
_ "github.com/go-gost/x/connector/ss" _ "github.com/go-gost/x/connector/ss"
_ "github.com/go-gost/x/connector/ss/udp" _ "github.com/go-gost/x/connector/ss/udp"
_ "github.com/go-gost/x/connector/sshd" _ "github.com/go-gost/x/connector/sshd"
_ "github.com/go-gost/x/connector/tcp"
_ "github.com/go-gost/x/connector/tunnel"
_ "github.com/go-gost/x/connector/unix"
_ "github.com/go-gost/x/connector/masque"
// Register dialers // Register dialers
_ "github.com/go-gost/x/dialer/direct" _ "github.com/go-gost/x/dialer/direct"
_ "github.com/go-gost/x/dialer/dtls"
_ "github.com/go-gost/x/dialer/ftcp" _ "github.com/go-gost/x/dialer/ftcp"
_ "github.com/go-gost/x/dialer/grpc" _ "github.com/go-gost/x/dialer/grpc"
_ "github.com/go-gost/x/dialer/http2" _ "github.com/go-gost/x/dialer/http2"
_ "github.com/go-gost/x/dialer/http2/h2" _ "github.com/go-gost/x/dialer/http2/h2"
_ "github.com/go-gost/x/dialer/http3" _ "github.com/go-gost/x/dialer/http3"
_ "github.com/go-gost/x/dialer/http3/masque"
_ "github.com/go-gost/x/dialer/http3/wt"
_ "github.com/go-gost/x/dialer/icmp" _ "github.com/go-gost/x/dialer/icmp"
_ "github.com/go-gost/x/dialer/kcp" _ "github.com/go-gost/x/dialer/kcp"
_ "github.com/go-gost/x/dialer/mtcp"
_ "github.com/go-gost/x/dialer/mtls" _ "github.com/go-gost/x/dialer/mtls"
_ "github.com/go-gost/x/dialer/mws" _ "github.com/go-gost/x/dialer/mws"
_ "github.com/go-gost/x/dialer/obfs/http" _ "github.com/go-gost/x/dialer/obfs/http"
_ "github.com/go-gost/x/dialer/obfs/tls" _ "github.com/go-gost/x/dialer/obfs/tls"
_ "github.com/go-gost/x/dialer/pht" _ "github.com/go-gost/x/dialer/pht"
_ "github.com/go-gost/x/dialer/quic" _ "github.com/go-gost/x/dialer/quic"
_ "github.com/go-gost/x/dialer/serial"
_ "github.com/go-gost/x/dialer/ssh" _ "github.com/go-gost/x/dialer/ssh"
_ "github.com/go-gost/x/dialer/sshd" _ "github.com/go-gost/x/dialer/sshd"
_ "github.com/go-gost/x/dialer/tcp" _ "github.com/go-gost/x/dialer/tcp"
_ "github.com/go-gost/x/dialer/tls" _ "github.com/go-gost/x/dialer/tls"
_ "github.com/go-gost/x/dialer/udp" _ "github.com/go-gost/x/dialer/udp"
_ "github.com/go-gost/x/dialer/unix"
_ "github.com/go-gost/x/dialer/ws" _ "github.com/go-gost/x/dialer/ws"
// Register handlers // Register handlers
_ "github.com/go-gost/x/handler/api"
_ "github.com/go-gost/x/handler/auto" _ "github.com/go-gost/x/handler/auto"
_ "github.com/go-gost/x/handler/dns" _ "github.com/go-gost/x/handler/dns"
_ "github.com/go-gost/x/handler/file"
_ "github.com/go-gost/x/handler/forward/local" _ "github.com/go-gost/x/handler/forward/local"
_ "github.com/go-gost/x/handler/forward/remote" _ "github.com/go-gost/x/handler/forward/remote"
_ "github.com/go-gost/x/handler/http" _ "github.com/go-gost/x/handler/http"
_ "github.com/go-gost/x/handler/http2" _ "github.com/go-gost/x/handler/http2"
_ "github.com/go-gost/x/handler/http3"
_ "github.com/go-gost/x/handler/masque"
_ "github.com/go-gost/x/handler/metrics"
_ "github.com/go-gost/x/handler/redirect/tcp" _ "github.com/go-gost/x/handler/redirect/tcp"
_ "github.com/go-gost/x/handler/redirect/udp" _ "github.com/go-gost/x/handler/redirect/udp"
_ "github.com/go-gost/x/handler/relay" _ "github.com/go-gost/x/handler/relay"
_ "github.com/go-gost/x/handler/router"
_ "github.com/go-gost/x/handler/serial"
_ "github.com/go-gost/x/handler/sni" _ "github.com/go-gost/x/handler/sni"
_ "github.com/go-gost/x/handler/socks/v4" _ "github.com/go-gost/x/handler/socks/v4"
_ "github.com/go-gost/x/handler/socks/v5" _ "github.com/go-gost/x/handler/socks/v5"
@ -74,23 +54,16 @@ import (
_ "github.com/go-gost/x/handler/sshd" _ "github.com/go-gost/x/handler/sshd"
_ "github.com/go-gost/x/handler/tap" _ "github.com/go-gost/x/handler/tap"
_ "github.com/go-gost/x/handler/tun" _ "github.com/go-gost/x/handler/tun"
_ "github.com/go-gost/x/handler/tungo"
_ "github.com/go-gost/x/handler/tunnel"
_ "github.com/go-gost/x/handler/unix"
// Register listeners // Register listeners
_ "github.com/go-gost/x/listener/dns" _ "github.com/go-gost/x/listener/dns"
_ "github.com/go-gost/x/listener/dtls"
_ "github.com/go-gost/x/listener/ftcp" _ "github.com/go-gost/x/listener/ftcp"
_ "github.com/go-gost/x/listener/grpc" _ "github.com/go-gost/x/listener/grpc"
_ "github.com/go-gost/x/listener/http2" _ "github.com/go-gost/x/listener/http2"
_ "github.com/go-gost/x/listener/http2/h2" _ "github.com/go-gost/x/listener/http2/h2"
_ "github.com/go-gost/x/listener/http3" _ "github.com/go-gost/x/listener/http3"
_ "github.com/go-gost/x/listener/http3/h3"
_ "github.com/go-gost/x/listener/http3/wt"
_ "github.com/go-gost/x/listener/icmp" _ "github.com/go-gost/x/listener/icmp"
_ "github.com/go-gost/x/listener/kcp" _ "github.com/go-gost/x/listener/kcp"
_ "github.com/go-gost/x/listener/mtcp"
_ "github.com/go-gost/x/listener/mtls" _ "github.com/go-gost/x/listener/mtls"
_ "github.com/go-gost/x/listener/mws" _ "github.com/go-gost/x/listener/mws"
_ "github.com/go-gost/x/listener/obfs/http" _ "github.com/go-gost/x/listener/obfs/http"
@ -101,15 +74,12 @@ import (
_ "github.com/go-gost/x/listener/redirect/udp" _ "github.com/go-gost/x/listener/redirect/udp"
_ "github.com/go-gost/x/listener/rtcp" _ "github.com/go-gost/x/listener/rtcp"
_ "github.com/go-gost/x/listener/rudp" _ "github.com/go-gost/x/listener/rudp"
_ "github.com/go-gost/x/listener/serial"
_ "github.com/go-gost/x/listener/ssh" _ "github.com/go-gost/x/listener/ssh"
_ "github.com/go-gost/x/listener/sshd" _ "github.com/go-gost/x/listener/sshd"
_ "github.com/go-gost/x/listener/tap" _ "github.com/go-gost/x/listener/tap"
_ "github.com/go-gost/x/listener/tcp" _ "github.com/go-gost/x/listener/tcp"
_ "github.com/go-gost/x/listener/tls" _ "github.com/go-gost/x/listener/tls"
_ "github.com/go-gost/x/listener/tun" _ "github.com/go-gost/x/listener/tun"
_ "github.com/go-gost/x/listener/tungo"
_ "github.com/go-gost/x/listener/udp" _ "github.com/go-gost/x/listener/udp"
_ "github.com/go-gost/x/listener/unix"
_ "github.com/go-gost/x/listener/ws" _ "github.com/go-gost/x/listener/ws"
) )

View File

@ -1,5 +1,5 @@
package main package main
var ( const (
version = "3.3.0" version = "3.0.0-beta.5"
) )

View File

@ -0,0 +1,4 @@
version: "3.4"
services:
gost:
build: .

215
go.mod
View File

@ -1,170 +1,103 @@
module github.com/go-gost/gost module github.com/go-gost/gost
go 1.26.3 go 1.18
replace github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a
require ( require (
github.com/go-gost/core v0.4.1 github.com/go-gost/core v0.0.0-20220914115321-50d443049f3b
github.com/go-gost/x v0.11.0 github.com/go-gost/x v0.0.0-20220914120035-01d7dc77c67a
github.com/judwhite/go-svc v1.2.1
github.com/moby/moby/client v0.4.0
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.42.0
) )
require ( require (
dario.cat/mergo v1.0.2 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-iptables v0.5.0 // indirect github.com/coreos/go-iptables v0.5.0 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/docker/go-connections v0.7.0 // indirect github.com/gin-contrib/cors v1.3.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dunglas/httpsfv v1.1.0 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/cors v1.7.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.1 // indirect github.com/gin-gonic/gin v1.7.7 // indirect
github.com/go-gost/go-shadowsocks2 v0.1.3 // indirect github.com/go-gost/gosocks4 v0.0.1 // indirect
github.com/go-gost/gosocks4 v0.1.0 // indirect github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 // indirect
github.com/go-gost/gosocks5 v0.5.0 // indirect github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 // indirect
github.com/go-gost/plugin v0.3.0 // indirect github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 // indirect
github.com/go-gost/relay v0.6.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-gost/tls-dissector v0.2.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-playground/validator/v10 v10.10.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gopacket v1.1.19 // indirect github.com/google/gopacket v1.1.19 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/reedsolomon v1.9.9 // indirect
github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/lucas-clemente/quic-go v0.29.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.6 // indirect
github.com/magiconair/properties v1.8.10 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/miekg/dns v1.1.61 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/miekg/dns v1.1.50 // indirect
github.com/moby/go-archive v0.2.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/moby/moby/api v1.54.1 // indirect github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pires/go-proxyproto v0.6.2 // indirect
github.com/pion/dtls/v3 v3.1.1 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.12.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.1 // indirect
github.com/quic-go/webtransport-go v0.10.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rs/xid v1.3.0 // indirect github.com/rs/xid v1.3.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 // indirect
github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect github.com/spf13/viper v1.10.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect
github.com/templexxx/cpu v0.1.1 // indirect github.com/templexxx/cpu v0.0.7 // indirect
github.com/templexxx/xorsimd v0.4.3 // indirect github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/ugorji/go/codec v1.2.7 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect github.com/vishvananda/netlink v1.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/xtaci/kcp-go/v5 v5.6.1 // indirect
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect github.com/xtaci/smux v1.5.16 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vulcand/predicate v1.2.0 // indirect
github.com/xjasonlyu/tun2socks/v2 v2.6.0 // indirect
github.com/xtaci/kcp-go/v5 v5.6.5 // indirect
github.com/xtaci/smux v1.5.31 // indirect
github.com/xtaci/tcpraw v1.2.25 // indirect github.com/xtaci/tcpraw v1.2.25 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect github.com/yl2chen/cidranger v1.0.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
github.com/zalando/go-keyring v0.2.4 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect golang.org/x/text v0.3.7 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect golang.org/x/tools v0.1.12 // indirect
go.uber.org/multierr v1.11.0 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
golang.org/x/arch v0.8.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 // indirect
golang.org/x/crypto v0.50.0 // indirect google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect google.golang.org/grpc v1.49.0 // indirect
golang.org/x/mod v0.34.0 // indirect google.golang.org/protobuf v1.27.1 // indirect
golang.org/x/net v0.53.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect
golang.org/x/sync v0.20.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
golang.org/x/sys v0.43.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.43.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 // indirect
) )

995
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,6 @@ chains:
failTimeout: 30s failTimeout: 30s
hops: hops:
- name: hop-0 - name: hop-0
- name: hop-1
interface: 192.168.1.2 interface: 192.168.1.2
selector: selector:
strategy: rand strategy: rand
@ -81,38 +80,6 @@ chains:
metadata: metadata:
bar: baz bar: baz
hops:
- name: hop-0
interface: 192.168.1.2
selector:
strategy: rand
maxFails: 3
failTimeout: 60s
bypass: bypass-0
nodes:
- name: node-0
addr: ":1080"
interface: eth1
bypass: bypass-0
connector:
type: socks5
auth:
username: user
password: pass
metadata:
foo: bar
dialer:
type: tcp
auth:
username: user
password: pass
tls:
caFile: "ca.pem"
secure: true
serverName: "example.com"
metadata:
bar: baz
tls: tls:
certFile: "cert.pem" certFile: "cert.pem"
keyFile: "key.pem" keyFile: "key.pem"

View File

@ -1,102 +0,0 @@
#!/usr/bin/env bash
# Check Root User
# If you want to run as another user, please modify $EUID to be owned by this user
if [[ "$EUID" -ne '0' ]]; then
echo "$(tput setaf 1)Error: You must run this script as root!$(tput sgr0)"
exit 1
fi
# Set the desired GitHub repository
repo="go-gost/gost"
base_url="https://api.github.com/repos/$repo/releases"
# Function to download and install gost
install_gost() {
version=$1
# Detect the operating system
if [[ "$(uname)" == "Linux" ]]; then
os="linux"
elif [[ "$(uname)" == "Darwin" ]]; then
os="darwin"
elif [[ "$(uname)" == "MINGW"* ]]; then
os="windows"
else
echo "Unsupported operating system."
exit 1
fi
# Detect the CPU architecture
arch=$(uname -m)
case $arch in
x86_64)
cpu_arch="amd64"
;;
armv5*)
cpu_arch="armv5"
;;
armv6*)
cpu_arch="armv6"
;;
armv7*)
cpu_arch="armv7"
;;
aarch64|arm64)
cpu_arch="arm64"
;;
i686)
cpu_arch="386"
;;
mips64*)
cpu_arch="mips64"
;;
mips*)
cpu_arch="mips"
;;
mipsel*)
cpu_arch="mipsle"
;;
riscv64)
cpu_arch="riscv64"
;;
*)
echo "Unsupported CPU architecture."
exit 1
;;
esac
get_download_url="$base_url/tags/$version"
download_url=$(curl -s "$get_download_url" | awk -F'"' -v re=".*${os}.*${cpu_arch}.*" '/"browser_download_url":/ && $4 ~ re { print $4 }' | head -n 1)
# Download and install the binary
install_path="/usr/local/bin"
echo "Downloading and installing gost version $version..."
curl -fsSL "$download_url" | tar -xzC "$install_path" gost
chmod +x "$install_path/gost"
# Remove binary from macOS quarantine when installing for first time
[[ "$os" == "darwin" ]] && { xattr -d com.apple.quarantine "$install_path/gost" 2>&-; }
echo "gost installation completed!"
}
# Retrieve available versions from GitHub API
versions=$(curl -s "$base_url" | awk -F'"' '/"tag_name":/ {print $4}')
# Check if --install option provided
if [[ "$1" == "--install" ]]; then
# Install the latest version automatically
latest_version=$(echo "$versions" | head -n 1)
install_gost $latest_version
else
# Display available versions to the user
echo "Available gost versions:"
select version in $versions; do
if [[ -n $version ]]; then
install_gost $version
break
else
echo "Invalid choice! Please select a valid option."
fi
done
fi

View File

@ -1,4 +0,0 @@
FROM alpine:3.22
# add tools needed by e2e containers and health checks
RUN apk add --no-cache iptables curl netcat-openbsd python3

View File

@ -1,53 +0,0 @@
# End-to-End Tests
Integration tests that spin up real gost instances inside Docker containers and verify protocol behavior over the network.
## Prerequisites
- [Docker](https://docs.docker.com/get-docker/) (running daemon)
- Go toolchain (for compiling the gost binary under test)
## Running
From the repository root:
```bash
# Run all e2e tests
go test ./tests/e2e/ -v -timeout 10m
# Run a specific test suite
go test ./tests/e2e/ -v -run TestShadowsocksSuite -timeout 5m
go test ./tests/e2e/ -v -run TestParallelSelectorSuite -timeout 5m
# Use a pre-built gost binary (skips compilation)
go test ./tests/e2e/ -v -gost-bin /path/to/gost
```
## Architecture
```
tests/e2e/
├── Dockerfile # Shared base image (Alpine + curl, python3, etc.)
├── main_test.go # TestMain: compiles gost, creates Docker network
├── utils.go # Helpers: container lifecycle, config rendering
├── scripts/
│ ├── tcp_echo.py # HTTP echo server (responds with "hello-gost")
│ └── udp_echo.py # UDP echo server (reflects payloads)
├── testdata/ # config files or data files for running cases
├── shadowsocks_test.go # Shadowsocks protocol tests
└── parallel_selector_test.go # Parallel node selector tests
```
### How it works
1. **TestMain** (`main_test.go`) compiles the gost binary from `../../cmd/gost` (unless `-gost-bin` is provided) and creates a shared Docker network for all containers.
2. Each test suite starts echo server containers (TCP/UDP), then launches separate gost containers for server and client roles.
3. Client configs use Go template syntax (`{{.ServerAddr}}`) so the server address is injected at runtime.
4. Tests verify end-to-end connectivity by sending traffic through the gost proxy chain and checking that the echo server responds correctly.
## Tips
- Increase `-timeout` for CI or slow networks. Container image builds on first run take extra time.
- Use `-gost-bin` to avoid recompiling when iterating on tests locally.
- Add `-v` to see container log output on failure.
- RunGostContainer will wait for exposedPorts automatically, but it's not reliable for udp ports. So, you should check the readiness of udp ports inside cases.

View File

@ -1,50 +0,0 @@
package e2e
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"testing"
"github.com/testcontainers/testcontainers-go/network"
)
var SharedNetworkName string
func TestMain(m *testing.M) {
flag.Parse()
ctx := context.Background()
shouldCleanup := false
if GostBinPath == "" {
GostBinPath = "/tmp/gost-test-bin"
cmd := exec.Command("go", "build", "-o", GostBinPath, "../../cmd/gost")
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Failed to compile gost: %v\n", err)
os.Exit(1)
}
shouldCleanup = true
}
net, err := network.New(ctx)
if err != nil {
fmt.Printf("Failed to create network: %v\n", err)
os.Exit(1)
}
SharedNetworkName = net.Name
code := m.Run()
net.Remove(ctx)
if shouldCleanup {
os.Remove(GostBinPath)
}
os.Exit(code)
}

View File

@ -1,57 +0,0 @@
package e2e
import (
"context"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/suite"
"github.com/testcontainers/testcontainers-go"
)
type ParallelSelectorSuite struct {
suite.Suite
ctx context.Context
echoC testcontainers.Container
echoIP string
}
func (s *ParallelSelectorSuite) SetupSuite() {
s.ctx = context.Background()
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.echoC = echoC
echoIP, err := echoC.ContainerIP(s.ctx)
s.Require().NoError(err)
s.echoIP = echoIP
}
func (s *ParallelSelectorSuite) TearDownSuite() {
if s.echoC != nil {
s.echoC.Terminate(s.ctx)
}
}
func (s *ParallelSelectorSuite) TestParallelSelector() {
gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, "testdata/parallel_selector/server.yaml", "8080/tcp")
s.Require().NoError(err)
defer gostC.Terminate(s.ctx)
// Test the proxy by running curl inside the gost container
cmd := []string{"curl", "-v", "-s", "-x", "http://127.0.0.1:8080", fmt.Sprintf("http://%s:5678", s.echoIP)}
code, out, err := gostC.Exec(s.ctx, cmd)
s.Require().NoError(err)
body, err := io.ReadAll(out)
s.Require().NoError(err)
s.Require().Equal(0, code)
s.Require().Contains(string(body), "hello-gost")
}
func TestParallelSelectorSuite(t *testing.T) {
suite.Run(t, new(ParallelSelectorSuite))
}

View File

@ -1,16 +0,0 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b"hello-gost"
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
return
HTTPServer(("0.0.0.0", 5678), Handler).serve_forever()

View File

@ -1,9 +0,0 @@
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 5679))
while True:
data, addr = sock.recvfrom(2048)
sock.sendto(data, addr)

View File

@ -1,160 +0,0 @@
package e2e
import (
"context"
"fmt"
"io"
"net"
"os"
"strings"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/testcontainers/testcontainers-go"
)
type ShadowsocksSuite struct {
suite.Suite
ctx context.Context
echoC testcontainers.Container
echoIP string
udpC testcontainers.Container
}
func (s *ShadowsocksSuite) SetupSuite() {
s.ctx = context.Background()
s.T().Logf("start tcp echo container...")
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.echoC = echoC
echoIP, err := echoC.ContainerIP(s.ctx)
s.Require().NoError(err)
s.echoIP = echoIP
s.T().Logf("start udp echo container...")
udpC, err := RunUDPEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.udpC = udpC
}
func (s *ShadowsocksSuite) TearDownSuite() {
if s.echoC != nil {
s.echoC.Terminate(s.ctx)
}
if s.udpC != nil {
s.udpC.Terminate(s.ctx)
}
}
func (s *ShadowsocksSuite) TestShadowsocksTCP() {
s.runTCPCase("aes256gcm", "testdata/shadowsocks/tcp_server_aes256gcm.yaml", "testdata/shadowsocks/tcp_client_aes256gcm.yaml")
s.runTCPCase("chacha20", "testdata/shadowsocks/tcp_server_chacha20.yaml", "testdata/shadowsocks/tcp_client_chacha20.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocks2022TCP() {
s.runTCPCase("2022-aes128", "testdata/shadowsocks/tcp_server_2022_aes128.yaml", "testdata/shadowsocks/tcp_client_2022_aes128.yaml")
s.runTCPCase("2022-aes256", "testdata/shadowsocks/tcp_server_2022_aes256.yaml", "testdata/shadowsocks/tcp_client_2022_aes256.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocks2022TCPMultiPSK() {
s.runTCPCase("2022-aes128-multipsk", "testdata/shadowsocks/tcp_server_2022_aes128_multipsk.yaml", "testdata/shadowsocks/tcp_client_2022_aes128_multipsk.yaml")
s.runTCPCase("2022-aes256-multipsk", "testdata/shadowsocks/tcp_server_2022_aes256_multipsk.yaml", "testdata/shadowsocks/tcp_client_2022_aes256_multipsk.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocksUDP() {
s.runUDPCase("aes256gcm", "testdata/shadowsocks/udp_server_aes256gcm.yaml", "testdata/shadowsocks/udp_client_aes256gcm.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocks2022UDP() {
s.runUDPCase("2022-aes128", "testdata/shadowsocks/udp_server_2022_aes128.yaml", "testdata/shadowsocks/udp_client_2022_aes128.yaml")
s.runUDPCase("2022-aes256", "testdata/shadowsocks/udp_server_2022_aes256.yaml", "testdata/shadowsocks/udp_client_2022_aes256.yaml")
}
func (s *ShadowsocksSuite) runUDPCase(name, serverConfig, clientConfig string) {
s.T().Run(name, func(t *testing.T) {
serverAlias := name + "-ssu-server"
serverC, err := RunGostContainerWithOptions(s.ctx, SharedNetworkName, serverConfig, []string{serverAlias}, []string{"8389/udp"})
s.Require().NoError(err)
defer serverC.Terminate(s.ctx)
rendered, err := RenderConfig(clientConfig, ConfigData{ServerAddr: serverAlias + ":8389"})
s.Require().NoError(err)
defer os.Remove(rendered)
clientC, err := RunGostContainerWithPorts(
s.ctx,
SharedNetworkName,
rendered,
"9000/udp",
)
s.Require().NoError(err)
defer clientC.Terminate(s.ctx)
host, err := clientC.Host(s.ctx)
s.Require().NoError(err)
port, err := clientC.MappedPort(s.ctx, "9000/udp")
s.Require().NoError(err)
conn, err := net.DialTimeout("udp", net.JoinHostPort(host, port.Port()), 5*time.Second)
s.Require().NoError(err)
defer conn.Close()
payload := []byte("hello-gost-udp")
buf := make([]byte, 2048)
var n int
for i := range 5 {
_, err = conn.Write(payload)
s.Require().NoError(err)
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
n, err = conn.Read(buf)
if err == nil {
break
}
s.T().Logf("udp read attempt %d failed: %v, retrying...", i+1, err)
time.Sleep(2 * time.Second)
}
if err != nil {
DumpLogs(s.T(), s.ctx, name+" udp client logs", clientC)
DumpLogs(s.T(), s.ctx, name+" udp server logs", serverC)
}
s.Require().NoError(err)
s.Require().Contains(string(buf[:n]), "hello-gost")
})
}
func TestShadowsocksSuite(t *testing.T) {
suite.Run(t, new(ShadowsocksSuite))
}
func (s *ShadowsocksSuite) runTCPCase(name, serverConfig, clientConfig string) {
s.T().Run(name, func(t *testing.T) {
serverAlias := name + "-ss-server"
serverC, err := RunGostContainerWithOptions(s.ctx, SharedNetworkName, serverConfig, []string{serverAlias}, []string{"8388/tcp"})
s.Require().NoError(err)
defer serverC.Terminate(s.ctx)
rendered, err := RenderConfig(clientConfig, ConfigData{ServerAddr: serverAlias + ":8388"})
s.Require().NoError(err)
defer os.Remove(rendered)
clientC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, rendered, "8080/tcp")
s.Require().NoError(err)
defer clientC.Terminate(s.ctx)
cmd := []string{"curl", "-v", "-s", "-x", "http://127.0.0.1:8080", fmt.Sprintf("http://%s:5678", s.echoIP)}
code, out, err := clientC.Exec(s.ctx, cmd)
s.Require().NoError(err)
body, err := io.ReadAll(out)
s.Require().NoError(err)
if code != 0 || !strings.Contains(string(body), "hello-gost") {
DumpLogs(s.T(), s.ctx, name+" client logs", clientC)
DumpLogs(s.T(), s.ctx, name+" server logs", serverC)
}
s.Require().Equal(0, code)
s.Require().Contains(string(body), "hello-gost")
})
}

View File

@ -1,30 +0,0 @@
services:
- name: proxy
addr: :8080
handler:
type: http
chain: my-chain
listener:
type: tcp
- name: dummy-1
addr: :18081
handler:
type: http
chains:
- name: my-chain
hops:
- name: hop-1
selector:
strategy: parallel
nodes:
- name: node-1
addr: 127.0.0.1:18081
connector:
type: http
# non existed node
- name: node-2
addr: 127.0.0.1:18082
connector:
type: http

View File

@ -1,23 +0,0 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
dialer:
type: tcp

View File

@ -1,23 +0,0 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==:Vbwi6yqCwvPMPR1bCi32Dg==
dialer:
type: tcp

View File

@ -1,23 +0,0 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
dialer:
type: tcp

View File

@ -1,23 +0,0 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
dialer:
type: tcp

View File

@ -1,23 +0,0 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: aes-256-gcm
password: secret
dialer:
type: tcp

View File

@ -1,23 +0,0 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: chacha20-ietf-poly1305
password: secret
dialer:
type: tcp

View File

@ -1,10 +0,0 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
listener:
type: tcp

View File

@ -1,13 +0,0 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
metadata:
users:
test: Vbwi6yqCwvPMPR1bCi32Dg==
listener:
type: tcp

View File

@ -1,10 +0,0 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
listener:
type: tcp

View File

@ -1,13 +0,0 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
metadata:
users:
test: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
listener:
type: tcp

View File

@ -1,10 +0,0 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: aes-256-gcm
password: secret
listener:
type: tcp

View File

@ -1,10 +0,0 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: chacha20-ietf-poly1305
password: secret
listener:
type: tcp

View File

@ -1,27 +0,0 @@
services:
- name: udp-proxy
addr: :9000
handler:
type: udp
chain: ssu-chain
forwarder:
nodes:
- name: udp-echo
addr: udp-echo:5679
listener:
type: udp
chains:
- name: ssu-chain
hops:
- name: ssu-hop
nodes:
- name: ssu-node
addr: {{.ServerAddr}}
connector:
type: ssu
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
dialer:
type: udp

View File

@ -1,27 +0,0 @@
services:
- name: udp-proxy
addr: :9000
handler:
type: udp
chain: ssu-chain
forwarder:
nodes:
- name: udp-echo
addr: udp-echo:5679
listener:
type: udp
chains:
- name: ssu-chain
hops:
- name: ssu-hop
nodes:
- name: ssu-node
addr: {{.ServerAddr}}
connector:
type: ssu
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
dialer:
type: udp

View File

@ -1,27 +0,0 @@
services:
- name: udp-proxy
addr: :9000
handler:
type: udp
chain: ssu-chain
forwarder:
nodes:
- name: udp-echo
addr: udp-echo:5679
listener:
type: udp
chains:
- name: ssu-chain
hops:
- name: ssu-hop
nodes:
- name: ssu-node
addr: {{.ServerAddr}}
connector:
type: ssu
auth:
username: aes-256-gcm
password: secret
dialer:
type: udp

View File

@ -1,10 +0,0 @@
services:
- name: ssu-server
addr: :8389
handler:
type: ssu
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
listener:
type: udp

View File

@ -1,10 +0,0 @@
services:
- name: ssu-server
addr: :8389
handler:
type: ssu
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
listener:
type: udp

View File

@ -1,10 +0,0 @@
services:
- name: ssu-server
addr: :8389
handler:
type: ssu
auth:
username: aes-256-gcm
password: secret
listener:
type: udp

View File

@ -1,173 +0,0 @@
package e2e
import (
"context"
"flag"
"io"
"os"
"testing"
"text/template"
"github.com/moby/moby/client"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
var GostBinPath string
func init() {
flag.StringVar(&GostBinPath, "gost-bin", "", "Path to a pre-built gost binary (skips compilation)")
}
type ConfigData struct {
ServerAddr string
}
func DumpLogs(t *testing.T, ctx context.Context, label string, c testcontainers.Container) {
logs, err := c.Logs(ctx)
if err != nil {
return
}
defer logs.Close()
body, err := io.ReadAll(logs)
if err != nil {
return
}
t.Logf("%s:\n%s", label, string(body))
}
func RenderConfig(tmplPath string, data ConfigData) (string, error) {
tmpl, err := template.ParseFiles(tmplPath)
if err != nil {
return "", err
}
f, err := os.CreateTemp("", "gost-e2e-config-*.yaml")
if err != nil {
return "", err
}
if err := tmpl.Execute(f, data); err != nil {
f.Close()
os.Remove(f.Name())
return "", err
}
if err := f.Close(); err != nil {
os.Remove(f.Name())
return "", err
}
return f.Name(), nil
}
func RunEchoContainer(ctx context.Context, networkName string) (testcontainers.Container, error) {
req := echoContainerRequest(ctx, networkName)
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
}
func echoContainerRequest(_ context.Context, networkName string) testcontainers.ContainerRequest {
return testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: ".",
Dockerfile: "Dockerfile",
Repo: "gost-e2e",
Tag: "latest",
KeepImage: true,
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
opts.NetworkMode = "host"
},
},
Networks: []string{networkName},
NetworkAliases: map[string][]string{
networkName: {"tcp-echo"},
},
Files: []testcontainers.ContainerFile{
{HostFilePath: "scripts/tcp_echo.py", ContainerFilePath: "/scripts/tcp_echo.py", FileMode: 0644},
},
ExposedPorts: []string{"5678/tcp"},
Cmd: []string{"python3", "/scripts/tcp_echo.py"},
WaitingFor: wait.ForExposedPort(),
}
}
func RunUDPEchoContainer(ctx context.Context, networkName string) (testcontainers.Container, error) {
req := udpEchoContainerRequest(ctx, networkName)
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
}
func udpEchoContainerRequest(_ context.Context, networkName string) testcontainers.ContainerRequest {
return testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: ".",
Dockerfile: "Dockerfile",
Repo: "gost-e2e",
Tag: "latest",
KeepImage: true,
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
opts.NetworkMode = "host"
},
},
Networks: []string{networkName},
NetworkAliases: map[string][]string{
networkName: {"udp-echo"},
},
Files: []testcontainers.ContainerFile{
{HostFilePath: "scripts/udp_echo.py", ContainerFilePath: "/scripts/udp_echo.py", FileMode: 0644},
},
ExposedPorts: []string{"5679/udp"},
Cmd: []string{"python3", "/scripts/udp_echo.py"},
WaitingFor: wait.ForExposedPort().SkipInternalCheck(),
}
}
func RunGostContainer(ctx context.Context, networkName, yamlPath string) (testcontainers.Container, error) {
return runGostContainer(ctx, networkName, yamlPath, nil, nil)
}
func RunGostContainerWithPorts(ctx context.Context, networkName, yamlPath string, exposedPorts ...string) (testcontainers.Container, error) {
return runGostContainer(ctx, networkName, yamlPath, nil, exposedPorts)
}
func RunGostContainerWithOptions(ctx context.Context, networkName, yamlPath string, aliases, exposedPorts []string) (testcontainers.Container, error) {
return runGostContainer(ctx, networkName, yamlPath, aliases, exposedPorts)
}
func runGostContainer(ctx context.Context, networkName, yamlPath string, aliases, exposedPorts []string) (testcontainers.Container, error) {
req := testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: ".",
Dockerfile: "Dockerfile",
Repo: "gost-e2e",
Tag: "latest",
KeepImage: true,
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
opts.NetworkMode = "host"
},
},
ExposedPorts: exposedPorts,
// interal check for udp ports will be failed
WaitingFor: wait.ForExposedPort().SkipInternalCheck(),
Networks: []string{networkName},
NetworkAliases: map[string][]string{
networkName: aliases,
},
Files: []testcontainers.ContainerFile{
{HostFilePath: GostBinPath, ContainerFilePath: "/bin/gost", FileMode: 0755},
{HostFilePath: yamlPath, ContainerFilePath: "/config.yaml", FileMode: 0644},
},
Cmd: []string{"/bin/gost", "-C", "/config.yaml"},
}
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
}