Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(caddy): add a WebSockets handler that connects to Shadowsocks handler #216

Open
wants to merge 142 commits into
base: master
Choose a base branch
from

Conversation

sbruens
Copy link

@sbruens sbruens commented Sep 23, 2024

This PR enables Shadowsocks-over-WebSockets functionality in the Caddy server, allowing clients to connect to Shadowsocks servers via WebSockets. This is achieved by introducing three new modules:

  1. Named Connection Handlers: These reusable handlers can be shared by different applications and are configured within the Outline app.

  2. WebSocket-to-Outline Handler: An HTTP app handler which bridges WebSocket requests to a designated named connection handler, enabling Shadowsocks connections over WebSockets.

  3. Layer4 Outline Handler: A layer 4 handler which bridges TCP/UDP connections to a designated named connection handler.

Example Configuration:

This example demonstrates how to configure a Shadowsocks handler with access keys and utilize it for both WebSocket and TCP connections:

  outline:
    ...
    connection_handlers:
    - name: ss1
      handle:
        handler: shadowsocks
        keys:
        - id: user-0
          cipher: chacha20-ietf-poly1305
          secret: Secret0

apps:
  http:
    servers:
      '1':
        listen:
        - ":8000"
        routes:
        - match:
          - path:
            - "/tcp"
          handle:
          - handler: websocket2layer4
            type: stream
            connection_handler: ss1
  layer4:
    servers:
      '1':
        listen:
        - tcp/[::]:9000
        routes:
        - handle:
          - handler: outline
            connection_handler: ss1

caddy/config_example.json Outdated Show resolved Hide resolved
caddy/config_example.json Outdated Show resolved Hide resolved
caddy/websocket_handler.go Outdated Show resolved Hide resolved
@sbruens sbruens requested a review from fortuna October 7, 2024 21:47
@sbruens sbruens changed the title feat(caddy): add a WebSockets handler feat(caddy): add a Layer4 WebSockets handler Oct 7, 2024
.github/workflows/license.yml Outdated Show resolved Hide resolved
.github/workflows/license.yml Outdated Show resolved Hide resolved
.github/workflows/license.yml Outdated Show resolved Hide resolved
caddy/README.md Outdated Show resolved Hide resolved
caddy/examples/config_example.json Outdated Show resolved Hide resolved
caddy/examples/config_example.json Outdated Show resolved Hide resolved
caddy/shadowsocks_handler.go Outdated Show resolved Hide resolved
caddy/websocket_handler.go Outdated Show resolved Hide resolved
Base automatically changed from sbruens/udp-split-serving to master January 24, 2025 22:31
.github/workflows/license.yml Show resolved Hide resolved
caddy/connection_handler.go Outdated Show resolved Hide resolved
caddy/examples/simple.yml Outdated Show resolved Hide resolved
go.mod Outdated Show resolved Hide resolved
cmd/caddy/main.go Outdated Show resolved Hide resolved
caddy/module.go Show resolved Hide resolved
compiledHandler layer4.NextHandler

logger *slog.Logger
zlogger *zap.Logger
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need both loggers?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because caddy's WrapConnection() expects a zap logger, not an slog logger: https://github.com/mholt/caddy-l4/blob/87e3e5e2c7f986b34c0df373a5799670d7b8ca03/layer4/connection.go#L33. We use it below in the ServeHTTP() function, so we have to reference both.

caddy/ws2outline_handler.go Outdated Show resolved Hide resolved
@sbruens sbruens requested a review from fortuna February 8, 2025 05:46
compiledHandler layer4.NextHandler

logger *slog.Logger
zlogger *zap.Logger
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because caddy's WrapConnection() expects a zap logger, not an slog logger: https://github.com/mholt/caddy-l4/blob/87e3e5e2c7f986b34c0df373a5799670d7b8ca03/layer4/connection.go#L33. We use it below in the ServeHTTP() function, so we have to reference both.

outlinecaddy/examples/websocket.yml Show resolved Hide resolved
@@ -346,8 +346,9 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
slog.Error("failed to upgrade", "err", err)
}
defer conn.Close()
if clientIP := net.ParseIP(r.RemoteAddr); clientIP != nil {
conn = &replaceAddrConn{StreamConn: conn, raddr: &net.TCPAddr{IP: clientIP}}
clientAddrPort, err := onet.ParseAddrPortOrIP(r.RemoteAddr)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of not knowing our types. This makes our code fragile.
At a minimum, we need to document when to expect an IP and when to expect an addr:port.
But the http documentation says this is a host:port. Why do you need to parse as IP?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation also states the RemoteAddr has no defined format. The http server sets it to host:port, but the forwarded proxy headers are usually just an IP. The gorilla library sets the header IP value in RemoteAddr: https://github.com/gorilla/handlers/blob/9c61bd81e701cf500437e1b516b675cdd3b73ca7/proxy_headers.go#L46C13-L46C18. If we want to use that we should support both. I can add a comment though.

cmd/outline-ss-server/main.go Show resolved Hide resolved
outlinecaddy/examples/websocket.yml Show resolved Hide resolved
outlinecaddy/examples/websocket.yml Show resolved Hide resolved
outlinecaddy/examples/websocket.yml Show resolved Hide resolved
outlinecaddy/ws2outline_handler.go Show resolved Hide resolved
outlinecaddy/ws2outline_handler.go Show resolved Hide resolved
outlinecaddy/ws2outline_handler.go Show resolved Hide resolved
}
}
}
cx := layer4.WrapConnection(conn, []byte{}, h.zlogger)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outlinecaddy/ws2outline_handler.go Show resolved Hide resolved
@sbruens sbruens requested a review from fortuna February 12, 2025 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants