diff --git a/.github/workflows/buildx.yaml b/.github/workflows/buildx.yaml index d4e9276..bc0e073 100644 --- a/.github/workflows/buildx.yaml +++ b/.github/workflows/buildx.yaml @@ -36,39 +36,54 @@ jobs: # 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 - # https://github.com/actions/checkout - - name: Checkout - uses: actions/checkout@v2 - - - name: Docker Login - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - + # https://github.com/crazy-max/ghaction-docker-buildx - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 - + 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 tags=${{ steps.prepare.outputs.tags }} + echo platforms=${{ steps.prepare.outputs.docker_platforms }} echo avail_platforms=${{ steps.buildx.outputs.platforms }} - - name: Build and push - uses: docker/build-push-action@v2 - with: - builder: ${{ steps.buildx.outputs.name }} - context: . - file: ./Dockerfile - platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 - push: true - tags: ${{ steps.prepare.outputs.tags }} - cache-from: type=registry,ref=${{ steps.prepare.outputs.docker_image }}:buildcache - cache-to: type=registry,ref=${{ steps.prepare.outputs.docker_image }}:buildcache,mode=max + # 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 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d846b0a..a943757 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,20 @@ -FROM golang:1-alpine as builder +FROM --platform=$BUILDPLATFORM golang:1-alpine as builder -RUN apk add --no-cache musl-dev gcc +# Convert TARGETPLATFORM to GOARCH format +# https://github.com/tonistiigi/xx +COPY --from=tonistiigi/xx:golang / / -WORKDIR /mod +ARG TARGETPLATFORM -ADD go.mod go.sum ./ - -RUN go mod download +RUN apk add --no-cache musl-dev git gcc ADD . /src WORKDIR /src -RUN cd cmd/gost && go env && go build +ENV GO111MODULE=on + +RUN cd cmd/gost && go env && go build FROM alpine:latest @@ -20,4 +22,4 @@ WORKDIR /bin/ COPY --from=builder /src/cmd/gost/gost . -ENTRYPOINT ["/bin/gost"] +ENTRYPOINT ["/bin/gost"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..51d8189 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,4 @@ +version: "3.4" +services: + gost: + build: . \ No newline at end of file diff --git a/gost.yml b/gost.yml index 26678fc..106d83a 100644 --- a/gost.yml +++ b/gost.yml @@ -5,7 +5,6 @@ log: services: - name: http+tcp - url: "http://gost:gost@:8000" addr: ":28000" handler: type: http @@ -23,7 +22,6 @@ services: metadata: keepAlive: 15s - name: ss - url: "ss://chacha20:gost@:8000" addr: ":28338" handler: type: ss @@ -40,7 +38,6 @@ services: metadata: keepAlive: 15s - name: socks5 - url: "socks5://gost:gost@:1080" addr: ":21080" handler: type: socks5 @@ -59,7 +56,6 @@ services: metadata: keepAlive: 15s - name: socks5+tcp - url: "socks5://gost:gost@:1080" addr: ":21081" handler: type: socks5 @@ -74,7 +70,6 @@ services: metadata: keepAlive: 15s - name: forward - url: "socks5://gost:gost@:1080" addr: ":10053" forwarder: targets: @@ -161,7 +156,6 @@ chains: nodes: - name: node01 addr: ":8081" - url: "http://gost:gost@:8081" # bypass: bypass01 connector: type: http @@ -173,7 +167,6 @@ chains: metadata: {} - name: node02 addr: ":8082" - url: "http://gost:gost@:8082" # bypass: bypass01 connector: type: http @@ -192,7 +185,6 @@ chains: nodes: - name: node03 addr: ":8083" - url: "http://gost:gost@:8083" # bypass: bypass01 connector: type: http @@ -222,7 +214,6 @@ chains: nodes: - name: node01 addr: ":21080" - url: "http://gost:gost@:8081" # bypass: bypass01 connector: type: socks5 diff --git a/pkg/handler/http/handler.go b/pkg/handler/http/handler.go index 2a6233f..6d216a9 100644 --- a/pkg/handler/http/handler.go +++ b/pkg/handler/http/handler.go @@ -287,22 +287,29 @@ func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http. if !strings.HasPrefix(url, "http") { url = "http://" + url } - if r, err := http.Get(url); err == nil { - resp = r - defer r.Body.Close() + r, err := http.Get(url) + if err != nil { + h.logger.Error(err) + break } + resp = r + defer resp.Body.Close() case "host": cc, err := net.Dial("tcp", pr.Value) - if err == nil { - defer cc.Close() - - req.Write(cc) - handler.Transport(conn, cc) - return + if err != nil { + h.logger.Error(err) + break } + defer cc.Close() + + req.Write(cc) + handler.Transport(conn, cc) + return case "file": f, _ := os.Open(pr.Value) if f != nil { + defer f.Close() + resp.StatusCode = http.StatusOK if finfo, _ := f.Stat(); finfo != nil { resp.ContentLength = finfo.Size() @@ -313,6 +320,9 @@ func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http. } } + if resp.Header == nil { + resp.Header = http.Header{} + } if resp.StatusCode == 0 { resp.StatusCode = http.StatusProxyAuthRequired resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"") @@ -325,7 +335,6 @@ func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http. h.logger.Info("proxy authentication required") } else { - resp.Header = http.Header{} resp.Header.Set("Server", "nginx/1.20.1") resp.Header.Set("Date", time.Now().Format(http.TimeFormat)) if resp.StatusCode == http.StatusOK { diff --git a/pkg/handler/http2/handler.go b/pkg/handler/http2/handler.go index d76d97b..0e78bcb 100644 --- a/pkg/handler/http2/handler.go +++ b/pkg/handler/http2/handler.go @@ -1,11 +1,15 @@ package http2 import ( + "bufio" + "bytes" "context" "encoding/base64" "encoding/binary" "errors" "hash/crc32" + "io" + "io/ioutil" "net" "net/http" "net/http/httputil" @@ -130,33 +134,22 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req w.Header().Set("Proxy-Agent", h.md.proxyAgent) } - /* - if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { - log.Logf("[http2] %s - %s : Unauthorized to tcp connect to %s", - r.RemoteAddr, laddr, host) - w.WriteHeader(http.StatusForbidden) - return - } - */ - if h.options.Bypass != nil && h.options.Bypass.Contains(addr) { w.WriteHeader(http.StatusForbidden) h.logger.Info("bypass: ", addr) return } - /* - resp := &http.Response{ - ProtoMajor: 2, - ProtoMinor: 0, - Header: http.Header{}, - Body: ioutil.NopCloser(bytes.NewReader([]byte{})), - } + resp := &http.Response{ + ProtoMajor: 2, + ProtoMinor: 0, + Header: http.Header{}, + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + } - if !h.authenticate(w, r, resp) { - return - } - */ + if !h.authenticate(w, req, resp) { + return + } // delete the proxy related headers. req.Header.Del("Proxy-Authorization") @@ -248,17 +241,16 @@ func (h *http2Handler) basicProxyAuth(proxyAuth string) (username, password stri return cs[:s], cs[s+1:], true } -func (h *http2Handler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) { - u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")) +func (h *http2Handler) authenticate(w http.ResponseWriter, r *http.Request, resp *http.Response) (ok bool) { + u, p, _ := h.basicProxyAuth(r.Header.Get("Proxy-Authorization")) if h.authenticator == nil || h.authenticator.Authenticate(u, p) { return true } - pr := h.md.probeResist + pr := h.md.probeResistance // probing resistance is enabled, and knocking host is mismatch. - if pr != nil && (pr.Knock == "" || !strings.EqualFold(req.URL.Hostname(), pr.Knock)) { + if pr != nil && (pr.Knock == "" || !strings.EqualFold(r.URL.Hostname(), pr.Knock)) { resp.StatusCode = http.StatusServiceUnavailable // default status code - switch pr.Type { case "code": resp.StatusCode, _ = strconv.Atoi(pr.Value) @@ -267,22 +259,30 @@ func (h *http2Handler) authenticate(conn net.Conn, req *http.Request, resp *http if !strings.HasPrefix(url, "http") { url = "http://" + url } - if r, err := http.Get(url); err == nil { - resp = r - defer r.Body.Close() + r, err := http.Get(url) + if err != nil { + h.logger.Error(err) + break } + resp = r + defer resp.Body.Close() case "host": cc, err := net.Dial("tcp", pr.Value) - if err == nil { - defer cc.Close() - - req.Write(cc) - handler.Transport(conn, cc) - return + if err != nil { + h.logger.Error(err) + break } + defer cc.Close() + + if err := h.forwardRequest(w, r, cc); err != nil { + h.logger.Error(err) + } + return case "file": f, _ := os.Open(pr.Value) if f != nil { + defer f.Close() + resp.StatusCode = http.StatusOK if finfo, _ := f.Stat(); finfo != nil { resp.ContentLength = finfo.Size() @@ -296,7 +296,7 @@ func (h *http2Handler) authenticate(conn net.Conn, req *http.Request, resp *http if resp.StatusCode == 0 { resp.StatusCode = http.StatusProxyAuthRequired resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"") - if strings.ToLower(req.Header.Get("Proxy-Connection")) == "keep-alive" { + if strings.ToLower(r.Header.Get("Proxy-Connection")) == "keep-alive" { // XXX libcurl will keep sending auth request in same conn // which we don't supported yet. resp.Header.Add("Connection", "close") @@ -318,6 +318,31 @@ func (h *http2Handler) authenticate(conn net.Conn, req *http.Request, resp *http h.logger.Debug(string(dump)) } - resp.Write(conn) + h.writeResponse(w, resp) + return } +func (h *http2Handler) forwardRequest(w http.ResponseWriter, r *http.Request, rw io.ReadWriter) (err error) { + if err = r.Write(rw); err != nil { + return + } + + resp, err := http.ReadResponse(bufio.NewReader(rw), r) + if err != nil { + return + } + defer resp.Body.Close() + + return h.writeResponse(w, resp) +} + +func (h *http2Handler) writeResponse(w http.ResponseWriter, resp *http.Response) error { + for k, v := range resp.Header { + for _, vv := range v { + w.Header().Add(k, vv) + } + } + w.WriteHeader(resp.StatusCode) + _, err := io.Copy(flushWriter{w}, resp.Body) + return err +} diff --git a/pkg/handler/http2/metadata.go b/pkg/handler/http2/metadata.go index 63cccd7..7610b79 100644 --- a/pkg/handler/http2/metadata.go +++ b/pkg/handler/http2/metadata.go @@ -7,16 +7,16 @@ import ( ) type metadata struct { - proxyAgent string - probeResist *probeResist - sni bool - enableUDP bool + proxyAgent string + probeResistance *probeResistance + sni bool + enableUDP bool } func (h *http2Handler) parseMetadata(md mdata.Metadata) error { const ( proxyAgent = "proxyAgent" - probeResistKey = "probeResist" + probeResistKey = "probeResistance" knock = "knock" sni = "sni" enableUDP = "udp" @@ -26,7 +26,7 @@ func (h *http2Handler) parseMetadata(md mdata.Metadata) error { if v := mdata.GetString(md, probeResistKey); v != "" { if ss := strings.SplitN(v, ":", 2); len(ss) == 2 { - h.md.probeResist = &probeResist{ + h.md.probeResistance = &probeResistance{ Type: ss[0], Value: ss[1], Knock: mdata.GetString(md, knock), @@ -39,7 +39,7 @@ func (h *http2Handler) parseMetadata(md mdata.Metadata) error { return nil } -type probeResist struct { +type probeResistance struct { Type string Value string Knock string diff --git a/pkg/internal/util/http2/conn.go b/pkg/internal/util/http2/conn.go index 8751aa4..1e0876b 100644 --- a/pkg/internal/util/http2/conn.go +++ b/pkg/internal/util/http2/conn.go @@ -66,18 +66,22 @@ func (c *ClientConn) SetWriteDeadline(t time.Time) error { // a dummy HTTP2 server conn used by HTTP2 handler type ServerConn struct { - r *http.Request - w http.ResponseWriter - cancel context.CancelFunc + r *http.Request + w http.ResponseWriter + localAddr net.Addr + remoteAddr net.Addr + cancel context.CancelFunc } -func NewServerConn(w http.ResponseWriter, r *http.Request) *ServerConn { +func NewServerConn(w http.ResponseWriter, r *http.Request, localAddr, remoteAddr net.Addr) *ServerConn { ctx, cancel := context.WithCancel(r.Context()) return &ServerConn{ - r: r.Clone(ctx), - w: w, - cancel: cancel, + r: r.Clone(ctx), + w: w, + localAddr: localAddr, + remoteAddr: remoteAddr, + cancel: cancel, } } @@ -112,13 +116,11 @@ func (c *ServerConn) Close() error { } func (c *ServerConn) LocalAddr() net.Addr { - addr, _ := net.ResolveTCPAddr("tcp", c.r.Host) - return addr + return c.localAddr } func (c *ServerConn) RemoteAddr() net.Addr { - addr, _ := net.ResolveTCPAddr("tcp", c.r.RemoteAddr) - return addr + return c.remoteAddr } func (c *ServerConn) SetDeadline(t time.Time) error { diff --git a/pkg/listener/http2/listener.go b/pkg/listener/http2/listener.go index 9a6caec..3efae13 100644 --- a/pkg/listener/http2/listener.go +++ b/pkg/listener/http2/listener.go @@ -106,7 +106,8 @@ func (l *http2Listener) Close() (err error) { } func (l *http2Listener) handleFunc(w http.ResponseWriter, r *http.Request) { - conn := http2_util.NewServerConn(w, r) + raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr) + conn := http2_util.NewServerConn(w, r, l.addr, raddr) select { case l.cqueue <- conn: default: