iprange.c 5.0 KB
Newer Older
1
#include <pthread.h>
2
#include <stdbool.h>
3 4 5 6
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
7
#include <string.h>
8 9
#include <arpa/inet.h>
#include <netinet/in.h>
10 11

#include "triton.h"
12
#include "events.h"
13
#include "list.h"
14
#include "log.h"
15
#include "utils.h"
16 17 18

#include "iprange.h"

D
Dmitry Kozlov 已提交
19 20
#include "memdebug.h"

21 22 23
struct iprange_t
{
	struct list_head entry;
24
	uint32_t begin;
25 26 27
	uint32_t end;
};

28 29
static pthread_mutex_t iprange_lock = PTHREAD_MUTEX_INITIALIZER;
static bool conf_disable = false;
30 31
static LIST_HEAD(client_ranges);

32
static void free_ranges(struct list_head *head)
33
{
34 35 36 37 38 39 40
	struct iprange_t *range;

	while (!list_empty(head)) {
		range = list_first_entry(head, typeof(*range), entry);
		list_del(&range->entry);
		_free(range);
	}
41 42
}

43 44 45 46 47
/* Parse a [client-ip-iprange] configuration entry.
 * Ranges can be defined in CIDR notation ("192.0.2.0/24") or by specifying an
 * upper bound for the last IPv4 byte, after a '-' character ("192.0.2.0-255").
 * For simplicity, only mention the CIDR notation in error messages.
 */
48
static int parse_iprange(const char *str, struct iprange_t **range)
49
{
50 51 52 53 54 55 56
	struct iprange_t *new_range;
	struct in_addr base_addr;
	const char *ptr;
	uint32_t ip_min;
	uint32_t ip_max;
	uint8_t suffix;
	size_t len;
57 58 59 60

	if (!strcmp(str, "disable"))
		goto disable;

61
	ptr = str;
62

63 64 65 66
	/* Try IPv4 CIDR notation first */
	len = u_parse_ip4cidr(ptr, &base_addr, &suffix);
	if (len) {
		uint32_t addr_hbo;
67 68
		uint32_t mask;

69 70 71 72 73 74 75 76 77 78 79
		/* Cast to uint64_t to avoid undefined 32 bits shift on 32 bits
		 * integer if 'suffix' is 0.
		 */
		mask = (uint64_t)0xffffffff << (32 - suffix);
		addr_hbo = ntohl(base_addr.s_addr);
		ip_min = addr_hbo & mask;
		ip_max = addr_hbo | ~mask;

		if (ip_min != addr_hbo) {
			struct in_addr min_addr = { .s_addr = htonl(ip_min) };
			char ipbuf[INET_ADDRSTRLEN];
80

81 82
			log_warn("iprange: network %s is equivalent to %s/%hhu\n",
				 str, u_ip4str(&min_addr, ipbuf), suffix);
83
		}
84 85
		goto addrange;
	}
86

87 88 89 90 91 92 93
	/* Not an IPv4 CIDR, try the IPv4 range notation */
	len = u_parse_ip4range(ptr, &base_addr, &suffix);
	if (len) {
		ip_min = ntohl(base_addr.s_addr);
		ip_max = (ip_min & 0xffffff00) | suffix;
		goto addrange;
	}
94

95 96 97
	log_error("iprange: parsing range \"%s\" failed:"
		  " expecting an IPv4 network prefix in CIDR notation\n",
		  str);
98

99
	return -1;
100

101 102
addrange:
	ptr += len;
103

104 105 106 107 108
	if (!u_parse_endstr(ptr)) {
		log_error("iprange: parsing range \"%s\" failed:"
			  " unexpected data at \"%s\"\n",
			  str, ptr);
		return -1;
109 110
	}

111 112 113 114 115 116 117 118
	if (ip_min == INADDR_ANY && ip_max == INADDR_BROADCAST)
		goto disable;

	new_range = _malloc(sizeof(*new_range));
	if (!new_range) {
		log_error("iprange: impossible to load range \"%s\":"
			  " memory allocation failed\n",
			  str);
119 120 121
		return -1;
	}

122 123 124 125
	new_range->begin = ip_min;
	new_range->end = ip_max;

	*range = new_range;
126 127 128 129 130 131 132

	return 0;

disable:
	*range = NULL;

	return 0;
133 134
}

135
static bool load_ranges(struct list_head *list, const char *conf_sect)
136 137 138 139 140
{
	struct conf_sect_t *s =	conf_get_section(conf_sect);
	struct conf_option_t *opt;
	struct iprange_t *r;

141
	if (!s)
142
		return false;
143 144

	list_for_each_entry(opt, &s->items, entry) {
145
		/* Ignore parsing errors, parse_iprange() already logs suitable
146
		 * error messages.
147 148 149 150
		 */
		if (parse_iprange(opt->name, &r) < 0)
			continue;

151
		if (!r) {
152 153
			free_ranges(list);

154
			return true;
155
		}
156

157 158
		list_add_tail(&r->entry, list);
	}
159 160

	return false;
161 162 163 164 165
}

static int check_range(struct list_head *list, in_addr_t ipaddr)
{
	struct iprange_t *r;
166
	uint32_t a = ntohl(ipaddr);
D
Dmitry Kozlov 已提交
167

168
	list_for_each_entry(r, list, entry) {
169 170
		if (a >= r->begin && a <= r->end)
			return 0;
171 172 173 174 175
	}

	return -1;
}

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
enum iprange_status __export iprange_check_activation(void)
{
	bool disabled;
	bool empty;

	pthread_mutex_lock(&iprange_lock);
	disabled = conf_disable;
	empty = list_empty(&client_ranges);
	pthread_mutex_unlock(&iprange_lock);

	if (disabled)
		return IPRANGE_DISABLED;

	if (empty)
		return IPRANGE_NO_RANGE;

	return IPRANGE_ACTIVE;
}

195 196
int __export iprange_client_check(in_addr_t ipaddr)
{
197 198 199
	int res;

	pthread_mutex_lock(&iprange_lock);
200
	if (conf_disable)
201 202 203 204
		res = 0;
	else
		res = check_range(&client_ranges, ipaddr);
	pthread_mutex_unlock(&iprange_lock);
205

206
	return res;
207
}
208

209
int __export iprange_tunnel_check(in_addr_t ipaddr)
210
{
211 212 213
	int res;

	pthread_mutex_lock(&iprange_lock);
214
	if (conf_disable)
215 216 217 218 219 220 221 222 223 224 225 226 227 228
		res = 0;
	else
		res = !check_range(&client_ranges, ipaddr);
	pthread_mutex_unlock(&iprange_lock);

	return res;
}

static void iprange_load_config(void *data)
{
	LIST_HEAD(new_ranges);
	LIST_HEAD(old_ranges);
	bool disable;

229
	disable = load_ranges(&new_ranges, IPRANGE_CONF_SECTION);
230 231 232 233 234 235

	pthread_mutex_lock(&iprange_lock);
	list_replace(&client_ranges, &old_ranges);
	list_replace(&new_ranges, &client_ranges);
	conf_disable = disable;
	pthread_mutex_unlock(&iprange_lock);
236

237
	free_ranges(&old_ranges);
238
}
239

240
static void iprange_init(void)
241
{
242 243 244 245 246
	iprange_load_config(NULL);
	if (triton_event_register_handler(EV_CONFIG_RELOAD,
					  iprange_load_config) < 0)
		log_error("iprange: registration of CONFIG_RELOAD event failed,"
			  " iprange will not be able to reload its configuration\n");
247 248
}

249
DEFINE_INIT(10, iprange_init);