[技術] 在Linux User-Space下實做L3 Protocol

Written on 4:01 下午 by Yu Lai

其實這篇應該是要接續上一篇寫的,因為用到的方法是和上一篇相同
的,但由於主題比較不太一樣,所以就分開來寫了。

在Linux中,一般要實做新的L3 protocol,大都是從kernel去下手,
效能較好,能存取的資源也比較多。但在開發embedded system下往
往有專案時間壓力以及軟體彈性的需求,去改kernel來達到這些要求
也就有點不太實際。所以我採用了在上一篇中使用的方法,也就是
Raw Socket與LPF(Linux Packet Filter)來完成一個L3 Protocol的
實做。

和Sniffer一樣的,我們使用了Raw Socket與LPF,比較不同的地方在
於LPF所使用的Filter。由於我們的protocol是定義成採用0x1219的
ether type,所以在配合tcpdump生成BPF code時用了以下的指令。

# tcpdump -dd -s 2048 ether proto 0x1219
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x00001219 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }

以下是部份的實做的程式碼:

#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <linux/sockios.h>


void *mcpV2Handler(void *arg) {

int sock, n;
int rr;
U8 buf[2048];
U8 tmp[8], tmpSlot;
U8 lastBegMac[6];
U32 lastBegTime = 0;
U32 * pGroupKey;
tMcpFrame *frame;
tMcpPacket *packet;
tMcpCardInfo *cardInfo;
pthread_t mcpV2Sender_tid;
pthread_t mcpV2TxRx_tid;
struct sock_fprog filter;

/* Using tcpdump to generate BPF code */
/*
# tcpdump -d -s 2048 ether proto 0x1219
(000) ldh [12]
(001) jeq #0x1219 jt 2 jf 3
(002) ret #2048
(003) ret #0
*/
struct sock_filter bpf_code[]= {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x00001219 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }
};

filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
filter.filter = bpf_code;

/* set detached thread */
pthread_detach(pthread_self());

if ((sock=socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL)))<0) {
perror("socket");
exit(1);
}

/* Attach the bpf filter to the socket */
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))<0){
perror("setsockopt");
close(sock);
exit(1);
}

/* ----------- Start implement ----------- */

while(1) {
n = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL);
if(n < 22) { /* 6+6+2+2+4+2+N */
printf("invalid packet\n");
continue;
}

frame = (tMcpFrame *)buf;

if(memcmp(frame->srcAddr, myMac, 6) == 0)
continue;

MCP_LOG("Dump: %02x%02x%02x%02x%02x%02x|"
"%02x%02x%02x%02x%02x%02x|"
"%04x|%04x|%08x|%04x|"
"%02x %02x %02x %02x %02x %02x %02x %02x %02x",
frame->dstAddr[0], frame->dstAddr[1], frame->dstAddr[2],
frame->dstAddr[3], frame->dstAddr[4], frame->dstAddr[5],
frame->srcAddr[0], frame->srcAddr[1], frame->srcAddr[2],
frame->srcAddr[3], frame->srcAddr[4], frame->srcAddr[5],
ntohs(frame->etherType),
ntohs(frame->mcpProtoType),
ntohl(frame->groupKey),
ntohs(frame->dataLength),
frame->data[0], frame->data[1], frame->data[2], frame->data[3],
frame->data[4], frame->data[5], frame->data[6], frame->data[7], frame->data[8]);

if(ntohs(frame->mcpProtoType) == 1 && ntohl(frame->groupKey) == myGroupKey) {
/* blah blah */
/* blah blah */
/* blah blah */
}

}

close(sock);

}

[技術] 在Linux下實作Sniffer

Written on 1:39 下午 by Yu Lai

在這裡先感謝一下黃仁竑老師,因為這篇其實這是之前唸研究所時,
上課所要交的作業。沒想到最近在工作上也用到了,呵呵。

雖然在Linux下有許多好用的Sniffer工具,如: tcpdump 或 libpcap等。
但若了解如何自己實作Sniffer,對於實作網路相關的protocol會有更深的了解。
在實作sniffer之前,在Linux下有幾個東西我們要先了解。

首先是raw socket,透過raw socket,可以讓我們從Layer 2(Ethernet)
的資料開始解讀,而不是已經被核心處理過的tcp/udp payload。

sock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));

接著是LPF(Linux Packet Filter),在Linux的kernel中,有個LPF的機制
可以預先設定過濾socket讀取到的內容。當然這個在實作sniffer不是必要
的,我們大可在讀取資料後自行filter掉非必要的data。但透過LPF,可以
讓我們專心在實作資料的呈現上。
而LPF的使用,我們可以使用一種pseudo-machine code language叫作BPF。
它是一種用來描述filter的語言,我們可以透過tcpdump這支好用的工具來
產生bpf的sample code。使用方法就是使用tcpdump -d
tcpdump -dd 來生出C使用的code。另外,tcpdump所生成的
sample code預設只會抓取96個字元,所以要再加上-s參數來指定抓取的數量。

tcpdump -d -s 2048 tcp and host 140.123.230.166
(000) ldh [12]
(001) jeq #0x86dd jt 10 jf 2
(002) jeq #0x800 jt 3 jf 10
(003) ldb [23]
(004) jeq #0x6 jt 5 jf 10
(005) ld [26]
(006) jeq #0x8c7be6a6 jt 9 jf 7
(007) ld [30]
(008) jeq #0x8c7be6a6 jt 9 jf 10
(009) ret #2048
(010) ret #0

tcpdump -dd -s 2048 tcp and host 140.123.230.166
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 8, 0, 0x000086dd },
{ 0x15, 0, 7, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 5, 0x00000006 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0x8c7be6a6 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0x8c7be6a6 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }

把Filter設到socket上。
struct sock_filter BPF_code[]= {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 8, 0, 0x000086dd },
{ 0x15, 0, 7, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 5, 0x00000006 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0x8c7be6a6 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0x8c7be6a6 },
{ 0x6, 0, 0, 0x00000060 },
{ 0x6, 0, 0, 0x00000000 }
};
struct sock_fprog Filter;
Filter.len = 11;
Filter.filter = BPF_code;

setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter))

最後是把網卡的promiscuos mode打開,網卡一般會比對dst mac和自己的
Mac Address,若不相同或不是broadcast的封包會被drop掉不處理。
若要實作sniffer,這個mode一定要打開,才能監聽到別人的封包。

strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
ioctl(sock,SIOCGIFFLAGS,ðreq);
ethreq.ifr_flags|=IFF_PROMISC;
ioctl(sock,SIOCSIFFLAGS,ðreq);

以下是完整的sample code,不過年代久遠,也沒時間compile測試,
如有錯誤不能compile,請見諒。XD

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <linux/filter.h>
#include <sys/ioctl.h>


int main(int argc, char **argv) {

int sock, sock_server, sock_client, n, i;
char buffer[2048];
unsigned char *iphead, *ethhead, *tcphead;
int addr_len;
struct sockaddr_in sa_cli;
struct ifreq ethreq;
FILE* fPtr;

struct sock_filter BPF_code[]= {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 8, 0, 0x000086dd },
{ 0x15, 0, 7, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 5, 0x00000006 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0x8c7be6a6 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0x8c7be6a6 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }
};

struct sock_fprog Filter;

Filter.len = 11;
Filter.filter = BPF_code;

if ((sock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)))<0) {
perror("socket");
exit(1);
}

/* Set the network card in promiscuos mode */
strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
if (ioctl(sock,SIOCGIFFLAGS,ðreq)==-1) {
perror("ioctl (SIOCGIFCONF) 1");
close(sock);
exit(1);
}
ethreq.ifr_flags|=IFF_PROMISC;
if (ioctl(sock,SIOCSIFFLAGS,ðreq)==-1) {
perror("ioctl (SIOCGIFCONF) 2");
close(sock);
exit(1);
}

/* Attach the filter to the socket */
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter))<0){
int temp=sizeof(Filter);
printf( "%d" , temp);
perror("setsockopt");
close(sock);
exit(1);
}

/* Open the result file. */
if ((fPtr = fopen("dump.txt", "w")) == NULL) {
perror("fopen");
close(sock);
exit(1);
}

while (1) {
n = recvfrom(sock, buffer, 2048, 0, NULL, NULL);
printf("%d bytes read\n",n);

/* Check to see if the packet contains at least
* complete Ethernet (14), IP (20) and TCP/UDP
* (8) headers.
*/
if (n<42) {
perror("recvfrom():");
printf("Incomplete packet (errno is %d)\n", errno);
close(sock);
exit(0);
}

ethhead = buffer;
printf("Source MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhead[0], ethhead[1], ethhead[2], ethhead[3], ethhead[4], ethhead[5]);
printf("Destination MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhead[6], ethhead[7], ethhead[8], ethhead[9], ethhead[10], ethhead[11]);

iphead = buffer+14; /* Skip Ethernet header */
if (*iphead==0x45) { /* Double check for IPv4 and no options present */
printf("Source host %d.%d.%d.%d\n",
iphead[12], iphead[13], iphead[14], iphead[15]);
printf("Dest host %d.%d.%d.%d\n",
iphead[16], iphead[17], iphead[18], iphead[19]);
printf("Source,Dest ports %d,%d\n",
(iphead[20]<<8)+iphead[21], (iphead[22]<<8)+iphead[23]);
printf("Layer-4 protocol %d\n",
iphead[9]);
}

tcphead = iphead+20;
if (iphead[9] == 0x06) {
printf("TCP Sequence Number: %x%x%x%x\n",
tcphead[4], tcphead[5], tcphead[6], tcphead[7]);
printf("TCP Acknowledgement Number: %x%x%x%x\n",
tcphead[8], tcphead[9], tcphead[10], tcphead[11]);
printf("TCP Header Length: %d\n",
tcphead[12]>>4);
printf("TCP Control Bits: %02x (UAPRSF)\n",
tcphead[13]);
printf("TCP Window Size: %d\n",
(tcphead[14]<<8)+tcphead[15]);
for(i=4*(tcphead[12]>>4);i<n-14-20;i++) {
printf("%c", tcphead[i]);
}
printf("\n");
}
}

}


Ref:
http://www.linuxjournal.com/article/4659