uvrrpd/vrrp_ip6.c

347 lines
8.3 KiB
C

/*
* vrrp_ip6.c - IPv6 helpers functions
*
* Copyright (C) 2014 Arnaud Andre
*
* This file is part of uvrrpd.
*
* uvrrpd is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* uvrrpd is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with uvrrpd. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_IP6
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
/* ifreq + ioctl */
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <netdb.h> /* NI_MAXHOST */
#include "vrrp_ipx.h"
#include "vrrp_net.h"
#include "log.h"
#include "common.h"
#define VRRP_MGROUP6 "ff02::12"
#define IP6HDR_SIZE sizeof(struct ip6_hdr)
/**
* pshdr_ip6 - pseudo header IPv6
*/
struct pshdr_ip6 {
struct in6_addr saddr;
struct in6_addr daddr;
uint32_t len;
uint8_t zeros[3];
uint8_t next_header;
};
/**
* vrrp_ip6_search_vip() - search one vip in vip list
* if vip is found
* set found = 1
* _vip_ptr point to vip in vnet->vip_list
*/
#define vrrp_ip6_search_vip(vnet, _vip_ptr, _addr, found) \
do { \
list_for_each_entry_reverse(_vip_ptr, &vnet->vip_list, iplist) { \
if (memcmp(&(_vip_ptr->ip_addr6), _addr, sizeof(struct in6_addr)) == 0) { \
found = 1; \
break; \
}\
} \
} while(0)
/**
* vrrp_ip6_setsockopt() - Set socket option
* used to find ancillary data in recvmsg()
* see vrrp_ip6_recv()
*/
static int vrrp_ip6_setsockopt(int socket, int vrid)
{
int on = 1;
/* IPV6_RECVPKTINFO */
if (setsockopt
(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) {
log_error("vrid %d :: setsockopt - %m", vrid);
return -1;
}
/* IPV6_RECVHOPLIMIT */
if (setsockopt
(socket, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) < 0) {
log_error("vrid %d :: setsockopt - %m", vrid);
return -1;
}
return 0;
}
/**
* vrrp_net_join_mgroup6() - join IPv6 VRRP multicast group
*/
static int vrrp_ip6_mgroup(struct vrrp_net *vnet)
{
/* Join VRRP multicast group */
struct ipv6_mreq group = { IN6ADDR_ANY_INIT, 0 };
struct in6_addr group_addr = IN6ADDR_ANY_INIT;
if (inet_pton(AF_INET6, VRRP_MGROUP6, &group_addr) < 0) {
log_error("vrid %d :: inet_pton - %m", vnet->vrid);
return -1;
}
memcpy(&group.ipv6mr_multiaddr, &group_addr, sizeof(struct in6_addr));
group.ipv6mr_interface = if_nametoindex(vnet->vif.ifname);
if (setsockopt
(vnet->socket, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &group,
sizeof(struct ipv6_mreq)) < 0) {
log_error("vrid %d :: setsockopt - %m", vnet->vrid);
return -1;
}
return 0;
}
/**
* vrrp_ip6_cmp() - Compare VIP list between received vrrpkt and our instance
* Return 0 if the list is the same,
* the number of differente VIP else
*/
static int vrrp_ip6_viplist_cmp(struct vrrp_net *vnet, struct vrrphdr *vrrpkt)
{
uint32_t *vip_addr =
(uint32_t *) ((unsigned char *) vrrpkt + VRRP_PKTHDR_SIZE);
uint32_t pos = 0;
int naddr = 0;
int ndiff = 0;
while (naddr < vnet->naddr) {
/* vip in vrrpkt */
struct in6_addr *vip = (struct in6_addr *) (vip_addr + pos);
/* search in vrrp_ip list */
struct vrrp_ip *vip_ptr = NULL;
int found = 0;
vrrp_ip6_search_vip(vnet, vip_ptr, vip->s6_addr, found);
if (!found) {
char host[NI_MAXHOST];
log_warning
("vrid %d :: Invalid pkt - Virtual IPv6 address unexpected %s",
vnet->vrid, inet_ntop(vnet->family, vip, host,
sizeof(host)));
++ndiff;
}
pos += 4;
++naddr;
}
return ndiff;
}
/**
* find_ancillary() - search & find IPv6 ancillary data after
* receiving data in a struct msghdr msg (recvmsg())
*/
static inline void *find_ancillary(struct msghdr *msg, int cmsg_type)
{
struct cmsghdr *cmsg = NULL;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if ((cmsg->cmsg_level == IPPROTO_IPV6)
&& (cmsg->cmsg_type == cmsg_type)) {
return (CMSG_DATA(cmsg));
}
}
return NULL;
}
/**
* vrrp_ip6_recv() - Fill vrrp_ipx_header from received pkt
*/
static int vrrp_ip6_recv(int sock_fd, struct vrrp_recv *recv,
unsigned char *buf, ssize_t buf_size, int *payload_pos)
{
ssize_t len;
/* IPv6 raw sockets return no IP header. We must query
* src/dest via socket options/ancillary data */
struct msghdr msg;
struct sockaddr_in6 src;
struct iovec iov;
uint8_t ancillary[64];
msg.msg_name = &src;
msg.msg_namelen = sizeof(src);
iov.iov_base = buf;
iov.iov_len = buf_size;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ancillary;
msg.msg_controllen = sizeof(ancillary);
msg.msg_flags = 0;
len = recvmsg(sock_fd, &msg, 0);
if (len < 0) {
log_error("recvmsg - %m");
return -1;
}
/* src address */
memcpy(&recv->ip_saddr6, &src.sin6_addr, sizeof(struct in6_addr));
/* hoplimit */
uint8_t *opt = find_ancillary(&msg, IPV6_HOPLIMIT);
if (opt == NULL) {
log_error("recvmsg - unknown hop limit");
return -1;
}
recv->header.ttl = *(int *) opt;
/* dst address */
opt = find_ancillary(&msg, IPV6_PKTINFO);
if (opt == NULL) {
log_error("recvmsg - unknown dst address");
return -1;
}
struct in6_pktinfo *pktinfo = (struct in6_pktinfo *) opt;
memcpy(&recv->ip_daddr6, &pktinfo->ipi6_addr, sizeof(struct in6_addr));
/* other options */
recv->header.len = sizeof(struct ip6_hdr);
recv->header.totlen = recv->header.len + len;
/* kludge, we force it since we have no way to read it in recvmsg().
* But we have set IPPROTO_VRRP in vrrp_net_socket() */
recv->header.proto = IPPROTO_VRRP;
/* buf is directly filled with VRRP adv
* no need to skip IPv6 header */
*payload_pos = 0;
return len;
}
/**
* vrrp_ip6_getsize() - return the current size of vrrp instance
*/
static int vrrp_ip6_getsize(const struct vrrp_net *vnet)
{
return sizeof(struct vrrphdr) + vnet->naddr * sizeof(struct in6_addr);
}
/**
* vrrp_ip6_chksum() - compute VRRP adv chksum
*/
uint16_t vrrp_ip6_chksum(const struct vrrp_net *vnet, struct vrrphdr *pkt,
union vrrp_ipx_addr *ipx_saddr,
union vrrp_ipx_addr *ipx_daddr)
{
/* reset chksum */
pkt->chksum = 0;
const struct iovec *iov_iph = &vnet->__adv[1];
const struct ip6_hdr *iph = iov_iph->iov_base;
/* pseudo_header ipv6 */
struct pshdr_ip6 psh = { IN6ADDR_ANY_INIT,
IN6ADDR_ANY_INIT,
0,
{0, 0, 0},
0
};
if ((ipx_saddr != NULL) && (ipx_daddr != NULL)) {
memcpy(&psh.saddr, &ipx_saddr->addr6, sizeof(struct in6_addr));
memcpy(&psh.daddr, &ipx_daddr->addr6, sizeof(struct in6_addr));
}
else {
memcpy(&psh.saddr, &iph->ip6_src, sizeof(struct in6_addr));
memcpy(&psh.daddr, &iph->ip6_dst, sizeof(struct in6_addr));
}
bzero(&psh.zeros, sizeof(psh.zeros));
psh.next_header = IPPROTO_VRRP;
psh.len = htons(vrrp_ip6_getsize(vnet));
uint32_t psh_size = sizeof(struct pshdr_ip6) + vrrp_ip6_getsize(vnet);
unsigned short buf[psh_size / sizeof(short)];
memcpy(buf, &psh, sizeof(struct pshdr_ip6));
memcpy(buf + sizeof(struct pshdr_ip6) / sizeof(short), pkt,
vrrp_ip6_getsize(vnet));
return cksum(buf, psh_size);
}
/**
* vrrp_ip6_ntop() - network to string representation
*/
static const char *vrrp_ip6_ntop(union vrrp_ipx_addr *ipx, char *dst)
{
return inet_ntop(AF_INET6, &ipx->addr6, dst, INET6_ADDRSTRLEN);
}
/**
* vrrp_ip6_pton() - string representation to network
*/
static int vrrp_ip6_pton(union vrrp_ipx_addr *dst, const char *src)
{
return inet_pton(AF_INET6, src, &dst->addr6);
}
/**
* vrrp_ip6_cmp() - compare two vipx
*/
int vrrp_ip6_cmp(union vrrp_ipx_addr *s1, union vrrp_ipx_addr *s2)
{
return memcmp(&s1->addr6, &s2->addr6, sizeof(struct in6_addr));
}
/* exported VRRP_IP6 helper */
struct vrrp_ipx VRRP_IP6 = {
.family = AF_INET6,
.setsockopt = vrrp_ip6_setsockopt,
.mgroup = vrrp_ip6_mgroup,
.recv = vrrp_ip6_recv,
.cmp = vrrp_ip6_cmp,
.chksum = vrrp_ip6_chksum,
.getsize = vrrp_ip6_getsize,
.viplist_cmp = vrrp_ip6_viplist_cmp,
.ipx_pton = vrrp_ip6_pton,
.ipx_ntop = vrrp_ip6_ntop
};
#endif /* HAVE_IP6 */