编程笔记

lifelong learning & practice makes perfect

Go|如何判断两个IP是否在同一个网段

在网络编程和管理中,判断两个IP地址是否位于同一个子网或网段是一个常见的需求。本文将详细解释其背后的原理,并提供一个使用Go语言实现的简洁方案。

原理

要理解如何判断IP地址是否在同一网段,首先需要了解IP地址和子网掩码的基本概念。

  1. IP地址 (IP Address): 一个32位(IPv4)的二进制数字,通常为了方便记忆和使用,会用点分十进制表示(如 192.168.1.10)。它由两部分组成:

    • 网络部分 (Network Part): 标识设备所在的网络。
    • 主机部分 (Host Part): 标识网络中的具体设备。
  2. 子网掩码 (Subnet Mask): 同样是一个32位的二进制数字,其作用就是区分IP地址中的网络部分和主机部分。子网掩码中连续的 1 代表网络位,连续的 0 代表主机位。例如,255.255.255.0 的二进制是 11111111.11111111.11111111.00000000

  3. 网络地址 (Network Address): 将IP地址和子网掩码进行按位与(Bitwise AND)运算,得到的结果就是该IP地址所在的网络地址。

判断方法
如果两个IP地址与同一个子网掩码进行“按位与”运算后,得到的网络地址是相同的,那么这两个IP地址就属于同一个网段。

举例

  • IP 1: 192.168.1.10
  • IP 2: 192.168.1.20
  • 子网掩码: 255.255.255.0

计算过程:

  • 192.168.1.10 & 255.255.255.0 = 192.168.1.0 (网络地址1)
  • 192.168.1.20 & 255.255.255.0 = 192.168.1.0 (网络地址2)

因为两个网络地址相同,所以这两个IP在同一个网段。


Go语言实现

Go语言的 net 标准库提供了强大的网络功能,可以非常方便地实现这个判断。最地道(idiomatic)的方法是使用CIDR(无类别域间路由)地址来定义一个网络,然后检查两个IP是否都属于这个网络。

以下是一个完整的实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"fmt"
"net"
)

// AreInSameSubnetCIDR 检查两个IP地址是否在同一个由CIDR表示的子网中。
// ip1Str: 第一个IP地址字符串
// ip2Str: 第二个IP地址字符串
// cidrStr: 定义子网的CIDR字符串,例如 "192.168.1.0/24"
func AreInSameSubnetCIDR(ip1Str, ip2Str, cidrStr string) (bool, error) {
// 解析CIDR,获取网络信息(包括网络地址和子网掩码)
_, ipNet, err := net.ParseCIDR(cidrStr)
if err != nil {
return false, fmt.Errorf("无效的CIDR地址: %s", cidrStr)
}

// 解析第一个IP
ip1 := net.ParseIP(ip1Str)
if ip1 == nil {
return false, fmt.Errorf("无效的IP地址: %s", ip1Str)
}

// 解析第二个IP
ip2 := net.ParseIP(ip2Str)
if ip2 == nil {
return false, fmt.Errorf("无效的IP地址: %s", ip2Str)
}

// 使用 IPNet.Contains() 方法判断两个IP是否都属于该网络
return ipNet.Contains(ip1) && ipNet.Contains(ip2), nil
}

func main() {
// 示例1: 两个IP在同一个子网
ip1 := "192.168.1.10"
ip2 := "192.168.1.200"
cidr := "192.168.1.0/24"
same, err := AreInSameSubnetCIDR(ip1, ip2, cidr)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Printf("IP %s 和 %s 是否在子网 %s 中? %t\n", ip1, ip2, cidr, same)
}

// 示例2: 两个IP不在同一个子网
ip3 := "192.168.2.50"
same, err = AreInSameSubnetCIDR(ip1, ip3, cidr)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Printf("IP %s 和 %s 是否在子网 %s 中? %t\n", ip1, ip3, cidr, same)
}

// 示例3: 无效的CIDR
_, err = AreInSameSubnetCIDR(ip1, ip2, "192.168.1.0/33")
if err != nil {
fmt.Println("错误:", err)
}
}

代码解释

  1. net.ParseCIDR(cidrStr): 这个函数会解析像 "192.168.1.0/24" 这样的字符串。它返回网络地址 net.IP 和一个 *net.IPNet 对象。*net.IPNet 对象代表了一个IP网络,包含了网络地址和掩码。
  2. net.ParseIP(ipStr): 用于将IP地址字符串解析为 net.IP 类型。
  3. ipNet.Contains(ip): 这是 *net.IPNet 类型的一个非常有用的方法。它会检查给定的 net.IP 是否属于该网络,自动处理了“按位与”和比较的逻辑。

这种方法比手动进行位运算更简洁、更安全,并且能同时处理IPv4和IPv6.

欢迎关注我的其它发布渠道