[Linux] Socket Programming with C - TraceRoute ㅠㅠ Computer makes me Crazy

기본 개념을 알고 헤더파일을 뒤져봐도.. 둘을 융합시키기란 쉽지가 않더군요. OTL..
삽질했던 부분 위주로 설명 들어갑니다. 소스코드는 나중에 올리등가.. 생각해보구요;





1. Convert Domain name to IP adress (ex. www.google.com ~ 74.125.155.91)

#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);

void freeaddrinfo(struct addrinfo *res);

struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};


23 struct addrinfo hints;
24 struct addrinfo *ipAddr, *rp, *lastRp;

54 memset(&hints, 0, sizeof(struct addrinfo));
55 hints.ai_family = 0; // allow IPv4 or IPv6
56 hints.ai_socktype = 0; // allow SOCK_STREAM or SOCK_DGRAM
57 hints.ai_protocol = 0; // allow any protocol.
58 hints.ai_flags = 0; // (the first of) the addrinfo structure in the returned list is set to point to the official name of the host.
59
60 success = getaddrinfo(argv[1], NULL, &hints, &ipAddr);
61 if (success != 0) {
62 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(success));
63 exit(EXIT_FAILURE);
64 }

71 printf("addressTo: %s", inet_ntoa(((struct sockaddr_in *)ipAddr->ai_addr)->sin_addr));


참고로 argv[1] == "www.google.com" 이고
inet_ntoa(((struct sockaddr_in *)ipAddr->ai_addr)->sin_addr == "74.125.155.91" 입니다.
모험삼아 getaddrinfo(2) 함수를 썼습니다.
저도 이번에 알게 된 거지만, struct hostent 와 gethostbyname (3) 조합은 지양하는게 좋답니다.
IPv4와 IPv6 둘 다 처리 가능하긴 하지만, 둘을 따로 처리해줘야 한다는 점에서 현 시대에 안 맞는다네요.
반면 getaddrinfo(2)와 struct addrinfo 는 두 경우를 자동으로 처리해서 주소를 가져온다네요.
허접한 프로그램 어차피 IPv4 쓸 거 뭔 상관이냐.. 싶지만. 이게 프로그램 짜는 건가요? 배우는 거지 ㅋㅋ
좋은 걸로 배웁시다. -_-ㅋ
아. 같은 맥락에서 inet_addr(3)도 isolate 되었다네요. inet_ntoa(3)과 inet_aton(3)을 쓰도록 합시다.





2. 소켓 생성; 옵션은 뭘로 설정하지?

28 int sendSock, recvSock;
80     sendSock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
81 if (sendSock < 0) {
82 perror("socket() error");
83 exit(EXIT_FAILURE);
84 }
85
86 recvSock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
87 if (recvSock < 0) {
88 perror("socket() error");
89 exit(EXIT_FAILURE);
90 }

일단 send할 socket과 receive할 socket을 따로 생성했습니다.
socket(2) 함수에 사용 가능한 옵션 목록은 $ man socket 에서 보시는게 제일 좋지요.
#include <bits/socket.h> 와 #include <netinet/in.h>에서도 볼 수는 있는데.. 아마 시간낭비 -ㅂ-;





3. Setting TTL (Time to live).

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optnamevoid *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

37 u_int8_t ttl;

107 ttl = i + 1;
108
109 if (setsockopt(sendSock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
110 perror("setsockopt() error");
111 exit(EXIT_FAILURE);
112 }

첨엔 TTL 설정하려면 SOCK_RAW 설정하고 ip header도 손수 제작해야되는 줄 알았다지요;
setsockopt(2)에서 IP_HDRINCL(Header Include) 설정하고 직접 만든 ip header 딸려보내야 하나.. 했습니다.
근데 소켓은 기본적으로 TCP/UDP Layer 에서 쓰는 것이더군요. IP header는 자동으로 완성되구요.
setsockopt(2)은 자동완성될 IP header를 사용자 임의로 수정할 수 있게 해주는 도구입니다.
물론 전반적인 수정은 힘들구요; TTL 손보는 것처럼 일부만 수정하면 딱 좋지요.
저렇게 소켓에 옵션을 설정해주면, 해당 소켓을 통해 발송되는 패킷은 모두 그 옵션을 적용받고.. 하나봅니다.

여기서 참고사항. level은 SOL_SOCKET과 SOL_TCP를 자주 쓴다고 하고, man에도 그것만 나와있는데요.
그 외의 설명들 찾기가 힘듭니다. 관련 macro들이 특정 header file에 모여있는 것도 아니구요;
전체 목록을 찾는 것은 실패했지만.. IP Layer에 관한 socket option 설정하는 것은 $ man 7 ip 에 나와있습니다.
직접 man page 보시면 알겠지만.. level은 IPPROTO_IP로 설정해주셔야 하구요.
TTL을 수정하시려면 optname은 IP_TTL macro를 써주셔야 합니다.
struct ip 의 ttl type이 u_int8_t 이기 때문에 변수 ttl도 저렇게 선언해 넣어주었습니다.
걍 int로 해줘도 상관 없던 것 같지만요;

그리고 노파심에 드리는 주의말씀.
getsockopt(2)는 사용법 따로 찾아보세요 setsockopt(2) 과 좀 달라요. ㅎㅎ




4. Use of select(2)
간혹 ICMP packet 따위엔 답장을 안해주는 게으른 router들이 존재합니다.
그리고 보통 recvfrom()은 해당 socket을 통해 packet이 수신될 때까지 하염없이 기다립니다.
..그러면 결국 프로그램이 멈춰버리지요.
그래서 select 함수의 사용이 필요합니다. 자세한건 나중에 포스팅; 검색하면 나와요 ~_~


5. Send.. what kind of packet?; struct icmp


6. checksum
icmp header의 체크섬.. 안넣어주면 에러 나는 것 같습니다. -_-+
ip header의 체크섬은.. 애초에 ip header를 수동제작하지 않으니 알아서 계산해주는 것 같지만
icmp header도 되것지.. 했다 낭패봐서 주섬주섬 찾아낸 코드 긁어붙였답니다;
근데 나흘째 실행이 제대로 안되길래 봤더니 인터넷에서 긁어붙인 checksum 함수가 잘못된 것이었더군요. -ㅁ-
여기서 믿을 만한 checksum 계산 함수는 http://kldp.org/node/19431 여기 있습니다. ㅠㅠ


7. sendto(2), recvfrom(2)


* Time measuring; gettimeofday(2)


* Convert IP address to Domain name (and Service); getnameinfo(3)


* user defined structure? struct myip, struct mysockaddr.. anything must be OK!
아. 이거 어려운 건 아니고 헤더파일 쫌만 뒤져보면 알게 되는 내용인데..
socket programming에 필요한 헤더파일을 비롯한 모든 헤더파일은 /usr/include에 있지요.
직접 보니깐 참.. 좋게 말하면 세월의 흔적이 묻어나오는 헤더파일들 -_-?
나쁘게 말하면. 중구난방에 정리가 하나도 안 되어 있습니다.
가령 struct icmp 와 struct icmphdr 둘씩이나 존재하며, 둘 다 사용 가능합니다.
ip header도 마찬가지. 각종 변수 타입도 그렇습니다. (예: in_addr_t 랑 u_int32_t랑 int가 같지..요 아마?)
C언어는 type casting이 자유롭다지만.. 그때문에 얼마나 난잡해질 수 있는지 이번에 새삼 깨달았지요;
기본 탑재된 structure의 표준이 모호하고 이름도 구조도 워낙 복잡하여 표준으로 자리잡지 못한 것 같습니다.
프로그래머들이 자신만의 헤더파일 structure와 관련 함수를 만들어 쓰는 경우를 심심치 않게 볼 수 있습니다.
structure의 용량만 같다면 문제 될 것이 없으니까요. struct myicmp 만들어 쓴다고 컴파일 안되나요?
...아마 인터넷에 소스 뒤지다 보면 뼈저리게 느낄 거에요. OTL
결론은. 기존 정의된 것들 중 아무거나 하나 붙잡고 써도 괜찮고
변수명이랑 타입이랑 하나도 머리에 안 들어온다, 하시는 분들은 직접 제작하셔도 무방하단거..


* using wireshark!!
절 구원해준 바로 그 프로그램! (함수이름이 아닙니다. -_-+)
이걸로 ICMP 패킷 체크하하다 checksum이 잘못됐다는걸 알아냈다지요;
소켓 프로그래밍 하는데 필수인 것 같습니다. ㅠㅠ
사용법은 비교적 직관적이어서 슥슥 사용하시면 되려..나? (제가 슥슥 갖고놀면서 사용법 터득하는건 잘하지요;;)
Capture - Interface 에서 해결 보시면 됩니다;
실행 결과에서 Filter에 ip.dst == 72.14.213.190 (즉, 내가 발송한 ICMP 패킷의 목적지) 적어주시면 편하구요.
관리자 권한으로 실행해야만 제대로 쓸 수 있는 것 같습니다. (현재 쿠분투 10.10 쓰고 있습니다.)
그를 위해선 터미널에서 $ sudo wireshark 라고 쳐서 실행해야지요.


아하하하.. 오늘 할 일이 많은데 잘도 말리고 있군요 ㅁㄴㅇㄹ
핵심 키워드는 모두 던져두었습니다. (아는 만큼만 보이고, 아는 만큼만 던지지요. -_ -)
저 키워드만 알았더라도 한결 쉬웠을 것을; 으흙;
게다가 정말 찾기 어려웠던 내용은 이미 다 적어버렸지 싶습니다.
나머진.. 또 시간 나면.. 어헝헝헝.. 시간 나면 리눅스에 문명을 설치해보려나..

트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://ttti07.egloos.com/tb/3873247 [도움말]

덧글

댓글 입력 영역