6

我想要一个函数,我可以将空格修剪的字符串传递给它,它将返回
0 表示错误(不是字符串) 1 表示 ipv4 2 表示 ipv6 3 表示不是 ip 的字符串。

IPv6 有以下规则:

ipv6 由 8 组 16 位十六进制值表示,用冒号 (:) 分隔
十六进制数字不区分大小写
缩写规则:
1:省略 16 位值中的前导零
2:将一组或多组连续零替换为双冒号

wiki 示例显示 3 种方式都是相同的 ipv6:

fe80:0000:0000:0000:0202:b3ff:fe1e:8329
fe80:0:0:0:202:b3ff:fe1e:8329
fe80::202:b3ff:fe1e:8329 

我有理由确定您只需检查三个 ipv4 。然后检查字符串是否为所有
数字,并且 .'s 被计为数字,最后一次检查字符串
将在 if 语句的末尾,所以如果它不是 ipv4/6 并且它是一个字符串,那么
它返回 3

4

5 回答 5

9

Mike 的解决方案很好,但可以通过多种方式进行改进。在目前的形式中,它不会进行 ipv6 地址检查,但很容易修复。ipv6 检查在和(将两者都识别为有效地址)和(将其识别为字符串)等内容"1050!0!0+0-5@600$300c#326b""1050:0:0:0:5:600:300c:326babcdef"失败"1050:::600:5:1000::"

这是改进的版本(假定 IPv4 是十进制数,而 IPv6 假定是十六进制数):

function GetIPType(ip)
  local R = {ERROR = 0, IPV4 = 1, IPV6 = 2, STRING = 3}
  if type(ip) ~= "string" then return R.ERROR end

  -- check for format 1.11.111.111 for ipv4
  local chunks = {ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
  if #chunks == 4 then
    for _,v in pairs(chunks) do
      if tonumber(v) > 255 then return R.STRING end
    end
    return R.IPV4
  end

  -- check for ipv6 format, should be 8 'chunks' of numbers/letters
  -- without leading/trailing chars
  -- or fewer than 8 chunks, but with only one `::` group
  local chunks = {ip:match("^"..(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$")))}
  if #chunks == 8
  or #chunks < 8 and ip:match('::') and not ip:gsub("::","",1):match('::') then
    for _,v in pairs(chunks) do
      if #v > 0 and tonumber(v, 16) > 65535 then return R.STRING end
    end
    return R.IPV6
  end

  return R.STRING
end

要检查的脚本:

local IPType = {[0] = "Error", "IPv4", "IPv6", "string"}
local ips = {
    "128.1.0.1", -- ipv4
    "223.255.254.254", -- ipv4
    "999.12345.0.0001", -- invalid ipv4
    "1050:0:0:0:5:600:300c:326b", -- ipv6
    "1050!0!0+0-5@600$300c#326b", -- string
    "1050:0:0:0:5:600:300c:326babcdef", -- string
    "1050:0000:0000:0000:0005:0600:300c:326b", -- ipv6
    "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", -- ipv6
    "fe80:0:0:0:202:b3ff:fe1e:8329", -- ipv6
    "fe80::202:b3ff:fe1e:8329", -- ipv6
    "1050:::600:5:1000::", -- contracted ipv6
    "::", -- ipv6
    "::1", -- ipv6
    "::1::", -- string
    "129.garbage.9.1", -- string
    "xxx127.0.0.0", -- error
    "xxx1050:0000:0000:0000:0005:0600:300c:326b", -- string
    129.10 -- error
}
for k,v in pairs(ips) do
    print(v, IPType[GetIPType(v)])
end

和输出:

128.1.0.1   IPv4
223.255.254.254 IPv4
999.12345.0.0001    string
1050:0:0:0:5:600:300c:326b  IPv6
1050!0!0+0-5@600$300c#326b  string
1050:0:0:0:5:600:300c:326babcdef    string
1050:0000:0000:0000:0005:0600:300c:326b IPv6
fe80:0000:0000:0000:0202:b3ff:fe1e:8329 IPv6
fe80:0:0:0:202:b3ff:fe1e:8329   IPv6
fe80::202:b3ff:fe1e:8329    IPv6
1050:::600:5:1000:: IPv6
::  IPv6
::1 IPv6
::1::   string
129.garbage.9.1 string
xxx127.0.0.0    string
xxx1050:0000:0000:0000:0005:0600:300c:326b  string
129.1   Error

于 2018 年 9 月 6 日更新,添加了地址之前/之后的垃圾处理和检查协定的 ipv6,这允许少于 8 个组,其中一个空组包含两个连续的冒号。

于 2013-05-20T06:17:16.710 回答
4

这似乎是一个需要解决的非常基本的问题。我认为这个功能可以满足您的需求...

function GetIPType(ip)
    -- must pass in a string value
    if ip == nil or type(ip) ~= "string" then
        return 0
    end

    -- check for format 1.11.111.111 for ipv4
    local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
    if (#chunks == 4) then
        for _,v in pairs(chunks) do
            if (tonumber(v) < 0 or tonumber(v) > 255) then
                return 0
            end
        end
        return 1
    else
        return 0
    end

    -- check for ipv6 format, should be 8 'chunks' of numbers/letters
    local _, chunks = ip:gsub("[%a%d]+%:?", "")
    if chunks == 8 then
        return 2
    end

    -- if we get here, assume we've been given a random string
    return 3
end

使用以下代码对其进行了测试:

local IPType = {
    [0] = "Error",
    [1] = "IPv4",
    [2] = "IPv6",
    [3] = "string",
}


local ips = {
    "128.1.0.1",        -- ipv4
    "223.255.254.254",  -- ipv4
    "999.12345.0.0001",     -- invalid ipv4
    "1050:0:0:0:5:600:300c:326b",               -- ipv6
    "1050:0000:0000:0000:0005:0600:300c:326b",  -- ipv6
    "1050:::600:5:1000::",  -- contracted ipv6
    "129.garbage.9.1",  -- string
    129.10              -- error
}

for k,v in pairs(ips) do
    print(v, IPType[GetIPType(v)])
end

这产生了这个输出:

128.1.0.1   IPv4
223.255.254.254 IPv4
1050:0:0:0:5:600:300c:326b  IPv6
1050:0000:0000:0000:0005:0600:300c:326b IPv6
129.garbage.9.1 string
129.1   Error

将来,如果您实际发布您为解决特定问题而尝试编写的代码,您将获得更多有用的反馈,并让我们知道您需要帮助的地方。如常见问题解答中所述,SO 不是个人代码编写服务。然而,我会给你怀疑的好处,因为你看起来很新,这可能会使其他人受益。上面的代码是基本的,所以如果它没有捕获我不知道的边缘测试用例,请随时更新它。

于 2012-06-11T16:36:26.433 回答
2

这似乎可以通过使用正则表达式轻松完成。lua 有很多正则表达式库。

但是,如果您不愿意或无法使用它们,我会这样做:

Start in ipv4 state
Take a character until string ends
    switch(state)
    ipv4:
        if it's a dot, check if we loaded at least one number
        if it's a number, check if it isn't the 4th in row
        if it's anything else, set state to ipv6 and proceed in this state
    ipv6:
        if it's a ':', check if we didnt exceed maximum number of segments
        if it's a number or letter<a;f> check if it isn't 5th in row
        in case anything breaks, return 3
    end

我没有发布完整的 lua 代码,因为它看起来像家庭作业/学习练习和完整的答案对你的伤害比对你的帮助更大。

于 2012-06-11T07:34:40.210 回答
1

有趣的是,上述答案都没有考虑原始问题的测试示例,因为使用它们,上述所有检查都会失败(因为#3):

fe80:0000:0000:0000:0202:b3ff:fe1e:8329
fe80:0:0:0:202:b3ff:fe1e:8329
fe80::202:b3ff:fe1e:8329 (!)

IPv6 表示规则说:

一个或多个连续的零值组可以使用两个连续的冒号 (::), 1替换为单个空组,但替换只能在地址中应用一次,因为多次出现会产生模棱两可的表示。 https://en.wikipedia.org/wiki/IPv6_address#Representation

由于 Lua 模式不支持Alternation,因此无法使用单个模式检查 IPv6。您可能会看到 David M. Syzdek 关于 IPv6 Regex 复杂性的回答:https ://stackoverflow.com/a/17871737/1895269

不过,更符合标准的方法是对 Paul Kulchenko 的回答进行以下改进:

function GetIPType(ip)
  local R = {ERROR = 0, IPV4 = 1, IPV6 = 2, STRING = 3}
  if type(ip) ~= "string" then return R.ERROR end

  -- check for format 1.11.111.111 for ipv4
  local chunks = { ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$") }
  if (#chunks == 4) then
    for _,v in pairs(chunks) do
      if tonumber(v) > 255 then return R.STRING end
    end
    return R.IPV4
  end


  -- check for ipv6 format, should be max 8 'chunks' of numbers/letters
  local addr = ip:match("^([a-fA-F0-9:]+)$")
  if addr ~= nil and #addr > 1 then
    -- address part
    local nc, dc = 0, false      -- chunk count, double colon
    for chunk, colons in addr:gmatch("([^:]*)(:*)") do
      if nc > (dc and 7 or 8) then return R.STRING end    -- max allowed chunks
      if #chunk > 0 and tonumber(chunk, 16) > 65535 then
        return R.STRING
      end
      if #colons > 0 then
        -- max consecutive colons allowed: 2
        if #colons > 2 then return R.STRING end
        -- double colon shall appear only once
        if #colons == 2 and dc == true then return R.STRING end
        if #colons == 2 and dc == false then dc = true end
      end
      nc = nc + 1      
    end
    return R.IPV6
  end


  return R.STRING
end

要检查的脚本:

local IPType = {[0] = "Error", "IPv4", "IPv6", "string"}
local ips = {
  "128.1.0.1",    -- ipv4
  "223.255.254.254",  -- ipv4
  "999.12345.0.0001",   -- invalid ipv4
  "1050:0:0:0:5:600:300c:326b",         -- ipv6
  "1050!0!0+0-5@600$300c#326b",         -- string
  "1050:0:0:0:5:600:300c:326babcdef",     -- string
  "1050:0000:0000:0000:0005:0600:300c:326b",  -- ipv6
  "1050:::600:5:1000::",  -- contracted ipv6 (invalid)
  "fe80::202:b3ff:fe1e:8329",   -- shortened ipv6
  "fe80::202:b3ff::fe1e:8329",  -- shortened ipv6 (invalid)
  "fe80:0000:0000:0000:0202:b3ff:fe1e:8329:abcd",  -- too many groups
  "::1",   -- valid IPv6
  "::",  -- valid IPv6
  ":",   -- string
  "129.garbage.9.1",  -- string
  129.10        -- error
}
for k,v in pairs(ips) do
  print(v, IPType[GetIPType(v)])
end

和输出:

128.1.0.1       IPv4
223.255.254.254 IPv4
999.12345.0.0001        string
1050:0:0:0:5:600:300c:326b      IPv6
1050!0!0+0-5@600$300c#326b      string
1050:0:0:0:5:600:300c:326babcdef        string
1050:0000:0000:0000:0005:0600:300c:326b IPv6
1050:::600:5:1000::     string
fe80::202:b3ff:fe1e:8329        IPv6
fe80::202:b3ff::fe1e:8329       string
fe80:0000:0000:0000:0202:b3ff:fe1e:8329:abcd    string
::1     IPv6
::      IPv6
:       string
129.garbage.9.1 string
129.1   Error
于 2017-07-12T10:55:55.543 回答
0

由于Lua 的正则表达式没有足够的表达力,您必须继续使用迭代算法。

我建议您查看我在意大利语维基百科上发布的那个(已经过全面测试):

local R = {ERROR = 0, IPV4 = 1, IPV6 = 2, STRING = 3}

function is_ipv4(str)
    local s = str:gsub("/[0-9]$", ""):gsub("/[12][0-9]$", ""):gsub("/[3][0-2]$", "")
    
    if not s:find("^%d+%.%d+%.%d+%.%d+$") then
        return nil
    end
    
    for substr in s:gmatch("(%d+)") do
        if not substr:find("^[1-9]?[0-9]$")
                and not substr:find("^1[0-9][0-9]$")
                and not substr:find( "^2[0-4][0-9]$")
                and not substr:find("^25[0-5]$") then
            return nil
        end
    end
    
    return R.IPV4
end

function is_ipv6(str)
    local s = str
    
    if not (s:find("^%w+:%w+:%w+:%w+:%w+:%w+:%w+:%w+$")          -- there are exactly seven ":"
                or (s:find("^%w*:%w*:%w*:?%w*:?%w*:?%w*:?%w*$")  -- otherwise there are two to six sei ":"
                    and s:find("::")))                           -- and there must be the substring "::"
            or s:find("::.*::")                                  -- but there cannot be neither two substrings "::"
            or s:find(":::") then                                -- nor a substring ":::"
        return nil
    end
    
    for substr in s:gmatch("(%w+)") do
        if not substr:find("^[0-9A-Fa-f][0-9A-Fa-f]?[0-9A-Fa-f]?[0-9A-Fa-f]?$") then
            return nil
        end
    end
    
    return R.IPV6
end

function ip_type(str)
    if type(str) ~= "string" then
        return R.ERROR
    else
        return is_ipv4(str) or is_ipv6(str) or R.STRING
    end
end

ip_type()编辑:我按照 OP 的要求更改了函数的输出。

于 2020-07-22T12:45:34.370 回答