From 4268bc1a9cfb0b7a527e13d1f6ca28f063e0e290 Mon Sep 17 00:00:00 2001 From: cloudflare-warp-bot Date: Tue, 12 Jun 2018 15:41:49 +0000 Subject: [PATCH] Release Argo Tunnel Client 2018.6.1 --- h2mux/h2mux.go | 12 +-- metallog/metallog.go | 13 +++ origin/tunnel.go | 65 +++++++----- tunnelrpc/pogs/tunnelrpc.go | 3 + tunnelrpc/tunnelrpc.capnp | 2 + tunnelrpc/tunnelrpc.capnp.go | 190 +++++++++++++++++++---------------- 6 files changed, 167 insertions(+), 118 deletions(-) create mode 100644 metallog/metallog.go diff --git a/h2mux/h2mux.go b/h2mux/h2mux.go index dd17df3f..ff67f035 100644 --- a/h2mux/h2mux.go +++ b/h2mux/h2mux.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/cloudflare/cloudflared/metallog" log "github.com/sirupsen/logrus" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" @@ -46,7 +47,7 @@ type MuxerConfig struct { // The minimum number of heartbeats to send before terminating the connection. MaxHeartbeats uint64 // Logger to use - Logger *log.Logger + Logger *log.Entry } type Muxer struct { @@ -95,7 +96,7 @@ func Handshake( config.Timeout = defaultTimeout } if config.Logger == nil { - config.Logger = log.New() + config.Logger = metallog.New().WithFields(log.Fields{}) } // Initialise connection state fields m := &Muxer{ @@ -259,10 +260,9 @@ func joinErrorsWithTimeout(errChan <-chan error, receiveCount int, timeout time. } func (m *Muxer) Serve(ctx context.Context) error { - logger := m.config.Logger.WithField("name", m.config.Name) errGroup, _ := errgroup.WithContext(ctx) errGroup.Go(func() error { - err := m.muxReader.run(logger) + err := m.muxReader.run(m.config.Logger) m.explicitShutdown.Fuse(false) m.r.Close() m.abort() @@ -270,7 +270,7 @@ func (m *Muxer) Serve(ctx context.Context) error { }) errGroup.Go(func() error { - err := m.muxWriter.run(logger) + err := m.muxWriter.run(m.config.Logger) m.explicitShutdown.Fuse(false) m.w.Close() m.abort() @@ -278,7 +278,7 @@ func (m *Muxer) Serve(ctx context.Context) error { }) errGroup.Go(func() error { - err := m.muxMetricsUpdater.run(logger) + err := m.muxMetricsUpdater.run(m.config.Logger) return err }) diff --git a/metallog/metallog.go b/metallog/metallog.go new file mode 100644 index 00000000..4482280e --- /dev/null +++ b/metallog/metallog.go @@ -0,0 +1,13 @@ +package metallog + +import ( + tunnellog "github.com/cloudflare/cloudflared/log" + log "github.com/sirupsen/logrus" +) + +// New creates a logger formatted for JSON output +func New() *log.Logger { + logger := log.New() + logger.Formatter = &tunnellog.JSONFormatter{} + return logger +} diff --git a/origin/tunnel.go b/origin/tunnel.go index 17cc2a4d..d4d8f37d 100644 --- a/origin/tunnel.go +++ b/origin/tunnel.go @@ -23,13 +23,13 @@ import ( raven "github.com/getsentry/raven-go" "github.com/pkg/errors" _ "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" rpc "zombiezen.com/go/capnproto2/rpc" ) const ( dialTimeout = 15 * time.Second - + lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;" TagHeaderNamePrefix = "Cf-Warp-Tag-" DuplicateConnectionError = "EDUPCONN" ) @@ -53,8 +53,8 @@ type TunnelConfig struct { HTTPTransport http.RoundTripper Metrics *TunnelMetrics MetricsUpdateFreq time.Duration - ProtocolLogger *logrus.Logger - Logger *logrus.Logger + ProtocolLogger *log.Logger + Logger *log.Logger IsAutoupdated bool GracePeriod time.Duration RunFromTerminal bool @@ -196,9 +196,9 @@ func ServeTunnel( tags["ha"] = connectionTag // Returns error from parsing the origin URL or handshake errors - handler, originLocalIP, err := NewTunnelHandler(ctx, logger, config, addr.String(), connectionID) + handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr.String(), connectionID) if err != nil { - errLog := logger.WithError(err) + errLog := config.Logger.WithError(err) switch err.(type) { case dialError: errLog.Error("Unable to dial edge") @@ -214,7 +214,7 @@ func ServeTunnel( errGroup, serveCtx := errgroup.WithContext(ctx) errGroup.Go(func() error { - err := RegisterTunnel(serveCtx, logger, handler.muxer, config, connectionID, originLocalIP) + err := RegisterTunnel(serveCtx, handler.muxer, config, connectionID, originLocalIP) if err == nil { connectedFuse.Fuse(true) backoff.SetGracePeriod() @@ -226,9 +226,9 @@ func ServeTunnel( updateMetricsTickC := time.Tick(config.MetricsUpdateFreq) for { select { - case <-serveCtx.Done(): + case <-serveCtx.Done(): // UnregisterTunnel blocks until the RPC call returns - err := UnregisterTunnel(logger, handler.muxer, config.GracePeriod) + err := UnregisterTunnel(handler.muxer, config.GracePeriod, config.Logger) handler.muxer.Shutdown() return err case <-updateMetricsTickC: @@ -241,7 +241,7 @@ func ServeTunnel( // All routines should stop when muxer finish serving. When muxer is shutdown // gracefully, it doesn't return an error, so we need to return errMuxerShutdown // here to notify other routines to stop - err := handler.muxer.Serve(serveCtx); + err := handler.muxer.Serve(serveCtx) if err == nil { return muxerShutdownError{} } @@ -285,8 +285,8 @@ func IsRPCStreamResponse(headers []h2mux.Header) bool { return true } -func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxer, config *TunnelConfig, connectionID uint8, originLocalIP string) error { - logger.Debug("initiating RPC stream to register") +func RegisterTunnel(ctx context.Context, muxer *h2mux.Muxer, config *TunnelConfig, connectionID uint8, originLocalIP string) error { + config.Logger.Debug("initiating RPC stream to register") stream, err := muxer.OpenStream([]h2mux.Header{ {Name: ":method", Value: "RPC"}, {Name: ":scheme", Value: "capnp"}, @@ -301,8 +301,8 @@ func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxe return clientRegisterTunnelError{cause: err} } conn := rpc.NewConn( - tunnelrpc.NewTransportLogger(logger.WithField("subsystem", "rpc-register"), rpc.StreamTransport(stream)), - tunnelrpc.ConnLog(logger.WithField("subsystem", "rpc-transport")), + tunnelrpc.NewTransportLogger(config.Logger.WithField("subsystem", "rpc-register"), rpc.StreamTransport(stream)), + tunnelrpc.ConnLog(config.Logger.WithField("subsystem", "rpc-transport")), ) defer conn.Close() ts := tunnelpogs.TunnelServer_PogsClient{Client: conn.Bootstrap(ctx)} @@ -317,13 +317,13 @@ func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxe config.Hostname, config.RegistrationOptions(connectionID, originLocalIP), ) - LogServerInfo(logger, serverInfoPromise.Result(), connectionID, config.Metrics) + LogServerInfo(serverInfoPromise.Result(), connectionID, config.Metrics, config.Logger) if err != nil { // RegisterTunnel RPC failure return clientRegisterTunnelError{cause: err} } for _, logLine := range registration.LogLines { - logger.Info(logLine) + config.Logger.Info(logLine) } if registration.Err == DuplicateConnectionError { return dupConnRegisterTunnelError{} @@ -334,10 +334,12 @@ func RegisterTunnel(ctx context.Context, logger *logrus.Entry, muxer *h2mux.Muxe } } + config.Logger.Info("Tunnel ID: " + registration.TunnelID) + return nil } -func UnregisterTunnel(logger *logrus.Entry, muxer *h2mux.Muxer, gracePeriod time.Duration) error { +func UnregisterTunnel(muxer *h2mux.Muxer, gracePeriod time.Duration, logger *log.Logger) error { logger.Debug("initiating RPC stream to unregister") stream, err := muxer.OpenStream([]h2mux.Header{ {Name: ":method", Value: "RPC"}, @@ -363,10 +365,11 @@ func UnregisterTunnel(logger *logrus.Entry, muxer *h2mux.Muxer, gracePeriod time return ts.UnregisterTunnel(ctx, gracePeriod.Nanoseconds()) } -func LogServerInfo(logger *logrus.Entry, +func LogServerInfo( promise tunnelrpc.ServerInfo_Promise, connectionID uint8, metrics *TunnelMetrics, + logger *log.Logger, ) { serverInfoMessage, err := promise.Struct() if err != nil { @@ -432,14 +435,13 @@ type TunnelHandler struct { metrics *TunnelMetrics // connectionID is only used by metrics, and prometheus requires labels to be string connectionID string - logger *logrus.Entry + logger *log.Logger } var dialer = net.Dialer{DualStack: true} // NewTunnelHandler returns a TunnelHandler, origin LAN IP and error func NewTunnelHandler(ctx context.Context, - logger *logrus.Entry, config *TunnelConfig, addr string, connectionID uint8, @@ -455,7 +457,7 @@ func NewTunnelHandler(ctx context.Context, tags: config.Tags, metrics: config.Metrics, connectionID: uint8ToString(connectionID), - logger: logger, + logger: config.Logger, } if h.httpClient == nil { h.httpClient = http.DefaultTransport @@ -484,7 +486,7 @@ func NewTunnelHandler(ctx context.Context, IsClient: true, HeartbeatInterval: config.HeartbeatInterval, MaxHeartbeats: config.MaxHeartbeats, - Logger: config.ProtocolLogger, + Logger: config.ProtocolLogger.WithFields(log.Fields{}), }) if err != nil { return h, "", errors.New("TLS handshake error") @@ -510,7 +512,8 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error { } h.AppendTagHeaders(req) cfRay := FindCfRayHeader(req) - h.logRequest(req, cfRay) + lbProbe := isLBProbeRequest(req) + h.logRequest(req, cfRay, lbProbe) if websocket.IsWebSocketUpgrade(req) { conn, response, err := websocket.ClientConnect(req, h.tlsConfig) if err != nil { @@ -522,7 +525,7 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error { // connection because cloudflared doesn't operate on the message themselves websocket.Stream(conn.UnderlyingConn(), stream) h.metrics.incrementResponses(h.connectionID, "200") - h.logResponse(response, cfRay) + h.logResponse(response, cfRay, lbProbe) } } else { response, err := h.httpClient.RoundTrip(req) @@ -533,7 +536,7 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error { stream.WriteHeaders(H1ResponseToH2Response(response)) io.Copy(stream, response.Body) h.metrics.incrementResponses(h.connectionID, "200") - h.logResponse(response, cfRay) + h.logResponse(response, cfRay, lbProbe) } } h.metrics.decrementConcurrentRequests(h.connectionID) @@ -547,18 +550,22 @@ func (h *TunnelHandler) logError(stream *h2mux.MuxedStream, err error) { h.metrics.incrementResponses(h.connectionID, "502") } -func (h *TunnelHandler) logRequest(req *http.Request, cfRay string) { +func (h *TunnelHandler) logRequest(req *http.Request, cfRay string, lbProbe bool) { if cfRay != "" { h.logger.WithField("CF-RAY", cfRay).Infof("%s %s %s", req.Method, req.URL, req.Proto) + } else if lbProbe { + h.logger.Debugf("Load Balancer health check %s %s %s", req.Method, req.URL, req.Proto) } else { h.logger.Warnf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", req.Method, req.URL, req.Proto) } h.logger.Debugf("Request Headers %+v", req.Header) } -func (h *TunnelHandler) logResponse(r *http.Response, cfRay string) { +func (h *TunnelHandler) logResponse(r *http.Response, cfRay string, lbProbe bool) { if cfRay != "" { h.logger.WithField("CF-RAY", cfRay).Infof("%s", r.Status) + } else if lbProbe { + h.logger.Debugf("Response to Load Balancer health check %s", r.Status) } else { h.logger.Infof("%s", r.Status) } @@ -572,3 +579,7 @@ func (h *TunnelHandler) UpdateMetrics(connectionID string) { func uint8ToString(input uint8) string { return strconv.FormatUint(uint64(input), 10) } + +func isLBProbeRequest(req *http.Request) bool { + return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix) +} diff --git a/tunnelrpc/pogs/tunnelrpc.go b/tunnelrpc/pogs/tunnelrpc.go index a2ba5fb2..83f2667a 100644 --- a/tunnelrpc/pogs/tunnelrpc.go +++ b/tunnelrpc/pogs/tunnelrpc.go @@ -2,6 +2,7 @@ package pogs import ( "github.com/cloudflare/cloudflared/tunnelrpc" + log "github.com/sirupsen/logrus" "golang.org/x/net/context" "zombiezen.com/go/capnproto2" "zombiezen.com/go/capnproto2/pogs" @@ -30,6 +31,7 @@ type TunnelRegistration struct { Url string LogLines []string PermanentFailure bool + TunnelID string `capnp:"tunnelID"` } func MarshalTunnelRegistration(s tunnelrpc.TunnelRegistration, p *TunnelRegistration) error { @@ -124,6 +126,7 @@ func (i TunnelServer_PogsImpl) RegisterTunnel(p tunnelrpc.TunnelServer_registerT if err != nil { return err } + log.Info(registration.TunnelID) return MarshalTunnelRegistration(result, registration) } diff --git a/tunnelrpc/tunnelrpc.capnp b/tunnelrpc/tunnelrpc.capnp index cbc25755..d531531f 100644 --- a/tunnelrpc/tunnelrpc.capnp +++ b/tunnelrpc/tunnelrpc.capnp @@ -17,6 +17,8 @@ struct TunnelRegistration { logLines @2 :List(Text); # In case of error, whether the client should attempt to reconnect. permanentFailure @3 :Bool; + # Displayed to user + tunnelID @4 :Text; } struct RegistrationOptions { diff --git a/tunnelrpc/tunnelrpc.capnp.go b/tunnelrpc/tunnelrpc.capnp.go index 112b59d1..95223f1e 100644 --- a/tunnelrpc/tunnelrpc.capnp.go +++ b/tunnelrpc/tunnelrpc.capnp.go @@ -119,12 +119,12 @@ type TunnelRegistration struct{ capnp.Struct } const TunnelRegistration_TypeID = 0xf41a0f001ad49e46 func NewTunnelRegistration(s *capnp.Segment) (TunnelRegistration, error) { - st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}) + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4}) return TunnelRegistration{st}, err } func NewRootTunnelRegistration(s *capnp.Segment) (TunnelRegistration, error) { - st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}) + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4}) return TunnelRegistration{st}, err } @@ -209,12 +209,31 @@ func (s TunnelRegistration) SetPermanentFailure(v bool) { s.Struct.SetBit(0, v) } +func (s TunnelRegistration) TunnelID() (string, error) { + p, err := s.Struct.Ptr(3) + return p.Text(), err +} + +func (s TunnelRegistration) HasTunnelID() bool { + p, err := s.Struct.Ptr(3) + return p.IsValid() || err != nil +} + +func (s TunnelRegistration) TunnelIDBytes() ([]byte, error) { + p, err := s.Struct.Ptr(3) + return p.TextBytes(), err +} + +func (s TunnelRegistration) SetTunnelID(v string) error { + return s.Struct.SetText(3, v) +} + // TunnelRegistration_List is a list of TunnelRegistration. type TunnelRegistration_List struct{ capnp.List } // NewTunnelRegistration creates a new list of TunnelRegistration. func NewTunnelRegistration_List(s *capnp.Segment, sz int32) (TunnelRegistration_List, error) { - l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}, sz) + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 4}, sz) return TunnelRegistration_List{l}, err } @@ -1226,88 +1245,89 @@ func (p TunnelServer_unregisterTunnel_Results_Promise) Struct() (TunnelServer_un return TunnelServer_unregisterTunnel_Results{s}, err } -const schema_db8274f9144abc7e = "x\xda\x9cU_l\x14U\x17\xff\x9d{w;\xdb\xa4" + - "e;\x99m\x02\x1bH\x13\xd2\x86B\xbe\xf2\xc1\xc7\x87" + - "B\xfd\xd3?\x02\xba\xb5\x94\xbdl1\x08}`\xd8\xbd" + - "l\xa7\xce\xcelffQH\x04%\x18#\x89\x04E" + - "^0\x9a@\xe2\x8b\x0fj\xe2\x83\x89\xc1\x04_\xe4\x81" + - "\x07^\xd4h$1J\x88\x91h\x88\x8d\x9ahb\xc6" + - "\xdc\xd9\xce\xecR\x14\xd0\xb7\x99\xdf=\xf7\x9c\xdf9\xf7" + - "\x9c\xdfY\xf7+\x1be\xeb\xd3_\xa7\x01\xb1%\xdd\x11" + - ">X\xbbr\xfe\xbe3\x97\x8fC\xcf\xb3\xf0\xc8\x85\x89" + - "\xdco\xc1\xb1\xaf\x00\xda\xf0\x19;L\xc6\xf7L\x03\x8c" + - "\xebl\x07(\xbc\xf2\x9f\x0b\x1f\x9ez\xff\xc5\xd7!V" + - "\x11\x01)\x0d\xd8\xf0\x07\xfb\x9d@\x86\xceG@\xe1k" + - "_|4U{\xe5\xecy\xe8\xab\xe2\xf3\xcd\x9c1\xa4" + - "\xc2\xde\x02]\xbd\xb8>\xf5A\xf3$\xcd\xd5\xd1\x10\xbf" + - "\xa1\xae\x8e\xf1wA\xe1\x8a\x1f\xc7\xbb\x9d\x9b\xc7.B" + - "\xcfS\x8bE\xd3\xf0\x1b>A\xc6/\xea\xd3\xf8)2" + - "\x9e\xd8{\xfa\xd5\xf4\xf5\xd3\x97 \xf2\xd4n\xdd\xa1\xac" + - "_Nyd\x9c\x8b\x82\xbf\x91\xba\x9f\x81\xc2\xfc{\x0f" + - "\xbc3^\xf9\xf2\xf2\"\xdfQf\x9d\xda\xbc\xd1\xab\xa9" + - "/]{\x1a\x14\xb2\xeb\xe6\xb2\xe7>\x7f\xf8j[\x0a" + - "5\xed[B*\x9czb\xef\\\xe7\xb3\xd7\xae-\xa4" + - "@\xea\xc8\xd4\xa2\x14\x1a\x9a\xca~\xe3\xbe19\xb3i" + - "\xf7\x0d\xe8y~K!\xcfh\xc3d\xbc\x15\x059\xa7" + - "]2Vg4 \xa0\x85\xb1\xc8a\xa4\x19r" + - "\x94\xc28;\xf4E\xf9\x8dR\x18+)\xc5b\x08\x8c" + - "R\x91\xee\xbd\xdc\xb7\x09q\x9f\x7f/\x15\x8b\x97\xde\xdd" + - "\xeb\xd5\x8c\x93U|U\xb5\xda\xfc\xce\x01\xa2\x8b\x93X" + - "\xca(\xb4\xdd\x05U\xccN\xb5\xb5\xd0\x9d\xd4\xaaI8" + - "\xd6\xac\xac\xba\xac\xfc\xf7$\xfeM%\xa83\x9c\xc4l" + - "[\xb7J\x05\xee\xe3$\xec6A\xb5\xd4b\x98\xe5$" + - "\x8e'\x1a\xa5?\x7f\x02\x10\xc79\x89S\x8c4\xe9y" + - "1%\xad\xe1\xb5$\xd6v\xab\x93\x96#}5\xd0\x0b" + - "3\xac\x8e\xd4\xe4\xd6\xa5W3\x1d\xe9P\xb0\xcd\xb4\xec" + - "\x86'\x91\x0c\xdd\x9f\x01\x00\x00\xff\xffR3\x9d#" +const schema_db8274f9144abc7e = "x\xda\x9cU_\x88Te\x14?\xbf\xef\x9b\x99;\x0b" + + "\xbb\xce^\xee,\xe8\xa0\x0c\xc8.\xae\xd2\x9af\x96n" + + "\x7f\xf6O\xab5\xdb\xba\xce\xe7\xaca\xeaC\xd7\x99\xcf" + + "\xf1n3\xf7\x0e\xf7\xde1\x95\xd4\x12\xa5\x12\x92\xca|" + + "1\x0c\x15\x82\x8a\xa8\xa0 \x08\x03{\xc9\x07\x1f|\xa9" + + "(\x12\xa2\xc4\x87\xa4\x90\x96z(\x88\x1b\xdf\x9d\xbd3" + + "\xe3Zj\xbd\xdd\xfb\xfb\xcew\xce\xef\x9c\xef\x9c\xdfY" + + "\x91\xe0\xc3le\xfc\xfb8\x91\x18\x8b'\x82\x07\xab\x97" + + "\xce\xdew\xe2\xe2a\xd23,8pn<\xfd\x87\x7f" + + "\xe8;\"\xac\xfa\x8a\xed\x83\xf1\x13\xd3\x88\x8c\xabl#" + + "!\xb8t\xd7\xb9O_\xf9\xe8\x857H,\x01\x88b" + + "\x1a\xd1\xaa\xbf\xd8\x9f \x18:\x1f\"\x04\xaf\x7f\xf3\xd9" + + "d\xf5\xd5\x93gI_\x12\x9d\xaf\xe5\x8cQ,\xe8\xc9" + + "\xe1\xf2\xf9\x95\xb1O\x1a'q\xae\x8e\x06\xf85uu" + + "\x84\x7f@\x08\x16\xfd2\xdae_?t\x9e\xf4\x0cZ" + + ",\x1a\x86?\xf0q\x18\xbf\xabO\xe3\xd7\xd0x|\xdb" + + "\xf1\xd7\xe2W\x8f_ \x91A\xbbuBY\xbf\x1cs" + + "a\x9c\x09\x83\x9f\x8a\xdd\xcf\x08A\xe6\xc3\x07\xde\x1f-" + + "}{q\x8e\xef0\xb3\x0em\xc6\xe8\xd1\xd4\x97\xae=" + + "C\x08\xd8Us\xc1s_?|\xb9-\x85\xaa\xf6#" + + "(\x16L>\xb1m\xbac\xff\x95+\xb3)@\x1d\x99" + + "Z\x98B]S\xd9\xaf~jDn_\xb3\xe5\x1a\xe9" + + "\x19~C!Oh\x830\xde\x0a\x83\x9c\xd1.\x18K" + + "\x93\x1aQp\xec\xc0\xd8\xc6\xb5\x8b?\x9fiw\xa7'" + + "g\x94\xbb\xbe\xa4r\xb7s\xcd\xcf\x8f\xf6\x1d\xfbbf" + + "\x0e\xeb\xd0p]r\x19\x8c\xcd\xca\x8f!\x94\xf1\xf5\xf5" + + "o~\x99Ie~\x9bS\x8f\x90~=9\x0d\xe3%" + + "e\xbb\xeaH2\x0b\x1a\x08\xfc\xbam\xcb\x8a[\x8b\x15" + + "\xef\x8e>\x8b\xcb\x8bf\xcd\xae\x0d\xae\xdbcy\xbee" + + "\x97\xa7B|(\xefT\xac\xe2\xde< :\xc1\x88\xf4" + + "E\x83D\x80\xde\xb3\x95\x08L\xd7G\x89\x86\xac\xb2\xed" + + "\xb82(Y^\xd1\xb1mI\xbc\xe8\x1f\xdcaVL" + + "\xbb(\x9b\x81\x127\x07j\x04(Hw\xb7t\x97\xd7" + + "mW\x96-\xcf\x97n\x03\xee\x1d\xca\x9b\xaeY\xf5D" + + "\x8c\xc7\x88b \xd2\xbbN\x12\x89n\x0e\xb1\x90!(" + + "\xbbfQ\xe6\xa5\x0b\xcb)M\x9a\xb6S\xe0\xb2\x888" + + "1\xc4\x09\xcd\xa0\xf3\xfek\xd0M\xd2\xabW|\x8f\x9a" + + "\xb7n}\x7f\xce\xed\xbc\x99\x0a)w6)\xaf\xdb\xaa" + + "&\x8cC\xe4\x19t \xadfF\xdf0N$&8" + + "\xc4\x16\x06\x9d\xb1tX\xd6\xcd\xa3D\"\xcf!\xb63" + + "\x04\x8ek\x95-\xfb\x11I\xdc\xf5\xd1E\x0c]\x84`" + + "\x97\xe3\xf9\xb6Y\x95D\x84Nb\xe8$\x1ctj\xbe" + + "\xe5\xd8\x1e\xba[\xf3@@w[\x09\xfe\xe1\x81G\xea" + + "\xfe.i\xfbV\xd1T\x97\x89\xc2\xb7mQ^L$" + + "\x869\xc4D\x1b\xe5\xdc=myD\x947\xech\xe5" + + "\xa1=-\xf7F\xac\xb2\xb2jZ\x95\xe8/Jf\x84" + + "\xb4\xc7[6\xb7\xe2\xb7)\xac\xaa\x1b\xb2\xdbX\xcb\x86" + + "\x19*\x8e\xfd\x11G\xa3\x03\xe3D\x85$8\x0ai\xb4" + + "h\x1a:F\x89\x0a\x9d\x0a\x9f\x8f\x16S\xa3\x07\x19\xa2" + + "B\xb7\xc2\x17\x82\x01<\x0dNd,\xc0\xbbD\x85\x85" + + "\x0a\xeeW\xe61\x9eF\x8c\xc8\xe8\x0b\xdd\xf7*|\x85" + + "\xc2\xe3\xb14\xe2D\xc6\x00\x96\x11\x15\xfa\x15>\xa6\xf0" + + "\x04K#Ad\x8c`\x9a\xa80\xac\xf0\x09\x85k\xf1" + + "\xb4\x1aQ#\x07\x97\xa8\xf0\x98\xc2\xa7\x14\x9e\x9c\x9fF" + + "R\xcdk\x88\xe7\x15\xbe]\xe1\x1d\x0b\xd2\xe8 2\x9e" + + "\xc4!\xa2\xc2\x16\x85\x97\xc0\x10\x14+\x96\xb4\xfd\\\xa9" + + "\xfd\xc5wK\xd7\xb3\x1c;\xfa\xe7\x8e\xd7,\xa9\x9c\x1d" + + "\\4\xda1\xef\xa4\xd4\xe4\"\xd5\x92w\x02R\x84\xa0" + + "\xe68\x95\xc9\x1b;)\xe5\x9be\x0f\xf3\x08y\x0et" + + "\xb7\xe4\x92\xa0\xc0 \x9c\xeb\xa2oQ\xca\xb1s%$" + + "\x88!\xd1|\xda\x09\x87\xb2E\xb3\x92\xab5\x99X\xde" + + "H\xddw\xea5\xca\x96L_\x96\x00b\x00!p\xeb" + + "\xf6z\xd7\xa9NA\xbaU\xcb6+\xd4<\x89z\x80" + + "\xcdm\x87lmp\xca,\xab\xe7O6[t\xe92" + + "\"\xd1\xcb!V\xb4\xb5\xe8\x80j\xd1~\x0eq/C" + + "J\xcdI\xb3\x1dw\x9b\x95\xba\xbc\xa9\xf1n'He" + + "\xe97\xber\xf6N\xa77o\xba\x9aY\xf5\xfe\xe7\xed" + + "M\xd2K)]i\x17\xb3A\"\x91\xe4\x10i\x86!" + + "7\x94\x1dt\xb7\x04\x7f\xce\x14\xf3\x7f\x0b7\xd4\x88\xd2" + + "\x18\xe18Qs\xcb\"Z.\xba\xd8GL\xcfih" + + "-6D{L\x7f\xc8%\xa6\xaf\xd6\xc0\x9a\x8b\x1d\xd1" + + "\x02\xd7\x97\x1e%\xa6\xf7iA$r4\xd4\x089\x8c" + + " \xca\x8e\xb2a~\xc3\x08\"%E$\x86D\xc3\xc8" + + "\xe3\xce\xcb}\x93\x10g\xbd;\xa9X\xb4\xf5n_\xaf" + + "F\x9c\x94\xe2\xab\xaa\xd5\xe6w\x9aHtr\x88\xf9\x0c" + + "A\xc5\x99U\xc5\xd4d[\x0b\xddJ\xad\x1a\x84#\xcd" + + "J\xa9\xcb\xca\x7f\xba\xe9\x7f\xbf\x12\xd4=\x1c\xe2p[" + + "\xb7>\xaf\xc0g9\xc4\x8bm\x82zD-\x86\xc3\x1c" + + "\xe2tS\xa3\xf4SG\x89\xc4i\x0e\xf1^K\xa0\xf4" + + "w\x94\xe1\xdb\x1c\xe2c\x06M\xban\xc4S\xab\xbb-" + + "\xdd\xad8\xe5\x09\xcb\x96\x9e\x9a\xf2\xd9\xc1VGj\x9c" + + "k\xd2\xad\x9a\xb6\xb4\xe1\xaf7\xadJ\xdd\x954w\x12" + + "scm\xe2\xf0w\x00\x00\x00\xff\xffk\x8c\xa4[" func init() { schemas.Register(schema_db8274f9144abc7e,