From a83b6a2155691455a3517a6c5c5db6e91000d10c Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 19 Feb 2020 16:16:13 -0600 Subject: [PATCH 001/100] TUN-2725: Specify in code that --edge is for internal testing only --- cmd/cloudflared/tunnel/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 62c6c1c4..142f63ef 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -690,7 +690,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag { }), altsrc.NewStringSliceFlag(&cli.StringSliceFlag{ Name: "edge", - Usage: "Address of the Cloudflare tunnel server.", + Usage: "Address of the Cloudflare tunnel server. Only works in Cloudflare's internal testing environment.", EnvVars: []string{"TUNNEL_EDGE"}, Hidden: true, }), From 269351bbea85210fcf0716ab1c70960e8766afd7 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 19 Feb 2020 18:57:29 -0600 Subject: [PATCH 002/100] TUN-2703: Muxer.Serve terminates when its context is Done --- h2mux/h2mux.go | 51 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/h2mux/h2mux.go b/h2mux/h2mux.go index 2b6defde..70da0447 100644 --- a/h2mux/h2mux.go +++ b/h2mux/h2mux.go @@ -321,24 +321,51 @@ func joinErrorsWithTimeout(errChan <-chan error, receiveCount int, timeout time. func (m *Muxer) Serve(ctx context.Context) error { errGroup, _ := errgroup.WithContext(ctx) errGroup.Go(func() error { - err := m.muxReader.run(m.config.Logger) - m.explicitShutdown.Fuse(false) - m.r.Close() - m.abort() - return err + ch := make(chan error) + go func() { + err := m.muxReader.run(m.config.Logger) + m.explicitShutdown.Fuse(false) + m.r.Close() + m.abort() + ch <- err + }() + select { + case err := <-ch: + return err + case <-ctx.Done(): + return ctx.Err() + } }) errGroup.Go(func() error { - err := m.muxWriter.run(m.config.Logger) - m.explicitShutdown.Fuse(false) - m.w.Close() - m.abort() - return err + ch := make(chan error) + go func() { + err := m.muxWriter.run(m.config.Logger) + m.explicitShutdown.Fuse(false) + m.w.Close() + m.abort() + ch <- err + }() + select { + case err := <-ch: + return err + case <-ctx.Done(): + return ctx.Err() + } }) errGroup.Go(func() error { - err := m.muxMetricsUpdater.run(m.config.Logger) - return err + ch := make(chan error) + go func() { + err := m.muxMetricsUpdater.run(m.config.Logger) + ch <- err + }() + select { + case err := <-ch: + return err + case <-ctx.Done(): + return ctx.Err() + } }) err := errGroup.Wait() From 52ab2c8227946b17faa17fd72e9a4c4a7ecf9a17 Mon Sep 17 00:00:00 2001 From: Areg Harutyunyan Date: Thu, 20 Feb 2020 19:41:31 +0000 Subject: [PATCH 003/100] TUN-2745: Rename existing header management functions --- h2mux/header.go | 6 +++--- h2mux/header_test.go | 14 +++++++------- origin/tunnel.go | 6 +++--- streamhandler/request.go | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/h2mux/header.go b/h2mux/header.go index 4da9e03c..822f99bb 100644 --- a/h2mux/header.go +++ b/h2mux/header.go @@ -17,10 +17,10 @@ type Header struct { var headerEncoding = base64.RawStdEncoding -// H2RequestHeadersToH1Request converts the HTTP/2 headers to an HTTP/1 Request +// OldH2RequestHeadersToH1Request converts the HTTP/2 headers to an HTTP/1 Request // object. This includes conversion of the pseudo-headers into their closest // HTTP/1 equivalents. See https://tools.ietf.org/html/rfc7540#section-8.1.2.3 -func H2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error { +func OldH2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error { for _, header := range h2 { switch header.Name { case ":method": @@ -73,7 +73,7 @@ func H2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error { return nil } -func H1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) { +func OldH1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) { h2 = []Header{{Name: ":status", Value: fmt.Sprintf("%d", h1.StatusCode)}} for headerName, headerValues := range h1.Header { for _, headerValue := range headerValues { diff --git a/h2mux/header_test.go b/h2mux/header_test.go index 83c7ac35..8c5301eb 100644 --- a/h2mux/header_test.go +++ b/h2mux/header_test.go @@ -19,7 +19,7 @@ func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - headersConversionErr := H2RequestHeadersToH1Request( + headersConversionErr := OldH2RequestHeadersToH1Request( []Header{ { Name: "Mock header 1", @@ -45,7 +45,7 @@ func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - headersConversionErr := H2RequestHeadersToH1Request( + headersConversionErr := OldH2RequestHeadersToH1Request( []Header{}, request, ) @@ -59,7 +59,7 @@ func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - headersConversionErr := H2RequestHeadersToH1Request( + headersConversionErr := OldH2RequestHeadersToH1Request( []Header{ { Name: ":path", @@ -86,7 +86,7 @@ func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil) assert.NoError(t, err) - headersConversionErr := H2RequestHeadersToH1Request( + headersConversionErr := OldH2RequestHeadersToH1Request( []Header{ { Name: ":path", @@ -113,7 +113,7 @@ func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil) assert.NoError(t, err) - headersConversionErr := H2RequestHeadersToH1Request( + headersConversionErr := OldH2RequestHeadersToH1Request( []Header{ { Name: ":path", @@ -276,7 +276,7 @@ func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) { request, err := http.NewRequest(http.MethodGet, requestURL, nil) assert.NoError(t, err) - headersConversionErr := H2RequestHeadersToH1Request( + headersConversionErr := OldH2RequestHeadersToH1Request( []Header{ { Name: ":path", @@ -362,7 +362,7 @@ func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) { h1, err := http.NewRequest("GET", testOrigin.url, nil) require.NoError(t, err) - err = H2RequestHeadersToH1Request(h2, h1) + err = OldH2RequestHeadersToH1Request(h2, h1) return assert.NoError(t, err) && assert.Equal(t, expectedMethod, h1.Method) && assert.Equal(t, expectedHostname, h1.Host) && diff --git a/origin/tunnel.go b/origin/tunnel.go index 4ee6b55e..d5ba4d71 100644 --- a/origin/tunnel.go +++ b/origin/tunnel.go @@ -580,7 +580,7 @@ func (h *TunnelHandler) createRequest(stream *h2mux.MuxedStream) (*http.Request, if err != nil { return nil, errors.Wrap(err, "Unexpected error from http.NewRequest") } - err = h2mux.H2RequestHeadersToH1Request(stream.Headers, req) + err = h2mux.OldH2RequestHeadersToH1Request(stream.Headers, req) if err != nil { return nil, errors.Wrap(err, "invalid request received") } @@ -599,7 +599,7 @@ func (h *TunnelHandler) serveWebsocket(stream *h2mux.MuxedStream, req *http.Requ return nil, err } defer conn.Close() - err = stream.WriteHeaders(h2mux.H1ResponseToH2ResponseHeaders(response)) + err = stream.WriteHeaders(h2mux.OldH1ResponseToH2ResponseHeaders(response)) if err != nil { return nil, errors.Wrap(err, "Error writing response header") } @@ -633,7 +633,7 @@ func (h *TunnelHandler) serveHTTP(stream *h2mux.MuxedStream, req *http.Request) } defer response.Body.Close() - err = stream.WriteHeaders(h2mux.H1ResponseToH2ResponseHeaders(response)) + err = stream.WriteHeaders(h2mux.OldH1ResponseToH2ResponseHeaders(response)) if err != nil { return nil, errors.Wrap(err, "Error writing response header") } diff --git a/streamhandler/request.go b/streamhandler/request.go index b6bb55d3..52d69bc2 100644 --- a/streamhandler/request.go +++ b/streamhandler/request.go @@ -26,7 +26,7 @@ func createRequest(stream *h2mux.MuxedStream, url *url.URL) (*http.Request, erro if err != nil { return nil, errors.Wrap(err, "unexpected error from http.NewRequest") } - err = h2mux.H2RequestHeadersToH1Request(stream.Headers, req) + err = h2mux.OldH2RequestHeadersToH1Request(stream.Headers, req) if err != nil { return nil, errors.Wrap(err, "invalid request received") } From 6488843ac4071c3d29fe49f6810565bfa94b3f7f Mon Sep 17 00:00:00 2001 From: Areg Harutyunyan Date: Fri, 21 Feb 2020 02:51:46 +0000 Subject: [PATCH 004/100] TUN-2746: Add the new header management functions --- h2mux/header.go | 170 ++++++++++++++++++++++++--- h2mux/header_test.go | 270 ++++++++++++++++++++++++++++++------------- 2 files changed, 340 insertions(+), 100 deletions(-) diff --git a/h2mux/header.go b/h2mux/header.go index 822f99bb..20a878e8 100644 --- a/h2mux/header.go +++ b/h2mux/header.go @@ -1,7 +1,6 @@ package h2mux import ( - "bytes" "encoding/base64" "fmt" "github.com/pkg/errors" @@ -17,9 +16,130 @@ type Header struct { var headerEncoding = base64.RawStdEncoding -// OldH2RequestHeadersToH1Request converts the HTTP/2 headers to an HTTP/1 Request -// object. This includes conversion of the pseudo-headers into their closest +const ( + RequestUserHeadersField = "cf-cloudflared-request-headers" + ResponseUserHeadersField = "cf-cloudflared-response-headers" +) + +// H2RequestHeadersToH1Request converts the HTTP/2 headers coming from origintunneld +// to an HTTP/1 Request object destined for the local origin web service. +// This operation includes conversion of the pseudo-headers into their closest // HTTP/1 equivalents. See https://tools.ietf.org/html/rfc7540#section-8.1.2.3 +func H2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error { + for _, header := range h2 { + switch strings.ToLower(header.Name) { + case ":method": + h1.Method = header.Value + case ":scheme": + // noop - use the preexisting scheme from h1.URL + case ":authority": + // Otherwise the host header will be based on the origin URL + h1.Host = header.Value + case ":path": + // We don't want to be an "opinionated" proxy, so ideally we would use :path as-is. + // However, this HTTP/1 Request object belongs to the Go standard library, + // whose URL package makes some opinionated decisions about the encoding of + // URL characters: see the docs of https://godoc.org/net/url#URL, + // in particular the EscapedPath method https://godoc.org/net/url#URL.EscapedPath, + // which is always used when computing url.URL.String(), whether we'd like it or not. + // + // Well, not *always*. We could circumvent this by using url.URL.Opaque. But + // that would present unusual difficulties when using an HTTP proxy: url.URL.Opaque + // is treated differently when HTTP_PROXY is set! + // See https://github.com/golang/go/issues/5684#issuecomment-66080888 + // + // This means we are subject to the behavior of net/url's function `shouldEscape` + // (as invoked with mode=encodePath): https://github.com/golang/go/blob/go1.12.7/src/net/url/url.go#L101 + + if header.Value == "*" { + h1.URL.Path = "*" + continue + } + // Due to the behavior of validation.ValidateUrl, h1.URL may + // already have a partial value, with or without a trailing slash. + base := h1.URL.String() + base = strings.TrimRight(base, "/") + // But we know :path begins with '/', because we handled '*' above - see RFC7540 + requestURL, err := url.Parse(base + header.Value) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("invalid path '%v'", header.Value)) + } + h1.URL = requestURL + case "content-length": + contentLength, err := strconv.ParseInt(header.Value, 10, 64) + if err != nil { + return fmt.Errorf("unparseable content length") + } + h1.ContentLength = contentLength + default: + // Ignore any other header; + // User headers will be read from `RequestUserHeadersField` + continue + } + } + + // Find and parse user headers serialized into a single one + userHeaders, err := ParseUserHeaders(RequestUserHeadersField, h2) + if err != nil { + return errors.Wrap(err, "Unable to parse user headers") + } + for _, userHeader := range userHeaders { + h1.Header.Add(http.CanonicalHeaderKey(userHeader.Name), userHeader.Value) + } + + return nil +} + +func ParseUserHeaders(headerNameToParseFrom string, headers []Header) ([]Header, error) { + for _, header := range headers { + if header.Name == headerNameToParseFrom { + return DeserializeHeaders(header.Value) + } + } + + return nil, fmt.Errorf("%v header not found", RequestUserHeadersField) +} + +func IsControlHeader(headerName string) bool { + headerName = strings.ToLower(headerName) + + return strings.ToLower(headerName) == "content-length" || + strings.HasPrefix(headerName, ":") || + strings.HasPrefix(headerName, "cf-") +} + +func H1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) { + h2 = []Header{ + {Name: ":status", Value: strconv.Itoa(h1.StatusCode)}, + } + userHeaders := http.Header{} + for header, values := range h1.Header { + for _, value := range values { + if strings.ToLower(header) == "content-length" { + // This header has meaning in HTTP/2 and will be used by the edge, + // so it should be sent as an HTTP/2 response header. + + // Since these are http2 headers, they're required to be lowercase + h2 = append(h2, Header{Name: strings.ToLower(header), Value: value}) + } else if !IsControlHeader(header) { + // User headers, on the other hand, must all be serialized so that + // HTTP/2 header validation won't be applied to HTTP/1 header values + if _, ok := userHeaders[header]; ok { + userHeaders[header] = append(userHeaders[header], value) + } else { + userHeaders[header] = []string{value} + } + } + } + } + + // Perform user header serialization and set them in the single header + h2 = append(h2, CreateSerializedHeaders(ResponseUserHeadersField, userHeaders)...) + + return h2 +} + +// Obsolete version of H2RequestHeadersToH1Request func OldH2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error { for _, header := range h2 { switch header.Name { @@ -73,6 +193,7 @@ func OldH2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error { return nil } +// Obsolete version of H1ResponseToH2ResponseHeaders func OldH1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) { h2 = []Header{{Name: ":status", Value: fmt.Sprintf("%d", h1.StatusCode)}} for headerName, headerValues := range h1.Header { @@ -86,9 +207,9 @@ func OldH1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) { // Serialize HTTP1.x headers by base64-encoding each header name and value, // and then joining them in the format of [key:value;] -func SerializeHeaders(h1 *http.Request) []byte { - var serializedHeaders [][]byte - for headerName, headerValues := range h1.Header { +func SerializeHeaders(h1Headers http.Header) string { + var serializedHeaders []string + for headerName, headerValues := range h1Headers { for _, headerValue := range headerValues { encodedName := make([]byte, headerEncoding.EncodedLen(len(headerName))) headerEncoding.Encode(encodedName, []byte(headerName)) @@ -98,28 +219,28 @@ func SerializeHeaders(h1 *http.Request) []byte { serializedHeaders = append( serializedHeaders, - bytes.Join( - [][]byte{encodedName, encodedValue}, - []byte(":"), + strings.Join( + []string{string(encodedName), string(encodedValue)}, + ":", ), ) } } - return bytes.Join(serializedHeaders, []byte(";")) + return strings.Join(serializedHeaders, ";") } // Deserialize headers serialized by `SerializeHeader` -func DeserializeHeaders(serializedHeaders []byte) (http.Header, error) { +func DeserializeHeaders(serializedHeaders string) ([]Header, error) { const unableToDeserializeErr = "Unable to deserialize headers" - deserialized := http.Header{} - for _, serializedPair := range bytes.Split(serializedHeaders, []byte(";")) { + var deserialized []Header + for _, serializedPair := range strings.Split(serializedHeaders, ";") { if len(serializedPair) == 0 { continue } - serializedHeaderParts := bytes.Split(serializedPair, []byte(":")) + serializedHeaderParts := strings.Split(serializedPair, ":") if len(serializedHeaderParts) != 2 { return nil, errors.New(unableToDeserializeErr) } @@ -129,15 +250,30 @@ func DeserializeHeaders(serializedHeaders []byte) (http.Header, error) { deserializedName := make([]byte, headerEncoding.DecodedLen(len(serializedName))) deserializedValue := make([]byte, headerEncoding.DecodedLen(len(serializedValue))) - if _, err := headerEncoding.Decode(deserializedName, serializedName); err != nil { + if _, err := headerEncoding.Decode(deserializedName, []byte(serializedName)); err != nil { return nil, errors.Wrap(err, unableToDeserializeErr) } - if _, err := headerEncoding.Decode(deserializedValue, serializedValue); err != nil { + if _, err := headerEncoding.Decode(deserializedValue, []byte(serializedValue)); err != nil { return nil, errors.Wrap(err, unableToDeserializeErr) } - deserialized.Add(string(deserializedName), string(deserializedValue)) + deserialized = append(deserialized, Header{ + Name: string(deserializedName), + Value: string(deserializedValue), + }) } return deserialized, nil } + +func CreateSerializedHeaders(headersField string, headers ...http.Header) []Header { + var serializedHeaderChunks []string + for _, headerChunk := range headers { + serializedHeaderChunks = append(serializedHeaderChunks, SerializeHeaders(headerChunk)) + } + + return []Header{{ + headersField, + strings.Join(serializedHeaderChunks, ";"), + }} +} diff --git a/h2mux/header_test.go b/h2mux/header_test.go index 8c5301eb..b5781d91 100644 --- a/h2mux/header_test.go +++ b/h2mux/header_test.go @@ -7,6 +7,7 @@ import ( "net/url" "reflect" "regexp" + "sort" "strings" "testing" "testing/quick" @@ -15,29 +16,30 @@ import ( "github.com/stretchr/testify/require" ) +type ByName []Header + +func (a ByName) Len() int { return len(a) } +func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByName) Less(i, j int) bool { + if a[i].Name == a[j].Name { + return a[i].Value < a[j].Value + } + + return a[i].Name < a[j].Name +} + func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - headersConversionErr := OldH2RequestHeadersToH1Request( - []Header{ - { - Name: "Mock header 1", - Value: "Mock value 1", - }, - { - Name: "Mock header 2", - Value: "Mock value 2", - }, - }, - request, - ) + mockHeaders := http.Header{ + "Mock header 1": {"Mock value 1"}, + "Mock header 2": {"Mock value 2"}, + } - assert.Equal(t, http.Header{ - "Mock header 1": []string{"Mock value 1"}, - "Mock header 2": []string{"Mock value 2"}, - }, request.Header) + headersConversionErr := H2RequestHeadersToH1Request(CreateSerializedHeaders(RequestUserHeadersField, mockHeaders), request) + assert.True(t, reflect.DeepEqual(mockHeaders, request.Header)) assert.NoError(t, headersConversionErr) } @@ -45,13 +47,15 @@ func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - headersConversionErr := OldH2RequestHeadersToH1Request( - []Header{}, + headersConversionErr := H2RequestHeadersToH1Request( + []Header{{ + RequestUserHeadersField, + SerializeHeaders(http.Header{}), + }}, request, ) - assert.Equal(t, http.Header{}, request.Header) - + assert.True(t, reflect.DeepEqual(http.Header{}, request.Header)) assert.NoError(t, headersConversionErr) } @@ -59,19 +63,12 @@ func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - headersConversionErr := OldH2RequestHeadersToH1Request( - []Header{ - { - Name: ":path", - Value: "//bad_path/", - }, - { - Name: "Mock header", - Value: "Mock value", - }, - }, - request, - ) + mockRequestHeaders := []Header{ + {Name: ":path", Value: "//bad_path/"}, + {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})}, + } + + headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request) assert.Equal(t, http.Header{ "Mock header": []string{"Mock value"}, @@ -86,19 +83,12 @@ func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil) assert.NoError(t, err) - headersConversionErr := OldH2RequestHeadersToH1Request( - []Header{ - { - Name: ":path", - Value: "/?query=mock%20value", - }, - { - Name: "Mock header", - Value: "Mock value", - }, - }, - request, - ) + mockRequestHeaders := []Header{ + {Name: ":path", Value: "/?query=mock%20value"}, + {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})}, + } + + headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request) assert.Equal(t, http.Header{ "Mock header": []string{"Mock value"}, @@ -113,19 +103,12 @@ func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil) assert.NoError(t, err) - headersConversionErr := OldH2RequestHeadersToH1Request( - []Header{ - { - Name: ":path", - Value: "/mock%20path", - }, - { - Name: "Mock header", - Value: "Mock value", - }, - }, - request, - ) + mockRequestHeaders := []Header{ + {Name: ":path", Value: "/mock%20path"}, + {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})}, + } + + headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request) assert.Equal(t, http.Header{ "Mock header": []string{"Mock value"}, @@ -276,19 +259,13 @@ func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) { request, err := http.NewRequest(http.MethodGet, requestURL, nil) assert.NoError(t, err) - headersConversionErr := OldH2RequestHeadersToH1Request( - []Header{ - { - Name: ":path", - Value: testCase.path, - }, - { - Name: "Mock header", - Value: "Mock value", - }, - }, - request, - ) + + mockRequestHeaders := []Header{ + {Name: ":path", Value: testCase.path}, + {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})}, + } + + headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request) assert.NoError(t, headersConversionErr) assert.Equal(t, @@ -358,11 +335,12 @@ func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) { {Name: ":scheme", Value: testScheme}, {Name: ":authority", Value: expectedHostname}, {Name: ":path", Value: testPath}, + {Name: RequestUserHeadersField, Value: ""}, } h1, err := http.NewRequest("GET", testOrigin.url, nil) require.NoError(t, err) - err = OldH2RequestHeadersToH1Request(h2, h1) + err = H2RequestHeadersToH1Request(h2, h1) return assert.NoError(t, err) && assert.Equal(t, expectedMethod, h1.Method) && assert.Equal(t, expectedHostname, h1.Host) && @@ -439,11 +417,21 @@ func randomHTTP2Path(t *testing.T, rand *rand.Rand) string { return result } +func stdlibHeaderToH2muxHeader(headers http.Header) (h2muxHeaders []Header) { + for name, values := range headers { + for _, value := range values { + h2muxHeaders = append(h2muxHeaders, Header{name, value}) + } + } + + return h2muxHeaders +} + func TestSerializeHeaders(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - mockHeaders := map[string][]string{ + mockHeaders := http.Header{ "Mock-Header-One": {"Mock header one value", "three"}, "Mock-Header-Two-Long": {"Mock header two value\nlong"}, ":;": {":;", ";:"}, @@ -465,7 +453,7 @@ func TestSerializeHeaders(t *testing.T) { } } - serializedHeaders := SerializeHeaders(request) + serializedHeaders := SerializeHeaders(request.Header) // Sanity check: the headers serialized to something that's not an empty string assert.NotEqual(t, "", serializedHeaders) @@ -474,18 +462,24 @@ func TestSerializeHeaders(t *testing.T) { deserializedHeaders, err := DeserializeHeaders(serializedHeaders) assert.NoError(t, err) - assert.Equal(t, len(mockHeaders), len(deserializedHeaders)) - for header, value := range deserializedHeaders { - assert.NotEqual(t, "", value) - assert.Equal(t, mockHeaders[header], value) - } + assert.Equal(t, 13, len(deserializedHeaders)) + h2muxExpectedHeaders := stdlibHeaderToH2muxHeader(mockHeaders) + + sort.Sort(ByName(deserializedHeaders)) + sort.Sort(ByName(h2muxExpectedHeaders)) + + assert.True( + t, + reflect.DeepEqual(h2muxExpectedHeaders, deserializedHeaders), + fmt.Sprintf("got = %#v, want = %#v\n", deserializedHeaders, h2muxExpectedHeaders), + ) } func TestSerializeNoHeaders(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "http://example.com", nil) assert.NoError(t, err) - serializedHeaders := SerializeHeaders(request) + serializedHeaders := SerializeHeaders(request.Header) deserializedHeaders, err := DeserializeHeaders(serializedHeaders) assert.NoError(t, err) assert.Equal(t, 0, len(deserializedHeaders)) @@ -502,7 +496,117 @@ func TestDeserializeMalformed(t *testing.T) { } for _, malformedValue := range malformedData { - _, err = DeserializeHeaders([]byte(malformedValue)) + _, err = DeserializeHeaders(malformedValue) assert.Error(t, err) } } + +func TestParseHeaders(t *testing.T) { + mockUserHeadersToSerialize := http.Header{ + "Mock-Header-One": {"1", "1.5"}, + "Mock-Header-Two": {"2"}, + "Mock-Header-Three": {"3"}, + } + + mockHeaders := []Header{ + {Name: "One", Value: "1"}, + {Name: "Cf-Two", Value: "cf-value-1"}, + {Name: "Cf-Two", Value: "cf-value-2"}, + {Name: RequestUserHeadersField, Value: SerializeHeaders(mockUserHeadersToSerialize)}, + } + + expectedHeaders := []Header{ + {Name: "Mock-Header-One", Value: "1"}, + {Name: "Mock-Header-One", Value: "1.5"}, + {Name: "Mock-Header-Two", Value: "2"}, + {Name: "Mock-Header-Three", Value: "3"}, + } + parsedHeaders, err := ParseUserHeaders(RequestUserHeadersField, mockHeaders) + assert.NoError(t, err) + assert.ElementsMatch(t, expectedHeaders, parsedHeaders) +} + +func TestParseHeadersNoSerializedHeader(t *testing.T) { + mockHeaders := []Header{ + {Name: "One", Value: "1"}, + {Name: "Cf-Two", Value: "cf-value-1"}, + {Name: "Cf-Two", Value: "cf-value-2"}, + } + + _, err := ParseUserHeaders(RequestUserHeadersField, mockHeaders) + assert.EqualError(t, err, fmt.Sprintf("%s header not found", RequestUserHeadersField)) +} + +func TestIsControlHeader(t *testing.T) { + controlHeaders := []string{ + // Anything that begins with cf- + "cf-sample-header", + "CF-SAMPLE-HEADER", + "Cf-Sample-Header", + + // Any http2 pseudoheader + ":sample-pseudo-header", + + // content-length is a special case, it has to be there + // for some requests to work (per the HTTP2 spec) + "content-length", + } + + for _, header := range controlHeaders { + assert.True(t, IsControlHeader(header)) + } +} + +func TestIsNotControlHeader(t *testing.T) { + notControlHeaders := []string{ + "Mock-header", + "Another-sample-header", + } + + for _, header := range notControlHeaders { + assert.False(t, IsControlHeader(header)) + } +} + +func TestH1ResponseToH2ResponseHeaders(t *testing.T) { + mockHeaders := http.Header{ + "User-header-one": {""}, + "User-header-two": {"1", "2"}, + "cf-header": {"cf-value"}, + "Content-Length": {"123"}, + } + mockResponse := http.Response{ + StatusCode: 200, + Header: mockHeaders, + } + + headers := H1ResponseToH2ResponseHeaders(&mockResponse) + + serializedHeadersIndex := -1 + for i, header := range headers { + if header.Name == ResponseUserHeadersField { + serializedHeadersIndex = i + break + } + } + assert.NotEqual(t, -1, serializedHeadersIndex) + actualControlHeaders := append( + headers[:serializedHeadersIndex], + headers[serializedHeadersIndex+1:]..., + ) + expectedControlHeaders := []Header{ + {Name: ":status", Value: "200"}, + {Name: "content-length", Value: "123"}, + } + + assert.ElementsMatch(t, expectedControlHeaders, actualControlHeaders) + + actualUserHeaders, err := DeserializeHeaders(headers[serializedHeadersIndex].Value) + expectedUserHeaders := []Header{ + {Name: "User-header-one", Value: ""}, + {Name: "User-header-two", Value: "1"}, + {Name: "User-header-two", Value: "2"}, + } + assert.NoError(t, err) + assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders) +} From 464bb5304997e142a6d15c1a32dc796557c6373f Mon Sep 17 00:00:00 2001 From: Rueian Date: Tue, 25 Feb 2020 01:06:19 +0800 Subject: [PATCH 005/100] perf(cloudflared): reuse memory from buffer pool to get better throughput (#161) * perf(cloudflared): reuse memory from buffer pool to get better throughput https://github.com/cloudflare/cloudflared/issues/160 --- buffer/pool.go | 29 +++++++++++++++++++++++++++++ origin/supervisor.go | 10 +++++++--- origin/tunnel.go | 14 ++++++++++++-- originservice/originservice.go | 23 ++++++++++++++++------- 4 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 buffer/pool.go diff --git a/buffer/pool.go b/buffer/pool.go new file mode 100644 index 00000000..3265283f --- /dev/null +++ b/buffer/pool.go @@ -0,0 +1,29 @@ +package buffer + +import ( + "sync" +) + +type Pool struct { + // A Pool must not be copied after first use. + // https://golang.org/pkg/sync/#Pool + buffers sync.Pool +} + +func NewPool(bufferSize int) *Pool { + return &Pool{ + buffers: sync.Pool{ + New: func() interface{} { + return make([]byte, bufferSize) + }, + }, + } +} + +func (p *Pool) Get() []byte { + return p.buffers.Get().([]byte) +} + +func (p *Pool) Put(buf []byte) { + p.buffers.Put(buf) +} diff --git a/origin/supervisor.go b/origin/supervisor.go index b8df583f..a1c78571 100644 --- a/origin/supervisor.go +++ b/origin/supervisor.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/sirupsen/logrus" + "github.com/cloudflare/cloudflared/buffer" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/edgediscovery" "github.com/cloudflare/cloudflared/h2mux" @@ -62,6 +63,8 @@ type Supervisor struct { eventDigestLock *sync.RWMutex eventDigest []byte + + bufferPool *buffer.Pool } type resolveResult struct { @@ -96,6 +99,7 @@ func NewSupervisor(config *TunnelConfig, u uuid.UUID) (*Supervisor, error) { logger: config.Logger.WithField("subsystem", "supervisor"), jwtLock: &sync.RWMutex{}, eventDigestLock: &sync.RWMutex{}, + bufferPool: buffer.NewPool(512 * 1024), }, nil } @@ -230,7 +234,7 @@ func (s *Supervisor) startFirstTunnel(ctx context.Context, connectedSignal *sign return } - err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID) + err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID, s.bufferPool) // If the first tunnel disconnects, keep restarting it. edgeErrors := 0 for s.unusedIPs() { @@ -253,7 +257,7 @@ func (s *Supervisor) startFirstTunnel(ctx context.Context, connectedSignal *sign return } } - err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID) + err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID, s.bufferPool) } } @@ -272,7 +276,7 @@ func (s *Supervisor) startTunnel(ctx context.Context, index int, connectedSignal if err != nil { return } - err = ServeTunnelLoop(ctx, s, s.config, addr, uint8(index), connectedSignal, s.cloudflaredUUID) + err = ServeTunnelLoop(ctx, s, s.config, addr, uint8(index), connectedSignal, s.cloudflaredUUID, s.bufferPool) } func (s *Supervisor) newConnectedTunnelSignal(index int) *signal.Signal { diff --git a/origin/tunnel.go b/origin/tunnel.go index d5ba4d71..991d95c1 100644 --- a/origin/tunnel.go +++ b/origin/tunnel.go @@ -20,6 +20,7 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" + "github.com/cloudflare/cloudflared/buffer" "github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/h2mux" @@ -178,6 +179,7 @@ func ServeTunnelLoop(ctx context.Context, connectionID uint8, connectedSignal *signal.Signal, u uuid.UUID, + bufferPool *buffer.Pool, ) error { connectionLogger := config.Logger.WithField("connectionID", connectionID) config.Metrics.incrementHaConnections() @@ -201,6 +203,7 @@ func ServeTunnelLoop(ctx context.Context, connectedFuse, &backoff, u, + bufferPool, ) if recoverable { if duration, ok := backoff.GetBackoffDuration(ctx); ok { @@ -223,6 +226,7 @@ func ServeTunnel( connectedFuse *h2mux.BooleanFuse, backoff *BackoffHandler, u uuid.UUID, + bufferPool *buffer.Pool, ) (err error, recoverable bool) { // Treat panics as recoverable errors defer func() { @@ -243,7 +247,7 @@ func ServeTunnel( tags["ha"] = connectionTag // Returns error from parsing the origin URL or handshake errors - handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr, connectionID) + handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr, connectionID, bufferPool) if err != nil { errLog := logger.WithError(err) switch err.(type) { @@ -500,6 +504,8 @@ type TunnelHandler struct { connectionID string logger *log.Logger noChunkedEncoding bool + + bufferPool *buffer.Pool } // NewTunnelHandler returns a TunnelHandler, origin LAN IP and error @@ -507,6 +513,7 @@ func NewTunnelHandler(ctx context.Context, config *TunnelConfig, addr *net.TCPAddr, connectionID uint8, + bufferPool *buffer.Pool, ) (*TunnelHandler, string, error) { originURL, err := validation.ValidateUrl(config.OriginUrl) if err != nil { @@ -522,6 +529,7 @@ func NewTunnelHandler(ctx context.Context, connectionID: uint8ToString(connectionID), logger: config.Logger, noChunkedEncoding: config.NoChunkedEncoding, + bufferPool: bufferPool, } if h.httpClient == nil { h.httpClient = http.DefaultTransport @@ -642,7 +650,9 @@ func (h *TunnelHandler) serveHTTP(stream *h2mux.MuxedStream, req *http.Request) } else { // Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream // compression generates dictionary on first write - io.CopyBuffer(stream, response.Body, make([]byte, 512*1024)) + buf := h.bufferPool.Get() + defer h.bufferPool.Put(buf) + io.CopyBuffer(stream, response.Body, buf) } return response, nil } diff --git a/originservice/originservice.go b/originservice/originservice.go index 3cd0af53..2277385f 100644 --- a/originservice/originservice.go +++ b/originservice/originservice.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + "github.com/cloudflare/cloudflared/buffer" "github.com/cloudflare/cloudflared/h2mux" "github.com/cloudflare/cloudflared/hello" "github.com/cloudflare/cloudflared/log" @@ -33,6 +34,7 @@ type HTTPService struct { client http.RoundTripper originURL *url.URL chunkedEncoding bool + bufferPool *buffer.Pool } func NewHTTPService(transport http.RoundTripper, url *url.URL, chunkedEncoding bool) OriginService { @@ -40,6 +42,7 @@ func NewHTTPService(transport http.RoundTripper, url *url.URL, chunkedEncoding b client: transport, originURL: url, chunkedEncoding: chunkedEncoding, + bufferPool: buffer.NewPool(512 * 1024), } } @@ -71,7 +74,9 @@ func (hc *HTTPService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*htt } else { // Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream // compression generates dictionary on first write - io.CopyBuffer(stream, resp.Body, make([]byte, 512*1024)) + buf := hc.bufferPool.Get() + defer hc.bufferPool.Put(buf) + io.CopyBuffer(stream, resp.Body, buf) } return resp, nil } @@ -142,10 +147,11 @@ func (wsc *WebsocketService) Shutdown() { // HelloWorldService talks to the hello world example origin type HelloWorldService struct { - client http.RoundTripper - listener net.Listener - originURL *url.URL - shutdownC chan struct{} + client http.RoundTripper + listener net.Listener + originURL *url.URL + shutdownC chan struct{} + bufferPool *buffer.Pool } func NewHelloWorldService(transport http.RoundTripper) (OriginService, error) { @@ -164,7 +170,8 @@ func NewHelloWorldService(transport http.RoundTripper) (OriginService, error) { Scheme: "https", Host: listener.Addr().String(), }, - shutdownC: shutdownC, + shutdownC: shutdownC, + bufferPool: buffer.NewPool(512 * 1024), }, nil } @@ -184,7 +191,9 @@ func (hwc *HelloWorldService) Proxy(stream *h2mux.MuxedStream, req *http.Request // Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream // compression generates dictionary on first write - io.CopyBuffer(stream, resp.Body, make([]byte, 512*1024)) + buf := hwc.bufferPool.Get() + defer hwc.bufferPool.Put(buf) + io.CopyBuffer(stream, resp.Body, buf) return resp, nil } From a5f67091bf90f249596d90c85cfc4b1f75135c93 Mon Sep 17 00:00:00 2001 From: Cameron Steel Date: Tue, 25 Feb 2020 04:08:14 +1100 Subject: [PATCH 006/100] Tweak HTTP host header. Fixes #107 (#168) --- tunneldns/https_upstream.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunneldns/https_upstream.go b/tunneldns/https_upstream.go index ac9b60ec..57f51deb 100644 --- a/tunneldns/https_upstream.go +++ b/tunneldns/https_upstream.go @@ -83,7 +83,7 @@ func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) { } req.Header.Add("Content-Type", "application/dns-message") - req.Host = u.endpoint.Hostname() + req.Host = u.endpoint.Host resp, err := u.client.Do(req) if err != nil { From afc2cd38e1f34caf095d254c868aa9829f8efe84 Mon Sep 17 00:00:00 2001 From: Areg Harutyunyan Date: Tue, 25 Feb 2020 23:45:48 +0000 Subject: [PATCH 007/100] TUN-2765: Add list of features to tunnelrpc --- tunnelrpc/tunnelrpc.capnp | 2 + tunnelrpc/tunnelrpc.capnp.go | 723 +++++++++++++---------------------- 2 files changed, 278 insertions(+), 447 deletions(-) diff --git a/tunnelrpc/tunnelrpc.capnp b/tunnelrpc/tunnelrpc.capnp index 779f1b36..9ef5be43 100644 --- a/tunnelrpc/tunnelrpc.capnp +++ b/tunnelrpc/tunnelrpc.capnp @@ -50,6 +50,8 @@ struct RegistrationOptions { uuid @11 :Text; # number of previous attempts to send RegisterTunnel/ReconnectTunnel numPreviousAttempts @12 :UInt8; + # Set of features this cloudflared knows it supports + features @13 :List(Text); } struct CapnpConnectParameters { diff --git a/tunnelrpc/tunnelrpc.capnp.go b/tunnelrpc/tunnelrpc.capnp.go index 1b29410e..51876fb7 100644 --- a/tunnelrpc/tunnelrpc.capnp.go +++ b/tunnelrpc/tunnelrpc.capnp.go @@ -106,11 +106,6 @@ func (s Authentication_List) At(i int) Authentication { return Authentication{s. func (s Authentication_List) Set(i int, v Authentication) error { return s.List.SetStruct(i, v.Struct) } -func (s Authentication_List) String() string { - str, _ := text.MarshalList(0xc082ef6e0d42ed1d, s.List) - return str -} - // Authentication_Promise is a wrapper for a Authentication promised by a client call. type Authentication_Promise struct{ *capnp.Pipeline } @@ -273,11 +268,6 @@ func (s TunnelRegistration_List) Set(i int, v TunnelRegistration) error { return s.List.SetStruct(i, v.Struct) } -func (s TunnelRegistration_List) String() string { - str, _ := text.MarshalList(0xf41a0f001ad49e46, s.List) - return str -} - // TunnelRegistration_Promise is a wrapper for a TunnelRegistration promised by a client call. type TunnelRegistration_Promise struct{ *capnp.Pipeline } @@ -292,12 +282,12 @@ type RegistrationOptions struct{ capnp.Struct } const RegistrationOptions_TypeID = 0xc793e50592935b4a func NewRegistrationOptions(s *capnp.Segment) (RegistrationOptions, error) { - st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 7}) + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 8}) return RegistrationOptions{st}, err } func NewRootRegistrationOptions(s *capnp.Segment) (RegistrationOptions, error) { - st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 7}) + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 8}) return RegistrationOptions{st}, err } @@ -498,12 +488,37 @@ func (s RegistrationOptions) SetNumPreviousAttempts(v uint8) { s.Struct.SetUint8(4, v) } +func (s RegistrationOptions) Features() (capnp.TextList, error) { + p, err := s.Struct.Ptr(7) + return capnp.TextList{List: p.List()}, err +} + +func (s RegistrationOptions) HasFeatures() bool { + p, err := s.Struct.Ptr(7) + return p.IsValid() || err != nil +} + +func (s RegistrationOptions) SetFeatures(v capnp.TextList) error { + return s.Struct.SetPtr(7, v.List.ToPtr()) +} + +// NewFeatures sets the features field to a newly +// allocated capnp.TextList, preferring placement in s's segment. +func (s RegistrationOptions) NewFeatures(n int32) (capnp.TextList, error) { + l, err := capnp.NewTextList(s.Struct.Segment(), n) + if err != nil { + return capnp.TextList{}, err + } + err = s.Struct.SetPtr(7, l.List.ToPtr()) + return l, err +} + // RegistrationOptions_List is a list of RegistrationOptions. type RegistrationOptions_List struct{ capnp.List } // NewRegistrationOptions creates a new list of RegistrationOptions. func NewRegistrationOptions_List(s *capnp.Segment, sz int32) (RegistrationOptions_List, error) { - l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 7}, sz) + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 8}, sz) return RegistrationOptions_List{l}, err } @@ -515,11 +530,6 @@ func (s RegistrationOptions_List) Set(i int, v RegistrationOptions) error { return s.List.SetStruct(i, v.Struct) } -func (s RegistrationOptions_List) String() string { - str, _ := text.MarshalList(0xc793e50592935b4a, s.List) - return str -} - // RegistrationOptions_Promise is a wrapper for a RegistrationOptions promised by a client call. type RegistrationOptions_Promise struct{ *capnp.Pipeline } @@ -669,11 +679,6 @@ func (s CapnpConnectParameters_List) Set(i int, v CapnpConnectParameters) error return s.List.SetStruct(i, v.Struct) } -func (s CapnpConnectParameters_List) String() string { - str, _ := text.MarshalList(0xa78f37418c1077c8, s.List) - return str -} - // CapnpConnectParameters_Promise is a wrapper for a CapnpConnectParameters promised by a client call. type CapnpConnectParameters_Promise struct{ *capnp.Pipeline } @@ -732,9 +737,6 @@ func (s ConnectResult_result) Which() ConnectResult_result_Which { return ConnectResult_result_Which(s.Struct.Uint16(0)) } func (s ConnectResult_result) Err() (ConnectError, error) { - if s.Struct.Uint16(0) != 0 { - panic("Which() != err") - } p, err := s.Struct.Ptr(0) return ConnectError{Struct: p.Struct()}, err } @@ -765,9 +767,6 @@ func (s ConnectResult_result) NewErr() (ConnectError, error) { } func (s ConnectResult_result) Success() (ConnectSuccess, error) { - if s.Struct.Uint16(0) != 1 { - panic("Which() != success") - } p, err := s.Struct.Ptr(0) return ConnectSuccess{Struct: p.Struct()}, err } @@ -810,11 +809,6 @@ func (s ConnectResult_List) At(i int) ConnectResult { return ConnectResult{s.Lis func (s ConnectResult_List) Set(i int, v ConnectResult) error { return s.List.SetStruct(i, v.Struct) } -func (s ConnectResult_List) String() string { - str, _ := text.MarshalList(0xff8d9848747c956a, s.List) - return str -} - // ConnectResult_Promise is a wrapper for a ConnectResult promised by a client call. type ConnectResult_Promise struct{ *capnp.Pipeline } @@ -916,11 +910,6 @@ func (s ConnectError_List) At(i int) ConnectError { return ConnectError{s.List.S func (s ConnectError_List) Set(i int, v ConnectError) error { return s.List.SetStruct(i, v.Struct) } -func (s ConnectError_List) String() string { - str, _ := text.MarshalList(0xb14ce48f4e2abb0d, s.List) - return str -} - // ConnectError_Promise is a wrapper for a ConnectError promised by a client call. type ConnectError_Promise struct{ *capnp.Pipeline } @@ -1011,11 +1000,6 @@ func (s ConnectSuccess_List) At(i int) ConnectSuccess { return ConnectSuccess{s. func (s ConnectSuccess_List) Set(i int, v ConnectSuccess) error { return s.List.SetStruct(i, v.Struct) } -func (s ConnectSuccess_List) String() string { - str, _ := text.MarshalList(0x8407e070e0d52605, s.List) - return str -} - // ConnectSuccess_Promise is a wrapper for a ConnectSuccess promised by a client call. type ConnectSuccess_Promise struct{ *capnp.Pipeline } @@ -1174,11 +1158,6 @@ func (s ClientConfig_List) At(i int) ClientConfig { return ClientConfig{s.List.S func (s ClientConfig_List) Set(i int, v ClientConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s ClientConfig_List) String() string { - str, _ := text.MarshalList(0xf0a143f1c95a678e, s.List) - return str -} - // ClientConfig_Promise is a wrapper for a ClientConfig promised by a client call. type ClientConfig_Promise struct{ *capnp.Pipeline } @@ -1259,11 +1238,6 @@ func (s SupervisorConfig_List) Set(i int, v SupervisorConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s SupervisorConfig_List) String() string { - str, _ := text.MarshalList(0xf7f49b3f779ae258, s.List) - return str -} - // SupervisorConfig_Promise is a wrapper for a SupervisorConfig promised by a client call. type SupervisorConfig_Promise struct{ *capnp.Pipeline } @@ -1365,11 +1339,6 @@ func (s EdgeConnectionConfig_List) Set(i int, v EdgeConnectionConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s EdgeConnectionConfig_List) String() string { - str, _ := text.MarshalList(0xc744e349009087aa, s.List) - return str -} - // EdgeConnectionConfig_Promise is a wrapper for a EdgeConnectionConfig promised by a client call. type EdgeConnectionConfig_Promise struct{ *capnp.Pipeline } @@ -1452,9 +1421,6 @@ func (s ReverseProxyConfig_originConfig) Which() ReverseProxyConfig_originConfig return ReverseProxyConfig_originConfig_Which(s.Struct.Uint16(0)) } func (s ReverseProxyConfig_originConfig) Http() (HTTPOriginConfig, error) { - if s.Struct.Uint16(0) != 0 { - panic("Which() != http") - } p, err := s.Struct.Ptr(1) return HTTPOriginConfig{Struct: p.Struct()}, err } @@ -1485,9 +1451,6 @@ func (s ReverseProxyConfig_originConfig) NewHttp() (HTTPOriginConfig, error) { } func (s ReverseProxyConfig_originConfig) Websocket() (WebSocketOriginConfig, error) { - if s.Struct.Uint16(0) != 1 { - panic("Which() != websocket") - } p, err := s.Struct.Ptr(1) return WebSocketOriginConfig{Struct: p.Struct()}, err } @@ -1518,9 +1481,6 @@ func (s ReverseProxyConfig_originConfig) NewWebsocket() (WebSocketOriginConfig, } func (s ReverseProxyConfig_originConfig) HelloWorld() (HelloWorldOriginConfig, error) { - if s.Struct.Uint16(0) != 2 { - panic("Which() != helloWorld") - } p, err := s.Struct.Ptr(1) return HelloWorldOriginConfig{Struct: p.Struct()}, err } @@ -1591,11 +1551,6 @@ func (s ReverseProxyConfig_List) Set(i int, v ReverseProxyConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s ReverseProxyConfig_List) String() string { - str, _ := text.MarshalList(0xc766a92976e389c4, s.List) - return str -} - // ReverseProxyConfig_Promise is a wrapper for a ReverseProxyConfig promised by a client call. type ReverseProxyConfig_Promise struct{ *capnp.Pipeline } @@ -1735,11 +1690,6 @@ func (s WebSocketOriginConfig_List) Set(i int, v WebSocketOriginConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s WebSocketOriginConfig_List) String() string { - str, _ := text.MarshalList(0xf9c895683ed9ac4c, s.List) - return str -} - // WebSocketOriginConfig_Promise is a wrapper for a WebSocketOriginConfig promised by a client call. type WebSocketOriginConfig_Promise struct{ *capnp.Pipeline } @@ -1917,11 +1867,6 @@ func (s HTTPOriginConfig_List) Set(i int, v HTTPOriginConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s HTTPOriginConfig_List) String() string { - str, _ := text.MarshalList(0xe4a6a1bc139211b4, s.List) - return str -} - // HTTPOriginConfig_Promise is a wrapper for a HTTPOriginConfig promised by a client call. type HTTPOriginConfig_Promise struct{ *capnp.Pipeline } @@ -2020,11 +1965,6 @@ func (s DoHProxyConfig_List) At(i int) DoHProxyConfig { return DoHProxyConfig{s. func (s DoHProxyConfig_List) Set(i int, v DoHProxyConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s DoHProxyConfig_List) String() string { - str, _ := text.MarshalList(0xb167b0bebe562cd0, s.List) - return str -} - // DoHProxyConfig_Promise is a wrapper for a DoHProxyConfig promised by a client call. type DoHProxyConfig_Promise struct{ *capnp.Pipeline } @@ -2075,11 +2015,6 @@ func (s HelloWorldOriginConfig_List) Set(i int, v HelloWorldOriginConfig) error return s.List.SetStruct(i, v.Struct) } -func (s HelloWorldOriginConfig_List) String() string { - str, _ := text.MarshalList(0x8891f360e47c30d3, s.List) - return str -} - // HelloWorldOriginConfig_Promise is a wrapper for a HelloWorldOriginConfig promised by a client call. type HelloWorldOriginConfig_Promise struct{ *capnp.Pipeline } @@ -2164,11 +2099,6 @@ func (s Tag_List) At(i int) Tag { return Tag{s.List.Struct(i)} } func (s Tag_List) Set(i int, v Tag) error { return s.List.SetStruct(i, v.Struct) } -func (s Tag_List) String() string { - str, _ := text.MarshalList(0xcbd96442ae3bb01a, s.List) - return str -} - // Tag_Promise is a wrapper for a Tag promised by a client call. type Tag_Promise struct{ *capnp.Pipeline } @@ -2294,11 +2224,6 @@ func (s ServerInfo_List) At(i int) ServerInfo { return ServerInfo{s.List.Struct( func (s ServerInfo_List) Set(i int, v ServerInfo) error { return s.List.SetStruct(i, v.Struct) } -func (s ServerInfo_List) String() string { - str, _ := text.MarshalList(0xf2c68e2547ec3866, s.List) - return str -} - // ServerInfo_Promise is a wrapper for a ServerInfo promised by a client call. type ServerInfo_Promise struct{ *capnp.Pipeline } @@ -2382,11 +2307,6 @@ func (s UseConfigurationResult_List) Set(i int, v UseConfigurationResult) error return s.List.SetStruct(i, v.Struct) } -func (s UseConfigurationResult_List) String() string { - str, _ := text.MarshalList(0xd58a254e7a792b87, s.List) - return str -} - // UseConfigurationResult_Promise is a wrapper for a UseConfigurationResult promised by a client call. type UseConfigurationResult_Promise struct{ *capnp.Pipeline } @@ -2451,9 +2371,6 @@ func (s FailedConfig_config) Which() FailedConfig_config_Which { return FailedConfig_config_Which(s.Struct.Uint16(0)) } func (s FailedConfig_config) Supervisor() (SupervisorConfig, error) { - if s.Struct.Uint16(0) != 0 { - panic("Which() != supervisor") - } p, err := s.Struct.Ptr(0) return SupervisorConfig{Struct: p.Struct()}, err } @@ -2484,9 +2401,6 @@ func (s FailedConfig_config) NewSupervisor() (SupervisorConfig, error) { } func (s FailedConfig_config) EdgeConnection() (EdgeConnectionConfig, error) { - if s.Struct.Uint16(0) != 1 { - panic("Which() != edgeConnection") - } p, err := s.Struct.Ptr(0) return EdgeConnectionConfig{Struct: p.Struct()}, err } @@ -2517,9 +2431,6 @@ func (s FailedConfig_config) NewEdgeConnection() (EdgeConnectionConfig, error) { } func (s FailedConfig_config) Doh() (DoHProxyConfig, error) { - if s.Struct.Uint16(0) != 2 { - panic("Which() != doh") - } p, err := s.Struct.Ptr(0) return DoHProxyConfig{Struct: p.Struct()}, err } @@ -2550,9 +2461,6 @@ func (s FailedConfig_config) NewDoh() (DoHProxyConfig, error) { } func (s FailedConfig_config) ReverseProxy() (ReverseProxyConfig, error) { - if s.Struct.Uint16(0) != 3 { - panic("Which() != reverseProxy") - } p, err := s.Struct.Ptr(0) return ReverseProxyConfig{Struct: p.Struct()}, err } @@ -2614,11 +2522,6 @@ func (s FailedConfig_List) At(i int) FailedConfig { return FailedConfig{s.List.S func (s FailedConfig_List) Set(i int, v FailedConfig) error { return s.List.SetStruct(i, v.Struct) } -func (s FailedConfig_List) String() string { - str, _ := text.MarshalList(0xea20b390b257d1a5, s.List) - return str -} - // FailedConfig_Promise is a wrapper for a FailedConfig promised by a client call. type FailedConfig_Promise struct{ *capnp.Pipeline } @@ -2757,11 +2660,6 @@ func (s AuthenticateResponse_List) Set(i int, v AuthenticateResponse) error { return s.List.SetStruct(i, v.Struct) } -func (s AuthenticateResponse_List) String() string { - str, _ := text.MarshalList(0x82c325a07ad22a65, s.List) - return str -} - // AuthenticateResponse_Promise is a wrapper for a AuthenticateResponse promised by a client call. type AuthenticateResponse_Promise struct{ *capnp.Pipeline } @@ -3155,11 +3053,6 @@ func (s TunnelServer_registerTunnel_Params_List) Set(i int, v TunnelServer_regis return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_registerTunnel_Params_List) String() string { - str, _ := text.MarshalList(0xb70431c0dc014915, s.List) - return str -} - // TunnelServer_registerTunnel_Params_Promise is a wrapper for a TunnelServer_registerTunnel_Params promised by a client call. type TunnelServer_registerTunnel_Params_Promise struct{ *capnp.Pipeline } @@ -3239,11 +3132,6 @@ func (s TunnelServer_registerTunnel_Results_List) Set(i int, v TunnelServer_regi return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_registerTunnel_Results_List) String() string { - str, _ := text.MarshalList(0xf2c122394f447e8e, s.List) - return str -} - // TunnelServer_registerTunnel_Results_Promise is a wrapper for a TunnelServer_registerTunnel_Results promised by a client call. type TunnelServer_registerTunnel_Results_Promise struct{ *capnp.Pipeline } @@ -3298,11 +3186,6 @@ func (s TunnelServer_getServerInfo_Params_List) Set(i int, v TunnelServer_getSer return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_getServerInfo_Params_List) String() string { - str, _ := text.MarshalList(0xdc3ed6801961e502, s.List) - return str -} - // TunnelServer_getServerInfo_Params_Promise is a wrapper for a TunnelServer_getServerInfo_Params promised by a client call. type TunnelServer_getServerInfo_Params_Promise struct{ *capnp.Pipeline } @@ -3378,11 +3261,6 @@ func (s TunnelServer_getServerInfo_Results_List) Set(i int, v TunnelServer_getSe return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_getServerInfo_Results_List) String() string { - str, _ := text.MarshalList(0xe3e37d096a5b564e, s.List) - return str -} - // TunnelServer_getServerInfo_Results_Promise is a wrapper for a TunnelServer_getServerInfo_Results promised by a client call. type TunnelServer_getServerInfo_Results_Promise struct{ *capnp.Pipeline } @@ -3445,11 +3323,6 @@ func (s TunnelServer_unregisterTunnel_Params_List) Set(i int, v TunnelServer_unr return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_unregisterTunnel_Params_List) String() string { - str, _ := text.MarshalList(0x9b87b390babc2ccf, s.List) - return str -} - // TunnelServer_unregisterTunnel_Params_Promise is a wrapper for a TunnelServer_unregisterTunnel_Params promised by a client call. type TunnelServer_unregisterTunnel_Params_Promise struct{ *capnp.Pipeline } @@ -3500,11 +3373,6 @@ func (s TunnelServer_unregisterTunnel_Results_List) Set(i int, v TunnelServer_un return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_unregisterTunnel_Results_List) String() string { - str, _ := text.MarshalList(0xa29a916d4ebdd894, s.List) - return str -} - // TunnelServer_unregisterTunnel_Results_Promise is a wrapper for a TunnelServer_unregisterTunnel_Results promised by a client call. type TunnelServer_unregisterTunnel_Results_Promise struct{ *capnp.Pipeline } @@ -3580,11 +3448,6 @@ func (s TunnelServer_connect_Params_List) Set(i int, v TunnelServer_connect_Para return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_connect_Params_List) String() string { - str, _ := text.MarshalList(0xa766b24d4fe5da35, s.List) - return str -} - // TunnelServer_connect_Params_Promise is a wrapper for a TunnelServer_connect_Params promised by a client call. type TunnelServer_connect_Params_Promise struct{ *capnp.Pipeline } @@ -3664,11 +3527,6 @@ func (s TunnelServer_connect_Results_List) Set(i int, v TunnelServer_connect_Res return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_connect_Results_List) String() string { - str, _ := text.MarshalList(0xfeac5c8f4899ef7c, s.List) - return str -} - // TunnelServer_connect_Results_Promise is a wrapper for a TunnelServer_connect_Results promised by a client call. type TunnelServer_connect_Results_Promise struct{ *capnp.Pipeline } @@ -3781,11 +3639,6 @@ func (s TunnelServer_authenticate_Params_List) Set(i int, v TunnelServer_authent return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_authenticate_Params_List) String() string { - str, _ := text.MarshalList(0x85c8cea1ab1894f3, s.List) - return str -} - // TunnelServer_authenticate_Params_Promise is a wrapper for a TunnelServer_authenticate_Params promised by a client call. type TunnelServer_authenticate_Params_Promise struct{ *capnp.Pipeline } @@ -3865,11 +3718,6 @@ func (s TunnelServer_authenticate_Results_List) Set(i int, v TunnelServer_authen return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_authenticate_Results_List) String() string { - str, _ := text.MarshalList(0xfc5edf80e39c0796, s.List) - return str -} - // TunnelServer_authenticate_Results_Promise is a wrapper for a TunnelServer_authenticate_Results promised by a client call. type TunnelServer_authenticate_Results_Promise struct{ *capnp.Pipeline } @@ -3996,11 +3844,6 @@ func (s TunnelServer_reconnectTunnel_Params_List) Set(i int, v TunnelServer_reco return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_reconnectTunnel_Params_List) String() string { - str, _ := text.MarshalList(0xa353a3556df74984, s.List) - return str -} - // TunnelServer_reconnectTunnel_Params_Promise is a wrapper for a TunnelServer_reconnectTunnel_Params promised by a client call. type TunnelServer_reconnectTunnel_Params_Promise struct{ *capnp.Pipeline } @@ -4080,11 +3923,6 @@ func (s TunnelServer_reconnectTunnel_Results_List) Set(i int, v TunnelServer_rec return s.List.SetStruct(i, v.Struct) } -func (s TunnelServer_reconnectTunnel_Results_List) String() string { - str, _ := text.MarshalList(0xd4d18de97bb12de3, s.List) - return str -} - // TunnelServer_reconnectTunnel_Results_Promise is a wrapper for a TunnelServer_reconnectTunnel_Results promised by a client call. type TunnelServer_reconnectTunnel_Results_Promise struct{ *capnp.Pipeline } @@ -4229,11 +4067,6 @@ func (s ClientService_useConfiguration_Params_List) Set(i int, v ClientService_u return s.List.SetStruct(i, v.Struct) } -func (s ClientService_useConfiguration_Params_List) String() string { - str, _ := text.MarshalList(0xb9d4ef45c2b5fc5b, s.List) - return str -} - // ClientService_useConfiguration_Params_Promise is a wrapper for a ClientService_useConfiguration_Params promised by a client call. type ClientService_useConfiguration_Params_Promise struct{ *capnp.Pipeline } @@ -4313,11 +4146,6 @@ func (s ClientService_useConfiguration_Results_List) Set(i int, v ClientService_ return s.List.SetStruct(i, v.Struct) } -func (s ClientService_useConfiguration_Results_List) String() string { - str, _ := text.MarshalList(0x91f7a001ca145b9d, s.List) - return str -} - // ClientService_useConfiguration_Results_Promise is a wrapper for a ClientService_useConfiguration_Results promised by a client call. type ClientService_useConfiguration_Results_Promise struct{ *capnp.Pipeline } @@ -4330,253 +4158,254 @@ func (p ClientService_useConfiguration_Results_Promise) Result() UseConfiguratio return UseConfigurationResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)} } -const schema_db8274f9144abc7e = "x\xda\xccZ}\x90\x14ez\x7f\x9e\xeeY\x9a\x85]" + - "f:=\x96+\x02#[\x12\x85\x13\xa2\"\x89\xb7I" + - "n\xf6\x03\xb8]\x8e\x8f\xe9\x9d\x05\xbd\x95K\xd1;\xf3" + - "\xeen/=\xddCw\x0f\xb0\x1b8\x84\xc2xl\xe4" + - "\x04OR\xe2\xe1\x95\xa0\xc4\x8fp9\xf0\xa0\xa2\x06-" + - "M\xee\"\xe6\x8ex\\ %\x17\xadS\xd1J\x9d\xa5" + - "eP)c\xca\xb3SO\x7f\xef\xec\xba\x80I\xaa\xee" + - "\x1f\x98z\xfay?\x9e\xaf\xdf\xf3\xf1\xee\x8dC\x93\x9a" + - "\xb9\x9bj\xaeK\x02\xc8\x87k&8l\xce/\x87\x1e" + - "\x9e\xf5\x8f\xdb@\x9e\x8a\xe8|\xfb\xf8\x92\xf4\xa7\xf6\xb6" + - "\x7f\x87\x1a^\x00\x98?(\x0c\xa1\xb4S\x10\x00\xa4\x1d" + - "\xc2\x7f\x00:5\xbf\x7f\xe6\x8d\xf2\x1b\xc2v\x10\xa7\xc6" + - "\x999b.M\\\x82\xd2\xd6\x89\xc4\xbcy\xe2\x06@" + - "\xe7OJ\xaf\x1c\xf8\xc3=?#f.b\x06\x9c\xff" + - "\xce\xc4!\x94>u9/L\\\x01\xe8|t\x7f\xc3" + - "\xdf\xec\xff\x97\x13w\x81x\x1d\x82\x7fv}\xed\xaf\x10" + - "P\x9aY\xfb#@\xe7_o\xdc\xf4\xf6\x9a\x8fv\x7f" + - "g\xe4\xb9\x09\xe2{\xb1v\x18\xa5\xb3\xb5\x02\xf0\xceC" + - "w\xa4\xff\x19\x1f\xfed7\x88\xd7\xd36H\x9f\x8f\xd5" + - "N\xe2\x00\xa5\x93\xb5Y@\xe7\x95\x1b\x8e?\xbb\xeb\xc7" + - "w\x7f\x1f\xe4\xeb\x10\xc1[\xff~\xed\x7f\xd398\x89" + - "\x18\xce?\xfa\x95\xc4\x0f_\xf9\xbd\x1f\xb8\x0c\xce\xc1S" + - "\xb7=\xb5\xeb\xc7\xd7\xbc\x0b+9\x01\x13\x00\xf3gO" + - "2\x89w\xc1$\xd2\xc5\xfd\xaf>\xb7\xbc\xb4\xfb\xc1\x03" + - "\xde\xa5\xdd\xbd\xae\x98\xccq\x90p\xb6w|RZ\xf9" + - "H\xfe\x11_\x1c\xf7S\xed\xe4\x0fi\xe9\xf4\xc9\xb4t" + - "\xc1\xaf\xdeY\xb1\xec\xa9\xde\xc7|\x06\xf7\xa2\x9fN~" + - "\x8a\x18j\xeb\xe8\x1e'6\xa4\xeei\xf9\xa3{\x1f\xab" + - "\xb6J\x0dq\xce\xad\x1bFiQ\x1d\xfdl\xa9\xbb\x0d" + - "\x01\x9d\xe1\xaf>\xb7\xea\xa3\xbf\xb0\x9e\x04y.&\x9c" + - "\x9f\xec8\xb7~\xf6\x13\xbd/\xb9\xd7\xe6\x01\xe6?S" + - "\xffK\xda\xfad=\xa9\xb2\xfe\xef\xe7,\xbf\xf7\xed\xa5" + - "Gh\xeb\x98Y\xbcK\x94\xa64\xa1\xb4y\x0aYf" + - "p\x0aq\xff\xe2\x86U\xcf?\x7f\xb8\xefH\xf5E\\" + - "\x8b_\x95\\\x82\xd2\xdc$q\xcfN\x12\xf7\x15\x1d\xf8" + - "\xda\x0b7%\xfe.n\xc7\xd7\x93\xef\xd2\xe1\xe7]\x86" + - ";>;\xf6\x0f\x8b>8\xfdL\xdcB\xbbS\x1cY" + - "\xe8`\x8a\x04\xef\x1e\xc6\xd2kM\xcd\xcf\x83|=\xa2" + - "3\xb0g\x93\xdd\xfe\xc0N\x07V\xa2\x80\x1c\xc0\xfc\x93" + - "\xa9!\xda\xecl\x8a\xfck\xfa\xfb\xad\xf5\xfa\x07\xdb^" + - "\xa8rF\xf7\xd4\x05\xe2\x12\x94:D\xba\xda\"\xf1G" + - "\x80\x9f" + - "\xf4y\x9ca\x7f\xe3\xb3\xc4p\xc4e\x08\x83q,O" + - ";\xd5\xd8\x8a\xd2\x9b\x8dt\xde\xeb\x8dY\x98\xeb\xd8\x15" + - "]g\x9aYN\x14\xfe \xf8Y\x98WP\xcaz\xb9" + - "\xa9\xa5b\xf73\xddV\x0b\x8a\xcd:Y\xd6*\x1b\xba" + - "\xc5r\x88r\x8aO\x00$\x10@T\x06\x00\xe45<" + - "\xca\x1a\x87\"b\x9a\xd0ZT\x89\xd8\xcf\xa3ls(" + - "r\\\x9a\x10A\\\xd7\x08 k<\xca\x1b9D>" + - "Mx'V\xee\x03\x907\xf2(o\xe7\xd0)3\xb3" + - "\xa4\xe8L\x87\xa4\xbd\xc84\xb1\x0e8\xac\x03tLf" + - "\x9b\x83J\x8f\x06I\x16#\x0b\x03\x1bl\xac\x07\x0e\xeb" + - "\x01\x9d~\xa3bZ+u\x1bU\xad\x93\xf5\x9a\xcc\xc2" + - "~\x9c\x00\x1cN\x00\x1cO\xbc6C\xd7Y\xc1\xceW" + - "\x0a\x05fY\x00$\xd9\xc4P\xb2\xd9\x0f\x02\xc87\xf0" + - "(\xdf\x1a\x93l\x01Iv\x0b\x8fr3\x87\x8e\xc5\xcc" + - "\xf5\xcc\\j`A\xb1UC_\xae\xf0%\x16^\xbb" + - "\xa0\xa9L\xb7\xdb\x0cH\xea\xbdj\x1f\xa6\xa2P\x00\xc4" + - "\xd4\xf8\x17[\xb4Q\xb5lU\xef\xebr\xe9\xd9\x9c\xa1" + - "\xa9\x85A\xba]\x9d\xab\xc9\xe9M\xb4\x87xE7\x00" + - "r\xa2\xd8\x0a\x90U\xfbt\xc3dNQ\xb5\x0a$\x14" + - "\xf0\x05{K\x8f\xa2)z\x81\x85\x07M\x18}\x90w" + - "@\xde\x95c\x9e\x12\xb3\xf6\xb59\xc5T\xf8\x92%\xd7" + - "\x85\xfaX\xd4\x0d /\xe4Q\xce\xc5\xf4\xb1l\x09\x80" + - "\xbc\x94G\xf9\xf6\x98\xa5W\xb6\x02\xc89\x1e\xe5\xd5\x1c" + - ":\x86\xa9\xf6\xa9z\x1b\x03\xde\x8c\x1b\xcc\xb2u\xa5\xc4" + - "\x00 P\xd8\x16\xa3LJ\xb40\x15\xa1t\x95\xa6j" + - "F\x0b\xd0\xce4\xcd\xb8\xcd0\xb5\xe2\x0a\xef\x1c\x83\xb4" + - "\xed\x9a2\\&\x8cay\xd78$\xb7Z`\xf3*" + - "\x16\xf3\xd6UL\xd7\x90\xd7v2\xab\xa2\xd9\x16\x80\x9c" + - "\x08\xc5\xafo\x02\x90'\xf2(\xa79\xcc\x9a.\x03\xa6" + - "\"P\xaf\xba\xea\xc5t]\xd1M\xd6\xa7Z63=" + - "\xf2\xb5YRx\xc9\x8a\x1fH\xfe\x97\xe2Q\x9e\xc6\xa1" + - "\xd3g*\x05\x96c&\xaaFq\xb9\xa2\x1by\x9e\x15" + - "\xb0\x068\xac\x19\xdf\x93\x16+\xaa\xc6\x8a\x9et\xf3\x0a" + - "\x19\xf7\x7f\x8a\xde:\xc7\xf1\xc2\xb7;\x0a\xdfz\xfc\xdc" + - "\xf1\xe3w(\x8a\xdfz\xee\xb7\xce\xe8\x00\xae\xe7?s" + - "\xfc\x10\xa6\x88\xb0y\x94\xef\xa4\x88\xa8\x94I\xa7\x16\xf0" + - "\x86\x89\xa9\x08%}\xed\xb0b\x1fiZ\x87,+\x90" + - "\xa21\x15d{\x8fA(\x1a\xfd\x98\x8aJ\x19\x7f\x99" + - "\xc9\xd63\xd3b9H\x9a\xc6\xc6ALEY\xbfJ" + - "\xebS.W\xeb\x81\xa1\xc3U\xe3\xaf7Y\xc1\x83\x0c" + - "\x7fy.\xe3\x19-\x06\x87\xa4\xa3\xd5<\xca\xfd\xb1 " + - "a=\x00r\x91G\xb9\x1c\x0b\x92\xd2\x92H\x9b\"\x1f" + - "\xe0!EN\x99Gy\x137\x12\xe1\xd8z\xa6\xdb\x0b" + - "\xd5>\x10\x98\xf5\xff\x10F#\xa4\xf4e\x0c\xa5\x8b\xb9" + - "$yK\x1d\x8fr\x03\xc15}e6\x056\x9d\x16" + - "\x16\xc2\x17?\xad\x8d\xfe\xf5\xc17\xe7\xefb\xfa\xf8\xdb" + - "\x10\x1e\xb6\x97\x0e{\x80G\xf9\xd1\x98*\xf7\x9b\x00\xf2" + - "\xc3<\xca\x878D_\x93O\x1c\x00\x90\x0f\xf1(?" + - "M\x9a\xe4\xa1\x9f)\xa6\xdd\xc3\x14\xb4;t\x9b\x99\xeb\x15\xd4" + - "\x02H\xd8b\xab%fT\xec\x10\"J\xcaF\xb7\xb0" + - "\xc2b\xbb\xb7JPl\x0bk\x81\xc3Z\x8aH\x8b\x99" + - "m&+\"YC\xd1r\x0ao\xf7_\x8a\x82F\x82" + - "xr\x0c\xf5PY\xb6\x89G\xf9;\x04%\x18\x9b&" + - "\x89w\x0d\x00\xe7\"\x09\xc9\xbc\xae5^Zp^\x9a" + - "\x8b\xb7ZnB\x9c\x00 n%\xedl\xe7Q\xde\xc5" + - "\x05Wk7 \xebEh\xb5\xa9\xfdVf\x0b\xa1\xa6" + - "\xca\"y\xfdzAEC\xefr\x15\x85\x91\xa6\x0aF" + - "\xa9l\x92+\xab\x86.W\x14M\xe5\xed\xc1p\xe1\xb8" + - "\xba H\xf2ByE9\xe3\x1a\x8b\x94qK\xa0\x0c" + - "\xe9[\xb8\x04 \xbf\x1ay\xcc\xf7c\xe4.\x12\xc3V" + - "\x80\xfc\x1a\xa2k\x18y\x8c\xa4\xe2T\x80|\x91\xe8e" + - "\x0c;P\xa9\x84O\x02\xe4\xcbD\xde\x84Q\xa9 \x0d" + - "\xba\xdbo$\xfav\x8c\xaa\x05i+\xce\x01\xc8o\"" + - "\xfa\x03D\x9f\xc0\xb9\x9a\x94\xf6\xe0\x00@\xfe~\xa2?" + - "Lt\xa1&M\xed\xb6\xf4\x10\x9a\x00\xf9}D\x7f\x9c" + - "\xe8\x13\x1b\xd28\x11@:\xe8\xd2\x1f%\xfaa\xa2\xd7" + - "^\x95\xc6Z\x00\xe9\x87\xb8\x0d \x7f\x88\xe8O\x13}" + - "\x12\xa6q\x12\x80t\x0c\x1f\x04\xc8?M\xf4\x9f\x10}" + - "\xf2\x844N\x06\x90^t\xefs\x9c\xe8'\x88^\x97" + - "Hc\x1d\x80\xf4S<\x00\x90?A\xf4\xd3\x18\xe2]" + - "G1\x0e\xbb\xe4njTv\xf0\x86\x15\x9a\x9c\xf9\x1d" + - "(z9!g$\xa9\x05\xc5d4*\x06\xc4$\xa0" + - "S6\x0cm\xf9H8\xbfX\xe5\xe3\xbb\x0b$\x0d\xbd" + - "\xa3\x18\xc6\x9f\xe7dK\x0d\xc8\x14\x14\xad\xa3\x1c\xd5B" + - "VK\xc56*e\xc8\x14\x15\x9b\x15\xc3\x84lV\xf4" + - "\xc5\xa6Q\xeaBf\x96T]\xd1 \xfc2\x9e\xcf%" + - "+\x15\xb5\x18\xee=n\x01\x17\xba'W\xed\x9e\x99r" + - "S\x97\xd2W5-\x98\x13a}\x08]so\x8e\xa0" + - ">\x19\x0f\xa9\xcczE\xab\xb0K\xa9\xec\xc6\xed?:" + - "\xb3^\xffr\xb165\x98m]\xbc4_Y\x95F" + - "\xbd\xe46j4\xd2\x1a\x09\x1b\xcaj\xfa\xe3\x92v." + - "J`\x81Iz\xfd6\x142\xb4w\xcc9\xc2\xe1\xa3" + - "\xef\x1c\x97\xaa\x89>f{\xbf:\xf4^\x83r\xbd\xa0" + - "\x94\xac/\xb9\xba\x93Y\xc9K\xd1b4N\xbcx2" + - "n\xef\xea\xcaE\x13\x09\xdeC\xf2\x1bC\xf0j\xc1N" + - "\x80|3E\xe7R\x0cu(u\xb8 \xd2N\xe4." + - "\x8cJXIv\xc1\"G\xf4\xd5\x1859\xd27\xdd" + - " \x8f00\xd1\xe2\x81\x17s\xb7\x0f\xb1N\xacA\x0f" + - "\xbcJ\xee\xfe\x1a\xd17\xc6\xc1\xab\x82\xc3#\xc0N\xe0" + - "=\xf0\xda\xea\x82\xcev\xa2\xefr\xc1+\xe1\x81\xd7N" + - "|\x0a \xbf\x8b\xe8\xfb\\\xf0\xaa\xf1\xc0k/>;" + - "\x02\xec&M\xf0\xc0\xeb\xa0\xcb\xff8\xd1\x8f\xba\xe0\xd5" + - "\xea\x81\xd7\x11\x17\xec\x0e\x13\xfd8\x81T\xc5\xd4\xf2\xb6" + - "\xa9\xea\x80}Ql\x14\xca\xdf`\xac\xdc\x02IM]" + - "\xcf\xc2\xc4RT\x15maE\xd1 \x93\xb7\x95\xc2\xda" + - "\xa8N\xd7\xacvE/Z\xd8\xaf\xace\x94\x8e\x84x" + - "\xe2\xb65k\x153\xd5^\xc0\xa8\xb2\x0f\x0b\x99d\xce" + - "0\xaa\xeb\x1b\xb7@d\xa6\x87p\xe1\xb7\x92\xb2\xb1\xa3" + - "\xa8\xb16\x0c\xca\x19^\x8f\xd2\xa1J_\x0c]G\xaf" + - "\xc6\xe8R3#\x8b\x87\xb2\xdf+\x04EHW\xb6\xaa" + - "\xba`\x1b\xcb\xac`\xb7\x19\xa8\xdb\xaa^a\xa36(" + - "\xf4W\xf4\xb5\xac\xb8\x08\xf5\x82QT\xf5>\x18\xd5\xa4" + - "\xf0_4\x08\x8aU]n4c\xec!M\x9c\xdd\x04" + - "\x9c\x0b]TC\x88MQ\xab\x9f-\xb8\xab\xb2&S" + - "\xacX\x97:\xcei\xfe\xe0\xd2\x0b2\xaf\xad\xaf\x01\x08" + - "_\x9d0\x98\xdc\x8bG\x86\x80\x13\x9f\x100z\xf0\xc0" + - "\xe0}C|\xc8\x04N\xdc# \x17\xbe\x06b\xf0\x92" + - "'\xee\x18\x06N\xbcK@>|\xa1\xc3`,.\x0e" + - "\xb6\x02'\x96\x04L\x84\xaf\x95\x18\xcc\xd4E\x85\xea\xa4" + - "o\x0aX\x13>\xfda\xf0p#.\xdb\x06\x9c\xb8H" + - "p\x82v\x08\xb2\x9e\x18\xcd\xe8\x04\x80\x01\x19\x172\x9a" + - "\xd1\x09FI\x18\xb4M\x00\xcd\xb8\xc5\x87\xe7ft\x82" + - "a*$\x0b\x8a\xcd\x9a\xa9\xd7\xf4>\xa2\x0f\xde\xd0\x8c" + - "\xf1!%\xffE\x0d\xce\xd8\x85rkT\xcc\x05\x00\xbc" + - "u8\xaa\xe5\xc2\xa6r\xe7\x93\xf1:\xd9\x9f\x8d\xec\xdd" + - "\xe6OV\x8e\xc6f#G\xa8x>\xca\xa3\xfc\x0b." + - "\xaa\x0c\x02\x9f\x0e\xe6zh\x98A\x97;\xcex\xcf\xf7" + - "|\xbf\x86\xad\x1e\xf29E\xa3\xdf\xadq\xd1\xdb\xca\x82" + - "(\x1d\xc4'\x7fSb\x93?\x0c\xfakaD\xf6\x88" + - "\xcf\x01\xa7\\\xa4Y\x8bw\x8bn:K\xb8.\x19\xbc" + - "sb\xf0$-\x8a\xe4Z\xf5\x82\x13t\x94\x18\xe4B" + - "\xa82\xd9e\xb6\xd5\x9d,\xf3\xbfI\xd6c8\x88w" + - "N\x92<\xd2\x13(\xdcw 6\xa7\xd3\x0c\xbf#L" + - ".\x8f\x17\xf5\xe3\xe8\xca\xbbpP\x82'i1\xed?" + - "-\xdc\xffX\xa3?\\;\x1e+v\x9ei\xf4\x1d\xe8" + - "\x85X\x9f\xf6\xdc\x12\x00\xf9\xb87q\x0b\x1e}\xce\x90" + - "\xa3\xbe\xca\xa3\xfcv\xcc\xfd\xde$\xc67x\x94\xdf\x8b" + - "\xf2\x95\xf8\x1b\xeaY\xde\xe3Q\xfe/JV\x09\xafg" + - "\xb9@\xfd\xe9\xc7VG\xaa\xac\xfb\xbby+\xee>\xb3\xec\x10" + + "m\x1d3\x8bw\x89\xd2\xd4&\x946O%\xcb\x8cL" + + "%\xee_\\\xb7\xea\xf9\xe7\x9f\x1a\x1d~\xd6e\xb8\xf5" + + "\x8bg\xfe\xbe\xfd\xa3\xe3\xcf\xc6-tO\x8a#\x0b=" + + "\x92\"\xc1\xfbF\xb1\xf4fS\xf3\xf3 _\x8b\xe8\x0c" + + "\xef\xdddu\xec\xdb\xed@/\x0a\xc8\x014\x1eKm" + + "\xa4\xcdN\xa6\xc8\xbf\x1a>l\xad\xd3>\xda\xfeB\x85" + + "3\xba\xa7.\x12\x97\xa2\xd4)\xd2\xd5\xda\xc5\x1f\x03~" + + "\xfe\xc4\x1d{:O/~I\x9e\x8e\x89J\xa1\xdf\x16" + + "7\xa2\xf4\x19\xf16\x9e\x153\xa4\xcfP\x83\x15\xec\xae" + + "\xd4\xb3\xa4a\x94\x16I\xf4s\xa1\xe4\xb2/\xbd\xf5\xfb" + + "\xf7V\xbd\xfb\xfd\x97*UJ\x1e\xde\xf8Gi\x03%" + + "9M?\x97\xa7\xaf\xe4\x01\x9d\xe9O\xfd\xc1\xdf\xb4\x16" + + "N\xfe|\x9c \x92\x8e]\xf9\xb1t\xf2J\xfau\xe2" + + "J\x92\xf1\xf4\xfcC\x7f\xfa\xde\xee\xd7\x8f\xc7=ea" + + "\xbd\xeb\xb1\xed\xf5\xa4\xb0;\xbe1\xb2q\xc5\x9c\xd1\x13" + + "\x95\x06r9Y\xfd(J\x9b\xeb]s\xd6\xd3v\xdc" + + "\xbb\xca\xb4\xad\xff\xf6\xed7c>\xfb^\xfd;\x08\x09" + + "g\xc5\xaa[\x87k6\x9f>\x1d?\xe8\xadz\xcft" + + "\xeeA\x87\xc5{\xa5#\x0f\xfd\xe5\x19:H\xa8T\xb7" + + "8\xad\x0f\xa59\xd3\\\xf5L{\x94\x83X\xec\x8c\xe7" + + "8\x0d3\x9aP\x9a?\xc3u\x9c\x19t\xafE\xb7\xb5" + + "\xb0\xd57\xde\xfc>\x88\xd3\xf91P\xa1\x12\xe7\x08q" + + "6\xda3\xee@is\x83\x00\xe0\xdc5\xd8\xf7\xca\xd9" + + "\xb6\x87~]\xb9\xb9+\x10khB\xc9&\xbe\xc6\xb5" + + "\x0d\xae}\x1a\x17\xfe\xf9\x87{\x1fn;{\xde\xeeG" + + "g\xb6\xa2tl&\xdd\xe3\x95\x99\xdf\x91\xce\xcdt7" + + "\xff\xde\xe2\x95\xdf\x9a\xfd\xe2\xc7qM\xbc=\xd3\x8d\xde" + + "\xcff\x92&\x06n\xfc\xe0;s\xee\xfa\xa7\x8f+\xec" + + "\xe72^\x91\x99\x87\xd2\x9c\x0c\xed8+\x93\x05\xfch" + + "\xc9\x0f\x8fOON\xfft\xbc8n\xcf\x0c\xa3t\x0b" + + "\xf16\xf6f\xee\xa6\x8b\xde\xfc\xce\xfd\xeb\xb3?\xf8\xf4" + + "s\x92\x8b\xaf\xc0\xb9\x9aY}(5\xcc\xa2\x9d\xa7\xcd" + + "\xa2XZ\xf6\xe4\xc9o\x0f\xed}\xf9\xdc\xb8\xc8\xfd\x8f" + + "\xb3\xb6\xa3t\xd2\xe5>1\x8b \xe7/\x84\x03\xa7\xb7" + + "\xfe\xc7\x9f|\x11\x97\xea\xe8\xecwH\xaa\xd7g\x93T" + + "\x9b>\xda\xdfq\xf7\xea'\xbf\x8a3\x9c\x9d\xfd\x9c\x8b" + + "\x8dW\x11C\x18\x8c\xe3y\xda\xac\xabZQZx\x15" + + "\x9d7\xff\xaa,\xccw,[\xd3X\xd1('\xf2\xbf" + + "\x1b\xfc\xcc/\xc8+e\xad\xdc\xd4b[CL\xb3\xd4" + + "\xbcb\xb1n\x965\xcb\xbaf\xb2.D9\xc5'\x00" + + "\x12\x08 *\xc3\x00\xf2m<\xcaE\x0eE\xc44\xa1" + + "\xb5\xa8\x12q\x88G\xd9\xe2P\xe4\xb84!\x82\xb8v" + + "6\x80\\\xe4Q\xde\xc0!\xf2i\xc2;\xd1\xbe\x17@" + + "\xde\xc0\xa3\xbc\x83C\xa7\xcc\x8c\x92\xa21\x0d\x92V\xbb" + + "a`-pX\x0b\xe8\x18\xcc2F\x94\xfe\"$Y" + + "\x8c,\x0c\xaf\xb7\xb0\x0e8\xac\x03t\x86t\xdb0{" + + "5\x0b\xd5b7\x1b0\x98\x89C8\x098\x9c\x048" + + "\x91xm\xba\xa6\xb1\xbc\x95\xb3\xf3yf\x9a\x00$Y" + + "u(\xd9\xdc\xfb\x01\xe4\xebx\x94o\x8cI\xb6\x88$" + + "\xfb&\x8fr3\x87\x8e\xc9\x8cu\xccX\xa6c^\xb1" + + "T][\xa1\xf0%\x16^;_T\x99f\xb5\xe9\x90" + + "\xd4\x06\xd4ALE\xa1\x00\x88\xa9\x89/\xd6\xbeA5" + + "-U\x1b\xecq\xe9\xd9.\xbd\xa8\xe6G\xe8v\xb5\xae" + + "&\x1b\x9ah\x0f\xf1\x8a>\x00\xe4D\xb1\x15 \xab\x0e" + + "j\xba\xc1\x9c\x82j\xe6I(\xe0\xf3\xd6\x96~\xa5\xa8" + + "hy\x16\x1e4\xe9\xfc\x83\xbc\x03r\xae\x1c\x0b\x94\x98" + + "\xb5\xaf\xeeR\x0c\x85/\x99rm\xa8\x8f\xf6>\x00y" + + "1\x8frWL\x1f\xcb\x97\x02\xc8\xcbx\x94o\x8eY" + + "\xba\xb7\x15@\xee\xe2Q^\xcd\xa1\xa3\x1b\xea\xa0\xaa\xb5" + + "1\xe0\x8d\xb8\xc1LKSJ\x0c\x00\x02\x85m\xd1\xcb" + + "\xa4D\x13S\x11JWh\xaa\xea|\x01:X\xb1\xa8" + + "\xdf\xa4\x1b\xc5\xc2J\xef\x1c\x9d\xb4\xed\x9a2\\&\x8c" + + "cy\xd78$\xb7\x9ag\x0bl\x93y\xebl\xc35" + + "\xe4\xd5\xdd\xcc\xb4\x8b\x96\x09 'B\xf1\xeb\x9a\x00\xe4" + + "j\x1e\xe54\x87Y\xc3e\xc0T\x04\xea\x15W\xbd\x98" + + "\xaem\xcd`\x83\xaai1\xc3#_\x9d%\x85\x97\xcc" + + "\xf8\x81\xe4\x7f)\x1e\xe5\x19\x1c:\x83\x86\x92g]\xcc" + + "@U/\xacP4=\xc7\xb3~\x10@~\x92G\xf9\xa7\xa4I\xce\xd3\xe4" + + "3\xf3\xa8m\xe2Q~\x95C1\xc1\xa7\xa9+\x10_" + + "\xa1\x90z\x95G\xf9\x0d\x0e\xc5\xaaD\x1a\xab\x00\xc4\x13" + + "d\x9d\xe3<\xca\xa7.\x84V\xf9\xa2n\x17\x06\x8a\x0a" + + "d\x0cV\xe8\\\x1c\xd25\xbb\xd4e\xb0u*\xea\xb6" + + "\xd9bY\xac$\x94-3H\xa9\xe8Z\x1e\xe5or(0\x83\xf2" + + "d\xd8\xe9{\x87n1\xbd\x8a\x14S\xd1\x18\xe7\xe2\xd7" + + "\x89\x15\xeb\xaa\xae\x9d\xe7\x86\xb3\xa3p\x09M\xd8yC" + + "\xcc\xae\x81\x09\x97\xf7Gv\x15\xd6\xb0\x91\xc0J\x19V" + + "R\xd4\x08\x8d|\xe3\xb6\x80\xf0\xdd\x88g\xc2\xa2\xd6O" + + "\xfe^\xea\xcfz\xd6\xa2K\xa6\xc3Kn\x1e\x05\x90\xb7" + + "\xf2(\xef\x8a]r'\xf5\x08\xbbx\x94\xf7\xc5.\xb9" + + "\x97\x94\xb8\x87G\xf9@,{\xee'\x03\x1f\xe0Q~" + + "\x8cCLx\x90\xff\x08A\xfec<\xca\x879\x17\xb0" + + ";Z\xdat\x0d\xfdK\x98\x00a\x9f0\xc4\x14\xc3\xea" + + "g\x0aZ\x9d\x9a\xc5\x8cu\x0a\x16\x03H\xd8b\xa9%" + + "\xa6\xdbV\x08\x11%e\x83[Xa\xa1\xc3[%(" + + "\x96\x895\xc0a\x0dE\xa4\xc9\x8c6\x83\x15\x90\xac\xa1" + + "\x14\xbb\x14\xde\x1a\xba\x14\x05\x8d\x05\xf1\xe48\xea\xa1\xb2" + + "l\x13\x8f\xf2\x9d\x04%\x18\x9b&\x89\xb7\x0f\x03\xe7\"" + + "\x09\xc9\xbc\xb65^Zp^\x9a\x8b\xb7ZnB\x9c" + + "\x04 n#\xed\xec\xe0Q\xde\xc3\x05W\xeb\xd0!\xeb" + + "Eh\xa5\xa9\xfdVf\x0b\xa1\xa6\xca\"y\xfdzA" + + "E]\xebq\x15\x85\x91\xa6\xf2z\xa9l\x90+\xab\xba" + + "&\xdbJQ\xe5\xad\x91p\xe1\x84\xba H\xf2By" + + "e9\xe3\x1a\x8b\x94qc\xa0\x0ci\x04\x97\x02\xe46" + + " \x8f\xb9\x1d\x18\xb9\x8b\xb4\x0d[\x01r\x9b\x88~'" + + "F\x1e#\xdd\x8e\xd3\x01r[\x89\xbe\x0b\xc3\x0eT\xda" + + "\x89O\x00\xe4v\x11y\x1fF\xa5\x82\xb4\xd7\xdd\xfe>" + + "\xa2?\x88Q\xb5 =\x80\xf3\x00r\xfb\x88~\x98\xe8" + + "\x938W\x93\xd2!\x1c\x06\xc8=E\xf4#D\x17\xaa" + + "\xd2\xd4nK\xcf\xa2\x01\x90\xfb)\xd1\x7fF\xf4\xea\xfa" + + "4V\x03H/\xba\xf4\x17\x88\xfe*\xd1k\xa6\xa5\xb1" + + "\x06@z\x05\xb7\x03\xe4^&\xfaq\xa2O\xc64N" + + "\x06\x90^\xc7\xfb\x01r\xc7\x89~\x8a\xe8S&\xa5q" + + "\x0a\x80\xf4\x96{\x9f7\x88~\x86\xe8\xb5\x894\xd6\x02" + + "Ho\xe3A\x80\xdc\x19\xa2\xff\x9a\xe8uB\x1a\xeb\x00" + + "\xa4\x0f]\xb9> z5\x17\xe2`g!\x0e\xc7\xe4" + + "\x86jT\x8e\xf0\xba\x19\xba\x02\xf3;S\xf4rE\x97" + + "\x9e\xa4\xd6\x14\x93\xd1\x08\x19\x10\x93\x80NY\xd7\x8b+" + + "\xc6\xc2\xfc\xc5*\"\xdf\x8d \xa9k\x9d\x850.=" + + "\xe7[\xa6C&\xaf\x14;\xcbQ\x8dd\xb6\xd8\x96n" + + "\x97!SP,V\x08\x13\xb5akK\x0c\xbd\xd4\x83" + + "\xcc(\xa9\x9aR\x84\xf0\xcbD\xbe\x98\xb4m\xb5\x10\xee" + + "=aa\xe7\x0c0\xc5\xb2\x0df\x92h\x17\xc8\xb8\\" + + "\xa5Gg\xcaM=\xca`\xc5\x80a^\x94\x1eB\xb4" + + "\x9b\x7fC\x94\x1d\x92\xf1(\xcc\xacS\x8a6\xbb\x94b" + + "p\xc2\x96\xa5;\xeb\xb5<\x17\xebl\x83q\xd8\xc5\xab" + + "\xf9\xde\x8a\xcc\xeb\xe5\xc3\xf3\xa6)\xad\x91\xb0\xa1\xac\x86" + + "?a\xe9\xe0\xa2\x9c\x17Xk\xc0\xef\\!C{\xc7" + + "\xfc&\x9cW\xfa~s\xa9\x9a\x18d\x96\xf7\xabS\x1b" + + "\xd0\xa9<\x10\x94\x92\xf95Ww33y)Z\x8c" + + "&\x90\x17\xcf\xdf\x1d==]\xd1\x10\x83\xf7\xc0\xff\xfa" + + "\x10\xefZ\xb0\x1b \xd7L\x81\xbb\x0cC\x1dJ\x9d." + + "\xeet\x10\xb9\x07\xa3\xaaW\x92]|\xe9\"\xfaj\x8c" + + "\xfa\"\xe9\x16\x17\x17V\x13}\xc8\xc5\xbb\x16\x0f\xef\x98" + + "\xbb}\x81\xe8e\x17\xef\xd0\xc3\xbb\x92\xbb\x7f\x91\xe8\x1b" + + "\xe2xg\xe3\xe8\x18\xf8\x15x\x0f\xef\xb6\xb98\xb5\x83" + + "\xe8{\\\xbcKxx\xb7\x1b\x9f\x06\xc8\xed!\xfa\x01" + + "\x17\xef\xaa<\xbc\xdb\x8f\xcf\x01\xe4\x0e\x10\xfd1\x17\xef" + + "&yx\xf7\x88\xcb\xffX\x88\xb3SZ=\xbc;\xe4" + + "\xe2c\x88\xb3\x8em\x14s\x96\xa1j\x80\x83Ql\xe4" + + "\xcb\xdfe\xac\xdc\x02\xc9\xa2\xba\x8e\x85\xb9\xa8\xa0*\xc5" + + "\xc5\xb6R\x84L\xceR\xf2k\xa2\xd2\xbehv(Z" + + "\xc1\xc4!e\x0d\xa3\x0c&\xc4s\xbdU4W1C" + + "\x1d\x00\x8c\x9a\x81\xb0\xf6Iv\xe9zeI\xe4\xd6\x94" + + "\xcc\xf0\xc0/\xfcVR6t\x16\x8a\xac\x0d\x83\x0a\x88" + + "\xd7\xa2\x0c\xaa\xd2\x17]\xd3\xd0+Kz\xd4\xcc\xd8z" + + "\xa3\xec\xb7\x17A\xdd\xd2\x93\xad(H\xd8\x862\xcb[" + + "m:j\x96\xaa\xd9\xec\xbc\x0d\xf2C\xb6\xb6\x86\x15\xda" + + "Q\xcb\xeb\x05U\x1b\x84\xf3\xfa\x1a\xfeB\xb3\xa3X\xa1" + + "\xe6F3\xc6\xde\xde\xc4\xb9M\xc0\xb9\xd0Ee\x87\xd8" + + "\x14M\x07\xb2ywU\xd6`\x8a\x19kl'8\xcd" + + "\x9fuzA\xe6M\x02\xaa\x00\xc2\x87*\x0c\x86\xfd\xe2" + + "\xa1\x8d\xc0\x89\x8f\x0b\x18\xbd\x91`\xf0$\">`\x00" + + "'\xee\x15\x90\x0b\x1f\x101x\xfc\x13w\x8e\x02'\xde" + + ". \x1f>\xeaa0I\x17GZ\x81\x13K\x02&" + + "\xc2\x07N\x0c\xc6\xf0\xa2B\xa5\xd5-\x02V\x85\xaf\x85" + + "\x18\xbc\xf5\x88\xcb\xb7\x03'\xb6\x0bN\xd0AA\xd6\x13" + + "\xa3\x19\x9d\x000 \xe3BF3:\xc1\xf4\x09\x83N" + + "\x0b\xa0\x19\xb7\xf8\xf0\xdc\x8cN0\x7f\x85d^\xb1X" + + "3\xb5\xa7\xdeG\xf4\xc1\x1b\x9a1>\xd7\xe4/\xd4\x13" + + "\x8d_[\xb7F\xf5_\x00\xc0\xdbF\xa3\xf2/\xecC" + + "w?\x11/\xad\xfdq\xca\xfe\xed\xfe0\xe6pl\x9c" + + "r\x88\xea\xed\xc3<\xca\xbf\xe0\xa2\xa2!\xf0\xe9`\x14" + + "\x88\xba\x114\xc6\x13L\x04}\xcf\xf7\xcb\xde\xca\xb9\xa0" + + "S\xd0\x87\xdc\xb2\x18\xbd\xadL\x88\xd2A|X85" + + "6,\xc4\xa0%\x17\xc6d\x8f\xf8\xe8p\xeaE\xfa\xbb" + + "x\x83\xe9\xa6\xb3\x84\xeb\x92\xc1\xd3(\x06\xaf\xd8\xa2H" + + "\xaeU'8A\x13\x8aA.\x84\x0a\x93]f'\xde" + + "\xcd2\xff\x9bd=\x8e\x83x\xe7$\xc9#=\x81\xc2" + + "}\x87c\xa3\xbd\xa2\xee7\x91\xc9\x15\xf1>`\x02]" + + "y\x17\x0e\xaa\xf6$-\xa6\xfdg\x84\xfb?3\xdb\x9f" + + "\xc7\x1d\x89\x15;\xcf\xce\xf6\x1d\xe8\x85Xkwt)" + + "\x80|\xc4\x1b\xd2\x05\xefD'\xc8Q\xdf\xe0Q>\x13" + + "s\xbf\xb7\x89\xf1\x14\x8f\xf2\x07Q\xbe\x12\xdf\xa36\xe7" + + "\x03\x1e\xe5\xff\xa2d\x95\xf0\xda\x9c\xcf\xa8\xa5\xfd\x94\xc7" + + "n\xf4[\xee\xe0\x11\xc96\"\xf4.\xea\x83\xcbTm" + + "\xdc\xb2.x\x99B\x8b0\xd16\x08\xd8\xc7\x02h\xe7" + + "\xe2X\xa1\x1b\xce\x95\x90\x199\x8a\xe1\x02\x9a\xe1\xbcf" + + "\xfcI\xee\x04\xaa\xcd\xf9\x81\xe4\xc5\x91_\x17\xc4\x1a\xfb" + + "\x83\xb1\x99W\xa0X\xf99\x7f\x96t[L\xb1\x7f\xdc" + + "\x1f\xcd\xa6\x09l\xf4\xderAA\x8b-1\xd8Z\x9b" + + "\x09Z~$jp\xa9\xc5\xcb\x9b\xbdX\xa6\xe2z\x89" + + "\xc1\xb2km\x16g\x08\xde#@P\xf5\xc2y\x0f\x11" + + "\xe3T\x897\xb1\xfe\x9c\x9e_\xc3\xac1\xef4\x15o" + + "\x89\xdd\xd1cD\xf8\x94\xd8\x1d\x7fJ\xf4!j\xedp" + + "4&\x0f!jd4\xea\x8e\xc7/\x0b\xfeo2\xf9" + + "\xd7zN\xa3\xa2X\xb8\x94\x821\xfc+\x9f\xaf9\xb4" + + "\xbf\xd4\xfa>z$\xbe\xccA\x17\x84\xb8\x81\xb1?\x02" + + "\xa1C8\x7f\xf3\xff\x09\x00\x00\xff\xff\xa6\xa4\xec\x8a" func init() { schemas.Register(schema_db8274f9144abc7e, From 8f9bbcb9a0830a895f22639143c9093cf2ef9c05 Mon Sep 17 00:00:00 2001 From: Areg Harutyunyan Date: Thu, 27 Feb 2020 16:02:52 +0000 Subject: [PATCH 008/100] Release 2020.2.1 --- RELEASE_NOTES | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 4edc2d5e..52fdc8a6 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,3 +1,14 @@ +2020.2.1 +- 2020-02-20 TUN-2745: Rename existing header management functions +- 2020-02-21 TUN-2746: Add the new header management functions +- 2020-02-25 perf(cloudflared): reuse memory from buffer pool to get better throughput (#161) +- 2020-02-25 Tweak HTTP host header. Fixes #107 (#168) +- 2020-02-25 TUN-2765: Add list of features to tunnelrpc +- 2020-02-19 TUN-2725: Specify in code that --edge is for internal testing only +- 2020-02-19 TUN-2703: Muxer.Serve terminates when its context is Done +- 2020-02-09 TUN-2717: Function to serialize/deserialize HTTP headers +- 2020-02-05 TUN-2714: New edge discovery. Connections try to reconnect to the same edge IP. + 2020.2.0 - 2020-01-30 TUN-2651: Fix panic in h2mux reader when a stream error is encountered - 2020-01-27 TUN-2645: Revert "TUN-2645: Turn on reconnect tokens" From a14aa0322cfda1c117033632019ef6a306c9bce9 Mon Sep 17 00:00:00 2001 From: Areg Harutyunyan Date: Wed, 26 Feb 2020 14:38:30 +0000 Subject: [PATCH 009/100] TUN-2767: Test for large headers --- h2mux/header_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/h2mux/header_test.go b/h2mux/header_test.go index b5781d91..95170230 100644 --- a/h2mux/header_test.go +++ b/h2mux/header_test.go @@ -610,3 +610,43 @@ func TestH1ResponseToH2ResponseHeaders(t *testing.T) { assert.NoError(t, err) assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders) } + +// The purpose of this test is to check that our code and the http.Header +// implementation don't throw validation errors about header size +func TestHeaderSize(t *testing.T) { + largeValue := randSeq(5 * 1024 * 1024) // 5Mb + largeHeaders := http.Header{ + "User-header": {largeValue}, + } + mockResponse := http.Response{ + StatusCode: 200, + Header: largeHeaders, + } + + serializedHeaders := H1ResponseToH2ResponseHeaders(&mockResponse) + request, err := http.NewRequest(http.MethodGet, "https://example.com/", nil) + assert.NoError(t, err) + for _, header := range serializedHeaders { + request.Header.Set(header.Name, header.Value) + } + + for _, header := range serializedHeaders { + if header.Name != ResponseUserHeadersField { + continue + } + + deserializedHeaders, err := DeserializeHeaders(header.Value) + assert.NoError(t, err) + assert.Equal(t, largeValue, deserializedHeaders[0].Value) + } +} + +func randSeq(n int) string { + randomizer := rand.New(rand.NewSource(17)) + var letters = []rune(":;,+/=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = letters[randomizer.Intn(len(letters))] + } + return string(b) +} From 29f4650e2530ada1145ddf46d8535f15b444538a Mon Sep 17 00:00:00 2001 From: Roman Iuvshyn Date: Fri, 28 Feb 2020 01:03:00 +0200 Subject: [PATCH 010/100] do not terminate tunnel if origin is not reachable on start-up (#177) --- cmd/cloudflared/tunnel/configuration.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/cloudflared/tunnel/configuration.go b/cmd/cloudflared/tunnel/configuration.go index 4c38aaaf..363be514 100644 --- a/cmd/cloudflared/tunnel/configuration.go +++ b/cmd/cloudflared/tunnel/configuration.go @@ -237,7 +237,6 @@ func prepareTunnelConfig( err = validation.ValidateHTTPService(originURL, hostname, httpTransport) if err != nil { logger.WithError(err).Error("unable to connect to the origin") - return nil, errors.Wrap(err, "unable to connect to the origin") } toEdgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c) From 26f5f80811d431c12ac29fa8e0c3fcb42d354dbe Mon Sep 17 00:00:00 2001 From: Areg Harutyunyan Date: Fri, 28 Feb 2020 17:36:29 +0000 Subject: [PATCH 011/100] TUN-2776: Add header serialization feature in cloudflared --- connection/features.go | 9 +++++++++ origin/tunnel.go | 1 + tunnelrpc/pogs/tunnelrpc.go | 1 + 3 files changed, 11 insertions(+) create mode 100644 connection/features.go diff --git a/connection/features.go b/connection/features.go new file mode 100644 index 00000000..c10f7cbf --- /dev/null +++ b/connection/features.go @@ -0,0 +1,9 @@ +package connection + +const ( + FEATURE_SERIALIZED_HEADERS = "serialized_headers" +) + +var SUPPORTED_FEATURES = []string{ + //FEATURE_SERIALIZED_HEADERS, +} diff --git a/origin/tunnel.go b/origin/tunnel.go index 991d95c1..5b04e0d2 100644 --- a/origin/tunnel.go +++ b/origin/tunnel.go @@ -161,6 +161,7 @@ func (c *TunnelConfig) RegistrationOptions(connectionID uint8, OriginLocalIP str RunFromTerminal: c.RunFromTerminal, CompressionQuality: c.CompressionQuality, UUID: uuid.String(), + Features: connection.SUPPORTED_FEATURES, } } diff --git a/tunnelrpc/pogs/tunnelrpc.go b/tunnelrpc/pogs/tunnelrpc.go index bbf0001a..6aab7d94 100644 --- a/tunnelrpc/pogs/tunnelrpc.go +++ b/tunnelrpc/pogs/tunnelrpc.go @@ -168,6 +168,7 @@ type RegistrationOptions struct { CompressionQuality uint64 `capnp:"compressionQuality"` UUID string `capnp:"uuid"` NumPreviousAttempts uint8 + Features []string } func MarshalRegistrationOptions(s tunnelrpc.RegistrationOptions, p *RegistrationOptions) error { From 7b81cf8aa6b47aa17db019136be225bb3b83a1ee Mon Sep 17 00:00:00 2001 From: Nick Vollmar Date: Mon, 2 Mar 2020 13:30:10 -0600 Subject: [PATCH 012/100] TUN-2779: update sample HTML pages --- h2mux/sample/index.html | 4 ++-- h2mux/sample/index1.html | 4 ++-- h2mux/sample/index2.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/h2mux/sample/index.html b/h2mux/sample/index.html index 6388b6c2..fe91d668 100644 --- a/h2mux/sample/index.html +++ b/h2mux/sample/index.html @@ -80,7 +80,7 @@ ghost.init({ - + + +