Skip to content

Commit 3fe7f64

Browse files
committed
[Tailscale] http2: add counters for HTTP/2 server errors
Updates tailscale/corp#2363
1 parent f416191 commit 3fe7f64

File tree

2 files changed

+66
-28
lines changed

2 files changed

+66
-28
lines changed

http2/server.go

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,7 @@ func (sc *serverConn) processFrame(f Frame) error {
13901390
// First frame received must be SETTINGS.
13911391
if !sc.sawFirstSettings {
13921392
if _, ok := f.(*SettingsFrame); !ok {
1393-
return ConnectionError(ErrCodeProtocol)
1393+
return countError("first_settings", ConnectionError(ErrCodeProtocol))
13941394
}
13951395
sc.sawFirstSettings = true
13961396
}
@@ -1415,7 +1415,7 @@ func (sc *serverConn) processFrame(f Frame) error {
14151415
case *PushPromiseFrame:
14161416
// A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE
14171417
// frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
1418-
return ConnectionError(ErrCodeProtocol)
1418+
return countError("push_promise", ConnectionError(ErrCodeProtocol))
14191419
default:
14201420
sc.vlogf("http2: server ignoring frame: %v", f.Header())
14211421
return nil
@@ -1435,7 +1435,7 @@ func (sc *serverConn) processPing(f *PingFrame) error {
14351435
// identifier field value other than 0x0, the recipient MUST
14361436
// respond with a connection error (Section 5.4.1) of type
14371437
// PROTOCOL_ERROR."
1438-
return ConnectionError(ErrCodeProtocol)
1438+
return countError("ping_on_stream", ConnectionError(ErrCodeProtocol))
14391439
}
14401440
if sc.inGoAway && sc.goAwayCode != ErrCodeNo {
14411441
return nil
@@ -1454,7 +1454,7 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
14541454
// or PRIORITY on a stream in this state MUST be
14551455
// treated as a connection error (Section 5.4.1) of
14561456
// type PROTOCOL_ERROR."
1457-
return ConnectionError(ErrCodeProtocol)
1457+
return countError("stream_idle", ConnectionError(ErrCodeProtocol))
14581458
}
14591459
if st == nil {
14601460
// "WINDOW_UPDATE can be sent by a peer that has sent a
@@ -1465,7 +1465,7 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
14651465
return nil
14661466
}
14671467
if !st.flow.add(int32(f.Increment)) {
1468-
return streamError(f.StreamID, ErrCodeFlowControl)
1468+
return countError("bad_flow", streamError(f.StreamID, ErrCodeFlowControl))
14691469
}
14701470
default: // connection-level flow control
14711471
if !sc.flow.add(int32(f.Increment)) {
@@ -1486,7 +1486,7 @@ func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
14861486
// identifying an idle stream is received, the
14871487
// recipient MUST treat this as a connection error
14881488
// (Section 5.4.1) of type PROTOCOL_ERROR.
1489-
return ConnectionError(ErrCodeProtocol)
1489+
return countError("reset_idle_stream", ConnectionError(ErrCodeProtocol))
14901490
}
14911491
if st != nil {
14921492
st.cancelCtx()
@@ -1538,15 +1538,15 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
15381538
// Why is the peer ACKing settings we never sent?
15391539
// The spec doesn't mention this case, but
15401540
// hang up on them anyway.
1541-
return ConnectionError(ErrCodeProtocol)
1541+
return countError("ack_mystery", ConnectionError(ErrCodeProtocol))
15421542
}
15431543
return nil
15441544
}
15451545
if f.NumSettings() > 100 || f.HasDuplicates() {
15461546
// This isn't actually in the spec, but hang up on
15471547
// suspiciously large settings frames or those with
15481548
// duplicate entries.
1549-
return ConnectionError(ErrCodeProtocol)
1549+
return countError("settings_big_or_dups", ConnectionError(ErrCodeProtocol))
15501550
}
15511551
if err := f.ForeachSetting(sc.processSetting); err != nil {
15521552
return err
@@ -1613,7 +1613,7 @@ func (sc *serverConn) processSettingInitialWindowSize(val uint32) error {
16131613
// control window to exceed the maximum size as a
16141614
// connection error (Section 5.4.1) of type
16151615
// FLOW_CONTROL_ERROR."
1616-
return ConnectionError(ErrCodeFlowControl)
1616+
return countError("setting_win_size", ConnectionError(ErrCodeFlowControl))
16171617
}
16181618
}
16191619
return nil
@@ -1646,7 +1646,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
16461646
// or PRIORITY on a stream in this state MUST be
16471647
// treated as a connection error (Section 5.4.1) of
16481648
// type PROTOCOL_ERROR."
1649-
return ConnectionError(ErrCodeProtocol)
1649+
return countError("data_on_idle", ConnectionError(ErrCodeProtocol))
16501650
}
16511651

16521652
// "If a DATA frame is received whose stream is not in "open"
@@ -1663,7 +1663,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
16631663
// and return any flow control bytes since we're not going
16641664
// to consume them.
16651665
if sc.inflow.available() < int32(f.Length) {
1666-
return streamError(id, ErrCodeFlowControl)
1666+
return countError("data_flow", streamError(id, ErrCodeFlowControl))
16671667
}
16681668
// Deduct the flow control from inflow, since we're
16691669
// going to immediately add it back in
@@ -1676,7 +1676,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
16761676
// Already have a stream error in flight. Don't send another.
16771677
return nil
16781678
}
1679-
return streamError(id, ErrCodeStreamClosed)
1679+
return countError("closed", streamError(id, ErrCodeStreamClosed))
16801680
}
16811681
if st.body == nil {
16821682
panic("internal error: should have a body in this state")
@@ -1688,20 +1688,20 @@ func (sc *serverConn) processData(f *DataFrame) error {
16881688
// RFC 7540, sec 8.1.2.6: A request or response is also malformed if the
16891689
// value of a content-length header field does not equal the sum of the
16901690
// DATA frame payload lengths that form the body.
1691-
return streamError(id, ErrCodeProtocol)
1691+
return countError("send_too_much", streamError(id, ErrCodeProtocol))
16921692
}
16931693
if f.Length > 0 {
16941694
// Check whether the client has flow control quota.
16951695
if st.inflow.available() < int32(f.Length) {
1696-
return streamError(id, ErrCodeFlowControl)
1696+
return countError("flow_on_data_length", streamError(id, ErrCodeFlowControl))
16971697
}
16981698
st.inflow.take(int32(f.Length))
16991699

17001700
if len(data) > 0 {
17011701
wrote, err := st.body.Write(data)
17021702
if err != nil {
17031703
sc.sendWindowUpdate(nil, int(f.Length)-wrote)
1704-
return streamError(id, ErrCodeStreamClosed)
1704+
return countError("body_write_err", streamError(id, ErrCodeStreamClosed))
17051705
}
17061706
if wrote != len(data) {
17071707
panic("internal error: bad Writer")
@@ -1787,7 +1787,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
17871787
// stream identifier MUST respond with a connection error
17881788
// (Section 5.4.1) of type PROTOCOL_ERROR.
17891789
if id%2 != 1 {
1790-
return ConnectionError(ErrCodeProtocol)
1790+
return countError("headers_even", ConnectionError(ErrCodeProtocol))
17911791
}
17921792
// A HEADERS frame can be used to create a new stream or
17931793
// send a trailer for an open one. If we already have a stream
@@ -1804,7 +1804,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18041804
// this state, it MUST respond with a stream error (Section 5.4.2) of
18051805
// type STREAM_CLOSED.
18061806
if st.state == stateHalfClosedRemote {
1807-
return streamError(id, ErrCodeStreamClosed)
1807+
return countError("headers_half_closed", streamError(id, ErrCodeStreamClosed))
18081808
}
18091809
return st.processTrailerHeaders(f)
18101810
}
@@ -1815,7 +1815,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18151815
// receives an unexpected stream identifier MUST respond with
18161816
// a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
18171817
if id <= sc.maxClientStreamID {
1818-
return ConnectionError(ErrCodeProtocol)
1818+
return countError("stream_went_down", ConnectionError(ErrCodeProtocol))
18191819
}
18201820
sc.maxClientStreamID = id
18211821

@@ -1832,14 +1832,14 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18321832
if sc.curClientStreams+1 > sc.advMaxStreams {
18331833
if sc.unackedSettings == 0 {
18341834
// They should know better.
1835-
return streamError(id, ErrCodeProtocol)
1835+
return countError("over_max_streams", streamError(id, ErrCodeProtocol))
18361836
}
18371837
// Assume it's a network race, where they just haven't
18381838
// received our last SETTINGS update. But actually
18391839
// this can't happen yet, because we don't yet provide
18401840
// a way for users to adjust server parameters at
18411841
// runtime.
1842-
return streamError(id, ErrCodeRefusedStream)
1842+
return countError("over_max_streams_race", streamError(id, ErrCodeRefusedStream))
18431843
}
18441844

18451845
initialState := stateOpen
@@ -1893,15 +1893,15 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
18931893
sc := st.sc
18941894
sc.serveG.check()
18951895
if st.gotTrailerHeader {
1896-
return ConnectionError(ErrCodeProtocol)
1896+
return countError("dup_trailers", ConnectionError(ErrCodeProtocol))
18971897
}
18981898
st.gotTrailerHeader = true
18991899
if !f.StreamEnded() {
1900-
return streamError(st.id, ErrCodeProtocol)
1900+
return countError("trailers_not_ended", streamError(st.id, ErrCodeProtocol))
19011901
}
19021902

19031903
if len(f.PseudoFields()) > 0 {
1904-
return streamError(st.id, ErrCodeProtocol)
1904+
return countError("trailers_pseudo", streamError(st.id, ErrCodeProtocol))
19051905
}
19061906
if st.trailer != nil {
19071907
for _, hf := range f.RegularFields() {
@@ -1910,7 +1910,7 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
19101910
// TODO: send more details to the peer somehow. But http2 has
19111911
// no way to send debug data at a stream level. Discuss with
19121912
// HTTP folk.
1913-
return streamError(st.id, ErrCodeProtocol)
1913+
return countError("trailers_bogus", streamError(st.id, ErrCodeProtocol))
19141914
}
19151915
st.trailer[key] = append(st.trailer[key], hf.Value)
19161916
}
@@ -1925,7 +1925,7 @@ func checkPriority(streamID uint32, p PriorityParam) error {
19251925
// this as a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
19261926
// Section 5.3.3 says that a stream can depend on one of its dependencies,
19271927
// so it's only self-dependencies that are forbidden.
1928-
return streamError(streamID, ErrCodeProtocol)
1928+
return countError("priority", streamError(streamID, ErrCodeProtocol))
19291929
}
19301930
return nil
19311931
}
@@ -2004,13 +2004,13 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
20042004
// "All HTTP/2 requests MUST include exactly one valid
20052005
// value for the :method, :scheme, and :path
20062006
// pseudo-header fields"
2007-
return nil, nil, streamError(f.StreamID, ErrCodeProtocol)
2007+
return nil, nil, countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
20082008
}
20092009

20102010
bodyOpen := !f.StreamEnded()
20112011
if rp.method == "HEAD" && bodyOpen {
20122012
// HEAD requests can't have bodies
2013-
return nil, nil, streamError(f.StreamID, ErrCodeProtocol)
2013+
return nil, nil, countError("head_body", streamError(f.StreamID, ErrCodeProtocol))
20142014
}
20152015

20162016
rp.header = make(http.Header)
@@ -2093,7 +2093,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
20932093
var err error
20942094
url_, err = url.ParseRequestURI(rp.path)
20952095
if err != nil {
2096-
return nil, nil, streamError(st.id, ErrCodeProtocol)
2096+
return nil, nil, countError("bad_path", streamError(st.id, ErrCodeProtocol))
20972097
}
20982098
requestURI = rp.path
20992099
}

http2/server_tailscale.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2021 Tailscale. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package http2
6+
7+
import (
8+
"expvar"
9+
"fmt"
10+
"strconv"
11+
)
12+
13+
// http2ErrorCounters is a counter with name fitting the conventions used by
14+
// tailscale.com/tsweb to format Prometheus metrics from an expvar.Map.
15+
// The label is "type" (the error type), it's of metric type "counter" (it only goes up),
16+
// and the metric name is "http2_server_errors".
17+
var http2ErrorCounters = expvar.NewMap("counter_labelmap_type_http2_server_errors")
18+
19+
func countError(name string, err error) error {
20+
var typ string
21+
var code ErrCode
22+
switch e := err.(type) {
23+
case ConnectionError:
24+
typ = "conn"
25+
code = ErrCode(e)
26+
case StreamError:
27+
typ = "stream"
28+
code = ErrCode(e.Code)
29+
default:
30+
return err
31+
}
32+
codeStr := errCodeName[code]
33+
if codeStr == "" {
34+
codeStr = strconv.Itoa(int(code))
35+
}
36+
http2ErrorCounters.Add(fmt.Sprintf("%s_%s_%s", typ, codeStr, name), 1)
37+
return err
38+
}

0 commit comments

Comments
 (0)