Tuesday, April 29, 2014

Using ipset

Finally got it to work. Nobody seems to have a complete and accurate solution for my case... So here it is.
 #include <iostream>  
 #include <thread>     // std::thread  
 #include <mutex>     // std::mutex  
 #include <assert.h>  
 extern "C" {  

 // Debian libipset libary is a C library. All the headers  
 // have to be enclosed in extern "C" brackets to have  
 // proper linkage  
 #include <libipset/session.h>      /* ipset_session_* */  
 #include <libipset/data.h>       /* enum ipset_data */  
 #include <libipset/parse.h>       /* ipset_parse_* */  
 #include <libipset/types.h>       /* struct ipset_type */  
 #include <libipset/ui.h>        /* core options, commands */  
 #include <libipset/utils.h>       /* STREQ */  
 }  
 // debian-ipset is not thread-safe. We're using  
 // a mutex to serialize the ipset calls.  
 std::mutex s_ipsetMutex;  
 // We're only adding a command/an element to the set,  
 // so the restore line no. remains 0  
 #define IPSET_SESSION_LINE_NO 0  
 int  
 ipsetAdd(std::string table, std::string set_type, uint32_t interval)  
 {  
  int32_t ret = 0;  
  const struct ipset_type *type = NULL;  
  enum ipset_cmd cmd = IPSET_CMD_ADD;  
  struct ipset_session *session;  
  // debian-ipset calls are not thread-safe. We're adding  
  // a mutex to serialize the calls.  
  s_ipsetMutex.lock();  
  ipset_load_types();  
  // Initialize an ipset session by allocating a session structure  
  // and filling out with the initialization data.  
  session = ipset_session_init(printf);  
  if (session == NULL)  
  {  
   printf("ipsetAdd: Cannot initialize ipset session for "  
     "set_type %s. aborting.\n", set_type.c_str());  
   ret = 1;  
  }  
  else  
  {  
   // Set session lineno to report parser errors correctly   
   ipset_session_lineno(session, IPSET_SESSION_LINE_NO);  
   // Parse string as a setname.  
   // The value is stored in the data blob of the session.  
   ret = ipset_parse_setname(session, IPSET_SETNAME, table.c_str());  
   if (ret == 0)  
   {  
    type = ipset_type_get(session, cmd);  
    if (!type)  
    {  
     printf("ipsetAdd: Failed to get the set type from Kernel "  
       "set_type=%s table=%s sess=%p cmd=%d\n", set_type.c_str(), table.c_str(),  
       session, cmd);  
     ret = 1;  
    }  
    else  
    {  
     // Parse string as a (multipart) element according to the settype.  
     // The value is stored in the data blob of the session.  
     ret = ipset_parse_elem(session, (ipset_opt)type->last_elem_optional,  
                 set_type.c_str());  
     if (ret == 0)  
     {  
      // Put a given kind of data into the data blob and mark the  
      // option kind as already set in the blob.  
      ret = ipset_data_set(ipset_session_data(session), IPSET_OPT_TIMEOUT, &interval);  
      if (!ret)  
      {  
       // Execute a command  
       ret = ipset_cmd(session, cmd, IPSET_SESSION_LINE_NO);  
       if (ret)  
       {  
        printf("ipsetAdd: WARNING: Failed to execute "  
          "IPSET_CMD_ADD command. ipset exists? ret=%d set_type=%s table=%s\n",  
          ret, set_type.c_str(), table.c_str());  
        // Not an error condition  
        ret = 0;  
       }  
      }  
      else  
      {  
       printf("ipsetAdd: Failed to set timeout option. "  
         "ret=%d set_type=%s table=%s\n", ret, set_type.c_str(), table.c_str());  
      }  
     }  
     else  
     {  
      printf("ipsetAdd: Failed to parse element. "  
        "ret=%d set_type=%s table=%s\n", ret, set_type.c_str(), table.c_str());  
     }  
    }  
   }  
   else  
   {  
    printf("ipsetAdd: Failed to parse setname. "  
      "ret=%d set_type=%s table=%s\n", ret, set_type.c_str(), table.c_str());  
   }  
   // Finish the ipset session  
   if (ipset_session_fini(session) != 0)  
   {  
    printf("ipsetAdd: Failed to destroy ipset "  
      "session. ret=%d set_type=%s table=%s\n", ret, set_type.c_str(),  
      table.c_str());  
   }  
  } // else of if (session == NULL)  
  s_ipsetMutex.unlock();  
  return ret;  
 }  
 int main()  
 {  
  ipsetAdd("my_ipset_table", "127.0.0.1,62142,127.0.0.1", 600);  
  return 0;  
 }  


To build it:
$ g++ myipset.cpp -std=c++0x  -lipset -o myipset

Create your ipset first:
$ sudo ipset create my_ipset_table hash:ip,port,ip family inet maxelem 600000

Due to the fact that some of the ipset commands need special
capability to call or get valid return (e.g. ipset_type_get & ipset_cmd), you have to run it as root.
$ sudo myipset

To check it:
$ sudo ipset -l

Here is the C version of the same function:
 #include <stdio.h>  
 #include <pthread.h>       
 #include <assert.h>  
 #include <libipset/session.h>      /* ipset_session_* */  
 #include <libipset/data.h>       /* enum ipset_data */  
 #include <libipset/parse.h>       /* ipset_parse_* */  
 #include <libipset/types.h>       /* struct ipset_type */  
 #include <libipset/ui.h>        /* core options, commands */  
 #include <libipset/utils.h>       /* STREQ */  
 // debian-ipset is not thread-safe. We're using  
 // a mutex to serialize the ipset calls.  
 pthread_mutex_t s_ipsetMutex;  
 // We're only adding a command/an element to to the set,  
 // so the restore line no. remains 0  
 #define IPSET_SESSION_LINE_NO 0  
 int  
 ipsetAdd(char *table, char *set_type, uint32_t interval)  
 {  
  int32_t ret = 0;  
  const struct ipset_type *type = NULL;  
  enum ipset_cmd cmd = IPSET_CMD_ADD;  
  struct ipset_session *session;  
  // debian-ipset calls are not thread-safe. We're adding  
  // a mutex to serialize the calls.  
  pthread_mutex_lock(&s_ipsetMutex);  
  ipset_load_types();  
  // Initialize an ipset session by allocating a session structure  
  // and filling out with the initialization data.  
  session = ipset_session_init(printf);  
  if (session == NULL)  
  {  
   printf("ipsetAdd: Cannot initialize ipset session for "  
     "set_type %s. aborting.\n", set_type);  
   ret = 1;  
  }  
  else  
  {  
   // Set session lineno to report parser errors correctly   
   ipset_session_lineno(session, IPSET_SESSION_LINE_NO);  
   // Parse string as a setname.  
   // The value is stored in the data blob of the session.  
   ret = ipset_parse_setname(session, IPSET_SETNAME, table);  
   if (ret == 0)  
   {  
    type = ipset_type_get(session, cmd);  
    if (!type)  
    {  
     printf("ipsetAdd: Failed to get the set type from Kernel "  
       "set_type=%s table=%s sess=%p cmd=%d\n", set_type, table,  
       session, cmd);  
     ret = 1;  
    }  
    else  
    {  
     // Parse string as a (multipart) element according to the settype.  
     // The value is stored in the data blob of the session.  
     ret = ipset_parse_elem(session, (enum ipset_opt)type->last_elem_optional,  
                 set_type);  
     if (ret == 0)  
     {  
      // Put a given kind of data into the data blob and mark the  
      // option kind as already set in the blob.  
      ret = ipset_data_set(ipset_session_data(session), IPSET_OPT_TIMEOUT, &interval);  
      if (!ret)  
      {  
       // Execute a command  
       ret = ipset_cmd(session, cmd, IPSET_SESSION_LINE_NO);  
       if (ret)  
       {  
        printf("ipsetAdd: WARNING: Failed to execute "  
          "IPSET_CMD_ADD command. ipset exists? ret=%d set_type=%s table=%s\n",  
          ret, set_type, table);  
        // Not an error condition  
        ret = 0;  
       }  
      }  
      else  
      {  
       printf("ipsetAdd: Failed to set timeout option. "  
         "ret=%d set_type=%s table=%s\n", ret, set_type, table);  
      }  
     }  
     else  
     {  
      printf("ipsetAdd: Failed to parse element. "  
        "ret=%d set_type=%s table=%s\n", ret, set_type, table);  
     }  
    }  
   }  
   else  
   {  
    printf("ipsetAdd: Failed to parse setname. "  
      "ret=%d set_type=%s table=%s\n", ret, set_type, table);  
   }  
   // Finish the ipset session  
   if (ipset_session_fini(session) != 0)  
   {  
    printf("ipsetAdd: Failed to destroy ipset "  
      "session. ret=%d set_type=%s table=%s\n", ret, set_type,  
      table);  
   }  
  } // else of if (session == NULL)  
  pthread_mutex_unlock(&s_ipsetMutex);  
  return ret;  
 }  

7 comments:

Tijl.Leenders said...

Great work!

Can't compile it in C though...

ipset_parse_elem(session, (ipset_opt)type->last_elem_optional , set_type);

Checking parse.h => The function expects enum as second argument of - not a bool

root@debian:~/c# gcc iptset.c -lipset -o iptset
iptset.c: In function ‘ipsetAdd’:
iptset.c:67:50: error: ‘ipset_opt’ undeclared (first use in this function)
iptset.c:67:50: note: each undeclared identifier is reported only once for each function it appears in
iptset.c:67:60: error: expected ‘)’ before ‘type’
iptset.c:67:60: error: too few arguments to function ‘ipset_parse_elem’
In file included from iptset.c:8:0:
/usr/include/libipset/parse.h:93:12: note: declared here

Anyway, I just need to call the library and add ONE ip to table x.
What would be the command without parsing, so hardcoding it directly with only the ip and the table as variables? (using -exist) ?

ChiMaster said...

Added C version.

ipset_opt undeclared problem:

63,64c72,73
< ret = ipset_parse_elem(session, (enum ipset_opt)type->last_elem_optional,
< set_type);
---
> ret = ipset_parse_elem(session, (ipset_opt)type->last_elem_optional,
> set_type.c_str());

Tijl.Leenders said...

Thanks for that :D
.. now it hangs on ipset_type_get(..)

created table first

root@debian:~/c# gcc iptset.c -lipset -o iptset
root@debian:~/c# ./iptset
ClientShaper ipsetAdd: Failed to get the set type from Kernel set_type=127.0.0.1,62142,127.0.0.1 table=my_ipset sess=0x96df008 cmd=9

ChiMaster said...

Create your ipset table first:
$ sudo ipset create my_ipset hash:ip,port,ip family inet maxelem 600000

Then run it:
$ sudo myipset

Tijl.Leenders said...

Hmm.. replaced my C code with yours.
Still not getting there...

root@debianhost:~# ipset list
Name: my_ipset_table
Type: hash:ip,port,ip
Header: family inet hashsize 1024 maxelem 600000
Size in memory: 8276
References: 0
Members:
root@debianhost:~#
root@debianhost:~#
root@debianhost:~# ipset --version
ipset v6.12.1, protocol version: 6
root@debianhost:~#
root@debianhost:~#
root@debianhost:~#
root@debianhost:~# uname -a
Linux debianhost 3.14-0.bpo.2-686-pae #1 SMP Debian 3.14.15-2~bpo70+1 (2014-08-21) i686 GNU/Linux
root@debianhost:~#
root@debianhost:~#
root@debianhost:~#
root@debianhost:~# gcc iptset.c -lipset -o iptset
root@debianhost:~#
root@debianhost:~#
root@debianhost:~# ./iptset
ipsetAdd: WARNING: Failed to execute IPSET_CMD_ADD command. ipset exists? ret=-1 set_type=127.0.0.1,62142,127.0.0.1 table=my_ipset_table
root@debianhost:~#

Tijl.Leenders said...

Got it working for adding 1 ip at a time (using ipset_parse_single_ip(session, IPSET_OPT_IP, "127.0.0.1");).

Do you know how to create a set of hash:ip type?

family inet hashsize 1024 maxelem 65536

Tijl.Leenders said...

Never mind, I figured it out using the PROTOCOL file:

// TODO Add mutex

// compile with 'gcc iptest.c -lipset -o iptest'

#include /* fprintf, fgets */

#include /* enum ipset_data */
#include /* ipset_parse_* */
#include /* ipset_session_* */
#include /* struct ipset_type */
#include /* core options, commands */
#include /* STREQ */

// We're only adding a command/an element to to the set,
// so the restore line no. remains 0
#define IPSET_SESSION_LINE_NO 0

int ipsetCreate(char *table)
{

int32_t ret = 0;
enum ipset_cmd cmd = IPSET_CMD_CREATE;
struct ipset_session *session;
uint32_t interval = 64800;
const struct ipset_type *type = NULL;

//debug
printf ("getting ready to load types\n");
ipset_load_types(); // what does this do?

//debug
printf ("getting ready to init session\n");
session = ipset_session_init(printf);
if (session == NULL) {
printf("\nCannot initialize ipset session, aborting.\n");
return -1;
}

//debug
printf ("getting ready to parse string as setname\n");
// Parse string as setname
// The value is stored in data blob of session
ret = ipset_parse_setname(session, IPSET_SETNAME, table);
if (ret != 0)
{
printf("\nFailed to parse setname.\n ");
}

//debug
printf ("getting ready to parse typename\n");
ret = ipset_parse_typename(session, IPSET_OPT_TYPENAME, "hash:ip");
if (ret != 0)
{
printf("\nFailed to parse typename\n.");
}

//insert IPSET_ATTR_REVISION ?

//debug
printf ("getting ready to parse family\n");
ret = ipset_parse_family(session, IPSET_OPT_FAMILY, "inet");
if (ret != 0)
{
printf("\nFailed to parse family\n.");
}

//insert IPSET_ATTR_FLAGS ?
//insert IPSET_ATTR_DATA ?

//debug
printf ("getting ready to exec command\n");
// execute a command
ret = ipset_cmd(session, cmd, IPSET_SESSION_LINE_NO);
if (ret)
{
printf ("\nFailed command\n");
}

// Finish the ipset session
if (ipset_session_fini(session) != 0)
{
printf("\nFailed to destroy ipset session\n");
}


return 0;
}

int main()
{
ipsetCreate("googlex");
return 0;
}