2011年7月21日 星期四

Head first Android RIL

RIL(Radio Interface Interface)是Android裡用來控制電話的一套系統, 其架構如下:

透過這套架構, 如果沒有太特別的需求, 只要針對不同的modem去修改Vendor RIL應該就可以了

網路上RIL相關的文章不少, 其中maxlengopendroid對RIL一系列的文章, 真的讓我獲益不少。

[Maxleng]
Android電話系統之概述篇 http://blog.csdn.net/maxleng/article/details/5576509
Android電話系統之RILD http://blog.csdn.net/maxleng/article/details/5576637
Android電話系統之RIL-Java http://blog.csdn.net/maxleng/article/details/5593759
Android電話系統之GSMCallTracker http://blog.csdn.net/maxleng/article/details/5593780

[Opendroid]
Android GSM 驅動模塊(rild)詳細分析(一)基本架構及初始化 http://blog.csdn.net/opendroid/article/details/4071149
Android GSM 驅動模塊(rild)詳細分析(二)request流程 http://blog.csdn.net/opendroid/article/details/4071153
Android GSM 驅動模塊(rild)詳細分析(三)response流程 http://blog.csdn.net/opendroid/article/details/4071154

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

2010年12月26日 星期日

Get Android Source Code and Setup the Build Environment

2010/12/17的一則消息:"Android 2.3 Gingerbread's source code now available", 從2.2開始, 其實預設就是用64bit OS來build, 之前為了不想重灌Linux, 在網路上找了許多如何在32bit OS上build code的方法, 花了不少時間才把froyo放上s3c6410, 但是仔細想想這樣好像也沒有比較厲害 , 這次還是乖乖照google建議的, 在64bit OS上build android. 為了怕日後換電腦, 又要全部重來一次, 把過程作個紀錄

1. Prepare
先裝些基本的工具與設定

1.1 apt-get
若需要設定proxy, 在/etc/apt/apt.conf.d/01xxxxx裡面加上proxy資訊
APT
{
    ......
}
Acquire::Http::Proxy "http://your_http_proxy:port";
#Acquire::Ftp::Proxy "ftp://your_ftp_proxy:port";
更新apt-get資料庫
sudo apt-get update
接著作個方便的alias
alias apt-install 'sudo apt-get -y install'

1.2 git
安裝git
apt-install git-core

p.s. 若無法直接連線, 需要透過proxy去避開firewall的話
先用apt-get裝"connect-proxy" (一般的狀況下, 不需要用到這個)
apt-install connect-proxy
接著寫個給git用的proxy script
my-git-proxy
#!/bin/sh
# You can get proxy from http://spys.ru/free-proxy-list/TW/
PROXY=IP:Port
connect-proxy -H http://$PROXY $@
設定git透過proxy連線
git config --global core.gitproxy ~/my-git-proxy

2. Required Packages
AOSP上面提到的一些必要安裝
sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl valgrind sun-java6-jdk zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev ia32-libs x11proto-core-dev libx11-dev lib32readline5-dev lib32z-dev
各個套件的功用, 可參考"ubuntu 9.04安裝Android注意事項"與"Build Android Platform"

其中裝JDK1.6時, 可能需要新增apt-get的source list
/etc/apt/sources.list
deb http://us.archive.ubuntu.com/ubuntu/ jaunty multiverse
deb http://us.archive.ubuntu.com/ubuntu/ jaunty-updates multiverse
接著再更新一下apt-get
sudo apt-get update
apt-install sun-java6-jdk
JDK安裝完後, 將相關的資訊export到環境變數裡
export JAVA_HOME=/usr/lib/jvm/java-6-sun
export PATH=/usr/lib/jvm/java-6-sun/bin:$PATH
export ANDROID_JAVA_HOME=$JAVA_HOME

[註] Java版本相關指令
查版本:java -version
列出已安裝版本:sudo update-java-alternatives -l
切換Java版本:sudo update-java-alternatives -s java-x-sun

3. Download Android Source Code
Download source code 只要照AOSP上面教的即可
curl http://android.git.kernel.org/repo >~/repo
chmod a+x ~/bin/repo
mkdir mydroid
cd mydroid
~/repo init -u git://android.git.kernel.org/platform/manifest.git
~/repo sync

4. Build Android
build code可以直接下make
make -j4
若是之後可能開發自己的product的話, 可以準備一個script
#!/bin/bash

function show_result()
{
        echo ""
        echo "#######################################################"
        echo " $1"
        echo "#######################################################"
        echo ""
}

CWD=$PWD
ANDROID_PATH=$CWD/my_android

export TARGET_PRODUCT=generic
export TARGET_BUILD_TYPE=release
export TARGET_BUILD_VARIANT=eng
export TARGET_SIMULATOR=false

cd ${ANDROID_PATH}

if ( make -j4 ); then
        show_result "Build Android Successfully!!"
        exit 0
else
        show_result "Build Android Failure!!"
        exit 1
fi
換成Ubuntu64順利很多, 只碰到一個error
make: *** [out/target/product/generic/obj/ SHARED_LIBRARIES/libwebcore_intermediates/LINKED/libwebcore.so] Error 1
拜了一下Google大神, 多數的說法都是開給VMware的記憶體不夠所造成, 照著網路上的作法先試著將swap加大後,
dd if=/dev/zero of=/swapfile bs=1024 count=1048576
mkswap /swapfile
swapon /swapfile
再加上相當長的build code時間, 總算成功得到android image了


參考資料:
[1] Android Open Source Project
[2] Build Android Platform
[3] Android获取源代码、编译、命令

2010年12月24日 星期五

L7-filter

第一次碰到要去filter一些application的時候, 都慢慢的去找每個application用到哪些port, 再用iptables把那些port擋掉. 但像ftp, telnet之類的, 雖然有預設的port, 但user卻能自己改成自己喜歡的, 這個時候L7 filter就派上用場了.

L7 filter的安裝可參考官網, 步驟為:
1. Patch kernel, 並打開kernel選項
2. 把l7 match加到iptables的extensions裡
3. 下載protocol pattern files

使用的指令:
iptables [-t table -A chain] -m layer7 --l7proto [protocol name] -j [action]
舉例來說, 若要擋yahoo即時通:
iptables -t mangle -A PREROUTING -m layer7 --l7proto yahoo -j DROP
net/netfilter/xt_layer7.c中可看出 (雖然不是很懂), 基本上他的作法就是抓TCP/UDP header後的payload跟protocol pattern file (*.pat) 內的regular expression利用regexec做比對.
在"L7-filter Pattern Writing HOWTO"中, 介紹如何透過regular expression去增加自己的filter. (題外話, 文中也講到regex API在kernel spaceuser space是有點差異的)

拿HTTP的一個封包來看


可以用下列的regex來辨識
http
http/(0\.9|1\.0|1\.1) [1-5][0-9][0-9]
但真的要自己寫出個堪用的pattern還是照L7-filter Pattern Writing HOWTO上的準則去做比較可行.


參考資料:
[1] Application Layer Packet Classifier for Linux
[2] 在 C 程式中,使用 Regex (Regular Expression) library

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 函數用法