Skip to content

Commit

Permalink
Eliminate awkward string manipulations when parsing IP addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
Deltik committed Aug 3, 2024
1 parent 0c4f8d8 commit b59d62a
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
| _Not installed_ | 100000 | 0.000573 | 0.001035 | 0.001123 | 0.001231 | 0.008474 | 0.001170 | 0.000300 |
| `= 0.7.2` | 100000 | 0.000539 | 0.001068 | 0.001166 | 0.001287 | 0.010362 | 0.001212 | 0.000314 |
| `= 0.8.0` | 100000 | 0.000533 | 0.001054 | 0.001151 | 0.001268 | 0.007452 | 0.001197 | 0.000293 |
- Improved the parsing of IP addresses by eliminating string mutations and reducing the number of memory allocations

## v0.7.2 (2024-07-24)

Expand Down
88 changes: 57 additions & 31 deletions ip_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,41 +248,74 @@ bool auto_convert_ipv4_to_ipv6(char *input_ip) {
return false;
}

bool parse_ipv6_address(char *input, struct in6_addr *dest) {
return inet_pton(AF_INET6, input, dest) == 1;
bool parse_ip_address(const char *input, struct in6_addr *dest) {
// Try parsing as IPv6 first
if (strchr(input, ':') != NULL) {
return inet_pton(AF_INET6, input, dest) == 1;
}

// If not IPv6, try parsing as IPv4 and convert to IPv4-mapped IPv6
struct in_addr ipv4addr;
if (inet_pton(AF_INET, input, &ipv4addr) == 1) {
dest->s6_addr32[0] = 0;
dest->s6_addr32[1] = 0;
dest->s6_addr32[2] = htonl(0xffff);
dest->s6_addr32[3] = ipv4addr.s_addr;
return true;
}

return false;
}

int parse_ip_range_hyphenated(char *input, struct in6_addr *ip_lower, struct in6_addr *ip_upper) {
int parse_ip_range_hyphenated(const char *input, struct in6_addr *ip_lower, struct in6_addr *ip_upper) {
const char *hyphen = strchr(input, '-');
if (!hyphen) return ANTILORIS_CONFIG_ERROR_IP_PARSE;

char input_ip_lower[INET6_ADDRSTRLEN];
strncpy(input_ip_lower, strtok(input, "-"), INET6_ADDRSTRLEN - 1);
char input_ip_upper[INET6_ADDRSTRLEN];
strncpy(input_ip_upper, strtok(NULL, "-"), INET6_ADDRSTRLEN - 1);

auto_convert_ipv4_to_ipv6(input_ip_lower);
if (!parse_ipv6_address(input_ip_lower, ip_lower)) return ANTILORIS_CONFIG_ERROR_IP_PARSE;
size_t lower_len = hyphen - input;
size_t upper_len = strlen(hyphen + 1);

auto_convert_ipv4_to_ipv6(input_ip_upper);
if (!parse_ipv6_address(input_ip_upper, ip_upper)) return ANTILORIS_CONFIG_ERROR_IP_PARSE;
if (lower_len >= INET6_ADDRSTRLEN || upper_len >= INET6_ADDRSTRLEN) {
return ANTILORIS_CONFIG_ERROR_IP_PARSE;
}

memcpy(input_ip_lower, input, lower_len);
input_ip_lower[lower_len] = '\0';
strcpy(input_ip_upper, hyphen + 1);

if (!parse_ip_address(input_ip_lower, ip_lower)) return ANTILORIS_CONFIG_ERROR_IP_PARSE;
if (!parse_ip_address(input_ip_upper, ip_upper)) return ANTILORIS_CONFIG_ERROR_IP_PARSE;

return 0;
}

int parse_ip_range_cidr(char *input, struct in6_addr *ip_lower, struct in6_addr *ip_upper) {
bool converted_to_ipv6;
char input_ip_lower[INET6_ADDRSTRLEN];
strncpy(input_ip_lower, strtok(input, "/"), INET6_ADDRSTRLEN - 1);
char *input_cidr = strtok(NULL, "/");
int parse_ip_range_cidr(const char *input, struct in6_addr *ip_lower, struct in6_addr *ip_upper) {
const char *slash = strchr(input, '/');
char input_ip[INET6_ADDRSTRLEN];
uint8_t raw_cidr = 128;

// Set CIDR if input contains CIDR
if (input_cidr != NULL) {
raw_cidr = strtoul(input_cidr, NULL, 10);
if (slash) {
size_t ip_len = slash - input;
if (ip_len >= INET6_ADDRSTRLEN) return ANTILORIS_CONFIG_ERROR_IP_PARSE;
memcpy(input_ip, input, ip_len);
input_ip[ip_len] = '\0';
raw_cidr = strtoul(slash + 1, NULL, 10);
} else {
strncpy(input_ip, input, INET6_ADDRSTRLEN - 1);
input_ip[INET6_ADDRSTRLEN - 1] = '\0';
}

// Convert IPv4 to IPv4-mapped IPv6
converted_to_ipv6 = auto_convert_ipv4_to_ipv6(input_ip_lower);
if (converted_to_ipv6) {
if (input_cidr == NULL) raw_cidr = 32;
if (!parse_ip_address(input_ip, ip_lower)) return ANTILORIS_CONFIG_ERROR_IP_PARSE;

// Adjust CIDR for IPv4-mapped IPv6 addresses
bool is_ipv4 = (!strchr(input, ':') &&
ip_lower->s6_addr32[0] == 0 &&
ip_lower->s6_addr32[1] == 0 &&
ip_lower->s6_addr32[2] == htonl(0xffff));
if (is_ipv4) {
if (!slash) raw_cidr = 32;

// Disallow bad CIDR input for IPv4
if (raw_cidr > 32 || raw_cidr < 0) return ANTILORIS_CONFIG_ERROR_IP_CIDR;
Expand All @@ -293,8 +326,6 @@ int parse_ip_range_cidr(char *input, struct in6_addr *ip_lower, struct in6_addr
// Disallow bad CIDR input for IPv6
if (raw_cidr > 128 || raw_cidr < 0) return ANTILORIS_CONFIG_ERROR_IP_CIDR;

if (!parse_ipv6_address(input_ip_lower, ip_lower)) return ANTILORIS_CONFIG_ERROR_IP_PARSE;

// Validate netmask and fill bits for upper bound
memcpy(ip_upper, ip_lower, sizeof(struct in6_addr));
for (uint8_t i = raw_cidr; i < 128; i++) {
Expand All @@ -311,7 +342,7 @@ int parse_ip_range_cidr(char *input, struct in6_addr *ip_lower, struct in6_addr
return 0;
}

int exempt_ip(patricia_trie *allowlist, char *input) {
int exempt_ip(patricia_trie *allowlist, const char *input) {
struct in6_addr ip_lower, ip_upper;
int rc;

Expand All @@ -330,14 +361,9 @@ int exempt_ip(patricia_trie *allowlist, char *input) {
return 0;
}

bool is_ip_exempted(char *ip_input, patricia_trie *allowlist) {
char ip[INET6_ADDRSTRLEN];
strncpy(ip, ip_input, INET6_ADDRSTRLEN - 1);
ip[INET6_ADDRSTRLEN - 1] = '\0';

bool is_ip_exempted(const char *ip_input, patricia_trie *allowlist) {
struct in6_addr ip_test;
auto_convert_ipv4_to_ipv6(ip);
if (!parse_ipv6_address(ip, &ip_test)) return false;
if (!parse_ip_address(ip_input, &ip_test)) return false;

return patricia_contains(allowlist, ip_test);
}
4 changes: 2 additions & 2 deletions ip_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ void insert_range(patricia_trie *trie, struct in6_addr start, struct in6_addr en
* @param input A single IP address with an optional CIDR suffix or a hyphenated IP address range. Can be IPv4 or IPv6.
* @return 0 if successful, error code if not successful
*/
int exempt_ip(patricia_trie *allowlist, char *input);
int exempt_ip(patricia_trie *allowlist, const char *input);

/**
* Check if the IP address is present in the PATRICIA trie
* @param ip_input A single IPv4 or IPv6 address
* @param allowlist The PATRICIA trie to check
* @return true if the provided IP address is present in the PATRICIA trie, false if not
*/
bool is_ip_exempted(char *ip_input, patricia_trie *allowlist);
bool is_ip_exempted(const char *ip_input, patricia_trie *allowlist);

#define ANTILORIS_IP_HELPER_H

Expand Down
52 changes: 33 additions & 19 deletions mod_antiloris.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
#include "http_connection.h"
#include "http_log.h"
#include "ap_mpm.h"
#include "apr_hash.h"
#include "apr_strings.h"
#include "scoreboard.h"
#include "ip_helper.h"
#include <stdlib.h>
#include <ctype.h>

#define MODULE_NAME "mod_antiloris"
#define MODULE_VERSION "0.8.0"
Expand Down Expand Up @@ -148,39 +148,53 @@ static const char *ip_other_limit_config_cmd(cmd_parms *parms, void *_mconfig, c
static const char *exempt_ips_config_cmd(cmd_parms *parms, void *_mconfig, const char *arg) {
int rc;
patricia_trie *ip_allowlist = _get_config(parms)->ip_exempt;
char input_ips[strlen(arg) + 1];
strcpy(input_ips, arg);
char *input_ip = strtok(input_ips, " ");
while (input_ip != NULL) {
char *original_input_ip = strdup(input_ip);
const char *input_ptr = arg;
const char *token_start;

while (*input_ptr != '\0') {
while (isspace(*input_ptr)) {
input_ptr++;
}
if (*input_ptr == '\0') {
break;
}

token_start = input_ptr;
while (!isspace(*input_ptr) && *input_ptr != '\0') {
input_ptr++;
}

size_t token_length = input_ptr - token_start;
char *input_ip = apr_palloc(parms->pool, token_length + 1);
memcpy(input_ip, token_start, token_length);
input_ip[token_length] = '\0';

rc = exempt_ip(ip_allowlist, input_ip);
if (rc != 0) {
const int MAX_ERROR_STRING_LENGTH = 128;
char error_string_buffer[MAX_ERROR_STRING_LENGTH];
char *error_string = apr_palloc(parms->pool, MAX_ERROR_STRING_LENGTH);
const char *error_format;
switch (rc) {
case ANTILORIS_CONFIG_ERROR_IP_PARSE:
snprintf(error_string_buffer, MAX_ERROR_STRING_LENGTH,
"Cannot parse this as an IP address: %s", original_input_ip);
error_format = "Cannot parse this as an IP address: %s";
break;
case ANTILORIS_CONFIG_ERROR_IP_CIDR:
snprintf(error_string_buffer, MAX_ERROR_STRING_LENGTH,
"Invalid CIDR provided: %s", original_input_ip);
error_format = "Invalid CIDR provided: %s";
break;
case ANTILORIS_CONFIG_ERROR_IP_IN_NETMASK:
snprintf(error_string_buffer, MAX_ERROR_STRING_LENGTH,
"IP address cannot have host bits in netmask: %s", original_input_ip);
error_format = "IP address cannot have host bits in netmask: %s";
break;
case ANTILORIS_CONFIG_ERROR_IP_RANGE_ORDER:
snprintf(error_string_buffer, MAX_ERROR_STRING_LENGTH,
"Lower bound cannot be higher than upper bound in range: %s", original_input_ip);
error_format = "Lower bound cannot be higher than upper bound in range: %s";
break;
default:
snprintf(error_string_buffer, MAX_ERROR_STRING_LENGTH,
"Unknown error (%d) parsing this IP address: %s", rc, original_input_ip);
snprintf(error_string, MAX_ERROR_STRING_LENGTH,
"Unknown error (%d) parsing this IP address: %s", rc, input_ip);
return error_string;
}
return strdup(error_string_buffer);
snprintf(error_string, MAX_ERROR_STRING_LENGTH, error_format, input_ip);
return error_string;
}
input_ip = strtok(NULL, " ");
}

return NULL;
Expand Down

0 comments on commit b59d62a

Please sign in to comment.