Compare commits

...

65 Commits

Author SHA1 Message Date
João Oliveirinha f81d35447e Release 2022.10.0 2022-10-03 09:55:15 +01:00
cthuang 49438f30f5 TUN-6813: Only proxy ICMP packets when warp-routing is enabled 2022-09-30 19:08:12 +01:00
cthuang eacc8c648d TUN-6812: Drop IP packets if ICMP proxy is not initialized 2022-09-30 14:10:32 +00:00
Sudarsan Reddy 5b30925773 TUN-6755: Remove unused publish functions
We no longer need the functions that publish deb and rpm to the old
pkg.cloudflare.com backed since we now send them to R2.
2022-09-30 10:28:28 +01:00
Devin Carr d7fb18be22 TUN-6810: Add component test for post-quantum 2022-09-29 09:22:43 -07:00
cthuang cbf8c71fab TUN-6716: Document limitation of Windows ICMP proxy 2022-09-29 14:51:53 +01:00
cthuang 870193c064 TUN-6811: Ping group range should be parsed as int32 2022-09-29 12:59:38 +01:00
cthuang fdddd86380 TUN-6715: Provide suggestion to add cloudflared to ping_group_range if it failed to open ICMP socket 2022-09-28 17:30:13 +01:00
Devin Carr b3e26420c0 TUN-6801: Add punycode alternatives for ingress rules 2022-09-26 17:59:45 +00:00
cthuang be0305ec58 TUN-6741: ICMP proxy tries to listen on specific IPv4 & IPv6 when possible
If it cannot determine the correct interface IP, it will fallback to all interfaces.
This commit also introduces the icmpv4-src and icmpv6-src flags
2022-09-26 11:37:08 +01:00
cthuang 3449ea35f2 TUN-6791: Calculate ICMPv6 checksum 2022-09-22 15:18:53 +00:00
Sudarsan Reddy 7f487c2651 TUN-6775: Add middleware.Handler verification to ProxyHTTP
ProxyHTTP now processes middleware Handler before executing the request.
A chain of handlers is now executed and appropriate response status
codes are sent.
2022-09-22 15:11:59 +01:00
Sudarsan Reddy 9bb7628fbc TUN-6772: Add a JWT Validator as an ingress verifier
This adds a new verifier interface that can be attached to ingress.Rule.
This would act as a middleware layer that gets executed at the start of
proxy.ProxyHTTP.

A jwt validator implementation for this verifier is also provided. The
validator downloads the public key from the access teams endpoint and
uses it to verify the JWT sent to cloudflared with the audtag (clientID)
information provided in the config.
2022-09-22 14:44:03 +01:00
Sudarsan Reddy eb36716ba4 TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware
We take advantage of the JWTValidator middleware and attach it to an
ingress rule based on Access configurations. We attach the Validator
directly to the ingress rules because we want to take advantage of
caching and token revert/handling that comes with go-oidc.
2022-09-22 14:44:03 +01:00
Sudarsan Reddy 5d6b0642db TUN-6772: Add a JWT Validator as an ingress verifier
This adds a new verifier interface that can be attached to ingress.Rule.
This would act as a middleware layer that gets executed at the start of
proxy.ProxyHTTP.

A jwt validator implementation for this verifier is also provided. The
validator downloads the public key from the access teams endpoint and
uses it to verify the JWT sent to cloudflared with the audtag (clientID)
information provided in the config.
2022-09-22 14:44:03 +01:00
Sudarsan Reddy 462d2f87df TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware
We take advantage of the JWTValidator middleware and attach it to an
ingress rule based on Access configurations. We attach the Validator
directly to the ingress rules because we want to take advantage of
caching and token revert/handling that comes with go-oidc.
2022-09-22 13:43:15 +00:00
Nuno Diegues 0aa21f302e TUN-6792: Fix brew core release by not auditing the formula 2022-09-22 11:58:17 +01:00
Sudarsan Reddy de07da02cd TUN-6772: Add a JWT Validator as an ingress verifier
This adds a new verifier interface that can be attached to ingress.Rule.
This would act as a middleware layer that gets executed at the start of
proxy.ProxyHTTP.

A jwt validator implementation for this verifier is also provided. The
validator downloads the public key from the access teams endpoint and
uses it to verify the JWT sent to cloudflared with the audtag (clientID)
information provided in the config.
2022-09-22 08:42:25 +00:00
Devin Carr e9a2c85671 Release 2022.9.1 2022-09-21 12:52:59 -07:00
Devin Carr b0f0741a9b TUN-6590: Use Windows Teamcity agent to build binary 2022-09-21 19:34:36 +00:00
Sudarsan Reddy db4564e5b9 TUN-6773: Add access based configuration to ingress.OriginRequestConfig
This PR adds some access related configurations to OriginRequestConfig.
This will eventually get validated to be part of Ingress.Rule.
2022-09-21 09:59:42 +00:00
cthuang 3d345d3748 TUN-6595: Enable datagramv2 and icmp proxy by default 2022-09-20 14:02:02 +00:00
cthuang b1995b4dd1 TUN-6777: Fix race condition in TestFunnelIdleTimeout 2022-09-20 13:17:38 +00:00
João Oliveirinha b457cca1e5 TUN-6780: Add support for certReload to also include support for client certificates 2022-09-20 08:18:59 +00:00
João Oliveirinha a0b6ba9b8d TUN-6779: cloudflared should also use the root CAs from system pool to validate edge certificate 2022-09-20 08:18:48 +00:00
cthuang de00396669 TUN-6778: Cleanup logs about ICMP 2022-09-19 15:46:34 +00:00
Devin Carr 013bdbd10c TUN-6718: Bump go and go-boring 1.18.6 2022-09-19 15:18:59 +00:00
cthuang b639b6627a TUN-6744: On posix platforms, assign unique echo ID per (src, dst, echo ID)
This also refactor FunnelTracker to provide a GetOrRegister method to prevent race condition
2022-09-19 14:39:47 +01:00
cthuang e454994e3e TUN-6767: Build ICMP proxy for Windows only when CGO is enabled 2022-09-16 10:14:05 +01:00
cthuang 8a53c1aa1d TUN-6592: Decrement TTL and return ICMP time exceed if it's 0 2022-09-15 17:53:26 +01:00
Devin Carr f5f3e6a453 TUN-6689: Utilize new RegisterUDPSession to begin tracing 2022-09-13 14:56:08 +00:00
cthuang 30c529e730 TUN-6743: Support ICMPv6 echo on Windows 2022-09-12 10:40:50 +01:00
cthuang bf3d70d1d2 TUN-6654: Support ICMPv6 on Linux and Darwin 2022-09-12 09:27:06 +01:00
cthuang a65f8bce7f TUN-6749: Fix icmp_generic build 2022-09-09 16:14:07 +01:00
cthuang 2ffff0687b TUN-6696: Refactor flow into funnel and close idle funnels
A funnel is an abstraction for 1 source to many destinations.
As part of this refactoring, shared logic between Darwin and Linux are moved into icmp_posix
2022-09-09 13:06:00 +01:00
Devin Carr e380333520 TUN-6688: Update RegisterUdpSession capnproto to include trace context 2022-09-08 21:50:58 +00:00
Bas Westerbaan 11cbff4ff7 RTG-1339 Support post-quantum hybrid key exchange
Func spec: https://wiki.cfops.it/x/ZcBKHw
2022-09-07 19:32:53 +00:00
Chung-Ting Huang 3e0ff3a771 TUN-6531: Implement ICMP proxy for Windows using IcmpSendEcho 2022-09-07 19:18:06 +00:00
Nuno Diegues 7a19798682 TUN-6740: Detect no UDP packets allowed and fallback from QUIC in that case 2022-09-07 16:32:15 +00:00
Nuno Diegues 4b75943d59 Release 2022.9.0 2022-09-07 07:58:04 +01:00
cthuang fc20a22685 TUN-6695: Implement ICMP proxy for linux 2022-09-05 14:49:42 +00:00
cthuang faa86ffeca TUN-6737: Fix datagramV2Type should be declared in its own block so it starts at 0 2022-09-05 15:09:53 +01:00
Devin Carr f7a14d9200 TUN-6728: Verify http status code ingress rule 2022-09-02 09:14:03 -07:00
Nuno Diegues 902e5beb4f TUN-6729: Fix flaky TestClosePreviousProxies
I can only reproduce the flakiness, which is the hello world still
responding when it should be shut down already, in Windows (both in
TeamCity as well as my local VM). Locally, it only happens when the
machine is under high load.

Anyway, it's valid that the proxies take some time to shut down since
they handle that via channels asynchronously with regards to the event
that updates the configuration.
Hence, nothing is wrong, as long as they eventually shut down, which the
test still verifies.
2022-09-01 21:32:59 +00:00
Nuno Diegues 7ca5f7569a TUN-6726: Fix maxDatagramPayloadSize for Windows QUIC datagrams 2022-09-01 21:32:59 +00:00
Nuno Diegues 4ac68711cd TUN-6725: Fix testProxySSEAllData
This test was failing on Windows. We did not catch it before because our
TeamCity Windows builds were ignoring failed unit tests: TUN-6727

 - the fix is implementing WriteString for mockSSERespWriter
 - reason is because cfio.Copy was calling that, and not Write method,
   thus not triggering the usage of the channel for the test to continue
 - mockSSERespWriter was providing a valid implementation of WriteString
   via ResponseRecorder, which it implements via the embedded mockHTTPRespWriter
 - it is not clear why this only happened on Windows
 - changed it to be a top-level test since it did not share any code
   with other sub-tests in the same top-level test
2022-09-01 21:32:59 +00:00
Devin Carr 075ac1acf1 Release 2022.8.4 2022-08-31 15:19:40 -07:00
Devin Carr cfef0e737f TUN-6720: Remove forcibly closing connection during reconnect signal
Previously allowing the reconnect signal forcibly close the connection
caused a race condition on which error was returned by the errgroup
in the tunnel connection. Allowing the signal to return and provide
a context cancel to the connection provides a safer shutdown of the
tunnel for this test-only scenario.
2022-08-31 21:50:02 +00:00
Devin Carr 8ec0f7746b Release 2022.8.3 2022-08-31 20:54:54 +00:00
cthuang 2b3707e2b9 TUN-6717: Update Github action to run with Go 1.19 2022-08-31 12:22:57 +01:00
cthuang 7e760f9fcc TUN-6586: Change ICMP proxy to only build for Darwin and use echo ID to track flows 2022-08-27 22:37:08 +01:00
cthuang efb99d90d7 TUN-6708: Fix replace flow logic 2022-08-26 17:52:06 +01:00
João Oliveirinha e131125558 TUN-6699: Add metric for packet too big dropped 2022-08-26 16:02:43 +00:00
Devin Carr af6bf5c4e5 TUN-6704: Honor protocol flag when edge discovery is unreachable 2022-08-26 15:31:19 +00:00
Sudarsan Reddy e3390fcb15 TUN-6705: Tunnel should retry connections forever
Protocolbackoff arrays now have Retryforever flag set to true to enable
cloudflared to keep trying to reconnect perpetually.
2022-08-26 08:27:15 +00:00
Devin Carr fc5749328d TUN-6691: Properly error check for net.ErrClosed
UDP session would check if the socket was closed before returning but the net.ErrClosed could be wrapped in another error.
2022-08-25 09:44:32 -07:00
cthuang 59f5b0df83 TUN-6530: Implement ICMPv4 proxy
This proxy uses unprivileged datagram-oriented endpoint and is shared by all quic connections
2022-08-24 17:33:03 +01:00
João Oliveirinha f6bd4aa039 TUN-6676: Add suport for trailers in http2 connections 2022-08-24 15:16:30 +00:00
cthuang d2bc15e224 TUN-6667: DatagramMuxerV2 provides a method to receive RawPacket 2022-08-24 14:56:08 +01:00
cthuang bad2e8e812 TUN-6666: Define packet package
This package defines IP and ICMP packet, decoders, encoder and flow
2022-08-24 11:36:57 +01:00
João Oliveirinha 20ed7557f9 TUN-6679: Allow client side of quic request to close body
In a previous commit, we fixed a bug where the client roundtrip code
could close the request body, which in fact would be the quic.Stream,
thus closing the write-side.
The way that was fixed, prevented the client roundtrip code from closing
also read-side (the body).

This fixes that, by allowing close to only close the read side, which
will guarantee that any subsquent will fail with an error or EOF it
occurred before the close.
2022-08-23 10:43:45 +01:00
Sudarsan Reddy 8e9e1d973e TUN-6657: Ask for Tunnel ID and Configuration on Bug Report 2022-08-16 17:07:54 +00:00
Devin Carr a97673e8b9 TUN-6575: Consume cf-trace-id from incoming http2 TCP requests 2022-08-16 15:30:44 +00:00
Sudarsan Reddy e123bbe1c5 Release 2022.8.2 2022-08-16 15:05:14 +01:00
Sudarsan Reddy 906eb2d840 TUN-6656: Docker for arm64 should not be deployed in an amd64 container 2022-08-16 13:29:40 +00:00
479 changed files with 73729 additions and 4357 deletions
+4
View File
@@ -15,6 +15,10 @@ Steps to reproduce the behavior:
2. Run '....'
3. See error
If it's an issue with Cloudflare Tunnel:
4. Tunnel ID :
5. cloudflared config:
**Expected behavior**
A clear and concise description of what you expected to happen.
+1 -1
View File
@@ -4,7 +4,7 @@ jobs:
check:
strategy:
matrix:
go-version: [1.17.x]
go-version: [1.19.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Vendored Executable
+17
View File
@@ -0,0 +1,17 @@
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
echo $VERSION
export TARGET_OS=windows
# This controls the directory the built artifacts go into
export ARTIFACT_DIR=built_artifacts/
mkdir -p $ARTIFACT_DIR
windowsArchs=("amd64" "386")
for arch in ${windowsArchs[@]}; do
export TARGET_ARCH=$arch
# Copy exe into final directory
cp ./artifacts/cloudflared-windows-$arch.exe $ARTIFACT_DIR/cloudflared-windows-$arch.exe
cp ./artifacts/cloudflared-windows-$arch.exe ./cloudflared.exe
make cloudflared-msi
# Copy msi into final directory
mv cloudflared-$VERSION-$arch.msi $ARTIFACT_DIR/cloudflared-windows-$arch.msi
done
+1 -1
View File
@@ -23,4 +23,4 @@ git config --global user.name "cloudflare-warp-bot"
git config --global user.email "warp-bot@cloudflare.com"
# bump formula pr
brew bump-formula-pr cloudflared --version="$VERSION" --no-browse
brew bump-formula-pr cloudflared --version="$VERSION" --no-browse --no-audit
+4
View File
@@ -1,3 +1,7 @@
## 2022.9.0
### New Features
- cloudflared now rejects ingress rules with invalid http status codes for http_status.
## 2022.8.1
### New Features
- cloudflared now remembers if it connected to a certain protocol successfully. If it did, it does not fall back to a lower
+1 -1
View File
@@ -14,7 +14,7 @@ COPY . .
RUN GOOS=linux GOARCH=arm64 make cloudflared
# use a distroless base image with glibc
FROM gcr.io/distroless/base-debian11:nonroot
FROM gcr.io/distroless/base-debian11:nonroot-arm64
# copy our compiled binary
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
+10 -19
View File
@@ -145,22 +145,6 @@ endif
test-ssh-server:
docker-compose -f ssh_server_tests/docker-compose.yml up
define publish_package
chmod 664 $(BINARY_NAME)*.$(1); \
for HOST in $(CF_PKG_HOSTS); do \
ssh-keyscan -t ecdsa $$HOST >> ~/.ssh/known_hosts; \
scp -p -4 $(BINARY_NAME)*.$(1) cfsync@$$HOST:/state/cf-pkg/staging/$(2)/$(TARGET_PUBLIC_REPO)/$(BINARY_NAME)/; \
done
endef
.PHONY: publish-deb
publish-deb: cloudflared-deb
$(call publish_package,deb,apt)
.PHONY: publish-rpm
publish-rpm: cloudflared-rpm
$(call publish_package,rpm,yum)
cloudflared.1: cloudflared_man_template
cat cloudflared_man_template | sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' > cloudflared.1
@@ -198,7 +182,7 @@ cloudflared-pkg: cloudflared cloudflared.1
$(call build_package,osxpkg)
.PHONY: cloudflared-msi
cloudflared-msi: cloudflared
cloudflared-msi:
wixl --define Version=$(VERSION) --define Path=$(EXECUTABLE_PATH) --output cloudflared-$(VERSION)-$(TARGET_ARCH).msi cloudflared.wxs
.PHONY: cloudflared-darwin-amd64.tgz
@@ -288,10 +272,17 @@ github-mac-upload:
python3 github_release.py --path artifacts/cloudflared-darwin-amd64.tgz --release-version $(VERSION) --name cloudflared-darwin-amd64.tgz
python3 github_release.py --path artifacts/cloudflared-amd64.pkg --release-version $(VERSION) --name cloudflared-amd64.pkg
.PHONY: github-windows-upload
github-windows-upload:
python3 github_release.py --path built_artifacts/cloudflared-windows-amd64.exe --release-version $(VERSION) --name cloudflared-windows-amd64.exe
python3 github_release.py --path built_artifacts/cloudflared-windows-amd64.msi --release-version $(VERSION) --name cloudflared-windows-amd64.msi
python3 github_release.py --path built_artifacts/cloudflared-windows-386.exe --release-version $(VERSION) --name cloudflared-windows-386.exe
python3 github_release.py --path built_artifacts/cloudflared-windows-386.msi --release-version $(VERSION) --name cloudflared-windows-386.msi
.PHONY: tunnelrpc-deps
tunnelrpc-deps:
which capnp # https://capnproto.org/install.html
which capnpc-go # go get zombiezen.com/go/capnproto2/capnpc-go
which capnpc-go # go install zombiezen.com/go/capnproto2/capnpc-go@latest
capnp compile -ogo tunnelrpc/tunnelrpc.capnp
.PHONY: quic-deps
@@ -302,7 +293,7 @@ quic-deps:
.PHONY: vet
vet:
go vet -mod=vendor ./...
go vet -v -mod=vendor ./...
.PHONY: goimports
goimports:
+71
View File
@@ -1,3 +1,74 @@
2022.10.0
- 2022-09-30 TUN-6755: Remove unused publish functions
- 2022-09-30 TUN-6813: Only proxy ICMP packets when warp-routing is enabled
- 2022-09-29 TUN-6811: Ping group range should be parsed as int32
- 2022-09-29 TUN-6812: Drop IP packets if ICMP proxy is not initialized
- 2022-09-28 TUN-6716: Document limitation of Windows ICMP proxy
- 2022-09-28 TUN-6810: Add component test for post-quantum
- 2022-09-27 TUN-6715: Provide suggestion to add cloudflared to ping_group_range if it failed to open ICMP socket
- 2022-09-22 TUN-6792: Fix brew core release by not auditing the formula
- 2022-09-22 TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware
- 2022-09-22 TUN-6775: Add middleware.Handler verification to ProxyHTTP
- 2022-09-22 TUN-6791: Calculate ICMPv6 checksum
- 2022-09-22 TUN-6801: Add punycode alternatives for ingress rules
- 2022-09-21 TUN-6772: Add a JWT Validator as an ingress verifier
- 2022-09-21 TUN-6772: Add a JWT Validator as an ingress verifier
- 2022-09-21 TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware
- 2022-09-21 TUN-6772: Add a JWT Validator as an ingress verifier
- 2022-09-20 TUN-6741: ICMP proxy tries to listen on specific IPv4 & IPv6 when possible
2022.9.1
- 2022-09-20 TUN-6777: Fix race condition in TestFunnelIdleTimeout
- 2022-09-20 TUN-6595: Enable datagramv2 and icmp proxy by default
- 2022-09-20 TUN-6773: Add access based configuration to ingress.OriginRequestConfig
- 2022-09-19 TUN-6778: Cleanup logs about ICMP
- 2022-09-19 TUN-6779: cloudflared should also use the root CAs from system pool to validate edge certificate
- 2022-09-19 TUN-6780: Add support for certReload to also include support for client certificates
- 2022-09-16 TUN-6767: Build ICMP proxy for Windows only when CGO is enabled
- 2022-09-15 TUN-6590: Use Windows Teamcity agent to build binary
- 2022-09-13 TUN-6592: Decrement TTL and return ICMP time exceed if it's 0
- 2022-09-09 TUN-6749: Fix icmp_generic build
- 2022-09-09 TUN-6744: On posix platforms, assign unique echo ID per (src, dst, echo ID)
- 2022-09-08 TUN-6743: Support ICMPv6 echo on Windows
- 2022-09-08 TUN-6689: Utilize new RegisterUDPSession to begin tracing
- 2022-09-07 TUN-6688: Update RegisterUdpSession capnproto to include trace context
- 2022-09-06 TUN-6740: Detect no UDP packets allowed and fallback from QUIC in that case
- 2022-09-06 TUN-6654: Support ICMPv6 on Linux and Darwin
- 2022-09-02 TUN-6696: Refactor flow into funnel and close idle funnels
- 2022-09-02 TUN-6718: Bump go and go-boring 1.18.6
- 2022-08-29 TUN-6531: Implement ICMP proxy for Windows using IcmpSendEcho
- 2022-08-24 RTG-1339 Support post-quantum hybrid key exchange
2022.9.0
- 2022-09-05 TUN-6737: Fix datagramV2Type should be declared in its own block so it starts at 0
- 2022-09-01 TUN-6725: Fix testProxySSEAllData
- 2022-09-01 TUN-6726: Fix maxDatagramPayloadSize for Windows QUIC datagrams
- 2022-09-01 TUN-6729: Fix flaky TestClosePreviousProxies
- 2022-09-01 TUN-6728: Verify http status code ingress rule
- 2022-08-25 TUN-6695: Implement ICMP proxy for linux
2022.8.4
- 2022-08-31 TUN-6717: Update Github action to run with Go 1.19
- 2022-08-31 TUN-6720: Remove forcibly closing connection during reconnect signal
- 2022-08-29 Release 2022.8.3
2022.8.3
- 2022-08-26 TUN-6708: Fix replace flow logic
- 2022-08-25 TUN-6705: Tunnel should retry connections forever
- 2022-08-25 TUN-6704: Honor protocol flag when edge discovery is unreachable
- 2022-08-25 TUN-6699: Add metric for packet too big dropped
- 2022-08-24 TUN-6691: Properly error check for net.ErrClosed
- 2022-08-22 TUN-6679: Allow client side of quic request to close body
- 2022-08-22 TUN-6586: Change ICMP proxy to only build for Darwin and use echo ID to track flows
- 2022-08-18 TUN-6530: Implement ICMPv4 proxy
- 2022-08-17 TUN-6666: Define packet package
- 2022-08-17 TUN-6667: DatagramMuxerV2 provides a method to receive RawPacket
- 2022-08-16 TUN-6657: Ask for Tunnel ID and Configuration on Bug Report
- 2022-08-16 TUN-6676: Add suport for trailers in http2 connections
- 2022-08-11 TUN-6575: Consume cf-trace-id from incoming http2 TCP requests
2022.8.2
- 2022-08-16 TUN-6656: Docker for arm64 should not be deployed in an amd64 container
2022.8.1
- 2022-08-15 TUN-6617: Updated CHANGES.md for protocol stickiness
- 2022-08-12 EDGEPLAT-3918: bump go and go-boring to 1.18.5
-9
View File
@@ -7,15 +7,6 @@ export CGO_ENABLED=0
# This controls the directory the built artifacts go into
export ARTIFACT_DIR=built_artifacts/
mkdir -p $ARTIFACT_DIR
windowsArchs=("amd64" "386")
export TARGET_OS=windows
for arch in ${windowsArchs[@]}; do
export TARGET_ARCH=$arch
make cloudflared-msi
mv ./cloudflared.exe $ARTIFACT_DIR/cloudflared-windows-$arch.exe
mv cloudflared-$VERSION-$arch.msi $ARTIFACT_DIR/cloudflared-windows-$arch.msi
done
linuxArchs=("386" "amd64" "arm" "armhf" "arm64")
export TARGET_OS=linux
+50 -47
View File
@@ -1,5 +1,5 @@
pinned_go: &pinned_go go=1.18.5-1
pinned_go_fips: &pinned_go_fips go-boring=1.18.5-2
pinned_go: &pinned_go go=1.18.6-1
pinned_go_fips: &pinned_go_fips go-boring=1.18.6-1
build_dir: &build_dir /cfsetup_build
default-flavor: bullseye
@@ -119,21 +119,9 @@ stretch: &stretch
- export GOOS=linux
- export GOARCH=arm64
- make cloudflared-deb
publish-deb:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- fakeroot
- rubygem-fpm
- openssh-client
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- make publish-deb
github-release-macos-amd64:
build_dir: *build_dir
builddeps:
builddeps: &build_pygithub
- *pinned_go
- build-essential
- python3-dev
@@ -145,6 +133,27 @@ stretch: &stretch
- pip3 install pygithub==1.55
post-cache:
- make github-mac-upload
github-release-windows:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
- wget
# libmsi and libgcab are libraries the wixl binary depends on.
- libmsi-dev
- libgcab-dev
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 pynacl==1.4.0
- pip3 install pygithub==1.55
post-cache:
- .teamcity/package-windows.sh
- make github-windows-upload
test:
build_dir: *build_dir
builddeps:
@@ -174,6 +183,26 @@ stretch: &stretch
- ./fmt-check.sh
- make test | gotest-to-teamcity
component-test:
build_dir: *build_dir
builddeps:
- *pinned_go
- python3.7
- python3-pip
- python3-setuptools
# procps installs the ps command which is needed in test_sysv_service because the init script
# uses ps pid to determine if the agent is running
- procps
pre-cache-copy-paths:
- component-tests/requirements.txt
pre-cache: &component_test_pre_cache
- sudo pip3 install --upgrade -r component-tests/requirements.txt
post-cache: &component_test_post_cache
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
- python3 component-tests/setup.py --type create
- pytest component-tests -o log_cli=true --log-cli-level=INFO
# The Named Tunnel is deleted and its route unprovisioned here.
- python3 component-tests/setup.py --type cleanup
component-test-fips:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
@@ -185,32 +214,21 @@ stretch: &stretch
- procps
pre-cache-copy-paths:
- component-tests/requirements.txt
pre-cache:
- sudo pip3 install --upgrade -r component-tests/requirements.txt
post-cache:
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
- python3 component-tests/setup.py --type create
- pytest component-tests -o log_cli=true --log-cli-level=INFO
# The Named Tunnel is deleted and its route unprovisioned here.
- python3 component-tests/setup.py --type cleanup
pre-cache: *component_test_pre_cache
post-cache: *component_test_post_cache
update-homebrew:
builddeps:
- openssh-client
- s3cmd
- jq
- build-essential
- procps
post-cache:
- .teamcity/update-homebrew.sh
- .teamcity/update-homebrew-core.sh
github-message-release:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
builddeps: *build_pygithub
pre-cache: *install_pygithub
post-cache:
- make github-message
@@ -221,13 +239,13 @@ stretch: &stretch
- build-essential
- python3
- genisoimage
- jetez
pre-cache:
- ln -s /usr/bin/genisoimage /usr/bin/mkisofs
post-cache:
- export CGO_ENABLED=0
- export GOOS=freebsd
- export GOARCH=amd64
- make cloudflared-junos
- make cloudflared
publish-junos:
build_dir: *build_dir
builddeps:
@@ -247,18 +265,3 @@ stretch: &stretch
buster: *stretch
bullseye: *stretch
bookworm: *stretch
centos-7:
publish-rpm:
build_dir: *build_dir
builddeps: &el7_builddeps
- https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
pre-cache:
- yum install -y fakeroot
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
- wget https://go.dev/dl/go1.18.5.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.18.5.linux-amd64.tar.gz
post-cache:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make publish-rpm
+1 -1
View File
@@ -375,7 +375,7 @@ func StartServer(
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, orchestrator, log)
}()
reconnectCh := make(chan supervisor.ReconnectSignal, 1)
reconnectCh := make(chan supervisor.ReconnectSignal, c.Int("ha-connections"))
if c.IsSet("stdin-control") {
log.Info().Msg("Enabling control through stdin")
go stdinControl(reconnectCh, log)
+183 -2
View File
@@ -4,6 +4,9 @@ import (
"crypto/tls"
"fmt"
"io/ioutil"
mathRand "math/rand"
"net"
"net/netip"
"os"
"path/filepath"
"strings"
@@ -19,6 +22,7 @@ import (
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
"github.com/cloudflare/cloudflared/packet"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
@@ -43,7 +47,7 @@ var (
LogFieldHostname = "hostname"
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders}
defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders, supervisor.FeatureDatagramV2}
configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq", "edge-ip-version"}
)
@@ -220,6 +224,19 @@ func prepareTunnelConfig(
)
transportProtocol := c.String("protocol")
needPQ := c.Bool("post-quantum")
if needPQ {
if FipsEnabled {
return nil, nil, fmt.Errorf("post-quantum not supported in FIPS mode")
}
// Error if the user tries to force a non-quic transport protocol
if transportProtocol != connection.AutoSelectFlag && transportProtocol != connection.QUIC.String() {
return nil, nil, fmt.Errorf("post-quantum is only supported with the quic transport")
}
transportProtocol = connection.QUIC.String()
}
protocolFetcher := edgediscovery.ProtocolPercentage
cfg := config.GetConfiguration()
@@ -230,6 +247,9 @@ func prepareTunnelConfig(
}
log.Info().Msgf("Generated Connector ID: %s", clientUUID)
features := append(c.StringSlice("features"), defaultFeatures...)
if needPQ {
features = append(features, supervisor.FeaturePostQuantum)
}
if c.IsSet(TunnelTokenFlag) {
if transportProtocol == connection.AutoSelectFlag {
protocolFetcher = func() (edgediscovery.ProtocolPercents, error) {
@@ -291,7 +311,7 @@ func prepareTunnelConfig(
}
warpRoutingEnabled := isWarpRoutingEnabled(cfg.WarpRouting, isNamedTunnel)
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, warpRoutingEnabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log)
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, warpRoutingEnabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log, c.Bool("post-quantum"))
if err != nil {
return nil, nil, err
}
@@ -330,6 +350,15 @@ func prepareTunnelConfig(
return nil, nil, err
}
var pqKexIdx int
if needPQ {
pqKexIdx = mathRand.Intn(len(supervisor.PQKexes))
log.Info().Msgf(
"Using experimental hybrid post-quantum key agreement %s",
supervisor.PQKexNames[supervisor.PQKexes[pqKexIdx]],
)
}
tunnelConfig := &supervisor.TunnelConfig{
GracePeriod: gracePeriod,
ReplaceExisting: c.Bool("force"),
@@ -355,6 +384,14 @@ func prepareTunnelConfig(
MuxerConfig: muxerConfig,
ProtocolSelector: protocolSelector,
EdgeTLSConfigs: edgeTLSConfigs,
NeedPQ: needPQ,
PQKexIdx: pqKexIdx,
}
packetConfig, err := newPacketConfig(c, log)
if err != nil {
log.Warn().Err(err).Msg("ICMP proxy feature is disabled")
} else {
tunnelConfig.PacketConfig = packetConfig
}
orchestratorConfig := &orchestration.Config{
Ingress: &ingressRules,
@@ -425,3 +462,147 @@ func parseConfigIPVersion(version string) (v allregions.ConfigIPVersion, err err
}
return
}
func newPacketConfig(c *cli.Context, logger *zerolog.Logger) (*packet.GlobalRouterConfig, error) {
ipv4Src, err := determineICMPv4Src(c.String("icmpv4-src"), logger)
if err != nil {
return nil, errors.Wrap(err, "failed to determine IPv4 source address for ICMP proxy")
}
logger.Info().Msgf("ICMP proxy will use %s as source for IPv4", ipv4Src)
ipv6Src, zone, err := determineICMPv6Src(c.String("icmpv6-src"), logger, ipv4Src)
if err != nil {
return nil, errors.Wrap(err, "failed to determine IPv6 source address for ICMP proxy")
}
if zone != "" {
logger.Info().Msgf("ICMP proxy will use %s in zone %s as source for IPv6", ipv6Src, zone)
} else {
logger.Info().Msgf("ICMP proxy will use %s as source for IPv6", ipv6Src)
}
icmpRouter, err := ingress.NewICMPRouter(ipv4Src, ipv6Src, zone, logger)
if err != nil {
return nil, err
}
return &packet.GlobalRouterConfig{
ICMPRouter: icmpRouter,
IPv4Src: ipv4Src,
IPv6Src: ipv6Src,
Zone: zone,
}, nil
}
func determineICMPv4Src(userDefinedSrc string, logger *zerolog.Logger) (netip.Addr, error) {
if userDefinedSrc != "" {
addr, err := netip.ParseAddr(userDefinedSrc)
if err != nil {
return netip.Addr{}, err
}
if addr.Is4() {
return addr, nil
}
return netip.Addr{}, fmt.Errorf("expect IPv4, but %s is IPv6", userDefinedSrc)
}
addr, err := findLocalAddr(net.ParseIP("192.168.0.1"), 53)
if err != nil {
addr = netip.IPv4Unspecified()
logger.Debug().Err(err).Msgf("Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo", addr)
}
return addr, nil
}
type interfaceIP struct {
name string
ip net.IP
}
func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src netip.Addr) (addr netip.Addr, zone string, err error) {
if userDefinedSrc != "" {
userDefinedIP, zone, _ := strings.Cut(userDefinedSrc, "%")
addr, err := netip.ParseAddr(userDefinedIP)
if err != nil {
return netip.Addr{}, "", err
}
if addr.Is6() {
return addr, zone, nil
}
return netip.Addr{}, "", fmt.Errorf("expect IPv6, but %s is IPv4", userDefinedSrc)
}
// Loop through all the interfaces, the preference is
// 1. The interface where ipv4Src is in
// 2. Interface with IPv6 address
// 3. Unspecified interface
interfaces, err := net.Interfaces()
if err != nil {
return netip.IPv6Unspecified(), "", nil
}
interfacesWithIPv6 := make([]interfaceIP, 0)
for _, interf := range interfaces {
interfaceAddrs, err := interf.Addrs()
if err != nil {
continue
}
foundIPv4SrcInterface := false
for _, interfaceAddr := range interfaceAddrs {
if ipnet, ok := interfaceAddr.(*net.IPNet); ok {
ip := ipnet.IP
if ip.Equal(ipv4Src.AsSlice()) {
foundIPv4SrcInterface = true
}
if ip.To4() == nil {
interfacesWithIPv6 = append(interfacesWithIPv6, interfaceIP{
name: interf.Name,
ip: ip,
})
}
}
}
// Found the interface of ipv4Src. Loop through the addresses to see if there is an IPv6
if foundIPv4SrcInterface {
for _, interfaceAddr := range interfaceAddrs {
if ipnet, ok := interfaceAddr.(*net.IPNet); ok {
ip := ipnet.IP
if ip.To4() == nil {
addr, err := netip.ParseAddr(ip.String())
if err == nil {
return addr, interf.Name, nil
}
}
}
}
}
}
for _, interf := range interfacesWithIPv6 {
addr, err := netip.ParseAddr(interf.ip.String())
if err == nil {
return addr, interf.name, nil
}
}
logger.Debug().Err(err).Msgf("Failed to determine the IPv6 for this machine. It will use %s to send/listen for ICMPv6 echo", netip.IPv6Unspecified())
return netip.IPv6Unspecified(), "", nil
}
// FindLocalAddr tries to dial UDP and returns the local address picked by the OS
func findLocalAddr(dst net.IP, port int) (netip.Addr, error) {
udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: dst,
Port: port,
})
if err != nil {
return netip.Addr{}, err
}
defer udpConn.Close()
localAddrPort, err := netip.ParseAddrPort(udpConn.LocalAddr().String())
if err != nil {
return netip.Addr{}, err
}
localAddr := localAddrPort.Addr()
return localAddr, nil
}
+3
View File
@@ -0,0 +1,3 @@
package tunnel
var FipsEnabled bool
+20
View File
@@ -140,6 +140,13 @@ var (
EnvVars: []string{"TUNNEL_TRANSPORT_PROTOCOL"},
Hidden: true,
})
postQuantumFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "post-quantum",
Usage: "When given creates an experimental post-quantum secure tunnel",
Aliases: []string{"pq"},
EnvVars: []string{"TUNNEL_POST_QUANTUM"},
Hidden: FipsEnabled,
})
sortInfoByFlag = &cli.StringFlag{
Name: "sort-by",
Value: "createdAt",
@@ -169,6 +176,16 @@ var (
Usage: "Base64 encoded secret to set for the tunnel. The decoded secret must be at least 32 bytes long. If not specified, a random 32-byte secret will be generated.",
EnvVars: []string{"TUNNEL_CREATE_SECRET"},
}
icmpv4SrcFlag = &cli.StringFlag{
Name: "icmpv4-src",
Usage: "Source address to send/receive ICMPv4 messages. If not provided cloudflared will dial a local address to determine the source IP or fallback to 0.0.0.0.",
EnvVars: []string{"TUNNEL_ICMPV4_SRC"},
}
icmpv6SrcFlag = &cli.StringFlag{
Name: "icmpv6-src",
Usage: "Source address and the interface name to send/receive ICMPv6 messages. If not provided cloudflared will dial a local address to determine the source IP or fallback to ::.",
EnvVars: []string{"TUNNEL_ICMPV6_SRC"},
}
)
func buildCreateCommand() *cli.Command {
@@ -602,9 +619,12 @@ func buildRunCommand() *cli.Command {
forceFlag,
credentialsFileFlag,
credentialsContentsFlag,
postQuantumFlag,
selectProtocolFlag,
featuresFlag,
tunnelTokenFlag,
icmpv4SrcFlag,
icmpv6SrcFlag,
}
flags = append(flags, configureProxyFlags(false)...)
return &cli.Command{
+1
View File
@@ -1,6 +1,7 @@
METRICS_PORT = 51000
MAX_RETRIES = 5
BACKOFF_SECS = 7
MAX_LOG_LINES = 50
PROXY_DNS_PORT = 9053
+15 -4
View File
@@ -2,6 +2,7 @@
import json
import os
from constants import MAX_LOG_LINES
from util import start_cloudflared, wait_tunnel_ready, send_requests
# Rolling logger rotate log files after 1 MB
@@ -11,14 +12,24 @@ expect_message = "Starting Hello"
def assert_log_to_terminal(cloudflared):
stderr = cloudflared.stderr.read(1500)
assert expect_message.encode() in stderr, f"{stderr} doesn't contain {expect_message}"
for _ in range(0, MAX_LOG_LINES):
line = cloudflared.stderr.readline()
if not line:
break
if expect_message.encode() in line:
return
raise Exception(f"terminal log doesn't contain {expect_message}")
def assert_log_in_file(file):
with open(file, "r") as f:
log = f.read(2000)
assert expect_message in log, f"{log} doesn't contain {expect_message}"
for _ in range(0, MAX_LOG_LINES):
line = f.readline()
if not line:
break
if expect_message in line:
return
raise Exception(f"log file doesn't contain {expect_message}")
def assert_json_log(file):
+17
View File
@@ -0,0 +1,17 @@
from util import LOGGER, nofips, start_cloudflared, wait_tunnel_ready
@nofips
class TestPostQuantum:
def _extra_config(self):
config = {
"protocol": "quic",
}
return config
def test_post_quantum(self, tmp_path, component_tests_config):
config = component_tests_config(self._extra_config())
LOGGER.debug(config)
with start_cloudflared(tmp_path, config, cfd_args=["run", "--post-quantum"], new_process=True):
wait_tunnel_ready(tunnel_url=config.get_url(),
require_min_connections=4)
+6 -11
View File
@@ -47,17 +47,12 @@ class TestReconnect:
cloudflared.stdin.flush()
def assert_reconnect(self, config, cloudflared, repeat):
wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=self.default_ha_conns)
wait_tunnel_ready(tunnel_url=config.get_url(),
require_min_connections=self.default_ha_conns)
for _ in range(repeat):
for i in range(self.default_ha_conns):
for _ in range(self.default_ha_conns):
self.send_reconnect(cloudflared, self.default_reconnect_secs)
expect_connections = self.default_ha_conns-i-1
if expect_connections > 0:
# Don't check if tunnel returns 200 here because there is a race condition between wait_tunnel_ready
# retrying to get 200 response and reconnecting
wait_tunnel_ready(require_min_connections=expect_connections)
else:
check_tunnel_not_connected()
check_tunnel_not_connected()
sleep(self.default_reconnect_secs * 2)
wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=self.default_ha_conns)
wait_tunnel_ready(tunnel_url=config.get_url(),
require_min_connections=self.default_ha_conns)
+11 -2
View File
@@ -15,10 +15,17 @@ from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
LOGGER = logging.getLogger(__name__)
def select_platform(plat):
return pytest.mark.skipif(
platform.system() != plat, reason=f"Only runs on {plat}")
def fips_enabled():
env_fips = os.getenv("COMPONENT_TESTS_FIPS")
return env_fips is not None and env_fips != "0"
nofips = pytest.mark.skipif(
fips_enabled(), reason=f"Only runs without FIPS (COMPONENT_TESTS_FIPS=0)")
def write_config(directory, config):
config_path = directory / "config.yml"
@@ -108,13 +115,15 @@ def _log_cloudflared_logs(cfd_logs):
LOGGER.warning(line)
@retry(stop_max_attempt_number=MAX_RETRIES * BACKOFF_SECS, wait_fixed=1000)
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
def check_tunnel_not_connected():
url = f'http://localhost:{METRICS_PORT}/ready'
try:
resp = requests.get(url, timeout=1)
resp = requests.get(url, timeout=BACKOFF_SECS)
assert resp.status_code == 503, f"Expect {url} returns 503, got {resp.status_code}"
assert resp.json()[
"readyConnections"] == 0, "Expected all connections to be terminated (pending reconnect)"
# cloudflared might already terminate
except requests.exceptions.ConnectionError as e:
LOGGER.warning(f"Failed to connect to {url}, error: {e}")
+13
View File
@@ -229,6 +229,19 @@ type OriginRequestConfig struct {
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules,omitempty"`
// Attempt to connect to origin with HTTP/2
Http2Origin *bool `yaml:"http2Origin" json:"http2Origin,omitempty"`
// Access holds all access related configs
Access *AccessConfig `yaml:"access" json:"access,omitempty"`
}
type AccessConfig struct {
// Required when set to true will fail every request that does not arrive through an access authenticated endpoint.
Required bool `yaml:"required" json:"required,omitempty"`
// TeamName is the organization team name to get the public key certificates for.
TeamName string `yaml:"teamName" json:"teamName"`
// AudTag is the AudTag to verify access JWT against.
AudTag []string `yaml:"audTag" json:"audTag"`
}
type IngressIPRule struct {
+22 -4
View File
@@ -24,14 +24,22 @@ const (
LogFieldConnIndex = "connIndex"
MaxGracePeriod = time.Minute * 3
MaxConcurrentStreams = math.MaxUint32
contentTypeHeader = "content-type"
sseContentType = "text/event-stream"
grpcContentType = "application/grpc"
)
var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
var (
switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
flushableContentTypes = []string{sseContentType, grpcContentType}
)
type Orchestrator interface {
UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
GetConfigJSON() ([]byte, error)
GetOriginProxy() (OriginProxy, error)
WarpRoutingEnabled() (enabled bool)
}
type NamedTunnelProperties struct {
@@ -174,6 +182,7 @@ func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) erro
Status: switchingProtocolText,
StatusCode: http.StatusSwitchingProtocols,
ContentLength: -1,
Header: http.Header{},
}
if secWebsocketKey := h.req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
@@ -189,6 +198,7 @@ func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) erro
type ResponseWriter interface {
WriteRespHeaders(status int, header http.Header) error
AddTrailer(trailerName, trailerValue string)
io.Writer
}
@@ -197,10 +207,18 @@ type ConnectedFuse interface {
IsConnected() bool
}
func IsServerSentEvent(headers http.Header) bool {
if contentType := headers.Get("content-type"); contentType != "" {
return strings.HasPrefix(strings.ToLower(contentType), "text/event-stream")
// Helper method to let the caller know what content-types should require a flush on every
// write to a ResponseWriter.
func shouldFlush(headers http.Header) bool {
if contentType := headers.Get(contentTypeHeader); contentType != "" {
contentType = strings.ToLower(contentType)
for _, c := range flushableContentTypes {
if strings.HasPrefix(contentType, c) {
return true
}
}
}
return false
}
+4 -39
View File
@@ -6,11 +6,9 @@ import (
"io"
"math/rand"
"net/http"
"testing"
"time"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
@@ -58,6 +56,10 @@ func (mcr *mockOrchestrator) GetOriginProxy() (OriginProxy, error) {
return mcr.originProxy, nil
}
func (mcr *mockOrchestrator) WarpRoutingEnabled() (enabled bool) {
return true
}
type mockOriginProxy struct{}
func (moc *mockOriginProxy) ProxyHTTP(
@@ -197,40 +199,3 @@ func (mcf mockConnectedFuse) Connected() {}
func (mcf mockConnectedFuse) IsConnected() bool {
return true
}
func TestIsEventStream(t *testing.T) {
tests := []struct {
headers http.Header
isEventStream bool
}{
{
headers: newHeader("Content-Type", "text/event-stream"),
isEventStream: true,
},
{
headers: newHeader("content-type", "text/event-stream"),
isEventStream: true,
},
{
headers: newHeader("Content-Type", "text/event-stream; charset=utf-8"),
isEventStream: true,
},
{
headers: newHeader("Content-Type", "application/json"),
isEventStream: false,
},
{
headers: http.Header{},
isEventStream: false,
},
}
for _, test := range tests {
assert.Equal(t, test.isEventStream, IsServerSentEvent(test.headers))
}
}
func newHeader(key, value string) http.Header {
header := http.Header{}
header.Add(key, value)
return header
}
+4
View File
@@ -27,6 +27,10 @@ func (e *EdgeQuicDialError) Error() string {
return "failed to dial to edge with quic: " + e.Cause.Error()
}
func (e *EdgeQuicDialError) Unwrap() error {
return e.Cause
}
// RegisterTunnel error from server
type ServerRegisterTunnelError struct {
Cause error
+4
View File
@@ -259,6 +259,10 @@ type h2muxRespWriter struct {
*h2mux.MuxedStream
}
func (rp *h2muxRespWriter) AddTrailer(trailerName, trailerValue string) {
// do nothing. we don't support trailers over h2mux
}
func (rp *h2muxRespWriter) WriteRespHeaders(status int, header http.Header) error {
headers := H1ResponseToH2ResponseHeaders(status, header)
headers = append(headers, h2mux.Header{Name: ResponseMetaHeader, Value: responseMetaHeaderOrigin})
+25 -11
View File
@@ -149,9 +149,10 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rws := NewHTTPResponseReadWriterAcker(respWriter, r)
if err := originProxy.ProxyTCP(r.Context(), rws, &TCPRequest{
Dest: host,
CFRay: FindCfRayHeader(r),
LBProbe: IsLBProbeRequest(r),
Dest: host,
CFRay: FindCfRayHeader(r),
LBProbe: IsLBProbeRequest(r),
CfTraceID: r.Header.Get(tracing.TracerContextName),
}); err != nil {
respWriter.WriteErrorResponse()
}
@@ -190,11 +191,12 @@ func (c *HTTP2Connection) close() {
}
type http2RespWriter struct {
r io.Reader
w http.ResponseWriter
flusher http.Flusher
shouldFlush bool
log *zerolog.Logger
r io.Reader
w http.ResponseWriter
flusher http.Flusher
shouldFlush bool
statusWritten bool
log *zerolog.Logger
}
func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type, log *zerolog.Logger) (*http2RespWriter, error) {
@@ -218,11 +220,20 @@ func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type, l
}, nil
}
func (rp *http2RespWriter) AddTrailer(trailerName, trailerValue string) {
if !rp.statusWritten {
rp.log.Warn().Msg("Tried to add Trailer to response before status written. Ignoring...")
return
}
rp.w.Header().Add(http2.TrailerPrefix+trailerName, trailerValue)
}
func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) error {
dest := rp.w.Header()
userHeaders := make(http.Header, len(header))
for name, values := range header {
// Since these are http2 headers, they're required to be lowercase
// lowercase headers for simplicity check
h2name := strings.ToLower(name)
if h2name == "content-length" {
@@ -233,7 +244,7 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
if h2name == tracing.IntCloudflaredTracingHeader {
// Add cf-int-cloudflared-tracing header outside of serialized userHeaders
rp.w.Header()[tracing.CanonicalCloudflaredTracingHeader] = values
dest[tracing.CanonicalCloudflaredTracingHeader] = values
continue
}
@@ -246,18 +257,21 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
// Perform user header serialization and set them in the single header
dest.Set(CanonicalResponseUserHeaders, SerializeHeaders(userHeaders))
rp.setResponseMetaHeader(responseMetaHeaderOrigin)
// HTTP2 removes support for 101 Switching Protocols https://tools.ietf.org/html/rfc7540#section-8.1.1
if status == http.StatusSwitchingProtocols {
status = http.StatusOK
}
rp.w.WriteHeader(status)
if IsServerSentEvent(header) {
if shouldFlush(header) {
rp.shouldFlush = true
}
if rp.shouldFlush {
rp.flusher.Flush()
}
rp.statusWritten = true
return nil
}
+23 -6
View File
@@ -1,6 +1,7 @@
package connection
import (
"errors"
"fmt"
"hash/fnv"
"sync"
@@ -130,6 +131,7 @@ type autoProtocolSelector struct {
refreshAfter time.Time
ttl time.Duration
log *zerolog.Logger
needPQ bool
}
func newAutoProtocolSelector(
@@ -139,6 +141,7 @@ func newAutoProtocolSelector(
fetchFunc PercentageFetcher,
ttl time.Duration,
log *zerolog.Logger,
needPQ bool,
) *autoProtocolSelector {
return &autoProtocolSelector{
current: current,
@@ -148,6 +151,7 @@ func newAutoProtocolSelector(
refreshAfter: time.Now().Add(ttl),
ttl: ttl,
log: log,
needPQ: needPQ,
}
}
@@ -187,6 +191,9 @@ func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThr
func (s *autoProtocolSelector) Fallback() (Protocol, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
if s.needPQ {
return 0, false
}
return s.current.fallback()
}
@@ -199,9 +206,14 @@ func NewProtocolSelector(
fetchFunc PercentageFetcher,
ttl time.Duration,
log *zerolog.Logger,
needPQ bool,
) (ProtocolSelector, error) {
// Classic tunnel is only supported with h2mux
if namedTunnel == nil {
if needPQ {
return nil, errors.New("Classic tunnel does not support post-quantum")
}
return &staticProtocolSelector{
current: H2mux,
}, nil
@@ -209,8 +221,11 @@ func NewProtocolSelector(
threshold := switchThreshold(namedTunnel.Credentials.AccountTag)
fetchedProtocol, err := getProtocol([]Protocol{QUIC, HTTP2}, fetchFunc, threshold)
if err != nil {
log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can set `--protocol h2mux` in your cloudflared command.")
if err != nil && protocolFlag == "auto" {
log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can attempt `--protocol quic` instead.")
if needPQ {
return nil, errors.New("http2 does not support post-quantum")
}
return &staticProtocolSelector{
current: HTTP2,
}, nil
@@ -221,10 +236,10 @@ func NewProtocolSelector(
protocolFlag = HTTP2.String()
fetchedProtocol = HTTP2Warp
}
return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol)
return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ)
}
return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol)
return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ)
}
func selectNamedTunnelProtocols(
@@ -234,6 +249,7 @@ func selectNamedTunnelProtocols(
log *zerolog.Logger,
threshold int32,
protocol Protocol,
needPQ bool,
) (ProtocolSelector, error) {
// If the user picks a protocol, then we stick to it no matter what.
switch protocolFlag {
@@ -248,7 +264,7 @@ func selectNamedTunnelProtocols(
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
// fallback on failures.
if protocolFlag == AutoSelectFlag {
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log), nil
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log, needPQ), nil
}
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
@@ -261,6 +277,7 @@ func selectWarpRoutingProtocols(
log *zerolog.Logger,
threshold int32,
protocol Protocol,
needPQ bool,
) (ProtocolSelector, error) {
// If the user picks a protocol, then we stick to it no matter what.
switch protocolFlag {
@@ -273,7 +290,7 @@ func selectWarpRoutingProtocols(
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
// fallback on failures.
if protocolFlag == AutoSelectFlag {
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log), nil
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log, needPQ), nil
}
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
+4 -4
View File
@@ -219,7 +219,7 @@ func TestNewProtocolSelector(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log)
selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log, false)
if test.wantErr {
assert.Error(t, err, fmt.Sprintf("test %s failed", test.name))
} else {
@@ -237,7 +237,7 @@ func TestNewProtocolSelector(t *testing.T) {
func TestAutoProtocolSelectorRefresh(t *testing.T) {
fetcher := dynamicMockFetcher{}
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log, false)
assert.NoError(t, err)
assert.Equal(t, H2mux, selector.Current())
@@ -267,7 +267,7 @@ func TestAutoProtocolSelectorRefresh(t *testing.T) {
func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
fetcher := dynamicMockFetcher{}
// Since the user chooses http2 on purpose, we always stick to it.
selector, err := NewProtocolSelector("http2", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
selector, err := NewProtocolSelector("http2", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log, false)
assert.NoError(t, err)
assert.Equal(t, HTTP2, selector.Current())
@@ -297,7 +297,7 @@ func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
func TestProtocolSelectorRefreshTTL(t *testing.T) {
fetcher := dynamicMockFetcher{}
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log)
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log, false)
assert.NoError(t, err)
assert.Equal(t, QUIC, selector.Current())
+90 -13
View File
@@ -7,18 +7,23 @@ import (
"io"
"net"
"net/http"
"net/netip"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/datagramsession"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/packet"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
@@ -45,7 +50,8 @@ type QUICConnection struct {
// sessionManager tracks active sessions. It receives datagrams from quic connection via datagramMuxer
sessionManager datagramsession.Manager
// datagramMuxer mux/demux datagrams from quic connection
datagramMuxer *quicpogs.DatagramMuxer
datagramMuxer *quicpogs.DatagramMuxerV2
packetRouter *packet.Router
controlStreamHandler ControlStreamHandler
connOptions *tunnelpogs.ConnectionOptions
}
@@ -59,15 +65,17 @@ func NewQUICConnection(
connOptions *tunnelpogs.ConnectionOptions,
controlStreamHandler ControlStreamHandler,
logger *zerolog.Logger,
packetRouterConfig *packet.GlobalRouterConfig,
) (*QUICConnection, error) {
session, err := quic.DialAddr(edgeAddr.String(), tlsConfig, quicConfig)
if err != nil {
return nil, &EdgeQuicDialError{Cause: err}
}
demuxChan := make(chan *quicpogs.SessionDatagram, demuxChanCapacity)
datagramMuxer := quicpogs.NewDatagramMuxer(session, logger, demuxChan)
sessionManager := datagramsession.NewManager(logger, datagramMuxer.MuxSession, demuxChan)
sessionDemuxChan := make(chan *packet.Session, demuxChanCapacity)
datagramMuxer := quicpogs.NewDatagramMuxerV2(session, logger, sessionDemuxChan)
sessionManager := datagramsession.NewManager(logger, datagramMuxer.SendToSession, sessionDemuxChan)
packetRouter := packet.NewRouter(packetRouterConfig, datagramMuxer, &returnPipe{muxer: datagramMuxer}, logger, orchestrator.WarpRoutingEnabled)
return &QUICConnection{
session: session,
@@ -75,6 +83,7 @@ func NewQUICConnection(
logger: logger,
sessionManager: sessionManager,
datagramMuxer: datagramMuxer,
packetRouter: packetRouter,
controlStreamHandler: controlStreamHandler,
connOptions: connOptions,
}, nil
@@ -110,11 +119,14 @@ func (q *QUICConnection) Serve(ctx context.Context) error {
defer cancel()
return q.sessionManager.Serve(ctx)
})
errGroup.Go(func() error {
defer cancel()
return q.datagramMuxer.ServeReceive(ctx)
})
errGroup.Go(func() error {
defer cancel()
return q.packetRouter.Serve(ctx)
})
return errGroup.Wait()
}
@@ -156,9 +168,10 @@ func (q *QUICConnection) runStream(quicStream quic.Stream) {
defer stream.Close()
// we are going to fuse readers/writers from stream <- cloudflared -> origin, and we want to guarantee that
// code executed in the code path of handleStream don't trigger an earlier close to the downstream stream.
// So, we wrap the stream with a no-op closer and only this method can actually close the stream.
noCloseStream := &nopCloserReadWriter{stream}
// code executed in the code path of handleStream don't trigger an earlier close to the downstream write stream.
// So, we wrap the stream with a no-op write closer and only this method can actually close write side of the stream.
// A call to close will simulate a close to the read-side, which will fail subsequent reads.
noCloseStream := &nopCloserReadWriter{ReadWriteCloser: stream}
if err := q.handleStream(ctx, noCloseStream); err != nil {
q.logger.Err(err).Msg("Failed to handle QUIC stream")
}
@@ -233,24 +246,42 @@ func (q *QUICConnection) handleRPCStream(rpcStream *quicpogs.RPCServerStream) er
}
// RegisterUdpSession is the RPC method invoked by edge to register and run a session
func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) error {
func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
traceCtx := tracing.NewTracedContext(ctx, traceContext, q.logger)
ctx, registerSpan := traceCtx.Tracer().Start(traceCtx, "register-session", trace.WithAttributes(
attribute.String("session-id", sessionID.String()),
attribute.String("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)),
))
// Each session is a series of datagram from an eyeball to a dstIP:dstPort.
// (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket.
originProxy, err := ingress.DialUDP(dstIP, dstPort)
if err != nil {
q.logger.Err(err).Msgf("Failed to create udp proxy to %s:%d", dstIP, dstPort)
return err
tracing.EndWithErrorStatus(registerSpan, err)
return nil, err
}
registerSpan.SetAttributes(
attribute.Bool("socket-bind-success", true),
attribute.String("src", originProxy.LocalAddr().String()),
)
session, err := q.sessionManager.RegisterSession(ctx, sessionID, originProxy)
if err != nil {
q.logger.Err(err).Str("sessionID", sessionID.String()).Msgf("Failed to register udp session")
return err
tracing.EndWithErrorStatus(registerSpan, err)
return nil, err
}
go q.serveUDPSession(session, closeAfterIdleHint)
q.logger.Debug().Str("sessionID", sessionID.String()).Str("src", originProxy.LocalAddr().String()).Str("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)).Msgf("Registered session")
return nil
tracing.End(registerSpan)
resp := tunnelpogs.RegisterUdpSessionResponse{
Spans: traceCtx.GetProtoSpans(),
}
return &resp, nil
}
func (q *QUICConnection) serveUDPSession(session *datagramsession.Session, closeAfterIdleHint time.Duration) {
@@ -326,6 +357,10 @@ func newHTTPResponseAdapter(s *quicpogs.RequestServerStream) httpResponseAdapter
return httpResponseAdapter{s}
}
func (hrw httpResponseAdapter) AddTrailer(trailerName, trailerValue string) {
// we do not support trailers over QUIC
}
func (hrw httpResponseAdapter) WriteRespHeaders(status int, header http.Header) error {
metadata := make([]quicpogs.Metadata, 0)
metadata = append(metadata, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(status)})
@@ -408,10 +443,52 @@ func isTransferEncodingChunked(req *http.Request) bool {
return strings.Contains(strings.ToLower(transferEncodingVal), "chunked")
}
// A helper struct that guarantees a call to close only affects read side, but not write side.
type nopCloserReadWriter struct {
io.ReadWriteCloser
// for use by Read only
// we don't need a memory barrier here because there is an implicit assumption that
// Read calls can't happen concurrently by different go-routines.
sawEOF bool
// should be updated and read using atomic primitives.
// value is read in Read method and written in Close method, which could be done by different
// go-routines.
closed uint32
}
func (n *nopCloserReadWriter) Close() error {
func (np *nopCloserReadWriter) Read(p []byte) (n int, err error) {
if np.sawEOF {
return 0, io.EOF
}
if atomic.LoadUint32(&np.closed) > 0 {
return 0, fmt.Errorf("closed by handler")
}
n, err = np.ReadWriteCloser.Read(p)
if err == io.EOF {
np.sawEOF = true
}
return
}
func (np *nopCloserReadWriter) Close() error {
atomic.StoreUint32(&np.closed, 1)
return nil
}
// returnPipe wraps DatagramMuxerV2 to satisfy the packet.FunnelUniPipe interface
type returnPipe struct {
muxer *quicpogs.DatagramMuxerV2
}
func (rp *returnPipe) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
return rp.muxer.SendPacket(pk)
}
func (rp *returnPipe) Close() error {
return nil
}
+61 -4
View File
@@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"os"
"strings"
"sync"
"testing"
"time"
@@ -25,6 +26,7 @@ import (
"github.com/cloudflare/cloudflared/datagramsession"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/tracing"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -527,6 +529,44 @@ func TestServeUDPSession(t *testing.T) {
cancel()
}
func TestNopCloserReadWriterCloseBeforeEOF(t *testing.T) {
readerWriter := nopCloserReadWriter{ReadWriteCloser: &mockReaderNoopWriter{Reader: strings.NewReader("123456789")}}
buffer := make([]byte, 5)
n, err := readerWriter.Read(buffer)
require.NoError(t, err)
require.Equal(t, n, 5)
// close
require.NoError(t, readerWriter.Close())
// read should get error
n, err = readerWriter.Read(buffer)
require.Equal(t, n, 0)
require.Equal(t, err, fmt.Errorf("closed by handler"))
}
func TestNopCloserReadWriterCloseAfterEOF(t *testing.T) {
readerWriter := nopCloserReadWriter{ReadWriteCloser: &mockReaderNoopWriter{Reader: strings.NewReader("123456789")}}
buffer := make([]byte, 20)
n, err := readerWriter.Read(buffer)
require.NoError(t, err)
require.Equal(t, n, 9)
// force another read to read eof
_, err = readerWriter.Read(buffer)
require.Equal(t, err, io.EOF)
// close
require.NoError(t, readerWriter.Close())
// read should get EOF still
n, err = readerWriter.Read(buffer)
require.Equal(t, n, 0)
require.Equal(t, err, io.EOF)
}
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
var (
payload = []byte(t.Name())
@@ -543,8 +583,12 @@ func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.
close(sessionDone)
}()
// Send a message to the quic session on edge side, it should be deumx to this datagram session
muxedPayload := append(payload, sessionID[:]...)
// Send a message to the quic session on edge side, it should be deumx to this datagram v2 session
muxedPayload, err := quicpogs.SuffixSessionID(sessionID, payload)
require.NoError(t, err)
muxedPayload, err = quicpogs.SuffixType(muxedPayload, quicpogs.DatagramTypeUDP)
require.NoError(t, err)
err = edgeQUICSession.SendMessage(muxedPayload)
require.NoError(t, err)
@@ -613,8 +657,8 @@ type mockSessionRPCServer struct {
calledUnregisterChan chan struct{}
}
func (s mockSessionRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration) error {
return fmt.Errorf("mockSessionRPCServer doesn't implement RegisterUdpSession")
func (s mockSessionRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
return nil, fmt.Errorf("mockSessionRPCServer doesn't implement RegisterUdpSession")
}
func (s mockSessionRPCServer) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, reason string) error {
@@ -643,7 +687,20 @@ func testQUICConnection(udpListenerAddr net.Addr, t *testing.T) *QUICConnection
&tunnelpogs.ConnectionOptions{},
fakeControlStream{},
&log,
nil,
)
require.NoError(t, err)
return qc
}
type mockReaderNoopWriter struct {
io.Reader
}
func (m *mockReaderNoopWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (m *mockReaderNoopWriter) Close() error {
return nil
}
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"github.com/google/uuid"
"github.com/rs/zerolog"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/packet"
)
const (
@@ -37,7 +37,7 @@ type manager struct {
registrationChan chan *registerSessionEvent
unregistrationChan chan *unregisterSessionEvent
sendFunc transportSender
receiveChan <-chan *quicpogs.SessionDatagram
receiveChan <-chan *packet.Session
closedChan <-chan struct{}
sessions map[uuid.UUID]*Session
log *zerolog.Logger
@@ -45,7 +45,7 @@ type manager struct {
timeout time.Duration
}
func NewManager(log *zerolog.Logger, sendF transportSender, receiveChan <-chan *quicpogs.SessionDatagram) *manager {
func NewManager(log *zerolog.Logger, sendF transportSender, receiveChan <-chan *packet.Session) *manager {
return &manager{
registrationChan: make(chan *registerSessionEvent),
unregistrationChan: make(chan *unregisterSessionEvent),
@@ -163,7 +163,7 @@ func (m *manager) unregisterSession(unregistration *unregisterSessionEvent) {
}
}
func (m *manager) sendToSession(datagram *quicpogs.SessionDatagram) {
func (m *manager) sendToSession(datagram *packet.Session) {
session, ok := m.sessions[datagram.ID]
if !ok {
m.log.Error().Str("sessionID", datagram.ID.String()).Msg("session not found")
+7 -7
View File
@@ -15,7 +15,7 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/packet"
)
var (
@@ -29,7 +29,7 @@ func TestManagerServe(t *testing.T) {
remoteUnregisterMsg = "eyeball closed connection"
)
requestChan := make(chan *quicpogs.SessionDatagram)
requestChan := make(chan *packet.Session)
transport := mockQUICTransport{
sessions: make(map[uuid.UUID]chan []byte),
}
@@ -241,9 +241,9 @@ type mockQUICTransport struct {
sessions map[uuid.UUID]chan []byte
}
func (me *mockQUICTransport) MuxSession(id uuid.UUID, payload []byte) error {
session := me.sessions[id]
session <- payload
func (me *mockQUICTransport) MuxSession(session *packet.Session) error {
s := me.sessions[session.ID]
s <- session.Payload
return nil
}
@@ -255,9 +255,9 @@ type mockEyeballSession struct {
respReceiver <-chan []byte
}
func (me *mockEyeballSession) serve(ctx context.Context, requestChan chan *quicpogs.SessionDatagram) error {
func (me *mockEyeballSession) serve(ctx context.Context, requestChan chan *packet.Session) error {
for i := 0; i < me.expectedMsgCount; i++ {
requestChan <- &quicpogs.SessionDatagram{
requestChan <- &packet.Session{
ID: me.id,
Payload: me.expectedMsg,
}
+12 -5
View File
@@ -2,6 +2,7 @@ package datagramsession
import (
"context"
"errors"
"fmt"
"io"
"net"
@@ -9,6 +10,8 @@ import (
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/packet"
)
const (
@@ -19,7 +22,7 @@ func SessionIdleErr(timeout time.Duration) error {
return fmt.Errorf("session idle for %v", timeout)
}
type transportSender func(sessionID uuid.UUID, payload []byte) error
type transportSender func(session *packet.Session) error
// Session is a bidirectional pipe of datagrams between transport and dstConn
// Destination can be a connection with origin or with eyeball
@@ -47,10 +50,10 @@ func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) (clos
readBuffer := make([]byte, maxPacketSize)
for {
if closeSession, err := s.dstToTransport(readBuffer); err != nil {
if err != net.ErrClosed {
s.log.Error().Err(err).Msg("Failed to send session payload from destination to transport")
if errors.Is(err, net.ErrClosed) {
s.log.Debug().Msg("Destination connection closed")
} else {
s.log.Debug().Msg("Session cannot read from destination because the connection is closed")
s.log.Error().Err(err).Msg("Failed to send session payload from destination to transport")
}
if closeSession {
s.closeChan <- err
@@ -101,7 +104,11 @@ func (s *Session) dstToTransport(buffer []byte) (closeSession bool, err error) {
s.markActive()
// https://pkg.go.dev/io#Reader suggests caller should always process n > 0 bytes
if n > 0 || err == nil {
if sendErr := s.sendFunc(s.ID, buffer[:n]); sendErr != nil {
session := packet.Session{
ID: s.ID,
Payload: buffer[:n],
}
if sendErr := s.sendFunc(&session); sendErr != nil {
return false, sendErr
}
}
+9 -9
View File
@@ -15,7 +15,7 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/packet"
)
// TestCloseSession makes sure a session will stop after context is done
@@ -118,7 +118,7 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
cfdConn, originConn := net.Pipe()
payload := testPayload(sessionID)
respChan := make(chan *quicpogs.SessionDatagram)
respChan := make(chan *packet.Session)
sender := newMockTransportSender(sessionID, payload)
mg := NewManager(&nopLogger, sender.muxSession, respChan)
session := mg.newSession(sessionID, cfdConn)
@@ -243,12 +243,12 @@ func newMockTransportSender(expectedSessionID uuid.UUID, expectedPayload []byte)
}
}
func (mts *mockTransportSender) muxSession(sessionID uuid.UUID, payload []byte) error {
if sessionID != mts.expectedSessionID {
return fmt.Errorf("Expect session %s, got %s", mts.expectedSessionID, sessionID)
func (mts *mockTransportSender) muxSession(session *packet.Session) error {
if session.ID != mts.expectedSessionID {
return fmt.Errorf("Expect session %s, got %s", mts.expectedSessionID, session.ID)
}
if !bytes.Equal(payload, mts.expectedPayload) {
return fmt.Errorf("Expect %v, read %v", mts.expectedPayload, payload)
if !bytes.Equal(session.Payload, mts.expectedPayload) {
return fmt.Errorf("Expect %v, read %v", mts.expectedPayload, session.Payload)
}
return nil
}
@@ -258,7 +258,7 @@ type sendOnceTransportSender struct {
sentChan chan struct{}
}
func (sots *sendOnceTransportSender) muxSession(sessionID uuid.UUID, payload []byte) error {
func (sots *sendOnceTransportSender) muxSession(session *packet.Session) error {
defer close(sots.sentChan)
return sots.baseSender.muxSession(sessionID, payload)
return sots.baseSender.muxSession(session)
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.18.1 as builder
FROM golang:1.18 as builder
ENV GO111MODULE=on \
CGO_ENABLED=0
WORKDIR /go/src/github.com/cloudflare/cloudflared/
+8 -1
View File
@@ -2,4 +2,11 @@
package main
import _ "crypto/tls/fipsonly"
import (
_ "crypto/tls/fipsonly"
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
)
func init () {
tunnel.FipsEnabled = true
}
+20 -8
View File
@@ -12,6 +12,7 @@ require (
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10
github.com/gobwas/ws v1.0.4
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/google/gopacket v1.1.19
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
@@ -33,10 +34,10 @@ require (
go.opentelemetry.io/otel/trace v1.6.3
go.opentelemetry.io/proto/otlp v0.15.0
go.uber.org/automaxprocs v1.4.0
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
google.golang.org/protobuf v1.28.0
gopkg.in/coreos/go-oidc.v2 v2.2.1
@@ -53,7 +54,9 @@ require (
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 // indirect
github.com/coredns/caddy v1.1.1 // indirect
github.com/coreos/go-oidc/v3 v3.4.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
@@ -87,13 +90,13 @@ require (
github.com/prometheus/procfs v0.7.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // 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
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect
google.golang.org/grpc v1.47.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
@@ -106,3 +109,12 @@ replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.2
replace github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
// Post-quantum tunnel RTG-1339
replace (
// branch go1.18
github.com/marten-seemann/qtls-go1-18 => github.com/cloudflare/qtls-pq v0.0.0-20220824105406-fb955667e0af
// branch go1.19
github.com/marten-seemann/qtls-go1-19 => github.com/cloudflare/qtls-pq v0.0.0-20220824104809-96561a41e0af
)
+100 -14
View File
@@ -28,14 +28,23 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -45,6 +54,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
@@ -99,6 +109,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
@@ -117,8 +128,14 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 h1:QrGfkZDnMxcWHaYDdB7CmqS9i26OAnUj/xcus/abYkY=
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93/go.mod h1:QiTe66jFdP7cUKMCCf/WrvDyYdtdmdZfVcdoLbzaKVY=
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 h1:YzpECHxZ9TzO7LpnKmPxItSd79lLgrR5heIlnqU4dTU=
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47/go.mod h1:qhx8gBILsYlbam7h09SvHDSkjpe3TfLA7b/z4rxJvkE=
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc h1:Dvk3ySBsOm5EviLx6VCyILnafPcQinXGP5jbTdHUJgE=
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc/go.mod h1:HlgKKR8V5a1wroIDDIz3/A+T+9Janfq+7n1P5sEFdi0=
github.com/cloudflare/qtls-pq v0.0.0-20220824104809-96561a41e0af h1:JMpOQAaXjRRBkUX73fTNe9mConJLFl6FsIp9fHdLm7Y=
github.com/cloudflare/qtls-pq v0.0.0-20220824104809-96561a41e0af/go.mod h1:aIsWqC0WXyUiUxBl/RfxAjDyWE9CCLqvSMnCMTd/+bc=
github.com/cloudflare/qtls-pq v0.0.0-20220824105406-fb955667e0af h1:bhCmedjwrOSyzLtHVeQ+KhimcNTSfs0P5T7kbRQS+gA=
github.com/cloudflare/qtls-pq v0.0.0-20220824105406-fb955667e0af/go.mod h1:mW0BgKFFDAiSmOdUwoORtjo0V2vqw5QzVYRtKQqw/Jg=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -126,11 +143,14 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
github.com/coredns/coredns v1.8.7 h1:wVMjAnyFnY7Mc18AFO+9qbGD6ODPtdVUIlzoWrHr3hk=
github.com/coredns/coredns v1.8.7/go.mod h1:bFmbgEfeRz5aizL2VsQ5LRlsvJuXWkgG/MWG9zxqjVM=
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@@ -164,6 +184,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
@@ -285,13 +306,16 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -316,14 +340,19 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -399,10 +428,6 @@ github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtU
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
@@ -619,8 +644,8 @@ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -706,10 +731,18 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -727,8 +760,13 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -740,8 +778,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -812,10 +851,21 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@@ -899,8 +949,11 @@ golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyj
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -934,7 +987,16 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -989,6 +1051,7 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@@ -1008,8 +1071,27 @@ google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEc
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@@ -1044,8 +1126,12 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+6
View File
@@ -265,6 +265,9 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
if c.Http2Origin != nil {
out.Http2Origin = *c.Http2Origin
}
if c.Access != nil {
out.Access = *c.Access
}
return out
}
@@ -310,6 +313,9 @@ type OriginRequestConfig struct {
IPRules []ipaccess.Rule `yaml:"ipRules" json:"ipRules"`
// Attempt to connect to origin with HTTP/2
Http2Origin bool `yaml:"http2Origin" json:"http2Origin"`
// Access holds all access related configs
Access config.AccessConfig `yaml:"access" json:"access,omitempty"`
}
func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) {
+272
View File
@@ -0,0 +1,272 @@
//go:build darwin
package ingress
// This file implements ICMPProxy for Darwin. It uses a non-privileged ICMP socket to send echo requests and listen for
// echo replies. The source IP of the requests are rewritten to the bind IP of the socket and the socket reads all
// messages, so we use echo ID to distinguish the replies. Each (source IP, destination IP, echo ID) is assigned a
// unique echo ID.
import (
"context"
"fmt"
"math"
"net"
"net/netip"
"strconv"
"sync"
"time"
"github.com/rs/zerolog"
"golang.org/x/net/icmp"
"github.com/cloudflare/cloudflared/packet"
)
type icmpProxy struct {
srcFunnelTracker *packet.FunnelTracker
echoIDTracker *echoIDTracker
conn *icmp.PacketConn
// Response is handled in one-by-one, so encoder can be shared between funnels
encoder *packet.Encoder
logger *zerolog.Logger
idleTimeout time.Duration
}
// echoIDTracker tracks which ID has been assigned. It first loops through assignment from lastAssignment to then end,
// then from the beginning to lastAssignment.
// ICMP echo are short lived. By the time an ID is revisited, it should have been released.
type echoIDTracker struct {
lock sync.Mutex
// maps the source IP, destination IP and original echo ID to a unique echo ID obtained from assignment
mapping map[flow3Tuple]uint16
// assignment tracks if an ID is assigned using index as the ID
// The size of the array is math.MaxUint16 because echo ID is 2 bytes
assignment [math.MaxUint16]bool
// nextAssignment is the next number to check for assigment
nextAssignment uint16
}
func newEchoIDTracker() *echoIDTracker {
return &echoIDTracker{
mapping: make(map[flow3Tuple]uint16),
}
}
// Get assignment or assign a new ID.
func (eit *echoIDTracker) getOrAssign(key flow3Tuple) (id uint16, success bool) {
eit.lock.Lock()
defer eit.lock.Unlock()
id, exists := eit.mapping[key]
if exists {
return id, true
}
if eit.nextAssignment == math.MaxUint16 {
eit.nextAssignment = 0
}
for i, assigned := range eit.assignment[eit.nextAssignment:] {
if !assigned {
echoID := uint16(i) + eit.nextAssignment
eit.set(key, echoID)
return echoID, true
}
}
for i, assigned := range eit.assignment[0:eit.nextAssignment] {
if !assigned {
echoID := uint16(i)
eit.set(key, echoID)
return echoID, true
}
}
return 0, false
}
// Caller should hold the lock
func (eit *echoIDTracker) set(key flow3Tuple, assignedEchoID uint16) {
eit.assignment[assignedEchoID] = true
eit.mapping[key] = assignedEchoID
eit.nextAssignment = assignedEchoID + 1
}
func (eit *echoIDTracker) release(key flow3Tuple, assigned uint16) bool {
eit.lock.Lock()
defer eit.lock.Unlock()
currentEchoID, exists := eit.mapping[key]
if exists && assigned == currentEchoID {
delete(eit.mapping, key)
eit.assignment[assigned] = false
return true
}
return false
}
type echoFunnelID uint16
func (snf echoFunnelID) Type() string {
return "echoID"
}
func (snf echoFunnelID) String() string {
return strconv.FormatUint(uint64(snf), 10)
}
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
conn, err := newICMPConn(listenIP, zone)
if err != nil {
return nil, err
}
logger.Info().Msgf("Created ICMP proxy listening on %s", conn.LocalAddr())
return &icmpProxy{
srcFunnelTracker: packet.NewFunnelTracker(),
echoIDTracker: newEchoIDTracker(),
encoder: packet.NewEncoder(),
conn: conn,
logger: logger,
idleTimeout: idleTimeout,
}, nil
}
func (ip *icmpProxy) Request(pk *packet.ICMP, responder packet.FunnelUniPipe) error {
if pk == nil {
return errPacketNil
}
originalEcho, err := getICMPEcho(pk.Message)
if err != nil {
return err
}
echoIDTrackerKey := flow3Tuple{
srcIP: pk.Src,
dstIP: pk.Dst,
originalEchoID: originalEcho.ID,
}
// TODO: TUN-6744 assign unique flow per (src, echo ID)
assignedEchoID, success := ip.echoIDTracker.getOrAssign(echoIDTrackerKey)
if !success {
return fmt.Errorf("failed to assign unique echo ID")
}
newFunnelFunc := func() (packet.Funnel, error) {
originalEcho, err := getICMPEcho(pk.Message)
if err != nil {
return nil, err
}
originSender := originSender{
conn: ip.conn,
echoIDTracker: ip.echoIDTracker,
echoIDTrackerKey: echoIDTrackerKey,
assignedEchoID: assignedEchoID,
}
icmpFlow := newICMPEchoFlow(pk.Src, &originSender, responder, int(assignedEchoID), originalEcho.ID, ip.encoder)
return icmpFlow, nil
}
funnelID := echoFunnelID(assignedEchoID)
funnel, isNew, err := ip.srcFunnelTracker.GetOrRegister(funnelID, newFunnelFunc)
if err != nil {
return err
}
if isNew {
ip.logger.Debug().
Str("src", pk.Src.String()).
Str("dst", pk.Dst.String()).
Int("originalEchoID", originalEcho.ID).
Int("assignedEchoID", int(assignedEchoID)).
Msg("New flow")
}
icmpFlow, err := toICMPEchoFlow(funnel)
if err != nil {
return err
}
return icmpFlow.sendToDst(pk.Dst, pk.Message)
}
// Serve listens for responses to the requests until context is done
func (ip *icmpProxy) Serve(ctx context.Context) error {
go func() {
<-ctx.Done()
ip.conn.Close()
}()
go func() {
ip.srcFunnelTracker.ScheduleCleanup(ctx, ip.idleTimeout)
}()
buf := make([]byte, mtu)
icmpDecoder := packet.NewICMPDecoder()
for {
n, from, err := ip.conn.ReadFrom(buf)
if err != nil {
return err
}
reply, err := parseReply(from, buf[:n])
if err != nil {
ip.logger.Debug().Err(err).Str("dst", from.String()).Msg("Failed to parse ICMP reply, continue to parse as full packet")
// In unit test, we found out when the listener listens on 0.0.0.0, the socket reads the full packet after
// the second reply
if err := ip.handleFullPacket(icmpDecoder, buf[:n]); err != nil {
ip.logger.Debug().Err(err).Str("dst", from.String()).Msg("Failed to parse ICMP reply as full packet")
}
continue
}
if !isEchoReply(reply.msg) {
ip.logger.Debug().Str("dst", from.String()).Msgf("Drop ICMP %s from reply", reply.msg.Type)
continue
}
if err := ip.sendReply(reply); err != nil {
ip.logger.Error().Err(err).Str("dst", from.String()).Msg("Failed to send ICMP reply")
continue
}
}
}
func (ip *icmpProxy) handleFullPacket(decoder *packet.ICMPDecoder, rawPacket []byte) error {
icmpPacket, err := decoder.Decode(packet.RawPacket{Data: rawPacket})
if err != nil {
return err
}
echo, err := getICMPEcho(icmpPacket.Message)
if err != nil {
return err
}
reply := echoReply{
from: icmpPacket.Src,
msg: icmpPacket.Message,
echo: echo,
}
if ip.sendReply(&reply); err != nil {
return err
}
return nil
}
func (ip *icmpProxy) sendReply(reply *echoReply) error {
funnelID := echoFunnelID(reply.echo.ID)
funnel, ok := ip.srcFunnelTracker.Get(funnelID)
if !ok {
return packet.ErrFunnelNotFound
}
icmpFlow, err := toICMPEchoFlow(funnel)
if err != nil {
return err
}
return icmpFlow.returnToSrc(reply)
}
// originSender wraps icmp.PacketConn to implement packet.FunnelUniPipe interface
type originSender struct {
conn *icmp.PacketConn
echoIDTracker *echoIDTracker
echoIDTrackerKey flow3Tuple
assignedEchoID uint16
}
func (os *originSender) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
_, err := os.conn.WriteTo(pk.Data, &net.UDPAddr{
IP: dst.AsSlice(),
})
return err
}
func (os *originSender) Close() error {
os.echoIDTracker.release(os.echoIDTrackerKey, os.assignedEchoID)
return nil
}
+121
View File
@@ -0,0 +1,121 @@
//go:build darwin
package ingress
import (
"math"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
)
func TestSingleEchoIDTracker(t *testing.T) {
tracker := newEchoIDTracker()
key := flow3Tuple{
srcIP: netip.MustParseAddr("172.16.0.1"),
dstIP: netip.MustParseAddr("172.16.0.2"),
originalEchoID: 5182,
}
// not assigned yet, so nothing to release
require.False(t, tracker.release(key, 0))
echoID, ok := tracker.getOrAssign(key)
require.True(t, ok)
require.Equal(t, uint16(0), echoID)
// Second time should return the same echo ID
echoID, ok = tracker.getOrAssign(key)
require.True(t, ok)
require.Equal(t, uint16(0), echoID)
// releasing a different ID returns false
require.False(t, tracker.release(key, 1999))
require.True(t, tracker.release(key, echoID))
// releasing the second time returns false
require.False(t, tracker.release(key, echoID))
// Move to the next IP
echoID, ok = tracker.getOrAssign(key)
require.True(t, ok)
require.Equal(t, uint16(1), echoID)
}
func TestFullEchoIDTracker(t *testing.T) {
var (
dstIP = netip.MustParseAddr("192.168.0.1")
originalEchoID = 41820
)
tracker := newEchoIDTracker()
firstSrcIP := netip.MustParseAddr("172.16.0.1")
srcIP := firstSrcIP
for i := uint16(0); i < math.MaxUint16; i++ {
key := flow3Tuple{
srcIP: srcIP,
dstIP: dstIP,
originalEchoID: originalEchoID,
}
echoID, ok := tracker.getOrAssign(key)
require.True(t, ok)
require.Equal(t, i, echoID)
echoID, ok = tracker.get(key)
require.True(t, ok)
require.Equal(t, i, echoID)
srcIP = srcIP.Next()
}
key := flow3Tuple{
srcIP: srcIP.Next(),
dstIP: dstIP,
originalEchoID: originalEchoID,
}
// All echo IDs are assigned
echoID, ok := tracker.getOrAssign(key)
require.False(t, ok)
require.Equal(t, uint16(0), echoID)
srcIP = firstSrcIP
for i := uint16(0); i < math.MaxUint16; i++ {
key := flow3Tuple{
srcIP: srcIP,
dstIP: dstIP,
originalEchoID: originalEchoID,
}
ok := tracker.release(key, i)
require.True(t, ok)
echoID, ok = tracker.get(key)
require.False(t, ok)
require.Equal(t, uint16(0), echoID)
srcIP = srcIP.Next()
}
// The IDs are assignable again
srcIP = firstSrcIP
for i := uint16(0); i < math.MaxUint16; i++ {
key := flow3Tuple{
srcIP: srcIP,
dstIP: dstIP,
originalEchoID: originalEchoID,
}
echoID, ok := tracker.getOrAssign(key)
require.True(t, ok)
require.Equal(t, i, echoID)
echoID, ok = tracker.get(key)
require.True(t, ok)
require.Equal(t, i, echoID)
srcIP = srcIP.Next()
}
}
func (eit *echoIDTracker) get(key flow3Tuple) (id uint16, exist bool) {
eit.lock.Lock()
defer eit.lock.Unlock()
id, exists := eit.mapping[key]
return id, exists
}
+31
View File
@@ -0,0 +1,31 @@
//go:build !darwin && !linux && (!windows || !cgo)
package ingress
import (
"context"
"fmt"
"net/netip"
"runtime"
"time"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/packet"
)
var errICMPProxyNotImplemented = fmt.Errorf("ICMP proxy is not implemented on %s %s", runtime.GOOS, runtime.GOARCH)
type icmpProxy struct{}
func (ip icmpProxy) Request(pk *packet.ICMP, responder packet.FunnelUniPipe) error {
return errICMPProxyNotImplemented
}
func (ip *icmpProxy) Serve(ctx context.Context) error {
return errICMPProxyNotImplemented
}
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
return nil, errICMPProxyNotImplemented
}
+213
View File
@@ -0,0 +1,213 @@
//go:build linux
package ingress
// This file implements ICMPProxy for Linux. Each (source IP, destination IP, echo ID) opens a non-privileged ICMP socket.
// The source IP of the requests are rewritten to the bind IP of the socket and echo ID rewritten to the port number of
// the socket. The kernel ensures the socket only reads replies whose echo ID matches the port number.
// For more information about the socket, see https://man7.org/linux/man-pages/man7/icmp.7.html and https://lwn.net/Articles/422330/
import (
"context"
"fmt"
"net"
"net/netip"
"os"
"regexp"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/icmp"
"github.com/cloudflare/cloudflared/packet"
)
const (
// https://lwn.net/Articles/550551/ IPv4 and IPv6 share the same path
pingGroupPath = "/proc/sys/net/ipv4/ping_group_range"
)
var (
findGroupIDRegex = regexp.MustCompile(`\d+`)
)
type icmpProxy struct {
srcFunnelTracker *packet.FunnelTracker
listenIP netip.Addr
ipv6Zone string
logger *zerolog.Logger
idleTimeout time.Duration
}
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
if err := testPermission(listenIP, zone, logger); err != nil {
return nil, err
}
return &icmpProxy{
srcFunnelTracker: packet.NewFunnelTracker(),
listenIP: listenIP,
ipv6Zone: zone,
logger: logger,
idleTimeout: idleTimeout,
}, nil
}
func testPermission(listenIP netip.Addr, zone string, logger *zerolog.Logger) error {
// Opens a non-privileged ICMP socket. On Linux the group ID of the process needs to be in ping_group_range
// Only check ping_group_range once for IPv4
if listenIP.Is4() {
if err := checkInPingGroup(); err != nil {
logger.Warn().Err(err).Msgf("The user running cloudflared process has a GID (group ID) that is not within ping_group_range. You might need to add that user to a group within that range, or instead update the range to encompass a group the user is already in by modifying %s. Otherwise cloudflared will not be able to ping this network", pingGroupPath)
return err
}
}
conn, err := newICMPConn(listenIP, zone)
if err != nil {
return err
}
// This conn is only to test if cloudflared has permission to open this type of socket
conn.Close()
return nil
}
func checkInPingGroup() error {
file, err := os.ReadFile(pingGroupPath)
if err != nil {
return err
}
groupID := os.Getgid()
// Example content: 999 59999
found := findGroupIDRegex.FindAll(file, 2)
if len(found) == 2 {
groupMin, err := strconv.ParseInt(string(found[0]), 10, 32)
if err != nil {
return errors.Wrapf(err, "failed to determine minimum ping group ID")
}
groupMax, err := strconv.ParseInt(string(found[1]), 10, 32)
if err != nil {
return errors.Wrapf(err, "failed to determine minimum ping group ID")
}
if groupID < int(groupMin) || groupID > int(groupMax) {
return fmt.Errorf("Group ID %d is not between ping group %d to %d", groupID, groupMin, groupMax)
}
return nil
}
return fmt.Errorf("did not find group range in %s", pingGroupPath)
}
func (ip *icmpProxy) Request(pk *packet.ICMP, responder packet.FunnelUniPipe) error {
if pk == nil {
return errPacketNil
}
originalEcho, err := getICMPEcho(pk.Message)
if err != nil {
return err
}
newConnChan := make(chan *icmp.PacketConn, 1)
newFunnelFunc := func() (packet.Funnel, error) {
conn, err := newICMPConn(ip.listenIP, ip.ipv6Zone)
if err != nil {
return nil, errors.Wrap(err, "failed to open ICMP socket")
}
ip.logger.Debug().Msgf("Opened ICMP socket listen on %s", conn.LocalAddr())
newConnChan <- conn
localUDPAddr, ok := conn.LocalAddr().(*net.UDPAddr)
if !ok {
return nil, fmt.Errorf("ICMP listener address %s is not net.UDPAddr", conn.LocalAddr())
}
originSender := originSender{conn: conn}
echoID := localUDPAddr.Port
icmpFlow := newICMPEchoFlow(pk.Src, &originSender, responder, echoID, originalEcho.ID, packet.NewEncoder())
return icmpFlow, nil
}
funnelID := flow3Tuple{
srcIP: pk.Src,
dstIP: pk.Dst,
originalEchoID: originalEcho.ID,
}
funnel, isNew, err := ip.srcFunnelTracker.GetOrRegister(funnelID, newFunnelFunc)
if err != nil {
return err
}
icmpFlow, err := toICMPEchoFlow(funnel)
if err != nil {
return err
}
if isNew {
ip.logger.Debug().
Str("src", pk.Src.String()).
Str("dst", pk.Dst.String()).
Int("originalEchoID", originalEcho.ID).
Msg("New flow")
conn := <-newConnChan
go func() {
defer ip.srcFunnelTracker.Unregister(funnelID, icmpFlow)
if err := ip.listenResponse(icmpFlow, conn); err != nil {
ip.logger.Debug().Err(err).
Str("src", pk.Src.String()).
Str("dst", pk.Dst.String()).
Int("originalEchoID", originalEcho.ID).
Msg("Failed to listen for ICMP echo response")
}
}()
}
if err := icmpFlow.sendToDst(pk.Dst, pk.Message); err != nil {
return errors.Wrap(err, "failed to send ICMP echo request")
}
return nil
}
func (ip *icmpProxy) Serve(ctx context.Context) error {
ip.srcFunnelTracker.ScheduleCleanup(ctx, ip.idleTimeout)
return ctx.Err()
}
func (ip *icmpProxy) listenResponse(flow *icmpEchoFlow, conn *icmp.PacketConn) error {
buf := make([]byte, mtu)
for {
n, from, err := conn.ReadFrom(buf)
if err != nil {
return err
}
reply, err := parseReply(from, buf[:n])
if err != nil {
ip.logger.Error().Err(err).Str("dst", from.String()).Msg("Failed to parse ICMP reply")
continue
}
if !isEchoReply(reply.msg) {
ip.logger.Debug().Str("dst", from.String()).Msgf("Drop ICMP %s from reply", reply.msg.Type)
continue
}
if err := flow.returnToSrc(reply); err != nil {
ip.logger.Err(err).Str("dst", from.String()).Msg("Failed to send ICMP reply")
continue
}
}
}
// originSender wraps icmp.PacketConn to implement packet.FunnelUniPipe interface
type originSender struct {
conn *icmp.PacketConn
}
func (os *originSender) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
_, err := os.conn.WriteTo(pk.Data, &net.UDPAddr{
IP: dst.AsSlice(),
})
return err
}
func (os *originSender) Close() error {
return os.conn.Close()
}
// Only linux uses flow3Tuple as FunnelID
func (ft flow3Tuple) Type() string {
return "srcIP_dstIP_echoID"
}
func (ft flow3Tuple) String() string {
return fmt.Sprintf("%s:%s:%d", ft.srcIP, ft.dstIP, ft.originalEchoID)
}
+142
View File
@@ -0,0 +1,142 @@
//go:build darwin || linux
package ingress
// This file extracts logic shared by Linux and Darwin implementation if ICMPProxy.
import (
"fmt"
"net"
"net/netip"
"github.com/google/gopacket/layers"
"golang.org/x/net/icmp"
"github.com/cloudflare/cloudflared/packet"
)
// Opens a non-privileged ICMP socket on Linux and Darwin
func newICMPConn(listenIP netip.Addr, zone string) (*icmp.PacketConn, error) {
if listenIP.Is4() {
return icmp.ListenPacket("udp4", listenIP.String())
}
listenAddr := listenIP.String()
if zone != "" {
listenAddr = listenAddr + "%" + zone
}
return icmp.ListenPacket("udp6", listenAddr)
}
func netipAddr(addr net.Addr) (netip.Addr, bool) {
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
return netip.Addr{}, false
}
return netip.AddrFromSlice(udpAddr.IP)
}
type flow3Tuple struct {
srcIP netip.Addr
dstIP netip.Addr
originalEchoID int
}
// icmpEchoFlow implements the packet.Funnel interface.
type icmpEchoFlow struct {
*packet.RawPacketFunnel
assignedEchoID int
originalEchoID int
// it's up to the user to ensure respEncoder is not used concurrently
respEncoder *packet.Encoder
}
func newICMPEchoFlow(src netip.Addr, sendPipe, returnPipe packet.FunnelUniPipe, assignedEchoID, originalEchoID int, respEncoder *packet.Encoder) *icmpEchoFlow {
return &icmpEchoFlow{
RawPacketFunnel: packet.NewRawPacketFunnel(src, sendPipe, returnPipe),
assignedEchoID: assignedEchoID,
originalEchoID: originalEchoID,
respEncoder: respEncoder,
}
}
// sendToDst rewrites the echo ID to the one assigned to this flow
func (ief *icmpEchoFlow) sendToDst(dst netip.Addr, msg *icmp.Message) error {
originalEcho, err := getICMPEcho(msg)
if err != nil {
return err
}
sendMsg := icmp.Message{
Type: msg.Type,
Code: msg.Code,
Body: &icmp.Echo{
ID: ief.assignedEchoID,
Seq: originalEcho.Seq,
Data: originalEcho.Data,
},
}
// For IPv4, the pseudoHeader is not used because the checksum is always calculated
var pseudoHeader []byte = nil
serializedPacket, err := sendMsg.Marshal(pseudoHeader)
if err != nil {
return err
}
return ief.SendToDst(dst, packet.RawPacket{Data: serializedPacket})
}
// returnToSrc rewrites the echo ID to the original echo ID from the eyeball
func (ief *icmpEchoFlow) returnToSrc(reply *echoReply) error {
reply.echo.ID = ief.originalEchoID
reply.msg.Body = reply.echo
pk := packet.ICMP{
IP: &packet.IP{
Src: reply.from,
Dst: ief.Src,
Protocol: layers.IPProtocol(reply.msg.Type.Protocol()),
TTL: packet.DefaultTTL,
},
Message: reply.msg,
}
serializedPacket, err := ief.respEncoder.Encode(&pk)
if err != nil {
return err
}
return ief.ReturnToSrc(serializedPacket)
}
type echoReply struct {
from netip.Addr
msg *icmp.Message
echo *icmp.Echo
}
func parseReply(from net.Addr, rawMsg []byte) (*echoReply, error) {
fromAddr, ok := netipAddr(from)
if !ok {
return nil, fmt.Errorf("cannot convert %s to netip.Addr", from)
}
proto := layers.IPProtocolICMPv4
if fromAddr.Is6() {
proto = layers.IPProtocolICMPv6
}
msg, err := icmp.ParseMessage(int(proto), rawMsg)
if err != nil {
return nil, err
}
echo, err := getICMPEcho(msg)
if err != nil {
return nil, err
}
return &echoReply{
from: fromAddr,
msg: msg,
echo: echo,
}, nil
}
func toICMPEchoFlow(funnel packet.Funnel) (*icmpEchoFlow, error) {
icmpFlow, ok := funnel.(*icmpEchoFlow)
if !ok {
return nil, fmt.Errorf("%v is not *ICMPEchoFunnel", funnel)
}
return icmpFlow, nil
}
+76
View File
@@ -0,0 +1,76 @@
//go:build darwin || linux
package ingress
import (
"context"
"os"
"testing"
"time"
"github.com/google/gopacket/layers"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"github.com/cloudflare/cloudflared/packet"
)
func TestFunnelIdleTimeout(t *testing.T) {
const (
idleTimeout = time.Second
echoID = 42573
startSeq = 8129
)
logger := zerolog.New(os.Stderr)
proxy, err := newICMPProxy(localhostIP, "", &logger, idleTimeout)
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
proxyDone := make(chan struct{})
go func() {
proxy.Serve(ctx)
close(proxyDone)
}()
// Send a packet to register the flow
pk := packet.ICMP{
IP: &packet.IP{
Src: localhostIP,
Dst: localhostIP,
Protocol: layers.IPProtocolICMPv4,
},
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: echoID,
Seq: startSeq,
Data: []byte(t.Name()),
},
},
}
responder := echoFlowResponder{
decoder: packet.NewICMPDecoder(),
respChan: make(chan []byte),
}
require.NoError(t, proxy.Request(&pk, &responder))
responder.validate(t, &pk)
// Send second request, should reuse the funnel
require.NoError(t, proxy.Request(&pk, nil))
responder.validate(t, &pk)
time.Sleep(idleTimeout * 2)
newResponder := echoFlowResponder{
decoder: packet.NewICMPDecoder(),
respChan: make(chan []byte),
}
require.NoError(t, proxy.Request(&pk, &newResponder))
newResponder.validate(t, &pk)
cancel()
<-proxyDone
}
+527
View File
@@ -0,0 +1,527 @@
//go:build windows && cgo
package ingress
/*
#include <iphlpapi.h>
#include <icmpapi.h>
*/
import "C"
import (
"context"
"encoding/binary"
"fmt"
"net/netip"
"runtime/debug"
"sync"
"syscall"
"time"
"unsafe"
"github.com/google/gopacket/layers"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"github.com/cloudflare/cloudflared/packet"
)
const (
// Value defined in https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw
AF_INET6 = 23
icmpEchoReplyCode = 0
nullParameter = uintptr(0)
)
var (
Iphlpapi = syscall.NewLazyDLL("Iphlpapi.dll")
IcmpCreateFile_proc = Iphlpapi.NewProc("IcmpCreateFile")
Icmp6CreateFile_proc = Iphlpapi.NewProc("Icmp6CreateFile")
IcmpSendEcho_proc = Iphlpapi.NewProc("IcmpSendEcho")
Icmp6SendEcho_proc = Iphlpapi.NewProc("Icmp6SendEcho2")
echoReplySize = unsafe.Sizeof(echoReply{})
echoV6ReplySize = unsafe.Sizeof(echoV6Reply{})
icmpv6ErrMessageSize = 8
ioStatusBlockSize = unsafe.Sizeof(ioStatusBlock{})
endian = binary.LittleEndian
)
// IP_STATUS code, see https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-icmp_echo_reply32#members
// for possible values
type ipStatus uint32
const (
success ipStatus = 0
bufTooSmall = iota + 11000
destNetUnreachable
destHostUnreachable
destProtocolUnreachable
destPortUnreachable
noResources
badOption
hwError
packetTooBig
reqTimedOut
badReq
badRoute
ttlExpiredTransit
ttlExpiredReassembly
paramProblem
sourceQuench
optionTooBig
badDestination
// Can be returned for malformed ICMP packets
generalFailure = 11050
)
// Additional IP_STATUS codes for ICMPv6 https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-icmpv6_echo_reply_lh#members
const (
ipv6DestUnreachable ipStatus = iota + 11040
ipv6TimeExceeded
ipv6BadHeader
ipv6UnrecognizedNextHeader
ipv6ICMPError
ipv6DestScopeMismatch
)
func (is ipStatus) String() string {
switch is {
case success:
return "Success"
case bufTooSmall:
return "The reply buffer too small"
case destNetUnreachable:
return "The destination network was unreachable"
case destHostUnreachable:
return "The destination host was unreachable"
case destProtocolUnreachable:
return "The destination protocol was unreachable"
case destPortUnreachable:
return "The destination port was unreachable"
case noResources:
return "Insufficient IP resources were available"
case badOption:
return "A bad IP option was specified"
case hwError:
return "A hardware error occurred"
case packetTooBig:
return "The packet was too big"
case reqTimedOut:
return "The request timed out"
case badReq:
return "Bad request"
case badRoute:
return "Bad route"
case ttlExpiredTransit:
return "The TTL expired in transit"
case ttlExpiredReassembly:
return "The TTL expired during fragment reassembly"
case paramProblem:
return "A parameter problem"
case sourceQuench:
return "Datagrams are arriving too fast to be processed and datagrams may have been discarded"
case optionTooBig:
return "The IP option was too big"
case badDestination:
return "Bad destination"
case ipv6DestUnreachable:
return "IPv6 destination unreachable"
case ipv6TimeExceeded:
return "IPv6 time exceeded"
case ipv6BadHeader:
return "IPv6 bad IP header"
case ipv6UnrecognizedNextHeader:
return "IPv6 unrecognized next header"
case ipv6ICMPError:
return "IPv6 ICMP error"
case ipv6DestScopeMismatch:
return "IPv6 destination scope ID mismatch"
case generalFailure:
return "The ICMP packet might be malformed"
default:
return fmt.Sprintf("Unknown ip status %d", is)
}
}
// https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-ip_option_information
type ipOption struct {
TTL uint8
Tos uint8
Flags uint8
OptionsSize uint8
OptionsData uintptr
}
// https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-icmp_echo_reply
type echoReply struct {
Address uint32
Status ipStatus
RoundTripTime uint32
DataSize uint16
Reserved uint16
// The pointer size defers between 32-bit and 64-bit platforms
DataPointer *byte
Options ipOption
}
type echoV6Reply struct {
Address ipv6AddrEx
Status ipStatus
RoundTripTime uint32
}
// https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-ipv6_address_ex
// All the fields are in network byte order. The memory alignment is 4 bytes
type ipv6AddrEx struct {
port uint16
// flowInfo is uint32. Because of field alignment, when we cast reply buffer to ipv6AddrEx, it starts at the 5th byte
// But looking at the raw bytes, flowInfo starts at the 3rd byte. We device flowInfo into 2 uint16 so it's aligned
flowInfoUpper uint16
flowInfoLower uint16
addr [8]uint16
scopeID uint32
}
// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
type sockAddrIn6 struct {
family int16
// Can't embed ipv6AddrEx, that changes the memory alignment
port uint16
flowInfo uint32
addr [16]byte
scopeID uint32
}
func newSockAddrIn6(addr netip.Addr) (*sockAddrIn6, error) {
if !addr.Is6() {
return nil, fmt.Errorf("%s is not IPv6", addr)
}
return &sockAddrIn6{
family: AF_INET6,
port: 10,
addr: addr.As16(),
}, nil
}
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_status_block#syntax
type ioStatusBlock struct {
// The first field is an union of NTSTATUS and PVOID. NTSTATUS is int32 while PVOID depends on the platform.
// We model it as uintptr whose size depends on if the platform is 32-bit or 64-bit
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
statusOrPointer uintptr
information uintptr
}
type icmpProxy struct {
// An open handle that can send ICMP requests https://docs.microsoft.com/en-us/windows/win32/api/icmpapi/nf-icmpapi-icmpcreatefile
handle uintptr
// This is a ICMPv6 if srcSocketAddr is not nil
srcSocketAddr *sockAddrIn6
logger *zerolog.Logger
// A pool of reusable *packet.Encoder
encoderPool sync.Pool
}
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
var (
srcSocketAddr *sockAddrIn6
handle uintptr
err error
)
if listenIP.Is4() {
handle, _, err = IcmpCreateFile_proc.Call()
} else {
srcSocketAddr, err = newSockAddrIn6(listenIP)
if err != nil {
return nil, err
}
handle, _, err = Icmp6CreateFile_proc.Call()
}
// Windows procedure calls always return non-nil error constructed from the result of GetLastError.
// Caller need to inspect the primary returned value
if syscall.Handle(handle) == syscall.InvalidHandle {
return nil, errors.Wrap(err, "invalid ICMP handle")
}
return &icmpProxy{
handle: handle,
srcSocketAddr: srcSocketAddr,
logger: logger,
encoderPool: sync.Pool{
New: func() any {
return packet.NewEncoder()
},
},
}, nil
}
func (ip *icmpProxy) Serve(ctx context.Context) error {
<-ctx.Done()
syscall.CloseHandle(syscall.Handle(ip.handle))
return ctx.Err()
}
// Request sends an ICMP echo request and wait for a reply or timeout.
// The async version of Win32 APIs take a callback whose memory is not garbage collected, so we use the synchronous version.
// It's possible that a slow request will block other requests, so we set the timeout to only 1s.
func (ip *icmpProxy) Request(pk *packet.ICMP, responder packet.FunnelUniPipe) error {
if pk == nil {
return errPacketNil
}
defer func() {
if r := recover(); r != nil {
ip.logger.Error().Interface("error", r).Msgf("Recover panic from sending icmp request/response, error %s", debug.Stack())
}
}()
echo, err := getICMPEcho(pk.Message)
if err != nil {
return err
}
respData, err := ip.icmpEchoRoundtrip(pk.Dst, echo)
if err != nil {
ip.logger.Err(err).Msg("ICMP echo roundtrip failed")
return err
}
err = ip.handleEchoReply(pk, echo, respData, responder)
if err != nil {
return errors.Wrap(err, "failed to handle ICMP echo reply")
}
return nil
}
func (ip *icmpProxy) handleEchoReply(request *packet.ICMP, echoReq *icmp.Echo, data []byte, responder packet.FunnelUniPipe) error {
var replyType icmp.Type
if request.Dst.Is4() {
replyType = ipv4.ICMPTypeEchoReply
} else {
replyType = ipv6.ICMPTypeEchoReply
}
pk := packet.ICMP{
IP: &packet.IP{
Src: request.Dst,
Dst: request.Src,
Protocol: layers.IPProtocol(request.Type.Protocol()),
TTL: packet.DefaultTTL,
},
Message: &icmp.Message{
Type: replyType,
Code: icmpEchoReplyCode,
Body: &icmp.Echo{
ID: echoReq.ID,
Seq: echoReq.Seq,
Data: data,
},
},
}
cachedEncoder := ip.encoderPool.Get()
// The encoded packet is a slice to of the encoder, so we shouldn't return the encoder back to the pool until
// the encoded packet is sent.
defer ip.encoderPool.Put(cachedEncoder)
encoder, ok := cachedEncoder.(*packet.Encoder)
if !ok {
return fmt.Errorf("encoderPool returned %T, expect *packet.Encoder", cachedEncoder)
}
serializedPacket, err := encoder.Encode(&pk)
if err != nil {
return err
}
return responder.SendPacket(request.Src, serializedPacket)
}
func (ip *icmpProxy) icmpEchoRoundtrip(dst netip.Addr, echo *icmp.Echo) ([]byte, error) {
if dst.Is6() {
if ip.srcSocketAddr == nil {
return nil, fmt.Errorf("cannot send ICMPv6 using ICMPv4 proxy")
}
resp, err := ip.icmp6SendEcho(dst, echo)
if err != nil {
return nil, errors.Wrap(err, "failed to send/receive ICMPv6 echo")
}
return resp.data, nil
}
if ip.srcSocketAddr != nil {
return nil, fmt.Errorf("cannot send ICMPv4 using ICMPv6 proxy")
}
resp, err := ip.icmpSendEcho(dst, echo)
if err != nil {
return nil, errors.Wrap(err, "failed to send/receive ICMPv4 echo")
}
return resp.data, nil
}
/*
Wrapper to call https://docs.microsoft.com/en-us/windows/win32/api/icmpapi/nf-icmpapi-icmpsendecho
Parameters:
- IcmpHandle: Handle created by IcmpCreateFile
- DestinationAddress: IPv4 in the form of https://docs.microsoft.com/en-us/windows/win32/api/inaddr/ns-inaddr-in_addr#syntax
- RequestData: A pointer to echo data
- RequestSize: Number of bytes in buffer pointed by echo data
- RequestOptions: IP header options
- ReplyBuffer: A pointer to the buffer for echoReply, options and data
- ReplySize: Number of bytes allocated for ReplyBuffer
- Timeout: Timeout in milliseconds to wait for a reply
Returns:
- the number of replies in uint32 https://docs.microsoft.com/en-us/windows/win32/api/icmpapi/nf-icmpapi-icmpsendecho#return-value
To retain the reference allocated objects, conversion from pointer to uintptr must happen as arguments to the
syscall function
*/
func (ip *icmpProxy) icmpSendEcho(dst netip.Addr, echo *icmp.Echo) (*echoResp, error) {
dataSize := len(echo.Data)
replySize := echoReplySize + uintptr(dataSize)
replyBuf := make([]byte, replySize)
noIPHeaderOption := nullParameter
inAddr, err := inAddrV4(dst)
if err != nil {
return nil, err
}
replyCount, _, err := IcmpSendEcho_proc.Call(
ip.handle,
uintptr(inAddr),
uintptr(unsafe.Pointer(&echo.Data[0])),
uintptr(dataSize),
noIPHeaderOption,
uintptr(unsafe.Pointer(&replyBuf[0])),
replySize,
icmpRequestTimeoutMs,
)
if replyCount == 0 {
// status is returned in 5th to 8th byte of reply buffer
if status, parseErr := unmarshalIPStatus(replyBuf[4:8]); parseErr == nil && status != success {
return nil, errors.Wrapf(err, "received ip status: %s", status)
}
return nil, errors.Wrap(err, "did not receive ICMP echo reply")
} else if replyCount > 1 {
ip.logger.Warn().Msgf("Received %d ICMP echo replies, only sending 1 back", replyCount)
}
return newEchoResp(replyBuf)
}
// Third definition of https://docs.microsoft.com/en-us/windows/win32/api/inaddr/ns-inaddr-in_addr#syntax is address in uint32
func inAddrV4(ip netip.Addr) (uint32, error) {
if !ip.Is4() {
return 0, fmt.Errorf("%s is not IPv4", ip)
}
v4 := ip.As4()
return endian.Uint32(v4[:]), nil
}
type echoResp struct {
reply *echoReply
data []byte
}
func newEchoResp(replyBuf []byte) (*echoResp, error) {
if len(replyBuf) == 0 {
return nil, fmt.Errorf("reply buffer is empty")
}
// This is pattern 1 of https://pkg.go.dev/unsafe@master#Pointer, conversion of *replyBuf to *echoReply
// replyBuf size is larger than echoReply
reply := *(*echoReply)(unsafe.Pointer(&replyBuf[0]))
if reply.Status != success {
return nil, fmt.Errorf("status %d", reply.Status)
}
dataBufStart := len(replyBuf) - int(reply.DataSize)
if dataBufStart < int(echoReplySize) {
return nil, fmt.Errorf("reply buffer size %d is too small to hold data of size %d", len(replyBuf), int(reply.DataSize))
}
return &echoResp{
reply: &reply,
data: replyBuf[dataBufStart:],
}, nil
}
/*
Wrapper to call https://docs.microsoft.com/en-us/windows/win32/api/icmpapi/nf-icmpapi-icmp6sendecho2
Parameters:
- IcmpHandle: Handle created by Icmp6CreateFile
- Event (optional): Event object to be signaled when a reply arrives
- ApcRoutine (optional): Routine to call when the calling thread is in an alertable thread and a reply arrives
- ApcContext (optional): Optional parameter to ApcRoutine
- SourceAddress: Source address of the request
- DestinationAddress: Destination address of the request
- RequestData: A pointer to echo data
- RequestSize: Number of bytes in buffer pointed by echo data
- RequestOptions (optional): A pointer to the IPv6 header options
- ReplyBuffer: A pointer to the buffer for echoReply, options and data
- ReplySize: Number of bytes allocated for ReplyBuffer
- Timeout: Timeout in milliseconds to wait for a reply
Returns:
- the number of replies in uint32
To retain the reference allocated objects, conversion from pointer to uintptr must happen as arguments to the
syscall function
*/
func (ip *icmpProxy) icmp6SendEcho(dst netip.Addr, echo *icmp.Echo) (*echoV6Resp, error) {
dstAddr, err := newSockAddrIn6(dst)
if err != nil {
return nil, err
}
dataSize := len(echo.Data)
// Reply buffer needs to be big enough to hold an echoV6Reply, echo data, 8 bytes for ICMP error message
// and ioStatusBlock
replySize := echoV6ReplySize + uintptr(dataSize) + uintptr(icmpv6ErrMessageSize) + ioStatusBlockSize
replyBuf := make([]byte, replySize)
noEvent := nullParameter
noApcRoutine := nullParameter
noAppCtx := nullParameter
noIPHeaderOption := nullParameter
replyCount, _, err := Icmp6SendEcho_proc.Call(
ip.handle,
noEvent,
noApcRoutine,
noAppCtx,
uintptr(unsafe.Pointer(ip.srcSocketAddr)),
uintptr(unsafe.Pointer(dstAddr)),
uintptr(unsafe.Pointer(&echo.Data[0])),
uintptr(dataSize),
noIPHeaderOption,
uintptr(unsafe.Pointer(&replyBuf[0])),
replySize,
icmpRequestTimeoutMs,
)
if replyCount == 0 {
// status is in the 4 bytes after ipv6AddrEx. The reply buffer size is at least size of ipv6AddrEx + 4
if status, parseErr := unmarshalIPStatus(replyBuf[unsafe.Sizeof(ipv6AddrEx{}) : unsafe.Sizeof(ipv6AddrEx{})+4]); parseErr == nil && status != success {
return nil, fmt.Errorf("received ip status: %s", status)
}
return nil, errors.Wrap(err, "did not receive ICMP echo reply")
} else if replyCount > 1 {
ip.logger.Warn().Msgf("Received %d ICMP echo replies, only sending 1 back", replyCount)
}
return newEchoV6Resp(replyBuf, dataSize)
}
type echoV6Resp struct {
reply *echoV6Reply
data []byte
}
func newEchoV6Resp(replyBuf []byte, dataSize int) (*echoV6Resp, error) {
if len(replyBuf) == 0 {
return nil, fmt.Errorf("reply buffer is empty")
}
reply := *(*echoV6Reply)(unsafe.Pointer(&replyBuf[0]))
if reply.Status != success {
return nil, fmt.Errorf("status %d", reply.Status)
}
if uintptr(len(replyBuf)) < unsafe.Sizeof(reply)+uintptr(dataSize) {
return nil, fmt.Errorf("reply buffer size %d is too small to hold reply size %d + data size %d", len(replyBuf), echoV6ReplySize, dataSize)
}
return &echoV6Resp{
reply: &reply,
data: replyBuf[echoV6ReplySize : echoV6ReplySize+uintptr(dataSize)],
}, nil
}
func unmarshalIPStatus(replyBuf []byte) (ipStatus, error) {
if len(replyBuf) != 4 {
return 0, fmt.Errorf("ipStatus needs to be 4 bytes, got %d", len(replyBuf))
}
return ipStatus(endian.Uint32(replyBuf)), nil
}
+234
View File
@@ -0,0 +1,234 @@
//go:build windows
package ingress
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net/netip"
"testing"
"time"
"unsafe"
"golang.org/x/net/icmp"
"github.com/stretchr/testify/require"
)
// TestParseEchoReply tests parsing raw bytes from icmpSendEcho into echoResp
func TestParseEchoReply(t *testing.T) {
dst, err := inAddrV4(netip.MustParseAddr("192.168.10.20"))
require.NoError(t, err)
validReplyData := []byte(t.Name())
validReply := echoReply{
Address: dst,
Status: success,
RoundTripTime: uint32(20),
DataSize: uint16(len(validReplyData)),
DataPointer: &validReplyData[0],
Options: ipOption{
TTL: 59,
},
}
destHostUnreachableReply := validReply
destHostUnreachableReply.Status = destHostUnreachable
tests := []struct {
testCase string
replyBuf []byte
expectedReply *echoReply
expectedData []byte
}{
{
testCase: "empty buffer",
},
{
testCase: "status not success",
replyBuf: destHostUnreachableReply.marshal(t, []byte{}),
},
{
testCase: "valid reply",
replyBuf: validReply.marshal(t, validReplyData),
expectedReply: &validReply,
expectedData: validReplyData,
},
}
for _, test := range tests {
resp, err := newEchoResp(test.replyBuf)
if test.expectedReply == nil {
require.Error(t, err)
require.Nil(t, resp)
} else {
require.NoError(t, err)
require.Equal(t, resp.reply, test.expectedReply)
require.True(t, bytes.Equal(resp.data, test.expectedData))
}
}
}
// TestParseEchoV6Reply tests parsing raw bytes from icmp6SendEcho into echoV6Resp
func TestParseEchoV6Reply(t *testing.T) {
dst := netip.MustParseAddr("2606:3600:4500::3333").As16()
var addr [8]uint16
for i := 0; i < 8; i++ {
addr[i] = binary.BigEndian.Uint16(dst[i*2 : i*2+2])
}
validReplyData := []byte(t.Name())
validReply := echoV6Reply{
Address: ipv6AddrEx{
addr: addr,
},
Status: success,
RoundTripTime: 25,
}
destHostUnreachableReply := validReply
destHostUnreachableReply.Status = ipv6DestUnreachable
tests := []struct {
testCase string
replyBuf []byte
expectedReply *echoV6Reply
expectedData []byte
}{
{
testCase: "empty buffer",
},
{
testCase: "status not success",
replyBuf: destHostUnreachableReply.marshal(t, []byte{}),
},
{
testCase: "valid reply",
replyBuf: validReply.marshal(t, validReplyData),
expectedReply: &validReply,
expectedData: validReplyData,
},
}
for _, test := range tests {
resp, err := newEchoV6Resp(test.replyBuf, len(test.expectedData))
if test.expectedReply == nil {
require.Error(t, err)
require.Nil(t, resp)
} else {
require.NoError(t, err)
require.Equal(t, resp.reply, test.expectedReply)
require.True(t, bytes.Equal(resp.data, test.expectedData))
}
}
}
// TestSendEchoErrors makes sure icmpSendEcho handles error cases
func TestSendEchoErrors(t *testing.T) {
testSendEchoErrors(t, netip.IPv4Unspecified())
testSendEchoErrors(t, netip.IPv6Unspecified())
}
func testSendEchoErrors(t *testing.T, listenIP netip.Addr) {
proxy, err := newICMPProxy(listenIP, "", &noopLogger, time.Second)
require.NoError(t, err)
echo := icmp.Echo{
ID: 6193,
Seq: 25712,
Data: []byte(t.Name()),
}
documentIP := netip.MustParseAddr("192.0.2.200")
if listenIP.Is6() {
documentIP = netip.MustParseAddr("2001:db8::1")
}
resp, err := proxy.icmpEchoRoundtrip(documentIP, &echo)
require.Error(t, err)
require.Nil(t, resp)
}
func (er *echoReply) marshal(t *testing.T, data []byte) []byte {
buf := new(bytes.Buffer)
for _, field := range []any{
er.Address,
er.Status,
er.RoundTripTime,
er.DataSize,
er.Reserved,
} {
require.NoError(t, binary.Write(buf, endian, field))
}
require.NoError(t, marshalPointer(buf, uintptr(unsafe.Pointer(er.DataPointer))))
for _, field := range []any{
er.Options.TTL,
er.Options.Tos,
er.Options.Flags,
er.Options.OptionsSize,
} {
require.NoError(t, binary.Write(buf, endian, field))
}
require.NoError(t, marshalPointer(buf, er.Options.OptionsData))
padSize := buf.Len() % int(unsafe.Alignof(er))
padding := make([]byte, padSize)
n, err := buf.Write(padding)
require.NoError(t, err)
require.Equal(t, padSize, n)
n, err = buf.Write(data)
require.NoError(t, err)
require.Equal(t, len(data), n)
return buf.Bytes()
}
func marshalPointer(buf io.Writer, ptr uintptr) error {
size := unsafe.Sizeof(ptr)
switch size {
case 4:
return binary.Write(buf, endian, uint32(ptr))
case 8:
return binary.Write(buf, endian, uint64(ptr))
default:
return fmt.Errorf("unexpected pointer size %d", size)
}
}
func (er *echoV6Reply) marshal(t *testing.T, data []byte) []byte {
buf := new(bytes.Buffer)
for _, field := range []any{
er.Address.port,
er.Address.flowInfoUpper,
er.Address.flowInfoLower,
er.Address.addr,
er.Address.scopeID,
} {
require.NoError(t, binary.Write(buf, endian, field))
}
padSize := buf.Len() % int(unsafe.Alignof(er))
padding := make([]byte, padSize)
n, err := buf.Write(padding)
require.NoError(t, err)
require.Equal(t, padSize, n)
for _, field := range []any{
er.Status,
er.RoundTripTime,
} {
require.NoError(t, binary.Write(buf, endian, field))
}
n, err = buf.Write(data)
require.NoError(t, err)
require.Equal(t, len(data), n)
return buf.Bytes()
}
+57 -7
View File
@@ -11,8 +11,10 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"golang.org/x/net/idna"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress/middleware"
"github.com/cloudflare/cloudflared/ipaccess"
)
@@ -168,6 +170,28 @@ func (ing Ingress) CatchAll() *Rule {
return &ing.Rules[len(ing.Rules)-1]
}
func validateAccessConfiguration(cfg *config.AccessConfig) error {
if !cfg.Required {
return nil
}
// It is possible to set `required:true` and not have these two configured yet.
// But if one of them is configured, we'd validate for correctness.
if len(cfg.AudTag) == 0 && cfg.TeamName == "" {
return nil
}
if len(cfg.AudTag) == 0 {
return errors.New("access audtag cannot be empty")
}
if cfg.TeamName == "" {
return errors.New("access.TeamName cannot be blank")
}
return nil
}
func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) {
rules := make([]Rule, len(ingress))
for i, r := range ingress {
@@ -182,11 +206,14 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
path := strings.TrimPrefix(r.Service, prefix)
service = &unixSocketPath{path: path, scheme: "https"}
} else if prefix := "http_status:"; strings.HasPrefix(r.Service, prefix) {
status, err := strconv.Atoi(strings.TrimPrefix(r.Service, prefix))
statusCode, err := strconv.Atoi(strings.TrimPrefix(r.Service, prefix))
if err != nil {
return Ingress{}, errors.Wrap(err, "invalid HTTP status")
return Ingress{}, errors.Wrap(err, "invalid HTTP status code")
}
srv := newStatusCode(status)
if statusCode < 100 || statusCode > 999 {
return Ingress{}, fmt.Errorf("invalid HTTP status code: %d", statusCode)
}
srv := newStatusCode(statusCode)
service = &srv
} else if r.Service == HelloWorldService || r.Service == "hello-world" || r.Service == "helloworld" {
service = new(helloWorld)
@@ -234,10 +261,31 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
}
}
var handlers []middleware.Handler
if access := r.OriginRequest.Access; access != nil {
if err := validateAccessConfiguration(access); err != nil {
return Ingress{}, err
}
if access.Required {
verifier := middleware.NewJWTValidator(access.TeamName, "", access.AudTag)
handlers = append(handlers, verifier)
}
}
if err := validateHostname(r, i, len(ingress)); err != nil {
return Ingress{}, err
}
isCatchAllRule := (r.Hostname == "" || r.Hostname == "*") && r.Path == ""
punycodeHostname := ""
if !isCatchAllRule {
punycode, err := idna.Lookup.ToASCII(r.Hostname)
// Don't provide the punycode hostname if it is the same as the original hostname
if err == nil && punycode != r.Hostname {
punycodeHostname = punycode
}
}
var pathRegexp *Regexp
if r.Path != "" {
var err error
@@ -249,10 +297,12 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
}
rules[i] = Rule{
Hostname: r.Hostname,
Service: service,
Path: pathRegexp,
Config: cfg,
Hostname: r.Hostname,
punycodeHostname: punycodeHostname,
Service: service,
Path: pathRegexp,
Handlers: handlers,
Config: cfg,
}
}
return Ingress{Rules: rules, Defaults: defaults}, nil
+38
View File
@@ -130,6 +130,36 @@ ingress:
},
},
},
{
name: "Unicode domain",
args: args{rawYAML: `
ingress:
- hostname: môô.cloudflare.com
service: https://localhost:8000
- service: https://localhost:8001
`},
want: []Rule{
{
Hostname: "môô.cloudflare.com",
punycodeHostname: "xn--m-xgaa.cloudflare.com",
Service: &httpService{url: localhost8000},
Config: defaultConfig,
},
{
Service: &httpService{url: localhost8001},
Config: defaultConfig,
},
},
},
{
name: "Invalid unicode domain",
args: args{rawYAML: fmt.Sprintf(`
ingress:
- hostname: %s
service: https://localhost:8000
`, string(rune(0xd8f3))+".cloudflare.com")},
wantErr: true,
},
{
name: "Invalid service",
args: args{rawYAML: `
@@ -208,6 +238,14 @@ ingress:
args: args{rawYAML: `
ingress:
- service: http_status:asdf
`},
wantErr: true,
},
{
name: "Invalid HTTP status code",
args: args{rawYAML: `
ingress:
- service: http_status:8080
`},
wantErr: true,
},
+79
View File
@@ -0,0 +1,79 @@
package middleware
import (
"context"
"fmt"
"net/http"
"github.com/coreos/go-oidc/v3/oidc"
)
const (
headerKeyAccessJWTAssertion = "Cf-Access-Jwt-Assertion"
)
var (
cloudflareAccessCertsURL = "https://%s.cloudflareaccess.com"
)
// JWTValidator is an implementation of Verifier that validates access based JWT tokens.
type JWTValidator struct {
*oidc.IDTokenVerifier
audTags []string
}
func NewJWTValidator(teamName string, certsURL string, audTags []string) *JWTValidator {
if certsURL == "" {
certsURL = fmt.Sprintf(cloudflareAccessCertsURL, teamName)
}
certsEndpoint := fmt.Sprintf("%s/cdn-cgi/access/certs", certsURL)
config := &oidc.Config{
SkipClientIDCheck: true,
}
ctx := context.Background()
keySet := oidc.NewRemoteKeySet(ctx, certsEndpoint)
verifier := oidc.NewVerifier(certsURL, keySet, config)
return &JWTValidator{
IDTokenVerifier: verifier,
audTags: audTags,
}
}
func (v *JWTValidator) Name() string {
return "AccessJWTValidator"
}
func (v *JWTValidator) Handle(ctx context.Context, r *http.Request) (*HandleResult, error) {
accessJWT := r.Header.Get(headerKeyAccessJWTAssertion)
if accessJWT == "" {
// log the exact error message here. the message is specific to the handler implementation logic, we don't gain anything
// in passing it upstream. and each handler impl know what logging level to use for each.
return &HandleResult{
ShouldFilterRequest: true,
StatusCode: http.StatusForbidden,
Reason: "no access token in request",
}, nil
}
token, err := v.IDTokenVerifier.Verify(ctx, accessJWT)
if err != nil {
return nil, err
}
// We want at least one audTag to match
for _, jwtAudTag := range token.Audience {
for _, acceptedAudTag := range v.audTags {
if acceptedAudTag == jwtAudTag {
return &HandleResult{ShouldFilterRequest: false}, nil
}
}
}
return &HandleResult{
ShouldFilterRequest: true,
StatusCode: http.StatusForbidden,
Reason: fmt.Sprintf("Invalid token in jwt: %v", token.Audience),
}, nil
}
+19
View File
@@ -0,0 +1,19 @@
package middleware
import (
"context"
"net/http"
)
type HandleResult struct {
// Tells that the request didn't passed the handler and should be filtered
ShouldFilterRequest bool
// The status code to return in case ShouldFilterRequest is true.
StatusCode int
Reason string
}
type Handler interface {
Name() string
Handle(ctx context.Context, r *http.Request) (result *HandleResult, err error)
}
+99
View File
@@ -0,0 +1,99 @@
package ingress
import (
"context"
"fmt"
"net/netip"
"time"
"github.com/rs/zerolog"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"github.com/cloudflare/cloudflared/packet"
)
const (
// funnelIdleTimeout controls how long to wait to close a funnel without send/return
funnelIdleTimeout = time.Second * 10
mtu = 1500
// icmpRequestTimeoutMs controls how long to wait for a reply
icmpRequestTimeoutMs = 1000
)
var (
errPacketNil = fmt.Errorf("packet is nil")
)
type icmpRouter struct {
ipv4Proxy *icmpProxy
ipv6Proxy *icmpProxy
}
// NewICMPRouter doesn't return an error if either ipv4 proxy or ipv6 proxy can be created. The machine might only
// support one of them
func NewICMPRouter(ipv4Addr, ipv6Addr netip.Addr, ipv6Zone string, logger *zerolog.Logger) (*icmpRouter, error) {
ipv4Proxy, ipv4Err := newICMPProxy(ipv4Addr, "", logger, funnelIdleTimeout)
ipv6Proxy, ipv6Err := newICMPProxy(ipv6Addr, ipv6Zone, logger, funnelIdleTimeout)
if ipv4Err != nil && ipv6Err != nil {
return nil, fmt.Errorf("cannot create ICMPv4 proxy: %v nor ICMPv6 proxy: %v", ipv4Err, ipv6Err)
}
if ipv4Err != nil {
logger.Debug().Err(ipv4Err).Msg("failed to create ICMPv4 proxy, only ICMPv6 proxy is created")
ipv4Proxy = nil
}
if ipv6Err != nil {
logger.Debug().Err(ipv6Err).Msg("failed to create ICMPv6 proxy, only ICMPv4 proxy is created")
ipv6Proxy = nil
}
return &icmpRouter{
ipv4Proxy: ipv4Proxy,
ipv6Proxy: ipv6Proxy,
}, nil
}
func (ir *icmpRouter) Serve(ctx context.Context) error {
if ir.ipv4Proxy != nil && ir.ipv6Proxy != nil {
errC := make(chan error, 2)
go func() {
errC <- ir.ipv4Proxy.Serve(ctx)
}()
go func() {
errC <- ir.ipv6Proxy.Serve(ctx)
}()
return <-errC
}
if ir.ipv4Proxy != nil {
return ir.ipv4Proxy.Serve(ctx)
}
if ir.ipv6Proxy != nil {
return ir.ipv6Proxy.Serve(ctx)
}
return fmt.Errorf("ICMPv4 proxy and ICMPv6 proxy are both nil")
}
func (ir *icmpRouter) Request(pk *packet.ICMP, responder packet.FunnelUniPipe) error {
if pk.Dst.Is4() {
if ir.ipv4Proxy != nil {
return ir.ipv4Proxy.Request(pk, responder)
}
return fmt.Errorf("ICMPv4 proxy was not instantiated")
}
if ir.ipv6Proxy != nil {
return ir.ipv6Proxy.Request(pk, responder)
}
return fmt.Errorf("ICMPv6 proxy was not instantiated")
}
func getICMPEcho(msg *icmp.Message) (*icmp.Echo, error) {
echo, ok := msg.Body.(*icmp.Echo)
if !ok {
return nil, fmt.Errorf("expect ICMP echo, got %s", msg.Type)
}
return echo, nil
}
func isEchoReply(msg *icmp.Message) bool {
return msg.Type == ipv4.ICMPTypeEchoReply || msg.Type == ipv6.ICMPTypeEchoReply
}
+327
View File
@@ -0,0 +1,327 @@
package ingress
import (
"context"
"fmt"
"net"
"net/netip"
"strings"
"sync"
"testing"
"github.com/google/gopacket/layers"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"github.com/cloudflare/cloudflared/packet"
)
var (
noopLogger = zerolog.Nop()
localhostIP = netip.MustParseAddr("127.0.0.1")
localhostIPv6 = netip.MustParseAddr("::1")
)
// TestICMPProxyEcho makes sure we can send ICMP echo via the Request method and receives response via the
// ListenResponse method
//
// Note: if this test fails on your device under Linux, then most likely you need to make sure that your user
// is allowed in ping_group_range. See the following gist for how to do that:
// https://github.com/ValentinBELYN/icmplib/blob/main/docs/6-use-icmplib-without-privileges.md
func TestICMPRouterEcho(t *testing.T) {
testICMPRouterEcho(t, true)
testICMPRouterEcho(t, false)
}
func testICMPRouterEcho(t *testing.T, sendIPv4 bool) {
const (
echoID = 36571
endSeq = 20
)
router, err := NewICMPRouter(localhostIP, localhostIPv6, "", &noopLogger)
require.NoError(t, err)
proxyDone := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func() {
router.Serve(ctx)
close(proxyDone)
}()
responder := echoFlowResponder{
decoder: packet.NewICMPDecoder(),
respChan: make(chan []byte, 1),
}
protocol := layers.IPProtocolICMPv6
if sendIPv4 {
protocol = layers.IPProtocolICMPv4
}
localIPs := getLocalIPs(t, sendIPv4)
ips := make([]*packet.IP, len(localIPs))
for i, localIP := range localIPs {
ips[i] = &packet.IP{
Src: localIP,
Dst: localIP,
Protocol: protocol,
TTL: packet.DefaultTTL,
}
}
var icmpType icmp.Type = ipv6.ICMPTypeEchoRequest
if sendIPv4 {
icmpType = ipv4.ICMPTypeEcho
}
for seq := 0; seq < endSeq; seq++ {
for i, ip := range ips {
pk := packet.ICMP{
IP: ip,
Message: &icmp.Message{
Type: icmpType,
Code: 0,
Body: &icmp.Echo{
ID: echoID + i,
Seq: seq,
Data: []byte(fmt.Sprintf("icmp echo seq %d", seq)),
},
},
}
require.NoError(t, router.Request(&pk, &responder))
responder.validate(t, &pk)
}
}
cancel()
<-proxyDone
}
// TestConcurrentRequests makes sure icmpRouter can send concurrent requests to the same destination with different
// echo ID. This simulates concurrent ping to the same destination.
func TestConcurrentRequestsToSameDst(t *testing.T) {
const (
concurrentPings = 5
endSeq = 5
)
router, err := NewICMPRouter(localhostIP, localhostIPv6, "", &noopLogger)
require.NoError(t, err)
proxyDone := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func() {
router.Serve(ctx)
close(proxyDone)
}()
var wg sync.WaitGroup
// icmpv4 and icmpv6 each has concurrentPings
wg.Add(concurrentPings * 2)
for i := 0; i < concurrentPings; i++ {
echoID := 38451 + i
go func() {
defer wg.Done()
responder := echoFlowResponder{
decoder: packet.NewICMPDecoder(),
respChan: make(chan []byte, 1),
}
for seq := 0; seq < endSeq; seq++ {
pk := &packet.ICMP{
IP: &packet.IP{
Src: localhostIP,
Dst: localhostIP,
Protocol: layers.IPProtocolICMPv4,
TTL: packet.DefaultTTL,
},
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: echoID,
Seq: seq,
Data: []byte(fmt.Sprintf("icmpv4 echo id %d, seq %d", echoID, seq)),
},
},
}
require.NoError(t, router.Request(pk, &responder))
responder.validate(t, pk)
}
}()
go func() {
defer wg.Done()
responder := echoFlowResponder{
decoder: packet.NewICMPDecoder(),
respChan: make(chan []byte, 1),
}
for seq := 0; seq < endSeq; seq++ {
pk := &packet.ICMP{
IP: &packet.IP{
Src: localhostIPv6,
Dst: localhostIPv6,
Protocol: layers.IPProtocolICMPv6,
TTL: packet.DefaultTTL,
},
Message: &icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: echoID,
Seq: seq,
Data: []byte(fmt.Sprintf("icmpv6 echo id %d, seq %d", echoID, seq)),
},
},
}
require.NoError(t, router.Request(pk, &responder))
responder.validate(t, pk)
}
}()
}
wg.Wait()
cancel()
<-proxyDone
}
// TestICMPProxyRejectNotEcho makes sure it rejects messages other than echo
func TestICMPRouterRejectNotEcho(t *testing.T) {
msgs := []icmp.Message{
{
Type: ipv4.ICMPTypeDestinationUnreachable,
Code: 1,
Body: &icmp.DstUnreach{
Data: []byte("original packet"),
},
},
{
Type: ipv4.ICMPTypeTimeExceeded,
Code: 1,
Body: &icmp.TimeExceeded{
Data: []byte("original packet"),
},
},
{
Type: ipv4.ICMPType(2),
Code: 0,
Body: &icmp.PacketTooBig{
MTU: 1280,
Data: []byte("original packet"),
},
},
}
testICMPRouterRejectNotEcho(t, localhostIP, msgs)
msgsV6 := []icmp.Message{
{
Type: ipv6.ICMPTypeDestinationUnreachable,
Code: 3,
Body: &icmp.DstUnreach{
Data: []byte("original packet"),
},
},
{
Type: ipv6.ICMPTypeTimeExceeded,
Code: 0,
Body: &icmp.TimeExceeded{
Data: []byte("original packet"),
},
},
{
Type: ipv6.ICMPTypePacketTooBig,
Code: 0,
Body: &icmp.PacketTooBig{
MTU: 1280,
Data: []byte("original packet"),
},
},
}
testICMPRouterRejectNotEcho(t, localhostIPv6, msgsV6)
}
func testICMPRouterRejectNotEcho(t *testing.T, srcDstIP netip.Addr, msgs []icmp.Message) {
router, err := NewICMPRouter(localhostIP, localhostIPv6, "", &noopLogger)
require.NoError(t, err)
responder := echoFlowResponder{
decoder: packet.NewICMPDecoder(),
respChan: make(chan []byte),
}
protocol := layers.IPProtocolICMPv4
if srcDstIP.Is6() {
protocol = layers.IPProtocolICMPv6
}
for _, m := range msgs {
pk := packet.ICMP{
IP: &packet.IP{
Src: srcDstIP,
Dst: srcDstIP,
Protocol: protocol,
TTL: packet.DefaultTTL,
},
Message: &m,
}
require.Error(t, router.Request(&pk, &responder))
}
}
type echoFlowResponder struct {
lock sync.Mutex
decoder *packet.ICMPDecoder
respChan chan []byte
}
func (efr *echoFlowResponder) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
efr.lock.Lock()
defer efr.lock.Unlock()
copiedPacket := make([]byte, len(pk.Data))
copy(copiedPacket, pk.Data)
efr.respChan <- copiedPacket
return nil
}
func (efr *echoFlowResponder) Close() error {
efr.lock.Lock()
defer efr.lock.Unlock()
close(efr.respChan)
return nil
}
func (efr *echoFlowResponder) validate(t *testing.T, echoReq *packet.ICMP) {
pk := <-efr.respChan
decoded, err := efr.decoder.Decode(packet.RawPacket{Data: pk})
require.NoError(t, err)
require.Equal(t, decoded.Src, echoReq.Dst)
require.Equal(t, decoded.Dst, echoReq.Src)
require.Equal(t, echoReq.Protocol, decoded.Protocol)
if echoReq.Type == ipv4.ICMPTypeEcho {
require.Equal(t, ipv4.ICMPTypeEchoReply, decoded.Type)
} else {
require.Equal(t, ipv6.ICMPTypeEchoReply, decoded.Type)
}
require.Equal(t, 0, decoded.Code)
require.NotZero(t, decoded.Checksum)
require.Equal(t, echoReq.Body, decoded.Body)
}
func getLocalIPs(t *testing.T, ipv4 bool) []netip.Addr {
interfaces, err := net.Interfaces()
require.NoError(t, err)
localIPs := []netip.Addr{}
for _, i := range interfaces {
// Skip TUN devices
if strings.Contains(i.Name, "tun") {
continue
}
addrs, err := i.Addrs()
require.NoError(t, err)
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && (ipnet.IP.IsPrivate() || ipnet.IP.IsLoopback()) {
if (ipv4 && ipnet.IP.To4() != nil) || (!ipv4 && ipnet.IP.To4() == nil) {
localIPs = append(localIPs, netip.MustParseAddr(ipnet.IP.String()))
}
}
}
}
return localIPs
}
+19 -2
View File
@@ -4,6 +4,8 @@ import (
"encoding/json"
"regexp"
"strings"
"github.com/cloudflare/cloudflared/ingress/middleware"
)
// Rule routes traffic from a hostname/path on the public internet to the
@@ -12,6 +14,9 @@ type Rule struct {
// Requests for this hostname will be proxied to this rule's service.
Hostname string `json:"hostname"`
// punycodeHostname is an additional optional hostname converted to punycode.
punycodeHostname string
// Path is an optional regex that can specify path-driven ingress rules.
Path *Regexp `json:"path"`
@@ -20,6 +25,9 @@ type Rule struct {
// address.
Service OriginService `json:"service"`
// Handlers is a list of functions that acts as a middleware during ProxyHTTP
Handlers []middleware.Handler
// Configure the request cloudflared sends to this specific origin.
Config OriginRequestConfig `json:"originRequest"`
}
@@ -45,9 +53,18 @@ func (r Rule) MultiLineString() string {
// Matches checks if the rule matches a given hostname/path combination.
func (r *Rule) Matches(hostname, path string) bool {
hostMatch := r.Hostname == "" || r.Hostname == "*" || matchHost(r.Hostname, hostname)
hostMatch := false
if r.Hostname == "" || r.Hostname == "*" {
hostMatch = true
} else {
hostMatch = matchHost(r.Hostname, hostname)
}
punycodeHostMatch := false
if r.punycodeHostname != "" {
punycodeHostMatch = matchHost(r.punycodeHostname, hostname)
}
pathMatch := r.Path == nil || r.Path.Regexp == nil || r.Path.Regexp.MatchString(path)
return hostMatch && pathMatch
return (hostMatch || punycodeHostMatch) && pathMatch
}
// Regexp adds unmarshalling from json for regexp.Regexp
+41 -29
View File
@@ -14,23 +14,18 @@ import (
)
func Test_rule_matches(t *testing.T) {
type fields struct {
Hostname string
Path *Regexp
Service OriginService
}
type args struct {
requestURL *url.URL
}
tests := []struct {
name string
fields fields
args args
want bool
name string
rule Rule
args args
want bool
}{
{
name: "Just hostname, pass",
fields: fields{
rule: Rule{
Hostname: "example.com",
},
args: args{
@@ -38,9 +33,31 @@ func Test_rule_matches(t *testing.T) {
},
want: true,
},
{
name: "Unicode hostname with unicode request, pass",
rule: Rule{
Hostname: "môô.cloudflare.com",
punycodeHostname: "xn--m-xgaa.cloudflare.com",
},
args: args{
requestURL: MustParseURL(t, "https://môô.cloudflare.com"),
},
want: true,
},
{
name: "Unicode hostname with punycode request, pass",
rule: Rule{
Hostname: "môô.cloudflare.com",
punycodeHostname: "xn--m-xgaa.cloudflare.com",
},
args: args{
requestURL: MustParseURL(t, "https://xn--m-xgaa.cloudflare.com"),
},
want: true,
},
{
name: "Entire hostname is wildcard, should match everything",
fields: fields{
rule: Rule{
Hostname: "*",
},
args: args{
@@ -50,7 +67,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Just hostname, fail",
fields: fields{
rule: Rule{
Hostname: "example.com",
},
args: args{
@@ -60,7 +77,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Just wildcard hostname, pass",
fields: fields{
rule: Rule{
Hostname: "*.example.com",
},
args: args{
@@ -70,7 +87,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Just wildcard hostname, fail",
fields: fields{
rule: Rule{
Hostname: "*.example.com",
},
args: args{
@@ -80,7 +97,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Just wildcard outside of subdomain in hostname, fail",
fields: fields{
rule: Rule{
Hostname: "*example.com",
},
args: args{
@@ -90,7 +107,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Wildcard over multiple subdomains",
fields: fields{
rule: Rule{
Hostname: "*.example.com",
},
args: args{
@@ -100,7 +117,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Hostname and path",
fields: fields{
rule: Rule{
Hostname: "*.example.com",
Path: &Regexp{Regexp: regexp.MustCompile("/static/.*\\.html")},
},
@@ -111,7 +128,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Hostname and empty Regex",
fields: fields{
rule: Rule{
Hostname: "example.com",
Path: &Regexp{},
},
@@ -122,7 +139,7 @@ func Test_rule_matches(t *testing.T) {
},
{
name: "Hostname and nil path",
fields: fields{
rule: Rule{
Hostname: "example.com",
Path: nil,
},
@@ -134,13 +151,8 @@ func Test_rule_matches(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := Rule{
Hostname: tt.fields.Hostname,
Path: tt.fields.Path,
Service: tt.fields.Service,
}
u := tt.args.requestURL
if got := r.Matches(u.Hostname(), u.Path); got != tt.want {
if got := tt.rule.Matches(u.Hostname(), u.Path); got != tt.want {
t.Errorf("rule.matches() = %v, want %v", got, tt.want)
}
})
@@ -182,25 +194,25 @@ func TestMarshalJSON(t *testing.T) {
{
name: "Nil",
path: nil,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
{
name: "Nil regex",
path: &Regexp{Regexp: nil},
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
{
name: "Empty",
path: &Regexp{Regexp: regexp.MustCompile("")},
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
{
name: "Basic",
path: &Regexp{Regexp: regexp.MustCompile("/echo")},
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
}
+19 -4
View File
@@ -27,10 +27,12 @@ type Orchestrator struct {
// Used by UpdateConfig to make sure one update at a time
lock sync.RWMutex
// Underlying value is proxy.Proxy, can be read without the lock, but still needs the lock to update
proxy atomic.Value
config *Config
tags []tunnelpogs.Tag
log *zerolog.Logger
proxy atomic.Value
// TODO: TUN-6815 Use atomic.Bool once we upgrade to go 1.19. 1 Means enabled and 0 means disabled
warpRoutingEnabled uint32
config *Config
tags []tunnelpogs.Tag
log *zerolog.Logger
// orchestrator must not handle any more updates after shutdownC is closed
shutdownC <-chan struct{}
@@ -122,6 +124,11 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting i
o.proxy.Store(newProxy)
o.config.Ingress = &ingressRules
o.config.WarpRouting = warpRouting
if warpRouting.Enabled {
atomic.StoreUint32(&o.warpRoutingEnabled, 1)
} else {
atomic.StoreUint32(&o.warpRoutingEnabled, 0)
}
// If proxyShutdownC is nil, there is no previous running proxy
if o.proxyShutdownC != nil {
@@ -190,6 +197,14 @@ func (o *Orchestrator) GetOriginProxy() (connection.OriginProxy, error) {
return proxy, nil
}
// TODO: TUN-6815 consider storing WarpRouting.Enabled as atomic.Bool once we upgrade to go 1.19
func (o *Orchestrator) WarpRoutingEnabled() (enabled bool) {
if atomic.LoadUint32(&o.warpRoutingEnabled) == 0 {
return false
}
return true
}
func (o *Orchestrator) waitToCloseLastProxy() {
<-o.shutdownC
o.lock.Lock()
+5 -1
View File
@@ -55,6 +55,7 @@ func TestUpdateConfiguration(t *testing.T) {
initOriginProxy, err := orchestrator.GetOriginProxy()
require.NoError(t, err)
require.IsType(t, &proxy.Proxy{}, initOriginProxy)
require.False(t, orchestrator.WarpRoutingEnabled())
configJSONV2 := []byte(`
{
@@ -122,6 +123,7 @@ func TestUpdateConfiguration(t *testing.T) {
require.Equal(t, false, configV2.Ingress.Rules[2].Config.NoTLSVerify)
require.Equal(t, true, configV2.Ingress.Rules[2].Config.NoHappyEyeballs)
require.True(t, configV2.WarpRouting.Enabled)
require.Equal(t, configV2.WarpRouting.Enabled, orchestrator.WarpRoutingEnabled())
require.Equal(t, configV2.WarpRouting.ConnectTimeout.Duration, 10*time.Second)
originProxyV2, err := orchestrator.GetOriginProxy()
@@ -166,6 +168,7 @@ func TestUpdateConfiguration(t *testing.T) {
require.True(t, configV10.Ingress.Rules[0].Matches("blogs.tunnel.io", "/2022/02/10"))
require.Equal(t, ingress.HelloWorldService, configV10.Ingress.Rules[0].Service.String())
require.False(t, configV10.WarpRouting.Enabled)
require.Equal(t, configV10.WarpRouting.Enabled, orchestrator.WarpRoutingEnabled())
originProxyV10, err := orchestrator.GetOriginProxy()
require.NoError(t, err)
@@ -500,7 +503,8 @@ func TestClosePreviousProxies(t *testing.T) {
require.NoError(t, err)
require.Equal(t, http.StatusTeapot, resp.StatusCode)
// The hello-world server in config v1 should have been stopped
// The hello-world server in config v1 should have been stopped. We wait a bit since it's closed asynchronously.
time.Sleep(time.Millisecond * 10)
resp, err = proxyHTTP(originProxyV1, hostname)
require.Error(t, err)
require.Nil(t, resp)
+184
View File
@@ -0,0 +1,184 @@
package packet
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/pkg/errors"
"golang.org/x/net/icmp"
)
func FindProtocol(p []byte) (layers.IPProtocol, error) {
version, err := FindIPVersion(p)
if err != nil {
return 0, err
}
switch version {
case 4:
if len(p) < ipv4MinHeaderLen {
return 0, fmt.Errorf("IPv4 packet should have at least %d bytes, got %d bytes", ipv4MinHeaderLen, len(p))
}
// Protocol is in the 10th byte of IPv4 header
return layers.IPProtocol(p[9]), nil
case 6:
if len(p) < ipv6HeaderLen {
return 0, fmt.Errorf("IPv6 packet should have at least %d bytes, got %d bytes", ipv6HeaderLen, len(p))
}
// Next header is in the 7th byte of IPv6 header
return layers.IPProtocol(p[6]), nil
default:
return 0, fmt.Errorf("unknow ip version %d", version)
}
}
func FindIPVersion(p []byte) (uint8, error) {
if len(p) == 0 {
return 0, fmt.Errorf("packet length is 0")
}
return p[0] >> 4, nil
}
// IPDecoder decodes raw packets into IP. It can process packets sequentially without allocating
// memory for the layers, so it cannot be called concurrently.
type IPDecoder struct {
ipv4 *layers.IPv4
ipv6 *layers.IPv6
layers uint8
v4parser *gopacket.DecodingLayerParser
v6parser *gopacket.DecodingLayerParser
}
func NewIPDecoder() *IPDecoder {
var (
ipv4 layers.IPv4
ipv6 layers.IPv6
)
dlpv4 := gopacket.NewDecodingLayerParser(layers.LayerTypeIPv4)
dlpv4.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
dlpv4.AddDecodingLayer(&ipv4)
// Stop parsing when it encounter a layer that it doesn't have a parser
dlpv4.IgnoreUnsupported = true
dlpv6 := gopacket.NewDecodingLayerParser(layers.LayerTypeIPv6)
dlpv6.SetDecodingLayerContainer(gopacket.DecodingLayerSparse(nil))
dlpv6.AddDecodingLayer(&ipv6)
dlpv6.IgnoreUnsupported = true
return &IPDecoder{
ipv4: &ipv4,
ipv6: &ipv6,
layers: 1,
v4parser: dlpv4,
v6parser: dlpv6,
}
}
func (pd *IPDecoder) Decode(packet RawPacket) (*IP, error) {
// Should decode to IP layer
decoded, err := pd.decodeByVersion(packet.Data)
if err != nil {
return nil, err
}
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeIPv4:
return newIPv4(pd.ipv4)
case layers.LayerTypeIPv6:
return newIPv6(pd.ipv6)
}
}
return nil, fmt.Errorf("no ip layer is decoded")
}
func (pd *IPDecoder) decodeByVersion(packet []byte) ([]gopacket.LayerType, error) {
version, err := FindIPVersion(packet)
if err != nil {
return nil, err
}
decoded := make([]gopacket.LayerType, 0, pd.layers)
switch version {
case 4:
err = pd.v4parser.DecodeLayers(packet, &decoded)
case 6:
err = pd.v6parser.DecodeLayers(packet, &decoded)
default:
err = fmt.Errorf("unknow ip version %d", version)
}
if err != nil {
return nil, err
}
return decoded, nil
}
// ICMPDecoder decodes raw packets into IP and ICMP. It can process packets sequentially without allocating
// memory for the layers, so it cannot be called concurrently.
type ICMPDecoder struct {
*IPDecoder
icmpv4 *layers.ICMPv4
icmpv6 *layers.ICMPv6
}
func NewICMPDecoder() *ICMPDecoder {
ipDecoder := NewIPDecoder()
var (
icmpv4 layers.ICMPv4
icmpv6 layers.ICMPv6
)
ipDecoder.layers++
ipDecoder.v4parser.AddDecodingLayer(&icmpv4)
ipDecoder.v6parser.AddDecodingLayer(&icmpv6)
return &ICMPDecoder{
IPDecoder: ipDecoder,
icmpv4: &icmpv4,
icmpv6: &icmpv6,
}
}
func (pd *ICMPDecoder) Decode(packet RawPacket) (*ICMP, error) {
// Should decode to IP and optionally ICMP layer
decoded, err := pd.decodeByVersion(packet.Data)
if err != nil {
return nil, err
}
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeICMPv4:
ipv4, err := newIPv4(pd.ipv4)
if err != nil {
return nil, err
}
msg, err := icmp.ParseMessage(int(layers.IPProtocolICMPv4), append(pd.icmpv4.Contents, pd.icmpv4.Payload...))
if err != nil {
return nil, errors.Wrap(err, "failed to parse ICMPv4 message")
}
return &ICMP{
IP: ipv4,
Message: msg,
}, nil
case layers.LayerTypeICMPv6:
ipv6, err := newIPv6(pd.ipv6)
if err != nil {
return nil, err
}
msg, err := icmp.ParseMessage(int(layers.IPProtocolICMPv6), append(pd.icmpv6.Contents, pd.icmpv6.Payload...))
if err != nil {
return nil, errors.Wrap(err, "failed to parse ICMPv6")
}
return &ICMP{
IP: ipv6,
Message: msg,
}, nil
}
}
layers := make([]string, len(decoded))
for i, l := range decoded {
layers[i] = l.String()
}
return nil, fmt.Errorf("Expect to decode IP and ICMP layers, got %s", layers)
}
+256
View File
@@ -0,0 +1,256 @@
package packet
import (
"net"
"net/netip"
"testing"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
func TestDecodeIP(t *testing.T) {
ipDecoder := NewIPDecoder()
icmpDecoder := NewICMPDecoder()
udps := []UDP{
{
IP: IP{
Src: netip.MustParseAddr("172.16.0.1"),
Dst: netip.MustParseAddr("10.0.0.1"),
Protocol: layers.IPProtocolUDP,
},
SrcPort: 31678,
DstPort: 53,
},
{
IP: IP{
Src: netip.MustParseAddr("fd51:2391:523:f4ee::1"),
Dst: netip.MustParseAddr("fd51:2391:697:f4ee::2"),
Protocol: layers.IPProtocolUDP,
},
SrcPort: 52139,
DstPort: 1053,
},
}
encoder := NewEncoder()
for _, udp := range udps {
p, err := encoder.Encode(&udp)
require.NoError(t, err)
ipPacket, err := ipDecoder.Decode(p)
require.NoError(t, err)
assertIPLayer(t, &udp.IP, ipPacket)
icmpPacket, err := icmpDecoder.Decode(p)
require.Error(t, err)
require.Nil(t, icmpPacket)
}
}
func TestDecodeICMP(t *testing.T) {
ipDecoder := NewIPDecoder()
icmpDecoder := NewICMPDecoder()
var (
ipv4Packet = IP{
Src: netip.MustParseAddr("172.16.0.1"),
Dst: netip.MustParseAddr("10.0.0.1"),
Protocol: layers.IPProtocolICMPv4,
TTL: DefaultTTL,
}
ipv6Packet = IP{
Src: netip.MustParseAddr("fd51:2391:523:f4ee::1"),
Dst: netip.MustParseAddr("fd51:2391:697:f4ee::2"),
Protocol: layers.IPProtocolICMPv6,
TTL: DefaultTTL,
}
icmpID = 100
icmpSeq = 52819
)
tests := []struct {
testCase string
packet *ICMP
}{
{
testCase: "icmpv4 time exceed",
packet: &ICMP{
IP: &ipv4Packet,
Message: &icmp.Message{
Type: ipv4.ICMPTypeTimeExceeded,
Code: 0,
Body: &icmp.TimeExceeded{
Data: []byte("original packet"),
},
},
},
},
{
testCase: "icmpv4 echo",
packet: &ICMP{
IP: &ipv4Packet,
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: icmpID,
Seq: icmpSeq,
Data: []byte("icmpv4 echo"),
},
},
},
},
{
testCase: "icmpv6 destination unreachable",
packet: &ICMP{
IP: &ipv6Packet,
Message: &icmp.Message{
Type: ipv6.ICMPTypeDestinationUnreachable,
Code: 4,
Body: &icmp.DstUnreach{
Data: []byte("original packet"),
},
},
},
},
{
testCase: "icmpv6 echo",
packet: &ICMP{
IP: &ipv6Packet,
Message: &icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: icmpID,
Seq: icmpSeq,
Data: []byte("icmpv6 echo"),
},
},
},
},
}
encoder := NewEncoder()
for _, test := range tests {
p, err := encoder.Encode(test.packet)
require.NoError(t, err)
ipPacket, err := ipDecoder.Decode(p)
require.NoError(t, err)
if ipPacket.Src.Is4() {
assertIPLayer(t, &ipv4Packet, ipPacket)
} else {
assertIPLayer(t, &ipv6Packet, ipPacket)
}
icmpPacket, err := icmpDecoder.Decode(p)
require.NoError(t, err)
require.Equal(t, ipPacket, icmpPacket.IP)
require.Equal(t, test.packet.Type, icmpPacket.Type)
require.Equal(t, test.packet.Code, icmpPacket.Code)
assertICMPChecksum(t, icmpPacket)
require.Equal(t, test.packet.Body, icmpPacket.Body)
expectedBody, err := test.packet.Body.Marshal(test.packet.Type.Protocol())
require.NoError(t, err)
decodedBody, err := icmpPacket.Body.Marshal(test.packet.Type.Protocol())
require.NoError(t, err)
require.Equal(t, expectedBody, decodedBody)
}
}
// TestDecodeBadPackets makes sure decoders don't decode invalid packets
func TestDecodeBadPackets(t *testing.T) {
var (
srcIPv4 = net.ParseIP("172.16.0.1")
dstIPv4 = net.ParseIP("10.0.0.1")
)
ipLayer := layers.IPv4{
Version: 10,
SrcIP: srcIPv4,
DstIP: dstIPv4,
Protocol: layers.IPProtocolICMPv4,
TTL: DefaultTTL,
}
icmpLayer := layers.ICMPv4{
TypeCode: layers.CreateICMPv4TypeCode(uint8(ipv4.ICMPTypeEcho), 0),
Id: 100,
Seq: 52819,
}
wrongIPVersion, err := createPacket(&ipLayer, &icmpLayer, nil, nil)
require.NoError(t, err)
tests := []struct {
testCase string
packet []byte
}{
{
testCase: "unknown IP version",
packet: wrongIPVersion,
},
{
testCase: "invalid packet",
packet: []byte("not a packet"),
},
{
testCase: "zero length packet",
packet: []byte{},
},
}
ipDecoder := NewIPDecoder()
icmpDecoder := NewICMPDecoder()
for _, test := range tests {
ipPacket, err := ipDecoder.Decode(RawPacket{Data: test.packet})
require.Error(t, err)
require.Nil(t, ipPacket)
icmpPacket, err := icmpDecoder.Decode(RawPacket{Data: test.packet})
require.Error(t, err)
require.Nil(t, icmpPacket)
}
}
func createPacket(ipLayer, secondLayer, thirdLayer gopacket.SerializableLayer, body []byte) ([]byte, error) {
payload := gopacket.Payload(body)
packet := gopacket.NewSerializeBuffer()
var err error
if thirdLayer != nil {
err = gopacket.SerializeLayers(packet, serializeOpts, ipLayer, secondLayer, thirdLayer, payload)
} else {
err = gopacket.SerializeLayers(packet, serializeOpts, ipLayer, secondLayer, payload)
}
if err != nil {
return nil, err
}
return packet.Bytes(), nil
}
func assertIPLayer(t *testing.T, expected, actual *IP) {
require.Equal(t, expected.Src, actual.Src)
require.Equal(t, expected.Dst, actual.Dst)
require.Equal(t, expected.Protocol, actual.Protocol)
require.Equal(t, expected.TTL, actual.TTL)
}
type UDP struct {
IP
SrcPort, DstPort layers.UDPPort
}
func (u *UDP) EncodeLayers() ([]gopacket.SerializableLayer, error) {
ipLayers, err := u.IP.EncodeLayers()
if err != nil {
return nil, err
}
udpLayer := layers.UDP{
SrcPort: u.SrcPort,
DstPort: u.DstPort,
}
udpLayer.SetNetworkLayerForChecksum(ipLayers[0].(gopacket.NetworkLayer))
return append(ipLayers, &udpLayer), nil
}
+39
View File
@@ -0,0 +1,39 @@
package packet
import "github.com/google/gopacket"
var (
serializeOpts = gopacket.SerializeOptions{
FixLengths: true,
ComputeChecksums: true,
}
)
// RawPacket represents a raw packet or one encoded by Encoder
type RawPacket struct {
Data []byte
}
type Encoder struct {
// buf is reusable because SerializeLayers calls the Clear method before each encoding
buf gopacket.SerializeBuffer
}
func NewEncoder() *Encoder {
return &Encoder{
buf: gopacket.NewSerializeBuffer(),
}
}
func (e Encoder) Encode(packet Packet) (RawPacket, error) {
encodedLayers, err := packet.EncodeLayers()
if err != nil {
return RawPacket{}, err
}
if err := gopacket.SerializeLayers(e.buf, serializeOpts, encodedLayers...); err != nil {
return RawPacket{}, err
}
return RawPacket{
Data: e.buf.Bytes(),
}, nil
}
+190
View File
@@ -0,0 +1,190 @@
package packet
import (
"context"
"errors"
"fmt"
"net/netip"
"sync"
"sync/atomic"
"time"
)
var (
ErrFunnelNotFound = errors.New("funnel not found")
)
// Funnel is an abstraction to pipe from 1 src to 1 or more destinations
type Funnel interface {
// SendToDst sends a raw packet to a destination
SendToDst(dst netip.Addr, pk RawPacket) error
// ReturnToSrc returns a raw packet to the source
ReturnToSrc(pk RawPacket) error
// LastActive returns the last time SendToDst or ReturnToSrc is called
LastActive() time.Time
// Close closes the funnel. Further call to SendToDst or ReturnToSrc should return an error
Close() error
// Equal compares if 2 funnels are equivalent
Equal(other Funnel) bool
}
// FunnelUniPipe is a unidirectional pipe for sending raw packets
type FunnelUniPipe interface {
// SendPacket sends a packet to/from the funnel. It must not modify the packet,
// and after return it must not read the packet
SendPacket(dst netip.Addr, pk RawPacket) error
Close() error
}
// RawPacketFunnel is an implementation of Funnel that sends raw packets. It can be embedded in other structs to
// satisfy the Funnel interface.
type RawPacketFunnel struct {
Src netip.Addr
// last active unix time. Unit is seconds
lastActive int64
sendPipe FunnelUniPipe
returnPipe FunnelUniPipe
}
func NewRawPacketFunnel(src netip.Addr, sendPipe, returnPipe FunnelUniPipe) *RawPacketFunnel {
return &RawPacketFunnel{
Src: src,
lastActive: time.Now().Unix(),
sendPipe: sendPipe,
returnPipe: returnPipe,
}
}
func (rpf *RawPacketFunnel) SendToDst(dst netip.Addr, pk RawPacket) error {
rpf.updateLastActive()
return rpf.sendPipe.SendPacket(dst, pk)
}
func (rpf *RawPacketFunnel) ReturnToSrc(pk RawPacket) error {
rpf.updateLastActive()
return rpf.returnPipe.SendPacket(rpf.Src, pk)
}
func (rpf *RawPacketFunnel) updateLastActive() {
atomic.StoreInt64(&rpf.lastActive, time.Now().Unix())
}
func (rpf *RawPacketFunnel) LastActive() time.Time {
lastActive := atomic.LoadInt64(&rpf.lastActive)
return time.Unix(lastActive, 0)
}
func (rpf *RawPacketFunnel) Close() error {
sendPipeErr := rpf.sendPipe.Close()
returnPipeErr := rpf.returnPipe.Close()
if sendPipeErr != nil {
return sendPipeErr
}
if returnPipeErr != nil {
return returnPipeErr
}
return nil
}
func (rpf *RawPacketFunnel) Equal(other Funnel) bool {
otherRawFunnel, ok := other.(*RawPacketFunnel)
if !ok {
return false
}
if rpf.Src != otherRawFunnel.Src {
return false
}
if rpf.sendPipe != otherRawFunnel.sendPipe {
return false
}
if rpf.returnPipe != otherRawFunnel.returnPipe {
return false
}
return true
}
// FunnelID represents a key type that can be used by FunnelTracker
type FunnelID interface {
// Type returns the name of the type that implements the FunnelID
Type() string
fmt.Stringer
}
// FunnelTracker tracks funnel from the perspective of eyeball to origin
type FunnelTracker struct {
lock sync.RWMutex
funnels map[FunnelID]Funnel
}
func NewFunnelTracker() *FunnelTracker {
return &FunnelTracker{
funnels: make(map[FunnelID]Funnel),
}
}
func (ft *FunnelTracker) ScheduleCleanup(ctx context.Context, idleTimeout time.Duration) {
checkIdleTicker := time.NewTicker(idleTimeout)
defer checkIdleTicker.Stop()
for {
select {
case <-ctx.Done():
return
case <-checkIdleTicker.C:
ft.cleanup(idleTimeout)
}
}
}
func (ft *FunnelTracker) cleanup(idleTimeout time.Duration) {
ft.lock.Lock()
defer ft.lock.Unlock()
now := time.Now()
for id, funnel := range ft.funnels {
lastActive := funnel.LastActive()
if now.After(lastActive.Add(idleTimeout)) {
funnel.Close()
delete(ft.funnels, id)
}
}
}
func (ft *FunnelTracker) Get(id FunnelID) (Funnel, bool) {
ft.lock.RLock()
defer ft.lock.RUnlock()
funnel, ok := ft.funnels[id]
return funnel, ok
}
// Registers a funnel. It replaces the current funnel.
func (ft *FunnelTracker) GetOrRegister(id FunnelID, newFunnelFunc func() (Funnel, error)) (funnel Funnel, new bool, err error) {
ft.lock.Lock()
defer ft.lock.Unlock()
currentFunnel, exists := ft.funnels[id]
if exists {
return currentFunnel, false, nil
}
newFunnel, err := newFunnelFunc()
if err != nil {
return nil, false, err
}
ft.funnels[id] = newFunnel
return newFunnel, true, nil
}
// Unregisters a funnel if the funnel equals to the current funnel
func (ft *FunnelTracker) Unregister(id FunnelID, funnel Funnel) (deleted bool) {
ft.lock.Lock()
defer ft.lock.Unlock()
currentFunnel, exists := ft.funnels[id]
if !exists {
return true
}
if currentFunnel.Equal(funnel) {
currentFunnel.Close()
delete(ft.funnels, id)
return true
}
return false
}
+16
View File
@@ -0,0 +1,16 @@
package packet
import "net/netip"
type mockFunnelUniPipe struct {
uniPipe chan RawPacket
}
func (mfui *mockFunnelUniPipe) SendPacket(dst netip.Addr, pk RawPacket) error {
mfui.uniPipe <- pk
return nil
}
func (mfui *mockFunnelUniPipe) Close() error {
return nil
}
+211
View File
@@ -0,0 +1,211 @@
package packet
import (
"encoding/binary"
"fmt"
"net/netip"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
ipv4MinHeaderLen = 20
ipv6HeaderLen = 40
ipv4MinMTU = 576
ipv6MinMTU = 1280
icmpHeaderLen = 8
// https://www.rfc-editor.org/rfc/rfc792 and https://datatracker.ietf.org/doc/html/rfc4443#section-3.3 define 2 codes.
// 0 = ttl exceed in transit, 1 = fragment reassembly time exceeded
icmpTTLExceedInTransitCode = 0
DefaultTTL uint8 = 255
pseudoHeaderLen = 40
)
// Packet represents an IP packet or a packet that is encapsulated by IP
type Packet interface {
// IPLayer returns the IP of the packet
IPLayer() *IP
// EncodeLayers returns the layers that make up this packet. They can be passed to an Encoder to serialize into RawPacket
EncodeLayers() ([]gopacket.SerializableLayer, error)
}
// IP represents a generic IP packet. It can be embedded in more specific IP protocols
type IP struct {
Src netip.Addr
Dst netip.Addr
Protocol layers.IPProtocol
TTL uint8
}
func newIPv4(ipLayer *layers.IPv4) (*IP, error) {
src, ok := netip.AddrFromSlice(ipLayer.SrcIP)
if !ok {
return nil, fmt.Errorf("cannot convert source IP %s to netip.Addr", ipLayer.SrcIP)
}
dst, ok := netip.AddrFromSlice(ipLayer.DstIP)
if !ok {
return nil, fmt.Errorf("cannot convert source IP %s to netip.Addr", ipLayer.DstIP)
}
return &IP{
Src: src,
Dst: dst,
Protocol: ipLayer.Protocol,
TTL: ipLayer.TTL,
}, nil
}
func newIPv6(ipLayer *layers.IPv6) (*IP, error) {
src, ok := netip.AddrFromSlice(ipLayer.SrcIP)
if !ok {
return nil, fmt.Errorf("cannot convert source IP %s to netip.Addr", ipLayer.SrcIP)
}
dst, ok := netip.AddrFromSlice(ipLayer.DstIP)
if !ok {
return nil, fmt.Errorf("cannot convert source IP %s to netip.Addr", ipLayer.DstIP)
}
return &IP{
Src: src,
Dst: dst,
Protocol: ipLayer.NextHeader,
TTL: ipLayer.HopLimit,
}, nil
}
func (ip *IP) IPLayer() *IP {
return ip
}
func (ip *IP) isIPv4() bool {
return ip.Src.Is4()
}
func (ip *IP) EncodeLayers() ([]gopacket.SerializableLayer, error) {
if ip.isIPv4() {
return []gopacket.SerializableLayer{
&layers.IPv4{
Version: 4,
SrcIP: ip.Src.AsSlice(),
DstIP: ip.Dst.AsSlice(),
Protocol: layers.IPProtocol(ip.Protocol),
TTL: ip.TTL,
},
}, nil
} else {
return []gopacket.SerializableLayer{
&layers.IPv6{
Version: 6,
SrcIP: ip.Src.AsSlice(),
DstIP: ip.Dst.AsSlice(),
NextHeader: layers.IPProtocol(ip.Protocol),
HopLimit: ip.TTL,
},
}, nil
}
}
// ICMP represents is an IP packet + ICMP message
type ICMP struct {
*IP
*icmp.Message
}
func (i *ICMP) EncodeLayers() ([]gopacket.SerializableLayer, error) {
ipLayers, err := i.IP.EncodeLayers()
if err != nil {
return nil, err
}
var serializedPsh []byte = nil
if i.Protocol == layers.IPProtocolICMPv6 {
psh := &PseudoHeader{
SrcIP: i.Src.As16(),
DstIP: i.Dst.As16(),
// i.Marshal re-calculates the UpperLayerPacketLength
UpperLayerPacketLength: 0,
NextHeader: uint8(i.Protocol),
}
serializedPsh = psh.Marshal()
}
msg, err := i.Marshal(serializedPsh)
if err != nil {
return nil, err
}
icmpLayer := gopacket.Payload(msg)
return append(ipLayers, icmpLayer), nil
}
// https://www.rfc-editor.org/rfc/rfc2460#section-8.1
type PseudoHeader struct {
SrcIP [16]byte
DstIP [16]byte
UpperLayerPacketLength uint32
zero [3]byte
NextHeader uint8
}
func (ph *PseudoHeader) Marshal() []byte {
buf := make([]byte, pseudoHeaderLen)
index := 0
copy(buf, ph.SrcIP[:])
index += 16
copy(buf[index:], ph.DstIP[:])
index += 16
binary.BigEndian.PutUint32(buf[index:], ph.UpperLayerPacketLength)
index += 4
copy(buf[index:], ph.zero[:])
buf[pseudoHeaderLen-1] = ph.NextHeader
return buf
}
func NewICMPTTLExceedPacket(originalIP *IP, originalPacket RawPacket, routerIP netip.Addr) *ICMP {
var (
protocol layers.IPProtocol
icmpType icmp.Type
)
if originalIP.Dst.Is4() {
protocol = layers.IPProtocolICMPv4
icmpType = ipv4.ICMPTypeTimeExceeded
} else {
protocol = layers.IPProtocolICMPv6
icmpType = ipv6.ICMPTypeTimeExceeded
}
return &ICMP{
IP: &IP{
Src: routerIP,
Dst: originalIP.Src,
Protocol: protocol,
TTL: DefaultTTL,
},
Message: &icmp.Message{
Type: icmpType,
Code: icmpTTLExceedInTransitCode,
Body: &icmp.TimeExceeded{
Data: originalDatagram(originalPacket, originalIP.Dst.Is4()),
},
},
}
}
// originalDatagram returns a slice of the original datagram for ICMP error messages
// https://www.rfc-editor.org/rfc/rfc1812#section-4.3.2.3 suggests to copy as much without exceeding 576 bytes.
// https://datatracker.ietf.org/doc/html/rfc4443#section-3.3 suggests to copy as much without exceeding 1280 bytes
func originalDatagram(originalPacket RawPacket, isIPv4 bool) []byte {
var upperBound int
if isIPv4 {
upperBound = ipv4MinMTU - ipv4MinHeaderLen - icmpHeaderLen
if upperBound > len(originalPacket.Data) {
upperBound = len(originalPacket.Data)
}
} else {
upperBound = ipv6MinMTU - ipv6HeaderLen - icmpHeaderLen
if upperBound > len(originalPacket.Data) {
upperBound = len(originalPacket.Data)
}
}
return originalPacket.Data[:upperBound]
}
+197
View File
@@ -0,0 +1,197 @@
package packet
import (
"bytes"
"net/netip"
"testing"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
func TestNewICMPTTLExceedPacket(t *testing.T) {
ipv4Packet := IP{
Src: netip.MustParseAddr("192.168.1.1"),
Dst: netip.MustParseAddr("10.0.0.1"),
Protocol: layers.IPProtocolICMPv4,
TTL: 0,
}
icmpV4Packet := ICMP{
IP: &ipv4Packet,
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: 25821,
Seq: 58129,
Data: []byte("test ttl=0"),
},
},
}
assertTTLExceedPacket(t, &icmpV4Packet)
icmpV4Packet.Body = &icmp.Echo{
ID: 3487,
Seq: 19183,
Data: make([]byte, ipv4MinMTU),
}
assertTTLExceedPacket(t, &icmpV4Packet)
ipv6Packet := IP{
Src: netip.MustParseAddr("fd51:2391:523:f4ee::1"),
Dst: netip.MustParseAddr("fd51:2391:697:f4ee::2"),
Protocol: layers.IPProtocolICMPv6,
TTL: 0,
}
icmpV6Packet := ICMP{
IP: &ipv6Packet,
Message: &icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: 25821,
Seq: 58129,
Data: []byte("test ttl=0"),
},
},
}
assertTTLExceedPacket(t, &icmpV6Packet)
icmpV6Packet.Body = &icmp.Echo{
ID: 1497,
Seq: 39284,
Data: make([]byte, ipv6MinMTU),
}
assertTTLExceedPacket(t, &icmpV6Packet)
}
func assertTTLExceedPacket(t *testing.T, pk *ICMP) {
encoder := NewEncoder()
rawPacket, err := encoder.Encode(pk)
require.NoError(t, err)
minMTU := ipv4MinMTU
headerLen := ipv4MinHeaderLen
routerIP := netip.MustParseAddr("172.16.0.3")
if pk.Dst.Is6() {
minMTU = ipv6MinMTU
headerLen = ipv6HeaderLen
routerIP = netip.MustParseAddr("fd51:2391:697:f4ee::3")
}
ttlExceedPacket := NewICMPTTLExceedPacket(pk.IP, rawPacket, routerIP)
require.Equal(t, routerIP, ttlExceedPacket.Src)
require.Equal(t, pk.Src, ttlExceedPacket.Dst)
require.Equal(t, pk.Protocol, ttlExceedPacket.Protocol)
require.Equal(t, DefaultTTL, ttlExceedPacket.TTL)
timeExceed, ok := ttlExceedPacket.Body.(*icmp.TimeExceeded)
require.True(t, ok)
if len(rawPacket.Data) > minMTU {
require.True(t, bytes.Equal(timeExceed.Data, rawPacket.Data[:minMTU-headerLen-icmpHeaderLen]))
} else {
require.True(t, bytes.Equal(timeExceed.Data, rawPacket.Data))
}
rawTTLExceedPacket, err := encoder.Encode(ttlExceedPacket)
require.NoError(t, err)
if len(rawPacket.Data) > minMTU {
require.Len(t, rawTTLExceedPacket.Data, minMTU)
} else {
require.Len(t, rawTTLExceedPacket.Data, headerLen+icmpHeaderLen+len(rawPacket.Data))
require.True(t, bytes.Equal(rawPacket.Data, rawTTLExceedPacket.Data[headerLen+icmpHeaderLen:]))
}
decoder := NewICMPDecoder()
decodedPacket, err := decoder.Decode(rawTTLExceedPacket)
require.NoError(t, err)
assertICMPChecksum(t, decodedPacket)
}
func assertICMPChecksum(t *testing.T, icmpPacket *ICMP) {
buf := gopacket.NewSerializeBuffer()
if icmpPacket.Protocol == layers.IPProtocolICMPv4 {
icmpv4 := layers.ICMPv4{
TypeCode: layers.CreateICMPv4TypeCode(uint8(icmpPacket.Type.(ipv4.ICMPType)), uint8(icmpPacket.Code)),
}
switch body := icmpPacket.Body.(type) {
case *icmp.Echo:
icmpv4.Id = uint16(body.ID)
icmpv4.Seq = uint16(body.Seq)
payload := gopacket.Payload(body.Data)
require.NoError(t, payload.SerializeTo(buf, serializeOpts))
default:
require.NoError(t, serializeICMPAsPayload(icmpPacket.Message, buf))
}
// SerializeTo sets the checksum in icmpv4
require.NoError(t, icmpv4.SerializeTo(buf, serializeOpts))
require.Equal(t, icmpv4.Checksum, uint16(icmpPacket.Checksum))
} else {
switch body := icmpPacket.Body.(type) {
case *icmp.Echo:
payload := gopacket.Payload(body.Data)
require.NoError(t, payload.SerializeTo(buf, serializeOpts))
echo := layers.ICMPv6Echo{
Identifier: uint16(body.ID),
SeqNumber: uint16(body.Seq),
}
require.NoError(t, echo.SerializeTo(buf, serializeOpts))
default:
require.NoError(t, serializeICMPAsPayload(icmpPacket.Message, buf))
}
icmpv6 := layers.ICMPv6{
TypeCode: layers.CreateICMPv6TypeCode(uint8(icmpPacket.Type.(ipv6.ICMPType)), uint8(icmpPacket.Code)),
}
ipLayer := layers.IPv6{
Version: 6,
SrcIP: icmpPacket.Src.AsSlice(),
DstIP: icmpPacket.Dst.AsSlice(),
NextHeader: icmpPacket.Protocol,
HopLimit: icmpPacket.TTL,
}
require.NoError(t, icmpv6.SetNetworkLayerForChecksum(&ipLayer))
// SerializeTo sets the checksum in icmpv4
require.NoError(t, icmpv6.SerializeTo(buf, serializeOpts))
require.Equal(t, icmpv6.Checksum, uint16(icmpPacket.Checksum))
}
}
func serializeICMPAsPayload(message *icmp.Message, buf gopacket.SerializeBuffer) error {
serializedBody, err := message.Body.Marshal(message.Type.Protocol())
if err != nil {
return err
}
payload := gopacket.Payload(serializedBody)
return payload.SerializeTo(buf, serializeOpts)
}
func TestChecksum(t *testing.T) {
data := []byte{0x63, 0x2c, 0x49, 0xd6, 0x00, 0x0d, 0xc1, 0xda}
pk := ICMP{
IP: &IP{
Src: netip.MustParseAddr("2606:4700:110:89c1:c63a:861:e08c:b049"),
Dst: netip.MustParseAddr("fde8:b693:d420:109b::2"),
Protocol: layers.IPProtocolICMPv6,
TTL: 3,
},
Message: &icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: 0x20a7,
Seq: 8,
Data: data,
},
},
}
encoder := NewEncoder()
encoded, err := encoder.Encode(&pk)
require.NoError(t, err)
decoder := NewICMPDecoder()
decoded, err := decoder.Decode(encoded)
require.Equal(t, 0xff96, decoded.Checksum)
}
+108
View File
@@ -0,0 +1,108 @@
package packet
import (
"context"
"net/netip"
"github.com/rs/zerolog"
)
// ICMPRouter sends ICMP messages and listens for their responses
type ICMPRouter interface {
// Serve starts listening for responses to the requests until context is done
Serve(ctx context.Context) error
// Request sends an ICMP message. Implementations should not modify pk after the function returns.
Request(pk *ICMP, responder FunnelUniPipe) error
}
// Upstream of raw packets
type Upstream interface {
// ReceivePacket waits for the next raw packet from upstream
ReceivePacket(ctx context.Context) (RawPacket, error)
}
// Router routes packets between Upstream and ICMPRouter. Currently it rejects all other type of ICMP packets
type Router struct {
upstream Upstream
returnPipe FunnelUniPipe
globalConfig *GlobalRouterConfig
logger *zerolog.Logger
checkRouterEnabledFunc func() bool
}
// GlobalRouterConfig is the configuration shared by all instance of Router.
type GlobalRouterConfig struct {
ICMPRouter ICMPRouter
IPv4Src netip.Addr
IPv6Src netip.Addr
Zone string
}
func NewRouter(globalConfig *GlobalRouterConfig, upstream Upstream, returnPipe FunnelUniPipe, logger *zerolog.Logger, checkRouterEnabledFunc func() bool) *Router {
return &Router{
upstream: upstream,
returnPipe: returnPipe,
globalConfig: globalConfig,
logger: logger,
checkRouterEnabledFunc: checkRouterEnabledFunc,
}
}
func (r *Router) Serve(ctx context.Context) error {
icmpDecoder := NewICMPDecoder()
encoder := NewEncoder()
for {
rawPacket, err := r.upstream.ReceivePacket(ctx)
if err != nil {
return err
}
// Drop packets if ICMPRouter wasn't created
if r.globalConfig == nil {
continue
}
if enabled := r.checkRouterEnabledFunc(); !enabled {
continue
}
icmpPacket, err := icmpDecoder.Decode(rawPacket)
if err != nil {
r.logger.Err(err).Msg("Failed to decode ICMP packet from quic datagram")
continue
}
if icmpPacket.TTL <= 1 {
if err := r.sendTTLExceedMsg(icmpPacket, rawPacket, encoder); err != nil {
r.logger.Err(err).Msg("Failed to return ICMP TTL exceed error")
}
continue
}
icmpPacket.TTL--
if err := r.globalConfig.ICMPRouter.Request(icmpPacket, r.returnPipe); err != nil {
r.logger.Err(err).
Str("src", icmpPacket.Src.String()).
Str("dst", icmpPacket.Dst.String()).
Interface("type", icmpPacket.Type).
Msg("Failed to send ICMP packet")
continue
}
}
}
func (r *Router) sendTTLExceedMsg(pk *ICMP, rawPacket RawPacket, encoder *Encoder) error {
var srcIP netip.Addr
if pk.Dst.Is4() {
srcIP = r.globalConfig.IPv4Src
} else {
srcIP = r.globalConfig.IPv6Src
}
ttlExceedPacket := NewICMPTTLExceedPacket(pk.IP, rawPacket, srcIP)
encodedTTLExceed, err := encoder.Encode(ttlExceedPacket)
if err != nil {
return err
}
return r.returnPipe.SendPacket(pk.Src, encodedTTLExceed)
}
+223
View File
@@ -0,0 +1,223 @@
package packet
import (
"bytes"
"context"
"fmt"
"net/netip"
"sync/atomic"
"testing"
"time"
"github.com/google/gopacket/layers"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
var (
noopLogger = zerolog.Nop()
packetConfig = &GlobalRouterConfig{
ICMPRouter: &mockICMPRouter{},
IPv4Src: netip.MustParseAddr("172.16.0.1"),
IPv6Src: netip.MustParseAddr("fd51:2391:523:f4ee::1"),
}
)
func TestRouterReturnTTLExceed(t *testing.T) {
upstream := &mockUpstream{
source: make(chan RawPacket),
}
returnPipe := &mockFunnelUniPipe{
uniPipe: make(chan RawPacket),
}
routerEnabled := &routerEnabledChecker{}
routerEnabled.set(true)
router := NewRouter(packetConfig, upstream, returnPipe, &noopLogger, routerEnabled.isEnabled)
ctx, cancel := context.WithCancel(context.Background())
routerStopped := make(chan struct{})
go func() {
router.Serve(ctx)
close(routerStopped)
}()
pk := ICMP{
IP: &IP{
Src: netip.MustParseAddr("192.168.1.1"),
Dst: netip.MustParseAddr("10.0.0.1"),
Protocol: layers.IPProtocolICMPv4,
TTL: 1,
},
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: 12481,
Seq: 8036,
Data: []byte("TTL exceed"),
},
},
}
assertTTLExceed(t, &pk, router.globalConfig.IPv4Src, upstream, returnPipe)
pk = ICMP{
IP: &IP{
Src: netip.MustParseAddr("fd51:2391:523:f4ee::1"),
Dst: netip.MustParseAddr("fd51:2391:697:f4ee::2"),
Protocol: layers.IPProtocolICMPv6,
TTL: 1,
},
Message: &icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: 42583,
Seq: 7039,
Data: []byte("TTL exceed"),
},
},
}
assertTTLExceed(t, &pk, router.globalConfig.IPv6Src, upstream, returnPipe)
cancel()
<-routerStopped
}
func TestRouterCheckEnabled(t *testing.T) {
upstream := &mockUpstream{
source: make(chan RawPacket),
}
returnPipe := &mockFunnelUniPipe{
uniPipe: make(chan RawPacket),
}
routerEnabled := &routerEnabledChecker{}
router := NewRouter(packetConfig, upstream, returnPipe, &noopLogger, routerEnabled.isEnabled)
ctx, cancel := context.WithCancel(context.Background())
routerStopped := make(chan struct{})
go func() {
router.Serve(ctx)
close(routerStopped)
}()
pk := ICMP{
IP: &IP{
Src: netip.MustParseAddr("192.168.1.1"),
Dst: netip.MustParseAddr("10.0.0.1"),
Protocol: layers.IPProtocolICMPv4,
TTL: 1,
},
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: 12481,
Seq: 8036,
Data: []byte(t.Name()),
},
},
}
// router is disabled
require.NoError(t, upstream.send(&pk))
select {
case <-time.After(time.Millisecond * 10):
case <-returnPipe.uniPipe:
t.Error("Unexpected reply when router is disabled")
}
routerEnabled.set(true)
// router is enabled, expects reply
require.NoError(t, upstream.send(&pk))
<-returnPipe.uniPipe
routerEnabled.set(false)
// router is disabled
require.NoError(t, upstream.send(&pk))
select {
case <-time.After(time.Millisecond * 10):
case <-returnPipe.uniPipe:
t.Error("Unexpected reply when router is disabled")
}
cancel()
<-routerStopped
}
func assertTTLExceed(t *testing.T, originalPacket *ICMP, expectedSrc netip.Addr, upstream *mockUpstream, returnPipe *mockFunnelUniPipe) {
encoder := NewEncoder()
rawPacket, err := encoder.Encode(originalPacket)
require.NoError(t, err)
upstream.source <- rawPacket
resp := <-returnPipe.uniPipe
decoder := NewICMPDecoder()
decoded, err := decoder.Decode(resp)
require.NoError(t, err)
require.Equal(t, expectedSrc, decoded.Src)
require.Equal(t, originalPacket.Src, decoded.Dst)
require.Equal(t, originalPacket.Protocol, decoded.Protocol)
require.Equal(t, DefaultTTL, decoded.TTL)
if originalPacket.Dst.Is4() {
require.Equal(t, ipv4.ICMPTypeTimeExceeded, decoded.Type)
} else {
require.Equal(t, ipv6.ICMPTypeTimeExceeded, decoded.Type)
}
require.Equal(t, 0, decoded.Code)
assertICMPChecksum(t, decoded)
timeExceed, ok := decoded.Body.(*icmp.TimeExceeded)
require.True(t, ok)
require.True(t, bytes.Equal(rawPacket.Data, timeExceed.Data))
}
type mockUpstream struct {
source chan RawPacket
}
func (ms *mockUpstream) send(pk Packet) error {
encoder := NewEncoder()
rawPacket, err := encoder.Encode(pk)
if err != nil {
return err
}
ms.source <- rawPacket
return nil
}
func (ms *mockUpstream) ReceivePacket(ctx context.Context) (RawPacket, error) {
select {
case <-ctx.Done():
return RawPacket{}, ctx.Err()
case pk := <-ms.source:
return pk, nil
}
}
type mockICMPRouter struct{}
func (mir mockICMPRouter) Serve(ctx context.Context) error {
return fmt.Errorf("Serve not implemented by mockICMPRouter")
}
func (mir mockICMPRouter) Request(pk *ICMP, responder FunnelUniPipe) error {
return fmt.Errorf("Request not implemented by mockICMPRouter")
}
type routerEnabledChecker struct {
enabled uint32
}
func (rec *routerEnabledChecker) isEnabled() bool {
if atomic.LoadUint32(&rec.enabled) == 0 {
return false
}
return true
}
func (rec *routerEnabledChecker) set(enabled bool) {
if enabled {
atomic.StoreUint32(&rec.enabled, 1)
} else {
atomic.StoreUint32(&rec.enabled, 0)
}
}
+8
View File
@@ -0,0 +1,8 @@
package packet
import "github.com/google/uuid"
type Session struct {
ID uuid.UUID
Payload []byte
}
+43 -33
View File
@@ -1,7 +1,6 @@
package proxy
import (
"bufio"
"context"
"fmt"
"io"
@@ -29,6 +28,8 @@ const (
LogFieldRule = "ingressRule"
LogFieldOriginService = "originService"
LogFieldFlowID = "flowID"
trailerHeaderName = "Trailer"
)
// Proxy represents a means to Proxy between cloudflared and the origin services.
@@ -59,6 +60,21 @@ func NewOriginProxy(
return proxy
}
func (p *Proxy) applyIngressMiddleware(rule *ingress.Rule, r *http.Request, w connection.ResponseWriter) (error, bool) {
for _, handler := range rule.Handlers {
result, err := handler.Handle(r.Context(), r)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error while processing middleware handler %s", handler.Name())), false
}
if result.ShouldFilterRequest {
w.WriteRespHeaders(result.StatusCode, nil)
return fmt.Errorf("request filtered by middleware handler (%s) due to: %s", handler.Name(), result.Reason), true
}
}
return nil, true
}
// ProxyHTTP further depends on ingress rules to establish a connection with the origin service. This may be
// a simple roundtrip or a tcp/websocket dial depending on ingres rule setup.
func (p *Proxy) ProxyHTTP(
@@ -85,6 +101,13 @@ func (p *Proxy) ProxyHTTP(
p.logRequest(req, logFields)
ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum))
ruleSpan.End()
if err, applied := p.applyIngressMiddleware(rule, req, w); err != nil {
if applied {
p.log.Error().Msg(err.Error())
return nil
}
return err
}
switch originProxy := rule.Service.(type) {
case ingress.HTTPOriginProxy:
@@ -207,15 +230,16 @@ func (p *Proxy) proxyHTTPRequest(
tracing.EndWithStatusCode(ttfbSpan, resp.StatusCode)
defer resp.Body.Close()
// resp headers can be nil
if resp.Header == nil {
resp.Header = make(http.Header)
headers := make(http.Header, len(resp.Header))
// copy headers
for k, v := range resp.Header {
headers[k] = v
}
// Add spans to response header (if available)
tr.AddSpans(resp.Header)
tr.AddSpans(headers)
err = w.WriteRespHeaders(resp.StatusCode, resp.Header)
err = w.WriteRespHeaders(resp.StatusCode, headers)
if err != nil {
return errors.Wrap(err, "Error writing response header")
}
@@ -236,12 +260,10 @@ func (p *Proxy) proxyHTTPRequest(
return nil
}
if connection.IsServerSentEvent(resp.Header) {
p.log.Debug().Msg("Detected Server-Side Events from Origin")
p.writeEventStream(w, resp.Body)
} else {
_, _ = cfio.Copy(w, resp.Body)
}
_, _ = cfio.Copy(w, resp.Body)
// copy trailers
copyTrailers(w, resp)
p.logOriginResponse(resp, fields)
return nil
@@ -256,7 +278,7 @@ func (p *Proxy) proxyStream(
connectionProxy ingress.StreamBasedOriginProxy,
) error {
ctx := tr.Context
_, connectSpan := tr.Tracer().Start(ctx, "stream_connect")
_, connectSpan := tr.Tracer().Start(ctx, "stream-connect")
originConn, err := connectionProxy.EstablishConnection(ctx, dest)
if err != nil {
tracing.EndWithErrorStatus(connectSpan, err)
@@ -296,26 +318,6 @@ func (wr *bidirectionalStream) Write(p []byte) (n int, err error) {
return wr.writer.Write(p)
}
func (p *Proxy) writeEventStream(w connection.ResponseWriter, respBody io.ReadCloser) {
reader := bufio.NewReader(respBody)
for {
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
}
}
}
func (p *Proxy) appendTagHeaders(r *http.Request) {
for _, tag := range p.tags {
r.Header.Add(TagHeaderNamePrefix+tag.Name, tag.Value)
@@ -329,6 +331,14 @@ type logFields struct {
flowID string
}
func copyTrailers(w connection.ResponseWriter, response *http.Response) {
for trailerHeader, trailerValues := range response.Trailer {
for _, trailerValue := range trailerValues {
w.AddTrailer(trailerHeader, trailerValue)
}
}
}
func (p *Proxy) logRequest(r *http.Request, fields logFields) {
if fields.cfRay != "" {
p.log.Debug().Msgf("CF-RAY: %s %s %s %s", fields.cfRay, r.Method, r.URL, r.Proto)
+32 -15
View File
@@ -22,6 +22,8 @@ import (
"github.com/urfave/cli/v2"
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/cfio"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/hello"
@@ -62,6 +64,10 @@ func (w *mockHTTPRespWriter) WriteRespHeaders(status int, header http.Header) er
return nil
}
func (w *mockHTTPRespWriter) AddTrailer(trailerName, trailerValue string) {
// do nothing
}
func (w *mockHTTPRespWriter) Read(data []byte) (int, error) {
return 0, fmt.Errorf("mockHTTPRespWriter doesn't implement io.Reader")
}
@@ -117,10 +123,17 @@ func newMockSSERespWriter() *mockSSERespWriter {
}
func (w *mockSSERespWriter) Write(data []byte) (int, error) {
w.writeNotification <- data
newData := make([]byte, len(data))
copy(newData, data)
w.writeNotification <- newData
return len(data), nil
}
func (w *mockSSERespWriter) WriteString(str string) (int, error) {
return w.Write([]byte(str))
}
func (w *mockSSERespWriter) ReadBytes() []byte {
return <-w.writeNotification
}
@@ -147,7 +160,6 @@ 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()
}
@@ -256,11 +268,8 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) {
for i := 0; i < pushCount; i++ {
line := responseWriter.ReadBytes()
expect := fmt.Sprintf("%d\n", i)
expect := fmt.Sprintf("%d\n\n", i)
require.Equal(t, []byte(expect), line, fmt.Sprintf("Expect to read %v, got %v", expect, line))
line = responseWriter.ReadBytes()
require.Equal(t, []byte("\n"), line, fmt.Sprintf("Expect to read '\n', got %v", line))
}
cancel()
@@ -270,17 +279,15 @@ 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()
func TestProxySSEAllData(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)
// responseWriter uses an unbuffered channel, so we call in a different go-routine
go cfio.Copy(responseWriter, eyeballReader)
result := string(<-responseWriter.writeNotification)
require.Equal(t, "data\r\r", result)
}
result := string(<-responseWriter.writeNotification)
require.Equal(t, "data\r\r", result)
}
func TestProxyMultipleOrigins(t *testing.T) {
@@ -525,6 +532,7 @@ func TestConnections(t *testing.T) {
},
want: want{
message: []byte("echo-test2"),
headers: http.Header{},
},
},
{
@@ -544,6 +552,7 @@ func TestConnections(t *testing.T) {
message: []byte("echo-test3"),
// We expect no headers here because they are sent back via
// the stream.
headers: http.Header{},
},
},
{
@@ -823,6 +832,10 @@ func (w *wsRespWriter) WriteRespHeaders(status int, header http.Header) error {
return nil
}
func (w *wsRespWriter) AddTrailer(trailerName, trailerValue string) {
// do nothing
}
// respHeaders is a test function to read respHeaders
func (w *wsRespWriter) headers() http.Header {
// Removing indeterminstic header because it cannot be asserted.
@@ -850,6 +863,10 @@ func (m *mockTCPRespWriter) Write(p []byte) (n int, err error) {
return m.w.Write(p)
}
func (w *mockTCPRespWriter) AddTrailer(trailerName, trailerValue string) {
// do nothing
}
func (m *mockTCPRespWriter) WriteRespHeaders(status int, header http.Header) error {
m.responseHeaders = header
m.code = status
+14 -18
View File
@@ -8,21 +8,18 @@ import (
"github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/packet"
)
const (
sessionIDLen = len(uuid.UUID{})
)
type SessionDatagram struct {
ID uuid.UUID
Payload []byte
}
type BaseDatagramMuxer interface {
// MuxSession suffix the session ID to the payload so the other end of the QUIC connection can demultiplex the
// payload from multiple datagram sessions
MuxSession(sessionID uuid.UUID, payload []byte) error
// SendToSession suffix the session ID to the payload so the other end of the QUIC connection can demultiplex the
// payload from multiple datagram sessions.
SendToSession(session *packet.Session) error
// ServeReceive starts a loop to receive datagrams from the QUIC connection
ServeReceive(ctx context.Context) error
}
@@ -30,10 +27,10 @@ type BaseDatagramMuxer interface {
type DatagramMuxer struct {
session quic.Connection
logger *zerolog.Logger
demuxChan chan<- *SessionDatagram
demuxChan chan<- *packet.Session
}
func NewDatagramMuxer(quicSession quic.Connection, log *zerolog.Logger, demuxChan chan<- *SessionDatagram) *DatagramMuxer {
func NewDatagramMuxer(quicSession quic.Connection, log *zerolog.Logger, demuxChan chan<- *packet.Session) *DatagramMuxer {
logger := log.With().Uint8("datagramVersion", 1).Logger()
return &DatagramMuxer{
session: quicSession,
@@ -47,13 +44,12 @@ func (dm *DatagramMuxer) mtu() int {
return maxDatagramPayloadSize
}
func (dm *DatagramMuxer) MuxSession(sessionID uuid.UUID, payload []byte) error {
if len(payload) > dm.mtu() {
// TODO: TUN-5302 return ICMP packet too big message
// drop packet for now, eventually reply with ICMP for PMTUD
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(payload), dm.mtu())
func (dm *DatagramMuxer) SendToSession(session *packet.Session) error {
if len(session.Payload) > dm.mtu() {
packetTooBigDropped.Inc()
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(session.Payload), dm.mtu())
}
payloadWithMetadata, err := suffixSessionID(sessionID, payload)
payloadWithMetadata, err := SuffixSessionID(session.ID, session.Payload)
if err != nil {
return errors.Wrap(err, "Failed to suffix session ID to datagram, it will be dropped")
}
@@ -86,7 +82,7 @@ func (dm *DatagramMuxer) demux(ctx context.Context, msg []byte) error {
if err != nil {
return err
}
sessionDatagram := SessionDatagram{
sessionDatagram := packet.Session{
ID: sessionID,
Payload: payload,
}
@@ -116,7 +112,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)
}
+72 -53
View File
@@ -1,7 +1,6 @@
package quic
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
@@ -10,14 +9,20 @@ import (
"encoding/pem"
"fmt"
"math/big"
"net/netip"
"testing"
"time"
"github.com/google/gopacket/layers"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/packet"
)
var (
@@ -26,7 +31,7 @@ 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)
@@ -45,11 +50,11 @@ func TestRemoveSessionIDError(t *testing.T) {
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)
}
@@ -57,7 +62,7 @@ func TestDatagram(t *testing.T) {
maxPayload := make([]byte, maxDatagramPayloadSize)
noPayloadSession := uuid.New()
maxPayloadSession := uuid.New()
sessionToPayload := []*SessionDatagram{
sessionToPayload := []*packet.Session{
{
ID: noPayloadSession,
Payload: make([]byte, 0),
@@ -67,15 +72,45 @@ func TestDatagram(t *testing.T) {
Payload: maxPayload,
},
}
flowPayloads := [][]byte{
maxPayload,
packets := []packet.ICMP{
{
IP: &packet.IP{
Src: netip.MustParseAddr("172.16.0.1"),
Dst: netip.MustParseAddr("192.168.0.1"),
Protocol: layers.IPProtocolICMPv4,
},
Message: &icmp.Message{
Type: ipv4.ICMPTypeTimeExceeded,
Code: 0,
Body: &icmp.TimeExceeded{
Data: []byte("original packet"),
},
},
},
{
IP: &packet.IP{
Src: netip.MustParseAddr("172.16.0.2"),
Dst: netip.MustParseAddr("192.168.0.2"),
Protocol: layers.IPProtocolICMPv4,
},
Message: &icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: 6182,
Seq: 9151,
Data: []byte("Test ICMP echo"),
},
},
},
}
testDatagram(t, 1, sessionToPayload, nil)
testDatagram(t, 2, sessionToPayload, flowPayloads)
testDatagram(t, 2, sessionToPayload, packets)
}
func testDatagram(t *testing.T, version uint8, sessionToPayloads []*SessionDatagram, packetPayloads [][]byte) {
func testDatagram(t *testing.T, version uint8, sessionToPayloads []*packet.Session, packets []packet.ICMP) {
quicConfig := &quic.Config{
KeepAlivePeriod: 5 * time.Millisecond,
EnableDatagrams: true,
@@ -95,19 +130,27 @@ func testDatagram(t *testing.T, version uint8, sessionToPayloads []*SessionDatag
return err
}
sessionDemuxChan := make(chan *SessionDatagram, 16)
sessionDemuxChan := make(chan *packet.Session, 16)
switch version {
case 1:
muxer := NewDatagramMuxer(quicSession, &logger, sessionDemuxChan)
muxer.ServeReceive(ctx)
case 2:
packetDemuxChan := make(chan []byte, len(packetPayloads))
muxer := NewDatagramMuxerV2(quicSession, &logger, sessionDemuxChan, packetDemuxChan)
muxer := NewDatagramMuxerV2(quicSession, &logger, sessionDemuxChan)
muxer.ServeReceive(ctx)
for _, expectedPayload := range packetPayloads {
require.Equal(t, expectedPayload, <-packetDemuxChan)
icmpDecoder := packet.NewICMPDecoder()
for _, pk := range packets {
received, err := muxer.ReceivePacket(ctx)
require.NoError(t, err)
receivedICMP, err := icmpDecoder.Decode(received)
require.NoError(t, err)
require.Equal(t, pk.IP, receivedICMP.IP)
require.Equal(t, pk.Type, receivedICMP.Type)
require.Equal(t, pk.Code, receivedICMP.Code)
require.Equal(t, pk.Body, receivedICMP.Body)
}
default:
return fmt.Errorf("unknown datagram version %d", version)
@@ -140,22 +183,30 @@ func testDatagram(t *testing.T, version uint8, sessionToPayloads []*SessionDatag
case 1:
muxer = NewDatagramMuxer(quicSession, &logger, nil)
case 2:
muxerV2 := NewDatagramMuxerV2(quicSession, &logger, nil, nil)
for _, payload := range packetPayloads {
require.NoError(t, muxerV2.MuxPacket(payload))
muxerV2 := NewDatagramMuxerV2(quicSession, &logger, nil)
encoder := packet.NewEncoder()
for _, pk := range packets {
encodedPacket, err := encoder.Encode(&pk)
require.NoError(t, err)
require.NoError(t, muxerV2.SendPacket(encodedPacket))
}
// Payload larger than transport MTU, should not be sent
require.Error(t, muxerV2.MuxPacket(largePayload))
require.Error(t, muxerV2.SendPacket(packet.RawPacket{
Data: largePayload,
}))
muxer = muxerV2
default:
return fmt.Errorf("unknown datagram version %d", version)
}
for _, sessionDatagram := range sessionToPayloads {
require.NoError(t, muxer.MuxSession(sessionDatagram.ID, sessionDatagram.Payload))
for _, session := range sessionToPayloads {
require.NoError(t, muxer.SendToSession(session))
}
// Payload larger than transport MTU, should not be sent
require.Error(t, muxer.MuxSession(testSessionID, largePayload))
require.Error(t, muxer.SendToSession(&packet.Session{
ID: testSessionID,
Payload: largePayload,
}))
// Wait for edge to finish receiving the messages
time.Sleep(time.Millisecond * 100)
@@ -198,35 +249,3 @@ func generateTLSConfig() *tls.Config {
NextProtos: []string{"argotunnel"},
}
}
type sessionMuxer interface {
SendToSession(sessionID uuid.UUID, payload []byte) error
}
type mockSessionReceiver struct {
expectedSessionToPayload map[uuid.UUID][]byte
receivedCount int
}
func (msr *mockSessionReceiver) ReceiveDatagram(sessionID uuid.UUID, payload []byte) error {
expectedPayload := msr.expectedSessionToPayload[sessionID]
if !bytes.Equal(expectedPayload, payload) {
return fmt.Errorf("expect %v to have payload %s, got %s", sessionID, string(expectedPayload), string(payload))
}
msr.receivedCount++
return nil
}
type mockFlowReceiver struct {
expectedPayloads [][]byte
receivedCount int
}
func (mfr *mockFlowReceiver) ReceiveFlow(payload []byte) error {
expectedPayload := mfr.expectedPayloads[mfr.receivedCount]
if !bytes.Equal(expectedPayload, payload) {
return fmt.Errorf("expect flow %d to have payload %s, got %s", mfr.receivedCount, string(expectedPayload), string(payload))
}
mfr.receivedCount++
return nil
}
+74 -48
View File
@@ -4,21 +4,28 @@ import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/packet"
)
type datagramV2Type byte
type DatagramV2Type byte
const (
udp datagramV2Type = iota
ip
DatagramTypeUDP DatagramV2Type = iota
DatagramTypeIP
)
func suffixType(b []byte, datagramType datagramV2Type) ([]byte, error) {
if len(b)+1 > MaxDatagramFrameSize {
const (
typeIDLen = 1
// Same as sessionDemuxChan capacity
packetChanCapacity = 128
)
func SuffixType(b []byte, datagramType DatagramV2Type) ([]byte, error) {
if len(b)+typeIDLen > MaxDatagramFrameSize {
return nil, fmt.Errorf("datagram size %d exceeds max frame size %d", len(b), MaxDatagramFrameSize)
}
b = append(b, byte(datagramType))
@@ -33,36 +40,36 @@ func (dm *DatagramMuxerV2) mtu() int {
type DatagramMuxerV2 struct {
session quic.Connection
logger *zerolog.Logger
sessionDemuxChan chan<- *SessionDatagram
packetDemuxChan chan<- []byte
sessionDemuxChan chan<- *packet.Session
packetDemuxChan chan packet.RawPacket
}
func NewDatagramMuxerV2(
quicSession quic.Connection,
log *zerolog.Logger,
sessionDemuxChan chan<- *SessionDatagram,
packetDemuxChan chan<- []byte) *DatagramMuxerV2 {
sessionDemuxChan chan<- *packet.Session,
) *DatagramMuxerV2 {
logger := log.With().Uint8("datagramVersion", 2).Logger()
return &DatagramMuxerV2{
session: quicSession,
logger: &logger,
sessionDemuxChan: sessionDemuxChan,
packetDemuxChan: packetDemuxChan,
packetDemuxChan: make(chan packet.RawPacket, packetChanCapacity),
}
}
// MuxSession suffix the session ID and datagram version to the payload so the other end of the QUIC connection can
// SendToSession suffix the session ID and datagram version to the payload so the other end of the QUIC connection can
// demultiplex the payload from multiple datagram sessions
func (dm *DatagramMuxerV2) MuxSession(sessionID uuid.UUID, payload []byte) error {
if len(payload) > dm.mtu() {
// 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())
func (dm *DatagramMuxerV2) SendToSession(session *packet.Session) error {
if len(session.Payload) > dm.mtu() {
packetTooBigDropped.Inc()
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(session.Payload), dm.mtu())
}
msgWithID, err := suffixSessionID(sessionID, payload)
msgWithID, err := SuffixSessionID(session.ID, session.Payload)
if err != nil {
return errors.Wrap(err, "Failed to suffix session ID to datagram, it will be dropped")
}
msgWithIDAndType, err := suffixType(msgWithID, udp)
msgWithIDAndType, err := SuffixType(msgWithID, DatagramTypeUDP)
if err != nil {
return errors.Wrap(err, "Failed to suffix datagram type, it will be dropped")
}
@@ -72,10 +79,10 @@ func (dm *DatagramMuxerV2) MuxSession(sessionID uuid.UUID, payload []byte) error
return nil
}
// MuxPacket suffix the datagram type to the packet. The other end of the QUIC connection can demultiplex by parsing
// SendPacket suffix the datagram type to the packet. The other end of the QUIC connection can demultiplex by parsing
// the payload as IP and look at the source and destination.
func (dm *DatagramMuxerV2) MuxPacket(packet []byte) error {
payloadWithVersion, err := suffixType(packet, ip)
func (dm *DatagramMuxerV2) SendPacket(pk packet.RawPacket) error {
payloadWithVersion, err := SuffixType(pk.Data, DatagramTypeIP)
if err != nil {
return errors.Wrap(err, "Failed to suffix datagram type, it will be dropped")
}
@@ -101,36 +108,55 @@ func (dm *DatagramMuxerV2) ServeReceive(ctx context.Context) error {
}
}
func (dm *DatagramMuxerV2) demux(ctx context.Context, msgWithType []byte) error {
if len(msgWithType) < 1 {
return fmt.Errorf("QUIC datagram should have at least 1 byte")
func (dm *DatagramMuxerV2) ReceivePacket(ctx context.Context) (packet.RawPacket, error) {
select {
case <-ctx.Done():
return packet.RawPacket{}, ctx.Err()
case pk := <-dm.packetDemuxChan:
return pk, nil
}
msgType := datagramV2Type(msgWithType[len(msgWithType)-1])
msg := msgWithType[0 : len(msgWithType)-1]
}
func (dm *DatagramMuxerV2) demux(ctx context.Context, msgWithType []byte) error {
if len(msgWithType) < typeIDLen {
return fmt.Errorf("QUIC datagram should have at least %d byte", typeIDLen)
}
msgType := DatagramV2Type(msgWithType[len(msgWithType)-typeIDLen])
msg := msgWithType[0 : len(msgWithType)-typeIDLen]
switch msgType {
case udp:
sessionID, payload, err := extractSessionID(msg)
if err != nil {
return err
}
sessionDatagram := SessionDatagram{
ID: sessionID,
Payload: payload,
}
select {
case dm.sessionDemuxChan <- &sessionDatagram:
return nil
case <-ctx.Done():
return ctx.Err()
}
case ip:
select {
case dm.packetDemuxChan <- msg:
return nil
case <-ctx.Done():
return ctx.Err()
}
case DatagramTypeUDP:
return dm.handleSession(ctx, msg)
case DatagramTypeIP:
return dm.handlePacket(ctx, msg)
default:
return fmt.Errorf("Unexpected datagram type %d", msgType)
}
}
func (dm *DatagramMuxerV2) handleSession(ctx context.Context, session []byte) error {
sessionID, payload, err := extractSessionID(session)
if err != nil {
return err
}
sessionDatagram := packet.Session{
ID: sessionID,
Payload: payload,
}
select {
case dm.sessionDemuxChan <- &sessionDatagram:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (dm *DatagramMuxerV2) handlePacket(ctx context.Context, pk []byte) error {
select {
case <-ctx.Done():
return ctx.Err()
case dm.packetDemuxChan <- packet.RawPacket{
Data: pk,
}:
return nil
}
}
+8
View File
@@ -144,6 +144,13 @@ var (
}
registerClient = sync.Once{}
registerServer = sync.Once{}
packetTooBigDropped = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: perspectiveString(logging.PerspectiveClient),
Name: "packet_too_big_dropped",
Help: "Count of packets received from origin that are too big to send to the edge and are dropped as a result",
})
)
// MetricsCollector abstracts the difference between client and server metrics from connTracer
@@ -264,6 +271,7 @@ func newClientCollector(index uint8) MetricsCollector {
clientMetrics.minRTT,
clientMetrics.latestRTT,
clientMetrics.smoothedRTT,
packetTooBigDropped,
)
})
return &clientCollector{
+1 -1
View File
@@ -7,5 +7,5 @@ const (
// 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
maxDatagramPayloadSize = MaxDatagramFrameSize - 3 - sessionIDLen - typeIDLen
)
+2 -6
View File
@@ -247,12 +247,8 @@ func NewRPCClientStream(ctx context.Context, stream io.ReadWriteCloser, logger *
}, nil
}
func (rcs *RPCClientStream) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfterHint time.Duration) error {
resp, err := rcs.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort, closeIdleAfterHint)
if err != nil {
return err
}
return resp.Err
func (rcs *RPCClientStream) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfterHint time.Duration, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
return rcs.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort, closeIdleAfterHint, traceContext)
}
func (rcs *RPCClientStream) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
+69 -36
View File
@@ -110,45 +110,74 @@ func TestConnectResponseMeta(t *testing.T) {
}
func TestRegisterUdpSession(t *testing.T) {
clientStream, serverStream := newMockRPCStreams()
unregisterMessage := "closed by eyeball"
sessionRPCServer := mockSessionRPCServer{
sessionID: uuid.New(),
dstIP: net.IP{172, 16, 0, 1},
dstPort: 8000,
closeIdleAfter: testCloseIdleAfterHint,
unregisterMessage: unregisterMessage,
var tests = []struct {
name string
sessionRPCServer mockSessionRPCServer
}{
{
name: "RegisterUdpSession (no trace context)",
sessionRPCServer: mockSessionRPCServer{
sessionID: uuid.New(),
dstIP: net.IP{172, 16, 0, 1},
dstPort: 8000,
closeIdleAfter: testCloseIdleAfterHint,
unregisterMessage: unregisterMessage,
traceContext: "",
},
},
{
name: "RegisterUdpSession (with trace context)",
sessionRPCServer: mockSessionRPCServer{
sessionID: uuid.New(),
dstIP: net.IP{172, 16, 0, 1},
dstPort: 8000,
closeIdleAfter: testCloseIdleAfterHint,
unregisterMessage: unregisterMessage,
traceContext: "1241ce3ecdefc68854e8514e69ba42ca:b38f1bf5eae406f3:0:1",
},
},
}
logger := zerolog.Nop()
sessionRegisteredChan := 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(sessionRPCServer, nil, &logger)
assert.NoError(t, err)
serverStream.Close()
close(sessionRegisteredChan)
}()
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
logger := zerolog.Nop()
clientStream, serverStream := newMockRPCStreams()
sessionRegisteredChan := 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(test.sessionRPCServer, nil, &logger)
assert.NoError(t, err)
rpcClientStream, err := NewRPCClientStream(context.Background(), clientStream, &logger)
assert.NoError(t, err)
serverStream.Close()
close(sessionRegisteredChan)
}()
assert.NoError(t, rpcClientStream.RegisterUdpSession(context.Background(), sessionRPCServer.sessionID, sessionRPCServer.dstIP, sessionRPCServer.dstPort, testCloseIdleAfterHint))
rpcClientStream, err := NewRPCClientStream(context.Background(), clientStream, &logger)
assert.NoError(t, err)
// Different sessionID, the RPC server should reject the registraion
assert.Error(t, rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), sessionRPCServer.dstIP, sessionRPCServer.dstPort, testCloseIdleAfterHint))
reg, err := rpcClientStream.RegisterUdpSession(context.Background(), test.sessionRPCServer.sessionID, test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
assert.NoError(t, err)
assert.NoError(t, reg.Err)
assert.NoError(t, rpcClientStream.UnregisterUdpSession(context.Background(), sessionRPCServer.sessionID, unregisterMessage))
// Different sessionID, the RPC server should reject the registraion
reg, err = rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
assert.NoError(t, err)
assert.Error(t, reg.Err)
// Different sessionID, the RPC server should reject the unregistraion
assert.Error(t, rpcClientStream.UnregisterUdpSession(context.Background(), uuid.New(), unregisterMessage))
assert.NoError(t, rpcClientStream.UnregisterUdpSession(context.Background(), test.sessionRPCServer.sessionID, unregisterMessage))
rpcClientStream.Close()
<-sessionRegisteredChan
// Different sessionID, the RPC server should reject the unregistraion
assert.Error(t, rpcClientStream.UnregisterUdpSession(context.Background(), uuid.New(), unregisterMessage))
rpcClientStream.Close()
<-sessionRegisteredChan
})
}
}
func TestManageConfiguration(t *testing.T) {
@@ -198,22 +227,26 @@ type mockSessionRPCServer struct {
dstPort uint16
closeIdleAfter time.Duration
unregisterMessage string
traceContext string
}
func (s mockSessionRPCServer) RegisterUdpSession(_ 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, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
if s.sessionID != sessionID {
return fmt.Errorf("expect session ID %s, got %s", s.sessionID, sessionID)
return nil, fmt.Errorf("expect session ID %s, got %s", s.sessionID, sessionID)
}
if !s.dstIP.Equal(dstIP) {
return fmt.Errorf("expect destination IP %s, got %s", s.dstIP, dstIP)
return nil, fmt.Errorf("expect destination IP %s, got %s", s.dstIP, dstIP)
}
if s.dstPort != dstPort {
return fmt.Errorf("expect destination port %d, got %d", s.dstPort, dstPort)
return nil, fmt.Errorf("expect destination port %d, got %d", s.dstPort, dstPort)
}
if s.closeIdleAfter != closeIdleAfter {
return fmt.Errorf("expect closeIdleAfter %d, got %d", s.closeIdleAfter, closeIdleAfter)
return nil, fmt.Errorf("expect closeIdleAfter %d, got %d", s.closeIdleAfter, closeIdleAfter)
}
return nil
if s.traceContext != traceContext {
return nil, fmt.Errorf("expect traceContext %s, got %s", s.traceContext, traceContext)
}
return &tunnelpogs.RegisterUdpSessionResponse{}, nil
}
func (s mockSessionRPCServer) UnregisterUdpSession(_ context.Context, sessionID uuid.UUID, message string) error {
+100
View File
@@ -0,0 +1,100 @@
package supervisor
import (
"bytes"
"crypto/tls"
"encoding/json"
"net/http"
"sync"
)
// When experimental post-quantum tunnels are enabled, and we're hitting an
// issue creating the tunnel, we'll report the first error
// to https://pqtunnels.cloudflareresearch.com.
var (
PQKexes = [...]tls.CurveID{
tls.CurveID(0xfe30), // X25519Kyber512Draft00
tls.CurveID(0xfe31), // X25519Kyber768Draft00
}
PQKexNames map[tls.CurveID]string = map[tls.CurveID]string{
tls.CurveID(0xfe30): "X25519Kyber512Draft00",
tls.CurveID(0xfe31): "X25519Kyber768Draft00",
}
pqtMux sync.Mutex // protects pqtSubmitted and pqtWaitForMessage
pqtSubmitted bool // whether an error has already been submitted
// Number of errors to ignore before printing elaborate instructions.
pqtWaitForMessage int
)
func handlePQTunnelError(rep error, config *TunnelConfig) {
needToMessage := false
pqtMux.Lock()
needToSubmit := !pqtSubmitted
if needToSubmit {
pqtSubmitted = true
}
pqtWaitForMessage--
if pqtWaitForMessage < 0 {
pqtWaitForMessage = 5
needToMessage = true
}
pqtMux.Unlock()
if needToMessage {
config.Log.Info().Msgf(
"\n\n" +
"===================================================================================\n" +
"You are hitting an error while using the experimental post-quantum tunnels feature.\n" +
"\n" +
"Please check:\n" +
"\n" +
" https://pqtunnels.cloudflareresearch.com\n" +
"\n" +
"for known problems.\n" +
"===================================================================================\n\n",
)
}
if needToSubmit {
go submitPQTunnelError(rep, config)
}
}
func submitPQTunnelError(rep error, config *TunnelConfig) {
body, err := json.Marshal(struct {
Group int `json:"g"`
Message string `json:"m"`
Version string `json:"v"`
}{
Group: int(PQKexes[config.PQKexIdx]),
Message: rep.Error(),
Version: config.ReportedVersion,
})
if err != nil {
config.Log.Err(err).Msg("Failed to create error report")
return
}
resp, err := http.Post(
"https://pqtunnels.cloudflareresearch.com",
"application/json",
bytes.NewBuffer(body),
)
if err != nil {
config.Log.Err(err).Msg(
"Failed to submit post-quantum tunnel error report",
)
return
}
if resp.StatusCode != 200 {
config.Log.Error().Msgf(
"Failed to submit post-quantum tunnel error report: status %d",
resp.StatusCode,
)
}
resp.Body.Close()
}
+18 -6
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net"
"strings"
"time"
@@ -44,7 +45,7 @@ type Supervisor struct {
config *TunnelConfig
orchestrator *orchestration.Orchestrator
edgeIPs *edgediscovery.Edge
edgeTunnelServer EdgeTunnelServer
edgeTunnelServer *EdgeTunnelServer
tunnelErrors chan tunnelError
tunnelsConnecting map[int]chan struct{}
tunnelsProtocolFallback map[int]*protocolFallback
@@ -125,7 +126,7 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato
config: config,
orchestrator: orchestrator,
edgeIPs: edgeIPs,
edgeTunnelServer: edgeTunnelServer,
edgeTunnelServer: &edgeTunnelServer,
tunnelErrors: make(chan tunnelError),
tunnelsConnecting: map[int]chan struct{}{},
tunnelsProtocolFallback: map[int]*protocolFallback{},
@@ -142,6 +143,18 @@ func (s *Supervisor) Run(
ctx context.Context,
connectedSignal *signal.Signal,
) error {
if s.config.PacketConfig != nil {
go func() {
if err := s.config.PacketConfig.ICMPRouter.Serve(ctx); err != nil {
if errors.Is(err, net.ErrClosed) {
s.log.Logger().Info().Err(err).Msg("icmp router terminated")
} else {
s.log.Logger().Err(err).Msg("icmp router terminated")
}
}
}()
}
if err := s.initialize(ctx, connectedSignal); err != nil {
if err == errEarlyShutdown {
return nil
@@ -249,7 +262,7 @@ func (s *Supervisor) initialize(
s.config.HAConnections = availableAddrs
}
s.tunnelsProtocolFallback[0] = &protocolFallback{
retry.BackoffHandler{MaxRetries: s.config.Retries},
retry.BackoffHandler{MaxRetries: s.config.Retries, RetryForever: true},
s.config.ProtocolSelector.Current(),
false,
}
@@ -271,12 +284,11 @@ func (s *Supervisor) initialize(
// At least one successful connection, so start the rest
for i := 1; i < s.config.HAConnections; i++ {
s.tunnelsProtocolFallback[i] = &protocolFallback{
retry.BackoffHandler{MaxRetries: s.config.Retries},
retry.BackoffHandler{MaxRetries: s.config.Retries, RetryForever: true},
s.config.ProtocolSelector.Current(),
false,
}
ch := signal.New(make(chan struct{}))
go s.startTunnel(ctx, i, ch)
go s.startTunnel(ctx, i, s.newConnectedTunnelSignal(i))
time.Sleep(registrationInterval)
}
return nil
+103 -104
View File
@@ -21,6 +21,7 @@ import (
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/orchestration"
"github.com/cloudflare/cloudflared/packet"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/retry"
"github.com/cloudflare/cloudflared/signal"
@@ -35,6 +36,7 @@ const (
FeatureQuickReconnects = "quick_reconnects"
FeatureAllowRemoteConfig = "allow_remote_config"
FeatureDatagramV2 = "support_datagram_v2"
FeaturePostQuantum = "postquantum"
)
type TunnelConfig struct {
@@ -58,11 +60,17 @@ type TunnelConfig struct {
Retries uint
RunFromTerminal bool
NeedPQ bool
// Index into PQKexes of post-quantum kex to use if NeedPQ is set.
PQKexIdx int
NamedTunnel *connection.NamedTunnelProperties
ClassicTunnel *connection.ClassicTunnelProperties
MuxerConfig *connection.MuxerConfig
ProtocolSelector connection.ProtocolSelector
EdgeTLSConfigs map[connection.Protocol]*tls.Config
PacketConfig *packet.GlobalRouterConfig
}
func (c *TunnelConfig) registrationOptions(connectionID uint8, OriginLocalIP string, uuid uuid.UUID) *tunnelpogs.RegistrationOptions {
@@ -197,7 +205,7 @@ type EdgeTunnelServer struct {
connAwareLogger *ConnAwareLogger
}
func (e EdgeTunnelServer) Serve(ctx context.Context, connIndex uint8, protocolFallback *protocolFallback, connectedSignal *signal.Signal) error {
func (e *EdgeTunnelServer) Serve(ctx context.Context, connIndex uint8, protocolFallback *protocolFallback, connectedSignal *signal.Signal) error {
haConnections.Inc()
defer haConnections.Dec()
@@ -229,20 +237,14 @@ func (e EdgeTunnelServer) Serve(ctx context.Context, connIndex uint8, protocolFa
// to another protocol when a particular metal doesn't support new protocol
// Each connection can also have it's own IP version because individual connections might fallback
// to another IP version.
err, recoverable := ServeTunnel(
err, recoverable := e.serveTunnel(
ctx,
connLog,
e.credentialManager,
e.config,
e.orchestrator,
addr,
connIndex,
connectedFuse,
protocolFallback,
e.cloudflaredUUID,
e.reconnectCh,
protocolFallback.protocol,
e.gracefulShutdownC,
)
// If the connection is recoverable, we want to maintain the same IP
@@ -321,16 +323,11 @@ func selectNextProtocol(
selector connection.ProtocolSelector,
cause error,
) bool {
var idleTimeoutError *quic.IdleTimeoutError
isNetworkActivityTimeout := errors.As(cause, &idleTimeoutError)
edgeQuicDialError, ok := cause.(*connection.EdgeQuicDialError)
if !isNetworkActivityTimeout && ok {
isNetworkActivityTimeout = errors.As(edgeQuicDialError.Cause, &idleTimeoutError)
}
isQuicBroken := isQuicBroken(cause)
_, hasFallback := selector.Fallback()
if protocolBackoff.ReachedMaxRetries() || (hasFallback && isNetworkActivityTimeout) {
if isNetworkActivityTimeout {
if protocolBackoff.ReachedMaxRetries() || (hasFallback && isQuicBroken) {
if isQuicBroken {
connLog.Warn().Msg("If this log occurs persistently, and cloudflared is unable to connect to " +
"Cloudflare Network with `quic` protocol, then most likely your machine/network is getting its egress " +
"UDP to port 7844 (or others) blocked or dropped. Make sure to allow egress connectivity as per " +
@@ -359,22 +356,30 @@ func selectNextProtocol(
return true
}
func isQuicBroken(cause error) bool {
var idleTimeoutError *quic.IdleTimeoutError
if errors.As(cause, &idleTimeoutError) {
return true
}
var transportError *quic.TransportError
if errors.As(cause, &transportError) && strings.Contains(cause.Error(), "operation not permitted") {
return true
}
return false
}
// ServeTunnel runs a single tunnel connection, returns nil on graceful shutdown,
// on error returns a flag indicating if error can be retried
func ServeTunnel(
func (e *EdgeTunnelServer) serveTunnel(
ctx context.Context,
connLog *ConnAwareLogger,
credentialManager *reconnectCredentialManager,
config *TunnelConfig,
orchestrator *orchestration.Orchestrator,
addr *allregions.EdgeAddr,
connIndex uint8,
fuse *h2mux.BooleanFuse,
backoff *protocolFallback,
cloudflaredUUID uuid.UUID,
reconnectCh chan ReconnectSignal,
protocol connection.Protocol,
gracefulShutdownC <-chan struct{},
) (err error, recoverable bool) {
// Treat panics as recoverable errors
defer func() {
@@ -389,21 +394,15 @@ func ServeTunnel(
}
}()
defer config.Observer.SendDisconnect(connIndex)
err, recoverable = serveTunnel(
defer e.config.Observer.SendDisconnect(connIndex)
err, recoverable = e.serveConnection(
ctx,
connLog,
credentialManager,
config,
orchestrator,
addr,
connIndex,
fuse,
backoff,
cloudflaredUUID,
reconnectCh,
protocol,
gracefulShutdownC,
)
if err != nil {
@@ -416,7 +415,7 @@ func ServeTunnel(
connLog.ConnAwareLogger().Err(err).Msg("Register tunnel error from server side")
// Don't send registration error return from server to Sentry. They are
// logged on server side
if incidents := config.IncidentLookup.ActiveIncidents(); len(incidents) > 0 {
if incidents := e.config.IncidentLookup.ActiveIncidents(); len(incidents) > 0 {
connLog.ConnAwareLogger().Msg(activeIncidentsMsg(incidents))
}
return err.Cause, !err.Permanent
@@ -442,93 +441,73 @@ func ServeTunnel(
return nil, false
}
func serveTunnel(
func (e *EdgeTunnelServer) serveConnection(
ctx context.Context,
connLog *ConnAwareLogger,
credentialManager *reconnectCredentialManager,
config *TunnelConfig,
orchestrator *orchestration.Orchestrator,
addr *allregions.EdgeAddr,
connIndex uint8,
fuse *h2mux.BooleanFuse,
backoff *protocolFallback,
cloudflaredUUID uuid.UUID,
reconnectCh chan ReconnectSignal,
protocol connection.Protocol,
gracefulShutdownC <-chan struct{},
) (err error, recoverable bool) {
connectedFuse := &connectedFuse{
fuse: fuse,
backoff: backoff,
}
controlStream := connection.NewControlStream(
config.Observer,
e.config.Observer,
connectedFuse,
config.NamedTunnel,
e.config.NamedTunnel,
connIndex,
addr.UDP.IP,
nil,
gracefulShutdownC,
config.GracePeriod,
e.gracefulShutdownC,
e.config.GracePeriod,
protocol,
)
switch protocol {
case connection.QUIC, connection.QUICWarp:
connOptions := config.connectionOptions(addr.UDP.String(), uint8(backoff.Retries()))
return ServeQUIC(ctx,
connOptions := e.config.connectionOptions(addr.UDP.String(), uint8(backoff.Retries()))
return e.serveQUIC(ctx,
addr.UDP,
config,
orchestrator,
connLog,
connOptions,
controlStream,
connIndex,
reconnectCh,
gracefulShutdownC)
connIndex)
case connection.HTTP2, connection.HTTP2Warp:
edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, config.EdgeTLSConfigs[protocol], addr.TCP)
edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, e.config.EdgeTLSConfigs[protocol], addr.TCP)
if err != nil {
connLog.ConnAwareLogger().Err(err).Msg("Unable to establish connection with Cloudflare edge")
return err, true
}
connOptions := config.connectionOptions(edgeConn.LocalAddr().String(), uint8(backoff.Retries()))
if err := ServeHTTP2(
connOptions := e.config.connectionOptions(edgeConn.LocalAddr().String(), uint8(backoff.Retries()))
if err := e.serveHTTP2(
ctx,
connLog,
config,
orchestrator,
edgeConn,
connOptions,
controlStream,
connIndex,
gracefulShutdownC,
reconnectCh,
); err != nil {
return err, false
}
default:
edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, config.EdgeTLSConfigs[protocol], addr.TCP)
edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, e.config.EdgeTLSConfigs[protocol], addr.TCP)
if err != nil {
connLog.ConnAwareLogger().Err(err).Msg("Unable to establish connection with Cloudflare edge")
return err, true
}
if err := ServeH2mux(
if err := e.serveH2mux(
ctx,
connLog,
credentialManager,
config,
orchestrator,
edgeConn,
connIndex,
connectedFuse,
cloudflaredUUID,
reconnectCh,
gracefulShutdownC,
); err != nil {
return err, false
}
@@ -544,30 +523,27 @@ func (r unrecoverableError) Error() string {
return r.err.Error()
}
func ServeH2mux(
func (e *EdgeTunnelServer) serveH2mux(
ctx context.Context,
connLog *ConnAwareLogger,
credentialManager *reconnectCredentialManager,
config *TunnelConfig,
orchestrator *orchestration.Orchestrator,
edgeConn net.Conn,
connIndex uint8,
connectedFuse *connectedFuse,
cloudflaredUUID uuid.UUID,
reconnectCh chan ReconnectSignal,
gracefulShutdownC <-chan struct{},
) error {
if e.config.NeedPQ {
return unrecoverableError{errors.New("H2Mux transport does not support post-quantum")}
}
connLog.Logger().Debug().Msgf("Connecting via h2mux")
// Returns error from parsing the origin URL or handshake errors
handler, err, recoverable := connection.NewH2muxConnection(
orchestrator,
config.GracePeriod,
config.MuxerConfig,
e.orchestrator,
e.config.GracePeriod,
e.config.MuxerConfig,
edgeConn,
connIndex,
config.Observer,
gracefulShutdownC,
config.Log,
e.config.Observer,
e.gracefulShutdownC,
e.config.Log,
)
if err != nil {
if !recoverable {
@@ -579,42 +555,48 @@ func ServeH2mux(
errGroup, serveCtx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
if config.NamedTunnel != nil {
connOptions := config.connectionOptions(edgeConn.LocalAddr().String(), uint8(connectedFuse.backoff.Retries()))
return handler.ServeNamedTunnel(serveCtx, config.NamedTunnel, connOptions, connectedFuse)
if e.config.NamedTunnel != nil {
connOptions := e.config.connectionOptions(edgeConn.LocalAddr().String(), uint8(connectedFuse.backoff.Retries()))
return handler.ServeNamedTunnel(serveCtx, e.config.NamedTunnel, connOptions, connectedFuse)
}
registrationOptions := config.registrationOptions(connIndex, edgeConn.LocalAddr().String(), cloudflaredUUID)
return handler.ServeClassicTunnel(serveCtx, config.ClassicTunnel, credentialManager, registrationOptions, connectedFuse)
registrationOptions := e.config.registrationOptions(connIndex, edgeConn.LocalAddr().String(), e.cloudflaredUUID)
return handler.ServeClassicTunnel(serveCtx, e.config.ClassicTunnel, e.credentialManager, registrationOptions, connectedFuse)
})
errGroup.Go(func() error {
return listenReconnect(serveCtx, reconnectCh, gracefulShutdownC)
err := listenReconnect(serveCtx, e.reconnectCh, e.gracefulShutdownC)
if err != nil {
// forcefully break the connection (this is only used for testing)
// errgroup will return context canceled for the handler.ServeClassicTunnel
connLog.Logger().Debug().Msg("Forcefully breaking h2mux connection")
}
return err
})
return errGroup.Wait()
}
func ServeHTTP2(
func (e *EdgeTunnelServer) serveHTTP2(
ctx context.Context,
connLog *ConnAwareLogger,
config *TunnelConfig,
orchestrator *orchestration.Orchestrator,
tlsServerConn net.Conn,
connOptions *tunnelpogs.ConnectionOptions,
controlStreamHandler connection.ControlStreamHandler,
connIndex uint8,
gracefulShutdownC <-chan struct{},
reconnectCh chan ReconnectSignal,
) error {
if e.config.NeedPQ {
return unrecoverableError{errors.New("HTTP/2 transport does not support post-quantum")}
}
connLog.Logger().Debug().Msgf("Connecting via http2")
h2conn := connection.NewHTTP2Connection(
tlsServerConn,
orchestrator,
e.orchestrator,
connOptions,
config.Observer,
e.config.Observer,
connIndex,
controlStreamHandler,
config.Log,
e.config.Log,
)
errGroup, serveCtx := errgroup.WithContext(ctx)
@@ -623,11 +605,11 @@ func ServeHTTP2(
})
errGroup.Go(func() error {
err := listenReconnect(serveCtx, reconnectCh, gracefulShutdownC)
err := listenReconnect(serveCtx, e.reconnectCh, e.gracefulShutdownC)
if err != nil {
// forcefully break the connection (this is only used for testing)
// errgroup will return context canceled for the h2conn.Serve
connLog.Logger().Debug().Msg("Forcefully breaking http2 connection")
_ = tlsServerConn.Close()
}
return err
})
@@ -635,19 +617,31 @@ func ServeHTTP2(
return errGroup.Wait()
}
func ServeQUIC(
func (e *EdgeTunnelServer) serveQUIC(
ctx context.Context,
edgeAddr *net.UDPAddr,
config *TunnelConfig,
orchestrator *orchestration.Orchestrator,
connLogger *ConnAwareLogger,
connOptions *tunnelpogs.ConnectionOptions,
controlStreamHandler connection.ControlStreamHandler,
connIndex uint8,
reconnectCh chan ReconnectSignal,
gracefulShutdownC <-chan struct{},
) (err error, recoverable bool) {
tlsConfig := config.EdgeTLSConfigs[connection.QUIC]
tlsConfig := e.config.EdgeTLSConfigs[connection.QUIC]
if e.config.NeedPQ {
// If the user passes the -post-quantum flag, we override
// CurvePreferences to only support hybrid post-quantum key agreements.
cs := make([]tls.CurveID, len(PQKexes))
copy(cs, PQKexes[:])
// It is unclear whether Kyber512 or Kyber768 will become the standard.
// Kyber768 is a bit bigger (and doesn't fit in one initial
// datagram anymore). We're enabling both, but pick randomly which
// one to put first. (TLS will use the first one in the list
// and allows a fallback to the second.)
cs[0], cs[e.config.PQKexIdx] = cs[e.config.PQKexIdx], cs[0]
tlsConfig.CurvePreferences = cs
}
quicConfig := &quic.Config{
HandshakeIdleTimeout: quicpogs.HandshakeIdleTimeout,
MaxIdleTimeout: quicpogs.MaxIdleTimeout,
@@ -663,11 +657,16 @@ func ServeQUIC(
quicConfig,
edgeAddr,
tlsConfig,
orchestrator,
e.orchestrator,
connOptions,
controlStreamHandler,
connLogger.Logger())
connLogger.Logger(),
e.config.PacketConfig)
if err != nil {
if e.config.NeedPQ {
handlePQTunnelError(err, e.config)
}
connLogger.ConnAwareLogger().Err(err).Msgf("Failed to create new quic connection")
return err, true
}
@@ -682,11 +681,11 @@ func ServeQUIC(
})
errGroup.Go(func() error {
err := listenReconnect(serveCtx, reconnectCh, gracefulShutdownC)
err := listenReconnect(serveCtx, e.reconnectCh, e.gracefulShutdownC)
if err != nil {
// forcefully break the connection (this is only used for testing)
// errgroup will return context canceled for the quicConn.Serve
connLogger.Logger().Debug().Msg("Forcefully breaking quic connection")
quicConn.Close()
}
return err
})
+2
View File
@@ -44,6 +44,7 @@ func TestWaitForBackoffFallback(t *testing.T) {
mockFetcher.fetch(),
resolveTTL,
&log,
false,
)
assert.NoError(t, err)
@@ -104,6 +105,7 @@ func TestWaitForBackoffFallback(t *testing.T) {
mockFetcher.fetch(),
resolveTTL,
&log,
false,
)
assert.NoError(t, err)
protoFallback = &protocolFallback{backoff, protocolSelector.Current(), false}
+13 -1
View File
@@ -40,12 +40,21 @@ func NewCertReloader(certPath, keyPath string) (*CertReloader, error) {
}
// Cert returns the TLS certificate most recently read by the CertReloader.
// This method works as a direct utility method for tls.Config#Cert.
func (cr *CertReloader) Cert(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cr.Lock()
defer cr.Unlock()
return cr.certificate, nil
}
// ClientCert returns the TLS certificate most recently read by the CertReloader.
// This method works as a direct utility method for tls.Config#ClientCert.
func (cr *CertReloader) ClientCert(certRequestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
cr.Lock()
defer cr.Unlock()
return cr.certificate, nil
}
// LoadCert loads a TLS certificate from the CertReloader's specified filepath.
// Call this after writing a new certificate to the disk (e.g. after renewing a certificate)
func (cr *CertReloader) LoadCert() error {
@@ -131,7 +140,10 @@ func CreateTunnelConfig(c *cli.Context, serverName string) (*tls.Config, error)
}
if tlsConfig.RootCAs == nil {
rootCAPool := x509.NewCertPool()
rootCAPool, err := x509.SystemCertPool()
if err != nil {
return nil, errors.Wrap(err, "unable to get x509 system cert pool")
}
cfRootCA, err := GetCloudflareRootCA()
if err != nil {
return nil, errors.Wrap(err, "could not append Cloudflare Root CAs to cloudflared certificate pool")
+15 -9
View File
@@ -12,15 +12,16 @@ import (
// Config is the user provided parameters to create a tls.Config
type TLSParameters struct {
Cert string
Key string
GetCertificate *CertReloader
ClientCAs []string
RootCAs []string
ServerName string
CurvePreferences []tls.CurveID
MinVersion uint16 // min tls version. If zero, TLS1.0 is defined as minimum.
MaxVersion uint16 // max tls version. If zero, last TLS version is used defined as limit (currently TLS1.3)
Cert string
Key string
GetCertificate *CertReloader
GetClientCertificate *CertReloader
ClientCAs []string
RootCAs []string
ServerName string
CurvePreferences []tls.CurveID
MinVersion uint16 // min tls version. If zero, TLS1.0 is defined as minimum.
MaxVersion uint16 // max tls version. If zero, last TLS version is used defined as limit (currently TLS1.3)
}
// GetConfig returns a TLS configuration according to the Config set by the user.
@@ -43,6 +44,11 @@ func GetConfig(p *TLSParameters) (*tls.Config, error) {
tlsconfig.GetCertificate = p.GetCertificate.Cert
}
if p.GetClientCertificate != nil {
// GetClientCertificate is called when using an HTTP client library and mTLS is required.
tlsconfig.GetClientCertificate = p.GetClientCertificate.ClientCert
}
if len(p.ClientCAs) > 0 {
// set of root certificate authorities that servers use if required to verify a client certificate
// by the policy in ClientAuth
+22 -9
View File
@@ -24,6 +24,9 @@ type InMemoryClient interface {
// Spans returns a copy of the list of in-memory stored spans as a base64
// encoded otlp protobuf string.
Spans() (string, error)
// ProtoSpans returns a copy of the list of in-memory stored spans as otlp
// protobuf byte array.
ProtoSpans() ([]byte, error)
}
// InMemoryOtlpClient is a client implementation for otlptrace.Client
@@ -55,21 +58,26 @@ func (mc *InMemoryOtlpClient) UploadTraces(_ context.Context, protoSpans []*trac
// Spans returns the list of in-memory stored spans as a base64 encoded otlp protobuf string.
func (mc *InMemoryOtlpClient) Spans() (string, error) {
mc.mu.Lock()
defer mc.mu.Unlock()
if len(mc.spans) <= 0 {
return "", errNoTraces
}
pbRequest := &coltracepb.ExportTraceServiceRequest{
ResourceSpans: mc.spans,
}
data, err := proto.Marshal(pbRequest)
data, err := mc.ProtoSpans()
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}
// ProtoSpans returns the list of in-memory stored spans as the protobuf byte array.
func (mc *InMemoryOtlpClient) ProtoSpans() ([]byte, error) {
mc.mu.Lock()
defer mc.mu.Unlock()
if len(mc.spans) <= 0 {
return nil, errNoTraces
}
pbRequest := &coltracepb.ExportTraceServiceRequest{
ResourceSpans: mc.spans,
}
return proto.Marshal(pbRequest)
}
// NoopOtlpClient is a client implementation for otlptrace.Client that does nothing
type NoopOtlpClient struct{}
@@ -89,3 +97,8 @@ func (mc *NoopOtlpClient) UploadTraces(_ context.Context, _ []*tracepb.ResourceS
func (mc *NoopOtlpClient) Spans() (string, error) {
return "", errNoopTracer
}
// Spans always returns no traces error
func (mc *NoopOtlpClient) ProtoSpans() ([]byte, error) {
return nil, errNoopTracer
}
+24 -1
View File
@@ -93,7 +93,7 @@ type TracedContext struct {
*cfdTracer
}
// NewTracedHTTPRequest creates a new tracer for the current HTTP request context.
// NewTracedContext creates a new tracer for the current context.
func NewTracedContext(ctx context.Context, traceContext string, log *zerolog.Logger) *TracedContext {
ctx, exists := extractTraceFromString(ctx, traceContext)
if !exists {
@@ -155,6 +155,24 @@ func (cft *cfdTracer) GetSpans() (enc string) {
return
}
// GetProtoSpans returns the spans as the otlp traces in protobuf byte array.
func (cft *cfdTracer) GetProtoSpans() (proto []byte) {
proto, err := cft.exporter.ProtoSpans()
switch err {
case nil:
break
case errNoTraces:
cft.log.Trace().Err(err).Msgf("expected traces to be available")
return
case errNoopTracer:
return // noop tracer has no traces
default:
cft.log.Debug().Err(err)
return
}
return
}
// AddSpans assigns spans as base64 encoded protobuf otlp traces to provided
// HTTP headers.
func (cft *cfdTracer) AddSpans(headers http.Header) {
@@ -171,6 +189,11 @@ func (cft *cfdTracer) AddSpans(headers http.Header) {
headers[CanonicalCloudflaredTracingHeader] = []string{enc}
}
// End will set the OK status for the span and then end it.
func End(span trace.Span) {
endSpan(span, -1, codes.Ok, nil)
}
// EndWithErrorStatus will set a status for the span and then end it.
func EndWithErrorStatus(span trace.Span, err error) {
endSpan(span, -1, codes.Error, err)
+26 -5
View File
@@ -14,7 +14,11 @@ import (
)
type SessionManager interface {
RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) error
// RegisterUdpSession is the call provided to cloudflared to handle an incoming
// capnproto RegisterUdpSession request from the edge.
RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*RegisterUdpSessionResponse, error)
// UnregisterUdpSession is the call provided to cloudflared to handle an incoming
// capnproto UnregisterUdpSession request from the edge.
UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error
}
@@ -50,9 +54,17 @@ func (i SessionManager_PogsImpl) RegisterUdpSession(p tunnelrpc.SessionManager_r
closeIdleAfterHint := time.Duration(p.Params.CloseAfterIdleHint())
resp := RegisterUdpSessionResponse{}
registrationErr := i.impl.RegisterUdpSession(p.Ctx, sessionID, dstIP, dstPort, closeIdleAfterHint)
traceContext, err := p.Params.TraceContext()
if err != nil {
return err
}
resp, registrationErr := i.impl.RegisterUdpSession(p.Ctx, sessionID, dstIP, dstPort, closeIdleAfterHint, traceContext)
if registrationErr != nil {
// Make sure to assign a response even if one is not returned from register
if resp == nil {
resp = &RegisterUdpSessionResponse{}
}
resp.Err = registrationErr
}
@@ -85,13 +97,17 @@ func (i SessionManager_PogsImpl) UnregisterUdpSession(p tunnelrpc.SessionManager
}
type RegisterUdpSessionResponse struct {
Err error
Err error
Spans []byte // Spans in protobuf format
}
func (p *RegisterUdpSessionResponse) Marshal(s tunnelrpc.RegisterUdpSessionResponse) error {
if p.Err != nil {
return s.SetErr(p.Err.Error())
}
if err := s.SetSpans(p.Spans); err != nil {
return err
}
return nil
}
@@ -103,6 +119,10 @@ func (p *RegisterUdpSessionResponse) Unmarshal(s tunnelrpc.RegisterUdpSessionRes
if respErr != "" {
p.Err = fmt.Errorf(respErr)
}
p.Spans, err = s.Spans()
if err != nil {
return err
}
return nil
}
@@ -116,7 +136,7 @@ func (c SessionManager_PogsClient) Close() error {
return c.Conn.Close()
}
func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) (*RegisterUdpSessionResponse, error) {
func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*RegisterUdpSessionResponse, error) {
client := tunnelrpc.SessionManager{Client: c.Client}
promise := client.RegisterUdpSession(ctx, func(p tunnelrpc.SessionManager_registerUdpSession_Params) error {
if err := p.SetSessionId(sessionID[:]); err != nil {
@@ -127,6 +147,7 @@ func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessi
}
p.SetDstPort(dstPort)
p.SetCloseAfterIdleHint(int64(closeAfterIdleHint))
p.SetTraceContext(traceContext)
return nil
})
result, err := promise.Result().Struct()
+3 -2
View File
@@ -148,12 +148,13 @@ interface TunnelServer extends (RegistrationServer) {
struct RegisterUdpSessionResponse {
err @0 :Text;
spans @1 :Data;
}
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) -> ();
registerUdpSession @0 (sessionId :Data, dstIp :Data, dstPort :UInt16, closeAfterIdleHint :Int64, traceContext :Text = "") -> (result :RegisterUdpSessionResponse);
unregisterUdpSession @1 (sessionId :Data, message :Text) -> ();
}
struct UpdateConfigurationResponse {
+269 -233
View File
@@ -3593,12 +3593,12 @@ type RegisterUdpSessionResponse struct{ capnp.Struct }
const RegisterUdpSessionResponse_TypeID = 0xab6d5210c1f26687
func NewRegisterUdpSessionResponse(s *capnp.Segment) (RegisterUdpSessionResponse, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return RegisterUdpSessionResponse{st}, err
}
func NewRootRegisterUdpSessionResponse(s *capnp.Segment) (RegisterUdpSessionResponse, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return RegisterUdpSessionResponse{st}, err
}
@@ -3631,12 +3631,26 @@ func (s RegisterUdpSessionResponse) SetErr(v string) error {
return s.Struct.SetText(0, v)
}
func (s RegisterUdpSessionResponse) Spans() ([]byte, error) {
p, err := s.Struct.Ptr(1)
return []byte(p.Data()), err
}
func (s RegisterUdpSessionResponse) HasSpans() bool {
p, err := s.Struct.Ptr(1)
return p.IsValid() || err != nil
}
func (s RegisterUdpSessionResponse) SetSpans(v []byte) error {
return s.Struct.SetData(1, v)
}
// RegisterUdpSessionResponse_List is a list of RegisterUdpSessionResponse.
type RegisterUdpSessionResponse_List struct{ capnp.List }
// NewRegisterUdpSessionResponse creates a new list of RegisterUdpSessionResponse.
func NewRegisterUdpSessionResponse_List(s *capnp.Segment, sz int32) (RegisterUdpSessionResponse_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
return RegisterUdpSessionResponse_List{l}, err
}
@@ -3681,7 +3695,7 @@ func (c SessionManager) RegisterUdpSession(ctx context.Context, params func(Sess
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 16, PointerCount: 2}
call.ParamsSize = capnp.ObjectSize{DataSize: 16, PointerCount: 3}
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))}
@@ -3776,12 +3790,12 @@ type SessionManager_registerUdpSession_Params struct{ capnp.Struct }
const SessionManager_registerUdpSession_Params_TypeID = 0x904e297b87fbecea
func NewSessionManager_registerUdpSession_Params(s *capnp.Segment) (SessionManager_registerUdpSession_Params, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2})
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 3})
return SessionManager_registerUdpSession_Params{st}, err
}
func NewRootSessionManager_registerUdpSession_Params(s *capnp.Segment) (SessionManager_registerUdpSession_Params, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2})
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 3})
return SessionManager_registerUdpSession_Params{st}, err
}
@@ -3839,12 +3853,31 @@ func (s SessionManager_registerUdpSession_Params) SetCloseAfterIdleHint(v int64)
s.Struct.SetUint64(8, uint64(v))
}
func (s SessionManager_registerUdpSession_Params) TraceContext() (string, error) {
p, err := s.Struct.Ptr(2)
return p.Text(), err
}
func (s SessionManager_registerUdpSession_Params) HasTraceContext() bool {
p, err := s.Struct.Ptr(2)
return p.IsValid() || err != nil
}
func (s SessionManager_registerUdpSession_Params) TraceContextBytes() ([]byte, error) {
p, err := s.Struct.Ptr(2)
return p.TextBytes(), err
}
func (s SessionManager_registerUdpSession_Params) SetTraceContext(v string) error {
return s.Struct.SetText(2, v)
}
// SessionManager_registerUdpSession_Params_List is a list of SessionManager_registerUdpSession_Params.
type SessionManager_registerUdpSession_Params_List struct{ capnp.List }
// NewSessionManager_registerUdpSession_Params creates a new list of SessionManager_registerUdpSession_Params.
func NewSessionManager_registerUdpSession_Params_List(s *capnp.Segment, sz int32) (SessionManager_registerUdpSession_Params_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}, sz)
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 3}, sz)
return SessionManager_registerUdpSession_Params_List{l}, err
}
@@ -4424,7 +4457,7 @@ func (c CloudflaredServer) RegisterUdpSession(ctx context.Context, params func(S
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 16, PointerCount: 2}
call.ParamsSize = capnp.ObjectSize{DataSize: 16, PointerCount: 3}
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))}
@@ -4533,231 +4566,234 @@ func CloudflaredServer_Methods(methods []server.Method, s CloudflaredServer_Serv
return methods
}
const schema_db8274f9144abc7e = "x\xda\xccZ}t\x1c\xd5u\xbfwfW#\x19\xad" +
"G\xc3,\x96\xb5\xc7B\x8d\x8e]j'\x06d\xd7)" +
"u\xd3H2\xb2\x83\x84?4\xbbV\x0e1&\x87\xd1" +
"\xee\x934\xea\xee\xcc23+l\x07\xe2\x0fl\x0c\x1c" +
"B0\xb1\xf9pBc\x9b\xd2\x9e\x98$\x85`\x9a\xba" +
"\x07Z\x9c\x86\x10Cpp\x0e\xa4&\x86\xa6\x89\xe3\xb6" +
"\xf8\x98R\x0cn\x8e\xdb\x98\xe9\xb93;\x1f\xda]$" +
"+\xce\x1f\xf9ou\xe7\xbe\xf7\xee\xfb\xbd\xdf\xfdx\xf7" +
"\xe9\xea;\x1a\xba\xb8\x8e\xf8F\x11@9\x10\xafs\xd8" +
"\xbc\x9fl\xd83\xe7\x9f\xb7\x80\x92Bt\xbe\xf8l_" +
"\xf2\x9c\xbd\xe58\xc4y\x01`\xe1\xe3\xc2\x06\x94\x0f\x0a" +
"\x02\x80\xfc\x8c\xf0\x1f\x80\xce\x9d3\x9e\xfc\xfa\xe3Kw" +
"\xde\x01R\x8a\x0f\x95\x01\x17>R\xdf\x87\xf2\xb7\xeaI" +
"\xf3\x1b\xf5\xdb\xe5\x86\x06\x01\xc0\xb9^\xba\xea\xc6\xe4\xab" +
"GH;:u\x8c\xa6~\xaf~\x1e\xcaHj\xf2\xf9" +
"z\x9a\xfaS\x85\x1f\xef\xfb\xe4\xae\x97\xb7\x82\x94\xe2\xc6" +
"M\xfdv\xc3\x06\x94\xcf\xbb\x9a\xe7\x1aV\x01:\xef\xef" +
"\x9c\xf9\xc4\xde#?\xdc\x06\xd2\x15\x08eK\xa5i?" +
"C@y\xce\xb4\xbf\x05t^9{\xe3\x07\x07~\xb0" +
"\xe8N\x90\xe6\x92\x02\x92\xc2\x0b\xd3\xda9@\xf9\xadi" +
"\x9d\x80\xce\xa9\xd3\xff\xb7\xfd\x0bsW\xde\x0f\xca\\\xe4" +
"\x00\xe2\x1ci\x9c\x9f\x96\"\x8d\xcb.!k:\x97\xbd" +
"r0\xb5\xf0\xc1\x9d\x15\xa6\xbb\x8ag/\x99\x87r\xbc" +
"\x91\x0c\xc2\xc6[\x01\x9dO\xff\xc9\xd3\xf7\xf5<\xb8i" +
"\x17HW\x05\xeb\xb1\xc654\xdb\xe6FZ\xef\x7f\xa6" +
"\x7f\xf5H\xe9\xda\xef>X6\xc8\x9deo\xe3<R" +
"8\xe8\xce\xd0>6\xe7\xe6\xef\xbd\xf0\xf4C\xa0\xccG" +
"t\xde\x1c\xfc\xf8\xeb\xfc\xa3\xfb\x8f\xc3\x00\x0ad\xdf\xc2" +
"\x96\xc4>\xda\xdd\xdc\x04\xe9\xfe\xf8\x13\xcf\xfe\xc3\xfdO" +
"o\xff*(W \x02\xb8h~)\xf1\xbf\xa4\xb07" +
"A\xab\xed<\xf6\xdc\xca\xc2\x8e\xdd\xfb<|\xdc\xef\x87" +
"\x13\x1c\x071gk\xef\xaf\x0b\x03\x8fe\x1e+#\x17" +
"\xa7O\xcf%\xce \xe0\xc2\xa3\x896\x04t\x16\xfd\xec" +
"\xe4\xaa\x15\xdf\x19\xfa\x9b\xc8\xd8w\xa6o\xa0\xb1\xdb\x87" +
"\xce\x1cjJ\x17\x9e\xa8@\xc4\xdd\xec\xdb\xd3\xf7\xa3\x8c" +
"\xa2{\x98\xd3\xc9\x84o]~}\xc3\xba\x93\xcb\x9e\x04" +
"i\xbe?M\xab\x98\xa6i\xc6^z\xec\x0f\xe7\xbct" +
"\xebS\xa0\\\x85\x01X-\xf4\x0d\xe5E\"\xed/v" +
"|\xce\xfe\xe7~~\xdf\x81*\x8e\xed\x107\xa0\xfc\xb8" +
"\xbb\xca^\xf13\xf2Q\xfa\xe5\xc4n\xe2?T\x1f\xfe" +
"\xa7\x03\x95\xfcu1>(\x0e\xa2\xfc\x8a\xe8\" \xba" +
"\xfb\xbb\xe7\xd0\xee\x8f\xd7\x7f\xfd\xfdgj\xaa\xbf\xd74" +
"\x88r\\r\x0fV\"\"]\xd6\x8bo>\xdf\x11\xfb" +
"n\x94i\x8fK\xa7\x08\xea\x83\xaeB\xeb;K\x12\xfa" +
"\xbb[\x9e\xaf\x00\xc5U\xfc\xdc\xa5}(\x17.\xa5\xd9" +
"\xb4KI9\xf6\xc9\xd1\xed\xd2\x89\x9f\xbe\xe0\x81\xe2\xed" +
"<.\x8f\xd2\xce?&\x13j}7~\xe5\x81\xf8\xc9" +
"\xaf\xbcH\xc6E| Nn\xb5\xb0[6Q\x1e\x90" +
"\xe9\xa7\"7\xf3\x80N\xea\xc9?\xfb\xf6\x92\xdc\x1b/" +
"\xd7\xe0\xa8|t\xc6\x19\xf9\xad\x19\xf4\xeb\x8d\x19\x04\xea" +
"\x89\xf9O}\xe1\xed/\x1d}\xad\xbc\x13w\xedE\xcd" +
".iz\x9bi\xedsk\xf7\\\xaf97\x1c\xaf\x04" +
"\xc6\xd5\xd4\x9a\xbf\x83\xf2\xe6f\x9a\xee\xf6f\x9a.`" +
"h-\xed_4\x8f\xa2|\xd6\xd5~\xcf\x9d\x9b;\xa9" +
"\xb6l\xfa\xe9\xa7\xdf\x8c\x90\xeal\xf3/\x11b\xce\xca" +
"\xcf\xde8\xdap\xfb\x89\x13Q\xb3\xdenv\x01>\xef" +
"\x0e\xfd\xc7\x7fyh\xe4\xa6o\x1f9\x19%\xd2L\x93" +
"\x88\xf4_\x7f}\xea\xcb\xa7\x0b\xb9\x7fw]\xc6?\x9c" +
"\x96\x99\x8b\x09\xce\x8e\x99\xe4\xc3\xcdm\x89\xa5\xed\xc7\xfa" +
"OE\xf1N\xb4,!\x859-4\xf9\xa2\x9b\xbb\xd9" +
"\xdakn8U\xc5\xb4\xa5-\x8bQ\x1ehq\xb1n" +
"\xd9\x8e\xb2\x96j\x06p\xc6\xfen\xc7\x0dO|\x7f\xe5" +
"\x19\xcf\x8b][>\x97Z@\xb6\xdc\xf7\xc5\x9eU\x7f" +
"\xda~\xe8Lt\x1bJ\x8a\xfcJf)Zi\xe8\x9a" +
"\xd3\x9f\x99s\xdf\x0f\xce\xd4r\x9em\xa9y(\xefJ" +
"\x11\\;H\xf9\xdde\x7f\xf9ZJL}P\x01m" +
"\x1d\xe9>\x93\x1aE\xf9p\xca\x0dh\xa9\x17\x89\xd0{" +
"\xffj\xdf\xbf\x9e;r\xdd\xd9\xaa=\x1c\x9cE\xdc\x9f" +
"E\xd3\x1e\x9e%\xc8\x87g]\x01\xe0\xdcy\xfc\xf3\xeb" +
"~r\xc7\xfbg+\x19\xe6\x1arhV\x1a\xe5\xd7\xdd" +
"\x11Gg\x11a\x1fZ\xfd\x9f\x1bO\xef\x9a\xf1\xeb\xaa" +
"\xb9oi\x1dEy[+inn}Q\xbe\xecr" +
"\xf2\xc4W\x85\xc7:z6\xbe|.rT\xe7[\xfb" +
"\x08\x9e\x07\x85\xaf\x9d\xd8\xf4\xf3\xcf\xff&\x0a\xcf\xb9\xd6" +
"_\x12<\x89\xcb\x09\x9e\xdb\xde}\xe4\xba/\xaf\xfd\xe6" +
"\x87\x11\x82\xcc\xbf|\x0b\x0d\xb5K\xba\xce\xf2f1\x96" +
"\xbd\xca\xff\x99\xbd2\xab\x16\xf5\xe2\xe2\xee\x92=\xc2t" +
"[\xcb\xaa6K\xb3N\xabh\xe8\x16\xebGT\x9a\xf8" +
"\x18@\x0c\x01$u\x14@\xb9\x99G%\xcf\xa1\x84\x98" +
"$\xa2H\x1a\x09GxTl\x0e%\x8eKR\x84\x95" +
"ni\x07P\xf2<*\xeb8D>\x89<\x80Tz" +
"\x00@Y\xc7\xa3\xb2\x95C\xa7\xc8\xcc\x82\xaa3\x1dD" +
"{\xa9ib#p\xd8\x08\xe8\x98\xcc6\xd7\xab\x83y" +
"\x10YD,\x8c\xdejc\x028L\x00:#F\xc9" +
"\xb4\x06t\x1b\xb5|\x9a\x0d\x99\xcc\xc2\x11\xac\x03\x0e\xeb" +
"\x00'\xda^\x86Y\x96f\xe8+T]\x1df&\x00" +
"\xed\xac\x9e\x8f\x03\x04\xd9\x0b\xfd<'u\xec\x06N\x9a" +
"/`\x98i\xd0'\xab\xf4\xb1\xfd\xc0I\xad\x82c\xb2" +
"a\xcd\xb2\x99\x89\x03\xb9\xa2;7o\xe8]\xe8\x94t" +
"\xef\x032\xd3\xfb \xd2\xaa]\xd8\x8f\xa1u|\xb5u" +
"\xd7\xe65\xa6\xdbb\xaf>dT@\xdeW\x0b\xf2\xbe" +
"2\xe4[#\x90o^\x02\xa0\xdc\xc6\xa3r\x17\x87\x12" +
"_\xc6|\xdb<\x00e\x13\x8f\xca\xbd\x1c:Yw\x91" +
"\xde\x1c\x00\x04h\x0e1\xd5.\x99\xcc\"\xd9t\xc0~" +
"\x1e]\xd0\xa7\x03n\x1cc&\xd9\xee\x1f\x82\xa8\x9a\xd9" +
"\x91\xe0\xa0&@z\xe9:\xcd\xb25}x\xb5+\xef" +
"\xec7\xf2Zv=\xed\xaa\xd1\xb5\xb3u1\x00\xa2t" +
"\xd9\x1a\x00\xe4$i\x09@\xa76\xac\x1b&sr\x9a" +
"\x955t\x9d\x01\x9f\xb57\x0e\xaayU\xcf\xb2`\xa1" +
"\xba\xea\x85\xbc\x052\xcc\x1cc\xe6\x95j\x84\xbe\xb3\xfb" +
"US\xe5\x0b\x96\xd2\x18\xe0\xb8t\x0d\x80\xd2\xc3\xa3\xd2" +
"\x1f\xc1q\x05\xe1\xb8\x9cG\xe5\x86\x08\x8e\x03\x84c?" +
"\x8f\xcaZ\x0e\x1d\xc3\xd4\x865\xfdZ\x06\xbc\x19e\xa0" +
"e\xebj\x81\x11fe<6\x1aE[3t\x0b\x9b" +
"\xc2\xac\x03\x88M\x11\xa4\x84\xc98y\xa5O)\x9fQ" +
"\x86>;\xcd\xac\x92\x90\xb7-%\x16\xec$\xb1\x18@" +
"\xa9\xe7QIr\xd8i2\xab\x94\xb7\xb1),'~" +
"\x17\xab\xfa\xf0Eh\x98\xaeE\xc3\x05\x00J\x8eG\xa5" +
"\xc8!\x96\xd1+,\x89D\x03\x1e=\x16\xde\xb2\x1b@" +
"\xb1yT6q\xe8X\xde\"\xbd\x809\x1f\xd1\xb6\x9c" +
"e\xf7\x16\xfd\xbf6\xe6,\xbb\xdf0m\x14\x80C\x01" +
"\x88\xb7\x86\xc5\xba\x87\xc8\xa7zsyv\x9d\xc6\xeb6" +
"\xc6\x81\xc38L\xe8T\x1e?D\x0al\x9e\xb7\xfb\xbb" +
"\x99Kd\xf8#\x1e\x95?\x8e\xec\xa6\x83\xe2\xd8\xd5<" +
"*\x9f\xe2\xd0Q\xb3Y\xa3\xa4\xdb\xab\x81W\x87+8" +
"\x9fa fM\x16\xd2\xc1_\xb6\xbe\x86[\x1b\xfa\x90" +
"6\\2U;\x02x\xa9\x98Sm6\xee\x93{\xce" +
"y\xfe\x02\xce9\xa8+\xa6|\xce~d\xaa8i\xd1" +
"T\x0bV\x14\x9bt-l\xe8T?\xc1\xa3rM\xed" +
"\x03\xdcX`\x96\xa5\x0e\xb3\xaa\xf0\x10\xaf\x89\x89\xce\xb2" +
"\xb4\xeb4\xf3\x92\xcc\x95&\xb3\x84R\xde&+\x1a\x1d" +
"\xc73\x83\xb85\x9bG\xe5j\x0e\x13\xf8\xa1\xe3\xd91" +
"\xff\x81\xf0\x8c\xda\x98i\x1a&6\x85I\xb8\x0cI\xb6" +
"\xbc\x00\x1az\x0f\xb3U-\x8f\xe4\x96A\xa5Z\x01\xdc" +
"dq%\x84\xcd\x13\xcf\xee$\xef(\x8c;)\xa2w" +
"\x13\x8f\xca,\x0e\x9daS\xcd\xb2~f\xa2f\xe4V" +
"\xaa\xba\x91\xe1Y\xb6\x8a\xac\xd3\xa7\xba\xa8\xcb\x0f\xdb\x82" +
"`\xd4\xc4\xe3MV\x06\xa1<\xbc\xbf\xcd\xb39\x19\xd8" +
"|{{\x98\x8c\x83c\xde<\x18f\x8b \x1e\xdeM" +
"\xcer\x17\x8f\xca\xceH^\xd9A\x91\xf3~\x1e\x95\xaf" +
"q(\xc5bI\x8c\x01H\x8f\x10Kv\xf2\xa8\xec\xe1" +
"\xc6\xa7l6\xc6t\xbbG\x1b\x06\x81Y\xa1\x94L\xec" +
"\xd1\x86\x19\xf0\xd6\xc5\xc6\xd6\xfaI\xf00\x06-#\xcf" +
"l\xd6\xc3\xb2y\x95\\n\x8cy\xdf\xcbd\xf4\x0fu" +
"\"\xde\xa6\xab\xbc\x87\xf8+\xfaUR\x84\x0e\xed\xa1\xe3" +
"\x0a,R\xdcL`\xad7\xb9\x17\x0c\xaa8\x10zL" +
"\x99\x07h\xfdN\x82\x8e\xbbg\x1c\xe7\xfcKB\xaf\xf3" +
"I1\x7fq\x18\x10\x82\x9a \x06\x1c\xc6\x00;\xb3\xee" +
"\x84U\xa106\x99U\x9d\x9eY\x1epT\x84\xf9\xf7" +
"X\xf4/\xff\x92\xb4\x0f8)!8\xbe\xe5\xe8\x8f\x17" +
"\xaa\x0a\xaa\xd8DQfU\xd1\xd6\x04C\xb7h\xad\x08" +
"\xff\x17\xd7\xe2\xbf\x19\xf2\xdfOhwo\x89\xd2\xbf\x9c" +
"\xd0v\xec\x0e\x99.\xc58\x8f\xfe\x8f\xee\x03P\xf6\xf0" +
"\xa8|\x93\xc3N\xaf\xd6\xc2\xa6\xb0iS\xa6\xacWQ" +
",7\xa0-\xab\xe6\xc3\xa4\xe7\x98\xac\x98W\xb3l)" +
"\x96\xab'@\x04\x0e\xd1\xf5\x93B\xd1d\x96\x85\x9a\xa1" +
"+%5\xaf\xf1\xf6\xfa\xa0\xe2\xd5K\x85~\x93\x8di" +
"h\x94\xacn\xdbf\x05\xa1h[\x17R\x0f\x87\x00Q" +
"\x90\x14\xb4\xbc\x0bP\xa4`\xa2\xca\xb1\x8bGey\x04" +
"\xa0^\xca\x91\xd7\xf1\xa8\xac\x0e\x01R\xbe\x07\xa0\xac\xe6" +
"Q\xb9\x99C\xb1T\xd2\x82\xa4\xe0\xe4\x8d\xac{\xda " +
"\xaeT\x0b\x95\xb9\xa1\xd7\xe2\xd2\xac`\xd8,\xbf\xde\xe3" +
"h.\xdc\xf1\x85\xc6\xe6\x8a \xe9'\xb3\xdf\xa7\xaao" +
"\xe2\x8b\x16\x81\x03\x15\xb8\xb7\xd7\xc2}Ad\x1f\xbe\xc9" +
"+\x06\xc3}\x08\x7f\xc1\xd6\xfbV\xb5\xb1\x02%=\x1f" +
"\xee\xf2f\xbaA\xb8>\xd4\x99j,r}p\xb9\x91" +
"U\xf3\x95!D\xacL\x86\xd1\xb2\xe5\xc2\xc3Ct\xd1" +
"U\xc56\x17V\x02\xe6\x1a\x7fby=\xf6\x01d\xd6" +
"!\x8f\x99\xad\x18b#o\xc6%\x00\x99\xdbH~\x17" +
"\x86\xf0\xc8\xdb0\x05\x90\xd9D\xf2{1\xb8\x85\xcaw" +
"\xe3~\x80\xcc\xbd$~\x98\xd4c\xbc\xeb\xbd\xf2.w" +
"\xfa\x9d$\xdfC\xf2x,\x89q\x00\xf9Q\x9c\x07\x90" +
"y\x98\xe4\x07H^\xc7%\xb1\x0e@~\x0aG\x012" +
"O\x92\xfcY\x92\x0b\xf1$]\xc4\xe5\x83h\x02d\xfe" +
"\x9e\xe4\xdf'y\xfd\xcc$\xd6\x03\xc8\x87\\\xf9\xf3$" +
"\xff\x11\xc9\x1bZ\x92\xd8\x00 \x1f\xc6-\x00\x99\x1f\x92" +
"\xfc5\x92O\xc3$N\x03\x90\x8f\xe2n\x80\xcck$" +
"\xff7\x92_R\x97\xc4K\x00\xe4\xb7\\{\x8e\x91\xfc" +
"W$o\x8c%\xb1\x11@\xfe\x05\xee\x03\xc8\xfc\x8a\xe4" +
"\xffM\xf2\x84\x90\xc4\x04\x80\xfc\x8e\xbb\xaf\xd3$\xaf\xe7" +
"*.\x81>\x8d+nz\xbca\x05<a\xe5p\x84" +
"\x9e\x8f\xf5\x1b\"\xdd\xe6P\x0c\xfb\xcb\x80(\x02:E" +
"\xc3\xc8\xaf\x1c\xef\x1e\xa2\xad\x0e[\xfe\xad\xb2)\xec\xaf" +
"\x01\x920\xa8\xd3@4\xf4\xde\\\x10\xb3*\x03\xa4o" +
"\x89fu\x97l\xa3T\x846\xe2b.\x08\x16fI" +
"_f\x1a\x85\xd5\xc8\xcc\x82\xa6\xab\xf9I\x02g\x03p" +
"\xd8\x00\xe5H\xe5\xcf=q\x14\xfd\xe8;r\xc0h\xae" +
"\x92\xd1m\xc5\xc5\xab\xd5\xe1\x8ak\xc7\xbc0\xbb\x06\xae" +
"=\x7fA\x98^E=\x12'\xdb\xc6\xd4|\xa9\xba\xa2" +
"\xae\x9bb\xe9\x97\xee\xf4J\xc7\xc9n\x16~\x9b\xac\"" +
"~\xd5(\x84\x06\xaaK\x894\xb3\xda\x82~Qd\xc3" +
"\xfb\xc3k\x83\xbf\xdfE\xed\x91kV^\xb5\x99ew" +
"\x17\xb1\x98\xd7X\xee\xb3\xcc\x14\xa3\xd5E\xcd\xe2)6" +
"\xd9\x8db|E\x86\x91\xc7\x00\xda8W\xde\xf0\x05\xe3" +
"9\xccl\xefW\xaf>dP\xc9$D\xeb\xc4\xa9\x8d" +
"N3K\xbc\x90\xb3\x08\xfb\x9b\x93\xd7\xb9S\x89\xd6i" +
"\xd6\xe6ra\xa2[c\x8d\xf9j\xd4\xa1\xfe%(\xd2" +
"! r\xaf\xe5Q\x19\x89\x90\x9b\xf5\xd5\xe8\x10\xa4\xc3" +
"\xd6\xa0\xc4s\xe5\xde \xa5\xdf\"\x8f\xcam\x1c\x8aj" +
"\xc9\x1e\xc1\xa6\xf0\xcdh\x1c\x08\xe3\xdbW\xc4\xf5^=" +
"\xc7\x00\xd7\xf9\xee\x1aI\xca\xc1c\xc6o\x05\xe3G\x96" +
"\xdf\x16Lz\x80AC\xbfb\xe5\x8f\xecRtz\x8b" +
"\x12og\xba\x05\xb1\xffV\x82~3\\zj\x03p" +
"\xd27\x04\x0c\x1f\x00\xd0\xef\xf7K\x8f\x9a\xc0I\xbb\x04" +
"\xe4\x82\xe7,\xf4\x9f\xad\xa4\xbb\xef\x01N\xda& \x1f" +
"\xbcF\xa1\xdf \xeeX?\x0d\x81\x93n\x170\x16<" +
"\x03\xa2\xdf^\x96n\x19\x05N\xd2\x04\x8c\x07\x0f]\xe8" +
"\xbf{H7m\x01N\x1a\x08\xdb\xa0\xd0\xe9\xed\xa3\x0b" +
"\x1d\x9f\xf3\xd0\xe6\xb2~|S\xd4\xd3\x02\xe8B\xc7\xbf" +
"\x94\xf1\x1fu+s\xb5\xfc\xbe\x1e\x88Y\xd5f]T" +
"({\x01\x0e\xcb\x11\x0e\xbaP\x89a\xa4\xbb\x0ep\xb1" +
"]\x91*?\x99b!\xea\x8f\xff-c._\xcbj" +
"Z'\xe8\x0fG\xe6\xa5\x8a\xbc\x91Ge&7I\xc1" +
"]3tz\x06\xfb\xe4\x17i0\xcd\xff\x07\xc1\xfcG" +
")\\\xff\x88G\xe5X\xc4\xad_'\xe1\xab<*o" +
"F\xca\xd17\xc8\xd7\x8f\xf1\xa8|\x10\xb6\xfc\xdf\xbb\x07" +
"@\xf9\x80\xc7t\xa4\xd2\x92\xce\x93\xe2o\xa8\x1eq\xeb" +
",\xf4\xea\xac8>\x00\x90\xa9\xa7:%\xe9\xd6Y1" +
"\xaf\xce\x92p\x10 \xd3D\xf2Y\xd1:\xab\x05\xd7\x00" +
"df\x92|6\x8e\xbfh\x0b%3,\x7f\xf3\xc6\xf0" +
"rM\xaf\x99\xbc\xfd7\x08\xb4\x97\xa9Z\xbed2\xa8" +
"\xbc\x82\xf4\xf6D\xca\x19\xefq\xc2\xebCf\x88\x849" +
"\xb4\x82\x1e\xe5\x14Z\x1c\x13e\xb2\xbcQ\xca\x0d\xe5U" +
"\x93\xe52\xcc\x14\xbc\x80\xd0\xcf\xc7\x95z\x8c\xfc\xb3\x00" +
"@\xf8\xa8\x1b!\xfb\x84\x99q\xa9i\x1ahV\\5" +
"\x16\x84W\x8d\xe0\xa6\xb1&\xbc\xe1I\\W\xf9\x8a7" +
"\x18^\x8e\xda\xb2j\xc9bU\x98\x00\xcf\xcc\xa0\xcde" +
"\x8d\x18\xa5|.\xcd@\xb0\xcd\xf5U\xb7\xba\xd8d\xd1" +
"W\xf4#a\xa3\x1b\x09\xfd\x97I\xf4\x1f %e7" +
"p\xd2\x0a\x8a\x84\xfe#\x19\xfa/\xe4R\xf7~\xe0\xa4" +
"?\xa7H\xe8\xbf\x0f\xa3\xff\xe8)u\xbc\x04\x9c\xd4\x11" +
"y\xbb\xf1\xf1\xa9z\xbb\xf1>\xb8\xfe@\x1f\xca\x09\x95" +
"\xab\xcc\xa8\x14\xa1\xa2\x8d\x88\x8b\xe8\xecx\x095r\x9c" +
"Sz\xf0\xb8\xe0w\x82\xe0\xffS*bN\xc3\xc5v" +
"\xd0\xfc\xd4\xf8\xff\x01\x00\x00\xff\xff|\xba\xdf\xe8"
const schema_db8274f9144abc7e = "x\xda\xccZ{t\x1c\xe5u\xbfwfW#\x19\xc9" +
"\xaba\x96H\x96#\xab\xd1\xb1Kqb@v\x9d\x82" +
"\x9bF\x92\x91\x1cV\xf8\xa1\xd9\xb5s\xa8\xb1s\x18\xed" +
"~\x92F\x9d\x9dYff\x85\xe5\xe0\xd886\x06\x0e" +
"\x10 6`'n\x8c\x1d\xda\x83\x09)\x0e\xa6)=" +
"\xa4\xc5i\x1c\x12H\x1c\x9c\x03\xa9\x89I\xd3\xc6q[" +
"\xfb\x98R\x0c4\xc7m\xcc\xf4\xdc\x99\x9d\x87v\x17=" +
" \x7f\xe4\xbf\xd5\x9d\xfb=\xee\xef\xfb\xdd\xc7w?]" +
"}\xa0\xae\x8b\xeb\x88oN\x00\xc8\x87\xe35\x0e\x9b\xff" +
"\xd3\x8d\xfb\xe6\xfd\xd3V\x90[\x10\x9d/<\xd7\x97\xbc" +
"`o=\x09q^\x00X\xf4\x98\xb0\x11\xa5g\x05\x01" +
"@zF\xf8\x0f@\xe7\x8e\x8f<\xf5\xb5\xc7zw~" +
"\x11\xc4\x16>T\x06\\\xb4\xbb\xb6\x0f\xa5'kI\xf3" +
"\xf1\xda\x1dR]\x9d\x00\xe0\xdc ^uS\xf2\xe5c" +
"\xa4\x1d\x9d:FS\xbfU;\x1f%$5\xe9b-" +
"M\xfd\xa9\xfcO\xf6\x7fr\xd7K\xdb@l\xe1\xc6M" +
"}\xa6n#J\x17]\xcd\x0bu\xab\x00\x9d\xb7w6" +
"?\xf1\xe8\xb1\x1fl\x07\xf1r\x84\xd2N\xc5\x19?G" +
"@i\xde\x8c\xbf\x01t~\xfc\xeeM\xef\x1c\xfe\xfe\xe2" +
";@\xbc\x82\x14\x90\x14\x8e\xceh\xe7\x00\xa5_\xcc\xe8" +
"\x04t\xce\x9e\xfb\xbf\x1d\x9f\xbfb\xe5\xfd _\x81\x9c" +
"?\xc5\xc5\x19-\x1c\xe0\xa2\xcb.iC@\xa7s\xd9" +
"\x8f\x9fmY\xf4\xd0\xce\xb2\xbds\xa4ym\xfd|\x94" +
"R\xf5\xb4\xa3\xde\xfa[\x01\x9dO\xff\xc9\xd3\xf7\xf5<" +
"\xb4e\x17\x88W\x05\x0b>^\xbf\x96\x16<ZO\x0b" +
"\xfe\xcf\xcc\xaf\x1c+^\xf7\xed\x87J;rg9]" +
"?\x9f\x14.\xba3\xb4\x8f\xce\xbb\xf9\xbbG\x9f~\x18" +
"\xe4\x05\x88\xce\xeb\x03\x1f\x7f\x95\xdf{\xf0$\xacA\x81" +
"6\xb8h}\xc3~2/\xdf@\xba?\xf9\xc4s\x7f" +
"\x7f\xff\xd3;\xbe\x02\xf2\xe5\x88\x00.\x9c\xc7\x1b\xfe\x97" +
"\x14N7\xd0j;O|ge\xfe\x81=\xfb=\x80" +
"\xdc\xef\x0d39\x0eb\xce\xb6\xd4o\xf2k\x0ed\x0e" +
"\x94\xa0\x8b\xd3'\x9cy\x1e\xc9\xee\x99\xae\xdd\x8b\x7f~" +
"z\xd5\x8ao\x0d\xfeudlGb#\x8d\xdd1x" +
"\xfeHc:\xffD5D\x16$\x0e\xa2\xd4\x9b D" +
"\xba\x13\xb4\xc7'\xe7\xdcP\xb7\xe1\xf4\xb2\xa7@\\\xe0" +
"O\xb37\x91\xa6iF_<\xf0\x87\xf3^\xbc\xf5\x10" +
"\xc8Wa\x00\xd6n\xfa\x86\xd23\xee\xd8\xd8\xc9y\x07" +
"\xbf\xf3\xcb\xfb\x0eW\x90Ll\xdc\x88\xd2\xbcFZ\xe5" +
"c\x8d\x9f\x91d\xfa\xe5\xc4\xd6\xf3\xef)\x8f\xfc\xe3\xe1" +
"r\x02{'\xd58\x80\xd2\x0a\xd2[\x94jt\xed\xbb" +
"\xfb\xc8\x9e\x8f\xd7~\xed\xedg\xaa\xaa\xdf\"\x0e\xa0\xb4" +
"]\xa4\x05n\x17\x89I\x97\xa5\xf0\xf5\xe7;b\xdf\x8e" +
"Rm\xde\xa5g\x09\xeak/%\x85\xd67\x966\xe8" +
"on}\xbe\x0c\x14W\xf1\xb5K\xfbPz\xe3R\x9a" +
"\xed\x8c\xab\x1c\xfb\xe4\xc8\x0e\xf1\xd4\xcf\x8ez\xa0x\x96" +
"o\x97F\xc8\xf2G%:\xb8\xbe\x9b\xbe\xfc`\xfc\xf4" +
"\x97_\xa0\xcdE\x9c N~\xb5\xe8\x88d\xa2\xf4\xaa" +
"\xe4\x9e\xb6\xd4\xc4\x03:-O\xfd\xe97\x97\xe6^{" +
"\xa9\xca\x89Hr\xd3yi}\x13\xfd\xfa\xf3&\x02\xf5" +
"\xd4\x82C\x9f?s\xef\xf1WJ\x96\xb8k?\xd3\xe4" +
"\x92\xe6\x87M\xb4\xf6\x85u\xfbnP\x9d\x1bO\x96\x03" +
"\xe3j\x9ei\xfa\x16J\xd8\xecz\xab;]\xc0\xd0j" +
"\xdaJ\xf3\x08JEW\xfb\x96f\x9a\x9b;\xad\xcc\xda" +
"\xf2\xb3O\xbf\x1e!U\xb1\xf9W\x081g\xe5go" +
"\x1a\xa9\xdbt\xeaTt[j\xb3\x0b\xf0&w\xe8?" +
"\xfc\xf3\xc3\xc3\xeb\xbfy\xect\x94H\xcd&\x11\xe9\xbf" +
"\xfe\xea\xec\x97\xce\xe5s\xff\xee\xba\x8c\x7f8\xbb\x9b\x97" +
"\x10\x9c\x87\x9a)\xa44\xb55\xf4\xb6\x9f\xe8?\x1b\xc5" +
"\xfb\xdeYKI\xe1\xb1Y4\xf9\xe2\x9b\xbb\xd9\xbak" +
"n<[\xc1\xb4\xa3\xb3\x96\xa0\xf4\xea,\x17\xebY;" +
"P:\xd3\xd2\x04\xe0\x8c\xfe\xed\x037>\xf1\xbd\x95\xe7" +
"=/v\xf7\xf2Z\xcbB\xda\xcb}_\xe8Yum" +
"\xfb\x91\xf3Q3\x8e\xb7\x9cw]\xb2\x85V\x1a\xbc\xe6" +
"\xdcg\xe6\xdd\xf7\xfd\xf3eG\xe5*\xc6g\xcfG\xe9" +
"\xb2\xd9\x04\x978\xbb\x13\xf0\xcde\x7f\xf9JK\xa2\xe5" +
"\x9d2hkHw\xf1\xec\x11\x94R\xa4\xbb\xa8w\xf6" +
"\x0bD\xe8G\xbf\xbe\xff_.\x1c\xbb\xfe\xdd\x0a\x1b\xae" +
"m%\xee\xb7\xd2\xb4\xa9VAJ\xb5^\x0e\xe0\xdcq" +
"\xf2s\x1b~\xfa\xc5\xb7\xdf-g\x98\xbb\x91\xee\xd64" +
"Jk\xdc\x11r+\x11\xf6\xe1\xd5\xff\xb9\xf9\xdc\xae\x8f" +
"\xfc\xa6b\xee\xb7ZGP\x8a\xcf!M\x9c\xf3\x82\xb4" +
"\x8b~9/\x0b\x07:z6\xbft!rT\x9b\xe6" +
"\xf4\x11<\x0f\x09_=\xb5\xe5\x97\x9f\xfbm\x14\x9e\xb1" +
"9\xbf\"x\xee\x9dC\xf0\xdc\xf6\xe6\xee\xeb\xbf\xb4\xee" +
"\x1b\xefE\x08\xf2\xe4\x9c\xad4\xd4.\xea:\xd3\xccB" +
",{\x95\xff3{eV)\xe8\x85%\xddE{\x98" +
"\xe9\xb6\x9aUl\x96f\x9dV\xc1\xd0-\xd6\x8f(7" +
"\xf21\x80\x18\x02\x88\xca\x08\x80|3\x8f\xb2\xc6\xa1\x88" +
"\x98$\xa2\x88*\x09\x87y\x94m\x0eE\x8eKR\x84" +
"\x15oi\x07\x905\x1e\xe5\x0d\x1c\"\x9fD\x1e@," +
">\x08 o\xe0Q\xde\xc6\xa1S`f^\xd1\x99\x0e" +
"\x09\xbb\xd74\xb1\x1e8\xac\x07tLf\x9bc\xca\x80" +
"\x06\x09\x16\x11\x0b#\xb7\xda\xd8\x00\x1c6\x00:\xc3F" +
"\xd1\xb4\xd6\xe86\xaaZ\x9a\x0d\x9a\xcc\xc2a\xac\x01\x0e" +
"k\x00'2/\xc3,K5\xf4\x15\x8a\xae\x0c1\x13" +
"\x80,\xab\xe5\xe3\x00A\xfaB?\xd1\x89\x1d{\x80\x13" +
"\x17\x08\x18f\x1a\xf4\xc9*~\xec pb\xab\xe0\x98" +
"lH\xb5lf\xe2\x9a\\\xc1\x9d\x9b7\xf4.t\x8a" +
"\xba\xf7\x01\x99\xe9}H\xd0\xaa]\xd8\x8f\xe1\xee\xf8\xca" +
"\xdd]\xa7\xa9L\xb7\x13)}\xd0(\x83\xbc\xaf\x1a\xe4" +
"}%\xc8\xb7E \xbf})\x80|\x1b\x8f\xf2\x9d\x1c" +
"\x8a|\x09\xf3\xed\xf3\x01\xe4-<\xca\xf7p\xe8d\xdd" +
"ER9\x00\x08\xd0\x1cd\x8a]4\x99E\xb2\x99\x80" +
"\xfd<\xba\xa0\xcf\x04\xdc<\xcaL\xda\xbb\x7f\x08\x09\xc5" +
"\xcc\x0e\x07\x075\x01\xd2\xbd\x1bT\xcbV\xf5\xa1\xd5\xae" +
"\xbc\xb3\xdf\xd0\xd4\xec\x18YU\xef\xee\xb3u\x09\x00\xa2" +
"x\xd9Z\x00\xe4Dq)@\xa7:\xa4\x1b&sr" +
"\xaa\x955t\x9d\x01\x9f\xb57\x0f(\x9a\xa2gY\xb0" +
"PM\xe5B\xde\x02\x19f\x8e2\xf3J%B\xdf\xb9" +
"\xfd\x8a\xa9\xf0yK\xae\x0fp\xec]\x0b \xf7\xf0(" +
"\xf7Gp\\A8.\xe7Q\xbe1\x82\xe3\x1a\xc2\xb1" +
"\x9fGy\x1d\x87\x8ea\xaaC\xaa~\x1d\x03\xde\x8c2" +
"\xd0\xb2u%\xcf\x08\xb3\x12\x1e\x9b\x8d\x82\xad\x1a\xba\x85" +
"\x8da\xd6\x01\xc4\xc6\x08R\xc2d\x9c\xbc\xd2\xa7\x94\xcf" +
"(C\x9f\x9bfVQ\xd0lK\x8e\x05\x964,\x01" +
"\x90ky\x94\x93\x1cv\x9a\xcc*j66\x86\xe5\xc4" +
"\xefbU\x1f\xbed\xb0\xe8\xa6t\x84\\>|\xdb\x17" +
"\x86\xe4\xc2\x12zw\x11z\xdbx\x94\xef'\x16\xa2\xc7" +
"\xc2{\xf7\x00\xc8\xf7\xf3(\x7f\x95C1\xc6%1\x86" +
"(\xee\xa6\xb8\xf1\x08\x8f\xf2\xd79t,o\xe5\x14`" +
"\xce\x87\xb9-g\xd9\xa9\x82\xff\xd7\xe6\x9ce\xf7\x1b\xa6" +
"\x8d\x02p(\x00\x91\xd9\xb0X\xf7 9Z*\xa7\xb1" +
"\xebU^\xb71\x0e\x1c\xc6\xc9zS\xc9\xb2\xeb\x0c\x8a" +
".l\x83]:$\x10q\x06\xc0D^\xe8\x11*A" +
"\x91\xd0\x0b\x0f\xbe\xf9W\x10{\xfe\x88G\xf9\x8f#\xe6" +
"w\x90\x01W\xf3(\x7f\x8aCG\xc9f\x8d\xa2n\xaf" +
"\x06^\x19*s\x92\x0c\x83D\xd6d!\x7f\xfcek" +
"\xab\xc4\x01C\x1fT\x87\x8a\xa6bGN\xa8X\xc8)" +
"6\x1b\xf7\xc9%\x86\xc6O\x81\x18A!2mb\xf8" +
"\xa1\xac\x8c\x1a\x09S\xc9[Ql\xd2\xd5\xb0!\x1a|" +
"\x82G\xf9\x9a\xea\x87\xbb9\xcf,K\x19b\x15\xf1$" +
"^\x15\x13\x9de\xc9\xea4\xf3\xb2\xd2\x95&\xb3\x84\xa2" +
"f\xd3.\xea\x1d\xc7\xdb\x06\x91q.\x8f\xf2\xd5\x1c6" +
"\xe0{\x8e\xb7\x8f\x05\x0f\x86g\xd4\xc6L\xd30\xb11" +
"\xcc\xda%H\xb2\xa5\x05\xd0\xd0{\x98\xad\xa8\x1a\x92\x1f" +
"\x07\xa5m\x19p\x93\x05\xa2\x106O<\xb7\x93\xdc)" +
"?\xee\xa4\xc8\x1f\x1ay\x94?\xca\xa13D\\\xedg" +
"&\xaaFn\xa5\xa2\x1b\x19\x9eeC\"\x97V\x9a9" +
"\xddE]~\xd8\x16\x04\xa3&\x1eo\xb2\x12\x08\xa5\xe1" +
"\xfdm\xde\x9e#\x11\xa0=\xcc\xde\xc11\xdf>\x10F" +
"\x80 \x80\xdeE\xcer'\x8f\xf2\xceH\"z\xa0/" +
"\x1a\x02bI\x8c\x01\x88\xbb\x89%;y\x94\xf7q\xe3" +
"s<\x1be\xba\xdd\xa3\x0e\x81\xc0\xacPJ[\xecQ" +
"\x87\x18\xf0\xd6\x87\x0d\xc6\xb5\x93\xe0a\x0cX\x86\xc6l" +
"\xd6\xc3\xb2\x9aB.7\xca\xbc\xef%2\xfa\x87:\x11" +
"o\xd3\x15\xdeC\xfcM\xf8eU\xc4\x83\xdaC\xea\x06" +
"\xd0.X\x18\xba\x95\xc0\xc2Z\xa8\xcd*(\xba5\x95" +
"X\xe2\xad\xef\xc5\x8b\x0a\x9a\x84NU\xa2\x0aZ\xbf\x93" +
"\xb8\xe4\xc2\x82\xe3\xe2\xc3\xd2\xd0\xba\xc0\xb8%\xa1qA" +
"\x9d\x11\x03\x0ec\x80\x9dYw\xc2\x0a\x0bc\x93\xed\xaa" +
"\xd3\xdb\x16a\x1bs\x0b;\xffn\x8c~CA\x14\xf7" +
"\x03'6\x08\x8e\xbfs\xf4\xc7\x0b\x15EZl\xa2@" +
"\xb4\xaa`\xab\x82\xa1[\xb4V\xc4E\x96Ts\x11\xb3" +
"J\x92\xdc\x1a\xf5\x90R\x92|`O\xe8\x0c^\x92\x04" +
"\x10\xf7\xee\x07\x90\xf7\xf1(\x7f\x83\xc3N\xaf~\xc3\xc6" +
"\xb0\x13Tb\xb5W\xa5,7\xa0-\xabha\xcet" +
"LV\xd0\x94,\xeb\xc5RE\x06\x88\xc0!\xba\xae\x94" +
"/\x98\xcc\xb2P5t\xb9\xa8h*o\x8f\x05U\xb4" +
"^\xcc\xf7\x9blTE\xa3hu\xdb6\xcb\x0b\x05\xdb" +
"\x9aJ\x8d\x1d\x02DqTP5\x17\xa0H\x11F\xd5" +
"h\x17\x8f\xf2\xf2\x08@)J\xa3\xd7\xf3(\xaf\x0e\x01" +
"\x92\xbf\x0b \xaf\xe6Q\xbe\x99\xc3D\xb1\xa8\x06y\xc3" +
"\xd1\x8c\xac{\xda\x90X\xa9\xe4\xcb\xd3G\xca\xe2\xd2," +
"o\xd8L\x1b\xf38\x9a\x0b-\x9ej\xf8.\x8b\xa3~" +
"\xbe\xfb}\xaa$'\xbe\xbc\x118P\x86{{5\xdc" +
"\x17F\xec\xf0\xb7\xbcb \xb4C\xf8\x0b6\x16D\x1d" +
"\x96\xa7\xbc\xe8\xc3]2\xa6\x1b\x84\x1bB\x9d\xe9\xc6\"" +
"\xd7\x07\x97\x1bYE+\x0f!\x89\xf2|\x19\xadl\xa6" +
"\x1e\x1e\xa2\x8b\xae*\xb4\xb9\xb0\x120\xd7\xf8\x13Kc" +
"\xd8\x07\x90\xd9\x80<f\xb6a\x88\x8dt;.\x05\xc8" +
"\xdcF\xf2;1\x84G\xda\x8e-\x00\x99-$\xbf\x07" +
"\x83\x9b\xadt\x17\x1e\x04\xc8\xdcC\xe2GH=\xc6\xbb" +
"\xde+\xedr\xa7\xdfI\xf2}$\x8f\xc7\x92\x18\x07\x90" +
"\xf6\xe2|\x80\xcc#$?L\xf2\x1a.\x895\x00\xd2" +
"!\x1c\x01\xc8<E\xf2\xe7H.\xc4\x93t\xb9\x97\x9e" +
"E\x13 \xf3w$\xff\x1e\xc9k\x9b\x93X\x0b \x1d" +
"q\xe5\xcf\x93\xfcG$\xaf\x9b\x95\xc4:\x00\xe9\x87\xb8" +
"\x15 \xf3\x03\x92\xbfB\xf2\x19\x98\xa4\xd2W:\x8e{" +
"\x002\xaf\x90\xfc_I~IM\x12/\x01\x90~\xe1" +
"\xee\xe7\x04\xc9\x7fM\xf2\xfaX\x92\xeae\xe9\xdfp?" +
"@\xe6\xd7$\xffo\x927\x08Il\x00\x90\xdep\xed" +
":G\xf2Z\xae\xecb\xe9\xd3\xb8\xec\xf6\xc8\x1bV\xc0" +
"\x13V\x0aG\xe8\xf9X\xbf\x91\xa0\x1b\"&\xc2\xa65" +
" &\x00\x9d\x82ah+\xc7\xbbG\xc2V\x86,\xff" +
"\xa6\xda\x18\xf6\xec\x00I\x18\x94r\x900\xf4T.\x88" +
"Y\xe5\x01\xd2\xdf\x89ju\x17m\xa3X\x806\xe2b" +
".\x08\x16fQ_f\x1a\xf9\xd5\xc8\xcc\xbc\xaa+\xda" +
"$\x81\xb3\x0e8\xac\x83R\xa4\xf2\xe7\x9e8\x8a\xbe\xff" +
"\xbd;`4W\xce\xe8\xb6\xc2\x92\xd5\xcaPY\xed0" +
"\x7f\x92\xda!\xa1G\xe2d\xdb\xa8\xa2\x15+\x8b\xee\x9a" +
"iV\x87\xe9N\xaf\xba\x9c\xec\xf2\xe1\xb7\xde\xca\xe2W" +
"\x95ZiMe)\x91fV[\xa1J\xb1t0\xbc" +
"Y\xf8\xf6.n\x8f\xdc\xc44\xc5f\x96\xdd]\xc0\x82" +
"\xa6\xb2\xdcg\x99\x99\x88V\x17\xd1Rjj\xa9l\\" +
"\xd1\xe6\x1a\x8c\x91\x07\x062\x9c+\x19<e<\x87\x98" +
"\xed\xfdJ\xe9\x83\x06\x95LB\xb4\x94\x9c\xde\xe84\xb3" +
"\x12S9\x8b\xb0g:y)<\x9dh\x9dfm." +
"\x17&\xbaXV\x99\xafJ\x1d\xea\xdf\x93\"\xcd/\"" +
"\xf7:\x1e\xe5\xe1\x08\xb9\x19\xa5\xda\x1c\x8fr!\xac\x17" +
"\xf2\xe9\xb0\xdd(\xf2\\\xa9\xdfH\xe9\xb7\xc0\xa3|\x1b" +
"\x87\x09\xa5h\x0fcc\xf8\x0e5\x0e\x84\xf1-1\xe2" +
"zJ\xcf1\xc0\x0d\xbe\xbbF\x92r\xf0@\xf2\x81`" +
"|\xdf\xf2\xdb\x82I\x0f0x$([\xf9}\x1b\x19" +
"\x9d\xde\xa2\xc4\xdbf\xb7 \xf6\xdf_\xd0o\xb0\x8b\x87" +
"6\x02'>.`\xf8\xa8\x80\xfe\x1b\x82\xb8\xd7\x04N" +
"\xdc% \x17<\x91\xa1\xff\x14&\xdeu7p\xe2v" +
"\x01\xf9\xe0\x85\x0b\xfd\xa6s\xc7\xd8\x0c\x04N\xdc$`" +
",x[D\xbfe-\xde2\x02\x9c\xa8\x0a\x18\x0f\x1e" +
"\xcf\xd0\x7fK\x11\xd7o\x05N\\\x13\xb6V\xa1\xd3\xb3" +
"\xa3\x0b\x1d\x9f\xf3\xd0\xe6\xb2~|\xa3\xd5\xd3\x02\xe8B" +
"\xc7\xbf\xb7\xf1\xefwqs\xb5\xfc^!$\xb2\x8a\xcd" +
"\xba\xa8P\xf6\x02\x1c\x96\"\x1ct\xa1\x1c\xc3H\xc7>" +
"\xd27\xfa\x80\x8d\x93\x0a?\x99f!\xea\x8f\xff\x801" +
"\x97\xaf\xb6kZ'\xe89G\xe6\xa5\x8a\xbc\x9eG\xb9" +
"\x99\x9b\xa4\xe0\xae\x1a:\xbd\x0d\xfb\xe4O\xd0`\x9a\xff" +
"\x0f\x82\xf9\x8fS\xb8\xfe\x11\x8f\xf2\x89\x88[\xbfJ\xc2" +
"\x97y\x94_\x8f\x94\xa3\xaf\x91\xaf\x9f\xe0Q~'|" +
"Fx\xebn\x00\xf9\x1d\x1e\xd3\x91JK\xbcH\x8a\xbf" +
"\xa5z\xc4\xad\xb3\xd0\xab\xb3\xe2\xf8 @\xa6\x96\xea\x94" +
"\xa4[g\xc5\xbc:K\xc4\x01\x80L#\xc9?\x1a\xad" +
"\xb3f\xe1Z\x80L3\xc9\xe7\xe2\xf8k\xb7P4\xc3" +
"\xf2W3\x86\x96\xabz\xd5\xe4\xed\xbfk\xa0\xbdLQ" +
"\xb5\xa2\xc9\xa0\xfc\x0a\x92\xea\x89\x943\xde\x83\x87\xd7\xc6" +
"\xcc\x10\x09sh\x05-\xceitA&\xcad\x9aQ" +
"\xcc\x0dj\x8a\xc9r\x19f\x0a^@\xe8\xe7\xe3r-" +
"F\xfe\x03\x01 |(\x8e\x90}\xc2\xcc\xd8k\x9a\x06" +
"\x9aeW\x8d\x85\xe1U#\xb8i\xac\x0dox\"\xd7" +
"U\xba\xe2\x0d\x84\x97\xa3\xb6\xacR\xb4X\x05&\xc03" +
"3\xe8\x84Y\xc3FQ\xcb\xa5\x19\x08\xb69Vq\xab" +
"\x8bM\x16}\x13~$\xacw#\xa1\xff\xda\x89\xfe\xa3" +
"\xa6(\xef\x01N\\A\x91\xd0\x7fxC\xff\xd5]\xec" +
">\x08\x9c\xf8g\x14\x09\xfd7g\xf4\x1fR\xc5\x8e\x17" +
"\x81\x13;\"\xefA>>\x15\xefA\xde\x07\xd7\x1f\xe8" +
"C)\xa1r\xe5\x19\x95\"T\xb4\x11\xf1!:;^" +
"B\x8d\x1c\xe7\xb4\x1eQ\xa6\xfc\xf6\x10\xfc\xd3KY\xcc" +
"\xa9\xfb\xb0M6?5\xfe\x7f\x00\x00\x00\xff\xff\xda\xbc" +
"\xea\xa1"
func init() {
schemas.Register(schema_db8274f9144abc7e,
+57
View File
@@ -0,0 +1,57 @@
Copyright (c) 2019 Cloudflare. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Cloudflare nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
========================================================================
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+96
View File
@@ -0,0 +1,96 @@
package x25519
import (
fp "github.com/cloudflare/circl/math/fp25519"
)
// ladderJoye calculates a fixed-point multiplication with the generator point.
// The algorithm is the right-to-left Joye's ladder as described
// in "How to precompute a ladder" in SAC'2017.
func ladderJoye(k *Key) {
w := [5]fp.Elt{} // [mu,x1,z1,x2,z2] order must be preserved.
fp.SetOne(&w[1]) // x1 = 1
fp.SetOne(&w[2]) // z1 = 1
w[3] = fp.Elt{ // x2 = G-S
0xbd, 0xaa, 0x2f, 0xc8, 0xfe, 0xe1, 0x94, 0x7e,
0xf8, 0xed, 0xb2, 0x14, 0xae, 0x95, 0xf0, 0xbb,
0xe2, 0x48, 0x5d, 0x23, 0xb9, 0xa0, 0xc7, 0xad,
0x34, 0xab, 0x7c, 0xe2, 0xee, 0xcd, 0xae, 0x1e,
}
fp.SetOne(&w[4]) // z2 = 1
const n = 255
const h = 3
swap := uint(1)
for s := 0; s < n-h; s++ {
i := (s + h) / 8
j := (s + h) % 8
bit := uint((k[i] >> uint(j)) & 1)
copy(w[0][:], tableGenerator[s*Size:(s+1)*Size])
diffAdd(&w, swap^bit)
swap = bit
}
for s := 0; s < h; s++ {
double(&w[1], &w[2])
}
toAffine((*[fp.Size]byte)(k), &w[1], &w[2])
}
// ladderMontgomery calculates a generic scalar point multiplication
// The algorithm implemented is the left-to-right Montgomery's ladder.
func ladderMontgomery(k, xP *Key) {
w := [5]fp.Elt{} // [x1, x2, z2, x3, z3] order must be preserved.
w[0] = *(*fp.Elt)(xP) // x1 = xP
fp.SetOne(&w[1]) // x2 = 1
w[3] = *(*fp.Elt)(xP) // x3 = xP
fp.SetOne(&w[4]) // z3 = 1
move := uint(0)
for s := 255 - 1; s >= 0; s-- {
i := s / 8
j := s % 8
bit := uint((k[i] >> uint(j)) & 1)
ladderStep(&w, move^bit)
move = bit
}
toAffine((*[fp.Size]byte)(k), &w[1], &w[2])
}
func toAffine(k *[fp.Size]byte, x, z *fp.Elt) {
fp.Inv(z, z)
fp.Mul(x, x, z)
_ = fp.ToBytes(k[:], x)
}
var lowOrderPoints = [5]fp.Elt{
{ /* (0,_,1) point of order 2 on Curve25519 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
{ /* (1,_,1) point of order 4 on Curve25519 */
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
{ /* (x,_,1) first point of order 8 on Curve25519 */
0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae,
0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a,
0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd,
0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00,
},
{ /* (x,_,1) second point of order 8 on Curve25519 */
0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24,
0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b,
0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86,
0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57,
},
{ /* (-1,_,1) a point of order 4 on the twist of Curve25519 */
0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
},
}
+30
View File
@@ -0,0 +1,30 @@
//go:build amd64 && !purego
// +build amd64,!purego
package x25519
import (
fp "github.com/cloudflare/circl/math/fp25519"
"golang.org/x/sys/cpu"
)
var hasBmi2Adx = cpu.X86.HasBMI2 && cpu.X86.HasADX
var _ = hasBmi2Adx
func double(x, z *fp.Elt) { doubleAmd64(x, z) }
func diffAdd(w *[5]fp.Elt, b uint) { diffAddAmd64(w, b) }
func ladderStep(w *[5]fp.Elt, b uint) { ladderStepAmd64(w, b) }
func mulA24(z, x *fp.Elt) { mulA24Amd64(z, x) }
//go:noescape
func ladderStepAmd64(w *[5]fp.Elt, b uint)
//go:noescape
func diffAddAmd64(w *[5]fp.Elt, b uint)
//go:noescape
func doubleAmd64(x, z *fp.Elt)
//go:noescape
func mulA24Amd64(z, x *fp.Elt)
+111
View File
@@ -0,0 +1,111 @@
#define ladderStepLeg \
addSub(x2,z2) \
addSub(x3,z3) \
integerMulLeg(b0,x2,z3) \
integerMulLeg(b1,x3,z2) \
reduceFromDoubleLeg(t0,b0) \
reduceFromDoubleLeg(t1,b1) \
addSub(t0,t1) \
cselect(x2,x3,regMove) \
cselect(z2,z3,regMove) \
integerSqrLeg(b0,t0) \
integerSqrLeg(b1,t1) \
reduceFromDoubleLeg(x3,b0) \
reduceFromDoubleLeg(z3,b1) \
integerMulLeg(b0,x1,z3) \
reduceFromDoubleLeg(z3,b0) \
integerSqrLeg(b0,x2) \
integerSqrLeg(b1,z2) \
reduceFromDoubleLeg(x2,b0) \
reduceFromDoubleLeg(z2,b1) \
subtraction(t0,x2,z2) \
multiplyA24Leg(t1,t0) \
additionLeg(t1,t1,z2) \
integerMulLeg(b0,x2,z2) \
integerMulLeg(b1,t0,t1) \
reduceFromDoubleLeg(x2,b0) \
reduceFromDoubleLeg(z2,b1)
#define ladderStepBmi2Adx \
addSub(x2,z2) \
addSub(x3,z3) \
integerMulAdx(b0,x2,z3) \
integerMulAdx(b1,x3,z2) \
reduceFromDoubleAdx(t0,b0) \
reduceFromDoubleAdx(t1,b1) \
addSub(t0,t1) \
cselect(x2,x3,regMove) \
cselect(z2,z3,regMove) \
integerSqrAdx(b0,t0) \
integerSqrAdx(b1,t1) \
reduceFromDoubleAdx(x3,b0) \
reduceFromDoubleAdx(z3,b1) \
integerMulAdx(b0,x1,z3) \
reduceFromDoubleAdx(z3,b0) \
integerSqrAdx(b0,x2) \
integerSqrAdx(b1,z2) \
reduceFromDoubleAdx(x2,b0) \
reduceFromDoubleAdx(z2,b1) \
subtraction(t0,x2,z2) \
multiplyA24Adx(t1,t0) \
additionAdx(t1,t1,z2) \
integerMulAdx(b0,x2,z2) \
integerMulAdx(b1,t0,t1) \
reduceFromDoubleAdx(x2,b0) \
reduceFromDoubleAdx(z2,b1)
#define difAddLeg \
addSub(x1,z1) \
integerMulLeg(b0,z1,ui) \
reduceFromDoubleLeg(z1,b0) \
addSub(x1,z1) \
integerSqrLeg(b0,x1) \
integerSqrLeg(b1,z1) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1) \
integerMulLeg(b0,x1,z2) \
integerMulLeg(b1,z1,x2) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1)
#define difAddBmi2Adx \
addSub(x1,z1) \
integerMulAdx(b0,z1,ui) \
reduceFromDoubleAdx(z1,b0) \
addSub(x1,z1) \
integerSqrAdx(b0,x1) \
integerSqrAdx(b1,z1) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1) \
integerMulAdx(b0,x1,z2) \
integerMulAdx(b1,z1,x2) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1)
#define doubleLeg \
addSub(x1,z1) \
integerSqrLeg(b0,x1) \
integerSqrLeg(b1,z1) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1) \
subtraction(t0,x1,z1) \
multiplyA24Leg(t1,t0) \
additionLeg(t1,t1,z1) \
integerMulLeg(b0,x1,z1) \
integerMulLeg(b1,t0,t1) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1)
#define doubleBmi2Adx \
addSub(x1,z1) \
integerSqrAdx(b0,x1) \
integerSqrAdx(b1,z1) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1) \
subtraction(t0,x1,z1) \
multiplyA24Adx(t1,t0) \
additionAdx(t1,t1,z1) \
integerMulAdx(b0,x1,z1) \
integerMulAdx(b1,t0,t1) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1)
+156
View File
@@ -0,0 +1,156 @@
// +build amd64
#include "textflag.h"
// Depends on circl/math/fp25519 package
#include "../../math/fp25519/fp_amd64.h"
#include "curve_amd64.h"
// CTE_A24 is (A+2)/4 from Curve25519
#define CTE_A24 121666
#define Size 32
// multiplyA24Leg multiplies x times CTE_A24 and stores in z
// Uses: AX, DX, R8-R13, FLAGS
// Instr: x86_64, cmov
#define multiplyA24Leg(z,x) \
MOVL $CTE_A24, AX; MULQ 0+x; MOVQ AX, R8; MOVQ DX, R9; \
MOVL $CTE_A24, AX; MULQ 8+x; MOVQ AX, R12; MOVQ DX, R10; \
MOVL $CTE_A24, AX; MULQ 16+x; MOVQ AX, R13; MOVQ DX, R11; \
MOVL $CTE_A24, AX; MULQ 24+x; \
ADDQ R12, R9; \
ADCQ R13, R10; \
ADCQ AX, R11; \
ADCQ $0, DX; \
MOVL $38, AX; /* 2*C = 38 = 2^256 MOD 2^255-19*/ \
IMULQ AX, DX; \
ADDQ DX, R8; \
ADCQ $0, R9; MOVQ R9, 8+z; \
ADCQ $0, R10; MOVQ R10, 16+z; \
ADCQ $0, R11; MOVQ R11, 24+z; \
MOVQ $0, DX; \
CMOVQCS AX, DX; \
ADDQ DX, R8; MOVQ R8, 0+z;
// multiplyA24Adx multiplies x times CTE_A24 and stores in z
// Uses: AX, DX, R8-R12, FLAGS
// Instr: x86_64, cmov, bmi2
#define multiplyA24Adx(z,x) \
MOVQ $CTE_A24, DX; \
MULXQ 0+x, R8, R10; \
MULXQ 8+x, R9, R11; ADDQ R10, R9; \
MULXQ 16+x, R10, AX; ADCQ R11, R10; \
MULXQ 24+x, R11, R12; ADCQ AX, R11; \
;;;;;;;;;;;;;;;;;;;;; ADCQ $0, R12; \
MOVL $38, DX; /* 2*C = 38 = 2^256 MOD 2^255-19*/ \
IMULQ DX, R12; \
ADDQ R12, R8; \
ADCQ $0, R9; MOVQ R9, 8+z; \
ADCQ $0, R10; MOVQ R10, 16+z; \
ADCQ $0, R11; MOVQ R11, 24+z; \
MOVQ $0, R12; \
CMOVQCS DX, R12; \
ADDQ R12, R8; MOVQ R8, 0+z;
#define mulA24Legacy \
multiplyA24Leg(0(DI),0(SI))
#define mulA24Bmi2Adx \
multiplyA24Adx(0(DI),0(SI))
// func mulA24Amd64(z, x *fp255.Elt)
TEXT ·mulA24Amd64(SB),NOSPLIT,$0-16
MOVQ z+0(FP), DI
MOVQ x+8(FP), SI
CHECK_BMI2ADX(LMA24, mulA24Legacy, mulA24Bmi2Adx)
// func ladderStepAmd64(w *[5]fp255.Elt, b uint)
// ladderStepAmd64 calculates a point addition and doubling as follows:
// (x2,z2) = 2*(x2,z2) and (x3,z3) = (x2,z2)+(x3,z3) using as a difference (x1,-).
// work = (x1,x2,z2,x3,z3) are five fp255.Elt of 32 bytes.
// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and
// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes.
TEXT ·ladderStepAmd64(SB),NOSPLIT,$192-16
// Parameters
#define regWork DI
#define regMove SI
#define x1 0*Size(regWork)
#define x2 1*Size(regWork)
#define z2 2*Size(regWork)
#define x3 3*Size(regWork)
#define z3 4*Size(regWork)
// Local variables
#define t0 0*Size(SP)
#define t1 1*Size(SP)
#define b0 2*Size(SP)
#define b1 4*Size(SP)
MOVQ w+0(FP), regWork
MOVQ b+8(FP), regMove
CHECK_BMI2ADX(LLADSTEP, ladderStepLeg, ladderStepBmi2Adx)
#undef regWork
#undef regMove
#undef x1
#undef x2
#undef z2
#undef x3
#undef z3
#undef t0
#undef t1
#undef b0
#undef b1
// func diffAddAmd64(w *[5]fp255.Elt, b uint)
// diffAddAmd64 calculates a differential point addition using a precomputed point.
// (x1,z1) = (x1,z1)+(mu) using a difference point (x2,z2)
// w = (mu,x1,z1,x2,z2) are five fp.Elt, and
// stack = (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes.
TEXT ·diffAddAmd64(SB),NOSPLIT,$128-16
// Parameters
#define regWork DI
#define regSwap SI
#define ui 0*Size(regWork)
#define x1 1*Size(regWork)
#define z1 2*Size(regWork)
#define x2 3*Size(regWork)
#define z2 4*Size(regWork)
// Local variables
#define b0 0*Size(SP)
#define b1 2*Size(SP)
MOVQ w+0(FP), regWork
MOVQ b+8(FP), regSwap
cswap(x1,x2,regSwap)
cswap(z1,z2,regSwap)
CHECK_BMI2ADX(LDIFADD, difAddLeg, difAddBmi2Adx)
#undef regWork
#undef regSwap
#undef ui
#undef x1
#undef z1
#undef x2
#undef z2
#undef b0
#undef b1
// func doubleAmd64(x, z *fp255.Elt)
// doubleAmd64 calculates a point doubling (x1,z1) = 2*(x1,z1).
// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and
// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes.
TEXT ·doubleAmd64(SB),NOSPLIT,$192-16
// Parameters
#define x1 0(DI)
#define z1 0(SI)
// Local variables
#define t0 0*Size(SP)
#define t1 1*Size(SP)
#define b0 2*Size(SP)
#define b1 4*Size(SP)
MOVQ x+0(FP), DI
MOVQ z+8(FP), SI
CHECK_BMI2ADX(LDOUB,doubleLeg,doubleBmi2Adx)
#undef x1
#undef z1
#undef t0
#undef t1
#undef b0
#undef b1
+85
View File
@@ -0,0 +1,85 @@
package x25519
import (
"encoding/binary"
"math/bits"
fp "github.com/cloudflare/circl/math/fp25519"
)
func doubleGeneric(x, z *fp.Elt) {
t0, t1 := &fp.Elt{}, &fp.Elt{}
fp.AddSub(x, z)
fp.Sqr(x, x)
fp.Sqr(z, z)
fp.Sub(t0, x, z)
mulA24Generic(t1, t0)
fp.Add(t1, t1, z)
fp.Mul(x, x, z)
fp.Mul(z, t0, t1)
}
func diffAddGeneric(w *[5]fp.Elt, b uint) {
mu, x1, z1, x2, z2 := &w[0], &w[1], &w[2], &w[3], &w[4]
fp.Cswap(x1, x2, b)
fp.Cswap(z1, z2, b)
fp.AddSub(x1, z1)
fp.Mul(z1, z1, mu)
fp.AddSub(x1, z1)
fp.Sqr(x1, x1)
fp.Sqr(z1, z1)
fp.Mul(x1, x1, z2)
fp.Mul(z1, z1, x2)
}
func ladderStepGeneric(w *[5]fp.Elt, b uint) {
x1, x2, z2, x3, z3 := &w[0], &w[1], &w[2], &w[3], &w[4]
t0 := &fp.Elt{}
t1 := &fp.Elt{}
fp.AddSub(x2, z2)
fp.AddSub(x3, z3)
fp.Mul(t0, x2, z3)
fp.Mul(t1, x3, z2)
fp.AddSub(t0, t1)
fp.Cmov(x2, x3, b)
fp.Cmov(z2, z3, b)
fp.Sqr(x3, t0)
fp.Sqr(z3, t1)
fp.Mul(z3, x1, z3)
fp.Sqr(x2, x2)
fp.Sqr(z2, z2)
fp.Sub(t0, x2, z2)
mulA24Generic(t1, t0)
fp.Add(t1, t1, z2)
fp.Mul(x2, x2, z2)
fp.Mul(z2, t0, t1)
}
func mulA24Generic(z, x *fp.Elt) {
const A24 = 121666
const n = 8
var xx [4]uint64
for i := range xx {
xx[i] = binary.LittleEndian.Uint64(x[i*n : (i+1)*n])
}
h0, l0 := bits.Mul64(xx[0], A24)
h1, l1 := bits.Mul64(xx[1], A24)
h2, l2 := bits.Mul64(xx[2], A24)
h3, l3 := bits.Mul64(xx[3], A24)
var c3 uint64
l1, c0 := bits.Add64(h0, l1, 0)
l2, c1 := bits.Add64(h1, l2, c0)
l3, c2 := bits.Add64(h2, l3, c1)
l4, _ := bits.Add64(h3, 0, c2)
_, l4 = bits.Mul64(l4, 38)
l0, c0 = bits.Add64(l0, l4, 0)
xx[1], c1 = bits.Add64(l1, 0, c0)
xx[2], c2 = bits.Add64(l2, 0, c1)
xx[3], c3 = bits.Add64(l3, 0, c2)
xx[0], _ = bits.Add64(l0, (-c3)&38, 0)
for i := range xx {
binary.LittleEndian.PutUint64(z[i*n:(i+1)*n], xx[i])
}
}
+11
View File
@@ -0,0 +1,11 @@
//go:build !amd64 || purego
// +build !amd64 purego
package x25519
import fp "github.com/cloudflare/circl/math/fp25519"
func double(x, z *fp.Elt) { doubleGeneric(x, z) }
func diffAdd(w *[5]fp.Elt, b uint) { diffAddGeneric(w, b) }
func ladderStep(w *[5]fp.Elt, b uint) { ladderStepGeneric(w, b) }
func mulA24(z, x *fp.Elt) { mulA24Generic(z, x) }
+19
View File
@@ -0,0 +1,19 @@
/*
Package x25519 provides Diffie-Hellman functions as specified in RFC-7748.
Validation of public keys.
The Diffie-Hellman function, as described in RFC-7748 [1], works for any
public key. However, if a different protocol requires contributory
behaviour [2,3], then the public keys must be validated against low-order
points [3,4]. To do that, the Shared function performs this validation
internally and returns false when the public key is invalid (i.e., it
is a low-order point).
References:
- [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt)
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
*/
package x25519
+47
View File
@@ -0,0 +1,47 @@
package x25519
import (
"crypto/subtle"
fp "github.com/cloudflare/circl/math/fp25519"
)
// Size is the length in bytes of a X25519 key.
const Size = 32
// Key represents a X25519 key.
type Key [Size]byte
func (k *Key) clamp(in *Key) *Key {
*k = *in
k[0] &= 248
k[31] = (k[31] & 127) | 64
return k
}
// isValidPubKey verifies if the public key is not a low-order point.
func (k *Key) isValidPubKey() bool {
fp.Modp((*fp.Elt)(k))
isLowOrder := false
for _, P := range lowOrderPoints {
isLowOrder = isLowOrder || subtle.ConstantTimeCompare(P[:], k[:]) != 0
}
return !isLowOrder
}
// KeyGen obtains a public key given a secret key.
func KeyGen(public, secret *Key) {
ladderJoye(public.clamp(secret))
}
// Shared calculates Alice's shared key from Alice's secret key and Bob's
// public key returning true on success. A failure case happens when the public
// key is a low-order point, thus the shared key is all-zeros and the function
// returns false.
func Shared(shared, secret, public *Key) bool {
validPk := *public
validPk[31] &= (1 << (255 % 8)) - 1
ok := validPk.isValidPubKey()
ladderMontgomery(shared.clamp(secret), &validPk)
return ok
}
+268
View File
@@ -0,0 +1,268 @@
package x25519
import "github.com/cloudflare/circl/math/fp25519"
// tableGenerator contains the set of points:
//
// t[i] = (xi+1)/(xi-1),
//
// where (xi,yi) = 2^iG and G is the generator point
// Size = (256)*(256/8) = 8192 bytes.
var tableGenerator = [256 * fp25519.Size]byte{
/* (2^ 0)P */ 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5f,
/* (2^ 1)P */ 0x96, 0xfe, 0xaa, 0x16, 0xf4, 0x20, 0x82, 0x6b, 0x34, 0x6a, 0x56, 0x4f, 0x2b, 0xeb, 0xeb, 0x82, 0x0f, 0x95, 0xa5, 0x75, 0xb0, 0xa5, 0xa9, 0xd5, 0xf4, 0x88, 0x24, 0x4b, 0xcf, 0xb2, 0x42, 0x51,
/* (2^ 2)P */ 0x0c, 0x68, 0x69, 0x00, 0x75, 0xbc, 0xae, 0x6a, 0x41, 0x9c, 0xf9, 0xa0, 0x20, 0x78, 0xcf, 0x89, 0xf4, 0xd0, 0x56, 0x3b, 0x18, 0xd9, 0x58, 0x2a, 0xa4, 0x11, 0x60, 0xe3, 0x80, 0xca, 0x5a, 0x4b,
/* (2^ 3)P */ 0x5d, 0x74, 0x29, 0x8c, 0x34, 0x32, 0x91, 0x32, 0xd7, 0x2f, 0x64, 0xe1, 0x16, 0xe6, 0xa2, 0xf4, 0x34, 0xbc, 0x67, 0xff, 0x03, 0xbb, 0x45, 0x1e, 0x4a, 0x9b, 0x2a, 0xf4, 0xd0, 0x12, 0x69, 0x30,
/* (2^ 4)P */ 0x54, 0x71, 0xaf, 0xe6, 0x07, 0x65, 0x88, 0xff, 0x2f, 0xc8, 0xee, 0xdf, 0x13, 0x0e, 0xf5, 0x04, 0xce, 0xb5, 0xba, 0x2a, 0xe8, 0x2f, 0x51, 0xaa, 0x22, 0xf2, 0xd5, 0x68, 0x1a, 0x25, 0x4e, 0x17,
/* (2^ 5)P */ 0x98, 0x88, 0x02, 0x82, 0x0d, 0x70, 0x96, 0xcf, 0xc5, 0x02, 0x2c, 0x0a, 0x37, 0xe3, 0x43, 0x17, 0xaa, 0x6e, 0xe8, 0xb4, 0x98, 0xec, 0x9e, 0x37, 0x2e, 0x48, 0xe0, 0x51, 0x8a, 0x88, 0x59, 0x0c,
/* (2^ 6)P */ 0x89, 0xd1, 0xb5, 0x99, 0xd6, 0xf1, 0xcb, 0xfb, 0x84, 0xdc, 0x9f, 0x8e, 0xd5, 0xf0, 0xae, 0xac, 0x14, 0x76, 0x1f, 0x23, 0x06, 0x0d, 0xc2, 0xc1, 0x72, 0xf9, 0x74, 0xa2, 0x8d, 0x21, 0x38, 0x29,
/* (2^ 7)P */ 0x18, 0x7f, 0x1d, 0xff, 0xbe, 0x49, 0xaf, 0xf6, 0xc2, 0xc9, 0x7a, 0x38, 0x22, 0x1c, 0x54, 0xcc, 0x6b, 0xc5, 0x15, 0x40, 0xef, 0xc9, 0xfc, 0x96, 0xa9, 0x13, 0x09, 0x69, 0x7c, 0x62, 0xc1, 0x69,
/* (2^ 8)P */ 0x0e, 0xdb, 0x33, 0x47, 0x2f, 0xfd, 0x86, 0x7a, 0xe9, 0x7d, 0x08, 0x9e, 0xf2, 0xc4, 0xb8, 0xfd, 0x29, 0xa2, 0xa2, 0x8e, 0x1a, 0x4b, 0x5e, 0x09, 0x79, 0x7a, 0xb3, 0x29, 0xc8, 0xa7, 0xd7, 0x1a,
/* (2^ 9)P */ 0xc0, 0xa0, 0x7e, 0xd1, 0xca, 0x89, 0x2d, 0x34, 0x51, 0x20, 0xed, 0xcc, 0xa6, 0xdd, 0xbe, 0x67, 0x74, 0x2f, 0xb4, 0x2b, 0xbf, 0x31, 0xca, 0x19, 0xbb, 0xac, 0x80, 0x49, 0xc8, 0xb4, 0xf7, 0x3d,
/* (2^ 10)P */ 0x83, 0xd8, 0x0a, 0xc8, 0x4d, 0x44, 0xc6, 0xa8, 0x85, 0xab, 0xe3, 0x66, 0x03, 0x44, 0x1e, 0xb9, 0xd8, 0xf6, 0x64, 0x01, 0xa0, 0xcd, 0x15, 0xc2, 0x68, 0xe6, 0x47, 0xf2, 0x6e, 0x7c, 0x86, 0x3d,
/* (2^ 11)P */ 0x8c, 0x65, 0x3e, 0xcc, 0x2b, 0x58, 0xdd, 0xc7, 0x28, 0x55, 0x0e, 0xee, 0x48, 0x47, 0x2c, 0xfd, 0x71, 0x4f, 0x9f, 0xcc, 0x95, 0x9b, 0xfd, 0xa0, 0xdf, 0x5d, 0x67, 0xb0, 0x71, 0xd8, 0x29, 0x75,
/* (2^ 12)P */ 0x78, 0xbd, 0x3c, 0x2d, 0xb4, 0x68, 0xf5, 0xb8, 0x82, 0xda, 0xf3, 0x91, 0x1b, 0x01, 0x33, 0x12, 0x62, 0x3b, 0x7c, 0x4a, 0xcd, 0x6c, 0xce, 0x2d, 0x03, 0x86, 0x49, 0x9e, 0x8e, 0xfc, 0xe7, 0x75,
/* (2^ 13)P */ 0xec, 0xb6, 0xd0, 0xfc, 0xf1, 0x13, 0x4f, 0x2f, 0x45, 0x7a, 0xff, 0x29, 0x1f, 0xca, 0xa8, 0xf1, 0x9b, 0xe2, 0x81, 0x29, 0xa7, 0xc1, 0x49, 0xc2, 0x6a, 0xb5, 0x83, 0x8c, 0xbb, 0x0d, 0xbe, 0x6e,
/* (2^ 14)P */ 0x22, 0xb2, 0x0b, 0x17, 0x8d, 0xfa, 0x14, 0x71, 0x5f, 0x93, 0x93, 0xbf, 0xd5, 0xdc, 0xa2, 0x65, 0x9a, 0x97, 0x9c, 0xb5, 0x68, 0x1f, 0xc4, 0xbd, 0x89, 0x92, 0xce, 0xa2, 0x79, 0xef, 0x0e, 0x2f,
/* (2^ 15)P */ 0xce, 0x37, 0x3c, 0x08, 0x0c, 0xbf, 0xec, 0x42, 0x22, 0x63, 0x49, 0xec, 0x09, 0xbc, 0x30, 0x29, 0x0d, 0xac, 0xfe, 0x9c, 0xc1, 0xb0, 0x94, 0xf2, 0x80, 0xbb, 0xfa, 0xed, 0x4b, 0xaa, 0x80, 0x37,
/* (2^ 16)P */ 0x29, 0xd9, 0xea, 0x7c, 0x3e, 0x7d, 0xc1, 0x56, 0xc5, 0x22, 0x57, 0x2e, 0xeb, 0x4b, 0xcb, 0xe7, 0x5a, 0xe1, 0xbf, 0x2d, 0x73, 0x31, 0xe9, 0x0c, 0xf8, 0x52, 0x10, 0x62, 0xc7, 0x83, 0xb8, 0x41,
/* (2^ 17)P */ 0x50, 0x53, 0xd2, 0xc3, 0xa0, 0x5c, 0xf7, 0xdb, 0x51, 0xe3, 0xb1, 0x6e, 0x08, 0xbe, 0x36, 0x29, 0x12, 0xb2, 0xa9, 0xb4, 0x3c, 0xe0, 0x36, 0xc9, 0xaa, 0x25, 0x22, 0x32, 0x82, 0xbf, 0x45, 0x1d,
/* (2^ 18)P */ 0xc5, 0x4c, 0x02, 0x6a, 0x03, 0xb1, 0x1a, 0xe8, 0x72, 0x9a, 0x4c, 0x30, 0x1c, 0x20, 0x12, 0xe2, 0xfc, 0xb1, 0x32, 0x68, 0xba, 0x3f, 0xd7, 0xc5, 0x81, 0x95, 0x83, 0x4d, 0x5a, 0xdb, 0xff, 0x20,
/* (2^ 19)P */ 0xad, 0x0f, 0x5d, 0xbe, 0x67, 0xd3, 0x83, 0xa2, 0x75, 0x44, 0x16, 0x8b, 0xca, 0x25, 0x2b, 0x6c, 0x2e, 0xf2, 0xaa, 0x7c, 0x46, 0x35, 0x49, 0x9d, 0x49, 0xff, 0x85, 0xee, 0x8e, 0x40, 0x66, 0x51,
/* (2^ 20)P */ 0x61, 0xe3, 0xb4, 0xfa, 0xa2, 0xba, 0x67, 0x3c, 0xef, 0x5c, 0xf3, 0x7e, 0xc6, 0x33, 0xe4, 0xb3, 0x1c, 0x9b, 0x15, 0x41, 0x92, 0x72, 0x59, 0x52, 0x33, 0xab, 0xb0, 0xd5, 0x92, 0x18, 0x62, 0x6a,
/* (2^ 21)P */ 0xcb, 0xcd, 0x55, 0x75, 0x38, 0x4a, 0xb7, 0x20, 0x3f, 0x92, 0x08, 0x12, 0x0e, 0xa1, 0x2a, 0x53, 0xd1, 0x1d, 0x28, 0x62, 0x77, 0x7b, 0xa1, 0xea, 0xbf, 0x44, 0x5c, 0xf0, 0x43, 0x34, 0xab, 0x61,
/* (2^ 22)P */ 0xf8, 0xde, 0x24, 0x23, 0x42, 0x6c, 0x7a, 0x25, 0x7f, 0xcf, 0xe3, 0x17, 0x10, 0x6c, 0x1c, 0x13, 0x57, 0xa2, 0x30, 0xf6, 0x39, 0x87, 0x75, 0x23, 0x80, 0x85, 0xa7, 0x01, 0x7a, 0x40, 0x5a, 0x29,
/* (2^ 23)P */ 0xd9, 0xa8, 0x5d, 0x6d, 0x24, 0x43, 0xc4, 0xf8, 0x5d, 0xfa, 0x52, 0x0c, 0x45, 0x75, 0xd7, 0x19, 0x3d, 0xf8, 0x1b, 0x73, 0x92, 0xfc, 0xfc, 0x2a, 0x00, 0x47, 0x2b, 0x1b, 0xe8, 0xc8, 0x10, 0x7d,
/* (2^ 24)P */ 0x0b, 0xa2, 0xba, 0x70, 0x1f, 0x27, 0xe0, 0xc8, 0x57, 0x39, 0xa6, 0x7c, 0x86, 0x48, 0x37, 0x99, 0xbb, 0xd4, 0x7e, 0xcb, 0xb3, 0xef, 0x12, 0x54, 0x75, 0x29, 0xe6, 0x73, 0x61, 0xd3, 0x96, 0x31,
/* (2^ 25)P */ 0xfc, 0xdf, 0xc7, 0x41, 0xd1, 0xca, 0x5b, 0xde, 0x48, 0xc8, 0x95, 0xb3, 0xd2, 0x8c, 0xcc, 0x47, 0xcb, 0xf3, 0x1a, 0xe1, 0x42, 0xd9, 0x4c, 0xa3, 0xc2, 0xce, 0x4e, 0xd0, 0xf2, 0xdb, 0x56, 0x02,
/* (2^ 26)P */ 0x7f, 0x66, 0x0e, 0x4b, 0xe9, 0xb7, 0x5a, 0x87, 0x10, 0x0d, 0x85, 0xc0, 0x83, 0xdd, 0xd4, 0xca, 0x9f, 0xc7, 0x72, 0x4e, 0x8f, 0x2e, 0xf1, 0x47, 0x9b, 0xb1, 0x85, 0x8c, 0xbb, 0x87, 0x1a, 0x5f,
/* (2^ 27)P */ 0xb8, 0x51, 0x7f, 0x43, 0xb6, 0xd0, 0xe9, 0x7a, 0x65, 0x90, 0x87, 0x18, 0x55, 0xce, 0xc7, 0x12, 0xee, 0x7a, 0xf7, 0x5c, 0xfe, 0x09, 0xde, 0x2a, 0x27, 0x56, 0x2c, 0x7d, 0x2f, 0x5a, 0xa0, 0x23,
/* (2^ 28)P */ 0x9a, 0x16, 0x7c, 0xf1, 0x28, 0xe1, 0x08, 0x59, 0x2d, 0x85, 0xd0, 0x8a, 0xdd, 0x98, 0x74, 0xf7, 0x64, 0x2f, 0x10, 0xab, 0xce, 0xc4, 0xb4, 0x74, 0x45, 0x98, 0x13, 0x10, 0xdd, 0xba, 0x3a, 0x18,
/* (2^ 29)P */ 0xac, 0xaa, 0x92, 0xaa, 0x8d, 0xba, 0x65, 0xb1, 0x05, 0x67, 0x38, 0x99, 0x95, 0xef, 0xc5, 0xd5, 0xd1, 0x40, 0xfc, 0xf8, 0x0c, 0x8f, 0x2f, 0xbe, 0x14, 0x45, 0x20, 0xee, 0x35, 0xe6, 0x01, 0x27,
/* (2^ 30)P */ 0x14, 0x65, 0x15, 0x20, 0x00, 0xa8, 0x9f, 0x62, 0xce, 0xc1, 0xa8, 0x64, 0x87, 0x86, 0x23, 0xf2, 0x0e, 0x06, 0x3f, 0x0b, 0xff, 0x4f, 0x89, 0x5b, 0xfa, 0xa3, 0x08, 0xf7, 0x4c, 0x94, 0xd9, 0x60,
/* (2^ 31)P */ 0x1f, 0x20, 0x7a, 0x1c, 0x1a, 0x00, 0xea, 0xae, 0x63, 0xce, 0xe2, 0x3e, 0x63, 0x6a, 0xf1, 0xeb, 0xe1, 0x07, 0x7a, 0x4c, 0x59, 0x09, 0x77, 0x6f, 0xcb, 0x08, 0x02, 0x0d, 0x15, 0x58, 0xb9, 0x79,
/* (2^ 32)P */ 0xe7, 0x10, 0xd4, 0x01, 0x53, 0x5e, 0xb5, 0x24, 0x4d, 0xc8, 0xfd, 0xf3, 0xdf, 0x4e, 0xa3, 0xe3, 0xd8, 0x32, 0x40, 0x90, 0xe4, 0x68, 0x87, 0xd8, 0xec, 0xae, 0x3a, 0x7b, 0x42, 0x84, 0x13, 0x13,
/* (2^ 33)P */ 0x14, 0x4f, 0x23, 0x86, 0x12, 0xe5, 0x05, 0x84, 0x29, 0xc5, 0xb4, 0xad, 0x39, 0x47, 0xdc, 0x14, 0xfd, 0x4f, 0x63, 0x50, 0xb2, 0xb5, 0xa2, 0xb8, 0x93, 0xff, 0xa7, 0xd8, 0x4a, 0xa9, 0xe2, 0x2f,
/* (2^ 34)P */ 0xdd, 0xfa, 0x43, 0xe8, 0xef, 0x57, 0x5c, 0xec, 0x18, 0x99, 0xbb, 0xf0, 0x40, 0xce, 0x43, 0x28, 0x05, 0x63, 0x3d, 0xcf, 0xd6, 0x61, 0xb5, 0xa4, 0x7e, 0x77, 0xfb, 0xe8, 0xbd, 0x29, 0x36, 0x74,
/* (2^ 35)P */ 0x8f, 0x73, 0xaf, 0xbb, 0x46, 0xdd, 0x3e, 0x34, 0x51, 0xa6, 0x01, 0xb1, 0x28, 0x18, 0x98, 0xed, 0x7a, 0x79, 0x2c, 0x88, 0x0b, 0x76, 0x01, 0xa4, 0x30, 0x87, 0xc8, 0x8d, 0xe2, 0x23, 0xc2, 0x1f,
/* (2^ 36)P */ 0x0e, 0xba, 0x0f, 0xfc, 0x91, 0x4e, 0x60, 0x48, 0xa4, 0x6f, 0x2c, 0x05, 0x8f, 0xf7, 0x37, 0xb6, 0x9c, 0x23, 0xe9, 0x09, 0x3d, 0xac, 0xcc, 0x91, 0x7c, 0x68, 0x7a, 0x43, 0xd4, 0xee, 0xf7, 0x23,
/* (2^ 37)P */ 0x00, 0xd8, 0x9b, 0x8d, 0x11, 0xb1, 0x73, 0x51, 0xa7, 0xd4, 0x89, 0x31, 0xb6, 0x41, 0xd6, 0x29, 0x86, 0xc5, 0xbb, 0x88, 0x79, 0x17, 0xbf, 0xfd, 0xf5, 0x1d, 0xd8, 0xca, 0x4f, 0x89, 0x59, 0x29,
/* (2^ 38)P */ 0x99, 0xc8, 0xbb, 0xb4, 0xf3, 0x8e, 0xbc, 0xae, 0xb9, 0x92, 0x69, 0xb2, 0x5a, 0x99, 0x48, 0x41, 0xfb, 0x2c, 0xf9, 0x34, 0x01, 0x0b, 0xe2, 0x24, 0xe8, 0xde, 0x05, 0x4a, 0x89, 0x58, 0xd1, 0x40,
/* (2^ 39)P */ 0xf6, 0x76, 0xaf, 0x85, 0x11, 0x0b, 0xb0, 0x46, 0x79, 0x7a, 0x18, 0x73, 0x78, 0xc7, 0xba, 0x26, 0x5f, 0xff, 0x8f, 0xab, 0x95, 0xbf, 0xc0, 0x3d, 0xd7, 0x24, 0x55, 0x94, 0xd8, 0x8b, 0x60, 0x2a,
/* (2^ 40)P */ 0x02, 0x63, 0x44, 0xbd, 0x88, 0x95, 0x44, 0x26, 0x9c, 0x43, 0x88, 0x03, 0x1c, 0xc2, 0x4b, 0x7c, 0xb2, 0x11, 0xbd, 0x83, 0xf3, 0xa4, 0x98, 0x8e, 0xb9, 0x76, 0xd8, 0xc9, 0x7b, 0x8d, 0x21, 0x26,
/* (2^ 41)P */ 0x8a, 0x17, 0x7c, 0x99, 0x42, 0x15, 0x08, 0xe3, 0x6f, 0x60, 0xb6, 0x6f, 0xa8, 0x29, 0x2d, 0x3c, 0x74, 0x93, 0x27, 0xfa, 0x36, 0x77, 0x21, 0x5c, 0xfa, 0xb1, 0xfe, 0x4a, 0x73, 0x05, 0xde, 0x7d,
/* (2^ 42)P */ 0xab, 0x2b, 0xd4, 0x06, 0x39, 0x0e, 0xf1, 0x3b, 0x9c, 0x64, 0x80, 0x19, 0x3e, 0x80, 0xf7, 0xe4, 0x7a, 0xbf, 0x95, 0x95, 0xf8, 0x3b, 0x05, 0xe6, 0x30, 0x55, 0x24, 0xda, 0x38, 0xaf, 0x4f, 0x39,
/* (2^ 43)P */ 0xf4, 0x28, 0x69, 0x89, 0x58, 0xfb, 0x8e, 0x7a, 0x3c, 0x11, 0x6a, 0xcc, 0xe9, 0x78, 0xc7, 0xfb, 0x6f, 0x59, 0xaf, 0x30, 0xe3, 0x0c, 0x67, 0x72, 0xf7, 0x6c, 0x3d, 0x1d, 0xa8, 0x22, 0xf2, 0x48,
/* (2^ 44)P */ 0xa7, 0xca, 0x72, 0x0d, 0x41, 0xce, 0x1f, 0xf0, 0x95, 0x55, 0x3b, 0x21, 0xc7, 0xec, 0x20, 0x5a, 0x83, 0x14, 0xfa, 0xc1, 0x65, 0x11, 0xc2, 0x7b, 0x41, 0xa7, 0xa8, 0x1d, 0xe3, 0x9a, 0xf8, 0x07,
/* (2^ 45)P */ 0xf9, 0x0f, 0x83, 0xc6, 0xb4, 0xc2, 0xd2, 0x05, 0x93, 0x62, 0x31, 0xc6, 0x0f, 0x33, 0x3e, 0xd4, 0x04, 0xa9, 0xd3, 0x96, 0x0a, 0x59, 0xa5, 0xa5, 0xb6, 0x33, 0x53, 0xa6, 0x91, 0xdb, 0x5e, 0x70,
/* (2^ 46)P */ 0xf7, 0xa5, 0xb9, 0x0b, 0x5e, 0xe1, 0x8e, 0x04, 0x5d, 0xaf, 0x0a, 0x9e, 0xca, 0xcf, 0x40, 0x32, 0x0b, 0xa4, 0xc4, 0xed, 0xce, 0x71, 0x4b, 0x8f, 0x6d, 0x4a, 0x54, 0xde, 0xa3, 0x0d, 0x1c, 0x62,
/* (2^ 47)P */ 0x91, 0x40, 0x8c, 0xa0, 0x36, 0x28, 0x87, 0x92, 0x45, 0x14, 0xc9, 0x10, 0xb0, 0x75, 0x83, 0xce, 0x94, 0x63, 0x27, 0x4f, 0x52, 0xeb, 0x72, 0x8a, 0x35, 0x36, 0xc8, 0x7e, 0xfa, 0xfc, 0x67, 0x26,
/* (2^ 48)P */ 0x2a, 0x75, 0xe8, 0x45, 0x33, 0x17, 0x4c, 0x7f, 0xa5, 0x79, 0x70, 0xee, 0xfe, 0x47, 0x1b, 0x06, 0x34, 0xff, 0x86, 0x9f, 0xfa, 0x9a, 0xdd, 0x25, 0x9c, 0xc8, 0x5d, 0x42, 0xf5, 0xce, 0x80, 0x37,
/* (2^ 49)P */ 0xe9, 0xb4, 0x3b, 0x51, 0x5a, 0x03, 0x46, 0x1a, 0xda, 0x5a, 0x57, 0xac, 0x79, 0xf3, 0x1e, 0x3e, 0x50, 0x4b, 0xa2, 0x5f, 0x1c, 0x5f, 0x8c, 0xc7, 0x22, 0x9f, 0xfd, 0x34, 0x76, 0x96, 0x1a, 0x32,
/* (2^ 50)P */ 0xfa, 0x27, 0x6e, 0x82, 0xb8, 0x07, 0x67, 0x94, 0xd0, 0x6f, 0x50, 0x4c, 0xd6, 0x84, 0xca, 0x3d, 0x36, 0x14, 0xe9, 0x75, 0x80, 0x21, 0x89, 0xc1, 0x84, 0x84, 0x3b, 0x9b, 0x16, 0x84, 0x92, 0x6d,
/* (2^ 51)P */ 0xdf, 0x2d, 0x3f, 0x38, 0x40, 0xe8, 0x67, 0x3a, 0x75, 0x9b, 0x4f, 0x0c, 0xa3, 0xc9, 0xee, 0x33, 0x47, 0xef, 0x83, 0xa7, 0x6f, 0xc8, 0xc7, 0x3e, 0xc4, 0xfb, 0xc9, 0xba, 0x9f, 0x44, 0xec, 0x26,
/* (2^ 52)P */ 0x7d, 0x9e, 0x9b, 0xa0, 0xcb, 0x38, 0x0f, 0x5c, 0x8c, 0x47, 0xa3, 0x62, 0xc7, 0x8c, 0x16, 0x81, 0x1c, 0x12, 0xfc, 0x06, 0xd3, 0xb0, 0x23, 0x3e, 0xdd, 0xdc, 0xef, 0xa5, 0xa0, 0x8a, 0x23, 0x5a,
/* (2^ 53)P */ 0xff, 0x43, 0xea, 0xc4, 0x21, 0x61, 0xa2, 0x1b, 0xb5, 0x32, 0x88, 0x7c, 0x7f, 0xc7, 0xf8, 0x36, 0x9a, 0xf9, 0xdc, 0x0a, 0x0b, 0xea, 0xfb, 0x88, 0xf9, 0xeb, 0x5b, 0xc2, 0x8e, 0x93, 0xa9, 0x5c,
/* (2^ 54)P */ 0xa0, 0xcd, 0xfc, 0x51, 0x5e, 0x6a, 0x43, 0xd5, 0x3b, 0x89, 0xcd, 0xc2, 0x97, 0x47, 0xbc, 0x1d, 0x08, 0x4a, 0x22, 0xd3, 0x65, 0x6a, 0x34, 0x19, 0x66, 0xf4, 0x9a, 0x9b, 0xe4, 0x34, 0x50, 0x0f,
/* (2^ 55)P */ 0x6e, 0xb9, 0xe0, 0xa1, 0x67, 0x39, 0x3c, 0xf2, 0x88, 0x4d, 0x7a, 0x86, 0xfa, 0x08, 0x8b, 0xe5, 0x79, 0x16, 0x34, 0xa7, 0xc6, 0xab, 0x2f, 0xfb, 0x46, 0x69, 0x02, 0xb6, 0x1e, 0x38, 0x75, 0x2a,
/* (2^ 56)P */ 0xac, 0x20, 0x94, 0xc1, 0xe4, 0x3b, 0x0a, 0xc8, 0xdc, 0xb6, 0xf2, 0x81, 0xc6, 0xf6, 0xb1, 0x66, 0x88, 0x33, 0xe9, 0x61, 0x67, 0x03, 0xf7, 0x7c, 0xc4, 0xa4, 0x60, 0xa6, 0xd8, 0xbb, 0xab, 0x25,
/* (2^ 57)P */ 0x98, 0x51, 0xfd, 0x14, 0xba, 0x12, 0xea, 0x91, 0xa9, 0xff, 0x3c, 0x4a, 0xfc, 0x50, 0x49, 0x68, 0x28, 0xad, 0xf5, 0x30, 0x21, 0x84, 0x26, 0xf8, 0x41, 0xa4, 0x01, 0x53, 0xf7, 0x88, 0xa9, 0x3e,
/* (2^ 58)P */ 0x6f, 0x8c, 0x5f, 0x69, 0x9a, 0x10, 0x78, 0xc9, 0xf3, 0xc3, 0x30, 0x05, 0x4a, 0xeb, 0x46, 0x17, 0x95, 0x99, 0x45, 0xb4, 0x77, 0x6d, 0x4d, 0x44, 0xc7, 0x5c, 0x4e, 0x05, 0x8c, 0x2b, 0x95, 0x75,
/* (2^ 59)P */ 0xaa, 0xd6, 0xf4, 0x15, 0x79, 0x3f, 0x70, 0xa3, 0xd8, 0x47, 0x26, 0x2f, 0x20, 0x46, 0xc3, 0x66, 0x4b, 0x64, 0x1d, 0x81, 0xdf, 0x69, 0x14, 0xd0, 0x1f, 0xd7, 0xa5, 0x81, 0x7d, 0xa4, 0xfe, 0x77,
/* (2^ 60)P */ 0x81, 0xa3, 0x7c, 0xf5, 0x9e, 0x52, 0xe9, 0xc5, 0x1a, 0x88, 0x2f, 0xce, 0xb9, 0xb4, 0xee, 0x6e, 0xd6, 0x9b, 0x00, 0xe8, 0x28, 0x1a, 0xe9, 0xb6, 0xec, 0x3f, 0xfc, 0x9a, 0x3e, 0xbe, 0x80, 0x4b,
/* (2^ 61)P */ 0xc5, 0xd2, 0xae, 0x26, 0xc5, 0x73, 0x37, 0x7e, 0x9d, 0xa4, 0xc9, 0x53, 0xb4, 0xfc, 0x4a, 0x1b, 0x4d, 0xb2, 0xff, 0xba, 0xd7, 0xbd, 0x20, 0xa9, 0x0e, 0x40, 0x2d, 0x12, 0x9f, 0x69, 0x54, 0x7c,
/* (2^ 62)P */ 0xc8, 0x4b, 0xa9, 0x4f, 0xe1, 0xc8, 0x46, 0xef, 0x5e, 0xed, 0x52, 0x29, 0xce, 0x74, 0xb0, 0xe0, 0xd5, 0x85, 0xd8, 0xdb, 0xe1, 0x50, 0xa4, 0xbe, 0x2c, 0x71, 0x0f, 0x32, 0x49, 0x86, 0xb6, 0x61,
/* (2^ 63)P */ 0xd1, 0xbd, 0xcc, 0x09, 0x73, 0x5f, 0x48, 0x8a, 0x2d, 0x1a, 0x4d, 0x7d, 0x0d, 0x32, 0x06, 0xbd, 0xf4, 0xbe, 0x2d, 0x32, 0x73, 0x29, 0x23, 0x25, 0x70, 0xf7, 0x17, 0x8c, 0x75, 0xc4, 0x5d, 0x44,
/* (2^ 64)P */ 0x3c, 0x93, 0xc8, 0x7c, 0x17, 0x34, 0x04, 0xdb, 0x9f, 0x05, 0xea, 0x75, 0x21, 0xe8, 0x6f, 0xed, 0x34, 0xdb, 0x53, 0xc0, 0xfd, 0xbe, 0xfe, 0x1e, 0x99, 0xaf, 0x5d, 0xc6, 0x67, 0xe8, 0xdb, 0x4a,
/* (2^ 65)P */ 0xdf, 0x09, 0x06, 0xa9, 0xa2, 0x71, 0xcd, 0x3a, 0x50, 0x40, 0xd0, 0x6d, 0x85, 0x91, 0xe9, 0xe5, 0x3c, 0xc2, 0x57, 0x81, 0x68, 0x9b, 0xc6, 0x1e, 0x4d, 0xfe, 0x5c, 0x88, 0xf6, 0x27, 0x74, 0x69,
/* (2^ 66)P */ 0x51, 0xa8, 0xe1, 0x65, 0x9b, 0x7b, 0xbe, 0xd7, 0xdd, 0x36, 0xc5, 0x22, 0xd5, 0x28, 0x3d, 0xa0, 0x45, 0xb6, 0xd2, 0x8f, 0x65, 0x9d, 0x39, 0x28, 0xe1, 0x41, 0x26, 0x7c, 0xe1, 0xb7, 0xe5, 0x49,
/* (2^ 67)P */ 0xa4, 0x57, 0x04, 0x70, 0x98, 0x3a, 0x8c, 0x6f, 0x78, 0x67, 0xbb, 0x5e, 0xa2, 0xf0, 0x78, 0x50, 0x0f, 0x96, 0x82, 0xc3, 0xcb, 0x3c, 0x3c, 0xd1, 0xb1, 0x84, 0xdf, 0xa7, 0x58, 0x32, 0x00, 0x2e,
/* (2^ 68)P */ 0x1c, 0x6a, 0x29, 0xe6, 0x9b, 0xf3, 0xd1, 0x8a, 0xb2, 0xbf, 0x5f, 0x2a, 0x65, 0xaa, 0xee, 0xc1, 0xcb, 0xf3, 0x26, 0xfd, 0x73, 0x06, 0xee, 0x33, 0xcc, 0x2c, 0x9d, 0xa6, 0x73, 0x61, 0x25, 0x59,
/* (2^ 69)P */ 0x41, 0xfc, 0x18, 0x4e, 0xaa, 0x07, 0xea, 0x41, 0x1e, 0xa5, 0x87, 0x7c, 0x52, 0x19, 0xfc, 0xd9, 0x6f, 0xca, 0x31, 0x58, 0x80, 0xcb, 0xaa, 0xbd, 0x4f, 0x69, 0x16, 0xc9, 0x2d, 0x65, 0x5b, 0x44,
/* (2^ 70)P */ 0x15, 0x23, 0x17, 0xf2, 0xa7, 0xa3, 0x92, 0xce, 0x64, 0x99, 0x1b, 0xe1, 0x2d, 0x28, 0xdc, 0x1e, 0x4a, 0x31, 0x4c, 0xe0, 0xaf, 0x3a, 0x82, 0xa1, 0x86, 0xf5, 0x7c, 0x43, 0x94, 0x2d, 0x0a, 0x79,
/* (2^ 71)P */ 0x09, 0xe0, 0xf6, 0x93, 0xfb, 0x47, 0xc4, 0x71, 0x76, 0x52, 0x84, 0x22, 0x67, 0xa5, 0x22, 0x89, 0x69, 0x51, 0x4f, 0x20, 0x3b, 0x90, 0x70, 0xbf, 0xfe, 0x19, 0xa3, 0x1b, 0x89, 0x89, 0x7a, 0x2f,
/* (2^ 72)P */ 0x0c, 0x14, 0xe2, 0x77, 0xb5, 0x8e, 0xa0, 0x02, 0xf4, 0xdc, 0x7b, 0x42, 0xd4, 0x4e, 0x9a, 0xed, 0xd1, 0x3c, 0x32, 0xe4, 0x44, 0xec, 0x53, 0x52, 0x5b, 0x35, 0xe9, 0x14, 0x3c, 0x36, 0x88, 0x3e,
/* (2^ 73)P */ 0x8c, 0x0b, 0x11, 0x77, 0x42, 0xc1, 0x66, 0xaa, 0x90, 0x33, 0xa2, 0x10, 0x16, 0x39, 0xe0, 0x1a, 0xa2, 0xc2, 0x3f, 0xc9, 0x12, 0xbd, 0x30, 0x20, 0xab, 0xc7, 0x55, 0x95, 0x57, 0x41, 0xe1, 0x3e,
/* (2^ 74)P */ 0x41, 0x7d, 0x6e, 0x6d, 0x3a, 0xde, 0x14, 0x92, 0xfe, 0x7e, 0xf1, 0x07, 0x86, 0xd8, 0xcd, 0x3c, 0x17, 0x12, 0xe1, 0xf8, 0x88, 0x12, 0x4f, 0x67, 0xd0, 0x93, 0x9f, 0x32, 0x0f, 0x25, 0x82, 0x56,
/* (2^ 75)P */ 0x6e, 0x39, 0x2e, 0x6d, 0x13, 0x0b, 0xf0, 0x6c, 0xbf, 0xde, 0x14, 0x10, 0x6f, 0xf8, 0x4c, 0x6e, 0x83, 0x4e, 0xcc, 0xbf, 0xb5, 0xb1, 0x30, 0x59, 0xb6, 0x16, 0xba, 0x8a, 0xb4, 0x69, 0x70, 0x04,
/* (2^ 76)P */ 0x93, 0x07, 0xb2, 0x69, 0xab, 0xe4, 0x4c, 0x0d, 0x9e, 0xfb, 0xd0, 0x97, 0x1a, 0xb9, 0x4d, 0xb2, 0x1d, 0xd0, 0x00, 0x4e, 0xf5, 0x50, 0xfa, 0xcd, 0xb5, 0xdd, 0x8b, 0x36, 0x85, 0x10, 0x1b, 0x22,
/* (2^ 77)P */ 0xd2, 0xd8, 0xe3, 0xb1, 0x68, 0x94, 0xe5, 0xe7, 0x93, 0x2f, 0x12, 0xbd, 0x63, 0x65, 0xc5, 0x53, 0x09, 0x3f, 0x66, 0xe0, 0x03, 0xa9, 0xe8, 0xee, 0x42, 0x3d, 0xbe, 0xcb, 0x62, 0xa6, 0xef, 0x61,
/* (2^ 78)P */ 0x2a, 0xab, 0x6e, 0xde, 0xdd, 0xdd, 0xf8, 0x2c, 0x31, 0xf2, 0x35, 0x14, 0xd5, 0x0a, 0xf8, 0x9b, 0x73, 0x49, 0xf0, 0xc9, 0xce, 0xda, 0xea, 0x5d, 0x27, 0x9b, 0xd2, 0x41, 0x5d, 0x5b, 0x27, 0x29,
/* (2^ 79)P */ 0x4f, 0xf1, 0xeb, 0x95, 0x08, 0x0f, 0xde, 0xcf, 0xa7, 0x05, 0x49, 0x05, 0x6b, 0xb9, 0xaa, 0xb9, 0xfd, 0x20, 0xc4, 0xa1, 0xd9, 0x0d, 0xe8, 0xca, 0xc7, 0xbb, 0x73, 0x16, 0x2f, 0xbf, 0x63, 0x0a,
/* (2^ 80)P */ 0x8c, 0xbc, 0x8f, 0x95, 0x11, 0x6e, 0x2f, 0x09, 0xad, 0x2f, 0x82, 0x04, 0xe8, 0x81, 0x2a, 0x67, 0x17, 0x25, 0xd5, 0x60, 0x15, 0x35, 0xc8, 0xca, 0xf8, 0x92, 0xf1, 0xc8, 0x22, 0x77, 0x3f, 0x6f,
/* (2^ 81)P */ 0xb7, 0x94, 0xe8, 0xc2, 0xcc, 0x90, 0xba, 0xf8, 0x0d, 0x9f, 0xff, 0x38, 0xa4, 0x57, 0x75, 0x2c, 0x59, 0x23, 0xe5, 0x5a, 0x85, 0x1d, 0x4d, 0x89, 0x69, 0x3d, 0x74, 0x7b, 0x15, 0x22, 0xe1, 0x68,
/* (2^ 82)P */ 0xf3, 0x19, 0xb9, 0xcf, 0x70, 0x55, 0x7e, 0xd8, 0xb9, 0x8d, 0x79, 0x95, 0xcd, 0xde, 0x2c, 0x3f, 0xce, 0xa2, 0xc0, 0x10, 0x47, 0x15, 0x21, 0x21, 0xb2, 0xc5, 0x6d, 0x24, 0x15, 0xa1, 0x66, 0x3c,
/* (2^ 83)P */ 0x72, 0xcb, 0x4e, 0x29, 0x62, 0xc5, 0xed, 0xcb, 0x16, 0x0b, 0x28, 0x6a, 0xc3, 0x43, 0x71, 0xba, 0x67, 0x8b, 0x07, 0xd4, 0xef, 0xc2, 0x10, 0x96, 0x1e, 0x4b, 0x6a, 0x94, 0x5d, 0x73, 0x44, 0x61,
/* (2^ 84)P */ 0x50, 0x33, 0x5b, 0xd7, 0x1e, 0x11, 0x6f, 0x53, 0x1b, 0xd8, 0x41, 0x20, 0x8c, 0xdb, 0x11, 0x02, 0x3c, 0x41, 0x10, 0x0e, 0x00, 0xb1, 0x3c, 0xf9, 0x76, 0x88, 0x9e, 0x03, 0x3c, 0xfd, 0x9d, 0x14,
/* (2^ 85)P */ 0x5b, 0x15, 0x63, 0x6b, 0xe4, 0xdd, 0x79, 0xd4, 0x76, 0x79, 0x83, 0x3c, 0xe9, 0x15, 0x6e, 0xb6, 0x38, 0xe0, 0x13, 0x1f, 0x3b, 0xe4, 0xfd, 0xda, 0x35, 0x0b, 0x4b, 0x2e, 0x1a, 0xda, 0xaf, 0x5f,
/* (2^ 86)P */ 0x81, 0x75, 0x19, 0x17, 0xdf, 0xbb, 0x00, 0x36, 0xc2, 0xd2, 0x3c, 0xbe, 0x0b, 0x05, 0x72, 0x39, 0x86, 0xbe, 0xd5, 0xbd, 0x6d, 0x90, 0x38, 0x59, 0x0f, 0x86, 0x9b, 0x3f, 0xe4, 0xe5, 0xfc, 0x34,
/* (2^ 87)P */ 0x02, 0x4d, 0xd1, 0x42, 0xcd, 0xa4, 0xa8, 0x75, 0x65, 0xdf, 0x41, 0x34, 0xc5, 0xab, 0x8d, 0x82, 0xd3, 0x31, 0xe1, 0xd2, 0xed, 0xab, 0xdc, 0x33, 0x5f, 0xd2, 0x14, 0xb8, 0x6f, 0xd7, 0xba, 0x3e,
/* (2^ 88)P */ 0x0f, 0xe1, 0x70, 0x6f, 0x56, 0x6f, 0x90, 0xd4, 0x5a, 0x0f, 0x69, 0x51, 0xaa, 0xf7, 0x12, 0x5d, 0xf2, 0xfc, 0xce, 0x76, 0x6e, 0xb1, 0xad, 0x45, 0x99, 0x29, 0x23, 0xad, 0xae, 0x68, 0xf7, 0x01,
/* (2^ 89)P */ 0xbd, 0xfe, 0x48, 0x62, 0x7b, 0xc7, 0x6c, 0x2b, 0xfd, 0xaf, 0x3a, 0xec, 0x28, 0x06, 0xd3, 0x3c, 0x6a, 0x48, 0xef, 0xd4, 0x80, 0x0b, 0x1c, 0xce, 0x23, 0x6c, 0xf6, 0xa6, 0x2e, 0xff, 0x3b, 0x4c,
/* (2^ 90)P */ 0x5f, 0xeb, 0xea, 0x4a, 0x09, 0xc4, 0x2e, 0x3f, 0xa7, 0x2c, 0x37, 0x6e, 0x28, 0x9b, 0xb1, 0x61, 0x1d, 0x70, 0x2a, 0xde, 0x66, 0xa9, 0xef, 0x5e, 0xef, 0xe3, 0x55, 0xde, 0x65, 0x05, 0xb2, 0x23,
/* (2^ 91)P */ 0x57, 0x85, 0xd5, 0x79, 0x52, 0xca, 0x01, 0xe3, 0x4f, 0x87, 0xc2, 0x27, 0xce, 0xd4, 0xb2, 0x07, 0x67, 0x1d, 0xcf, 0x9d, 0x8a, 0xcd, 0x32, 0xa5, 0x56, 0xff, 0x2b, 0x3f, 0xe2, 0xfe, 0x52, 0x2a,
/* (2^ 92)P */ 0x3d, 0x66, 0xd8, 0x7c, 0xb3, 0xef, 0x24, 0x86, 0x94, 0x75, 0xbd, 0xff, 0x20, 0xac, 0xc7, 0xbb, 0x45, 0x74, 0xd3, 0x82, 0x9c, 0x5e, 0xb8, 0x57, 0x66, 0xec, 0xa6, 0x86, 0xcb, 0x52, 0x30, 0x7b,
/* (2^ 93)P */ 0x1e, 0xe9, 0x25, 0x25, 0xad, 0xf0, 0x82, 0x34, 0xa0, 0xdc, 0x8e, 0xd2, 0x43, 0x80, 0xb6, 0x2c, 0x3a, 0x00, 0x1b, 0x2e, 0x05, 0x6d, 0x4f, 0xaf, 0x0a, 0x1b, 0x78, 0x29, 0x25, 0x8c, 0x5f, 0x18,
/* (2^ 94)P */ 0xd6, 0xe0, 0x0c, 0xd8, 0x5b, 0xde, 0x41, 0xaa, 0xd6, 0xe9, 0x53, 0x68, 0x41, 0xb2, 0x07, 0x94, 0x3a, 0x4c, 0x7f, 0x35, 0x6e, 0xc3, 0x3e, 0x56, 0xce, 0x7b, 0x29, 0x0e, 0xdd, 0xb8, 0xc4, 0x4c,
/* (2^ 95)P */ 0x0e, 0x73, 0xb8, 0xff, 0x52, 0x1a, 0xfc, 0xa2, 0x37, 0x8e, 0x05, 0x67, 0x6e, 0xf1, 0x11, 0x18, 0xe1, 0x4e, 0xdf, 0xcd, 0x66, 0xa3, 0xf9, 0x10, 0x99, 0xf0, 0xb9, 0xa0, 0xc4, 0xa0, 0xf4, 0x72,
/* (2^ 96)P */ 0xa7, 0x4e, 0x3f, 0x66, 0x6f, 0xc0, 0x16, 0x8c, 0xba, 0x0f, 0x97, 0x4e, 0xf7, 0x3a, 0x3b, 0x69, 0x45, 0xc3, 0x9e, 0xd6, 0xf1, 0xe7, 0x02, 0x21, 0x89, 0x80, 0x8a, 0x96, 0xbc, 0x3c, 0xa5, 0x0b,
/* (2^ 97)P */ 0x37, 0x55, 0xa1, 0xfe, 0xc7, 0x9d, 0x3d, 0xca, 0x93, 0x64, 0x53, 0x51, 0xbb, 0x24, 0x68, 0x4c, 0xb1, 0x06, 0x40, 0x84, 0x14, 0x63, 0x88, 0xb9, 0x60, 0xcc, 0x54, 0xb4, 0x2a, 0xa7, 0xd2, 0x40,
/* (2^ 98)P */ 0x75, 0x09, 0x57, 0x12, 0xb7, 0xa1, 0x36, 0x59, 0x57, 0xa6, 0xbd, 0xde, 0x48, 0xd6, 0xb9, 0x91, 0xea, 0x30, 0x43, 0xb6, 0x4b, 0x09, 0x44, 0x33, 0xd0, 0x51, 0xee, 0x12, 0x0d, 0xa1, 0x6b, 0x00,
/* (2^ 99)P */ 0x58, 0x5d, 0xde, 0xf5, 0x68, 0x84, 0x22, 0x19, 0xb0, 0x05, 0xcc, 0x38, 0x4c, 0x2f, 0xb1, 0x0e, 0x90, 0x19, 0x60, 0xd5, 0x9d, 0x9f, 0x03, 0xa1, 0x0b, 0x0e, 0xff, 0x4f, 0xce, 0xd4, 0x02, 0x45,
/* (2^100)P */ 0x89, 0xc1, 0x37, 0x68, 0x10, 0x54, 0x20, 0xeb, 0x3c, 0xb9, 0xd3, 0x6d, 0x4c, 0x54, 0xf6, 0xd0, 0x4f, 0xd7, 0x16, 0xc4, 0x64, 0x70, 0x72, 0x40, 0xf0, 0x2e, 0x50, 0x4b, 0x11, 0xc6, 0x15, 0x6e,
/* (2^101)P */ 0x6b, 0xa7, 0xb1, 0xcf, 0x98, 0xa3, 0xf2, 0x4d, 0xb1, 0xf6, 0xf2, 0x19, 0x74, 0x6c, 0x25, 0x11, 0x43, 0x60, 0x6e, 0x06, 0x62, 0x79, 0x49, 0x4a, 0x44, 0x5b, 0x35, 0x41, 0xab, 0x3a, 0x5b, 0x70,
/* (2^102)P */ 0xd8, 0xb1, 0x97, 0xd7, 0x36, 0xf5, 0x5e, 0x36, 0xdb, 0xf0, 0xdd, 0x22, 0xd6, 0x6b, 0x07, 0x00, 0x88, 0x5a, 0x57, 0xe0, 0xb0, 0x33, 0xbf, 0x3b, 0x4d, 0xca, 0xe4, 0xc8, 0x05, 0xaa, 0x77, 0x37,
/* (2^103)P */ 0x5f, 0xdb, 0x78, 0x55, 0xc8, 0x45, 0x27, 0x39, 0xe2, 0x5a, 0xae, 0xdb, 0x49, 0x41, 0xda, 0x6f, 0x67, 0x98, 0xdc, 0x8a, 0x0b, 0xb0, 0xf0, 0xb1, 0xa3, 0x1d, 0x6f, 0xd3, 0x37, 0x34, 0x96, 0x09,
/* (2^104)P */ 0x53, 0x38, 0xdc, 0xa5, 0x90, 0x4e, 0x82, 0x7e, 0xbd, 0x5c, 0x13, 0x1f, 0x64, 0xf6, 0xb5, 0xcc, 0xcc, 0x8f, 0xce, 0x87, 0x6c, 0xd8, 0x36, 0x67, 0x9f, 0x24, 0x04, 0x66, 0xe2, 0x3c, 0x5f, 0x62,
/* (2^105)P */ 0x3f, 0xf6, 0x02, 0x95, 0x05, 0xc8, 0x8a, 0xaf, 0x69, 0x14, 0x35, 0x2e, 0x0a, 0xe7, 0x05, 0x0c, 0x05, 0x63, 0x4b, 0x76, 0x9c, 0x2e, 0x29, 0x35, 0xc3, 0x3a, 0xe2, 0xc7, 0x60, 0x43, 0x39, 0x1a,
/* (2^106)P */ 0x64, 0x32, 0x18, 0x51, 0x32, 0xd5, 0xc6, 0xd5, 0x4f, 0xb7, 0xc2, 0x43, 0xbd, 0x5a, 0x06, 0x62, 0x9b, 0x3f, 0x97, 0x3b, 0xd0, 0xf5, 0xfb, 0xb5, 0x5e, 0x6e, 0x20, 0x61, 0x36, 0xda, 0xa3, 0x13,
/* (2^107)P */ 0xe5, 0x94, 0x5d, 0x72, 0x37, 0x58, 0xbd, 0xc6, 0xc5, 0x16, 0x50, 0x20, 0x12, 0x09, 0xe3, 0x18, 0x68, 0x3c, 0x03, 0x70, 0x15, 0xce, 0x88, 0x20, 0x87, 0x79, 0x83, 0x5c, 0x49, 0x1f, 0xba, 0x7f,
/* (2^108)P */ 0x9d, 0x07, 0xf9, 0xf2, 0x23, 0x74, 0x8c, 0x5a, 0xc5, 0x3f, 0x02, 0x34, 0x7b, 0x15, 0x35, 0x17, 0x51, 0xb3, 0xfa, 0xd2, 0x9a, 0xb4, 0xf9, 0xe4, 0x3c, 0xe3, 0x78, 0xc8, 0x72, 0xff, 0x91, 0x66,
/* (2^109)P */ 0x3e, 0xff, 0x5e, 0xdc, 0xde, 0x2a, 0x2c, 0x12, 0xf4, 0x6c, 0x95, 0xd8, 0xf1, 0x4b, 0xdd, 0xf8, 0xda, 0x5b, 0x9e, 0x9e, 0x5d, 0x20, 0x86, 0xeb, 0x43, 0xc7, 0x75, 0xd9, 0xb9, 0x92, 0x9b, 0x04,
/* (2^110)P */ 0x5a, 0xc0, 0xf6, 0xb0, 0x30, 0x97, 0x37, 0xa5, 0x53, 0xa5, 0xf3, 0xc6, 0xac, 0xff, 0xa0, 0x72, 0x6d, 0xcd, 0x0d, 0xb2, 0x34, 0x2c, 0x03, 0xb0, 0x4a, 0x16, 0xd5, 0x88, 0xbc, 0x9d, 0x0e, 0x47,
/* (2^111)P */ 0x47, 0xc0, 0x37, 0xa2, 0x0c, 0xf1, 0x9c, 0xb1, 0xa2, 0x81, 0x6c, 0x1f, 0x71, 0x66, 0x54, 0xb6, 0x43, 0x0b, 0xd8, 0x6d, 0xd1, 0x1b, 0x32, 0xb3, 0x8e, 0xbe, 0x5f, 0x0c, 0x60, 0x4f, 0xc1, 0x48,
/* (2^112)P */ 0x03, 0xc8, 0xa6, 0x4a, 0x26, 0x1c, 0x45, 0x66, 0xa6, 0x7d, 0xfa, 0xa4, 0x04, 0x39, 0x6e, 0xb6, 0x95, 0x83, 0x12, 0xb3, 0xb0, 0x19, 0x5f, 0xd4, 0x10, 0xbc, 0xc9, 0xc3, 0x27, 0x26, 0x60, 0x31,
/* (2^113)P */ 0x0d, 0xe1, 0xe4, 0x32, 0x48, 0xdc, 0x20, 0x31, 0xf7, 0x17, 0xc7, 0x56, 0x67, 0xc4, 0x20, 0xeb, 0x94, 0x02, 0x28, 0x67, 0x3f, 0x2e, 0xf5, 0x00, 0x09, 0xc5, 0x30, 0x47, 0xc1, 0x4f, 0x6d, 0x56,
/* (2^114)P */ 0x06, 0x72, 0x83, 0xfd, 0x40, 0x5d, 0x3a, 0x7e, 0x7a, 0x54, 0x59, 0x71, 0xdc, 0x26, 0xe9, 0xc1, 0x95, 0x60, 0x8d, 0xa6, 0xfb, 0x30, 0x67, 0x21, 0xa7, 0xce, 0x69, 0x3f, 0x84, 0xc3, 0xe8, 0x22,
/* (2^115)P */ 0x2b, 0x4b, 0x0e, 0x93, 0xe8, 0x74, 0xd0, 0x33, 0x16, 0x58, 0xd1, 0x84, 0x0e, 0x35, 0xe4, 0xb6, 0x65, 0x23, 0xba, 0xd6, 0x6a, 0xc2, 0x34, 0x55, 0xf3, 0xf3, 0xf1, 0x89, 0x2f, 0xc1, 0x73, 0x77,
/* (2^116)P */ 0xaa, 0x62, 0x79, 0xa5, 0x4d, 0x40, 0xba, 0x8c, 0x56, 0xce, 0x99, 0x19, 0xa8, 0x97, 0x98, 0x5b, 0xfc, 0x92, 0x16, 0x12, 0x2f, 0x86, 0x8e, 0x50, 0x91, 0xc2, 0x93, 0xa0, 0x7f, 0x90, 0x81, 0x3a,
/* (2^117)P */ 0x10, 0xa5, 0x25, 0x47, 0xff, 0xd0, 0xde, 0x0d, 0x03, 0xc5, 0x3f, 0x67, 0x10, 0xcc, 0xd8, 0x10, 0x89, 0x4e, 0x1f, 0x9f, 0x1c, 0x15, 0x9d, 0x5b, 0x4c, 0xa4, 0x09, 0xcb, 0xd5, 0xc1, 0xa5, 0x32,
/* (2^118)P */ 0xfb, 0x41, 0x05, 0xb9, 0x42, 0xa4, 0x0a, 0x1e, 0xdb, 0x85, 0xb4, 0xc1, 0x7c, 0xeb, 0x85, 0x5f, 0xe5, 0xf2, 0x9d, 0x8a, 0xce, 0x95, 0xe5, 0xbe, 0x36, 0x22, 0x42, 0x22, 0xc7, 0x96, 0xe4, 0x25,
/* (2^119)P */ 0xb9, 0xe5, 0x0f, 0xcd, 0x46, 0x3c, 0xdf, 0x5e, 0x88, 0x33, 0xa4, 0xd2, 0x7e, 0x5a, 0xe7, 0x34, 0x52, 0xe3, 0x61, 0xd7, 0x11, 0xde, 0x88, 0xe4, 0x5c, 0x54, 0x85, 0xa0, 0x01, 0x8a, 0x87, 0x0e,
/* (2^120)P */ 0x04, 0xbb, 0x21, 0xe0, 0x77, 0x3c, 0x49, 0xba, 0x9a, 0x89, 0xdf, 0xc7, 0x43, 0x18, 0x4d, 0x2b, 0x67, 0x0d, 0xe8, 0x7a, 0x48, 0x7a, 0xa3, 0x9e, 0x94, 0x17, 0xe4, 0x11, 0x80, 0x95, 0xa9, 0x67,
/* (2^121)P */ 0x65, 0xb0, 0x97, 0x66, 0x1a, 0x05, 0x58, 0x4b, 0xd4, 0xa6, 0x6b, 0x8d, 0x7d, 0x3f, 0xe3, 0x47, 0xc1, 0x46, 0xca, 0x83, 0xd4, 0xa8, 0x4d, 0xbb, 0x0d, 0xdb, 0xc2, 0x81, 0xa1, 0xca, 0xbe, 0x68,
/* (2^122)P */ 0xa5, 0x9a, 0x98, 0x0b, 0xe9, 0x80, 0x89, 0x8d, 0x9b, 0xc9, 0x93, 0x2c, 0x4a, 0xb1, 0x5e, 0xf9, 0xa2, 0x73, 0x6e, 0x79, 0xc4, 0xc7, 0xc6, 0x51, 0x69, 0xb5, 0xef, 0xb5, 0x63, 0x83, 0x22, 0x6e,
/* (2^123)P */ 0xc8, 0x24, 0xd6, 0x2d, 0xb0, 0xc0, 0xbb, 0xc6, 0xee, 0x70, 0x81, 0xec, 0x7d, 0xb4, 0x7e, 0x77, 0xa9, 0xaf, 0xcf, 0x04, 0xa0, 0x15, 0xde, 0x3c, 0x9b, 0xbf, 0x60, 0x71, 0x08, 0xbc, 0xc6, 0x1d,
/* (2^124)P */ 0x02, 0x40, 0xc3, 0xee, 0x43, 0xe0, 0x07, 0x2e, 0x7f, 0xdc, 0x68, 0x7a, 0x67, 0xfc, 0xe9, 0x18, 0x9a, 0x5b, 0xd1, 0x8b, 0x18, 0x03, 0xda, 0xd8, 0x53, 0x82, 0x56, 0x00, 0xbb, 0xc3, 0xfb, 0x48,
/* (2^125)P */ 0xe1, 0x4c, 0x65, 0xfb, 0x4c, 0x7d, 0x54, 0x57, 0xad, 0xe2, 0x58, 0xa0, 0x82, 0x5b, 0x56, 0xd3, 0x78, 0x44, 0x15, 0xbf, 0x0b, 0xaf, 0x3e, 0xf6, 0x18, 0xbb, 0xdf, 0x14, 0xf1, 0x1e, 0x53, 0x47,
/* (2^126)P */ 0x87, 0xc5, 0x78, 0x42, 0x0a, 0x63, 0xec, 0xe1, 0xf3, 0x83, 0x8e, 0xca, 0x46, 0xd5, 0x07, 0x55, 0x2b, 0x0c, 0xdc, 0x3a, 0xc6, 0x35, 0xe1, 0x85, 0x4e, 0x84, 0x82, 0x56, 0xa8, 0xef, 0xa7, 0x0a,
/* (2^127)P */ 0x15, 0xf6, 0xe1, 0xb3, 0xa8, 0x1b, 0x69, 0x72, 0xfa, 0x3f, 0xbe, 0x1f, 0x70, 0xe9, 0xb4, 0x32, 0x68, 0x78, 0xbb, 0x39, 0x2e, 0xd9, 0xb6, 0x97, 0xe8, 0x39, 0x2e, 0xa0, 0xde, 0x53, 0xfe, 0x2c,
/* (2^128)P */ 0xb0, 0x52, 0xcd, 0x85, 0xcd, 0x92, 0x73, 0x68, 0x31, 0x98, 0xe2, 0x10, 0xc9, 0x66, 0xff, 0x27, 0x06, 0x2d, 0x83, 0xa9, 0x56, 0x45, 0x13, 0x97, 0xa0, 0xf8, 0x84, 0x0a, 0x36, 0xb0, 0x9b, 0x26,
/* (2^129)P */ 0x5c, 0xf8, 0x43, 0x76, 0x45, 0x55, 0x6e, 0x70, 0x1b, 0x7d, 0x59, 0x9b, 0x8c, 0xa4, 0x34, 0x37, 0x72, 0xa4, 0xef, 0xc6, 0xe8, 0x91, 0xee, 0x7a, 0xe0, 0xd9, 0xa9, 0x98, 0xc1, 0xab, 0xd6, 0x5c,
/* (2^130)P */ 0x1a, 0xe4, 0x3c, 0xcb, 0x06, 0xde, 0x04, 0x0e, 0x38, 0xe1, 0x02, 0x34, 0x89, 0xeb, 0xc6, 0xd8, 0x72, 0x37, 0x6e, 0x68, 0xbb, 0x59, 0x46, 0x90, 0xc8, 0xa8, 0x6b, 0x74, 0x71, 0xc3, 0x15, 0x72,
/* (2^131)P */ 0xd9, 0xa2, 0xe4, 0xea, 0x7e, 0xa9, 0x12, 0xfd, 0xc5, 0xf2, 0x94, 0x63, 0x51, 0xb7, 0x14, 0x95, 0x94, 0xf2, 0x08, 0x92, 0x80, 0xd5, 0x6f, 0x26, 0xb9, 0x26, 0x9a, 0x61, 0x85, 0x70, 0x84, 0x5c,
/* (2^132)P */ 0xea, 0x94, 0xd6, 0xfe, 0x10, 0x54, 0x98, 0x52, 0x54, 0xd2, 0x2e, 0x4a, 0x93, 0x5b, 0x90, 0x3c, 0x67, 0xe4, 0x3b, 0x2d, 0x69, 0x47, 0xbb, 0x10, 0xe1, 0xe9, 0xe5, 0x69, 0x2d, 0x3d, 0x3b, 0x06,
/* (2^133)P */ 0xeb, 0x7d, 0xa5, 0xdd, 0xee, 0x26, 0x27, 0x47, 0x91, 0x18, 0xf4, 0x10, 0xae, 0xc4, 0xb6, 0xef, 0x14, 0x76, 0x30, 0x7b, 0x91, 0x41, 0x16, 0x2b, 0x7c, 0x5b, 0xf4, 0xc4, 0x4f, 0x55, 0x7c, 0x11,
/* (2^134)P */ 0x12, 0x88, 0x9d, 0x8f, 0x11, 0xf3, 0x7c, 0xc0, 0x39, 0x79, 0x01, 0x50, 0x20, 0xd8, 0xdb, 0x01, 0x27, 0x28, 0x1b, 0x17, 0xf4, 0x03, 0xe8, 0xd7, 0xea, 0x25, 0xd2, 0x87, 0x74, 0xe8, 0x15, 0x10,
/* (2^135)P */ 0x4d, 0xcc, 0x3a, 0xd2, 0xfe, 0xe3, 0x8d, 0xc5, 0x2d, 0xbe, 0xa7, 0x94, 0xc2, 0x91, 0xdb, 0x50, 0x57, 0xf4, 0x9c, 0x1c, 0x3d, 0xd4, 0x94, 0x0b, 0x4a, 0x52, 0x37, 0x6e, 0xfa, 0x40, 0x16, 0x6b,
/* (2^136)P */ 0x09, 0x0d, 0xda, 0x5f, 0x6c, 0x34, 0x2f, 0x69, 0x51, 0x31, 0x4d, 0xfa, 0x59, 0x1c, 0x0b, 0x20, 0x96, 0xa2, 0x77, 0x07, 0x76, 0x6f, 0xc4, 0xb8, 0xcf, 0xfb, 0xfd, 0x3f, 0x5f, 0x39, 0x38, 0x4b,
/* (2^137)P */ 0x71, 0xd6, 0x54, 0xbe, 0x00, 0x5e, 0xd2, 0x18, 0xa6, 0xab, 0xc8, 0xbe, 0x82, 0x05, 0xd5, 0x60, 0x82, 0xb9, 0x78, 0x3b, 0x26, 0x8f, 0xad, 0x87, 0x32, 0x04, 0xda, 0x9c, 0x4e, 0xf6, 0xfd, 0x50,
/* (2^138)P */ 0xf0, 0xdc, 0x78, 0xc5, 0xaa, 0x67, 0xf5, 0x90, 0x3b, 0x13, 0xa3, 0xf2, 0x0e, 0x9b, 0x1e, 0xef, 0x71, 0xde, 0xd9, 0x42, 0x92, 0xba, 0xeb, 0x0e, 0xc7, 0x01, 0x31, 0xf0, 0x9b, 0x3c, 0x47, 0x15,
/* (2^139)P */ 0x95, 0x80, 0xb7, 0x56, 0xae, 0xe8, 0x77, 0x7c, 0x8e, 0x07, 0x6f, 0x6e, 0x66, 0xe7, 0x78, 0xb6, 0x1f, 0xba, 0x48, 0x53, 0x61, 0xb9, 0xa0, 0x2d, 0x0b, 0x3f, 0x73, 0xff, 0xc1, 0x31, 0xf9, 0x7c,
/* (2^140)P */ 0x6c, 0x36, 0x0a, 0x0a, 0xf5, 0x57, 0xb3, 0x26, 0x32, 0xd7, 0x87, 0x2b, 0xf4, 0x8c, 0x70, 0xe9, 0xc0, 0xb2, 0x1c, 0xf9, 0xa5, 0xee, 0x3a, 0xc1, 0x4c, 0xbb, 0x43, 0x11, 0x99, 0x0c, 0xd9, 0x35,
/* (2^141)P */ 0xdc, 0xd9, 0xa0, 0xa9, 0x04, 0xc4, 0xc1, 0x47, 0x51, 0xd2, 0x72, 0x19, 0x45, 0x58, 0x9e, 0x65, 0x31, 0x8c, 0xb3, 0x73, 0xc4, 0xa8, 0x75, 0x38, 0x24, 0x1f, 0x56, 0x79, 0xd3, 0x9e, 0xbd, 0x1f,
/* (2^142)P */ 0x8d, 0xc2, 0x1e, 0xd4, 0x6f, 0xbc, 0xfa, 0x11, 0xca, 0x2d, 0x2a, 0xcd, 0xe3, 0xdf, 0xf8, 0x7e, 0x95, 0x45, 0x40, 0x8c, 0x5d, 0x3b, 0xe7, 0x72, 0x27, 0x2f, 0xb7, 0x54, 0x49, 0xfa, 0x35, 0x61,
/* (2^143)P */ 0x9c, 0xb6, 0x24, 0xde, 0xa2, 0x32, 0xfc, 0xcc, 0x88, 0x5d, 0x09, 0x1f, 0x8c, 0x69, 0x55, 0x3f, 0x29, 0xf9, 0xc3, 0x5a, 0xed, 0x50, 0x33, 0xbe, 0xeb, 0x7e, 0x47, 0xca, 0x06, 0xf8, 0x9b, 0x5e,
/* (2^144)P */ 0x68, 0x9f, 0x30, 0x3c, 0xb6, 0x8f, 0xce, 0xe9, 0xf4, 0xf9, 0xe1, 0x65, 0x35, 0xf6, 0x76, 0x53, 0xf1, 0x93, 0x63, 0x5a, 0xb3, 0xcf, 0xaf, 0xd1, 0x06, 0x35, 0x62, 0xe5, 0xed, 0xa1, 0x32, 0x66,
/* (2^145)P */ 0x4c, 0xed, 0x2d, 0x0c, 0x39, 0x6c, 0x7d, 0x0b, 0x1f, 0xcb, 0x04, 0xdf, 0x81, 0x32, 0xcb, 0x56, 0xc7, 0xc3, 0xec, 0x49, 0x12, 0x5a, 0x30, 0x66, 0x2a, 0xa7, 0x8c, 0xa3, 0x60, 0x8b, 0x58, 0x5d,
/* (2^146)P */ 0x2d, 0xf4, 0xe5, 0xe8, 0x78, 0xbf, 0xec, 0xa6, 0xec, 0x3e, 0x8a, 0x3c, 0x4b, 0xb4, 0xee, 0x86, 0x04, 0x16, 0xd2, 0xfb, 0x48, 0x9c, 0x21, 0xec, 0x31, 0x67, 0xc3, 0x17, 0xf5, 0x1a, 0xaf, 0x1a,
/* (2^147)P */ 0xe7, 0xbd, 0x69, 0x67, 0x83, 0xa2, 0x06, 0xc3, 0xdb, 0x2a, 0x1e, 0x2b, 0x62, 0x80, 0x82, 0x20, 0xa6, 0x94, 0xff, 0xfb, 0x1f, 0xf5, 0x27, 0x80, 0x6b, 0xf2, 0x24, 0x11, 0xce, 0xa1, 0xcf, 0x76,
/* (2^148)P */ 0xb6, 0xab, 0x22, 0x24, 0x56, 0x00, 0xeb, 0x18, 0xc3, 0x29, 0x8c, 0x8f, 0xd5, 0xc4, 0x77, 0xf3, 0x1a, 0x56, 0x31, 0xf5, 0x07, 0xc2, 0xbb, 0x4d, 0x27, 0x8a, 0x12, 0x82, 0xf0, 0xb7, 0x53, 0x02,
/* (2^149)P */ 0xe0, 0x17, 0x2c, 0xb6, 0x1c, 0x09, 0x1f, 0x3d, 0xa9, 0x28, 0x46, 0xd6, 0xab, 0xe1, 0x60, 0x48, 0x53, 0x42, 0x9d, 0x30, 0x36, 0x74, 0xd1, 0x52, 0x76, 0xe5, 0xfa, 0x3e, 0xe1, 0x97, 0x6f, 0x35,
/* (2^150)P */ 0x5b, 0x53, 0x50, 0xa1, 0x1a, 0xe1, 0x51, 0xd3, 0xcc, 0x78, 0xd8, 0x1d, 0xbb, 0x45, 0x6b, 0x3e, 0x98, 0x2c, 0xd9, 0xbe, 0x28, 0x61, 0x77, 0x0c, 0xb8, 0x85, 0x28, 0x03, 0x93, 0xae, 0x34, 0x1d,
/* (2^151)P */ 0xc3, 0xa4, 0x5b, 0xa8, 0x8c, 0x48, 0xa0, 0x4b, 0xce, 0xe6, 0x9c, 0x3c, 0xc3, 0x48, 0x53, 0x98, 0x70, 0xa7, 0xbd, 0x97, 0x6f, 0x4c, 0x12, 0x66, 0x4a, 0x12, 0x54, 0x06, 0x29, 0xa0, 0x81, 0x0f,
/* (2^152)P */ 0xfd, 0x86, 0x9b, 0x56, 0xa6, 0x9c, 0xd0, 0x9e, 0x2d, 0x9a, 0xaf, 0x18, 0xfd, 0x09, 0x10, 0x81, 0x0a, 0xc2, 0xd8, 0x93, 0x3f, 0xd0, 0x08, 0xff, 0x6b, 0xf2, 0xae, 0x9f, 0x19, 0x48, 0xa1, 0x52,
/* (2^153)P */ 0x73, 0x1b, 0x8d, 0x2d, 0xdc, 0xf9, 0x03, 0x3e, 0x70, 0x1a, 0x96, 0x73, 0x18, 0x80, 0x05, 0x42, 0x70, 0x59, 0xa3, 0x41, 0xf0, 0x87, 0xd9, 0xc0, 0x49, 0xd5, 0xc0, 0xa1, 0x15, 0x1f, 0xaa, 0x07,
/* (2^154)P */ 0x24, 0x72, 0xd2, 0x8c, 0xe0, 0x6c, 0xd4, 0xdf, 0x39, 0x42, 0x4e, 0x93, 0x4f, 0x02, 0x0a, 0x6d, 0x59, 0x7b, 0x89, 0x99, 0x63, 0x7a, 0x8a, 0x80, 0xa2, 0x95, 0x3d, 0xe1, 0xe9, 0x56, 0x45, 0x0a,
/* (2^155)P */ 0x45, 0x30, 0xc1, 0xe9, 0x1f, 0x99, 0x1a, 0xd2, 0xb8, 0x51, 0x77, 0xfe, 0x48, 0x85, 0x0e, 0x9b, 0x35, 0x00, 0xf3, 0x4b, 0xcb, 0x43, 0xa6, 0x5d, 0x21, 0xf7, 0x40, 0x39, 0xd6, 0x28, 0xdb, 0x77,
/* (2^156)P */ 0x11, 0x90, 0xdc, 0x4a, 0x61, 0xeb, 0x5e, 0xfc, 0xeb, 0x11, 0xc4, 0xe8, 0x9a, 0x41, 0x29, 0x52, 0x74, 0xcf, 0x1d, 0x7d, 0x78, 0xe7, 0xc3, 0x9e, 0xb5, 0x4c, 0x6e, 0x21, 0x3e, 0x05, 0x0d, 0x34,
/* (2^157)P */ 0xb4, 0xf2, 0x8d, 0xb4, 0x39, 0xaf, 0xc7, 0xca, 0x94, 0x0a, 0xa1, 0x71, 0x28, 0xec, 0xfa, 0xc0, 0xed, 0x75, 0xa5, 0x5c, 0x24, 0x69, 0x0a, 0x14, 0x4c, 0x3a, 0x27, 0x34, 0x71, 0xc3, 0xf1, 0x0c,
/* (2^158)P */ 0xa5, 0xb8, 0x24, 0xc2, 0x6a, 0x30, 0xee, 0xc8, 0xb0, 0x30, 0x49, 0xcb, 0x7c, 0xee, 0xea, 0x57, 0x4f, 0xe7, 0xcb, 0xaa, 0xbd, 0x06, 0xe8, 0xa1, 0x7d, 0x65, 0xeb, 0x2e, 0x74, 0x62, 0x9a, 0x7d,
/* (2^159)P */ 0x30, 0x48, 0x6c, 0x54, 0xef, 0xb6, 0xb6, 0x9e, 0x2e, 0x6e, 0xb3, 0xdd, 0x1f, 0xca, 0x5c, 0x88, 0x05, 0x71, 0x0d, 0xef, 0x83, 0xf3, 0xb9, 0xe6, 0x12, 0x04, 0x2e, 0x9d, 0xef, 0x4f, 0x65, 0x58,
/* (2^160)P */ 0x26, 0x8e, 0x0e, 0xbe, 0xff, 0xc4, 0x05, 0xa9, 0x6e, 0x81, 0x31, 0x9b, 0xdf, 0xe5, 0x2d, 0x94, 0xe1, 0x88, 0x2e, 0x80, 0x3f, 0x72, 0x7d, 0x49, 0x8d, 0x40, 0x2f, 0x60, 0xea, 0x4d, 0x68, 0x30,
/* (2^161)P */ 0x34, 0xcb, 0xe6, 0xa3, 0x78, 0xa2, 0xe5, 0x21, 0xc4, 0x1d, 0x15, 0x5b, 0x6f, 0x6e, 0xfb, 0xae, 0x15, 0xca, 0x77, 0x9d, 0x04, 0x8e, 0x0b, 0xb3, 0x81, 0x89, 0xb9, 0x53, 0xcf, 0xc9, 0xc3, 0x28,
/* (2^162)P */ 0x2a, 0xdd, 0x6c, 0x55, 0x21, 0xb7, 0x7f, 0x28, 0x74, 0x22, 0x02, 0x97, 0xa8, 0x7c, 0x31, 0x0d, 0x58, 0x32, 0x54, 0x3a, 0x42, 0xc7, 0x68, 0x74, 0x2f, 0x64, 0xb5, 0x4e, 0x46, 0x11, 0x7f, 0x4a,
/* (2^163)P */ 0xa6, 0x3a, 0x19, 0x4d, 0x77, 0xa4, 0x37, 0xa2, 0xa1, 0x29, 0x21, 0xa9, 0x6e, 0x98, 0x65, 0xd8, 0x88, 0x1a, 0x7c, 0xf8, 0xec, 0x15, 0xc5, 0x24, 0xeb, 0xf5, 0x39, 0x5f, 0x57, 0x03, 0x40, 0x60,
/* (2^164)P */ 0x27, 0x9b, 0x0a, 0x57, 0x89, 0xf1, 0xb9, 0x47, 0x78, 0x4b, 0x5e, 0x46, 0xde, 0xce, 0x98, 0x2b, 0x20, 0x5c, 0xb8, 0xdb, 0x51, 0xf5, 0x6d, 0x02, 0x01, 0x19, 0xe2, 0x47, 0x10, 0xd9, 0xfc, 0x74,
/* (2^165)P */ 0xa3, 0xbf, 0xc1, 0x23, 0x0a, 0xa9, 0xe2, 0x13, 0xf6, 0x19, 0x85, 0x47, 0x4e, 0x07, 0xb0, 0x0c, 0x44, 0xcf, 0xf6, 0x3a, 0xbe, 0xcb, 0xf1, 0x5f, 0xbe, 0x2d, 0x81, 0xbe, 0x38, 0x54, 0xfe, 0x67,
/* (2^166)P */ 0xb0, 0x05, 0x0f, 0xa4, 0x4f, 0xf6, 0x3c, 0xd1, 0x87, 0x37, 0x28, 0x32, 0x2f, 0xfb, 0x4d, 0x05, 0xea, 0x2a, 0x0d, 0x7f, 0x5b, 0x91, 0x73, 0x41, 0x4e, 0x0d, 0x61, 0x1f, 0x4f, 0x14, 0x2f, 0x48,
/* (2^167)P */ 0x34, 0x82, 0x7f, 0xb4, 0x01, 0x02, 0x21, 0xf6, 0x90, 0xb9, 0x70, 0x9e, 0x92, 0xe1, 0x0a, 0x5d, 0x7c, 0x56, 0x49, 0xb0, 0x55, 0xf4, 0xd7, 0xdc, 0x01, 0x6f, 0x91, 0xf0, 0xf1, 0xd0, 0x93, 0x7e,
/* (2^168)P */ 0xfa, 0xb4, 0x7d, 0x8a, 0xf1, 0xcb, 0x79, 0xdd, 0x2f, 0xc6, 0x74, 0x6f, 0xbf, 0x91, 0x83, 0xbe, 0xbd, 0x91, 0x82, 0x4b, 0xd1, 0x45, 0x71, 0x02, 0x05, 0x17, 0xbf, 0x2c, 0xea, 0x73, 0x5a, 0x58,
/* (2^169)P */ 0xb2, 0x0d, 0x8a, 0x92, 0x3e, 0xa0, 0x5c, 0x48, 0xe7, 0x57, 0x28, 0x74, 0xa5, 0x01, 0xfc, 0x10, 0xa7, 0x51, 0xd5, 0xd6, 0xdb, 0x2e, 0x48, 0x2f, 0x8a, 0xdb, 0x8f, 0x04, 0xb5, 0x33, 0x04, 0x0f,
/* (2^170)P */ 0x47, 0x62, 0xdc, 0xd7, 0x8d, 0x2e, 0xda, 0x60, 0x9a, 0x81, 0xd4, 0x8c, 0xd3, 0xc9, 0xb4, 0x88, 0x97, 0x66, 0xf6, 0x01, 0xc0, 0x3a, 0x03, 0x13, 0x75, 0x7d, 0x36, 0x3b, 0xfe, 0x24, 0x3b, 0x27,
/* (2^171)P */ 0xd4, 0xb9, 0xb3, 0x31, 0x6a, 0xf6, 0xe8, 0xc6, 0xd5, 0x49, 0xdf, 0x94, 0xa4, 0x14, 0x15, 0x28, 0xa7, 0x3d, 0xb2, 0xc8, 0xdf, 0x6f, 0x72, 0xd1, 0x48, 0xe5, 0xde, 0x03, 0xd1, 0xe7, 0x3a, 0x4b,
/* (2^172)P */ 0x7e, 0x9d, 0x4b, 0xce, 0x19, 0x6e, 0x25, 0xc6, 0x1c, 0xc6, 0xe3, 0x86, 0xf1, 0x5c, 0x5c, 0xff, 0x45, 0xc1, 0x8e, 0x4b, 0xa3, 0x3c, 0xc6, 0xac, 0x74, 0x65, 0xe6, 0xfe, 0x88, 0x18, 0x62, 0x74,
/* (2^173)P */ 0x1e, 0x0a, 0x29, 0x45, 0x96, 0x40, 0x6f, 0x95, 0x2e, 0x96, 0x3a, 0x26, 0xe3, 0xf8, 0x0b, 0xef, 0x7b, 0x64, 0xc2, 0x5e, 0xeb, 0x50, 0x6a, 0xed, 0x02, 0x75, 0xca, 0x9d, 0x3a, 0x28, 0x94, 0x06,
/* (2^174)P */ 0xd1, 0xdc, 0xa2, 0x43, 0x36, 0x96, 0x9b, 0x76, 0x53, 0x53, 0xfc, 0x09, 0xea, 0xc8, 0xb7, 0x42, 0xab, 0x7e, 0x39, 0x13, 0xee, 0x2a, 0x00, 0x4f, 0x3a, 0xd6, 0xb7, 0x19, 0x2c, 0x5e, 0x00, 0x63,
/* (2^175)P */ 0xea, 0x3b, 0x02, 0x63, 0xda, 0x36, 0x67, 0xca, 0xb7, 0x99, 0x2a, 0xb1, 0x6d, 0x7f, 0x6c, 0x96, 0xe1, 0xc5, 0x37, 0xc5, 0x90, 0x93, 0xe0, 0xac, 0xee, 0x89, 0xaa, 0xa1, 0x63, 0x60, 0x69, 0x0b,
/* (2^176)P */ 0xe5, 0x56, 0x8c, 0x28, 0x97, 0x3e, 0xb0, 0xeb, 0xe8, 0x8b, 0x8c, 0x93, 0x9f, 0x9f, 0x2a, 0x43, 0x71, 0x7f, 0x71, 0x5b, 0x3d, 0xa9, 0xa5, 0xa6, 0x97, 0x9d, 0x8f, 0xe1, 0xc3, 0xb4, 0x5f, 0x1a,
/* (2^177)P */ 0xce, 0xcd, 0x60, 0x1c, 0xad, 0xe7, 0x94, 0x1c, 0xa0, 0xc4, 0x02, 0xfc, 0x43, 0x2a, 0x20, 0xee, 0x20, 0x6a, 0xc4, 0x67, 0xd8, 0xe4, 0xaf, 0x8d, 0x58, 0x7b, 0xc2, 0x8a, 0x3c, 0x26, 0x10, 0x0a,
/* (2^178)P */ 0x4a, 0x2a, 0x43, 0xe4, 0xdf, 0xa9, 0xde, 0xd0, 0xc5, 0x77, 0x92, 0xbe, 0x7b, 0xf8, 0x6a, 0x85, 0x1a, 0xc7, 0x12, 0xc2, 0xac, 0x72, 0x84, 0xce, 0x91, 0x1e, 0xbb, 0x9b, 0x6d, 0x1b, 0x15, 0x6f,
/* (2^179)P */ 0x6a, 0xd5, 0xee, 0x7c, 0x52, 0x6c, 0x77, 0x26, 0xec, 0xfa, 0xf8, 0xfb, 0xb7, 0x1c, 0x21, 0x7d, 0xcc, 0x09, 0x46, 0xfd, 0xa6, 0x66, 0xae, 0x37, 0x42, 0x0c, 0x77, 0xd2, 0x02, 0xb7, 0x81, 0x1f,
/* (2^180)P */ 0x92, 0x83, 0xc5, 0xea, 0x57, 0xb0, 0xb0, 0x2f, 0x9d, 0x4e, 0x74, 0x29, 0xfe, 0x89, 0xdd, 0xe1, 0xf8, 0xb4, 0xbe, 0x17, 0xeb, 0xf8, 0x64, 0xc9, 0x1e, 0xd4, 0xa2, 0xc9, 0x73, 0x10, 0x57, 0x29,
/* (2^181)P */ 0x54, 0xe2, 0xc0, 0x81, 0x89, 0xa1, 0x48, 0xa9, 0x30, 0x28, 0xb2, 0x65, 0x9b, 0x36, 0xf6, 0x2d, 0xc6, 0xd3, 0xcf, 0x5f, 0xd7, 0xb2, 0x3e, 0xa3, 0x1f, 0xa0, 0x99, 0x41, 0xec, 0xd6, 0x8c, 0x07,
/* (2^182)P */ 0x2f, 0x0d, 0x90, 0xad, 0x41, 0x4a, 0x58, 0x4a, 0x52, 0x4c, 0xc7, 0xe2, 0x78, 0x2b, 0x14, 0x32, 0x78, 0xc9, 0x31, 0x84, 0x33, 0xe8, 0xc4, 0x68, 0xc2, 0x9f, 0x68, 0x08, 0x90, 0xea, 0x69, 0x7f,
/* (2^183)P */ 0x65, 0x82, 0xa3, 0x46, 0x1e, 0xc8, 0xf2, 0x52, 0xfd, 0x32, 0xa8, 0x04, 0x2d, 0x07, 0x78, 0xfd, 0x94, 0x9e, 0x35, 0x25, 0xfa, 0xd5, 0xd7, 0x8c, 0xd2, 0x29, 0xcc, 0x54, 0x74, 0x1b, 0xe7, 0x4d,
/* (2^184)P */ 0xc9, 0x6a, 0xda, 0x1e, 0xad, 0x60, 0xeb, 0x42, 0x3a, 0x9c, 0xc0, 0xdb, 0xdf, 0x37, 0xad, 0x0a, 0x91, 0xc1, 0x3c, 0xe3, 0x71, 0x4b, 0x00, 0x81, 0x3c, 0x80, 0x22, 0x51, 0x34, 0xbe, 0xe6, 0x44,
/* (2^185)P */ 0xdb, 0x20, 0x19, 0xba, 0x88, 0x83, 0xfe, 0x03, 0x08, 0xb0, 0x0d, 0x15, 0x32, 0x7c, 0xd5, 0xf5, 0x29, 0x0c, 0xf6, 0x1a, 0x28, 0xc4, 0xc8, 0x49, 0xee, 0x1a, 0x70, 0xde, 0x18, 0xb5, 0xed, 0x21,
/* (2^186)P */ 0x99, 0xdc, 0x06, 0x8f, 0x41, 0x3e, 0xb6, 0x7f, 0xb8, 0xd7, 0x66, 0xc1, 0x99, 0x0d, 0x46, 0xa4, 0x83, 0x0a, 0x52, 0xce, 0x48, 0x52, 0xdd, 0x24, 0x58, 0x83, 0x92, 0x2b, 0x71, 0xad, 0xc3, 0x5e,
/* (2^187)P */ 0x0f, 0x93, 0x17, 0xbd, 0x5f, 0x2a, 0x02, 0x15, 0xe3, 0x70, 0x25, 0xd8, 0x77, 0x4a, 0xf6, 0xa4, 0x12, 0x37, 0x78, 0x15, 0x69, 0x8d, 0xbc, 0x12, 0xbb, 0x0a, 0x62, 0xfc, 0xc0, 0x94, 0x81, 0x49,
/* (2^188)P */ 0x82, 0x6c, 0x68, 0x55, 0xd2, 0xd9, 0xa2, 0x38, 0xf0, 0x21, 0x3e, 0x19, 0xd9, 0x6b, 0x5c, 0x78, 0x84, 0x54, 0x4a, 0xb2, 0x1a, 0xc8, 0xd5, 0xe4, 0x89, 0x09, 0xe2, 0xb2, 0x60, 0x78, 0x30, 0x56,
/* (2^189)P */ 0xc4, 0x74, 0x4d, 0x8b, 0xf7, 0x55, 0x9d, 0x42, 0x31, 0x01, 0x35, 0x43, 0x46, 0x83, 0xf1, 0x22, 0xff, 0x1f, 0xc7, 0x98, 0x45, 0xc2, 0x60, 0x1e, 0xef, 0x83, 0x99, 0x97, 0x14, 0xf0, 0xf2, 0x59,
/* (2^190)P */ 0x44, 0x4a, 0x49, 0xeb, 0x56, 0x7d, 0xa4, 0x46, 0x8e, 0xa1, 0x36, 0xd6, 0x54, 0xa8, 0x22, 0x3e, 0x3b, 0x1c, 0x49, 0x74, 0x52, 0xe1, 0x46, 0xb3, 0xe7, 0xcd, 0x90, 0x53, 0x4e, 0xfd, 0xea, 0x2c,
/* (2^191)P */ 0x75, 0x66, 0x0d, 0xbe, 0x38, 0x85, 0x8a, 0xba, 0x23, 0x8e, 0x81, 0x50, 0xbb, 0x74, 0x90, 0x4b, 0xc3, 0x04, 0xd3, 0x85, 0x90, 0xb8, 0xda, 0xcb, 0xc4, 0x92, 0x61, 0xe5, 0xe0, 0x4f, 0xa2, 0x61,
/* (2^192)P */ 0xcb, 0x5b, 0x52, 0xdb, 0xe6, 0x15, 0x76, 0xcb, 0xca, 0xe4, 0x67, 0xa5, 0x35, 0x8c, 0x7d, 0xdd, 0x69, 0xdd, 0xfc, 0xca, 0x3a, 0x15, 0xb4, 0xe6, 0x66, 0x97, 0x3c, 0x7f, 0x09, 0x8e, 0x66, 0x2d,
/* (2^193)P */ 0xf0, 0x5e, 0xe5, 0x5c, 0x26, 0x7e, 0x7e, 0xa5, 0x67, 0xb9, 0xd4, 0x7c, 0x52, 0x4e, 0x9f, 0x5d, 0xe5, 0xd1, 0x2f, 0x49, 0x06, 0x36, 0xc8, 0xfb, 0xae, 0xf7, 0xc3, 0xb7, 0xbe, 0x52, 0x0d, 0x09,
/* (2^194)P */ 0x7c, 0x4d, 0x7b, 0x1e, 0x5a, 0x51, 0xb9, 0x09, 0xc0, 0x44, 0xda, 0x99, 0x25, 0x6a, 0x26, 0x1f, 0x04, 0x55, 0xc5, 0xe2, 0x48, 0x95, 0xc4, 0xa1, 0xcc, 0x15, 0x6f, 0x12, 0x87, 0x42, 0xf0, 0x7e,
/* (2^195)P */ 0x15, 0xef, 0x30, 0xbd, 0x9d, 0x65, 0xd1, 0xfe, 0x7b, 0x27, 0xe0, 0xc4, 0xee, 0xb9, 0x4a, 0x8b, 0x91, 0x32, 0xdf, 0xa5, 0x36, 0x62, 0x4d, 0x88, 0x88, 0xf7, 0x5c, 0xbf, 0xa6, 0x6e, 0xd9, 0x1f,
/* (2^196)P */ 0x9a, 0x0d, 0x19, 0x1f, 0x98, 0x61, 0xa1, 0x42, 0xc1, 0x52, 0x60, 0x7e, 0x50, 0x49, 0xd8, 0x61, 0xd5, 0x2c, 0x5a, 0x28, 0xbf, 0x13, 0xe1, 0x9f, 0xd8, 0x85, 0xad, 0xdb, 0x76, 0xd6, 0x22, 0x7c,
/* (2^197)P */ 0x7d, 0xd2, 0xfb, 0x2b, 0xed, 0x70, 0xe7, 0x82, 0xa5, 0xf5, 0x96, 0xe9, 0xec, 0xb2, 0x05, 0x4c, 0x50, 0x01, 0x90, 0xb0, 0xc2, 0xa9, 0x40, 0xcd, 0x64, 0xbf, 0xd9, 0x13, 0x92, 0x31, 0x95, 0x58,
/* (2^198)P */ 0x08, 0x2e, 0xea, 0x3f, 0x70, 0x5d, 0xcc, 0xe7, 0x8c, 0x18, 0xe2, 0x58, 0x12, 0x49, 0x0c, 0xb5, 0xf0, 0x5b, 0x20, 0x48, 0xaa, 0x0b, 0xe3, 0xcc, 0x62, 0x2d, 0xa3, 0xcf, 0x9c, 0x65, 0x7c, 0x53,
/* (2^199)P */ 0x88, 0xc0, 0xcf, 0x98, 0x3a, 0x62, 0xb6, 0x37, 0xa4, 0xac, 0xd6, 0xa4, 0x1f, 0xed, 0x9b, 0xfe, 0xb0, 0xd1, 0xa8, 0x56, 0x8e, 0x9b, 0xd2, 0x04, 0x75, 0x95, 0x51, 0x0b, 0xc4, 0x71, 0x5f, 0x72,
/* (2^200)P */ 0xe6, 0x9c, 0x33, 0xd0, 0x9c, 0xf8, 0xc7, 0x28, 0x8b, 0xc1, 0xdd, 0x69, 0x44, 0xb1, 0x67, 0x83, 0x2c, 0x65, 0xa1, 0xa6, 0x83, 0xda, 0x3a, 0x88, 0x17, 0x6c, 0x4d, 0x03, 0x74, 0x19, 0x5f, 0x58,
/* (2^201)P */ 0x88, 0x91, 0xb1, 0xf1, 0x66, 0xb2, 0xcf, 0x89, 0x17, 0x52, 0xc3, 0xe7, 0x63, 0x48, 0x3b, 0xe6, 0x6a, 0x52, 0xc0, 0xb4, 0xa6, 0x9d, 0x8c, 0xd8, 0x35, 0x46, 0x95, 0xf0, 0x9d, 0x5c, 0x03, 0x3e,
/* (2^202)P */ 0x9d, 0xde, 0x45, 0xfb, 0x12, 0x54, 0x9d, 0xdd, 0x0d, 0xf4, 0xcf, 0xe4, 0x32, 0x45, 0x68, 0xdd, 0x1c, 0x67, 0x1d, 0x15, 0x9b, 0x99, 0x5c, 0x4b, 0x90, 0xf6, 0xe7, 0x11, 0xc8, 0x2c, 0x8c, 0x2d,
/* (2^203)P */ 0x40, 0x5d, 0x05, 0x90, 0x1d, 0xbe, 0x54, 0x7f, 0x40, 0xaf, 0x4a, 0x46, 0xdf, 0xc5, 0x64, 0xa4, 0xbe, 0x17, 0xe9, 0xf0, 0x24, 0x96, 0x97, 0x33, 0x30, 0x6b, 0x35, 0x27, 0xc5, 0x8d, 0x01, 0x2c,
/* (2^204)P */ 0xd4, 0xb3, 0x30, 0xe3, 0x24, 0x50, 0x41, 0xa5, 0xd3, 0x52, 0x16, 0x69, 0x96, 0x3d, 0xff, 0x73, 0xf1, 0x59, 0x9b, 0xef, 0xc4, 0x42, 0xec, 0x94, 0x5a, 0x8e, 0xd0, 0x18, 0x16, 0x20, 0x47, 0x07,
/* (2^205)P */ 0x53, 0x1c, 0x41, 0xca, 0x8a, 0xa4, 0x6c, 0x4d, 0x19, 0x61, 0xa6, 0xcf, 0x2f, 0x5f, 0x41, 0x66, 0xff, 0x27, 0xe2, 0x51, 0x00, 0xd4, 0x4d, 0x9c, 0xeb, 0xf7, 0x02, 0x9a, 0xc0, 0x0b, 0x81, 0x59,
/* (2^206)P */ 0x1d, 0x10, 0xdc, 0xb3, 0x71, 0xb1, 0x7e, 0x2a, 0x8e, 0xf6, 0xfe, 0x9f, 0xb9, 0x5a, 0x1c, 0x44, 0xea, 0x59, 0xb3, 0x93, 0x9b, 0x5c, 0x02, 0x32, 0x2f, 0x11, 0x9d, 0x1e, 0xa7, 0xe0, 0x8c, 0x5e,
/* (2^207)P */ 0xfd, 0x03, 0x95, 0x42, 0x92, 0xcb, 0xcc, 0xbf, 0x55, 0x5d, 0x09, 0x2f, 0x75, 0xba, 0x71, 0xd2, 0x1e, 0x09, 0x2d, 0x97, 0x5e, 0xad, 0x5e, 0x34, 0xba, 0x03, 0x31, 0xa8, 0x11, 0xdf, 0xc8, 0x18,
/* (2^208)P */ 0x4c, 0x0f, 0xed, 0x9a, 0x9a, 0x94, 0xcd, 0x90, 0x7e, 0xe3, 0x60, 0x66, 0xcb, 0xf4, 0xd1, 0xc5, 0x0b, 0x2e, 0xc5, 0x56, 0x2d, 0xc5, 0xca, 0xb8, 0x0d, 0x8e, 0x80, 0xc5, 0x00, 0xe4, 0x42, 0x6e,
/* (2^209)P */ 0x23, 0xfd, 0xae, 0xee, 0x66, 0x69, 0xb4, 0xa3, 0xca, 0xcd, 0x9e, 0xe3, 0x0b, 0x1f, 0x4f, 0x0c, 0x1d, 0xa5, 0x83, 0xd6, 0xc9, 0xc8, 0x9d, 0x18, 0x1b, 0x35, 0x09, 0x4c, 0x05, 0x7f, 0xf2, 0x51,
/* (2^210)P */ 0x82, 0x06, 0x32, 0x2a, 0xcd, 0x7c, 0x48, 0x4c, 0x96, 0x1c, 0xdf, 0xb3, 0x5b, 0xa9, 0x7e, 0x58, 0xe8, 0xb8, 0x5c, 0x55, 0x9e, 0xf7, 0xcc, 0xc8, 0x3d, 0xd7, 0x06, 0xa2, 0x29, 0xc8, 0x7d, 0x54,
/* (2^211)P */ 0x06, 0x9b, 0xc3, 0x80, 0xcd, 0xa6, 0x22, 0xb8, 0xc6, 0xd4, 0x00, 0x20, 0x73, 0x54, 0x6d, 0xe9, 0x4d, 0x3b, 0x46, 0x91, 0x6f, 0x5b, 0x53, 0x28, 0x1d, 0x6e, 0x48, 0xe2, 0x60, 0x46, 0x8f, 0x22,
/* (2^212)P */ 0xbf, 0x3a, 0x8d, 0xde, 0x38, 0x95, 0x79, 0x98, 0x6e, 0xca, 0xeb, 0x45, 0x00, 0x33, 0xd8, 0x8c, 0x38, 0xe7, 0x21, 0x82, 0x00, 0x2a, 0x95, 0x79, 0xbb, 0xd2, 0x5c, 0x53, 0xa7, 0xe1, 0x22, 0x43,
/* (2^213)P */ 0x1c, 0x80, 0xd1, 0x19, 0x18, 0xc1, 0x14, 0xb1, 0xc7, 0x5e, 0x3f, 0x4f, 0xd8, 0xe4, 0x16, 0x20, 0x4c, 0x0f, 0x26, 0x09, 0xf4, 0x2d, 0x0e, 0xdd, 0x66, 0x72, 0x5f, 0xae, 0xc0, 0x62, 0xc3, 0x5e,
/* (2^214)P */ 0xee, 0xb4, 0xb2, 0xb8, 0x18, 0x2b, 0x46, 0xc0, 0xfb, 0x1a, 0x4d, 0x27, 0x50, 0xd9, 0xc8, 0x7c, 0xd2, 0x02, 0x6b, 0x43, 0x05, 0x71, 0x5f, 0xf2, 0xd3, 0xcc, 0xf9, 0xbf, 0xdc, 0xf8, 0xbb, 0x43,
/* (2^215)P */ 0xdf, 0xe9, 0x39, 0xa0, 0x67, 0x17, 0xad, 0xb6, 0x83, 0x35, 0x9d, 0xf6, 0xa8, 0x4d, 0x71, 0xb0, 0xf5, 0x31, 0x29, 0xb4, 0x18, 0xfa, 0x55, 0x5e, 0x61, 0x09, 0xc6, 0x33, 0x8f, 0x55, 0xd5, 0x4e,
/* (2^216)P */ 0xdd, 0xa5, 0x47, 0xc6, 0x01, 0x79, 0xe3, 0x1f, 0x57, 0xd3, 0x81, 0x80, 0x1f, 0xdf, 0x3d, 0x59, 0xa6, 0xd7, 0x3f, 0x81, 0xfd, 0xa4, 0x49, 0x02, 0x61, 0xaf, 0x9c, 0x4e, 0x27, 0xca, 0xac, 0x69,
/* (2^217)P */ 0xc9, 0x21, 0x07, 0x33, 0xea, 0xa3, 0x7b, 0x04, 0xa0, 0x1e, 0x7e, 0x0e, 0xc2, 0x3f, 0x42, 0x83, 0x60, 0x4a, 0x31, 0x01, 0xaf, 0xc0, 0xf4, 0x1d, 0x27, 0x95, 0x28, 0x89, 0xab, 0x2d, 0xa6, 0x09,
/* (2^218)P */ 0x00, 0xcb, 0xc6, 0x9c, 0xa4, 0x25, 0xb3, 0xa5, 0xb6, 0x6c, 0xb5, 0x54, 0xc6, 0x5d, 0x4b, 0xe9, 0xa0, 0x94, 0xc9, 0xad, 0x79, 0x87, 0xe2, 0x3b, 0xad, 0x4a, 0x3a, 0xba, 0xf8, 0xe8, 0x96, 0x42,
/* (2^219)P */ 0xab, 0x1e, 0x45, 0x1e, 0x76, 0x89, 0x86, 0x32, 0x4a, 0x59, 0x59, 0xff, 0x8b, 0x59, 0x4d, 0x2e, 0x4a, 0x08, 0xa7, 0xd7, 0x53, 0x68, 0xb9, 0x49, 0xa8, 0x20, 0x14, 0x60, 0x19, 0xa3, 0x80, 0x49,
/* (2^220)P */ 0x42, 0x2c, 0x55, 0x2f, 0xe1, 0xb9, 0x65, 0x95, 0x96, 0xfe, 0x00, 0x71, 0xdb, 0x18, 0x53, 0x8a, 0xd7, 0xd0, 0xad, 0x43, 0x4d, 0x0b, 0xc9, 0x05, 0xda, 0x4e, 0x5d, 0x6a, 0xd6, 0x4c, 0x8b, 0x53,
/* (2^221)P */ 0x9f, 0x03, 0x9f, 0xe8, 0xc3, 0x4f, 0xe9, 0xf4, 0x45, 0x80, 0x61, 0x6f, 0xf2, 0x9a, 0x2c, 0x59, 0x50, 0x95, 0x4b, 0xfd, 0xb5, 0x6e, 0xa3, 0x08, 0x19, 0x14, 0xed, 0xc2, 0xf6, 0xfa, 0xff, 0x25,
/* (2^222)P */ 0x54, 0xd3, 0x79, 0xcc, 0x59, 0x44, 0x43, 0x34, 0x6b, 0x47, 0xd5, 0xb1, 0xb4, 0xbf, 0xec, 0xee, 0x99, 0x5d, 0x61, 0x61, 0xa0, 0x34, 0xeb, 0xdd, 0x73, 0xb7, 0x64, 0xeb, 0xcc, 0xce, 0x29, 0x51,
/* (2^223)P */ 0x20, 0x35, 0x99, 0x94, 0x58, 0x21, 0x43, 0xee, 0x3b, 0x0b, 0x4c, 0xf1, 0x7c, 0x9c, 0x2f, 0x77, 0xd5, 0xda, 0xbe, 0x06, 0xe3, 0xfc, 0xe2, 0xd2, 0x97, 0x6a, 0xf0, 0x46, 0xb5, 0x42, 0x5f, 0x71,
/* (2^224)P */ 0x1a, 0x5f, 0x5b, 0xda, 0xce, 0xcd, 0x4e, 0x43, 0xa9, 0x41, 0x97, 0xa4, 0x15, 0x71, 0xa1, 0x0d, 0x2e, 0xad, 0xed, 0x73, 0x7c, 0xd7, 0x0b, 0x68, 0x41, 0x90, 0xdd, 0x4e, 0x35, 0x02, 0x7c, 0x48,
/* (2^225)P */ 0xc4, 0xd9, 0x0e, 0xa7, 0xf3, 0xef, 0xef, 0xb8, 0x02, 0xe3, 0x57, 0xe8, 0xa3, 0x2a, 0xa3, 0x56, 0xa0, 0xa5, 0xa2, 0x48, 0xbd, 0x68, 0x3a, 0xdf, 0x44, 0xc4, 0x76, 0x31, 0xb7, 0x50, 0xf6, 0x07,
/* (2^226)P */ 0xb1, 0xcc, 0xe0, 0x26, 0x16, 0x9b, 0x8b, 0xe3, 0x36, 0xfb, 0x09, 0x8b, 0xc1, 0x53, 0xe0, 0x79, 0x64, 0x49, 0xf9, 0xc9, 0x19, 0x03, 0xd9, 0x56, 0xc4, 0xf5, 0x9f, 0xac, 0xe7, 0x41, 0xa9, 0x1c,
/* (2^227)P */ 0xbb, 0xa0, 0x2f, 0x16, 0x29, 0xdf, 0xc4, 0x49, 0x05, 0x33, 0xb3, 0x82, 0x32, 0xcf, 0x88, 0x84, 0x7d, 0x43, 0xbb, 0xca, 0x14, 0xda, 0xdf, 0x95, 0x86, 0xad, 0xd5, 0x64, 0x82, 0xf7, 0x91, 0x33,
/* (2^228)P */ 0x5d, 0x09, 0xb5, 0xe2, 0x6a, 0xe0, 0x9a, 0x72, 0x46, 0xa9, 0x59, 0x32, 0xd7, 0x58, 0x8a, 0xd5, 0xed, 0x21, 0x39, 0xd1, 0x62, 0x42, 0x83, 0xe9, 0x92, 0xb5, 0x4b, 0xa5, 0xfa, 0xda, 0xfe, 0x27,
/* (2^229)P */ 0xbb, 0x48, 0xad, 0x29, 0xb8, 0xc5, 0x9d, 0xa9, 0x60, 0xe2, 0x9e, 0x49, 0x42, 0x57, 0x02, 0x5f, 0xfd, 0x13, 0x75, 0x5d, 0xcd, 0x8e, 0x2c, 0x80, 0x38, 0xd9, 0x6d, 0x3f, 0xef, 0xb3, 0xce, 0x78,
/* (2^230)P */ 0x94, 0x5d, 0x13, 0x8a, 0x4f, 0xf4, 0x42, 0xc3, 0xa3, 0xdd, 0x8c, 0x82, 0x44, 0xdb, 0x9e, 0x7b, 0xe7, 0xcf, 0x37, 0x05, 0x1a, 0xd1, 0x36, 0x94, 0xc8, 0xb4, 0x1a, 0xec, 0x64, 0xb1, 0x64, 0x50,
/* (2^231)P */ 0xfc, 0xb2, 0x7e, 0xd3, 0xcf, 0xec, 0x20, 0x70, 0xfc, 0x25, 0x0d, 0xd9, 0x3e, 0xea, 0x31, 0x1f, 0x34, 0xbb, 0xa1, 0xdf, 0x7b, 0x0d, 0x93, 0x1b, 0x44, 0x30, 0x11, 0x48, 0x7a, 0x46, 0x44, 0x53,
/* (2^232)P */ 0xfb, 0x6d, 0x5e, 0xf2, 0x70, 0x31, 0x07, 0x70, 0xc8, 0x4c, 0x11, 0x50, 0x1a, 0xdc, 0x85, 0xe3, 0x00, 0x4f, 0xfc, 0xc8, 0x8a, 0x69, 0x48, 0x23, 0xd8, 0x40, 0xdd, 0x84, 0x52, 0xa5, 0x77, 0x2a,
/* (2^233)P */ 0xe4, 0x6c, 0x8c, 0xc9, 0xe0, 0xaf, 0x06, 0xfe, 0xe4, 0xd6, 0xdf, 0xdd, 0x96, 0xdf, 0x35, 0xc2, 0xd3, 0x1e, 0xbf, 0x33, 0x1e, 0xd0, 0x28, 0x14, 0xaf, 0xbd, 0x00, 0x93, 0xec, 0x68, 0x57, 0x78,
/* (2^234)P */ 0x3b, 0xb6, 0xde, 0x91, 0x7a, 0xe5, 0x02, 0x97, 0x80, 0x8b, 0xce, 0xe5, 0xbf, 0xb8, 0xbd, 0x61, 0xac, 0x58, 0x1d, 0x3d, 0x6f, 0x42, 0x5b, 0x64, 0xbc, 0x57, 0xa5, 0x27, 0x22, 0xa8, 0x04, 0x48,
/* (2^235)P */ 0x01, 0x26, 0x4d, 0xb4, 0x8a, 0x04, 0x57, 0x8e, 0x35, 0x69, 0x3a, 0x4b, 0x1a, 0x50, 0xd6, 0x68, 0x93, 0xc2, 0xe1, 0xf9, 0xc3, 0x9e, 0x9c, 0xc3, 0xe2, 0x63, 0xde, 0xd4, 0x57, 0xf2, 0x72, 0x41,
/* (2^236)P */ 0x01, 0x64, 0x0c, 0x33, 0x50, 0xb4, 0x68, 0xd3, 0x91, 0x23, 0x8f, 0x41, 0x17, 0x30, 0x0d, 0x04, 0x0d, 0xd9, 0xb7, 0x90, 0x60, 0xbb, 0x34, 0x2c, 0x1f, 0xd5, 0xdf, 0x8f, 0x22, 0x49, 0xf6, 0x16,
/* (2^237)P */ 0xf5, 0x8e, 0x92, 0x2b, 0x8e, 0x81, 0xa6, 0xbe, 0x72, 0x1e, 0xc1, 0xcd, 0x91, 0xcf, 0x8c, 0xe2, 0xcd, 0x36, 0x7a, 0xe7, 0x68, 0xaa, 0x4a, 0x59, 0x0f, 0xfd, 0x7f, 0x6c, 0x80, 0x34, 0x30, 0x31,
/* (2^238)P */ 0x65, 0xbd, 0x49, 0x22, 0xac, 0x27, 0x9d, 0x8a, 0x12, 0x95, 0x8e, 0x01, 0x64, 0xb4, 0xa3, 0x19, 0xc7, 0x7e, 0xb3, 0x52, 0xf3, 0xcf, 0x6c, 0xc2, 0x21, 0x7b, 0x79, 0x1d, 0x34, 0x68, 0x6f, 0x05,
/* (2^239)P */ 0x27, 0x23, 0xfd, 0x7e, 0x75, 0xd6, 0x79, 0x5e, 0x15, 0xfe, 0x3a, 0x55, 0xb6, 0xbc, 0xbd, 0xfa, 0x60, 0x5a, 0xaf, 0x6e, 0x2c, 0x22, 0xe7, 0xd3, 0x3b, 0x74, 0xae, 0x4d, 0x6d, 0xc7, 0x46, 0x70,
/* (2^240)P */ 0x55, 0x4a, 0x8d, 0xb1, 0x72, 0xe8, 0x0b, 0x66, 0x96, 0x14, 0x4e, 0x57, 0x18, 0x25, 0x99, 0x19, 0xbb, 0xdc, 0x2b, 0x30, 0x3a, 0x05, 0x03, 0xc1, 0x8e, 0x8e, 0x21, 0x0b, 0x80, 0xe9, 0xd8, 0x3e,
/* (2^241)P */ 0x3e, 0xe0, 0x75, 0xfa, 0x39, 0x92, 0x0b, 0x7b, 0x83, 0xc0, 0x33, 0x46, 0x68, 0xfb, 0xe9, 0xef, 0x93, 0x77, 0x1a, 0x39, 0xbe, 0x5f, 0xa3, 0x98, 0x34, 0xfe, 0xd0, 0xe2, 0x0f, 0x51, 0x65, 0x60,
/* (2^242)P */ 0x0c, 0xad, 0xab, 0x48, 0x85, 0x66, 0xcb, 0x55, 0x27, 0xe5, 0x87, 0xda, 0x48, 0x45, 0x58, 0xb4, 0xdd, 0xc1, 0x07, 0x01, 0xea, 0xec, 0x43, 0x2c, 0x35, 0xde, 0x72, 0x93, 0x80, 0x28, 0x60, 0x52,
/* (2^243)P */ 0x1f, 0x3b, 0x21, 0xf9, 0x6a, 0xc5, 0x15, 0x34, 0xdb, 0x98, 0x7e, 0x01, 0x4d, 0x1a, 0xee, 0x5b, 0x9b, 0x70, 0xcf, 0xb5, 0x05, 0xb1, 0xf6, 0x13, 0xb6, 0x9a, 0xb2, 0x82, 0x34, 0x0e, 0xf2, 0x5f,
/* (2^244)P */ 0x90, 0x6c, 0x2e, 0xcc, 0x75, 0x9c, 0xa2, 0x0a, 0x06, 0xe2, 0x70, 0x3a, 0xca, 0x73, 0x7d, 0xfc, 0x15, 0xc5, 0xb5, 0xc4, 0x8f, 0xc3, 0x9f, 0x89, 0x07, 0xc2, 0xff, 0x24, 0xb1, 0x86, 0x03, 0x25,
/* (2^245)P */ 0x56, 0x2b, 0x3d, 0xae, 0xd5, 0x28, 0xea, 0x54, 0xce, 0x60, 0xde, 0xd6, 0x9d, 0x14, 0x13, 0x99, 0xc1, 0xd6, 0x06, 0x8f, 0xc5, 0x4f, 0x69, 0x16, 0xc7, 0x8f, 0x01, 0xeb, 0x75, 0x39, 0xb2, 0x46,
/* (2^246)P */ 0xe2, 0xb4, 0xb7, 0xb4, 0x0f, 0x6a, 0x0a, 0x47, 0xde, 0x53, 0x72, 0x8f, 0x5a, 0x47, 0x92, 0x5d, 0xdb, 0x3a, 0xbd, 0x2f, 0xb5, 0xe5, 0xee, 0xab, 0x68, 0x69, 0x80, 0xa0, 0x01, 0x08, 0xa2, 0x7f,
/* (2^247)P */ 0xd2, 0x14, 0x77, 0x9f, 0xf1, 0xfa, 0xf3, 0x76, 0xc3, 0x60, 0x46, 0x2f, 0xc1, 0x40, 0xe8, 0xb3, 0x4e, 0x74, 0x12, 0xf2, 0x8d, 0xcd, 0xb4, 0x0f, 0xd2, 0x2d, 0x3a, 0x1d, 0x25, 0x5a, 0x06, 0x4b,
/* (2^248)P */ 0x4a, 0xcd, 0x77, 0x3d, 0x38, 0xde, 0xeb, 0x5c, 0xb1, 0x9c, 0x2c, 0x88, 0xdf, 0x39, 0xdf, 0x6a, 0x59, 0xf7, 0x9a, 0xb0, 0x2e, 0x24, 0xdd, 0xa2, 0x22, 0x64, 0x5f, 0x0e, 0xe5, 0xc0, 0x47, 0x31,
/* (2^249)P */ 0xdb, 0x50, 0x13, 0x1d, 0x10, 0xa5, 0x4c, 0x16, 0x62, 0xc9, 0x3f, 0xc3, 0x79, 0x34, 0xd1, 0xf8, 0x08, 0xda, 0xe5, 0x13, 0x4d, 0xce, 0x40, 0xe6, 0xba, 0xf8, 0x61, 0x50, 0xc4, 0xe0, 0xde, 0x4b,
/* (2^250)P */ 0xc9, 0xb1, 0xed, 0xa4, 0xc1, 0x6d, 0xc4, 0xd7, 0x8a, 0xd9, 0x7f, 0x43, 0xb6, 0xd7, 0x14, 0x55, 0x0b, 0xc0, 0xa1, 0xb2, 0x6b, 0x2f, 0x94, 0x58, 0x0e, 0x71, 0x70, 0x1d, 0xab, 0xb2, 0xff, 0x2d,
/* (2^251)P */ 0x68, 0x6d, 0x8b, 0xc1, 0x2f, 0xcf, 0xdf, 0xcc, 0x67, 0x61, 0x80, 0xb7, 0xa8, 0xcb, 0xeb, 0xa8, 0xe3, 0x37, 0x29, 0x5e, 0xf9, 0x97, 0x06, 0x98, 0x8c, 0x6e, 0x12, 0xd0, 0x1c, 0xba, 0xfb, 0x02,
/* (2^252)P */ 0x65, 0x45, 0xff, 0xad, 0x60, 0xc3, 0x98, 0xcb, 0x19, 0x15, 0xdb, 0x4b, 0xd2, 0x01, 0x71, 0x44, 0xd5, 0x15, 0xfb, 0x75, 0x74, 0xc8, 0xc4, 0x98, 0x7d, 0xa2, 0x22, 0x6e, 0x6d, 0xc7, 0xf8, 0x05,
/* (2^253)P */ 0x94, 0xf4, 0xb9, 0xfe, 0xdf, 0xe5, 0x69, 0xab, 0x75, 0x6b, 0x40, 0x18, 0x9d, 0xc7, 0x09, 0xae, 0x1d, 0x2d, 0xa4, 0x94, 0xfb, 0x45, 0x9b, 0x19, 0x84, 0xfa, 0x2a, 0xae, 0xeb, 0x0a, 0x71, 0x79,
/* (2^254)P */ 0xdf, 0xd2, 0x34, 0xf3, 0xa7, 0xed, 0xad, 0xa6, 0xb4, 0x57, 0x2a, 0xaf, 0x51, 0x9c, 0xde, 0x7b, 0xa8, 0xea, 0xdc, 0x86, 0x4f, 0xc6, 0x8f, 0xa9, 0x7b, 0xd0, 0x0e, 0xc2, 0x35, 0x03, 0xbe, 0x6b,
/* (2^255)P */ 0x44, 0x43, 0x98, 0x53, 0xbe, 0xdc, 0x7f, 0x66, 0xa8, 0x49, 0x59, 0x00, 0x1c, 0xbc, 0x72, 0x07, 0x8e, 0xd6, 0xbe, 0x4e, 0x9f, 0xa4, 0x07, 0xba, 0xbf, 0x30, 0xdf, 0xba, 0x85, 0xb0, 0xa7, 0x1f,
}
+104
View File
@@ -0,0 +1,104 @@
package x448
import (
fp "github.com/cloudflare/circl/math/fp448"
)
// ladderJoye calculates a fixed-point multiplication with the generator point.
// The algorithm is the right-to-left Joye's ladder as described
// in "How to precompute a ladder" in SAC'2017.
func ladderJoye(k *Key) {
w := [5]fp.Elt{} // [mu,x1,z1,x2,z2] order must be preserved.
w[1] = fp.Elt{ // x1 = S
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}
fp.SetOne(&w[2]) // z1 = 1
w[3] = fp.Elt{ // x2 = G-S
0x20, 0x27, 0x9d, 0xc9, 0x7d, 0x19, 0xb1, 0xac,
0xf8, 0xba, 0x69, 0x1c, 0xff, 0x33, 0xac, 0x23,
0x51, 0x1b, 0xce, 0x3a, 0x64, 0x65, 0xbd, 0xf1,
0x23, 0xf8, 0xc1, 0x84, 0x9d, 0x45, 0x54, 0x29,
0x67, 0xb9, 0x81, 0x1c, 0x03, 0xd1, 0xcd, 0xda,
0x7b, 0xeb, 0xff, 0x1a, 0x88, 0x03, 0xcf, 0x3a,
0x42, 0x44, 0x32, 0x01, 0x25, 0xb7, 0xfa, 0xf0,
}
fp.SetOne(&w[4]) // z2 = 1
const n = 448
const h = 2
swap := uint(1)
for s := 0; s < n-h; s++ {
i := (s + h) / 8
j := (s + h) % 8
bit := uint((k[i] >> uint(j)) & 1)
copy(w[0][:], tableGenerator[s*Size:(s+1)*Size])
diffAdd(&w, swap^bit)
swap = bit
}
for s := 0; s < h; s++ {
double(&w[1], &w[2])
}
toAffine((*[fp.Size]byte)(k), &w[1], &w[2])
}
// ladderMontgomery calculates a generic scalar point multiplication
// The algorithm implemented is the left-to-right Montgomery's ladder.
func ladderMontgomery(k, xP *Key) {
w := [5]fp.Elt{} // [x1, x2, z2, x3, z3] order must be preserved.
w[0] = *(*fp.Elt)(xP) // x1 = xP
fp.SetOne(&w[1]) // x2 = 1
w[3] = *(*fp.Elt)(xP) // x3 = xP
fp.SetOne(&w[4]) // z3 = 1
move := uint(0)
for s := 448 - 1; s >= 0; s-- {
i := s / 8
j := s % 8
bit := uint((k[i] >> uint(j)) & 1)
ladderStep(&w, move^bit)
move = bit
}
toAffine((*[fp.Size]byte)(k), &w[1], &w[2])
}
func toAffine(k *[fp.Size]byte, x, z *fp.Elt) {
fp.Inv(z, z)
fp.Mul(x, x, z)
_ = fp.ToBytes(k[:], x)
}
var lowOrderPoints = [3]fp.Elt{
{ /* (0,_,1) point of order 2 on Curve448 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
{ /* (1,_,1) a point of order 4 on the twist of Curve448 */
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
{ /* (-1,_,1) point of order 4 on Curve448 */
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
},
}
+30
View File
@@ -0,0 +1,30 @@
//go:build amd64 && !purego
// +build amd64,!purego
package x448
import (
fp "github.com/cloudflare/circl/math/fp448"
"golang.org/x/sys/cpu"
)
var hasBmi2Adx = cpu.X86.HasBMI2 && cpu.X86.HasADX
var _ = hasBmi2Adx
func double(x, z *fp.Elt) { doubleAmd64(x, z) }
func diffAdd(w *[5]fp.Elt, b uint) { diffAddAmd64(w, b) }
func ladderStep(w *[5]fp.Elt, b uint) { ladderStepAmd64(w, b) }
func mulA24(z, x *fp.Elt) { mulA24Amd64(z, x) }
//go:noescape
func doubleAmd64(x, z *fp.Elt)
//go:noescape
func diffAddAmd64(w *[5]fp.Elt, b uint)
//go:noescape
func ladderStepAmd64(w *[5]fp.Elt, b uint)
//go:noescape
func mulA24Amd64(z, x *fp.Elt)
+111
View File
@@ -0,0 +1,111 @@
#define ladderStepLeg \
addSub(x2,z2) \
addSub(x3,z3) \
integerMulLeg(b0,x2,z3) \
integerMulLeg(b1,x3,z2) \
reduceFromDoubleLeg(t0,b0) \
reduceFromDoubleLeg(t1,b1) \
addSub(t0,t1) \
cselect(x2,x3,regMove) \
cselect(z2,z3,regMove) \
integerSqrLeg(b0,t0) \
integerSqrLeg(b1,t1) \
reduceFromDoubleLeg(x3,b0) \
reduceFromDoubleLeg(z3,b1) \
integerMulLeg(b0,x1,z3) \
reduceFromDoubleLeg(z3,b0) \
integerSqrLeg(b0,x2) \
integerSqrLeg(b1,z2) \
reduceFromDoubleLeg(x2,b0) \
reduceFromDoubleLeg(z2,b1) \
subtraction(t0,x2,z2) \
multiplyA24Leg(t1,t0) \
additionLeg(t1,t1,z2) \
integerMulLeg(b0,x2,z2) \
integerMulLeg(b1,t0,t1) \
reduceFromDoubleLeg(x2,b0) \
reduceFromDoubleLeg(z2,b1)
#define ladderStepBmi2Adx \
addSub(x2,z2) \
addSub(x3,z3) \
integerMulAdx(b0,x2,z3) \
integerMulAdx(b1,x3,z2) \
reduceFromDoubleAdx(t0,b0) \
reduceFromDoubleAdx(t1,b1) \
addSub(t0,t1) \
cselect(x2,x3,regMove) \
cselect(z2,z3,regMove) \
integerSqrAdx(b0,t0) \
integerSqrAdx(b1,t1) \
reduceFromDoubleAdx(x3,b0) \
reduceFromDoubleAdx(z3,b1) \
integerMulAdx(b0,x1,z3) \
reduceFromDoubleAdx(z3,b0) \
integerSqrAdx(b0,x2) \
integerSqrAdx(b1,z2) \
reduceFromDoubleAdx(x2,b0) \
reduceFromDoubleAdx(z2,b1) \
subtraction(t0,x2,z2) \
multiplyA24Adx(t1,t0) \
additionAdx(t1,t1,z2) \
integerMulAdx(b0,x2,z2) \
integerMulAdx(b1,t0,t1) \
reduceFromDoubleAdx(x2,b0) \
reduceFromDoubleAdx(z2,b1)
#define difAddLeg \
addSub(x1,z1) \
integerMulLeg(b0,z1,ui) \
reduceFromDoubleLeg(z1,b0) \
addSub(x1,z1) \
integerSqrLeg(b0,x1) \
integerSqrLeg(b1,z1) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1) \
integerMulLeg(b0,x1,z2) \
integerMulLeg(b1,z1,x2) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1)
#define difAddBmi2Adx \
addSub(x1,z1) \
integerMulAdx(b0,z1,ui) \
reduceFromDoubleAdx(z1,b0) \
addSub(x1,z1) \
integerSqrAdx(b0,x1) \
integerSqrAdx(b1,z1) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1) \
integerMulAdx(b0,x1,z2) \
integerMulAdx(b1,z1,x2) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1)
#define doubleLeg \
addSub(x1,z1) \
integerSqrLeg(b0,x1) \
integerSqrLeg(b1,z1) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1) \
subtraction(t0,x1,z1) \
multiplyA24Leg(t1,t0) \
additionLeg(t1,t1,z1) \
integerMulLeg(b0,x1,z1) \
integerMulLeg(b1,t0,t1) \
reduceFromDoubleLeg(x1,b0) \
reduceFromDoubleLeg(z1,b1)
#define doubleBmi2Adx \
addSub(x1,z1) \
integerSqrAdx(b0,x1) \
integerSqrAdx(b1,z1) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1) \
subtraction(t0,x1,z1) \
multiplyA24Adx(t1,t0) \
additionAdx(t1,t1,z1) \
integerMulAdx(b0,x1,z1) \
integerMulAdx(b1,t0,t1) \
reduceFromDoubleAdx(x1,b0) \
reduceFromDoubleAdx(z1,b1)
+193
View File
@@ -0,0 +1,193 @@
// +build amd64
#include "textflag.h"
// Depends on circl/math/fp448 package
#include "../../math/fp448/fp_amd64.h"
#include "curve_amd64.h"
// CTE_A24 is (A+2)/4 from Curve448
#define CTE_A24 39082
#define Size 56
// multiplyA24Leg multiplies x times CTE_A24 and stores in z
// Uses: AX, DX, R8-R15, FLAGS
// Instr: x86_64, cmov, adx
#define multiplyA24Leg(z,x) \
MOVQ $CTE_A24, R15; \
MOVQ 0+x, AX; MULQ R15; MOVQ AX, R8; ;;;;;;;;;;;; MOVQ DX, R9; \
MOVQ 8+x, AX; MULQ R15; ADDQ AX, R9; ADCQ $0, DX; MOVQ DX, R10; \
MOVQ 16+x, AX; MULQ R15; ADDQ AX, R10; ADCQ $0, DX; MOVQ DX, R11; \
MOVQ 24+x, AX; MULQ R15; ADDQ AX, R11; ADCQ $0, DX; MOVQ DX, R12; \
MOVQ 32+x, AX; MULQ R15; ADDQ AX, R12; ADCQ $0, DX; MOVQ DX, R13; \
MOVQ 40+x, AX; MULQ R15; ADDQ AX, R13; ADCQ $0, DX; MOVQ DX, R14; \
MOVQ 48+x, AX; MULQ R15; ADDQ AX, R14; ADCQ $0, DX; \
MOVQ DX, AX; \
SHLQ $32, AX; \
ADDQ DX, R8; MOVQ $0, DX; \
ADCQ $0, R9; \
ADCQ $0, R10; \
ADCQ AX, R11; \
ADCQ $0, R12; \
ADCQ $0, R13; \
ADCQ $0, R14; \
ADCQ $0, DX; \
MOVQ DX, AX; \
SHLQ $32, AX; \
ADDQ DX, R8; \
ADCQ $0, R9; \
ADCQ $0, R10; \
ADCQ AX, R11; \
ADCQ $0, R12; \
ADCQ $0, R13; \
ADCQ $0, R14; \
MOVQ R8, 0+z; \
MOVQ R9, 8+z; \
MOVQ R10, 16+z; \
MOVQ R11, 24+z; \
MOVQ R12, 32+z; \
MOVQ R13, 40+z; \
MOVQ R14, 48+z;
// multiplyA24Adx multiplies x times CTE_A24 and stores in z
// Uses: AX, DX, R8-R14, FLAGS
// Instr: x86_64, bmi2
#define multiplyA24Adx(z,x) \
MOVQ $CTE_A24, DX; \
MULXQ 0+x, R8, R9; \
MULXQ 8+x, AX, R10; ADDQ AX, R9; \
MULXQ 16+x, AX, R11; ADCQ AX, R10; \
MULXQ 24+x, AX, R12; ADCQ AX, R11; \
MULXQ 32+x, AX, R13; ADCQ AX, R12; \
MULXQ 40+x, AX, R14; ADCQ AX, R13; \
MULXQ 48+x, AX, DX; ADCQ AX, R14; \
;;;;;;;;;;;;;;;;;;;; ADCQ $0, DX; \
MOVQ DX, AX; \
SHLQ $32, AX; \
ADDQ DX, R8; MOVQ $0, DX; \
ADCQ $0, R9; \
ADCQ $0, R10; \
ADCQ AX, R11; \
ADCQ $0, R12; \
ADCQ $0, R13; \
ADCQ $0, R14; \
ADCQ $0, DX; \
MOVQ DX, AX; \
SHLQ $32, AX; \
ADDQ DX, R8; \
ADCQ $0, R9; \
ADCQ $0, R10; \
ADCQ AX, R11; \
ADCQ $0, R12; \
ADCQ $0, R13; \
ADCQ $0, R14; \
MOVQ R8, 0+z; \
MOVQ R9, 8+z; \
MOVQ R10, 16+z; \
MOVQ R11, 24+z; \
MOVQ R12, 32+z; \
MOVQ R13, 40+z; \
MOVQ R14, 48+z;
#define mulA24Legacy \
multiplyA24Leg(0(DI),0(SI))
#define mulA24Bmi2Adx \
multiplyA24Adx(0(DI),0(SI))
// func mulA24Amd64(z, x *fp448.Elt)
TEXT ·mulA24Amd64(SB),NOSPLIT,$0-16
MOVQ z+0(FP), DI
MOVQ x+8(FP), SI
CHECK_BMI2ADX(LMA24, mulA24Legacy, mulA24Bmi2Adx)
// func ladderStepAmd64(w *[5]fp448.Elt, b uint)
// ladderStepAmd64 calculates a point addition and doubling as follows:
// (x2,z2) = 2*(x2,z2) and (x3,z3) = (x2,z2)+(x3,z3) using as a difference (x1,-).
// w = {x1,x2,z2,x3,z4} are five fp255.Elt of 56 bytes.
// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and
// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes.
TEXT ·ladderStepAmd64(SB),NOSPLIT,$336-16
// Parameters
#define regWork DI
#define regMove SI
#define x1 0*Size(regWork)
#define x2 1*Size(regWork)
#define z2 2*Size(regWork)
#define x3 3*Size(regWork)
#define z3 4*Size(regWork)
// Local variables
#define t0 0*Size(SP)
#define t1 1*Size(SP)
#define b0 2*Size(SP)
#define b1 4*Size(SP)
MOVQ w+0(FP), regWork
MOVQ b+8(FP), regMove
CHECK_BMI2ADX(LLADSTEP, ladderStepLeg, ladderStepBmi2Adx)
#undef regWork
#undef regMove
#undef x1
#undef x2
#undef z2
#undef x3
#undef z3
#undef t0
#undef t1
#undef b0
#undef b1
// func diffAddAmd64(work *[5]fp.Elt, swap uint)
// diffAddAmd64 calculates a differential point addition using a precomputed point.
// (x1,z1) = (x1,z1)+(mu) using a difference point (x2,z2)
// work = {mu,x1,z1,x2,z2} are five fp448.Elt of 56 bytes, and
// stack = (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes.
// This is Equation 7 at https://eprint.iacr.org/2017/264.
TEXT ·diffAddAmd64(SB),NOSPLIT,$224-16
// Parameters
#define regWork DI
#define regSwap SI
#define ui 0*Size(regWork)
#define x1 1*Size(regWork)
#define z1 2*Size(regWork)
#define x2 3*Size(regWork)
#define z2 4*Size(regWork)
// Local variables
#define b0 0*Size(SP)
#define b1 2*Size(SP)
MOVQ w+0(FP), regWork
MOVQ b+8(FP), regSwap
cswap(x1,x2,regSwap)
cswap(z1,z2,regSwap)
CHECK_BMI2ADX(LDIFADD, difAddLeg, difAddBmi2Adx)
#undef regWork
#undef regSwap
#undef ui
#undef x1
#undef z1
#undef x2
#undef z2
#undef b0
#undef b1
// func doubleAmd64(x, z *fp448.Elt)
// doubleAmd64 calculates a point doubling (x1,z1) = 2*(x1,z1).
// stack = (t0,t1) are two fp.Elt of fp.Size bytes, and
// (b0,b1) are two-double precision fp.Elt of 2*fp.Size bytes.
TEXT ·doubleAmd64(SB),NOSPLIT,$336-16
// Parameters
#define x1 0(DI)
#define z1 0(SI)
// Local variables
#define t0 0*Size(SP)
#define t1 1*Size(SP)
#define b0 2*Size(SP)
#define b1 4*Size(SP)
MOVQ x+0(FP), DI
MOVQ z+8(FP), SI
CHECK_BMI2ADX(LDOUB,doubleLeg,doubleBmi2Adx)
#undef x1
#undef z1
#undef t0
#undef t1
#undef b0
#undef b1

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