#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <libintl.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <libinetutil.h>
#include "traceroute.h"
int check_reply6(struct msghdr *, int, int, uchar_t *, uchar_t *);
void *find_ancillary_data(struct msghdr *, int, int);
extern char *inet_name(union any_in_addr *, int);
static int IPv6_hdrlen(ip6_t *, int, uint8_t *);
static char *pr_type6(uchar_t);
void print_addr6(uchar_t *, int, struct sockaddr *);
boolean_t print_icmp_other6(uchar_t, uchar_t);
void send_probe6(int, struct msghdr *, struct ip *, int, int,
struct timeval *, int);
void set_ancillary_data(struct msghdr *, int, union any_in_addr *, int, uint_t);
struct ip *set_buffers6(int);
static boolean_t update_hoplimit_ancillary_data(struct msghdr *, int);
struct ip *
set_buffers6(int plen)
{
struct ip *outip;
uchar_t *outp;
struct udphdr *outudp;
struct icmp *outicmp;
int optlen = 0;
outip = (struct ip *)malloc((size_t)plen);
if (outip == NULL) {
Fprintf(stderr, "%s: malloc: %s\n", prog, strerror(errno));
exit(EXIT_FAILURE);
}
if (gw_count > 0) {
optlen = sizeof (struct ip6_rthdr0) +
gw_count * sizeof (struct in6_addr);
}
(void) memset((char *)outip, 0, (size_t)plen);
outp = (uchar_t *)(outip + 1);
if (useicmp) {
outicmp = (struct icmp *)outp;
outicmp->icmp_type = ICMP6_ECHO_REQUEST;
outicmp->icmp_id = htons(ident);
} else {
outudp = (struct udphdr *)outp;
outudp->uh_ulen = htons((ushort_t)(plen -
(sizeof (struct ip6_hdr) + optlen)));
}
return (outip);
}
void
set_ancillary_data(struct msghdr *msgp, int hoplimit,
union any_in_addr *gwIPlist, int gw_cnt, uint_t if_index)
{
size_t hoplimit_space;
size_t rthdr_space;
size_t pktinfo_space;
size_t bufspace;
struct cmsghdr *cmsgp;
uchar_t *cmsg_datap;
int i;
msgp->msg_control = NULL;
msgp->msg_controllen = 0;
rthdr_space = 0;
pktinfo_space = 0;
hoplimit_space = sizeof (int);
bufspace = sizeof (struct cmsghdr) + _MAX_ALIGNMENT +
hoplimit_space + _MAX_ALIGNMENT;
if (gw_cnt > 0) {
rthdr_space = inet6_rth_space(IPV6_RTHDR_TYPE_0, gw_cnt);
bufspace += sizeof (struct cmsghdr) + _MAX_ALIGNMENT +
rthdr_space + _MAX_ALIGNMENT;
}
if (if_index != 0) {
pktinfo_space = sizeof (struct in6_pktinfo);
bufspace += sizeof (struct cmsghdr) + _MAX_ALIGNMENT +
pktinfo_space + _MAX_ALIGNMENT;
}
bufspace += sizeof (struct cmsghdr);
msgp->msg_controllen = bufspace;
msgp->msg_control = (struct cmsghdr *)malloc(bufspace);
if (msgp->msg_control == NULL) {
Fprintf(stderr, "%s: malloc %s\n", prog, strerror(errno));
exit(EXIT_FAILURE);
}
cmsgp = CMSG_FIRSTHDR(msgp);
cmsgp->cmsg_level = IPPROTO_IPV6;
cmsgp->cmsg_type = IPV6_HOPLIMIT;
cmsg_datap = CMSG_DATA(cmsgp);
*(int *)cmsg_datap = hoplimit;
cmsgp->cmsg_len = cmsg_datap + hoplimit_space - (uchar_t *)cmsgp;
cmsgp = CMSG_NXTHDR(msgp, cmsgp);
if (gw_cnt > 0) {
struct ip6_rthdr0 *rthdr0p;
cmsgp->cmsg_level = IPPROTO_IPV6;
cmsgp->cmsg_type = IPV6_RTHDR;
cmsg_datap = CMSG_DATA(cmsgp);
rthdr0p = (struct ip6_rthdr0 *)cmsg_datap;
if (inet6_rth_init(rthdr0p, rthdr_space,
IPV6_RTHDR_TYPE_0, gw_cnt) == NULL) {
Fprintf(stderr, "%s: inet6_rth_init failed\n",
prog);
exit(EXIT_FAILURE);
}
for (i = 0; i < gw_cnt; i++) {
if (inet6_rth_add(rthdr0p,
&gwIPlist[i].addr6) == -1) {
Fprintf(stderr,
"%s: inet6_rth_add\n", prog);
exit(EXIT_FAILURE);
}
}
cmsgp->cmsg_len = cmsg_datap + rthdr_space - (uchar_t *)cmsgp;
cmsgp = CMSG_NXTHDR(msgp, cmsgp);
}
if (if_index != 0) {
struct in6_pktinfo *pktinfop;
cmsgp->cmsg_level = IPPROTO_IPV6;
cmsgp->cmsg_type = IPV6_PKTINFO;
cmsg_datap = CMSG_DATA(cmsgp);
pktinfop = (struct in6_pktinfo *)cmsg_datap;
(void) bcopy(&in6addr_any, &pktinfop->ipi6_addr,
sizeof (struct in6_addr));
pktinfop->ipi6_ifindex = if_index;
cmsgp->cmsg_len = cmsg_datap + pktinfo_space - (uchar_t *)cmsgp;
cmsgp = CMSG_NXTHDR(msgp, cmsgp);
}
msgp->msg_controllen = (char *)cmsgp - (char *)msgp->msg_control;
}
static boolean_t
update_hoplimit_ancillary_data(struct msghdr *msg, int hoplimit)
{
struct cmsghdr *cmsg;
int *intp;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6 &&
cmsg->cmsg_type == IPV6_HOPLIMIT) {
intp = (int *)(CMSG_DATA(cmsg));
*intp = hoplimit;
return (_B_TRUE);
}
}
return (_B_FALSE);
}
void
send_probe6(int sndsock, struct msghdr *msg6, struct ip *outip, int seq,
int ttl, struct timeval *tp, int packlen)
{
uchar_t *outp;
struct icmp *outicmp;
struct outdata *outdata;
struct iovec iov;
int cc;
int optlen = 0;
int send_size;
struct sockaddr_in6 *to6;
if (gw_count > 0) {
optlen = sizeof (struct ip6_rthdr0) +
gw_count * sizeof (struct in6_addr);
}
send_size = packlen - sizeof (struct ip6_hdr) - optlen;
if (!useicmp)
send_size -= sizeof (struct udphdr);
outp = (uchar_t *)(outip + 1);
outicmp = (struct icmp *)outp;
outdata = (struct outdata *)(outp + ICMP6_MINLEN);
if (!update_hoplimit_ancillary_data(msg6, ttl)) {
Fprintf(stderr,
"%s: can't find IPV6_HOPLIMIT ancillary data\n", prog);
exit(EXIT_FAILURE);
}
outdata->seq = seq;
outdata->ttl = ttl;
outdata->tv = *tp;
if (useicmp) {
outicmp->icmp_seq = htons(seq);
} else {
to6 = (struct sockaddr_in6 *)msg6->msg_name;
to6->sin6_port = htons((port + seq) % (MAX_PORT + 1));
}
iov.iov_base = outp;
iov.iov_len = send_size;
msg6->msg_iov = &iov;
msg6->msg_iovlen = 1;
cc = sendmsg(sndsock, msg6, 0);
if (cc < 0 || cc != send_size) {
if (cc < 0) {
Fprintf(stderr, "%s: sendmsg: %s\n", prog,
strerror(errno));
}
Printf("%s: wrote %s %d chars, ret=%d\n",
prog, hostname, send_size, cc);
(void) fflush(stdout);
}
}
void *
find_ancillary_data(struct msghdr *msg, int cmsg_level, int cmsg_type)
{
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == cmsg_level &&
cmsg->cmsg_type == cmsg_type) {
return (CMSG_DATA(cmsg));
}
}
return (NULL);
}
int
check_reply6(struct msghdr *msg, int cc, int seq, uchar_t *type, uchar_t *code)
{
uchar_t *buf = msg->msg_iov->iov_base;
struct sockaddr_in6 *from_in6 = (struct sockaddr_in6 *)msg->msg_name;
icmp6_t *icp6;
ulong_t ip6hdr_len;
uint8_t last_hdr;
int save_cc = cc;
char temp_buf[INET6_ADDRSTRLEN];
if (msg->msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
if (verbose) {
Printf("Truncated message: msg_flags 0x%x from %s\n",
msg->msg_flags,
inet_ntop(AF_INET6,
(void *)&(from_in6->sin6_addr),
temp_buf, sizeof (temp_buf)));
}
return (REPLY_SHORT_PKT);
}
if (cc < ICMP6_MINLEN) {
if (verbose) {
Printf("packet too short (%d bytes) from %s\n",
cc,
inet_ntop(AF_INET6,
(void *)&(from_in6->sin6_addr),
temp_buf, sizeof (temp_buf)));
}
return (REPLY_SHORT_PKT);
}
icp6 = (icmp6_t *)buf;
*type = icp6->icmp6_type;
*code = icp6->icmp6_code;
if ((*type == ICMP6_TIME_EXCEEDED &&
*code == ICMP6_TIME_EXCEED_TRANSIT) ||
*type == ICMP6_DST_UNREACH || *type == ICMP6_ECHO_REPLY ||
*type == ICMP6_PACKET_TOO_BIG ||
(*type == ICMP6_PARAM_PROB &&
*code == ICMP6_PARAMPROB_NEXTHEADER)) {
ip6_t *hip6;
struct udphdr *up;
icmp6_t *hicmp6;
cc -= ICMP6_MINLEN;
hip6 = (ip6_t *)&(icp6->icmp6_data32[1]);
last_hdr = hip6->ip6_nxt;
ip6hdr_len = IPv6_hdrlen(hip6, cc, &last_hdr);
cc -= ip6hdr_len;
if (useicmp) {
if (*type == ICMP6_ECHO_REPLY &&
icp6->icmp6_id == htons(ident) &&
icp6->icmp6_seq == htons(seq)) {
return (REPLY_GOT_TARGET);
}
hicmp6 = (icmp6_t *)((uchar_t *)hip6 + ip6hdr_len);
if (ICMP6_MINLEN <= cc &&
last_hdr == IPPROTO_ICMPV6 &&
hicmp6->icmp6_id == htons(ident) &&
hicmp6->icmp6_seq == htons(seq)) {
if (*type == ICMP6_TIME_EXCEEDED) {
return (REPLY_GOT_GATEWAY);
} else {
return (REPLY_GOT_OTHER);
}
}
} else {
up = (struct udphdr *)((uchar_t *)hip6 + ip6hdr_len);
if (4 <= cc &&
last_hdr == IPPROTO_UDP &&
up->uh_sport == htons(ident) &&
up->uh_dport == htons((port + seq) %
(MAX_PORT + 1))) {
if (*type == ICMP6_DST_UNREACH &&
*code == ICMP6_DST_UNREACH_NOPORT) {
return (REPLY_GOT_TARGET);
} else if (*type == ICMP6_TIME_EXCEEDED) {
return (REPLY_GOT_GATEWAY);
} else {
return (REPLY_GOT_OTHER);
}
}
}
}
if (verbose) {
int i, j;
uchar_t *lp = (uchar_t *)icp6;
struct in6_addr *dst;
struct in6_pktinfo *pkti;
pkti = (struct in6_pktinfo *)find_ancillary_data(msg,
IPPROTO_IPV6, IPV6_PKTINFO);
if (pkti == NULL) {
Fprintf(stderr,
"%s: can't find IPV6_PKTINFO ancillary data\n",
prog);
exit(EXIT_FAILURE);
}
dst = &pkti->ipi6_addr;
cc = save_cc;
Printf("\n%d bytes from %s to ", cc,
inet_ntop(AF_INET6, (const void *)&(from_in6->sin6_addr),
temp_buf, sizeof (temp_buf)));
Printf("%s: icmp type %d (%s) code %d\n",
inet_ntop(AF_INET6, (const void *)dst,
temp_buf, sizeof (temp_buf)),
*type, pr_type6(*type), *code);
for (i = 0; i < cc; i += 4) {
Printf("%2d: x", i);
for (j = 0; ((j < 4) && ((i + j) < cc)); j++)
Printf("%2.2x", *lp++);
(void) putchar('\n');
}
}
return (REPLY_SHORT_PKT);
}
static int
IPv6_hdrlen(ip6_t *ip6h, int pkt_len, uint8_t *last_hdr_rtrn)
{
int length;
int exthdrlength;
uint8_t nexthdr;
uint8_t *whereptr;
ip6_hbh_t *hbhhdr;
ip6_dest_t *desthdr;
ip6_rthdr_t *rthdr;
ip6_frag_t *fraghdr;
uint8_t *endptr;
length = sizeof (ip6_t);
whereptr = ((uint8_t *)&ip6h[1]);
endptr = ((uint8_t *)ip6h) + pkt_len;
nexthdr = ip6h->ip6_nxt;
*last_hdr_rtrn = IPPROTO_NONE;
if (whereptr >= endptr)
return (length);
while (whereptr < endptr) {
*last_hdr_rtrn = nexthdr;
switch (nexthdr) {
case IPPROTO_HOPOPTS:
hbhhdr = (ip6_hbh_t *)whereptr;
exthdrlength = 8 * (hbhhdr->ip6h_len + 1);
if ((uchar_t *)hbhhdr + exthdrlength > endptr)
return (length);
nexthdr = hbhhdr->ip6h_nxt;
length += exthdrlength;
break;
case IPPROTO_DSTOPTS:
desthdr = (ip6_dest_t *)whereptr;
exthdrlength = 8 * (desthdr->ip6d_len + 1);
if ((uchar_t *)desthdr + exthdrlength > endptr)
return (length);
nexthdr = desthdr->ip6d_nxt;
length += exthdrlength;
break;
case IPPROTO_ROUTING:
rthdr = (ip6_rthdr_t *)whereptr;
exthdrlength = 8 * (rthdr->ip6r_len + 1);
if ((uchar_t *)rthdr + exthdrlength > endptr)
return (length);
nexthdr = rthdr->ip6r_nxt;
length += exthdrlength;
break;
case IPPROTO_FRAGMENT:
fraghdr = (ip6_frag_t *)whereptr;
if ((uchar_t *)&fraghdr[1] > endptr)
return (length);
nexthdr = fraghdr->ip6f_nxt;
length += sizeof (struct ip6_frag);
break;
case IPPROTO_NONE:
default:
return (length);
}
whereptr = (uint8_t *)ip6h + length;
}
*last_hdr_rtrn = nexthdr;
return (length);
}
static char *
pr_type6(uchar_t type)
{
static struct icmptype_table ttab6[] = {
{ICMP6_DST_UNREACH, "Dest Unreachable"},
{ICMP6_PACKET_TOO_BIG, "Packet Too Big"},
{ICMP6_TIME_EXCEEDED, "Time Exceeded"},
{ICMP6_PARAM_PROB, "Param Problem"},
{ICMP6_ECHO_REQUEST, "Echo Request"},
{ICMP6_ECHO_REPLY, "Echo Reply"},
{MLD_LISTENER_QUERY, "Multicast Listener Query"},
{MLD_LISTENER_REPORT, "Multicast Listener Report"},
{MLD_LISTENER_REDUCTION, "Multicast Listener Done"},
{ND_ROUTER_SOLICIT, "Router Solicitation"},
{ND_ROUTER_ADVERT, "Router Advertisement"},
{ND_NEIGHBOR_SOLICIT, "Neighbor Solicitation"},
{ND_NEIGHBOR_ADVERT, "Neighbor Advertisement"},
{ND_REDIRECT, "Redirect Message"}
};
int i = 0;
for (i = 0; i < A_CNT(ttab6); i++) {
if (ttab6[i].type == type)
return (ttab6[i].message);
}
return ("OUT-OF-RANGE");
}
void
print_addr6(uchar_t *buf, int cc, struct sockaddr *from)
{
struct sockaddr_in6 *from_in6 = (struct sockaddr_in6 *)from;
ip6_t *ip;
union any_in_addr ip_addr;
char *resolved_name;
char temp_buf[INET6_ADDRSTRLEN];
ip_addr.addr6 = from_in6->sin6_addr;
ip = (ip6_t *)buf;
(void) inet_ntop(AF_INET6, &(from_in6->sin6_addr), temp_buf,
sizeof (temp_buf));
if (!nflag)
resolved_name = inet_name(&ip_addr, AF_INET6);
if (nflag || (strcmp(temp_buf, resolved_name) == 0))
Printf(" %s", temp_buf);
else
Printf(" %s (%s)", resolved_name, temp_buf);
if (verbose) {
Printf(" %d bytes to %s", cc, inet_ntop(AF_INET6,
(const void *) &(ip->ip6_dst), temp_buf,
sizeof (temp_buf)));
}
}
boolean_t
print_icmp_other6(uchar_t type, uchar_t code)
{
boolean_t unreach = _B_FALSE;
switch (type) {
case ICMP6_PACKET_TOO_BIG:
unreach = _B_TRUE;
Printf(" !B");
break;
case ICMP6_PARAM_PROB:
if (code == ICMP6_PARAMPROB_NEXTHEADER) {
unreach = _B_TRUE;
Printf(" !R");
}
break;
case ICMP6_DST_UNREACH:
switch (code) {
case ICMP6_DST_UNREACH_NOPORT:
break;
case ICMP6_DST_UNREACH_NOROUTE:
unreach = _B_TRUE;
Printf(" !H");
break;
case ICMP6_DST_UNREACH_ADMIN:
unreach = _B_TRUE;
Printf(" !X");
break;
case ICMP6_DST_UNREACH_ADDR:
unreach = _B_TRUE;
Printf(" !A");
break;
case ICMP6_DST_UNREACH_NOTNEIGHBOR:
unreach = _B_TRUE;
Printf(" !E");
break;
default:
unreach = _B_TRUE;
Printf(" !<%d>", code);
break;
}
break;
default:
break;
}
return (unreach);
}