diff --git a/.github/workflows/update-cloudflare-proxies.yml b/.github/workflows/update-cloudflare-proxies.yml
index 9f9bb8e6..6ed0e573 100644
--- a/.github/workflows/update-cloudflare-proxies.yml
+++ b/.github/workflows/update-cloudflare-proxies.yml
@@ -4,9 +4,10 @@ on:
schedule:
- cron: '0 0 1 * *' # runs at 00:00 UTC on the 1st day of every month
workflow_dispatch:
-
-env:
- IP_MERGED_FILE: Common/cloudflare-ips.txt
+ push:
+ paths:
+ - '.github/workflows/update-cloudflare-proxies.yml'
+ - 'Common/CloudflareIPs.targets'
jobs:
update-proxies:
@@ -17,23 +18,18 @@ jobs:
with:
ref: ${{ github.ref }}
- - name: Fetch Cloudflare IPs and Update Files
- env:
- IPV4_URL: https://www.cloudflare.com/ips-v4
- IPV6_URL: https://www.cloudflare.com/ips-v6
- run: |
- set -euo pipefail
+ - uses: actions/setup-dotnet@v5
+ with:
+ global-json-file: global.json
- echo "Fetching Cloudflare IP lists and merging"
- curl -s $IPV4_URL > $IP_MERGED_FILE
- echo "" >> $IP_MERGED_FILE
- curl -s $IPV6_URL >> $IP_MERGED_FILE
+ - name: Regenerate Cloudflare IPs source
+ run: dotnet build Common/Common.csproj -p:UpdateCloudflareIPs=true
- name: Commit and Push Changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git add ${{ env.IP_MERGED_FILE }}
+ git add Common/Utils/CloudflareNetworks.g.cs
if git diff --cached --quiet; then
echo "No changes detected."
diff --git a/Common/CloudflareIPs.targets b/Common/CloudflareIPs.targets
new file mode 100644
index 00000000..afd006a6
--- /dev/null
+++ b/Common/CloudflareIPs.targets
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+ ");
+ sb.AppendLine("// DO NOT EDIT - manual changes are overwritten on the next regeneration.");
+ sb.AppendLine("// ");
+ sb.AppendLine("// Cloudflare public proxy IP ranges (https://www.cloudflare.com/ips), baked in as a startup fallback for TrustedProxiesFetcher when the live fetch at application start fails or times out.");
+ sb.AppendLine("// Regenerated by the FetchCloudflareIPs target in Common.csproj, gated on -p:UpdateCloudflareIPs=true. The Update Cloudflare Proxies GitHub workflow is the only caller that passes that flag.");
+ sb.AppendLine("// ");
+ sb.AppendLine($"// Generated: {timestamp}");
+ sb.AppendLine($"// IPv4 SHA256: {v4Hash}");
+ sb.AppendLine($"// IPv6 SHA256: {v6Hash}");
+ sb.AppendLine("using System.Net;");
+ sb.AppendLine();
+ sb.AppendLine("namespace OpenShock.Common.Utils;");
+ sb.AppendLine();
+ sb.AppendLine("public static partial class TrustedProxiesFetcher");
+ sb.AppendLine("{");
+ sb.AppendLine(" private static readonly IPNetwork[] CloudflareNetworks =");
+ sb.AppendLine(" [");
+ foreach (var line in lines)
+ {
+ var slash = line.IndexOf('/');
+ if (slash < 0)
+ throw new System.FormatException($"Cloudflare IP entry '{line}' is missing the '/prefix' suffix.");
+
+ var addressPart = line.Substring(0, slash);
+ var prefixPart = line.Substring(slash + 1);
+
+ if (!int.TryParse(prefixPart, out var prefix))
+ throw new System.FormatException($"Cloudflare IP entry '{line}' has a non-integer prefix '{prefixPart}'.");
+
+ var bytes = System.Net.IPAddress.Parse(addressPart).GetAddressBytes();
+ var inv = System.Globalization.CultureInfo.InvariantCulture;
+
+ string address;
+ if (bytes.Length == 4)
+ {
+ // IPAddress(long) packs octet 0 in the low byte, octet 3 in the high byte.
+ long value = (long)bytes[0]
+ | ((long)bytes[1] << 8)
+ | ((long)bytes[2] << 16)
+ | ((long)bytes[3] << 24);
+ address = $"new IPAddress(0x{value.ToString("x8", inv)}L)";
+ }
+ else
+ {
+ // IPAddress(ReadOnlySpan) — 16 bytes as hex pairs for readability.
+ var byteList = string.Join(", ", bytes.Select(b => "0x" + b.ToString("x2", inv)));
+ address = $"new IPAddress([{byteList}])";
+ }
+
+ sb.AppendLine($" // {line}");
+ sb.AppendLine($" new IPNetwork({address}, prefixLength: {prefix}),");
+ sb.AppendLine();
+ }
+ sb.AppendLine(" ];");
+ sb.AppendLine("}");
+
+ File.WriteAllText(OutputFile, sb.ToString());
+ }
+ ]]>
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/Common.csproj b/Common/Common.csproj
index 8c6c3174..a1eb241f 100644
--- a/Common/Common.csproj
+++ b/Common/Common.csproj
@@ -35,12 +35,7 @@
-
-
-
- Never
-
-
+
diff --git a/Common/Utils/CloudflareNetworks.g.cs b/Common/Utils/CloudflareNetworks.g.cs
new file mode 100644
index 00000000..5682c2a0
--- /dev/null
+++ b/Common/Utils/CloudflareNetworks.g.cs
@@ -0,0 +1,85 @@
+//
+// DO NOT EDIT - manual changes are overwritten on the next regeneration.
+//
+// Cloudflare public proxy IP ranges (https://www.cloudflare.com/ips), baked in as a startup fallback for TrustedProxiesFetcher when the live fetch at application start fails or times out.
+// Regenerated by the FetchCloudflareIPs target in Common.csproj, gated on -p:UpdateCloudflareIPs=true. The Update Cloudflare Proxies GitHub workflow is the only caller that passes that flag.
+//
+// Generated: 2026-04-24T17:08:34Z
+// IPv4 SHA256: f02c6d83bc01ab0ae8577160e036d700c7455359bce054df884e5d7d9e4e9e7b
+// IPv6 SHA256: 9e9d39e3e83bad00c4decafd53c63fa62029f3d95db68de937d2be28234ca0a9
+using System.Net;
+
+namespace OpenShock.Common.Utils;
+
+public static partial class TrustedProxiesFetcher
+{
+ private static readonly IPNetwork[] CloudflareNetworks =
+ [
+ // 173.245.48.0/20
+ new IPNetwork(new IPAddress(0x0030f5adL), prefixLength: 20),
+
+ // 103.21.244.0/22
+ new IPNetwork(new IPAddress(0x00f41567L), prefixLength: 22),
+
+ // 103.22.200.0/22
+ new IPNetwork(new IPAddress(0x00c81667L), prefixLength: 22),
+
+ // 103.31.4.0/22
+ new IPNetwork(new IPAddress(0x00041f67L), prefixLength: 22),
+
+ // 141.101.64.0/18
+ new IPNetwork(new IPAddress(0x0040658dL), prefixLength: 18),
+
+ // 108.162.192.0/18
+ new IPNetwork(new IPAddress(0x00c0a26cL), prefixLength: 18),
+
+ // 190.93.240.0/20
+ new IPNetwork(new IPAddress(0x00f05dbeL), prefixLength: 20),
+
+ // 188.114.96.0/20
+ new IPNetwork(new IPAddress(0x006072bcL), prefixLength: 20),
+
+ // 197.234.240.0/22
+ new IPNetwork(new IPAddress(0x00f0eac5L), prefixLength: 22),
+
+ // 198.41.128.0/17
+ new IPNetwork(new IPAddress(0x008029c6L), prefixLength: 17),
+
+ // 162.158.0.0/15
+ new IPNetwork(new IPAddress(0x00009ea2L), prefixLength: 15),
+
+ // 104.16.0.0/13
+ new IPNetwork(new IPAddress(0x00001068L), prefixLength: 13),
+
+ // 104.24.0.0/14
+ new IPNetwork(new IPAddress(0x00001868L), prefixLength: 14),
+
+ // 172.64.0.0/13
+ new IPNetwork(new IPAddress(0x000040acL), prefixLength: 13),
+
+ // 131.0.72.0/22
+ new IPNetwork(new IPAddress(0x00480083L), prefixLength: 22),
+
+ // 2400:cb00::/32
+ new IPNetwork(new IPAddress([0x24, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32),
+
+ // 2606:4700::/32
+ new IPNetwork(new IPAddress([0x26, 0x06, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32),
+
+ // 2803:f800::/32
+ new IPNetwork(new IPAddress([0x28, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32),
+
+ // 2405:b500::/32
+ new IPNetwork(new IPAddress([0x24, 0x05, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32),
+
+ // 2405:8100::/32
+ new IPNetwork(new IPAddress([0x24, 0x05, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32),
+
+ // 2a06:98c0::/29
+ new IPNetwork(new IPAddress([0x2a, 0x06, 0x98, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 29),
+
+ // 2c0f:f248::/32
+ new IPNetwork(new IPAddress([0x2c, 0x0f, 0xf2, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), prefixLength: 32),
+
+ ];
+}
diff --git a/Common/Utils/TrustedProxiesFetcher.cs b/Common/Utils/TrustedProxiesFetcher.cs
index 1a3142e9..bc1bc087 100644
--- a/Common/Utils/TrustedProxiesFetcher.cs
+++ b/Common/Utils/TrustedProxiesFetcher.cs
@@ -2,7 +2,7 @@
namespace OpenShock.Common.Utils;
-public static class TrustedProxiesFetcher
+public static partial class TrustedProxiesFetcher
{
private static readonly HttpClient Client = new();
@@ -77,14 +77,7 @@ public static async Task GetTrustedNetworksAsync(bool fetch = true)
cfProxies = await FetchCloudflareIPs();
}
- if (cfProxies is null)
- {
- var assembly = typeof(TrustedProxiesFetcher).Assembly;
- var resourceName = assembly.GetName().Name + ".cloudflare-ips.txt";
- await using var stream = assembly.GetManifestResourceStream(resourceName) ?? throw new NullReferenceException("Could not open embedded cloudflare-ips.txt file");
- using var reader = new StreamReader(stream);
- cfProxies = ParseNetworks(await reader.ReadToEndAsync());
- }
+ cfProxies ??= CloudflareNetworks;
return [.. PrivateNetworksParsed, .. cfProxies];
}
diff --git a/Common/cloudflare-ips.txt b/Common/cloudflare-ips.txt
deleted file mode 100644
index fd160bd4..00000000
--- a/Common/cloudflare-ips.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-173.245.48.0/20
-103.21.244.0/22
-103.22.200.0/22
-103.31.4.0/22
-141.101.64.0/18
-108.162.192.0/18
-190.93.240.0/20
-188.114.96.0/20
-197.234.240.0/22
-198.41.128.0/17
-162.158.0.0/15
-104.16.0.0/13
-104.24.0.0/14
-172.64.0.0/13
-131.0.72.0/22
-2400:cb00::/32
-2606:4700::/32
-2803:f800::/32
-2405:b500::/32
-2405:8100::/32
-2a06:98c0::/29
-2c0f:f248::/32
\ No newline at end of file
diff --git a/global.json b/global.json
index 2e206e82..7cceb992 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "10.0.0",
+ "version": "10.0.100",
"rollForward": "latestMinor",
"allowPrerelease": false
},