IP地址验证

14

我正在重构我的代码,想要使用 IPAddress.TryParse 方法来验证一个字符串是否是有效的IPv4地址,而不是使用正则表达式:

public static bool IsIPv4(string value)
{
    IPAddress address;

    if (IPAddress.TryParse(value, out address))
    {
        if (address.AddressFamily == AddressFamily.InterNetwork)
        {
            return true;
        }
    }

    return false;
}

我的单元测试现在失败了,因为这些输入值返回true并被解析为以下的IPAddress对象:

value = "0.0.0.0"      ->  address = {0.0.0.0}
value = "255.255.255"  ->  address = {255.255.0.255}
value = "65536"        ->  address = {0.1.0.0}

这是否有意义?我知道从技术上讲0.0.0.0是一个有效的IPv4地址,即使对于用户输入来说毫无意义。其他两个呢?它们为什么被转换成这样,即使对于用户来说可能并不透明(比如,用户忘记输入点“.”而输入了65536而不是6.5.5.36),我应该将它们视为有效的吗?

非常感谢任何帮助。


1
最后一个是从基数为256的数字转换而来,这是IP地址的有效表示。已知有一种隐藏URL的方案,其中使用该数字,类似于http://65536 - mellamokb
10个回答

21
IPAddress.TryParse() 的作用并不是检查字符串本身是否为有效的 IP 地址,而是检查该字符串的内容能否被解析(即转换)为有效的 IP 地址。
你测试用例中的所有选项实际上都可以解析为表示 IP 地址。问题在于,你的测试用例数据无效,或者你没有使用正确的工具来获得预期结果。
如果你想要专门测试一个有效的 IPv4,确保它恰好由 4 个四元组(每个四元组都是介于 0 到 255 的整数)组成,并且想要避免使用正则表达式,那么可以将其拆分后进行解析和验证。
public static bool IsIPv4(string value)
{
    var octets = value.Split('.');

    // if we do not have 4 octets, return false
    if (octets.Length!=4) return false;

    // for each octet
    foreach(var octet in octets) 
    {
        int q;
        // if parse fails 
        // or length of parsed int != length of octet string (i.e.; '1' vs '001')
        // or parsed int < 0
        // or parsed int > 255
        // return false
        if (!Int32.TryParse(octet, out q) 
            || !q.ToString().Length.Equals(octet.Length) 
            || q < 0 
            || q > 255) { return false; }

    }

    return true;
}

2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Skuami
1
维基百科关于IPv4的文章中使用了“八位组”一词,而非“四元组”。 - DavidRR

9
看起来,IPAddress.Parse的文档通过指出输入更少的部分对于输入A类和B类地址更为方便来解释了这种行为。如果您想强制使用四个部分的地址,在将其提供给IPAddress.TryParse之前,您可能需要检查地址中是否有三个句点。以下是一些参考代码:
// verify that IP consists of 4 parts
if (value.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Length == 4)
{
    IPAddress ipAddr;
    if (IPAddress.TryParse(value, out ipAddr))
    {
        // IP is valid
    }
    else
        // invalid IP
}
else
    // invalid IP

1
尝试使用以下代码: ValidIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"; - vml19

6

如果你想对输入进行严格的检查,那么可以比较解析后的 IPAddressToString() 版本,如果不同则拒绝该输入。

全零地址和其他特殊情况需要特别处理。


1
即使指定了四个八位字节,ToString() 的结果可能与原始表示不同。例如,以一个或多个 0 开头的八位字节被视为八进制数。举个例子,在维基百科上关于 IPv4 的文章中,ToString()0300.0000.0002.0353 转换为其十进制等效值 192.0.2.235。因此,一些人可能会发现使用 ToString() 比仅检查四个八位字节更具优势。 - DavidRR

5
我建议:
    public bool IsValidIp(string addr)
    {
        IPAddress ip;
        bool valid = !string.IsNullOrEmpty(addr) && IPAddress.TryParse(addr, out ip);
        return valid;
    }

4
这并没有回答问题,只是将空值检查添加到了问题的范围内。 - Nick Rippe
1
这正是他所询问的内容,请好好阅读问题。 - Mubashar
3
不,完全不是。 - Rushyo

4
    public static bool IsIPv4(string ipAddress)
    {
        return Regex.IsMatch(ipAddress, @"^\d{1,3}(\.\d{1,3}){3}$") &&
            ipAddress.Split('.').SingleOrDefault(s => int.Parse(s) > 255) == null;
    }

其他答案可能会允许IPv6,或者允许像“1.-0.1.1”这样的输入。


我喜欢这个,但是如果你指定一个无效的IP地址,比如10.300.400.10,它会从SingleOrDefault抛出异常。我将其更改为FirstOrDefault以进行修复... - Steve0212

2
ipString中的部分数量(每个部分由句点分隔)决定了IP地址的构造方式。一个部分地址直接存储在网络地址中。方便指定A类地址的两个部分地址将前导部分放在第一个字节中,将尾随部分放在网络地址的最右三个字节中。方便指定B类地址的三个部分地址将第一个部分放在第一个字节中,将第二个部分放在第二个字节中,将最后一个部分放在网络地址的最右两个字节中。例如:
部分数量和示例ipString IPv4地址为IPAddress
1 -- "65536" 0.0.255.255
2 -- "20.2" 20.0.0.2
2 -- "20.65535" 20.0.255.255
3 -- "128.1.2" 128.1.0.2
您可能需要参考MSDN文档http://msdn.microsoft.com/en-us/library/system.net.ipaddress.parse.aspx 您最好使用IPAddress.ToString()或正则表达式。

1
我有一个有效的正则表达式方法。出于某种原因,我认为重构为IPAddress.TryParse更加优雅,但实际上它似乎更加令人困惑 ;) - Martin Buberl
3
MSDN文档中有一个打字错误。65536被解析为0.1.0.0,而65535则被解析为0.0.255.255 - Martin Buberl

1

1
从那里开始,我不明白第二个例子是如何解析到所提到的结果value = "255.255.255" -> address = {255.255.0.255}。具体来说,为什么第三个八位组被清零而不是第一个或最后一个? - Davy8

1
虽然这个问题已经在两年前得到回答,但我认为将我今天寻找同样事物时找到的内容提出来是相关的。在找到这个页面后,我觉得自己太懒了,不想通过编程验证所有信息。
如果信息是通过一个“表单”和一个“文本框”接受的,最好使用一个“掩码文本框”代替。您可以强制用户以IP地址格式输入信息,而不必自己猜测。
用于此类验证的掩码是990.990.990.990,表示可选数字、可选数字、必填数字,因为1.1.1.1、10.10.10.10和123.123.123.123都是有效的IP地址格式。这些掩码与Microsoft在Access中使用的格式相同。

我认为当您可以在UI级别控制用户输入时,掩码文本框是一个好主意。无论如何,问题更关注的是为什么IPAddress.TryParse会以所描述的方式行为。 - Martin Buberl
但是,您所提倡的掩码不会接受321.321.321.321吗?这可能会有问题,因为IPv4地址中的任何一个八位组都不能超过十进制值255。 - DavidRR

0
以下代码使用正则表达式,由Roshe建议。
        string txt = textBox1.Text;

        string ValidIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9‌​]{2}|2[0-4][0-9]|25[0-5])$";    // IP validation 

        Regex r = new Regex(ValidIpAddressRegex, RegexOptions.IgnoreCase | RegexOptions.Singleline);
        Match m = r.Match(txt);

        if (!m.Success)
        {
          //Not a valid IP
        }

        else
        {


          //A valid IP

        }

0

这是有道理的,因为65536等于0x00010000或0.1.0.0。我猜TryParse也会接受十六进制数。无论如何,我认为你不会想从普通用户那里接受这样的值,但这取决于最终用户。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接