Saturday, August 16, 2014

C++ to get Public IP of Router with libcurl

I posted an article about how to get Public IP address of your Router with cURL in shell script. I converted the script to C++ program with libcurl.

pi@desktoppi ~ $ sudo apt-get install libcurl4-openssl-dev
You need to install libcurl in order to compile the program.

Also the example below is written in C++11, so you will need to install g++-4.7, which supports C++11.
pi@desktoppi ~ $ sudo apt-get install g++-4.7

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// MycURL.hpp written by Jae Hyuk Kwak
namespace MycURLNS
{
    class MycURL
    {
        unique_ptr< CURL, decltype( curl_easy_cleanup )* > curl_;
    public:
        MycURL() : curl_( curl_easy_init(), curl_easy_cleanup )
        {
            if ( nullptr == curl_ )
                throw runtime_error( "curl_easy_init() failed." );
        }
        CURL *native() { return curl_.get(); }
    };

    template< typename ParamType >
    void SetOpt_( MycURL *mycURL, CURLoption option, const ParamType &param )
    {
        CURLcode res = curl_easy_setopt( mycURL->native(), option, param );
        if ( CURLE_OK != res )
            throw runtime_error( string( "curl_easy_setopt() failed." )
            + curl_easy_strerror( res ) );
    }

    void SetOpt_URL( MycURL *mycURL, const string url )
    {
        SetOpt_( mycURL, CURLOPT_URL, url.c_str() );
    }

    void SetOpt_USERPWD( MycURL *mycURL, const string user, const string pwd )
    {
        SetOpt_( mycURL, CURLOPT_USERPWD, ( user + ":" + pwd ).c_str() );
    }

    size_t WriteMemoryCallback_( void *contents, size_t size, size_t numberOfMemoryBlocks, void *userChunk )
    {
        const size_t realSize = size * numberOfMemoryBlocks;
        string &html = *static_cast< string * >( userChunk );
        html += string( static_cast< char * >( contents ), realSize );
        return realSize;
    }

    string RequestGet( MycURL *mycURL )
    {
        string html;
        SetOpt_( mycURL, CURLOPT_WRITEDATA, &html );
        SetOpt_( mycURL, CURLOPT_WRITEFUNCTION, WriteMemoryCallback_ );
        const CURLcode res = curl_easy_perform( mycURL->native() );
        if ( CURLE_OK != res )
            throw runtime_error( string( "curl_easy_perform() failed: " )
            + curl_easy_strerror( res ) );
        return html;
    }
}

The source code above is a wrapping class for libcurl easy-functions. You can find document from here. There are three functions from libcurl:
  1. curl_easy_init() : will create an handle for CURL,
  2. curl_easy_setopt() : will allow us to set options such as user id and URL and
  3. curl_easy_perform() : will allow us to execute whatever options you set.

There are several options with curl_easy_setopt() and that was little tricky part:
  • CURLOPT_URL : is for setting the URL,
  • CURLOPT_USERPWD : is for setting user ID and Password,
  • CURLOPT_WRITEFUNCTION : is to set a function pointer that will be called when HTML data is retrieved.
  • CURLOPT_WRITEDATA : is to set a data point which will be used by a function set with CURLOPT_WRITEFUNCTION.
In other words, you send a memory pointer of variable with CURLOPT_WRITEDATA and the pointer will come into the function that you set as a call-back-function with CURLOPT_WRITEFUNCTION.

The source code also reveals my C++ programming style little bit.
  • I like to have private member variable and methods to have "_" at the end of the name.
  • I almost always hold member pointer with shared_ptr or unique_ptr; when I return the pointer to users, I return the raw-pointer not the share_ptr or unique_ptr so that the ownership remains.
  • I use a lot of exceptions because it makes source code simpler.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// public_ip.cpp written by Jae Hyuk Kwak
#include "stdafx.h"
#include "MycURL.hpp"
#include "config.hpp"
using namespace Configuration;
using namespace MycURLNS;
int main( int argn, const char *argv[] )
{
    curl_global_init( CURL_GLOBAL_ALL ); // required once
    const string html = []()    // lamda with C++11
    {
        MycURL curl;
        SetOpt_URL( &curl, URL );
        SetOpt_USERPWD( &curl, ID, PW );
        //SetOpt_( &curl, CURLOPT_VERBOSE, 1 );
        return RequestGet( &curl );
    }();

    // find and filter "<td>IP Address</td><td><b>XX.XX.XX.XX</b>"
    stringstream ss( [&]()
    {
        const size_t offset0 = html.find( "IP Address" );
        const size_t offset1 = html.find_first_of( "0123456789", offset0 );
        return ( html.c_str() + offset1 );
    }() );

    char dot;
    int ip0, ip1, ip2, ip3;
    ss >> ip0 >> dot >> ip1 >> dot >> ip2 >> dot >> ip3;
    cout << ip0 << "." << ip1 << "." << ip2 << "." << ip3 << endl;
    return 0;
}

1
2
3
4
5
6
7
// stdafx.h written by jae hyuk kwak.
#include <stdexcept>    // runtime_exception
#include <memory>   // unique_ptr
#include <iostream> // cerr, cout
#include <curl/curl.h>
#include <sstream>  // stringstream
using namespace std;

1
2
3
4
5
6
7
// config.hpp written by Jae Hyuk Kwak
namespace Configuration
{
    static const string ID = "admin"; // modify this for your setting
    static const string PW = "your_password"; // modify this
    static const string URL = "http://192.168.0.1/RgSetup.asp"; // modify this
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Makefile written by Jae Hyuk Kwak
CC=g++-4.7
CFLAGS=-Wall -g -std=c++11

all: public_ip

public_ip: *.hpp public_ip.cpp stdafx.h.gch
 $(CC) $(CFLAGS) -o public_ip -lcurl public_ip.cpp

stdafx.h.gch: stdafx.h
 $(CC) $(CFLAGS) -c stdafx.h

clean:
 rm stdafx.h.gch public_ip
The source code is an example of how to use the wrapping class. You will need to modify Configuration::PW or ID. You may need to modify the search patter depending on what your Router send you.

I am using -std=c++11, which is supported only by 4.7 at the moment of writing this post.
*Note: don't be confused with gcc-4.7, which is C compiler not C++ compiler.

The Makefile uses pre-compiled-header, which improves a lot of compile time; the compiled file from the pre-compile-header is 14MB, which is very large.

pi@desktoppi ~/prog/MycURL $ make
g++-4.7 -Wall -g -std=c++11 -c stdafx.h
g++-4.7 -Wall -g -std=c++11 -o public_ip -lcurl public_ip.cpp
pi@desktoppi ~/prog/MycURL $ ./public_ip 
68.6.129.115
pi@desktoppi ~/prog/MycURL $
Once you have all the source code, you can compile by a command: make
You will get the executable, public_ip.

No comments:

Post a Comment

About Me

My photo
Tomorrow may not come, so I want to do my best now.