/* ** XMLPING.CXX ** (c) 2003 Software Poetry, Inc. ** http://www.softwarepoetry.com/webob ** ** Most inspiration taken from the public domain code of Mike Muuss, ** U.S. Army Ballistic Research Laboratory, December, 1983. ** It has, however, been rewritten from the ground up for our purposes. ** ** This source has been compiled only for x86 Linux using: ** ** g++ -o xmlping xmlping.cxx ** ** Note you will have to run the app setuid root so that it has permissions ** to use the icmp raw socket. After building you can do this with: ** ** su root ** (enter your password) ** chown root.root xmlping ** chmod +s xmlping ** ** WARNING: KNOW WHAT YOU'RE DOING HERE! ** ** License: This software is provided as-is with NO WARRANTY WHATSOEVER. ** Software Poetry makes no claims as to its fitness for any purpose. ** This file and any derivatives or translations of it may be freely ** copied and redistributed so long as the original license and copyright ** notices are included and not altered. ** */ #include #include #include #include #include #include #include #include #include #include /*--------------------------------------------------------------------------+ | Defaults | +--------------------------------------------------------------------------*/ #define DEFAULT_COUNT (5) #define DEFAULT_TIMEOUT (2) /*--------------------------------------------------------------------------+ | CPinger | +--------------------------------------------------------------------------*/ #define STATUS_NODATA "no data" #define STATUS_BADHOST "unknown host" #define STATUS_NOICMP "icmp not spoken here" #define STATUS_NOSOCK "no socket (permissions?)" #define STATUS_OK "ok" #define STATUS_SENDPING "failed sendto" #define STATUS_READRESPONSE "failed recvfrom" #define MAX_STATUS (128) #define MAX_HOSTLEN (256) #define MAX_IPADDR (32) #define MIN_INTERVAL (1) // min time to wait between pings #define PACKET_SIZE (sizeof(struct icmp) + sizeof(struct timeval)) class CPinger { public: CPinger(void); virtual ~CPinger(void); void RunTest(const char *szHost, int cSamples, int csecTimeout); void PrintResultXML(void); bool FAnyResponses(void); private: bool FDoPingLoop(int sock, struct sockaddr_in *psin, int cSamples, int csecTimeout); void Reset(); bool FSetupHost(const char *szHost, struct sockaddr_in *psin); bool FSendPing(int sock, struct sockaddr_in *psin, int nSeq); bool FReadResponse(int sock, int nSeq); u_short CalculateChecksum(u_short *pw, int cb); private: char m_szStatus[MAX_STATUS]; char m_szHost[MAX_HOSTLEN]; char m_szIP[MAX_IPADDR]; int m_cSent; int m_cReceived; int m_msTotalTransit; int m_msMin; int m_msMax; int m_nIdentifier; bool m_fWaitingForResponse; }; /* ** CPinger::CPinger ** CPinger::~CPinger ** CPinger::Reset ** ** Creation & destruction */ CPinger::CPinger() { m_nIdentifier = (((int)this) * ((int)getpid())) & 0xFFFF; this->Reset(); } CPinger::~CPinger() { // nut-n-honey } void CPinger::Reset() { strcpy(m_szStatus, STATUS_NODATA); m_szHost[0] = 0; m_szIP[0] = 0; m_fWaitingForResponse = false; m_cSent = m_cReceived = 0; m_msTotalTransit = 0; m_msMin = 999999; m_msMax = 0; } /* ** CPinger::FSetupHost ** ** Resolve the given host (gethostbyname_r will do the right thing ** if we get an IP as well). */ bool CPinger::FSetupHost(const char *szHost, struct sockaddr_in *psin) { struct hostent he; struct hostent *phe; char rgb[2048]; int herrno; unsigned long int uAddrNet; strncpy(m_szHost, szHost, MAX_HOSTLEN); m_szHost[MAX_HOSTLEN-1] = 0; // first resolve the host memset(psin, 0, sizeof(struct sockaddr_in)); psin->sin_family = AF_INET; if (gethostbyname_r(m_szHost, &he, rgb, sizeof(rgb), &phe, &herrno)) { strcpy(m_szStatus, STATUS_BADHOST); return(false); } memcpy(&(psin->sin_addr), phe->h_addr, phe->h_length); // remember the ip address uAddrNet = htonl(*((unsigned long int *)(&(psin->sin_addr)))); snprintf(m_szIP, MAX_IPADDR, "%d.%d.%d.%d", (unsigned char)(uAddrNet >> 24), (unsigned char)(uAddrNet >> 16), (unsigned char)(uAddrNet >> 8), (unsigned char)(uAddrNet)); return(true); } /* ** CPinger::RunTest ** CPinger::DoPingLoop ** ** Send pings for csecTest seconds and record results */ void CPinger::RunTest(const char *szHost, int cSamples, int csecTimeout) { struct sockaddr_in sin; char rgb[2048]; struct protoent pe; struct protoent *ppe; int sock; this->Reset(); // set up the non-blocking socket if (!this->FSetupHost(szHost, &sin)) return; if (getprotobyname_r("icmp", &pe, rgb, sizeof(rgb), &ppe)) { strcpy(m_szStatus, STATUS_NOICMP); return; } if ((sock = socket(AF_INET, SOCK_RAW, ppe->p_proto)) < 0) { strcpy(m_szStatus, STATUS_NOSOCK); return; } int opts = fcntl(sock, F_GETFL); opts |= O_NONBLOCK; fcntl(sock, F_SETFL, opts); // loop sending packets, cleanup and bail if (this->FDoPingLoop(sock, &sin, cSamples, csecTimeout)) strcpy(m_szStatus, STATUS_OK); close(sock); } bool _FKeepWaiting(struct timeval *ptvQuit) { struct timeval tvNow; struct timezone tzJunk; gettimeofday(&tvNow, &tzJunk); if (tvNow.tv_sec < ptvQuit->tv_sec) return(true); if (tvNow.tv_sec > ptvQuit->tv_sec) return(false); return(tvNow.tv_usec < ptvQuit->tv_usec); } bool CPinger::FDoPingLoop(int sock, struct sockaddr_in *psin, int cSamples, int csecTimeout) { int nSeq; struct timeval tvNextTimeout, tvAtLeast, tv; struct timezone tzJunk; fd_set fdset; int csockReady; for (nSeq = 1; nSeq <= cSamples; ++nSeq) { gettimeofday(&tvNextTimeout, &tzJunk); memcpy(&tvAtLeast, &tvNextTimeout, sizeof(tvAtLeast)); tvNextTimeout.tv_sec += csecTimeout; tvAtLeast.tv_sec += MIN_INTERVAL; if (!this->FSendPing(sock, psin, nSeq)) { strcpy(m_szStatus, STATUS_SENDPING); return(false); } while (m_fWaitingForResponse && _FKeepWaiting(&tvNextTimeout)) { FD_ZERO(&fdset); FD_SET(sock, &fdset); tv.tv_sec = 1; tv.tv_usec = 0; csockReady = ::select(sock + 1, &fdset, NULL, NULL, &tv); if (csockReady > 0) { if (!this->FReadResponse(sock, nSeq)) { strcpy(m_szStatus, STATUS_READRESPONSE); return(false); } } else { // something else ... probably a timeout, but could be an // error; don't sweat it because we'll time out anyways // and that's a fine error, thank you very much. } } if (!m_fWaitingForResponse) { // got a response ... wait around until the min interval // has passed so we don't flood a host that responds nicely. // could do this sleep thing better but this'll work fine while (_FKeepWaiting(&tvAtLeast)) usleep(100000); // 100 ms } } return(true); } void _tvsub(struct timeval *ptvNow, struct timeval *ptvStart) { if ((ptvNow->tv_usec -= ptvStart->tv_usec) < 0) { ptvNow->tv_sec--; ptvNow->tv_usec += 1000000; } ptvNow->tv_sec -= ptvStart->tv_sec; } bool CPinger::FReadResponse(int sock, int nSeq) { struct sockaddr_in sinFrom; u_char rgbPacket[4096]; // may get back somebody else's packet int cbRead; socklen_t cbFrom; struct ip *pip; struct icmp *picmp; int hlen, msTransit; struct timeval tvNow, *ptvSent; struct timezone tzJunk; cbFrom = sizeof(sinFrom); cbRead = recvfrom(sock, rgbPacket, sizeof(rgbPacket), 0, (struct sockaddr *) &sinFrom, &cbFrom); if (cbRead < 0) return(false); pip = (struct ip *) rgbPacket; hlen = pip->ip_hl << 2; if (cbRead < hlen + ICMP_MINLEN) { // too short; ignore it return(true); } picmp = (struct icmp *) (rgbPacket + hlen); if (picmp->icmp_type != ICMP_ECHOREPLY) { // not an echo; ignore it return(true); } if (picmp->icmp_id != m_nIdentifier) { // not ours return(true); } if (picmp->icmp_seq != nSeq) { // ours, but not the one we're looking for .. since we only // have one outstanding this must be one that timed out return(true); } ptvSent = (struct timeval *) (((char*)picmp) + 8); gettimeofday(&tvNow, &tzJunk); _tvsub(&tvNow, ptvSent); msTransit = (tvNow.tv_sec * 1000) + (tvNow.tv_usec / 1000); m_msTotalTransit += msTransit; if (msTransit < m_msMin) m_msMin = msTransit; if (msTransit > m_msMax) m_msMax = msTransit; ++m_cReceived; m_fWaitingForResponse = false; return(true); } bool CPinger::FSendPing(int sock, struct sockaddr_in *psin, int nSeq) { u_char rgbPacket[PACKET_SIZE]; struct icmp *picmp = (struct icmp *) rgbPacket; struct timeval *ptvStamp = (struct timeval *) &(rgbPacket[8]); struct timezone tzJunk; int cbSent; memset(rgbPacket, 0, sizeof(rgbPacket)); picmp->icmp_type = ICMP_ECHO; picmp->icmp_code = 0; picmp->icmp_cksum = 0; picmp->icmp_seq = nSeq; picmp->icmp_id = m_nIdentifier; gettimeofday(ptvStamp, &tzJunk); picmp->icmp_cksum = this->CalculateChecksum((u_short*) picmp, sizeof(rgbPacket)); cbSent = sendto(sock, rgbPacket, sizeof(rgbPacket), 0, (struct sockaddr *) psin, sizeof(struct sockaddr)); if (cbSent < 0 || cbSent != sizeof(rgbPacket)) return(false); m_fWaitingForResponse = true; ++m_cSent; return(true); } /* ** CPinger::CalculateChecksum ** ** Helpers */ u_short CPinger::CalculateChecksum(u_short *pw, int cb) { u_short wChecksum; int cbLeft = cb; int nSum = 0; u_short wOddByte = 0; /* * Our algorithm is simple, using a 32 bit accumulator (sum), * we add sequential 16 bit words to it, and at the end, fold * back all the carry bits from the top 16 bits into the lower * 16 bits. */ while (cbLeft > 1) { nSum += *pw++; cbLeft -= 2; } /* mop up an odd byte, if necessary */ if (cbLeft == 1) { *(u_char *)(&wOddByte) = *(u_char *)pw; nSum += wOddByte; } /* * add back carry outs from top 16 bits to low 16 bits */ nSum = (nSum >> 16) + (nSum & 0xffff); /* add hi 16 to low 16 */ nSum += (nSum >> 16); /* add carry */ wChecksum = ~nSum; /* truncate to 16 bits */ return(wChecksum); } /* ** CPinger::PrintResultXML ** CPinger::FAnyResponses ** ** Just let the world know what we found */ void CPinger::PrintResultXML() { printf("\n"); printf("\t%s\n", m_szStatus); printf("\t%s\n", m_szHost); printf("\t
%s
\n", m_szIP); printf("\t%d\n", m_cSent); printf("\t%d\n", m_cReceived); printf("\t%d\n", (m_cSent ? ((m_cSent - m_cReceived) * 100 / m_cSent) : 100)); printf("\t%d\n", m_msMin); printf("\t%d\n", m_msMax); printf("\t%d\n", (m_cReceived ? (m_msTotalTransit / m_cReceived) : 0)); printf("
\n"); } bool CPinger::FAnyResponses() { return(m_cReceived != 0); } /*--------------------------------------------------------------------------+ | Main | +--------------------------------------------------------------------------*/ int main(int argc, char *argv[]) { CPinger pinger; int cSamples, csecTimeout; if (argc < 2) { printf("Usage: xmlping host [packet-count] [packet-timeout-secs]\n"); printf(" packet-count defaults to %d\n", DEFAULT_COUNT); printf(" timeout defaults to %d\n", DEFAULT_TIMEOUT); return(2); } csecTimeout = ((argc >= 4) ? atoi(argv[3]) : DEFAULT_TIMEOUT); cSamples = ((argc >= 3) ? atoi(argv[2]) : DEFAULT_COUNT); pinger.RunTest(argv[1], cSamples, csecTimeout); pinger.PrintResultXML(); return(pinger.FAnyResponses() ? 0 : 1); }