【问题标题】:Lua function check if ipv4 or ipv6 or stringLua函数检查ipv4或ipv6或字符串
【发布时间】:2012-06-14 02:41:16
【问题描述】:

我想要一个函数,我可以将空格修剪的字符串传递给它,它将返回
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 三个 .然后检查字符串是所有
数字,并且 . 被计为数字,最后一次检查字符串
将在 if 语句的末尾,所以如果它不是 ipv4/6 并且它是一个字符串然后
它返回 3

【问题讨论】:

    标签: lua pattern-matching ip-address ipv6 ipv4


    【解决方案1】:

    由于Lua's regular expressions 的表达力不够,您必须继续使用迭代算法。

    我建议你查看我在意大利语维基百科上发布的one(以前是fully tested):

    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
    

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

    【讨论】:

      【解决方案2】:

      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 个组,其中一个空组包含两个连续的冒号。

      【讨论】:

      • 谢谢!我将尝试将其用作瑞典语维基百科的模板。这正是我想要的!
      • 完美!它还与 localhost ::1 和许多其他匹配。现在它也被用于葡萄牙语维基百科。 :)
      • 无论是这个还是标记为接受的答案的解决方案都不能处理带有随机垃圾前缀/后缀的地址(例如 xxx127.0.0.0 被认为是有效的)。恕我直言 ^$ 符号应添加到模式中。
      • 已更新以解决 @JeFf 的评论并添加对 ipv6 中一个空组的支持。
      【解决方案3】:

      有趣的是,以上答案都没有考虑原始问题的测试示例,因为使用它们,上述所有检查都会失败(因为#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
      

      【讨论】:

        【解决方案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 不是个人代码编写服务。然而,我会给你怀疑的好处,因为你看起来很新,这可能会使其他人受益。上面的代码是基本的,所以如果它没有捕捉到我不知道的边缘测试用例,请随时更新它。

        【讨论】:

        • 我喜欢这种方法,但它说 999.12345.0.0001 是 IPv4,它不能识别协定的 IPv6 地址:-/
        • 给我一点,我会尝试更新答案以处理收缩的 IPv6 格式,并为 IPv4 地址格式中的每个 ip 块添加有效性检查。
        • 嗯,检查 IPv6 地址的所有变体并不是很简单。如果其他人有时间并想解决它-请随意。此外,需要更新对 IPv4 地址的检查以处理基于二进制的 ip(格式如 1111000.00001111.01010101.111111)
        • 迈克,至少对我来说十进制表示法对于 IPv4 和十六进制对于 IPv6 就足够了。否则我们可以开始检查所有的碱基,这太荒谬了。
        • 有没有人用像“10.1.2.3.4.5”这样的IPV4地址测试过这个?当它应该失败时似乎通过了
        【解决方案5】:

        这似乎可以通过使用正则表达式轻松完成。 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 代码,因为它看起来像家庭作业/学习练习和完整的答案对你的伤害大于对你的帮助。

        【讨论】:

        • 谢谢,但这些都不是真正的 lua 代码,所以如果你能想到一个完整的代码,它就行不通了。
        • 我想我已经说清楚了。这是伪代码——你需要将它转换为实际的 lua 源代码。
        • @Mick:“我认为帮助请求很明显”“帮助”和“请给我代码!”之间有区别。 Bartek 为您提供帮助。你似乎想要后者。
        • @Mick:我“知道吗?”不,我在某个地方没有那个功能。我当然可以。但我不会,因为为你工作似乎是你会接受的唯一答案。
        • @Nicol Bolas,只是想知道您加入帮助论坛的动机是什么?我的意思是“曾经”,因为你根本没有任何帮助,只是重申我自己的要求,而另一个人的帖子毫无意义,谢谢你的时间!
        猜你喜欢
        • 1970-01-01
        • 2013-01-04
        • 2021-05-16
        • 2013-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-18
        • 1970-01-01
        相关资源
        最近更新 更多