mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-25 02:21:05 +01:00
feat(agent): agent stream tunneling with TLS and dTLS (UDP) (#188)
* **New Features** * Multiplexed TLS port: HTTP API and a custom stream protocol can share one port via ALPN. * Agent-side TCP and DTLS/UDP stream tunneling with health-check support and runtime capability detection. * Agents now advertise per-agent stream support (TCP/UDP). * **Documentation** * Added comprehensive stream protocol documentation. * **Tests** * Extended integration and concurrency tests covering multiplexing, TCP/UDP streams, and health checks. * **Chores** * Compose/template updated to expose both TCP and UDP ports.
This commit is contained in:
@@ -1,21 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent/stream"
|
||||
"github.com/yusing/godoxy/agent/pkg/env"
|
||||
"github.com/yusing/godoxy/agent/pkg/server"
|
||||
"github.com/yusing/godoxy/agent/pkg/handler"
|
||||
"github.com/yusing/godoxy/internal/metrics/systeminfo"
|
||||
socketproxy "github.com/yusing/godoxy/socketproxy/pkg"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httpServer "github.com/yusing/goutils/server"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
"github.com/yusing/goutils/task"
|
||||
"github.com/yusing/goutils/version"
|
||||
)
|
||||
|
||||
// TODO: support IPv6
|
||||
|
||||
func main() {
|
||||
writer := zerolog.ConsoleWriter{
|
||||
Out: os.Stderr,
|
||||
@@ -52,16 +62,84 @@ func main() {
|
||||
Tips:
|
||||
1. To change the agent name, you can set the AGENT_NAME environment variable.
|
||||
2. To change the agent port, you can set the AGENT_PORT environment variable.
|
||||
`)
|
||||
`)
|
||||
|
||||
t := task.RootTask("agent", false)
|
||||
opts := server.Options{
|
||||
CACert: caCert,
|
||||
ServerCert: srvCert,
|
||||
Port: env.AgentPort,
|
||||
|
||||
// One TCP listener on AGENT_PORT, then multiplex by TLS ALPN:
|
||||
// - Stream ALPN: route to TCP stream tunnel handler (via http.Server.TLSNextProto)
|
||||
// - Otherwise: route to HTTPS API handler
|
||||
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: env.AgentPort})
|
||||
if err != nil {
|
||||
gperr.LogFatal("failed to listen on port", err)
|
||||
}
|
||||
|
||||
server.StartAgentServer(t, opts)
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AddCert(caCert.Leaf)
|
||||
|
||||
muxTLSConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{*srvCert},
|
||||
ClientCAs: caCertPool,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
// Keep HTTP limited to HTTP/1.1 (matching current agent server behavior)
|
||||
// and add the stream tunnel ALPN for multiplexing.
|
||||
NextProtos: []string{"http/1.1", stream.StreamALPN},
|
||||
}
|
||||
if env.AgentSkipClientCertCheck {
|
||||
muxTLSConfig.ClientAuth = tls.NoClientCert
|
||||
}
|
||||
|
||||
// TLS listener feeds the HTTP server. ALPN stream connections are intercepted
|
||||
// using http.Server.TLSNextProto.
|
||||
tlsLn := tls.NewListener(tcpListener, muxTLSConfig)
|
||||
|
||||
streamSrv := stream.NewTCPServerHandler(t.Context())
|
||||
|
||||
httpSrv := &http.Server{
|
||||
Handler: handler.NewAgentHandler(),
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return t.Context()
|
||||
},
|
||||
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){
|
||||
// When a client negotiates StreamALPN, net/http will call this hook instead
|
||||
// of treating the connection as HTTP.
|
||||
stream.StreamALPN: func(_ *http.Server, conn *tls.Conn, _ http.Handler) {
|
||||
// ServeConn blocks until the tunnel finishes.
|
||||
streamSrv.ServeConn(conn)
|
||||
},
|
||||
},
|
||||
}
|
||||
{
|
||||
subtask := t.Subtask("agent-http", true)
|
||||
t.OnCancel("stop_http", func() {
|
||||
_ = streamSrv.Close()
|
||||
_ = httpSrv.Close()
|
||||
_ = tlsLn.Close()
|
||||
})
|
||||
go func() {
|
||||
err := httpSrv.Serve(tlsLn)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Error().Err(err).Msg("agent HTTP server stopped with error")
|
||||
}
|
||||
subtask.Finish(err)
|
||||
}()
|
||||
log.Info().Int("port", env.AgentPort).Msg("HTTPS API server started (ALPN mux enabled)")
|
||||
}
|
||||
log.Info().Int("port", env.AgentPort).Msg("TCP stream handler started (via TLSNextProto)")
|
||||
|
||||
{
|
||||
udpServer := stream.NewUDPServer(t.Context(), "udp", &net.UDPAddr{Port: env.AgentPort}, caCert.Leaf, srvCert)
|
||||
subtask := t.Subtask("agent-stream-udp", true)
|
||||
t.OnCancel("stop_stream_udp", func() {
|
||||
_ = udpServer.Close()
|
||||
})
|
||||
go func() {
|
||||
err := udpServer.Start()
|
||||
subtask.Finish(err)
|
||||
}()
|
||||
log.Info().Int("port", env.AgentPort).Msg("UDP stream server started")
|
||||
}
|
||||
|
||||
if socketproxy.ListenAddr != "" {
|
||||
runtime := strutils.Title(string(env.Runtime))
|
||||
|
||||
Reference in New Issue
Block a user