437 lines
11 KiB
C
437 lines
11 KiB
C
/*
|
|
* This software is Copyright 2011 by Sean Groarke <sgroarke@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* This file is part of npd6.
|
|
*
|
|
* npd6 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.
|
|
*
|
|
* npd6 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 npd6. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* $Id$
|
|
* $HeadURL$
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "npd6.h"
|
|
#include <netpacket/packet.h>
|
|
|
|
|
|
/*****************************************************************************
|
|
* open_packet_socket
|
|
* Opens the packet-level socket, for incoming traffic,
|
|
* and sets up the appropriate BSD PF.
|
|
*
|
|
* Inputs:
|
|
* Index of the interface we're opening it for.
|
|
*
|
|
* Outputs:
|
|
* none
|
|
*
|
|
* Return:
|
|
* int sock on success, otherwise -1
|
|
*
|
|
*/
|
|
int open_packet_socket(int ifIndex)
|
|
{
|
|
int sock, err;
|
|
struct sock_fprog fprog;
|
|
struct sockaddr_ll lladdr;
|
|
static const struct sock_filter filter[] =
|
|
{
|
|
BPF_STMT(BPF_LD|BPF_B|BPF_ABS,
|
|
ETH_HLEN +
|
|
sizeof(struct ip6_hdr) +
|
|
offsetof(struct icmp6_hdr, icmp6_type)),
|
|
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ND_NEIGHBOR_SOLICIT, 1, 0),
|
|
BPF_STMT(BPF_RET|BPF_K, 0),
|
|
BPF_STMT(BPF_RET|BPF_K, 0xffffffff),
|
|
};
|
|
|
|
fprog.filter = (struct sock_filter *)filter;
|
|
fprog.len = sizeof filter / sizeof filter[0];
|
|
|
|
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IPV6) );
|
|
if (sock < 0)
|
|
{
|
|
flog(LOG_ERR, "Can't create socket(PF_PACKET): %s", strerror(errno));
|
|
return (-1);
|
|
}
|
|
flog(LOG_DEBUG2, "Created PF_PACKET socket OK.");
|
|
|
|
// Bind the socket to the interface we're interested in
|
|
memset(&lladdr, 0, sizeof(lladdr));
|
|
lladdr.sll_family = PF_PACKET;
|
|
lladdr.sll_protocol = htons(ETH_P_IPV6);
|
|
lladdr.sll_ifindex = ifIndex;
|
|
lladdr.sll_hatype = 0;
|
|
lladdr.sll_pkttype = 0;
|
|
lladdr.sll_halen = ETH_ALEN;
|
|
err=bind(sock, (struct sockaddr *)&lladdr, sizeof(lladdr));
|
|
if (err < 0)
|
|
{
|
|
flog(LOG_ERR, "packet socket bind to interface %d failed: %s", ifIndex, strerror(errno));
|
|
return (-1);
|
|
}
|
|
flog(LOG_DEBUG2, "packet socket bind to interface %d OK", ifIndex);
|
|
|
|
// Tie the BSD-PF filter to the socket
|
|
err = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
|
|
if (err < 0)
|
|
{
|
|
flog(LOG_ERR, "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
|
|
return (-1);
|
|
}
|
|
flog(LOG_DEBUG2, "setsockopt(SO_ATTACH_FILTER) OK");
|
|
|
|
return sock;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* open_icmpv6_socket
|
|
* Opens the ipv6-level socket, for outgoing traffic.
|
|
*
|
|
* Inputs:
|
|
* none
|
|
*
|
|
* Outputs:
|
|
* none
|
|
*
|
|
* Return:
|
|
* int sock on success, otherwise -1
|
|
*
|
|
*/
|
|
int open_icmpv6_socket(void)
|
|
{
|
|
int sock, err, optval = 1;
|
|
|
|
sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
|
|
if (sock < 0)
|
|
{
|
|
flog(LOG_ERR, "Can't create socket(AF_INET6): %s", strerror(errno));
|
|
return (-1);
|
|
}
|
|
|
|
err = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &maxHops, sizeof(maxHops));
|
|
if (err < 0)
|
|
{
|
|
flog(LOG_ERR, "setsockopt(IPV6_UNICAST_HOPS = %d): %s", maxHops, strerror(errno));
|
|
return (-1);
|
|
}
|
|
flog(LOG_DEBUG2, "setsockopt(IPV6_UNICAST_HOPS = %d) OK", maxHops);
|
|
|
|
/* Set packet info return via recvmsg() later */
|
|
/* Needed to get src addr of received ICMP pkts */
|
|
err = setsockopt(sock, IPPROTO_IPV6, IPV6_2292PKTINFO, &optval, sizeof(optval));
|
|
if (err < 0)
|
|
{
|
|
flog(LOG_ERR, "setsockopt(IPV6_2292PKTINFO): %s", strerror(errno));
|
|
return (-1);
|
|
}
|
|
flog(LOG_DEBUG2, "setsockopt(IPV6_2292PKTINFO) OK");
|
|
|
|
|
|
return sock;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* get_rx
|
|
* Called from the dispatcher to pull in the received packet.
|
|
*
|
|
* Inputs:
|
|
* socket is where the data is waiting.
|
|
*
|
|
* Outputs:
|
|
* unsigned char *msg
|
|
* The data.
|
|
*
|
|
* Return:
|
|
* int length of data received, otherwise -1 on error
|
|
*
|
|
* NOTES:
|
|
* There's a lot of temp data structures needed here, but we really don't
|
|
* care about them afterwards. Once we've got the raw data and the len
|
|
* we're good.
|
|
*/
|
|
int get_rx(int socket, unsigned char *msg)
|
|
{
|
|
struct sockaddr_in6 saddr;
|
|
struct msghdr mhdr;
|
|
struct iovec iov;
|
|
int len;
|
|
fd_set rfds;
|
|
|
|
FD_ZERO( &rfds );
|
|
FD_SET( socket, &rfds );
|
|
|
|
if( select( socket+1, &rfds, NULL, NULL, NULL ) < 0 )
|
|
{
|
|
if (errno != EINTR)
|
|
flog(LOG_ERR, "select failed with: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
iov.iov_len = MAX_MSG_SIZE;
|
|
iov.iov_base = (caddr_t) msg;
|
|
|
|
memset(&mhdr, 0, sizeof(mhdr));
|
|
mhdr.msg_name = (caddr_t)&saddr;
|
|
mhdr.msg_namelen = sizeof(saddr);
|
|
mhdr.msg_iov = &iov;
|
|
mhdr.msg_iovlen = 1;
|
|
mhdr.msg_control = NULL;
|
|
mhdr.msg_controllen = 0;
|
|
|
|
len = recvmsg(socket, &mhdr, 0);
|
|
|
|
/* Impossible.. But let's not take chances */
|
|
if (len > MAX_MSG_SIZE)
|
|
{
|
|
flog(LOG_ERR, "Read more data from socket than we can handle. Ignoring it.");
|
|
return -1;
|
|
}
|
|
|
|
if (len < 0)
|
|
{
|
|
if (errno != EINTR)
|
|
flog(LOG_ERR, "recvmsg failed with: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* get_rx_icmp6
|
|
* Called from the dispatcher to pull in the received packet.
|
|
*
|
|
* Inputs:
|
|
* socket is where the data is waiting.
|
|
*
|
|
* Outputs:
|
|
* unsigned char *msg
|
|
* The data.
|
|
*
|
|
* Return:
|
|
* int length of data received, otherwise -1 on error
|
|
*
|
|
* NOTES:
|
|
* There's a lot of temp data structures needed here, but we really don't
|
|
* care about them afterwards. Once we've got the raw data and the len
|
|
* we're good.
|
|
*/
|
|
int get_rx_icmp6(int socket, unsigned char *msg, struct in6_addr *addr6)
|
|
{
|
|
struct sockaddr_in6 saddr;
|
|
struct msghdr mhdr;
|
|
struct iovec iov[2];
|
|
int len;
|
|
fd_set rfds;
|
|
|
|
/* For ancillary data */
|
|
u_char cbuf[2048];
|
|
struct cmsghdr *cm;
|
|
struct in6_pktinfo *thispkt6;
|
|
char addr_str[INET6_ADDRSTRLEN];
|
|
|
|
FD_ZERO( &rfds );
|
|
FD_SET( socket, &rfds );
|
|
|
|
if( select( socket+1, &rfds, NULL, NULL, NULL ) < 0 )
|
|
{
|
|
if (errno != EINTR)
|
|
flog(LOG_ERR, "select failed with: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
iov[0].iov_len = MAX_MSG_SIZE;
|
|
iov[0].iov_base = (caddr_t) msg;
|
|
|
|
memset(&mhdr, 0, sizeof(mhdr));
|
|
mhdr.msg_name = &saddr;
|
|
mhdr.msg_namelen = sizeof(saddr);
|
|
mhdr.msg_iov = iov;
|
|
mhdr.msg_iovlen = 1;
|
|
mhdr.msg_control = (caddr_t)cbuf;
|
|
mhdr.msg_controllen = sizeof(cbuf);
|
|
|
|
len = recvmsg(socket, &mhdr, 0);
|
|
|
|
/* Src addr - copy it for caller*/
|
|
memcpy(addr6, &(saddr.sin6_addr), sizeof(struct in6_addr));
|
|
print_addr(&(saddr.sin6_addr), addr_str);
|
|
flog( LOG_DEBUG2, "RA received src address: %s", addr_str);
|
|
|
|
/* Walk the chain of cmsg blocks (although unlikely to be more than 1) */
|
|
for (cm = CMSG_FIRSTHDR(&mhdr); cm != NULL; cm = CMSG_NXTHDR(&mhdr, cm))
|
|
{
|
|
if (cm->cmsg_level == IPPROTO_IPV6 &&
|
|
cm->cmsg_type == IPV6_2292PKTINFO &&
|
|
cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo)))
|
|
{
|
|
flog(LOG_DEBUG2, "Ancillary data contains in6_pktinfo.");
|
|
thispkt6 = (struct in6_pktinfo *)CMSG_DATA(cm);
|
|
// Dst addr
|
|
char addr_str[INET6_ADDRSTRLEN];
|
|
print_addr(&(thispkt6->ipi6_addr), addr_str);
|
|
flog( LOG_DEBUG2, "RA received dst address: %s", addr_str);
|
|
}
|
|
else
|
|
{
|
|
flog(LOG_DEBUG2, "Ancillary data was unrecognised.");
|
|
}
|
|
}
|
|
|
|
/* Impossible.. But let's not take chances */
|
|
if (len > MAX_MSG_SIZE)
|
|
{
|
|
flog(LOG_ERR, "Read more data from socket than we can handle. Ignoring it.");
|
|
return -1;
|
|
}
|
|
|
|
if (len < 0)
|
|
{
|
|
if (errno != EINTR)
|
|
flog(LOG_ERR, "recvmsg failed with: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* if_allmulti
|
|
* Called during startup and shutdown. Set/clear allmulti
|
|
* as required.
|
|
*
|
|
* Inputs:
|
|
* ifname is interface name
|
|
* state: 1-> Set (or confirm) flag is enabled
|
|
* state: 0-> Set flag to unset condition.
|
|
*
|
|
* Outputs:
|
|
* none
|
|
*
|
|
* Return:
|
|
* The previous value of the flag, prior to change.
|
|
*
|
|
* Notes:
|
|
* Miserere mihi peccatori.
|
|
*/
|
|
int if_allmulti(char *ifname, unsigned int state)
|
|
{
|
|
struct ifreq ifr;
|
|
int skfd;
|
|
int current;
|
|
|
|
flog(LOG_DEBUG2, "Requesting that %s be set to state = %d",
|
|
ifname, state);
|
|
|
|
skfd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
|
|
// Get current flags, etc.
|
|
if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0)
|
|
{
|
|
flog(LOG_ERR, "Unknown interface: %s, failed err = %s", ifname, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
current = ifr.ifr_flags;
|
|
|
|
if (state)
|
|
{
|
|
flog(LOG_DEBUG, "Setting IFFALLMULTI if required.");
|
|
ifr.ifr_flags |= IFF_ALLMULTI;
|
|
if (ifr.ifr_flags == current)
|
|
{
|
|
// Already set
|
|
flog(LOG_DEBUG, "Not required, was set at anyway.");
|
|
goto sinfulexit;;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flog(LOG_DEBUG, "Clearing IFFALLMULTI if required.");
|
|
ifr.ifr_flags &= ~IFF_ALLMULTI;
|
|
}
|
|
|
|
if (ioctl(skfd, SIOCSIFFLAGS, &ifr) < 0)
|
|
{
|
|
flog(LOG_ERR, "Flag change failed: %s, failed err = %s", ifname, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
sinfulexit:
|
|
close(skfd);
|
|
return (current || IFF_ALLMULTI);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* init_sockets
|
|
* Initialises the tx and rx sockets. Normally just called during startup,
|
|
* but also to reinitialise sockets if they go bad.
|
|
*
|
|
* Inputs:
|
|
* void
|
|
*
|
|
* Outputs:
|
|
* Set global sockicmp and per i/f rx pkt socket
|
|
*
|
|
* Return:
|
|
* Non-0 if failure, else 0.
|
|
*/
|
|
int init_sockets(void)
|
|
{
|
|
int errcount = 0;
|
|
int loop, sock, sockicmp;
|
|
|
|
/* Raw socket for receiving NSs */
|
|
for (loop=0; loop < interfaceCount; loop++)
|
|
{
|
|
sock = open_packet_socket(interfaces[loop].index);
|
|
|
|
if (sock < 0)
|
|
{
|
|
flog(LOG_ERR, "open_packet_socket: failed on iteration %d", loop);
|
|
errcount++;
|
|
}
|
|
interfaces[loop].pktSock = sock;
|
|
flog(LOG_DEBUG, "open_packet_socket: %d OK.", loop);
|
|
flog(LOG_DEBUG2, "open_packet_socket value = %d", sock);
|
|
|
|
/* ICMPv6 socket for sending NAs */
|
|
sockicmp = open_icmpv6_socket();
|
|
if (sockicmp < 0)
|
|
{
|
|
flog(LOG_ERR, "open_icmpv6_socket: failed.");
|
|
errcount++;
|
|
}
|
|
flog(LOG_DEBUG, "open_icmpv6_socket: OK.");
|
|
interfaces[loop].icmpSock = sockicmp;
|
|
}
|
|
|
|
return errcount;
|
|
}
|