前言
由于工作中有个需求需要判断ipv6网络是否连通,第一个想法就是通过ping一个ipv6的地址或者域名,来判断当前ipv6网络是否可用;通过阅读busybox的ping的源码,实现了一个简单的接口
ping的原理
ping的原理主要涉及ICMP协议,网上的资料很多,这里不过讲述,直接上代码
实现代码
1. 初始化socket
int ipv6_socket_init(void)
{
int socket_fd = -1;
int sockopt;
socket_fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (socket_fd < 0) {
printf("Error, create socket failed, errmsg=[%s]\n", strerror(errno));
return NT_FAILED;
}
/* 开启内核自动计算校验和 */
sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
setsockopt(socket_fd, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
return socket_fd;
}
2. 反初始化
void ipv6_socket_uninit(int fd)
{
if (fd < 0) return ;
close(fd);
}
3. 主要的ping接口
int ipv6_ping(int fd, const char *hostname, int timeout_ms)
{
struct sockaddr_in6 dest;
struct in6_addr inaddr;
struct icmp6_hdr *pkt;
int ret = -1;
char ip_str[50] = {0};
memset(&dest, 0, sizeof(struct sockaddr_in6));
dest.sin6_family = AF_INET6;
if (inet_pton(AF_INET6, hostname, &inaddr) <= 0) {
struct addrinfo hints;
struct addrinfo *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_RAW;
hints.ai_flags = AI_ALL;
hints.ai_protocol = IPPROTO_ICMPV6;
if(getaddrinfo(hostname, NULL, &hints, &result) != 0)
return NT_FAILED;
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)result->ai_addr;
memcpy(&dest.sin6_addr, &ipv6->sin6_addr, sizeof(ipv6->sin6_addr));
freeaddrinfo(result);
inet_ntop(AF_INET6, &ipv6->sin6_addr, ip_str, sizeof(ip_str));
// printf("[%s] get ip [%s]\n", hostname, ip_str);
}
else {
memcpy(ip_str, hostname, sizeof(ip_str)-1);
memcpy(&dest.sin6_addr, &inaddr, sizeof(inaddr));
}
static unsigned short ipv6_seq = 0;
unsigned short curr_pid = getpid();
char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN] = {0};
pkt = (struct icmp6_hdr *)packet;
pkt->icmp6_type = ICMP6_ECHO_REQUEST;
pkt->icmp6_id = curr_pid;
pkt->icmp6_seq = ipv6_seq++;
ret = sendto(fd, packet, DEFDATALEN + sizeof(struct icmp6_hdr), 0, (struct sockaddr*)&dest, sizeof(dest));
if (ret < 0) {
printf("Send icmp failed! ip=[%s] errmsg=[%s]\n", ip_str, strerror(errno));
return NT_FAILED;
}
struct timeval interval = { timeout_ms / 1000, (timeout_ms % 1000) * 1000 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &interval, sizeof(struct timeval));
time_t start_ts = time(NULL);
/* listen for replies */
while (1) {
if (time(NULL) > start_ts + start_ts / 1000) {
printf("recv icmp reply timeout!\n");
return NT_TIMEOUT;
}
ret = recv(fd, packet, sizeof(packet), 0);
if (ret < 0) {
if (errno != EINTR)
printf("recv error\n");
continue;
}
if (ret >= ICMP_MINLEN) { /* icmp6_hdr */
if (pkt->icmp6_id != curr_pid)
continue; /* not our ping */
if (pkt->icmp6_type == ICMP6_ECHO_REPLY) {
printf("ping [%s] success\n", ip_str);
return NT_SUCCESS;
}
}
}
}
结语
以上就是一个简单的ping ipv6的实现,如有错误,欢迎指出。