首页 > 解决方案 > 从 C 中的任何特定 CIDR 范围中选择一个随机 IP

问题描述

我正在尝试创建一个函数,该函数将能够解析任何 IP/CIDR 范围并在此特定范围内选择一个随机 IP 作为 C 中的字符串(包括 a /32,它每次只返回单个 IP 地址)。截至目前,我很好,包括保留的 IP(例如广播),如果我将来无法排除这些,我会发布一个单独的问题。

我对这个领域还很陌生,因为我还没有太多在整数位​​上使用按位运算符的经验(我了解按位运算符本身,但我正在尝试弄清楚如何将它们与网络和 IP 一起使用)。我也阅读了这个问题的大部分内容,它提供了很多很好的建议/指导(感谢 Ron Maupin 为我提供了这个),但我仍然在努力让这个功能完全正常工作。

我有几乎可以工作的代码,但由于某种原因,使用/8CIDR 或任何小于/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.

使用 a 时/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

这也适用于 a /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 上开发这个(5.4.0内核上的 Ubuntu 20.04)。

任何帮助将不胜感激,并感谢您的宝贵时间!

标签: cnetworkingcidr

解决方案


我使用主机端计算对其进行了重新设计,并将很多原本不应该存在的东西移出循环:

#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;
}

播种随机数时,请尝试仅播种一次。除非您有与生成多个可重现系列相关的特定目标,否则不要乱用它。


推荐阅读