顯示具有 C 標籤的文章。 顯示所有文章
顯示具有 C 標籤的文章。 顯示所有文章

2011年1月27日 星期四

nf_sockopt: A Mechanism for IPC between The Kernel And User Space

今天trace ebtables如何把rule下到kernel的過程中, 學到了一種kernel/user space溝通的方式:nf_sockopt。

nf_sockopt的使用相當簡單:

1. 定義get/set options
#ifndef __MY_SOCKOPT_H__
#define __MY_SOCKOPT_H__

/* {g,s}etsockopt numbers */
#define MY_BASE_CTL            999

enum
{
    MY_SO_SET_CMD1 = MY_BASE_CTL,
    MY_SO_SET_CMD2,
    MY_SO_SET_MAX,
}MY_SO_SET;

enum
{
    MY_SO_GET_CMD1 = MY_BASE_CTL,
    MY_SO_GET_CMD2,
    MY_SO_GET_CMD3,
    MY_SO_GET_MAX
}MY_SO_GET;

#endif
2. 在kernel space註冊要handle的options
#include <linux/module.h>
#include <net/sock.h>
#include <linux/netfilter.h>
#include "my_sockopt.h"

static int do_my_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
{
    printk("Get from user space [%s]\n", (char*)user);
   
    return 0;
}

static int do_my_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
{
    int ret;
    printk("Send to user space [%d]\n", cmd);
    ret = cmd;
    //copy_to_user(void __user *to, const void *from, unsigned long n)
    ret = copy_to_user(user, &ret, sizeof(ret));
   
    return ret;
}

static struct nf_sockopt_ops my_sockopts =
{
    .pf         = PF_INET,
    .set_optmin = MY_BASE_CTL,
    .set_optmax = MY_SO_SET_MAX + 1,
    .set        = do_my_set_ctl,
    .get_optmin = MY_BASE_CTL,
    .get_optmax = MY_SO_GET_MAX + 1,
    .get        = do_my_get_ctl,
};

static int __init mysockopt_init(void)
{
    int ret;
   
    if ((ret = nf_register_sockopt(&my_sockopts)) < 0)
    {
        printk("nf_register_sockopt failed [%d]\n", ret);
        return ret;
    }
    return 0;
}

static void __exit mysockopt_fini(void)
{
    nf_unregister_sockopt(&my_sockopts);
}

module_init(mysockopt_init);
module_exit(mysockopt_fini);
MODULE_LICENSE("GPL");
3. 在user space開一個RAW socket, 再利用g/setsockopt透過設定socket的options, 便可以取得/設定kernel space的資訊
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "my_sockopt.h"

int sockfd = -1;

static int get_sockfd()
{
    int ret = 0;
    if (sockfd == -1) {
        sockfd = socket(AF_INET, SOCK_RAW, PF_INET);
        if (sockfd < 0)
        {
            perror("get_sockfd");
            ret = -1;
        }
    }
    return ret;
}

int set_to_kernel()
{
    char buf[64] = "Hello, sockopt!!!";

    if (get_sockfd())
        return -1;
       
    if (!setsockopt(sockfd, IPPROTO_IP, MY_SO_SET_CMD1, buf, sizeof(buf)))
        return -1;

    return 0;
}

int get_from_kernel()
{
    int ret = -1;
    int len = 0;

    if (get_sockfd())
        return -1;
       
    if (getsockopt(sockfd, IPPROTO_IP, MY_SO_GET_CMD3, &ret, &len))
        return -1;
   
    printf("Get from kernel [%d]\n", ret);

    return 0;
}

int main()
{
    set_to_kernel();
    get_from_kernel();
    return 0;
}
相較於netlink, nf_sockopt容易許多。兩者間的差異, 比較能夠直接感受到的, 便是netlink可以雙向initiate session; 而nf_sockopt則是由user space去主控, kernel space只是被動地聽命行事。


參考資料:
[1] ebtables.c
[2] communication.c
[3] 使用sockopt与內核交換數据

2010年12月24日 星期五

sscanf

今天遇到了要去抓某個interface的tx/rx數量的問題, 以前最常用的方法就利用多次的strtok_r去抓到自己要的欄位, 其實, sscanf有時候可以更簡單的達成這個目的, 但是過去苦於看不懂他format的用法, 而沒有利用sscanf來做, 但看完"sscanf 函數用法"後, 總算對sscanf表示式的寫法多了點概念.

在Linux上, 各個network interface的統計資料會被記錄在"/proc/net/dev"裡面
Receive/Transmit bytes分別位於第2欄與第10欄
假設經把eth0那列的string都存到buf內
用strtok來做
void eth_statistics_parser_strtok(char *buf)
{
        char *ptr=NULL, *saveptr=NULL;
        unsigned long tx=0, rx=0;

        strtok_r(buf, ":", &saveptr);
        ptr = strtok_r(NULL, " ", &saveptr);
        rx = atol(ptr);
        strtok_r(NULL, " ", &saveptr);
        strtok_r(NULL, " ", &saveptr);
        strtok_r(NULL, " ", &saveptr);
        strtok_r(NULL, " ", &saveptr);
        strtok_r(NULL, " ", &saveptr);
        strtok_r(NULL, " ", &saveptr);
        strtok_r(NULL, " ", &saveptr);
        ptr = strtok_r(NULL, " ", &saveptr);
        tx = atol(ptr);

        printf("Rx Bytes: %d; Tx Bytes: %d\n", rx, tx);
}
用sscanf
void eth_statistics_parser_sscanf(char *buf)
{
        unsigned long tx=0, rx=0;
        //sscanf(buf, "%*s%lu%*s%*s%*s%*s%*s%*s%*s%lu", &rx, &tx);
        sscanf(buf, "%*[^:]:%lu%*s%*s%*s%*s%*s%*s%*s%lu", &rx, &tx);

        printf("Rx Bytes: %d; Tx Bytes: %d\n", rx, tx);
}
雖然不知道有沒有比較好, 但是看起來酷很多.


參考資料:
[1] sscanf 函數用法

2010年12月21日 星期二

Hash Table in C

Hash Table應該上計概或資料結構的時候都會講到, 一般的用法就是傳入一個Key, 透過查表, 回傳相對應的Value.
看看wiki上"Hash table"的解釋, 感覺實做上似乎不是那麼容易, 尤其是hash function的設計, 但多數的程式語言, 如perl, java, c#, 都提供了hash相關的library. 在linux下, 也有一系列的"hash table management" APIs.

以下提供一個簡單的範例
my_hash.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <search.h>

typedef struct _node_t
{
    char key[16];
    char value[16];
}node_t;

static node_t *node_list = NULL;

static struct hsearch_data htab;

void set_node(char *name, char *value)
{
    ENTRY e, *ep;

    memset(&e, 0, sizeof(ENTRY));
    e.key = name;

    hsearch_r(e, FIND, &ep, &htab);
    if(ep)
        ep->data = value;
    else
    {
        e.data = value;
        hsearch_r(e, ENTER, &ep, &htab);
    }
}

void get_node(char *name, char *buf)
{
    ENTRY e={NULL,NULL}, *ep;

    e.key = name;
    hsearch_r(e, FIND, &ep, &htab);

    if (ep && ep->data)
        strcpy(buf, ep->data);
    else
        strcpy(buf, "");
}

void release_htab()
{
    if (node_list) free(node_list);
    hdestroy_r(&htab);
}

void create_htab(char *fpath)
{
    FILE *fp = NULL;
    int nel=0;   

    fp = fopen(fpath, "r");
    if (!fp)
    {
        CM_DBG("Cannot open %s\n", fpath);
        return;
    }

    release_htab();

    // Get the total number
    while(!feof(fp) && fgets(str, BUF_SIZE, fp))
    {
        nel++;
    }

    if(!hcreate_r(nel, &htab))
        return;

    node_list = malloc(sizeof(node_t)*nel);
    memset(node_list, 0, (sizeof(node_t)*nel));

    rewind(fp);

    char str[256] = {0};
    char *ptr=NULL, *saveptr=NULL;
    node_t *p_node = NULL;
    int i = 0;

    while(!feof(fp) && fgets(str, BUF_SIZE, fp))
    {
        p_node = (node_t *)(node_list+i);

        //The string format: Name,PhoneNo
        ptr = strtok_r(str, ",", &saveptr);        // Name
        if (ptr)
            strcpy(p_node->key, ptr);
        else
            continue;

        ptr = strtok_r(NULL, ",\n", &saveptr);    // Phone No
        if (ptr)
            strcat(p_node->value, ptr);
        else
            continue;

        set_node(p_node->key, p_node->value);    //Put into hash table
        i++;
    }
}
其中, 要注意的地方在於, 塞hash table時, 需要動態配置記憶體空間來儲存值組(key, value), 但hdestroy並不會釋放動態配置的記憶體空間, 使用者需要自己free. 在範例程式中, 單純開了一個大塊的空間去存值, 但比較好的作法,可能是用linked list去紀錄各個值組, 這樣比較不用怕浪費太多記憶體空間或是buffer不夠大的問題.

參考資料:
[1] hsearch_r.c

2010年12月17日 星期五

Unix Domain Socket

由Wiki上對"Unix domain socket"的解釋, 很清楚的知道它也是IPC的一種方式, 因為他所用的API與internet socket幾乎相同, 所以對於寫過socket程式的人, 應該很好上手.

這裡提供一個小範例, 將unix socket api再包一層, 讓開發程式時能更容易的達成IPC目的.

my_unix_socket.h
#ifndef __MY_UNIX_SOCKET_H__
#define __MY_UNIX_SOCKET_H__

int create_listener(char* name, void* funp(char*));
int send_to_listener(char* app_name, void* buf, int buf_size);

#endif /* __MY_UNIX_SOCKET_H__ */

my_unix_socket.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>

#define MAX_BUF_SIZE  (1024)

static void first_handler(int fd, char* buf)
{
  int connected_fd;
  struct sockaddr_un from;
  int lenfrom = sizeof(from);
 
  bzero(&from, sizeof(from));
 
  if ((connected_fd = accept(fd, (struct sockaddr *)&from, &lenfrom)) < 0 )
  {
    printf("accept failed\n");
    return;
  }

  if (recv(connected_fd, buf, MAX_BUF_SIZE, 0) < 0)
  {
    printf("recv failed\n");
    return;
  }

  close(connected_fd);

  return;
}

int create_listener(char* name, void* funp(char*))
{
  int fd, ret=-1;
  struct sockaddr_un saddr;
  struct sockaddr_un from;
  char buf[MAX_BUF_SIZE];

  /* open a socket */
  if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
  {
    printf("socket failed\n");
    goto fun_exit;
  }
 
  /* set the addr */
  unlink(name);
  bzero(&saddr, sizeof(saddr));
  saddr.sun_family = AF_UNIX;
  strcpy(saddr.sun_path, name);

  /* bind socket with addr */
  if (bind(fd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
  {
    printf("bind failed\n");
    goto fun_exit;
  }
 
  /* listen fd */
  if (listen(fd, 10) < 0)
  {
    printf("listen failed\n");
    goto fun_exit;
  }

  fd_set rset;
  FD_ZERO(&rset);
  FD_SET(fd, &rset);

  while (1)
  {
    select(fd+1, &rset, NULL, NULL, NULL);
    if (FD_ISSET(fd, &rset))
    {
      first_handler(fd, buf);
      funp(buf);
    }
  }
  ret = 0;

fun_exit:
  if (fd>0) close(fd);
  return ret;
}

int send_to_listener(char* app_name, void* buf, int buf_size)
{
  int fd, ret=-1;
  struct sockaddr_un saddr;

  if (buf_size > MAX_BUF_SIZE)
  {
    printf("The max message size is %d\n", MAX_BUF_SIZE);
    goto fun_exit;
  }

  /* open a socket */
  if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
  {
    printf("socket failed\n");
    goto fun_exit;
  }
 
  /* set the saddr */
  bzero(&saddr, sizeof(saddr));
  saddr.sun_family = AF_UNIX;
  strcpy(saddr.sun_path, app_name);

  /* connect to server */
  if (connect(fd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
  {
    printf("connect failed\n");
    goto fun_exit;
  }

  /* send data */
  if (send(fd, buf, buf_size, 0) < 0)
  {
    printf("send failed\n");
    goto fun_exit;
  }
  ret = 0;

fun_exit:
  if (fd>0) close(fd);
  return ret;
}

兩隻簡單的應用程式:

server.c
#include <stdio.h>

void my_recv(char* buf)
{
  printf("%s,%s: %s\n", __FILE__, __FUNCTION__, buf);
}

int main()
{
  create_listener("my_server", my_recv);
}

client.c
#include <stdio.h>
#include "my_unix_socket.h"

int main()
{
  char buf[] = "hello unix socket";
  send_to_listener("my_server", buf, sizeof(buf));
}


參考資料:
[1] Unix Sockets
[2] Unix domain sockets vs. internet sockets