8

I am trying to write a simple C++ ping function to see if a network address is responding. I don't need ICMP specifically, I just need to see if the server is there and responding to anything. I have been doing some research and every solution I come up with requires creating a raw socket or something which requires the program to have sudo access. I won't be able to guarantee that the system I am running this on will be able to modify the network stack, so this is not valid.

Here are some related questions I have already looked at.

  1. Opening RAW sockets in linux without being superuser
  2. ICMP sockets (linux)
  3. How to Ping Using Sockets Library - C
  4. Why does ping work without administrator privileges?
  5. C++ Boost.asio Ping

It appears that ping requires superuser access for a good reason. I don't want to purposefully create a security loophole, I just want to see if a server is responding. Is there a good c++ function or resource that can do this? I will make sure to post any sample solution I come up with. I need a Linux (BSD) socket solution. Since almost every unix-like system runs SSH, I can even just test against port 22. I am only going to target Linux systems as a constraint.

Thanks

Community
  • 1
  • 1
msmith81886
  • 2,286
  • 2
  • 20
  • 27
  • 1
    Seems like you've already tried the obvious approach and hit a wall due to access limitations. So what other approaches can you think of? This question is too broad if you're asking us to list the ones _we_ can think of. – Lightness Races in Orbit May 20 '15 at 17:44
  • 1
    Would it be too heavyweight to execute the `ping` program and parse the output? – 01d55 May 20 '15 at 17:48
  • Good point. I can use popen and write it to a string. Let me take a crack at an example code solution. That is not as elegant as I was thinking but beggars can't be choosers. – msmith81886 May 20 '15 at 17:49
  • 3
    Why not just try using the server the way it's intended to be used and see if it works? – David Schwartz May 20 '15 at 18:44
  • Not sure about Linux, but on some systems, you can use a UDP socket with the ICMP protocol to send/receive ping/echo packets so you don't need admin/sudo rights (Linux traceroute uses UDP). – Remy Lebeau May 20 '15 at 20:00
  • That is a good point with using the server's own methods. I guess that is what they make operating systems for... – msmith81886 May 20 '15 at 21:19
  • Why bother pinging a server? If you want to ssh to it, just ssh to it. The result of the ping tells you nothing about the possible success or failure of an ssh attempt. For example, a failed ping could just mean something along the route is configured to drop ping packets, so the ssh could still work. Conversely, just because the ping succeeds doesn't mean the server is accepting ssh connections. So after the ping fails or succeeds, you still do the ssh, which fails or succeeds on its own. As David Schwartz already said: "just [use] the server the way it's intended". – Andrew Henle May 20 '15 at 23:52
  • The answer to 4 is that /bin/ping is owned by root and has set uid turned on. – Dmitry Rubanovich Oct 14 '15 at 01:35

2 Answers2

6

Here is an example with popen. I would love a better solution, so if there is a socket or other method which does not resort to a shell call, I would greatly appreciate it. This should run with just g++ ping_demo.cpp -o ping_demo. Let me know if it causes compile errors.

// C++ Libraries
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>


/**
 * @brief Convert String to Number
 */
template <typename TP>
TP str2num( std::string const& value ){

    std::stringstream sin;
    sin << value;
    TP output;
    sin >> output;
    return output;
}


/**
 * @brief Convert number to string
 */
template <typename TP>
std::string num2str( TP const& value ){
    std::stringstream sin;
    sin << value;
    return sin.str();
}


/**
 * @brief Execute Generic Shell Command
 *
 * @param[in]   command Command to execute.
 * @param[out]  output  Shell output.
 * @param[in]   mode read/write access
 *
 * @return 0 for success, 1 otherwise.
 *
*/
int Execute_Command( const std::string&  command,
                     std::string&        output,
                     const std::string&  mode = "r")
{
    // Create the stringstream
    std::stringstream sout;

    // Run Popen
    FILE *in;
    char buff[512];

    // Test output
    if(!(in = popen(command.c_str(), mode.c_str()))){
        return 1;
    }

    // Parse output
    while(fgets(buff, sizeof(buff), in)!=NULL){
        sout << buff;
    }

    // Close
    int exit_code = pclose(in);

    // set output
    output = sout.str();

    // Return exit code
    return exit_code;
}


/**
 * @brief Ping
 *
 * @param[in] address Address to ping.
 * @param[in] max_attempts Number of attempts to try and ping.
 * @param[out] details Details of failure if one occurs.
 *
 * @return True if responsive, false otherwise.
 *
 * @note { I am redirecting stderr to stdout.  I would recommend 
 *         capturing this information separately.}
 */
bool Ping( const std::string& address,
           const int&         max_attempts,
           std::string&       details )
{
    // Format a command string
    std::string command = "ping -c " + num2str(max_attempts) + " " + address + " 2>&1";
    std::string output;

    // Execute the ping command
    int code = Execute_Command( command, details );

    return (code == 0);
}


/**
 * @brief Main Function
 */
int main( int argc, char* argv[] )
{
    // Parse input
    if( argc < 2 ){
        std::cerr << "usage: " << argv[0] << " <address> <max-attempts = 3>" << std::endl;
        return 1;
    }

    // Get the address
    std::string host = argv[1];

    // Get the max attempts
    int max_attempts = 1;
    if( argc > 2 ){
        max_attempts = str2num<int>(argv[2]);
    }
    if( max_attempts < 1 ){
        std::cerr << "max-attempts must be > 0" << std::endl;
        return 1;
    }

    // Execute the command
    std::string details;
    bool result = Ping( host, max_attempts, details );

    // Print the result
    std::cout << host << " ";
    if( result == true ){
        std::cout << " is responding." << std::endl;
    }
    else{
        std::cout << " is not responding.  Cause: " << details << std::endl;
    }

    return 0;
}

Sample outputs

$> g++ ping_demo.cpp -o ping_demo

$> # Valid Example
$> ./ping_demo localhost
localhost  is responding.

$> # Invalid Example
$> ./ping_demo localhostt
localhostt  is not responding.  Cause: ping: unknown host localhostt

$> # Valid Example
$> ./ping_demo 8.8.8.8
8.8.8.8  is responding.
msmith81886
  • 2,286
  • 2
  • 20
  • 27
  • Just to clarify, pclose doesn't return 0 or 1 (from the manpage): The pclose() function returns -1 if wait4(2) returns an error, or some other error is detected. In the event of an error, these functions set errno to indicate the cause of the error. All in all though, great answer. – bergercookie Dec 04 '16 at 16:09
1

I created an easy to use ping class with added ethernet port detection capability. I based the code on msmith81886's answer but wanted to condense for those using it in industry.

ping.hpp

#ifndef __PING_HPP_
#define __PING_HPP_

#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <cerrno>
#include <cstring>

class system_ping
{
    public:
        int test_connection (std::string ip_address, int max_attempts, bool check_eth_port = false, int eth_port_number = 0);
    private:
        int ping_ip_address(std::string ip_address, int max_attempts, std::string& details);
};

#endif

ping.cpp

#include "../include/ping.hpp"

int system_ping::ping_ip_address(std::string ip_address, int max_attempts, std::string& details)
{
    std::stringstream ss;
    std::string command;
    FILE *in;
    char buff[512];
    int exit_code;

    try
    {
        command = "ping -c " + std::to_string(max_attempts) + " " + ip_address + " 2>&1";

        if(!(in = popen(command.c_str(), "r"))) // open process as read only
        {
            std::cerr << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | popen error = " << std::strerror(errno) << std::endl;       
            return -1;
        }

        while(fgets(buff, sizeof(buff), in) != NULL)    // put response into stream
        {
            ss << buff;
        }

        exit_code = pclose(in); // blocks until process is done; returns exit status of command

        details = ss.str();
    }
    catch (const std::exception &e)
    {
        std::cerr << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | e.what() = " << e.what() << std::endl;      
        return -2;
    }

    return (exit_code == 0);
}

int system_ping::test_connection (std::string ip_address, int max_attempts, bool check_eth_port, int eth_port_number)
{
    std::cout << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | started" << std::endl;      

    int eth_conn_status_int;
    std::string details;

    try
    {
        if (check_eth_port)
        {
            std::ifstream eth_conn_status ("/sys/class/net/eth" + std::to_string(eth_port_number) + "/carrier");

            eth_conn_status >> eth_conn_status_int; // 0: not connected; 1: connected
            if (eth_conn_status_int != 1)
            {
                std::cerr << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | eth" << std::to_string(eth_port_number) << " unplugged";        
                return -1;
            }
        }

        if (ping_ip_address(ip_address, max_attempts, details) != 1)
        {
            std::cerr << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | cannot ping " << ip_address << " | " << details << std::endl;   
            return -2;
        }
    }
    catch (const std::exception &e)
    {
        std::cerr << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | e.what() = " << e.what() << std::endl;      
        return -3;
    }

    std::cout << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | ping " << ip_address << " OK" << std::endl;         
    std::cout << __FILE__ << "(" << __FUNCTION__ << ":" << __LINE__ << ") | ended" << std::endl;        

    return 0;
}
xinthose
  • 3,213
  • 3
  • 40
  • 59
  • could you please explain the what the `std::string& details` is for? What are we passing in for that field? currently I only pass in a `" "` – user2883071 Apr 29 '16 at 18:43
  • 1
    @user2883071 Sure. `details` is passed by reference and is only printed if there is an error. It contains the command's response. You should leave the function as is, and only call `test_connection`. – xinthose Apr 29 '16 at 18:51