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与內核交換數据

2011年1月10日 星期一

dnw on linux

DNW是Samsung提供的軟體, 讓我們能透過USB做檔案傳輸, 原本Samsung只提供了Windows版本, 而網路上的不知名高手亦將DNW porting到Linux上.

藉由前人的努力, 現在想在Linux上跑DNW只要簡單幾個步驟
1. Download dnw2 source code
2. Add checksum in dnw2 and compile it

This source code is come from am8 and patched by cyl's blog
/* dnw2 linux main file. This depends on libusb.
 *
 * Author:   Fox <hulifox008@163.com>
 * License:  GPL
 *
 */

#include <stdio.h>
#include <usb.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define    IDEA6410_SECBULK_IDVENDOR   0x04e8
#define    IDEA6410_SECBULK_IDPRODUCT  0x1234

struct usb_dev_handle * open_port()
{
  struct usb_bus *busses, *bus;

  usb_init();
  usb_find_busses();
  usb_find_devices();

  busses = usb_get_busses();
  for(bus=busses;bus;bus=bus->next)
  {
     struct usb_device *dev;
    for(dev=bus->devices;dev;dev=dev->next)
    {
      if( IDEA6410_SECBULK_IDVENDOR==dev->descriptor.idVendor
      &&  IDEA6410_SECBULK_IDPRODUCT==dev->descriptor.idProduct)
      {
        printf("Target usb device found!\n");
        struct usb_dev_handle *hdev = usb_open(dev);
        if(!hdev)
        {
          perror("Cannot open device"); 
        }
        else
        {
          if(0!=usb_claim_interface(hdev, 0))
          {
            perror("Cannot claim interface");
            usb_close(hdev);
            hdev = NULL;
          }
        }
        return hdev;
      }
    }
  }
 
  printf("Target usb device not found!\n");

  return NULL;
}

void usage()
{
  printf("Usage: dnw2 <file>\n\n");
}

unsigned char* prepare_write_buf(char *filename, unsigned int *len)
{
  unsigned char *write_buf = NULL;
  struct stat fs;

  int fd = open(filename, O_RDONLY);
  if(-1==fd)
  {
    perror("Cannot open file");
    return NULL;
  }
  if(-1==fstat(fd, &fs))
  {
    perror("Cannot get file size");
    goto error;
  }
  write_buf = (unsigned char*)malloc(fs.st_size+10);
  if(NULL==write_buf)
  {
    perror("malloc failed");
    goto error;
  }

  if(fs.st_size != read(fd, write_buf+8, fs.st_size))
  {
    perror("Reading file failed");
    goto error;
  }

  printf("Filename : %s\n", filename);
  printf("Filesize : %d bytes\n", fs.st_size);

  *((u_int32_t*)write_buf) = 0x30000000;    //download address
  *((u_int32_t*)write_buf+1) = fs.st_size + 10;  //download size;

  //cy: compute checksum
  int k;
  u_int16_t csum=0;
  for(k=0; k<(int)fs.st_size; k++){
    csum +=write_buf[8+k];
  }
  printf("CheckSum=%d\n",(int)csum);

  write_buf[fs.st_size+ 8] = csum; //checksum;
  write_buf[fs.st_size+ 9] = csum>>8; //checksum;
  // end of checksum

  *len = fs.st_size + 10;
  return write_buf;

error:
  if(fd!=-1) close(fd);
  if(NULL!=write_buf) free(write_buf);
  fs.st_size = 0;
  return NULL;
 
}

int main(int argc, char *argv[])
{
  if(2!=argc)
  {
    usage();
    return 1;
  }

  struct usb_dev_handle *hdev = open_port();
  if(!hdev)
  {
    return 1;
  }

  unsigned int len = 0;
  unsigned char* write_buf = prepare_write_buf(argv[1], &len);
  if(NULL==write_buf) return 1;

  unsigned int remain = len;
  unsigned int towrite;
  printf("Writing data ...\n");
  while(remain)
  {
    towrite = remain>512 ? 512 : remain;
    if(towrite != usb_bulk_write(hdev, 0x2, write_buf+(len-remain), towrite, 3000))
    {
      perror("usb_bulk_write failed");
      break;
    }
    remain-=towrite;
    printf("\r%d%\t %d bytes     ", (len-remain)*100/len, len-remain);
    fflush(stdout);
  }
  if(0==remain) printf("Done!\n");
  return 0;
}
當換版子時, 可能會改的地方有USB的VID/PID 跟 download address
USB資訊可透過lsusb得到, 而download address可能需要找data sheet裡面RAM的起始位址

3. Execute dnw2 to send file to target board
sudo dnw2 file_name

參考資料:
[1] linux 上的dnw程式
[2] Linux下用USB传输文件到开发板

2011年1月6日 星期四

Using iptables to Implement URL Filter

URL filter是router很常見的功能; 在PC上, 或許可以透過squid來達成, 若是在flash很小的embedded system上, 還不知道有什麼軟體可以實現該功能(squid-lite?!). 因此, 只好先透過iptables來做URL filter

有幾個match可能可以拿來實現URL filter:  destination, string, webstr, weburl

destination
iptables -D FORWARD -d www.google.com.tw -j DROP
在某個版本以後的iptables會幫忙做DNS反查,若該Domain Name對應到多個IP時, iptables會幫你加上所有的rule
$iptables -L FORWARD -v -n
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source          destination        
    0     0 DROP       all  --  *      *       0.0.0.0/0       72.14.203.106      
    0     0 DROP       all  --  *      *       0.0.0.0/0       72.14.203.147      
    0     0 DROP       all  --  *      *       0.0.0.0/0       72.14.203.99       
    0     0 DROP       all  --  *      *       0.0.0.0/0       72.14.203.103      
    0     0 DROP       all  --  *      *       0.0.0.0/0       72.14.203.104      
    0     0 DROP       all  --  *      *       0.0.0.0/0       72.14.203.105
但是如果你再試一次的話
$iptables -F FORWARD
$iptables -A FORWARD -d www.google.com.tw -j DROP
$iptables -L FORWARD -v -n
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source          destination        
    0     0 DROP       all  --  *      *       0.0.0.0/0       64.233.183.104     
    0     0 DROP       all  --  *      *       0.0.0.0/0       64.233.183.105     
    0     0 DROP       all  --  *      *       0.0.0.0/0       64.233.183.106     
    0     0 DROP       all  --  *      *       0.0.0.0/0       64.233.183.147     
    0     0 DROP       all  --  *      *       0.0.0.0/0       64.233.183.99      
    0     0 DROP       all  --  *      *       0.0.0.0/0       64.233.183.103
IP變了...囧...但用來鎖一般的網站應該還OK...

string
iptables -A FORWARD -m string --string "www.google.com" –algo bm -j DROP
這樣做應該可以成功阻擋連到google的封包, 但是你也會發現, 若是有些網頁的內容含有字串"www.google.com", 那麼那個網頁應該也不能開了, 一個可能的解法, 就是利用string match裡面的--from/--to去限制字串比對的範圍

webstr
iptables -A FORWARD -m webstr --url "www.google.com" -j DROP
xt_webstr.c, 可以大概知道, 他是去抓HTTP Get/Post/Head封包裡帶的URL來作比對
webstr解決string match比對範圍的問題, 多了些HTTP相關條件, 減少誤判的可能性

weburl
URL Filter有時候可能會用regex或某個關鍵字而非完整的URL; 如輸入"yahoo", 是要擋"www.yahoo.com"與"tw.yahoo.com", 這時候就可利用weburl
iptables -A FORWARD -m weburl --contains "yahoo" -j DROP
原始碼可參考Gargoyle


參考資料:
[1] OpenWrt
[2] iptables webstr not blocking https