Skip to content

Instantly share code, notes, and snippets.

@H2NCH2COOH
Last active February 26, 2019 09:21
Show Gist options
  • Save H2NCH2COOH/4d4ecff49efbb55615b80bdb59e5a573 to your computer and use it in GitHub Desktop.
Save H2NCH2COOH/4d4ecff49efbb55615b80bdb59e5a573 to your computer and use it in GitHub Desktop.
Network Address Parsing in C
#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;
}
}
#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