Don't receive TCP packet

Linux 2011. 2. 2. 09:07
개발 중인 모델에, 1차 검증 단계에서 PC와의 socket 통신으로 진행하는 단계가 있다.

이까짓것 하면서 붙였는데, 얼레... 진행이 안된다.  소스를 뜯어봐도 문제가 없는 것 같다.

Wireshark를 설치하고 packet의 흐름을 살펴봤다.  그 결과, board에서 UDP에 대해서는 정상적으로 반응하는데 TCP에 대해서는 제대로 반응하지 않는다는 것을 알았다.  뭐가 문제일까...

혹시나 .application의 문제일까 하고 TFTP를 실행해보니 이것은 역시 정상 동작, telnet과 NFS를 -o tcp 옵션을 사용하여보니 이것도 마찬가지 증상이다.  의심해봐야 자기만 손해라는 linux kernel중에서 그 역사가 깊고 유구한 networking쪽을 의심하기 시작, kernel을 파집고 들어가기 시작했다.

역시나, tcp_v4_rcv()에서 packet을 dropt시키는 현상을 발견했는데, 그 조건은,

if (skb->pkt_type != PACKET_HOST)
    goto discard_it;


확인 결과, skb->pkt_type은 include/linux/if_packet.h에 다음과 같이 정의되어 있다.

#define PACKET_HOST 0
#define PACKET_BROADCAST 1
#define PACKET_MULTICAST 2
#define PACKET_OTHERHOST 3
#define PACKET_OUTGOING 4
#define PACKET_LOOPBACK 5
#define PACKET_FASTROUTE 6


이 중에서, 나의 경우는 항상 PACKET_MULTICAST로 되어있는 것이 문제였다.  어떤 녀석이 이것을 셋팅할까.  역추적을 시작한 결과, 각각의 ethernet device에서 호출하는 함수인 eth_type_trans()에서 값이 결정된다는 것을 파악했다.
if (unlikely(is_multicast_ether_addr(eth->h_dest))) {
    if (!compare_ether_addr_64bits(eth->h_dest, dev->broadcast))
        skb->pkt_type = PACKET_BROADCAST;
    else
        skb->pkt_type = PACKET_MULTICAST;


is_multicast_ether_addr()은 해당 ethernet address (MAC address)가 multicast address인지 아닌지 파악한다.  근데 나는 multicast로 지정한 적이 없는데!! 내가 임시로 지정한 MAC address가 11:00:aa:bb:00:11 이었다... -_-

아.  역시.. 또 spec를 모르고 일을 하는데서 오는 문제점이었단 말인가... 쳇... -.-

http://en.wikipedia.org/wiki/MAC_address
Posted by 강군님

댓글을 달아 주세요

  1. Favicon of http://www.squidoo.com/ukdissertationwritinghelp ukdissertationwritinghelp 2011.02.04 13:36  댓글주소  수정/삭제  댓글쓰기

    It’s a great tip on Do It yourself stuff. Thanks!

  2. 장모군 2011.02.11 10:24  댓글주소  수정/삭제  댓글쓰기

    삽질을 해야 지식이 남는 것 같아요.

Linux kernel에서 usb-storage 등을 꽂으면 자동으로 인식되어 mount까지 되는 것을 볼 수 있는데, 이것을 hotplug라고 한다.  Hotplug가 일어나는 과정은 대략 다음과 같이 기술할 수 있다.

1. 장비 꽂음

2. Kernel의 interrupt routine을 거친 후 kobject에서 인식

3. uevent가 발생하고 user application인 hotplug routine 실행

따라서 이러한 hotplug signal을 받아낼 수 있다면 새로운 장치가 인식되었을 때에 원하는 작업을 수행할 수 있게 된다.  이 문서에서는 이를 가능하게 하는 방법을 기술하는 것을 목표로 한다.

Kernel 2.6.17에서 uevent에 관련된 정보는 lib/kobject_uevent.c에서 찾아볼 수 있다.

void kobject_uevent (struct kobject *kobj, enum kobject_action action)

{

  char **envp;

  char *buffer;

  char *scratch;

  const char *action_string;

  const char *devpath = NULL;

  const char *subsystem;


  ...

 

  /* call uevent_helper, usually only enabled during early boot */

  if (uevent_helper[0]) {

    char *argv[3];

  

    argv[0] = uevent_helper;

    argv[1] = (char *) subsystem;

    argv[2] = NULL;

    call_usermodehelper (argv[0], argv, envp, 0);

    /* send signal to detect daemon routine end */.

  }

  

  ...

}


모든 routine이 실행되고 마지막으로 call_usermodehelper ()를 호출하여 user 영역의 application을 실행한다.  현재 uevent_helper는 “/sbin/hotplug”이다.

따라서, signal을 받기 원하는 daemon에게 이러한 signal을 전달해줄 user application을 작성하고, 이 uevent_helper에 등록하면 실행될 것이다.  여기서는 이러한 user application이 /root/detect_test라고 가정하도록 하겠다.

...


  /* call uvent_helper, usually only enabled during early boot */

  if (uevent_helper[0]) {

    char *argv[3];


    argv[0] = uevent_helper;

    argv[1] = (char *) subsystem;

    argv[2] = NULL;

    call_usermodehelper (argv[0], argv, envp, 0);


    /* edit part start */

    if (0 == strcmp (“mount”, action_string) || 0 == strcmp (“unmount”, action_string)) {

      argv[0] = “/root/detect_test”;

      call_usermodehelper (argv[0], argv, envp, 0);

    }

    /* edit part end */


    /* send signal to detect daemon routine end */

  }

  

  ...

}


이렇게 작성한다면 장비를 꽂거나 제거할 때에 /root/detect_test라는 user application이 호출된다.  이때 /root/detect_test에 전달된 argv와 envp를 사용하면 여러가지 정보를 알 수 있다.

/root/detect_test

block


HOME=/

PATH=/sbin;/bin;/usr/sbin;/usr/bin;

ACTION=mount

DEVPATH=/block/sda/sda1

SUBSYSTEM=block

SEQNUM=706

MINOR=1

MAJOR=8

PHYSDEVPATH=/devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0

PHYSDEVBUS=scsi

PHYSDEVDRIVER=sd


2010년 3월 10일에 추가함
============================================
2.6.23.17 kernel에서 확인한 결과, user helper를 사용하지 않고도 kernel과 user app을 연결시키는 방법이 아래와 같다.

   
    int ns = -1;
    struct sockaddr_nl sa;
    char buf[1024];
    struct iovec iov = { buf, sizeof(buf) };
    struct msghdr msg = { &sa, sizeof(sa),&iov, 1, NULL, 0, 0};
    int len;

    (void)params;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;
    sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;

    ns = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    if(ns < 0)
    {
        PrintError("[%s]%d socket()",__func__,__LINE__);
        return;
    }

    if(bind(ns, (struct sockaddr *)&sa, sizeof(sa)))
    {
        PrintError("[%s]%d bind()",__func__,__LINE__);
        close(ns);
        return;
    }

    while(1) {
        len = recvmsg(ns, &msg, 0);
        ...
    }

    close(ns);

RAW socket을 이용하여 recv가 가능하구나... 이제야 알았다... -.-;;

Posted by 강군님

댓글을 달아 주세요

  1. Sunny 2012.02.13 11:39  댓글주소  수정/삭제  댓글쓰기

    올려주신 post잘 보았습니다. uevent_helper를 사용하지 않고 socket으로 kobject_uevent를 받아서 처리해보려고 하는데, 막상 recvmsg()까지 와서 kobject까지 얻어내는 방안이 묘연합니다.
    예를 들어 usb가 꽂혔을 때 uevent가 발생하고 난 후 "/sbin/hotplug"를 실행시키고자 하는데,(물론 default로 들어가있긴하지만 만약 없다고 가정하면) 이를 실행시키려면 subsystem을 넣어줘야 다음과 같이 호출이 가능합니다.

    call_usermodehelper ("/sbin/hotplug", (char *)subsystem, NULL, 0); (linux/lib/kobject_uevent.c참고)
    여기서 subsystem은 kobject로부터 받아오게 되는데요.
    recvmsg로 uevent를 받고서 msghdr로부터 kobject를 받아와야 되는걸로 보이는데, 방법이 잘 찾아지지 않네요. .ㅠ.ㅠ 도움 부탁드립니다

  2. Favicon of https://kangun.tistory.com 강군님 2012.02.15 10:09 신고  댓글주소  수정/삭제  댓글쓰기

    출장지라서 답글을 늦게 보았습니다 ^^;; 그런데 무엇을 원하시는 것인지요?

    지금 올리신 내용으로는 잘 이해가 안되서요.. 알고 계시겠지만 위쪽에 있는 code는 kernel space에서의 것이고, 아래쪽에 새로 넣은 것은 user space에서의 code입니다.

    무엇을 원하시는 것인지 잘 이해를 못해서... 뭐라고 말씀드리기가 힘드네요 ^^

  3. Sunny 2012.02.17 15:40  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 답글 감사드립니다.
    제가 두 개의 구현 방법을 참고삼아, 보던중에, 실제 구현은 아래 user space의 code를 작업하던 중이었는데, 하다가 막히는 바람에 글을 올리게 되었습니다. 실제 kernel space에서 작업을 하게 되면 관련 환경 변수들(envp)이 넘어가게 되니까 문제가 없는데, user space에서 recvmsg로 받고 난 후에 이 envp를 얻는 것에서 막혔습니다. 실제 envp가 넘어가질 않으니 제가 user space에서 user event를 받아도 제대로 처리가 되지 않더군요.

    아 참고로 제가 작업하데 된 background는 proc/sys/kernel/hotplug에 sbin/hotplug를 넣지 않는 상황에서 user event를 받아 이 sbin/hotplug를 trigger하려는 것입니다.

  4. Favicon of https://kangun.tistory.com 강군님 2012.03.14 09:20 신고  댓글주소  수정/삭제  댓글쓰기

    헐~ 답글이 굉장히 많이 늦어졌네요.. ^^;;; 아직도 출장지에 있습니다.. ㅎㅎ 집이 그립네요 -.-

    실제로 말씀하신 것처럼 envp가 넘어오지는 않습니다만, 넘어온 정보들로 sdXn인지 block device인지 등등을 알 수 있습니다. 넘겨진 정보들을 사용하여 /sys의 값들을 활용하여도 찾을 수가 있고요..

    다만 kernel space에서 바로 넘겨준 것처럼 보기 쉽게(?) 정리가 되지는 않긴 하네요.. 방법을 한번 찾아봐야겠군요.. ㅎㅎ (이글을 보실지도 의문입니다만... ^^)

RTL8201CP linux driver

Linux 2009. 4. 28. 09:28
테스트 S/W로 linux가 결정되었고, tftp/nfsroot조합을 사용하려고 했다.  RTL8201CP linux driver를 찾기 위해 백방으로 발품을 팔았으나 망할 RealTek의 홈페이지에 써 있는 글은 대략 다음과 같았다.

Phyceiver는 MAC이 따로 관리해주니까, 우리 소관은 아니고 그 칩 가져다 달은데에서 달라고 해~
  • Q9: Why don't I find RTL8201x driver on the website?
  • A9: 
    RTL8201, RTL8201BL, RTL8201CL, RTL8201CP, RTL8201N and RTL8211B(L) are all PHYceiver. That is a driverless hardware device. Software driver are relative to Network controller ( MAC ) which is integrated into chipset in such case mostly. Please contact your mother board maker or chipset manufacturer to obtain proper driver support.


아놔.. 스켈렉톤 코드라도 주던가.. (내가 몰라서 그런건지... 이게 드라이버가 필요없는 아닌거 같다.. -.-)

쩝.  어쨌뜬.. 또 백방으로 발품을 팔았으나 의외로 없다.  에씨.. 사실 다른 phyceiver 드라이버로도 잘 동작할 것 같은데 (-_-;;) 한가지 찜찜한거는 RTL8201CP가 MDIINT가 없는 거라서... 그게 좀 찜찜했다.  어쨌든간에.. driver/ney/phy에 있는  다른 phydriver하나를 골라서 아래와 같이 고쳤다.

     #include <linux/delay.h>
     #include <linux/skbuff.h>
     #include <linux/phy.h>
    
     static struct phy_driver rtl8201cp_phy_driver = {
             .phy_id         = 0x00008201,
             .name           = "RealTek 8201CP",
             .phy_id_mask    = 0x01ffffff,
             .features       = PHY_BASIC_FEATURES,
             .flags          = PHY_POLL,
             .config_aneg    = genphy_config_aneg,
             .read_status    = genphy_read_status,
             .ack_interrupt  = NULL,
             .config_intr    = NULL,
             .driver = {
                     .owner  = THIS_MODULE,
             },
     };
    
     static int __init rtl8201cp_phy_init(void)
     {
             int ret;
    
             ret = phy_driver_register(&rtl8201cp_phy_driver);
    
             return ret;
     }
    
     static void __exit rtl8201cp_phy_exit(void)
     {
             phy_driver_unregister(&rtl8201cp_phy_driver);
     }
    
     module_init(rtl8201cp_phy_init);
     module_exit(rtl8201cp_phy_exit);


ㅋㅋ 회사에 올릴 때는 맘대로 AUTHOR에 내 이름도 넣었다 -.-  어쨌든 나름 중요하다고 생각한 포인트는 PHY_POLL과 .ack_interrup와 .ctrl_intr를 NULL로 처리한 2부분이라고 생각된다.  물론 이거 말고도 각 board 별로의 configuration도 있지만 그건 올릴만한 내용은 아니라고 생각된다...  PHY_POLL은 phy.h에 지정되어 있고 주석에 나와있길 PHY가 interrupt를 support하지 않으면 쓰라고 한다.  자세한 메카니즘이 궁금하긴 하지만 동작은 대충 알 것 같다.  (main application 에서 실제로 task로 처리함) 

뭐 어찌되었던 잘 동작하니 잘 되었구려~~
Posted by 강군님

댓글을 달아 주세요

이 문서를 읽기 전에 우선 숙지해야 할 사항은 kernel 영역에서 user 영역의 file에 대해 read/write를 사용하는 것은 절대 좋지 않은 정책이라는 것이다.  첫번째 이유는 커널의 보호문제이다.  File의 data를 interrupt해가는 과정에서 유발되는 error는 buffer overflow 공격을 받을 가능성이 있다.  두번째 이유는 정책의 문제이다.  특정 fle system으로부터 특정 위치에서 특정 file을 읽어야 하는 kernel module은 user 영역에 의존성을 가지게 된다.  이것은 kernel developer들이 가장 피하는 정책이기도 하다.

이럼에도 불구하고 kernel 영역에서 user 영역의 file을 handling하여야 하는 경우는 종종 발생하는데, 이런 경우에서의 handling 하는 방법을 정리하는 것이 이 문서의 목표이다.

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/syscalls.h>

#include <linux/fcntl.h>

#include <asm/uaccess.h>


static void read_file (char *filename)

{

    int fd;

    char buf[1];

    loff_t pos = 0;

    struct file *file;

    mm_segment_t old_fs = get_fs ();


    set_fs (KERNEL_DS);

    fd = sys_open (filename, O_RDONLY, 0);

    if (fd >= 0)

    {

        file = fget (fd);

        if (file)

        {

            vfs_read (file, buf, sizeof (buf), &pos);

            printk (“buf is %c\n”, buf[0]);

        }

        sys_close (fd);

    }

    set_fs (old_fs);

}


static void write_file (char *filename, char *data)

{

    int fd;

    loff_t pos = 0;

    struct file *file;

    mm_segment_t old_fs = get_fs ();


    set_fs (KERNEL_DS);

  

    fd = sys_open (filename, ORWONLY | O_CREAT, 0644);

    if (fd >= 0)

    {

      file = fget (fd);

      if (file)

      {

          vfs_write (file, data, strlen (data), &pos);

          fput (file);

      }

    }

      set_fs (old_fs);

}


static int __init init (void)

{

    printk (“<0> file handling start\n”);

    read_file (“/etc/shadow”);

    write_file (“/etc/temp”, “foo”);

    printk (“<0> file handing end\n”);


    return 0;

}


static void __exit exit (void)

{

    printk (“<0> bye\n”);

}


MODULE_LICENSE (“GPL”);

module_init (init);

module_exit (exit);


mm_segment_t get_fs (void) / void set_fs (mm_segment_t) 

Kernel 은 sys_open에서 전달된 pointer가 user 영역에서 온 것이라고 판단한다.  따라서 이를 kernel 영역에 맞도록 전환하려고 하는데 이 예제에서는 kernel 영역에서 pointer를 넘긴 것이므로 여기서 발생하는 mistmach를 제거하기 위해 사용된다.  set_fs ()에서 사용하는 parameter는 KERNEL_DS (kernel segment), USER_DS (user segment) 뿐이며, get_fs ()는 parameter 없이 현재의 segment 정보를 가져온다.


long sys_open (const char *, int, int) / long sys_close (unsigned int)

System call인 open / close가 kernel 내부에서 불리워지는 형태이며 parameter도 동일하다.


struct file *fget (unsigned int) / void fput (struct file *)

sys_open을 통해 생성된 fd를 vfs_ 계열의 함수에서 사용할 수 있는 struct file *의 형태로 변환시켜주고, fput ()으로 쓰여진 buffer를 sync한다.


ssize_t vfs_write (struct file *, const char *,size_t, loff_t)
       ssize_t vfs_read (struct file *, char *, size_t, loff_t)

System call인 write / read가 kernel 내부에서 불리워지는 형태이며 parameter도 동일하다. 

Posted by 강군님

댓글을 달아 주세요

2.6.24.17에서의 kmalloc

Linux 2008. 6. 10. 13:56
몇개월동안 커널을 들여다보지 않았더니 그사이 또 많이 바뀐 느낌이다.  예전에 유영창씨의 "리눅스 디바이스 드라이버"에서

kmalloc (33 X PAGE_SIZE);


요게 안된다는 문구를 보고 왜 안되나, 어떻게하면 될 수 있나... 찾아보면서 그 구조를 알았다는 느낌이었는데 (SLAB allocator를 사용하지 않으면 가능) 이제와서 또 틀려졌으니 잠시라도 게을리하면 안되겠다는 생각이 든다.

kmalloc (1024 X PAGE_SIZE);


이게 되는 것이 아닌가? 혹시 SLAB allocator가 활성화안된건가? 컴파일 된 리눅스 소스 디렉토리로 가서 설정파일(.config)을 확인했더니

# CONFIG_SLAB is not set
CONFIG_SLABINFO=y
...
CONFIG_SLUB=y


음... SLAB allocator가 아니라 SLUB allocator를 사용하고 있는 것이로구나.  SLUB allocator 아래서 동작하는 kmalloc은 slub_def.h에 정의되어 있으며 __builtin_constant_p ()의 성공여부에 따라 달라지긴 여기서 바로 부르던 간에 아니면 __kmalloc ()을 호출하던간에 아래의 문맥은 또 나온다.

...
if (size > PAGE_SIZE)
    return kmalloc_large (size, flags);
...


음.  PAGE_SIZE가 현재 4096이니 만약 그것보다 큰 메모리를 할당받으려고 할 때는 kmalloc_large()가 호출이 된다.  kmalloc_large는 마찬가지로 slub_def.h에 선언이 되어있고,

...
return (void *) __get_free_pages (flags | __GFP_COMP, get_order (size));
...


이렇게 된다.  역시 결론은 __get_free_pages ()인가? __get_free_pages ()는 page_alloc.c에 정의되어있고, page를 할당받기 위해 gfp.h에 선언되어있는 alloc_pages ()를 호출한다.  alloc_pages ()는 page를 할당하기에 앞서 order를 검사한다.


...
if (unlikely (order >= MAX_ORDER))
    return NULL;
...


MAX_ORDER는 11로 define되어있고, 이 의미는 대략 다음과 같이 풀이될거다.

...
if (unlikely (size >= (PAGE_SIZE * 1024)))
    return NULL;
...


2의 10승이 1024이므로 여기에 PAGE_SIZE인 4096을 곱해주면

4194304byte = 4096KB = 4MB


고로, 4MB를 초과하는 메모리 (4MB + 1byte부터)는 kmalloc으로 할당이 불가능 하다는 이야기가 된다.  다음과 같이 테스트 해보니 명확해졌다.

buff = kmalloc (PAGE_SIZE * 1024 + 1, GFP_KERNEL);
if (buff != NULL)
{
    printk ("kmalloc 1 success!\n");
    free (buff);
}

buff = kmalloc (PAGE_SIZE * 1024, GFP_KERNEL);
if (buff != NULL)
{
    printk ("kmalloc 2 success!\n");
    free (buff);
}


다시 말하지만 현대기술은 날 기다려주지 않는건가 -.-;
Posted by 강군님

댓글을 달아 주세요

2.6.22부터 사용되어지고 있는 SLUB에 대해 정리되어있는 링크.

세월이 변하긴 하는구나 -.-;;

http://monac.egloos.com/1117183

아래는 SLUB allocator에 대한 article

http://lwn.net/Articles/229984/
Posted by 강군님
TAG linux, slab, slub

댓글을 달아 주세요