SIP/RTP initiation bug

늘 있는 일이었지만, 특별히 더 이상한 일이었다. 분명히 발생하는 장애였다. 하지만 늘 그랬듯이 불규칙적이었고, 재현이 불가능하였다.

사건의 발생은, 고객사 중 하나에서 “정상적으로 음성메시지를 남겨도, 실제로 음성 메시지를 플레이 할 때는, 아무 소리가 안난다”는 것이었다. 가볍게 생각했다. record 함수의 잘못된 사용이나, 녹음할 데이터가 없었거나….

 

일단, 로그 파일부터 확인했다.

/var/log/freeswitch/freeswitch.log

먼저 문제가 발생한 케이스를 추려내고, 그 다음, 정상적인 케이스의 로그를 비교해보았다. 모든 부분에서 같은 내용이었지만, 단 한곳 다른 부분이 있었다.

[DEBUG] switch_rtp.c:5617 Correct ip/port confirmed.

문제가 발생한 로그에서는 위의 라인이 빠져 있었던 것. 바로 freeswitch 소스 파일을 확인해보았다. 해당 로그 라인이 어떨때 나타나는 로그인지 확인했다. 해당 로그는 freeswitch 에서 RTP 패킷을 read 를 시작할 때, 나타내는 로그 중 하나였다. 즉, 문제가 되는 로그에서는 RTP read 시작 로그를 확인할 수 없었다.

이상했다. 왜냐하면 나머지 SIP 관련 로그들은 정상적으로 송/수신 했기 때문이다. 로그내용으로 확인할 수 있었다.

그리고 분석된 장애 내용은 실제로 사용자는 음성사서함 메시지를 남기려고 시도 했었지만 무슨일이에선지, 생성된 wav 파일 사이즈가 모두 44 바이트(wav header size)가 생성되는 현상이었다.

그리고.. 계속된 재현 실패.. 수십 차례에 걸친 다양한 테스트 케이스에도 재현에 성공할 수 없었다. 사실 44 byte가 녹음되는 현상은 몇개 찾을 수 있었다. 하지만 더 세밀한 분석 결과, 이번 장애와는 상관없는 다른 이유에서 발생한 것으로 결론이 난 것들이었다.

그러다가 겨우겨우 오늘 재현에 성공했다.

앞서 말했듯이, 장애는 불규칙적으로 발생하고 있었다. 하지만 그 중에서도 장애가 ‘자주’ 발생하는 고객사가 있었는데, 그 고객사의 전화번호를 이용해서 재현할 수 있었던 것이다.

우리회사는 덴마크에 있었고, 해당 고객사는 독일에 있었다. 덴마크에서 독일 고객사로 전화를 걸어 음성 메시지를 남기면 정상적으로 남길 수 있었다. 하지만 독일 전화번호를 이용하여 독일 고객사로 전화를 걸면 44 바이트의 음성 파일이 생성되는 것이었다.

좋다. 이제야 겨우 재현에 성공했다. 여기까지 도달하기까지 굉장히 많은 시간이 걸렸다(약, 2주일?)

아무튼 테스트를 진행했는데, 모든 서버에서 tcpdump 명령어를 이용하여 패킷을 캡쳐하고 해당 캡쳐 내용들을 비교했다. 분석된 내용은 독일 네트워크 어딘가에서 RTP 패킷을 차단하고 있었던 것. 왜 차단을 했을까? 열띤 토론끝에, 이런 현상은 비단 독일뿐 아니라, 다른 어느곳에서도 가능한 현상이라는 추측이 나왔다. 그래야만 그동안 발생했던 불규칙 장애에 대한 설명이 된다는 것이다.

결론적으로… 아직까지 장애는 진행중이다. 이제야 겨우 재현에 성공하고 문제의 실마리를 잡았기 때문이다. 그리고, 오늘로써 개발자(나)의 잘못이 아닌것으로 밝혀졌지만(ㅎㅎㅎㅎㅎ) 그것과는 별개로 굉장히 인상적인 장애였고, 장애 분석이었다.

왜냐하면, 오늘 네트워크 관리자, 개발자(나), 개발 선임, 컨설턴트 이렇게 4명이 모여 이런저런 토론을 하고, 구체적인 방법을 구상하고, 테스트를 했는데 굉장히 인상적이었기 때문이다.

 

결론은…혼자 끙끙 앓고 있던 2주일간의 고민이 한방에 해결되었다.  🙂

[olive] ask again and over again.

일주일 전, 한가지 풀리지 않는 문제가 있었다.

바로 Call transfer 에 관한 문제였다.

일단 parking 된 channel 을 Transfer 하고 싶은데, AMI 명령어 만으로는 해결이 안되는 것이었다.
물론 AMI 명령어로 Transfer 와 관련된 명령이 있긴했다.

blindtransfer
redirect

하지만 내가 원하는 것은 아니었다. 왜냐하면 위의 명령어들은 peer 로 transfer 를 하는 것이 아닌, context 의 extension 으로 transfer 를 하는 것이기 때문이다.

한참동안 고민을 하다가 Asterisk IRC 채널에 물어보고 답변을 들었다.

“Asterisk 는 그렇게 동작을 안해. 왜냐하면 그렇게 디자인 된 것이 아니거든. Transfer 를 하고자 한다면, 어느 순간에는 Dialplan 을 써야할 때가 올꺼야.”

그랬었다. 애초에 불가능한 것이었다.

그랬는데.. 그새 깜박했다.
왜냐하면.. 오늘 또 똑같은 질문을 했기 때문이다.

I thought it was clear a week ago when you asked the same thing and got the same answer’

하아.. 부끄러운 질문이었다.. ^^;;;;

[olive] fixed stupid bug.

바쁜 주말이었다.

Make call 관련 버그때문에 너무 신경이 쓰이는 주말이었다.
다음에 같은 실수를 하지 않을까 싶은 마음에 버그 내용을 정리한다.

버그 현상은 다음과 같았다.

1. Originate
2. Create new channel
3. Dial begin
4. Hangup – Almost immediate call hangup after dial begin

원래 의도했던 상황은 다음과 같았다.

1. Originate
2. Create new channel
3. Dial begin
4. Dial End – Wait until destination pick up the phone or 30 seconds.5. Parked call
6. Hangup

자꾸만 Dial begin 직후 콜이 끊어지는 것이었다. Asterisk cli 에서 명령어로 거의 같은 옵션으로 originate 를 했을 때는 예상했던 시나리오 대로 흘러갔다.
이상했다… 문제가 될 부분은 없어보였는데, 콜이 너무 빨리 종료되는 것이 이상했다.

무엇이 문제일까?

테스트 환경은 다음과 같았다.

테스트 전화번호는 IBK VIP 고객 센터 전화번호였다.
100% 응대를 하는 곳이었다.

트렁크 관련 설정 역시 문제가 될 부분은 없었다.
다른 VoIP Client 프로그램으로 테스트시 정상이었다.

즉, 테스트 환경으로 인한 문제는 아니었다.

콜이 끊어지는 현상은 크게 두 가지로 분석할 수 있었다.
발신자 쪽에서 끊던가, 수신자 쪽에서 끊던가.

Hangup event 발생시, Hangup 사유가 같이 따라온다.
처음에는 그 부분을 눈여겨 보지 않았다.
단지 문제 해결에만 급급해서 로그분석을 게을리 했기 때문이다.

Hangup 사유는 3번 Remote end is ringing 이었다.

AST_CONTROL_RINGING = 3,        /*!< Remote end is ringing */

이게 무슨 말이냐면.. 상대쪽 전화기에 벨이 울리고 있는데, 우리쪽에서 전화를 끊은 것이다.
이제,상대쪽 전화기의 문제는 아니라는 점이 명백해졌다.

그렇다면 문제는 단 하나, 프로그램상에서 originate 를 할때 전달되는 옵션에 문제가 있는 것이다.
왜냐하면 cli 에서는 문제가 없기 때문이다.

프로그램에서 originate 를 할 때 옵션을 넣는 부분들을 하나씩 하나씩 주석 처리하고 다시 테스트를 했다.
그리고 문제를 찾았다.

문제는 Originate 시, 설정하는 timeout 에 있었다.

https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+ManagerAction_Originate

에 originate 명령에 대한 설명이 나와있다.

본디, originate 시, 30초의 timeout 을 주려고 생각했었다.
그런데, 문제는 단위가 ms(밀리초) 단위였던 것.

그런데 거기까지 생각을 못하고 그냥 30 을 입력해 버린 것이었다.

그렇게 되니, 단지 30 ms 까지만 대기하고 timeout으로 콜을 끊어버린 것이 문제였다.

설정값 30 을 30000 으로 수정 후, 문제가 해결되었다.

고생은 했지만, 이래저래 얻은 것이 많은 버그였다. 🙂

stack smashing detected

프로그램 TEST 중 아래와 같은 오류가 발생했다.

결과는 Core dump.

jonathan@jonathan-laptop:~/workspace/TEST$ ./TEST
*** stack smashing detected ***: ./TEST terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x50)[0xb775c390]
/lib/tls/i686/cmov/libc.so.6(+0xe233a)[0xb775c33a]
./TEST[0x804a2f4]
./TEST[0x8049189]
./TEST[0x8049258]
./TEST[0x8049205]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7690bd6]
./TEST[0x8049081]
======= Memory map: ========
08048000-0804f000 r-xp 00000000 08:05 7344439    /home/jonathan/workspace/TEST/TEST
0804f000-08050000 r–p 00007000 08:05 7344439    /home/jonathan/workspace/TEST/TEST
08050000-08051000 rw-p 00008000 08:05 7344439    /home/jonathan/workspace/TEST/TEST
08051000-080fd000 rw-p 00000000 00:00 0
09a2c000-09a4d000 rw-p 00000000 00:00 0          [heap]
b763d000-b765a000 r-xp 00000000 08:05 4849747    /lib/libgcc_s.so.1
b765a000-b765b000 r–p 0001c000 08:05 4849747    /lib/libgcc_s.so.1
b765b000-b765c000 rw-p 0001d000 08:05 4849747    /lib/libgcc_s.so.1
b7678000-b767a000 rw-p 00000000 00:00 0
b767a000-b77cd000 r-xp 00000000 08:05 4984558    /lib/tls/i686/cmov/libc-2.11.1.so
b77cd000-b77ce000 —p 00153000 08:05 4984558    /lib/tls/i686/cmov/libc-2.11.1.so
b77ce000-b77d0000 r–p 00153000 08:05 4984558    /lib/tls/i686/cmov/libc-2.11.1.so
b77d0000-b77d1000 rw-p 00155000 08:05 4984558    /lib/tls/i686/cmov/libc-2.11.1.so
b77d1000-b77d4000 rw-p 00000000 00:00 0
b77ef000-b77f2000 rw-p 00000000 00:00 0
b77f2000-b77f3000 r-xp 00000000 00:00 0          [vdso]
b77f3000-b780e000 r-xp 00000000 08:05 4855235    /lib/ld-2.11.1.so
b780e000-b780f000 r–p 0001a000 08:05 4855235    /lib/ld-2.11.1.so
b780f000-b7810000 rw-p 0001b000 08:05 4855235    /lib/ld-2.11.1.so
bff3e000-bff53000 rw-p 00000000 00:00 0          [stack]
Aborted (core dumped)

원인 분석을 위해 코어 파일과 함께 gdb를 실행 시켜 보았다.

jonathan@jonathan-laptop:~/workspace/TEST$ gdb -c core TEST
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type “show copying”
and “show warranty” for details.
This GDB was configured as “i486-linux-gnu”.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>…
Reading symbols from /home/jonathan/workspace/TEST/TEST…done.
[New Thread 5966]

warning: Can’t read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libc.so.6…(no debugging symbols found)…done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2…(no debugging symbols found)…done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/libgcc_s.so.1…Reading symbols from /usr/lib/debug/lib/libgcc_s.so.1…done.
done.
Loaded symbols for /lib/libgcc_s.so.1
Core was generated by `./TEST’.
Program terminated with signal 6, Aborted.
#0  0xb77f2430 in __kernel_vsyscall ()
(gdb) where
#0  0xb77f2430 in __kernel_vsyscall ()
#1  0xb76a4651 in raise () from /lib/tls/i686/cmov/libc.so.6
#2  0xb76a7a82 in abort () from /lib/tls/i686/cmov/libc.so.6
#3  0xb76db49d in ?? () from /lib/tls/i686/cmov/libc.so.6
#4  0xb775c390 in __fortify_fail () from /lib/tls/i686/cmov/libc.so.6
#5  0xb775c33a in __stack_chk_fail () from /lib/tls/i686/cmov/libc.so.6
#6  0x0804a2f4 in TestHeadMake () at test.c:515
#7  0x08049189 in TestLog (loglvl=1, title=0x804df59 “TEST”, fmt=0x804df57 “n”) at test.c:196
#8  0x08049258 in StartMessage () at test.c:237
#9  0x08049205 in main (argc=1, argv=0xbff509a4) at test.c:218

문제는 간단했다.

함수 TestHeadMake() 내에서 사용하는 문자열 버퍼 timeStr 의 사이즈가 데이터를 담기에 작았던 것.

바로 찾아서 해결을… 했으면 좋았겠지만 한참을 찾았다…;;