Skip to content

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Oct 31, 2025

let's see what benchmarks think...

@EgorBo
Copy link
Member Author

EgorBo commented Oct 31, 2025

@MihuBot

@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@EgorBo EgorBo added reduce-unsafe area-System.Net and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

@EgorBo

This comment was marked as outdated.

@EgorBo

This comment was marked as outdated.

@EgorBo
Copy link
Member Author

EgorBo commented Oct 31, 2025

@EgorBot -amd -arm -windows_intel

using System;
using System.Net;
using BenchmarkDotNet.Attributes;

public class IPv4_u16_Benchmarks
{
    public IEnumerable<string> Data() => [
        "127.0.0.1",
        "192.168.0.1",
        "8.8.8.8",
        "255.255.255.255"];

    [Benchmark, ArgumentsSource(nameof(Data))]
    public IPAddress Parse_IPv4_u16(string ip) => IPAddress.Parse(ip);
}

public class IPv4_u8_Benchmarks
{
    public IEnumerable<byte[]> Data() => [
        "127.0.0.1"u8.ToArray(),
        "192.168.0.1"u8.ToArray(),
        "8.8.8.8"u8.ToArray(),
        "255.255.255.255"u8.ToArray()];

    [Benchmark, ArgumentsSource(nameof(Data))]
    public IPAddress Parse_IPv4_u8(byte[] ip) => IPAddress.Parse(ip);
}

public class IPv6_u16_Benchmarks
{
    public IEnumerable<string> Data() => [
        "::1",
        "2001:db8::1",
        "fe80::1ff:fe23:4567:890a",
        "2001:4860:4860::8888"];

    [Benchmark, ArgumentsSource(nameof(Data))]
    public IPAddress Parse_IPv6_u16(string ip) => IPAddress.Parse(ip);
}

public class IPv6_u8_Benchmarks
{
    public IEnumerable<byte[]> Data() => [
        "::1"u8.ToArray(),
        "2001:db8::1"u8.ToArray(),
        "fe80::1ff:fe23:4567:890a"u8.ToArray(),
        "2001:4860:4860::8888"u8.ToArray()];

    [Benchmark, ArgumentsSource(nameof(Data))]
    public IPAddress Parse_IPv6_u8(byte[] ip) => IPAddress.Parse(ip);
}

@EgorBo EgorBo marked this pull request as ready for review November 1, 2025 02:12
Copilot AI review requested due to automatic review settings November 1, 2025 02:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the IPv4 and IPv6 address validation methods across the Uri and networking libraries to eliminate unsafe code and use ReadOnlySpan<T> instead of pointer-based operations. The key changes include:

  • Converting pointer-based (char*) validation methods to span-based implementations
  • Changing ref int end parameters to out int end parameters with 0-based indexing
  • Removing unsafe blocks and fixed statements throughout the codebase

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/libraries/System.Private.Uri/src/System/Uri.cs Updated CheckHostName and CheckAuthorityHelper to use span-based IPv4/IPv6 helpers with adjusted index calculations
src/libraries/System.Private.Uri/src/System/IPv6AddressHelper.cs Converted InternalIsValid and IsValid from unsafe pointer methods to span-based methods
src/libraries/System.Private.Uri/src/System/IPv4AddressHelper.cs Simplified ParseCanonicalName by removing unsafe code and using span slicing
src/libraries/System.Net.Primitives/src/System/Net/IPAddressParser.cs Removed unsafe blocks from IsValid, TryParseIpv4, and TryParseIPv6 methods
src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs Converted IsValidStrict from pointer-based to span-based with adjusted indexing and removed sequenceLength = 0 assignment
src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs Refactored IsValid, IsValidCanonical, and ParseNonCanonical to use spans with 0-based indexing

@EgorBo
Copy link
Member Author

EgorBo commented Nov 1, 2025

PTAL @stephentoub @MihaZupan

It seems to slightly improve the performance a bit (see EgorBot/runtime-utils#534) due to a bit of tweaking, but mostly because of ref end argument that was used directly instead of a local. And, obviously, we no longer need pinning. Also, had to use [0, 0, 0], see #121248

It seems like this code has a good test coverage, all off-by-one mistakes led to test failures.
I also encountered a small out-of-bounds load in IPv6AddressHelper.InternalIsValid that was accessing string's null terminator most likely unintentionally. It can be reproduced via calling Uri.TryCreate("http://[", UriKind.Absolute, out Uri uri);. Since we can't get there with anything other than a string object, it's not a security issue, but I had to add an extra bound check and return false there.

Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

I also encountered a small out-of-bounds load in IPv6AddressHelper.InternalIsValid that was accessing string's null terminator most likely unintentionally

Nice catch

@MihaZupan
Copy link
Member

@MihuBot fuzz IPAddress

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
@EgorBo
Copy link
Member Author

EgorBo commented Nov 3, 2025

/ba-g deadletter

@EgorBo EgorBo merged commit 0cb6f71 into dotnet:main Nov 3, 2025
82 of 86 checks passed
@EgorBo EgorBo deleted the safe-ipaddress-parsing branch November 3, 2025 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants