Author Archives: mix1009

FreeBSD blackhole 기능

FreeBSD에서 DoS 공격을 근본적으로 막을 방법은 뾰족히 없지만 좀 느리게 만드는 방법입니다. PF(Packet Filter)로 잘 설정하고 싶지만 연결을 많이 유지하는 서버에서 PF가 성능저하가 있는거 같아서 일단 다른방법으로 대처했습니다.

man blackhole 하면 자세한 설명 볼수 있으며,

net.inet.tcp.blackhole와 net.inet.udp.blackhole을 세팅하면 됩니다.

# sysctl -w net.inet.tcp.blackhole=1
# sysctl -w net.inet.udp.blackhole=1

포트 스캔할때, 닫혀있는 포트에 대해서는 리셋(RST) 패킷이 날라가는데 이걸 아예 안보내게 합니다. tcp.blackhole의 경우 1이면 연결(SYN) 패킷에 대해서 RST를 안보내며, 2로 세팅하면 모든 패킷에 대해서 RST를 안보냅니다. UDP는 따로 연결 패킷이 없기 때문에 1로 세팅하면 닫혀있는 포트로 오는 패킷에 대해서 RST 응답을 안보내고 무시합니다.

포트스캔을 하면 없는 포트로 많은 패킷이 오기 때문에 RST 패킷이 많이 나가게 됩니다. 이럴 경우 시스템에 부하를 일으킬 소지가 있으므로, FreeBSD에서는 RST를 보낼때 1초에 200개의 RST 패킷만 보내도록 제한이 되어있습니다. 이를 넘을 경우 아래와 같은 경고 메시지가 dmesg 등에 나타납니다.

Limiting closed port RST response from 286 to 200 packets/sec
Limiting closed port RST response from 463 to 200 packets/sec

여기 IP 정보가 나오지 않기 때문에 정보를 보기 위해서는

# sysctl -w net.inet.tcp.log_in_vain=1
# sysctl -w net.inet.udp.log_in_vain=1

하지만 이렇게 하면 로깅되는 정보가 너무 상당히 때문에 시스템에 더 큰 부하를 일으킬 가능성이있습니다. 공격을 당하는 서버에서는 잠깐동안 기능을 켰다가 끄도록 아래처럼 실행하세요.

# sysctl -w net.inet.tcp.log_in_vain=1; sleep 5; sysctl -w net.inet.tcp.log_in_vain=0

위에 처럼 서버(SERVER_IP)에서 돌리고, 원격 서버(SCANNER_IP)에서 nmap을 돌려봤습니다. 스캔 당하는 SERVER_IP에서는 blackhole 기능은 끈상태입니다.

Connection attempt to TCP SERVER_IP:25 from SCANNER_IP:38947 flags:0x02
Connection attempt to TCP SERVER_IP:1723 from SCANNER_IP:38947 flags:0x02
Connection attempt to TCP SERVER_IP:3389 from SCANNER_IP:38947 flags:0x02
… (생략)
Limiting closed port RST response from 233 to 200 packets/sec
… (생략)
Limiting closed port RST response from 262 to 200 packets/sec
… (생략)
Limiting closed port RST response from 279 to 200 packets/sec

blackhole=1로 설정한 상태에서는 Limiting closed port… 메시지가 나타나지 않고, nmap의 실행속도가 많이 느려집니다. 글쓰면서 실행해봤는데 몇분이 지나도 끝나지가 않는군요. 답답해서 nmap 실행창에서 엔터를 쳤더니 다음과 같은 메시지가 나오네요. ㅎㅎ 콘솔 프로그램인데 이런거까지 신경써주는군요. ^^

Stats: 0:06:49 elapsed; 1 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 72.17% done; ETC: 01:59 (0:02:37 remaining)

한가지 문제가 blackhole 기능을 켜두면 원격에서 서버 모니터링할때 서버가 죽어도 응답이 바로 오지않는 문제가 있는데, PF를 통해서 포트에 따라서 선택적으로 blackhole 기능처럼 RST 보내지 않도록 설정하는것이 더 좋을거 같네요.

ocaml findlib : ocamlfind

OCaml 언어에 예전부터 관심이 많았었는데, 오랜만에 다시 보니 새롭군요. 그전에 배우면서 CVS에 올렸던 날짜를 보니 2002년이네요. 5년이 지났지만 실전에 아직 OCaml를 쓸 기회가 없다보니 조금 안쓰다보면 까먹어서 자꾸 다시 배우게 되네요.

그 전에 배울때는 OCaml에 포함된 기본 컴파일러만 make와 병행해서 사용했었는데, ocaml-event 라이브러리를 깔다보니 ocamlfind라는 실행파일을 통해서 인스톨하게 되어 있더군요. ocaml-event는 libevent의 ocaml에서 사용할수 있도록 하는 라이브러리입니다. FreeBSD ports중에 /usr/ports/devel/ocaml-findlib를 깔고, ocaml-event는 수동으로 소스를 컴파일하여 인스톨했습니다. /usr/ports/devel/ocaml-event 에 있지만 여기서 인스톨하면 ocamlfind에 등록이 이상하게 안되더군요…

ocaml-findlib를 깔면 중심 실행파일이 ocamlfind인데, 이 파일이 상당히 다양한 기능들은 가지고 있습니다. Ocaml 라이브러리/프로그램 인스톨, 언인스톨, 컴파일, 소스에서 문서 추출, 라이브러리 종속성 조회, 라이브러리 브라우징 등의 기능을 제공하며, 브라우징의 경우 Tk를 이용하여 X11 환경에서만 실행됩니다. 자세한 것은 man 페이지나 findlib user guide 문서를 참고하세요.

저는 컴파일 할때 ocamlc나 ocamlopt를 바로 호출하지 않고 ocamlfind를 통해서 호출한 방법만 설명드리도록 하겠습니다. ocamlc는 자바처럼 byte code 형태로 컴파일되고, ocamlopt는 타겟 CPU에서만 실행되는 native 형태로 컴파일이 됩니다. 한 ocaml 프로그램 내에서 byte code와 native code로 컴파일된것을 링크할수 없으므로 Makefile를 이용한다면 이를 따로따로 관리해야합니다. ocamlfind를 이용하면 cmo(byte code object)나 cmx(native object)를 지정하지 않아도 되서 Makefile의 크기가 많이 줄어드는것 같네요. 라이브러리 지정도 cma, cmxa를 따로 지정하지 않으며, 또한 라이브러리의 종속성을 자동으로 판단하여 라이브러리 링크 순서도 자동으로 판단해주는거 같습니다.

예전에 짰던 echo_server fork버전과 thread버전의 Makefile.old와 변경된 Makefile.new을 비교해보시면 ocamlfind가 유용하다는걸 느끼실수 있을겁니다. 그리고 Makefile.old는 native로 컴파일할려면 많은 부분 고쳐야하지만, Makefile.new에서는 한줄만 바꾸면 됩니다.

[CODE type=make]
# Makefile.old
# Copyright (c) 2002 Chun-Koo Park

OCAMLC=ocamlc
OCAMLCFLAGS=-thread
.SUFFIXES: .ml .mli .cmo .cmi

LIBS=unix.cma

EXEC=echo_server_fork echo_server_thread
all: $(EXEC)

clean:
   rm -f *.cmo *.cmi *.cmx *.o $(EXEC) .depend

.mli.cmi:
   $(OCAMLC) $(OCAMLCFLAGS) -c $<

.ml.cmo:
   $(OCAMLC) $(OCAMLCFLAGS) -c $<

echo_server_fork: $@.cmo $(OBJS)
   $(OCAMLC) -o $@ $(OCAMLCFLAGS) $(LIBS) $@.cmo

echo_server_thread: $@.cmo $(OBJS)
   $(OCAMLC) -thread -o $@ $(OCAMLCFLAGS) unix.cma threads.cma $@.cmo

# vi:set noet:
[/HTML][/CODE]

[CODE type=make]
# Makefile.new
# Copyright (c) 2007 Chun-Koo Park

OCAMLC=ocamlfind ocamlc
#OCAMLC=ocamlfind ocamlopt

EXEC=echo_server_fork echo_server_thread
all: $(EXEC)

echo_server_fork: echo_server_fork.ml
   $(OCAMLC) -o $@ -package unix -linkpkg $<

echo_server_thread: echo_server_thread.ml
   $(OCAMLC) -o $@ -thread -package “threads unix” -linkpkg $<

clean:
   rm -f *.cmo *.cmi *.cmx *.o $(EXEC) .depend

# vi:set noet:
[/HTML][/CODE]

위 Makefile 들은 FreeBSD에서 BSD Parallel Make에서 테스트된 파일들입니다. 마지막 줄은 vim에서 탭을 자동으로 공백으로 확장하지 않도록 하는 명령입니다. (noexpandtab)

findlib에 포함된 make_wizard를 사용하면 위저드 형태로 7단계로 Makefile를 만들어줍니다. 생성되는 Makefile이 상당히 길고, 생성된 Makefile은 위저드로 편집이 불가능하기 때문에 사용이 꺼려지긴 합니다. 하지만, 참고할만한 마땅한 Makefile이 별로 없었는데 소스만 봐도 큰 도움이 될듯하네요. 다음은 make_wizard를 실행한 화면입니다.

UNIX Socket Model & libevent

회사에서 libevent에 대한 스터디를 하기로 했습니다. 서버 프로그램 개발시 FreeBSD에서 KQueue와 Solaris에서 /dev/poll 개발 경험이 있습니다. 최근에는 리눅스 서버에서 libevent를 이용하여 서버 프로그램도 제작했습니다. OS별로 각기 API가 조금씩 틀려서 이식성이 떨어져서 이제는 libevent로 통일해서 개발하려고 합니다.

여기서 설명하는 소켓 모델은 여러 소켓을 한 프로세스에서 동시에 처리하기 위한 방식으로, 주로 단일 쓰레드 방식의 어플리케이션에서 여러 소켓 연결(세션)을 처리하기 위해서 사용합니다. 소켓 뿐 아니라 파일 IO 등을 같이 처리할수 있으며, File Descriptor를 통해서 액세스 합니다.

select와 poll이 유닉스 표준으로 정해져 있는 함수이며, 프로토타입은 다음과 같습니다.
[CODE type=cpp]
int select(int nfds, fd_set *r_fds, fd_set *w_fds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
[/HTML][/CODE]

select와 poll 모두 file descriptor 리스트를 인자로 넘기고, timeout 동안 기다립니다. Timeout 시간 이전에 전달한 fd 중에 이벤트가 있으면 리턴합니다.

단일 쓰레드 소켓 서버 프로그램의 pseudo code는 대충 아래와 같습니다.
[CODE type=cpp]
while (1) {
  setup_fd_list();
  select_or_poll();
  handle_fd_events();
}
[/HTML][/CODE]

Select는 fd의 리스트를 비트 단위로 인코딩해서 보냅니다. FD_SETSIZE를 정의해서 bitset의 길이를 정해줄수 있지만, 시스템에 따라서 제약이 있는 경우도 있다고 합니다. Poll은 struct array로 fd 리스트를 전달하여 길이에는 제한이 없지만, array의 중간 fd가 지워지면 처리가 좀더 복잡해집니다. Select와 poll 모두 호출할때마다 fd 리스트 전체를 전달하기 때문에 연결수가 많아지면 비효율적이 됩니다. 유저스페이스에서 커널로 전달하는 데이타가 연결수에 비례하여 선형적으로 늘게됩니다.

아래는 libevent 홈페이지에 있는 소켓 모델간의 비교 그래프입니다. select와 poll 모두 연결수가 많아질수록 처리시간이 비례하여 증가하는것을 볼수 있습니다.

이러한 문제점을 해결하기 위해서 OS별로 다른 소켓 모델을 개발했습니다. BSD에서는 Kqueue, Solaris에서는 /dev/poll, Linux에서는 epoll 등이 있습니다. 이 모델들은 fd 리스트 전체를 항상 전달하지 않고, 한번 등록한 fd는 호출할때 다시 등록하지 않아도 등록된 상태로 되어 커널 쪽에서 관리가 됩니다. fd를 등록하고, 나중에 삭제할 때나 이벤트가 있을 때 까지 등록된 상태로 남아있어, 커널과 유저스페이스 간의 통신 오버헤드를 대폭 줄였습니다. 따라서 연결이 늘더라도 이를 커널쪽에서 효율적으로 처리하여 처리시간이 크게 늘지 않도록 구현되어 있습니다. 위에 그래프를 보면 Kqueue와 epoll은 연결수가 늘어나도 처리시간은 거의 일정한 것을 관찰할수 있습니다.

하지만 이러한 소켓 모델들은 표준화 되지 않고, 구현 범위도 조금씩 차이가 나기때문에 이식성이 좋은 소켓 서버를 구현하는 것은 쉽지 않습니다. Libevent는 이러한 소켓 모델들을 모두 지원하는 라이브러리로 시스템에 따라서 제일좋은 소켓 모델을 선택하여 동작하도록 되어 있습니다.

참고 URL: http://www.kegel.com/c10k.html

c10k 문제(10000 연결 문제)라고 하여 연결을 동시에 많이 유지하는 서버 프로그래밍에 대해서잘 정리되어 있는 페이지입니다.

스터디 주제가 이미 어느정도 아는 내용으로 정해졌고, 새로 오신 분들이 유닉스 경험이 많지 않아서 스터디하면서 나름대로 새로운 내용들을 공부해봐야겠습니다. 일단 개인 관심언어인 Ocaml로 ocaml-event를 이용해서 서버 프로그램을 작성해보고, Make대신 automake와 autoconf 공부도 좀 해야겠네요. 워낙 BSD Parallel Make에 익숙해져서 요즘 리눅스에서 GNU Make 쓰는데 당황스럽더군요. –;

One Laptop Per Child (OLPC)

OLPC라는 프로젝트에 대해 근래 많은 소식을 접할수 있습니다. 교육이 필요한 어려운 나라에 싸게 노트북을 제공하는 프로젝트 정도로만 생각했었는데, 기술적으로도 많은 고민을 한것 같네요. 구글 비디오에서 1시간 정도의 소개를 봤습니다.

http://1laptop1student.blogspot.com/2007/04/olpc-tech-talk-w-google.html

가격은 $175 로 정해졌다고 얼마전에 slashdot에 올라왔었습니다. 국가를 대상으로 파는거고, 원래는 개인에게는 안판다고 했었는데, $200에 판다는 뉴스도 있네요.

하드웨어를 보면, CPU는 AMD Geode 700MHz, 메모리는 256MB로 사양은 떨어집니다. 디스플레이가 좀 특이한데 듀얼모드입니다. 칼라모드는 일반적인 LCD와 같지만 해상도는 낮고, 흑백모드는 직사광선이 있는곳에서 볼수 있으며 해상도는 1200×900이고 전력소비가 매우 적다고 합니다. 흑백모드는 백라이트가 없기 때문에 어두운곳에서는 잘 안보입니다. 무선랜을 지원하는데 네트워크 인프라가 없는 낙후된 지역에서 원활한 네트워크가 가능하도록 무서랜은 컴퓨터를 끈상태에서도 동작할수 있다고 합니다. OLPC들로만 adhoc 네트워크가 원활히 유지되도록 신경을 쓴거 같네요. 크기는 좀 큰거 같지만, 무게는 1.5 Kg 이하일거라 합니다. 전력사용은 최대 일반 노트북의 10분의 1 수준이라고 합니다.

OS는 리눅스(FedoraCore) 기반이고, 그 위에 어플리케이션은 python으로 대부분 구현되었다고 합니다. Sugar라는 UI 라이브러리를 개발하였지만, 아직 UI는 개발중이라고 합니다. 전력 소모를 줄이기 위해서 사용하는 중에도 suspend 모드로 간다고 합니다. 이를 위해서 suspend 상태로 오가는 시간을 대폭 줄여 사용자가 인식하지 못하는 시간(100ms)으로 줄이려고 노력하고 있다고 합니다. 현재 200ms 정도 소요된다고 합니다.

가격을 낮추는거에만 초점을 둔것이 아니라 정말 여러 사항들을 고려하고 있다는 생각이 들고, 그래서 많은 주목을 받고 있는거 같네요. 취지 또한 좋죠. 전세계 인구의 6분의 1 정도가 학습을 받지 못하는 어린 아이들이라고 합니다. 이러한 어린이들에게 교육 기회를 주자는 거죠.

LiveCD가 있어서 한번 구해서 돌려봤습니다. ^^

http://olpc.download.redhat.com/olpc/streams/sdk/build1/livecd/

아직 완성도가 떨어지고, UI가 너무 단순해서 좀 실망이긴 하지만, 아이들이 쓰기에는 괜찮을거 같네요. 저는 네트워크 인식이 되지 않아서 네트워크 기능은 테스트해보지 못했습니다.

아래가 부팅 후 처음 뜨는 로그인 창입니다. My Name에 이름을 적고, 사람모양을 누르면 색을 바꿀수 있습니다. 커서가 좀 엽기적으로 크네요.


로그인을 하면 아주 단순한 창이 뜨는데 마우스를 코너로 가져가면 테두리에 메뉴가 생깁니다. 밑에는 어플리케이션(activity) 리스트, 위에는 시스템메뉴(?)와 실행중인 어플리케이션이 나옵니다.

어플리케이션은 웹브라우저, RSS 리더, 그림판, 테트리스, 카메라, 계산기, 문서작성기, EToy, 음악 프로그램 등이 제공됩니다. EToy는 Squeak Smalltalk에서 제공하는 환경으로 아이들이 쉽고 재미있게 프로그래밍을 학습할수 있는 환경입니다. EToy는 squeakland에서 많은 정보를 얻을수 있습니다. 음악 프로그램은 Csound에 기반하여 여러악기를 연주할수있는 프로그램들로, 건반프로그램, 시퀀싱 프로그램, 악기를 만들수 있는 프로그램 등이 있습니다.

아래는 문서편집기를 실행한 화면입니다. 아주 기본적인 기능만 있으며, 가운데 창은 이미지를 삽입하기 위해서 파일을 선택하는 창이 뜬건데, GTK+ 기반의 인터페이스를 아직 안고친거 같네요. Sugar UI 라이브러리가 GTK+ 위에 구현되었고, GTK 위젯을 안쓰고 따로 구현한 캔버스만을 사용한다고 들었는데 아직 공통(common) 다이얼로그는 개발이 안되었나보네요. 글씨가 너무 작고 비율도 안맞아서 파일 고르기도 힘들더군요. 차차 나아지겠죠… 문서편집기 기능은 딱 꼭 필요한거만 있는거 같네요.


구글 비디오를 보고, 라이브 CD를 실행해보고 느끼는게 국제화에 대한 얘기가 전혀 없다는게 좀 의아하긴 하네요. 6개월의 개발 기간이 남았다고 하는데 6개월후에 어떤 모습으로 변해있을지 지켜봐야겠네요.

CLucene CJK 분석기

CLucene을 이용하여 검색엔진 구현하는데, 한글처리에 대한 정보가 거의 없더군요. 아주 기본적인 한글처리만 구현해보았습니다. CLucene에서 한글 처리에 대해서 참고하시면 도움이 되리라 생각하여 소스를 공개합니다.

리눅스와 윈도우즈에서 동작하지만 먼저 리눅스 소스만 공개합니다. 윈도우즈에서 아직 _MBCS 정의를 빼지않고 컴파일에 성공하지 못했습니다. 좀더 연구해봐야할듯하네요. 소스는 코드변환 외에는 차이가 없습니다.

clucene-core-0.9.16a 버전을 사용했으며 Makefile에서 CLUCENEPATH를 설정하고 make하시면 됩니다. 소스에 포함된 한글은 UTF-8로 인코딩되어있으며, CentOS 4.4 AMD64 리눅스(LANG=ko_KR.UTF-8)에서 테스트했습니다.

clucene의 StandardTokenizer에 보면 CJK관련 처리가 있지만, next()에서 _CJK로 인식하기 전에 다른곳(_istalpha)으로 빠져서 CJK 토큰으로 분류가 안되더군요. 그래서 복사해서 CJKTokenizer.cpp를 만들고 비교 순서만 바꿔줬습니다. 왜 한글코드가 _istalpha으로 인식되어 빠져나가는지는 잘 모르겠네요.

KoreanStemFilter.cpp에서는 CJK 토큰을 2글자 단위로 나누는 역할을 합니다. 루씬인액션에 설명되어있는데 clucene에는 구현이 안되어 있는거 같더군요. “검색엔진” 토큰을 “검색” “색엔” “엔진” 토큰으로 바꾸죠. 한글의 조사를 뺀다던가 하는 기능을 추가하기 위해서 KoreamStemFilter로 만들었는데 지금 기능은 CJK 필터만 구현되어있네요.

ConvertUtil.cpp는 iconv를 이용하여 UTF-8을 UTF-32LE로 바꾸는 소스 입니다. 윈도우즈의 경우는 MultiByteToWideChar()와 WideCharToMultiByte() 함수를 이용했습니다.

CLuceneTest.cpp는 간단한 데이타 3개를 넣고 터미널 상에서 검색할 수 있는 테스트 프로그램입니다. clucene의 데모 소스를 약간 바꿔서 구현했습니다.

다음은 실행한 화면입니다:

$ ./CLuceneTest
adding doc: doc1 – hahaha 한글단어 hohoho 비 bye 검색엔진
adding doc: doc2 – hello zaza 한글 김현정 김건 건모 검색
adding doc: doc3 – goodbye 김건모 서영은 검색 엔진 SG워너비
Indexing took: 5 ms.

Enter query string: 검색엔진
Searching for: “검색 색엔 엔진”

0. doc1 – hahaha 한글단어 hohoho 비 bye 검색엔진 (0.974307)

Search took: 1 ms.
Screen dump took: 0 ms.

Enter query string: +검색 +엔진
Searching for: +검색 +엔진

0. doc1 – hahaha 한글단어 hohoho 비 bye 검색엔진 (0.383675)
1. doc3 – goodbye 김건모 서영은 검색 엔진 SG워너비 (0.383675)

Search took: 0 ms.
Screen dump took: 0 ms.

소스입니다.
1204400252.tgz

검색 기술 좌충우돌

검색 기술은 제대로 배워본적이 없었는데, 서비스 운영하다보니 여러번 발목을 잡더군요. 서버에서 검색 기술이 필요한 부분도 있었고, 클라이언트에서도 필요했습니다.

클라이언트 단에서는 처음에는 인덱스 없이 직접 스트링 비교를 해서 검색하다가, 검색할 데이타가 늘면서 느려져서, 검색 인덱스를 메모리에 간단히 만들고 (stl map 이용), 사용하니 큰 문제없이 잘 동작하더군요. 근데 경우에 따라서 검색 인덱스가 아주 커지는 클라이언트 들이 생겨서 메모리에 전체 인덱스를 올리는것이 불가능한 경우가 생겼습니다. 그래서, 어쩔수 없이 파일 기반으로 map처럼 사용할수 있는 dbm 계열의 라이브러리를 사용하여 인덱스를 저장하여 사용하고 있습니다. 현재까지는 큰 문제없이 사용하고 있습니다. 하지만 검색 인덱스가 커지고 쿼리가 늘면 좀 버벅대더군요. 아무래도 인덱스 만드는거나 검색자체가 효율적으로 구현한것이 아니고, 저장하는 방법 또한 효율적으로 검색할수 있는 구조가 아니었던것 같네요.

서버 단에서는 디비에 있는 필드 검색이라 MySQL의 FullText 검색을 이용할 방법을 고민했었습니다. 요즘도 해결 안됐을거 같은데 FullText 검색은 한글을 지원하지 않죠. 그래서 고민하다가 생각한 방법이 한글을 영문으로 인코딩하여 필드하나 더 만들어서 박아넣는거였습니다. 일단 영문은 다 소문자로 변경해서 넣고, 한글은 대문자로 자~알 인코딩 해서 넣고, 디비 Insert,Update할때 인덱스 필드에 변환해서 넣고, 검색할때 한글 변환해서 fulltext search하고.. 이렇게 하니 잘 동작은 하더군요. 몇달간 서비스하면서 데이타가 몇기가 쌓이니, 점점 느려져만 가더군요. 이건 서비스 기획의 문제였지만, 운영하던 서비스는 거의 삭제의 개념이 없고 계속 누적되는 형태로 운영이 되었습니다. 그러니 문제가 될수 밖에 없었죠. 나중에는 검색을 새로 구현할 엄두가 나지 않아서 검색 자체를 빼고 P2P 검색으로 대체했습니다…

서버 단에서 다시 검색이 필요하여, MySQL fulltext보다는 좀더 좋은 방법을 찾으려고 했는데… 인덱싱하는 페이지들이 모두 html로 만들어져 있기 때문에, 웹기반 인덱싱 쪽으로 알아보았습니다. 외국에서 만든 많은 검색엔진들이 단어 단위로 인덱스를 만들기 때문에 한글 검색이 잘 안되는 경우가 많더군요. “검색엔진”이라는 단어가 인덱싱 되면 “검색”으로는 해당 문서가 검색이 안되는 엔진들이 많더군요. mnogosearch라는 검색엔진을 발견했는데, 한글 검색이 완벽하게 되더군요. 구현할 시간은 없고 일단 동작을 하다보니 그냥 사용하기로 하고 서비스 했습니다 –; 뭐 처음에는 사용자들이 검색 들어갔는지도 잘 몰랐기 때문에, 서비스 운영에 전혀 문제 없었습니다. 데이타가 쌓이고, 검색 많아지고 하면서 문제가 조금씩 생기더군요. 일단 서버 여러대에 분산 처리했습니다. 근데 문제는 몇몇 사용자가 검색을 집중적으로 해도 서비스가 상당히 불안해졌습니다. 서비스가 다운되거나 그런건 아니지만, 디비에 부하가 걸려서 응답속도가 느려지더군요. mnogosearch에 대한 최적화 방법 같은걸 찾아보고, 들어오는 쿼리들을 저장하고 벤치마킹을 해봤습니다. 처음 벤치마크 결과는 절망적이더군요. 일단 쿼리에 따라 검색시간에 큰 편차가 있었습니다. 내부적인 동작을 모르니, 최적화는 한계가 있을거 같다는 생각을 하고 mnogosearch를 대체할수 밖에 없다고 결론을 냈습니다.

로레벨(ㅎㅎ) 개발자이기 때문에 먼저 위키피디아에서 검색 알고리즘들을 찾아보고, 필요한 소스코드(주로C)를 수집하기 시작했습니다. 알고리즘 소스들을 연결하여 인덱싱하고 검색하는거 구현하는 거는 정말 쉬운일이 아닐거 같다는 생각이 금방 들더군요. 그래서 좀더 검색하다 찾은것이 루씬(Lucene)입니다.

루씬은 자바로 된 검색엔진입니다. 검색엔진이라는 용어 자체가 좀 애매한데, mnogosearch는 사용자를 위한 검색 엔진이라면, 루씬은 개발자용 검색 엔진입니다. mnogosearch는 인덱스할 url을 입력하고, 화면에 검색결과가 어떻게 표시될지만 설정해주면 바로 사용이 가능합니다. 루씬의 경우는 검색엔진 라이브러리입니다. 개발자가 직접 문서를 읽어들이는 코드, 인덱싱하고, 검색하는 코드를 직접 작성해야합니다. 또한 어느정도 기술에 대한 이해도가 있어야 좋은 성능을 낼수 있다고 하네요. 다행히 Lucene in Action이라는 책이 나와있어서 큰 도움이 되더군요. 번역판도 나온거 같더군요…

제가 자바를 선호하지 않고, 잘 동작한다면 클라이언트(win32)단에서도 사용하고 싶었기 때문에 C++로 포팅된 CLucene을 사용하기로 결정했습니다. CLucene이 자바 루씬에 비해서 버전이 낮긴하지만, 성능도 뛰어나고, 제가 C++이 더 익숙해서요…. win32 클라이언트는 종속성을 줄이기 위해서 static 링크되지 않는 외부라이브러리는 가능하면 안씁니다. ( 특히 .net 같은거는 절대 안씁니다. )

CLucene으로 현재 검색 서버를 개발 완료했으며, 클라이언트 단에도 실험적으로 돌려보고 있는데 성능은 아주 잘 나오고, 검색 시간 편차는 정말 적더군요. 서버단에서 벤치마크 결과 아주 만족스런 결과가 나왔습니다.

시간내서 CLucene으로 한글 검색 되도록 스탬필터로 개발한 소스를 공개하도록 하겠습니다. 한글스탬필터는 2글자씩 짜르는거 외에 별도 처리는 구현하지 않았습니다. 자바 루씬에는 이렇게 구현된 것이 들어있는지 모르겠는데, CLucene에서는 소스 찾아봐도 없더군요. 그리고 CLucene의 StandardAnalyzer로는 CJK 처리가 정상적으로 동작안하더군요. (win32, linux에서 모두)

소스 조만간 공개하겠습니다 ^^

Unix/Linux 모임 참석 후기

정말 오랜만에 오프라인 모임에 나가보았습니다. 온라인으로도 활동을 거의 안하긴 하지만… 얼마전부터 다른 사람들의 블로그 글들을 꾸준히 읽다보니 제가 뒤쳐지는 느낌도 들고, 회사 내에서 회사일만 하기도 바쁘다보니 보는 시야가 너무 한정되는거 같아서 오프라인 모임 같은곳에 한번 나가야겠다는 생각은 했었습니다.

스마트플레이스에서 “Unix/Linux 엔지니어, 개발자, IT 매니저와의 대화“글을 보고 설문에 참여했었는데 연락 주셔서 어제 모임에 참석하게 되었습니다. 모임의 성격은 약간 애매했던거 같은데, 개발자가 아닌 다른 입장에서 왜 유닉스를 사용하는지에 대해서 들을수 있어서 도움이 되었습니다. 또한 작은 기업에서 서비스 운영하다보니 싼 서버에 오픈 소스 유닉스만 사용하고, 최적화의 끝을 찾아헤매는데, CPU 32개 이상 서버, 문제있을때 4시간만에 출장은 다른 나라 얘기같더군요. 그래도 최적화를 하면서 많은 보람을 느끼는데 최적화를 통해서 다른 곳에서 하지 못하는 서비스를 언젠가 할수 있다고 믿고 싶네요.

모임은 스플 멤버 외에 참가한 사람들의 소개로 시작해서 소개로 끝났습니다. 많은 얘기 나누지는 못했지만 참가한 모든 사람들의 열정적인 모습 보고 많은 자극 받았습니다. 블로그 들어가서 보니 참 많은 생각 하시고, 부지런한 분들이시더군요.

앞으로도 오프모임 자주 참석해서 사람들 많이 만나봐야겠다는 생각이 드네요.

그리고 준비해주신 기념품 너무 고맙게 받았습니다. ^^

화이트도메인, SPF, RBL & Postfix

회사 서비스에서 회원들에게 메일을 보내보면, 메일이 잘 안가고 반송되고 해서, 얼마동안 이메일 발송을 거의 안했었는데, 작년에 화이트 도메인 제도가 포탈 등에 적용되면서 좀 상황이 나아진 것도 같네요.

자세한 내용은 www.kisarbl.or.kr에서 확인할수 있습니다.

자신의 도메인(DNS) 서비스에서 어떤 아이피 주소에서 메일을 보낼수 있는지를 DNS TXT 영역에 등록합니다. 메일 받는 서버에서 메일을 보내는 도메인의 DNS TXT를 확인하여 등록된 아이피 주소와 다르면 메일을 드랍하도록 하는 원리입니다. SPF(Sender Policy Framework)라고 불리는 기술인데 2006년 RFC 4408  표준으로 지정되었으며, KISA에서 발빠르게 도입을 하여 국내 메이저 포탈등에 대부분 적용되었습니다.

여기 등록을 하면, 다른 화이트도메인 서비스에 등록한 서버간에는 메일이 원활히 전달되며, 서비스에 등록하고 스팸을 보낼수도 있으므로, 등록한 도메인에 대해서 내부적으로 평가하는 시스템을 갖춰서 스팸을 보내는것으로 판단되면 RBL(Real-time Blackhole List)에 등록되어 메일 전달이 어렵게 됩니다. RBL은 스팸을 보내는 아이피주소인지 알려주는 서비스로 여러곳에서 제공하고 있습니다. 원리는 간단합니다. 아이피주소를 조회하면 그 아이피가 스팸을 보내는지 알려주는 서비스입니다. 서비스를 제공하는 곳에 따라서 다른 아이피 주소 리스트를 관리하기 때문에, 보통 여러 RBL을 참조하도록 메일서버를 설정합니다.

KISA에서 제공하는 RBL이 spamlist.or.kr이며, 여기를 보시면 여러 RBL에 대한 비교가 있습니다.

그럼 화이트 도메인 등록과 메일서버 세팅을 하면서 사용한 방법을 설명드리도록 하겠습니다. 먼저 DNS서버의 해당 도메인의 zone 파일에 다음 라인을 삽입하고

내도메인명.  IN   TXT   “v=spf1 ip4:메일보내는아이피1 -all

named를 재시작했습니다. -all이나 ~all로 지정할수 있는데, -all은 내도메인명에서 발송되는 메일중 아이피가 SPF 리스트에 없으면 무조건 드랍하고, ~all로 지정하면 받는메일서버의 정책에 따라 판단하라 입니다. 여러 아이피를 지정할수 있으며 1.2.3.0/24 형태로 아이피 영역을 지정할수도 있습니다.

이렇게 하고 도메인정보가 어느정도 전파되면, www.kisarbl.or.kr에서 화이트도메인으로 등록을 할수 있습니다. 등록방법도 어렵지 않고, 친절히 잘 설명되어 있습니다.

메일서버는 sendmail, qmail을 거쳐서 postfix를 사용하고 있는데 postfix가 제일 설정이 쉽고 모듈화가 잘 되어있는듯하더군요… 하여튼 postfix에서 RBL 등록하는 방법은 매우 간단합니다. main.cf 파일에서 smtpd_recipent_restrictions에 적당한 위치에 reject_rbl_client 항목을 추가해주면 됩니다. 제 서버 설정입니다:

smtpd_recipient_restrictions =
   permit_sasl_authenticated,
   reject_invalid_hostname,
   reject_non_fqdn_hostname,
   reject_non_fqdn_sender,
   reject_non_fqdn_recipient,
   reject_unknown_sender_domain,
   reject_unknown_recipient_domain,
   permit_mynetworks,
   reject_unauth_destination,
   reject_rbl_client spamlist.or.kr,
   reject_rbl_client cbl.abuseat.org,
   reject_rbl_client list.dsbl.org,
   reject_rbl_client sbl.spamhaus.org,
   reject_rbl_client pbl.spamhaus.org,
  permit

smtpd_recipent_restrictions 원래 설정된 항목에서 reject_rbl_client들만 원하는 위치에 등록하면 됩니다. 위에서부터 순차적으로 처리하므로 위치는 원하는 위치에 두시면 됩니다.

메일서버에 RBL을 지정하고 나니 스팸메일 95% 이상이 걸러지는것 같네요… 안오는 메일이 있으면 문제지만 그건 RBL에 아이피가 등록되면 보내는 측에서 잘못한거니 그쪽에서 해결해야할 문제죠. 예전에 실수로 서버에 프록시를 테스트용으로 잠깐 열어두었다가 해외 RBL에 서버중 하나가 등록된 적이 있었는데 RBL에서 빠른 시간내에 제거하는 게 어렵더군요. 등록되는 RBL에 따라 제거 방법도 다르고요… 관리하는 메일서버에서 스팸이 나가지 않도록 잘 관리해야겠습니다.

Synergy로 여러컴퓨터 편하게 쓰기

Synergy는 하나의 마우스와 키보드로 여러 컴퓨터를 제어할수 있는 프로그램입니다. 원격데스크톱이나 VNC류의 프로그램들은 하나의 화면으로 여러 컴퓨터를 보도록 해주지만, Synergy는 화면은 따로따로 봐야합니다. 한 책상에 2개의 컴퓨터와 두개의 모니터가 있다면 아주 유용하게 쓸수 있습니다. 키보드와 마우스는 하나만으로 두컴퓨터를 자유롭게 사용할수 있죠. 프로그램도 가볍고 설정만 좀 하면 하나의 컴퓨터에 듀얼 모니터를 쓰는 것과 느낌이 비슷합니다. 그리고 간단한 텍스트는 클립보드로 복사가 되서 양쪽으로 쉽게 정보를 주고 받을수 있습니다.

얼마전에 친구 블로그를 통해 알게된 프로그램인데, 회사에 새 데스크탑이 들어와서 노트북은 귀찮아서 잘 사용안했었는데, 시너지로 연결하여 편하게 사용하고 있습니다.

단 사용하면서 한가지 문제가 있었습니다. 한영전환이 키보드로 안되는 치명적인 문제가 있는데, 이 문제를 해결한 패치버전을 찾았습니다 🙂

http://www.magoja.com/tt/242

한글패치된 1.3.1 인스톨 버전입니다: SynergyInstaller_hangulkey.exe

제 서버에서도 받으실수 있습니다: 1020477966.xxx

출처: http://www.javaservice.net/~java/bbs/read.cgi?m=etc&b=etc&c=r_p&n=1151464351&p=1&s=t

XP에서 멀티쓰레드 프로그램 디버깅시 멈추는 문제

XP에서 개발한 이후로 디버깅하면서 모든 프로그램이 멈추는 듯한 현상이 자주 발생했었는데, 이제야 문제에 대한 해결책(?)을 우연히 블로그에서 발견하게 됐습니다.

http://snaiper.tistory.com/198

2000에서 개발할때까지는 디버깅에 전혀 문제가 없었는데, 멀티쓰레드 프로그램 디버깅을 할때 메인 쓰레드가 아닌곳에 breakpoint를 잡고 디버깅하다 보면 마우스만 움직이고 전체 프로그램이 클릭도 안되는 상황이 발생하더군요. 그래서 2000에 개발환경도 갖춘적도 있었는데, 최근에는 주로 OutputDebugString로 원시적인 디버깅을 많이합니다. ㅎㅎ

정확한 원인은 잘 모르겠지만, 고급텍스트서비스라는데서 글로벌 락을 이용해서, 디버거에서 그쪽에 락을 걸어버려서 다른 모든 프로그램들도 데드락이 걸리는거라는 거 같습니다. –;

해결책은

제어판-국가및언어옵션-언어탭-텍스트 및 입력언어 “자세히”-고급탭-고급텍스트서비스사용안함에 체크하고 리부팅

이렇게 바꾸면 멈추는 현상이 대부분(?) 없어진다고 하는군요.