Skip to content

Commit

Permalink
refactor: use the SDK's WebSocket Upgrade() function (#234)
Browse files Browse the repository at this point in the history
* Use the new WS `Upgrade()` function from the SDK.

* Use `r.Context()`.

* Make sure the clientIP is not nil when we replace it.

* Bump the `outline-sdk` dependency.

* Explicitly allow `BSD-2-Clause` license.

* Bump the `outline-sdk` dependency.
  • Loading branch information
sbruens authored Jan 31, 2025
1 parent b7d1c7a commit 0387dfb
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 282 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/license.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ jobs:

- name: Check licenses
# We allow only "notice" type of licenses.
run: go run github.com/google/go-licenses check --ignore=golang.org/x --allowed_licenses=Apache-2.0,Apache-3,BSD-3-Clause,BSD-4-Clause,CC0-1.0,ISC,MIT ./...
run: go run github.com/google/go-licenses check --ignore=golang.org/x --allowed_licenses=Apache-2.0,Apache-3,BSD-2-Clause,BSD-3-Clause,BSD-4-Clause,CC0-1.0,ISC,MIT ./...
96 changes: 35 additions & 61 deletions cmd/outline-ss-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ import (

"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks"
"github.com/Jigsaw-Code/outline-sdk/x/websocket"
"github.com/gorilla/handlers"
"github.com/lmittmann/tint"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/net/websocket"
"golang.org/x/term"

"github.com/Jigsaw-Code/outline-ss-server/ipinfo"
Expand Down Expand Up @@ -306,18 +307,22 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
if err != nil {
return err
}
logger := slog.Default().With(
slog.Int("access_keys", len(serviceConfig.Keys)),
slog.Any("fwmark", func() any {
if serviceConfig.Dialer.Fwmark == 0 {
return "disabled"
}
return serviceConfig.Dialer.Fwmark
}()),
)
for _, cfg := range serviceConfig.Listeners {
if cfg.TCP != nil {
ln, err := lnSet.ListenStream(cfg.TCP.Address)
if err != nil {
return err
}
slog.Info("TCP service started.", "address", ln.Addr().String(), "fwmark", func() any {
if serviceConfig.Dialer.Fwmark == 0 {
return "disabled"
}
return serviceConfig.Dialer.Fwmark
}())
logger.Info("TCP service started.", "address", ln.Addr().String())
go service.StreamServe(ln.AcceptStream, func(ctx context.Context, conn transport.StreamConn) {
streamHandler.HandleStream(ctx, conn, s.serviceMetrics.AddOpenTCPConnection(conn))
})
Expand All @@ -326,12 +331,7 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
if err != nil {
return err
}
slog.Info("UDP service started.", "address", pc.LocalAddr().String(), "fwmark", func() any {
if serviceConfig.Dialer.Fwmark == 0 {
return "disabled"
}
return serviceConfig.Dialer.Fwmark
}())
logger.Info("UDP service started.", "address", pc.LocalAddr().String())
go service.PacketServe(pc, func(ctx context.Context, conn net.Conn) {
associationHandler.HandleAssociation(ctx, conn, s.serviceMetrics.AddOpenUDPAssociation(conn))
}, s.serverMetrics)
Expand All @@ -340,48 +340,37 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
return fmt.Errorf("websocket-stream listener references unknown web server `%s`", cfg.WebsocketStream.WebServer)
}
mux := webServers[cfg.WebsocketStream.WebServer]
// TODO: Support a "half-closed" state for WebSockets.
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := func(wsConn *websocket.Conn) {
defer wsConn.Close()
ctx, contextCancel := context.WithCancel(context.Background())
defer contextCancel()
clientIP, err := onet.GetClientIPFromRequest(r)
if err != nil {
slog.Error("failed to determine client address", "err", err)
w.WriteHeader(http.StatusBadGateway)
return
}
conn := &streamConn{&replaceAddrConn{Conn: wsConn, raddr: &net.TCPAddr{IP: clientIP}}}
streamHandler.HandleStream(ctx, conn, s.serviceMetrics.AddOpenTCPConnection(conn))
conn, err := websocket.Upgrade(w, r, nil)
if err != nil {
slog.Error("failed to upgrade", "err", err)
}
websocket.Handler(handler).ServeHTTP(w, r)
defer conn.Close()
if clientIP := net.ParseIP(r.RemoteAddr); clientIP != nil {
conn = &replaceAddrConn{StreamConn: conn, raddr: &net.TCPAddr{IP: clientIP}}
}
streamHandler.HandleStream(r.Context(), conn, s.serviceMetrics.AddOpenTCPConnection(conn))
})
mux.Handle(cfg.WebsocketStream.Path, http.StripPrefix(cfg.WebsocketStream.Path, handler))
slog.Info("WebSocket stream service started.", "ID", cfg.WebsocketStream.WebServer, "path", cfg.WebsocketStream.Path)
mux.Handle(cfg.WebsocketStream.Path, http.StripPrefix(cfg.WebsocketStream.Path, handlers.ProxyHeaders(handler)))
logger.Info("WebSocket stream service started.", "ID", cfg.WebsocketStream.WebServer, "path", cfg.WebsocketStream.Path)
} else if cfg.WebsocketPacket != nil {
if _, exists := webServers[cfg.WebsocketPacket.WebServer]; !exists {
return fmt.Errorf("websocket-packet listener references unknown web server `%s`", cfg.WebsocketPacket.WebServer)
}
mux := webServers[cfg.WebsocketPacket.WebServer]
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler := func(wsConn *websocket.Conn) {
defer wsConn.Close()
ctx, contextCancel := context.WithCancel(context.Background())
defer contextCancel()
clientIP, err := onet.GetClientIPFromRequest(r)
if err != nil {
slog.Error("failed to determine client address", "err", err)
w.WriteHeader(http.StatusBadGateway)
return
}
conn := &replaceAddrConn{Conn: wsConn, raddr: &net.UDPAddr{IP: clientIP}}
associationHandler.HandleAssociation(ctx, conn, s.serviceMetrics.AddOpenUDPAssociation(conn))
conn, err := websocket.Upgrade(w, r, nil)
if err != nil {
slog.Error("failed to upgrade", "err", err)
}
defer conn.Close()
if clientIP := net.ParseIP(r.RemoteAddr); clientIP != nil {
conn = &replaceAddrConn{StreamConn: conn, raddr: &net.UDPAddr{IP: clientIP}}
}
websocket.Handler(handler).ServeHTTP(w, r)
associationHandler.HandleAssociation(r.Context(), conn, s.serviceMetrics.AddOpenUDPAssociation(conn))
})
mux.Handle(cfg.WebsocketPacket.Path, http.StripPrefix(cfg.WebsocketPacket.Path, handler))
slog.Info("WebSocket packet service started.", "ID", cfg.WebsocketPacket.WebServer, "path", cfg.WebsocketPacket.Path)
mux.Handle(cfg.WebsocketPacket.Path, http.StripPrefix(cfg.WebsocketPacket.Path, handlers.ProxyHeaders(handler)))
logger.Info("WebSocket packet service started.", "ID", cfg.WebsocketPacket.WebServer, "path", cfg.WebsocketPacket.Path)
} else {
return fmt.Errorf("unknown listener configuration: %v", cfg)
}
Expand Down Expand Up @@ -452,31 +441,16 @@ func RunOutlineServer(filename string, natTimeout time.Duration, serverMetrics *
}

// TODO: Create a dedicated `ClientConn` struct with `ClientAddr` and `Conn`.
// replaceAddrConn overrides [websocket.Conn]'s remote address handling.
// replaceAddrConn overrides a [transport.StreamConn]'s remote address handling.
type replaceAddrConn struct {
*websocket.Conn
transport.StreamConn
raddr net.Addr
}

func (c replaceAddrConn) RemoteAddr() net.Addr {
return c.raddr
}

type streamConn struct {
net.Conn
}

var _ transport.StreamConn = (*streamConn)(nil)

// TODO: Support a "half-closed" state.
func (c *streamConn) CloseRead() error {
return c.Close()
}

func (c *streamConn) CloseWrite() error {
return c.Close()
}

func main() {
slog.SetDefault(slog.New(logHandler))

Expand Down
36 changes: 19 additions & 17 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
module github.com/Jigsaw-Code/outline-ss-server

require (
github.com/Jigsaw-Code/outline-sdk v0.0.14
github.com/Jigsaw-Code/outline-sdk v0.0.18-0.20241106233708-faffebb12629
github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20250130222646-80b6430a1fc8
github.com/go-task/task/v3 v3.34.1
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/google/addlicense v1.1.1
github.com/google/go-licenses v1.6.0
github.com/goreleaser/goreleaser v1.18.2
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33
github.com/lmittmann/tint v1.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/oschwald/geoip2-golang v1.8.0
github.com/prometheus/client_golang v1.15.0
github.com/shadowsocks/go-shadowsocks2 v0.1.5
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.17.0
golang.org/x/term v0.16.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.26.0
golang.org/x/term v0.23.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down Expand Up @@ -133,7 +136,6 @@ require (
github.com/go-openapi/validate v0.22.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
Expand All @@ -154,7 +156,7 @@ require (
github.com/goreleaser/chglog v0.4.2 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/goreleaser/nfpm/v2 v2.28.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
Expand All @@ -171,7 +173,7 @@ require (
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
Expand Down Expand Up @@ -219,7 +221,7 @@ require (
github.com/sigstore/cosign/v2 v2.0.0 // indirect
github.com/sigstore/rekor v1.1.1 // indirect
github.com/sigstore/sigstore v1.6.3 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/slack-go/slack v0.12.2 // indirect
github.com/spf13/afero v1.9.3 // indirect
Expand All @@ -245,21 +247,21 @@ require (
go.opencensus.io v0.24.0 // indirect
go.uber.org/automaxprocs v1.5.2 // indirect
gocloud.dev v0.29.0 // indirect
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.119.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
Expand All @@ -274,4 +276,4 @@ require (
sigs.k8s.io/yaml v1.3.0 // indirect
)

go 1.21
go 1.22
Loading

0 comments on commit 0387dfb

Please sign in to comment.