Skip to content

NetworkInterface.GetAllNetworkInterfaces throws DirectoryNotFoundException on Linux when /etc does not exist #135568

Description

@caraioniurie47

Description

On Linux, NetworkInterface.GetAllNetworkInterfaces() throws System.IO.DirectoryNotFoundException when /etc does not exist. When /etc exists but /etc/resolv.conf does not, the same call returns the interfaces with no DNS server addresses.

/etc/resolv.conf is read with File.ReadAllText, and the catch covers only FileNotFoundException and UnauthorizedAccessException:

LinuxNetworkInterface.cs:55-64

                try
                {
                    string resolverConfig = File.ReadAllText(NetworkFiles.EtcResolvConfFile);
                    DnsSuffix = StringParsingHelpers.ParseDnsSuffixFromResolvConfFile(resolverConfig);
                    DnsAddresses = new InternalIPAddressCollection(StringParsingHelpers.ParseDnsAddressesFromResolvConfFile(resolverConfig));
                }
                catch (Exception e) when (e is FileNotFoundException || e is UnauthorizedAccessException)
                {
                    DnsAddresses = new InternalIPAddressCollection();
                }

When the parent directory is missing, ENOENT is mapped to DirectoryNotFoundException, which that catch does not cover:

Interop.IOErrors.cs:120-138

            case Error.ENOENT:
                // For Windows compatibility, throw DirectoryNotFoundException instead of FileNotFoundException
                // when the parent folder does not exist.
                if (!isDirError && (path is null || ParentDirectoryExists(path)))
                {
                    return !string.IsNullOrEmpty(path) ?
                        new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path) :
                        new FileNotFoundException(SR.IO_FileNotFound);
                }
                goto case Error.ENOTDIR;

            case Error.ENOTDIR:
                return !string.IsNullOrEmpty(path) ?
#if NET11_0_OR_GREATER
                    new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path), path) :
#else
                    new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path)) :
#endif
                    new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName);

The two /etc/resolv.conf readers in UnixIPInterfaceProperties, which the BSD and Apple implementations call from the IPInterfaceProperties constructor, catch only FileNotFoundException (UnixIPInterfaceProperties.cs:87-110). Those platforms were not run.

Reproduction Steps

Run as root on Linux. run.sh runs the app twice, each time in its own mount namespace: once with an empty tmpfs mounted over /etc (so /etc exists but /etc/resolv.conf does not), and once chrooted into a tmpfs root that has no /etc. The chroot root contains only /bin, /lib, /lib64, /usr, /proc, /sys, /dev, /tmp, /home and /root, so in the second case the real paths (run.sh applies readlink -f) of the app and the dotnet host must be under one of those.

Program.cs:

using System;
using System.IO;
using System.Net.NetworkInformation;

Console.WriteLine($"/etc exists: {Directory.Exists("/etc")}, /etc/resolv.conf exists: {File.Exists("/etc/resolv.conf")}");

foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
    IPInterfaceProperties props = nic.GetIPProperties();
    Console.WriteLine($"{nic.Name}: IsDnsEnabled={props.IsDnsEnabled}, DnsAddresses={props.DnsAddresses.Count}");
}

repro.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <!-- Lets the app run on a locally built runtime with a higher major version. -->
    <RollForward>LatestMajor</RollForward>
  </PropertyGroup>
</Project>

run.sh:

#!/bin/sh
# Usage (as root): ./run.sh <path to dotnet host> <path to repro.dll>
# Runs the repro with /etc present but no /etc/resolv.conf, then with no /etc at all.
# Each case runs in its own mount namespace (unshare --mount), so the host's /etc is untouched.
set -u
DOTNET=$(readlink -f "$1")
APP=$(readlink -f "$2")
export DOTNET

echo "== /etc present, /etc/resolv.conf missing (empty tmpfs mounted over /etc)"
unshare --mount sh -c 'mount -t tmpfs tmpfs /etc && "$DOTNET" "$0"' "$APP"
echo "exit code: $?"

echo "== /etc missing (chroot into a tmpfs root that has no /etc)"
unshare --mount sh -c '
    ROOT=$(mktemp -d)
    mount -t tmpfs tmpfs "$ROOT"
    for d in bin lib lib64 usr proc sys dev tmp home root; do
        if [ -L "/$d" ]; then
            cp -P "/$d" "$ROOT/$d"
        elif [ -d "/$d" ]; then
            mkdir "$ROOT/$d" && mount --bind "/$d" "$ROOT/$d"
        fi
    done
    exec chroot "$ROOT" "$DOTNET" "$0"' "$APP"
echo "exit code: $?"
dotnet build -c Release -o out
./run.sh "$(command -v dotnet)" out/repro.dll

Expected behavior

With no /etc, the same result as with no /etc/resolv.conf: the interfaces are returned, DnsAddresses is empty and IsDnsEnabled is false.

Actual behavior

Output on a runtime built from 9bfbe1d (source paths and the file-open frames between Interop.CheckIo and LinuxNetworkInterfaceSystemProperties..ctor trimmed):

== /etc present, /etc/resolv.conf missing (empty tmpfs mounted over /etc)
/etc exists: True, /etc/resolv.conf exists: False
lo: IsDnsEnabled=False, DnsAddresses=0
eth0: IsDnsEnabled=False, DnsAddresses=0
exit code: 0
== /etc missing (chroot into a tmpfs root that has no /etc)
/etc exists: False, /etc/resolv.conf exists: False
Unhandled exception. System.IO.DirectoryNotFoundException: Could not find a part of the path '/etc/resolv.conf'.
Directory path: '/etc/resolv.conf'
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   at Interop.CheckIo(Error error, String path, Boolean isDirError)
   ...
   at System.Net.NetworkInformation.LinuxNetworkInterface.LinuxNetworkInterfaceSystemProperties..ctor()
   at System.Net.NetworkInformation.LinuxNetworkInterface.GetLinuxNetworkInterfaces()
   at System.Net.NetworkInformation.NetworkInterfacePal.GetAllNetworkInterfaces()
   at System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
   at Program.<Main>$(String[] args)
Aborted (core dumped)
exit code: 134

Regression?

Not checked against earlier releases.

Known Workarounds

An /etc directory, even an empty one, avoids the exception (the first case in the output above).

Configuration

Runtime built from dotnet/runtime 9bfbe1d (./build.sh clr+libs -rc Release), app built with SDK 11.0.100-rc.1.26420.103. Ubuntu 22.04.5 LTS on WSL2, x64, Linux 6.18.40.1-microsoft-standard-WSL2. Only seen when /etc is missing.

Other information

Found while porting .NET to another operating system, where /etc did not exist in the test environment.

A fix: read /etc/resolv.conf through one internal helper that returns null for both FileNotFoundException and DirectoryNotFoundException, used by all three readers. No public API change.

A missing /etc/resolv.conf used to make DnsAddresses throw PlatformNotSupportedException on Linux (#109582); #111471 fixed that by setting an empty DnsAddresses in the Linux reader's FileNotFoundException catch. A missing /etc throws DirectoryNotFoundException instead, which that catch does not cover.

I have a fix with a test ready and will open a PR for it shortly. Could this be assigned to me?

Note

AI-generated, written at my direction and reviewed by me before posting. Verified on dotnet/runtime 9bfbe1d, on Ubuntu 22.04.5 LTS x64 on WSL2 (Linux 6.18.40.1), with the runtime from ./build.sh clr+libs -rc Release: the repro commands above were run as written, as root, from a directory under /home, with dotnet on PATH being the repo's SDK (.dotnet/dotnet) for the build and the built runtime's host (artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet) for run.sh.

Activity

  1. dotnet-policy-service commented on Oct 10, 2026

    @dotnet-policy-service
    Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.IOuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions