#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <sys/filio.h>
#include <ctype.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#define KERNEL_PRIVATE
#include <netinet6/in6_var.h>
#undef KERNEL_PRIVATE
#include <netinet/icmp6.h>
#include <netinet6/nd6.h>
#include <arpa/inet.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <syslog.h>
#include <sys/uio.h>
#include "RTADVSocket.h"
#include "util.h"
#include "interfaces.h"
#include "FDSet.h"
#include "dynarray.h"
#include "ipconfigd_globals.h"
#include "symbol_scope.h"
#include "timer.h"
#include "IPv6Sock_Compat.h"
STATIC const struct sockaddr_in6 sin6_allrouters = {
sizeof(sin6_allrouters),
AF_INET6, 0, 0,
IN6ADDR_LINKLOCAL_ALLROUTERS_INIT, 0
};
#define RECEIVE_CMSG_BUF_SIZE (CMSG_SPACE(sizeof(struct in6_pktinfo)) \
+ CMSG_SPACE(sizeof(int)))
#define SEND_CMSG_BUF_SIZE RECEIVE_CMSG_BUF_SIZE
#define ND_OPT_ALIGN 8
#define ND_RTADV_HOP_LIMIT 255
#ifndef ND_OPT_RDNSS
#define ND_OPT_RDNSS 25
struct nd_opt_rdnss {
u_int8_t nd_opt_rdnss_type;
u_int8_t nd_opt_rdnss_len;
u_int16_t nd_opt_rdnss_reserved;
u_int32_t nd_opt_rdnss_lifetime;
struct in6_addr nd_opt_rdnss_addr[1];
} __attribute__((__packed__));
#endif
struct _nd_opt_linkaddr {
u_int8_t nd_opt_linkaddr_type;
u_int8_t nd_opt_linkaddr_len;
u_int8_t nd_opt_linkaddr_data[1];
} __attribute__((__packed__));
#define RTSOL_PACKET_MAX (sizeof(struct nd_router_solicit) \
+ roundup(sizeof(struct nd_opt_hdr) \
+ MAX_LINK_ADDR_LEN, ND_OPT_ALIGN))
typedef struct RTADVSocketGlobals {
dynarray_t sockets;
FDCalloutRef read_fd;
int read_fd_refcount;
timer_callout_t * timer_callout;
} RTADVSocketGlobals, * RTADVSocketGlobalsRef;
struct RTADVSocket {
interface_t * if_p;
boolean_t fd_open;
RTADVSocketReceiveFuncPtr receive_func;
void * receive_arg1;
void * receive_arg2;
};
STATIC RTADVSocketGlobalsRef S_globals;
STATIC int
open_rtadv_socket(void)
{
struct icmp6_filter filt;
int opt = 1;
int sockfd;
if ((sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
my_log(LOG_ERR, "RTADVSocket: error opening socket: %s",
strerror(errno));
goto failed;
}
if (ioctl(sockfd, FIONBIO, &opt) < 0) {
my_log(LOG_ERR, "RTADVSocket: ioctl FIONBIO failed %s",
strerror(errno));
goto failed;
}
if (setsockopt(sockfd, IPPROTO_IPV6,
IPCONFIG_SOCKOPT_PKTINFO, &opt, sizeof(opt)) < 0) {
my_log(LOG_ERR, "RTADVSocket: IPV6_PKTINFO: %s",
strerror(errno));
goto failed;
}
if (setsockopt(sockfd, IPPROTO_IPV6,
IPCONFIG_SOCKOPT_HOPLIMIT, &opt, sizeof(opt)) < 0) {
my_log(LOG_ERR, "RTADVSocket: IPV6_HOPLIMIT: %s",
strerror(errno));
goto failed;
}
#ifdef SO_TC_CTL
opt = SO_TC_CTL;
(void)setsockopt(sockfd, SOL_SOCKET, SO_TRAFFIC_CLASS, &opt,
sizeof(opt));
#endif
ICMP6_FILTER_SETBLOCKALL(&filt);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
if (setsockopt(sockfd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
sizeof(filt)) == -1) {
my_log(LOG_ERR, "RTADVSocket: setsockopt(ICMP6_FILTER): %s",
strerror(errno));
goto failed;
}
return (sockfd);
failed:
if (sockfd >= 0) {
close(sockfd);
}
return (-1);
}
STATIC void
RTADVSocketFreeElement(void * arg);
STATIC RTADVSocketGlobalsRef
RTADVSocketCreateGlobals(void)
{
RTADVSocketGlobalsRef globals = malloc(sizeof(*globals));
if (globals == NULL) {
return (NULL);
}
bzero(globals, sizeof(*globals));
dynarray_init(&globals->sockets, RTADVSocketFreeElement, NULL);
globals->timer_callout = timer_callout_init();
return (globals);
}
STATIC void
RTADVSocketReleaseGlobals(RTADVSocketGlobalsRef * globals_p)
{
RTADVSocketGlobalsRef globals;
if (globals_p == NULL) {
return;
}
globals = *globals_p;
if (globals == NULL) {
return;
}
*globals_p = NULL;
dynarray_free(&globals->sockets);
FDCalloutRelease(&globals->read_fd);
timer_callout_free(&globals->timer_callout);
bzero(globals, sizeof(*globals));
free(globals);
return;
}
STATIC RTADVSocketGlobalsRef
RTADVSocketGetGlobals(void)
{
if (S_globals != NULL) {
return (S_globals);
}
S_globals = RTADVSocketCreateGlobals();
return (S_globals);
}
STATIC RTADVSocketRef
RTADVSocketFind(int if_index)
{
int i;
for (i = 0; i < dynarray_count(&S_globals->sockets); i++) {
interface_t * if_p;
RTADVSocketRef sock;
sock = dynarray_element(&S_globals->sockets, i);
if_p = RTADVSocketGetInterface(sock);
if (if_index == if_link_index(if_p)) {
return (sock);
}
}
return (NULL);
}
STATIC void
RTADVSocketDelayedClose(void * arg1, void * arg2, void * arg3)
{
if (S_globals->read_fd == NULL) {
my_log(LOG_ERR,
"RTADVSocketDelayedClose(): socket is already closed");
return;
}
if (S_globals->read_fd_refcount > 0) {
my_log(LOG_ERR,
"RTADVSocketDelayedClose(): called when socket in use");
return;
}
my_log(LOG_DEBUG,
"RTADVSocketDelayedClose(): closing RTADV socket %d",
FDCalloutGetFD(S_globals->read_fd));
FDCalloutRelease(&S_globals->read_fd);
return;
}
STATIC void
parse_nd_options(ptrlist_t * options_p, const char * buf, int len)
{
int left = len;
const struct nd_opt_hdr * opt;
int opt_len = 0;
const char * scan;
ptrlist_init(options_p);
for (scan = buf; left >= sizeof(*opt); ) {
opt = (struct nd_opt_hdr *)scan;
opt_len = opt->nd_opt_len * ND_OPT_ALIGN;
if (opt_len > left) {
ptrlist_free(options_p);
break;
}
ptrlist_add(options_p, (void *)opt);
scan += opt_len;
left -= opt_len;
}
return;
}
#define ND_OPT_RDNSS_MIN_LENGTH sizeof(struct nd_opt_rdnss)
#define ND_OPT_RDNSS_HEADER_LENGTH offsetof(struct nd_opt_rdnss, \
nd_opt_rdnss_addr)
#ifdef DEBUG
STATIC const char *
S_nd_opt_name(int nd_opt)
{
const char * str;
switch (nd_opt) {
case ND_OPT_SOURCE_LINKADDR:
str = "Source Link Address";
break;
case ND_OPT_TARGET_LINKADDR:
str = "Target Link Address";
break;
case ND_OPT_PREFIX_INFORMATION:
str = "Prefix Information";
break;
case ND_OPT_REDIRECTED_HEADER:
str = "Redirected Header";
break;
case ND_OPT_MTU:
str = "MTU";
break;
case ND_OPT_RDNSS:
str = "Domain Name Servers";
break;
default:
str = "<unknown>";
break;
}
return (str);
}
STATIC void
print_nd_options(ptrlist_t * options_p)
{
int count;
int i;
count = ptrlist_count(options_p);
printf("There are %d options\n", count);
for (i = 0; i < count; i++) {
const struct nd_opt_hdr * opt;
const char * opt_name;
opt = (const struct nd_opt_hdr *)ptrlist_element(options_p, i);
opt_name = S_nd_opt_name(opt->nd_opt_type);
printf("%s (%d) length %d\n", opt_name, opt->nd_opt_type,
opt->nd_opt_len * ND_OPT_ALIGN);
}
return;
}
#endif
STATIC const struct in6_addr *
find_rdnss(ptrlist_t * options_p, int * how_many)
{
int count;
int i;
count = ptrlist_count(options_p);
*how_many = 0;
for (i = 0; i < count; i++) {
const struct nd_opt_rdnss * opt;
opt = (const struct nd_opt_rdnss *)ptrlist_element(options_p, i);
if (opt->nd_opt_rdnss_type == ND_OPT_RDNSS) {
int opt_len = opt->nd_opt_rdnss_len * ND_OPT_ALIGN;
if (opt_len < ND_OPT_RDNSS_MIN_LENGTH) {
break;
}
if (opt->nd_opt_rdnss_lifetime == 0) {
continue;
}
*how_many = (opt_len - ND_OPT_RDNSS_HEADER_LENGTH)
/ sizeof(struct in6_addr);
return (opt->nd_opt_rdnss_addr);
}
}
return (NULL);
}
#define _ND_OPT_LINKADDR_HDR_SIZE offsetof(struct _nd_opt_linkaddr, nd_opt_linkaddr_data)
STATIC const uint8_t *
find_source_link_address(ptrlist_t * options_p, int * ret_len)
{
int count;
int i;
count = ptrlist_count(options_p);
*ret_len = 0;
for (i = 0; i < count; i++) {
const struct _nd_opt_linkaddr * opt;
opt = (const struct _nd_opt_linkaddr *)ptrlist_element(options_p, i);
if (opt->nd_opt_linkaddr_type == ND_OPT_SOURCE_LINKADDR) {
int opt_len = opt->nd_opt_linkaddr_len * ND_OPT_ALIGN;
if (opt_len < (_ND_OPT_LINKADDR_HDR_SIZE + ETHER_ADDR_LEN)) {
break;
}
*ret_len = opt_len - _ND_OPT_LINKADDR_HDR_SIZE;
return (opt->nd_opt_linkaddr_data);
}
}
return (NULL);
}
STATIC void
RTADVSocketReceiveDataInit(RTADVSocketReceiveDataRef data_p,
const struct in6_addr * router_p,
const struct nd_router_advert * ndra_p, int n)
{
ptrlist_t options;
data_p->router = *router_p;
data_p->managed_bit
= (ndra_p->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) != 0;
data_p->other_bit
= (ndra_p->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) != 0;
parse_nd_options(&options, (char *)(ndra_p + 1), n - sizeof(*ndra_p));
#ifdef DEBUG
print_nd_options(&options);
#endif
data_p->dns_servers = find_rdnss(&options, &data_p->dns_servers_count);
data_p->router_hwaddr
= find_source_link_address(&options, &data_p->router_hwaddr_len);
ptrlist_free(&options);
return;
}
STATIC void
RTADVSocketDemux(int if_index, const struct in6_addr * router_p,
const struct nd_router_advert * ndra_p, int n)
{
RTADVSocketRef sock;
sock = RTADVSocketFind(if_index);
if (sock == NULL) {
return;
}
if (sock->receive_func != NULL) {
RTADVSocketReceiveData data;
RTADVSocketReceiveDataInit(&data, router_p, ndra_p, n);
(*sock->receive_func)(sock->receive_arg1, sock->receive_arg2, &data);
}
return;
}
STATIC void
RTADVSocketRead(void * arg1, void * arg2)
{
struct cmsghdr * cm;
char cmsgbuf[RECEIVE_CMSG_BUF_SIZE];
int * hop_limit_p;
struct icmp6_hdr * icmp_hdr;
struct iovec iov;
struct sockaddr_in6 from;
struct msghdr mhdr;
int n;
struct nd_router_advert * ndra_p;
char ntopbuf[INET6_ADDRSTRLEN];
struct in6_pktinfo * pktinfo = NULL;
char receive_buf[1500];
iov.iov_base = (caddr_t)receive_buf;
iov.iov_len = sizeof(receive_buf);
mhdr.msg_name = (caddr_t)&from;
mhdr.msg_namelen = sizeof(from);
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = (caddr_t)cmsgbuf;
mhdr.msg_controllen = sizeof(cmsgbuf);
n = recvmsg(FDCalloutGetFD(S_globals->read_fd), &mhdr, 0);
if (n < 0) {
if (errno != EAGAIN) {
my_log(LOG_ERR, "RTADVSocketRead: recvfrom failed %s (%d)",
strerror(errno), errno);
}
return;
}
if (n == 0) {
return;
}
if (n < sizeof(struct nd_router_advert)) {
my_log(LOG_ERR, "RTADVSocketRead: packet size(%d) is too short", n);
return;
}
if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
my_log(LOG_ERR,
"RTADVSocketRead: RA has non link-local source address %s",
inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf, sizeof(ntopbuf)));
return;
}
pktinfo = NULL;
hop_limit_p = NULL;
for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&mhdr);
cm != NULL;
cm = (struct cmsghdr *)CMSG_NXTHDR(&mhdr, cm)) {
if (cm->cmsg_level != IPPROTO_IPV6) {
continue;
}
switch (cm->cmsg_type) {
case IPV6_PKTINFO:
if (cm->cmsg_len < CMSG_LEN(sizeof(struct in6_pktinfo))) {
continue;
}
pktinfo = (struct in6_pktinfo *)(void *)(CMSG_DATA(cm));
break;
case IPV6_HOPLIMIT:
if (cm->cmsg_len < CMSG_LEN(sizeof(int))) {
continue;
}
hop_limit_p = (int *)(void *)CMSG_DATA(cm);
break;
default:
my_log(LOG_ERR, "RTADVSocketRead: why control message type %d?",
cm->cmsg_type);
break;
}
}
if (pktinfo == NULL) {
my_log(LOG_ERR,
"RTADVSocketRead: missing IPV6_PKTINFO");
return;
}
if (hop_limit_p == NULL) {
my_log(LOG_ERR, "RTADVSocketRead: missing IPV6_HOPLIMIT");
return;
}
ndra_p = (struct nd_router_advert *)receive_buf;
icmp_hdr = &ndra_p->nd_ra_hdr;
if (icmp_hdr->icmp6_type != ND_ROUTER_ADVERT) {
my_log(LOG_ERR, "RTADVSocket: received unexpected ND packet type %d",
icmp_hdr->icmp6_type);
return;
}
if (icmp_hdr->icmp6_code != 0) {
my_log(LOG_ERR, "RTADVSocket: invalid icmp code %d",
icmp_hdr->icmp6_code);
return;
}
if (ndra_p->nd_ra_router_lifetime == 0) {
return;
}
if (*hop_limit_p != ND_RTADV_HOP_LIMIT) {
my_log(LOG_ERR, "RTADVSocket: invalid RA hop limit %d from %s",
*hop_limit_p,
inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf, sizeof(ntopbuf)));
return;
}
RTADVSocketDemux(pktinfo->ipi6_ifindex, &from.sin6_addr, ndra_p, n);
return;
}
PRIVATE_EXTERN interface_t *
RTADVSocketGetInterface(RTADVSocketRef sock)
{
return (sock->if_p);
}
STATIC int
RTADVSocketInitTXBuf(RTADVSocketRef sock, uint32_t * txbuf, bool lladdr_ok)
{
interface_t * if_p = RTADVSocketGetInterface(sock);
struct nd_router_solicit * ndrs;
int txbuf_used;
ndrs = (struct nd_router_solicit *)txbuf;
ndrs->nd_rs_type = ND_ROUTER_SOLICIT;
ndrs->nd_rs_code = 0;
ndrs->nd_rs_cksum = 0;
ndrs->nd_rs_reserved = 0;
txbuf_used = sizeof(struct nd_router_solicit);
if (lladdr_ok && if_link_type(if_p) == IFT_ETHER) {
int opt_len;
struct nd_opt_hdr * ndopt;
opt_len = roundup(ETHER_ADDR_LEN + sizeof(*ndopt), ND_OPT_ALIGN);
ndopt = (struct nd_opt_hdr *)(ndrs + 1);
ndopt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
ndopt->nd_opt_len = (opt_len / ND_OPT_ALIGN);
bcopy(if_link_address(if_p), (ndopt + 1), if_link_length(if_p));
txbuf_used += opt_len;
}
return (txbuf_used);
}
PRIVATE_EXTERN RTADVSocketRef
RTADVSocketCreate(interface_t * if_p)
{
RTADVSocketRef sock;
RTADVSocketGlobalsRef globals;
globals = RTADVSocketGetGlobals();
if (globals == NULL) {
my_log(LOG_ERR, "RTADVSocketCreate: could not allocate globals");
return (NULL);
}
sock = RTADVSocketFind(if_link_index(if_p));
if (sock != NULL) {
my_log(LOG_ERR, "RTADVSocketCreate(%s): socket already allocated",
if_name(if_p));
return (NULL);
}
sock = malloc(sizeof(*sock));
if (sock == NULL) {
return (NULL);
}
bzero(sock, sizeof(*sock));
if (dynarray_add(&globals->sockets, sock) == FALSE) {
free(sock);
return (NULL);
}
sock->if_p = if_p;
return (sock);
}
STATIC void
RTADVSocketFreeElement(void * arg)
{
RTADVSocketRef sock = (RTADVSocketRef)arg;
RTADVSocketDisableReceive(sock);
free(sock);
return;
}
PRIVATE_EXTERN void
RTADVSocketRelease(RTADVSocketRef * sock_p)
{
RTADVSocketRef sock = *sock_p;
int i;
if (sock == NULL) {
return;
}
i = dynarray_index(&S_globals->sockets, sock);
if (i != -1) {
dynarray_remove(&S_globals->sockets, i, NULL);
}
else {
my_log(LOG_ERR, "RTADVSocketRelease: %s not in list?",
if_name(sock->if_p));
}
RTADVSocketFreeElement(sock);
*sock_p = NULL;
if (dynarray_count(&S_globals->sockets) == 0) {
RTADVSocketReleaseGlobals(&S_globals);
}
return;
}
STATIC void
RTADVSocketCloseSocket(RTADVSocketRef sock)
{
if (sock->fd_open == FALSE) {
return;
}
if (S_globals->read_fd_refcount <= 0) {
my_log(LOG_ERR, "RTADVSocketCloseSocket(%s): refcount %d",
if_name(sock->if_p), S_globals->read_fd_refcount);
return;
}
S_globals->read_fd_refcount--;
my_log(LOG_DEBUG, "RTADVSocketCloseSocket(%s): refcount %d",
if_name(sock->if_p), S_globals->read_fd_refcount);
sock->fd_open = FALSE;
if (S_globals->read_fd_refcount == 0) {
struct timeval tv;
my_log(LOG_DEBUG,
"RTADVSocketCloseSocket(): scheduling delayed close");
tv.tv_sec = 1;
tv.tv_usec = 0;
timer_set_relative(S_globals->timer_callout, tv,
RTADVSocketDelayedClose,
NULL, NULL, NULL);
}
return;
}
STATIC bool
RTADVSocketOpenSocket(RTADVSocketRef sock)
{
if (sock->fd_open) {
return (TRUE);
}
timer_cancel(S_globals->timer_callout);
S_globals->read_fd_refcount++;
my_log(LOG_DEBUG, "RTADVSocketOpenSocket (%s): refcount %d",
if_name(sock->if_p), S_globals->read_fd_refcount);
sock->fd_open = TRUE;
if (S_globals->read_fd_refcount > 1) {
return (TRUE);
}
if (S_globals->read_fd != NULL) {
my_log(LOG_DEBUG, "RTADVSocketOpenSocket(): socket is still open");
}
else {
int sockfd;
sockfd = open_rtadv_socket();
if (sockfd < 0) {
my_log(LOG_ERR,
"RTADVSocketOpenSocket: socket() failed, %m");
goto failed;
}
my_log(LOG_DEBUG,
"RTADVSocketOpenSocket(): opened RTADV socket %d\n",
sockfd);
S_globals->read_fd = FDCalloutCreate(sockfd,
RTADVSocketRead,
NULL, NULL);
}
return (TRUE);
failed:
RTADVSocketCloseSocket(sock);
return (FALSE);
}
PRIVATE_EXTERN void
RTADVSocketEnableReceive(RTADVSocketRef sock,
RTADVSocketReceiveFuncPtr func,
void * arg1, void * arg2)
{
sock->receive_func = func;
sock->receive_arg1 = arg1;
sock->receive_arg2 = arg2;
if (RTADVSocketOpenSocket(sock) == FALSE) {
my_log(LOG_ERR, "RTADVSocketEnableReceive(%s): failed",
if_name(sock->if_p));
}
return;
}
PRIVATE_EXTERN void
RTADVSocketDisableReceive(RTADVSocketRef sock)
{
sock->receive_func = NULL;
sock->receive_arg1 = NULL;
sock->receive_arg2 = NULL;
RTADVSocketCloseSocket(sock);
return;
}
STATIC int
S_send_packet(int sockfd, int ifindex, char * pkt, int pkt_size)
{
struct cmsghdr * cm;
char cmsgbuf[SEND_CMSG_BUF_SIZE];
struct iovec iov;
struct msghdr mhdr;
int n;
struct in6_pktinfo *pi;
int ret;
iov.iov_base = (caddr_t)pkt;
iov.iov_len = pkt_size;
mhdr.msg_name = (caddr_t)&sin6_allrouters;
mhdr.msg_namelen = sizeof(struct sockaddr_in6);
mhdr.msg_flags = 0;
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = (caddr_t)cmsgbuf;
mhdr.msg_controllen = sizeof(cmsgbuf);
bzero(cmsgbuf, sizeof(cmsgbuf));
cm = CMSG_FIRSTHDR(&mhdr);
if (cm == NULL) {
return (EINVAL);
}
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
pi = (struct in6_pktinfo *)(void *)CMSG_DATA(cm);
pi->ipi6_ifindex = ifindex;
cm = CMSG_NXTHDR(&mhdr, cm);
if (cm == NULL) {
return (EINVAL);
}
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_HOPLIMIT;
cm->cmsg_len = CMSG_LEN(sizeof(int));
*((int *)(void *)CMSG_DATA(cm)) = 255;
n = sendmsg(sockfd, &mhdr, 0);
if (n != pkt_size) {
ret = errno;
}
else {
ret = 0;
}
return (ret);
}
PRIVATE_EXTERN int
RTADVSocketSendSolicitation(RTADVSocketRef sock, bool lladdr_ok)
{
interface_t * if_p = RTADVSocketGetInterface(sock);
boolean_t needs_close = FALSE;
int ret;
uint32_t txbuf[RTSOL_PACKET_MAX/sizeof(uint32_t)];
int txbuf_used;
if (sock->fd_open == FALSE) {
if (RTADVSocketOpenSocket(sock) == FALSE) {
my_log(LOG_ERR, "RTADVSocket: failed to open socket\n");
return (FALSE);
}
needs_close = TRUE;
}
txbuf_used = RTADVSocketInitTXBuf(sock, txbuf, lladdr_ok);
ret = S_send_packet(FDCalloutGetFD(S_globals->read_fd),
if_link_index(if_p), (void *)txbuf,
txbuf_used);
if (needs_close) {
RTADVSocketCloseSocket(sock);
}
return (ret);
}
#ifdef TEST_RTADVSOCKET
#include <CoreFoundation/CFRunLoop.h>
#include "ipconfigd_threads.h"
int G_IPConfiguration_verbose = 1;
typedef struct {
RTADVSocketRef sock;
timer_callout_t * timer;
int try;
} RTADVInfo, * RTADVInfoRef;
STATIC void
start_rtadv(RTADVInfoRef rtadv, IFEventID_t event_id, void * event_data)
{
RTADVSocketReceiveDataRef data;
int error;
interface_t * if_p = RTADVSocketGetInterface(rtadv->sock);
char ntopbuf[INET6_ADDRSTRLEN];
switch (event_id) {
case IFEventID_start_e:
rtadv->try = 1;
RTADVSocketEnableReceive(rtadv->sock,
(RTADVSocketReceiveFuncPtr)start_rtadv,
rtadv, (void *)IFEventID_data_e);
case IFEventID_timeout_e:
if (rtadv->try > MAX_RTR_SOLICITATIONS) {
break;
}
my_log(LOG_DEBUG,
"RTADV %s: sending Router Solicitation (%d of %d)",
if_name(if_p), rtadv->try, MAX_RTR_SOLICITATIONS);
error = RTADVSocketSendSolicitation(rtadv->sock);
switch (error) {
case 0:
case ENXIO:
case ENETDOWN:
case EADDRNOTAVAIL:
break;
default:
my_log(LOG_ERR, "RTADV %s: send Router Solicitation: failed, %s",
if_name(if_p), strerror(error));
break;
}
rtadv->try++;
break;
case IFEventID_data_e:
data = (RTADVSocketReceiveDataRef)event_data;
my_log(LOG_NOTICE,
"RTADV %s: Received RA from %s%s%s",
if_name(if_p),
inet_ntop(AF_INET6, &data->router,
ntopbuf, sizeof(ntopbuf)),
data->managed_bit ? " [Managed]" : "",
data->other_bit ? " [OtherConfig]" : "");
if (data->dns_servers != NULL) {
int i;
for (i = 0; i < data->dns_servers_count; i++) {
my_log(LOG_NOTICE,
"RTADV %s: DNS Server %s",
if_name(if_p),
inet_ntop(AF_INET6, data->dns_servers + i,
ntopbuf, sizeof(ntopbuf)));
}
}
break;
default:
break;
}
return;
}
int
main(int argc, char * argv[])
{
interface_t * if_p;
const char * ifname;
interface_list_t * interfaces = NULL;
RTADVInfo rtadv;
(void) openlog("rtadv", LOG_PERROR | LOG_PID, LOG_DAEMON);
interfaces = ifl_init();
if (argc != 2) {
fprintf(stderr, "rtadv <ifname>\n");
exit(1);
}
ifname = argv[1];
if_p = ifl_find_name(interfaces, ifname);
if (if_p == NULL) {
fprintf(stderr, "no such interface '%s'\n", ifname);
exit(2);
}
rtadv.sock = RTADVSocketCreate(if_p);
rtadv.timer = timer_callout_init();
start_rtadv(&rtadv, IFEventID_start_e, NULL);
CFRunLoopRun();
exit(0);
return (0);
}
#endif