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.

1 comment:

  1. After you have logged into the Arris admin interface you should have the ability to change any settings here that are readily available.

    ReplyDelete

About Me

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