545 lines
12 KiB
C
545 lines
12 KiB
C
|
/*
|
||
|
* vrrp_net.c - net layer
|
||
|
*
|
||
|
* 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/>.
|
||
|
*/
|
||
|
|
||
|
#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/ip.h>
|
||
|
#include <netinet/ip6.h>
|
||
|
#include <arpa/inet.h>
|
||
|
/* ifreq + ioctl */
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <net/if.h>
|
||
|
/* pselect() */
|
||
|
#include <sys/select.h>
|
||
|
#include <signal.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <ifaddrs.h>
|
||
|
#include <netdb.h> /* NI_MAXHOST */
|
||
|
|
||
|
#include "vrrp.h"
|
||
|
#include "vrrp_net.h"
|
||
|
#include "vrrp_timer.h"
|
||
|
#include "vrrp_adv.h"
|
||
|
|
||
|
#include "common.h"
|
||
|
#include "list.h"
|
||
|
#include "log.h"
|
||
|
|
||
|
#include "linux/types.h"
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#define VRRP_MGROUP4 "224.0.0.18"
|
||
|
#define VRRP_MGROUP6 "ff02::12"
|
||
|
#define VRRP_TTL 255
|
||
|
|
||
|
static inline void vrrp_net_invalidate_buffer(struct vrrp_net *vnet);
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_init() - init struct vrrp_net of vrrp instance
|
||
|
*/
|
||
|
void vrrp_net_init(struct vrrp_net *vnet)
|
||
|
{
|
||
|
vnet->vrid = 0;
|
||
|
vnet->naddr = 0;
|
||
|
vnet->socket = 0;
|
||
|
vnet->xmit = 0;
|
||
|
vnet->family = AF_INET;
|
||
|
vnet->ipx_helper = NULL;
|
||
|
|
||
|
/* init VRRP IPs list */
|
||
|
INIT_LIST_HEAD(&vnet->vip_list);
|
||
|
|
||
|
/* init vrrp interface */
|
||
|
bzero((void *) &vnet->vif, sizeof(struct vrrp_if));
|
||
|
|
||
|
/* init pkt buffer */
|
||
|
bzero((void *) &vnet->__pkt, sizeof(struct vrrp_recv));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_cleanup() - cleanup struct vrrp_net
|
||
|
*/
|
||
|
void vrrp_net_cleanup(struct vrrp_net *vnet)
|
||
|
{
|
||
|
/* clean VIP addr */
|
||
|
struct vrrp_ip *vip_ptr = NULL;
|
||
|
struct vrrp_ip *n = NULL;
|
||
|
list_for_each_entry_safe(vip_ptr, n, &vnet->vip_list, iplist)
|
||
|
free(vip_ptr);
|
||
|
|
||
|
free(vnet->vif.ifname);
|
||
|
|
||
|
/* close sockets */
|
||
|
close(vnet->socket);
|
||
|
close(vnet->xmit);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_socket() - create VRRP socket destined to receive VRRP pkt
|
||
|
*/
|
||
|
int vrrp_net_socket(struct vrrp_net *vnet)
|
||
|
{
|
||
|
/* Open RAW socket */
|
||
|
vnet->socket = socket(vnet->family, SOCK_RAW, IPPROTO_VRRP);
|
||
|
|
||
|
if (vnet->socket < 0) {
|
||
|
log_error("vrid %d :: socket - %s", vnet->vrid,
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
vnet->ipx_helper = vrrp_ipx_set(vnet->family);
|
||
|
|
||
|
if (vnet->ipx_helper == NULL) {
|
||
|
log_error("family not valid");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int status = -1;
|
||
|
|
||
|
status = vnet->set_sockopt(vnet->socket, vnet->vrid);
|
||
|
status |= vnet->join_mgroup(vnet);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_socket_xmit() - open raw VRRP xmit socket
|
||
|
*/
|
||
|
int vrrp_net_socket_xmit(struct vrrp_net *vnet)
|
||
|
{
|
||
|
vnet->xmit = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
||
|
|
||
|
if (vnet->xmit < 0) {
|
||
|
log_error("vrid %d :: socket xmit - %s", vnet->vrid,
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_vif_getaddr() - get IPvX addr from a VRRP interface
|
||
|
*/
|
||
|
int vrrp_net_vif_getaddr(struct vrrp_net *vnet)
|
||
|
{
|
||
|
|
||
|
struct ifaddrs *ifaddr, *ifa;
|
||
|
int family;
|
||
|
|
||
|
if (getifaddrs(&ifaddr) == -1) {
|
||
|
log_error("vrid %d :: getifaddrs - %s", vnet->vrid,
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* search address */
|
||
|
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||
|
if (strcmp(ifa->ifa_name, vnet->vif.ifname) != 0)
|
||
|
continue;
|
||
|
|
||
|
if (ifa->ifa_addr == NULL)
|
||
|
continue;
|
||
|
|
||
|
family = ifa->ifa_addr->sa_family;
|
||
|
|
||
|
if (family != vnet->family)
|
||
|
continue;
|
||
|
|
||
|
if (vnet->family == AF_INET) {
|
||
|
vnet->vif.ip_addr.s_addr =
|
||
|
((struct sockaddr_in *) ifa->ifa_addr)->
|
||
|
sin_addr.s_addr;
|
||
|
}
|
||
|
else { /* AF_INET6 */
|
||
|
struct sockaddr_in6 *src =
|
||
|
(struct sockaddr_in6 *) ifa->ifa_addr;
|
||
|
memcpy(&vnet->vif.ip_addr6, &src->sin6_addr,
|
||
|
sizeof(struct in6_addr));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
freeifaddrs(ifaddr);
|
||
|
|
||
|
vrrp_net_vif_mtu(vnet);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_vif_mtu() - get MTU of vrrp interface
|
||
|
*/
|
||
|
int vrrp_net_vif_mtu(struct vrrp_net *vnet)
|
||
|
{
|
||
|
struct ifreq ifr;
|
||
|
|
||
|
strncpy(ifr.ifr_name, vnet->vif.ifname, IFNAMSIZ - 1);
|
||
|
|
||
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||
|
|
||
|
ifr.ifr_addr.sa_family = AF_INET;
|
||
|
|
||
|
if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
|
||
|
log_error("vrid %d :: ioctl - %s", vnet->vrid, strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
vnet->vif.mtu = ifr.ifr_mtu;
|
||
|
log_debug("%s mtu : %d", vnet->vif.ifname, vnet->vif.mtu);
|
||
|
|
||
|
close(fd);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_vip_set() - register VRRP virtual IPvX addresses
|
||
|
*/
|
||
|
int vrrp_net_vip_set(struct vrrp_net *vnet, const char *ip)
|
||
|
{
|
||
|
struct vrrp_ip *vip = malloc(sizeof(struct vrrp_ip));
|
||
|
|
||
|
if (vip == NULL) {
|
||
|
log_error("vrid %d :: malloc - %s", vnet->vrid,
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* split ip / netmask */
|
||
|
int status = -1;
|
||
|
|
||
|
if (vnet->family == AF_INET)
|
||
|
status =
|
||
|
split_ip_netmask(vnet->family, ip, &vip->ip_addr,
|
||
|
&vip->netmask);
|
||
|
|
||
|
if (vnet->family == AF_INET6)
|
||
|
status =
|
||
|
split_ip_netmask(vnet->family, ip, &vip->ip_addr6,
|
||
|
&vip->netmask);
|
||
|
|
||
|
if (status != 0) {
|
||
|
fprintf(stderr, "vrid %d :: invalid IP addr %s", vnet->vrid,
|
||
|
ip);
|
||
|
free(vip);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
list_add_tail(&vip->iplist, &vnet->vip_list);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_listen() - Wait for a VRRP pkt on vnet->socket
|
||
|
*
|
||
|
* @return TIMER if current timer is expired
|
||
|
* @return PKT else
|
||
|
*/
|
||
|
int vrrp_net_listen(struct vrrp_net *vnet, struct vrrp *vrrp)
|
||
|
{
|
||
|
struct vrrp_timer *vt;
|
||
|
|
||
|
/* Check which timer is running
|
||
|
* Advertisement timer or Masterdown timer ?
|
||
|
*/
|
||
|
if (vrrp_timer_is_running(&vrrp->adv_timer)) {
|
||
|
log_debug("vrid %d :: adv_timer is running", vrrp->vrid);
|
||
|
vt = &vrrp->adv_timer;
|
||
|
}
|
||
|
else if (vrrp_timer_is_running(&vrrp->masterdown_timer)) {
|
||
|
log_debug("vrid %d :: masterdown_timer is running", vrrp->vrid);
|
||
|
vt = &vrrp->masterdown_timer;
|
||
|
}
|
||
|
else { /* No timer ? ... exit */
|
||
|
log_error("vrid %d :: no timer running !", vrrp->vrid);
|
||
|
/* TODO die() */
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
/* update timer before pselect() */
|
||
|
if (vrrp_timer_update(vt)) {
|
||
|
log_debug("vrid %d :: timer expired before pselect",
|
||
|
vrrp->vrid);
|
||
|
/* timer expired or invalid */
|
||
|
return TIMER;
|
||
|
}
|
||
|
|
||
|
/* pselect */
|
||
|
fd_set readfds;
|
||
|
FD_ZERO(&readfds);
|
||
|
FD_SET(vnet->socket, &readfds);
|
||
|
|
||
|
sigset_t emptyset;
|
||
|
sigemptyset(&emptyset);
|
||
|
|
||
|
/* Wait for packet or timer expiration */
|
||
|
if (pselect
|
||
|
(vnet->socket + 1, &readfds, NULL, NULL,
|
||
|
(const struct timespec *) &vt->delta, &emptyset) >= 0) {
|
||
|
|
||
|
|
||
|
/* Timer is expired */
|
||
|
if (vrrp_timer_is_expired(vt)) {
|
||
|
log_debug("vrid %d :: timer expired", vrrp->vrid);
|
||
|
return TIMER;
|
||
|
}
|
||
|
|
||
|
/* Else we have received a pkt */
|
||
|
log_debug("vrid %d :: VRRP pkt received", vrrp->vrid);
|
||
|
|
||
|
/* check if received is valid or not */
|
||
|
if (vrrp_net_recv(vnet, vrrp) > 0)
|
||
|
return PKT;
|
||
|
else {
|
||
|
log_error("vrid %d :: %s", vrrp->vrid,
|
||
|
"Received an invalid packet");
|
||
|
return INVALID;
|
||
|
}
|
||
|
}
|
||
|
else { /* Signal or pselect error */
|
||
|
if (errno == EINTR) {
|
||
|
log_debug("vrid %d :: signal caught", vrrp->vrid);
|
||
|
|
||
|
return SIGNAL;
|
||
|
}
|
||
|
|
||
|
log_error("vrid %d :: pselect - %s %d", vrrp->vrid,
|
||
|
strerror(errno), errno);
|
||
|
}
|
||
|
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_invalidate_buffer()
|
||
|
* invalidate internal buffer used to stock received pkt
|
||
|
* vnet->__pkt
|
||
|
*/
|
||
|
static inline void vrrp_net_invalidate_buffer(struct vrrp_net *vnet)
|
||
|
{
|
||
|
vnet->__pkt.adv.version_type = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_recv() - read and check a received VRRP pkt advertisement
|
||
|
*
|
||
|
* @return len of VRRP pkt if valid, -1 if invalid
|
||
|
*/
|
||
|
int vrrp_net_recv(struct vrrp_net *vnet, const struct vrrp *vrrp)
|
||
|
{
|
||
|
/* fetch pkt data received to buf */
|
||
|
unsigned char buf[IP_MAXPACKET];
|
||
|
|
||
|
/* read IPvX header values and fill vrrp_recv buffer __pkt */
|
||
|
int payload_pos = 0;
|
||
|
ssize_t len = vnet->pkt_receive(vnet->socket, &vnet->__pkt, buf,
|
||
|
IP_MAXPACKET, &payload_pos);
|
||
|
|
||
|
/* check len */
|
||
|
if (len == -1) {
|
||
|
log_error("vrid %d :: invalid pkt", vnet->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
if ((len > vnet->vif.mtu) || (len < vnet->adv_getsize(vnet))) {
|
||
|
log_error("vrid %d :: invalid pkt len", vnet->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* read and check vrrp advertisement pkt */
|
||
|
struct vrrphdr *vrrpkt;
|
||
|
|
||
|
vrrpkt = (struct vrrphdr *) (buf + payload_pos);
|
||
|
|
||
|
/* TODO : is this really necessary ?? */
|
||
|
vrrp_net_invalidate_buffer(vnet);
|
||
|
|
||
|
/* check VRRP pkt size (including VRRP IP address(es) and Auth data) */
|
||
|
unsigned int payload_size =
|
||
|
vnet->__pkt.header.totlen - vnet->__pkt.header.len;
|
||
|
|
||
|
if ((payload_size < VRRP_PKT_MINSIZE)
|
||
|
|| (payload_size > VRRP_PKT_MAXSIZE)) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt - Invalid packet size %d, expecting size between %ld and %ld",
|
||
|
vrrp->vrid, payload_size, VRRP_PKT_MINSIZE,
|
||
|
VRRP_PKT_MAXSIZE);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* verify ip proto */
|
||
|
if (vnet->__pkt.header.proto != IPPROTO_VRRP) {
|
||
|
log_info("vrid %d :: Invalid pkt - ip proto not valid %d",
|
||
|
vrrp->vrid, vnet->__pkt.header.proto);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* verify VRRP version */
|
||
|
if ((vrrpkt->version_type >> 4) != vrrp->version) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt - version %d mismatch, expecting %d",
|
||
|
vrrp->vrid, vrrpkt->version_type >> 4, vrrp->version);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* TTL must be 255 */
|
||
|
if (vnet->__pkt.header.ttl != VRRP_TTL) {
|
||
|
log_info("vrid %d :: Invalid pkt - TTL isn't %d", vrrp->vrid,
|
||
|
VRRP_TTL);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* verify VRRP checksum */
|
||
|
int chksum = vrrpkt->chksum; /* save checksum */
|
||
|
if (vnet->adv_checksum(vnet, vrrpkt, &vnet->__pkt.s_ipx,
|
||
|
&vnet->__pkt.d_ipx) != chksum) {
|
||
|
log_info("vrid %d :: Invalid pkt - Invalid checksum %x",
|
||
|
vrrp->vrid, chksum);
|
||
|
|
||
|
return INVALID;
|
||
|
}
|
||
|
/* restore checksum */
|
||
|
vrrpkt->chksum = chksum;
|
||
|
|
||
|
/* check if VRID is the same as the current instance */
|
||
|
if (vrrpkt->vrid != vrrp->vrid) {
|
||
|
log_info("vrid %d :: Invalid pkt - Invalid VRID %d",
|
||
|
vrrp->vrid, vrrpkt->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* local router is the IP address owner
|
||
|
* (Priority equals 255)
|
||
|
*/
|
||
|
if (vrrp->priority == PRIO_OWNER) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt - *We* are the owner of IP address(es) (priority %d)",
|
||
|
vrrp->vrid, vrrp->priority);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Auth type (RFC2338/3768) */
|
||
|
if (vrrp->version != RFC5798) {
|
||
|
|
||
|
/* auth type must be the same locally configured */
|
||
|
if (vrrpkt->auth_type != vrrp->auth_type) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt - Invalid authentication type",
|
||
|
vrrp->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* auth type is simple */
|
||
|
if (vrrpkt->auth_type == SIMPLE) {
|
||
|
uint32_t *auth_data =
|
||
|
(uint32_t *) ((unsigned char *) vrrpkt +
|
||
|
VRRP_PKTHDR_SIZE +
|
||
|
vrrp->naddr * sizeof(uint32_t));
|
||
|
if (memcmp
|
||
|
(auth_data, vrrp->auth_data,
|
||
|
strlen(vrrp->auth_data))
|
||
|
!= 0) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt - Invalid authentication password",
|
||
|
vrrp->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* count of IP address(es) and list may be the same
|
||
|
* or generated by the owner
|
||
|
*/
|
||
|
if (((vrrpkt->naddr != vrrp->naddr)
|
||
|
|| (vnet->vip_compare(vnet, vrrpkt) != 0))
|
||
|
&& (vrrpkt->priority != PRIO_OWNER) && (vrrp->version == 2)) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt not generated by the owner, drop it",
|
||
|
vrrp->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* advert interval must be the same as the locally configured */
|
||
|
if (vrrpkt->adv_int != vrrp->adv_int) {
|
||
|
log_info
|
||
|
("vrid %d :: Invalid pkt - Advertisement interval mismatch\n",
|
||
|
vrrp->vrid);
|
||
|
return INVALID;
|
||
|
}
|
||
|
|
||
|
/* pkt is valid, keep it in internal buffer */
|
||
|
memcpy(&vnet->__pkt.adv, vrrpkt, sizeof(struct vrrphdr));
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* vrrp_net_send - send pkt
|
||
|
*/
|
||
|
int vrrp_net_send(const struct vrrp_net *vnet, struct iovec *iov, size_t len)
|
||
|
{
|
||
|
if (iov == NULL) {
|
||
|
log_error("vrid %d :: No data to send !?", vnet->vrid);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
struct sockaddr_ll device = { 0 };
|
||
|
|
||
|
device.sll_family = AF_PACKET;
|
||
|
device.sll_ifindex = if_nametoindex(vnet->vif.ifname);
|
||
|
|
||
|
if (device.sll_ifindex == 0) {
|
||
|
log_error("vrid %d :: if_nametoindex - %s", vnet->vrid,
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
struct msghdr msg = { 0 };
|
||
|
|
||
|
msg.msg_name = &device;
|
||
|
msg.msg_namelen = sizeof(device);
|
||
|
msg.msg_iov = iov;
|
||
|
msg.msg_iovlen = len;
|
||
|
msg.msg_control = NULL;
|
||
|
msg.msg_controllen = 0;
|
||
|
msg.msg_flags = 0;
|
||
|
|
||
|
if (sendmsg(vnet->xmit, &msg, 0) < 0) {
|
||
|
log_error("vrid %d :: sendmsg - %s", vnet->vrid,
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|