diff --git a/src/backend/backend.pri b/src/backend/backend.pri index 0c597c0f..647c9b33 100644 --- a/src/backend/backend.pri +++ b/src/backend/backend.pri @@ -9,6 +9,7 @@ HEADERS += \ $$PWD/kylin-dbus-interface.h \ $$PWD/kylin-network-interface.h \ $$PWD/kylinipv4arping.h \ + $$PWD/kylinipv6arping.h \ $$PWD/sysdbusregister.h \ $$PWD/utils.h \ $$PWD/wifi-auth-thread.h @@ -20,6 +21,7 @@ SOURCES += \ $$PWD/kylin-dbus-interface.cpp \ $$PWD/kylin-network-interface.c \ $$PWD/kylinipv4arping.cpp \ + $$PWD/kylinipv6arping.cpp \ $$PWD/sysdbusregister.cpp \ $$PWD/utils.cpp \ $$PWD/wifi-auth-thread.cpp diff --git a/src/backend/kylinipv4arping.cpp b/src/backend/kylinipv4arping.cpp index 9ad8a6f4..7287342e 100644 --- a/src/backend/kylinipv4arping.cpp +++ b/src/backend/kylinipv4arping.cpp @@ -91,7 +91,7 @@ int KyIpv4Arping::sendIpv4Packet() memcpy(p, &p_me->sll_addr, ah->ar_hln); p += p_me->sll_halen; - qWarning()<<"m_src address:" << inet_ntoa(m_srcAddress); + qWarning()<<"[KyIpv4Arping]" <<"m_src address:" << inet_ntoa(m_srcAddress); memcpy(p, &m_srcAddress, 4); p += 4; @@ -237,7 +237,7 @@ int KyIpv4Arping::ipv4EventLoop() case POLLFD_TIMER: bytes = read(tfd, &exp, sizeof(uint64_t)); if (bytes != sizeof(uint64_t)) { - qWarning() << "could not read timerfd"; + qWarning() <<"[KyIpv4Arping]" << "could not read timerfd"; continue; } @@ -254,7 +254,7 @@ int KyIpv4Arping::ipv4EventLoop() bytes = recvfrom(m_ipv4Socket, packet, sizeof(packet), 0, (struct sockaddr *)&from, &addr_len); if (bytes < 0) { - qWarning() << "recvfrom function failed, errno" << errno; + qWarning()<<"[KyIpv4Arping]" << "recvfrom function failed, errno" << errno; continue; } if (ipv4PacketProcess(packet, bytes, (struct sockaddr_ll *)&from) == FINAL_PACKS) { @@ -263,7 +263,7 @@ int KyIpv4Arping::ipv4EventLoop() } break; default: - qWarning()<<"the fd index is undefine" << index; + qWarning()<<"[KyIpv4Arping]" <<"the fd index is undefine" << index; break; } } @@ -278,12 +278,12 @@ int KyIpv4Arping::ipv4EventLoop() int KyIpv4Arping::checkIfflags(unsigned int ifflags) { if (!(ifflags & IFF_UP)) { - qWarning()<<"the iface" << m_ifaceName <<" is down."; + qWarning()<<"[KyIpv4Arping]" <<"the iface" << m_ifaceName <<" is down."; return -1; } if (ifflags & (IFF_NOARP | IFF_LOOPBACK)) { - qWarning()<< "Interface" << m_ifaceName << "is not ARPable."; + qWarning()<<"[KyIpv4Arping]" << "Interface" << m_ifaceName << "is not ARPable."; return -1; } diff --git a/src/backend/kylinipv6arping.cpp b/src/backend/kylinipv6arping.cpp new file mode 100644 index 00000000..18088137 --- /dev/null +++ b/src/backend/kylinipv6arping.cpp @@ -0,0 +1,394 @@ +#include "kylinipv6arping.h" + +#include +#include +#include + +KyIpv6Arping::KyIpv6Arping(QString ifaceName, QString ipAddress, int retryCount, int timeout, QObject *parent) : QObject(parent) +{ + m_ifaceName = ifaceName; + m_ipv6Address = ipAddress; + m_retryCount = retryCount; + m_timeoutMs = timeout; + + m_ipv6Conflict = false; +} + +KyIpv6Arping::~KyIpv6Arping() +{ + +} + +void KyIpv6Arping::monoGetTime (struct timespec *ts) +{ +#ifdef CLOCK_MONOTONIC + if (clock_gettime (CLOCK_MONOTONIC, ts)) +#endif + { + static long freq = 0; + if (freq == 0) + freq = sysconf (_SC_CLK_TCK); + + struct tms dummy; + clock_t t = times (&dummy); + ts->tv_sec = t / freq; + ts->tv_nsec = (t % freq) * (1000000000 / freq); + } +} + +int KyIpv6Arping::getLocalMacAddress(const char *ifname, unsigned char *addr) +{ +# ifdef SIOCGIFHWADDR + struct ifreq req; + memset (&req, 0, sizeof (req)); + + if (((unsigned)strlen (ifname)) >= (unsigned)IFNAMSIZ) { + return -1; /* buffer overflow = local root */ + } + + strcpy (req.ifr_name, ifname); + + int fd = socket (AF_INET6, SOCK_DGRAM, 0); + if (fd == -1) { + return -1; + } + + if (ioctl (fd, SIOCGIFHWADDR, &req)) { + qWarning()<<"[KyIpv6Arping]"<<"get local mac address failed, errno" << errno; + close (fd); + return -1; + } + close (fd); + + memcpy (addr, req.ifr_hwaddr.sa_data, 6); + return 0; +# else + /* No SIOCGIFHWADDR, which seems Linux specific. */ + struct ifaddrs *ifa = NULL; + struct ifaddrs *ifp = NULL; + getifaddrs(&ifa); + ifp = ifa; /* preserve the address of ifa to free memory later */ + while (ifp != NULL) { + if (ifp->ifa_addr->sa_family == AF_LINK && strcmp(ifp->ifa_name, ifname) == 0) { + const struct sockaddr_dl* sdl = (const struct sockaddr_dl*) ifp->ifa_addr; + memcpy(addr, sdl->sdl_data + sdl->sdl_nlen, 6); + freeifaddrs(ifa); + return 0; + } + ifp = ifp->ifa_next; + } + freeifaddrs(ifa); + return -1; +# endif +} + +int KyIpv6Arping::getIpv6ByName(struct sockaddr_in6 *addr) +{ + struct addrinfo hints, *res; + memset (&hints, 0, sizeof (hints)); + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags = 0; + + int val = getaddrinfo (m_ipv6Address.toUtf8(), NULL, &hints, &res); + if (val) { + qWarning()<<"[KyIpv6Arping]" << m_ipv6Address <<"get addrinfo failed, errno" << val; + return -1; + } + + memcpy (addr, res->ai_addr, sizeof (struct sockaddr_in6)); + freeaddrinfo (res); + + val = if_nametoindex (m_ifaceName.toUtf8()); + if (val == 0) { + qWarning()<<"[KyIpv6Arping]" <<"if_nametoindex failed, errno" << errno; + return -1; + } + addr->sin6_scope_id = val; + + return 0; +} + +int KyIpv6Arping::buildSolicitationPacket(solicit_packet *ns, struct sockaddr_in6 *tgt, const char *ifname) +{ + /* builds ICMPv6 Neighbor Solicitation packet */ + ns->hdr.nd_ns_type = ND_NEIGHBOR_SOLICIT; + ns->hdr.nd_ns_code = 0; + ns->hdr.nd_ns_cksum = 0; /* computed by the kernel */ + ns->hdr.nd_ns_reserved = 0; + memcpy (&ns->hdr.nd_ns_target, &tgt->sin6_addr, 16); + + /* determines actual multicast destination address */ + memcpy (&tgt->sin6_addr.s6_addr, "\xff\x02\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x01\xff", 13); + + /* gets our own interface's link-layer address (MAC) */ + if (getLocalMacAddress (m_ifaceName.toUtf8().constData(), ns->hw_addr)) { + return sizeof (ns->hdr); + } + + ns->opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR; + ns->opt.nd_opt_len = 1; /* 8 bytes */ + return sizeof (*ns); +} + +void KyIpv6Arping::saveMacAddress (const uint8_t *ptr, size_t len) +{ + int index; + char macAddress[64] = {0}; + + for (index = 0; index < len; index++) { + snprintf(&macAddress[strlen(macAddress)], sizeof(macAddress) - strlen(macAddress), "%02X", ptr[index]); + if (index != len - 1) { + snprintf(&macAddress[strlen(macAddress)], sizeof(macAddress) - strlen(macAddress), "%s", ":"); + } + } + + m_conflictMacAddress = QString(macAddress); + return ; +} + +int KyIpv6Arping::parseIpv6Packet(const uint8_t *buf, size_t len, const struct sockaddr_in6 *tgt) +{ + const struct nd_neighbor_advert *na = + (const struct nd_neighbor_advert *)buf; + const uint8_t *ptr; + + /* checks if the packet is a Neighbor Advertisement, and + * if the target IPv6 address is the right one */ + if ((len < sizeof (struct nd_neighbor_advert)) + || (na->nd_na_type != ND_NEIGHBOR_ADVERT) + || (na->nd_na_code != 0) + || memcmp (&na->nd_na_target, &tgt->sin6_addr, 16)) { + return -1; + } + len -= sizeof (struct nd_neighbor_advert); + + /* looks for Target Link-layer address option */ + ptr = buf + sizeof (struct nd_neighbor_advert); + + while (len >= 8) + { + uint16_t optlen; + + optlen = ((uint16_t)(ptr[1])) << 3; + if (optlen == 0) + break; /* invalid length */ + + if (len < optlen) /* length > remaining bytes */ + break; + len -= optlen; + + + /* skips unrecognized option */ + if (ptr[0] != ND_OPT_TARGET_LINKADDR) + { + ptr += optlen; + continue; + } + + /* Found! displays link-layer address */ + ptr += 2; + optlen -= 2; + + saveMacAddress (ptr, optlen); + setIpv6ConflictFlag(true); + return 0; + } + + return -1; +} + +int KyIpv6Arping::readIpv6Packet(void *buf, + size_t len, + int flags, + struct sockaddr_in6 *addr) +{ + char cbuf[CMSG_SPACE (sizeof (int))]; + + struct iovec iov = { + .iov_base = buf, + .iov_len = len + }; + + struct msghdr hdr = { + .msg_name = addr, + .msg_namelen = sizeof (*addr), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof (cbuf) + }; + + ssize_t val = recvmsg (m_ipv6Socket, &hdr, flags); + if (val == -1) { + return val; + } + + /* ensures the hop limit is 255 */ + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR (&hdr); + cmsg != NULL; cmsg = CMSG_NXTHDR (&hdr, cmsg)) { + if ((cmsg->cmsg_level == IPPROTO_IPV6) + && (cmsg->cmsg_type == IPV6_HOPLIMIT)) { + if (255 != *(int *)CMSG_DATA (cmsg)) { + // pretend to be a spurious wake-up + errno = EAGAIN; + return -1; + } + } + } + + return val; +} + +int KyIpv6Arping::reciveAndProcessIpv6Packet(const struct sockaddr_in6 *tgt, unsigned wait_ms) +{ + struct timespec end; + struct pollfd fds; + + /* computes deadline time */ + monoGetTime (&end); + div_t d; + d = div (wait_ms, 1000); + end.tv_sec = end.tv_sec + d.quot; + end.tv_nsec = end.tv_nsec + (d.rem * 1000000); + + /* receive loop */ + for (;;) + { + /* waits for reply until deadline */ + struct timespec now; + int val = 0; + + monoGetTime (&now); + if (end.tv_sec >= now.tv_sec) { + val = (end.tv_sec - now.tv_sec) * 1000 + + (int)((end.tv_nsec - now.tv_nsec) / 1000000); + if (val < 0) { + val = 0; + } + } + + fds.fd = m_ipv6Socket; + fds.events = POLLIN; + val = poll (&fds, 1, val); + if (val < 0) + break; + + if (val == 0) + return 0; + + /* receives an ICMPv6 packet */ + // TODO: use interface MTU as buffer size + uint8_t buf[1460]; + struct sockaddr_in6 addr; + + val = readIpv6Packet(buf, sizeof (buf), MSG_DONTWAIT, &addr); + if (val < 0) { + if (errno != EAGAIN) + qWarning()<<"[KyIpv6Arping]"<<"Receiving ICMPv6 packet failed"; + continue; + } + + /* ensures the response came through the right interface */ + if (addr.sin6_scope_id + && (addr.sin6_scope_id != tgt->sin6_scope_id)) + continue; + + if (parseIpv6Packet(buf, val, tgt) == 0) { + return 1 /* = responses */; + } + } + + return -1; /* error */ +} + + +int KyIpv6Arping::ipv6ConflictCheck() +{ + struct sockaddr_in6 tgt; + struct icmp6_filter filter; + int retry = 0; + + m_ipv6Socket = socket (PF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (m_ipv6Socket < 0) { + qDebug()<<"[KyIpv6Arping]" <<"create ipv6 socket failed:"; + return -1; + } + + fcntl (m_ipv6Socket, F_SETFD, FD_CLOEXEC); + + /* set ICMPv6 filter */ + ICMP6_FILTER_SETBLOCKALL (&filter); + ICMP6_FILTER_SETPASS (ND_NEIGHBOR_ADVERT, &filter); + setsockopt (m_ipv6Socket, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof (filter)); + + int soDontRoute = 1; + setsockopt (m_ipv6Socket, SOL_SOCKET, SO_DONTROUTE, &soDontRoute, sizeof(soDontRoute)); + + /* sets Hop-by-hop limit to 255 */ + int multicastHopLimit = 255; + setsockopt (m_ipv6Socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &multicastHopLimit, sizeof (multicastHopLimit)); + + int unicastHops = 255; + setsockopt (m_ipv6Socket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &unicastHops, sizeof (unicastHops)); + + int recvHopLimit = 1; + setsockopt(m_ipv6Socket, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, + &recvHopLimit, sizeof (recvHopLimit)); + + /* resolves target's IPv6 address */ + int ret = getIpv6ByName(&tgt); + if (ret < 0) { + qWarning()<<"[KyIpv6Arping]"<<"get ipv6 by name failed."; + close (m_ipv6Socket); + return ret; + } + + solicit_packet packet; + struct sockaddr_in6 dst; + ssize_t plen; + + memcpy (&dst, &tgt, sizeof (dst)); + plen = buildSolicitationPacket(&packet, &dst, m_ifaceName.toUtf8().constData()); + if (plen == -1) { + qWarning()<<"[KyIpv6Arping]"<<"build solicit packet failed."; + close (m_ipv6Socket); + return -1; + } + + while (retry < m_retryCount) { + /* sends a Solitication */ + if (sendto (m_ipv6Socket, &packet, plen, MSG_DONTROUTE, + (const struct sockaddr *)&dst, + sizeof (dst)) != plen) { + qWarning()<<"[KyIpv6Arping]"<<"Sending ICMPv6 packet failed."; + close (m_ipv6Socket); + return -1; + } + + retry++; + + /* receives an Advertisement */ + ssize_t val = reciveAndProcessIpv6Packet(&tgt, m_timeoutMs); + if (val > 0) { + close (m_ipv6Socket); + return 0; + } else if (val == 0) { + continue; + } else { + close (m_ipv6Socket); + return -1; + } + } + + close(m_ipv6Socket); + + if (retry == m_retryCount) { + return 0; + } else { + return -2; + } +} diff --git a/src/backend/kylinipv6arping.h b/src/backend/kylinipv6arping.h new file mode 100644 index 00000000..7f92c8ee --- /dev/null +++ b/src/backend/kylinipv6arping.h @@ -0,0 +1,109 @@ +#ifndef KYLINIPV6ARPING_H +#define KYLINIPV6ARPING_H + +//#include + +#include +#include +#include /* div() */ +#include /* uint8_t */ +#include /* UINT_MAX */ +#include +#include + +#include /* EMFILE */ +#include +#include /* close() */ +#include /* clock_gettime() */ +#include /* poll() */ +#include +#include +#include + +//#include "gettime.h" + +#ifdef HAVE_GETOPT_H +# include +#endif + +#include /* getaddrinfo() */ +#include /* inet_ntop() */ +#include /* if_nametoindex() */ + +#include +#include + +#ifndef IPV6_RECVHOPLIMIT +/* Using obsolete RFC 2292 instead of RFC 3542 */ +# define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT +#endif + +/* BSD-like systems define ND_RA_FLAG_HA instead of ND_RA_FLAG_HOME_AGENT */ +#ifndef ND_RA_FLAG_HOME_AGENT +# ifdef ND_RA_FLAG_HA +# define ND_RA_FLAG_HOME_AGENT ND_RA_FLAG_HA +# endif +#endif + +#ifndef AI_IDN +# define AI_IDN 0 +#endif + + +#include +#include +#include + +typedef struct +{ + struct nd_neighbor_solicit hdr; + struct nd_opt_hdr opt; + uint8_t hw_addr[6]; +} solicit_packet; + +class KyIpv6Arping: public QObject +{ + Q_OBJECT +public: + explicit KyIpv6Arping(QString ifaceName, QString ipAddress, int retryCount=3, int timeout=1000, QObject *parent = nullptr); + ~KyIpv6Arping(); + +public: + bool ipv6IsConflict() { + return m_ipv6Conflict; + } + + void setIpv6ConflictFlag(bool conflict) { + m_ipv6Conflict = conflict; + } + + QString getConflictMacAddress() { + return m_conflictMacAddress; + } + + int ipv6ConflictCheck(); + +private: + void monoGetTime (struct timespec *ts); + void saveMacAddress (const uint8_t *ptr, size_t len); + int getLocalMacAddress(const char *ifname, unsigned char *addr); + + int getIpv6ByName(struct sockaddr_in6 *addr); + int buildSolicitationPacket(solicit_packet *ns, struct sockaddr_in6 *tgt, const char *ifname); + int parseIpv6Packet(const uint8_t *buf, size_t len, const struct sockaddr_in6 *tgt); + int readIpv6Packet(void *buf, size_t len, int flags, struct sockaddr_in6 *addr); + int reciveAndProcessIpv6Packet(const struct sockaddr_in6 *tgt, unsigned wait_ms); + +private: + int m_ipv6Socket = 0; + + QString m_ifaceName; + QString m_ipv6Address; + int m_retryCount; + int m_timeoutMs; + + bool m_ipv6Conflict = false; + QString m_conflictMacAddress = nullptr; +}; + +#endif // KYLINIPV6ARPING_H