Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Managed implementation of DNS resolver #6104

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from

Conversation

rzikm
Copy link
Member

@rzikm rzikm commented Oct 4, 2024

Description

This PR brings in a C# implementation of a DNS resolver that is able to signal the TTL information together with the query results.

Main features

  • Async network I/O, fully cancellable
  • Mockable
  • Resolves IP Addresses (A/AAAA records) and Service records (SRV + related A/AAAA)
  • Transparent fallback to TCP
  • Autodetection of OS settings (i.e. reads nameservers from /etc/resolv.conf file)
  • Thread-safe

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?
    • Yes
      • Link to aspire-docs issue:
    • No
Microsoft Reviewers: Open in CodeFlow

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 4, 2024
@davidfowl davidfowl requested a review from ReubenBond October 29, 2024 21:16
Copy link
Member

@BrennanConroy BrennanConroy left a comment

Choose a reason for hiding this comment

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

Only reviewed a subset of files. It's a large PR and has a lot of RFC reading attached to it. Will review more later.

// padding is used.
//

if (!Encoding.ASCII.TryGetBytes(name, destination.IsEmpty ? destination : destination.Slice(1), out int length) || destination.Length < length + 2)
Copy link
Member

Choose a reason for hiding this comment

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

If the destination is empty, we should just return.

Copy link
Member

Choose a reason for hiding this comment

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

Do we need to add validation that only A-Z, a-z, 0-9, -, " ", and . are allowed?

Copy link
Member

Choose a reason for hiding this comment

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

Add 255 length check?

names 255 octets or less

Copy link
Member Author

Choose a reason for hiding this comment

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

The domain name is already validated at this point, it is done by DnsResolver.GetNormalizedHostName(name) (which throws if any of the domain name limits is violated)

Copy link
Member Author

Choose a reason for hiding this comment

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

I will add Debug.Asserts for some of the properties


}

internal static bool TryReadQName(ReadOnlySpan<byte> messageBuffer, int offset, [NotNullWhen(true)] out string? name, out int bytesRead)
Copy link
Member

Choose a reason for hiding this comment

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

messageBuffer doesn't have any endianness normalization when it's passed in. Is that problematic?

Copy link
Member Author

Choose a reason for hiding this comment

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

The only multibyte information is the offset in pointers and we read higher and lower byte separately and combine them correctly. Otherwise we only ever read individual bytes or (ascii) strings, so endianness should not matter.

{
// the message is not a response for our query.
// don't dispose reader, we will reuse the buffer
continue;
Copy link
Member

Choose a reason for hiding this comment

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

This wouldn't use the bytes past DnsMessageHeader.HeaderLength if we read more than that.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is UDP path, the DNS message is received in entirety in a single ReceiveAsync call, and here we checked that we are not interested in this particular message.

int bytesRead = 0;
while (responseLength < 0 || bytesRead < length + 2)
{
int read = await socket.ReceiveAsync(buffer.AsMemory(bytesRead), SocketFlags.None, cancellationToken).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

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

Need to check if (read == 0)?


if (responseLength > buffer.Length)
{
var largerBuffer = ArrayPool<byte>.Shared.Rent(responseLength);
Copy link
Member

Choose a reason for hiding this comment

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

This is kind of dangerous, user controlled pre-allocation. Since we're limited to 65k here you could argue it's not too risky, but we should probably explicitly call it out in a comment and make sure we're okay with the risk.

Technically the array pool (ConfigurableArrayPool) could return a 65 * 2 * 2 (or more) buffer since the implementation is free to search multiple bucket sizes to find a pooled byte[].

Copy link
Member Author

Choose a reason for hiding this comment

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

I am curious, if we were to decide it's too risky, what would you suggest as a mitigation? We need the entire message in memory for parsing purposes (domain name compression via pointers with offsets), so I don't see us reading the contents of the message from a stream.

ResolverOptions options = ResolvConf.GetOptions(reader);

IPEndPoint ipAddress = Assert.Single(options.Servers);
Assert.Equal(new IPEndPoint(IPAddress.Parse("10.96.0.10"), 53), ipAddress);
Copy link
Member

Choose a reason for hiding this comment

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

Verify search domains as well?

@danmoseley
Copy link
Member

@rzikm could you set expectations on this one ?

@danmoseley danmoseley added area-service-discovery needs-author-action An issue or pull request that requires more info or actions from the author. labels Feb 2, 2025
@rzikm
Copy link
Member Author

rzikm commented Feb 3, 2025

@rzikm could you set expectations on this one ?

in parallel to this, I am also finishing Threat Model Review and will need to setup fuzzing. If there are no major distractions I hope to finish the work this month.

@dotnet-policy-service dotnet-policy-service bot removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Feb 3, 2025
@rzikm
Copy link
Member Author

rzikm commented Feb 13, 2025

@BrennanConroy I pushed a substantial update which should resolve most of the comments, please take another look.

@rzikm rzikm requested a review from BrennanConroy February 13, 2025 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-service-discovery community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants