#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/pfkeyv2.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libdscp.h>
#define DSCP_CONFIGFILE "/var/run/dscp.ifname"
static int get_ifname(char *);
static int convert_ipv6(struct sockaddr_in6 *, uint32_t *);
static int convert_ipv4(struct sockaddr_in *,
struct sockaddr_in6 *, int *);
int
dscpBind(int domain_id, int sockfd, int port)
{
int len;
int len6;
int error;
struct sockaddr_in addr;
struct sockaddr_in6 addr6;
if ((sockfd < 0) || (port >= IPPORT_RESERVED)) {
return (DSCP_ERROR_INVALID);
}
error = dscpAddr(domain_id, DSCP_ADDR_LOCAL,
(struct sockaddr *)&addr, &len);
if (error != DSCP_OK) {
return (error);
}
if (port != 0) {
addr.sin_port = htons(port);
}
if (bind(sockfd, (struct sockaddr *)&addr, len) < 0) {
if (errno == EINVAL) {
return (DSCP_ERROR_ALREADY);
}
if (errno != EAFNOSUPPORT) {
return (DSCP_ERROR);
}
if (convert_ipv4(&addr, &addr6, &len6) < 0) {
return (DSCP_ERROR);
}
if (bind(sockfd, (struct sockaddr *)&addr6, len6) < 0) {
if (errno == EINVAL) {
return (DSCP_ERROR_ALREADY);
}
return (DSCP_ERROR);
}
}
return (DSCP_OK);
}
int
dscpSecure(int domain_id, int sockfd)
{
ipsec_req_t opt;
if (sockfd < 0) {
return (DSCP_ERROR_INVALID);
}
(void) memset(&opt, 0, sizeof (opt));
opt.ipsr_ah_req = IPSEC_PREF_REQUIRED;
opt.ipsr_esp_req = IPSEC_PREF_NEVER;
opt.ipsr_self_encap_req = IPSEC_PREF_NEVER;
opt.ipsr_auth_alg = SADB_AALG_MD5HMAC;
if (setsockopt(sockfd, IPPROTO_IP, IP_SEC_OPT, (const char *)&opt,
sizeof (opt)) < 0) {
return (DSCP_ERROR);
}
return (DSCP_OK);
}
int
dscpAuth(int domain_id, struct sockaddr *saddr, int len)
{
int dlen;
struct sockaddr daddr;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
uint32_t spaddr;
uint32_t reqaddr;
if (saddr == NULL) {
return (DSCP_ERROR_INVALID);
}
if (dscpAddr(0, DSCP_ADDR_REMOTE, &daddr, &dlen) != DSCP_OK) {
return (DSCP_ERROR_DB);
}
switch (saddr->sa_family) {
case AF_INET:
sin = (struct sockaddr_in *)saddr;
reqaddr = ntohl(*((uint32_t *)&(sin->sin_addr)));
break;
case AF_INET6:
sin6 = (struct sockaddr_in6 *)saddr;
if (convert_ipv6(sin6, &reqaddr) < 0) {
return (DSCP_ERROR);
}
break;
default:
return (DSCP_ERROR);
}
sin = (struct sockaddr_in *)&daddr;
spaddr = ntohl(*((uint32_t *)&(sin->sin_addr)));
if (reqaddr != spaddr) {
return (DSCP_ERROR_REJECT);
}
return (DSCP_OK);
}
int
dscpAddr(int domain_id, int which, struct sockaddr *saddr, int *lenp)
{
int error;
int sockfd;
uint64_t flags;
char ifname[LIFNAMSIZ];
struct lifreq lifr;
if (((saddr == NULL) || (lenp == NULL)) ||
((which != DSCP_ADDR_LOCAL) && (which != DSCP_ADDR_REMOTE))) {
return (DSCP_ERROR_INVALID);
}
if (get_ifname(ifname) != 0) {
return (DSCP_ERROR_DB);
}
if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
return (DSCP_ERROR_DB);
}
(void) memset(&lifr, 0, sizeof (lifr));
(void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
if (ioctl(sockfd, SIOCGLIFFLAGS, (char *)&lifr) < 0) {
(void) close(sockfd);
return (DSCP_ERROR_DB);
}
flags = lifr.lifr_flags;
if (((flags & IFF_IPV4) == 0) ||
((flags & IFF_POINTOPOINT) == 0)) {
(void) close(sockfd);
return (DSCP_ERROR_DB);
}
(void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
if (which == DSCP_ADDR_LOCAL) {
error = ioctl(sockfd, SIOCGLIFADDR, (char *)&lifr);
} else {
error = ioctl(sockfd, SIOCGLIFDSTADDR, (char *)&lifr);
}
if (error < 0) {
(void) close(sockfd);
return (DSCP_ERROR_DB);
}
(void) memset(saddr, 0, sizeof (struct sockaddr));
(void) memcpy(saddr, &lifr.lifr_addr, sizeof (struct sockaddr_in));
*lenp = sizeof (struct sockaddr_in);
(void) close(sockfd);
return (DSCP_OK);
}
int
dscpIdent(struct sockaddr *saddr, int len, int *domainp)
{
int error;
if ((saddr == NULL) || (domainp == NULL)) {
return (DSCP_ERROR_INVALID);
}
error = dscpAuth(0, saddr, len);
if (error != DSCP_OK) {
if (error == DSCP_ERROR_REJECT) {
return (DSCP_ERROR);
}
return (error);
}
*domainp = DSCP_IDENT_SP;
return (DSCP_OK);
}
static int
get_ifname(char *ifname)
{
int i;
int fd;
int len;
int size;
int count;
int end;
int begin;
struct stat stbuf;
(void) memset(ifname, 0, LIFNAMSIZ);
if ((stat(DSCP_CONFIGFILE, &stbuf) < 0) ||
(S_ISREG(stbuf.st_mode) == 0) ||
(stbuf.st_size > LIFNAMSIZ)) {
return (-1);
}
if ((fd = open(DSCP_CONFIGFILE, O_RDONLY)) < 0) {
return (-1);
}
count = 0;
size = stbuf.st_size;
do {
i = read(fd, &ifname[count], size - count);
if (i <= 0) {
(void) close(fd);
return (-1);
}
count += i;
} while (count < size);
(void) close(fd);
for (begin = -1, i = 0; i < size; i++) {
if (isalnum(ifname[i]) != 0) {
begin = i;
break;
}
}
if (begin < 0) {
return (-1);
}
for (end = size - 1, i = begin; i < size; i++) {
if (isalnum(ifname[i]) == 0) {
end = i;
break;
}
}
len = end - begin;
if (begin > 0) {
(void) memmove(ifname, &ifname[begin], len);
}
if (len < size) {
(void) memset(&ifname[len], 0, size - len);
}
return (0);
}
static int
convert_ipv6(struct sockaddr_in6 *addr6, uint32_t *addrp)
{
uint32_t addr;
char *ipv4str;
char ipv6str[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &addr6->sin6_addr, ipv6str,
sizeof (ipv6str)) == NULL) {
return (-1);
}
if ((ipv4str = strrchr(ipv6str, ':')) != NULL) {
ipv4str++;
} else {
return (-1);
}
if (inet_pton(AF_INET, ipv4str, &addr) <= 0) {
return (-1);
}
*addrp = ntohl(addr);
return (0);
}
static int
convert_ipv4(struct sockaddr_in *addr, struct sockaddr_in6 *addr6, int *lenp)
{
int len;
uint32_t ipv4addr;
char ipv4str[INET_ADDRSTRLEN];
char ipv6str[INET6_ADDRSTRLEN];
ipv4addr = *((uint32_t *)&(addr->sin_addr));
if (inet_ntop(AF_INET, &ipv4addr, ipv4str, sizeof (ipv4str)) == NULL) {
return (-1);
}
len = snprintf(ipv6str, INET6_ADDRSTRLEN, "::ffff:%s", ipv4str);
if (len >= INET6_ADDRSTRLEN) {
return (-1);
}
(void) memset(addr6, 0, sizeof (*addr6));
addr6->sin6_family = AF_INET6;
addr6->sin6_port = addr->sin_port;
if (inet_pton(AF_INET6, ipv6str, &addr6->sin6_addr) <= 0) {
return (-1);
}
*lenp = sizeof (struct sockaddr_in6);
return (0);
}