[완료]ip_send_check + tcp checksum 제발 도움부탁드립니다.
삽질중에 문제해결이 안보여 이렇게 질문드립니다.
netfilter PORTROUTING으로 source ip를 수정하여 보내고 PREROUTING으로 destination ip를 수정하
는것을 하고 있습니다.
icmp, udp(dns)는 되는걸 확인했는데 tcp가 안되더군요.
제가 했던 방법은 아래와 같습니다.
const unsigned int tcphoff = ip_hdrlen(*skb);
[1번]-------------------------------------------------------
● POSTROUTING
iph->saddr = "1.1.1.1" datalen = skb->len - iph->ihl * 4; tcph->check = tcp_v4_check(datalen, iph->saddr, iph->daddr, csum_partial((char *)tcph, datalen, 0)); iph->check = 0; ip_send_check(iph); return NF_ACCEPT
● PREROUTING
iph->daddr = "1.1.1.1" datalen = skb->len - iph->ihl * 4; tcph->check = tcp_v4_check(datalen, iph->saddr, iph->daddr, csum_partial((char *)tcph, datalen, 0)); iph->check = 0; ip_send_check(iph); return NF_ACCEPT
------------------------------------------------------------
[2번]-------------------------------------------------------
● POSTROUTING
iph->saddr = "1.1.1.1" tcph->check = 0; skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0); tcph->check = csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len - tcphoff, iph->protocol, skb->csum); skb->ip_summed = CHECKSUM_UNNECESSARY; iph->check = 0; ip_send_check(iph); return NF_ACCEPT
● PREROUTING
iph->daddr = "1.1.1.1" tcph->check = 0; skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0); tcph->check = csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len - tcphoff, iph->protocol, skb->csum); skb->ip_summed = CHECKSUM_UNNECESSARY; iph->check = 0; ip_send_check(iph); return NF_ACCEPT
------------------------------------------------------------
[3번]-------------------------------------------------------
● POSTROUTING
iph->saddr = "1.1.1.1" if (skb->ip_summed == CHECKSUM_PARTIAL) { tcph->check = ~tcp_v4_check(datalen, iph->saddr, iph->daddr, 0); skb->csum_start = skb_transport_header(skb) - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); } else { tcph->check = tcp_v4_check(datalen, inet->saddr, inet->daddr, csum_partial((char *)tcph, th->doff << 2, skb->csum)); } iph->check = 0; ip_send_check(iph); return NF_ACCEPT
● PREROUTING
iph->daddr = "1.1.1.1" if (skb->ip_summed == CHECKSUM_PARTIAL) { tcph->check = ~tcp_v4_check(datalen, iph->saddr, iph->daddr, 0); skb->csum_start = skb_transport_header(skb) - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); } else { tcph->check = tcp_v4_check(datalen, iph->saddr, iph->daddr, csum_partial((char *)tcph, th->doff << 2, skb->csum)); } iph->check = 0; ip_send_check(iph); return NF_ACCEPT
------------------------------------------------------------
그리고 csum_tcpudp_magic 함수를 사용할때 oldip, newip 방법으로도 해봤습니다면 역시 안되더군요.
tcp 헤더는 ip와 포트를 수정할때 udp, icmp처럼 ip와 포트 수정하고 체크섬만 하면 안되나요?
커널소스의 net/ipv4/netfilter/nf_nat_proto_tcp.c 파일에 tcp_manip_pkt함수를 보면
nf_proto_csum_replace4(&hdr->check, *pskb, oldip, newip, 1); nf_proto_csum_replace2(&hdr->check, *pskb, oldport, newport, 0);
이라고 있는데 이 경우 tcp 헤더에서 포트번호가 바뀌지 않을 경우 checksum은 어떻게 해줘야 할까요.
커널 netfilter에서 코드조각들을 보고 따라하는데도 tcp에서 막힙니다.
udp(dns)로 도메인 정보 가져오고 난 후 원격지 서버에서 rst 패킷을 날려 끊깁니다.
icmp도 도메인으로 핑하면 잘 나가구요.
왜 이런 현상이 있는지 원인을 잘 모르겠습니다. 제발 비슷한 경험을 하셨거나 해결하신 분들의 가이드가
절실히 필요합니다.
첨부파일은 압축된 파일로 리눅스 박스에서 패킷캡쳐한 정보와 리눅스 박스 뒤에 있는 컴퓨터에서
패킷캡쳐한 파일이 있습니다.
패킷캡쳐할때 윈도우에서 www.daum.net에 접속하여 패킷정보를 저장했습니다.
리눅스 : linux_box.txt
윈도우 : win.pcap
커널버전 : 2.6.22.19
고수님들의 조언 부탁드립니다. 감사합니다.
첨부 | 파일 크기 |
---|---|
정보.zip | 3.28 KB |
라우팅은 잘되나요?
아래와 같이 점검 해보세요.
1. 리눅스 장비의 checksum offload 기능을 off 한다.
-. ethtool -k로 조회 -K로 변경
-. 되로록이면 코드 디버깅시는 off 한다. 그래야 명확히 무엇을 잘못하는지 알수 있다.
2. ip가 변경되면 라우팅 경우가 변경 되어야 하는지 점검 한다.
-. skb의 dst가 NULL이 아닌 경우 dev name을 출력해서 디버깅 가능 하다.
-. 라우팅 캐쉬가 꼬여서 라우팅이 안되는 경우도 있다.
3. 그래도 안되면 checksum을 모두 새로 생성해본다.
아래의 코드는 제가 테스트용으로 사용했던 코드 입니다.
리눅스 커널용이니 조금 손보면 바로 동작 할 겁니다.
아. 코드는 검증 된것이며,
만일 아래의 코드로도 안되면 다른 원인을 찾아 보세요.
int32_t nat_sw_checksum(skb_t* skb, ipadr_t newip, int32_t is_chg_src)
{
/*****************************************
1)TCP checksum 구성 요소:
-. 12 byte pseudo header + TCP header + TCP payload
2) pseudo header의 구조
-. 4 byte: source address
-. 4 byte: destination address
-. 1 byte: zero
-. 1 byte: protocol (IPPROTO_TCP = 6)
-. 2 byte: TCP length (TCP header + TCP payload)
3) 리눅스에서는 skb->sum에 tcp payload checksum을 미리 계산하고
tcp/ip 스택에서 tcp/ip 헤더의 내용이 결정되면 checksum을 완성 한다.
****************************************************/
iph_t *iph = ip header를 구한다.
tph_t *tph = tcp header를 구한다.
unsigned int tcphoff = iph->ihl * 4;
uint16_t tt;
// tcp checksum 전체를 재계산 한다.
tph->check = 0;
tt = skb->csum;
// tcp payload 부분에 대한 checksum 계산.
skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
printk("old csum=0x%x, new csum=0x%x", tt, skb->csum);
// pseudo header 포함한 tcp header에 대한 checksum 계산.
if (is_chg_src) {
tph->check = csum_tcpudp_magic(newip, iph->daddr,
skb->len - tcphoff,
iph->protocol,
skb->csum);
}
else {
tph->check = csum_tcpudp_magic(iph->saddr, newip,
skb->len - tcphoff,
iph->protocol,
skb->csum);
}
skb->ip_summed = CHECKSUM_UNNECESSARY;
//skb_checksum_help(skb, 0);
return 0;
}
감사합니다. 아직 안되는군요. ㅜㅜ
먼저 정성스런 답변 정말 감사드립니다.
님 말씀처럼 점검해봤습니다.
offload 값
route
소스코드 일부
icmp ( ping )
dev name
tcp test
윈도우에서 브라우저 띄우고 웹접속을 시도하면 outbound만 발생합니다.
여기서 어디를 더 와야 할지 모르겠습니다. 답답함이 엄습해옵니다. ㅜㅜ
참고로 tcp_v4_check를 이용해서 브라우저로 접속하면 브라우저 주소창에 URL 표기 옆에 아이콘이
보이고 그 다음부터 페이지를 볼수 없습니다. 원격지에서 rst패킷을 보내서 끊기는데 이유를 도통
찾을 수 없었습니다.
다시한번 진심어린 답변 정말 감사합니다.
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
체크섬 문제 맞나요?
이전 답글에도 말했듯이 체크섬 문제 맞나요?
세션이 많이 열리는 웹 말고 telnet 서버를 대상으로 테스트 하는 것이 더 좋을듯 합니다.
딱 하나의 세션만 열리는, 그리고 딸랑 몇개의 패킷만 교환하는 환경에서 모든 패킷을 잡아서(리눅스 박스 말고, 대상 서버에서)
패킷을 분석해보는게 좋을듯 합니다.
또한 소스에서 체크섬 값을 출력하고
해당 패킷을 수신한 쪽에서 잡은 패킷의 체크섬 값이 같은지도 보시고....
ip 체크섬 문제인지 tcp 체크섬 문제인지... 등등....
공개적인 서버를 이용하면 패킷을 잡아서 분석이 어렵더라구요...
제 직감(!)에는 체크섬 문제가 아닐 가능성도 충분히 있어 보입니다.
전체 로직을 모르니 단정하기는 어렵지만, 정말 IP/port만 변경하는 것인지도 확실하지 않아서요.
데이터를 변경한다면 정말 복잡해질 가능성도 있구요.
정성스런 답변 감사드립니다.
tcp 수정하는것은 없었구요. icmp, udp, tcp 모두 ip만 수정해서 테스트 중에 있습니다.
일단 일차적으로 icmp, udp는 정상적으로 되고 있는것을 확인했구요 port 수정은 테스트 하지
않았습니다. ip 수정시 tcp 패킷전송의 문제로 인하여 port 수정 테스트는 그 다음에 하려고 합니다.
님의 말씀데로 telnet으로 테스트 해봤습니다.
결과는 대상서버, 그리고 중간에 있는 리눅스 박스 모두에서 23번 패킷을 보니 체크섬이나 포트번호, ip등은 동일하게 나왔습니다. 그런데 서버에서 전송은 하지 않는것으로 보였습니다. ㅜㅜ
아래는 제가 아웃바운드를 POST_ROUTING으로 hooking할때 테스트로 작성해본 코드입니다. 이 코드로
한 결과 제가 말씀드린데로 나온거구요.
흠... 머리가 아프네요. ㅡㅡ tcpdump할때는 아래와 같이 했습니다.
다시한번 친절한 님의 답변에 정말 감사드립니다.
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
tcpdump시 ip checksum이 안보이네요?!
님께서 사용한 tcpdump 옵션을 사용하면 ip checksum이 안보이네요.
ip checksum이 정상인지 확인도 해야 할듯 합니다.
제가 비슷한 작업을 할때는
대상 서버에서 wireshark로 패킷을 잡아서 IP/TCP 체크섬을 둘 모두를 소스에서 출력한 값과 같은지 비교 했습니다.
wireshark에서 체크섬이 문제가 있으면, 정확한 값을 화면에 보여 줬던듯 합니다.
(xxxxx값이 나와야 하는데, yyyyy 값이다. hw offload를 사용하는냐 라는 식으로....)
답변중에 "그런데 서버에서 전송은 하지 않는것으로 보였습니다" 를 문장대로 해석하면,
서버에서 패킷을 안 잡아 봤다는 건가요?
서버에서 tcpdump로 잡아서 파일로 저장하고 wireshark로 보면서 패킷의 TCP/IP의 모든 필드가 정상인지 확인해 보길 추천 합니다. 분명 네트웍 패킷에(체크섬 포함) 문제가 있을 것으로 추측 됩니다.
그리고 서버에서 200.200.x.x 의 패킷이 테스트를 진행하고 있는 리눅스 박스로 정확히 라우팅 하나요?
예측하건데, 10.0.0.x의 네트웍의 라우팅 엔트리는 있는데 200.200.x.x를 default gateway로 라우팅 되어서
패킷이 다른데로 사라져 버리는 증상은 아닌가요?
체크섬 등등이 모두 정상인데, 서버가 패킷에 응답 안한다는게 이상하네요.
올려주신 코드로는 솔직히 저도 어디가 문제인지 추측하기 어렵네요.
더 많은 도움을 드리지 못 할 것 같아 이만 줄입니다.
아닙니다. 님께서 이미 많은 도움을 주셨습니다.
누군가의 질문에 선뜻 대답하기 힘든건데 님께서는 제 질문에 대답해주신 것만으로도 이미 많은 도움을 주신겁니다. ^^
tcpdump 옵션중에 ip checksum 을 확인하는 옵션이 있는지요.
tcpdump ip proto \\tcp port 23
이렇게 해봤는데 문법에러가 나네요. ^^;
telnet 서버가 있는 리눅스에서 패킷을 잡아봤습니다. 파일에 저장하진 않고 출력으로 확인만 해봤는데
tcp checksum 부분에 아래와 비슷한 출력을 하였습니다.
제가 테스트용으로 만든 모듈을 빼고 iptables을 이용한 마스커레이딩으로 하여 telnet 서버가 있는
리눅스에서 패킷을 캡쳐하니 checksum 부분에 아래와 같이 비슷하게 출력됬습니다.
두개 차이가 correct와 incorrect인것 같은데 왜 그러는지 잘 모르겠습니다. 추측으로 tcp checksum 계산이 잘못된듯 한데.. 쩝..
여러보로 답변 감사드립니다.
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
마지막 답글 ~~~ ^^
제가 사용했던 샘플 코드와 비교 해보니 아래의 부분에서 순서가 다르군요.
cphoff = ip_hdrlen(*skb);
tcph = (struct tcphdr *)(skb_network_header(*skb) + (iph->ihl * 4));
tcph->check = 0; //-------> 여기가
sb->csum = skb_checksum( sb, tcphoff, sb->len - tcphoff, 0 );
//tcph->check = 0; -------> 여기가
tcph->check = csum_tcpudp_magic( tip, iph->daddr,
sb->len - tcphoff,
iph->protocol,
sb->csum );
skb_checksum() 할때 tcp header도 포함 되기 때문에 체크섬을 0으로 설정하고 skb_checksum()이 호출 되어야 합니다.
제가 사용한 코드에는 위와 같이 앞에서 0으로 설정하게 되어 있습니다.
혹시 ~~~~
한번 해보세요.
^.^
^^ 감사합니다.
수정해서 해봤는데요. incorrect문제는 안보였습니다.
테스트 하면서 telnet 서버가 있는 linux, ip 바꾸는 모듈이 있는 linux 박스 두개에서 패킷을 떠본
결과 첨부파일처럼 결과가 나왔습니다. ip 바꾸는 모듈이 있는 linux, 즉 중간에 있는 linux 장비에서 rst 패킷을 주는것 같습니다.
linux에서 저장한 패킷파일들을 wireshark에서 확인하여 봤는데 이상한게 있는건
중간에 있는 linux 장비의 패킷파일을 확인해봤습니다.
첨부파일의 압축을 푸시면 linux_box.pcap ( 테스트 모듈 사용 ), linux_box_msq.pcap ( linux msq 사용 )파일을 보시면 tcp 에서 Checksum 다음에 있는 SEQ/ACK analysis 이란 항목
이 있습니다.
이 항목이 서로 틀립니다. 그 이후 telnet 접속 창에서 제가 키 하나를 입력하면 바로 종료되고 키를 입력하지 않으면 화면에 login이 출력되지 않으면서 정지되고 있었습니다.
무엇이 문제인지 참.. 휴..
참으로 정성이 담긴 답변 감사드립니다. ^^
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
체크섬 문제는 해결 된거죠 !!!
맞나요?
7번째 패킷은 서버에서 클라이언트로 보내는 패킷이므로 목적지가 10.0.0.2로 와야 하는데, 다른 아이피로 오네요.
7번 전의 모든 패킷은 서버에서 올때는 10.0.0.2의 목적지로 오는데 7번만 다른것이 이상합니다.
단순 NAT라고 생각 한다면,
절대로 서버에서 다른 아이피로 보낼 수 없는데...
그런데, 서버 패킷을 보면 10.0.0.2가 전혀 안보이고....
흠...
이 증상은 님이 작성하시는 어플의 비지니스적인 내용이라 제가 다 이해를 못하겠습니다.
사실 이런 에러는 기술적인 어려움이라기 보다는 tcp handshake 및 응용 어플의 기능에 매우 민감하므로
패킷을 일일이 다 까보면서 디버깅 하면 충분히 답이 보일듯 합니다.
패킷 덤프 + 소스 코드 디버그 메세지를 잘 활용해 보심이.... ^.^
아무튼 체크섬 탈출을 축하 드립니다.
^^ 감사합니다.
본의 아니게 귀찮게 해드리네요. ^^
제 생각이 맞는지 한번 봐주십시요.
client(window:10.0.0.2) <----> (10.0.0.1)linux box(200.200.xxx.xxx) <----> server
이렇게 되있다고 가정하면
linux box에서 각각 PRE_ROUTING, POST_ROUTING로 hooking을 겁니다.
POST_ROUTING hooking이 되면 소스 주소인 10.0.0.2를 200.200.xxx.xxx로 바꿉니다.
즉 linux box ip로 바꿔서 서버에 전송합니다.
PRE_ROUTING hooking이 도면 목적지 주소가 200.200.xxx.xxx이면 10.0.0.2로 바꿔서 accept합니다.
이 개념이 맞는건가요.
단순 NAT인데 단지 tcp에서 checksum만 바꾸면 될줄 알았는데 안되서 제가 생각하는 개념이 틀린지
의구심이 들 정도입니다. ㅜㅜ tcp data 내용을 바꾼것도 아닌데 말이죠.
아.. 그리고 패킷을 좀 들여다보니 PRE_ROUTING 할 때 tcp checksum 을 통과한 후에 SEQ/ACK analysis 값들이 보이지 않습니다. 제 생각으로는 PRE_ROUTING 할 때 달리 해줘야하는게 있지 않을까
라는 생각이 듭니다. ^^;
아무튼 님과 인연이 닫는다면 술한잔 사드리고 싶군요. ^^
정성스런 답변 감사드립니다.
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
Source NAT와 동일한
Source NAT와 동일한 개념이므로 님이 말씀하신 내용이 맞습니다.
그러므로 각 후킹의 위치도 적당해 보입니다.
그러나 여러가지 고려해야 할 사항이 많습니다.
NAT가 생각보다 복잡 합니다.
1. sys 패킷이 c->s로 갈때는 패킷의 소스를 post에서 변경하여 보내도 별 문제 없습니다.
단, client가 여러대라면 단순히 IP만 변경해서는 포트 충돌이 발생 합니다.
다시 말해서,c1의 소스 포트가 5000번인 패킷이 지나가고, c2도 동일한 패킷을
발생 한다면 과연 어떻게 처리 할 것인가를 결정 해야 합니다.
물론, 1:1 맵핑이면 문제가 없습니다.
즉 10.0.0.2 -> 200.200.0.2, 10.0.0.3 -> 200.200.03 이런 식이면 문제가 없지만
그 이외의 경우는 포트 충돌 문제를 해결 해야 합니다.
이를 해결 한것이 port translation(포트 변경?) 입니다.
2. sys/ack 패킷이 s->c로 올때는 1번 문제와 관련한 사항이 해결 되어야 합니다.
어느 패킷이 어느 client로 가야 하는지 정확히 파악해서 원위치 해주어야 합니다.
또한, 200.200.x.x를 목적지로 오는 패킷은 pre_routing 처리시 라우팅 캐시를 주의 해야 합니다.
3. tcp checksum 이후 SEQ/ACK가 보이지 않는다는 말은 이해가 안됩니다.
tcp payload 데이터를 추가/삭제 하지 않는 경우에는 SEQ/ACK가 변동이 없습니다.
단순히 ip/port만을 변경하는 것은 전혀 관련 없습니다
단, 1번 문제와 관련하여 충돌에 의해 SEQ/ACK가 이상해 지는 경우는 가능 합니다.
이런 경우 서버 또는 클라이언트 tcp stack에서 리셋을 보냅니다.
음.. 더 자세히 설명하고 싶지만... 이 내용은 tcp stack에 대한 내용이라 어렵네요...
4. 제안 사항
지금 님의 요구사항이라면, netfilter로도 충분히 가능한 내용인데 왜 직접 코딩 하세요.
그냥 iptables로 룰을 설정하는게 편한 보이는데요...
답변감사합니다. ^^
일단 클라이언트 한대에서 테스트를 하고 있습니다. 지금 문제가 포트 충돌문제는 아닌것같습니다.
SEQ/ACK 를 말씀드린거는 모듈을 올리고 난 후 telnet 서버가 있는 linux에서
패킷캡쳐를 하고 이 결과 파일을 wireshark으로 보았을때 'SEQ/ACK analysis'라는 문구가
없었습니다.
위에 제가 첨부로 올린 압축파일을 보시면 'SEQ/ACK analysis'라는 문구가 없는게 보이실겁니다.
왜 없는지는 아직 찾고 있는 중입니다. ^^;
iptables를 사용하지 않는 이유는 직접 만들어서 사용하고자 함이며 알아두면 좋을 내용인듯싶고
나중에 써먹을 곳도 많을것 같아서 입니다. ^^ iptables이용하면 좀 늦는 경우도 있어서 그것도
피하기 위함이라고 말씀드릴수 있겠습니다.
routing cache가 잘못됬다면 icmp(ping), udp(dns)도 모두 안되야 할듯 한데 지금 상황으로 봐선
routing cache가 아닌듯 싶습니다. ^^
님의 정성스런 답변 항상 감사드립니다. ^^
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
해결했습니다. ^^
패킷의 ip를 수정할때 빼먹은 패킷이 일부 발생하여 올바른 tcp 패킷 통신이 이루어 지지않은 문제였습니다.
제가 위에 적은 소스와 좀 차이가 있는걸로 하다보니 이런 결과가 나왔네요.
저의 바보스런 코드로 인하여 irongate님을 귀찮게 해드렸군요. ^^;
irongate님의 정성스런 답변에 다시한번 감사의 말씀드립니다.
다음에 질문할때는 꼼꼼히 따져보고 질문드리겠습니다. ^^;
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
지식의 여인은 옷을 쉽게 벗지 않는다.
잡초인생. 잡초처럼 끈길기게....
댓글 달기