Last active
February 26, 2019 09:21
-
-
Save H2NCH2COOH/4d4ecff49efbb55615b80bdb59e5a573 to your computer and use it in GitHub Desktop.
Network Address Parsing in C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <errno.h> | |
#include "net_addr.h" | |
static int ipv4_aton(const char* str, uint8_t addr[4]) | |
{ | |
const char* DIGIT = "0123456789"; | |
uint8_t saddr[4]; | |
int i = 0; | |
int b = 0; | |
char s = 'i'; | |
while(1) | |
{ | |
switch(s) | |
{ | |
case 'd': | |
if(*str == '.') | |
{ | |
if(i > 3) | |
{ | |
return -1; | |
} | |
case 'e': | |
if(b > 255) | |
{ | |
return -1; | |
} | |
saddr[i++] = b; | |
b = 0; | |
if(s == 'e') | |
{ | |
goto out; | |
} | |
s = 'i'; | |
break; | |
} | |
/* fall-through */ | |
case 'i': | |
if(!isdigit(*str)) | |
{ | |
return -1; | |
} | |
s = 'd'; | |
b *= 10; | |
b += strchr(DIGIT, *str) - DIGIT; | |
break; | |
} | |
++str; | |
if(*str == '\0') | |
{ | |
if(s != 'd' || i != 3) | |
{ | |
return -1; | |
} | |
s = 'e'; | |
} | |
} | |
return -1; | |
out: | |
memcpy(addr, &saddr, sizeof(saddr)); | |
return 0; | |
} | |
static int ipv6_aton(const char* str, uint8_t addr[16], const char** scope) | |
{ | |
const char* XDIGIT = "0123456789abcdef"; | |
uint32_t d = 0; | |
int i = 0; | |
int blank = -1; | |
char s = 'i'; | |
uint16_t buf[8]; | |
while(1) | |
{ | |
switch(s) | |
{ | |
case 's': | |
if(*str == ':') | |
{ | |
if(blank > 0) | |
{ | |
return -1; | |
} | |
blank = i; | |
break; | |
} | |
s = 'i'; | |
/* fall-through */ | |
case 'i': | |
if(*str == ':') | |
{ | |
case 'e': | |
if(i + (blank > 0) > 7) | |
{ | |
return -1; | |
} | |
if(d > 0xFFFF) | |
{ | |
return -1; | |
} | |
buf[i] = d; | |
++i; | |
d = 0; | |
if(s == 'e') | |
{ | |
goto out; | |
} | |
s = 's'; | |
break; | |
} | |
if(!isxdigit(*str)) | |
{ | |
return -1; | |
} | |
d <<= 4; | |
d |= strchr(XDIGIT, tolower(*str)) - XDIGIT; | |
break; | |
} | |
++str; | |
if(*str == '\0' || *str == '%') | |
{ | |
if(s != 'i' && s != 's') | |
{ | |
return -1; | |
} | |
s = 'e'; | |
} | |
} | |
out: | |
if(blank > 0) | |
{ | |
int j = 8 - i; | |
memmove(buf + j + blank, buf + blank, sizeof(uint16_t) * (i - blank)); | |
memset(buf + blank, 0, sizeof(uint16_t) * j); | |
i = 8; | |
} | |
if(i != 8) | |
{ | |
return -1; | |
} | |
for(i = 0; i < 8; ++i) | |
{ | |
addr[i << 1] = (buf[i] & 0xFF00) >> 8; | |
addr[(i << 1) | 1] = buf[i] & 0xFF; | |
} | |
if(*str == '%') | |
{ | |
*scope = str + 1; | |
} | |
else | |
{ | |
*scope = NULL; | |
} | |
return 0; | |
} | |
NetAddr* parse_network_address(const char* str) | |
{ | |
if(str == NULL) | |
{ | |
errno = EINVAL; | |
return NULL; | |
} | |
char* buff = strdup(str); | |
if(buff == NULL) | |
{ | |
errno = ENOMEM; | |
return NULL; | |
} | |
NetAddr* addr = NULL; | |
if(buff[0] == '[') | |
{ | |
//IPv6 | |
//[<IPv6 addr>]:<port> | |
char* end = strchr(buff + 1, ']'); | |
if(end == NULL || end[1] != ':') | |
{ | |
goto bad; | |
} | |
*end = '\0'; | |
uint8_t ipv6[16]; | |
const char* scope = NULL; | |
if(ipv6_aton(buff + 1, ipv6, &scope) != 0) | |
{ | |
goto bad; | |
} | |
int port = atoi(end + 2); | |
if(port <= 0 || port > 65535) | |
{ | |
goto bad; | |
} | |
size_t scope_len = 0; | |
if(scope != NULL) | |
{ | |
scope_len = strlen(scope); | |
} | |
addr = malloc(sizeof(NetAddr) + scope_len + 1); | |
if(addr == NULL) | |
{ | |
goto nomem; | |
} | |
addr->type = NET_ADDR_TYPE_IPV6; | |
addr->port = port; | |
memcpy(addr->addr.ipv6.a, ipv6, 16); | |
addr->addr.ipv6.scope_len = scope_len; | |
if(scope_len > 0) | |
{ | |
memcpy(addr->addr.ipv6.scope, scope, scope_len); | |
addr->addr.ipv6.scope[scope_len] = '\0'; | |
} | |
} | |
else | |
{ | |
//IPv4 or domain | |
char* end = strchr(buff, ':'); | |
if(end == NULL) | |
{ | |
goto bad; | |
} | |
*end = '\0'; | |
int port = atoi(end + 1); | |
if(port <= 0 || port > 65535) | |
{ | |
goto bad; | |
} | |
uint8_t ipv4[4]; | |
if(ipv4_aton(buff, ipv4) == 0) | |
{ | |
addr = malloc(sizeof(NetAddr)); | |
if(addr == NULL) | |
{ | |
goto nomem; | |
} | |
addr->type = NET_ADDR_TYPE_IPV4; | |
addr->port = port; | |
memcpy(addr->addr.ipv4.a, ipv4, 4); | |
} | |
else | |
{ | |
char* p = buff; | |
for(p = buff; *p != '\0'; ++p) | |
{ | |
if(strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.", *p) == NULL) | |
{ | |
goto bad; | |
} | |
} | |
size_t domain_len = end - buff; | |
addr = malloc(sizeof(NetAddr) + domain_len + 1); | |
if(addr == NULL) | |
{ | |
goto nomem; | |
} | |
addr->type = NET_ADDR_TYPE_DOMAIN; | |
addr->port = port; | |
addr->addr.domain.len = domain_len; | |
memcpy(addr->addr.domain.a, buff, domain_len); | |
addr->addr.domain.a[domain_len] = '\0'; | |
} | |
} | |
free(buff); | |
return addr; | |
bad: | |
free(buff); | |
errno = EINVAL; | |
return NULL; | |
nomem: | |
free(buff); | |
errno = ENOMEM; | |
return NULL; | |
} | |
int network_address_to_sockaddr(NetAddr* addr, struct sockaddr_storage* sockaddr) | |
{ | |
struct sockaddr_in* in = (struct sockaddr_in*)sockaddr; | |
struct sockaddr_in6* in6 = (struct sockaddr_in6*)sockaddr; | |
switch(addr->type) | |
{ | |
case NET_ADDR_TYPE_IPV4: | |
in->sin_family = AF_INET; | |
in->sin_port = htons(addr->port); | |
memcpy(&in->sin_addr, addr->addr.ipv4.a, 4); | |
return sizeof(struct sockaddr_in); | |
case NET_ADDR_TYPE_IPV6: | |
in6->sin6_family = AF_INET6; | |
in6->sin6_port = htons(addr->port); | |
in6->sin6_flowinfo = 0; //XXX | |
in6->sin6_scope_id = 0; | |
memcpy(&in6->sin6_addr, addr->addr.ipv6.a, 16); | |
if(addr->addr.ipv6.scope_len > 0) | |
{ | |
char* end = NULL; | |
long scope = strtol(addr->addr.ipv6.scope, &end, 10); | |
if(*end != '\0' || scope < 0) | |
{ | |
return -1; | |
} | |
in6->sin6_scope_id = scope; | |
} | |
return sizeof(struct sockaddr_in6); | |
default: | |
return -1; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _NET_ADDR_H_ | |
#define _NET_ADDR_H_ | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <netinet/in.h> | |
typedef struct | |
{ | |
enum | |
{ | |
NET_ADDR_TYPE_IPV4, | |
NET_ADDR_TYPE_IPV6, | |
NET_ADDR_TYPE_DOMAIN | |
} type; | |
uint16_t port; //Host order | |
union | |
{ | |
struct | |
{ | |
uint8_t a[4]; | |
} ipv4; | |
struct | |
{ | |
uint8_t a[16]; | |
size_t scope_len; | |
char scope[]; | |
} ipv6; | |
struct | |
{ | |
size_t len; | |
char a[]; | |
} domain; | |
} addr; | |
} NetAddr; | |
NetAddr* parse_network_address(const char* str); | |
int network_address_to_sockaddr(NetAddr* addr, struct sockaddr_storage* sockaddr); | |
#endif /* _NET_ADDR_H_ */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment