This brings in CL 722200 which adds necessary HTTP/2 support for
net/http.Transport.NewClientConn.

For #75772

Change-Id: I5489232401096982ed21002f293dd0f87fe2fba6
Reviewed-on: https://go-review.googlesource.com/c/go/+/723901
Reviewed-by: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
This commit is contained in:
Damien Neil 2025-11-24 11:32:38 -08:00 committed by Gopher Robot
parent 1a53ce9734
commit 6465818435
12 changed files with 729 additions and 95 deletions

View file

@ -16,6 +16,6 @@ require (
require ( require (
github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.31.0 // indirect
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect
) )

View file

@ -20,8 +20,8 @@ golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxU
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w= golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w=
golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=

View file

@ -63,7 +63,7 @@ golang.org/x/telemetry/internal/upload
# golang.org/x/term v0.34.0 # golang.org/x/term v0.34.0
## explicit; go 1.23.0 ## explicit; go 1.23.0
golang.org/x/term golang.org/x/term
# golang.org/x/text v0.30.0 # golang.org/x/text v0.31.0
## explicit; go 1.24.0 ## explicit; go 1.24.0
golang.org/x/text/cases golang.org/x/text/cases
golang.org/x/text/internal golang.org/x/text/internal

View file

@ -3,11 +3,11 @@ module std
go 1.26 go 1.26
require ( require (
golang.org/x/crypto v0.43.0 golang.org/x/crypto v0.44.0
golang.org/x/net v0.46.0 golang.org/x/net v0.47.1-0.20251124223553-bff14c525670
) )
require ( require (
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.31.0 // indirect
) )

View file

@ -1,8 +1,8 @@
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 h1:6OE5meBQStq9OFgbyv9VH3wiSVw9HDJ7GBz2L5pkhuo=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.47.1-0.20251124223553-bff14c525670/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=

View file

@ -19,6 +19,7 @@ package http
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"compress/flate"
"compress/gzip" "compress/gzip"
"context" "context"
"crypto/rand" "crypto/rand"
@ -1842,6 +1843,8 @@ type http2Framer struct {
// lastHeaderStream is non-zero if the last frame was an // lastHeaderStream is non-zero if the last frame was an
// unfinished HEADERS/CONTINUATION. // unfinished HEADERS/CONTINUATION.
lastHeaderStream uint32 lastHeaderStream uint32
// lastFrameType holds the type of the last frame for verifying frame order.
lastFrameType http2FrameType
maxReadSize uint32 maxReadSize uint32
headerBuf [http2frameHeaderLen]byte headerBuf [http2frameHeaderLen]byte
@ -2053,30 +2056,41 @@ func http2terminalReadFrameError(err error) bool {
return err != nil return err != nil
} }
// ReadFrame reads a single frame. The returned Frame is only valid // ReadFrameHeader reads the header of the next frame.
// until the next call to ReadFrame. // It reads the 9-byte fixed frame header, and does not read any portion of the
// frame payload. The caller is responsible for consuming the payload, either
// with ReadFrameForHeader or directly from the Framer's io.Reader.
// //
// If the frame is larger than previously set with SetMaxReadFrameSize, the // If the frame is larger than previously set with SetMaxReadFrameSize, it
// returned error is ErrFrameTooLarge. Other errors may be of type // returns the frame header and ErrFrameTooLarge.
// ConnectionError, StreamError, or anything else from the underlying
// reader.
// //
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID // If the returned FrameHeader.StreamID is non-zero, it indicates the stream
// indicates the stream responsible for the error. // responsible for the error.
func (fr *http2Framer) ReadFrame() (http2Frame, error) { func (fr *http2Framer) ReadFrameHeader() (http2FrameHeader, error) {
fr.errDetail = nil fr.errDetail = nil
if fr.lastFrame != nil {
fr.lastFrame.invalidate()
}
fh, err := http2readFrameHeader(fr.headerBuf[:], fr.r) fh, err := http2readFrameHeader(fr.headerBuf[:], fr.r)
if err != nil { if err != nil {
return nil, err return fh, err
} }
if fh.Length > fr.maxReadSize { if fh.Length > fr.maxReadSize {
if fh == http2invalidHTTP1LookingFrameHeader() { if fh == http2invalidHTTP1LookingFrameHeader() {
return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge) return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge)
} }
return nil, http2ErrFrameTooLarge return fh, http2ErrFrameTooLarge
}
if err := fr.checkFrameOrder(fh); err != nil {
return fh, err
}
return fh, nil
}
// ReadFrameForHeader reads the payload for the frame with the given FrameHeader.
//
// It behaves identically to ReadFrame, other than not checking the maximum
// frame size.
func (fr *http2Framer) ReadFrameForHeader(fh http2FrameHeader) (http2Frame, error) {
if fr.lastFrame != nil {
fr.lastFrame.invalidate()
} }
payload := fr.getReadBuf(fh.Length) payload := fr.getReadBuf(fh.Length)
if _, err := io.ReadFull(fr.r, payload); err != nil { if _, err := io.ReadFull(fr.r, payload); err != nil {
@ -2092,9 +2106,7 @@ func (fr *http2Framer) ReadFrame() (http2Frame, error) {
} }
return nil, err return nil, err
} }
if err := fr.checkFrameOrder(f); err != nil { fr.lastFrame = f
return nil, err
}
if fr.logReads { if fr.logReads {
fr.debugReadLoggerf("http2: Framer %p: read %v", fr, http2summarizeFrame(f)) fr.debugReadLoggerf("http2: Framer %p: read %v", fr, http2summarizeFrame(f))
} }
@ -2104,6 +2116,24 @@ func (fr *http2Framer) ReadFrame() (http2Frame, error) {
return f, nil return f, nil
} }
// ReadFrame reads a single frame. The returned Frame is only valid
// until the next call to ReadFrame or ReadFrameBodyForHeader.
//
// If the frame is larger than previously set with SetMaxReadFrameSize, the
// returned error is ErrFrameTooLarge. Other errors may be of type
// ConnectionError, StreamError, or anything else from the underlying
// reader.
//
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
// indicates the stream responsible for the error.
func (fr *http2Framer) ReadFrame() (http2Frame, error) {
fh, err := fr.ReadFrameHeader()
if err != nil {
return nil, err
}
return fr.ReadFrameForHeader(fh)
}
// connError returns ConnectionError(code) but first // connError returns ConnectionError(code) but first
// stashes away a public reason to the caller can optionally relay it // stashes away a public reason to the caller can optionally relay it
// to the peer before hanging up on them. This might help others debug // to the peer before hanging up on them. This might help others debug
@ -2116,20 +2146,19 @@ func (fr *http2Framer) connError(code http2ErrCode, reason string) error {
// checkFrameOrder reports an error if f is an invalid frame to return // checkFrameOrder reports an error if f is an invalid frame to return
// next from ReadFrame. Mostly it checks whether HEADERS and // next from ReadFrame. Mostly it checks whether HEADERS and
// CONTINUATION frames are contiguous. // CONTINUATION frames are contiguous.
func (fr *http2Framer) checkFrameOrder(f http2Frame) error { func (fr *http2Framer) checkFrameOrder(fh http2FrameHeader) error {
last := fr.lastFrame lastType := fr.lastFrameType
fr.lastFrame = f fr.lastFrameType = fh.Type
if fr.AllowIllegalReads { if fr.AllowIllegalReads {
return nil return nil
} }
fh := f.Header()
if fr.lastHeaderStream != 0 { if fr.lastHeaderStream != 0 {
if fh.Type != http2FrameContinuation { if fh.Type != http2FrameContinuation {
return fr.connError(http2ErrCodeProtocol, return fr.connError(http2ErrCodeProtocol,
fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d", fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d",
fh.Type, fh.StreamID, fh.Type, fh.StreamID,
last.Header().Type, fr.lastHeaderStream)) lastType, fr.lastHeaderStream))
} }
if fh.StreamID != fr.lastHeaderStream { if fh.StreamID != fr.lastHeaderStream {
return fr.connError(http2ErrCodeProtocol, return fr.connError(http2ErrCodeProtocol,
@ -2726,7 +2755,7 @@ var http2defaultRFC9218Priority = http2PriorityParam{
// PriorityParam struct below is a superset of both schemes. The exported // PriorityParam struct below is a superset of both schemes. The exported
// symbols are from RFC 7540 and the non-exported ones are from RFC 9218. // symbols are from RFC 7540 and the non-exported ones are from RFC 9218.
// PriorityParam are the stream prioritzation parameters. // PriorityParam are the stream prioritization parameters.
type http2PriorityParam struct { type http2PriorityParam struct {
// StreamDep is a 31-bit stream identifier for the // StreamDep is a 31-bit stream identifier for the
// stream that this stream depends on. Zero means no // stream that this stream depends on. Zero means no
@ -7637,11 +7666,24 @@ type http2ClientConn struct {
// completely unresponsive connection. // completely unresponsive connection.
pendingResets int pendingResets int
// readBeforeStreamID is the smallest stream ID that has not been followed by
// a frame read from the peer. We use this to determine when a request may
// have been sent to a completely unresponsive connection:
// If the request ID is less than readBeforeStreamID, then we have had some
// indication of life on the connection since sending the request.
readBeforeStreamID uint32
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests. // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
// Write to reqHeaderMu to lock it, read from it to unlock. // Write to reqHeaderMu to lock it, read from it to unlock.
// Lock reqmu BEFORE mu or wmu. // Lock reqmu BEFORE mu or wmu.
reqHeaderMu chan struct{} reqHeaderMu chan struct{}
// internalStateHook reports state changes back to the net/http.ClientConn.
// Note that this is different from the user state hook registered by
// net/http.ClientConn.SetStateHook: The internal hook calls ClientConn,
// which calls the user hook.
internalStateHook func()
// wmu is held while writing. // wmu is held while writing.
// Acquire BEFORE mu when holding both, to avoid blocking mu on network writes. // Acquire BEFORE mu when holding both, to avoid blocking mu on network writes.
// Only acquire both at the same time when changing peer settings. // Only acquire both at the same time when changing peer settings.
@ -7972,7 +8014,7 @@ func http2canRetryError(err error) bool {
func (t *http2Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*http2ClientConn, error) { func (t *http2Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*http2ClientConn, error) {
if t.http2transportTestHooks != nil { if t.http2transportTestHooks != nil {
return t.newClientConn(nil, singleUse) return t.newClientConn(nil, singleUse, nil)
} }
host, _, err := net.SplitHostPort(addr) host, _, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
@ -7982,7 +8024,7 @@ func (t *http2Transport) dialClientConn(ctx context.Context, addr string, single
if err != nil { if err != nil {
return nil, err return nil, err
} }
return t.newClientConn(tconn, singleUse) return t.newClientConn(tconn, singleUse, nil)
} }
func (t *http2Transport) newTLSConfig(host string) *tls.Config { func (t *http2Transport) newTLSConfig(host string) *tls.Config {
@ -8034,10 +8076,10 @@ func (t *http2Transport) expectContinueTimeout() time.Duration {
} }
func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) { func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) {
return t.newClientConn(c, t.disableKeepAlives()) return t.newClientConn(c, t.disableKeepAlives(), nil)
} }
func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2ClientConn, error) { func (t *http2Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*http2ClientConn, error) {
conf := http2configFromTransport(t) conf := http2configFromTransport(t)
cc := &http2ClientConn{ cc := &http2ClientConn{
t: t, t: t,
@ -8059,6 +8101,7 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client
pings: make(map[[8]byte]chan struct{}), pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1), reqHeaderMu: make(chan struct{}, 1),
lastActive: time.Now(), lastActive: time.Now(),
internalStateHook: internalStateHook,
} }
if t.http2transportTestHooks != nil { if t.http2transportTestHooks != nil {
t.http2transportTestHooks.newclientconn(cc) t.http2transportTestHooks.newclientconn(cc)
@ -8299,10 +8342,7 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) {
maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams)
} }
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay && st.canTakeNewRequest = maxConcurrentOkay && cc.isUsableLocked()
!cc.doNotReuse &&
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
!cc.tooIdleLocked()
// If this connection has never been used for a request and is closed, // If this connection has never been used for a request and is closed,
// then let it take a request (which will fail). // then let it take a request (which will fail).
@ -8318,6 +8358,31 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) {
return return
} }
func (cc *http2ClientConn) isUsableLocked() bool {
return cc.goAway == nil &&
!cc.closed &&
!cc.closing &&
!cc.doNotReuse &&
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
!cc.tooIdleLocked()
}
// canReserveLocked reports whether a net/http.ClientConn can reserve a slot on this conn.
//
// This follows slightly different rules than clientConnIdleState.canTakeNewRequest.
// We only permit reservations up to the conn's concurrency limit.
// This differs from ClientConn.ReserveNewRequest, which permits reservations
// past the limit when StrictMaxConcurrentStreams is set.
func (cc *http2ClientConn) canReserveLocked() bool {
if cc.currentRequestCountLocked() >= int(cc.maxConcurrentStreams) {
return false
}
if !cc.isUsableLocked() {
return false
}
return true
}
// currentRequestCountLocked reports the number of concurrency slots currently in use, // currentRequestCountLocked reports the number of concurrency slots currently in use,
// including active streams, reserved slots, and reset streams waiting for acknowledgement. // including active streams, reserved slots, and reset streams waiting for acknowledgement.
func (cc *http2ClientConn) currentRequestCountLocked() int { func (cc *http2ClientConn) currentRequestCountLocked() int {
@ -8329,6 +8394,14 @@ func (cc *http2ClientConn) canTakeNewRequestLocked() bool {
return st.canTakeNewRequest return st.canTakeNewRequest
} }
// availableLocked reports the number of concurrency slots available.
func (cc *http2ClientConn) availableLocked() int {
if !cc.canTakeNewRequestLocked() {
return 0
}
return max(0, int(cc.maxConcurrentStreams)-cc.currentRequestCountLocked())
}
// tooIdleLocked reports whether this connection has been been sitting idle // tooIdleLocked reports whether this connection has been been sitting idle
// for too much wall time. // for too much wall time.
func (cc *http2ClientConn) tooIdleLocked() bool { func (cc *http2ClientConn) tooIdleLocked() bool {
@ -8353,6 +8426,7 @@ func (cc *http2ClientConn) closeConn() {
t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn) t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn)
defer t.Stop() defer t.Stop()
cc.tconn.Close() cc.tconn.Close()
cc.maybeCallStateHook()
} }
// A tls.Conn.Close can hang for a long time if the peer is unresponsive. // A tls.Conn.Close can hang for a long time if the peer is unresponsive.
@ -8878,6 +8952,8 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
} }
bodyClosed := cs.reqBodyClosed bodyClosed := cs.reqBodyClosed
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
// Have we read any frames from the connection since sending this request?
readSinceStream := cc.readBeforeStreamID > cs.ID
cc.mu.Unlock() cc.mu.Unlock()
if mustCloseBody { if mustCloseBody {
cs.reqBody.Close() cs.reqBody.Close()
@ -8909,8 +8985,10 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
// //
// This could be due to the server becoming unresponsive. // This could be due to the server becoming unresponsive.
// To avoid sending too many requests on a dead connection, // To avoid sending too many requests on a dead connection,
// we let the request continue to consume a concurrency slot // if we haven't read any frames from the connection since
// until we can confirm the server is still responding. // sending this request, we let it continue to consume
// a concurrency slot until we can confirm the server is
// still responding.
// We do this by sending a PING frame along with the RST_STREAM // We do this by sending a PING frame along with the RST_STREAM
// (unless a ping is already in flight). // (unless a ping is already in flight).
// //
@ -8921,7 +8999,7 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
// because it's short lived and will probably be closed before // because it's short lived and will probably be closed before
// we get the ping response. // we get the ping response.
ping := false ping := false
if !closeOnIdle { if !closeOnIdle && !readSinceStream {
cc.mu.Lock() cc.mu.Lock()
// rstStreamPingsBlocked works around a gRPC behavior: // rstStreamPingsBlocked works around a gRPC behavior:
// see comment on the field for details. // see comment on the field for details.
@ -8955,6 +9033,7 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
} }
close(cs.donec) close(cs.donec)
cc.maybeCallStateHook()
} }
// awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams. // awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams.
@ -10008,6 +10087,7 @@ func (rl *http2clientConnReadLoop) streamByID(id uint32, headerOrData bool) *htt
// See comment on ClientConn.rstStreamPingsBlocked for details. // See comment on ClientConn.rstStreamPingsBlocked for details.
rl.cc.rstStreamPingsBlocked = false rl.cc.rstStreamPingsBlocked = false
} }
rl.cc.readBeforeStreamID = rl.cc.nextStreamID
cs := rl.cc.streams[id] cs := rl.cc.streams[id]
if cs != nil && !cs.readAborted { if cs != nil && !cs.readAborted {
return cs return cs
@ -10058,6 +10138,7 @@ func (rl *http2clientConnReadLoop) processSettings(f *http2SettingsFrame) error
func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) error { func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) error {
cc := rl.cc cc := rl.cc
defer cc.maybeCallStateHook()
cc.mu.Lock() cc.mu.Lock()
defer cc.mu.Unlock() defer cc.mu.Unlock()
@ -10238,6 +10319,7 @@ func (cc *http2ClientConn) Ping(ctx context.Context) error {
func (rl *http2clientConnReadLoop) processPing(f *http2PingFrame) error { func (rl *http2clientConnReadLoop) processPing(f *http2PingFrame) error {
if f.IsAck() { if f.IsAck() {
cc := rl.cc cc := rl.cc
defer cc.maybeCallStateHook()
cc.mu.Lock() cc.mu.Lock()
defer cc.mu.Unlock() defer cc.mu.Unlock()
// If ack, notify listener if any // If ack, notify listener if any
@ -10343,35 +10425,103 @@ func (rt http2erringRoundTripper) RoundTripErr() error { return rt.err }
func (rt http2erringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err } func (rt http2erringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err }
var http2errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body")
// gzipReader wraps a response body so it can lazily // gzipReader wraps a response body so it can lazily
// call gzip.NewReader on the first call to Read // get gzip.Reader from the pool on the first call to Read.
// After Close is called it puts gzip.Reader to the pool immediately
// if there is no Read in progress or later when Read completes.
type http2gzipReader struct { type http2gzipReader struct {
_ http2incomparable _ http2incomparable
body io.ReadCloser // underlying Response.Body body io.ReadCloser // underlying Response.Body
zr *gzip.Reader // lazily-initialized gzip reader mu sync.Mutex // guards zr and zerr
zerr error // sticky error zr *gzip.Reader // stores gzip reader from the pool between reads
zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close
}
type http2eofReader struct{}
func (http2eofReader) Read([]byte) (int, error) { return 0, io.EOF }
func (http2eofReader) ReadByte() (byte, error) { return 0, io.EOF }
var http2gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.
func http2gzipPoolGet(r io.Reader) (*gzip.Reader, error) {
zr := http2gzipPool.Get().(*gzip.Reader)
if err := zr.Reset(r); err != nil {
http2gzipPoolPut(zr)
return nil, err
}
return zr, nil
}
// gzipPoolPut puts a gzip.Reader back into the pool.
func http2gzipPoolPut(zr *gzip.Reader) {
// Reset will allocate bufio.Reader if we pass it anything
// other than a flate.Reader, so ensure that it's getting one.
var r flate.Reader = http2eofReader{}
zr.Reset(r)
http2gzipPool.Put(zr)
}
// acquire returns a gzip.Reader for reading response body.
// The reader must be released after use.
func (gz *http2gzipReader) acquire() (*gzip.Reader, error) {
gz.mu.Lock()
defer gz.mu.Unlock()
if gz.zerr != nil {
return nil, gz.zerr
}
if gz.zr == nil {
gz.zr, gz.zerr = http2gzipPoolGet(gz.body)
if gz.zerr != nil {
return nil, gz.zerr
}
}
ret := gz.zr
gz.zr, gz.zerr = nil, http2errConcurrentReadOnResBody
return ret, nil
}
// release returns the gzip.Reader to the pool if Close was called during Read.
func (gz *http2gzipReader) release(zr *gzip.Reader) {
gz.mu.Lock()
defer gz.mu.Unlock()
if gz.zerr == http2errConcurrentReadOnResBody {
gz.zr, gz.zerr = zr, nil
} else { // fs.ErrClosed
http2gzipPoolPut(zr)
}
}
// close returns the gzip.Reader to the pool immediately or
// signals release to do so after Read completes.
func (gz *http2gzipReader) close() {
gz.mu.Lock()
defer gz.mu.Unlock()
if gz.zerr == nil && gz.zr != nil {
http2gzipPoolPut(gz.zr)
gz.zr = nil
}
gz.zerr = fs.ErrClosed
} }
func (gz *http2gzipReader) Read(p []byte) (n int, err error) { func (gz *http2gzipReader) Read(p []byte) (n int, err error) {
if gz.zerr != nil { zr, err := gz.acquire()
return 0, gz.zerr
}
if gz.zr == nil {
gz.zr, err = gzip.NewReader(gz.body)
if err != nil { if err != nil {
gz.zerr = err
return 0, err return 0, err
} }
} defer gz.release(zr)
return gz.zr.Read(p)
return zr.Read(p)
} }
func (gz *http2gzipReader) Close() error { func (gz *http2gzipReader) Close() error {
if err := gz.body.Close(); err != nil { gz.close()
return err
} return gz.body.Close()
gz.zerr = fs.ErrClosed
return nil
} }
type http2errorReader struct{ err error } type http2errorReader struct{ err error }
@ -10397,9 +10547,13 @@ func http2registerHTTPSProtocol(t *Transport, rt http2noDialH2RoundTripper) (err
} }
// noDialH2RoundTripper is a RoundTripper which only tries to complete the request // noDialH2RoundTripper is a RoundTripper which only tries to complete the request
// if there's already has a cached connection to the host. // if there's already a cached connection to the host.
// (The field is exported so it can be accessed via reflect from net/http; tested // (The field is exported so it can be accessed via reflect from net/http; tested
// by TestNoDialH2RoundTripperType) // by TestNoDialH2RoundTripperType)
//
// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol,
// and the http1.Transport can use type assertions to call non-RoundTrip methods on it.
// This lets us expose, for example, NewClientConn to net/http.
type http2noDialH2RoundTripper struct{ *http2Transport } type http2noDialH2RoundTripper struct{ *http2Transport }
func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) { func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) {
@ -10410,6 +10564,85 @@ func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) {
return res, err return res, err
} }
func (rt http2noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (RoundTripper, error) {
tr := rt.http2Transport
cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook)
if err != nil {
return nil, err
}
// RoundTrip should block when the conn is at its concurrency limit,
// not return an error. Setting strictMaxConcurrentStreams enables this.
cc.strictMaxConcurrentStreams = true
return http2netHTTPClientConn{cc}, nil
}
// netHTTPClientConn wraps ClientConn and implements the interface net/http expects from
// the RoundTripper returned by NewClientConn.
type http2netHTTPClientConn struct {
cc *http2ClientConn
}
func (cc http2netHTTPClientConn) RoundTrip(req *Request) (*Response, error) {
return cc.cc.RoundTrip(req)
}
func (cc http2netHTTPClientConn) Close() error {
return cc.cc.Close()
}
func (cc http2netHTTPClientConn) Err() error {
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
if cc.cc.closed {
return errors.New("connection closed")
}
return nil
}
func (cc http2netHTTPClientConn) Reserve() error {
defer cc.cc.maybeCallStateHook()
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
if !cc.cc.canReserveLocked() {
return errors.New("connection is unavailable")
}
cc.cc.streamsReserved++
return nil
}
func (cc http2netHTTPClientConn) Release() {
defer cc.cc.maybeCallStateHook()
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
// We don't complain if streamsReserved is 0.
//
// This is consistent with RoundTrip: both Release and RoundTrip will
// consume a reservation iff one exists.
if cc.cc.streamsReserved > 0 {
cc.cc.streamsReserved--
}
}
func (cc http2netHTTPClientConn) Available() int {
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
return cc.cc.availableLocked()
}
func (cc http2netHTTPClientConn) InFlight() int {
cc.cc.mu.Lock()
defer cc.cc.mu.Unlock()
return cc.cc.currentRequestCountLocked()
}
func (cc *http2ClientConn) maybeCallStateHook() {
if cc.internalStateHook != nil {
cc.internalStateHook()
}
}
func (t *http2Transport) idleConnTimeout() time.Duration { func (t *http2Transport) idleConnTimeout() time.Duration {
// to keep things backwards compatible, we use non-zero values of // to keep things backwards compatible, we use non-zero values of
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying // IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
@ -11069,45 +11302,75 @@ func (wr *http2FrameWriteRequest) replyToWriter(err error) {
} }
// writeQueue is used by implementations of WriteScheduler. // writeQueue is used by implementations of WriteScheduler.
//
// Each writeQueue contains a queue of FrameWriteRequests, meant to store all
// FrameWriteRequests associated with a given stream. This is implemented as a
// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done
// by incrementing currPos of currQueue. Adding an item is done by appending it
// to the nextQueue. If currQueue is empty when trying to remove an item, we
// can swap currQueue and nextQueue to remedy the situation.
// This two-stage queue is analogous to the use of two lists in Okasaki's
// purely functional queue but without the overhead of reversing the list when
// swapping stages.
//
// writeQueue also contains prev and next, this can be used by implementations
// of WriteScheduler to construct data structures that represent the order of
// writing between different streams (e.g. circular linked list).
type http2writeQueue struct { type http2writeQueue struct {
s []http2FrameWriteRequest currQueue []http2FrameWriteRequest
nextQueue []http2FrameWriteRequest
currPos int
prev, next *http2writeQueue prev, next *http2writeQueue
} }
func (q *http2writeQueue) empty() bool { return len(q.s) == 0 } func (q *http2writeQueue) empty() bool {
return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0
}
func (q *http2writeQueue) push(wr http2FrameWriteRequest) { func (q *http2writeQueue) push(wr http2FrameWriteRequest) {
q.s = append(q.s, wr) q.nextQueue = append(q.nextQueue, wr)
} }
func (q *http2writeQueue) shift() http2FrameWriteRequest { func (q *http2writeQueue) shift() http2FrameWriteRequest {
if len(q.s) == 0 { if q.empty() {
panic("invalid use of queue") panic("invalid use of queue")
} }
wr := q.s[0] if q.currPos >= len(q.currQueue) {
// TODO: less copy-happy queue. q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0]
copy(q.s, q.s[1:]) }
q.s[len(q.s)-1] = http2FrameWriteRequest{} wr := q.currQueue[q.currPos]
q.s = q.s[:len(q.s)-1] q.currQueue[q.currPos] = http2FrameWriteRequest{}
q.currPos++
return wr return wr
} }
func (q *http2writeQueue) peek() *http2FrameWriteRequest {
if q.currPos < len(q.currQueue) {
return &q.currQueue[q.currPos]
}
if len(q.nextQueue) > 0 {
return &q.nextQueue[0]
}
return nil
}
// consume consumes up to n bytes from q.s[0]. If the frame is // consume consumes up to n bytes from q.s[0]. If the frame is
// entirely consumed, it is removed from the queue. If the frame // entirely consumed, it is removed from the queue. If the frame
// is partially consumed, the frame is kept with the consumed // is partially consumed, the frame is kept with the consumed
// bytes removed. Returns true iff any bytes were consumed. // bytes removed. Returns true iff any bytes were consumed.
func (q *http2writeQueue) consume(n int32) (http2FrameWriteRequest, bool) { func (q *http2writeQueue) consume(n int32) (http2FrameWriteRequest, bool) {
if len(q.s) == 0 { if q.empty() {
return http2FrameWriteRequest{}, false return http2FrameWriteRequest{}, false
} }
consumed, rest, numresult := q.s[0].Consume(n) consumed, rest, numresult := q.peek().Consume(n)
switch numresult { switch numresult {
case 0: case 0:
return http2FrameWriteRequest{}, false return http2FrameWriteRequest{}, false
case 1: case 1:
q.shift() q.shift()
case 2: case 2:
q.s[0] = rest *q.peek() = rest
} }
return consumed, true return consumed, true
} }
@ -11118,10 +11381,15 @@ type http2writeQueuePool []*http2writeQueue
// put inserts an unused writeQueue into the pool. // put inserts an unused writeQueue into the pool.
func (p *http2writeQueuePool) put(q *http2writeQueue) { func (p *http2writeQueuePool) put(q *http2writeQueue) {
for i := range q.s { for i := range q.currQueue {
q.s[i] = http2FrameWriteRequest{} q.currQueue[i] = http2FrameWriteRequest{}
} }
q.s = q.s[:0] for i := range q.nextQueue {
q.nextQueue[i] = http2FrameWriteRequest{}
}
q.currQueue = q.currQueue[:0]
q.nextQueue = q.nextQueue[:0]
q.currPos = 0
*p = append(*p, q) *p = append(*p, q)
} }
@ -11344,8 +11612,8 @@ func (z http2sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k]
func (z http2sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool { func (z http2sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool {
// Prefer the subtree that has sent fewer bytes relative to its weight. // Prefer the subtree that has sent fewer bytes relative to its weight.
// See sections 5.3.2 and 5.3.4. // See sections 5.3.2 and 5.3.4.
wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes) wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes)
wk, bk := float64(z[k].weight+1), float64(z[k].subtreeBytes) wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes)
if bi == 0 && bk == 0 { if bi == 0 && bk == 0 {
return wi >= wk return wi >= wk
} }
@ -11432,7 +11700,6 @@ func (ws *http2priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) {
q := n.q q := n.q
ws.queuePool.put(&q) ws.queuePool.put(&q)
n.q.s = nil
if ws.maxClosedNodesInTree > 0 { if ws.maxClosedNodesInTree > 0 {
ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n) ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n)
} else { } else {
@ -11610,7 +11877,7 @@ type http2priorityWriteSchedulerRFC9218 struct {
prioritizeIncremental bool prioritizeIncremental bool
} }
func http2newPriorityWriteSchedulerRFC9128() http2WriteScheduler { func http2newPriorityWriteSchedulerRFC9218() http2WriteScheduler {
ws := &http2priorityWriteSchedulerRFC9218{ ws := &http2priorityWriteSchedulerRFC9218{
streams: make(map[uint32]http2streamMetadata), streams: make(map[uint32]http2streamMetadata),
} }

View file

@ -29,7 +29,7 @@ loop:
MOVD $NUM_ROUNDS, R21 MOVD $NUM_ROUNDS, R21
VLD1 (R11), [V30.S4, V31.S4] VLD1 (R11), [V30.S4, V31.S4]
// load contants // load constants
// VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4] // VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
WORD $0x4D60E940 WORD $0x4D60E940

View file

@ -56,7 +56,10 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
ret, out := sliceForAppend(dst, len(plaintext)+16) ret, out := sliceForAppend(dst, len(plaintext)+16)
if alias.InexactOverlap(out, plaintext) { if alias.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap") panic("chacha20poly1305: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
} }
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData) chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
return ret return ret
@ -73,7 +76,10 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
ciphertext = ciphertext[:len(ciphertext)-16] ciphertext = ciphertext[:len(ciphertext)-16]
ret, out := sliceForAppend(dst, len(ciphertext)) ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) { if alias.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap") panic("chacha20poly1305: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
} }
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) { if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
for i := range out { for i := range out {

View file

@ -31,7 +31,10 @@ func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []b
ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize) ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
ciphertext, tag := out[:len(plaintext)], out[len(plaintext):] ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
if alias.InexactOverlap(out, plaintext) { if alias.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap") panic("chacha20poly1305: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
} }
var polyKey [32]byte var polyKey [32]byte
@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
ret, out := sliceForAppend(dst, len(ciphertext)) ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) { if alias.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap") panic("chacha20poly1305: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
} }
if !p.Verify(tag) { if !p.Verify(tag) {
for i := range out { for i := range out {

View file

@ -17,8 +17,21 @@ import (
) )
// Message formats // Message formats
//
// To add a new Resource Record type:
// 1. Create Resource Record types
// 1.1. Add a Type constant named "Type<name>"
// 1.2. Add the corresponding entry to the typeNames map
// 1.3. Add a [ResourceBody] implementation named "<name>Resource"
// 2. Implement packing
// 2.1. Implement Builder.<name>Resource()
// 3. Implement unpacking
// 3.1. Add the unpacking code to unpackResourceBody()
// 3.2. Implement Parser.<name>Resource()
// A Type is a type of DNS request and response. // A Type is the type of a DNS Resource Record, as defined in the [IANA registry].
//
// [IANA registry]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
type Type uint16 type Type uint16
const ( const (
@ -33,6 +46,8 @@ const (
TypeAAAA Type = 28 TypeAAAA Type = 28
TypeSRV Type = 33 TypeSRV Type = 33
TypeOPT Type = 41 TypeOPT Type = 41
TypeSVCB Type = 64
TypeHTTPS Type = 65
// Question.Type // Question.Type
TypeWKS Type = 11 TypeWKS Type = 11
@ -53,6 +68,8 @@ var typeNames = map[Type]string{
TypeAAAA: "TypeAAAA", TypeAAAA: "TypeAAAA",
TypeSRV: "TypeSRV", TypeSRV: "TypeSRV",
TypeOPT: "TypeOPT", TypeOPT: "TypeOPT",
TypeSVCB: "TypeSVCB",
TypeHTTPS: "TypeHTTPS",
TypeWKS: "TypeWKS", TypeWKS: "TypeWKS",
TypeHINFO: "TypeHINFO", TypeHINFO: "TypeHINFO",
TypeMINFO: "TypeMINFO", TypeMINFO: "TypeMINFO",
@ -273,6 +290,8 @@ var (
errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)") errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)") errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)")
errStringTooLong = errors.New("character string exceeds maximum length (255)") errStringTooLong = errors.New("character string exceeds maximum length (255)")
errParamOutOfOrder = errors.New("parameter out of order")
errTooLongSVCBValue = errors.New("value too long (>65535 bytes)")
) )
// Internal constants. // Internal constants.
@ -2220,6 +2239,16 @@ func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody,
rb, err = unpackSRVResource(msg, off) rb, err = unpackSRVResource(msg, off)
r = &rb r = &rb
name = "SRV" name = "SRV"
case TypeSVCB:
var rb SVCBResource
rb, err = unpackSVCBResource(msg, off, hdr.Length)
r = &rb
name = "SVCB"
case TypeHTTPS:
var rb HTTPSResource
rb.SVCBResource, err = unpackSVCBResource(msg, off, hdr.Length)
r = &rb
name = "HTTPS"
case TypeOPT: case TypeOPT:
var rb OPTResource var rb OPTResource
rb, err = unpackOPTResource(msg, off, hdr.Length) rb, err = unpackOPTResource(msg, off, hdr.Length)

326
src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go generated vendored Normal file
View file

@ -0,0 +1,326 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dnsmessage
import (
"slices"
)
// An SVCBResource is an SVCB Resource record.
type SVCBResource struct {
Priority uint16
Target Name
Params []SVCParam // Must be in strict increasing order by Key.
}
func (r *SVCBResource) realType() Type {
return TypeSVCB
}
// GoString implements fmt.GoStringer.GoString.
func (r *SVCBResource) GoString() string {
b := []byte("dnsmessage.SVCBResource{" +
"Priority: " + printUint16(r.Priority) + ", " +
"Target: " + r.Target.GoString() + ", " +
"Params: []dnsmessage.SVCParam{")
if len(r.Params) > 0 {
b = append(b, r.Params[0].GoString()...)
for _, p := range r.Params[1:] {
b = append(b, ", "+p.GoString()...)
}
}
b = append(b, "}}"...)
return string(b)
}
// An HTTPSResource is an HTTPS Resource record.
// It has the same format as the SVCB record.
type HTTPSResource struct {
// Alias for SVCB resource record.
SVCBResource
}
func (r *HTTPSResource) realType() Type {
return TypeHTTPS
}
// GoString implements fmt.GoStringer.GoString.
func (r *HTTPSResource) GoString() string {
return "dnsmessage.HTTPSResource{SVCBResource: " + r.SVCBResource.GoString() + "}"
}
// GetParam returns a parameter value by key.
func (r *SVCBResource) GetParam(key SVCParamKey) (value []byte, ok bool) {
for i := range r.Params {
if r.Params[i].Key == key {
return r.Params[i].Value, true
}
if r.Params[i].Key > key {
break
}
}
return nil, false
}
// SetParam sets a parameter value by key.
// The Params list is kept sorted by key.
func (r *SVCBResource) SetParam(key SVCParamKey, value []byte) {
i := 0
for i < len(r.Params) {
if r.Params[i].Key >= key {
break
}
i++
}
if i < len(r.Params) && r.Params[i].Key == key {
r.Params[i].Value = value
return
}
r.Params = slices.Insert(r.Params, i, SVCParam{Key: key, Value: value})
}
// DeleteParam deletes a parameter by key.
// It returns true if the parameter was present.
func (r *SVCBResource) DeleteParam(key SVCParamKey) bool {
for i := range r.Params {
if r.Params[i].Key == key {
r.Params = slices.Delete(r.Params, i, i+1)
return true
}
if r.Params[i].Key > key {
break
}
}
return false
}
// A SVCParam is a service parameter.
type SVCParam struct {
Key SVCParamKey
Value []byte
}
// GoString implements fmt.GoStringer.GoString.
func (p SVCParam) GoString() string {
return "dnsmessage.SVCParam{" +
"Key: " + p.Key.GoString() + ", " +
"Value: []byte{" + printByteSlice(p.Value) + "}}"
}
// A SVCParamKey is a key for a service parameter.
type SVCParamKey uint16
// Values defined at https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys.
const (
SVCParamMandatory SVCParamKey = 0
SVCParamALPN SVCParamKey = 1
SVCParamNoDefaultALPN SVCParamKey = 2
SVCParamPort SVCParamKey = 3
SVCParamIPv4Hint SVCParamKey = 4
SVCParamECH SVCParamKey = 5
SVCParamIPv6Hint SVCParamKey = 6
SVCParamDOHPath SVCParamKey = 7
SVCParamOHTTP SVCParamKey = 8
SVCParamTLSSupportedGroups SVCParamKey = 9
)
var svcParamKeyNames = map[SVCParamKey]string{
SVCParamMandatory: "Mandatory",
SVCParamALPN: "ALPN",
SVCParamNoDefaultALPN: "NoDefaultALPN",
SVCParamPort: "Port",
SVCParamIPv4Hint: "IPv4Hint",
SVCParamECH: "ECH",
SVCParamIPv6Hint: "IPv6Hint",
SVCParamDOHPath: "DOHPath",
SVCParamOHTTP: "OHTTP",
SVCParamTLSSupportedGroups: "TLSSupportedGroups",
}
// String implements fmt.Stringer.String.
func (k SVCParamKey) String() string {
if n, ok := svcParamKeyNames[k]; ok {
return n
}
return printUint16(uint16(k))
}
// GoString implements fmt.GoStringer.GoString.
func (k SVCParamKey) GoString() string {
if n, ok := svcParamKeyNames[k]; ok {
return "dnsmessage.SVCParam" + n
}
return printUint16(uint16(k))
}
func (r *SVCBResource) pack(msg []byte, _ map[string]uint16, _ int) ([]byte, error) {
oldMsg := msg
msg = packUint16(msg, r.Priority)
// https://datatracker.ietf.org/doc/html/rfc3597#section-4 prohibits name
// compression for RR types that are not "well-known".
// https://datatracker.ietf.org/doc/html/rfc9460#section-2.2 explicitly states that
// compression of the Target is prohibited, following RFC 3597.
msg, err := r.Target.pack(msg, nil, 0)
if err != nil {
return oldMsg, &nestedError{"SVCBResource.Target", err}
}
var previousKey SVCParamKey
for i, param := range r.Params {
if i > 0 && param.Key <= previousKey {
return oldMsg, &nestedError{"SVCBResource.Params", errParamOutOfOrder}
}
if len(param.Value) > (1<<16)-1 {
return oldMsg, &nestedError{"SVCBResource.Params", errTooLongSVCBValue}
}
msg = packUint16(msg, uint16(param.Key))
msg = packUint16(msg, uint16(len(param.Value)))
msg = append(msg, param.Value...)
}
return msg, nil
}
func unpackSVCBResource(msg []byte, off int, length uint16) (SVCBResource, error) {
// Wire format reference: https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2.
r := SVCBResource{}
paramsOff := off
bodyEnd := off + int(length)
var err error
if r.Priority, paramsOff, err = unpackUint16(msg, paramsOff); err != nil {
return SVCBResource{}, &nestedError{"Priority", err}
}
if paramsOff, err = r.Target.unpack(msg, paramsOff); err != nil {
return SVCBResource{}, &nestedError{"Target", err}
}
// Two-pass parsing to avoid allocations.
// First, count the number of params.
n := 0
var totalValueLen uint16
off = paramsOff
var previousKey uint16
for off < bodyEnd {
var key, len uint16
if key, off, err = unpackUint16(msg, off); err != nil {
return SVCBResource{}, &nestedError{"Params key", err}
}
if n > 0 && key <= previousKey {
// As per https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2, clients MUST
// consider the RR malformed if the SvcParamKeys are not in strictly increasing numeric order
return SVCBResource{}, &nestedError{"Params", errParamOutOfOrder}
}
if len, off, err = unpackUint16(msg, off); err != nil {
return SVCBResource{}, &nestedError{"Params value length", err}
}
if off+int(len) > bodyEnd {
return SVCBResource{}, errResourceLen
}
totalValueLen += len
off += int(len)
n++
}
if off != bodyEnd {
return SVCBResource{}, errResourceLen
}
// Second, fill in the params.
r.Params = make([]SVCParam, n)
// valuesBuf is used to hold all param values to reduce allocations.
// Each param's Value slice will point into this buffer.
valuesBuf := make([]byte, totalValueLen)
off = paramsOff
for i := 0; i < n; i++ {
p := &r.Params[i]
var key, len uint16
if key, off, err = unpackUint16(msg, off); err != nil {
return SVCBResource{}, &nestedError{"param key", err}
}
p.Key = SVCParamKey(key)
if len, off, err = unpackUint16(msg, off); err != nil {
return SVCBResource{}, &nestedError{"param length", err}
}
if copy(valuesBuf, msg[off:off+int(len)]) != int(len) {
return SVCBResource{}, &nestedError{"param value", errCalcLen}
}
p.Value = valuesBuf[:len:len]
valuesBuf = valuesBuf[len:]
off += int(len)
}
return r, nil
}
// genericSVCBResource parses a single Resource Record compatible with SVCB.
func (p *Parser) genericSVCBResource(svcbType Type) (SVCBResource, error) {
if !p.resHeaderValid || p.resHeaderType != svcbType {
return SVCBResource{}, ErrNotStarted
}
r, err := unpackSVCBResource(p.msg, p.off, p.resHeaderLength)
if err != nil {
return SVCBResource{}, err
}
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
}
// SVCBResource parses a single SVCBResource.
//
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) SVCBResource() (SVCBResource, error) {
return p.genericSVCBResource(TypeSVCB)
}
// HTTPSResource parses a single HTTPSResource.
//
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) HTTPSResource() (HTTPSResource, error) {
svcb, err := p.genericSVCBResource(TypeHTTPS)
if err != nil {
return HTTPSResource{}, err
}
return HTTPSResource{svcb}, nil
}
// genericSVCBResource is the generic implementation for adding SVCB-like resources.
func (b *Builder) genericSVCBResource(h ResourceHeader, r SVCBResource) error {
if err := b.checkResourceSection(); err != nil {
return err
}
msg, lenOff, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"ResourceBody", err}
}
if err := h.fixLen(msg, lenOff, preLen); err != nil {
return err
}
if err := b.incrementSectionCount(); err != nil {
return err
}
b.msg = msg
return nil
}
// SVCBResource adds a single SVCBResource.
func (b *Builder) SVCBResource(h ResourceHeader, r SVCBResource) error {
h.Type = r.realType()
return b.genericSVCBResource(h, r)
}
// HTTPSResource adds a single HTTPSResource.
func (b *Builder) HTTPSResource(h ResourceHeader, r HTTPSResource) error {
h.Type = r.realType()
return b.genericSVCBResource(h, r.SVCBResource)
}

View file

@ -1,4 +1,4 @@
# golang.org/x/crypto v0.43.0 # golang.org/x/crypto v0.44.0
## explicit; go 1.24.0 ## explicit; go 1.24.0
golang.org/x/crypto/chacha20 golang.org/x/crypto/chacha20
golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305
@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte
golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/cryptobyte/asn1
golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305 golang.org/x/crypto/internal/poly1305
# golang.org/x/net v0.46.0 # golang.org/x/net v0.47.1-0.20251124223553-bff14c525670
## explicit; go 1.24.0 ## explicit; go 1.24.0
golang.org/x/net/dns/dnsmessage golang.org/x/net/dns/dnsmessage
golang.org/x/net/http/httpguts golang.org/x/net/http/httpguts
@ -18,7 +18,7 @@ golang.org/x/net/nettest
# golang.org/x/sys v0.38.0 # golang.org/x/sys v0.38.0
## explicit; go 1.24.0 ## explicit; go 1.24.0
golang.org/x/sys/cpu golang.org/x/sys/cpu
# golang.org/x/text v0.30.0 # golang.org/x/text v0.31.0
## explicit; go 1.24.0 ## explicit; go 1.24.0
golang.org/x/text/secure/bidirule golang.org/x/text/secure/bidirule
golang.org/x/text/transform golang.org/x/text/transform