File: System\Net\NetworkInformation\StringParsingHelpers.Connections.cs
Web Access
Project: src\src\libraries\System.Net.NetworkInformation\src\System.Net.NetworkInformation.csproj (System.Net.NetworkInformation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Globalization;
using System.IO;
 
namespace System.Net.NetworkInformation
{
    internal static partial class StringParsingHelpers
    {
        internal static int ParseNumSocketConnections(string filePath, string protocolName)
        {
            // Parse the number of active connections out of /proc/net/sockstat
            string sockstatFile = ReadAllText(filePath);
            int indexOfTcp = sockstatFile.IndexOf(protocolName, StringComparison.Ordinal);
            int endOfTcpLine = sockstatFile.IndexOf(Environment.NewLine, indexOfTcp + 1, StringComparison.Ordinal);
            string tcpLineData = sockstatFile.Substring(indexOfTcp, endOfTcpLine - indexOfTcp);
            StringParser sockstatParser = new StringParser(tcpLineData, ' ');
            sockstatParser.MoveNextOrFail(); // Skip "<name>:"
            sockstatParser.MoveNextOrFail(); // Skip: "inuse"
            return sockstatParser.ParseNextInt32();
        }
 
        internal static TcpConnectionInformation[] ParseActiveTcpConnectionsFromFiles(string? tcp4ConnectionsFile, string? tcp6ConnectionsFile)
        {
            string[] v4connections;
            string[] v6connections;
 
            if (tcp4ConnectionsFile != null)
            {
                string tcp4FileContents = ReadAllText(tcp4ConnectionsFile);
                v4connections = tcp4FileContents.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                v4connections = Array.Empty<string>();
            }
 
            if (tcp6ConnectionsFile != null)
            {
                string tcp6FileContents = ReadAllText(tcp6ConnectionsFile);
                v6connections = tcp6FileContents.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                v6connections = Array.Empty<string>();
            }
 
            // First line is header in each file. On WSL, this file may be empty.
            int count = 0;
            if (v4connections.Length > 0)
            {
                count += v4connections.Length - 1;
            }
 
            if (v6connections.Length > 0)
            {
                count += v6connections.Length - 1;
            }
 
            // First line is header in each file.
            TcpConnectionInformation[] connections = new TcpConnectionInformation[count];
            int index = 0;
            int skip = 0;
 
            // TCP Connections
            for (int i = 1; i < v4connections.Length; i++) // Skip first line header.
            {
                string line = v4connections[i];
                connections[index] = ParseTcpConnectionInformationFromLine(line);
                if (connections[index].State == TcpState.Listen)
                {
                    skip++;
                }
                else
                {
                    index++;
                }
            }
 
            // TCP6 Connections
            for (int i = 1; i < v6connections.Length; i++) // Skip first line header.
            {
                string line = v6connections[i];
                connections[index] = ParseTcpConnectionInformationFromLine(line);
                if (connections[index].State == TcpState.Listen)
                {
                    skip++;
                }
                else
                {
                    index++;
                }
            }
 
            if (skip != 0)
            {
                Array.Resize(ref connections, connections.Length - skip);
            }
 
            return connections;
        }
 
        internal static IPEndPoint[] ParseActiveTcpListenersFromFiles(string? tcp4ConnectionsFile, string? tcp6ConnectionsFile)
        {
            string[] v4connections;
            string[] v6connections;
 
            if (tcp4ConnectionsFile != null)
            {
                string tcp4FileContents = ReadAllText(tcp4ConnectionsFile);
                v4connections = tcp4FileContents.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                v4connections = Array.Empty<string>();
            }
 
            if (tcp6ConnectionsFile != null)
            {
                string tcp6FileContents = ReadAllText(tcp6ConnectionsFile);
                v6connections = tcp6FileContents.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                v6connections = Array.Empty<string>();
            }
 
            // First line is header in each file. On WSL, this file may be empty.
            int count = 0;
            if (v4connections.Length > 0)
            {
                count += v4connections.Length - 1;
            }
 
            if (v6connections.Length > 0)
            {
                count += v6connections.Length - 1;
            }
 
            // First line is header in each file.
            IPEndPoint[] endPoints = new IPEndPoint[count];
            int index = 0;
            int skip = 0;
 
            // TCP Connections
            for (int i = 1; i < v4connections.Length; i++) // Skip first line header.
            {
                TcpConnectionInformation ti = ParseTcpConnectionInformationFromLine(v4connections[i]);
                if (ti.State == TcpState.Listen)
                {
                    endPoints[index] = ti.LocalEndPoint;
                    index++;
                }
                else
                {
                    skip++;
                }
            }
 
            // TCP6 Connections
            for (int i = 1; i < v6connections.Length; i++) // Skip first line header.
            {
                TcpConnectionInformation ti = ParseTcpConnectionInformationFromLine(v6connections[i]);
                if (ti.State == TcpState.Listen)
                {
                    endPoints[index] = ti.LocalEndPoint;
                    index++;
                }
                else
                {
                    skip++;
                }
            }
 
            if (skip != 0)
            {
                Array.Resize(ref endPoints, endPoints.Length - skip);
            }
 
            return endPoints;
        }
 
        public static IPEndPoint[] ParseActiveUdpListenersFromFiles(string? udp4File, string? udp6File)
        {
            string[] v4connections;
            string[] v6connections;
 
            if (udp4File != null)
            {
                string udp4FileContents = ReadAllText(udp4File);
                v4connections = udp4FileContents.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                v4connections = Array.Empty<string>();
            }
 
            if (udp6File != null)
            {
                string udp6FileContents = ReadAllText(udp6File);
                v6connections = udp6FileContents.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                v6connections = Array.Empty<string>();
            }
 
            // First line is header in each file. On WSL, this file may be empty.
            int count = 0;
            if (v4connections.Length > 0)
            {
                count += v4connections.Length - 1;
            }
 
            if (v6connections.Length > 0)
            {
                count += v6connections.Length - 1;
            }
 
            IPEndPoint[] endPoints = new IPEndPoint[count];
            int index = 0;
 
            // UDP Connections
            for (int i = 1; i < v4connections.Length; i++) // Skip first line header.
            {
                string line = v4connections[i];
                IPEndPoint endPoint = ParseLocalConnectionInformation(line);
                endPoints[index++] = endPoint;
            }
 
            // UDP6 Connections
            for (int i = 1; i < v6connections.Length; i++) // Skip first line header.
            {
                string line = v6connections[i];
                IPEndPoint endPoint = ParseLocalConnectionInformation(line);
                endPoints[index++] = endPoint;
            }
 
            return endPoints;
        }
 
        // Parsing logic for local and remote addresses and ports, as well as socket state.
        internal static TcpConnectionInformation ParseTcpConnectionInformationFromLine(string line)
        {
            StringParser parser = new StringParser(line, ' ', skipEmpty: true);
            parser.MoveNextOrFail(); // skip Index
 
            string localAddressAndPort = parser.MoveAndExtractNext(); // local_address
            IPEndPoint localEndPoint = ParseAddressAndPort(localAddressAndPort);
 
            string remoteAddressAndPort = parser.MoveAndExtractNext(); // rem_address
            IPEndPoint remoteEndPoint = ParseAddressAndPort(remoteAddressAndPort);
 
            string socketStateHex = parser.MoveAndExtractNext();
            int nativeTcpState;
            if (!int.TryParse(socketStateHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out nativeTcpState))
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            TcpState tcpState = MapTcpState(nativeTcpState);
 
            return new SimpleTcpConnectionInformation(localEndPoint, remoteEndPoint, tcpState);
        }
 
        // Common parsing logic for the local connection information.
        private static IPEndPoint ParseLocalConnectionInformation(string line)
        {
            StringParser parser = new StringParser(line, ' ', skipEmpty: true);
            parser.MoveNextOrFail(); // skip Index
 
            string localAddressAndPort = parser.MoveAndExtractNext();
            int indexOfColon = localAddressAndPort.IndexOf(':');
            if (indexOfColon == -1)
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            IPAddress localIPAddress = ParseHexIPAddress(localAddressAndPort.AsSpan(0, indexOfColon));
 
            ReadOnlySpan<char> portSpan = localAddressAndPort.AsSpan(indexOfColon + 1, localAddressAndPort.Length - (indexOfColon + 1));
            int localPort;
            if (!int.TryParse(portSpan, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out localPort))
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            return new IPEndPoint(localIPAddress, localPort);
        }
 
        private static IPEndPoint ParseAddressAndPort(string colonSeparatedAddress)
        {
            int indexOfColon = colonSeparatedAddress.IndexOf(':');
            if (indexOfColon == -1)
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            IPAddress ipAddress = ParseHexIPAddress(colonSeparatedAddress.AsSpan(0, indexOfColon));
 
            ReadOnlySpan<char> portSpan = colonSeparatedAddress.AsSpan(indexOfColon + 1, colonSeparatedAddress.Length - (indexOfColon + 1));
            int port;
            if (!int.TryParse(portSpan, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out port))
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            return new IPEndPoint(ipAddress, port);
        }
 
        // Maps from Linux TCP states to .NET TcpStates.
        private static TcpState MapTcpState(int state)
        {
            return Interop.Sys.MapTcpState((int)state);
        }
 
        internal static IPAddress ParseHexIPAddress(ReadOnlySpan<char> remoteAddressString)
        {
            if (remoteAddressString.Length <= 8) // IPv4 Address
            {
                return ParseIPv4HexString(remoteAddressString);
            }
            else if (remoteAddressString.Length == 32) // IPv6 Address
            {
                return ParseIPv6HexString(remoteAddressString);
            }
            else
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
        }
 
        // Simply converts the hex string into a long and uses the IPAddress(long) constructor.
        // Strings passed to this method must be 8 or less characters in length (32-bit address).
        private static IPAddress ParseIPv4HexString(ReadOnlySpan<char> hexAddress)
        {
            IPAddress ipAddress;
            long addressValue;
            if (!long.TryParse(hexAddress, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out addressValue))
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            ipAddress = new IPAddress(addressValue);
            return ipAddress;
        }
 
        // Parses a 128-bit IPv6 Address stored as 4 concatenated 32-bit hex numbers.
        // If isSequence is true it assumes that hexAddress is in sequence of IPv6 bytes.
        // First number corresponds to lower address part
        // E.g. IP-address:                           fe80::215:5dff:fe00:402
        //      It's bytes in direct order:           FE-80-00-00  00-00-00-00  02-15-5D-FF  FE-00-04-02
        //      It's representation in /proc/net/tcp6: 00-00-80-FE  00-00-00-00  FF-5D-15-02  02-04-00-FE
        //                                             (dashes and spaces added above for readability)
        // Strings passed to this must be 32 characters in length.
        private static IPAddress ParseIPv6HexString(ReadOnlySpan<char> hexAddress, bool isNetworkOrder = false)
        {
            Debug.Assert(hexAddress.Length == 32);
            Span<byte> addressBytes = stackalloc byte[16];
            if (isNetworkOrder || !BitConverter.IsLittleEndian)
            {
                for (int i = 0; i < 16; i++)
                {
                    addressBytes[i] = (byte)(HexToByte(hexAddress[(i * 2)]) * 16
                                           + HexToByte(hexAddress[(i * 2) + 1]));
                }
            }
            else
            {
                for (int i = 0; i < 4; i++)
                {
                    for (int j = 0; j < 4; j++)
                    {
                        int srcIndex = i * 4 + 3 - j;
                        int targetIndex = i * 4 + j;
                        addressBytes[targetIndex] = (byte)(HexToByte(hexAddress[srcIndex * 2]) * 16
                                                         + HexToByte(hexAddress[srcIndex * 2 + 1]));
                    }
                }
            }
 
            IPAddress ipAddress = new IPAddress(addressBytes);
            return ipAddress;
        }
 
        private static byte HexToByte(char val)
        {
            int result = HexConverter.FromChar(val);
            if (result == 0xFF)
            {
                throw ExceptionHelper.CreateForParseFailure();
            }
 
            return (byte)result;
        }
    }
}