eth0 으로 들어오는 패킷을 NAT을 거치지 않고 eth1로 송신하는 방법이 있나요?
글쓴이: julggol / 작성시간: 화, 2007/04/17 - 2:15오후
며칠 계속 고생하다가 질문 드립니다. 아시는분들 꼭 조언좀 해주세요... 죄송합니다.
패킷이 eth0 으로 수신되면 드라이버에서 netif_rx() 함수로 skb를 넘겨주고
net_rx_action() 함수를 거쳐서 netif_receive_skb() 함수로 넘어오는걸로 알고 있습니다.
제가 구현하려고 하는 기능은 netif_receive_skb() 에서 받은 패킷을 분석하여
특정한 MAC 에서 송신되는 경우에 소스 주소와 목적 주소를 변경하여 dev_queue_xmit() 함수로
패킷을 송신하려고 하는 겁니다. 한마디로 리눅스의 복잡한 NAT을 거치지 않고 바로 전송하려고
하는거죠. NAT을 사용하니 내부 100M 환경에서 제대로된 성능이 나오지 않더라구요.
아래는 제가 수정한 소스 입니다. 커널 패닉도 발생하고 데이타는 제대로 송신되지만 체크섬이
틀려서 그냥 버리더라구요.. 이건 패킷 캡쳐 프로그램으로 확인했습니다.
PC1 client PC 가 리눅스에서 마스커레이딩으로 인터넷을 사용하는 PC 입니다.(eth0에 물림)
PC2 는 리눅스 서버와 같은 네트워크단에 존재하는 서버 PC 입니다.
확인좀 부탁드리겠습니다. 감사합니다.
// eth0 MAC 주소 : 00:12:34:56:78:90 // eth0 IP 주소 : 192.168.1.1 // eth1 MAC 주소 : 00:12:34:56:78:91 // eth1 IP 주소 : 192.168.10.101 // PC1(client) MAC 주소 : 00:01:02:03:04:05 // PC1 IP 주소 : 192.168.1.2 // PC2(server) MAC 주소 : 00:01:02:03:04:06 // PC2 IP 주소 : 192.168.10.18 int netif_receive_skb(struct sk_buff *skb) { struct packet_type *ptype, *pt_prev; int ret = NET_RX_DROP; unsigned short type = skb->protocol; struct iphdr *iph = skb->h.ipiph; struct iphdr *niph; struct tcphdr *tcph; int i; struct iphdr *iph_ck = skb->nh.iph; struct tcphdr *tcph_ck = (void *)iph_ck + iph_ck->ihl*4; u_int32_t tcplen = skb->len - iph_ck->ihl*4; u_int32_t datalen = tcplen - tcph_ck->doff*4; __u8 *ipsaddr; __u8 *ipdaddr; ipsaddr = (__u8*)&(iph->saddr); ipdaddr = (__u8*)&(iph->daddr); if (skb->stamp.tv_sec == 0) do_gettimeofday(&skb->stamp); skb_bond(skb); netdev_rx_stat[smp_processor_id()].total++; TRACE_NETWORK(TRACE_EV_NETWORK_PACKET_IN, skb->protocol); // eth0 에서 들어오는 패킷중 PC1에서 보내는 패킷이라면 PC2로 그냥 전송함 if( (strcmp(skb->dev->name, "eth0") == 0) ) { if( (skb->mac.ethernet->h_source[0] == 0x00) && (skb->mac.ethernet->h_source[1] == 0x01) && (skb->mac.ethernet->h_source[2] == 0x02) && (skb->mac.ethernet->h_source[3] == 0x03) && (skb->mac.ethernet->h_source[4] == 0x04) && (skb->mac.ethernet->h_source[5] == 0x05) && (skb->mac.ethernet->h_dest[0] == 0x00) && (skb->mac.ethernet->h_dest[1] == 0x01) && (skb->mac.ethernet->h_dest[2] == 0x02) && (skb->mac.ethernet->h_dest[3] == 0x03) && (skb->mac.ethernet->h_dest[4] == 0x04) && (skb->mac.ethernet->h_dest[5] == 0x06) ) { printk("\neth0 receive(%d Bytes)!\n", skb->len); *(ipsaddr+2) = 10; // 소스 아이피 주소 변경 192.168.10.101 *(ipsaddr+3) = 101; skb->mac.ethernet->h_source[0] = 0x00; // eth1의 MAC 주소 skb->mac.ethernet->h_source[1] = 0x12; skb->mac.ethernet->h_source[2] = 0x34; skb->mac.ethernet->h_source[3] = 0x56; skb->mac.ethernet->h_source[4] = 0x78; skb->mac.ethernet->h_source[5] = 0x91; skb->mac.ethernet->h_dest[0] = 0x00; // PC2의 MAC 주소 skb->mac.ethernet->h_dest[1] = 0x01; skb->mac.ethernet->h_dest[2] = 0x02; skb->mac.ethernet->h_dest[3] = 0x03; skb->mac.ethernet->h_dest[4] = 0x04; skb->mac.ethernet->h_dest[5] = 0x06; skb->dev = dev_get_by_name("eth1"); // IP 헤더 체크섬 ip_send_check(iph); // TCP 체크섬 niph = skb->nh.iph; tcph = (void *)niph + niph->ihl*4; datalen = skb->len - niph->ihl*4; tcph->check = 0; tcph->check = tcp_v4_check(tcph, datalen, niph->saddr, niph->daddr, csum_partial((char *)tcph, datalen, 0)); skb_push(skb, skb->dev->hard_header_len); printk("source mac : %02X:%02X:%02X:%02X:%02X:%02X\n", skb->mac.ethernet->h_source[0], skb->mac.ethernet->h_source[1], skb->mac.ethernet->h_source[2], skb->mac.ethernet->h_source[3], skb->mac.ethernet->h_source[4], skb->mac.ethernet->h_source[5]); printk("dest mac : %02X:%02X:%02X:%02X:%02X:%02X\n", skb->mac.ethernet->h_dest[0], skb->mac.ethernet->h_dest[1], skb->mac.ethernet->h_dest[2], skb->mac.ethernet->h_dest[3], skb->mac.ethernet->h_dest[4], skb->mac.ethernet->h_dest[5]); for( i = 0; i < skb->len; i++ ) { printk("%02X ", skb->data[i]); if( ((i+1) % 8) == 0 ) { printk(" "); } if( ((i+1) % 16 == 0) ) { printk("\n"); } } printk("\n"); return dev_queue_xmit(skb); } } // eth1 에서 들어오는 패킷중 PC2에서 보내는 패킷이라면 PC1로 그냥 전송함 else if( (strcmp(skb->dev->name, "eth1") == 0) ) { if( (skb->mac.ethernet->h_source[0] == 0x00) && (skb->mac.ethernet->h_source[1] == 0x01) && (skb->mac.ethernet->h_source[2] == 0x02) && (skb->mac.ethernet->h_source[3] == 0x03) && (skb->mac.ethernet->h_source[4] == 0x04) && (skb->mac.ethernet->h_source[5] == 0x06) && (skb->mac.ethernet->h_dest[0] == 0x00) && (skb->mac.ethernet->h_dest[1] == 0x12) && (skb->mac.ethernet->h_dest[2] == 0x34) && (skb->mac.ethernet->h_dest[3] == 0x56) && (skb->mac.ethernet->h_dest[4] == 0x78) && (skb->mac.ethernet->h_dest[5] == 0x91) ) { printk("\neth1 receive(%d Bytes)!\n", skb->len); *(ipsaddr+2) = 10; *(ipsaddr+3) = 18; *(ipdaddr+2) = 1; *(ipdaddr+3) = 2; skb->mac.ethernet->h_source[0] = 0x00; // eth0 의 MAC 주소 skb->mac.ethernet->h_source[1] = 0x12; skb->mac.ethernet->h_source[2] = 0x34; skb->mac.ethernet->h_source[3] = 0x56; skb->mac.ethernet->h_source[4] = 0x78; skb->mac.ethernet->h_source[5] = 0x90; skb->mac.ethernet->h_dest[0] = 0x00; // PC1의 MAC 주소 skb->mac.ethernet->h_dest[1] = 0x01; skb->mac.ethernet->h_dest[2] = 0x02; skb->mac.ethernet->h_dest[3] = 0x03; skb->mac.ethernet->h_dest[4] = 0x04; skb->mac.ethernet->h_dest[5] = 0x05; skb->dev = dev_get_by_name("eth0"); // IP 헤더 체크섬 ip_send_check(iph); // TCP 체크섬 iph = skb->nh.iph; tcph = (void *)iph + iph->ihl*4; datalen = skb->len - iph->ihl*4; tcph->check = 0; tcph->check = tcp_v4_check(tcph, datalen, iph->saddr, iph->daddr, csum_partial((char *)tcph, datalen, 0)); skb_push(skb, skb->dev->hard_header_len); printk("source mac : %02X:%02X:%02X:%02X:%02X:%02X\n", skb->mac.ethernet->h_source[0], skb->mac.ethernet->h_source[1], skb->mac.ethernet->h_source[2], skb->mac.ethernet->h_source[3], skb->mac.ethernet->h_source[4], skb->mac.ethernet->h_source[5]); printk("dest mac : %02X:%02X:%02X:%02X:%02X:%02X\n", skb->mac.ethernet->h_dest[0], skb->mac.ethernet->h_dest[1], skb->mac.ethernet->h_dest[2], skb->mac.ethernet->h_dest[3], skb->mac.ethernet->h_dest[4], skb->mac.ethernet->h_dest[5]); for( i = 0; i < skb->len; i++ ) { printk("%02X ", skb->data[i]); if( ((i+1) % 8) == 0 ) { printk(" "); } if( ((i+1) % 16 == 0) ) { printk("\n"); } } printk("\n"); return dev_queue_xmit(skb); } } #ifdef CONFIG_NET_FASTROUTE if (skb->pkt_type == PACKET_FASTROUTE) { netdev_rx_stat[smp_processor_id()].fastroute_deferred_out++; return dev_queue_xmit(skb); } #endif skb->h.raw = skb->nh.raw = skb->data; pt_prev = NULL; for (ptype = ptype_all; ptype; ptype = ptype->next) { if (!ptype->dev || ptype->dev == skb->dev) { if (pt_prev) { if (!pt_prev->data) { ret = deliver_to_old_ones(pt_prev, skb, 0); } else { atomic_inc(&skb->users); ret = pt_prev->func(skb, skb->dev, pt_prev); } } pt_prev = ptype; } } #ifdef CONFIG_NET_DIVERT if (skb->dev->divert && skb->dev->divert->divert) ret = handle_diverter(skb); #endif /* CONFIG_NET_DIVERT */ #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) if (skb->dev->br_port != NULL && br_handle_frame_hook != NULL) { return handle_bridge(skb, pt_prev); } #endif for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) { if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { if (pt_prev) { if (!pt_prev->data) { ret = deliver_to_old_ones(pt_prev, skb, 0); } else { atomic_inc(&skb->users); ret = pt_prev->func(skb, skb->dev, pt_prev); } } pt_prev = ptype; } } if (pt_prev) { if (!pt_prev->data) { ret = deliver_to_old_ones(pt_prev, skb, 1); } else { ret = pt_prev->func(skb, skb->dev, pt_prev); } } else { kfree_skb(skb); /* Jamal, now you will not able to escape explaining * me how you were going to use this. :-) */ ret = NET_RX_DROP; } return ret; }
Forums:
음... 정확하게
음... 정확하게 원하시는게 뭔지는 모르겠습니다만.....
굳이 커널까지 건드리실 필요 없이 두 네트워크 카드를 bridge로 묶으면 되지 않나요?
------------------------------------------------------------
이 멍청이~! 나한테 이길 수 있다고 생각했었냐~?
광란의 귀공자 데코스 와이즈멜 님이라구~!
------------------------------------------------------------
이 멍청이~! 나한테 이길 수 있다고 생각했었냐~?
광란의 귀공자 데코스 와이즈멜 님이라구~!
브릿지를 사용하면 될까요?
NAT 사용시 CPU 사용률이 100%까지 올라가고
전송속도도 현저하게 떨어집니다. 이런 현상을 줄일려고 커널을 변경하여
조건에 맞는 패킷 수신시에 NAT을 통과하지 않고 사용하려고 하는 겁니다.
리눅스 서버에서도 서버 프로그램을 돌리고 있고 내부 클라이언트
PC 에서도 서버프로그램이 돌고 있어서 iptables 로 포트 포워딩
해주고 있거든요.
브릿지 사용시에도 이렇게 사용이 가능한지요?
브릿지는 한번도 사용해 본적이 없어서요...
감사합니다.
저도 브릿지로
저도 브릿지로 묶어본 경험은 없어서 정확한 말씀은 못드리겠습니다만 (이것도 커널 설정후 재컴파일을 해야 하는 것으로 알고 있는지라...) 일단 개념상으로 브릿지는 두개의 물리 NIC를 묶어서 하나의 통로를 만드는 것이기 때문에 NAT와 같은 네트워크 주소 변환 작업이 필요없습니다. 받은 그대로 넘겨주면 그만입니다. 따라서 NAT같은 cpu 점유율이 증가한다던가 하는 문제는 훨씬 적을 것 입니다.
다만 문제라면 이렇게 연결해버리면 client pc랑 리눅스 서버랑 또 다른 pc가 모두 같은 네트워크 상에 있는것과 동일한 상태가 되버린다는 것입니다. 즉, 세 컴퓨터를 같은 허브에 꼳아버린 거랑 비슷하다고 할까요? 그런 상태가 되었을 때에 원하시는 작업을 하려고 할때 문제가 생기거나 하지 않을지 모르겠습니다.
만약 조건에 맞는 패킷을 걸러내시는 작업까지 원하시면 bridge firewall로 검색 해보시면 다양한 정보를 얻으실 수 있을 듯 합니다. 다만 NAT처럼 패킷을 좀 처리해야 하는 작업이기 때문에 cpu점유율은 좀 올라가게 될지도....;
저도 이론적으로만 알고 있는거라 큰 도움을 못드려 죄송하네요....
------------------------------------------------------------
이 멍청이~! 나한테 이길 수 있다고 생각했었냐~?
광란의 귀공자 데코스 와이즈멜 님이라구~!
------------------------------------------------------------
이 멍청이~! 나한테 이길 수 있다고 생각했었냐~?
광란의 귀공자 데코스 와이즈멜 님이라구~!
아~ 그렇군요...
bridge 사용시에 리눅스 서버에서도 서버 프로그램을 돌리려면 bridge firewall 을 사용하는군요.
님께서 답변 달아주시고 브릿지 관련 자료를 찾아보았는데 브릿지 사용시에 리눅스 서버에는
어떻게 접속해야 하는건지 고민하고 있었어요. 이 문제에 대한 궁금증은 해결은 됐지만
결국 제가 원하는 방향으로 브릿지 사용은 못하겠네요. 외부에서 리눅스로 접속을 해야 하므로
브릿지 사용에 NAT을 끼워 넣어야 하는 거라면 기존이랑 별 차이가 없겠네요.
그렇다면 결국 커널 소스를 분석해서 불필요한 루틴을 건너띄게 만드는 방법 뿐일까요?
kldp 에서 검색 하니까 dev_queue_xmit() 함수를 사용해서 직접 패킷을 전송하려고 시도하신
분들도 있는걸로 보니 분명 가능할 것 같은데 막상 적용시키기가 어렵네요.
또 CONFIG_NET_FASTROUTE 옵션 설정시 내부 LAN끼리의 통신에서는 NAT를 통과하지 않고 바로
dev_queue_xmit() 함수를 호출하고 리턴해 버리더라구요. 이 루틴을 보면 분명히 가능한데
skb 내의 IP 주소와 MAC 주소를 변경하면서 메모리 공유로 인한 커널 패닉이 발생하고 그렇치 않더라도
체크섬이 틀려서 패킷이 버려지는 현상만 해결한다면 제대로 동작할거라고 믿고 싶습니다. ^^;
그럼 두가지만 더 질문 드릴께요.. 죄송합니다.
1. netif_receive_skb() 함수내에서 skb 의 내용 변경시 메모리 공유 오류로 인한 커널 패닉이
발생하지 않게 하려면 변경전에 lock 을 걸고 변경후에 unlock 을 해야 할것 같습니다. 이를
위해 어떤 함수를 호출하여 lock/unlock을 시킬 수 있는지요?
2. 1번에서 변경한 skb의 체크섬 생성은 어떻게 해야 하는지요?
패킷 캡쳐 프로그램으로 받아보니 체크섬이 IP 부분과 TCP 부분에 각각 존재하던데 이 두 가지를
모두 변경을 해야 하나요?
읽어 주셔서 감사합니다.
Bridged firewall
NAT 와 Bridge 는 다르지 않나요?
제가 아는 NAT 는 Port 를 기반으로 Translation 을 하는 것으로 알고 있는데...
예전에, Bridged Firewall 을 이용해, 허니넷 을 구축한 적이 있었습니다.
Bridged Firewall 을 위해서 커널 패치를 했어야 했고, bptables(맞나? 가물) 이라는 별도의 툴을 이용해 두 인터페이스를 묶어 주면,
패킷이 브리지 서버를 통해 내부의 특정 컴퓨터로 연결되는 식이죠.
이것이 원하시는게 아닌가 싶습니다.
bptables 를 사용하기 위해서는 (eth0,eth1,eth2 가 있는경우) eth0 과 eth1 을 연결해주는, 즉 인터페이스간의 연결을 해야 했던 것으로 기억합니다.
http://nicesj.com
https://nicesj.com
https://blog.nicesj.com
아차
질문하신 내용중에 skb 조작이 있는데
skb (소켓버퍼) 를 조작하는 함수가 커널에서 제공되고 있습니다.
paste 해 주신 코드에서 skb_push 가 있는데, 이런 종류의 API 들입니다.
Kernel API Document 를 살펴보시면, 소켓버퍼 관련 API 들을 보실 수 있을꺼에요.. (찾아 드리지 못해서 죄송..^^;;)
http://nicesj.com
https://nicesj.com
https://blog.nicesj.com
답변 감사드립니다.
그렇다면 브릿지 파이어월을 사용하면 NAT 는 사용 안해도 되는건가요?
리눅스 서버와 하위 클라이언트 PC의 서버 프로그램이 외부에서 모두
접속이 가능하게 되나요?
bptables 관련 자료도 찾지를 못하겠습니다.
skb 관련 함수 찾아보아도 lock/unlock 에 관한 api는 찾지를 못하겠네요.
음... 어떤식으로 접근을 해야 할지 막막하네요.
관심 가져 주셔서 감사합니다. 일단 좀더 해본뒤에 다시 질문을 올려야 겠네요.
즐거운 하루 되세요..~~
댓글 달기