#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <ctype.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>
#include <SystemConfiguration/SCPrivate.h>
#include "configthreads_common.h"
#include "globals.h"
#include "ip6config_utils.h"
CFArrayRef
SCNetworkReachabilityCopyResolvedAddress(SCNetworkReachabilityRef target,
int * error_num);
typedef struct {
SCNetworkReachabilityRef target;
struct in6_addr our_6to4_addr;
int our_prefixLen;
int addr_flags;
struct in6_addr our_relay;
struct in6_addr prefixmask;
struct in6_addr netaddr;
} Service_6to4_t;
#define TEN_NET 0x0a000000
#define IN_TEN_NET(i) (((u_int32_t)(i) & 0xff000000) == TEN_NET)
#define ONE_NINE_TWO_NET 0xc0a80000
#define IN_ONE_NINE_TWO_NET(i) (((u_int32_t)(i) & 0xffff0000) == ONE_NINE_TWO_NET)
#define ONE_SEVEN_TWO_NET_START 0xac100000
#define ONE_SEVEN_TWO_NET_END 0xac1f0000
#define IN_ONE_SEVEN_TWO_NET(i) \
((((u_int32_t)(i) & 0xfff00000) >= ONE_SEVEN_TWO_NET_START) \
&& (((u_int32_t)(i) & 0xfff00000) <= ONE_SEVEN_TWO_NET_END))
#define IS_NOT_ROUTABLE_IP(i) \
((IN_TEN_NET(i)) || (IN_ONE_NINE_TWO_NET(i)) || (IN_ONE_SEVEN_TWO_NET(i)))
static boolean_t
stf_get_valid_ip4(ip6config_method_data_t * method_data, struct in_addr * ip4_addr)
{
int i, count = method_data->stf_data.n_ip4;
boolean_t found = FALSE;
if (method_data->stf_data.ip4_addrs_list != NULL) {
for (i = 0; i < count; i++) {
if (IN_LINKLOCAL(method_data->stf_data.ip4_addrs_list[i].s_addr)
|| IS_NOT_ROUTABLE_IP(method_data->stf_data.ip4_addrs_list[i].s_addr)) {
my_log(LOG_DEBUG, "6to4: not a good address");
continue;
}
*ip4_addr = method_data->stf_data.ip4_addrs_list[i];
found = TRUE;
break;
}
}
return (found);
}
static void
stf_construct_6to4_address(struct in_addr * ip4_addr, struct in6_addr * ip6_addr, boolean_t relay)
{
char str[64];
uint32_t tmp_addr = (uint32_t)ip4_addr->s_addr;
ip6_addr->s6_addr[0] = 0x20;
ip6_addr->s6_addr[1] = 0x02;
ip6_addr->s6_addr[2] = ((tmp_addr & 0xff000000) >> 24);
ip6_addr->s6_addr[3] = ((tmp_addr & 0x00ff0000) >> 16);
ip6_addr->s6_addr[4] = ((tmp_addr & 0x0000ff00) >> 8);
ip6_addr->s6_addr[5] = (tmp_addr & 0x000000ff);
ip6_addr->s6_addr[6] = 0x00;
if (relay == FALSE) {
ip6_addr->s6_addr[7] = 0x01;
}
else {
ip6_addr->s6_addr[7] = 0x00;
}
ip6_addr->s6_addr[8] = 0x00;
ip6_addr->s6_addr[9] = 0x00;
ip6_addr->s6_addr[10] = 0x00;
ip6_addr->s6_addr[11] = 0x00;
ip6_addr->s6_addr[12] = 0x00;
ip6_addr->s6_addr[13] = 0x00;
ip6_addr->s6_addr[14] = 0x00;
if (relay == FALSE) {
ip6_addr->s6_addr[15] = 0x01;
}
else {
ip6_addr->s6_addr[15] = 0x00;
}
inet_ntop(AF_INET6, (const void *)ip6_addr, str, sizeof(str));
my_log(LOG_DEBUG, "6to4: constructed address: %s", str);
return;
}
#define DEFAULT_6TO4_PREFIXLEN 16
static void
stf_configure_address(Service_t * service_p, void * event_data)
{
Service_6to4_t * stf = (Service_6to4_t *)service_p->private;
start_event_data_t * evdata = ((start_event_data_t *)event_data);
ip6config_method_data_t * stf_config_data = evdata->config.data;
struct in_addr ip4_addr;
if (stf_get_valid_ip4(stf_config_data, &ip4_addr) == FALSE) {
my_log(LOG_ERR, "ip6config: bad or no ip4 address for 6to4");
return;
}
stf_construct_6to4_address(&ip4_addr, &stf->our_6to4_addr, FALSE);
stf->our_prefixLen = DEFAULT_6TO4_PREFIXLEN;
return;
}
static void
stf_resolve_callback(SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags,
void *info)
{
Service_t * service_p = (Service_t *)info;
Service_6to4_t * stf = service_p->private;
CFArrayRef relay_addrs = NULL;
int i, n, err, found = 0;
if ((flags & kSCNetworkFlagsReachable)
&& !(flags & kSCNetworkFlagsConnectionRequired)) {
relay_addrs = SCNetworkReachabilityCopyResolvedAddress(target,
&err);
}
if (relay_addrs == NULL) {
my_log(LOG_DEBUG, "STF_RESOLVE: relay address not reachable.");
return;
}
SCLog(G_verbose, LOG_INFO, CFSTR("STF_RESOLVE: relays: %@"), relay_addrs);
n = CFArrayGetCount(relay_addrs);
for (i = 0; i < n; i++) {
CFDataRef data = NULL;
struct sockaddr *sa;
data = CFArrayGetValueAtIndex(relay_addrs, i);
if (data == NULL) {
my_log(LOG_DEBUG, "STF_RESOLVE: array member %d contains no data", i);
continue;
}
sa = (struct sockaddr *)CFDataGetBytePtr(data);
if (sa->sa_family == AF_INET6) {
char buf[64];
struct sockaddr_in6 * sa6 = (struct sockaddr_in6 *)sa;
inet_ntop(AF_INET6, &sa6->sin6_addr, buf, sizeof(buf));
my_log(LOG_DEBUG, "STF_RESOLVE: returned addr is IPv6: %s", buf);
memcpy(&stf->our_relay, &sa6->sin6_addr, sizeof(struct in6_addr));
found = 1;
break;
}
}
if (!found) {
for (i = 0; i < n; i++) {
CFDataRef data = NULL;
struct sockaddr *sa;
struct in6_addr tmp6_addr = IN6ADDR_ANY_INIT;
data = CFArrayGetValueAtIndex(relay_addrs, i);
if (data == NULL) {
my_log(LOG_DEBUG, "STF_RESOLVE: array member %d contains no data", i);
continue;
}
sa = (struct sockaddr *)CFDataGetBytePtr(data);
if (sa->sa_family == AF_INET) {
struct sockaddr_in * sa4 = (struct sockaddr_in *)sa;
char buf1[64], buf2[64];
stf_construct_6to4_address(&sa4->sin_addr, &tmp6_addr, TRUE);
inet_ntop(AF_INET, &sa4->sin_addr, buf1, sizeof(buf1));
inet_ntop(AF_INET6, &tmp6_addr, buf2, sizeof(buf2));
my_log(LOG_DEBUG, "STF_RESOLVE: returned addr is IPv4: %s %s", buf1, buf2);
memcpy(&stf->our_relay, &tmp6_addr, sizeof(struct in6_addr));
found = 1;
break;
}
}
}
if (!found) {
my_log(LOG_DEBUG, "stf_resolve_callback: no v6 or v4 addresses found!!!");
}
else {
if (!IN6_IS_ADDR_UNSPECIFIED(&stf->our_6to4_addr)) {
service_publish_clear(service_p);
(void)service_set_address(service_p, &stf->our_6to4_addr,
stf->our_prefixLen);
}
}
if (relay_addrs) {
my_CFRelease(&relay_addrs);
}
SCNetworkReachabilityUnscheduleFromRunLoop(stf->target,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
my_CFRelease(&stf->target);
return;
}
static void
stf_resolve_hostname(char * address, Service_t * service_p)
{
Service_6to4_t * stf = service_p->private;
SCNetworkReachabilityRef target = NULL;
SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
if (address == NULL) {
my_log(LOG_DEBUG, "stf_resolve_hostname: address == NULL");
return;
}
context.info = service_p;
target = SCNetworkReachabilityCreateWithName(NULL, address);
if (target == NULL) {
my_log(LOG_DEBUG, "stf_resolve_hostname: error creating target");
return;
}
if (SCNetworkReachabilitySetCallback(target, stf_resolve_callback, &context)) {
SCNetworkReachabilityScheduleWithRunLoop(target,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
}
stf->target = target;
return;
}
static void
stf_get_relay_address(Service_t * service_p, void * event_data)
{
Service_6to4_t * stf = (Service_6to4_t *)service_p->private;
start_event_data_t * evdata = ((start_event_data_t *)event_data);
ip6config_method_data_t * stf_config_data = evdata->config.data;
relay_address_t * relay_info = &stf_config_data->stf_data.relay_address;
my_log(LOG_DEBUG, "stf_get_relay_address: relay address type is %s",
relay_address_type_string(relay_info->addr_type));
switch(relay_info->addr_type) {
case relay_address_type_ipv6_e: {
memcpy(&stf->our_relay, &relay_info->relay_address_u.ip6_relay_addr,
sizeof(struct in6_addr));
break;
}
case relay_address_type_ipv4_e: {
stf_construct_6to4_address(&relay_info->relay_address_u.ip4_relay_addr,
&stf->our_relay, TRUE);
break;
}
case relay_address_type_dns_e: {
my_log(LOG_DEBUG, "stf_get_relay_address: resolving hostname");
stf_resolve_hostname(relay_info->relay_address_u.dns_relay_addr,
service_p);
break;
}
default: {
my_log(LOG_DEBUG, "stf_get_relay_address: no relay address type given");
break;
}
}
return;
}
__private_extern__ ip6config_status_t
stf_thread(Service_t * service_p, IFEventID_t evid, void * event_data)
{
interface_t * if_p = service_interface(service_p);
Service_6to4_t * stf = (Service_6to4_t *)service_p->private;
ip6config_status_t status = ip6config_status_success_e;
switch (evid) {
case IFEventID_start_e: {
if (if_flags(if_p) & IFF_LOOPBACK) {
status = ip6config_status_invalid_operation_e;
break;
}
my_log(LOG_DEBUG, "STF_THREAD %s: STARTING", if_name(if_p));
if (stf) {
my_log(LOG_DEBUG, "STF_THREAD %s: re-entering start state",
if_name(if_p));
status = ip6config_status_internal_error_e;
break;
}
stf = calloc(1, sizeof(*stf));
if (stf == NULL) {
my_log(LOG_ERR, "STF_THREAD %s: calloc failed",
if_name(if_p));
status = ip6config_status_allocation_failed_e;
break;
}
service_p->private = stf;
stf_configure_address(service_p, event_data);
stf_get_relay_address(service_p, event_data);
if (!IN6_IS_ADDR_UNSPECIFIED(&stf->our_relay)) {
(void)service_set_address(service_p, &stf->our_6to4_addr,
stf->our_prefixLen);
}
break;
}
case IFEventID_stop_e: {
my_log(LOG_DEBUG, "STF_THREAD %s: STOPPING", if_name(if_p));
if (stf == NULL) {
my_log(LOG_DEBUG, "STF_THREAD %s: private data is NULL",
if_name(if_p));
status = ip6config_status_internal_error_e;
break;
}
service_remove_address(service_p);
service_publish_clear(service_p);
if (stf->target) {
SCNetworkReachabilityUnscheduleFromRunLoop(stf->target,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
my_CFRelease(&stf->target);
}
free(stf);
service_p->private = NULL;
break;
}
case IFEventID_change_e: {
change_event_data_t * evdata = ((change_event_data_t *)event_data);
my_log(LOG_DEBUG, "STF_THREAD %s: CHANGE", if_name(if_p));
if (stf == NULL) {
my_log(LOG_DEBUG, "STF_THREAD %s: private data is NULL",
if_name(if_p));
status = ip6config_status_internal_error_e;
break;
}
if (stf->target) {
SCNetworkReachabilityUnscheduleFromRunLoop(stf->target,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
my_CFRelease(&stf->target);
}
service_publish_clear(service_p);
bzero(&stf->our_relay, sizeof(struct in6_addr));
stf_get_relay_address(service_p, event_data);
if (!IN6_IS_ADDR_UNSPECIFIED(&stf->our_relay)) {
memcpy(&service_p->info.router, &stf->our_relay, sizeof(struct in6_addr));
service_publish_success(service_p);
}
else {
service_remove_address(service_p);
}
evdata->needs_stop = FALSE;
break;
}
case IFEventID_state_change_e: {
int i;
ip6_addrinfo_list_t * ip6_addrs = ((ip6_addrinfo_list_t *)event_data);
my_log(LOG_DEBUG, "STF_THREAD %s: STATE_CHANGE", if_name(if_p));
if (stf == NULL) {
my_log(LOG_DEBUG, "STF_THREAD %s: private data is NULL",
if_name(if_p));
status = ip6config_status_internal_error_e;
break;
}
for (i = 0; i < ip6_addrs->n_addrs; i++) {
ip6_addrinfo_t new_addr = ip6_addrs->addr_list[i];
if (!IN6_IS_ADDR_LINKLOCAL(&new_addr.addr)
&& !(new_addr.flags & IN6_IFF_AUTOCONF)) {
memcpy(&service_p->info.addr, &new_addr.addr,
sizeof(struct in6_addr));
service_p->info.prefixlen = new_addr.prefixlen;
service_p->info.addr_flags = new_addr.flags;
prefixLen2mask(&service_p->info.prefixmask,
service_p->info.prefixlen);
network_addr(&service_p->info.addr,
&service_p->info.prefixmask,
&service_p->info.netaddr);
memcpy(&stf->our_6to4_addr, &service_p->info.addr,
sizeof(struct in6_addr));
stf->our_prefixLen = service_p->info.prefixlen;
stf->addr_flags = service_p->info.addr_flags;
memcpy(&stf->prefixmask, &service_p->info.prefixmask,
sizeof(struct in6_addr));
memcpy(&stf->netaddr, &service_p->info.netaddr,
sizeof(struct in6_addr));
if (!IN6_IS_ADDR_UNSPECIFIED(&stf->our_relay)) {
memcpy(&service_p->info.router, &stf->our_relay, sizeof(struct in6_addr));
service_publish_success(service_p);
}
break;
}
}
break;
}
case IFEventID_ipv4_primary_change_e: {
struct in6_addr old_addr;
my_log(LOG_DEBUG, "STF_THREAD: ipv4_primary_change");
memcpy(&old_addr, &stf->our_6to4_addr, sizeof(struct in6_addr));
bzero( &stf->our_6to4_addr, sizeof(struct in6_addr));
stf_configure_address(service_p, event_data);
if (!IN6_ARE_ADDR_EQUAL(&old_addr, &stf->our_6to4_addr)) {
service_remove_address(service_p);
service_publish_clear(service_p);
if (!IN6_IS_ADDR_UNSPECIFIED(&stf->our_6to4_addr) &&
!IN6_IS_ADDR_UNSPECIFIED(&stf->our_relay)) {
(void)service_set_address(service_p, &stf->our_6to4_addr,
stf->our_prefixLen);
}
}
break;
}
default: {
break;
}
}
return (status);
}