feat: observe HTTP2 reset and goaway frames
This commit is contained in:
106
transport/http2/frameObserver.go
Normal file
106
transport/http2/frameObserver.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
frameTypeRSTStream byte = 0x3
|
||||
frameTypeGoAway byte = 0x7
|
||||
)
|
||||
|
||||
type frameDirection uint8
|
||||
|
||||
const (
|
||||
frameRead frameDirection = iota
|
||||
frameWrite
|
||||
)
|
||||
|
||||
// observedConn keeps the HTTP/2 implementation opaque while exposing the
|
||||
// small amount of frame-level telemetry that x/net/http2 does not provide.
|
||||
// It parses only the nine-byte frame header and skips payload bytes without
|
||||
// buffering them, so large DATA frames cannot increase telemetry memory.
|
||||
type observedConn struct {
|
||||
net.Conn
|
||||
onFrame func(frameDirection, byte)
|
||||
readParser frameParser
|
||||
writeParser frameParser
|
||||
writeMu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *observedConn) Read(buffer []byte) (int, error) {
|
||||
n, err := c.Conn.Read(buffer)
|
||||
if n > 0 {
|
||||
c.readParser.observe(buffer[:n], frameRead, c.onFrame)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *observedConn) Write(buffer []byte) (int, error) {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
n, err := c.Conn.Write(buffer)
|
||||
if n > 0 {
|
||||
c.writeParser.observe(buffer[:n], frameWrite, c.onFrame)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// observedTLSConn preserves the connection state hook used by x/net/http2 so
|
||||
// handlers still receive Request.TLS after frame observation is added.
|
||||
type observedTLSConn struct {
|
||||
*observedConn
|
||||
state tls.ConnectionState
|
||||
}
|
||||
|
||||
func (c *observedTLSConn) ConnectionState() tls.ConnectionState { return c.state }
|
||||
|
||||
type frameParser struct {
|
||||
prefaceRemain int
|
||||
header [9]byte
|
||||
headerBytes int
|
||||
payloadRemain int
|
||||
frameType byte
|
||||
}
|
||||
|
||||
func (p *frameParser) observe(buffer []byte, direction frameDirection, onFrame func(frameDirection, byte)) {
|
||||
for len(buffer) > 0 {
|
||||
if p.prefaceRemain > 0 {
|
||||
skip := p.prefaceRemain
|
||||
if skip > len(buffer) {
|
||||
skip = len(buffer)
|
||||
}
|
||||
p.prefaceRemain -= skip
|
||||
buffer = buffer[skip:]
|
||||
continue
|
||||
}
|
||||
if p.payloadRemain > 0 {
|
||||
skip := p.payloadRemain
|
||||
if skip > len(buffer) {
|
||||
skip = len(buffer)
|
||||
}
|
||||
p.payloadRemain -= skip
|
||||
buffer = buffer[skip:]
|
||||
if p.payloadRemain > 0 {
|
||||
continue
|
||||
}
|
||||
onFrame(direction, p.frameType)
|
||||
continue
|
||||
}
|
||||
|
||||
copied := copy(p.header[p.headerBytes:], buffer)
|
||||
p.headerBytes += copied
|
||||
buffer = buffer[copied:]
|
||||
if p.headerBytes < len(p.header) {
|
||||
continue
|
||||
}
|
||||
p.payloadRemain = int(p.header[0])<<16 | int(p.header[1])<<8 | int(p.header[2])
|
||||
p.frameType = p.header[3]
|
||||
p.headerBytes = 0
|
||||
if p.payloadRemain == 0 {
|
||||
onFrame(direction, p.frameType)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user