uvrrpd is a VRRP daemon written in C, providing an implementation of VRRPv2 (rfc3768) and VRRPv3 (rfc5798), with IPv4 and IPv6 support.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

346 lines
8.3 KiB

/*
* 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 */