Infrastructure/Http adds the client the roadmap's S3 and S4 ask for: - the connect callback resolves the name itself and refuses loopback, private, link-local, CGNAT, documentation, multicast, ULA, NAT64, 6to4, Teredo and IPv4-mapped/compatible forms, then connects to the vetted address, so DNS rebinding cannot swap it afterwards; - redirects are followed by hand, at most three, each one re-checked; - bodies are capped at 1 MB after decompression, only JSON media types are read, every request has a 15 s budget, and a refused URL is not asked again for five minutes. Actor and WebFinger fetches and inbox deliveries all use it. Test networks can switch on Federation:AllowPrivateNetworks/AllowPlainHttp; startup refuses both in Production. PrivaPub.Tests (xUnit v3) starts with the address table and the fetcher's limits against an in-process peer; build.yml and deploy.yml run it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
56 lines
1.5 KiB
C#
56 lines
1.5 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
|
|
namespace PrivaPub.Infrastructure.Http
|
|
{
|
|
public static class IpRangeGuard
|
|
{
|
|
static readonly IPNetwork[] BlockedV4 =
|
|
{
|
|
IPNetwork.Parse("0.0.0.0/8"),
|
|
IPNetwork.Parse("10.0.0.0/8"),
|
|
IPNetwork.Parse("100.64.0.0/10"),
|
|
IPNetwork.Parse("127.0.0.0/8"),
|
|
IPNetwork.Parse("169.254.0.0/16"),
|
|
IPNetwork.Parse("172.16.0.0/12"),
|
|
IPNetwork.Parse("192.0.0.0/24"),
|
|
IPNetwork.Parse("192.0.2.0/24"),
|
|
IPNetwork.Parse("192.88.99.0/24"),
|
|
IPNetwork.Parse("192.168.0.0/16"),
|
|
IPNetwork.Parse("198.18.0.0/15"),
|
|
IPNetwork.Parse("198.51.100.0/24"),
|
|
IPNetwork.Parse("203.0.113.0/24"),
|
|
IPNetwork.Parse("224.0.0.0/4"),
|
|
IPNetwork.Parse("240.0.0.0/4")
|
|
};
|
|
|
|
static readonly IPNetwork GlobalUnicastV6 = IPNetwork.Parse("2000::/3");
|
|
|
|
static readonly IPNetwork[] BlockedV6 =
|
|
{
|
|
IPNetwork.Parse("2001::/32"),//Teredo
|
|
IPNetwork.Parse("2001:2::/48"),
|
|
IPNetwork.Parse("2001:10::/28"),
|
|
IPNetwork.Parse("2001:20::/28"),
|
|
IPNetwork.Parse("2001:db8::/32"),
|
|
IPNetwork.Parse("2002::/16"),//6to4
|
|
IPNetwork.Parse("3fff::/20")
|
|
};
|
|
|
|
public static bool IsPublic(IPAddress address)
|
|
{
|
|
if (address == default)
|
|
return false;
|
|
if (address.IsIPv4MappedToIPv6)
|
|
address = address.MapToIPv4();
|
|
|
|
return address.AddressFamily switch
|
|
{
|
|
AddressFamily.InterNetwork => !BlockedV4.Any(range => range.Contains(address)),
|
|
AddressFamily.InterNetworkV6 => GlobalUnicastV6.Contains(address) && !BlockedV6.Any(range => range.Contains(address)),
|
|
_ => false
|
|
};
|
|
}
|
|
}
|
|
}
|