summaryrefslogtreecommitdiff
path: root/src/sp_network_utils.c
blob: 0a26254054cb5630118ce18b17a839938ecea2ee (plain)
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
#include "php_snuffleupagus.h"

static inline bool cidr4_match(const struct in_addr addr,
                               const struct in_addr net, uint8_t bits);
static inline bool cidr6_match(const struct in6_addr address,
                               const struct in6_addr network, uint8_t bits);
static inline int get_ip_version(const char *ip);

/* http://fxr.watson.org/fxr/source/include/net/xfrm.h?v=linux-2.6#L840 */
static inline bool cidr4_match(const struct in_addr addr,
                               const struct in_addr net, uint8_t bits) {
  if (bits == 0) {  // C99 6.5.7 (3): u32 << 32 is undefined behaviour
    return true;
  }
  return !((addr.s_addr ^ net.s_addr) & htonl(0xFFFFFFFFu << (32 - bits)));
}

static inline bool cidr6_match(const struct in6_addr address,
                               const struct in6_addr network, uint8_t bits) {
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
    defined(__APPLE__)
  const uint32_t *a = address.__u6_addr.__u6_addr32;
  const uint32_t *n = network.__u6_addr.__u6_addr32;
#else
  const uint32_t *a = address.s6_addr32;
  const uint32_t *n = network.s6_addr32;
#endif

  int bits_whole = bits >> 5;         // number of whole u32
  int bits_incomplete = bits & 0x1F;  // number of bits in incomplete u32

  if (bits_whole) {
    if (memcmp(a, n, bits_whole << 2)) {
      return false;
    }
  }

  if (bits_incomplete) {
    uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
    if ((a[bits_whole] ^ n[bits_whole]) & mask) {
      return false;
    }
  }

  return true;
}

static inline int get_ip_version(const char *ip) {
  struct in_addr out4;
  struct in6_addr out6;
  int res = inet_pton(AF_INET, ip, &out4);
  if ((0 == res) && (1 == inet_pton(AF_INET6, ip, &out6))) {
    return AF_INET6;
  } else if (1 == res) {
    return AF_INET;
  } else {
    return -1;
  }
}

bool cidr_match(const char *ip, const sp_cidr *cidr) {
  struct in_addr out4;
  struct in6_addr out6;

  switch (get_ip_version(ip)) {
    case AF_INET:
      if (AF_INET != cidr->ip_version) {
        return false;
      }
      inet_pton(AF_INET, ip, &out4);
      return cidr4_match(out4, cidr->ip.ipv4, cidr->mask);
    case AF_INET6:
      if (AF_INET6 != cidr->ip_version) {
        return false;
      }
      inet_pton(AF_INET6, ip, &out6);
      return cidr6_match(out6, cidr->ip.ipv6, cidr->mask);
    default:
      sp_log_err("cidr_match", "Weird ip (%s) family", ip);
  }
  return false;
}

int get_ip_and_cidr(char *ip, sp_cidr *cidr) {
  char *mask = strchr(ip, '/');

  if (NULL == mask) {
    sp_log_err("config", "'%s' isn't a valid network mask, it seems that you forgot a '/'.", ip);
    return -1;
  }

  int masklen = strlen(mask+1);
  int imask = atoi(mask+1);
  if (masklen < 1 || masklen > 3 || !isdigit(*(mask+1)) || (masklen >= 2 && !isdigit(*(mask+2)))  || (masklen == 3 && !isdigit(*(mask+3))) || imask < 0 || imask > 128) {
    sp_log_err("config", "'%s' isn't a valid network mask.", mask + 1);
    return -1;
  }
  cidr->mask = (uint8_t)imask;

  ip[mask - ip] = '\0';  // NULL the '/' char

  cidr->ip_version = get_ip_version(ip);

  assert(cidr->ip_version == AF_INET6 || cidr->ip_version == AF_INET);

  if (AF_INET == cidr->ip_version) {
    if (cidr->mask > 32) {
      sp_log_err("config", "'%d' isn't a valid ipv4 mask.", cidr->mask);
      return -1;
    }
    inet_pton(AF_INET, ip, &(cidr->ip.ipv4));
  } else if (AF_INET6 == cidr->ip_version) {
    inet_pton(AF_INET6, ip, &(cidr->ip.ipv6));
  }

  ip[mask - ip] = '/';
  if (cidr->ip_version < 0) {
    sp_log_err("config", "Weird ip (%s) family", ip);
    return -1;
  }

  return 0;
}