fix(io,tunnel): nil deref in RecorderObjectFromContext, CloseRead checked Writer, SetReadDeadline no unwrap
- entrypoint.go: guard ictx.RecorderObjectFromContext with nil checks on ClientID and Redirect assignments - io.go: fix CloseRead checking Writer instead of Reader, add Reader fallback to CloseWrite, unwrap *readWriter/*readWriteCloser in SetReadDeadline to reach underlying net.Conn - docs: add package doc and exported symbol comments to internal/ctx and internal/io
This commit is contained in:
@@ -147,8 +147,9 @@ func (ep *entrypoint) dial(ctx context.Context, network, addr string) (conn net.
|
|||||||
return nil, fmt.Errorf("%w %s", ErrTunnelRoute, addr)
|
return nil, fmt.Errorf("%w %s", ErrTunnelRoute, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
ro := ictx.RecorderObjectFromContext(ctx)
|
if ro := ictx.RecorderObjectFromContext(ctx); ro != nil {
|
||||||
ro.ClientID = tunnelID.String()
|
ro.ClientID = tunnelID.String()
|
||||||
|
}
|
||||||
|
|
||||||
if tunnelID.IsPrivate() {
|
if tunnelID.IsPrivate() {
|
||||||
return nil, fmt.Errorf("%w: tunnel %s is private for host %s", ErrPrivateTunnel, tunnelID, addr)
|
return nil, fmt.Errorf("%w: tunnel %s is private for host %s", ErrPrivateTunnel, tunnelID, addr)
|
||||||
@@ -172,8 +173,11 @@ func (ep *entrypoint) dial(ctx context.Context, network, addr string) (conn net.
|
|||||||
}
|
}
|
||||||
log.Debugf("dial: connected to host %s, tunnel: %s, connector: %s", addr, tunnelID, cid)
|
log.Debugf("dial: connected to host %s, tunnel: %s, connector: %s", addr, tunnelID, cid)
|
||||||
|
|
||||||
|
ro := ictx.RecorderObjectFromContext(ctx)
|
||||||
if node == ep.node {
|
if node == ep.node {
|
||||||
|
if ro != nil {
|
||||||
ro.Redirect = ""
|
ro.Redirect = ""
|
||||||
|
}
|
||||||
|
|
||||||
var clientAddr string
|
var clientAddr string
|
||||||
if addr := xctx.SrcAddrFromContext(ctx); addr != nil {
|
if addr := xctx.SrcAddrFromContext(ctx); addr != nil {
|
||||||
@@ -197,8 +201,10 @@ func (ep *entrypoint) dial(ctx context.Context, network, addr string) (conn net.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if ro != nil {
|
||||||
ro.Redirect = node
|
ro.Redirect = node
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Package ctx provides typed context value helpers for passing per-request
|
||||||
|
// data (buffer, logger, metadata, recorder objects) through the handler chain.
|
||||||
package ctx
|
package ctx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,10 +13,14 @@ import (
|
|||||||
|
|
||||||
type bufferKey struct{}
|
type bufferKey struct{}
|
||||||
|
|
||||||
|
// ContextWithBuffer returns a copy of ctx with the given buffer stored.
|
||||||
|
// The buffer is used by dialers and connectors to reuse a *bytes.Buffer
|
||||||
|
// for protocol handshake data (e.g. TLS ClientHello, HTTP requests).
|
||||||
func ContextWithBuffer(ctx context.Context, buffer *bytes.Buffer) context.Context {
|
func ContextWithBuffer(ctx context.Context, buffer *bytes.Buffer) context.Context {
|
||||||
return context.WithValue(ctx, bufferKey{}, buffer)
|
return context.WithValue(ctx, bufferKey{}, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferFromContext returns the *bytes.Buffer stored in ctx, or nil.
|
||||||
func BufferFromContext(ctx context.Context) *bytes.Buffer {
|
func BufferFromContext(ctx context.Context) *bytes.Buffer {
|
||||||
v, _ := ctx.Value(bufferKey{}).(*bytes.Buffer)
|
v, _ := ctx.Value(bufferKey{}).(*bytes.Buffer)
|
||||||
return v
|
return v
|
||||||
@@ -22,10 +28,12 @@ func BufferFromContext(ctx context.Context) *bytes.Buffer {
|
|||||||
|
|
||||||
type loggerKey struct{}
|
type loggerKey struct{}
|
||||||
|
|
||||||
|
// ContextWithLogger returns a copy of ctx with the given logger stored.
|
||||||
func ContextWithLogger(ctx context.Context, log logger.Logger) context.Context {
|
func ContextWithLogger(ctx context.Context, log logger.Logger) context.Context {
|
||||||
return context.WithValue(ctx, loggerKey{}, log)
|
return context.WithValue(ctx, loggerKey{}, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoggerFromContext returns the logger stored in ctx, or nil.
|
||||||
func LoggerFromContext(ctx context.Context) logger.Logger {
|
func LoggerFromContext(ctx context.Context) logger.Logger {
|
||||||
v, _ := ctx.Value(loggerKey{}).(logger.Logger)
|
v, _ := ctx.Value(loggerKey{}).(logger.Logger)
|
||||||
return v
|
return v
|
||||||
@@ -33,10 +41,12 @@ func LoggerFromContext(ctx context.Context) logger.Logger {
|
|||||||
|
|
||||||
type mdKey struct{}
|
type mdKey struct{}
|
||||||
|
|
||||||
|
// ContextWithMetadata returns a copy of ctx with the given metadata stored.
|
||||||
func ContextWithMetadata(ctx context.Context, md metadata.Metadata) context.Context {
|
func ContextWithMetadata(ctx context.Context, md metadata.Metadata) context.Context {
|
||||||
return context.WithValue(ctx, mdKey{}, md)
|
return context.WithValue(ctx, mdKey{}, md)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MetadataFromContext returns the metadata stored in ctx, or nil.
|
||||||
func MetadataFromContext(ctx context.Context) metadata.Metadata {
|
func MetadataFromContext(ctx context.Context) metadata.Metadata {
|
||||||
v, _ := ctx.Value(mdKey{}).(metadata.Metadata)
|
v, _ := ctx.Value(mdKey{}).(metadata.Metadata)
|
||||||
return v
|
return v
|
||||||
@@ -44,10 +54,12 @@ func MetadataFromContext(ctx context.Context) metadata.Metadata {
|
|||||||
|
|
||||||
type recorderObjectCtxKey struct{}
|
type recorderObjectCtxKey struct{}
|
||||||
|
|
||||||
|
// ContextWithRecorderObject returns a copy of ctx with the given recorder object stored.
|
||||||
func ContextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
|
func ContextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
|
||||||
return context.WithValue(ctx, recorderObjectCtxKey{}, ro)
|
return context.WithValue(ctx, recorderObjectCtxKey{}, ro)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecorderObjectFromContext returns the recorder object stored in ctx, or nil.
|
||||||
func RecorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
|
func RecorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
|
||||||
v, _ := ctx.Value(recorderObjectCtxKey{}).(*xrecorder.HandlerRecorderObject)
|
v, _ := ctx.Value(recorderObjectCtxKey{}).(*xrecorder.HandlerRecorderObject)
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Package io provides composite IO types that combine separate readers and
|
||||||
|
// writers into standard [io.ReadWriter] and [io.ReadWriteCloser] pairs, plus
|
||||||
|
// helpers for half-close and read-deadline propagation.
|
||||||
package io
|
package io
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -7,13 +10,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// ErrUnsupported is returned when a half-close operation is not supported
|
||||||
|
// by the underlying reader or writer.
|
||||||
ErrUnsupported = errors.New("unsupported")
|
ErrUnsupported = errors.New("unsupported")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CloseRead is the interface that groups the basic CloseRead method.
|
||||||
|
//
|
||||||
|
// CloseRead closes the read side of a connection. After CloseRead, reads
|
||||||
|
// return io.EOF while writes remain unaffected. It is typically implemented
|
||||||
|
// by [net.TCPConn] and connection wrappers throughout the x module.
|
||||||
type CloseRead interface {
|
type CloseRead interface {
|
||||||
CloseRead() error
|
CloseRead() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CloseWrite is the interface that groups the basic CloseWrite method.
|
||||||
|
//
|
||||||
|
// CloseWrite closes the write side of a connection. After CloseWrite, writes
|
||||||
|
// return an error while reads continue to work. It is typically implemented
|
||||||
|
// by [net.TCPConn] and connection wrappers throughout the x module.
|
||||||
type CloseWrite interface {
|
type CloseWrite interface {
|
||||||
CloseWrite() error
|
CloseWrite() error
|
||||||
}
|
}
|
||||||
@@ -23,6 +38,9 @@ type readWriter struct {
|
|||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewReadWriter returns an [io.ReadWriter] that reads from r and writes to w.
|
||||||
|
// The returned value supports [CloseRead] and [CloseWrite] by delegating to
|
||||||
|
// the corresponding methods on r or w when available.
|
||||||
func NewReadWriter(r io.Reader, w io.Writer) io.ReadWriter {
|
func NewReadWriter(r io.Reader, w io.Writer) io.ReadWriter {
|
||||||
return &readWriter{
|
return &readWriter{
|
||||||
Reader: r,
|
Reader: r,
|
||||||
@@ -31,6 +49,9 @@ func NewReadWriter(r io.Reader, w io.Writer) io.ReadWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rw *readWriter) CloseRead() error {
|
func (rw *readWriter) CloseRead() error {
|
||||||
|
if sc, ok := rw.Reader.(CloseRead); ok {
|
||||||
|
return sc.CloseRead()
|
||||||
|
}
|
||||||
if sc, ok := rw.Writer.(CloseRead); ok {
|
if sc, ok := rw.Writer.(CloseRead); ok {
|
||||||
return sc.CloseRead()
|
return sc.CloseRead()
|
||||||
}
|
}
|
||||||
@@ -41,6 +62,9 @@ func (rw *readWriter) CloseWrite() error {
|
|||||||
if sc, ok := rw.Writer.(CloseWrite); ok {
|
if sc, ok := rw.Writer.(CloseWrite); ok {
|
||||||
return sc.CloseWrite()
|
return sc.CloseWrite()
|
||||||
}
|
}
|
||||||
|
if sc, ok := rw.Reader.(CloseWrite); ok {
|
||||||
|
return sc.CloseWrite()
|
||||||
|
}
|
||||||
return ErrUnsupported
|
return ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +74,10 @@ type readWriteCloser struct {
|
|||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewReadWriteCloser returns an [io.ReadWriteCloser] that reads from r, writes
|
||||||
|
// to w, and closes c. Close delegates to c. The returned value also supports
|
||||||
|
// [CloseRead] and [CloseWrite] by delegating to the corresponding methods on
|
||||||
|
// r or w when available.
|
||||||
func NewReadWriteCloser(r io.Reader, w io.Writer, c io.Closer) io.ReadWriteCloser {
|
func NewReadWriteCloser(r io.Reader, w io.Writer, c io.Closer) io.ReadWriteCloser {
|
||||||
return &readWriteCloser{
|
return &readWriteCloser{
|
||||||
Reader: r,
|
Reader: r,
|
||||||
@@ -59,6 +87,9 @@ func NewReadWriteCloser(r io.Reader, w io.Writer, c io.Closer) io.ReadWriteClose
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rwc *readWriteCloser) CloseRead() error {
|
func (rwc *readWriteCloser) CloseRead() error {
|
||||||
|
if sc, ok := rwc.Reader.(CloseRead); ok {
|
||||||
|
return sc.CloseRead()
|
||||||
|
}
|
||||||
if sc, ok := rwc.Writer.(CloseRead); ok {
|
if sc, ok := rwc.Writer.(CloseRead); ok {
|
||||||
return sc.CloseRead()
|
return sc.CloseRead()
|
||||||
}
|
}
|
||||||
@@ -69,6 +100,9 @@ func (rwc *readWriteCloser) CloseWrite() error {
|
|||||||
if sc, ok := rwc.Writer.(CloseWrite); ok {
|
if sc, ok := rwc.Writer.(CloseWrite); ok {
|
||||||
return sc.CloseWrite()
|
return sc.CloseWrite()
|
||||||
}
|
}
|
||||||
|
if sc, ok := rwc.Reader.(CloseWrite); ok {
|
||||||
|
return sc.CloseWrite()
|
||||||
|
}
|
||||||
return ErrUnsupported
|
return ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +110,30 @@ type setReadDeadline interface {
|
|||||||
SetReadDeadline(t time.Time) error
|
SetReadDeadline(t time.Time) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the read deadline on rw if the underlying connection
|
||||||
|
// supports it. It checks rw itself first, then unwraps *readWriter and
|
||||||
|
// *readWriteCloser to reach their embedded Reader or Writer. If no
|
||||||
|
// settable deadline is found the call is a no-op and returns nil.
|
||||||
func SetReadDeadline(rw io.ReadWriter, t time.Time) error {
|
func SetReadDeadline(rw io.ReadWriter, t time.Time) error {
|
||||||
if v, _ := rw.(setReadDeadline); v != nil {
|
if v, _ := rw.(setReadDeadline); v != nil {
|
||||||
return v.SetReadDeadline(t)
|
return v.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
// Unwrap readWriter/readWriteCloser to reach the underlying connection.
|
||||||
|
switch r := rw.(type) {
|
||||||
|
case *readWriter:
|
||||||
|
if v, _ := r.Reader.(setReadDeadline); v != nil {
|
||||||
|
return v.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
if v, _ := r.Writer.(setReadDeadline); v != nil {
|
||||||
|
return v.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
case *readWriteCloser:
|
||||||
|
if v, _ := r.Reader.(setReadDeadline); v != nil {
|
||||||
|
return v.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
if v, _ := r.Writer.(setReadDeadline); v != nil {
|
||||||
|
return v.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user