[fix]修正UdpServer在接收广播时连续启动接收的错误,在StarAgent中,此时可能收到广播包,SocketFlags是Broadcast,需要清空,否则报错“参考的对象类型不支持尝试的操作”; 无需设置SocketOptionName.PacketInformation,在ReceiveMessageFromAsync时会自动设置,并且支持ipv6;
石头 编写于 2024-10-10 00:36:00 石头 提交于 2024-10-10 00:45:43
X
using System.Globalization;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using NewLife.Log;

namespace NewLife.Net;

/// <summary>Tcp连接信息</summary>
public class TcpConnectionInformation2 : TcpConnectionInformation
{
    /// <summary>本地结点</summary>
    public override IPEndPoint LocalEndPoint { get; }

    /// <summary>远程结点</summary>
    public override IPEndPoint RemoteEndPoint { get; }

    /// <summary>Tcp状态</summary>
    public override TcpState State { get; }

    /// <summary>进程标识</summary>
    public Int32 ProcessId { get; set; }

    /// <summary>inode标识</summary>
    public String? Node { get; set; }

    /// <summary>实例化Tcp连接信息</summary>
    /// <param name="local"></param>
    /// <param name="remote"></param>
    /// <param name="state"></param>
    /// <param name="processId"></param>
    public TcpConnectionInformation2(IPEndPoint local, IPEndPoint remote, TcpState state, Int32 processId)
    {
        LocalEndPoint = local;
        RemoteEndPoint = remote;
        State = state;
        ProcessId = processId;
    }

    private TcpConnectionInformation2(MIB_TCPROW_OWNER_PID row)
    {
        State = (TcpState)row.state;
        var port = (row.localPort1 << 8) | row.localPort2;
        var port2 = (State != TcpState.Listen) ? ((row.remotePort1 << 8) | row.remotePort2) : 0;
        LocalEndPoint = new IPEndPoint(row.localAddr, port);
        RemoteEndPoint = new IPEndPoint(row.remoteAddr, port2);
        ProcessId = row.owningPid;
    }

    /// <summary>已重载。</summary>
    /// <returns></returns>
    public override String ToString() => $"{LocalEndPoint}<=>{RemoteEndPoint} {State} {ProcessId}";

    #region Windows连接信息
    private enum TCP_TABLE_CLASS : Int32
    {
        TCP_TABLE_BASIC_LISTENER,
        TCP_TABLE_BASIC_CONNECTIONS,
        TCP_TABLE_BASIC_ALL,
        TCP_TABLE_OWNER_PID_LISTENER,
        TCP_TABLE_OWNER_PID_CONNECTIONS,
        TCP_TABLE_OWNER_PID_ALL,
        TCP_TABLE_OWNER_MODULE_LISTENER,
        TCP_TABLE_OWNER_MODULE_CONNECTIONS,
        TCP_TABLE_OWNER_MODULE_ALL
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MIB_TCPROW_OWNER_PID
    {
        public UInt32 state;
        public UInt32 localAddr;
        public Byte localPort1;
        public Byte localPort2;
        public Byte localPort3;
        public Byte localPort4;
        public UInt32 remoteAddr;
        public Byte remotePort1;
        public Byte remotePort2;
        public Byte remotePort3;
        public Byte remotePort4;
        public Int32 owningPid;

        public UInt16 LocalPort => BitConverter.ToUInt16([localPort2, localPort1], 0);

        public UInt16 RemotePort => BitConverter.ToUInt16([remotePort2, remotePort1], 0);
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MIB_TCPTABLE_OWNER_PID
    {
        public UInt32 dwNumEntries;
        private MIB_TCPROW_OWNER_PID table;
    }

    [DllImport("iphlpapi.dll", SetLastError = true)]
    private static extern UInt32 GetExtendedTcpTable(IntPtr pTcpTable,
        ref Int32 dwOutBufLen,
        Boolean sort,
        Int32 ipVersion,
        TCP_TABLE_CLASS tblClass,
        Int32 reserved);

    /// <summary>获取所有Tcp连接</summary>
    /// <returns></returns>
    [Obsolete("=>GetWindowsTcpConnections")]
    public static TcpConnectionInformation2[] GetAllTcpConnections() => GetWindowsTcpConnections();

    /// <summary>获取所有Tcp连接</summary>
    /// <returns></returns>
    public static TcpConnectionInformation2[] GetWindowsTcpConnections()
    {
        //MIB_TCPROW_OWNER_PID[] tTable;
        var AF_INET = 2;    // IP_v4
        var buffSize = 0;

        // how much memory do we need?
        var ret = GetExtendedTcpTable(IntPtr.Zero,
            ref buffSize,
            true,
            AF_INET,
            TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL,
            0);
        if (ret is not 0 and not 122) // 122 insufficient buffer size
            throw new Exception("bad ret on check " + ret);
        var buffTable = Marshal.AllocHGlobal(buffSize);

        var list = new List<TcpConnectionInformation2>();
        try
        {
            ret = GetExtendedTcpTable(buffTable,
                ref buffSize,
                true,
                AF_INET,
                TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL,
                0);
            if (ret != 0)
                throw new Exception("bad ret " + ret);

            // get the number of entries in the table
            var tab =
                (MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(
                    buffTable,
                    typeof(MIB_TCPTABLE_OWNER_PID))!;
            var rowPtr = (IntPtr)((Int64)buffTable +
                Marshal.SizeOf(tab.dwNumEntries));
            //tTable = new MIB_TCPROW_OWNER_PID[tab.dwNumEntries];

            for (var i = 0; i < tab.dwNumEntries; i++)
            {
                var tcpRow = (MIB_TCPROW_OWNER_PID)Marshal
                    .PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID))!;
                //tTable[i] = tcpRow;
                list.Add(new TcpConnectionInformation2(tcpRow));

                // next entry
                rowPtr = (IntPtr)((Int64)rowPtr + Marshal.SizeOf(tcpRow));
            }
        }
        finally
        {
            // Free the Memory
            Marshal.FreeHGlobal(buffTable);
        }
        //return tTable;
        return list.ToArray();
    }
    #endregion

    #region Linux连接信息
    /// <summary>获取指定进程的Tcp连接</summary>
    /// <param name="processId">目标进程。默认-1未指定,获取所有进程的Tcp连接</param>
    /// <returns></returns>
    public static TcpConnectionInformation2[] GetLinuxTcpConnections(Int32 processId = -1)
    {
        var list = new List<TcpConnectionInformation2>();

        String[]? nodes = null;
        if (processId > 0)
        {
            // 获取指定进程的所有inode
            nodes = GetNodes(processId);
            if (nodes == null || nodes.Length == 0) return list.ToArray();
        }

        // 各个进程底下的/net/tcp,实际上是所有进程的连接
        var rs = ParseTcpsFromFile(processId > 0 ? $"/proc/{processId}/net/tcp" : "/proc/net/tcp");
        if (rs != null && rs.Count > 0) list.AddRange(rs);

        var rs2 = ParseTcpsFromFile(processId > 0 ? $"/proc/{processId}/net/tcp6" : "/proc/net/tcp6");
        if (rs2 != null && rs2.Count > 0) list.AddRange(rs2);
        //XTrace.WriteLine("tcps: {0} nodes: {1}", list.Count, nodes?.Length);

        // 过滤指定进程的连接
        if (processId > 0 && nodes != null)
        {
            var list2 = new List<TcpConnectionInformation2>();
            foreach (var item in list)
            {
                if (nodes.Contains(item.Node))
                {
                    item.ProcessId = processId;
                    list2.Add(item);
                }
            }
            list = list2;
        }
        //XTrace.WriteLine("tcps2: {0}", list.Count);

        return list.ToArray();
    }

    private static IList<TcpConnectionInformation2> ParseTcpsFromFile(String file)
    {
#if DEBUG
        //XTrace.WriteLine("ParseTcpsFromFile {0}", file);
        //DefaultSpan.Current?.AppendTag($"ParseTcpsFromFile {file}");
#endif

        var text = File.ReadAllText(file);

        return ParseTcps(text);
    }

    /// <summary>分析Tcp连接信息</summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public static IList<TcpConnectionInformation2> ParseTcps(String text)
    {
        var list = new List<TcpConnectionInformation2>();

        if (text.IsNullOrEmpty()) return list;

        // 逐行读取TCP连接信息
        foreach (var line in text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
        {
            var parts = line.Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length < 4 || parts[1].IndexOf(':') < 0) continue;

            //// 提取连接信息
            //var ps1 = parts[1].Split(':');
            //var ps2 = parts[2].Split(':');
            //if (ps1.Length < 2 || ps2.Length < 2) continue;

            var state = GetState(parts[3]);

            //// IPv4 和 IPv6 解析方式不同
            //if (ps1[0].Length <= 8)
            //{
            //    var localAddress = new IPAddress(ps1[0].ToHex().Reverse().ToArray());
            //    var local = new IPEndPoint(localAddress, Int32.Parse(ps1[1], NumberStyles.HexNumber));
            //    var remoteAddress = new IPAddress(ps2[0].ToHex().Reverse().ToArray());
            //    var remote = new IPEndPoint(remoteAddress, Int32.Parse(ps2[1], NumberStyles.HexNumber));
            //    var info = new TcpConnectionInformation2(local, remote, state, 0);

            //    list.Add(info);
            //}
            //else
            //{
            //    var localAddress = GetIPv6(ps1[0]);
            //    var local = new IPEndPoint(localAddress, Int32.Parse(ps1[1], NumberStyles.HexNumber));

            //    var remoteAddress = GetIPv6(ps2[0]);
            //    var remote = new IPEndPoint(remoteAddress, Int32.Parse(ps2[1], NumberStyles.HexNumber));

            //    var info = new TcpConnectionInformation2(local, remote, state, 0);

            //    list.Add(info);
            //}

            var local = ParseAddressAndPort(parts[1]);
            var remote = ParseAddressAndPort(parts[2]);
            //var pid = GetProcessIdFromInode(parts[9]);
            var info = new TcpConnectionInformation2(local, remote, state, 0);
            info.Node = parts[9];

            list.Add(info);
        }

        return list;
    }

    private static String[] GetNodes(Int32 processId)
    {
        var path = $"/proc/{processId}/fd".AsDirectory();
        if (!path.Exists) return new String[0];

        var files = new List<String>();
        foreach (var fi in path.GetFiles())
        {
            var name = fi.Name;
#if NET6_0_OR_GREATER
            if (fi.Attributes.HasFlag(FileAttributes.ReparsePoint))
                name = fi.ResolveLinkTarget(true)?.Name;
#endif

            if (!name.IsNullOrEmpty()) files.Add(name);
        }

        return ParseNodes(files);
    }

    /// <summary>分析Socket的inode</summary>
    /// <param name="files"></param>
    /// <returns></returns>
    public static String[] ParseNodes(IList<String> files)
    {
        var list = new List<String>();
        foreach (var item in files)
        {
            var node = item.Substring("socket:[", "]");
            if (!node.IsNullOrEmpty()) list.Add(node);
        }

        return list.ToArray();
    }

    private static IPEndPoint ParseAddressAndPort(String colonSeparatedAddress)
    {
        var num = colonSeparatedAddress.IndexOf(':');
        if (num == -1) throw new NetworkInformationException();

        var address = ParseHexIPAddress(colonSeparatedAddress.Substring(0, num));
        if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4();
        var s = colonSeparatedAddress.Substring(num + 1);
        return !Int32.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)
            ? throw new NetworkInformationException()
            : new IPEndPoint(address, result);
    }

    internal static IPAddress ParseHexIPAddress(String remoteAddressString)
    {
        if (remoteAddressString.Length <= 8) return ParseIPv4HexString(remoteAddressString);

        if (remoteAddressString.Length == 32) return ParseIPv6HexString(remoteAddressString);

        throw new NetworkInformationException();
    }

    private static IPAddress ParseIPv4HexString(String hexAddress)
    {
        return !Int64.TryParse(hexAddress, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)
            ? throw new NetworkInformationException()
            : new IPAddress(result);
    }

    private static IPAddress ParseIPv6HexString(String hexAddress, Boolean isNetworkOrder = false)
    {
        var span = hexAddress.ToHex();
        if (!isNetworkOrder && BitConverter.IsLittleEndian)
        {
            for (var j = 0; j < 4; j++)
            {
                Array.Reverse(span, j * 4, 4);
            }
        }

        return new IPAddress(span);
    }

    private static TcpState GetState(String hexState)
    {
        return hexState switch
        {
            "01" => TcpState.Established,
            "02" => TcpState.SynSent,
            "03" => TcpState.SynReceived,
            "04" => TcpState.FinWait1,
            "05" => TcpState.FinWait2,
            "06" => TcpState.TimeWait,
            "07" => TcpState.Closed,
            "08" => TcpState.CloseWait,
            "09" => TcpState.LastAck,
            "0A" => TcpState.Listen,
            "0B" => TcpState.Closing,
            _ => TcpState.Unknown,
        };
    }
    #endregion
}