从 C 中的任何特定 CIDR 范围中选择随机 IP
Choosing A Random IP From Any Specific CIDR Range In C
我正在尝试创建一个函数,该函数将能够解析任何 IP/CIDR 范围并在该特定范围内选择一个随机 IP 作为 C 中的字符串(包括 /32
,其中每次只会 return 一个 IP 地址)。截至目前,我对它包括保留 IP(例如广播)没意见,如果我以后在排除这些 IP 时遇到问题,我会 post 一个单独的问题。
我对这个领域还很陌生,因为我还没有太多在整数位上使用按位运算符的经验(我了解按位运算符本身,但我正在尝试弄清楚如何将它们与网络和 IP)。我还阅读了 this 问题的大部分内容,这些问题给出了很多很棒的 advice/guidance (感谢 Ron Maupin 为我提供了这个),但我仍在努力让这个功能完全发挥作用。
我的代码几乎可以正常工作,但出于某种原因,使用 /8
CIDR 或任何小于 /24
的东西会导致奇怪的行为。使用 /16
和 /24
可以按预期工作(这些都是我到目前为止测试过的)。
这是我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <arpa/inet.h>
#include <time.h>
int main()
{
for (int i = 0; i < 25; i++)
{
// IP/CIDR.
char *sip = "10.0.0.0";
uint8_t cidr = 8;
// Randomize the rand() seed.
time_t t;
srand((unsigned) time(&t) + i);
// Create in_addr and convert the IP string to a 32-bit integer.
struct in_addr inaddr;
inet_aton(sip, &inaddr);
uint32_t ipaddr = inaddr.s_addr;
// Get the mask (the complement of 2 to the power of the CIDR minus one).
uint32_t mask = ((1 << cidr) - 1);
// Generate a random number using rand().
uint32_t randnum = rand(); // Also tried rand() % 256.
// Attempt to pick a random IP from the CIDR range. We shift left by the CIDR range since it's big endian.
uint32_t newIP = ipaddr & mask | ((0x0000ffff & randnum) << cidr);
// Convert the new IP to a string and print it.
struct in_addr ip;
ip.s_addr = newIP;
fprintf(stdout, "%s\n", inet_ntoa(ip));
}
return 0;
}
这只是从给定的 IP/CIDR 中随机选择一个 IP 25 次。使用 /8
(例如 10.0.0.0/8
)时,这是我收到的输出:
10.220.186.0
10.180.229.0
10.231.159.0
10.24.70.0
10.217.108.0
10.50.250.0
10.170.108.0
10.48.139.0
10.183.205.0
10.61.48.0
10.3.221.0
10.161.252.0
10.48.1.0
10.146.183.0
10.138.139.0
10.33.27.0
10.19.70.0
10.109.253.0
10.5.8.0
10.124.154.0
10.109.145.0
10.53.29.0
10.223.111.0
10.18.229.0
10.255.99.0
最后一个八位字节总是 0
。我想我在创建随机 IP 32 位整数时向左移动 CIDR 范围时做错了什么。但是,我不确定我应该在这里做什么。
当使用 /30
范围(例如 192.168.90.4/30
)时,这是我收到的输出:
192.168.90.68
192.168.90.196
192.168.90.68
192.168.90.68
192.168.90.68
192.168.90.4
192.168.90.196
192.168.90.68
192.168.90.196
192.168.90.68
192.168.90.132
192.168.90.4
192.168.90.196
192.168.90.68
192.168.90.196
192.168.90.196
192.168.90.4
192.168.90.68
192.168.90.132
192.168.90.4
192.168.90.68
192.168.90.68
192.168.90.132
192.168.90.196
192.168.90.196
它有时选择192.168.90.4
是正确的,但其他三个随机IP在/30
范围之外,但在192.168.90.0/24
.
范围内
当使用 /16
时(例如本例中的 172.16.0.0/16
),这是预期的输出:
172.16.35.154
172.16.97.234
172.16.31.37
172.16.201.87
172.16.57.212
172.16.254.128
172.16.183.172
172.16.54.210
172.16.248.145
172.16.186.83
172.16.250.34
172.16.250.160
172.16.23.185
172.16.125.238
172.16.206.16
172.16.57.32
172.16.65.137
172.16.202.94
172.16.164.138
172.16.241.182
172.16.154.186
172.16.197.103
172.16.184.21
172.16.96.172
172.16.195.86
这也适用于 /24
(例如 192.168.90.0/24
):
192.168.90.253
192.168.90.156
192.168.90.65
192.168.90.189
192.168.90.22
192.168.90.238
192.168.90.150
192.168.90.106
192.168.90.63
192.168.90.64
192.168.90.64
192.168.90.54
192.168.90.104
192.168.90.110
192.168.90.34
192.168.90.187
192.168.90.202
192.168.90.73
192.168.90.206
192.168.90.13
192.168.90.15
192.168.90.220
192.168.90.114
192.168.90.125
192.168.90.70
我想知道是否有人知道我在这里做错了什么。如果我也遗漏了一些明显的东西,我深表歉意。
我也在 Linux 上开发这个(Ubuntu 20.04 在 5.4.0
内核上)。
非常感谢任何帮助,感谢您抽出宝贵时间!
我使用 host-endian 计算重新设计了这个,并且还从循环中移出了很多本来不应该存在的东西:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <arpa/inet.h>
#include <time.h>
int main(int argc, char** argv)
{
if (argc < 3) {
printf("Usage: cidrrand net cidr_size\n");
exit(-1);
}
char *sip = argv[1];
uint8_t cidr = atoi(argv[2]);
srand(time(NULL));
struct in_addr inaddr;
inet_aton(sip, &inaddr);
uint32_t ipaddr = ntohl(inaddr.s_addr);
uint32_t host_mask = (1 << (32 - cidr)) - 1;
for (int i = 0; i < 25; i++)
{
uint32_t host_rand = rand();
// Attempt to pick a random IP from the CIDR range. We shift left by the CIDR range since it's big endian.
uint32_t newIP = (ipaddr & ~host_mask) | (host_mask & host_rand);
// Convert the new IP to a string and print it.
struct in_addr ip;
ip.s_addr = htonl(newIP);
fprintf(stdout, "%s\n", inet_ntoa(ip));
}
return 0;
}
播种随机数时,尽量只播种一次。除非您有与生成多个可复制系列相关的特定目标,否则不要乱用它。
这里的主题非常有用 - 感谢 Christian,并感谢 Scott 重新访问了初始代码版本。
以防万一有人在这里遇到类似的 IPv6 问题,我已经为 IPv4 和 IPv6 生成器创建了一个简短示例 here。
它包括地址和掩码的二进制表示,以便更好地理解。
我目前在 IPv6 中遇到的问题是重用来自单个 32 位随机值的所有可用八位字节,同时迭代需要根据网络掩码更改的 IPv6 八位字节:
https://github.com/defanator/cidr-random/blob/master/cidr_random6.c#L133-L150
无论如何,希望它能作为任何感兴趣的人的起点。
我正在尝试创建一个函数,该函数将能够解析任何 IP/CIDR 范围并在该特定范围内选择一个随机 IP 作为 C 中的字符串(包括 /32
,其中每次只会 return 一个 IP 地址)。截至目前,我对它包括保留 IP(例如广播)没意见,如果我以后在排除这些 IP 时遇到问题,我会 post 一个单独的问题。
我对这个领域还很陌生,因为我还没有太多在整数位上使用按位运算符的经验(我了解按位运算符本身,但我正在尝试弄清楚如何将它们与网络和 IP)。我还阅读了 this 问题的大部分内容,这些问题给出了很多很棒的 advice/guidance (感谢 Ron Maupin 为我提供了这个),但我仍在努力让这个功能完全发挥作用。
我的代码几乎可以正常工作,但出于某种原因,使用 /8
CIDR 或任何小于 /24
的东西会导致奇怪的行为。使用 /16
和 /24
可以按预期工作(这些都是我到目前为止测试过的)。
这是我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <arpa/inet.h>
#include <time.h>
int main()
{
for (int i = 0; i < 25; i++)
{
// IP/CIDR.
char *sip = "10.0.0.0";
uint8_t cidr = 8;
// Randomize the rand() seed.
time_t t;
srand((unsigned) time(&t) + i);
// Create in_addr and convert the IP string to a 32-bit integer.
struct in_addr inaddr;
inet_aton(sip, &inaddr);
uint32_t ipaddr = inaddr.s_addr;
// Get the mask (the complement of 2 to the power of the CIDR minus one).
uint32_t mask = ((1 << cidr) - 1);
// Generate a random number using rand().
uint32_t randnum = rand(); // Also tried rand() % 256.
// Attempt to pick a random IP from the CIDR range. We shift left by the CIDR range since it's big endian.
uint32_t newIP = ipaddr & mask | ((0x0000ffff & randnum) << cidr);
// Convert the new IP to a string and print it.
struct in_addr ip;
ip.s_addr = newIP;
fprintf(stdout, "%s\n", inet_ntoa(ip));
}
return 0;
}
这只是从给定的 IP/CIDR 中随机选择一个 IP 25 次。使用 /8
(例如 10.0.0.0/8
)时,这是我收到的输出:
10.220.186.0
10.180.229.0
10.231.159.0
10.24.70.0
10.217.108.0
10.50.250.0
10.170.108.0
10.48.139.0
10.183.205.0
10.61.48.0
10.3.221.0
10.161.252.0
10.48.1.0
10.146.183.0
10.138.139.0
10.33.27.0
10.19.70.0
10.109.253.0
10.5.8.0
10.124.154.0
10.109.145.0
10.53.29.0
10.223.111.0
10.18.229.0
10.255.99.0
最后一个八位字节总是 0
。我想我在创建随机 IP 32 位整数时向左移动 CIDR 范围时做错了什么。但是,我不确定我应该在这里做什么。
当使用 /30
范围(例如 192.168.90.4/30
)时,这是我收到的输出:
192.168.90.68
192.168.90.196
192.168.90.68
192.168.90.68
192.168.90.68
192.168.90.4
192.168.90.196
192.168.90.68
192.168.90.196
192.168.90.68
192.168.90.132
192.168.90.4
192.168.90.196
192.168.90.68
192.168.90.196
192.168.90.196
192.168.90.4
192.168.90.68
192.168.90.132
192.168.90.4
192.168.90.68
192.168.90.68
192.168.90.132
192.168.90.196
192.168.90.196
它有时选择192.168.90.4
是正确的,但其他三个随机IP在/30
范围之外,但在192.168.90.0/24
.
当使用 /16
时(例如本例中的 172.16.0.0/16
),这是预期的输出:
172.16.35.154
172.16.97.234
172.16.31.37
172.16.201.87
172.16.57.212
172.16.254.128
172.16.183.172
172.16.54.210
172.16.248.145
172.16.186.83
172.16.250.34
172.16.250.160
172.16.23.185
172.16.125.238
172.16.206.16
172.16.57.32
172.16.65.137
172.16.202.94
172.16.164.138
172.16.241.182
172.16.154.186
172.16.197.103
172.16.184.21
172.16.96.172
172.16.195.86
这也适用于 /24
(例如 192.168.90.0/24
):
192.168.90.253
192.168.90.156
192.168.90.65
192.168.90.189
192.168.90.22
192.168.90.238
192.168.90.150
192.168.90.106
192.168.90.63
192.168.90.64
192.168.90.64
192.168.90.54
192.168.90.104
192.168.90.110
192.168.90.34
192.168.90.187
192.168.90.202
192.168.90.73
192.168.90.206
192.168.90.13
192.168.90.15
192.168.90.220
192.168.90.114
192.168.90.125
192.168.90.70
我想知道是否有人知道我在这里做错了什么。如果我也遗漏了一些明显的东西,我深表歉意。
我也在 Linux 上开发这个(Ubuntu 20.04 在 5.4.0
内核上)。
非常感谢任何帮助,感谢您抽出宝贵时间!
我使用 host-endian 计算重新设计了这个,并且还从循环中移出了很多本来不应该存在的东西:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <arpa/inet.h>
#include <time.h>
int main(int argc, char** argv)
{
if (argc < 3) {
printf("Usage: cidrrand net cidr_size\n");
exit(-1);
}
char *sip = argv[1];
uint8_t cidr = atoi(argv[2]);
srand(time(NULL));
struct in_addr inaddr;
inet_aton(sip, &inaddr);
uint32_t ipaddr = ntohl(inaddr.s_addr);
uint32_t host_mask = (1 << (32 - cidr)) - 1;
for (int i = 0; i < 25; i++)
{
uint32_t host_rand = rand();
// Attempt to pick a random IP from the CIDR range. We shift left by the CIDR range since it's big endian.
uint32_t newIP = (ipaddr & ~host_mask) | (host_mask & host_rand);
// Convert the new IP to a string and print it.
struct in_addr ip;
ip.s_addr = htonl(newIP);
fprintf(stdout, "%s\n", inet_ntoa(ip));
}
return 0;
}
播种随机数时,尽量只播种一次。除非您有与生成多个可复制系列相关的特定目标,否则不要乱用它。
这里的主题非常有用 - 感谢 Christian,并感谢 Scott 重新访问了初始代码版本。
以防万一有人在这里遇到类似的 IPv6 问题,我已经为 IPv4 和 IPv6 生成器创建了一个简短示例 here。
它包括地址和掩码的二进制表示,以便更好地理解。
我目前在 IPv6 中遇到的问题是重用来自单个 32 位随机值的所有可用八位字节,同时迭代需要根据网络掩码更改的 IPv6 八位字节:
https://github.com/defanator/cidr-random/blob/master/cidr_random6.c#L133-L150
无论如何,希望它能作为任何感兴趣的人的起点。