fix: validate IP addresses from Cloudflare trace endpoint #12

Merged
leon merged 2 commits from cloudy/New-CFUpdater:fix/validate-ip-address into main 2026-03-10 20:44:50 +01:00
Collaborator

Summary

This PR fixes issue #11 by adding validation for IP addresses returned from Cloudflare's /cdn-cgi/trace endpoint.

Problem

Cloudflare has a bug where they may return their own IPs from the trace endpoint instead of the client's actual IP. This causes the updater to set DNS records to Cloudflare's IPs, rendering the target unreachable.

Solution

Added comprehensive IP validation in externalip/utils.go:

  1. Address family validation: Verify that IPv4 endpoint returns IPv4 and IPv6 endpoint returns IPv6
  2. Private/reserved address check: Reject loopback, link-local, and private addresses
  3. Cloudflare IP range check: Reject IPs within Cloudflare's documented IP ranges

Key Changes

  • validateIPv4() / validateIPv6(): Validate returned IP addresses
  • isPublicIP(): Check if IP is public (not private/reserved)
  • isCloudflareIPv4() / isCloudflareIPv6(): Check against Cloudflare's IP ranges
  • Includes special handling for Cloudflare DNS resolver IPs (1.1.1.1, 1.0.0.1) which are not in standard CIDR ranges

Testing

Added comprehensive unit tests covering:

  • Valid public IPs (pass)
  • Invalid format (fail)
  • Wrong address family (fail)
  • Private/reserved addresses (fail)
  • Cloudflare IPs including 1.1.1.1, 2606:4700::/32, etc. (fail)

Fixes #11

## Summary This PR fixes issue #11 by adding validation for IP addresses returned from Cloudflare's `/cdn-cgi/trace` endpoint. ## Problem Cloudflare has a bug where they may return their own IPs from the trace endpoint instead of the client's actual IP. This causes the updater to set DNS records to Cloudflare's IPs, rendering the target unreachable. ## Solution Added comprehensive IP validation in `externalip/utils.go`: 1. **Address family validation**: Verify that IPv4 endpoint returns IPv4 and IPv6 endpoint returns IPv6 2. **Private/reserved address check**: Reject loopback, link-local, and private addresses 3. **Cloudflare IP range check**: Reject IPs within Cloudflare's documented IP ranges ### Key Changes - `validateIPv4()` / `validateIPv6()`: Validate returned IP addresses - `isPublicIP()`: Check if IP is public (not private/reserved) - `isCloudflareIPv4()` / `isCloudflareIPv6()`: Check against Cloudflare's IP ranges - Includes special handling for Cloudflare DNS resolver IPs (1.1.1.1, 1.0.0.1) which are not in standard CIDR ranges ### Testing Added comprehensive unit tests covering: - Valid public IPs (pass) - Invalid format (fail) - Wrong address family (fail) - Private/reserved addresses (fail) - Cloudflare IPs including 1.1.1.1, 2606:4700::/32, etc. (fail) Fixes #11
This fix addresses issue #11 where Cloudflare's buggy /cdn-cgi/trace endpoint
may return Cloudflare's own IPs instead of the client's actual IP address.

Changes:
- Add validateIPv4() and validateIPv6() functions in utils.go
- Check that returned IP matches expected address family (IPv4/IPv6)
- Verify IP is not private/reserved (loopback, link-local, private ranges)
- Check IP is not within Cloudflare's IP ranges (including 1.1.1.1, 1.0.0.1)
- Add comprehensive unit tests for all validation functions

If an invalid IP is detected, the functions return an error instead of
silently updating DNS records with wrong addresses.
Owner

@cloudy Nice work! But can you change isCloudflareIPv4 and isCloudflareIPv6 to use a for-loop instead of writing all IPs in there manually? Would be nicer to maintain in the future. Please add a new function that is called only during startup, which fetches Cloudflares current IP lists (https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6) and stores them in global string arrays, which the isCloudIPv4/6 can iterate over.

@cloudy Nice work! But can you change `isCloudflareIPv4` and `isCloudflareIPv6` to use a for-loop instead of writing all IPs in there manually? Would be nicer to maintain in the future. Please add a new function that is called only during startup, which fetches Cloudflares current IP lists (https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6) and stores them in global string arrays, which the isCloudIPv4/6 can iterate over.
Author
Collaborator

@leon Got it! I'll refactor the code to:

  1. Add a startup function that fetches Cloudflare's current IP lists from https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6
  2. Store them in global string arrays
  3. Refactor isCloudflareIPv4 and isCloudflareIPv6 to iterate over these arrays instead of hardcoding all IPs

Starting work on this now.

@leon Got it! I'll refactor the code to: 1. Add a startup function that fetches Cloudflare's current IP lists from `https://www.cloudflare.com/ips-v4` and `https://www.cloudflare.com/ips-v6` 2. Store them in global string arrays 3. Refactor `isCloudflareIPv4` and `isCloudflareIPv6` to iterate over these arrays instead of hardcoding all IPs Starting work on this now.
- Add cloudflare_ips.go with FetchCloudflareIPRanges() startup function
- Fetch IPv4 ranges from https://www.cloudflare.com/ips-v4
- Fetch IPv6 ranges from https://www.cloudflare.com/ips-v6
- Store ranges in global slices (CloudflareIPRangesV4, CloudflareIPRangesV6)
- Refactor isCloudflareIPv4/IPv6 to iterate over fetched ranges
- Call FetchCloudflareIPRanges() in main.go during startup
- Add TestMain to load IP ranges before running tests
Author
Collaborator

Done! Refactored the code:

New file: cloudflare_ips.go

  • FetchCloudflareIPRanges() - fetches IP lists at startup
  • Stores ranges in CloudflareIPRangesV4 and CloudflareIPRangesV6 slices
  • Thread-safe with mutex protection

Updated: utils.go

  • isCloudflareIPv4() and isCloudflareIPv6() now iterate over the fetched ranges
  • Much cleaner, no more hardcoded IP lists

Updated: main.go

  • Calls FetchCloudflareIPRanges() during startup

Updated: utils_test.go

  • Added TestMain to load IP ranges before running tests

All tests pass. The IP lists are now fetched dynamically from Cloudflare's official endpoints, making future maintenance much easier.

✅ Done! Refactored the code: **New file:** `cloudflare_ips.go` - `FetchCloudflareIPRanges()` - fetches IP lists at startup - Stores ranges in `CloudflareIPRangesV4` and `CloudflareIPRangesV6` slices - Thread-safe with mutex protection **Updated:** `utils.go` - `isCloudflareIPv4()` and `isCloudflareIPv6()` now iterate over the fetched ranges - Much cleaner, no more hardcoded IP lists **Updated:** `main.go` - Calls `FetchCloudflareIPRanges()` during startup **Updated:** `utils_test.go` - Added `TestMain` to load IP ranges before running tests All tests pass. The IP lists are now fetched dynamically from Cloudflare's official endpoints, making future maintenance much easier.
leon requested review from leon 2026-03-10 19:46:30 +01:00
leon merged commit 0ef204cf0c into main 2026-03-10 20:44:50 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
leon/New-CFUpdater!12
No description provided.