Posted by 미스란디르
Thu, 14 Feb 2008 04:06:00 GMT
emerge glibc를 하는데, 똑같은 짓을 계속 하고 있길래 왜그런가 했더니,
/usr/include/linux 에 깔린 리눅스 헤더의 날짜가 미래라고 거부를 하는 거였다.
find /usr/include -print |xargs
로 해결했다.
freeglut을 빌드하는데 gcc가 (정확히는 링커가) libGLU.so 가 요구하는 libstdc++ 을 찾을수가 없댄다. /etc/ld.so.conf 를 제대로 지정했는데도 불구하고 여전히 뭔가 이상하다. ldconfig를 아무리 해도 안된다. 알고 봤더니 /etc/ld.so.cache 의 날짜가 미래라서 ldconfig 가 안먹는 것이었다.
바로 요런 에러다.
/i686-pc-linux-gnu/4.2.0/../../../libGLU.so, not found (try using -rpath or -rpath-link)
/usr/lib/gcc/i686-pc-linux-gnu/4.2.0/../../../libGLU.so: undefined reference to operator delete(void*)@GLIBCXX_3.4'
/usr/lib/gcc/i686-pc-linux-gnu/4.2.0/../../../libGLU.so: undefined reference tovtable for cxxabiv1::vmiclasstypeinfo@CXXABI1.3'
그러나 사실 위의 짐작은 틀린 것이었는데, ld.so.cache를 지우고 새로 생성해도 libstdc++를 찾을 수가 없다고 한다. ldd로 libGLU.so 를 찍어보면
ldd /usr/lib/libGLU.so
linux-gate.so.1 => (0xffffe000)
libGL.so.1 => //usr//lib/opengl/nvidia/lib/libGL.so.1 (0xb7ddb000)
libstdc++.so.6 => /usr/lib/gcc/i686-pc-linux-gnu/4.2.0/libstdc++.so.6 (0xb7cf5000)
이하 생략.
이렇게 제대로 나왔으니까.
ld하는 과정을 strace로 찍어봤더니,
[pid 1241] open("/usr/i486-pc-linux-gnu/lib/libGL.so.1", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
[pid 1241] open("/usr/lib/gcc/i486-pc-linux-gnu/4.1.1/libGL.so.1", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
이런게 보인다. 대체 i486은 어디 들어간걸까?
참고로 ld.so.conf에는
/usr/lib
/usr/local/lib
/usr/lib/gcc/i686-pc-linux-gnu/4.2.2/
정도가 들어있지 저런내용은 전혀 없다.
env-update ; etc-update
도 해보고, gcc, binutils, 심지어는 첫번째에서 얘기한 glibc까지 다시 emerge해봤지만 결과는 마찬가지.
결국 밝혀진 원인은 바로
/usr/etc/ld.so.conf 의 존재였다. 실수로 stage파일을 /usr 에다 풀어서 쓰레기가 있었는데, 이걸 ld가 무식하게 불러온것. 정말 신기하지 않은가?
좀 허무했지만 아무튼 밝혀졌으니 다행
Posted in 리눅스 | 1 comment | no trackbacks
Posted by 미스란디르
Sat, 15 Dec 2007 04:01:00 GMT
시작하기
요즘 동물의 숲을 하고 있다. 이거 무척 재미있는데, 게임이야기를 하려는 것은 아니고,
게임중에서 친구와 노는데 사용되는 wifi에 대한 이야기를 잠시.
※ 동물의 숲은 닌덴도 DS용 게임으로 wifi(네트워크 플레이)를 지원한다. 이하 동숲.
동숲을 하면서 혹시 86420에러를 본적이 있는가? udp에러라고 한다. 사실 몰라도 상관없지만.
아무튼, wifi를 통해 친구네 마을에 놀러가기도 하고 놀러오기도 하고 그러면서 노는데
나랑 할때만 안된다나 뭐라나. 자꾸 저 에러가 뜨면서 접속이 안되는거다. 수십번 하다보면 되긴 되는데,
그냥 잘 되는 사람들도 있거든. 그중의 한분은 네트웍을 상당히 복잡하게 쓰셔서 그 탓이 아닌가 하고
네트웍 설정을 보기도 했다. 참고로 그분의 네트웍은
ndsl ------- (노트북 무선랜 === 회사 유선랜) --------(VPN으로 어떤 서버에 연결) ------ (NAT를 통해서 외부로 연결) --------
이다. 그래서 VPN설정탓은 아닌지, NAT(리눅스에서 iptables로)탓은 아닌지 점검을 해 보았는데, 몇번 해봤지만 역시 될때는 되고 안될때는 안되더라
그래서 일단 어떤 일이 일어나는지 보기로 했다. 관심 없는 분은 10번으로 뛰어 넘으면 된다.
ndsl이 접속하는 과정 (놀러갈 때)
- 802.11b 네트웍으로 연결시도한다. DHCP로 세팅되어 있다면 아이피도 받아온다.
- http://conntest.nintendo.co.jp 로 간단한 GET 요청을 날려서 테스트페이지를 받아오는지 확인한다.
- acrossingds.available.gs.nintendowifi.net:27960에 udp패킷을 날리기 시작한다
- https://nas.nintendowifi.net 로 먼가 작업을 시작한다. 아마도 접속용 인증키를 발급 받는 것 같은데 https라서 까볼 수가 없었다 - 사실 여기서 squid를 가지고 중간자 공격을 시도했지만, 중간에서 가로채니까 ndsl이 바로 뻗어버렸다(이건 인증서가 달라져서 알 수 있는것 같다)
- gpcm.gs.nintentowifi.net:279990에서 challenge코드를 받아와서 다시 응답을 날린다. 그러고나서 다시 토큰을 받고, 자기가 등록한 친구들이 누구누구인지 서버에 얘기한다.
- http://www.nintendo.co.jp/ds/admj/oidydm/forestmailKOR.bin 로 GET요청을 보낸다. 결과데이터는 바이너리인데 뭔지는 모르겠다.
- acrossingds.ms12.gs.nintendowifi.net:28910 으로 역시 먼가 보내고 받는다
- acrossingds.natneg1.gs.nintendowifi.net:27901, acrossingds.natneg2.gs.nintendowifi.net:27901 로 "acrossing ds" 가 포함된 몇바이트짜리 udp패킷을 보낸다.
- 위에서 보낸 서버들이 뒤에 몇글자를 뺀 응답 패킷을 보낸다.
- tcp로 먼가 신호가 오고, natneg1:27901에서 상대편 아이피를 알려온다.
이제 어느 순간, 다음과 같은 패킷들이 오고간다. (111.111.111.57 은 상대편 공인 아이피, 222.222.222.57 은 내 공인 아이피)
22:13:50.458290 IP 111.111.111.57.1024 > 222.222.222.57.59375: UDP, length 20
22:13:50.460578 IP 222.222.222.57 > 111.111.111.57: ICMP 222.222.222.57 udp port 59375 unreachable, length 56
22:13:51.126067 IP 222.222.222.57.59375 > 111.111.111.57.63196: UDP, length 20
22:13:51.126120 IP 111.111.111.57 > 222.222.222.57: ICMP 111.111.111.57 udp port 63196 unreachable, length 56
원래는 서로 통신이 되야하는데, udp port unreachable이 발생한다. 대체 왜?
가만히 보니 포트번호가 이상하다.
내가 상대편에게 보낼때는 111.111.111.57:63196인데, 상대편이 보낼땐 111.111.111.57:1024 으로 온다.
복잡한 생각없이 보면 저게 쌍이 맞아야 할 것 같다.
그래서 계속 조사해보니 안될때는 계속 1024더라. 물론 저게 공유기 안쪽에서는 63196(이경우에는)으로 맞게 온다. 공유기가 바꾸는 것이다. 대체 왜?
wifi의 동작방식
여기서 또 잠깐. nintendo-wifi가 NAT-T(traversal)를 하기 위해 어떤 짓을 하고 있는지 보자.
원래 양쪽이 공유기이면 서버를 통하지 않고는 p2p 통신을 하기가 어렵다. msn에서 하고 있는 짓을 보면, 파일전송 따위의 p2p통신을 할 때
각각 서버를 열어서 상대편 서버로 접속 시도를 해보고, 되는 쪽으로 통신을 한다. 한쪽이 공인아이피를 그대로 쓰고 있다거나 DMZ를 쓴다면 그걸로 ok.
만약 양쪽 다 공유기라면 서버를 통해서 간다. 무슨 소리인지 모르겠다는 분은 그냥 넘어가자.
그런데 DS는 일단 무선랜이고, 인증기능도 단순하기 그지 없다. 따라서 상용 무선 인터넷 보다는 집에서 공유기로 접속할 확률이 3만배 많다. 즉 p2p에 있어서 최악인
공유기 vs 공유기인 설정. 그런데 똑똑한 닌텐도 개발자들은 재미있는 트릭을 썼다. 공유기 때문에 고민한 개발자라면 어떻게 안될까?.. 라고 생각해본적이 있을지도 모르는 트릭이다.
위에서 8번, udp패킷을 서버에 보내는 장면. 이때 서버는 저 패킷을 통해서 ds wifi 패킷이 처한 공유기 환경을 인식한다. 즉 외부 공인아이피가 뭔지를 확인하는 것이다. 그리고 아마도 10번에서 udp패킷으로
상대편 공인 IP / PORT 를 알려준다. 받자마자 ds는 접속시도.
공유기의 동작방식 - NAT가 무엇인가
이제 공유기에서는 어떤일이 일어나는가? 공유기들이 편리한 인터넷 환경을 위해서 무엇을 구현하고 있는가를 알아야 한다.
NAT(Network Address Translation) 은 S-NAT과 D-NAT이 있다. S-NAT은 흔히 말하는 인터넷 공유, 즉 소스 아이피를 바꿔주는 것이다.
D-NAT은 흔히 말하는 포트포워딩에 해당한다. 즉 목적지 주소를 바꿔주는 것이다. 공유기에서 S-NAT 설정을 하게 되면,
조건에 맞는 패킷이 지나갈 때 소스 아이피를 바꿔준다.
예를들면,
192.168.0.3: 공유기 안쪽 PC
192.168.0.1: 공유기 LAN쪽 IP
211.211.211.100: 공유기가 가진 공인 IP
100.100.100.100: 접속하려는 서버
192.168.0.3:54321 -> 100.100.100.100:80 LAN에서는 이랬던 패킷이,
====> 211.211.211.100:54321 -> 100.100.100.100:80 요렇게 변한다.
여기서 192.168.0.3:54321 -> 100.100.100.100:80 이나 211.211.211.100:54321 -> 100.100.100.100:80 을 tuple이라고 하고,
요것 두개를 묶어서 tuple pair라고 하자.
이게 변할 수 있는 것은, 외부로 나가는 패킷의 소스 주소를 211.211.211.100으로 변환해달라는 '규칙' 이 있기 때문인데, 들어오는 규칙에 대해서는 따로 설정을 한 적이 없다. (※ 나가는 규칙도 아이피를 직접 쓰는 일은 거의 없는데, 이건 MASQUERADE라고 해서 나가는 인터페이스의 IP주소를 그걸로 쓴다고 약속하기 때문이다)
그러면 응답패킷이 오면 어떻게 할까?
100.100.100.100:80 -> 211.211.211.100:54321 공유기 밖
====> 100.100.100.100:80 -> 192.168.0.3:54321 공유기 안쪽
가만히 보면 위쪽거랑 거꾸로다. 요걸 inverted tuple이라고 부르자.
이걸 보니, 나가는 패킷이랑 반대로 변환하면 다시 들어오는 패킷도 처리할 수 있을 것 같다.
즉 패킷이 하나 나가면 위의 4가지 정보(혹은 inverted는 생략하고 2가지만)가 공유기의 메모리 어딘가에 딱 생긴다. linux 는 요걸 conntrack (connection tracking)이라고 부르고 /proc/net/ip_conntrack 에서 볼 수 있다. 얘네들은 보통 상태정보도 가진다. tcp라면 SYN 보냄, SYN-ACK 받음, ACK 보냄(ESTABLISHED) 뭐 이런식이다. 또 패킷이 한쪽으로만 가고 한쪽으로 안갔다면 UNREPLIED. 양쪽 다 갔다면 ASSURED라는 딱지가 붙기도 한다.
그런데
가끔 재미있는 경우가 생긴다.
패킷 주소가 변환되는 과정을 다시 한번 보자.
PC가 한대가 아니라 2대이고, 우연히도
192.168.0.3:54321 -> 100.100.100.100:80
192.168.0.4:54321 -> 100.100.100.100:80
이라면,
먼저 온쪽이
211.211.211.100:54321 -> 100.100.100.100:80
을 차지하게 된다.
그럼 또 한놈은? 저걸 또 쓰면 겹치니까 그럴 수는 없고(응답 패킷이 왔을때를 생각해보자. 어찌 되겠는가. 혹은 서버입장에서도 마찬가지) 적당히 다른 번호를 준다.
211.211.211.100:1024 -> 100.100.100.100:80
왜 하필이면 1024인가 하면, 1023번 까지는 중요 포트라서 시스템에서만 쓰도록 하게 둔다. 이것보다 넓게 잡는 os도 있다.
이제 원인을 반쯤 알아냈다. 안될때 1024로 되는 이유는 바로 이거다.
.
.
.
아하
이런 경우는 정말정말정말정말 우연히 일어나기 때문에, ds에서 자꾸 접속이 안되는건 설명이 안된다. 그 수많은 pair가 겹칠리가 있는가? ds가 두대인것도 아니고. 같은 포트를 쓰더라도, 상대편 주소가 다르면 상관 없기 때문에 겹친다는건 말이 안된다.
사실 위 메커니즘을 다시 떠올리느라고 오랜만에 netfilter 소스를 돌아다니면서 기억을 되짚었다. 그러다가 생각난 "아하!"
바로 UNREPLIED conntrack의 존재이다.
222.222.222.57:59375 -> 111.111.111.57:63196
으 로 패킷이 갔다고 치자. 그리고 그게 111.111.111.57 공유기에 도착을 했다. 그런데, 111.111.111.57 공유기에서는 아직 자기쪽 DS에서 패킷이 안나온거다! 그럼 아무정보도 없는 공유기는, 지금 온 패킷을 진짜 자기에게 온 패킷으로 가정하고 시스템한테 던진다. 시스템에서는 이 패킷에 해당하는 listen 소켓이 없으니까 없다고 udp unreachable ICMP메시지를 날린다. 동시에, conntrack에는 이 패킷의 tuple이 UNREPLIED로 등록이 된다(!). 여기서 눈치 챘으리라. 정작 자기 DS에서 나온 패킷이 나갈 때, 이 포트를 쓸 수가 없는거다. UNREPLIED이지만 사용중인 것처럼 보이는거다.
아아 그렇다. 게다가 접속 시도 때문에 이쪽으로 패킷이 계속 오니까 이 tuple 정보는 없어지지도 않는다. 결국 접속시도는 실패.
근 데 양쪽에서 동시에 접속 시도를 한다고 하면 왜 이런일이 생길까? 이유는 간단하다. 네트웍 속도는 동일하지 않기 때문이다. A공유기에는 10ms 만에 서버에서 신호가 오고, B공유기에는 100ms 만에 왔다고 치면, B공유기까지 가는동안 A공유기를 거쳐서 들어갔다가 DS가 다시 응답하는데 충분한 시간일 수도 있다. 그리고 선로 속도는 평균이 있어서 쉽게 변하지 않기 때문에, 우연히 서버와의 속도만 나쁜 회선 사용자가 있다면 저 현상은 쉽게 고쳐지지 않을 것이다. 방법은 반대쪽 사용자가 DMZ같은 세팅을 하는 것 뿐.
사실은 공유기에서 저 UNREPLIED tuple을 만들지 않으면 되는건데.. 그게 실제로 unreachable일 경우에 그렇게 하면 안되는 이유라도 있는건지 모르겠다.
그래서 내가 떠올린 해결책 하나.
ds 에서 접속 시도 패킷을 쏠때, TTL을 1부터 시작해서 하나씩 늘리는거다. 그럼 공유기를 한단계씩 지나가겠지만, 그렇다고 상대편 공유기한테 바로 도달하지는 않는다. 이런식으로 길을 하나씩 뚫으면서 가면, 몇중으로 공유기로 둘러쌓인 환경이라도 문제 없지 않을까.
오늘따라 너굴 상점이 증축공사로 휴업을 하는 바람에 심심해서 써봤다. 재미없는 글 여기까지 읽어주시느라 고생하셨다.
Posted in 리눅스 | Tags 동물의숲 | 9 comments | no trackbacks
Posted by 미스란디르
Mon, 19 Feb 2007 15:25:00 GMT
어떤 리눅스 커널 모듈을 만들었다고 치고.
insmod xxx.ko test=hello world
저러면 원하는대로 test에 hello world라는 값을 넣을 수 없다.
요건 어찌하면 좋을까?
쉘에서 분리하지 않게 test=”hello world” 해도 안된다.
insmod.c를 살펴보면,
for (i = 2; i < argc; i++) {
options = realloc(options,
strlen(options) + 1 + strlen(argv[i]) + 1);
strcat(options, argv[i]);
strcat(options, " ");
}
.
.
.
ret = init_module(file, len, options);
요게 전부다.
따로 strtok를 하지도 않고 그냥 붙여서 init_module 해버린다.
그런데 저 init_module을 사실은 시스템콜이다. 본체를 찾아보니 kernel/module.c 에 @sys_init_module@이 있다.
요녀석은 다시
mod = load_module(umod, len, uargs);
로 넘긴다.
이녀석을 찾아보면,
args = strndup_user(uargs, ~0UL >> 1);
.
.
.
mod->args = args;
if (obsparmindex)
printk(KERN_WARNING "%s: Ignoring obsolete parameters\n",
mod->name);
/* Size of section 0 is 0, so this works well if no params */
err = parse_args(mod->name, mod->args,
(struct kernel_param *)
sechdrs[setupindex].sh_addr,
sechdrs[setupindex].sh_size
/ sizeof(struct kernel_param),
NULL);
뭐 이런걸 찾을 수 있다.
parse_args를 찾아 kernel/params.c로 뛰어보면,
args = next_arg(args, ¶m, &val);
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, params, num, unknown);
슬슬 목표에 가까워지는 기분이다.
우선 next_arg
static char *next_arg(char *args, char **param, char **val)
{
unsigned int i, equals = 0;
int in_quote = 0, quoted = 0;
char *next;
if (*args == '"') {
args++;
in_quote = 1;
quoted = 1;
}
“로 시작하면 인용문자열이라고 인식하는거다.
for (i = 0; args[i]; i++) {
if (args[i] == ' ' && !in_quote)
break;
if (equals == 0) {
if (args[i] == '=')
equals = i;
}
if (args[i] == '"')
in_quote = !in_quote;
}
이제 공백이 나왔는데 인용문자열이 아니면 끝내는거다. 바로 요거다. 공백을 넣으려면 “로 싸야한다. 그 다음은 = 이 나왔을때 등호의 위치를 기억해두는거다. 그리고 그 다음은 quote상태를 토글하는거고.
그 밑에는 등호의 위치를 이용해서 param과 val를 나누고, val에서 “를 빼는 따위의 짓을 한다.
이제 대충 결론이 났다. test에 Hello world를 넣으려면,
insmod xxx.ko 'test="Hello world"'
라고 해주면 되겠다.
Posted in 리눅스 | Tags 리눅스, 모듈, 커널, 파라메터 | 1 comment | no trackbacks
Posted by 미스란디르
Wed, 03 Jan 2007 21:03:00 GMT
2.4 커널에서 잘 돌아가던 어플이 하나 있다.
요녀석은 ip관리에 netlink(rtnetlink) 를 사용한다. 어떤식이냐면, 시작할때 control용 netlink소켓 하나, 그리고 broadcasting을 받기위한 listen netlink소켓 하나를 열어둔다. 그리고 ip를 추가하거나 뺄 때 control소켓을 쓴다.
요 소켓을 통해서 RTM_NEWADDR이나 RTM_DELADDR같은 이벤트를 발생시키면 커널이 알아서 잘 해준다. 그리고 ip가 더해지거나 빼지면, 커널은 listen소켓에 broadcasting으로 그 사실을 알려준다.
그리고 요 listen 소켓 핸들러에 이런걸 해놨다. 아이피가 없다가 하나가 더해지거나, 전부 빠지면 ip_exist란 플래그를 켜거나 끄도록 말이다. 그리고 다른데서 요 플래그를 참조해서 뭔가 할지 말지 정한다.
그런데 요게 2.6.19에서 돌리니 갑자기 안돈다. ip_exist를 설정해야되는데, 이게 죽어도 안되는 거다. 신기한건 자기가 직접 추가 안하고 외부에서 iproute2 같은걸로 추가하면 listen소켓이 제대로 반응한다.
니가 이기나 내가 이기나 열심히 뒤져 봤다. 그랬더니 이런 코드가 있었다.
if (nl != &netlink_cmd && h->nlmsg_pid == netlink_cmd.snl.nl_pid)
{
/* control 소켓에서 온건 무시~ */
.
.
}
물론 볼것도 없이 h->nlmsgpid 가 컨트롤소켓이 바인딩된 nlpid 값이다. 그리고 약간 이상한 점이 있는데, 저게 -4097인가 -4098가 하는 이상한 값이다. 세상에 저런 pid가 어디 있겠는가. 뭐 아무튼 그렇다.
여기서 첫번째 의심 - 대체 어떻게 하면 음수의 pid값이 나오나.. 뭔가 잘못된 값이라고 짐작해봤다. 구조체가 살짝 밀렸거나 뭐 그런경우가 아닐까 하고 한참 뚫어져라 봤지만 문제가 전혀 없다. 이건 포기.
그렇다면.. 저걸 무시하면 잘 넘어가나? 그렇다. 저부분을 신경쓰지 않도록 하면 잘 넘어간다. 음 그런데 2.4에선 저걸 저렇게 해도 문제가 없었다. 즉 저 pid값이 컨트롤 소켓 바인딩된 값이랑 분명히 다르게 온거다.
이제 비교시작. nlh에 pid를 채우는 부분을 커널에서 열심히 찾아봤다. 유레카! 2.6.19에서 갑자기 커널이 notify하는 넷링크 패킷의 pid를 발생자 pid로 채우기 시작했다. rtmsg_ifa() 함수가 인자로 pid를 받기 시작한거다. 덕분에 원래는 커널 notify라서 pid 0번으로 오던 저 메시지들이 전부 pid를 달고 오기 시작했다. 해결책은 두가지가 되겠다. 예전처럼 pid 0번으로 바꿔버리던가, control 소켓이 보낸 거라도 무시하지 말고 잘 받아도록 하면 되겠다.
PS. 이거 누가 바꾼건지 Changelog에도 안나와서 고민했는데, git를 뒤진끝에 드디어 로그를 발견했다. 근데 정작 pid 왜추가했는지에 대한 설명은 있지도 않다 -_-;; 사소한거에 집착을 너무 했나보다.
Posted in 프로그래밍, 리눅스 | Tags kernel, linux, netlink | no comments | no trackbacks