tcp stream (und damit: tls passthrough) ist implementiert

This commit is contained in:
br committed 2026-10-11 01:04:45 +02:00
1 parent 6a5cb95f92
commit fdcd06c93f
16 files changed
+1008 -75

No files matched your search

+123 -17
View File
@@ -46,6 +46,8 @@ func FromLabels(host string, labels map[string]string) (*Configuration, error) {
routers: map[string]*Router{},
services: map[string]*serviceSpec{},
middlewares: map[string]Middleware{},
tcpRouters: map[string]*TCPRouter{},
tcpServices: map[string]*serviceSpec{},
}
keys := make([]string, 0, len(labels))
@@ -93,6 +95,8 @@ type builder struct {
routers map[string]*Router
services map[string]*serviceSpec
middlewares map[string]Middleware
tcpRouters map[string]*TCPRouter
tcpServices map[string]*serviceSpec
errs []error
}
@@ -101,7 +105,7 @@ func (b *builder) apply(key, value string) error {
switch strings.ToLower(parts[0]) {
case "enable", "docker":
return nil
case "http":
case "http", "tcp":
default:
return ErrUnsupportedLabel
}
@@ -112,6 +116,9 @@ func (b *builder) apply(key, value string) error {
if name == "" {
return fmt.Errorf("%w: leerer Name", ErrInvalidLabel)
}
if strings.EqualFold(parts[0], "tcp") {
return b.applyTCP(kind, name, strings.ToLower(strings.Join(attr, ".")), value)
}
switch kind {
case "routers":
r, ok := b.routers[name]
@@ -173,6 +180,65 @@ func setRouterAttr(r *Router, attr, value string) error {
return nil
}
// applyTCP übersetzt traefik.tcp.*. Unterstützt wird nur, was für
// TLS-Passthrough nötig ist; TCP-Middlewares und TLS-Terminierung nicht.
func (b *builder) applyTCP(kind, name, attr, value string) error {
switch kind {
case "routers":
r, ok := b.tcpRouters[name]
if !ok {
r = &TCPRouter{}
b.tcpRouters[name] = r
}
return setTCPRouterAttr(r, attr, value)
case "services":
if attr != "loadbalancer.server.port" {
return ErrUnsupportedLabel
}
s, ok := b.tcpServices[name]
if !ok {
s = &serviceSpec{}
b.tcpServices[name] = s
}
return setServiceAttr(s, attr, value)
default:
return ErrUnsupportedLabel
}
}
func setTCPRouterAttr(r *TCPRouter, attr, value string) error {
switch attr {
case "rule":
r.Rule = strings.TrimSpace(value)
case "entrypoints":
r.EntryPoints = splitList(value)
case "service":
r.Service = strings.TrimSpace(value)
case "priority":
p, err := strconv.Atoi(strings.TrimSpace(value))
if err != nil {
return fmt.Errorf("%w: %q ist keine Zahl", ErrInvalidLabel, value)
}
r.Priority = p
case "tls", "tls.passthrough":
on, err := strconv.ParseBool(strings.TrimSpace(value))
if err != nil {
return fmt.Errorf("%w: %q ist kein Bool", ErrInvalidLabel, value)
}
if attr == "tls.passthrough" {
if r.TLS == nil {
r.TLS = &TCPRouterTLS{}
}
r.TLS.Passthrough = on
} else if on && r.TLS == nil {
r.TLS = &TCPRouterTLS{}
}
default:
return ErrUnsupportedLabel
}
return nil
}
func ensureTLS(r *Router) *RouterTLS {
if r.TLS == nil {
r.TLS = &RouterTLS{}
@@ -239,15 +305,20 @@ func setMiddlewareAttr(m Middleware, attr []string, value string) error {
}
func (b *builder) build(host string) *Configuration {
cfg := &Configuration{HTTP: b.buildHTTP(host), TCP: b.buildTCP(host)}
if (len(b.services) > 0 || len(b.tcpServices) > 0) && strings.TrimSpace(host) == "" {
b.errs = append(b.errs, fmt.Errorf("%w: keine Backend-Adresse (host) angegeben", ErrIncomplete))
}
return cfg
}
func (b *builder) buildHTTP(host string) *HTTPConfiguration {
if len(b.routers) == 0 && len(b.services) == 0 && len(b.middlewares) == 0 {
return &Configuration{}
return nil
}
http := &HTTPConfiguration{}
if len(b.services) > 0 {
if strings.TrimSpace(host) == "" {
b.errs = append(b.errs, fmt.Errorf("%w: keine Backend-Adresse (host) angegeben", ErrIncomplete))
}
http.Services = map[string]*Service{}
for name, s := range b.services {
if s.port == "" {
@@ -271,17 +342,8 @@ func (b *builder) build(host string) *Configuration {
if r.Rule == "" {
b.errs = append(b.errs, fmt.Errorf("router %q: %w: rule fehlt", name, ErrIncomplete))
}
if r.Service != "" {
continue
}
// Wie Traefiks Docker-Provider: ohne explizite Angabe wird der
// Router an den einzigen Service des Containers gebunden.
if len(b.services) != 1 {
b.errs = append(b.errs, fmt.Errorf("router %q: %w: service fehlt und ist bei %d Services nicht eindeutig", name, ErrIncomplete, len(b.services)))
continue
}
for svc := range b.services {
r.Service = svc
if r.Service == "" {
r.Service = b.defaultService("router", name, b.services)
}
}
}
@@ -289,7 +351,51 @@ func (b *builder) build(host string) *Configuration {
if len(b.middlewares) > 0 {
http.Middlewares = b.middlewares
}
return &Configuration{HTTP: http}
return http
}
func (b *builder) buildTCP(host string) *TCPConfiguration {
if len(b.tcpRouters) == 0 && len(b.tcpServices) == 0 {
return nil
}
tcp := &TCPConfiguration{}
if len(b.tcpServices) > 0 {
tcp.Services = map[string]*TCPService{}
for name, s := range b.tcpServices {
if s.port == "" {
b.errs = append(b.errs, fmt.Errorf("tcp service %q: %w: loadbalancer.server.port fehlt", name, ErrIncomplete))
continue
}
tcp.Services[name] = &TCPService{LoadBalancer: &TCPServersLoadBalancer{
Servers: []TCPServer{{Address: net.JoinHostPort(host, s.port)}},
}}
}
}
if len(b.tcpRouters) > 0 {
tcp.Routers = b.tcpRouters
for name, r := range b.tcpRouters {
if r.Rule == "" {
b.errs = append(b.errs, fmt.Errorf("tcp router %q: %w: rule fehlt", name, ErrIncomplete))
}
if r.Service == "" {
r.Service = b.defaultService("tcp router", name, b.tcpServices)
}
}
}
return tcp
}
// defaultService bindet wie Traefiks Docker-Provider einen Router ohne
// explizite Angabe an den einzigen Service des Containers.
func (b *builder) defaultService(kind, router string, services map[string]*serviceSpec) string {
if len(services) != 1 {
b.errs = append(b.errs, fmt.Errorf("%s %q: %w: service fehlt und ist bei %d Services nicht eindeutig", kind, router, ErrIncomplete, len(services)))
return ""
}
for svc := range services {
return svc
}
return ""
}
func splitList(value string) []string {