Compare commits

...

20 Commits

Author SHA1 Message Date
Nuno Diegues 5b12e74099 Release 2022.2.0 2022-02-04 09:31:15 +00:00
cthuang b1edf5b96d TUN-5702: Allow to deserialize config from JSON 2022-02-04 08:47:59 +00:00
cthuang d07d24e5a2 TUN-5695: Define RPC method to update configuration 2022-02-03 15:05:46 +00:00
Nuno Diegues 0ab6867ae5 TUN-4947: Use http when talking to Unix sockets origins
Right now the proxying of cloudflared -> unix socket is a bit of
a no man's land, where we do not have the ability to specify the
actual protocol since the user just configures "unix:/path/"

In practice, we proxy using an HTTP client.
But it could be that the origin expects HTTP or HTTPS. However,
we have no way of knowing.

So how are we proxying to it? We are configuring the http.Request
in ways that depend on the transport and edge implementation, and
it so happens that for h2mux and http2 we are using a http.Request
whose Scheme is HTTP, whereas for quic we are generating a http.Request
whose scheme is HTTPS.

Since it does not make sense to have different behaviours depending
on the transport, we are making a (hopefully temporary) change so
that proxied requests to Unix sockets are systematically HTTP.

In practice we should do https://github.com/cloudflare/cloudflared/issues/502
to make this configurable.
2022-02-02 19:33:30 +00:00
Nuno Diegues ed2bac026d TUN-5621: Correctly manage QUIC stream closing
Until this PR, we were naively closing the quic.Stream whenever
the callstack for handling the request (HTTP or TCP) finished.
However, our proxy handler may still be reading or writing from
the quic.Stream at that point, because we return the callstack if
either side finishes, but not necessarily both.

This is a problem for quic-go library because quic.Stream#Close
cannot be called concurrently with quic.Stream#Write

Furthermore, we also noticed that quic.Stream#Close does nothing
to do receiving stream (since, underneath, quic.Stream has 2 streams,
1 for each direction), thus leaking memory, as explained in:
https://github.com/lucas-clemente/quic-go/issues/3322

This PR addresses both problems by wrapping the quic.Stream that
is passed down to the proxying logic and handle all these concerns.
2022-02-01 22:01:57 +00:00
João Oliveirinha e09dcf6d60 Release 2022.1.3 2022-01-28 15:10:37 +00:00
João Oliveirinha 76fb329a65 TUN-5724: Fix SSE streaming by guaranteeing we write everything we read 2022-01-28 14:48:54 +00:00
Nuno Diegues 7bac4b15b0 TUN-5719: Re-attempt connection to edge with QUIC despite network error when there is no fallback
We have made 2 changes in the past that caused an unexpected edge case:
 1. when faced with QUIC "no network activity", give up re-attempts and fall-back
 2. when a protocol is chosen explicitly, rather than using auto (the default), do not fallback

The reasoning for 1. was to fallback quickly in situations where the user may not
have chosen QUIC, and simply got it because we auto-chose it (with the TXT DNS record),
but the users' environment does not allow egress via UDP.

The reasoning for 2. was to avoid falling back if the user explicitly chooses a
protocol. E.g., if the user chooses QUIC, she may want to do UDP proxying, so if
we fallback to HTTP2 protocol that will be unexpected since it does not support
UDP (and same applies for HTTP2 falling back to h2mux and TCP proxying).

This PR fixes the edge case that happens when both those changes 1. and 2. are
put together: when faced with a QUIC "no network activity", we should only try
to fallback if there is a possible fallback. Otherwise, we should exhaust the
retries as normal.
2022-01-27 22:12:25 +00:00
cthuang 8a5343d0a5 TUN-5675: Remove github.com/dgrijalva/jwt-go dependency by upgrading coredns version 2022-01-25 15:24:13 +00:00
Sudarsan Reddy a84cbcde7e TUN-5669: Change network command to vnet 2022-01-24 11:26:16 +00:00
Sudarsan Reddy 1a92f1acfe TUN-5477: Unhide vnet commands 2022-01-21 12:41:58 +00:00
cthuang c196679bc7 TUN-5659: Proxy UDP with zero-byte payload 2022-01-21 09:42:05 +00:00
Silver 10fc450ae5 Merge pull request #492 from ibigbug/support-x-forwarded-host
Add X-Forwarded-Host for http proxy
2022-01-19 16:11:25 -06:00
João Oliveirinha 74556bcd7d TUN-5547: Bump golang x/net package to fix http2 transport bugs 2022-01-17 11:13:25 +00:00
cthuang 97309d81ab Release 2022.1.2 2022-01-13 12:13:08 +00:00
cthuang 0292727a95 TUN-5650: Fix pynacl version to 1.4.0 and pygithub version to 1.55 so release doesn't break unexpectedly 2022-01-13 11:34:13 +00:00
cthuang f33897615d Release 2022.1.1 2022-01-12 21:06:45 +00:00
cthuang 6fa58aadba TUN-5623: Configure quic max datagram frame size to 1350 bytes for none Windows platforms 2022-01-11 14:55:43 +00:00
cthuang ef3152f334 TUN-5631: Build everything with go 1.17.5 2022-01-10 12:34:04 +00:00
Yuwei B d1be558ca7 Add X-Forwarded-Host for http proxy 2021-11-16 02:20:36 +11:00
619 changed files with 46820 additions and 15101 deletions
+23
View File
@@ -1,3 +1,26 @@
2022.2.0
- 2022-02-02 TUN-4947: Use http when talking to Unix sockets origins
- 2022-02-02 TUN-5695: Define RPC method to update configuration
- 2022-01-27 TUN-5621: Correctly manage QUIC stream closing
- 2022-01-28 TUN-5702: Allow to deserialize config from JSON
2022.1.3
- 2022-01-21 TUN-5477: Unhide vnet commands
- 2022-01-24 TUN-5669: Change network command to vnet
- 2022-01-25 TUN-5675: Remove github.com/dgrijalva/jwt-go dependency by upgrading coredns version
- 2022-01-27 TUN-5719: Re-attempt connection to edge with QUIC despite network error when there is no fallback
- 2022-01-28 TUN-5724: Fix SSE streaming by guaranteeing we write everything we read
- 2022-01-17 TUN-5547: Bump golang x/net package to fix http2 transport bugs
- 2022-01-19 TUN-5659: Proxy UDP with zero-byte payload
- 2021-10-22 Add X-Forwarded-Host for http proxy
2022.1.2
- 2022-01-13 TUN-5650: Fix pynacl version to 1.4.0 and pygithub version to 1.55 so release doesn't break unexpectedly
2022.1.1
- 2022-01-10 TUN-5631: Build everything with go 1.17.5
- 2022-01-06 TUN-5623: Configure quic max datagram frame size to 1350 bytes for none Windows platforms
2022.1.0
- 2022-01-03 TUN-5612: Add support for specifying TLS min/max version
- 2022-01-03 TUN-5612: Make tls min/max version public visible
+1 -1
View File
@@ -36,7 +36,7 @@ var (
Usage: "Show only routes with this comment.",
}
filterIpRouteByVnet = cli.StringFlag{
Name: "filter-virtual-network-id",
Name: "filter-vnet-id",
Usage: "Show only routes that are attached to the given virtual network ID.",
}
+9 -20
View File
@@ -1,5 +1,5 @@
pinned_go: &pinned_go go=1.17-1
pinned_go_fips: &pinned_go_fips go-boring=1.16.6-7
pinned_go: &pinned_go go=1.17.5-1
pinned_go_fips: &pinned_go_fips go-boring=1.17.5-1
build_dir: &build_dir /cfsetup_build
default-flavor: buster
@@ -43,7 +43,8 @@ stretch: &stretch
pre-cache: &github_release_pkgs_pre_cache
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
- chmod a+x /usr/local/bin/wixl
- pip3 install pygithub
- pip3 install pynacl==1.4.0
- pip3 install pygithub==1.55
post-cache:
# build all packages (except macos and FIPS) and move them to /cfsetup/built_artifacts
- ./build-packages.sh
@@ -134,7 +135,8 @@ stretch: &stretch
- python3-setuptools
- python3-pip
pre-cache: &install_pygithub
- pip3 install pygithub
- pip3 install pynacl==1.4.0
- pip3 install pygithub==1.55
post-cache:
- make github-mac-upload
test:
@@ -244,23 +246,10 @@ centos-7:
pre-cache:
- yum install -y fakeroot
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
- wget https://golang.org/dl/go1.16.3.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.16.3.linux-amd64.tar.gz
- wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.17.5.linux-amd64.tar.gz
post-cache:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make publish-rpm
build-rpm:
build_dir: *build_dir
builddeps: *el7_builddeps
pre-cache:
- yum install -y fakeroot
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
- wget https://golang.org/dl/go1.16.3.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.16.3.linux-amd64.tar.gz
post-cache:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make cloudflared-rpm
- make publish-rpm
+1 -2
View File
@@ -101,8 +101,7 @@ func Commands() []*cli.Command {
buildLoginSubcommand(false),
buildCreateCommand(),
buildRouteCommand(),
// TODO TUN-5477 this should not be hidden
buildVirtualNetworkSubcommand(true),
buildVirtualNetworkSubcommand(false),
buildRunCommand(),
buildListCommand(),
buildInfoCommand(),
@@ -17,7 +17,7 @@ import (
var (
vnetFlag = &cli.StringFlag{
Name: "virtual-network",
Name: "vnet",
Aliases: []string{"vn"},
Usage: "The ID or name of the virtual network to which the route is associated to.",
}
@@ -35,7 +35,7 @@ determine who can reach certain routes.
By default IP routes all exist within a single virtual network. If you use the same IP
space(s) in different physical private networks, all meant to be reachable via IP routes,
then you have to manage the ambiguous IP routes by associating them to virtual networks.
See "cloudflared tunnel network --help" for more information.`,
See "cloudflared tunnel vnet --help" for more information.`,
Subcommands: []*cli.Command{
{
Name: "add",
@@ -50,9 +50,9 @@ the specified Tunnel, and reach an IP in the given CIDR, as long as that IP is
reachable from cloudflared.
If the CIDR exists in more than one private network, to be connected with Cloudflare
Tunnels, then you have to manage those IP routes with virtual networks (see
"cloudflared tunnel network --help)". In those cases, you then have to tell
"cloudflared tunnel vnet --help)". In those cases, you then have to tell
which virtual network's routing table you want to add the route to with:
"cloudflared tunnel route ip add --virtual-network [ID/name] [CIDR] [TUNNEL]".`,
"cloudflared tunnel route ip add --vnet [ID/name] [CIDR] [TUNNEL]".`,
Flags: []cli.Flag{vnetFlag},
},
{
+2 -2
View File
@@ -37,7 +37,7 @@ var (
func buildVirtualNetworkSubcommand(hidden bool) *cli.Command {
return &cli.Command{
Name: "network",
Name: "vnet",
Usage: "Configure and query virtual networks to manage private IP routes with overlapping IPs.",
UsageText: "cloudflared tunnel [--config FILEPATH] network COMMAND [arguments...]",
Description: `cloudflared allows to manage IP routes that expose origins in your private network space via their IP directly
@@ -177,7 +177,7 @@ func listVirtualNetworksCommand(c *cli.Context) error {
if len(vnets) > 0 {
formatAndPrintVnetsList(vnets)
} else {
fmt.Println("No virtual networks were found for the given filter flags. You can use 'cloudflared tunnel network add' to add a virtual network.")
fmt.Println("No virtual networks were found for the given filter flags. You can use 'cloudflared tunnel vnet add' to add a virtual network.")
}
return nil
+27 -25
View File
@@ -175,60 +175,62 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
}
type UnvalidatedIngressRule struct {
Hostname string
Path string
Service string
OriginRequest OriginRequestConfig `yaml:"originRequest"`
Hostname string `json:"hostname"`
Path string `json:"path"`
Service string `json:"service"`
OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"`
}
// OriginRequestConfig is a set of optional fields that users may set to
// customize how cloudflared sends requests to origin services. It is used to set
// up general config that apply to all rules, and also, specific per-rule
// config.
// Note: To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
// Note:
// - To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
// - To specify a time.Duration in json, use int64 of the nanoseconds
type OriginRequestConfig struct {
// HTTP proxy timeout for establishing a new connection
ConnectTimeout *time.Duration `yaml:"connectTimeout"`
ConnectTimeout *time.Duration `yaml:"connectTimeout" json:"connectTimeout"`
// HTTP proxy timeout for completing a TLS handshake
TLSTimeout *time.Duration `yaml:"tlsTimeout"`
TLSTimeout *time.Duration `yaml:"tlsTimeout" json:"tlsTimeout"`
// HTTP proxy TCP keepalive duration
TCPKeepAlive *time.Duration `yaml:"tcpKeepAlive"`
TCPKeepAlive *time.Duration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
NoHappyEyeballs *bool `yaml:"noHappyEyeballs"`
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
// HTTP proxy maximum keepalive connection pool size
KeepAliveConnections *int `yaml:"keepAliveConnections"`
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
// HTTP proxy timeout for closing an idle connection
KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout"`
KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
// Sets the HTTP Host header for the local webserver.
HTTPHostHeader *string `yaml:"httpHostHeader"`
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader"`
// Hostname on the origin server certificate.
OriginServerName *string `yaml:"originServerName"`
OriginServerName *string `yaml:"originServerName" json:"originServerName"`
// Path to the CA for the certificate of your origin.
// This option should be used only if your certificate is not signed by Cloudflare.
CAPool *string `yaml:"caPool"`
CAPool *string `yaml:"caPool" json:"caPool"`
// Disables TLS verification of the certificate presented by your origin.
// Will allow any certificate from the origin to be accepted.
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
NoTLSVerify *bool `yaml:"noTLSVerify"`
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify"`
// Disables chunked transfer encoding.
// Useful if you are running a WSGI server.
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding"`
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
// Runs as jump host
BastionMode *bool `yaml:"bastionMode"`
BastionMode *bool `yaml:"bastionMode" json:"bastionMode"`
// Listen address for the proxy.
ProxyAddress *string `yaml:"proxyAddress"`
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress"`
// Listen port for the proxy.
ProxyPort *uint `yaml:"proxyPort"`
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort"`
// Valid options are 'socks' or empty.
ProxyType *string `yaml:"proxyType"`
ProxyType *string `yaml:"proxyType" json:"proxyType"`
// IP rules for the proxy service
IPRules []IngressIPRule `yaml:"ipRules"`
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"`
}
type IngressIPRule struct {
Prefix *string `yaml:"prefix"`
Ports []int `yaml:"ports"`
Allow bool `yaml:"allow"`
Prefix *string `yaml:"prefix" json:"prefix"`
Ports []int `yaml:"ports" json:"ports"`
Allow bool `yaml:"allow" json:"allow"`
}
type Configuration struct {
@@ -240,7 +242,7 @@ type Configuration struct {
}
type WarpRoutingConfig struct {
Enabled bool `yaml:"enabled"`
Enabled bool `yaml:"enabled" json:"enabled"`
}
type configFileSettings struct {
+96
View File
@@ -1,6 +1,7 @@
package config
import (
"encoding/json"
"testing"
"time"
@@ -26,6 +27,18 @@ func TestConfigFileSettings(t *testing.T) {
)
rawYAML := `
tunnel: config-file-test
originRequest:
ipRules:
- prefix: "10.0.0.0/8"
ports:
- 80
- 8080
allow: false
- prefix: "fc00::/7"
ports:
- 443
- 4443
allow: true
ingress:
- hostname: tunnel1.example.com
path: /id
@@ -53,6 +66,21 @@ counters:
assert.Equal(t, firstIngress, config.Ingress[0])
assert.Equal(t, secondIngress, config.Ingress[1])
assert.Equal(t, warpRouting, config.WarpRouting)
privateV4 := "10.0.0.0/8"
privateV6 := "fc00::/7"
ipRules := []IngressIPRule{
{
Prefix: &privateV4,
Ports: []int{80, 8080},
Allow: false,
},
{
Prefix: &privateV6,
Ports: []int{443, 4443},
Allow: true,
},
}
assert.Equal(t, ipRules, config.OriginRequest.IPRules)
retries, err := config.Int("retries")
assert.NoError(t, err)
@@ -81,3 +109,71 @@ counters:
assert.Equal(t, 456, counters[1])
}
func TestUnmarshalOriginRequestConfig(t *testing.T) {
raw := []byte(`
{
"connectTimeout": 10000000000,
"tlsTimeout": 30000000000,
"tcpKeepAlive": 30000000000,
"noHappyEyeballs": true,
"keepAliveTimeout": 60000000000,
"keepAliveConnections": 10,
"httpHostHeader": "app.tunnel.com",
"originServerName": "app.tunnel.com",
"caPool": "/etc/capool",
"noTLSVerify": true,
"disableChunkedEncoding": true,
"bastionMode": true,
"proxyAddress": "127.0.0.3",
"proxyPort": 9000,
"proxyType": "socks",
"ipRules": [
{
"prefix": "10.0.0.0/8",
"ports": [80, 8080],
"allow": false
},
{
"prefix": "fc00::/7",
"ports": [443, 4443],
"allow": true
}
]
}
`)
var config OriginRequestConfig
assert.NoError(t, json.Unmarshal(raw, &config))
assert.Equal(t, time.Second*10, *config.ConnectTimeout)
assert.Equal(t, time.Second*30, *config.TLSTimeout)
assert.Equal(t, time.Second*30, *config.TCPKeepAlive)
assert.Equal(t, true, *config.NoHappyEyeballs)
assert.Equal(t, time.Second*60, *config.KeepAliveTimeout)
assert.Equal(t, 10, *config.KeepAliveConnections)
assert.Equal(t, "app.tunnel.com", *config.HTTPHostHeader)
assert.Equal(t, "app.tunnel.com", *config.OriginServerName)
assert.Equal(t, "/etc/capool", *config.CAPool)
assert.Equal(t, true, *config.NoTLSVerify)
assert.Equal(t, true, *config.DisableChunkedEncoding)
assert.Equal(t, true, *config.BastionMode)
assert.Equal(t, "127.0.0.3", *config.ProxyAddress)
assert.Equal(t, true, *config.NoTLSVerify)
assert.Equal(t, uint(9000), *config.ProxyPort)
assert.Equal(t, "socks", *config.ProxyType)
privateV4 := "10.0.0.0/8"
privateV6 := "fc00::/7"
ipRules := []IngressIPRule{
{
Prefix: &privateV4,
Ports: []int{80, 8080},
Allow: false,
},
{
Prefix: &privateV6,
Ports: []int{443, 4443},
Allow: true,
},
}
assert.Equal(t, ipRules, config.IPRules)
}
+10 -3
View File
@@ -122,7 +122,7 @@ func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream q
func (q *QUICConnection) acceptStream(ctx context.Context) error {
defer q.Close()
for {
stream, err := q.session.AcceptStream(ctx)
quicStream, err := q.session.AcceptStream(ctx)
if err != nil {
// context.Canceled is usually a user ctrl+c. We don't want to log an error here as it's intentional.
if errors.Is(err, context.Canceled) || q.controlStreamHandler.IsStopped() {
@@ -131,7 +131,9 @@ func (q *QUICConnection) acceptStream(ctx context.Context) error {
return fmt.Errorf("failed to accept QUIC stream: %w", err)
}
go func() {
stream := quicpogs.NewSafeStreamCloser(quicStream)
defer stream.Close()
if err = q.handleStream(stream); err != nil {
q.logger.Err(err).Msg("Failed to handle QUIC stream")
}
@@ -144,7 +146,7 @@ func (q *QUICConnection) Close() {
q.session.CloseWithError(0, "")
}
func (q *QUICConnection) handleStream(stream quic.Stream) error {
func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
signature, err := quicpogs.DetermineProtocol(stream)
if err != nil {
return err
@@ -190,7 +192,7 @@ func (q *QUICConnection) handleDataStream(stream *quicpogs.RequestServerStream)
}
func (q *QUICConnection) handleRPCStream(rpcStream *quicpogs.RPCServerStream) error {
return rpcStream.Serve(q, q.logger)
return rpcStream.Serve(q, q, q.logger)
}
// RegisterUdpSession is the RPC method invoked by edge to register and run a session
@@ -258,6 +260,11 @@ func (q *QUICConnection) UnregisterUdpSession(ctx context.Context, sessionID uui
return q.sessionManager.UnregisterSession(ctx, sessionID, message, true)
}
// UpdateConfiguration is the RPC method invoked by edge when there is a new configuration
func (q *QUICConnection) UpdateConfiguration(ctx context.Context, version int32, config []byte) (*tunnelpogs.UpdateConfigurationResponse, error) {
return nil, fmt.Errorf("TODO: TUN-5698")
}
// streamReadWriteAcker is a light wrapper over QUIC streams with a callback to send response back to
// the client.
type streamReadWriteAcker struct {
+17 -42
View File
@@ -3,14 +3,9 @@ package connection
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http"
"net/url"
@@ -33,7 +28,7 @@ import (
)
var (
testTLSServerConfig = generateTLSConfig()
testTLSServerConfig = quicpogs.GenerateTLSConfig()
testQUICConfig = &quic.Config{
KeepAlive: true,
EnableDatagrams: true,
@@ -84,7 +79,7 @@ func TestQUICServer(t *testing.T) {
},
{
desc: "test http body request streaming",
dest: "/echo_body",
dest: "/slow_echo_body",
connectionType: quicpogs.ConnectionTypeHTTP,
metadata: []quicpogs.Metadata{
{
@@ -195,8 +190,9 @@ func quicServer(
session, err := earlyListener.Accept(ctx)
require.NoError(t, err)
stream, err := session.OpenStreamSync(context.Background())
quicStream, err := session.OpenStreamSync(context.Background())
require.NoError(t, err)
stream := quicpogs.NewSafeStreamCloser(quicStream)
reqClientStream := quicpogs.RequestClientStream{ReadWriteCloser: stream}
err = reqClientStream.WriteConnectRequestData(dest, connectionType, metadata...)
@@ -207,42 +203,20 @@ func quicServer(
if message != nil {
// ALPN successful. Write data.
_, err := stream.Write([]byte(message))
_, err := stream.Write(message)
require.NoError(t, err)
}
response := make([]byte, len(expectedResponse))
stream.Read(response)
require.NoError(t, err)
_, err = stream.Read(response)
if err != io.EOF {
require.NoError(t, err)
}
// For now it is an echo server. Verify if the same data is returned.
assert.Equal(t, expectedResponse, response)
}
// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"argotunnel"},
}
}
type mockOriginProxyWithRequest struct{}
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, r *http.Request, isWebsocket bool) error {
@@ -264,6 +238,9 @@ func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, r *http.Reque
switch r.URL.Path {
case "/ok":
originRespEndpoint(w, http.StatusOK, []byte(http.StatusText(http.StatusOK)))
case "/slow_echo_body":
time.Sleep(5)
fallthrough
case "/echo_body":
resp := &http.Response{
StatusCode: http.StatusOK,
@@ -560,8 +537,7 @@ func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.
}()
// Send a message to the quic session on edge side, it should be deumx to this datagram session
muxedPayload, err := quicpogs.SuffixSessionID(sessionID, payload)
require.NoError(t, err)
muxedPayload := append(payload, sessionID[:]...)
err = edgeQUICSession.SendMessage(muxedPayload)
require.NoError(t, err)
@@ -584,12 +560,12 @@ func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.
if closeType != closedByRemote {
// Session was not closed by remote, so closeUDPSession should be invoked to unregister from remote
unregisterFromEdgeChan := make(chan struct{})
rpcServer := &mockSessionRPCServer{
sessionRPCServer := &mockSessionRPCServer{
sessionID: sessionID,
unregisterReason: expectedReason,
calledUnregisterChan: unregisterFromEdgeChan,
}
go runMockSessionRPCServer(ctx, edgeQUICSession, rpcServer, t)
go runRPCServer(ctx, edgeQUICSession, sessionRPCServer, nil, t)
<-unregisterFromEdgeChan
}
@@ -605,7 +581,7 @@ const (
closedByTimeout
)
func runMockSessionRPCServer(ctx context.Context, session quic.Session, rpcServer *mockSessionRPCServer, t *testing.T) {
func runRPCServer(ctx context.Context, session quic.Session, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
stream, err := session.AcceptStream(ctx)
require.NoError(t, err)
@@ -620,7 +596,7 @@ func runMockSessionRPCServer(ctx context.Context, session quic.Session, rpcServe
assert.NoError(t, err)
log := zerolog.New(os.Stdout)
err = rpcServerStream.Serve(rpcServer, &log)
err = rpcServerStream.Serve(sessionRPCServer, configRPCServer, &log)
assert.NoError(t, err)
}
@@ -642,7 +618,6 @@ func (s mockSessionRPCServer) UnregisterUdpSession(ctx context.Context, sessionI
return fmt.Errorf("expect unregister reason %s, got %s", s.unregisterReason, reason)
}
close(s.calledUnregisterChan)
fmt.Println("unregister from edge")
return nil
}
+6 -1
View File
@@ -106,6 +106,7 @@ func (s *Session) waitForCloseCondition(ctx context.Context, closeAfterIdle time
func (s *Session) dstToTransport(buffer []byte) error {
n, err := s.dstConn.Read(buffer)
s.markActive()
// https://pkg.go.dev/io#Reader suggests caller should always process n > 0 bytes
if n > 0 {
if n <= int(s.transport.MTU()) {
err = s.transport.SendTo(s.ID, buffer[:n])
@@ -114,10 +115,14 @@ func (s *Session) dstToTransport(buffer []byte) error {
s.log.Debug().
Str("session", s.ID.String()).
Int("len", n).
Uint("mtu", s.transport.MTU()).
Int("mtu", s.transport.MTU()).
Msg("dropped packet exceeding MTU")
}
}
// Some UDP application might send 0-size payload.
if err == nil && n == 0 {
err = s.transport.SendTo(s.ID, []byte{})
}
return err
}
+37
View File
@@ -195,3 +195,40 @@ func TestMarkActiveNotBlocking(t *testing.T) {
}
wg.Wait()
}
func TestZeroBytePayload(t *testing.T) {
sessionID := uuid.New()
cfdConn, originConn := net.Pipe()
transport := &mockQUICTransport{
reqChan: newDatagramChannel(1),
respChan: newDatagramChannel(1),
}
log := zerolog.Nop()
session := newSession(sessionID, transport, cfdConn, &log)
ctx, cancel := context.WithCancel(context.Background())
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
// Read from underlying conn and send to transport
closedByRemote, err := session.Serve(ctx, time.Minute*2)
require.Equal(t, context.Canceled, err)
require.False(t, closedByRemote)
return nil
})
errGroup.Go(func() error {
// Write to underlying connection
n, err := originConn.Write([]byte{})
require.NoError(t, err)
require.Equal(t, 0, n)
return nil
})
receivedSessionID, payload, err := transport.respChan.Receive(ctx)
require.NoError(t, err)
require.Len(t, payload, 0)
require.Equal(t, sessionID, receivedSessionID)
cancel()
require.NoError(t, errGroup.Wait())
}
+1 -1
View File
@@ -9,5 +9,5 @@ type transport interface {
// ReceiveFrom reads the next datagram from the transport
ReceiveFrom() (uuid.UUID, []byte, error)
// Max transmission unit to receive from the transport
MTU() uint
MTU() int
}
+2 -2
View File
@@ -22,8 +22,8 @@ func (mt *mockQUICTransport) ReceiveFrom() (uuid.UUID, []byte, error) {
return mt.reqChan.Receive(context.Background())
}
func (mt *mockQUICTransport) MTU() uint {
return 1217
func (mt *mockQUICTransport) MTU() int {
return 1280
}
func (mt *mockQUICTransport) newRequest(ctx context.Context, sessionID uuid.UUID, payload []byte) error {
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.16.4 as builder
FROM golang:1.17.5 as builder
ENV GO111MODULE=on \
CGO_ENABLED=0
WORKDIR /go/src/github.com/cloudflare/cloudflared/
+28 -27
View File
@@ -6,7 +6,7 @@ require (
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
github.com/coredns/coredns v1.7.0
github.com/coredns/coredns v1.8.7
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
@@ -22,52 +22,50 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.0.4
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.7.3
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/json-iterator/go v1.1.10
github.com/kr/text v0.2.0 // indirect
github.com/json-iterator/go v1.1.12
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucas-clemente/quic-go v0.24.0
github.com/mattn/go-colorable v0.1.8
github.com/miekg/dns v1.1.31
github.com/miekg/dns v1.1.45
github.com/mitchellh/go-homedir v1.1.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.13.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92
github.com/rs/zerolog v1.20.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/testify v1.6.0
github.com/urfave/cli/v2 v2.2.0
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
go.uber.org/automaxprocs v1.4.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20211109214657-ef0fda0de508
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
google.golang.org/grpc v1.32.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
google.golang.org/grpc v1.43.0 // indirect
gopkg.in/coreos/go-oidc.v2 v2.1.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
zombiezen.com/go/capnproto2 v2.18.0+incompatible
)
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/apparentlymart/go-cidr v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/caddy v1.0.5 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/coredns/caddy v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
@@ -78,23 +76,26 @@ require (
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.1.3 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.1.1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.26.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
+354 -405
View File
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
package ingress
import (
"encoding/json"
"time"
"github.com/urfave/cli/v2"
@@ -38,6 +39,34 @@ const (
socksProxy = "socks"
)
// RemoteConfig models ingress settings that can be managed remotely, for example through the dashboard.
type RemoteConfig struct {
Ingress Ingress
WarpRouting config.WarpRoutingConfig
}
type remoteConfigJSON struct {
GlobalOriginRequest config.OriginRequestConfig `json:"originRequest"`
IngressRules []config.UnvalidatedIngressRule `json:"ingress"`
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
}
func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
var rawConfig remoteConfigJSON
if err := json.Unmarshal(b, &rawConfig); err != nil {
return err
}
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(rawConfig.GlobalOriginRequest))
if err != nil {
return err
}
rc.Ingress = ingress
rc.WarpRouting = rawConfig.WarpRouting
return nil
}
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
var connectTimeout time.Duration = defaultConnectTimeout
var tlsTimeout time.Duration = defaultTLSTimeout
@@ -119,7 +148,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
}
}
func originRequestFromYAML(y config.OriginRequestConfig) OriginRequestConfig {
func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
out := OriginRequestConfig{
ConnectTimeout: defaultConnectTimeout,
TLSTimeout: defaultTLSTimeout,
@@ -128,50 +157,58 @@ func originRequestFromYAML(y config.OriginRequestConfig) OriginRequestConfig {
KeepAliveTimeout: defaultKeepAliveTimeout,
ProxyAddress: defaultProxyAddress,
}
if y.ConnectTimeout != nil {
out.ConnectTimeout = *y.ConnectTimeout
if c.ConnectTimeout != nil {
out.ConnectTimeout = *c.ConnectTimeout
}
if y.TLSTimeout != nil {
out.TLSTimeout = *y.TLSTimeout
if c.TLSTimeout != nil {
out.TLSTimeout = *c.TLSTimeout
}
if y.TCPKeepAlive != nil {
out.TCPKeepAlive = *y.TCPKeepAlive
if c.TCPKeepAlive != nil {
out.TCPKeepAlive = *c.TCPKeepAlive
}
if y.NoHappyEyeballs != nil {
out.NoHappyEyeballs = *y.NoHappyEyeballs
if c.NoHappyEyeballs != nil {
out.NoHappyEyeballs = *c.NoHappyEyeballs
}
if y.KeepAliveConnections != nil {
out.KeepAliveConnections = *y.KeepAliveConnections
if c.KeepAliveConnections != nil {
out.KeepAliveConnections = *c.KeepAliveConnections
}
if y.KeepAliveTimeout != nil {
out.KeepAliveTimeout = *y.KeepAliveTimeout
if c.KeepAliveTimeout != nil {
out.KeepAliveTimeout = *c.KeepAliveTimeout
}
if y.HTTPHostHeader != nil {
out.HTTPHostHeader = *y.HTTPHostHeader
if c.HTTPHostHeader != nil {
out.HTTPHostHeader = *c.HTTPHostHeader
}
if y.OriginServerName != nil {
out.OriginServerName = *y.OriginServerName
if c.OriginServerName != nil {
out.OriginServerName = *c.OriginServerName
}
if y.CAPool != nil {
out.CAPool = *y.CAPool
if c.CAPool != nil {
out.CAPool = *c.CAPool
}
if y.NoTLSVerify != nil {
out.NoTLSVerify = *y.NoTLSVerify
if c.NoTLSVerify != nil {
out.NoTLSVerify = *c.NoTLSVerify
}
if y.DisableChunkedEncoding != nil {
out.DisableChunkedEncoding = *y.DisableChunkedEncoding
if c.DisableChunkedEncoding != nil {
out.DisableChunkedEncoding = *c.DisableChunkedEncoding
}
if y.BastionMode != nil {
out.BastionMode = *y.BastionMode
if c.BastionMode != nil {
out.BastionMode = *c.BastionMode
}
if y.ProxyAddress != nil {
out.ProxyAddress = *y.ProxyAddress
if c.ProxyAddress != nil {
out.ProxyAddress = *c.ProxyAddress
}
if y.ProxyPort != nil {
out.ProxyPort = *y.ProxyPort
if c.ProxyPort != nil {
out.ProxyPort = *c.ProxyPort
}
if y.ProxyType != nil {
out.ProxyType = *y.ProxyType
if c.ProxyType != nil {
out.ProxyType = *c.ProxyType
}
if len(c.IPRules) > 0 {
for _, r := range c.IPRules {
rule, err := ipaccess.NewRuleByCIDR(r.Prefix, r.Ports, r.Allow)
if err == nil {
out.IPRules = append(out.IPRules, rule)
}
}
}
return out
}
@@ -188,10 +225,10 @@ type OriginRequestConfig struct {
TCPKeepAlive time.Duration `yaml:"tcpKeepAlive"`
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
NoHappyEyeballs bool `yaml:"noHappyEyeballs"`
// HTTP proxy maximum keepalive connection pool size
KeepAliveConnections int `yaml:"keepAliveConnections"`
// HTTP proxy timeout for closing an idle connection
KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"`
// HTTP proxy maximum keepalive connection pool size
KeepAliveConnections int `yaml:"keepAliveConnections"`
// Sets the HTTP Host header for the local webserver.
HTTPHostHeader string `yaml:"httpHostHeader"`
// Hostname on the origin server certificate.
@@ -308,6 +345,19 @@ func (defaults *OriginRequestConfig) setProxyType(overrides config.OriginRequest
}
}
func (defaults *OriginRequestConfig) setIPRules(overrides config.OriginRequestConfig) {
if val := overrides.IPRules; len(val) > 0 {
ipAccessRule := make([]ipaccess.Rule, len(overrides.IPRules))
for i, r := range overrides.IPRules {
rule, err := ipaccess.NewRuleByCIDR(r.Prefix, r.Ports, r.Allow)
if err == nil {
ipAccessRule[i] = rule
}
}
defaults.IPRules = ipAccessRule
}
}
// SetConfig gets config for the requests that cloudflared sends to origins.
// Each field has a setter method which sets a value for the field by trying to find:
// 1. The user config for this rule
@@ -332,5 +382,6 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi
cfg.setProxyPort(overrides)
cfg.setProxyAddress(overrides)
cfg.setProxyType(overrides)
cfg.setIPRules(overrides)
return cfg
}
+422
View File
@@ -0,0 +1,422 @@
package ingress
import (
"encoding/json"
"flag"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
yaml "gopkg.in/yaml.v2"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ipaccess"
)
// Ensure that the nullable config from `config` package and the
// non-nullable config from `ingress` package have the same number of
// fields.
// This test ensures that programmers didn't add a new field to
// one struct and forget to add it to the other ;)
func TestCorrespondingFields(t *testing.T) {
require.Equal(
t,
CountFields(t, config.OriginRequestConfig{}),
CountFields(t, OriginRequestConfig{}),
)
}
func CountFields(t *testing.T, val interface{}) int {
b, err := yaml.Marshal(val)
require.NoError(t, err)
m := make(map[string]interface{}, 0)
err = yaml.Unmarshal(b, &m)
require.NoError(t, err)
return len(m)
}
func TestUnmarshalRemoteConfigOverridesGlobal(t *testing.T) {
rawConfig := []byte(`
{
"originRequest": {
"connectTimeout": 90,
"noHappyEyeballs": true
},
"ingress": [
{
"hostname": "jira.cfops.com",
"service": "http://192.16.19.1:80",
"originRequest": {
"noTLSVerify": true,
"connectTimeout": 10
}
},
{
"service": "http_status:404"
}
],
"warp-routing": {
"enabled": true
}
}
`)
var remoteConfig RemoteConfig
err := json.Unmarshal(rawConfig, &remoteConfig)
require.NoError(t, err)
require.True(t, remoteConfig.Ingress.Rules[0].Config.NoTLSVerify)
require.True(t, remoteConfig.Ingress.defaults.NoHappyEyeballs)
}
func TestOriginRequestConfigOverrides(t *testing.T) {
validate := func(ing Ingress) {
// Rule 0 didn't override anything, so it inherits the user-specified
// root-level configuration.
actual0 := ing.Rules[0].Config
expected0 := OriginRequestConfig{
ConnectTimeout: 1 * time.Minute,
TLSTimeout: 1 * time.Second,
TCPKeepAlive: 1 * time.Second,
NoHappyEyeballs: true,
KeepAliveTimeout: 1 * time.Second,
KeepAliveConnections: 1,
HTTPHostHeader: "abc",
OriginServerName: "a1",
CAPool: "/tmp/path0",
NoTLSVerify: true,
DisableChunkedEncoding: true,
BastionMode: true,
ProxyAddress: "127.1.2.3",
ProxyPort: uint(100),
ProxyType: "socks5",
IPRules: []ipaccess.Rule{
newIPRule(t, "10.0.0.0/8", []int{80, 8080}, false),
newIPRule(t, "fc00::/7", []int{443, 4443}, true),
},
}
require.Equal(t, expected0, actual0)
// Rule 1 overrode all the root-level config.
actual1 := ing.Rules[1].Config
expected1 := OriginRequestConfig{
ConnectTimeout: 2 * time.Minute,
TLSTimeout: 2 * time.Second,
TCPKeepAlive: 2 * time.Second,
NoHappyEyeballs: false,
KeepAliveTimeout: 2 * time.Second,
KeepAliveConnections: 2,
HTTPHostHeader: "def",
OriginServerName: "b2",
CAPool: "/tmp/path1",
NoTLSVerify: false,
DisableChunkedEncoding: false,
BastionMode: false,
ProxyAddress: "interface",
ProxyPort: uint(200),
ProxyType: "",
IPRules: []ipaccess.Rule{
newIPRule(t, "10.0.0.0/16", []int{3000, 3030}, false),
newIPRule(t, "192.16.0.0/24", []int{5000, 5050}, true),
},
}
require.Equal(t, expected1, actual1)
}
rulesYAML := `
originRequest:
connectTimeout: 1m
tlsTimeout: 1s
noHappyEyeballs: true
tcpKeepAlive: 1s
keepAliveConnections: 1
keepAliveTimeout: 1s
httpHostHeader: abc
originServerName: a1
caPool: /tmp/path0
noTLSVerify: true
disableChunkedEncoding: true
bastionMode: True
proxyAddress: 127.1.2.3
proxyPort: 100
proxyType: socks5
ipRules:
- prefix: "10.0.0.0/8"
ports:
- 80
- 8080
allow: false
- prefix: "fc00::/7"
ports:
- 443
- 4443
allow: true
ingress:
- hostname: tun.example.com
service: https://localhost:8000
- hostname: "*"
service: https://localhost:8001
originRequest:
connectTimeout: 2m
tlsTimeout: 2s
noHappyEyeballs: false
tcpKeepAlive: 2s
keepAliveConnections: 2
keepAliveTimeout: 2s
httpHostHeader: def
originServerName: b2
caPool: /tmp/path1
noTLSVerify: false
disableChunkedEncoding: false
bastionMode: false
proxyAddress: interface
proxyPort: 200
proxyType: ""
ipRules:
- prefix: "10.0.0.0/16"
ports:
- 3000
- 3030
allow: false
- prefix: "192.16.0.0/24"
ports:
- 5000
- 5050
allow: true
`
ing, err := ParseIngress(MustReadIngress(rulesYAML))
require.NoError(t, err)
validate(ing)
rawConfig := []byte(`
{
"originRequest": {
"connectTimeout": 60000000000,
"tlsTimeout": 1000000000,
"noHappyEyeballs": true,
"tcpKeepAlive": 1000000000,
"keepAliveConnections": 1,
"keepAliveTimeout": 1000000000,
"httpHostHeader": "abc",
"originServerName": "a1",
"caPool": "/tmp/path0",
"noTLSVerify": true,
"disableChunkedEncoding": true,
"bastionMode": true,
"proxyAddress": "127.1.2.3",
"proxyPort": 100,
"proxyType": "socks5",
"ipRules": [
{
"prefix": "10.0.0.0/8",
"ports": [80, 8080],
"allow": false
},
{
"prefix": "fc00::/7",
"ports": [443, 4443],
"allow": true
}
]
},
"ingress": [
{
"hostname": "tun.example.com",
"service": "https://localhost:8000"
},
{
"hostname": "*",
"service": "https://localhost:8001",
"originRequest": {
"connectTimeout": 120000000000,
"tlsTimeout": 2000000000,
"noHappyEyeballs": false,
"tcpKeepAlive": 2000000000,
"keepAliveConnections": 2,
"keepAliveTimeout": 2000000000,
"httpHostHeader": "def",
"originServerName": "b2",
"caPool": "/tmp/path1",
"noTLSVerify": false,
"disableChunkedEncoding": false,
"bastionMode": false,
"proxyAddress": "interface",
"proxyPort": 200,
"proxyType": "",
"ipRules": [
{
"prefix": "10.0.0.0/16",
"ports": [3000, 3030],
"allow": false
},
{
"prefix": "192.16.0.0/24",
"ports": [5000, 5050],
"allow": true
}
]
}
}
],
"warp-routing": {
"enabled": true
}
}
`)
var remoteConfig RemoteConfig
err = json.Unmarshal(rawConfig, &remoteConfig)
require.NoError(t, err)
validate(remoteConfig.Ingress)
}
func TestOriginRequestConfigDefaults(t *testing.T) {
validate := func(ing Ingress) {
// Rule 0 didn't override anything, so it inherits the cloudflared defaults
actual0 := ing.Rules[0].Config
expected0 := OriginRequestConfig{
ConnectTimeout: defaultConnectTimeout,
TLSTimeout: defaultTLSTimeout,
TCPKeepAlive: defaultTCPKeepAlive,
KeepAliveConnections: defaultKeepAliveConnections,
KeepAliveTimeout: defaultKeepAliveTimeout,
ProxyAddress: defaultProxyAddress,
}
require.Equal(t, expected0, actual0)
// Rule 1 overrode all defaults.
actual1 := ing.Rules[1].Config
expected1 := OriginRequestConfig{
ConnectTimeout: 2 * time.Minute,
TLSTimeout: 2 * time.Second,
TCPKeepAlive: 2 * time.Second,
NoHappyEyeballs: false,
KeepAliveTimeout: 2 * time.Second,
KeepAliveConnections: 2,
HTTPHostHeader: "def",
OriginServerName: "b2",
CAPool: "/tmp/path1",
NoTLSVerify: false,
DisableChunkedEncoding: false,
BastionMode: false,
ProxyAddress: "interface",
ProxyPort: uint(200),
ProxyType: "",
IPRules: []ipaccess.Rule{
newIPRule(t, "10.0.0.0/16", []int{3000, 3030}, false),
newIPRule(t, "192.16.0.0/24", []int{5000, 5050}, true),
},
}
require.Equal(t, expected1, actual1)
}
rulesYAML := `
ingress:
- hostname: tun.example.com
service: https://localhost:8000
- hostname: "*"
service: https://localhost:8001
originRequest:
connectTimeout: 2m
tlsTimeout: 2s
noHappyEyeballs: false
tcpKeepAlive: 2s
keepAliveConnections: 2
keepAliveTimeout: 2s
httpHostHeader: def
originServerName: b2
caPool: /tmp/path1
noTLSVerify: false
disableChunkedEncoding: false
bastionMode: false
proxyAddress: interface
proxyPort: 200
proxyType: ""
ipRules:
- prefix: "10.0.0.0/16"
ports:
- 3000
- 3030
allow: false
- prefix: "192.16.0.0/24"
ports:
- 5000
- 5050
allow: true
`
ing, err := ParseIngress(MustReadIngress(rulesYAML))
if err != nil {
t.Error(err)
}
validate(ing)
rawConfig := []byte(`
{
"ingress": [
{
"hostname": "tun.example.com",
"service": "https://localhost:8000"
},
{
"hostname": "*",
"service": "https://localhost:8001",
"originRequest": {
"connectTimeout": 120000000000,
"tlsTimeout": 2000000000,
"noHappyEyeballs": false,
"tcpKeepAlive": 2000000000,
"keepAliveConnections": 2,
"keepAliveTimeout": 2000000000,
"httpHostHeader": "def",
"originServerName": "b2",
"caPool": "/tmp/path1",
"noTLSVerify": false,
"disableChunkedEncoding": false,
"bastionMode": false,
"proxyAddress": "interface",
"proxyPort": 200,
"proxyType": "",
"ipRules": [
{
"prefix": "10.0.0.0/16",
"ports": [3000, 3030],
"allow": false
},
{
"prefix": "192.16.0.0/24",
"ports": [5000, 5050],
"allow": true
}
]
}
}
]
}
`)
var remoteConfig RemoteConfig
err = json.Unmarshal(rawConfig, &remoteConfig)
require.NoError(t, err)
validate(remoteConfig.Ingress)
}
func TestDefaultConfigFromCLI(t *testing.T) {
set := flag.NewFlagSet("contrive", 0)
c := cli.NewContext(nil, set, nil)
expected := OriginRequestConfig{
ConnectTimeout: defaultConnectTimeout,
TLSTimeout: defaultTLSTimeout,
TCPKeepAlive: defaultTCPKeepAlive,
KeepAliveConnections: defaultKeepAliveConnections,
KeepAliveTimeout: defaultKeepAliveTimeout,
ProxyAddress: defaultProxyAddress,
}
actual := originRequestFromSingeRule(c)
require.Equal(t, expected, actual)
}
func newIPRule(t *testing.T, prefix string, ports []int, allow bool) ipaccess.Rule {
rule, err := ipaccess.NewRuleByCIDR(&prefix, ports, allow)
require.NoError(t, err)
return rule
}
+2 -2
View File
@@ -163,7 +163,7 @@ func (ing Ingress) CatchAll() *Rule {
return &ing.Rules[len(ing.Rules)-1]
}
func validate(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) {
func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) {
rules := make([]Rule, len(ingress))
for i, r := range ingress {
cfg := setConfig(defaults, r.OriginRequest)
@@ -290,7 +290,7 @@ func ParseIngress(conf *config.Configuration) (Ingress, error) {
if len(conf.Ingress) == 0 {
return Ingress{}, ErrNoIngressRules
}
return validate(conf.Ingress, originRequestFromYAML(conf.OriginRequest))
return validateIngress(conf.Ingress, originRequestFromConfig(conf.OriginRequest))
}
func isHTTPService(url *url.URL) bool {
+18 -4
View File
@@ -34,7 +34,7 @@ func Test_parseIngress(t *testing.T) {
localhost8000 := MustParseURL(t, "https://localhost:8000")
localhost8001 := MustParseURL(t, "https://localhost:8001")
fourOhFour := newStatusCode(404)
defaultConfig := setConfig(originRequestFromYAML(config.OriginRequestConfig{}), config.OriginRequestConfig{})
defaultConfig := setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{})
require.Equal(t, defaultKeepAliveConnections, defaultConfig.KeepAliveConnections)
tr := true
type args struct {
@@ -324,7 +324,17 @@ ingress:
{
Hostname: "socks.foo.com",
Service: newSocksProxyOverWSService(accessPolicy()),
Config: defaultConfig,
Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{IPRules: []config.IngressIPRule{
{
Prefix: ipRulePrefix("1.1.1.0/24"),
Ports: []int{80, 443},
Allow: true,
},
{
Prefix: ipRulePrefix("0.0.0.0/0"),
Allow: false,
},
}}),
},
{
Service: &fourOhFour,
@@ -345,7 +355,7 @@ ingress:
{
Hostname: "bastion.foo.com",
Service: newBastionService(),
Config: setConfig(originRequestFromYAML(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}),
Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}),
},
{
Service: &fourOhFour,
@@ -365,7 +375,7 @@ ingress:
{
Hostname: "bastion.foo.com",
Service: newBastionService(),
Config: setConfig(originRequestFromYAML(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}),
Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}),
},
{
Service: &fourOhFour,
@@ -397,6 +407,10 @@ ingress:
}
}
func ipRulePrefix(s string) *string {
return &s
}
func TestSingleOriginSetsConfig(t *testing.T) {
flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
flagSet.Bool("hello-world", true, "")
+3
View File
@@ -23,6 +23,7 @@ type StreamBasedOriginProxy interface {
}
func (o *unixSocketPath) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = "http"
return o.transport.RoundTrip(req)
}
@@ -40,6 +41,8 @@ func (o *httpService) RoundTrip(req *http.Request) (*http.Response, error) {
if o.hostHeader != "" {
// For incoming requests, the Host header is promoted to the Request.Host field and removed from the Header map.
// Pass the original Host header as X-Forwarded-Host.
req.Header.Set("X-Forwarded-Host", req.Host)
req.Host = o.hostHeader
}
return o.transport.RoundTrip(req)
+8 -1
View File
@@ -3,6 +3,7 @@ package ingress
import (
"context"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
@@ -118,7 +119,9 @@ func TestHTTPServiceHostHeaderOverride(t *testing.T) {
w.WriteHeader(http.StatusSwitchingProtocols)
return
}
w.Write([]byte("ok"))
// return the X-Forwarded-Host header for assertions
// as the httptest Server URL isn't available here yet
w.Write([]byte(r.Header.Get("X-Forwarded-Host")))
}
origin := httptest.NewServer(http.HandlerFunc(handler))
defer origin.Close()
@@ -141,6 +144,10 @@ func TestHTTPServiceHostHeaderOverride(t *testing.T) {
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
respBody, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, respBody, []byte(originURL.Host))
}
func tcpListenRoutine(listener net.Listener, closeChan chan struct{}) {
-203
View File
@@ -1,203 +0,0 @@
package ingress
import (
"flag"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
yaml "gopkg.in/yaml.v2"
"github.com/cloudflare/cloudflared/config"
)
// Ensure that the nullable config from `config` package and the
// non-nullable config from `ingress` package have the same number of
// fields.
// This test ensures that programmers didn't add a new field to
// one struct and forget to add it to the other ;)
func TestCorrespondingFields(t *testing.T) {
require.Equal(
t,
CountFields(t, config.OriginRequestConfig{}),
CountFields(t, OriginRequestConfig{}),
)
}
func CountFields(t *testing.T, val interface{}) int {
b, err := yaml.Marshal(val)
require.NoError(t, err)
m := make(map[string]interface{}, 0)
err = yaml.Unmarshal(b, &m)
require.NoError(t, err)
return len(m)
}
func TestOriginRequestConfigOverrides(t *testing.T) {
rulesYAML := `
originRequest:
connectTimeout: 1m
tlsTimeout: 1s
noHappyEyeballs: true
tcpKeepAlive: 1s
keepAliveConnections: 1
keepAliveTimeout: 1s
httpHostHeader: abc
originServerName: a1
caPool: /tmp/path0
noTLSVerify: true
disableChunkedEncoding: true
bastionMode: True
proxyAddress: 127.1.2.3
proxyPort: 100
proxyType: socks5
ingress:
- hostname: tun.example.com
service: https://localhost:8000
- hostname: "*"
service: https://localhost:8001
originRequest:
connectTimeout: 2m
tlsTimeout: 2s
noHappyEyeballs: false
tcpKeepAlive: 2s
keepAliveConnections: 2
keepAliveTimeout: 2s
httpHostHeader: def
originServerName: b2
caPool: /tmp/path1
noTLSVerify: false
disableChunkedEncoding: false
bastionMode: false
proxyAddress: interface
proxyPort: 200
proxyType: ""
`
ing, err := ParseIngress(MustReadIngress(rulesYAML))
if err != nil {
t.Error(err)
}
// Rule 0 didn't override anything, so it inherits the user-specified
// root-level configuration.
actual0 := ing.Rules[0].Config
expected0 := OriginRequestConfig{
ConnectTimeout: 1 * time.Minute,
TLSTimeout: 1 * time.Second,
NoHappyEyeballs: true,
TCPKeepAlive: 1 * time.Second,
KeepAliveConnections: 1,
KeepAliveTimeout: 1 * time.Second,
HTTPHostHeader: "abc",
OriginServerName: "a1",
CAPool: "/tmp/path0",
NoTLSVerify: true,
DisableChunkedEncoding: true,
BastionMode: true,
ProxyAddress: "127.1.2.3",
ProxyPort: uint(100),
ProxyType: "socks5",
}
require.Equal(t, expected0, actual0)
// Rule 1 overrode all the root-level config.
actual1 := ing.Rules[1].Config
expected1 := OriginRequestConfig{
ConnectTimeout: 2 * time.Minute,
TLSTimeout: 2 * time.Second,
NoHappyEyeballs: false,
TCPKeepAlive: 2 * time.Second,
KeepAliveConnections: 2,
KeepAliveTimeout: 2 * time.Second,
HTTPHostHeader: "def",
OriginServerName: "b2",
CAPool: "/tmp/path1",
NoTLSVerify: false,
DisableChunkedEncoding: false,
BastionMode: false,
ProxyAddress: "interface",
ProxyPort: uint(200),
ProxyType: "",
}
require.Equal(t, expected1, actual1)
}
func TestOriginRequestConfigDefaults(t *testing.T) {
rulesYAML := `
ingress:
- hostname: tun.example.com
service: https://localhost:8000
- hostname: "*"
service: https://localhost:8001
originRequest:
connectTimeout: 2m
tlsTimeout: 2s
noHappyEyeballs: false
tcpKeepAlive: 2s
keepAliveConnections: 2
keepAliveTimeout: 2s
httpHostHeader: def
originServerName: b2
caPool: /tmp/path1
noTLSVerify: false
disableChunkedEncoding: false
bastionMode: false
proxyAddress: interface
proxyPort: 200
proxyType: ""
`
ing, err := ParseIngress(MustReadIngress(rulesYAML))
if err != nil {
t.Error(err)
}
// Rule 0 didn't override anything, so it inherits the cloudflared defaults
actual0 := ing.Rules[0].Config
expected0 := OriginRequestConfig{
ConnectTimeout: defaultConnectTimeout,
TLSTimeout: defaultTLSTimeout,
TCPKeepAlive: defaultTCPKeepAlive,
KeepAliveConnections: defaultKeepAliveConnections,
KeepAliveTimeout: defaultKeepAliveTimeout,
ProxyAddress: defaultProxyAddress,
}
require.Equal(t, expected0, actual0)
// Rule 1 overrode all defaults.
actual1 := ing.Rules[1].Config
expected1 := OriginRequestConfig{
ConnectTimeout: 2 * time.Minute,
TLSTimeout: 2 * time.Second,
NoHappyEyeballs: false,
TCPKeepAlive: 2 * time.Second,
KeepAliveConnections: 2,
KeepAliveTimeout: 2 * time.Second,
HTTPHostHeader: "def",
OriginServerName: "b2",
CAPool: "/tmp/path1",
NoTLSVerify: false,
DisableChunkedEncoding: false,
BastionMode: false,
ProxyAddress: "interface",
ProxyPort: uint(200),
ProxyType: "",
}
require.Equal(t, expected1, actual1)
}
func TestDefaultConfigFromCLI(t *testing.T) {
set := flag.NewFlagSet("contrive", 0)
c := cli.NewContext(nil, set, nil)
expected := OriginRequestConfig{
ConnectTimeout: defaultConnectTimeout,
TLSTimeout: defaultTLSTimeout,
TCPKeepAlive: defaultTCPKeepAlive,
KeepAliveConnections: defaultKeepAliveConnections,
KeepAliveTimeout: defaultKeepAliveTimeout,
ProxyAddress: defaultProxyAddress,
}
actual := originRequestFromSingeRule(c)
require.Equal(t, expected, actual)
}
+13 -4
View File
@@ -271,11 +271,20 @@ func (wr *bidirectionalStream) Write(p []byte) (n int, err error) {
func (p *Proxy) writeEventStream(w connection.ResponseWriter, respBody io.ReadCloser) {
reader := bufio.NewReader(respBody)
for {
line, err := reader.ReadBytes('\n')
if err != nil {
break
line, readErr := reader.ReadBytes('\n')
// We first try to write whatever we read even if an error occurred
// The reason for doing it is to guarantee we really push everything to the eyeball side
// before returning
if len(line) > 0 {
if _, writeErr := w.Write(line); writeErr != nil {
return
}
}
if readErr != nil {
return
}
_, _ = w.Write(line)
}
}
+19 -2
View File
@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
@@ -138,6 +139,7 @@ func TestProxySingleOrigin(t *testing.T) {
t.Run("testProxyHTTP", testProxyHTTP(proxy))
t.Run("testProxyWebsocket", testProxyWebsocket(proxy))
t.Run("testProxySSE", testProxySSE(proxy))
t.Run("testProxySSEAllData", testProxySSEAllData(proxy))
cancel()
wg.Wait()
}
@@ -256,6 +258,21 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) {
}
}
// Regression test to guarantee that we always write the contents downstream even if EOF is reached without
// hitting the delimiter
func testProxySSEAllData(proxy *Proxy) func(t *testing.T) {
return func(t *testing.T) {
eyeballReader := io.NopCloser(strings.NewReader("data\r\r"))
responseWriter := newMockSSERespWriter()
// responseWriter uses an unbuffered channel, so we call in a different go-routine
go proxy.writeEventStream(responseWriter, eyeballReader)
result := string(<-responseWriter.writeNotification)
require.Equal(t, "data\r\r", result)
}
}
func TestProxyMultipleOrigins(t *testing.T) {
api := httptest.NewServer(mockAPI{})
defer api.Close()
@@ -447,7 +464,7 @@ func TestConnections(t *testing.T) {
// eyeball connection type.
connectionType connection.Type
//requestheaders to be sent in the call to proxy.Proxy
// requestheaders to be sent in the call to proxy.Proxy
requestHeaders http.Header
}
@@ -508,7 +525,7 @@ func TestConnections(t *testing.T) {
args: args{
ingressServiceScheme: "ws://",
originService: runEchoWSService,
//eyeballResponseWriter gets set after roundtrip dial.
// eyeballResponseWriter gets set after roundtrip dial.
eyeballRequestBody: newPipedWSRequestBody([]byte("test3")),
warpRoutingService: ingress.NewWarpRoutingService(),
requestHeaders: map[string][]string{
+10 -8
View File
@@ -31,8 +31,6 @@ const (
dialTimeout = 15 * time.Second
FeatureSerializedHeaders = "serialized_headers"
FeatureQuickReconnects = "quick_reconnects"
quicHandshakeIdleTimeout = 5 * time.Second
quicMaxIdleTimeout = 15 * time.Second
)
type TunnelConfig struct {
@@ -185,12 +183,11 @@ func ServeTunnelLoop(
case <-gracefulShutdownC:
return nil
case <-protocolFallback.BackoffTimer():
var idleTimeoutError *quic.IdleTimeoutError
if !selectNextProtocol(
connLog.Logger(),
protocolFallback,
config.ProtocolSelector,
errors.As(err, &idleTimeoutError),
err,
) {
return err
}
@@ -223,9 +220,13 @@ func selectNextProtocol(
connLog *zerolog.Logger,
protocolBackoff *protocolFallback,
selector connection.ProtocolSelector,
isNetworkActivityTimeout bool,
cause error,
) bool {
if protocolBackoff.ReachedMaxRetries() || isNetworkActivityTimeout {
var idleTimeoutError *quic.IdleTimeoutError
isNetworkActivityTimeout := errors.As(cause, &idleTimeoutError)
_, hasFallback := selector.Fallback()
if protocolBackoff.ReachedMaxRetries() || (hasFallback && isNetworkActivityTimeout) {
fallback, hasFallback := selector.Fallback()
if !hasFallback {
return false
@@ -520,12 +521,13 @@ func ServeQUIC(
) (err error, recoverable bool) {
tlsConfig := config.EdgeTLSConfigs[connection.QUIC]
quicConfig := &quic.Config{
HandshakeIdleTimeout: quicHandshakeIdleTimeout,
MaxIdleTimeout: quicMaxIdleTimeout,
HandshakeIdleTimeout: quicpogs.HandshakeIdleTimeout,
MaxIdleTimeout: quicpogs.MaxIdleTimeout,
MaxIncomingStreams: connection.MaxConcurrentStreams,
MaxIncomingUniStreams: connection.MaxConcurrentStreams,
KeepAlive: true,
EnableDatagrams: true,
MaxDatagramFrameSize: quicpogs.MaxDatagramFrameSize,
Tracer: quicpogs.NewClientTracer(connLogger.Logger(), connIndex),
}
+41 -17
View File
@@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/lucas-clemente/quic-go"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
@@ -53,7 +54,7 @@ func TestWaitForBackoffFallback(t *testing.T) {
initProtocol := protocolSelector.Current()
assert.Equal(t, connection.HTTP2, initProtocol)
protocolFallback := &protocolFallback{
protoFallback := &protocolFallback{
backoff,
initProtocol,
false,
@@ -61,40 +62,63 @@ func TestWaitForBackoffFallback(t *testing.T) {
// Retry #0 and #1. At retry #2, we switch protocol, so the fallback loop has one more retry than this
for i := 0; i < int(maxRetries-1); i++ {
protocolFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protocolFallback, protocolSelector, false)
protoFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
assert.True(t, ok)
assert.Equal(t, initProtocol, protocolFallback.protocol)
assert.Equal(t, initProtocol, protoFallback.protocol)
}
// Retry fallback protocol
for i := 0; i < int(maxRetries); i++ {
protocolFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protocolFallback, protocolSelector, false)
protoFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
assert.True(t, ok)
fallback, ok := protocolSelector.Fallback()
assert.True(t, ok)
assert.Equal(t, fallback, protocolFallback.protocol)
assert.Equal(t, fallback, protoFallback.protocol)
}
currentGlobalProtocol := protocolSelector.Current()
assert.Equal(t, initProtocol, currentGlobalProtocol)
// No protocol to fallback, return error
protocolFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protocolFallback, protocolSelector, false)
protoFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
assert.False(t, ok)
protocolFallback.reset()
protocolFallback.BackoffTimer() // simulate retry
ok = selectNextProtocol(&log, protocolFallback, protocolSelector, false)
protoFallback.reset()
protoFallback.BackoffTimer() // simulate retry
ok = selectNextProtocol(&log, protoFallback, protocolSelector, nil)
assert.True(t, ok)
assert.Equal(t, initProtocol, protocolFallback.protocol)
assert.Equal(t, initProtocol, protoFallback.protocol)
protocolFallback.reset()
protocolFallback.BackoffTimer() // simulate retry
ok = selectNextProtocol(&log, protocolFallback, protocolSelector, true)
protoFallback.reset()
protoFallback.BackoffTimer() // simulate retry
ok = selectNextProtocol(&log, protoFallback, protocolSelector, &quic.IdleTimeoutError{})
// Check that we get a true after the first try itself when this flag is true. This allows us to immediately
// switch protocols.
// switch protocols when there is a fallback.
assert.True(t, ok)
// But if there is no fallback available, then we exhaust the retries despite the type of error.
// The reason why there's no fallback available is because we pick a specific protocol instead of letting it be auto.
protocolSelector, err = connection.NewProtocolSelector(
"quic",
warpRoutingEnabled,
namedTunnel,
mockFetcher.fetch(),
resolveTTL,
&log,
)
assert.NoError(t, err)
protoFallback = &protocolFallback{backoff, protocolSelector.Current(), false}
for i := 0; i < int(maxRetries-1); i++ {
protoFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protoFallback, protocolSelector, &quic.IdleTimeoutError{})
assert.True(t, ok)
assert.Equal(t, connection.QUIC, protoFallback.protocol)
}
// And finally it fails as it should, with no fallback.
protoFallback.BackoffTimer()
ok = selectNextProtocol(&log, protoFallback, protocolSelector, &quic.IdleTimeoutError{})
assert.False(t, ok)
}
+9 -12
View File
@@ -9,10 +9,7 @@ import (
)
const (
// Max datagram frame size is limited to 1220 https://github.com/lucas-clemente/quic-go/blob/v0.24.0/internal/protocol/params.go#L138
// However, 3 more bytes are reserved https://github.com/lucas-clemente/quic-go/blob/v0.24.0/internal/wire/datagram_frame.go#L61
MaxDatagramFrameSize = 1217
sessionIDLen = len(uuid.UUID{})
sessionIDLen = len(uuid.UUID{})
)
type DatagramMuxer struct {
@@ -34,11 +31,11 @@ func NewDatagramMuxer(quicSession quic.Session) (*DatagramMuxer, error) {
// SendTo suffix the session ID to the payload so the other end of the QUIC session can demultiplex
// the payload from multiple datagram sessions
func (dm *DatagramMuxer) SendTo(sessionID uuid.UUID, payload []byte) error {
if len(payload) > MaxDatagramFrameSize-sessionIDLen {
if len(payload) > maxDatagramPayloadSize {
// TODO: TUN-5302 return ICMP packet too big message
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(payload), dm.MTU())
}
msgWithID, err := SuffixSessionID(sessionID, payload)
msgWithID, err := suffixSessionID(sessionID, payload)
if err != nil {
return errors.Wrap(err, "Failed to suffix session ID to datagram, it will be dropped")
}
@@ -56,17 +53,17 @@ func (dm *DatagramMuxer) ReceiveFrom() (uuid.UUID, []byte, error) {
if err != nil {
return uuid.Nil, nil, err
}
return ExtractSessionID(msg)
return extractSessionID(msg)
}
// Maximum application payload to send to / receive from QUIC datagram frame
func (dm *DatagramMuxer) MTU() uint {
return uint(MaxDatagramFrameSize - sessionIDLen)
func (dm *DatagramMuxer) MTU() int {
return maxDatagramPayloadSize
}
// Each QUIC datagram should be suffixed with session ID.
// ExtractSessionID extracts the session ID and a slice with only the payload
func ExtractSessionID(b []byte) (uuid.UUID, []byte, error) {
// extractSessionID extracts the session ID and a slice with only the payload
func extractSessionID(b []byte) (uuid.UUID, []byte, error) {
msgLen := len(b)
if msgLen < sessionIDLen {
return uuid.Nil, nil, fmt.Errorf("session ID has %d bytes, but data only has %d", sessionIDLen, len(b))
@@ -82,7 +79,7 @@ func ExtractSessionID(b []byte) (uuid.UUID, []byte, error) {
// SuffixSessionID appends the session ID at the end of the payload. Suffix is more performant than prefix because
// the payload slice might already have enough capacity to append the session ID at the end
func SuffixSessionID(sessionID uuid.UUID, b []byte) ([]byte, error) {
func suffixSessionID(sessionID uuid.UUID, b []byte) ([]byte, error) {
if len(b)+len(sessionID) > MaxDatagramFrameSize {
return nil, fmt.Errorf("datagram size exceed %d", MaxDatagramFrameSize)
}
+28 -14
View File
@@ -10,6 +10,7 @@ import (
"encoding/pem"
"math/big"
"testing"
"time"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
@@ -23,11 +24,11 @@ var (
func TestSuffixThenRemoveSessionID(t *testing.T) {
msg := []byte(t.Name())
msgWithID, err := SuffixSessionID(testSessionID, msg)
msgWithID, err := suffixSessionID(testSessionID, msg)
require.NoError(t, err)
require.Len(t, msgWithID, len(msg)+sessionIDLen)
sessionID, msgWithoutID, err := ExtractSessionID(msgWithID)
sessionID, msgWithoutID, err := extractSessionID(msgWithID)
require.NoError(t, err)
require.Equal(t, msg, msgWithoutID)
require.Equal(t, testSessionID, sessionID)
@@ -36,26 +37,27 @@ func TestSuffixThenRemoveSessionID(t *testing.T) {
func TestRemoveSessionIDError(t *testing.T) {
// message is too short to contain session ID
msg := []byte("test")
_, _, err := ExtractSessionID(msg)
_, _, err := extractSessionID(msg)
require.Error(t, err)
}
func TestSuffixSessionIDError(t *testing.T) {
msg := make([]byte, MaxDatagramFrameSize-sessionIDLen)
_, err := SuffixSessionID(testSessionID, msg)
_, err := suffixSessionID(testSessionID, msg)
require.NoError(t, err)
msg = make([]byte, MaxDatagramFrameSize-sessionIDLen+1)
_, err = SuffixSessionID(testSessionID, msg)
_, err = suffixSessionID(testSessionID, msg)
require.Error(t, err)
}
func TestMaxDatagramPayload(t *testing.T) {
payload := make([]byte, MaxDatagramFrameSize-sessionIDLen)
payload := make([]byte, maxDatagramPayloadSize)
quicConfig := &quic.Config{
KeepAlive: true,
EnableDatagrams: true,
KeepAlive: true,
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
}
quicListener := newQUICListener(t, quicConfig)
defer quicListener.Close()
@@ -65,13 +67,19 @@ func TestMaxDatagramPayload(t *testing.T) {
errGroup.Go(func() error {
// Accept quic connection
quicSession, err := quicListener.Accept(ctx)
require.NoError(t, err)
if err != nil {
return err
}
muxer, err := NewDatagramMuxer(quicSession)
require.NoError(t, err)
if err != nil {
return err
}
sessionID, receivedPayload, err := muxer.ReceiveFrom()
require.NoError(t, err)
if err != nil {
return err
}
require.Equal(t, testSessionID, sessionID)
require.True(t, bytes.Equal(payload, receivedPayload))
@@ -89,13 +97,19 @@ func TestMaxDatagramPayload(t *testing.T) {
require.NoError(t, err)
muxer, err := NewDatagramMuxer(quicSession)
require.NoError(t, err)
if err != nil {
return err
}
// Wait a few milliseconds for MTU discovery to take place
time.Sleep(time.Millisecond * 100)
err = muxer.SendTo(testSessionID, payload)
require.NoError(t, err)
if err != nil {
return err
}
// Payload larger than transport MTU, should return an error
largePayload := append(payload, byte(1))
largePayload := make([]byte, MaxDatagramFrameSize)
err = muxer.SendTo(testSessionID, largePayload)
require.Error(t, err)
+9
View File
@@ -0,0 +1,9 @@
//go:build !windows
package quic
const (
MaxDatagramFrameSize = 1350
// maxDatagramPayloadSize is the maximum packet size allowed by warp client
maxDatagramPayloadSize = 1280
)
+11
View File
@@ -0,0 +1,11 @@
//go:build windows
package quic
const (
// Due to https://github.com/lucas-clemente/quic-go/issues/3273, MTU discovery is disabled on Windows
// 1220 is the default value https://github.com/lucas-clemente/quic-go/blob/84e03e59760ceee37359688871bb0688fcc4e98f/internal/protocol/params.go#L138
MaxDatagramFrameSize = 1220
// 3 more bytes are reserved at https://github.com/lucas-clemente/quic-go/blob/v0.24.0/internal/wire/datagram_frame.go#L61
maxDatagramPayloadSize = MaxDatagramFrameSize - 3 - sessionIDLen
)
+16 -9
View File
@@ -17,8 +17,8 @@ import (
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
// The first 6 bytes of the stream is used to distinguish the type of stream. It ensures whoever performs a handshake does
// not write data before writing the metadata.
// ProtocolSignature defines the first 6 bytes of the stream, which is used to distinguish the type of stream. It
// ensures whoever performs a handshake does not write data before writing the metadata.
type ProtocolSignature [6]byte
var (
@@ -29,12 +29,15 @@ var (
RPCStreamProtocolSignature = ProtocolSignature{0x52, 0xBB, 0x82, 0x5C, 0xDB, 0x65}
)
const protocolVersionLength = 2
type protocolVersion string
const (
protocolV1 protocolVersion = "01"
protocolVersionLength = 2
HandshakeIdleTimeout = 5 * time.Second
MaxIdleTimeout = 15 * time.Second
)
// RequestServerStream is a stream to serve requests
@@ -122,7 +125,7 @@ func (rcs *RequestClientStream) ReadConnectResponseData() (*ConnectResponse, err
return nil, err
}
if signature != DataStreamProtocolSignature {
return nil, fmt.Errorf("Wrong protocol signature %v", signature)
return nil, fmt.Errorf("wrong protocol signature %v", signature)
}
// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
@@ -154,13 +157,13 @@ func NewRPCServerStream(stream io.ReadWriteCloser, protocol ProtocolSignature) (
return &RPCServerStream{stream}, nil
}
func (s *RPCServerStream) Serve(sessionManager tunnelpogs.SessionManager, logger *zerolog.Logger) error {
func (s *RPCServerStream) Serve(sessionManager tunnelpogs.SessionManager, configManager tunnelpogs.ConfigurationManager, logger *zerolog.Logger) error {
// RPC logs are very robust, create a new logger that only logs error to reduce noise
rpcLogger := logger.Level(zerolog.ErrorLevel)
rpcTransport := tunnelrpc.NewTransportLogger(&rpcLogger, rpc.StreamTransport(s))
defer rpcTransport.Close()
main := tunnelpogs.SessionManager_ServerToClient(sessionManager)
main := tunnelpogs.CloudflaredServer_ServerToClient(sessionManager, configManager)
rpcConn := rpc.NewConn(
rpcTransport,
rpc.MainInterface(main.Client),
@@ -220,7 +223,7 @@ func writeSignature(stream io.Writer, signature ProtocolSignature) error {
// RPCClientStream is a stream to call methods of SessionManager
type RPCClientStream struct {
client tunnelpogs.SessionManager_PogsClient
client tunnelpogs.CloudflaredServer_PogsClient
transport rpc.Transport
}
@@ -238,7 +241,7 @@ func NewRPCClientStream(ctx context.Context, stream io.ReadWriteCloser, logger *
tunnelrpc.ConnLog(logger),
)
return &RPCClientStream{
client: tunnelpogs.SessionManager_PogsClient{Client: conn.Bootstrap(ctx), Conn: conn},
client: tunnelpogs.NewCloudflaredServer_PogsClient(conn.Bootstrap(ctx), conn),
transport: transport,
}, nil
}
@@ -255,6 +258,10 @@ func (rcs *RPCClientStream) UnregisterUdpSession(ctx context.Context, sessionID
return rcs.client.UnregisterUdpSession(ctx, sessionID, message)
}
func (rcs *RPCClientStream) UpdateConfiguration(ctx context.Context, version int32, config []byte) (*tunnelpogs.UpdateConfigurationResponse, error) {
return rcs.client.UpdateConfiguration(ctx, version, config)
}
func (rcs *RPCClientStream) Close() {
_ = rcs.client.Close()
_ = rcs.transport.Close()
+76 -13
View File
@@ -14,6 +14,8 @@ import (
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
const (
@@ -108,14 +110,10 @@ func TestConnectResponseMeta(t *testing.T) {
}
func TestRegisterUdpSession(t *testing.T) {
clientReader, serverWriter := io.Pipe()
serverReader, clientWriter := io.Pipe()
clientStream := mockRPCStream{clientReader, clientWriter}
serverStream := mockRPCStream{serverReader, serverWriter}
clientStream, serverStream := newMockRPCStreams()
unregisterMessage := "closed by eyeball"
rpcServer := mockRPCServer{
sessionRPCServer := mockSessionRPCServer{
sessionID: uuid.New(),
dstIP: net.IP{172, 16, 0, 1},
dstPort: 8000,
@@ -129,7 +127,7 @@ func TestRegisterUdpSession(t *testing.T) {
assert.NoError(t, err)
rpcServerStream, err := NewRPCServerStream(serverStream, protocol)
assert.NoError(t, err)
err = rpcServerStream.Serve(rpcServer, &logger)
err = rpcServerStream.Serve(sessionRPCServer, nil, &logger)
assert.NoError(t, err)
serverStream.Close()
@@ -139,12 +137,12 @@ func TestRegisterUdpSession(t *testing.T) {
rpcClientStream, err := NewRPCClientStream(context.Background(), clientStream, &logger)
assert.NoError(t, err)
assert.NoError(t, rpcClientStream.RegisterUdpSession(context.Background(), rpcServer.sessionID, rpcServer.dstIP, rpcServer.dstPort, testCloseIdleAfterHint))
assert.NoError(t, rpcClientStream.RegisterUdpSession(context.Background(), sessionRPCServer.sessionID, sessionRPCServer.dstIP, sessionRPCServer.dstPort, testCloseIdleAfterHint))
// Different sessionID, the RPC server should reject the registraion
assert.Error(t, rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), rpcServer.dstIP, rpcServer.dstPort, testCloseIdleAfterHint))
assert.Error(t, rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), sessionRPCServer.dstIP, sessionRPCServer.dstPort, testCloseIdleAfterHint))
assert.NoError(t, rpcClientStream.UnregisterUdpSession(context.Background(), rpcServer.sessionID, unregisterMessage))
assert.NoError(t, rpcClientStream.UnregisterUdpSession(context.Background(), sessionRPCServer.sessionID, unregisterMessage))
// Different sessionID, the RPC server should reject the unregistraion
assert.Error(t, rpcClientStream.UnregisterUdpSession(context.Background(), uuid.New(), unregisterMessage))
@@ -153,7 +151,48 @@ func TestRegisterUdpSession(t *testing.T) {
<-sessionRegisteredChan
}
type mockRPCServer struct {
func TestManageConfiguration(t *testing.T) {
var (
version int32 = 168
config = []byte(t.Name())
)
clientStream, serverStream := newMockRPCStreams()
configRPCServer := mockConfigRPCServer{
version: version,
config: config,
}
logger := zerolog.Nop()
updatedChan := make(chan struct{})
go func() {
protocol, err := DetermineProtocol(serverStream)
assert.NoError(t, err)
rpcServerStream, err := NewRPCServerStream(serverStream, protocol)
assert.NoError(t, err)
err = rpcServerStream.Serve(nil, configRPCServer, &logger)
assert.NoError(t, err)
serverStream.Close()
close(updatedChan)
}()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
rpcClientStream, err := NewRPCClientStream(ctx, clientStream, &logger)
assert.NoError(t, err)
result, err := rpcClientStream.UpdateConfiguration(ctx, version, config)
assert.NoError(t, err)
require.Equal(t, version, result.LastAppliedVersion)
require.NoError(t, result.Err)
rpcClientStream.Close()
<-updatedChan
}
type mockSessionRPCServer struct {
sessionID uuid.UUID
dstIP net.IP
dstPort uint16
@@ -161,7 +200,7 @@ type mockRPCServer struct {
unregisterMessage string
}
func (s mockRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration) error {
func (s mockSessionRPCServer) RegisterUdpSession(_ context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration) error {
if s.sessionID != sessionID {
return fmt.Errorf("expect session ID %s, got %s", s.sessionID, sessionID)
}
@@ -177,7 +216,7 @@ func (s mockRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UU
return nil
}
func (s mockRPCServer) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
func (s mockSessionRPCServer) UnregisterUdpSession(_ context.Context, sessionID uuid.UUID, message string) error {
if s.sessionID != sessionID {
return fmt.Errorf("expect session ID %s, got %s", s.sessionID, sessionID)
}
@@ -187,11 +226,35 @@ func (s mockRPCServer) UnregisterUdpSession(ctx context.Context, sessionID uuid.
return nil
}
type mockConfigRPCServer struct {
version int32
config []byte
}
func (s mockConfigRPCServer) UpdateConfiguration(_ context.Context, version int32, config []byte) (*tunnelpogs.UpdateConfigurationResponse, error) {
if s.version != version {
return nil, fmt.Errorf("expect version %d, got %d", s.version, version)
}
if !bytes.Equal(s.config, config) {
return nil, fmt.Errorf("expect config %v, got %v", s.config, config)
}
return &tunnelpogs.UpdateConfigurationResponse{LastAppliedVersion: version}, nil
}
type mockRPCStream struct {
io.ReadCloser
io.WriteCloser
}
func newMockRPCStreams() (client io.ReadWriteCloser, server io.ReadWriteCloser) {
clientReader, serverWriter := io.Pipe()
serverReader, clientWriter := io.Pipe()
client = mockRPCStream{clientReader, clientWriter}
server = mockRPCStream{serverReader, serverWriter}
return
}
func (s mockRPCStream) Close() error {
_ = s.ReadCloser.Close()
_ = s.WriteCloser.Close()
+43
View File
@@ -0,0 +1,43 @@
package quic
import (
"sync"
"time"
"github.com/lucas-clemente/quic-go"
)
type SafeStreamCloser struct {
lock sync.Mutex
stream quic.Stream
}
func NewSafeStreamCloser(stream quic.Stream) *SafeStreamCloser {
return &SafeStreamCloser{
stream: stream,
}
}
func (s *SafeStreamCloser) Read(p []byte) (n int, err error) {
return s.stream.Read(p)
}
func (s *SafeStreamCloser) Write(p []byte) (n int, err error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.stream.Write(p)
}
func (s *SafeStreamCloser) Close() error {
// Make sure a possible writer does not block the lock forever. We need it, so we can close the writer
// side of the stream safely.
_ = s.stream.SetWriteDeadline(time.Now())
// This lock is eventually acquired despite Write also acquiring it, because we set a deadline to writes.
s.lock.Lock()
defer s.lock.Unlock()
// We have to clean up the receiving stream ourselves since the Close in the bottom does not handle that.
s.stream.CancelRead(0)
return s.stream.Close()
}
+142
View File
@@ -0,0 +1,142 @@
package quic
import (
"context"
"crypto/tls"
"io"
"net"
"sync"
"testing"
"github.com/lucas-clemente/quic-go"
"github.com/stretchr/testify/require"
)
var (
testTLSServerConfig = GenerateTLSConfig()
testQUICConfig = &quic.Config{
KeepAlive: true,
EnableDatagrams: true,
}
exchanges = 1000
msgsPerExchange = 10
testMsg = "Ok message"
)
func TestSafeStreamClose(t *testing.T) {
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
require.NoError(t, err)
udpListener, err := net.ListenUDP(udpAddr.Network(), udpAddr)
require.NoError(t, err)
defer udpListener.Close()
var serverReady sync.WaitGroup
serverReady.Add(1)
var done sync.WaitGroup
done.Add(1)
go func() {
defer done.Done()
quicServer(t, &serverReady, udpListener)
}()
done.Add(1)
go func() {
serverReady.Wait()
defer done.Done()
quicClient(t, udpListener.LocalAddr())
}()
done.Wait()
}
func quicClient(t *testing.T, addr net.Addr) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"argotunnel"},
}
session, err := quic.DialAddr(addr.String(), tlsConf, testQUICConfig)
require.NoError(t, err)
var wg sync.WaitGroup
for exchange := 0; exchange < exchanges; exchange++ {
quicStream, err := session.AcceptStream(context.Background())
require.NoError(t, err)
wg.Add(1)
go func(iter int) {
defer wg.Done()
stream := NewSafeStreamCloser(quicStream)
defer stream.Close()
// Do a bunch of round trips over this stream that should work.
for msg := 0; msg < msgsPerExchange; msg++ {
clientRoundTrip(t, stream, true)
}
// And one that won't work necessarily, but shouldn't break other streams in the session.
if iter%2 == 0 {
clientRoundTrip(t, stream, false)
}
}(exchange)
}
wg.Wait()
}
func quicServer(t *testing.T, serverReady *sync.WaitGroup, conn net.PacketConn) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
earlyListener, err := quic.Listen(conn, testTLSServerConfig, testQUICConfig)
require.NoError(t, err)
serverReady.Done()
session, err := earlyListener.Accept(ctx)
require.NoError(t, err)
var wg sync.WaitGroup
for exchange := 0; exchange < exchanges; exchange++ {
quicStream, err := session.OpenStreamSync(context.Background())
require.NoError(t, err)
wg.Add(1)
go func(iter int) {
defer wg.Done()
stream := NewSafeStreamCloser(quicStream)
defer stream.Close()
// Do a bunch of round trips over this stream that should work.
for msg := 0; msg < msgsPerExchange; msg++ {
serverRoundTrip(t, stream, true)
}
// And one that won't work necessarily, but shouldn't break other streams in the session.
if iter%2 == 1 {
serverRoundTrip(t, stream, false)
}
}(exchange)
}
wg.Wait()
}
func clientRoundTrip(t *testing.T, stream io.ReadWriteCloser, mustWork bool) {
response := make([]byte, len(testMsg))
_, err := stream.Read(response)
if !mustWork {
return
}
if err != io.EOF {
require.NoError(t, err)
}
require.Equal(t, testMsg, string(response))
}
func serverRoundTrip(t *testing.T, stream io.ReadWriteCloser, mustWork bool) {
_, err := stream.Write([]byte(testMsg))
if !mustWork {
return
}
require.NoError(t, err)
}
+34
View File
@@ -0,0 +1,34 @@
package quic
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
)
// GenerateTLSConfig sets up a bare-bones TLS config for a QUIC server
func GenerateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"argotunnel"},
}
}
+4 -12
View File
@@ -2,7 +2,6 @@ package tunneldns
import (
"context"
"sync"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
@@ -11,10 +10,11 @@ import (
"github.com/coredns/coredns/plugin/pkg/rcode"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
)
var once sync.Once
const (
pluginName = "cloudflared"
)
// MetricsPlugin is an adapter for CoreDNS and built-in metrics
type MetricsPlugin struct {
@@ -23,14 +23,6 @@ type MetricsPlugin struct {
// NewMetricsPlugin creates a plugin with configured metrics
func NewMetricsPlugin(next plugin.Handler) *MetricsPlugin {
once.Do(func() {
prometheus.MustRegister(vars.RequestCount)
prometheus.MustRegister(vars.RequestDuration)
prometheus.MustRegister(vars.RequestSize)
prometheus.MustRegister(vars.RequestDo)
prometheus.MustRegister(vars.ResponseSize)
prometheus.MustRegister(vars.ResponseRcode)
})
return &MetricsPlugin{Next: next}
}
@@ -43,7 +35,7 @@ func (p MetricsPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
// Update built-in metrics
server := metrics.WithServer(ctx)
vars.Report(server, state, ".", rcode.ToString(rw.Rcode), rw.Len, rw.Start)
vars.Report(server, state, ".", rcode.ToString(rw.Rcode), pluginName, rw.Len, rw.Start)
return status, err
}
+53
View File
@@ -0,0 +1,53 @@
package pogs
import (
"github.com/cloudflare/cloudflared/tunnelrpc"
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/rpc"
)
type CloudflaredServer interface {
SessionManager
ConfigurationManager
}
type CloudflaredServer_PogsImpl struct {
SessionManager_PogsImpl
ConfigurationManager_PogsImpl
}
func CloudflaredServer_ServerToClient(s SessionManager, c ConfigurationManager) tunnelrpc.CloudflaredServer {
return tunnelrpc.CloudflaredServer_ServerToClient(CloudflaredServer_PogsImpl{
SessionManager_PogsImpl: SessionManager_PogsImpl{s},
ConfigurationManager_PogsImpl: ConfigurationManager_PogsImpl{c},
})
}
type CloudflaredServer_PogsClient struct {
SessionManager_PogsClient
ConfigurationManager_PogsClient
Client capnp.Client
Conn *rpc.Conn
}
func NewCloudflaredServer_PogsClient(client capnp.Client, conn *rpc.Conn) CloudflaredServer_PogsClient {
sessionManagerClient := SessionManager_PogsClient{
Client: client,
Conn: conn,
}
configManagerClient := ConfigurationManager_PogsClient{
Client: client,
Conn: conn,
}
return CloudflaredServer_PogsClient{
SessionManager_PogsClient: sessionManagerClient,
ConfigurationManager_PogsClient: configManagerClient,
Client: client,
Conn: conn,
}
}
func (c CloudflaredServer_PogsClient) Close() error {
c.Client.Close()
return c.Conn.Close()
}
+99
View File
@@ -0,0 +1,99 @@
package pogs
import (
"context"
"fmt"
"github.com/cloudflare/cloudflared/tunnelrpc"
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/rpc"
"zombiezen.com/go/capnproto2/server"
)
type ConfigurationManager interface {
UpdateConfiguration(ctx context.Context, version int32, config []byte) (*UpdateConfigurationResponse, error)
}
type ConfigurationManager_PogsImpl struct {
impl ConfigurationManager
}
func ConfigurationManager_ServerToClient(c ConfigurationManager) tunnelrpc.ConfigurationManager {
return tunnelrpc.ConfigurationManager_ServerToClient(ConfigurationManager_PogsImpl{c})
}
func (i ConfigurationManager_PogsImpl) UpdateConfiguration(p tunnelrpc.ConfigurationManager_updateConfiguration) error {
server.Ack(p.Options)
version := p.Params.Version()
config, err := p.Params.Config()
if err != nil {
return err
}
updateResp, err := i.impl.UpdateConfiguration(p.Ctx, version, config)
if err != nil {
return err
}
result, err := p.Results.NewResult()
if err != nil {
return err
}
return updateResp.Marshal(result)
}
type ConfigurationManager_PogsClient struct {
Client capnp.Client
Conn *rpc.Conn
}
func (c ConfigurationManager_PogsClient) Close() error {
c.Client.Close()
return c.Conn.Close()
}
func (c ConfigurationManager_PogsClient) UpdateConfiguration(ctx context.Context, version int32, config []byte) (*UpdateConfigurationResponse, error) {
client := tunnelrpc.ConfigurationManager{Client: c.Client}
promise := client.UpdateConfiguration(ctx, func(p tunnelrpc.ConfigurationManager_updateConfiguration_Params) error {
p.SetVersion(version)
return p.SetConfig(config)
})
result, err := promise.Result().Struct()
if err != nil {
return nil, wrapRPCError(err)
}
response := new(UpdateConfigurationResponse)
err = response.Unmarshal(result)
if err != nil {
return nil, err
}
return response, nil
}
type UpdateConfigurationResponse struct {
LastAppliedVersion int32 `json:"lastAppliedVersion"`
Err error `json:"err"`
}
func (p *UpdateConfigurationResponse) Marshal(s tunnelrpc.UpdateConfigurationResponse) error {
s.SetLatestAppliedVersion(p.LastAppliedVersion)
if p.Err != nil {
return s.SetErr(p.Err.Error())
}
return nil
}
func (p *UpdateConfigurationResponse) Unmarshal(s tunnelrpc.UpdateConfigurationResponse) error {
p.LastAppliedVersion = s.LatestAppliedVersion()
respErr, err := s.Err()
if err != nil {
return err
}
if respErr != "" {
p.Err = fmt.Errorf(respErr)
}
return nil
}
+16 -1
View File
@@ -151,4 +151,19 @@ interface SessionManager {
# Let the edge decide closeAfterIdle to make sure cloudflared doesn't close session before the edge closes its side
registerUdpSession @0 (sessionId :Data, dstIp :Data, dstPort: UInt16, closeAfterIdleHint: Int64) -> (result :RegisterUdpSessionResponse);
unregisterUdpSession @1 (sessionId :Data, message: Text) -> ();
}
}
struct UpdateConfigurationResponse {
# Latest configuration that was applied successfully. The err field might be populated at the same time to indicate
# that cloudflared is using an older configuration because the latest cannot be applied
latestAppliedVersion @0 :Int32;
# Any error encountered when trying to apply the last configuration
err @1 :Text;
}
# ConfigurationManager defines RPC to manage cloudflared configuration remotely
interface ConfigurationManager {
updateConfiguration @0 (version :Int32, config :Data) -> (result: UpdateConfigurationResponse);
}
interface CloudflaredServer extends(SessionManager, ConfigurationManager) {}
+660 -198
View File
@@ -3880,204 +3880,661 @@ func (p SessionManager_unregisterUdpSession_Results_Promise) Struct() (SessionMa
return SessionManager_unregisterUdpSession_Results{s}, err
}
const schema_db8274f9144abc7e = "x\xda\xccY}p\x14e\x9a\x7f\x9e\xee\x99t\x02\x19" +
"f\xbaz 0%\x97\x93\xc2\xf2\x88\x82\x06\xce+\x8e" +
"\xb3.\x09\x06\xceD>\xd23p\xe5\x09Zvf\xde" +
"\x84\xc9\xcdt\x0f\xdd=\x91 \xc8\x87 b\xf9\x05\x82" +
"\"\xca\xc9ayW\xa0\xde\xc1\xa9\xe7\xb2%\xb5\xb2+" +
"*\xa5\xa8X\xb0\x85\x8a\xb5\x8b\xc8\xeeJ\xc1\xba\"\xac" +
"\xe5\xaeko=\xdd\xd3\x1f\x99\x84$\xc8\xfe\xb1\xffM" +
"\x9e~\xde\xf7}>~\xcf\xef}\xde'\xd7wT6" +
"r\xf5\xe1\x9a\x08\x80\xbc%\\a\xb1\xba\x0f\x97n\xbf" +
"\xeag\xabAN Z\xf7\xbc\xd6\x1a\xff\xd6\\\xfd\x09" +
"\x84y\x01`\xca\xe2\x8a\xa5(\xad\xad\x10\x00\xa4U\x15" +
"\xbf\x06\xb4\xee\x1b\xb5\xfb\x99\xe7fl\xba\x17\xc4\x04\xef" +
"+\x03NaB+J=\x02i\x16\x85u\xd2Q\xfa" +
"e\xdd\"^\xb7 \xfe\xc1{\xa4\x1d\xdc:D[\xef" +
"\x13\xeaP:d/8(\xd0\xd67\xe6\xdf\xdf\xf1\x0f" +
"\x9b\xdfY\x03b\x82\xeb\xb5\xf5+\x95KQ:XI" +
"\x9a\x07*\xe7\x02Z_o\x1a\xfd\xfc\x7f\xbe\xf7\xf6Z" +
"\x10\xafF(Y\xfai\xe5\xc7\x08(}U\xf9\xbf\x80" +
"\xd6\xa1\x0b\x0b\xce\xbf\xfc\xe6\x0d\xf7\x818\x81\x14\x90\x14" +
"6T\x8d\xe3\x00\xa5\x9dU\x0d\x80\xd6\xe93\x7f\\w" +
"\xf7\x849\x8f\x82<\x019\x800G\x1a\x07\xab\x12\xa4" +
"q\xa2\x8a\xaci\x98yhob\xca\xe3\x9b\xcaL\xb7" +
"\x15\xf7\x0f\xabC\xe9\xf002\xe8\xd0\xb0\xbb\x00\xad\xdf" +
"\x8fx\xea\xbd\xe2M\xaf>^:\xcfV\xaa\x1f^G" +
"\xbb\xb5\x0c'\x85q\xddW\xdd\xf9\xd3\x03/=\x01\xf2" +
"DD\xebx\xfb5G\xf9m\xbb>\x81\xf9(\xd0\xf1" +
"Sv\x0e\xdfA\xc6\xef\xb5u\xdf\xbf\xf6\xb5\x1f?\xfa" +
"\xd2\xba\xa7@\xbe\x1a\x11\xc0\x0e\xd6\xd8\xea?\x90B}" +
"5\x19\xbf\xe9\xd8\xbe9\xf9\x0d[w8\xee\xdb\xdf\xff" +
"\xad\x9a\xe3 d\xadi\xf9&?\xff\xd9\xd4\xb3\xa5\xc0" +
"\x84\xe9\xd3\xec\xeas\x088E\xa9\xaeE@\xeb\x86\x8f" +
"O\xcd\x9d\xfd\x7f\x1d\xff\x1dX\xbb<\xb2\x94\xd6\xae\xeb" +
"8\xb7?\x96\xcc?_\xe6\xb0\x1d\xbb\x9e\xc8.\x946" +
"D\xc8\xe1\x87\"d\xc2\x8b\x7fsK\xd5\x92S3w" +
"\x838\xd1\xdd\xe6\xc5H\x92\xb6\x09\xdd\xce\x7f\xafl\xf9" +
"\xc9\xcb\xe5p\xb2c\xb23\xd2\x8e\xd2>\xdag\xca\xde" +
"\x88m\xcf\x03\xfb\xb7^S\xf9\xcc\xd7\xaf\xf4\x17\xe6\x13" +
"#\xdaQ\xba0\x82N\xfdj\x04Efd\x0b\x1e\x7f" +
"\xbd>\xf4j0\xefr\xf44E\x86E)\xefc\xcf" +
"N\x8f\xa8_\xae~\xbdl7[1\x1ckEiL" +
"\x8cv\x1b\x19#\xe5\xd6\x05\x8fm\x0c\x9fz\xec-\xb2" +
"4\x00\xb80\x01m\xca\x9e\x98\x8e\xd2\x81\x98\x9d\xedX" +
"\x0d\x0fh%v\xff\xd3\xffL\xcf|\xf4N?\x96J" +
"M\xf1s\xd2\xec8\xfdj\x89\x93\xa1''\xee\xb9\xfb" +
"\x8b\x87\x0e\x1f)\x19j\xc7\xf0\xb9\xb8\x9d\xc2\xbdq\x8a" +
"\x9f\x87\x80\xb2(\xd9\x9a\x1f\xc5\xbbP:ko\xf7\x85" +
"\xad\xcd\x9dR\xc6\xac\xfc\xf9?\x1f\x0f$\xedl\xfc3" +
"\x84\x905\xe7_\x17tU-?y2x\xd0\x89\xb8" +
"\x1d\x91\x0b\xf6\xd2\xdf\xfe\xd7\xe9G\xce\xe43\xbf\xb2\x81" +
"\xe7\xc6l\xe4\xc8i\x04\xcd\x89#\x09\xe85\xb5\x91\x19" +
"\xe3\x8e\xb5\x9dvR\xe9lQ5j:)\\9\x8a" +
"\xb6\xb8\xe1\xce&\xb6p\xea\xad\xa7\xfb\x94|\xd3\xa8i" +
"(\xc9\xa3l\x90\x8dZ\x87\x12\xab\xa9\x01\xb0\xba\xff\x7f" +
"\xc3\xad\xcf\xbf1\xe7\x9cS\x0b\xb6\xb1\xf3k&\x134" +
"\x1e\xbe\xa7y\xee?\x8e\xdb\x7f.h\xec\xec\x1aB\xa7" +
"\xa4\xd4\xd0I\x1dS\xcf\xfc\xcbU\x0f\xbfy\xae?\x08" +
"\xae\xaa\xa9CiC\x8d\x0dAR\xfer\xe6\x7f\x1cI" +
"D\x13\xe7\xcb\x02Xa'\xaf\xa6\x0b\xa5\x035v\xf2" +
"j\xde\"\x98\xdd\xf7\xc9\x1dK>\xbc\xf7\xeb\x0b\xe5\xb9" +
"\xb6\xb7~eL\x12\xa5\x83cl~\x19C\xc8xb" +
"\xdeoV\x9c\xd9<\xea\x9b\xbe$\x97\xe8B\xa9'a" +
"\x93\\b\x9dt\x94~Y\x1f\x08\xcf\xd67\xafx\xe7" +
"\xdb@-\xecK\xb4\x92\xc3\x8f\x0bO\x9f\\\xf9\x8b;" +
"\xbe\x0b:\xbc7\xf1\x199|(A\x0e/\xfb\xf2\xc9" +
"\x9b\x1fY\xf8\xc2\xf7\xc1\xc4&V\xd3R\xb3\xa8\xaa," +
"\xa7\x17B\xe9\xeb\xdc\x9f\xe9Ii\xa5\xa0\x16\xa65\x15" +
"\xcdEL5\xb3i\xc5dI\xd6`\x144\xd5`m" +
"\x88r\x8c\x0f\x01\x84\x10@T\xba\x00\xe4;y\x94s" +
"\x1c\x8a\x88qJ\xbd\x98%\xe1\"\x1ee\x93C\x91\xe3" +
"\xe2\xc4<\xe2\xe2q\x00r\x8eGy\x09\x87\xc8\xc7\x91" +
"\x07\x10\x8b\x1b\x01\xe4%<\xcak8\xb4\x0aL\xcf+" +
"*S!j\xce\xd0u\xac\x06\x0e\xab\x01-\x9d\x99z" +
"\x8f\xd2\x9e\x83(\x0b\x88\x85\xae\xbbL\x8c\x00\x87\x11@" +
"k\x91V\xd4\x8d\xf9\xaa\x89\xd9\\\x92u\xe8\xcc\xc0E" +
"X\x01\x1cV\x00\x0e\xe4^\x8a\x19FVSg+\xaa" +
"\xd2\xc9t\x00\xf2\xac\x92\x0f\x03x\xa4\x8d.\xbd\x8b\xf5" +
"[\x81\x13'\x0a\xe830\xba\xf0\x13\xaf\xdc\x05\x9c8" +
"V\xb0t\xd6\x995L\xa6\xe3\xfcL\xc1\xde\x9b\xd7\xd4" +
"F\xb4\x8a\xaa\xf3\x01\x99\xee|\x88\xd2\xa9\x8d\xd8\x86\xbe" +
"u|_\xebn\xcae\x99jF[\xd4\x0e\xad,\xe4" +
"\xad\xfd\x85\xbc\xb5\x14\xf25\x81\x90\xaf\x9a\x0e /\xe3" +
"Q\xbe\x9fC\x91/\xc5|m\x1d\x80\xbc\x92G\xf9A" +
"\x0e\xad\xb4}HK\x06\x00\xbchv0\xc5,\xea\xcc" +
" \xd9\x08\xc06\x1e\xed\xa0\x8f\x00\\\xd1\xcdt\xb2\xdd" +
"MBT\xd1\xd3\x8b\xbcD\x0d\x10\xe9\x19K\xb2\x86\x99" +
"U;\xe7\xd9\xf2\x866-\x97M\xf7\x90W\xd5\xb6\x9d" +
"c\xa7\x01 \x8a#o\x03@N\x14\xa7\x034d;" +
"UMgV&k\xa45Ue\xc0\xa7\xcd\x15\xedJ" +
"NQ\xd3\xcc;\xa8\xa2\xefA\xce\x01)\xa6w3}" +
"\x92\x12\x80\xef\xf86EW\xf8\xbc!W{q\x9cq" +
"\x1b\x80\xdc\xcc\xa3\xdc\x16\x88\xe3l\x8a\xe3,\x1e\xe5[" +
"\x03q\x9cOql\xe3Q^\xc8\xa1\xa5\xe9\xd9\xce\xac" +
"z\x13\x03^\x0f\"\xd00U%\xcf(f\xa5x\xac" +
"\xd0\x0afVS\x0d\x8c\xf9\xfc\x0f\x88\xb1@\xa4\x84\xc1" +
"09\xc9\x85\x94\x8b(M\x1d\x9fdFQ\xc8\x99\x86" +
"\x1c\xf2<\x89L\x03\x90+y\x94\xe3\x1c6\xe8\xcc(" +
"\xe6L\x8c\xf9\xd7\xec_\xe2T7|\x01\x18&\xfb\x83" +
"\xe1d\x009\xc3\xa3\\\xe0\x10K\xd1\xcbO\x0f\xb0\x01" +
"\x8f\x0e\x0a\x17o\x05\x90M\x1e\xe5\x95\x1cZ\x86sH" +
"\x0b`\xc6\x8dhm\xc60[\x0a\xee_+2\x86\xd9" +
"\xa6\xe9&\x0a\xc0\xa1\x00\x84[\xcd`M\x1dTS-" +
"\x99\x1c\xbb9\xcb\xab&\x86\x81\xc30\x0cXT\x0e>" +
"\xa2DlN\xb5\xbb\xdeL 0\xfc\x1d\x8f\xf2\xdf\x07" +
"\xbc\xa9'\x1e\xbb\x9eG\xf9F\x0e-%\x9d\xd6\x8a\xaa" +
"9\x0fx\xa5\xb3\x0c\xf3)\x06\xd1\xb4\xce|8\x0c=" +
"\xd4.9\x94\x05;\xaa+y#h^\xb2?\xf3(" +
"\xb0\xd7\xf2(O\xed?\x86+\xf2\xcc0\x94N\xd6\xa7" +
"B\xc3\xfd\xb0\x0dUY\x9a\x00\x9bd\x0e\xcfO\xd2\x99" +
"!\x14s&YQmY\x8e\x19\x94\xde\xf1<\xca\xd7" +
"s\x18\xc1\xef-\xc7\x8e\x89\x1b\xfd0\xd52]\xd7t" +
"\x8c\xf9\xf7`\x09}\xe9\xd2\x01\xa8\xa9\xcd\xccT\xb29" +
"\xa4\xca\xf0\x9a\xb22\x8c\x0eV\xda~\xd8\x1c\xf1\xf8\x06" +
"\x02h\xbeWQ\x10\xc2b<\xcaWphu\xeaJ" +
"\x9a\xb51\x1d\xb3Zf\x8e\xa2j)\x9e\xa5\xfb\xe0e" +
"\xc4\xa5\x1e\x9a\xb4K\xcd\x00o\xd5\xc0\xebuV\x0aB" +
"iy[\xadcs\xdc\xb3y\xf98\xff>\xf4\xd2\xbc" +
"\xaa\xdd'l\x8f\x92\xd6\x13^\xef\xe7Q\xde\x14\xa0\xf6" +
"\x0dD^\x8f\xf2(?\xcd\xa1\x18\x0a\xc51\x04 >" +
"I(\xd9\xc4\xa3\xbc\x9d\xeb}k\xb2n\xa6\x9a\xcd\xd9" +
"N\x10\x98\xe1K\xc9\xc4\xe6l'\x03\xde\xb8\\z\xab" +
"\x1c$\x1eZ\xbb\xa1\xe5\x98\xc9\x9aY:\xa7\xe8\x8a\x99" +
"\xedf\xce\xf7\x12\x18\xdd\xa4\x0e\x84\xdbd\x9f\xea!\xfc" +
"F\xddF%\x00\x87q>G\x0a,\xd0_\x0c`\xad" +
"\xb39Y\xa6\xa9}0\xe0WL\x09\x07h\x0ct\x05" +
"\xfa\xeas\x0bfV\xd0T\x83\xec\x0b\xa4~Z\x7f\xa9" +
"\xd7\xfd\xd4\xbbt\xba~u0\xf3%:\xdd\xb0\xd5O" +
"\xb2\x18\xe2\x9c\xcco\xdb\x01 o\xe7Q~\x81\xc3\x06" +
"\xe7\xa6\xc7\x98\xffR.e\xcb\xb9\xcffiP\x9bV" +
"r>\xe5Z:+\xe4\x944\x9b\x81\xa5\xbb\x1b\x10\x81" +
"C\xb4!\x92/\xe8\xcc00\xab\xa9rQ\xc9ey" +
"\xb3\xc7\xeb\xb7\xd4b\xbeMg\xddY\xd4\x8aF\x93i" +
"\xb2\xbcP0\x8d\xa1tc~\x80\x88\x1f\x84l\xce(" +
"c\xe8:\x9f{\xbc\x00M\xec\xf2)0Z,f=" +
"\xee\xb3rZ\xda\xce\x1bD\xe7(\xf9\xbe\x14X1h" +
"\xad\xf6\xaat\x97\x91\xff\x9a\xba\x87\x81\x1bvr\xdd\xee" +
"h\x03&S\x094\xf2(\xcf\x0a\x98\xdc29\xe0\x87" +
"k\xf2\xecv\xdf\x0f\xe1\xdfY\x8fkU-\xcb\x13s" +
"\xbb\xc1,9\xd3\x04\xc2-\xbe\xce@\xf6\x05\x0bjn" +
"\xa1\xd6\xf6\x90l\x9c\xea\xda(\xf5`+@j\x09\xf2" +
"\x98Z\x83\xbe\x99\xd2*\x9c\x0e\x90ZF\xf2\xfb\xd1\xb7" +
"TZ\x8b\x09\x80\xd4J\x92?\x88\xde\xc3BZ\x8f\xbb" +
"\x00R\x0f\x92x\x0b\xa9\x87x\xbb$\xa4\xcd\xf6\xf6\x9b" +
"H\xbe\x9d\xe4\xe1P\x1c\xc3\x00\xd26\xac\x03Hm!" +
"\xf9\xcb$\xaf\xe0\xe2X\x01 \xed\xc1.\x80\xd4n\x92" +
"\xbfFr!\x1c\xa7\xb7\x95\xb4\x17u\x80\xd4\x8fH\xfe" +
"\x06\xc9+G\xc7\xb1\x12@\xdao\xcb_'\xf9\xbb$" +
"\xaf\x1a\x13\xc7*\x00\xe9 \xae\x06H\xbdM\xf2#$" +
"\x1f\x86q\x1c\x06 \x1d\xc6\xad\x00\xa9#$\xff%\xc9" +
"\x87W\xc4q8\x80\xf4\xa9m\xcf1\x92\x7fN\xf2\xea" +
"P\x1c\xab\x01\xa4\x13\xb8\x03 \xf59\xc9\x7fG\xf2\x88" +
"\x10\xc7\x08\x80t\xd6\xf6\xeb\x0c\xc9+\xb9\xb2\xbe\xdeE" +
"TY\xf3\xcek\x86\x972V\xaaqt\xe0\xde\xa6E" +
"\xa9A\xc7\xa8?)\x03\xc4(\xa0U\xd0\xb4\xdc\x9c\xde" +
"H\x8d\x9aJ\xa7\xe1>\x14b\xfe\xf0\x02\x90\x84\xde\xbd" +
"\x0fQMm\xc9xDP\xce:\xae%Y\xa3\xa9h" +
"j\xc5\x02\xd4f\x14\x93e<\xce\xd1\x8b\xeaL]\xcb" +
"\xcfC\xa6\xe7\xb3\xaa\x92\x1b\x84\x8d\xaa\x80\xc3*(Q" +
"\x82\xbb\xf7\xc0\xd4t\xf1g\x8f\x87h\xae\x1c\xd1\xb5\x85" +
"i\xf3\x94\xce\xa1\xf0\xd4d\xbf\x7f\x8b\xaa\x01B\xaa\xed" +
"Vr\xc5\x1fBO\xbd[\x89d\x83\xd3\x8a\x0c\xf6(" +
"pg\x19\x83SI\xef\x86\xb0\xf7\x85\x8a\x811#\x9d" +
"\xc3\x95\xf6\x1f\xb2\xf9\x9d\xcct~\xd1\xeb\x96\xde\x16B" +
"\xf0\x9a\xbf\xb4\xd5IfD\x87\xe2\xba?\xf3\x19\xfc=" +
"\xd4\xcf\xc5\xdf\xcf\xb5\xef\xf6\x9c\x817\x11\xe5~!\x8f" +
"\xf2\xa2@\xeeYk?o\xa2\xa4?\x0c\x11y\xae4" +
"\x0d\xa1\x8b\xa2\xc0\xa3\xbc\x8c\xc3(=^1\xe6\x0f\x87" +
"{\x19\xdd\xfb\xc1NPhQ3\x0cp\x89\x8b\xe6\xc0" +
"\xf5\xe1\x8dI\x07\xef\xce\x86\xe6\xb6\xdb\xf5\x0e\x1apo" +
"\xf4Xv\xf2E\xdfe\x0d\xce\xa1\x84\xb3\xd1\xf6\x1c\xc6" +
"\x1d\xc3\xa2;\xd0\x13\xf7,\x05N\xdc)\xa0?\xaaD" +
"w2)n\xd3\x81\x137\x0b\xc8y\x83mt\x07\xd8" +
"\xe2\xfa\x07\x80\x13\xd7\x0a\xc8{sitGb\xf5=" +
"\xc3\x108q\xb9\x80!o\xde\x8f\xee@M\\\xdc\x05" +
"\x9c\x98\x150\xec\x8d\xbc\xd1\x9d\xb9\x8a\xb7\xaf\x06N\x9c" +
"\xef\x0f~\xa0\xc1\xf1\xa3\x11-\x17\xa3Pk\xa3\xb4\xf7" +
"\x18\xc8\xd1\x02hD\xcb\xed\x81\xf9\x8b5\xc1\xb6\x96;" +
"\xc9\x80hZ1Y#5gN\xfdc\x89\x00\xa0\x11" +
"\xe5\x10\x06\xe6\x89\x00\x97\xfb\x08M\xb2Z;\xcf?\xb4" +
"er\xd7\xff@J\xe2\xfb\xb3\x9a\xce\xf1&b\x81}" +
"\xa9\x0b\xac\xe6Q\x1e\xcd\x0d\xda\xf8\x85.\xe6\x85\x0b\xfe" +
"(-\xa6\xfd\xff\xd6\xdb\xff05N\xef\xf2(\x1f\x0b" +
"\x94\xf5Q\x12~\xc0\xa3|<\xd08}D\xb5~\x8c" +
"G\xf9\xbc?\xe4\xfc\xea\x01\x00\xf9<\x8f\xc9@#\"" +
"\xfe\x89\x14\xbf\xa3\xeb\xdanC\xd0iC\xc2\xb8\x11 " +
"UI\xd7x\xdcnCBN\x1b\"b;@*F" +
"\xf2+\x82m\xc8\x18\xbc\x0d 5\x9a\xe4\xe3\xb1\xf7\xbb" +
"F(\xea~\xa3\x96\xd3:ge\xd5~\xef6w\xea" +
"\x8a\xe6L%\x9b+\xea\x0c\xfc\xab\xb5D6\xcd\x81\xdb" +
"\xde\x19\xc7:\x93\x97\x14\x810\x83\x867\x95\xb9\x84\x17" +
"\xe5\x90n\x9e\x19\xba\xae\xa1^\xd6\xc4N\xf6\x9bX\xaf" +
"\x87\xa5^\xfcf\x1e\xe5y\x94\x8aF'\x15r\xbb\xdf" +
"v\xd7\xa6\x95\xa2\xc1\xfa\xf8\x00<\xd3\xbd)\x80\xb1H" +
"+\xe62I\x06\x82\xa9\xf7\x94\x85`\xd0f6\xc5\xa2" +
".s9\x13d\xf7\xbf!\xe8\xfe\xd3#0Av\xc7" +
"\xf8\xe8\xfeo\xab\xef\x04\xd9\x8dA\x9f\x09\xb2\xf3\xc1\xc6" +
"h\xef\x09\xf2e<_\x9dk,\xc0\x18\x974X\x1d" +
"\xf2<\xd2\xfb\xf7oY\xa5W]\xee\x98\xc0\xbd\x90\xfe" +
"\x1c\x00\x00\xff\xff\xa1\x1ap\xe9"
type UpdateConfigurationResponse struct{ capnp.Struct }
// UpdateConfigurationResponse_TypeID is the unique identifier for the type UpdateConfigurationResponse.
const UpdateConfigurationResponse_TypeID = 0xdb58ff694ba05cf9
func NewUpdateConfigurationResponse(s *capnp.Segment) (UpdateConfigurationResponse, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
return UpdateConfigurationResponse{st}, err
}
func NewRootUpdateConfigurationResponse(s *capnp.Segment) (UpdateConfigurationResponse, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
return UpdateConfigurationResponse{st}, err
}
func ReadRootUpdateConfigurationResponse(msg *capnp.Message) (UpdateConfigurationResponse, error) {
root, err := msg.RootPtr()
return UpdateConfigurationResponse{root.Struct()}, err
}
func (s UpdateConfigurationResponse) String() string {
str, _ := text.Marshal(0xdb58ff694ba05cf9, s.Struct)
return str
}
func (s UpdateConfigurationResponse) LatestAppliedVersion() int32 {
return int32(s.Struct.Uint32(0))
}
func (s UpdateConfigurationResponse) SetLatestAppliedVersion(v int32) {
s.Struct.SetUint32(0, uint32(v))
}
func (s UpdateConfigurationResponse) Err() (string, error) {
p, err := s.Struct.Ptr(0)
return p.Text(), err
}
func (s UpdateConfigurationResponse) HasErr() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s UpdateConfigurationResponse) ErrBytes() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return p.TextBytes(), err
}
func (s UpdateConfigurationResponse) SetErr(v string) error {
return s.Struct.SetText(0, v)
}
// UpdateConfigurationResponse_List is a list of UpdateConfigurationResponse.
type UpdateConfigurationResponse_List struct{ capnp.List }
// NewUpdateConfigurationResponse creates a new list of UpdateConfigurationResponse.
func NewUpdateConfigurationResponse_List(s *capnp.Segment, sz int32) (UpdateConfigurationResponse_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}, sz)
return UpdateConfigurationResponse_List{l}, err
}
func (s UpdateConfigurationResponse_List) At(i int) UpdateConfigurationResponse {
return UpdateConfigurationResponse{s.List.Struct(i)}
}
func (s UpdateConfigurationResponse_List) Set(i int, v UpdateConfigurationResponse) error {
return s.List.SetStruct(i, v.Struct)
}
func (s UpdateConfigurationResponse_List) String() string {
str, _ := text.MarshalList(0xdb58ff694ba05cf9, s.List)
return str
}
// UpdateConfigurationResponse_Promise is a wrapper for a UpdateConfigurationResponse promised by a client call.
type UpdateConfigurationResponse_Promise struct{ *capnp.Pipeline }
func (p UpdateConfigurationResponse_Promise) Struct() (UpdateConfigurationResponse, error) {
s, err := p.Pipeline.Struct()
return UpdateConfigurationResponse{s}, err
}
type ConfigurationManager struct{ Client capnp.Client }
// ConfigurationManager_TypeID is the unique identifier for the type ConfigurationManager.
const ConfigurationManager_TypeID = 0xb48edfbdaa25db04
func (c ConfigurationManager) UpdateConfiguration(ctx context.Context, params func(ConfigurationManager_updateConfiguration_Params) error, opts ...capnp.CallOption) ConfigurationManager_updateConfiguration_Results_Promise {
if c.Client == nil {
return ConfigurationManager_updateConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
Method: capnp.Method{
InterfaceID: 0xb48edfbdaa25db04,
MethodID: 0,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:ConfigurationManager",
MethodName: "updateConfiguration",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 8, PointerCount: 1}
call.ParamsFunc = func(s capnp.Struct) error { return params(ConfigurationManager_updateConfiguration_Params{Struct: s}) }
}
return ConfigurationManager_updateConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
type ConfigurationManager_Server interface {
UpdateConfiguration(ConfigurationManager_updateConfiguration) error
}
func ConfigurationManager_ServerToClient(s ConfigurationManager_Server) ConfigurationManager {
c, _ := s.(server.Closer)
return ConfigurationManager{Client: server.New(ConfigurationManager_Methods(nil, s), c)}
}
func ConfigurationManager_Methods(methods []server.Method, s ConfigurationManager_Server) []server.Method {
if cap(methods) == 0 {
methods = make([]server.Method, 0, 1)
}
methods = append(methods, server.Method{
Method: capnp.Method{
InterfaceID: 0xb48edfbdaa25db04,
MethodID: 0,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:ConfigurationManager",
MethodName: "updateConfiguration",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := ConfigurationManager_updateConfiguration{c, opts, ConfigurationManager_updateConfiguration_Params{Struct: p}, ConfigurationManager_updateConfiguration_Results{Struct: r}}
return s.UpdateConfiguration(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 1},
})
return methods
}
// ConfigurationManager_updateConfiguration holds the arguments for a server call to ConfigurationManager.updateConfiguration.
type ConfigurationManager_updateConfiguration struct {
Ctx context.Context
Options capnp.CallOptions
Params ConfigurationManager_updateConfiguration_Params
Results ConfigurationManager_updateConfiguration_Results
}
type ConfigurationManager_updateConfiguration_Params struct{ capnp.Struct }
// ConfigurationManager_updateConfiguration_Params_TypeID is the unique identifier for the type ConfigurationManager_updateConfiguration_Params.
const ConfigurationManager_updateConfiguration_Params_TypeID = 0xb177ca2526a3ca76
func NewConfigurationManager_updateConfiguration_Params(s *capnp.Segment) (ConfigurationManager_updateConfiguration_Params, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
return ConfigurationManager_updateConfiguration_Params{st}, err
}
func NewRootConfigurationManager_updateConfiguration_Params(s *capnp.Segment) (ConfigurationManager_updateConfiguration_Params, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1})
return ConfigurationManager_updateConfiguration_Params{st}, err
}
func ReadRootConfigurationManager_updateConfiguration_Params(msg *capnp.Message) (ConfigurationManager_updateConfiguration_Params, error) {
root, err := msg.RootPtr()
return ConfigurationManager_updateConfiguration_Params{root.Struct()}, err
}
func (s ConfigurationManager_updateConfiguration_Params) String() string {
str, _ := text.Marshal(0xb177ca2526a3ca76, s.Struct)
return str
}
func (s ConfigurationManager_updateConfiguration_Params) Version() int32 {
return int32(s.Struct.Uint32(0))
}
func (s ConfigurationManager_updateConfiguration_Params) SetVersion(v int32) {
s.Struct.SetUint32(0, uint32(v))
}
func (s ConfigurationManager_updateConfiguration_Params) Config() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return []byte(p.Data()), err
}
func (s ConfigurationManager_updateConfiguration_Params) HasConfig() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s ConfigurationManager_updateConfiguration_Params) SetConfig(v []byte) error {
return s.Struct.SetData(0, v)
}
// ConfigurationManager_updateConfiguration_Params_List is a list of ConfigurationManager_updateConfiguration_Params.
type ConfigurationManager_updateConfiguration_Params_List struct{ capnp.List }
// NewConfigurationManager_updateConfiguration_Params creates a new list of ConfigurationManager_updateConfiguration_Params.
func NewConfigurationManager_updateConfiguration_Params_List(s *capnp.Segment, sz int32) (ConfigurationManager_updateConfiguration_Params_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}, sz)
return ConfigurationManager_updateConfiguration_Params_List{l}, err
}
func (s ConfigurationManager_updateConfiguration_Params_List) At(i int) ConfigurationManager_updateConfiguration_Params {
return ConfigurationManager_updateConfiguration_Params{s.List.Struct(i)}
}
func (s ConfigurationManager_updateConfiguration_Params_List) Set(i int, v ConfigurationManager_updateConfiguration_Params) error {
return s.List.SetStruct(i, v.Struct)
}
func (s ConfigurationManager_updateConfiguration_Params_List) String() string {
str, _ := text.MarshalList(0xb177ca2526a3ca76, s.List)
return str
}
// ConfigurationManager_updateConfiguration_Params_Promise is a wrapper for a ConfigurationManager_updateConfiguration_Params promised by a client call.
type ConfigurationManager_updateConfiguration_Params_Promise struct{ *capnp.Pipeline }
func (p ConfigurationManager_updateConfiguration_Params_Promise) Struct() (ConfigurationManager_updateConfiguration_Params, error) {
s, err := p.Pipeline.Struct()
return ConfigurationManager_updateConfiguration_Params{s}, err
}
type ConfigurationManager_updateConfiguration_Results struct{ capnp.Struct }
// ConfigurationManager_updateConfiguration_Results_TypeID is the unique identifier for the type ConfigurationManager_updateConfiguration_Results.
const ConfigurationManager_updateConfiguration_Results_TypeID = 0x958096448eb3373e
func NewConfigurationManager_updateConfiguration_Results(s *capnp.Segment) (ConfigurationManager_updateConfiguration_Results, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return ConfigurationManager_updateConfiguration_Results{st}, err
}
func NewRootConfigurationManager_updateConfiguration_Results(s *capnp.Segment) (ConfigurationManager_updateConfiguration_Results, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return ConfigurationManager_updateConfiguration_Results{st}, err
}
func ReadRootConfigurationManager_updateConfiguration_Results(msg *capnp.Message) (ConfigurationManager_updateConfiguration_Results, error) {
root, err := msg.RootPtr()
return ConfigurationManager_updateConfiguration_Results{root.Struct()}, err
}
func (s ConfigurationManager_updateConfiguration_Results) String() string {
str, _ := text.Marshal(0x958096448eb3373e, s.Struct)
return str
}
func (s ConfigurationManager_updateConfiguration_Results) Result() (UpdateConfigurationResponse, error) {
p, err := s.Struct.Ptr(0)
return UpdateConfigurationResponse{Struct: p.Struct()}, err
}
func (s ConfigurationManager_updateConfiguration_Results) HasResult() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s ConfigurationManager_updateConfiguration_Results) SetResult(v UpdateConfigurationResponse) error {
return s.Struct.SetPtr(0, v.Struct.ToPtr())
}
// NewResult sets the result field to a newly
// allocated UpdateConfigurationResponse struct, preferring placement in s's segment.
func (s ConfigurationManager_updateConfiguration_Results) NewResult() (UpdateConfigurationResponse, error) {
ss, err := NewUpdateConfigurationResponse(s.Struct.Segment())
if err != nil {
return UpdateConfigurationResponse{}, err
}
err = s.Struct.SetPtr(0, ss.Struct.ToPtr())
return ss, err
}
// ConfigurationManager_updateConfiguration_Results_List is a list of ConfigurationManager_updateConfiguration_Results.
type ConfigurationManager_updateConfiguration_Results_List struct{ capnp.List }
// NewConfigurationManager_updateConfiguration_Results creates a new list of ConfigurationManager_updateConfiguration_Results.
func NewConfigurationManager_updateConfiguration_Results_List(s *capnp.Segment, sz int32) (ConfigurationManager_updateConfiguration_Results_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
return ConfigurationManager_updateConfiguration_Results_List{l}, err
}
func (s ConfigurationManager_updateConfiguration_Results_List) At(i int) ConfigurationManager_updateConfiguration_Results {
return ConfigurationManager_updateConfiguration_Results{s.List.Struct(i)}
}
func (s ConfigurationManager_updateConfiguration_Results_List) Set(i int, v ConfigurationManager_updateConfiguration_Results) error {
return s.List.SetStruct(i, v.Struct)
}
func (s ConfigurationManager_updateConfiguration_Results_List) String() string {
str, _ := text.MarshalList(0x958096448eb3373e, s.List)
return str
}
// ConfigurationManager_updateConfiguration_Results_Promise is a wrapper for a ConfigurationManager_updateConfiguration_Results promised by a client call.
type ConfigurationManager_updateConfiguration_Results_Promise struct{ *capnp.Pipeline }
func (p ConfigurationManager_updateConfiguration_Results_Promise) Struct() (ConfigurationManager_updateConfiguration_Results, error) {
s, err := p.Pipeline.Struct()
return ConfigurationManager_updateConfiguration_Results{s}, err
}
func (p ConfigurationManager_updateConfiguration_Results_Promise) Result() UpdateConfigurationResponse_Promise {
return UpdateConfigurationResponse_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
}
type CloudflaredServer struct{ Client capnp.Client }
// CloudflaredServer_TypeID is the unique identifier for the type CloudflaredServer.
const CloudflaredServer_TypeID = 0xf548cef9dea2a4a1
func (c CloudflaredServer) RegisterUdpSession(ctx context.Context, params func(SessionManager_registerUdpSession_Params) error, opts ...capnp.CallOption) SessionManager_registerUdpSession_Results_Promise {
if c.Client == nil {
return SessionManager_registerUdpSession_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
Method: capnp.Method{
InterfaceID: 0x839445a59fb01686,
MethodID: 0,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:SessionManager",
MethodName: "registerUdpSession",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 16, PointerCount: 2}
call.ParamsFunc = func(s capnp.Struct) error { return params(SessionManager_registerUdpSession_Params{Struct: s}) }
}
return SessionManager_registerUdpSession_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
func (c CloudflaredServer) UnregisterUdpSession(ctx context.Context, params func(SessionManager_unregisterUdpSession_Params) error, opts ...capnp.CallOption) SessionManager_unregisterUdpSession_Results_Promise {
if c.Client == nil {
return SessionManager_unregisterUdpSession_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
Method: capnp.Method{
InterfaceID: 0x839445a59fb01686,
MethodID: 1,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:SessionManager",
MethodName: "unregisterUdpSession",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 2}
call.ParamsFunc = func(s capnp.Struct) error { return params(SessionManager_unregisterUdpSession_Params{Struct: s}) }
}
return SessionManager_unregisterUdpSession_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
func (c CloudflaredServer) UpdateConfiguration(ctx context.Context, params func(ConfigurationManager_updateConfiguration_Params) error, opts ...capnp.CallOption) ConfigurationManager_updateConfiguration_Results_Promise {
if c.Client == nil {
return ConfigurationManager_updateConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
Method: capnp.Method{
InterfaceID: 0xb48edfbdaa25db04,
MethodID: 0,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:ConfigurationManager",
MethodName: "updateConfiguration",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 8, PointerCount: 1}
call.ParamsFunc = func(s capnp.Struct) error { return params(ConfigurationManager_updateConfiguration_Params{Struct: s}) }
}
return ConfigurationManager_updateConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
type CloudflaredServer_Server interface {
RegisterUdpSession(SessionManager_registerUdpSession) error
UnregisterUdpSession(SessionManager_unregisterUdpSession) error
UpdateConfiguration(ConfigurationManager_updateConfiguration) error
}
func CloudflaredServer_ServerToClient(s CloudflaredServer_Server) CloudflaredServer {
c, _ := s.(server.Closer)
return CloudflaredServer{Client: server.New(CloudflaredServer_Methods(nil, s), c)}
}
func CloudflaredServer_Methods(methods []server.Method, s CloudflaredServer_Server) []server.Method {
if cap(methods) == 0 {
methods = make([]server.Method, 0, 3)
}
methods = append(methods, server.Method{
Method: capnp.Method{
InterfaceID: 0x839445a59fb01686,
MethodID: 0,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:SessionManager",
MethodName: "registerUdpSession",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := SessionManager_registerUdpSession{c, opts, SessionManager_registerUdpSession_Params{Struct: p}, SessionManager_registerUdpSession_Results{Struct: r}}
return s.RegisterUdpSession(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 1},
})
methods = append(methods, server.Method{
Method: capnp.Method{
InterfaceID: 0x839445a59fb01686,
MethodID: 1,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:SessionManager",
MethodName: "unregisterUdpSession",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := SessionManager_unregisterUdpSession{c, opts, SessionManager_unregisterUdpSession_Params{Struct: p}, SessionManager_unregisterUdpSession_Results{Struct: r}}
return s.UnregisterUdpSession(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
})
methods = append(methods, server.Method{
Method: capnp.Method{
InterfaceID: 0xb48edfbdaa25db04,
MethodID: 0,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:ConfigurationManager",
MethodName: "updateConfiguration",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := ConfigurationManager_updateConfiguration{c, opts, ConfigurationManager_updateConfiguration_Params{Struct: p}, ConfigurationManager_updateConfiguration_Results{Struct: r}}
return s.UpdateConfiguration(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 1},
})
return methods
}
const schema_db8274f9144abc7e = "x\xda\xccZ{t\x14\xe7u\xbfwfW#\x81V" +
"\xab\xf1\xac\xd1\x03T\xb5:P\x179\xd8\x06Jk\xab" +
"9\xd1\xc3\x12\xb1d\x03\x9a]\x94\xe3cC\x8eG\xbb" +
"\x9f\xa4Qwg\x96\x99Y\x19\x11\x130\x01c\xfb\xb8" +
"\x8eq\xc0\xb1Ih0.\xed\x01\xdb\xad\x89\xdd\xa6\xee" +
"1\xa7\xa6\xcd\xabq\xc0&\x87\xf4\x90@\x9a&\x84>" +
"8\xb8\xae14\x876\xf1\xf4\xdc\x99\x9d\x87v\x17\x09" +
"\x8c\xff\xc8\x7f\xab;\xdf\xe3\xde\xdf\xf7\xbb\x8f\xef~\xba" +
"\xed\xe6\x9a.nq\xf4\xed:\x00\xf9\xa5h\x95\xcd\xda" +
"\x7f\xb0a\xef\x82\x7f\xdc\x02r3\xa2\xfd\xf97\x06\x12" +
"\x97\xad-\xa7 \xca\x0b\x00KW\x08\x1bPR\x04\x01" +
"@Z+\xfc;\xa0\xfd\xc8\x9cW\xbe\xb6\xbfo\xe7\x17" +
"@l\xe6\x83\xc1\x80K\xbb\xab\x07P\x1a\xaa\xa6\x91r" +
"\xf5v\xe9\x10\xfd\xb2\xef\x16o\xbd?\xf1\xce1\x1a\x1d" +
"^:BK?W\xdd\x8e\xd2\x01g\xc2\xfejZ\xfa" +
"\x93\xb9\xb7\xf7\xfd\xc1\xae\xb7\xb6\x82\xd8\xccMYzG" +
"\xcd\x06\x94\xf6\xd7\xd0\xc8\xe7kV\x01\xda\x1f\xecl|" +
"\xf1\xf9c\xdf\xdd\x06\xe2M\x08EM_\xaf\xf91\x02" +
"JGk\xfe\x0a\xd0>z\xe9\xfe\x8b\xaf}{\xd9#" +
" .\xa4\x01H\x03r\xb3\xda8@i\xdb\xacN@" +
"\xfb\xdc\xf9\xff\xdb\xfe\xb9\x85+\x9f\x02y!r\x00Q" +
"\x8eF\xec\x9f\xd5L#\x0e\xcf\"m:\x97\x1f}\xbd" +
"y\xe93;KTw\x06\xee\x99\xdd\x8e\xd2\xcb\xb3I" +
"\xa1\x03\xb3\x1f\x04\xb4?\xf5\x87\xaf>\xd9\xfb\xcc\xe6]" +
" \xde\xea\xef\x17\xab\xbd\x8fV[XK\xfb\xfdO\xdd" +
"W\x8e\x15\xee\xfc\xc63E\x85\x9cU\xfak\xdbi\x80" +
"RK+\xb4M,x\xe0\x1f\xbe\xf5\xea\x97A^\x84" +
"h\x9f\x1e\xbe\xf9\x87\xfc\x9e\x83\xa7`\x08\x05\xd2o\xe9" +
"\x91\xda}d\xddqg\xec\xdb\x9fx\xe3\xef\x9ezu" +
"\xfbW@\xbe\x09\x11\xc0AsY\xec\x7fi@\x7f\x8c" +
"v\xdby\xf2\xf0\xca\xdc\x8e\xdd\xfb\\|\x9c\xef\xebb" +
"\x1c\x07\x11{k\xff/sC/\xa4^(\"\x17\xa5" +
"O,v\x01\x01\x97N\xc6Z\x11\xd0^\xf6\xe3\xb3\xab" +
"V|}\xe4/Bsw\xd5m\xa0\xb9\xdbG.\x1c" +
"\xa9O\xe6^,A\xc41vG\xddA\x94\x0e\xd49" +
"\x87YG*\xbc\xfc[w\xd7\xac?\xbb\xfc\x15\x10\x17" +
"y\xcb|\xab.I\xcbL|\xef\x85\xdf]\xf0\xbd\x07" +
"\x0f\x81|+\xfa`\x1d\xa1o(\xfd\xa4\x8e\xec\x8b\x9c" +
"Zp\xf0\xf0O\x9f|\xad\x8ccw\xc47\xa0\xb4\"" +
"N\xbb\xf4\xc7?-M\xd2/;\xb2\x96\xffPy\xf6" +
"\xef_+\xe5\xaf\x83\xb1\x12\x1fF\xa9\x10w\x10\x88;" +
"\xf6=~d\xf7\xcd\xd5_\xfb\xe0\xaf+\x9d\xebs\xf5" +
"\xc3(\xbd\\\xef\x9ck=irc?\x9e~sq" +
"\xe4\x1ba\xa2\xd5\x88\xe7\x08\xe9\x16\x91\x88\xd6\xf2nO" +
"L{o\xcb\x9b%\xab9\x03\x0f\x8b\x03(\x1d\x17i" +
"\xb5\xa3\xce\xe0\x81\xfb\xbf\xf4t\xf4\xec\x97\xbeC\x9a\x86" +
"\x18\x1e%\x1fX\xaa\xde`\xa0\xb4\xf1\x06\xfa9yC" +
"\x03\x0fh7\xbf\xf2G\x7f\xd9\x93\xf9\xd1[\x154\x95" +
".\xdfxA\x8a\xce\xa1_8\x87\x14=\xb3\xe8\xd0\xe7" +
"\xfe\xf3O\x8e\x9f(*\xea`\xbav\x8eC\x89us" +
"\xe8<.\xaf\xd9{\xb7j\xdf{\xaa\x14%\xf7\xf4\xe6" +
"|\x1d\xa5\x03\xcer\xfb\x9d\xe5|\xfeU\x1a]\xd30" +
"\x8eRK\x03\x8dnj\xa0\xb5\xb9\xb3J\xd3\xe6\x7f\xfe" +
"\xd4\xe9\x10eZ\x1a~\x8e\x10\xb1W~\xe6\xfe\xf1\x9a" +
"\x8dg\xce\x84\xd5\x12\x1b\x1c\xfc\x168S\xff\xeb\xcf\xcf" +
"}\xf1|.\xf3o\x0e\xed=\x84\xfb\x1a:\x88\x0ck" +
"\x1b\xc8\x0f\x1bZc}m'\x07\xcf\xb9Dr\x97\xb8" +
"\xa3\xb1\x87\x06\xc8\x8d\xb4\xc4\xb2\x07\xba\xd9\x9a\xdb\xef=" +
"W\xc6\x96u\x8d\x1d(=\xdcH\x1366nGi" +
"WS\x03\x80=\xf17;\xee}\xf1\x9b+/\xb8\x9e" +
"\xe8(\xbb\xadi\x09\x11\xf3\xc9\xcf\xf7\xae\xba\xa3\xed\xc8" +
"\x85\xb0\xb2\x1b\x9b\xc87\xa4\x1dM\xb4\xd3\xc8\xed\xe7?" +
"\xbd\xe0\xc9o_\xa8\xe4\x00\x87\x9a\xdaQ:\xd2D\xa0" +
"\x1c\xa6\xc1\xef-\xff\xd3\x13\xcd\xf1\xe6\x8b%\x00V\xd1" +
"\xd8\x9f5\x8d\xa3t\x89\xc6.}\xbf\xe9;D\xca\xe7" +
"\xffl\xdf\xbf\\>v\xd7\xa52\x1b\xce\xce\x1dF\xe9" +
"\xf2\\Z\xf6\xd2\\A\xba4\xf7&\x00\xfb\x91S\x9f" +
"]\xff\x83/|p\xa9\x94G\x8e\"\xef\xceM\xa2\x84" +
"\xf3h\xc6\xaf\xe7\x12\xeb\xbe\xbc\xfa?6\x9d\xdf5\xe7" +
"\x97ek\xef\x997\x8e\xd2!g\xe4\xcb\xf3\xb6K\xb1" +
"\x16\xf2\xa6w\x84\x17\x16\xf7nz\xebr\xc8o/\xcd" +
"\x1b x\x9e\x11\xbezf\xf3O?\xfb\xab0<\xef" +
"\xcf\xfb9\xc1\x13m!x\x1ez\xef\xb9\xbb\xbe\xb8\xe6" +
"\xa5\x0fC4X\xd0\xb2\x85\xa6Z\x05McY#\x1f" +
"I\xdf\xea\xfdL\xdf\x92V\xf2Z\xbe\xa3\xbb`\x8d1" +
"\xcdR\xd3\x8a\xc5\x92\xac\xd3\xcc\xeb\x9a\xc9\x06\x11\xe5z" +
">\x02\x10A\x00Q\x19\x07\x90\x1f\xe0Q\xcer(\"" +
"&\x88(\xa2J\xc21\x1ee\x8bC\x91\xe3\x12\x14%" +
"\xc5um\x00r\x96Gy=\x87\xc8'\x90\x07\x10\x0b" +
"O\x03\xc8\xeby\x94\xb7rh\xe7\x99\x91S4\xa6A" +
"\xdc\xea3\x0c\xac\x05\x0ek\x01m\x83Y\xc6\xa42\x9c" +
"\x858\x0b\x89\x85\xf1\x07-\x8c\x01\x871@{L/" +
"\x18\xe6\x90f\xa1\x9aM\xb2\x11\x83\x998\x86U\xc0a" +
"\x15\xe0t\xe6\xa5\x98i\xaa\xba\xb6B\xd1\x94Qf\x00" +
"\x90e\xd5|\x14\xc0\xcf@\xe8\xe5*q\xf1n\xe0\xc4" +
"E\x02\x06\xd9\x02=\xb2\x8a\xbfs\x108\xb1E\xb0\x0d" +
"6\xaa\x9a\x163p(\x93w\xd6\xe6u\xad\x0b\xed\x82" +
"\xe6~@f\xb8\x1f\xe2\xb4k\x17\x0eb\xa0\x1d_\xae" +
"\xdd\x9dY\x95iV\xbc_\x1b\xd1K \x1f\xa8\x04\xf9" +
"@\x11\xf2\xad!\xc8\x1f\xee\x01\x90\x1f\xe2Q~\x94C" +
"\x91/b\xbe\xad\x1d@\xde\xcc\xa3\xfc\x04\x87v\xda\xd9" +
"\xa4?\x03\x00>\x9a#L\xb1\x0a\x063IV\x078" +
"\xc8\xa3\x03z\x1d\xe0\xa6\x09f\x90\xee\xde!\xc4\x15#" +
"=\xe6\x1f\xd44H\xf7\xadWMK\xd5FW;\xf2" +
"\xceA=\xab\xa6'\xc9\xaaZG\xcf\x96\x0e\x00D\xf1" +
"\xc6\xfb\x00\x90\x13\xc5\x1e\x80NuT\xd3\x0dfgT" +
"3\xadk\x1a\x03>mm\x1aV\xb2\x8a\x96f\xfeF" +
"U\xe5\x1b\xb9\x1b\xa4\x981\xc1\x8c[\x94\x10}\xe7\x0f" +
"*\x86\xc2\xe7L\xb9\xd6\xc7\xb1\xef>\x00\xb9\x97Gy" +
"0\x84\xe3\x0a\xc2\xf1\x1e\x1e\xe5{C8\x0e\x11\x8e\x83" +
"<\xcak8\xb4uC\x1dU\xb5;\x19\xf0F\x98\x81" +
"\xa6\xa5)9F\x98\x15\xf1\xd8\xa4\xe7-U\xd7L\xac" +
"\x0fr\x0b \xd6\x87\x90\x12f\xe2\xe4-\x1e\xa5<F" +
"\xe9\xda\xfc$3\x0bB\xd62\xe5\x88oI\xac\x03@" +
"\xae\xe6QNp\xd8i0\xb3\x90\xb5\xb0>(\x09>" +
"\x8e]=\xf8B4LV\xa2\xe1\x12\x009\xc3\xa3\x9c" +
"\xe7\x10\x8b\xe8\xe5zB\xd1\x80G\x97\x85\xebv\x03\xc8" +
"\x16\x8f\xf2f\x0em\xd3\xdd\xa4\x1f0\xe3!\xda\x9a1" +
"\xad\xfe\xbc\xf7\xd7\xa6\x8ci\x0d\xea\x86\x85\x02p(\x00" +
"\xf1V7Y\xf7\x08\xf9T\x7f&\xcb\xeeRy\xcd\xc2" +
"(p\x18\x85i\x9d\xca\xe5G\x9c\x02\x9b\xeb\xed\x9e5" +
"\x0b\x89\x0c\xbf\xc7\xa3\xfc\xfb!k\x16S\x1c\xbb\x8dG" +
"\xf9\x93\x1c\xdaJ:\xad\x174k5\xf0\xcah\x09\xe7" +
"S\x0c\xe2i\x83\x05t\xf0\xb6\xad\xae\xe0\xd6\xba6\xa2" +
"\x8e\x16\x0c\xc5\x0a\x01^\xc8g\x14\x8bM\xf9\xe4\x9cs" +
"\x96\xbf\x8as\xf6\xab\x87k>g/2\x95\x9ct\xdc" +
"Prf\x18\x9bd%l\xe8T?\xc1\xa3|{\xe5" +
"\x03\xdc\x94c\xa6\xa9\x8c\xb2\xb2\xf0\x10\xad\x88\x89\xc6\xd2" +
"du\x92\xb9I\xe6\x16\x83\x99B!k\x91\x16\xb5\xb6" +
"\xed\xaaA\xdc\x9a\xcf\xa3|\x1b\x871\xfc\xd0v\xf5X" +
"\xf4tpF\xad\xcc0t\x03\xeb\x83$\\\x84$]" +
"\xdc\x00u\xad\x97Y\x8a\x9aErK\xbf\xda,\x01n" +
"\xa6\xb8\x12\xc0\xe6\x8a\xe7w\x92w\xe4\xa6\x9c\x14\xd1\xbb" +
"\x9eGy\x1e\x87\xf6\xa8\xa1\xa4\xd9 3P\xd53+" +
"\x15MO\xf1,]F\xd6\xbak\xdd\xd4\xe1\x87e\x82" +
"?k\xfa\xf9\x06+\x82P\x9c>\xd8\xea\xea\x9c\xf0u" +
"\xde\xd8\x16$c\xff\x98\x1f\x1e\x0e\xb2\x85\x1f\x0f\x1f#" +
"gy\x94Gyg(\xaf\xec\xa0\xc8\xf9\x14\x8f\xf2W" +
"9\x14#\x91\x04F\x00\xc4\xe7\x88%;y\x94\xf7r" +
"SS6\x9b`\x9a\xd5\xab\x8e\x82\xc0\xcc@J*\xf6" +
"\xaa\xa3\x0cx\xf3zck\xf5\x0cx\xe8\xc3\xa6\x9ee" +
"\x16\xebe\xe9\xacB.7\xc1\xdc\xefE2z\x87:" +
"\x1do\x93e\xdeC\xfc\x8d{UR\x88\x0em\x81\xe3" +
"\x0a,T\xdcL\xa3\xad\xbb\xb8\x1b\x0c\xca8\x10xL" +
"\x91\x07h~,A\xc7\xb1\x19\xa78\x7fO\xe0u\x1e" +
")\x16u\x04\x01\xc1\xaf\x09\"\xc0a\x04\xb03\xed," +
"X\x16\x0a#3i\xd5\xe9\xaa\xe5\x02GE\x98w\x17" +
"E\xef\x02/\x8a\xfb\x80\x13c\x82\xedi\x8e\xde|\xa1" +
"\xac\xa0\x8aL\x17eV\xe5-U\xd05\x93\xf6\x0a\xf1" +
"\xbf\xa3\x12\xff\x8d\x80\xff^B{lK\x98\xfe\xc5\x84" +
"\xb6cw\xc0t1\xc2\xb9\xf4\xdf\xb3\x0f@\xde\xcb\xa3" +
"\xfc\x12\x87\x9dn\xad\x85\xf5A\xe3\xa5HY\xb7\xa2\xb8" +
"G\x87\xd6\xb4\x92\x0d\x92\x9em\xb0|VI\xb3>," +
"VO\x80\x08\x1c\xa2\xe3'\xb9\xbc\xc1L\x13U]\x93" +
"\x0bJV\xe5\xadI\xbf\xe2\xd5\x0a\xb9A\x83M\xa8\xa8" +
"\x17\xccn\xcbb9!o\x99WS\x0f\x07\x00Q\x90" +
"\x14\xd4\xacY\x92#\xdb\x03*\xf8\x00-\x1a\x0f\xf2@" +
"\xbcPP\xfd\x04`g\xf5\xb4s\xb2\x10_\xa9\xe4\xca" +
"\xf3@\xd5\x8c\x01kJ\xb8\xf3\xd2\xd2oR\xfd6\xfd" +
"\x95\x89Lw\xee\x14!\x95)\x0et\xf1(\xdf\x13R" +
"\xb9\x7fI\xc8\x0eO\xe5\x15\xc3\x81\x1d\xc2\x1f\xb3IO" +
"\xabV\x96\xa3\xf4\xe5\x81Y4\xa6\x1b\x84\xbb\x831\xd3" +
"\xe9\x17\x8e*\xab\xf2\xad\x8e\x85\xa4\xe3\xed\x9e\x8e\xd2$" +
"\x0e\x00\xa4\xd6#\x8f\xa9\xad\x18\xa8)=\x8c=\x00\xa9" +
"\x87H\xfe(\x06\x9aJ\xdb\xb0\x19 \xb5\x99\xe4O\xa0" +
"\x7f\xb5\x93\x1e\xc3\x83\x00\xa9'H\xfc,\x0d\x8f\xf0\x8e" +
"KH\xbb\x9c\xe5w\x92|/\xc9\xa3\x91\x04F\x01\xa4" +
"=\xd8\x0e\x90z\x96\xe4\xaf\x91\xbc\x8aK`\x15\x80t" +
"\x08\xc7\x01R\xaf\x90\xfc\x0d\x92\x0b\xd1\x04\xddn\xa5\xd7" +
"\xd1\x00H\xfd-\xc9\xbfI\xf2\xea\xc6\x04V\x03HG" +
"\x1c\xf9\x9b$\xff>\xc9k\x9a\x12X\x03 \xfd\x13n" +
"\x01H}\x97\xe4'H>\x0b\x138\x0b@:\x8e\xbb" +
"\x01R'H\xfe\xaf$\x9f]\x95\xc0\xd9\x00\xd2O\x1c" +
"}N\x92\xfc\x17$\xaf\x8d$\xb0\x16@\xfa\x19\xee\x03" +
"H\xfd\x82\xe4\xffM\xf2\x98\x90\xc0\x18\x80\xf4\xaec\xd7" +
"y\x92Ws%7+\x8fQ%\xd7'^7\xfd#" +
"cE\x1fG\x97\xee\x83z\x9c\xaeH\x18\x0f\x1a\xaf\x80" +
"\x18\x07\xb4\xf3\xba\x9e]9\x95\xa9qK\x195\xbd\xab" +
"Z}\xd0\x9a\x02$\xa1_\xfc@\\\xd7\xfa3~ " +
"(\x8d:\x9e&\xaa\xd9]\xb0\xf4B\x1eZ)\xc8f" +
"\xfc\x98c\x14\xb4\xe5\x86\x9e[\x8d\xcc\xc8\xa9\x9a\x92\x9d" +
"!\x1a\xd5\x00\x875P\x0c\x09\xde\xda\xd3\x87\xa6+_" +
"<}Fs\xa5\x8cn\xcdw\xacVF\xaf&N-" +
"\x09rV\\\x0b\x05\xa4\xd6\x09%[\xf8(\xe1ij" +
"=\x95\xect\xeb\xb1\x99\xcau\xaf\xf7T\x12J*T" +
"\x17C\xe5\xf99\xc9\xccV\xbf\x09\x132\xf8`\x10\x83" +
"={\x97\xb5\x85\xee.Y\xc5b\xa6\xd5\x9d\xc7|V" +
"e\x99\xcf0#\x1eN\xd9\x15+\x92\xc8Le\xfa\xd4" +
"2\x07C]r2\x9c+\x1a|\xd5x\x8e2\xcb\xfd" +
"\xd5\xaf\x8d\xe8T\x87\x08\xe1\xe2\xeb\xdaf'\x99\x19\xbf" +
"\x9a\xb3\x08\x9a\x863_\x9d*\x94c\x15\x8a1\xef&" +
"\x10\xba&\x13\x19\xd7\xf0(\x8f\x85\xc8\xc8\x06*\\\x93" +
"\x93A\x7fL\xe4\xb9b\x83\x8c2W\x9eG\xf9!\x0e" +
"\xe3J\xc1\x1a\xc3\xfa\xe0\xf1c\x8a\xd2S{8\xc4\xcd" +
"~-\xc3\x00\xd7{\xee\x15\xcag~W~\xe6\x9a\xf9" +
"\xea\xcc\xf6\xee\"3\x02\xee\xf7\xaeKv\xbe\xe2U\xbd" +
"\xd3\xdd\x94x\xd6\xe8T\x85^\xd7\x1f\xbd\x8e\xb0xh" +
"\x03p\xe2\x01\x01\x83^7z\xadmq\x8f\x01\x9c\xb8" +
"K@\xce\x7f\x97A\xef\xfdE|\xecq\xe0\xc4m\x02" +
"\xf2\xfe\xb3\x0az]\xd2\xc5\x93\xb3\x108q\xa3\x80\x11" +
"\xff=\x0b\xbd\x1e\xab\xb8n\x1c8Q\x150\xea\xbf\xd8" +
"\xa0\xd7\xe2\x17\xd7n\x01N\x1c\x0az\x81\xd0\xe9\xda\xd1" +
"\x85\xb6\xc7QhuX:\xb53\xe8\x8e\x02\xe8B\xdb" +
"\xbb\x99\xf0W\xba\x9a8\xa3\xbc\xe6\x16\xc4\xd3\x8a\xc5\xba" +
"\xa8Zt\x03\x12\x16#\x12t\xa1\x1c\xc1P\x8b\x19\xe0" +
"z[\x03I\xd6\xea\x9c\xf3G\xad\xe1\xbc\xf9\x1f1F" +
"\xf2\x95\xb4\xa6}\xfc&ih]*Kky\x94\x1b" +
"\xb9\x19+\xd1\xc8\x95\xac\xf0\xc8\x1f\xa7\xc9\xb4\xfeo\xfb" +
"\xeb\x1f\xa7\xf0\xfa}\x1e\xe5\x93!\xb7\xfe!\x09\xdf\xe1" +
"Q>\x1d\xaa\xe4~D\xbe~\x92G\xf9b\xd0\xf7~" +
"\xffq\x00\xf9\"\x8f\xc9Pe$\xfe\x9a\x06\xfe\x8a\xea" +
"\x07\xa7.B\xb7.\x8a\xe2\xd3\x00\xa9j\xaa+\x12N" +
"]\x14q\xeb\"\x11\x87\x01R\xf5$\x9f\x17\xae\x8b\x9a" +
"\xf0>\x80T#\xc9\xe7\xe3\xd4\xdb\xa6P0\x82\xca1" +
"\xab\x8f\xde\xa3j\x15\x93\xad\xd7\x88Gk\xb9\xa2f\x0b" +
"\x06\x83 \xd7\x17\x83Mo\xa8\xfcp;\xf4n3." +
"E$\xcc\xa0\xe97\xea\xae\xe1\x9e?]\xe6\xc9\xea\x85" +
"\xccHV1X&\xc5\x0c\xc1\x0d\x08\x83|T\xae\xc6" +
"\xd0\xab7@\xf0:\x19\"\xfb\xb4\x99\xac\xcf0t4" +
"J\xaa\xf4%A\x95\xee\x17\xe9t\xd9\xb8\x8bGy5" +
"\x1dm\x97{\xb4\xf2pp\xafhM+\x05\x93\x95a" +
"\x02<3\xfc^\x8f9\xa6\x17\xb2\x99$\x03\xc12&" +
"K \x9d\xb1ZO\xb1\xb8\x17\x09\xddG\x0a\xefy\x0e" +
"\xbdW\xb8\xd0#\x85\xf7R\x84\xdeSo\xf9#\x85\x87" +
"A\xd9#\x85\xfb\xc1\xe1\xfc\xd4;\xf5u4)\xdc\xb4" +
"\x18:\x94k\xea\xdd_u\xcb\xdb\xffw\x89\x92\xc8Q" +
"s\xbd\xcd /\xc1\xfd\x7f\x00\x00\x00\xff\xff\xf1\xc3d" +
"\xc6"
func init() {
schemas.Register(schema_db8274f9144abc7e,
@@ -4089,6 +4546,7 @@ func init() {
0x8635c6b4f45bf5cd,
0x904e297b87fbecea,
0x9496331ab9cd463f,
0x958096448eb3373e,
0x96b74375ce9b0ef6,
0x97b3c5c260257622,
0x9b87b390babc2ccf,
@@ -4097,6 +4555,8 @@ func init() {
0xa766b24d4fe5da35,
0xab6d5210c1f26687,
0xb046e578094b1ead,
0xb177ca2526a3ca76,
0xb48edfbdaa25db04,
0xb4bf9861fe035d04,
0xb5f39f082b9ac18a,
0xb70431c0dc014915,
@@ -4104,6 +4564,7 @@ func init() {
0xc793e50592935b4a,
0xcbd96442ae3bb01a,
0xd4d18de97bb12de3,
0xdb58ff694ba05cf9,
0xdbaa9d03d52b62dc,
0xdc3ed6801961e502,
0xe3e37d096a5b564e,
@@ -4114,6 +4575,7 @@ func init() {
0xf2c122394f447e8e,
0xf2c68e2547ec3866,
0xf41a0f001ad49e46,
0xf548cef9dea2a4a1,
0xf5f383d2785edb86,
0xf71695ec7fe85497,
0xf9cb7f4431a307d0,
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2015 Martin Atkins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+236
View File
@@ -0,0 +1,236 @@
// Package cidr is a collection of assorted utilities for computing
// network and host addresses within network ranges.
//
// It expects a CIDR-type address structure where addresses are divided into
// some number of prefix bits representing the network and then the remaining
// suffix bits represent the host.
//
// For example, it can help to calculate addresses for sub-networks of a
// parent network, or to calculate host addresses within a particular prefix.
//
// At present this package is prioritizing simplicity of implementation and
// de-prioritizing speed and memory usage. Thus caution is advised before
// using this package in performance-critical applications or hot codepaths.
// Patches to improve the speed and memory usage may be accepted as long as
// they do not result in a significant increase in code complexity.
package cidr
import (
"fmt"
"math/big"
"net"
)
// Subnet takes a parent CIDR range and creates a subnet within it
// with the given number of additional prefix bits and the given
// network number.
//
// For example, 10.3.0.0/16, extended by 8 bits, with a network number
// of 5, becomes 10.3.5.0/24 .
func Subnet(base *net.IPNet, newBits int, num int) (*net.IPNet, error) {
return SubnetBig(base, newBits, big.NewInt(int64(num)))
}
// SubnetBig takes a parent CIDR range and creates a subnet within it with the
// given number of additional prefix bits and the given network number. It
// differs from Subnet in that it takes a *big.Int for the num, instead of an int.
//
// For example, 10.3.0.0/16, extended by 8 bits, with a network number of 5,
// becomes 10.3.5.0/24 .
func SubnetBig(base *net.IPNet, newBits int, num *big.Int) (*net.IPNet, error) {
ip := base.IP
mask := base.Mask
parentLen, addrLen := mask.Size()
newPrefixLen := parentLen + newBits
if newPrefixLen > addrLen {
return nil, fmt.Errorf("insufficient address space to extend prefix of %d by %d", parentLen, newBits)
}
maxNetNum := uint64(1<<uint64(newBits)) - 1
if num.Uint64() > maxNetNum {
return nil, fmt.Errorf("prefix extension of %d does not accommodate a subnet numbered %d", newBits, num)
}
return &net.IPNet{
IP: insertNumIntoIP(ip, num, newPrefixLen),
Mask: net.CIDRMask(newPrefixLen, addrLen),
}, nil
}
// Host takes a parent CIDR range and turns it into a host IP address with the
// given host number.
//
// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2.
func Host(base *net.IPNet, num int) (net.IP, error) {
return HostBig(base, big.NewInt(int64(num)))
}
// HostBig takes a parent CIDR range and turns it into a host IP address with
// the given host number. It differs from Host in that it takes a *big.Int for
// the num, instead of an int.
//
// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2.
func HostBig(base *net.IPNet, num *big.Int) (net.IP, error) {
ip := base.IP
mask := base.Mask
parentLen, addrLen := mask.Size()
hostLen := addrLen - parentLen
maxHostNum := big.NewInt(int64(1))
maxHostNum.Lsh(maxHostNum, uint(hostLen))
maxHostNum.Sub(maxHostNum, big.NewInt(1))
numUint64 := big.NewInt(int64(num.Uint64()))
if num.Cmp(big.NewInt(0)) == -1 {
numUint64.Neg(num)
numUint64.Sub(numUint64, big.NewInt(int64(1)))
num.Sub(maxHostNum, numUint64)
}
if numUint64.Cmp(maxHostNum) == 1 {
return nil, fmt.Errorf("prefix of %d does not accommodate a host numbered %d", parentLen, num)
}
var bitlength int
if ip.To4() != nil {
bitlength = 32
} else {
bitlength = 128
}
return insertNumIntoIP(ip, num, bitlength), nil
}
// AddressRange returns the first and last addresses in the given CIDR range.
func AddressRange(network *net.IPNet) (net.IP, net.IP) {
// the first IP is easy
firstIP := network.IP
// the last IP is the network address OR NOT the mask address
prefixLen, bits := network.Mask.Size()
if prefixLen == bits {
// Easy!
// But make sure that our two slices are distinct, since they
// would be in all other cases.
lastIP := make([]byte, len(firstIP))
copy(lastIP, firstIP)
return firstIP, lastIP
}
firstIPInt, bits := ipToInt(firstIP)
hostLen := uint(bits) - uint(prefixLen)
lastIPInt := big.NewInt(1)
lastIPInt.Lsh(lastIPInt, hostLen)
lastIPInt.Sub(lastIPInt, big.NewInt(1))
lastIPInt.Or(lastIPInt, firstIPInt)
return firstIP, intToIP(lastIPInt, bits)
}
// AddressCount returns the number of distinct host addresses within the given
// CIDR range.
//
// Since the result is a uint64, this function returns meaningful information
// only for IPv4 ranges and IPv6 ranges with a prefix size of at least 65.
func AddressCount(network *net.IPNet) uint64 {
prefixLen, bits := network.Mask.Size()
return 1 << (uint64(bits) - uint64(prefixLen))
}
//VerifyNoOverlap takes a list subnets and supernet (CIDRBlock) and verifies
//none of the subnets overlap and all subnets are in the supernet
//it returns an error if any of those conditions are not satisfied
func VerifyNoOverlap(subnets []*net.IPNet, CIDRBlock *net.IPNet) error {
firstLastIP := make([][]net.IP, len(subnets))
for i, s := range subnets {
first, last := AddressRange(s)
firstLastIP[i] = []net.IP{first, last}
}
for i, s := range subnets {
if !CIDRBlock.Contains(firstLastIP[i][0]) || !CIDRBlock.Contains(firstLastIP[i][1]) {
return fmt.Errorf("%s does not fully contain %s", CIDRBlock.String(), s.String())
}
for j := 0; j < len(subnets); j++ {
if i == j {
continue
}
first := firstLastIP[j][0]
last := firstLastIP[j][1]
if s.Contains(first) || s.Contains(last) {
return fmt.Errorf("%s overlaps with %s", subnets[j].String(), s.String())
}
}
}
return nil
}
// PreviousSubnet returns the subnet of the desired mask in the IP space
// just lower than the start of IPNet provided. If the IP space rolls over
// then the second return value is true
func PreviousSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) {
startIP := checkIPv4(network.IP)
previousIP := make(net.IP, len(startIP))
copy(previousIP, startIP)
cMask := net.CIDRMask(prefixLen, 8*len(previousIP))
previousIP = Dec(previousIP)
previous := &net.IPNet{IP: previousIP.Mask(cMask), Mask: cMask}
if startIP.Equal(net.IPv4zero) || startIP.Equal(net.IPv6zero) {
return previous, true
}
return previous, false
}
// NextSubnet returns the next available subnet of the desired mask size
// starting for the maximum IP of the offset subnet
// If the IP exceeds the maxium IP then the second return value is true
func NextSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) {
_, currentLast := AddressRange(network)
mask := net.CIDRMask(prefixLen, 8*len(currentLast))
currentSubnet := &net.IPNet{IP: currentLast.Mask(mask), Mask: mask}
_, last := AddressRange(currentSubnet)
last = Inc(last)
next := &net.IPNet{IP: last.Mask(mask), Mask: mask}
if last.Equal(net.IPv4zero) || last.Equal(net.IPv6zero) {
return next, true
}
return next, false
}
//Inc increases the IP by one this returns a new []byte for the IP
func Inc(IP net.IP) net.IP {
IP = checkIPv4(IP)
incIP := make([]byte, len(IP))
copy(incIP, IP)
for j := len(incIP) - 1; j >= 0; j-- {
incIP[j]++
if incIP[j] > 0 {
break
}
}
return incIP
}
//Dec decreases the IP by one this returns a new []byte for the IP
func Dec(IP net.IP) net.IP {
IP = checkIPv4(IP)
decIP := make([]byte, len(IP))
copy(decIP, IP)
decIP = checkIPv4(decIP)
for j := len(decIP) - 1; j >= 0; j-- {
decIP[j]--
if decIP[j] < 255 {
break
}
}
return decIP
}
func checkIPv4(ip net.IP) net.IP {
// Go for some reason allocs IPv6len for IPv4 so we have to correct it
if v4 := ip.To4(); v4 != nil {
return v4
}
return ip
}
+37
View File
@@ -0,0 +1,37 @@
package cidr
import (
"fmt"
"math/big"
"net"
)
func ipToInt(ip net.IP) (*big.Int, int) {
val := &big.Int{}
val.SetBytes([]byte(ip))
if len(ip) == net.IPv4len {
return val, 32
} else if len(ip) == net.IPv6len {
return val, 128
} else {
panic(fmt.Errorf("Unsupported address length %d", len(ip)))
}
}
func intToIP(ipInt *big.Int, bits int) net.IP {
ipBytes := ipInt.Bytes()
ret := make([]byte, bits/8)
// Pack our IP bytes into the end of the return array,
// since big.Int.Bytes() removes front zero padding.
for i := 1; i <= len(ipBytes); i++ {
ret[len(ret)-i] = ipBytes[len(ipBytes)-i]
}
return net.IP(ret)
}
func insertNumIntoIP(ip net.IP, bigNum *big.Int, prefixLen int) net.IP {
ipInt, totalBits := ipToInt(ip)
bigNum.Lsh(bigNum, uint(totalBits-prefixLen))
ipInt.Or(ipInt, bigNum)
return intToIP(ipInt, totalBits)
}
-88
View File
@@ -1,88 +0,0 @@
# Mutilated beyond recognition from the example at:
# https://docs.microsoft.com/azure/devops/pipelines/languages/go
trigger:
- master
strategy:
matrix:
linux:
imageName: ubuntu-16.04
gorootDir: /usr/local
mac:
imageName: macos-10.13
gorootDir: /usr/local
windows:
imageName: windows-2019
gorootDir: C:\
pool:
vmImage: $(imageName)
variables:
GOROOT: $(gorootDir)/go
GOPATH: $(system.defaultWorkingDirectory)/gopath
GOBIN: $(GOPATH)/bin
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)'
# TODO: modules should be the default in Go 1.13, so this won't be needed
GO111MODULE: on
steps:
- bash: |
latestGo=$(curl "https://golang.org/VERSION?m=text")
echo "##vso[task.setvariable variable=LATEST_GO]$latestGo"
echo "Latest Go version: $latestGo"
displayName: "Get latest Go version"
- bash: |
sudo rm -f $(which go)
echo '##vso[task.prependpath]$(GOBIN)'
echo '##vso[task.prependpath]$(GOROOT)/bin'
mkdir -p '$(modulePath)'
shopt -s extglob
shopt -s dotglob
mv !(gopath) '$(modulePath)'
displayName: Remove old Go, set GOBIN/GOROOT, and move project into GOPATH
# Install Go (this varies by platform)
- bash: |
wget "https://dl.google.com/go/$(LATEST_GO).linux-amd64.tar.gz"
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).linux-amd64.tar.gz"
condition: eq( variables['Agent.OS'], 'Linux' )
displayName: Install Go on Linux
- bash: |
wget "https://dl.google.com/go/$(LATEST_GO).darwin-amd64.tar.gz"
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).darwin-amd64.tar.gz"
condition: eq( variables['Agent.OS'], 'Darwin' )
displayName: Install Go on macOS
- powershell: |
Write-Host "Downloading Go... (please be patient, I am very slow)"
(New-Object System.Net.WebClient).DownloadFile("https://dl.google.com/go/$(LATEST_GO).windows-amd64.zip", "$(LATEST_GO).windows-amd64.zip")
Write-Host "Extracting Go... (I'm slow too)"
Expand-Archive "$(LATEST_GO).windows-amd64.zip" -DestinationPath "$(gorootDir)"
condition: eq( variables['Agent.OS'], 'Windows_NT' )
displayName: Install Go on Windows
# TODO: When this issue is fixed, replace with installer script:
# https://github.com/golangci/golangci-lint/issues/472
- script: go get -v github.com/golangci/golangci-lint/cmd/golangci-lint
displayName: Install golangci-lint
- bash: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
displayName: Print Go version and environment
- script: |
go get -v -t -d ./...
golangci-lint run -E gofmt -E goimports -E misspell
go test -race ./...
workingDirectory: '$(modulePath)'
displayName: Run tests
-310
View File
@@ -1,310 +0,0 @@
// Copyright 2015 Light Code Labs, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package telemetry
import (
"fmt"
"hash/fnv"
"log"
"strings"
"github.com/google/uuid"
)
// Init initializes this package so that it may
// be used. Do not call this function more than
// once. Init panics if it is called more than
// once or if the UUID value is empty. Once this
// function is called, the rest of the package
// may safely be used. If this function is not
// called, the collector functions may still be
// invoked, but they will be no-ops.
//
// Any metrics keys that are passed in the second
// argument will be permanently disabled for the
// lifetime of the process.
func Init(instanceID uuid.UUID, disabledMetricsKeys []string) {
if enabled {
panic("already initialized")
}
if str := instanceID.String(); str == "" ||
str == "00000000-0000-0000-0000-000000000000" {
panic("empty UUID")
}
instanceUUID = instanceID
disabledMetricsMu.Lock()
for _, key := range disabledMetricsKeys {
disabledMetrics[strings.TrimSpace(key)] = false
}
disabledMetricsMu.Unlock()
enabled = true
}
// StartEmitting sends the current payload and begins the
// transmission cycle for updates. This is the first
// update sent, and future ones will be sent until
// StopEmitting is called.
//
// This function is non-blocking (it spawns a new goroutine).
//
// This function panics if it was called more than once.
// It is a no-op if this package was not initialized.
func StartEmitting() {
if !enabled {
return
}
updateTimerMu.Lock()
if updateTimer != nil {
updateTimerMu.Unlock()
panic("updates already started")
}
updateTimerMu.Unlock()
updateMu.Lock()
if updating {
updateMu.Unlock()
panic("update already in progress")
}
updateMu.Unlock()
go logEmit(false)
}
// StopEmitting sends the current payload and terminates
// the update cycle. No more updates will be sent.
//
// It is a no-op if the package was never initialized
// or if emitting was never started.
//
// NOTE: This function is blocking. Run in a goroutine if
// you want to guarantee no blocking at critical times
// like exiting the program.
func StopEmitting() {
if !enabled {
return
}
updateTimerMu.Lock()
if updateTimer == nil {
updateTimerMu.Unlock()
return
}
updateTimerMu.Unlock()
logEmit(true) // likely too early; may take minutes to return
}
// Reset empties the current payload buffer.
func Reset() {
resetBuffer()
}
// Set puts a value in the buffer to be included
// in the next emission. It overwrites any
// previous value.
//
// This function is safe for multiple goroutines,
// and it is recommended to call this using the
// go keyword after the call to SendHello so it
// doesn't block crucial code.
func Set(key string, val interface{}) {
if !enabled || isDisabled(key) {
return
}
bufferMu.Lock()
if _, ok := buffer[key]; !ok {
if bufferItemCount >= maxBufferItems {
bufferMu.Unlock()
return
}
bufferItemCount++
}
buffer[key] = val
bufferMu.Unlock()
}
// SetNested puts a value in the buffer to be included
// in the next emission, nested under the top-level key
// as subkey. It overwrites any previous value.
//
// This function is safe for multiple goroutines,
// and it is recommended to call this using the
// go keyword after the call to SendHello so it
// doesn't block crucial code.
func SetNested(key, subkey string, val interface{}) {
if !enabled || isDisabled(key) {
return
}
bufferMu.Lock()
if topLevel, ok1 := buffer[key]; ok1 {
topLevelMap, ok2 := topLevel.(map[string]interface{})
if !ok2 {
bufferMu.Unlock()
log.Printf("[PANIC] Telemetry: key %s is already used for non-nested-map value", key)
return
}
if _, ok3 := topLevelMap[subkey]; !ok3 {
// don't exceed max buffer size
if bufferItemCount >= maxBufferItems {
bufferMu.Unlock()
return
}
bufferItemCount++
}
topLevelMap[subkey] = val
} else {
// don't exceed max buffer size
if bufferItemCount >= maxBufferItems {
bufferMu.Unlock()
return
}
bufferItemCount++
buffer[key] = map[string]interface{}{subkey: val}
}
bufferMu.Unlock()
}
// Append appends value to a list named key.
// If key is new, a new list will be created.
// If key maps to a type that is not a list,
// a panic is logged, and this is a no-op.
func Append(key string, value interface{}) {
if !enabled || isDisabled(key) {
return
}
bufferMu.Lock()
if bufferItemCount >= maxBufferItems {
bufferMu.Unlock()
return
}
// TODO: Test this...
bufVal, inBuffer := buffer[key]
sliceVal, sliceOk := bufVal.([]interface{})
if inBuffer && !sliceOk {
bufferMu.Unlock()
log.Printf("[PANIC] Telemetry: key %s already used for non-slice value", key)
return
}
if sliceVal == nil {
buffer[key] = []interface{}{value}
} else if sliceOk {
buffer[key] = append(sliceVal, value)
}
bufferItemCount++
bufferMu.Unlock()
}
// AppendUnique adds value to a set named key.
// Set items are unordered. Values in the set
// are unique, but how many times they are
// appended is counted. The value must be
// hashable.
//
// If key is new, a new set will be created for
// values with that key. If key maps to a type
// that is not a counting set, a panic is logged,
// and this is a no-op.
func AppendUnique(key string, value interface{}) {
if !enabled || isDisabled(key) {
return
}
bufferMu.Lock()
bufVal, inBuffer := buffer[key]
setVal, setOk := bufVal.(countingSet)
if inBuffer && !setOk {
bufferMu.Unlock()
log.Printf("[PANIC] Telemetry: key %s already used for non-counting-set value", key)
return
}
if setVal == nil {
// ensure the buffer is not too full, then add new unique value
if bufferItemCount >= maxBufferItems {
bufferMu.Unlock()
return
}
buffer[key] = countingSet{value: 1}
bufferItemCount++
} else if setOk {
// unique value already exists, so just increment counter
setVal[value]++
}
bufferMu.Unlock()
}
// Add adds amount to a value named key.
// If it does not exist, it is created with
// a value of 1. If key maps to a type that
// is not an integer, a panic is logged,
// and this is a no-op.
func Add(key string, amount int) {
atomicAdd(key, amount)
}
// Increment is a shortcut for Add(key, 1)
func Increment(key string) {
atomicAdd(key, 1)
}
// atomicAdd adds amount (negative to subtract)
// to key.
func atomicAdd(key string, amount int) {
if !enabled || isDisabled(key) {
return
}
bufferMu.Lock()
bufVal, inBuffer := buffer[key]
intVal, intOk := bufVal.(int)
if inBuffer && !intOk {
bufferMu.Unlock()
log.Printf("[PANIC] Telemetry: key %s already used for non-integer value", key)
return
}
if !inBuffer {
if bufferItemCount >= maxBufferItems {
bufferMu.Unlock()
return
}
bufferItemCount++
}
buffer[key] = intVal + amount
bufferMu.Unlock()
}
// FastHash hashes input using a 32-bit hashing algorithm
// that is fast, and returns the hash as a hex-encoded string.
// Do not use this for cryptographic purposes.
func FastHash(input []byte) string {
h := fnv.New32a()
if _, err := h.Write(input); err != nil {
log.Println("[ERROR] failed to write bytes: ", err)
}
return fmt.Sprintf("%x", h.Sum32())
}
// isDisabled returns whether key is
// a disabled metric key. ALL collection
// functions should call this and not
// save the value if this returns true.
func isDisabled(key string) bool {
// for keys that are augmented with data, such as
// "tls_client_hello_ua:<hash>", just
// check the prefix "tls_client_hello_ua"
checkKey := key
if idx := strings.Index(key, ":"); idx > -1 {
checkKey = key[:idx]
}
disabledMetricsMu.RLock()
_, ok := disabledMetrics[checkKey]
disabledMetricsMu.RUnlock()
return ok
}
-428
View File
@@ -1,428 +0,0 @@
// Copyright 2015 Light Code Labs, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package telemetry implements the client for server-side telemetry
// of the network. Functions in this package are synchronous and blocking
// unless otherwise specified. For convenience, most functions here do
// not return errors, but errors are logged to the standard logger.
//
// To use this package, first call Init(). You can then call any of the
// collection/aggregation functions. Call StartEmitting() when you are
// ready to begin sending telemetry updates.
//
// When collecting metrics (functions like Set, AppendUnique, or Increment),
// it may be desirable and even recommended to invoke them in a new
// goroutine in case there is lock contention; they are thread-safe (unless
// noted), and you may not want them to block the main thread of execution.
// However, sometimes blocking may be necessary too; for example, adding
// startup metrics to the buffer before the call to StartEmitting().
//
// This package is designed to be as fast and space-efficient as reasonably
// possible, so that it does not disrupt the flow of execution.
package telemetry
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/google/uuid"
)
// logEmit calls emit and then logs the error, if any.
// See docs for emit.
func logEmit(final bool) {
err := emit(final)
if err != nil {
log.Printf("[ERROR] Sending telemetry: %v", err)
}
}
// emit sends an update to the telemetry server.
// Set final to true if this is the last call to emit.
// If final is true, no future updates will be scheduled.
// Otherwise, the next update will be scheduled.
func emit(final bool) error {
if !enabled {
return fmt.Errorf("telemetry not enabled")
}
// some metrics are updated/set at time of emission
setEmitTimeMetrics()
// ensure only one update happens at a time;
// skip update if previous one still in progress
updateMu.Lock()
if updating {
updateMu.Unlock()
log.Println("[NOTICE] Skipping this telemetry update because previous one is still working")
return nil
}
updating = true
updateMu.Unlock()
defer func() {
updateMu.Lock()
updating = false
updateMu.Unlock()
}()
// terminate any pending update if this is the last one
if final {
stopUpdateTimer()
}
payloadBytes, err := makePayloadAndResetBuffer()
if err != nil {
return err
}
// this will hold the server's reply
var reply Response
// transmit the payload - use a loop to retry in case of failure
for i := 0; i < 4; i++ {
if i > 0 && err != nil {
// don't hammer the server; first failure might have been
// a fluke, but back off more after that
log.Printf("[WARNING] Sending telemetry (attempt %d): %v - backing off and retrying", i, err)
time.Sleep(time.Duration((i+1)*(i+1)*(i+1)) * time.Second)
}
// send it
var resp *http.Response
resp, err = httpClient.Post(endpoint+instanceUUID.String(), "application/json", bytes.NewReader(payloadBytes))
if err != nil {
continue
}
// check for any special-case response codes
if resp.StatusCode == http.StatusGone {
// the endpoint has been deprecated and is no longer servicing clients
err = fmt.Errorf("telemetry server replied with HTTP %d; upgrade required", resp.StatusCode)
if clen := resp.Header.Get("Content-Length"); clen != "0" && clen != "" {
bodyBytes, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
log.Printf("[ERROR] Reading response body from server: %v", readErr)
}
err = fmt.Errorf("%v - %s", err, bodyBytes)
}
resp.Body.Close()
reply.Stop = true
break
}
if resp.StatusCode == http.StatusUnavailableForLegalReasons {
// the endpoint is unavailable, at least to this client, for legal reasons (!)
err = fmt.Errorf("telemetry server replied with HTTP %d %s: please consult the project website and developers for guidance", resp.StatusCode, resp.Status)
if clen := resp.Header.Get("Content-Length"); clen != "0" && clen != "" {
bodyBytes, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
log.Printf("[ERROR] Reading response body from server: %v", readErr)
}
err = fmt.Errorf("%v - %s", err, bodyBytes)
}
resp.Body.Close()
reply.Stop = true
break
}
// okay, ensure we can interpret the response
if ct := resp.Header.Get("Content-Type"); (resp.StatusCode < 300 || resp.StatusCode >= 400) &&
!strings.Contains(ct, "json") {
err = fmt.Errorf("telemetry server replied with unknown content-type: '%s' and HTTP %s", ct, resp.Status)
resp.Body.Close()
continue
}
// read the response body
err = json.NewDecoder(resp.Body).Decode(&reply)
resp.Body.Close() // close response body as soon as we're done with it
if err != nil {
continue
}
// update the list of enabled/disabled keys, if any
for _, key := range reply.EnableKeys {
disabledMetricsMu.Lock()
// only re-enable this metric if it is temporarily disabled
if temp, ok := disabledMetrics[key]; ok && temp {
delete(disabledMetrics, key)
}
disabledMetricsMu.Unlock()
}
for _, key := range reply.DisableKeys {
disabledMetricsMu.Lock()
disabledMetrics[key] = true // all remotely-disabled keys are "temporarily" disabled
disabledMetricsMu.Unlock()
}
// make sure we didn't send the update too soon; if so,
// just wait and try again -- this is a special case of
// error that we handle differently, as you can see
if resp.StatusCode == http.StatusTooManyRequests {
if reply.NextUpdate <= 0 {
raStr := resp.Header.Get("Retry-After")
if ra, err := strconv.Atoi(raStr); err == nil {
reply.NextUpdate = time.Duration(ra) * time.Second
}
}
if !final {
log.Printf("[NOTICE] Sending telemetry: we were too early; waiting %s before trying again", reply.NextUpdate)
time.Sleep(reply.NextUpdate)
continue
}
} else if resp.StatusCode >= 400 {
err = fmt.Errorf("telemetry server returned status code %d", resp.StatusCode)
continue
}
break
}
if err == nil && !final {
// (remember, if there was an error, we return it
// below, so it WILL get logged if it's supposed to)
log.Println("[INFO] Sending telemetry: success")
}
// even if there was an error after all retries, we should
// schedule the next update using our default update
// interval because the server might be healthy later
// ensure we won't slam the telemetry server; add a little variance
if reply.NextUpdate < 1*time.Second {
reply.NextUpdate = defaultUpdateInterval + time.Duration(rand.Int63n(int64(1*time.Minute)))
}
// schedule the next update (if this wasn't the last one and
// if the remote server didn't tell us to stop sending)
if !final && !reply.Stop {
updateTimerMu.Lock()
updateTimer = time.AfterFunc(reply.NextUpdate, func() {
logEmit(false)
})
updateTimerMu.Unlock()
}
return err
}
func stopUpdateTimer() {
updateTimerMu.Lock()
updateTimer.Stop()
updateTimer = nil
updateTimerMu.Unlock()
}
// setEmitTimeMetrics sets some metrics that should
// be recorded just before emitting.
func setEmitTimeMetrics() {
Set("goroutines", runtime.NumGoroutine())
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
SetNested("memory", "heap_alloc", mem.HeapAlloc)
SetNested("memory", "sys", mem.Sys)
}
// makePayloadAndResetBuffer prepares a payload
// by emptying the collection buffer. It returns
// the bytes of the payload to send to the server.
// Since the buffer is reset by this, if the
// resulting byte slice is lost, the payload is
// gone with it.
func makePayloadAndResetBuffer() ([]byte, error) {
bufCopy := resetBuffer()
// encode payload in preparation for transmission
payload := Payload{
InstanceID: instanceUUID.String(),
Timestamp: time.Now().UTC(),
Data: bufCopy,
}
return json.Marshal(payload)
}
// resetBuffer makes a local pointer to the buffer,
// then resets the buffer by assigning to be a newly-
// made value to clear it out, then sets the buffer
// item count to 0. It returns the copied pointer to
// the original map so the old buffer value can be
// used locally.
func resetBuffer() map[string]interface{} {
bufferMu.Lock()
bufCopy := buffer
buffer = make(map[string]interface{})
bufferItemCount = 0
bufferMu.Unlock()
return bufCopy
}
// Response contains the body of a response from the
// telemetry server.
type Response struct {
// NextUpdate is how long to wait before the next update.
NextUpdate time.Duration `json:"next_update"`
// Stop instructs the telemetry server to stop sending
// telemetry. This would only be done under extenuating
// circumstances, but we are prepared for it nonetheless.
Stop bool `json:"stop,omitempty"`
// Error will be populated with an error message, if any.
// This field should be empty if the status code is < 400.
Error string `json:"error,omitempty"`
// DisableKeys will contain a list of keys/metrics that
// should NOT be sent until further notice. The client
// must NOT store these items in its buffer or send them
// to the telemetry server while they are disabled. If
// this list and EnableKeys have the same value (which is
// not supposed to happen), this field should dominate.
DisableKeys []string `json:"disable_keys,omitempty"`
// EnableKeys will contain a list of keys/metrics that
// MAY be sent until further notice.
EnableKeys []string `json:"enable_keys,omitempty"`
}
// Payload is the data that gets sent to the telemetry server.
type Payload struct {
// The universally unique ID of the instance
InstanceID string `json:"instance_id"`
// The UTC timestamp of the transmission
Timestamp time.Time `json:"timestamp"`
// The timestamp before which the next update is expected
// (NOT populated by client - the server fills this in
// before it stores the data)
ExpectNext time.Time `json:"expect_next,omitempty"`
// The metrics
Data map[string]interface{} `json:"data,omitempty"`
}
// Int returns the value of the data keyed by key
// if it is an integer; otherwise it returns 0.
func (p Payload) Int(key string) int {
val, _ := p.Data[key]
switch p.Data[key].(type) {
case int:
return val.(int)
case float64: // after JSON-decoding, int becomes float64...
return int(val.(float64))
}
return 0
}
// countingSet implements a set that counts how many
// times a key is inserted. It marshals to JSON in a
// way such that keys are converted to values next
// to their associated counts.
type countingSet map[interface{}]int
// MarshalJSON implements the json.Marshaler interface.
// It converts the set to an array so that the values
// are JSON object values instead of keys, since keys
// are difficult to query in databases.
func (s countingSet) MarshalJSON() ([]byte, error) {
type Item struct {
Value interface{} `json:"value"`
Count int `json:"count"`
}
var list []Item
for k, v := range s {
list = append(list, Item{Value: k, Count: v})
}
return json.Marshal(list)
}
var (
// httpClient should be used for HTTP requests. It
// is configured with a timeout for reliability.
httpClient = http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: 30 * time.Second,
DisableKeepAlives: true,
},
Timeout: 1 * time.Minute,
}
// buffer holds the data that we are building up to send.
buffer = make(map[string]interface{})
bufferItemCount = 0
bufferMu sync.RWMutex // protects both the buffer and its count
// updating is used to ensure only one
// update happens at a time.
updating bool
updateMu sync.Mutex
// updateTimer fires off the next update.
// If no update is scheduled, this is nil.
updateTimer *time.Timer
updateTimerMu sync.Mutex
// disabledMetrics is a set of metric keys
// that should NOT be saved to the buffer
// or sent to the telemetry server. The value
// indicates whether the entry is temporary.
// If the value is true, it may be removed if
// the metric is re-enabled remotely later. If
// the value is false, it is permanent
// (presumably because the user explicitly
// disabled it) and can only be re-enabled
// with user consent.
disabledMetrics = make(map[string]bool)
disabledMetricsMu sync.RWMutex
// instanceUUID is the ID of the current instance.
// This MUST be set to emit telemetry.
// This MUST NOT be openly exposed to clients, for privacy.
instanceUUID uuid.UUID
// enabled indicates whether the package has
// been initialized and can be actively used.
enabled bool
// maxBufferItems is the maximum number of items we'll allow
// in the buffer before we start dropping new ones, in a
// rough (simple) attempt to keep memory use under control.
maxBufferItems = 100000
)
const (
// endpoint is the base URL to remote telemetry server;
// the instance ID will be appended to it.
endpoint = "https://telemetry.caddyserver.com/v1/update/"
// defaultUpdateInterval is how long to wait before emitting
// more telemetry data if all retires fail. This value is
// only used if the client receives a nonsensical value, or
// doesn't send one at all, or if a connection can't be made,
// likely indicating a problem with the server. Thus, this
// value should be a long duration to help alleviate extra
// load on the server.
defaultUpdateInterval = 1 * time.Hour
)
@@ -1,24 +1,5 @@
<p align="center">
<a href="https://caddyserver.com"><img src="https://user-images.githubusercontent.com/1128849/36338535-05fb646a-136f-11e8-987b-e6901e717d5a.png" alt="Caddy" width="450"></a>
</p>
<h3 align="center">Every Site on HTTPS <!-- Serve Confidently --></h3>
<p align="center">Caddy is a general-purpose HTTP/2 web server that serves HTTPS by default.</p>
<p align="center">
<a href="https://dev.azure.com/mholt-dev/Caddy/_build?definitionId=5"><img src="https://img.shields.io/azure-devops/build/mholt-dev/afec6074-9842-457f-98cf-69df6adbbf2e/5/master.svg?label=cross-platform%20tests"></a>
<a href="https://godoc.org/github.com/caddyserver/caddy"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
<a href="https://goreportcard.com/report/caddyserver/caddy"><img src="https://goreportcard.com/badge/github.com/caddyserver/caddy"></a>
<br>
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
</p>
<p align="center">
<a href="https://caddyserver.com/download">Download</a> ·
<a href="https://caddyserver.com/docs">Documentation</a> ·
<a href="https://caddy.community">Community</a>
</p>
THIS IS A FORK OF CADDY v1 - EVERYTHING IS STRIPPED EXCEPT THE PIECES NEEDED IN COREDNS.
---
Caddy is a **production-ready** open-source web server that is fast, easy to use, and makes you more productive.
@@ -51,7 +32,7 @@ Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.co
- **Extensible with plugins** because a convenient web server is a helpful one
- **Runs anywhere** with **no external dependencies** (not even libc)
[See a more complete list of features built into Caddy.](https://caddyserver.com/features) On top of all those, Caddy does even more with plugins: choose which plugins you want at [download](https://caddyserver.com/download).
[See a more complete list of features built into Caddy.](https://caddyserver.com/#features) On top of all those, Caddy does even more with plugins: choose which plugins you want at [download](https://caddyserver.com/download).
Altogether, Caddy can do things other web servers simply cannot do. Its features and plugins save you time and mistakes, and will cheer you up. Your Caddy instance takes care of the details for you!
@@ -94,7 +75,7 @@ package main
import (
"github.com/caddyserver/caddy/caddy/caddymain"
// plug in plugins here, for example:
// _ "import/path/here"
)
@@ -194,7 +175,7 @@ If you want to contribute to the documentation, please [submit an issue](https:/
### Good First Issue
If you are looking for somewhere to start and would like to help out by working on an existing issue, take a look at our [`Good First Issue`](https://github.com/caddyserver/caddy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) tag
If you are looking for somewhere to start and would like to help out by working on an existing issue, take a look at our [`Good First Issue`](https://github.com/caddyserver/caddy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) tag
Thanks for making Caddy -- and the Web -- better!
@@ -20,7 +20,7 @@
// 2. Call LoadCaddyfile() to get the Caddyfile.
// Pass in the name of the server type (like "http").
// Make sure the server type's package is imported
// (import _ "github.com/caddyserver/caddy/caddyhttp").
// (import _ "github.com/coredns/caddy/caddyhttp").
// 3. Call caddy.Start() to start Caddy. You get back
// an Instance, on which you can call Restart() to
// restart it or Stop() to stop it.
@@ -43,8 +43,7 @@ import (
"sync"
"time"
"github.com/caddyserver/caddy/caddyfile"
"github.com/caddyserver/caddy/telemetry"
"github.com/coredns/caddy/caddyfile"
)
// Configurable application parameters
@@ -600,12 +599,6 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo
return err
}
for _, sb := range sblocks {
for dir := range sb.Tokens {
telemetry.AppendUnique("directives", dir)
}
}
inst.context = stype.NewContext(inst)
if inst.context == nil {
return fmt.Errorf("server type %s produced a nil Context", stypeName)
@@ -616,8 +609,6 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo
return fmt.Errorf("error inspecting server blocks: %v", err)
}
telemetry.Set("num_server_blocks", len(sblocks))
return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
}
@@ -657,6 +648,11 @@ func executeDirectives(inst *Instance, filename string,
ServerBlockStorage: storages[i][dir],
}
// only set up directives for the first key in a block
if j > 0 {
continue
}
setup, err := DirectiveAction(inst.serverType, dir)
if err != nil {
return err
@@ -48,6 +48,9 @@ func (l *lexer) load(input io.Reader) error {
// discard byte order mark, if present
firstCh, _, err := l.reader.ReadRune()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
if firstCh != 0xFEFF {
@@ -17,7 +17,7 @@ package caddy
import (
"strings"
"github.com/caddyserver/caddy/caddyfile"
"github.com/coredns/caddy/caddyfile"
)
// Controller is given to the setup function of directives which
@@ -21,7 +21,7 @@ import (
"sort"
"sync"
"github.com/caddyserver/caddy/caddyfile"
"github.com/coredns/caddy/caddyfile"
)
// These are all the registered plugins.
@@ -19,8 +19,6 @@ import (
"os"
"os/signal"
"sync"
"github.com/caddyserver/caddy/telemetry"
)
// TrapSignals create signal handlers for all applicable signals for this
@@ -54,9 +52,6 @@ func trapSignalsCrossPlatform() {
log.Println("[INFO] SIGINT: Shutting down")
telemetry.AppendUnique("sigtrap", "SIGINT")
go telemetry.StopEmitting() // not guaranteed to finish in time; that's OK (just don't block!)
// important cleanup actions before shutdown callbacks
for _, f := range OnProcessExit {
f()
@@ -21,8 +21,6 @@ import (
"os"
"os/signal"
"syscall"
"github.com/caddyserver/caddy/telemetry"
)
// trapSignalsPosix captures POSIX-only signals.
@@ -52,14 +50,10 @@ func trapSignalsPosix() {
exitCode = 3
}
telemetry.AppendUnique("sigtrap", "SIGTERM")
go telemetry.StopEmitting() // won't finish in time, but that's OK - just don't block
os.Exit(exitCode)
case syscall.SIGUSR1:
log.Println("[INFO] SIGUSR1: Reloading")
go telemetry.AppendUnique("sigtrap", "SIGUSR1")
// Start with the existing Caddyfile
caddyfileToUse, inst, err := getCurrentCaddyfile()
@@ -100,14 +94,12 @@ func trapSignalsPosix() {
case syscall.SIGUSR2:
log.Println("[INFO] SIGUSR2: Upgrading")
go telemetry.AppendUnique("sigtrap", "SIGUSR2")
if err := Upgrade(); err != nil {
log.Printf("[ERROR] SIGUSR2: upgrading: %v", err)
}
case syscall.SIGHUP:
// ignore; this signal is sometimes sent outside of the user's control
go telemetry.AppendUnique("sigtrap", "SIGHUP")
}
}
}()
+2 -35
View File
@@ -4,20 +4,13 @@ import (
"fmt"
"net"
"strings"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/parse"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/miekg/dns"
)
type zoneAddr struct {
Zone string
Port string
Transport string // dns, tls or grpc
IPNet *net.IPNet // if reverse zone this hold the IPNet
Address string // used for bound zoneAddr - validation of overlapping
Transport string // dns, tls or grpc
Address string // used for bound zoneAddr - validation of overlapping
}
// String returns the string representation of z.
@@ -29,32 +22,6 @@ func (z zoneAddr) String() string {
return s
}
// normalizeZone parses a zone string into a structured format with separate
// host, and port portions, as well as the original input string.
func normalizeZone(str string) (zoneAddr, error) {
trans, str := parse.Transport(str)
host, port, ipnet, err := plugin.SplitHostPort(str)
if err != nil {
return zoneAddr{}, err
}
if port == "" {
switch trans {
case transport.DNS:
port = Port
case transport.TLS:
port = transport.TLSPort
case transport.GRPC:
port = transport.GRPCPort
case transport.HTTPS:
port = transport.HTTPSPort
}
}
return zoneAddr{Zone: dns.Fqdn(host), Port: port, Transport: trans, IPNet: ipnet}, nil
}
// SplitProtocolHostPort splits a full formed address like "dns://[::1]:53" into parts.
func SplitProtocolHostPort(address string) (protocol string, ip string, port string, err error) {
parts := strings.Split(address, "://")
+11 -7
View File
@@ -3,10 +3,10 @@ package dnsserver
import (
"crypto/tls"
"fmt"
"net/http"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin"
"github.com/caddyserver/caddy"
)
// Config configuration for a single server.
@@ -32,10 +32,10 @@ type Config struct {
// DNS-over-TLS or DNS-over-gRPC.
Transport string
// If this function is not nil it will be used to further filter access
// to this handler. The primary use is to limit access to a reverse zone
// on a non-octet boundary, i.e. /17
FilterFunc func(string) bool
// If this function is not nil it will be used to inspect and validate
// HTTP requests. Although this isn't referenced in-tree, external plugins
// may depend on it.
HTTPRequestValidateFunc func(*http.Request) bool
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
TLSConfig *tls.Config
@@ -50,9 +50,13 @@ type Config struct {
// on them should register themselves here. The name should be the name as return by the
// Handler's Name method.
registry map[string]plugin.Handler
// firstConfigInBlock is used to reference the first config in a server block, for the
// purpose of sharing single instance of each plugin among all zones in a server block.
firstConfigInBlock *Config
}
// keyForConfig build a key for identifying the configs during setup time
// keyForConfig builds a key for identifying the configs during setup time
func keyForConfig(blocIndex int, blocKeyIndex int) string {
return fmt.Sprintf("%d:%d", blocIndex, blocKeyIndex)
}
+7
View File
@@ -2,6 +2,7 @@ package dnsserver
import (
"net"
"net/http"
"github.com/coredns/coredns/plugin/pkg/nonwriter"
)
@@ -14,6 +15,9 @@ type DoHWriter struct {
raddr net.Addr
// laddr is our address. This can be optionally set.
laddr net.Addr
// request is the HTTP request we're currently handling.
request *http.Request
}
// RemoteAddr returns the remote address.
@@ -21,3 +25,6 @@ func (d *DoHWriter) RemoteAddr() net.Addr { return d.raddr }
// LocalAddr returns the local address.
func (d *DoHWriter) LocalAddr() net.Addr { return d.laddr }
// Request returns the HTTP request
func (d *DoHWriter) Request() *http.Request { return d.request }
+14 -3
View File
@@ -1,14 +1,25 @@
package dnsserver
import "fmt"
import (
"fmt"
"sort"
)
// startUpZones create the text that we show when starting up:
// startUpZones creates the text that we show when starting up:
// grpc://example.com.:1055
// example.com.:1053 on 127.0.0.1
func startUpZones(protocol, addr string, zones map[string]*Config) string {
s := ""
for zone := range zones {
keys := make([]string, len(zones))
i := 0
for k := range zones {
keys[i] = k
i++
}
sort.Strings(keys)
for _, zone := range keys {
// split addr into protocol, IP and Port
_, ip, port, err := SplitProtocolHostPort(addr)
+70 -21
View File
@@ -4,16 +4,15 @@ import (
"flag"
"fmt"
"net"
"strings"
"time"
"github.com/coredns/caddy"
"github.com/coredns/caddy/caddyfile"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/parse"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyfile"
"github.com/miekg/dns"
)
const serverType = "dns"
@@ -22,6 +21,7 @@ const serverType = "dns"
// wise they potentially clash with other server types.
func init() {
flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port")
flag.StringVar(&Port, "p", DefaultPort, "Default port")
caddy.RegisterServerType(serverType, caddy.ServerType{
Directives: func() []string { return Directives },
@@ -61,11 +61,57 @@ var _ caddy.Context = &dnsContext{}
func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
// Normalize and check all the zone names and check for duplicates
for ib, s := range serverBlocks {
// Walk the s.Keys and expand any reverse address in their proper DNS in-addr zones. If the expansions leads for
// more than one reverse zone, replace the current value and add the rest to s.Keys.
zoneAddrs := []zoneAddr{}
for ik, k := range s.Keys {
za, err := normalizeZone(k)
trans, k1 := parse.Transport(k) // get rid of any dns:// or other scheme.
hosts, port, err := plugin.SplitHostPort(k1)
// We need to make this a fully qualified domain name to catch all errors here and not later when
// plugin.Normalize is called again on these strings, with the prime difference being that the domain
// name is fully qualified. This was found by fuzzing where "ȶ" is deemed OK, but "ȶ." is not (might be a
// bug in miekg/dns actually). But here we were checking ȶ, which is OK, and later we barf in ȶ. leading to
// "index out of range".
for ih := range hosts {
_, _, err := plugin.SplitHostPort(dns.Fqdn(hosts[ih]))
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
if port == "" {
switch trans {
case transport.DNS:
port = Port
case transport.TLS:
port = transport.TLSPort
case transport.GRPC:
port = transport.GRPCPort
case transport.HTTPS:
port = transport.HTTPSPort
}
}
if len(hosts) > 1 {
s.Keys[ik] = hosts[0] + ":" + port // replace for the first
for _, h := range hosts[1:] { // add the rest
s.Keys = append(s.Keys, h+":"+port)
}
}
for i := range hosts {
zoneAddrs = append(zoneAddrs, zoneAddr{Zone: dns.Fqdn(hosts[i]), Port: port, Transport: trans})
}
}
serverBlocks[ib].Keys = s.Keys // important to save back the new keys that are potentially created here.
var firstConfigInBlock *Config
for ik := range s.Keys {
za := zoneAddrs[ik]
s.Keys[ik] = za.String()
// Save the config to our master list, and key it for lookups.
cfg := &Config{
@@ -74,23 +120,16 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
Port: za.Port,
Transport: za.Transport,
}
keyConfig := keyForConfig(ib, ik)
if za.IPNet == nil {
h.saveConfig(keyConfig, cfg)
continue
}
ones, bits := za.IPNet.Mask.Size()
if (bits-ones)%8 != 0 { // only do this for non-octet boundaries
cfg.FilterFunc = func(s string) bool {
// TODO(miek): strings.ToLower! Slow and allocates new string.
addr := dnsutil.ExtractAddressFromReverse(strings.ToLower(s))
if addr == "" {
return true
}
return za.IPNet.Contains(net.ParseIP(addr))
}
// Set reference to the first config in the current block.
// This is used later by MakeServers to share a single plugin list
// for all zones in a server block.
if ik == 0 {
firstConfigInBlock = cfg
}
cfg.firstConfigInBlock = firstConfigInBlock
keyConfig := keyForConfig(ib, ik)
h.saveConfig(keyConfig, cfg)
}
}
@@ -107,6 +146,17 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
return nil, errValid
}
// Copy the Plugin, ListenHosts and Debug from first config in the block
// to all other config in the same block . Doing this results in zones
// sharing the same plugin instances and settings as other zones in
// the same block.
for _, c := range h.configs {
c.Plugin = c.firstConfigInBlock.Plugin
c.ListenHosts = c.firstConfigInBlock.ListenHosts
c.Debug = c.firstConfigInBlock.Debug
c.TLSConfig = c.firstConfigInBlock.TLSConfig
}
// we must map (group) each config to a bind address
groups, err := groupConfigsByListenAddr(h.configs)
if err != nil {
@@ -223,7 +273,6 @@ func (h *dnsContext) validateZonesAndListeningAddresses() error {
// address (what you pass into net.Listen) to the list of site configs.
// This function does NOT vet the configs to ensure they are compatible.
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
groups := make(map[string][]*Config)
for _, conf := range configs {
for _, h := range conf.ListenHosts {
+26 -25
View File
@@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics/vars"
"github.com/coredns/coredns/plugin/pkg/edns"
@@ -20,7 +21,6 @@ import (
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/coredns/coredns/request"
"github.com/caddyserver/caddy"
"github.com/miekg/dns"
ot "github.com/opentracing/opentracing-go"
)
@@ -66,10 +66,6 @@ func NewServer(addr string, group []*Config) (*Server, error) {
if site.Debug {
s.debug = true
log.D.Set()
} else {
// When reloading we need to explicitly disable debug logging if it is now disabled.
s.debug = false
log.D.Clear()
}
// set the config per zone
s.zones[site.Zone] = site
@@ -97,6 +93,11 @@ func NewServer(addr string, group []*Config) (*Server, error) {
site.pluginChain = stack
}
if !s.debug {
// When reloading we need to explicitly disable debug logging if it is now disabled.
log.D.Clear()
}
return s, nil
}
@@ -109,6 +110,7 @@ func (s *Server) Serve(l net.Listener) error {
s.m.Lock()
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
@@ -122,6 +124,7 @@ func (s *Server) ServePacket(p net.PacketConn) error {
s.m.Lock()
s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
@@ -193,7 +196,7 @@ func (s *Server) Stop() (err error) {
// Address together with Stop() implement caddy.GracefulServer.
func (s *Server) Address() string { return s.Addr }
// ServeDNS is the entry point for every request to the address that s
// ServeDNS is the entry point for every request to the address that
// is bound to. It acts as a multiplexer for the requests zonename as
// defined in the request so that the correct zone
// (configuration and plugin stack) will handle the request.
@@ -210,7 +213,7 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
// In case the user doesn't enable error plugin, we still
// need to make sure that we stay alive up here
if rec := recover(); rec != nil {
log.Errorf("Recovered from panic in server: %q", s.Addr)
log.Errorf("Recovered from panic in server: %q %v", s.Addr, rec)
vars.Panic.Inc()
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeServerFailure)
}
@@ -239,23 +242,16 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
for {
if h, ok := s.zones[q[off:]]; ok {
if h.pluginChain == nil { // zone defined, but has not got any plugins
errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)
return
}
if r.Question[0].Qtype != dns.TypeDS {
if h.FilterFunc == nil {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
}
// FilterFunc is set, call it to see if we should use this handler.
// This is given to full query name.
if h.FilterFunc(q) {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
}
// The type is DS, keep the handler, but keep on searching as maybe we are serving
// the parent as well and the DS should be routed to it - this will probably *misroute* DS
@@ -332,7 +328,7 @@ func errorAndMetricsFunc(server string, w dns.ResponseWriter, r *dns.Msg, rc int
answer.SetRcode(r, rc)
state.SizeAndDo(answer)
vars.Report(server, state, vars.Dropped, rcode.ToString(rc), answer.Len(), time.Now())
vars.Report(server, state, vars.Dropped, rcode.ToString(rc), "" /* plugin */, answer.Len(), time.Now())
w.WriteMsg(answer)
}
@@ -342,8 +338,13 @@ const (
udp = 1
)
// Key is the context key for the current server added to the context.
type Key struct{}
type (
// Key is the context key for the current server added to the context.
Key struct{}
// LoopKey is the context key to detect server wide loops.
LoopKey struct{}
)
// EnableChaos is a map with plugin names for which we should open CH class queries as we block these by default.
var EnableChaos = map[string]struct{}{
+8 -2
View File
@@ -7,11 +7,11 @@ import (
"fmt"
"net"
"github.com/coredns/caddy"
"github.com/coredns/coredns/pb"
"github.com/coredns/coredns/plugin/pkg/reuseport"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/caddyserver/caddy"
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
"github.com/miekg/dns"
"github.com/opentracing/opentracing-go"
@@ -34,12 +34,17 @@ func NewServergRPC(addr string, group []*Config) (*ServergRPC, error) {
return nil, err
}
// The *tls* plugin must make sure that multiple conflicting
// TLS configuration return an error: it can only be specified once.
// TLS configuration returns an error: it can only be specified once.
var tlsConfig *tls.Config
for _, conf := range s.zones {
// Should we error if some configs *don't* have TLS?
tlsConfig = conf.TLSConfig
}
// http/2 is required when using gRPC. We need to specify it in next protos
// or the upgrade won't happen.
if tlsConfig != nil {
tlsConfig.NextProtos = []string{"h2"}
}
return &ServergRPC{Server: s, tlsConfig: tlsConfig}, nil
}
@@ -134,6 +139,7 @@ func (s *ServergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket
w := &gRPCresponse{localAddr: s.listenAddr, remoteAddr: a, Msg: msg}
dnsCtx := context.WithValue(ctx, Key{}, s.Server)
dnsCtx = context.WithValue(dnsCtx, LoopKey{}, 0)
s.ServeDNS(dnsCtx, w, msg)
packed, err := w.Msg.Pack()
+37 -10
View File
@@ -9,38 +9,60 @@ import (
"strconv"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/doh"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/coredns/coredns/plugin/pkg/reuseport"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/caddyserver/caddy"
)
// ServerHTTPS represents an instance of a DNS-over-HTTPS server.
type ServerHTTPS struct {
*Server
httpsServer *http.Server
listenAddr net.Addr
tlsConfig *tls.Config
httpsServer *http.Server
listenAddr net.Addr
tlsConfig *tls.Config
validRequest func(*http.Request) bool
}
// NewServerHTTPS returns a new CoreDNS GRPC server and compiles all plugins in to it.
// NewServerHTTPS returns a new CoreDNS HTTPS server and compiles all plugins in to it.
func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
// The *tls* plugin must make sure that multiple conflicting
// TLS configuration return an error: it can only be specified once.
// TLS configuration returns an error: it can only be specified once.
var tlsConfig *tls.Config
for _, conf := range s.zones {
// Should we error if some configs *don't* have TLS?
tlsConfig = conf.TLSConfig
}
sh := &ServerHTTPS{Server: s, tlsConfig: tlsConfig, httpsServer: new(http.Server)}
// http/2 is recommended when using DoH. We need to specify it in next protos
// or the upgrade won't happen.
if tlsConfig != nil {
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
}
// Use a custom request validation func or use the standard DoH path check.
var validator func(*http.Request) bool
for _, conf := range s.zones {
validator = conf.HTTPRequestValidateFunc
}
if validator == nil {
validator = func(r *http.Request) bool { return r.URL.Path == doh.Path }
}
srv := &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
sh := &ServerHTTPS{
Server: s, tlsConfig: tlsConfig, httpsServer: srv, validRequest: validator,
}
sh.httpsServer.Handler = sh
return sh, nil
@@ -104,7 +126,7 @@ func (s *ServerHTTPS) Stop() error {
// chain, converts it back and write it to the client.
func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != doh.Path {
if !s.validRequest(r) {
http.Error(w, "", http.StatusNotFound)
return
}
@@ -118,11 +140,16 @@ func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Create a DoHWriter with the correct addresses in it.
h, p, _ := net.SplitHostPort(r.RemoteAddr)
port, _ := strconv.Atoi(p)
dw := &DoHWriter{laddr: s.listenAddr, raddr: &net.TCPAddr{IP: net.ParseIP(h), Port: port}}
dw := &DoHWriter{
laddr: s.listenAddr,
raddr: &net.TCPAddr{IP: net.ParseIP(h), Port: port},
request: r,
}
// We just call the normal chain handler - all error handling is done there.
// We should expect a packet to be returned that we can send to the client.
ctx := context.WithValue(context.Background(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, dw, msg)
// See section 4.2.1 of RFC 8484.
+3 -2
View File
@@ -6,10 +6,10 @@ import (
"fmt"
"net"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin/pkg/reuseport"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/caddyserver/caddy"
"github.com/miekg/dns"
)
@@ -26,7 +26,7 @@ func NewServerTLS(addr string, group []*Config) (*ServerTLS, error) {
return nil, err
}
// The *tls* plugin must make sure that multiple conflicting
// TLS configuration return an error: it can only be specified once.
// TLS configuration returns an error: it can only be specified once.
var tlsConfig *tls.Config
for _, conf := range s.zones {
// Should we error if some configs *don't* have TLS?
@@ -50,6 +50,7 @@ func (s *ServerTLS) Serve(l net.Listener) error {
// Only fill out the TCP server for this one.
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp-tls", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
+4
View File
@@ -11,6 +11,7 @@ package dnsserver
// care what plugin above them are doing.
var Directives = []string{
"metadata",
"geoip",
"cancel",
"tls",
"reload",
@@ -27,6 +28,7 @@ var Directives = []string{
"errors",
"log",
"dnstap",
"local",
"dns64",
"acl",
"any",
@@ -34,8 +36,10 @@ var Directives = []string{
"loadbalance",
"cache",
"rewrite",
"header",
"dnssec",
"autopath",
"minimal",
"template",
"transfer",
"hosts",
+3 -33
View File
@@ -4,15 +4,13 @@ package coremain
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
"strings"
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/caddyserver/caddy"
)
func init() {
@@ -36,21 +34,6 @@ func init() {
// Run is CoreDNS's main() function.
func Run() {
caddy.TrapSignals()
// Reset flag.CommandLine to get rid of unwanted flags for instance from glog (used in kubernetes).
// And read the ones we want to keep.
flag.VisitAll(func(f *flag.Flag) {
if _, ok := flagsBlacklist[f.Name]; ok {
return
}
flagsToKeep = append(flagsToKeep, f)
})
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
for _, f := range flagsToKeep {
flag.Var(f.Value, f.Name, f.Usage)
}
flag.Parse()
if len(flag.Args()) > 0 {
@@ -112,7 +95,7 @@ func confLoader(serverType string) (caddy.Input, error) {
return caddy.CaddyfileFromPipe(os.Stdin, serverType)
}
contents, err := ioutil.ReadFile(conf)
contents, err := os.ReadFile(conf)
if err != nil {
return nil, err
}
@@ -125,7 +108,7 @@ func confLoader(serverType string) (caddy.Input, error) {
// defaultLoader loads the Corefile from the current working directory.
func defaultLoader(serverType string) (caddy.Input, error) {
contents, err := ioutil.ReadFile(caddy.DefaultConfigFile)
contents, err := os.ReadFile(caddy.DefaultConfigFile)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
@@ -198,16 +181,3 @@ var (
// Gitcommit contains the commit where we built CoreDNS from.
GitCommit string
)
// flagsBlacklist removes flags with these names from our flagset.
var flagsBlacklist = map[string]struct{}{
"logtostderr": {},
"alsologtostderr": {},
"v": {},
"stderrthreshold": {},
"vmodule": {},
"log_backtrace_at": {},
"log_dir": {},
}
var flagsToKeep []*flag.Flag
+1 -1
View File
@@ -2,7 +2,7 @@ package coremain
// Various CoreDNS constants.
const (
CoreVersion = "1.7.0"
CoreVersion = "1.8.7"
coreName = "CoreDNS"
serverType = "dns"
)
+1 -10
View File
@@ -26,23 +26,14 @@ type ServiceBackend interface {
// Note: it does not implement a specific service.
Records(ctx context.Context, state request.Request, exact bool) ([]msg.Service, error)
// IsNameError return true if err indicated a record not found condition
// IsNameError returns true if err indicated a record not found condition
IsNameError(err error) bool
Transferer
}
// Transferer defines an interface for backends that provide AXFR of all records.
type Transferer interface {
// Serial returns a SOA serial number to construct a SOA record.
Serial(state request.Request) uint32
// MinTTL returns the minimum TTL to be used in the SOA record.
MinTTL(state request.Request) uint32
// Transfer handles a zone transfer it writes to the client just
// like any other handler.
Transfer(ctx context.Context, state request.Request) (int, error)
}
// Options are extra options that can be specified for a lookup.
+4 -8
View File
@@ -422,7 +422,7 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques
old := state.QName()
state.Clear()
state.Req.Question[0].Name = "ns.dns." + zone
state.Req.Question[0].Name = dnsutil.Join("ns.dns.", zone)
services, err := b.Services(ctx, state, false, opt)
if err != nil {
return nil, nil, err
@@ -440,8 +440,8 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques
case dns.TypeA, dns.TypeAAAA:
serv.Host = msg.Domain(serv.Key)
extra = append(extra, newAddress(serv, serv.Host, ip, what))
ns := serv.NewNS(state.QName())
extra = append(extra, newAddress(serv, ns.Ns, ip, what))
if _, ok := seen[ns.Ns]; ok {
continue
}
@@ -462,12 +462,8 @@ func SOA(ctx context.Context, b ServiceBackend, zone string, state request.Reque
header := dns.RR_Header{Name: zone, Rrtype: dns.TypeSOA, Ttl: ttl, Class: dns.ClassINET}
Mbox := hostmaster + "."
Ns := "ns.dns."
if zone[0] != '.' {
Mbox += zone
Ns += zone
}
Mbox := dnsutil.Join(hostmaster, zone)
Ns := dnsutil.Join("ns.dns", zone)
soa := &dns.SOA{Hdr: header,
Mbox: Mbox,
+7 -1
View File
@@ -10,6 +10,9 @@ With *cache* enabled, all records except zone transfers and metadata records wil
3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream,
database, etc.) is expensive.
*Cache* will change the query to enable DNSSEC (DNSSEC OK; DO) if it passes through the plugin. If
the client didn't request any DNSSEC (records), these are filtered out when replying.
This plugin can only be used once per Server Block.
## Syntax
@@ -72,9 +75,12 @@ If monitoring is enabled (via the *prometheus* plugin) then the following metric
* `coredns_cache_entries{server, type}` - Total elements in the cache by cache type.
* `coredns_cache_hits_total{server, type}` - Counter of cache hits by cache type.
* `coredns_cache_misses_total{server}` - Counter of cache misses.
* `coredns_cache_misses_total{server}` - Counter of cache misses. - Deprecated, derive misses from cache hits/requests counters.
* `coredns_cache_requests_total{server}` - Counter of cache requests.
* `coredns_cache_prefetch_total{server}` - Counter of times the cache has prefetched a cached item.
* `coredns_cache_drops_total{server}` - Counter of responses excluded from the cache due to request/response question name mismatch.
* `coredns_cache_served_stale_total{server}` - Counter of requests served from stale cache entries.
* `coredns_cache_evictions_total{server, type}` - Counter of cache evictions.
Cache types are either "denial" or "success". `Server` is the server handling the request, see the
prometheus plugin for documentation.
+21 -32
View File
@@ -65,31 +65,21 @@ func New() *Cache {
// key returns key under which we store the item, -1 will be returned if we don't store the message.
// Currently we do not cache Truncated, errors zone transfers or dynamic update messages.
// qname holds the already lowercased qname.
func key(qname string, m *dns.Msg, t response.Type, do bool) (bool, uint64) {
func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
// We don't store truncated responses.
if m.Truncated {
return false, 0
}
// Nor errors or Meta or Update
// Nor errors or Meta or Update.
if t == response.OtherError || t == response.Meta || t == response.Update {
return false, 0
}
return true, hash(qname, m.Question[0].Qtype, do)
return true, hash(qname, m.Question[0].Qtype)
}
var one = []byte("1")
var zero = []byte("0")
func hash(qname string, qtype uint16, do bool) uint64 {
func hash(qname string, qtype uint16) uint64 {
h := fnv.New64()
if do {
h.Write(one)
} else {
h.Write(zero)
}
h.Write([]byte{byte(qtype >> 8)})
h.Write([]byte{byte(qtype)})
h.Write([]byte(qname))
@@ -114,6 +104,7 @@ type ResponseWriter struct {
state request.Request
server string // Server handling the request.
do bool // When true the original request had the DO bit set.
prefetch bool // When true write nothing back to the client.
remoteAddr net.Addr
}
@@ -152,14 +143,10 @@ func (w *ResponseWriter) RemoteAddr() net.Addr {
// WriteMsg implements the dns.ResponseWriter interface.
func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
do := false
mt, opt := response.Typify(res, w.now().UTC())
if opt != nil {
do = opt.Do()
}
mt, _ := response.Typify(res, w.now().UTC())
// key returns empty string for anything we don't want to cache.
hasKey, key := key(w.state.Name(), res, mt, do)
hasKey, key := key(w.state.Name(), res, mt)
msgTTL := dnsutil.MinimalTTL(res, mt)
var duration time.Duration
@@ -188,18 +175,16 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
}
// Apply capped TTL to this reply to avoid jarring TTL experience 1799 -> 8 (e.g.)
// We also may need to filter out DNSSEC records, see toMsg() for similar code.
ttl := uint32(duration.Seconds())
for i := range res.Answer {
res.Answer[i].Header().Ttl = ttl
}
for i := range res.Ns {
res.Ns[i].Header().Ttl = ttl
}
for i := range res.Extra {
if res.Extra[i].Header().Rrtype != dns.TypeOPT {
res.Extra[i].Header().Ttl = ttl
}
res.Answer = filterRRSlice(res.Answer, ttl, w.do, false)
res.Ns = filterRRSlice(res.Ns, ttl, w.do, false)
res.Extra = filterRRSlice(res.Extra, ttl, w.do, false)
if !w.do {
res.AuthenticatedData = false // unset AD bit if client is not OK with DNSSEC
}
return w.ResponseWriter.WriteMsg(res)
}
@@ -209,7 +194,9 @@ func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration
switch mt {
case response.NoError, response.Delegation:
i := newItem(m, w.now(), duration)
w.pcache.Add(key, i)
if w.pcache.Add(key, i) {
evictions.WithLabelValues(w.server, Success).Inc()
}
// when pre-fetching, remove the negative cache entry if it exists
if w.prefetch {
w.ncache.Remove(key)
@@ -217,7 +204,9 @@ func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration
case response.NameError, response.NoData, response.ServerError:
i := newItem(m, w.now(), duration)
w.ncache.Add(key, i)
if w.ncache.Add(key, i) {
evictions.WithLabelValues(w.server, Denial).Inc()
}
case response.OtherError:
// don't cache these
+46
View File
@@ -0,0 +1,46 @@
package cache
import "github.com/miekg/dns"
// isDNSSEC returns true if r is a DNSSEC record. NSEC,NSEC3,DS and RRSIG/SIG
// are DNSSEC records. DNSKEYs is not in this list on the assumption that the
// client explicitly asked for it.
func isDNSSEC(r dns.RR) bool {
switch r.Header().Rrtype {
case dns.TypeNSEC:
return true
case dns.TypeNSEC3:
return true
case dns.TypeDS:
return true
case dns.TypeRRSIG:
return true
case dns.TypeSIG:
return true
}
return false
}
// filterRRSlice filters rrs and removes DNSSEC RRs when do is false. In the returned slice
// the TTLs are set to ttl. If dup is true the RRs in rrs are _copied_ into the slice that is
// returned.
func filterRRSlice(rrs []dns.RR, ttl uint32, do, dup bool) []dns.RR {
j := 0
rs := make([]dns.RR, len(rrs))
for _, r := range rrs {
if !do && isDNSSEC(r) {
continue
}
if r.Header().Rrtype == dns.TypeOPT {
continue
}
r.Header().Ttl = ttl
if dup {
rs[j] = dns.Copy(r)
} else {
rs[j] = r
}
j++
}
return rs[:j]
}
+53 -22
View File
@@ -14,50 +14,53 @@ import (
// ServeDNS implements the plugin.Handler interface.
func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
rc := r.Copy() // We potentially modify r, to prevent other plugins from seeing this (r is a pointer), copy r into rc.
state := request.Request{W: w, Req: rc}
do := state.Do()
zone := plugin.Zones(c.Zones).Matches(state.Name())
if zone == "" {
return plugin.NextOrFailure(c.Name(), c.Next, ctx, w, r)
return plugin.NextOrFailure(c.Name(), c.Next, ctx, w, rc)
}
now := c.now().UTC()
server := metrics.WithServer(ctx)
// On cache miss, if the request has the OPT record and the DO bit set we leave the message as-is. If there isn't a DO bit
// set we will modify the request to _add_ one. This means we will always do DNSSEC lookups on cache misses.
// When writing to cache, any DNSSEC RRs in the response are written to cache with the response.
// When sending a response to a non-DNSSEC client, we remove DNSSEC RRs from the response. We use a 2048 buffer size, which is
// less than 4096 (and older default) and more than 1024 which may be too small. We might need to tweaks this
// value to be smaller still to prevent UDP fragmentation?
ttl := 0
i := c.getIgnoreTTL(now, state, server)
if i != nil {
ttl = i.ttl(now)
}
if i == nil {
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server}
return plugin.NextOrFailure(c.Name(), c.Next, ctx, crr, r)
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do}
return c.doRefresh(ctx, state, crr)
}
if ttl < 0 {
servedStale.WithLabelValues(server).Inc()
// Adjust the time to get a 0 TTL in the reply built from a stale item.
now = now.Add(time.Duration(ttl) * time.Second)
go func() {
r := r.Copy()
crr := &ResponseWriter{Cache: c, state: state, server: server, prefetch: true, remoteAddr: w.LocalAddr()}
plugin.NextOrFailure(c.Name(), c.Next, ctx, crr, r)
}()
cw := newPrefetchResponseWriter(server, state, c)
go c.doPrefetch(ctx, state, cw, i, now)
} else if c.shouldPrefetch(i, now) {
cw := newPrefetchResponseWriter(server, state, c)
go c.doPrefetch(ctx, state, cw, i, now)
}
resp := i.toMsg(r, now)
resp := i.toMsg(r, now, do)
w.WriteMsg(resp)
if c.shouldPrefetch(i, now) {
go c.doPrefetch(ctx, state, server, i, now)
}
return dns.RcodeSuccess, nil
}
func (c *Cache) doPrefetch(ctx context.Context, state request.Request, server string, i *item, now time.Time) {
cw := newPrefetchResponseWriter(server, state, c)
cachePrefetches.WithLabelValues(server).Inc()
plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
func (c *Cache) doPrefetch(ctx context.Context, state request.Request, cw *ResponseWriter, i *item, now time.Time) {
cachePrefetches.WithLabelValues(cw.server).Inc()
c.doRefresh(ctx, state, cw)
// When prefetching we loose the item i, and with it the frequency
// that we've gathered sofar. See we copy the frequencies info back
@@ -67,6 +70,13 @@ func (c *Cache) doPrefetch(ctx context.Context, state request.Request, server st
}
}
func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw *ResponseWriter) (int, error) {
if !state.Do() {
setDo(state.Req)
}
return plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
}
func (c *Cache) shouldPrefetch(i *item, now time.Time) bool {
if c.prefetch <= 0 {
return false
@@ -80,7 +90,8 @@ func (c *Cache) shouldPrefetch(i *item, now time.Time) bool {
func (c *Cache) Name() string { return "cache" }
func (c *Cache) get(now time.Time, state request.Request, server string) (*item, bool) {
k := hash(state.Name(), state.QType(), state.Do())
k := hash(state.Name(), state.QType())
cacheRequests.WithLabelValues(server).Inc()
if i, ok := c.ncache.Get(k); ok && i.(*item).ttl(now) > 0 {
cacheHits.WithLabelValues(server, Denial).Inc()
@@ -97,7 +108,8 @@ func (c *Cache) get(now time.Time, state request.Request, server string) (*item,
// getIgnoreTTL unconditionally returns an item if it exists in the cache.
func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string) *item {
k := hash(state.Name(), state.QType(), state.Do())
k := hash(state.Name(), state.QType())
cacheRequests.WithLabelValues(server).Inc()
if i, ok := c.ncache.Get(k); ok {
ttl := i.(*item).ttl(now)
@@ -118,7 +130,7 @@ func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string
}
func (c *Cache) exists(state request.Request) *item {
k := hash(state.Name(), state.QType(), state.Do())
k := hash(state.Name(), state.QType())
if i, ok := c.ncache.Get(k); ok {
return i.(*item)
}
@@ -127,3 +139,22 @@ func (c *Cache) exists(state request.Request) *item {
}
return nil
}
// setDo sets the DO bit and UDP buffer size in the message m.
func setDo(m *dns.Msg) {
o := m.IsEdns0()
if o != nil {
o.SetDo()
o.SetUDPSize(defaultUDPBufSize)
return
}
o = &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
o.SetDo()
o.SetUDPSize(defaultUDPBufSize)
m.Extra = append(m.Extra, o)
}
// defaultUDPBufsize is the bufsize the cache plugin uses on outgoing requests that don't
// have an OPT RR.
const defaultUDPBufSize = 2048
+9 -14
View File
@@ -4,6 +4,7 @@ import (
"time"
"github.com/coredns/coredns/plugin/cache/freq"
"github.com/miekg/dns"
)
@@ -55,7 +56,7 @@ func newItem(m *dns.Msg, now time.Time, d time.Duration) *item {
// So we're forced to always set this to 1; regardless if the answer came from the cache or not.
// On newer systems(e.g. ubuntu 16.04 with glib version 2.23), this issue is resolved.
// So we may set this bit back to 0 in the future ?
func (i *item) toMsg(m *dns.Msg, now time.Time) *dns.Msg {
func (i *item) toMsg(m *dns.Msg, now time.Time, do bool) *dns.Msg {
m1 := new(dns.Msg)
m1.SetReply(m)
@@ -64,6 +65,9 @@ func (i *item) toMsg(m *dns.Msg, now time.Time) *dns.Msg {
// just set it to true.
m1.Authoritative = true
m1.AuthenticatedData = i.AuthenticatedData
if !do {
m1.AuthenticatedData = false // when DNSSEC was not wanted, it can't be authenticated data.
}
m1.RecursionAvailable = i.RecursionAvailable
m1.Rcode = i.Rcode
@@ -72,19 +76,10 @@ func (i *item) toMsg(m *dns.Msg, now time.Time) *dns.Msg {
m1.Extra = make([]dns.RR, len(i.Extra))
ttl := uint32(i.ttl(now))
for j, r := range i.Answer {
m1.Answer[j] = dns.Copy(r)
m1.Answer[j].Header().Ttl = ttl
}
for j, r := range i.Ns {
m1.Ns[j] = dns.Copy(r)
m1.Ns[j].Header().Ttl = ttl
}
// newItem skips OPT records, so we can just use i.Extra as is.
for j, r := range i.Extra {
m1.Extra[j] = dns.Copy(r)
m1.Extra[j].Header().Ttl = ttl
}
m1.Answer = filterRRSlice(i.Answer, ttl, do, true)
m1.Ns = filterRRSlice(i.Ns, ttl, do, true)
m1.Extra = filterRRSlice(i.Extra, ttl, do, true)
return m1
}
+24 -9
View File
@@ -4,49 +4,64 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
// cacheSize is total elements in the cache by cache type.
cacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
cacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "entries",
Help: "The number of elements in the cache.",
}, []string{"server", "type"})
// cacheRequests is a counter of all requests through the cache.
cacheRequests = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "requests_total",
Help: "The count of cache requests.",
}, []string{"server"})
// cacheHits is counter of cache hits by cache type.
cacheHits = prometheus.NewCounterVec(prometheus.CounterOpts{
cacheHits = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "hits_total",
Help: "The count of cache hits.",
}, []string{"server", "type"})
// cacheMisses is the counter of cache misses.
cacheMisses = prometheus.NewCounterVec(prometheus.CounterOpts{
// cacheMisses is the counter of cache misses. - Deprecated
cacheMisses = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "misses_total",
Help: "The count of cache misses.",
Help: "The count of cache misses. Deprecated, derive misses from cache hits/requests counters.",
}, []string{"server"})
// cachePrefetches is the number of time the cache has prefetched a cached item.
cachePrefetches = prometheus.NewCounterVec(prometheus.CounterOpts{
cachePrefetches = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "prefetch_total",
Help: "The number of time the cache has prefetched a cached item.",
Help: "The number of times the cache has prefetched a cached item.",
}, []string{"server"})
// cacheDrops is the number responses that are not cached, because the reply is malformed.
cacheDrops = prometheus.NewCounterVec(prometheus.CounterOpts{
cacheDrops = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "drops_total",
Help: "The number responses that are not cached, because the reply is malformed.",
}, []string{"server"})
// servedStale is the number of requests served from stale cache entries.
servedStale = prometheus.NewCounterVec(prometheus.CounterOpts{
servedStale = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "served_stale_total",
Help: "The number of requests served from stale cache entries.",
}, []string{"server"})
// evictions is the counter of cache evictions.
evictions = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "evictions_total",
Help: "The count of cache evictions.",
}, []string{"server", "type"})
)
+2 -20
View File
@@ -6,13 +6,11 @@ import (
"strconv"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
"github.com/coredns/coredns/plugin/pkg/cache"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/caddyserver/caddy"
)
var log = clog.NewWithPlugin("cache")
@@ -29,13 +27,6 @@ func setup(c *caddy.Controller) error {
return ca
})
c.OnStartup(func() error {
metrics.MustRegister(c,
cacheSize, cacheHits, cacheMisses,
cachePrefetches, cacheDrops, servedStale)
return nil
})
return nil
}
@@ -50,10 +41,7 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
j++
// cache [ttl] [zones..]
origins := make([]string, len(c.ServerBlockKeys))
copy(origins, c.ServerBlockKeys)
args := c.RemainingArgs()
if len(args) > 0 {
// first args may be just a number, then it is the ttl, if not it is a zone
ttl, err := strconv.Atoi(args[0])
@@ -66,10 +54,8 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
ca.nttl = time.Duration(ttl) * time.Second
args = args[1:]
}
if len(args) > 0 {
copy(origins, args)
}
}
origins := plugin.OriginsFromArgsOrServerBlock(args, c.ServerBlockKeys)
// Refinements? In an extra block.
for c.NextBlock() {
@@ -198,11 +184,7 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
}
}
for i := range origins {
origins[i] = plugin.Host(origins[i]).Normalize()
}
ca.Zones = origins
ca.pcache = cache.New(ca.pcap)
ca.ncache = cache.New(ca.ncap)
}
+4 -2
View File
@@ -17,7 +17,7 @@ The following metrics are exported:
* `coredns_dns_request_size_bytes{server, zone, proto}` - size of the request in bytes.
* `coredns_dns_do_requests_total{server, zone}` - queries that have the DO bit set
* `coredns_dns_response_size_bytes{server, zone, proto}` - response size in bytes.
* `coredns_dns_responses_total{server, zone, rcode}` - response per zone and rcode.
* `coredns_dns_responses_total{server, zone, rcode, plugin}` - response per zone, rcode and plugin.
* `coredns_plugin_enabled{server, zone, name}` - indicates whether a plugin is enabled on per server and zone basis.
Each counter has a label `zone` which is the zonename used for the request/response.
@@ -30,8 +30,10 @@ Extra labels used are:
* `proto` which holds the transport of the response ("udp" or "tcp")
* The address family (`family`) of the transport (1 = IP (IP version 4), 2 = IP6 (IP version 6)).
* `type` which holds the query type. It holds most common types (A, AAAA, MX, SOA, CNAME, PTR, TXT,
NS, SRV, DS, DNSKEY, RRSIG, NSEC, NSEC3, IXFR, AXFR and ANY) and "other" which lumps together all
NS, SRV, DS, DNSKEY, RRSIG, NSEC, NSEC3, HTTPS, IXFR, AXFR and ANY) and "other" which lumps together all
other types.
* the `plugin` label holds the name of the plugin that made the write to the client. If the server
did the write (on error for instance), the value is empty.
If monitoring is enabled, queries that do not enter the plugin chain are exported under the fake
name "dropped" (without a closing dot - this is never a valid domain name).
+25 -3
View File
@@ -2,10 +2,10 @@ package metrics
import (
"context"
"path/filepath"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics/vars"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/rcode"
"github.com/coredns/coredns/request"
@@ -23,13 +23,35 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
}
// Record response to get status code and size of the reply.
rw := dnstest.NewRecorder(w)
rw := NewRecorder(w)
status, err := plugin.NextOrFailure(m.Name(), m.Next, ctx, rw, r)
vars.Report(WithServer(ctx), state, zone, rcode.ToString(rw.Rcode), rw.Len, rw.Start)
rc := rw.Rcode
if !plugin.ClientWrite(status) {
// when no response was written, fallback to status returned from next plugin as this status
// is actually used as rcode of DNS response
// see https://github.com/coredns/coredns/blob/master/core/dnsserver/server.go#L318
rc = status
}
plugin := m.authoritativePlugin(rw.Caller)
vars.Report(WithServer(ctx), state, zone, rcode.ToString(rc), plugin, rw.Len, rw.Start)
return status, err
}
// Name implements the Handler interface.
func (m *Metrics) Name() string { return "prometheus" }
// authoritativePlugin returns which of made the write, if none is found the empty string is returned.
func (m *Metrics) authoritativePlugin(caller [3]string) string {
// a b and c contain the full path of the caller, the plugin name 2nd last elements
// .../coredns/plugin/whoami/whoami.go --> whoami
// this is likely FS specific, so use filepath.
for _, c := range caller {
plug := filepath.Base(filepath.Dir(c))
if _, ok := m.plugins[plug]; ok {
return plug
}
}
return ""
}
+20 -17
View File
@@ -8,11 +8,12 @@ import (
"sync"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics/vars"
"github.com/coredns/coredns/plugin/pkg/reuseport"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
@@ -31,29 +32,18 @@ type Metrics struct {
zoneNames []string
zoneMap map[string]struct{}
zoneMu sync.RWMutex
plugins map[string]struct{} // all available plugins, used to determine which plugin made the client write
}
// New returns a new instance of Metrics with the given address.
func New(addr string) *Metrics {
met := &Metrics{
Addr: addr,
Reg: prometheus.NewRegistry(),
Reg: prometheus.DefaultRegisterer.(*prometheus.Registry),
zoneMap: make(map[string]struct{}),
plugins: pluginList(caddy.ListPlugins()),
}
// Add the default collectors
met.MustRegister(prometheus.NewGoCollector())
met.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
// Add all of our collectors
met.MustRegister(buildInfo)
met.MustRegister(vars.Panic)
met.MustRegister(vars.RequestCount)
met.MustRegister(vars.RequestDuration)
met.MustRegister(vars.RequestSize)
met.MustRegister(vars.RequestDo)
met.MustRegister(vars.ResponseSize)
met.MustRegister(vars.ResponseRcode)
met.MustRegister(vars.PluginEnabled)
return met
}
@@ -154,6 +144,19 @@ func keys(m map[string]struct{}) []string {
return sx
}
// pluginList iterates over the returned plugin map from caddy and removes the "dns." prefix from them.
func pluginList(m map[string][]string) map[string]struct{} {
pm := map[string]struct{}{}
for _, p := range m["others"] {
// only add 'dns.' plugins
if len(p) > 3 {
pm[p[4:]] = struct{}{}
continue
}
}
return pm
}
// ListenAddr is assigned the address of the prometheus listener. Its use is mainly in tests where
// we listen on "localhost:0" and need to retrieve the actual address.
var ListenAddr string
@@ -162,7 +165,7 @@ var ListenAddr string
// before erroring when it tries to close the metrics server
const shutdownTimeout time.Duration = time.Second * 5
var buildInfo = prometheus.NewGaugeVec(prometheus.GaugeOpts{
var buildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: plugin.Namespace,
Name: "build_info",
Help: "A metric with a constant '1' value labeled by version, revision, and goversion from which CoreDNS was built.",
+30
View File
@@ -0,0 +1,30 @@
package metrics
import (
"runtime"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/miekg/dns"
)
// Recorder is a dnstest.Recorder specific to the metrics plugin.
type Recorder struct {
*dnstest.Recorder
// CallerN holds the string return value of the call to runtime.Caller(N+1)
Caller [3]string
}
// NewRecorder makes and returns a new Recorder.
func NewRecorder(w dns.ResponseWriter) *Recorder { return &Recorder{Recorder: dnstest.NewRecorder(w)} }
// WriteMsg records the status code and calls the
// underlying ResponseWriter's WriteMsg method.
func (r *Recorder) WriteMsg(res *dns.Msg) error {
_, r.Caller[0], _, _ = runtime.Caller(1)
_, r.Caller[1], _, _ = runtime.Caller(2)
_, r.Caller[2], _, _ = runtime.Caller(3)
r.Len += res.Len()
r.Msg = res
return r.ResponseWriter.WriteMsg(res)
}
-23
View File
@@ -1,23 +0,0 @@
package metrics
import (
"github.com/coredns/coredns/core/dnsserver"
"github.com/caddyserver/caddy"
"github.com/prometheus/client_golang/prometheus"
)
// MustRegister registers the prometheus Collectors when the metrics plugin is used.
func MustRegister(c *caddy.Controller, cs ...prometheus.Collector) {
m := dnsserver.GetConfig(c).Handler("prometheus")
if m == nil {
return
}
x, ok := m.(*Metrics)
if !ok {
return
}
for _, c := range cs {
x.MustRegister(c)
}
}
+4 -4
View File
@@ -4,14 +4,13 @@ import (
"net"
"runtime"
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/coremain"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics/vars"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/pkg/uniq"
"github.com/caddyserver/caddy"
)
var (
@@ -81,8 +80,9 @@ func parse(c *caddy.Controller) (*Metrics, error) {
}
i++
for _, z := range c.ServerBlockKeys {
met.AddZone(plugin.Host(z).Normalize())
zones := plugin.OriginsFromArgsOrServerBlock(nil /* args */, c.ServerBlockKeys)
for _, z := range zones {
met.AddZone(z)
}
args := c.RemainingArgs()
+36
View File
@@ -0,0 +1,36 @@
package vars
import (
"github.com/miekg/dns"
)
var monitorType = map[uint16]struct{}{
dns.TypeAAAA: {},
dns.TypeA: {},
dns.TypeCNAME: {},
dns.TypeDNSKEY: {},
dns.TypeDS: {},
dns.TypeMX: {},
dns.TypeNSEC3: {},
dns.TypeNSEC: {},
dns.TypeNS: {},
dns.TypePTR: {},
dns.TypeRRSIG: {},
dns.TypeSOA: {},
dns.TypeSRV: {},
dns.TypeTXT: {},
dns.TypeHTTPS: {},
// Meta Qtypes
dns.TypeIXFR: {},
dns.TypeAXFR: {},
dns.TypeANY: {},
}
// qTypeString returns the RR type based on monitorType. It returns the text representation
// of those types. RR types not in that list will have "other" returned.
func qTypeString(qtype uint16) string {
if _, known := monitorType[qtype]; known {
return dns.Type(qtype).String()
}
return "other"
}
+6 -36
View File
@@ -4,14 +4,12 @@ import (
"time"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Report reports the metrics data associated with request. This function is exported because it is also
// called from core/dnsserver to report requests hitting the server that should not be handled and are thus
// not sent down the plugin chain.
func Report(server string, req request.Request, zone, rcode string, size int, start time.Time) {
func Report(server string, req request.Request, zone, rcode, plugin string, size int, start time.Time) {
// Proto and Family.
net := req.Proto()
fam := "1"
@@ -19,45 +17,17 @@ func Report(server string, req request.Request, zone, rcode string, size int, st
fam = "2"
}
typ := req.QType()
if req.Do() {
RequestDo.WithLabelValues(server, zone).Inc()
}
if _, known := monitorType[typ]; known {
RequestCount.WithLabelValues(server, zone, net, fam, dns.Type(typ).String()).Inc()
RequestDuration.WithLabelValues(server, zone, dns.Type(typ).String()).Observe(time.Since(start).Seconds())
} else {
RequestCount.WithLabelValues(server, zone, net, fam, other).Inc()
RequestDuration.WithLabelValues(server, zone, other).Observe(time.Since(start).Seconds())
}
qType := qTypeString(req.QType())
RequestCount.WithLabelValues(server, zone, net, fam, qType).Inc()
RequestDuration.WithLabelValues(server, zone).Observe(time.Since(start).Seconds())
ResponseSize.WithLabelValues(server, zone, net).Observe(float64(size))
RequestSize.WithLabelValues(server, zone, net).Observe(float64(req.Len()))
ResponseRcode.WithLabelValues(server, zone, rcode).Inc()
ResponseRcode.WithLabelValues(server, zone, rcode, plugin).Inc()
}
var monitorType = map[uint16]struct{}{
dns.TypeAAAA: {},
dns.TypeA: {},
dns.TypeCNAME: {},
dns.TypeDNSKEY: {},
dns.TypeDS: {},
dns.TypeMX: {},
dns.TypeNSEC3: {},
dns.TypeNSEC: {},
dns.TypeNS: {},
dns.TypePTR: {},
dns.TypeRRSIG: {},
dns.TypeSOA: {},
dns.TypeSRV: {},
dns.TypeTXT: {},
// Meta Qtypes
dns.TypeIXFR: {},
dns.TypeAXFR: {},
dns.TypeANY: {},
}
const other = "other"
+13 -12
View File
@@ -4,41 +4,42 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// Request* and Response* are the prometheus counters and gauges we are using for exporting metrics.
var (
RequestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
RequestCount = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "requests_total",
Help: "Counter of DNS requests made per zone, protocol and family.",
}, []string{"server", "zone", "proto", "family", "type"})
RequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
RequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "request_duration_seconds",
Buckets: plugin.TimeBuckets,
Help: "Histogram of the time (in seconds) each request took.",
}, []string{"server", "zone", "type"})
Help: "Histogram of the time (in seconds) each request took per zone.",
}, []string{"server", "zone"})
RequestSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
RequestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "request_size_bytes",
Help: "Size of the EDNS0 UDP buffer in bytes (64K for TCP).",
Help: "Size of the EDNS0 UDP buffer in bytes (64K for TCP) per zone and protocol.",
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
}, []string{"server", "zone", "proto"})
RequestDo = prometheus.NewCounterVec(prometheus.CounterOpts{
RequestDo = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "do_requests_total",
Help: "Counter of DNS requests with DO bit set per zone.",
}, []string{"server", "zone"})
ResponseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
ResponseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "response_size_bytes",
@@ -46,20 +47,20 @@ var (
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
}, []string{"server", "zone", "proto"})
ResponseRcode = prometheus.NewCounterVec(prometheus.CounterOpts{
ResponseRcode = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "responses_total",
Help: "Counter of response status codes.",
}, []string{"server", "zone", "rcode"})
}, []string{"server", "zone", "rcode", "plugin"})
Panic = prometheus.NewCounter(prometheus.CounterOpts{
Panic = promauto.NewCounter(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Name: "panics_total",
Help: "A metrics that counts the number of panics.",
})
PluginEnabled = prometheus.NewGaugeVec(prometheus.GaugeOpts{
PluginEnabled = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: plugin.Namespace,
Name: "plugin_enabled",
Help: "A metric that indicates whether a plugin is enabled on per server and zone basis.",
+97 -42
View File
@@ -3,10 +3,14 @@ package plugin
import (
"fmt"
"net"
"runtime"
"strconv"
"strings"
"github.com/coredns/coredns/plugin/pkg/cidr"
"github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/pkg/parse"
"github.com/miekg/dns"
)
@@ -62,81 +66,132 @@ type (
// Normalize will return the host portion of host, stripping
// of any port or transport. The host will also be fully qualified and lowercased.
// An empty string is returned on failure
// Deprecated: use OriginsFromArgsOrServerBlock or NormalizeExact
func (h Host) Normalize() string {
// The error can be ignored here, because this function should only be called after the corefile has already been vetted.
host, _ := h.MustNormalize()
return host
var caller string
if _, file, line, ok := runtime.Caller(1); ok {
caller = fmt.Sprintf("(%v line %d) ", file, line)
}
log.Warning("An external plugin " + caller + "is using the deprecated function Normalize. " +
"This will be removed in a future versions of CoreDNS. The plugin should be updated to use " +
"OriginsFromArgsOrServerBlock or NormalizeExact instead.")
s := string(h)
_, s = parse.Transport(s)
// The error can be ignored here, because this function is called after the corefile has already been vetted.
hosts, _, err := SplitHostPort(s)
if err != nil {
return ""
}
return Name(hosts[0]).Normalize()
}
// MustNormalize will return the host portion of host, stripping
// of any port or transport. The host will also be fully qualified and lowercased.
// An error is returned on error
// Deprecated: use OriginsFromArgsOrServerBlock or NormalizeExact
func (h Host) MustNormalize() (string, error) {
var caller string
if _, file, line, ok := runtime.Caller(1); ok {
caller = fmt.Sprintf("(%v line %d) ", file, line)
}
log.Warning("An external plugin " + caller + "is using the deprecated function MustNormalize. " +
"This will be removed in a future versions of CoreDNS. The plugin should be updated to use " +
"OriginsFromArgsOrServerBlock or NormalizeExact instead.")
s := string(h)
_, s = parse.Transport(s)
// The error can be ignored here, because this function is called after the corefile has already been vetted.
host, _, _, err := SplitHostPort(s)
hosts, _, err := SplitHostPort(s)
if err != nil {
return "", err
}
return Name(host).Normalize(), nil
return Name(hosts[0]).Normalize(), nil
}
// SplitHostPort splits s up in a host and port portion, taking reverse address notation into account.
// String the string s should *not* be prefixed with any protocols, i.e. dns://. The returned ipnet is the
// *net.IPNet that is used when the zone is a reverse and a netmask is given.
func SplitHostPort(s string) (host, port string, ipnet *net.IPNet, err error) {
// NormalizeExact will return the host portion of host, stripping
// of any port or transport. The host will also be fully qualified and lowercased.
// An empty slice is returned on failure
func (h Host) NormalizeExact() []string {
// The error can be ignored here, because this function should only be called after the corefile has already been vetted.
s := string(h)
_, s = parse.Transport(s)
hosts, _, err := SplitHostPort(s)
if err != nil {
return nil
}
for i := range hosts {
hosts[i] = Name(hosts[i]).Normalize()
}
return hosts
}
// SplitHostPort splits s up in a host(s) and port portion, taking reverse address notation into account.
// String the string s should *not* be prefixed with any protocols, i.e. dns://. SplitHostPort can return
// multiple hosts when a reverse notation on a non-octet boundary is given.
func SplitHostPort(s string) (hosts []string, port string, err error) {
// If there is: :[0-9]+ on the end we assume this is the port. This works for (ascii) domain
// names and our reverse syntax, which always needs a /mask *before* the port.
// So from the back, find first colon, and then check if it's a number.
host = s
colon := strings.LastIndex(s, ":")
if colon == len(s)-1 {
return "", "", nil, fmt.Errorf("expecting data after last colon: %q", s)
return nil, "", fmt.Errorf("expecting data after last colon: %q", s)
}
if colon != -1 {
if p, err := strconv.Atoi(s[colon+1:]); err == nil {
port = strconv.Itoa(p)
host = s[:colon]
s = s[:colon]
}
}
// TODO(miek): this should take escaping into account.
if len(host) > 255 {
return "", "", nil, fmt.Errorf("specified zone is too long: %d > 255", len(host))
if len(s) > 255 {
return nil, "", fmt.Errorf("specified zone is too long: %d > 255", len(s))
}
_, d := dns.IsDomainName(host)
if !d {
return "", "", nil, fmt.Errorf("zone is not a valid domain name: %s", host)
if _, ok := dns.IsDomainName(s); !ok {
return nil, "", fmt.Errorf("zone is not a valid domain name: %s", s)
}
// Check if it parses as a reverse zone, if so we use that. Must be fully specified IP and mask.
ip, n, err := net.ParseCIDR(host)
ones, bits := 0, 0
if err == nil {
if rev, e := dns.ReverseAddr(ip.String()); e == nil {
ones, bits = n.Mask.Size()
// get the size, in bits, of each portion of hostname defined in the reverse address. (8 for IPv4, 4 for IPv6)
sizeDigit := 8
if len(n.IP) == net.IPv6len {
sizeDigit = 4
}
// Get the first lower octet boundary to see what encompassing zone we should be authoritative for.
mod := (bits - ones) % sizeDigit
nearest := (bits - ones) + mod
offset := 0
var end bool
for i := 0; i < nearest/sizeDigit; i++ {
offset, end = dns.NextLabel(rev, offset)
if end {
break
}
}
host = rev[offset:]
}
_, n, err := net.ParseCIDR(s)
if err != nil {
return []string{s}, port, nil
}
return host, port, n, nil
if s[0] == ':' || (s[0] == '0' && strings.Contains(s, ":")) {
return nil, "", fmt.Errorf("invalid CIDR %s", s)
}
// now check if multiple hosts must be returned.
nets := cidr.Split(n)
hosts = cidr.Reverse(nets)
return hosts, port, nil
}
// OriginsFromArgsOrServerBlock returns the normalized args if that slice
// is not empty, otherwise the serverblock slice is returned (in a newly copied slice).
func OriginsFromArgsOrServerBlock(args, serverblock []string) []string {
if len(args) == 0 {
s := make([]string, len(serverblock))
copy(s, serverblock)
for i := range s {
s[i] = Host(s[i]).NormalizeExact()[0] // expansion of these already happened in dnsserver/register.go
}
return s
}
s := []string{}
for i := range args {
sx := Host(args[i]).NormalizeExact()
if len(sx) == 0 {
continue // silently ignores errors.
}
s = append(s, sx...)
}
return s
}
+35 -3
View File
@@ -45,9 +45,10 @@ func New(size int) *Cache {
}
// Add adds a new element to the cache. If the element already exists it is overwritten.
func (c *Cache) Add(key uint64, el interface{}) {
// Returns true if an existing element was evicted to make room for this element.
func (c *Cache) Add(key uint64, el interface{}) bool {
shard := key & (shardSize - 1)
c.shards[shard].Add(key, el)
return c.shards[shard].Add(key, el)
}
// Get looks up element index under key.
@@ -71,22 +72,33 @@ func (c *Cache) Len() int {
return l
}
// Walk walks each shard in the cache.
func (c *Cache) Walk(f func(map[uint64]interface{}, uint64) bool) {
for _, s := range c.shards {
s.Walk(f)
}
}
// newShard returns a new shard with size.
func newShard(size int) *shard { return &shard{items: make(map[uint64]interface{}), size: size} }
// Add adds element indexed by key into the cache. Any existing element is overwritten
func (s *shard) Add(key uint64, el interface{}) {
// Returns true if an existing element was evicted to make room for this element.
func (s *shard) Add(key uint64, el interface{}) bool {
eviction := false
s.Lock()
if len(s.items) >= s.size {
if _, ok := s.items[key]; !ok {
for k := range s.items {
delete(s.items, k)
eviction = true
break
}
}
}
s.items[key] = el
s.Unlock()
return eviction
}
// Remove removes the element indexed by key from the cache.
@@ -122,4 +134,24 @@ func (s *shard) Len() int {
return l
}
// Walk walks the shard for each element the function f is executed while holding a write lock.
func (s *shard) Walk(f func(map[uint64]interface{}, uint64) bool) {
s.RLock()
items := make([]uint64, len(s.items))
i := 0
for k := range s.items {
items[i] = k
i++
}
s.RUnlock()
for _, k := range items {
s.Lock()
ok := f(s.items, k)
s.Unlock()
if !ok {
return
}
}
}
const shardSize = 256
+83
View File
@@ -0,0 +1,83 @@
// Package cidr contains functions that deal with classless reverse zones in the DNS.
package cidr
import (
"math"
"net"
"strings"
"github.com/apparentlymart/go-cidr/cidr"
"github.com/miekg/dns"
)
// Split returns a slice of non-overlapping subnets that in union equal the subnet n,
// and where each subnet falls on a reverse name segment boundary.
// for ipv4 this is any multiple of 8 bits (/8, /16, /24 or /32)
// for ipv6 this is any multiple of 4 bits
func Split(n *net.IPNet) []string {
boundary := 8
nstr := n.String()
if strings.Contains(nstr, ":") {
boundary = 4
}
ones, _ := n.Mask.Size()
if ones%boundary == 0 {
return []string{n.String()}
}
mask := int(math.Ceil(float64(ones)/float64(boundary))) * boundary
networks := nets(n, mask)
cidrs := make([]string, len(networks))
for i := range networks {
cidrs[i] = networks[i].String()
}
return cidrs
}
// nets return a slice of prefixes with the desired mask subnetted from original network.
func nets(network *net.IPNet, newPrefixLen int) []*net.IPNet {
prefixLen, _ := network.Mask.Size()
maxSubnets := int(math.Exp2(float64(newPrefixLen)) / math.Exp2(float64(prefixLen)))
nets := []*net.IPNet{{network.IP, net.CIDRMask(newPrefixLen, 8*len(network.IP))}}
for i := 1; i < maxSubnets; i++ {
next, exceeds := cidr.NextSubnet(nets[len(nets)-1], newPrefixLen)
nets = append(nets, next)
if exceeds {
break
}
}
return nets
}
// Reverse return the reverse zones that are authoritative for each net in ns.
func Reverse(nets []string) []string {
rev := make([]string, len(nets))
for i := range nets {
ip, n, _ := net.ParseCIDR(nets[i])
r, err1 := dns.ReverseAddr(ip.String())
if err1 != nil {
continue
}
ones, bits := n.Mask.Size()
// get the size, in bits, of each portion of hostname defined in the reverse address. (8 for IPv4, 4 for IPv6)
sizeDigit := 8
if len(n.IP) == net.IPv6len {
sizeDigit = 4
}
// Get the first lower octet boundary to see what encompassing zone we should be authoritative for.
mod := (bits - ones) % sizeDigit
nearest := (bits - ones) + mod
offset := 0
var end bool
for i := 0; i < nearest/sizeDigit; i++ {
offset, end = dns.NextLabel(r, offset)
if end {
break
}
}
rev[i] = r[offset:]
}
return rev
}

Some files were not shown because too many files have changed in this diff Show More