IPAM

IPAM简单实现

IPAM(Internet Protocol Address Management,互联网协议地址管理)是一个系统,用于在企业环境中管理IP地址,确保IP地址的有效分配和回收,以及网络的合理组织和资源利用

本文实现简单的IPAM,包含Allocate、Release、CheckIsFree的功能。

实现原理

对于存储采用map,key为网段,value为在这个网段上的所有ip的分配情况,对于Allocate而言,只需要传入一个合法的subnet,即可进行分配一个此subnet中的空闲ip。具体的实现是,找到[]byte中第一个非网关为0的index,再通过index计算出首个的偏移量。

对于Release和CheckIsFree而言,就得要反着计算出index,来置为0或者判断是否为0。

整体实现上对于key是一个string,value是一个[]byte,每个value的length都是等于此网段可用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
62
63
64
65
66
67
68
69
70
71
72
import ipaddress


def get_info(ip: str):
network = ipaddress.ip_network(ip, False)
ints_r = [int(c) for c in str(network.network_address).split(".")]
ints = [int(c) for c in ip[:len(ip) - 3].split('.')]
idx = 0
for k in range(4):
idx += (ints[k] - ints_r[k]) << ((3 - k) * 8)
return idx, network


class Ipam:

def __init__(self):
self.net_map = {}

def allocate(self, sub_net):
def info(mask: str):
sp = mask.split(".")
# For convenience, IPv4 is defaulted here
r_ones = 0
for s in sp:
k = int(s)
for i in range(7, -1, -1):
if k | (1 << i) == k:
r_ones += 1
else:
return r_ones, 32
return r_ones, 32

network = ipaddress.ip_network(sub_net, False)
netmask = network.netmask
if network not in self.net_map:
ones, size = info(str(netmask))
self.net_map[network] = [0] * (1 << (size - ones))
q = -1
for idx, v in enumerate(self.net_map[network]):
# the gateway is first
if v == 0 and idx != 0:
q = idx
break
if q == -1:
raise ValueError("have no available ip in this subnet")
self.net_map[network][q] = 1
ints = [int(c) for c in str(network.network_address).split(".")]
for u in range(4):
ints[3 - u] += q % (1 << 8)
q //= (1 << 8)
return ipaddress.ip_address('.'.join([str(c) for c in ints]))

def release(self, ip: str):
idx, network = get_info(ip)
if network not in self.net_map:
raise ValueError('There is no network segment with this IP address')
self.net_map[network][idx] = 0

def check_is_free(self, ip: str):
idx, network = get_info(ip)
return self.net_map[network][idx] == 0


if __name__ == "__main__":
i = Ipam()
print(i.allocate("192.168.0.2/14"))
print(i.allocate("192.168.2.2/14"))
print(i.allocate("192.168.2.2/14"))
i.release("192.168.0.2/14")
print(i.check_is_free("192.168.0.2/14"))
print(i.allocate("192.168.0.2/14"))

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
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"net"
)

type IPAM struct {
netMap *map[string][]byte
}

func (ipam *IPAM) Allocate(subnet string) (ip net.IP, err error) {
_, ipNet, err := net.ParseCIDR(subnet)
if err != nil {
return nil, fmt.Errorf("非法host")
}
mask := ipNet.Mask
ones, bits := mask.Size()
if _, exist := (*ipam.netMap)[ipNet.String()]; !exist {
(*ipam.netMap)[ipNet.String()] = make([]byte, 1<<(bits-ones))
}
var k = -1
for idx, v := range (*ipam.netMap)[ipNet.String()] {
if idx != 0 && v == 0 {
(*ipam.netMap)[ipNet.String()][idx] = 1
k = idx
break
}
}
if k == -1 {
return nil, fmt.Errorf("此网段的ip已经分配完了")
}

ip = ipNet.IP
for i := uint(4); i > 0; i-- {
ip[4-i] += uint8(k >> ((i - 1) * 8))
}
return ip, nil
}

func (ipam *IPAM) Release(sip string) error {
ip, ipNet, err := net.ParseCIDR(sip)
if err != nil {
return fmt.Errorf("非法host")
}
if (*ipam.netMap)[ipNet.String()] == nil {
return fmt.Errorf("非法host")
}
idx := 0
for i := uint(4); i > 0; i-- {
idx += int(ip.To4()[4-i]-ipNet.IP[4-i]) << ((i - 1) * 8)
}
(*ipam.netMap)[ipNet.String()][idx] = 0
return nil
}

func main() {

ipam := &IPAM{
netMap: &map[string][]byte{},
}
ip, _ := ipam.Allocate("192.168.0.2/14")
fmt.Println(ip.String())
ip1, _ := ipam.Allocate("192.168.0.2/14")
fmt.Println(ip1.String())
ip2, _ := ipam.Allocate("192.168.0.2/14")
fmt.Println(ip2.String())
_ = ipam.Release("192.168.0.2/14")
ip3, _ := ipam.Allocate("192.168.0.2/14")
fmt.Println(ip3.String())
}

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

public class IPAM {

private Map<String, int[]> netMap;

public IPAM() {
this.netMap = new HashMap<>();
}

private int getIndex(String ip, String networkAddress) throws UnknownHostException {
byte[] ipAddr = InetAddress.getByName(ip).getAddress();
byte[] networkAddr = InetAddress.getByName(networkAddress).getAddress();
int index = 0;
for (int i = 0; i < ipAddr.length; i++) {
index = (index << 8) + ((ipAddr[i] & 0xFF) - (networkAddr[i] & 0xFF));
}
return index;
}

private String getNetworkAddress(String ip, int prefixLength) throws UnknownHostException {
InetAddress address = InetAddress.getByName(ip);
byte[] addr = address.getAddress();
int mask = 0xffffffff << (32 - prefixLength);
byte[] networkAddr = new byte[addr.length];
for (int i = 0; i < addr.length; i++) {
networkAddr[i] = (byte) (addr[i] & (mask >> (8 * (3 - i))));
}
return InetAddress.getByAddress(networkAddr).getHostAddress();
}

private int getPrefixLength(String subnet) {
return Integer.parseInt(subnet.split("/")[1]);
}

private int[] info(String mask) {
String[] parts = mask.split("\\.");
int rOnes = 0;
for (String part : parts) {
int k = Integer.parseInt(part);
for (int i = 7; i >= 0; i--) {
if ((k & (1 << i)) != 0) {
rOnes++;
} else {
return new int[]{rOnes, 32};
}
}
}
return new int[]{rOnes, 32};
}

public String allocate(String subNet) throws UnknownHostException {
String[] parts = subNet.split("/");
String ip = parts[0];
int prefixLength = Integer.parseInt(parts[1]);

String networkAddress = getNetworkAddress(ip, prefixLength);
InetAddress network = InetAddress.getByName(networkAddress);

if (!netMap.containsKey(network.getHostAddress())) {
int[] maskInfo = info(networkAddress);
int ones = maskInfo[0];
int size = maskInfo[1];
netMap.put(network.getHostAddress(), new int[1 << (size - ones)]);
}

int q = -1;
for (int idx = 0; idx < netMap.get(network.getHostAddress()).length; idx++) {
if (netMap.get(network.getHostAddress())[idx] == 0 && idx != 0) {
q = idx;
break;
}
}

if (q == -1) {
throw new IllegalArgumentException("No available IP in this subnet");
}

netMap.get(network.getHostAddress())[q] = 1;

byte[] ints = InetAddress.getByName(network.getHostAddress()).getAddress();
for (int u = 0; u < 4; u++) {
ints[3 - u] += q % 256;
q /= 256;
}

return InetAddress.getByAddress(ints).getHostAddress();
}

public void release(String ip) throws UnknownHostException {
String[] parts = ip.split("/");
String ipAddr = parts[0];
int prefixLength = Integer.parseInt(parts[1]);

String networkAddress = getNetworkAddress(ipAddr, prefixLength);
int index = getIndex(ipAddr, networkAddress);

if (!netMap.containsKey(networkAddress)) {
throw new IllegalArgumentException("No network segment with this IP address");
}
netMap.get(networkAddress)[index] = 0;
}

public boolean checkIsFree(String ip) throws UnknownHostException {
String[] parts = ip.split("/");
String ipAddr = parts[0];
int prefixLength = Integer.parseInt(parts[1]);

String networkAddress = getNetworkAddress(ipAddr, prefixLength);
int index = getIndex(ipAddr, networkAddress);

return netMap.get(networkAddress)[index] == 0;
}

public static void main(String[] args) {
try {
IPAM IPAM = new IPAM();
System.out.println(IPAM.allocate("192.168.0.2/14"));
System.out.println(IPAM.allocate("192.168.2.2/14"));
System.out.println(IPAM.allocate("192.168.2.2/14"));
IPAM.release("192.168.0.2/14");
System.out.println(IPAM.checkIsFree("192.168.0.2/14"));
System.out.println(IPAM.allocate("192.168.0.2/14"));
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}

代码实现中用到了位运算的技巧。对于mask: 255.255.0.0/16255.255.0.0/16而言,也就是1111111111111111000000000000000011111111111111110000000000000000,实际可用的ip就是1<<(3216)1 << (32 - 16)。在计算idx对于每一位的偏移量是可以循环使用index%(1<<8)index>>=8index\%(1 << 8)和index >>= 8来计算。也可以利用int8的特性来计算(golang实现)。


IPAM
http://example.com/2024/06/13/IPAM/
作者
ykexc
发布于
2024年6月13日
许可协议