My previous code example for IPv4 needed a bunch of modifications to work for an IPv6 address. The thing that took me the longest to figure out was that because IPv6 seems to send a lot more ICMP messages on the local network, I needed to filter the response messages to only the type I was listening for.
bool send_ping6(const std::string& ping_ip, const std::string& HostName4Output, const bool bOutput = false)
{
bool rval = false;
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << "send_ping6(" << ping_ip << ", " << HostName4Output << ");" << std::endl;
struct timespec tfs;
clock_gettime(CLOCK_MONOTONIC, &tfs);
auto ping_sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (ping_sockfd < 0)
{
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << "Socket file descriptor not received!!" << std::endl;
}
else
{
// set socket options at ip to TTL and value to 64,
// change to what you want by setting ttl_val
int ttl_val = 64;
if (setsockopt(ping_sockfd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl_val, sizeof(ttl_val)) != 0)
{
if (bOutput)
std::cerr << "[" << getTimeExcelLocal() << "] " << "Setting socket options to TTL failed!" << std::endl;
}
else
{
struct icmp6_filter filt;
ICMP6_FILTER_SETBLOCKALL(&filt);
ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt);
setsockopt(ping_sockfd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt));
// setting timeout of recv setting
struct timeval tv_out;
tv_out.tv_sec = RECV_TIMEOUT;
tv_out.tv_usec = 0;
setsockopt(ping_sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));
int msg_count = 0;
int flag = 1;
int msg_received_count = 0;
// send icmp packet in a loop
for (auto pingloop = 4; pingloop > 0; pingloop--)
{
// flag is whether packet was sent or not
flag = 1;
//filling packet
struct ping_pkt pckt;
bzero(&pckt, sizeof(pckt));
for (auto i = 0; i < sizeof(pckt.msg) - 1; i++)
pckt.msg[i] = i + '0';
pckt.msg[sizeof(pckt.msg) - 1] = 0;
pckt.hdr.type = ICMP6_ECHO_REQUEST;
pckt.hdr.un.echo.id = getpid();
pckt.hdr.un.echo.sequence = msg_count++;
pckt.hdr.checksum = checksum(&pckt, sizeof(pckt));
usleep(PING_SLEEP_RATE);
struct timespec time_start;
clock_gettime(CLOCK_MONOTONIC, &time_start);
struct sockaddr_in6 ping_addr;
ping_addr.sin6_family = AF_INET6;
ping_addr.sin6_port = htons(0);
inet_pton(AF_INET6, ping_ip.c_str(), &ping_addr.sin6_addr);
if (sendto(ping_sockfd, &pckt, sizeof(pckt), 0, (struct sockaddr*)&ping_addr, sizeof(ping_addr)) <= 0)
{
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << "Packet Sending Failed!" << std::endl;
flag = 0;
}
//receive packet
struct sockaddr_in6 r_addr;
auto addr_len = sizeof(r_addr);
if (recvfrom(ping_sockfd, &pckt, sizeof(pckt), 0, (struct sockaddr*)&r_addr, (socklen_t*)&addr_len) <= 0 && msg_count > 1)
{
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << "Packet receive failed!" << std::endl;
}
else
{
struct timespec time_end;
clock_gettime(CLOCK_MONOTONIC, &time_end);
double timeElapsed = ((double)(time_end.tv_nsec - time_start.tv_nsec)) / 1000000.0;
long double rtt_msec = (time_end.tv_sec - time_start.tv_sec) * 1000.0 + timeElapsed;
// if packet was not sent, don't receive
if (flag)
{
char szAddr[NI_MAXHOST] = { 0 };
inet_ntop(AF_INET6, &r_addr.sin6_addr, szAddr, sizeof(szAddr));
if (!(pckt.hdr.type == ICMP6_ECHO_REPLY && pckt.hdr.code == 0))
{
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << "Error..Packet received from (" << szAddr << ") with ICMP type " << int(pckt.hdr.type) << " code " << int(pckt.hdr.code) << std::endl;
}
else
{
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << PING_PKT_S << " bytes from (" << szAddr << ") (" << HostName4Output << ") msg_seq=" << msg_count << " ttl=" << "ttl_val" << " rtt= " << rtt_msec << " ms." << std::endl;
msg_received_count++;
}
}
}
}
rval = msg_received_count > 0;
struct timespec tfe;
clock_gettime(CLOCK_MONOTONIC, &tfe);
double timeElapsed = ((double)(tfe.tv_nsec - tfs.tv_nsec)) / 1000000.0;
long double total_msec = (tfe.tv_sec - tfs.tv_sec) * 1000.0 + timeElapsed;
if (bOutput)
std::cout << "[" << getTimeExcelLocal() << "] " << "=== " << ping_ip << " ping statistics === " << msg_count << " packets sent, " << msg_received_count << " packets received, " << ((msg_count - msg_received_count) / msg_count) * 100.0 << " percent packet loss. Total time : " << total_msec << " ms." << std::endl;
}
close(ping_sockfd);
}
return(rval);
}
Because my calling routine is keeping the addresses for the hosts as strings, I’m calling each of these routines with those strings and converting them to proper addresses inside the function. I’m making a simple choice of whether it’s an IPv4 address or an IPv6 address by the fact that IPv4 addresses have “.” in them and IPv6 addresses have “:”.
bool send_ping(const std::string& ping_ip, const std::string& HostName4Output, const bool bOutput = false)
{
bool rval = false;
if (ping_ip.find('.') == std::string::npos)
rval = send_ping6(ping_ip, HostName4Output, bOutput);
else
rval = send_ping4(ping_ip, HostName4Output, bOutput);
return(rval);
}
Here’s a bunch of links I found useful while creating this code:
- https://www.geeksforgeeks.org/ping-in-c/
- http://tcpipguide.com/free/t_ICMPv6EchoRequestandEchoReplyMessages-2.htm
- https://cboard.cprogramming.com/c-programming/38408-ipv6-ping-windows-problem-lots-ode.html
- https://www.tutorialspoint.com/unix_sockets/ip_address_functions.htm
- https://en.wikipedia.org/wiki/ICMPv6
- https://github.com/octo/liboping/blob/master/src/liboping.c
- https://pall.as/icmpv6-and-ipv6-neighborships/
- https://git.busybox.net/busybox/tree/networking/ping.c