写点什么

自己动手写 Docker 系列 -- 6.1 ip 分配管理

作者:
  • 2022 年 4 月 15 日
  • 本文字数:3198 字

    阅读完需:约 10 分钟

简介

在前面的文章中,我们完成了基本的容器操作,容器已经能在无网络情况下运行,接下来的文章中,将开始网络相关的部分,本地是编写一个 ip 分配工具

源码说明

同时放到了 Gitee 和 Github 上,都可进行获取



本章节对应的版本标签是:5.2,防止后面代码过多,不好查看,可切换到标签版本进行查看

代码实现

在本篇中将实现一个 ip 分配管理工具


主要的思路如下:


1 给定一个网段,能自动分配未使用的 IP 地址


2 能回收 IP 地址,提供给下次使用


IP 地址需要一个存储,目前如书中所说,简单采用字符串进行记录,一个位置就是一个 IP 标识位

IP 地址分配管理

下面的代码包含了 IP 地址的分配,回收和再分配


const ipamDefaultAllocatorPath = "/var/run/mydocker/network/ipam/subnet.json"
// IPAM 存放IP地址分配信息type IPAM struct { // 分配文件存放位置 SubnetAllocatorPath string // 网段和位图算法的数组map,key是网段,value是分配的位图数组 Subnets *map[string]string}
// 初始化一个IPAMd对象var ipAllocator = &IPAM{ SubnetAllocatorPath: ipamDefaultAllocatorPath,}
// 加载网段地址分配信息func (ipam *IPAM) load() error { if _, err := os.Stat(ipam.SubnetAllocatorPath); err != nil { if os.IsNotExist(err) { return nil } return err } subnetConfigFile, err := os.Open(ipam.SubnetAllocatorPath) defer subnetConfigFile.Close() if err != nil { return err } subnetJson := make([]byte, 2000) n, err := subnetConfigFile.Read(subnetJson) if err != nil { return err }
err = json.Unmarshal(subnetJson[:n], ipam.Subnets) if err != nil { return fmt.Errorf("dump allocation info err: %v", err) } log.Infof("load ipam file from: %s", subnetConfigFile.Name()) return nil}
// 存储网段地址分配信息func (ipam *IPAM) dump() error { ipamConfigFileDir, _ := path.Split(ipam.SubnetAllocatorPath) if _, err := os.Stat(ipamConfigFileDir); err != nil { if os.IsNotExist(err) { os.MkdirAll(ipamConfigFileDir, 0644) } else { return err } }
subnetConfigFile, err := os.OpenFile(ipam.SubnetAllocatorPath, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) defer subnetConfigFile.Close() if err != nil { return err }
ipamConfigJson, err := json.Marshal(ipam.Subnets) if err != nil { return err } _, err = subnetConfigFile.Write(ipamConfigJson) if err != nil { return err } log.Infof("dump ipam file from: %s", ipamConfigFileDir) return nil}
// Allocate 在网段中分配一个可用的IP地址func (ipam *IPAM) Allocate(subnet *net.IPNet) (ip net.IP, err error) { // 存储网段中地址分配信息的数组 ipam.Subnets = &map[string]string{}
// 从文件中加载已经分配了的网段信息 err = ipam.load() if err != nil { return nil, fmt.Errorf("load subnet file err: %v", err) }
// net.ipnet.nask.size() 返回网段的子网掩码的总长度和网段前面的固定位的长度 // 比如 127.0.0.0/8 网段的子网掩码是 255.0.0.0 // 返回的是前面255所对应的位数和总位数,即8和24 one, size := subnet.Mask.Size()
// 如果之前没有分配过这个网段,则初始化网段的分配配置 if _, exist := (*ipam.Subnets)[subnet.String()]; !exist { // 用0填满这个网段的配置, 1<<uint8(size-one)表示这个网段中有多少个可用的地址 // size - one 是子网掩码后面的网络位数,2^(size-one)(即1<<uint8(size-one))表示可用的IP数 (*ipam.Subnets)[subnet.String()] = strings.Repeat("0", 1<<uint8(size-one)) }
// 遍历网段的位图数组 for c := range (*ipam.Subnets)[subnet.String()] { // 找到网段中为0的项和数组序号,即可分配的IP if (*ipam.Subnets)[subnet.String()][c] == '0' { // 设置当前的序号值为1,即分配这个IP ipalloc := []byte((*ipam.Subnets)[subnet.String()])
// Go中字符串不能修改,通过转成byte数组,再转成字符串赋值 ipalloc[c] = '1' (*ipam.Subnets)[subnet.String()] = string(ipalloc)
// 这里的IP为初始IP,比如192.168.0.0/16,这里就是192.168.0.0 ip = subnet.IP
// 通过网段的IP与上面的偏移相加计算出分配的IP地址,由于IP地址是uint的一个数组 // 需要通过数组中的每一项加所需要的值,比如网段172.16.0.0/12,数组序号是65555 // 那么在[172,16,0,0]上依次加[uint8(65555 >> 24)、[uint8(65555 >> 16)、[uint8(65555 >> 8)、[uint8(65555 >> 8) // 即[0, 1, 0, 19],那么最后得到的172.17.0.19 for t := uint(4); t > 0; t -= 1 { []byte(ip)[4-t] += uint8(c >> ((t - 1) * 8)) }
// 由于IP是从1开始的,所以最后加1 ip[3] += 1 break } }
// 通过dump将分配结果保存到文件中 return ip, ipam.dump()}
// Release 地址释放func (ipam *IPAM) Release(subnet *net.IPNet, ipaddr *net.IP) error { ipam.Subnets = &map[string]string{}
// 从文件中加载网段分配信息 err := ipam.load() if err != nil { return fmt.Errorf("load subnet file err: %v", err) }
// 计算IP地址在网段位图数组中的索引位置 index := 0 // 将IP地址转换成4个字节的表现形式 releaseIp := ipaddr.To4() // 由于IP是从1开始分配的,所以转换成索引应减一 releaseIp[3] -= 1 // 与分配IP相反,释放IP获得索引的方式是将IP地址的每一位相减后分别左移将对应的数值加到索引上 for t := uint(4); t > 0; t -= 1 { index += int(releaseIp[t-1]-subnet.IP[t-1]) << ((4 - t) * 8) } log.Infof("release index: %d", index)
// 将分配的位图索引中的位置的值置为0 ipalloc := []byte((*ipam.Subnets)[subnet.String()]) ipalloc[index] = '0' (*ipam.Subnets)[subnet.String()] = string(ipalloc)
// 保存释放IP后的配置信息 return ipam.dump()}
复制代码

测试

写一个测试验证下


func TestIPAM_Allocate(t *testing.T) {  // 首先把文件删除了,清理重置下环境  _ = os.RemoveAll(ipamDefaultAllocatorPath)
// 每次释放和分配ip时,都需要重新调用下面的函数进行IPNet的获取,因为函数调用后,IPNet的值会发生变化 _, ipNet, _ := net.ParseCIDR("192.168.0.0/24") // 第一次分配 ip1, err := ipAllocator.Allocate(ipNet) assert.Equal(t, nil, err) assert.Equal(t, "192.168.0.1", ip1.String())
// 第二个ip分配 _, ipNet, _ = net.ParseCIDR("192.168.0.0/24") ip2, err := ipAllocator.Allocate(ipNet) assert.Equal(t, nil, err) assert.Equal(t, "192.168.0.2", ip2.String())
// 释放调第一个IP _, ipNet, _ = net.ParseCIDR("192.168.0.0/24") assert.Equal(t, nil, ipAllocator.Release(ipNet, &ip1))
// 能分配得第一个IP _, ipNet, _ = net.ParseCIDR("192.168.0.0/24") ip3, err := ipAllocator.Allocate(ipNet) assert.Equal(t, nil, err) assert.Equal(t, "192.168.0.1", ip3.String())
// 分配第三个IP _, ipNet, _ = net.ParseCIDR("192.168.0.0/24") ip4, err := ipAllocator.Allocate(ipNet) assert.Equal(t, nil, err) assert.Equal(t, "192.168.0.3", ip4.String())}
复制代码


发布于: 刚刚阅读数: 3
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
自己动手写Docker系列 -- 6.1 ip分配管理_Go_萧_InfoQ写作平台