#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <netinet/udp.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <libgen.h>
#include <fcntl.h>
#define PROBE_PING "ilb_ping"
#define PROBE_PROTO "ilb_probe"
#define PROTO_TCP "TCP"
#define PROTO_UDP "UDP"
enum probe_type { ping_probe, tcp_probe, udp_probe };
#define MODE_DSR "DSR"
#define MODE_NAT "NAT"
#define MODE_HALF_NAT "HALF_NAT"
enum lb_mode { dsr, nat, half_nat };
#define PROG_ARGC 7
#define RECV_PKT_SZ 256
typedef struct {
enum probe_type probe;
struct in6_addr vip;
struct in6_addr srv_addr;
int proto;
enum lb_mode mode;
in_port_t port;
uint32_t timeout;
uint16_t echo_id;
uint16_t echo_seq;
uint32_t echo_cookie;
in_port_t lport;
} probe_param_t;
static boolean_t timeout_is_good;
static void
probe_exit(int s)
{
if (timeout_is_good) {
(void) printf("0");
exit(0);
} else {
(void) printf("-1");
exit(255);
}
}
static ushort_t
in_cksum(ushort_t *addr, int len)
{
int nleft = len;
ushort_t *w = addr;
ushort_t answer;
ushort_t odd_byte = 0;
int sum = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(uchar_t *)(&odd_byte) = *(uchar_t *)w;
sum += odd_byte;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
static boolean_t
parse_probe_param(char *argv[], probe_param_t *param)
{
int32_t port;
int64_t timeout;
struct in_addr v4addr;
if (strcmp(basename(argv[0]), PROBE_PING) == 0) {
param->probe = ping_probe;
} else {
if (strcmp(basename(argv[0]), PROBE_PROTO) != 0)
return (B_FALSE);
if (strcasecmp(argv[3], PROTO_TCP) == 0) {
param->probe = tcp_probe;
param->proto = IPPROTO_TCP;
} else if (strcasecmp(argv[3], PROTO_UDP) == 0) {
param->probe = udp_probe;
param->proto = IPPROTO_UDP;
} else {
return (B_FALSE);
}
}
if (strchr(argv[1], ':') != NULL) {
if (inet_pton(AF_INET6, argv[1], ¶m->vip) == 0)
return (B_FALSE);
} else if (strchr(argv[1], '.') != NULL) {
if (inet_pton(AF_INET, argv[1], &v4addr) == 0)
return (B_FALSE);
IN6_INADDR_TO_V4MAPPED(&v4addr, ¶m->vip);
} else {
return (B_FALSE);
}
if (strchr(argv[2], ':') != NULL) {
if (inet_pton(AF_INET6, argv[2], ¶m->srv_addr) == 0)
return (B_FALSE);
} else if (strchr(argv[2], '.') != NULL) {
if (inet_pton(AF_INET, argv[2], &v4addr) == 0)
return (B_FALSE);
IN6_INADDR_TO_V4MAPPED(&v4addr, ¶m->srv_addr);
} else {
return (B_FALSE);
}
if (strcasecmp(argv[4], MODE_DSR) == 0)
param->mode = dsr;
else if (strcasecmp(argv[4], MODE_NAT) == 0)
param->mode = nat;
else if (strcasecmp(argv[4], MODE_HALF_NAT) == 0)
param->mode = half_nat;
else
return (B_FALSE);
if ((port = atoi(argv[5])) <= 0 || port > USHRT_MAX)
return (B_FALSE);
param->port = port;
if ((timeout = strtoll(argv[6], NULL, 10)) <= 0 || timeout > UINT_MAX)
return (B_FALSE);
param->timeout = timeout;
return (B_TRUE);
}
static int
set_sockaddr(struct sockaddr_storage *addr, socklen_t *addr_len,
void **next_hop, probe_param_t *param)
{
int af;
struct in6_addr *param_addr;
struct sockaddr_in *v4_addr;
struct sockaddr_in6 *v6_addr;
boolean_t nh = B_FALSE;
switch (param->mode) {
case dsr:
param_addr = ¶m->vip;
nh = B_TRUE;
break;
case nat:
case half_nat:
param_addr = ¶m->srv_addr;
break;
}
if (IN6_IS_ADDR_V4MAPPED(param_addr)) {
af = AF_INET;
v4_addr = (struct sockaddr_in *)addr;
IN6_V4MAPPED_TO_INADDR(param_addr, &v4_addr->sin_addr);
v4_addr->sin_family = AF_INET;
v4_addr->sin_port = htons(param->port);
*addr_len = sizeof (*v4_addr);
} else {
af = AF_INET6;
v6_addr = (struct sockaddr_in6 *)addr;
v6_addr->sin6_family = AF_INET6;
v6_addr->sin6_addr = *param_addr;
v6_addr->sin6_port = htons(param->port);
v6_addr->sin6_flowinfo = 0;
v6_addr->sin6_scope_id = 0;
*addr_len = sizeof (*v6_addr);
}
if (!nh) {
*next_hop = NULL;
return (af);
}
if (af == AF_INET) {
ipaddr_t *nh_addr;
nh_addr = malloc(sizeof (ipaddr_t));
IN6_V4MAPPED_TO_IPADDR(¶m->srv_addr, *nh_addr);
*next_hop = nh_addr;
} else {
struct sockaddr_in6 *nh_addr;
nh_addr = malloc(sizeof (*nh_addr));
nh_addr->sin6_family = AF_INET6;
nh_addr->sin6_addr = param->srv_addr;
nh_addr->sin6_flowinfo = 0;
nh_addr->sin6_scope_id = 0;
*next_hop = nh_addr;
}
return (af);
}
static int
tcp_query(probe_param_t *param)
{
int ret;
int sd, af;
struct sockaddr_storage dst_addr;
socklen_t dst_addr_len;
void *next_hop;
hrtime_t start, end;
uint32_t rtt;
ret = 0;
next_hop = NULL;
af = set_sockaddr(&dst_addr, &dst_addr_len, &next_hop, param);
if ((sd = socket(af, SOCK_STREAM, param->proto)) == -1)
return (-1);
if (next_hop != NULL) {
if (af == AF_INET) {
if (setsockopt(sd, IPPROTO_IP, IP_NEXTHOP, next_hop,
sizeof (ipaddr_t)) < 0) {
ret = -1;
goto out;
}
} else {
if (setsockopt(sd, IPPROTO_IPV6, IPV6_NEXTHOP,
next_hop, sizeof (struct sockaddr_in6)) < 0) {
ret = -1;
goto out;
}
}
}
timeout_is_good = B_FALSE;
(void) alarm(param->timeout);
start = gethrtime();
if (connect(sd, (struct sockaddr *)&dst_addr, dst_addr_len) != 0) {
ret = -1;
goto out;
}
end = gethrtime();
rtt = (end - start) / (NANOSEC / MICROSEC);
if (rtt == 0)
rtt = 1;
(void) printf("%u", rtt);
out:
(void) close(sd);
return (ret);
}
static int
check_icmp_unreach_v4(struct icmp *icmph, probe_param_t *param)
{
struct udphdr *udph;
struct ip *iph;
if (icmph->icmp_type != ICMP_UNREACH)
return (-1);
if (icmph->icmp_code != ICMP_UNREACH_PORT)
return (-1);
iph = (struct ip *)((char *)icmph + ICMP_MINLEN);
if (iph->ip_p != IPPROTO_UDP)
return (-1);
udph = (struct udphdr *)((char *)iph + (iph->ip_hl << 2));
if (udph->uh_dport != htons(param->port))
return (-1);
if (udph->uh_sport != param->lport)
return (-1);
return (0);
}
static int
check_icmp_echo_v4(struct icmp *icmph, probe_param_t *param)
{
uint32_t cookie;
in_port_t port;
if (icmph->icmp_type != ICMP_ECHOREPLY)
return (-1);
if (icmph->icmp_id != param->echo_id)
return (-1);
if (icmph->icmp_seq != param->echo_seq)
return (-1);
bcopy(icmph->icmp_data, &cookie, sizeof (cookie));
if (cookie != param->echo_cookie)
return (-1);
bcopy(icmph->icmp_data + sizeof (cookie), &port, sizeof (port));
if (port != param->port)
return (-1);
return (0);
}
static int
check_icmp_v4(char *buf, ssize_t rcvd, probe_param_t *param)
{
struct ip *iph;
struct icmp *icmph;
iph = (struct ip *)buf;
icmph = (struct icmp *)((char *)iph + (iph->ip_hl << 2));
if (param->probe == udp_probe) {
if (rcvd < 2 * sizeof (struct ip) + ICMP_MINLEN +
sizeof (struct udphdr)) {
return (-1);
}
return (check_icmp_unreach_v4(icmph, param));
} else {
if (rcvd < sizeof (struct ip) + ICMP_MINLEN)
return (-1);
return (check_icmp_echo_v4(icmph, param));
}
}
static int
check_icmp_unreach_v6(icmp6_t *icmp6h, probe_param_t *param)
{
ip6_t *ip6h;
struct udphdr *udph;
if (icmp6h->icmp6_type != ICMP6_DST_UNREACH)
return (-1);
if (icmp6h->icmp6_code != ICMP6_DST_UNREACH_NOPORT)
return (-1);
ip6h = (ip6_t *)((char *)icmp6h + ICMP6_MINLEN);
if (ip6h->ip6_nxt != IPPROTO_UDP)
return (-1);
udph = (struct udphdr *)(ip6h + 1);
if (udph->uh_dport != htons(param->port))
return (-1);
if (udph->uh_sport != param->lport)
return (-1);
return (0);
}
static int
check_icmp_echo_v6(icmp6_t *icmp6h, probe_param_t *param)
{
char *tmp;
uint32_t cookie;
in_port_t port;
if (icmp6h->icmp6_type != ICMP6_ECHO_REPLY)
return (-1);
if (icmp6h->icmp6_id != param->echo_id)
return (-1);
if (icmp6h->icmp6_seq != param->echo_seq)
return (-1);
tmp = (char *)icmp6h + ICMP6_MINLEN;
bcopy(tmp, &cookie, sizeof (cookie));
if (cookie != param->echo_cookie)
return (-1);
tmp += sizeof (cookie);
bcopy(tmp, &port, sizeof (port));
if (port != param->port)
return (-1);
return (0);
}
static int
check_icmp_v6(char *buf, ssize_t rcvd, probe_param_t *param)
{
icmp6_t *icmp6h;
icmp6h = (icmp6_t *)(buf);
if (param->probe == udp_probe) {
if (rcvd < sizeof (ip6_t) + ICMP6_MINLEN +
sizeof (struct udphdr)) {
return (-1);
}
return (check_icmp_unreach_v6(icmp6h, param));
} else {
if (rcvd < ICMP6_MINLEN)
return (-1);
return (check_icmp_echo_v6(icmp6h, param));
}
}
static int
wait_icmp_reply(int af, int recv_sd, struct sockaddr_storage *exp_from,
probe_param_t *param)
{
char buf[RECV_PKT_SZ];
socklen_t from_len;
ssize_t rcvd;
int ret;
for (;;) {
if (af == AF_INET) {
struct sockaddr_in v4_from;
from_len = sizeof (v4_from);
if ((rcvd = recvfrom(recv_sd, buf, RECV_PKT_SZ, 0,
(struct sockaddr *)&v4_from, &from_len)) < 0) {
ret = -1;
break;
}
if ((((struct sockaddr_in *)exp_from)->sin_addr.s_addr)
!= v4_from.sin_addr.s_addr) {
continue;
}
if (check_icmp_v4(buf, rcvd, param) == 0) {
ret = 0;
break;
}
} else {
struct sockaddr_in6 v6_from;
from_len = sizeof (struct sockaddr_in6);
if ((rcvd = recvfrom(recv_sd, buf, RECV_PKT_SZ, 0,
(struct sockaddr *)&v6_from, &from_len)) < 0) {
ret = -1;
break;
}
if (!IN6_ARE_ADDR_EQUAL(&(v6_from.sin6_addr),
&((struct sockaddr_in6 *)exp_from)->sin6_addr)) {
continue;
}
if (check_icmp_v6(buf, rcvd, param) == 0) {
ret = 0;
break;
}
}
}
return (ret);
}
static int
get_lport(int sd, in_port_t *lport)
{
struct sockaddr_storage addr;
socklen_t addr_sz;
addr_sz = sizeof (addr);
if (getsockname(sd, (struct sockaddr *)&addr, &addr_sz) != 0)
return (-1);
if (addr.ss_family == AF_INET)
*lport = ((struct sockaddr_in *)&addr)->sin_port;
else
*lport = ((struct sockaddr_in6 *)&addr)->sin6_port;
return (0);
}
static int
udp_query(probe_param_t *param)
{
int ret;
int send_sd, recv_sd, af;
struct sockaddr_storage dst_addr;
socklen_t addr_len;
void *next_hop;
char buf[1];
struct itimerval timeout;
uint64_t tm;
ret = 0;
next_hop = NULL;
af = set_sockaddr(&dst_addr, &addr_len, &next_hop, param);
if ((send_sd = socket(af, SOCK_DGRAM, param->proto)) == -1)
return (-1);
if ((recv_sd = socket(af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP :
IPPROTO_ICMPV6)) == -1) {
return (-1);
}
if (next_hop != NULL) {
if (af == AF_INET) {
if (setsockopt(send_sd, IPPROTO_IP, IP_NEXTHOP,
next_hop, sizeof (ipaddr_t)) < 0) {
ret = -1;
goto out;
}
} else {
if (setsockopt(send_sd, IPPROTO_IPV6, IPV6_NEXTHOP,
next_hop, sizeof (struct sockaddr_in6)) < 0) {
ret = -1;
goto out;
}
}
}
timeout.it_interval.tv_sec = 0;
timeout.it_interval.tv_usec = 0;
tm = (param->timeout * MICROSEC >> 2) * 3;
if (tm > MICROSEC) {
timeout.it_value.tv_sec = tm / MICROSEC;
timeout.it_value.tv_usec = tm - (timeout.it_value.tv_sec *
MICROSEC);
} else {
timeout.it_value.tv_sec = 0;
timeout.it_value.tv_usec = tm;
}
timeout_is_good = B_TRUE;
if (setitimer(ITIMER_REAL, &timeout, NULL) != 0) {
ret = -1;
goto out;
}
if (sendto(send_sd, buf, 0, 0, (struct sockaddr *)&dst_addr,
addr_len) != 0) {
ret = -1;
goto out;
}
if ((ret = get_lport(send_sd, ¶m->lport)) != 0)
goto out;
(void) wait_icmp_reply(af, recv_sd, &dst_addr, param);
ret = -1;
out:
(void) close(send_sd);
(void) close(recv_sd);
return (ret);
}
#define PING_PKT_LEN \
((ICMP_MINLEN + 2 * sizeof (uint32_t)) / sizeof (uint32_t))
static uint32_t
get_random(void)
{
int fd;
uint32_t num;
if ((fd = open("/dev/urandom", O_RDONLY)) == -1)
return ((uint32_t)gethrtime());
if (read(fd, &num, sizeof (num)) != sizeof (num))
num = ((uint32_t)gethrtime());
(void) close(fd);
return (num);
}
static int
ping_query(probe_param_t *param)
{
int ret;
int sd, af;
struct sockaddr_storage dst_addr;
socklen_t dst_addr_len;
void *next_hop;
hrtime_t start, end;
uint32_t rtt;
uint32_t buf[PING_PKT_LEN];
struct icmp *icmph;
ret = 0;
next_hop = NULL;
af = set_sockaddr(&dst_addr, &dst_addr_len, &next_hop, param);
if ((sd = socket(af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP :
IPPROTO_ICMPV6)) == -1) {
return (-1);
}
if (next_hop != NULL) {
if (af == AF_INET) {
if (setsockopt(sd, IPPROTO_IP, IP_NEXTHOP, next_hop,
sizeof (ipaddr_t)) < 0) {
ret = -1;
goto out;
}
} else {
if (setsockopt(sd, IPPROTO_IPV6, IPV6_NEXTHOP,
next_hop, sizeof (struct sockaddr_in6)) < 0) {
ret = -1;
goto out;
}
}
}
bzero(buf, sizeof (buf));
icmph = (struct icmp *)buf;
icmph->icmp_type = af == AF_INET ? ICMP_ECHO : ICMP6_ECHO_REQUEST;
icmph->icmp_code = 0;
icmph->icmp_cksum = 0;
icmph->icmp_id = htons(gethrtime() % USHRT_MAX);
icmph->icmp_seq = htons(gethrtime() % USHRT_MAX);
param->echo_cookie = get_random();
bcopy(¶m->echo_cookie, icmph->icmp_data,
sizeof (param->echo_cookie));
bcopy(¶m->port, icmph->icmp_data + sizeof (param->echo_cookie),
sizeof (param->port));
icmph->icmp_cksum = in_cksum((ushort_t *)buf, sizeof (buf));
param->echo_id = icmph->icmp_id;
param->echo_seq = icmph->icmp_seq;
timeout_is_good = B_FALSE;
(void) alarm(param->timeout);
start = gethrtime();
if (sendto(sd, buf, sizeof (buf), 0, (struct sockaddr *)&dst_addr,
dst_addr_len) != sizeof (buf)) {
ret = -1;
goto out;
}
if (wait_icmp_reply(af, sd, &dst_addr, param) != 0) {
ret = -1;
goto out;
}
end = gethrtime();
rtt = (end - start) / (NANOSEC / MICROSEC);
if (rtt == 0)
rtt = 1;
(void) printf("%u", rtt);
out:
(void) close(sd);
return (ret);
}
int
main(int argc, char *argv[])
{
probe_param_t param;
int ret;
if (argc != PROG_ARGC) {
(void) printf("-1");
return (-1);
}
if (signal(SIGALRM, probe_exit) == SIG_ERR) {
(void) printf("-1");
return (-1);
}
if (!parse_probe_param(argv, ¶m)) {
(void) printf("-1");
return (-1);
}
switch (param.probe) {
case ping_probe:
ret = ping_query(¶m);
break;
case tcp_probe:
ret = tcp_query(¶m);
break;
case udp_probe:
ret = udp_query(¶m);
break;
}
if (ret == -1)
(void) printf("-1");
return (ret);
}