diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml deleted file mode 100644 index 69fb231..0000000 --- a/.github/workflows/build-master.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Build master - -on: - push: - branches: - - 'master' - tags-ignore: - - 'v*' - -jobs: - - build: - name: Build - runs-on: ubuntu-latest - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - - steps: - - - name: Check out - uses: actions/checkout@v3 - - - name: Get time - uses: gerred/actions/current-time@master - id: current-time - - - name: Build - uses: tobyxdd/go-cross-build@d00fc41eb205f57dd90f6e5af4613e21c7ebe73f - env: - TIME: "${{ steps.current-time.outputs.time }}" - GOFLAGS: "-tags=gpl" - CGO_ENABLED: "0" - with: - name: hysteria - dest: dist - ldflags: -w -s -X main.appCommit=${{ github.sha }} -X main.appDate=${{ env.TIME }} - platforms: 'darwin/amd64, darwin/arm64, windows/amd64, windows/386, linux/amd64, linux/386, linux/arm, linux/arm64, linux/s390x, linux/mipsle, freebsd/amd64, freebsd/386, freebsd/arm, freebsd/arm64' - package: cmd - compress: false - - - name: Archive - uses: actions/upload-artifact@v3 - with: - name: dist - path: dist diff --git a/.github/workflows/dev-build-master.yml b/.github/workflows/dev-build-master.yml new file mode 100644 index 0000000..5e447d9 --- /dev/null +++ b/.github/workflows/dev-build-master.yml @@ -0,0 +1,38 @@ +name: "Build master" + +on: + push: + branches: + - 'master' + tags-ignore: + - 'v*' + +jobs: + + build: + name: Build + runs-on: ubuntu-latest + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + + steps: + + - name: Check out + uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Run build script + env: + HY_APP_PLATFORMS: 'darwin/amd64,darwin/arm64,windows/amd64,windows/386,linux/amd64,linux/386,linux/arm,linux/arm64,linux/s390x,linux/mipsle,freebsd/amd64,freebsd/386,freebsd/arm,freebsd/arm64' + run: ./build.sh + shell: bash + + - name: Archive + uses: actions/upload-artifact@v3 + with: + name: hysteria-binaries-${{ github.sha }} + path: ./build diff --git a/.github/workflows/docker.yaml b/.github/workflows/release-docker.yaml similarity index 83% rename from .github/workflows/docker.yaml rename to .github/workflows/release-docker.yaml index 85d6198..b27ed6a 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/release-docker.yaml @@ -16,8 +16,8 @@ jobs: uses: actions/checkout@v3 - name: Get tag - uses: olegtarasov/get-tag@v2 - id: tagName + id: get_tag + run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -38,7 +38,7 @@ jobs: context: . push: true platforms: linux/amd64,linux/arm64 - tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:${{ env.GIT_TAG_NAME }} + tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:${{ steps.get_tag.outputs.tag }} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e5f557..f19e94e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,31 +18,20 @@ jobs: - name: Check out uses: actions/checkout@v3 - - name: Get tag - uses: olegtarasov/get-tag@v2 - id: tagName - - - name: Get time - uses: gerred/actions/current-time@master - id: current-time - - - name: Build - uses: tobyxdd/go-cross-build@d00fc41eb205f57dd90f6e5af4613e21c7ebe73f - env: - TIME: "${{ steps.current-time.outputs.time }}" - GOFLAGS: "-tags=gpl" - CGO_ENABLED: "0" + - name: Setup Go + uses: actions/setup-go@v3 with: - name: hysteria - dest: dist - ldflags: -w -s -X main.appVersion=${{ env.GIT_TAG_NAME }} -X main.appCommit=${{ github.sha }} -X main.appDate=${{ env.TIME }} - platforms: 'darwin/amd64, darwin/arm64, windows/amd64, windows/386, linux/amd64, linux/386, linux/arm, linux/arm64, linux/s390x, linux/mipsle, freebsd/amd64, freebsd/386, freebsd/arm, freebsd/arm64' - package: cmd - compress: false + go-version: 1.19 + + - name: Run build script + env: + HY_APP_PLATFORMS: 'darwin/amd64,darwin/arm64,windows/amd64,windows/386,linux/amd64,linux/386,linux/arm,linux/arm64,linux/s390x,linux/mipsle,freebsd/amd64,freebsd/386,freebsd/arm,freebsd/arm64' + run: ./build.sh + shell: bash - name: Generate hashes run: | - cd dist + cd build for f in $(find . -type f); do sha256sum $f | sudo tee -a hashes.txt done @@ -52,18 +41,18 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: files: | - ./dist/hysteria-darwin-amd64 - ./dist/hysteria-darwin-arm64 - ./dist/hysteria-windows-amd64.exe - ./dist/hysteria-windows-386.exe - ./dist/hysteria-linux-amd64 - ./dist/hysteria-linux-386 - ./dist/hysteria-linux-arm - ./dist/hysteria-linux-arm64 - ./dist/hysteria-linux-s390x - ./dist/hysteria-linux-mipsle - ./dist/hysteria-freebsd-amd64 - ./dist/hysteria-freebsd-386 - ./dist/hysteria-freebsd-arm - ./dist/hysteria-freebsd-arm64 - ./dist/hashes.txt + ./build/hysteria-darwin-amd64 + ./build/hysteria-darwin-arm64 + ./build/hysteria-windows-amd64.exe + ./build/hysteria-windows-386.exe + ./build/hysteria-linux-amd64 + ./build/hysteria-linux-386 + ./build/hysteria-linux-arm + ./build/hysteria-linux-arm64 + ./build/hysteria-linux-s390x + ./build/hysteria-linux-mipsle + ./build/hysteria-freebsd-amd64 + ./build/hysteria-freebsd-386 + ./build/hysteria-freebsd-arm + ./build/hysteria-freebsd-arm64 + ./build/hashes.txt diff --git a/Dockerfile b/Dockerfile index 41d3a94..496f634 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,17 +11,12 @@ ENV GOPROXY ${GOPROXY} COPY . /go/src/github.com/hynetwork/hysteria -WORKDIR /go/src/github.com/hynetwork/hysteria/cmd +WORKDIR /go/src/github.com/hynetwork/hysteria RUN set -ex \ && apk add git build-base \ - && export VERSION=$(git describe --tags) \ - && export COMMIT=$(git rev-parse HEAD) \ - && export TIMESTAMP=$(date "+%F %T") \ - && go build -trimpath -o /go/bin/hysteria -ldflags \ - "-w -s -X 'main.appVersion=${VERSION}' \ - -X 'main.appCommit=${COMMIT}' \ - -X 'main.appDate=${TIMESTAMP}'" + && ./build.sh \ + && mv ./build/hysteria-* /go/bin/hysteria # multi-stage builds to create the final image FROM alpine AS dist diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..b3053d0 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,69 @@ +# Hysteria build script for Windows (PowerShell) + +# Environment variable options: +# - HY_APP_VERSION: App version +# - HY_APP_COMMIT: App commit hash +# - HY_APP_PLATFORMS: Platforms to build for (e.g. "windows/amd64,linux/amd64,darwin/amd64") + +if (!(Get-Command go -ErrorAction SilentlyContinue)) { + Write-Host "Error: go is not installed." -ForegroundColor Red + exit 1 +} + +if (!(Get-Command git -ErrorAction SilentlyContinue)) { + Write-Host "Error: git is not installed." -ForegroundColor Red + exit 1 +} +if (!(git rev-parse --is-inside-work-tree 2>$null)) { + Write-Host "Error: not in a git repository." -ForegroundColor Red + exit 1 +} + +$ldflags = "-s -w -X 'main.appDate=$(Get-Date -Format "yyyy-MM-dd HH:mm:ss")'" +if ($env:HY_APP_VERSION) { + $ldflags += " -X 'main.appVersion=$($env:HY_APP_VERSION)'" +} +else { + $ldflags += " -X 'main.appVersion=$(git describe --tags --always)'" +} +if ($env:HY_APP_COMMIT) { + $ldflags += " -X 'main.appCommit=$($env:HY_APP_COMMIT)'" +} +else { + $ldflags += " -X 'main.appCommit=$(git rev-parse HEAD)'" +} + +if ($env:HY_APP_PLATFORMS) { + $platforms = $env:HY_APP_PLATFORMS.Split(",") +} +else { + $goos = go env GOOS + $goarch = go env GOARCH + $platforms = @("$goos/$goarch") +} + +if (Test-Path build) { + Remove-Item -Recurse -Force build +} +New-Item -ItemType Directory -Force -Path build + +Write-Host "Starting build..." -ForegroundColor Green + +foreach ($platform in $platforms) { + $env:GOOS = $platform.Split("/")[0] + $env:GOARCH = $platform.Split("/")[1] + Write-Host "Building $env:GOOS/$env:GOARCH" -ForegroundColor Green + $output = "build/hysteria-$env:GOOS-$env:GOARCH" + if ($env:GOOS -eq "windows") { + $output = "$output.exe" + } + go build -o $output -tags=gpl -ldflags $ldflags -trimpath ./cmd/ + if ($LastExitCode -ne 0) { + Write-Host "Error: failed to build $env:GOOS/$env:GOARCH" -ForegroundColor Red + exit 1 + } +} + +Write-Host "Build complete." -ForegroundColor Green + +Get-ChildItem -Path build | Format-Table -AutoSize diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..64556fa --- /dev/null +++ b/build.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Hysteria build script for Linux +# Environment variable options: +# - HY_APP_VERSION: App version +# - HY_APP_COMMIT: App commit hash +# - HY_APP_PLATFORMS: Platforms to build for (e.g. "windows/amd64,linux/amd64,darwin/amd64") + +if ! [ -x "$(command -v go)" ]; then + echo 'Error: go is not installed.' >&2 + exit 1 +fi + +if ! [ -x "$(command -v git)" ]; then + echo 'Error: git is not installed.' >&2 + exit 1 +fi +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo 'Error: not in a git repository.' >&2 + exit 1 +fi + +ldflags="-s -w -X 'main.appDate=$(date -u '+%F %T')'" +if [ -n "$HY_APP_VERSION" ]; then + ldflags="$ldflags -X 'main.appVersion=$HY_APP_VERSION'" +else + ldflags="$ldflags -X 'main.appVersion=$(git describe --tags --always)'" +fi +if [ -n "$HY_APP_COMMIT" ]; then + ldflags="$ldflags -X 'main.appCommit=$HY_APP_COMMIT'" +else + ldflags="$ldflags -X 'main.appCommit=$(git rev-parse HEAD)'" +fi + +if [ -z "$HY_APP_PLATFORMS" ]; then + HY_APP_PLATFORMS="$(go env GOOS)/$(go env GOARCH)" +fi +platforms=(${HY_APP_PLATFORMS//,/ }) + +mkdir -p build +rm -rf build/* + +echo "Starting build..." + +for platform in "${platforms[@]}"; do + GOOS=${platform%/*} + GOARCH=${platform#*/} + echo "Building $GOOS/$GOARCH" + output="build/hysteria-$GOOS-$GOARCH" + if [ $GOOS = "windows" ]; then + output="$output.exe" + fi + env GOOS=$GOOS GOARCH=$GOARCH go build -o $output -tags=gpl -ldflags "$ldflags" -trimpath ./cmd/ + if [ $? -ne 0 ]; then + echo "Error: failed to build $GOOS/$GOARCH" + exit 1 + fi +done + +echo "Build complete." + +ls -lh build/ | awk '{print $9, $5}' diff --git a/cmd/client.go b/cmd/client.go index d81635a..f1487f1 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -40,6 +40,7 @@ var clientPacketConnFuncFactoryMap = map[string]pktconns.ClientPacketConnFuncFac func client(config *clientConfig) { logrus.WithField("config", config.String()).Info("Client configuration loaded") + config.Fill() // Fill default values // Resolver if len(config.Resolver) > 0 { err := setResolver(config.Resolver) @@ -51,15 +52,11 @@ func client(config *clientConfig) { } // TLS tlsConfig := &tls.Config{ + NextProtos: []string{config.ALPN}, ServerName: config.ServerName, InsecureSkipVerify: config.Insecure, MinVersion: tls.VersionTLS13, } - if config.ALPN != "" { - tlsConfig.NextProtos = []string{config.ALPN} - } else { - tlsConfig.NextProtos = []string{DefaultALPN} - } // Load CA if len(config.CustomCA) > 0 { bs, err := ioutil.ReadFile(config.CustomCA) @@ -84,24 +81,11 @@ func client(config *clientConfig) { InitialConnectionReceiveWindow: config.ReceiveWindow, MaxConnectionReceiveWindow: config.ReceiveWindow, HandshakeIdleTimeout: time.Duration(config.HandshakeTimeout) * time.Second, + MaxIdleTimeout: time.Duration(config.IdleTimeout) * time.Second, + KeepAlivePeriod: time.Duration(config.IdleTimeout) * time.Second * 2 / 5, DisablePathMTUDiscovery: config.DisableMTUDiscovery, EnableDatagrams: true, } - if config.IdleTimeout == 0 { - quicConfig.MaxIdleTimeout = DefaultClientMaxIdleTimeout - quicConfig.KeepAlivePeriod = DefaultClientKeepAlivePeriod - } else { - quicConfig.MaxIdleTimeout = time.Duration(config.IdleTimeout) * time.Second - quicConfig.KeepAlivePeriod = quicConfig.MaxIdleTimeout * 2 / 5 - } - if config.ReceiveWindowConn == 0 { - quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow - quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow - } - if config.ReceiveWindow == 0 { - quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow - quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow - } if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery { logrus.Info("Path MTU Discovery is not yet supported on this platform") } @@ -136,11 +120,7 @@ func client(config *clientConfig) { var err error aclEngine, err = acl.LoadFromFile(config.ACL, transport.DefaultClientTransport.ResolveIPAddr, func() (*geoip2.Reader, error) { - if len(config.MMDB) > 0 { - return loadMMDBReader(config.MMDB) - } else { - return loadMMDBReader(DefaultMMDBFilename) - } + return loadMMDBReader(config.MMDB) }) if err != nil { logrus.WithFields(logrus.Fields{ diff --git a/cmd/config.go b/cmd/config.go index 0b03f7a..78f1461 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -5,7 +5,6 @@ import ( "fmt" "regexp" "strconv" - "time" "github.com/sirupsen/logrus" "github.com/yosuke-furukawa/json5/encoding/json5" @@ -15,17 +14,16 @@ const ( mbpsToBps = 125000 minSpeedBPS = 16384 + DefaultALPN = "hysteria" + DefaultStreamReceiveWindow = 15728640 // 15 MB/s DefaultConnectionReceiveWindow = 67108864 // 64 MB/s DefaultMaxIncomingStreams = 1024 - DefaultALPN = "hysteria" - DefaultMMDBFilename = "GeoLite2-Country.mmdb" - ServerMaxIdleTimeout = 60 * time.Second - DefaultClientMaxIdleTimeout = 20 * time.Second - DefaultClientKeepAlivePeriod = 8 * time.Second + ServerMaxIdleTimeoutSec = 60 + DefaultClientIdleTimeoutSec = 20 ) var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`) @@ -98,10 +96,10 @@ func (c *serverConfig) Speed() (uint64, uint64, error) { func (c *serverConfig) Check() error { if len(c.Listen) == 0 { - return errors.New("no listen address") + return errors.New("missing listen address") } if len(c.ACME.Domains) == 0 && (len(c.CertFile) == 0 || len(c.KeyFile) == 0) { - return errors.New("ACME domain or TLS cert not provided") + return errors.New("need either ACME info or cert/key files") } if up, down, err := c.Speed(); err != nil || (up != 0 && up < minSpeedBPS) || (down != 0 && down < minSpeedBPS) { return errors.New("invalid speed") @@ -116,6 +114,24 @@ func (c *serverConfig) Check() error { return nil } +func (c *serverConfig) Fill() { + if len(c.ALPN) == 0 { + c.ALPN = DefaultALPN + } + if c.ReceiveWindowConn == 0 { + c.ReceiveWindowConn = DefaultStreamReceiveWindow + } + if c.ReceiveWindowClient == 0 { + c.ReceiveWindowClient = DefaultConnectionReceiveWindow + } + if c.MaxConnClient == 0 { + c.MaxConnClient = DefaultMaxIncomingStreams + } + if len(c.MMDB) == 0 { + c.MMDB = DefaultMMDBFilename + } +} + func (c *serverConfig) String() string { return fmt.Sprintf("%+v", *c) } @@ -128,10 +144,10 @@ type Relay struct { func (r *Relay) Check() error { if len(r.Listen) == 0 { - return errors.New("no relay listen address") + return errors.New("missing relay listen address") } if len(r.Remote) == 0 { - return errors.New("no relay remote address") + return errors.New("missing relay remote address") } if r.Timeout != 0 && r.Timeout < 4 { return errors.New("invalid relay timeout") @@ -252,10 +268,10 @@ func (c *clientConfig) Check() error { return errors.New("invalid TUN timeout") } if len(c.TCPRelay.Listen) > 0 && len(c.TCPRelay.Remote) == 0 { - return errors.New("no TCP relay remote address") + return errors.New("missing TCP relay remote address") } if len(c.UDPRelay.Listen) > 0 && len(c.UDPRelay.Remote) == 0 { - return errors.New("no UDP relay remote address") + return errors.New("missing UDP relay remote address") } if c.TCPRelay.Timeout != 0 && c.TCPRelay.Timeout < 4 { return errors.New("invalid TCP relay timeout") @@ -283,7 +299,7 @@ func (c *clientConfig) Check() error { return errors.New("invalid TCP Redirect timeout") } if len(c.Server) == 0 { - return errors.New("no server address") + return errors.New("missing server address") } if up, down, err := c.Speed(); err != nil || up < minSpeedBPS || down < minSpeedBPS { return errors.New("invalid speed") @@ -293,14 +309,32 @@ func (c *clientConfig) Check() error { return errors.New("invalid receive window size") } if len(c.TCPRelay.Listen) > 0 { - logrus.Warn("'relay_tcp' is deprecated, please use 'relay_tcps' instead") + logrus.Warn("'relay_tcp' is deprecated, consider using 'relay_tcps' instead") } if len(c.UDPRelay.Listen) > 0 { - logrus.Warn("config 'relay_udp' is deprecated, please use 'relay_udps' instead") + logrus.Warn("'relay_udp' is deprecated, consider using 'relay_udps' instead") } return nil } +func (c *clientConfig) Fill() { + if len(c.ALPN) == 0 { + c.ALPN = DefaultALPN + } + if c.ReceiveWindowConn == 0 { + c.ReceiveWindowConn = DefaultStreamReceiveWindow + } + if c.ReceiveWindow == 0 { + c.ReceiveWindow = DefaultConnectionReceiveWindow + } + if len(c.MMDB) == 0 { + c.MMDB = DefaultMMDBFilename + } + if c.IdleTimeout == 0 { + c.IdleTimeout = DefaultClientIdleTimeoutSec + } +} + func (c *clientConfig) String() string { return fmt.Sprintf("%+v", *c) } diff --git a/cmd/server.go b/cmd/server.go index defbe50..7df1a20 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -34,6 +34,7 @@ var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFac func server(config *serverConfig) { logrus.WithField("config", config.String()).Info("Server configuration loaded") + config.Fill() // Fill default values // Resolver if len(config.Resolver) > 0 { err := setResolver(config.Resolver) @@ -55,6 +56,7 @@ func server(config *serverConfig) { "error": err, }).Fatal("Failed to get a certificate with ACME") } + tc.NextProtos = []string{config.ALPN} tc.MinVersion = tls.VersionTLS13 tlsConfig = tc } else { @@ -69,14 +71,10 @@ func server(config *serverConfig) { } tlsConfig = &tls.Config{ GetCertificate: kpl.GetCertificateFunc(), + NextProtos: []string{config.ALPN}, MinVersion: tls.VersionTLS13, } } - if config.ALPN != "" { - tlsConfig.NextProtos = []string{config.ALPN} - } else { - tlsConfig.NextProtos = []string{DefaultALPN} - } // QUIC config quicConfig := &quic.Config{ InitialStreamReceiveWindow: config.ReceiveWindowConn, @@ -84,22 +82,11 @@ func server(config *serverConfig) { InitialConnectionReceiveWindow: config.ReceiveWindowClient, MaxConnectionReceiveWindow: config.ReceiveWindowClient, MaxIncomingStreams: int64(config.MaxConnClient), - MaxIdleTimeout: ServerMaxIdleTimeout, + MaxIdleTimeout: ServerMaxIdleTimeoutSec * time.Second, KeepAlivePeriod: 0, // Keep alive should solely be client's responsibility DisablePathMTUDiscovery: config.DisableMTUDiscovery, EnableDatagrams: true, } - if config.ReceiveWindowConn == 0 { - quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow - quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow - } - if config.ReceiveWindowClient == 0 { - quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow - quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow - } - if quicConfig.MaxIncomingStreams == 0 { - quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams - } if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery { logrus.Info("Path MTU Discovery is not yet supported on this platform") } @@ -109,8 +96,8 @@ func server(config *serverConfig) { switch authMode := config.Auth.Mode; authMode { case "", "none": if len(config.Obfs) == 0 { - logrus.Warn("No authentication or obfuscation enabled. " + - "Your server could be accessed by anyone! Are you sure this is what you intended?") + logrus.Warn("Neither authentication nor obfuscation is turned on. " + + "Your server could be used by anyone! Are you sure this is what you want?") } authFunc = func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { return true, "Welcome" @@ -200,11 +187,7 @@ func server(config *serverConfig) { return ipAddr, err }, func() (*geoip2.Reader, error) { - if len(config.MMDB) > 0 { - return loadMMDBReader(config.MMDB) - } else { - return loadMMDBReader(DefaultMMDBFilename) - } + return loadMMDBReader(config.MMDB) }) if err != nil { logrus.WithFields(logrus.Fields{