http3: qlog the frame length and payload length of parsed frames (#5380)

This commit is contained in:
Marten Seemann
2025-10-12 16:58:32 +08:00
committed by GitHub
parent ce12204bc3
commit b7d987f5e4
12 changed files with 132 additions and 76 deletions

View File

@@ -22,6 +22,29 @@ type frame any
var errHijacked = errors.New("hijacked")
type countingByteReader struct {
quicvarint.Reader
NumRead int
}
func (r *countingByteReader) ReadByte() (byte, error) {
b, err := r.Reader.ReadByte()
if err == nil {
r.NumRead++
}
return b, err
}
func (r *countingByteReader) Read(b []byte) (int, error) {
n, err := r.Reader.Read(b)
r.NumRead += n
return n, err
}
func (r *countingByteReader) Reset() {
r.NumRead = 0
}
type frameParser struct {
r io.Reader
streamID quic.StreamID
@@ -30,9 +53,9 @@ type frameParser struct {
}
func (p *frameParser) ParseNext(qlogger qlogwriter.Recorder) (frame, error) {
qr := quicvarint.NewReader(p.r)
r := &countingByteReader{Reader: quicvarint.NewReader(p.r)}
for {
t, err := quicvarint.Read(qr)
t, err := quicvarint.Read(r)
if err != nil {
if p.unknownFrameHandler != nil {
hijacked, err := p.unknownFrameHandler(0, err)
@@ -56,36 +79,46 @@ func (p *frameParser) ParseNext(qlogger qlogwriter.Recorder) (frame, error) {
}
// If the unknownFrameHandler didn't process the frame, it is our responsibility to skip it.
}
l, err := quicvarint.Read(qr)
l, err := quicvarint.Read(r)
if err != nil {
return nil, err
}
switch t {
case 0x0:
case 0x0: // DATA
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.DataFrame{}},
Raw: qlog.RawInfo{
Length: int(l) + r.NumRead,
PayloadLength: int(l),
},
Frame: qlog.Frame{Frame: qlog.DataFrame{}},
})
}
return &dataFrame{Length: l}, nil
case 0x1:
return &headersFrame{Length: l}, nil
case 0x4:
return parseSettingsFrame(p.r, l, p.streamID, qlogger)
case 0x1: // HEADERS
return &headersFrame{
Length: l,
headerLen: r.NumRead,
}, nil
case 0x4: // SETTINGS
return parseSettingsFrame(r, l, p.streamID, qlogger)
case 0x3: // CANCEL_PUSH
case 0x5: // PUSH_PROMISE
case 0x7:
f, err := parseGoAwayFrame(qr, l)
case 0x7: // GOAWAY
f, err := parseGoAwayFrame(r, l)
if err != nil {
return nil, err
}
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: f.StreamID}},
Raw: qlog.RawInfo{
Length: r.NumRead,
PayloadLength: int(l),
},
Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: f.StreamID}},
})
}
return f, nil
@@ -95,9 +128,10 @@ func (p *frameParser) ParseNext(qlogger qlogwriter.Recorder) (frame, error) {
return nil, fmt.Errorf("http3: reserved frame type: %d", t)
}
// skip over unknown frames
if _, err := io.CopyN(io.Discard, qr, int64(l)); err != nil {
if _, err := io.CopyN(io.Discard, r, int64(l)); err != nil {
return nil, err
}
r.Reset()
}
}
@@ -111,7 +145,8 @@ func (f *dataFrame) Append(b []byte) []byte {
}
type headersFrame struct {
Length uint64
Length uint64
headerLen int // number of bytes read for type and length field
}
func (f *headersFrame) Append(b []byte) []byte {
@@ -137,7 +172,7 @@ func pointer[T any](v T) *T {
return &v
}
func parseSettingsFrame(r io.Reader, l uint64, streamID quic.StreamID, qlogger qlogwriter.Recorder) (*settingsFrame, error) {
func parseSettingsFrame(r *countingByteReader, l uint64, streamID quic.StreamID, qlogger qlogwriter.Recorder) (*settingsFrame, error) {
if l > 8*(1<<10) {
return nil, fmt.Errorf("unexpected size for SETTINGS frame: %d", l)
}
@@ -202,8 +237,11 @@ func parseSettingsFrame(r io.Reader, l uint64, streamID quic.StreamID, qlogger q
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: streamID,
Raw: qlog.RawInfo{PayloadLength: len(buf)},
Frame: qlog.Frame{Frame: settingsFrame},
Raw: qlog.RawInfo{
Length: r.NumRead,
PayloadLength: int(l),
},
Frame: qlog.Frame{Frame: settingsFrame},
})
}
return frame, nil
@@ -241,14 +279,14 @@ type goAwayFrame struct {
StreamID quic.StreamID
}
func parseGoAwayFrame(r io.ByteReader, l uint64) (*goAwayFrame, error) {
func parseGoAwayFrame(r *countingByteReader, l uint64) (*goAwayFrame, error) {
frame := &goAwayFrame{}
cbr := countingByteReader{ByteReader: r}
id, err := quicvarint.Read(&cbr)
startLen := r.NumRead
id, err := quicvarint.Read(r)
if err != nil {
return nil, err
}
if cbr.Read != int(l) {
if r.NumRead-startLen != int(l) {
return nil, errors.New("GOAWAY frame: inconsistent length")
}
frame.StreamID = quic.StreamID(id)