【问题标题】:Check if IP address is in private network space检查IP地址是否在私网空间
【发布时间】:2017-05-05 13:51:29
【问题描述】:

我有一个程序,它接受来自客户端的 URL 并使用 net/http 包获取它们。在进行进一步处理之前,我想检查 URL 是否映射到私有(不可路由/RFC1918 网络)地址空间。

直接的方法是执行显式 DNS 请求并检查已知私有范围的地址。之后,对 URL 执行 HTTP GET 请求。

有没有更好的方法来做到这一点?最好与 http.Client 集成,以便它可以作为 GET 请求的一部分执行。

【问题讨论】:

  • 什么是“私有网络空间”? RFC1918 网络?
  • 我遇到了这个问题并发现:没有更好的选择。见github.com/mhausenblas/clump/blob/master/main.go#L83
  • 对不起,是的,RFC1918 网络。我更新了问题以说明这一点。
  • 你到底想做什么?如果主机在专用网络中,您只是想记录,还是在发出请求之前需要做其他事情? @MichaelHausenblas:仅供参考net.IPNet 有一个Contains method
  • 对于一个用例,我想中止对 RFC1918 网络的 GET 请求。对于另一种情况,我只记录 IP 地址以及它是私有的还是公共的。

标签: go network-programming ip


【解决方案1】:

您可能还希望包括对环回(IPv4 或 IPv6)和/或 IPv6 链路本地或唯一本地地址的检查。这是一个包含 RFC1918 地址列表以及其他地址列表的示例,并以 isPrivateIP(ip net.IP) 对它们进行简单检查:

var privateIPBlocks []*net.IPNet

func init() {
    for _, cidr := range []string{
        "127.0.0.0/8",    // IPv4 loopback
        "10.0.0.0/8",     // RFC1918
        "172.16.0.0/12",  // RFC1918
        "192.168.0.0/16", // RFC1918
        "169.254.0.0/16", // RFC3927 link-local
        "::1/128",        // IPv6 loopback
        "fe80::/10",      // IPv6 link-local
        "fc00::/7",       // IPv6 unique local addr
    } {
        _, block, err := net.ParseCIDR(cidr)
        if err != nil {
            panic(fmt.Errorf("parse error on %q: %v", cidr, err))
        }
        privateIPBlocks = append(privateIPBlocks, block)
    }
}

func isPrivateIP(ip net.IP) bool {
    if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
        return true
    }

    for _, block := range privateIPBlocks {
        if block.Contains(ip) {
            return true
        }
    }
    return false
  }

【讨论】:

【解决方案2】:

使用 Go 1.17(2021 年第四季度,5 年后)应该更容易做到这一点,如 reported by Go 101

commit c73fcccCL 272668

net:加IP.IsPrivate()

添加 IsPrivate() 助手以根据 RFC 1918 和 RFC 4193 检查 IP 是否为私有

这修复了由Aaran McGuire 提出的golang issue 29146

net 包似乎有很多助手来报告 IP 是什么。例如:

  • IsLoopback()
  • IsMulticast()
  • IsInterfaceLocalMulticast()

但是,如果 IP 地址在私有范围内(RFC 1918RFC 4193),则没有帮助报告。

【讨论】:

  • @StefanOS 感谢您的编辑。非常感谢。
  • 顺便说一句,一旦 Go 1.17 发布,我希望将接受的答案更改为这个答案。
【解决方案3】:
package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println(privateIPCheck("1.1.1.1"))  // False since this is not a private IP
    fmt.Println(privateIPCheck("10.8.0.1")) // True: Since this is a private ip.
}

// Check if a ip is private.
func privateIPCheck(ip string) bool {
    ipAddress := net.ParseIP(ip)
    return ipAddress.IsPrivate()
}

这需要 Go 1.17。

【讨论】:

    【解决方案4】:

    似乎没有比我描述的更好的方法了。结合@MichaelHausenblas 的代码和@JimB 的建议,我的代码最终变成了这样。

    func privateIP(ip string) (bool, error) {
        var err error
        private := false
        IP := net.ParseIP(ip)
        if IP == nil {
            err = errors.New("Invalid IP")
        } else {
            _, private24BitBlock, _ := net.ParseCIDR("10.0.0.0/8")
            _, private20BitBlock, _ := net.ParseCIDR("172.16.0.0/12")
            _, private16BitBlock, _ := net.ParseCIDR("192.168.0.0/16")
            private = private24BitBlock.Contains(IP) || private20BitBlock.Contains(IP) || private16BitBlock.Contains(IP)
        }
        return private, err
    }
    

    【讨论】:

    • 这里缺少一些东西。您检查 IP,但我可以创建一个 DNS,它将在第一次呼叫时回答公共 IP,然后在第二次呼叫时回答 127.0.0.1。这称为 DNS 重新绑定 en.wikipedia.org/wiki/DNS_rebinding 你是如何保护这个的?
    • 我同意这可能是一个问题,具体取决于您的用例,据我所知,使用 http.Client 无法防范这种情况。
    • 我找到了一个方法 :) 这篇文章解释得很好:linkai.io/blog/securely-issuing-http-requests-from-the-cloud 我尝试(并应用)了它,我没有问题:+1:
    • @StefanOS 从 lyrixx 提到的 linkai.io 文章中总结:您将 http.Clients 底层 TCP 和 TLS 机制本身限制为在构建时不向某些 IP 发出请求。这样,实际请求甚至重定向在完成的那一刻就会被检查,并且没有时间问题。至少在不允许请求时,我想您还可以更改已解析 IP 地址的域(已检查)并向该已检查 IP 发出请求,但我不确定这是否足够。 http.Client 的另一种方法对我来说似乎更干净,更容易推理。
    猜你喜欢
    • 2012-11-28
    • 2012-01-25
    • 2015-11-03
    • 2017-09-02
    • 2013-08-22
    • 1970-01-01
    • 2013-07-29
    • 2010-12-02
    • 2019-10-30
    相关资源
    最近更新 更多