| File: System\Net\ResolvConf.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.NameResolution\src\System.Net.NameResolution.csproj (System.Net.NameResolution) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.IO; namespace System.Net { // Parses the system DNS server configuration from /etc/resolv.conf (RFC-style // "nameserver" directives). Used when DnsResolverOptions.Servers is empty. internal static class ResolvConf { private const string ResolvConfPath = "/etc/resolv.conf"; internal const int DefaultDnsPort = 53; private static readonly object s_lock = new object(); private static List<IPEndPoint>? s_cachedServers; private static DateTime s_cachedWriteTimeUtc; public static List<IPEndPoint> GetNameServers() { // /etc/resolv.conf is read on every query when no explicit servers are configured. // Cache the parsed result and only re-parse when the file's last-write time changes. DateTime writeTimeUtc = GetLastWriteTimeUtc(); lock (s_lock) { if (s_cachedServers is not null && writeTimeUtc == s_cachedWriteTimeUtc) { return s_cachedServers; } } List<IPEndPoint> servers = ReadNameServers(); lock (s_lock) { s_cachedServers = servers; s_cachedWriteTimeUtc = writeTimeUtc; return servers; } } private static DateTime GetLastWriteTimeUtc() { try { // Returns a sentinel (not an exception) when the file is missing, which is a // stable key that avoids re-reading a nonexistent file on every query. return File.GetLastWriteTimeUtc(ResolvConfPath); } catch (IOException) { return default; } catch (UnauthorizedAccessException) { return default; } } private static List<IPEndPoint> ReadNameServers() { try { using StreamReader reader = new StreamReader(ResolvConfPath); return Parse(reader); } catch (IOException) { return new List<IPEndPoint>(); } catch (UnauthorizedAccessException) { return new List<IPEndPoint>(); } } // Parses "nameserver <address>" directives from a resolv.conf-formatted stream. // Lines beginning with '#' or ';' are comments. Any text following the address // on a nameserver line is ignored. internal static List<IPEndPoint> Parse(TextReader reader) { List<IPEndPoint> servers = new List<IPEndPoint>(); string? line; while ((line = reader.ReadLine()) is not null) { ReadOnlySpan<char> span = line.AsSpan().Trim(); if (span.IsEmpty || span[0] == '#' || span[0] == ';') { continue; } const string Directive = "nameserver"; if (!span.StartsWith(Directive, StringComparison.Ordinal)) { continue; } ReadOnlySpan<char> rest = span[Directive.Length..]; if (rest.IsEmpty || (rest[0] != ' ' && rest[0] != '\t')) { continue; } rest = rest.TrimStart(); // The address is the first whitespace-delimited token; ignore anything after it. int ws = rest.IndexOfAny(' ', '\t'); if (ws >= 0) { rest = rest[..ws]; } if (IPAddress.TryParse(rest, out IPAddress? address)) { servers.Add(new IPEndPoint(address, DefaultDnsPort)); } } return servers; } } }