v0.21.0 scram password auth, cred and token management, browser tls-terminating proxy, viewer client
Test / test (push) Canceled after 0s
Test / test (push) Canceled after 0s
This commit is contained in:
1 parent
f192c26fa2
commit
9ab8c356d2
60 files changed
+6791
-747
No files matched your search
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
@@ -50,7 +51,6 @@ type TCPChainSink struct {
|
||||
|
||||
node string
|
||||
addr string
|
||||
helloLine []byte
|
||||
tlsConfig *tls.Config
|
||||
|
||||
// Authorization: pins the downstream server's identity
|
||||
@@ -59,8 +59,12 @@ type TCPChainSink struct {
|
||||
input chan core.TransportEvent
|
||||
logger *log.Logger
|
||||
|
||||
// conn owned exclusively by run loop goroutine
|
||||
// conn and the backoff state are owned exclusively by the run loop goroutine.
|
||||
// failures survives a link that dies right after connecting, so a refusing
|
||||
// source is retried with growing delays rather than in a tight loop.
|
||||
conn net.Conn
|
||||
connectedAt time.Time
|
||||
failures int
|
||||
everConnected bool
|
||||
dialTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
@@ -125,15 +129,11 @@ func NewTCPChainSinkPlugin(
|
||||
}
|
||||
}
|
||||
|
||||
helloLine, err := chain.EncodeHello(node)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hello: %w", err)
|
||||
}
|
||||
tlsCfg, err := tlsx.Client(opts.TLS, opts.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authPolicy, err := authz.New(opts.Auth, opts.TLS, authz.RoleDialer)
|
||||
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleDialer, authz.TCP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,7 +149,6 @@ func NewTCPChainSinkPlugin(
|
||||
config: opts,
|
||||
node: node,
|
||||
addr: net.JoinHostPort(opts.Host, strconv.FormatInt(opts.Port, 10)),
|
||||
helloLine: helloLine,
|
||||
tlsConfig: tlsCfg,
|
||||
auth: authPolicy,
|
||||
input: make(chan core.TransportEvent, opts.BufferSize),
|
||||
@@ -179,6 +178,7 @@ func NewTCPChainSinkPlugin(
|
||||
"mtls", tlsCfg != nil && len(tlsCfg.Certificates) > 0,
|
||||
"auth", authPolicy.Describe())
|
||||
tlsx.LogWarnings(logger, "tcp_chain_sink", id, opts.TLS, false)
|
||||
authPolicy.LogStartup(logger, "tcp_chain_sink", id, false)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@@ -267,6 +267,17 @@ func (t *TCPChainSink) runLoop(ctx context.Context) {
|
||||
defer t.wg.Done()
|
||||
defer t.closeConn()
|
||||
|
||||
// Fold done into the context, so a connect or exchange in flight ends on Stop
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
go func() {
|
||||
select {
|
||||
case <-t.done:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -302,22 +313,30 @@ func (t *TCPChainSink) runLoop(ctx context.Context) {
|
||||
// deliver writes one line, holding it across reconnects until sent or shutdown.
|
||||
// Backpressure during outage propagates to the pipeline dispatch drop counter.
|
||||
func (t *TCPChainSink) deliver(ctx context.Context, line []byte) bool {
|
||||
failures := 0
|
||||
for {
|
||||
if t.conn == nil {
|
||||
if failures > 0 && !t.waitBackoff(ctx, failures) {
|
||||
if t.failures > 0 && !t.waitBackoff(ctx, t.failures) {
|
||||
return false
|
||||
}
|
||||
if err := t.connect(ctx); err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return false
|
||||
}
|
||||
failures++
|
||||
t.logger.Debug("msg", "Chain connect failed",
|
||||
"component", "tcp_chain_sink",
|
||||
"target", t.addr,
|
||||
"attempt", failures,
|
||||
"error", err)
|
||||
t.failures++
|
||||
if errors.Is(err, authz.ErrRefused) {
|
||||
// A refusal is configuration, not weather: show it by default
|
||||
t.logger.Warn("msg", "Chain connect refused",
|
||||
"component", "tcp_chain_sink",
|
||||
"target", t.addr,
|
||||
"attempt", t.failures,
|
||||
"error", err)
|
||||
} else {
|
||||
t.logger.Debug("msg", "Chain connect failed",
|
||||
"component", "tcp_chain_sink",
|
||||
"target", t.addr,
|
||||
"attempt", t.failures,
|
||||
"error", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -325,7 +344,7 @@ func (t *TCPChainSink) deliver(ctx context.Context, line []byte) bool {
|
||||
t.conn.SetWriteDeadline(time.Now().Add(t.writeTimeout))
|
||||
if _, err := t.conn.Write(line); err != nil {
|
||||
t.writeErrors.Add(1)
|
||||
failures++
|
||||
t.failures++
|
||||
t.logger.Warn("msg", "Chain write failed",
|
||||
"component", "tcp_chain_sink",
|
||||
"target", t.addr,
|
||||
@@ -333,6 +352,10 @@ func (t *TCPChainSink) deliver(ctx context.Context, line []byte) bool {
|
||||
t.closeConn()
|
||||
continue
|
||||
}
|
||||
// Healthy once it outlives the shortest backoff
|
||||
if t.failures > 0 && time.Since(t.connectedAt) >= time.Duration(t.config.BackoffMinMS)*time.Millisecond {
|
||||
t.failures = 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -363,11 +386,26 @@ func (t *TCPChainSink) connect(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(t.writeTimeout))
|
||||
if _, err := conn.Write(t.helloLine); err != nil {
|
||||
r, err := t.auth.Greet(ctx, conn, t.node)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("hello: %w", err)
|
||||
return err
|
||||
}
|
||||
// A source never writes once a link is up: a line is a refusal this sink
|
||||
// could not otherwise see, EOF a close. Stop writing into either.
|
||||
t.wg.Add(1)
|
||||
go func() {
|
||||
defer t.wg.Done()
|
||||
err := authz.AwaitClose(r)
|
||||
conn.Close()
|
||||
if errors.Is(err, authz.ErrRefused) {
|
||||
t.logger.Warn("msg", "Chain link refused",
|
||||
"component", "tcp_chain_sink",
|
||||
"target", t.addr,
|
||||
"error", err)
|
||||
}
|
||||
}()
|
||||
t.connectedAt = time.Now()
|
||||
|
||||
t.conn = conn
|
||||
t.connected.Store(true)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package tcpchain
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"logwisp/internal/core"
|
||||
"logwisp/internal/session"
|
||||
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
// A source that refuses each link right after the hello, as a scram source
|
||||
// does to a sink without credentials, is retried with growing delays, not in
|
||||
// a tight loop that logs an established link every round.
|
||||
func TestRefusingSourceIsRetriedWithBackoff(t *testing.T) {
|
||||
ln, err := net.Listen("tcp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { ln.Close() })
|
||||
var accepts atomic.Int64
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
accepts.Add(1)
|
||||
bufio.NewReader(conn).ReadString('\n')
|
||||
conn.Write([]byte(`{"error":"authentication required"}` + "\n"))
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
_, port, _ := net.SplitHostPort(ln.Addr().String())
|
||||
p, _ := strconv.ParseInt(port, 10, 64)
|
||||
manager := session.NewManager(time.Hour)
|
||||
t.Cleanup(manager.Stop)
|
||||
created, err := NewTCPChainSinkPlugin("fwd", map[string]any{
|
||||
"host": "127.0.0.1", "port": p, "backoff_min_ms": int64(20), "backoff_max_ms": int64(5000),
|
||||
}, log.NewLogger(), session.NewProxy(manager, "fwd"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sink := created.(*TCPChainSink)
|
||||
if err := sink.Start(t.Context()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(sink.Stop)
|
||||
deadline := time.Now().Add(1500 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
select {
|
||||
case sink.Input() <- core.TransportEvent{Time: time.Now(), Payload: []byte("entry")}:
|
||||
default:
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
// 20 ms doubling reaches 1.5 s in about 7 attempts; a constant delay
|
||||
// would take dozens
|
||||
if n := accepts.Load(); n > 10 {
|
||||
t.Fatalf("%d connections in 1.5 s against a refusing source", n)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user