/* * Module: lbpathtest.cc * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lbdata.hh" #include "lbtest_icmp.hh" ICMPEngine LBTestICMP::_engine; using namespace std; /** * * **/ void ICMPEngine::init() { _results.erase(_results.begin(),_results.end()); if (_initialized == true) { return; } _initialized = true; if (_debug) { cout << "LBTestICMP::init(): initializing test system" << endl; } struct protoent *ppe = getprotobyname("icmp"); _send_sock = socket(PF_INET, SOCK_RAW, ppe->p_proto); if (_send_sock < 0){ if (_debug) { cerr << "LBTestICMP::LBTestICMP(): no send sock: " << _send_sock << endl; } syslog(LOG_ERR, "wan_lb: failed to acquired socket"); _send_sock = 0; return; } //set options for broadcasting. int val = 1; setsockopt(_send_sock, SOL_SOCKET, SO_BROADCAST, &val, 4); setsockopt(_send_sock, SOL_SOCKET, SO_REUSEADDR, &val, 4); struct sockaddr_in addr; memset( &addr, 0, sizeof( struct sockaddr_in )); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = 0; _recv_sock = socket(PF_INET, SOCK_RAW, ppe->p_proto); if (_recv_sock < 0) { if (_debug) { cerr << "LBTestICMP::LBTestICMP(): no recv sock: " << _recv_sock << endl; } syslog(LOG_ERR, "wan_lb: failed to acquired socket"); _recv_sock = 0; return; } if (bind(_recv_sock, (struct sockaddr*)&addr, sizeof(addr))==-1) { if (_debug) { cerr << "failed on bind" << endl; } syslog(LOG_ERR, "wan_lb: failed to bind recv sock"); } } /** * * **/ int ICMPEngine::process(LBHealth &health,LBTestICMP *data) { //iterate over packets and send string target = data->_target; if (target.empty()) { if (health._nexthop == "dhcp") { target = health._dhcp_nexthop; } else { target = health._nexthop; } } //don't have target yet... if (target.empty()) { return -1; } if (_debug) { cout << "LBTestICMP::start(): sending ping test for: " << health._interface << " for " << target << endl; } _packet_id = ++_packet_id % 32767; send(health._interface, target, _packet_id); _results.insert(pair(_packet_id,PktData(health._interface,-1))); } /** * * **/ int ICMPEngine::recv(LBHealth &health,LBTestICMP *data) { struct timeval send_time; gettimeofday(&send_time,NULL); //use gettimeofday to calculate time to millisecond //then iterate over recv socket and receive and record //use sysinfo to make sure we don't get stuck in a loop with timechange struct sysinfo si; sysinfo(&si); //for now hardcode to 5 second overall timeout unsigned long timeout = si.uptime + 5; //seconds unsigned long cur_time = si.uptime; while (cur_time < timeout) { int id = receive(); if (_debug) { cout << "LBTestICMP::start(): " << id << endl; } //update current time for comparison struct sysinfo si; sysinfo(&si); timeval recv_time; gettimeofday(&recv_time,NULL); cur_time = si.uptime; map::iterator r_iter = _results.find(id); if (r_iter != _results.end()) { //calculate time in milliseconds int secs = 0; int msecs = recv_time.tv_usec - send_time.tv_usec; if (msecs < 0) { secs = recv_time.tv_sec - send_time.tv_sec - 1; } else { secs = recv_time.tv_sec - send_time.tv_sec; } //time in milliseconds below int rtt = abs(msecs) / 1000 + 1000 * secs; if (rtt < data->_resp_time) { return rtt; } else { return -1; } _results.erase(r_iter); } } if (_debug) { cout << "LBTestICMP::start(): finished heath test" << endl; } return -1; } /** * * **/ void ICMPEngine::send(const string &iface, const string &target_addr, int packet_id) { int err; sockaddr_in taddr; timeval send_time; icmphdr *icmp_hdr; int icmp_pktsize = 40; char buffer[icmp_pktsize]; if (iface.empty() || target_addr.empty()) { return; } // bind a socket to a device name (might not work on all systems): if (setsockopt(_send_sock, SOL_SOCKET, SO_BINDTODEVICE, iface.c_str(), iface.size()) != 0) { syslog(LOG_ERR, "wan_lb: failure to bind to interface: %s", iface.c_str()); return; //will allow the test to time out then } //convert target_addr to ip addr struct hostent *h = gethostbyname(target_addr.c_str()); if (h == NULL) { if (_debug) { cerr << "LBTestICMP::send() Error in resolving hostname" << endl; } syslog(LOG_ERR, "wan_lb: error in resolving configured hostname: %s", target_addr.c_str()); return; } icmp_hdr = (struct icmphdr *)buffer; icmp_hdr->type = ICMP_ECHO; icmp_hdr->code = 0; icmp_hdr->checksum = 0; icmp_hdr->un.echo.id = htons(getpid()); icmp_hdr->un.echo.sequence = 0; int length = sizeof(buffer); //we'll put in time of packet sent for the heck of it, may //want to use this in future tests, feel free to remove. gettimeofday(&send_time, (struct timezone*)NULL); char* datap = &buffer[8]; memcpy(datap, (char*)&send_time.tv_sec, sizeof(send_time.tv_sec)); datap = &buffer[12]; memcpy(datap, (char*)&send_time.tv_usec, sizeof(send_time.tv_usec)); datap = &buffer[16]; memcpy(datap, (char*)&packet_id, sizeof(packet_id)); //packet id datap = &buffer[18]; int val(icmp_pktsize); memcpy(datap, (char*)&val, 2); //packet id icmp_hdr->un.echo.sequence = 1; icmp_hdr->checksum = 0; icmp_hdr->checksum = in_checksum((unsigned short *)icmp_hdr,length>>1); struct in_addr ia; memcpy(&ia, h->h_addr_list[0], sizeof(ia)); unsigned long addr = ia.s_addr; taddr.sin_addr.s_addr = addr; taddr.sin_family = AF_INET; bzero(&(taddr.sin_zero), 8); //need to direct this packet out a specific interface!!!!!!!!!!!!! err = sendto(_send_sock, buffer, icmp_pktsize, 0, (struct sockaddr*)&taddr, sizeof(taddr)); if (_debug) { cout << "LBTestICMP::send(): sendto: " << err << ", packet id: " << packet_id << endl; } if(err < 0) { if (_debug) { if (errno == EBADF) cout << "EBADF" << endl; else if (errno == ENOTSOCK) cout << "ENOTSOCK" << endl; else if (errno == EFAULT) cout << "EFAULT" << endl; else if (errno == EMSGSIZE) cout << "EMSGSIZE" << endl; else if (errno == EWOULDBLOCK) cout << "EWOULDBLOCK" << endl; else if (errno == EAGAIN) cout << "EAGAIN" << endl; else if (errno == ENOBUFS) cout << "ENOBUFS" << endl; else if (errno == EINTR) cout << "EINTR" << endl; else if (errno == ENOMEM) cout << "ENOMEM" << endl; else if (errno == EACCES) cout << "EACCES" << endl; else if (errno == EINVAL) cout << "EINVAL" << endl; else if (errno == EPIPE) cout << "EPIPE" << endl; else cout << "unknown error: " << errno << endl; } syslog(LOG_ERR, "wan_lb: error on sending icmp packet: %d", errno); } } /** * * **/ int ICMPEngine::receive() { int icmp_pktsize = 40; char resp_buf[icmp_pktsize]; icmphdr *icmp_hdr; timeval wait_time; fd_set readfs; int ret; FD_ZERO(&readfs); FD_SET(_recv_sock, &readfs); wait_time.tv_usec = 0; wait_time.tv_sec = 3; //3 second timeout if (_debug) { cout << "LBTestICMP::receive(): start" << endl; } while (select(_recv_sock+1, &readfs, NULL, NULL, &wait_time) != 0) { ret = ::recv(_recv_sock, &resp_buf, icmp_pktsize, 0); if (ret != -1) { if (_debug) { cout << "LBTestICMP::receive(): recv: " << ret << endl; } icmp_hdr = (struct icmphdr *)(resp_buf + sizeof(iphdr)); if (icmp_hdr->type == ICMP_ECHOREPLY) { if (_debug) { cout << "LBTestICMP::receive(): " << endl; } //process packet data char* data; int id = 0; data = (char*)(&resp_buf) + 36; memcpy(&id, data, sizeof(unsigned short)); return id; } } } return -1; } /** * * **/ unsigned short ICMPEngine::in_checksum(const unsigned short *buffer, int length) const { unsigned long sum; for (sum=0; length>0; length--) sum += *buffer++; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return ~sum; }