7. 시험도구(Test Suite)

test suite는 CVS 저장소에 있으며, test suite가 적용되는 범위가 넓을 수록, 여러분의 코드가 오동작을 하는 경우가 거의 없을 수 있다는 자신감은 더 커지게 된다. 사소한 테스트(trivial test)는 상당히 어려운 테스트(tricky test) 만큼이나 중요하며, 이는 복잡한 시험을 단순화한 것이 사소한 테스트이기 때문이다. 일단 복잡한 테스트를 수행하기 전에 기본적인 작업은 만족한다는 것을 알 수 있기 때문이다.

테스트는 단순하며, 그저 testsuite/의 서브디렉토리에 있는 쉘스크립트이다. 실행되는 순서는 알파벳 순서다. 따라서 `01test'가 `02test'보다 먼저 실행된다. 현재는 다섯 개의 테스트 디렉토리가 있다.

00netfilter/

General netfilter framework tests.

01iptables/

iptables tests.

02conntrack/

connection tracking test.

03NAT/

NAT tests.

04ipchains-compat/

ipchains/ipfwadm compatibility tests.

testsuite/ 디렉토리 내부에는 `test.sh'라는 쉘 스크립트가 존재하며, 두개의 더미 인터페이스(tap0와 tap1)를 구성하여, forwarding을 on 시키고, 모든 netfilter 모듈을 제거하는 역할을 한다. 그리고 나서 하위 디렉토리에 존재하는 모든 쉘 스크립트를 수행하는 데 이중 하나라도 오류가 발생하면 수행을 멈추게 된다. `test.sh'는 두개의 옵션을 넘겨받을 수 있는 데, `-v'는 수행하는 각각의 테스트를 출력하며, 추가적인 test 이름이 주어지면 해당하는 테스트가 발견될 때까지 다른 테스트는 모두 건너뛰게 된다.

7.1. 테스트를 위한 스크립트 작성

우선 적당한 디렉토리에 새로운 파일을 생성하고, 적절한 시기에 실행할 수 있도록 번호를 부여한다. 예를 들어보면, ICMP reply tracking (02conntrack/02reply.sh)을 테스트하기 위해서는 외부로 나가는 ICMP가 적절하게 추적되고 있는 지를 먼저 확인해야한다(02conntrack/01simple.sh).

작은 크기의 파일을 많이 만들어서, 각각의 파일은 한 가지 분야만 담당하도록 하는 것이 좋을 것이며, 그 이유는 testsuite를 실행시킨 사람들이 문제를 즉각 알아 낼 수 있도록 할 수 있기 때문이다.

테스트 도중 문제가 발생하면, 그냥 `exit 1'을 하면 되고, 그러면 failure가 발생한다. 즉, 여러분들이 문제가 발생한 것을 감지할 수 있게 되면, 적절한 메시지를 출력하는 것이 좋다. 문제가 발생하지 않았다면, `exit 0'로 종료하면 된다. 스크립트의 최상위에서 `set -e'를 사용하거나, 각 명령의 마지막에 `|| exit 1'을 추가하여 각 명령의 성공여부를 확인해야 한다.

helper 함수인 `load_module'와 `remove_module'는 모듈을 올리거나 내리는 데 사용할 수 있는 데, 특별히 테스트라고 지정하지 않는 한 testsuite에서는 autoloading을 지원하지 않는다.

7.2. 변수와 환경

일단 tap0와 tap1이라는 두개의 인터페이스를 가지고 있다. 각 인터페이스의 주소는 $TAP0와 $TAP1이라는 변수에 저장되어 있으며, 넷매스크는 모두 255.255.255.0이다. network은 $TAP0NET과 $TAP1NET에 저장되어 있다.

$TMPFILE은 임시파일로, 테스트가 종료되는 시점에서 삭제된다.

여러분의 스크립트는 testsuite/ 디렉토리부터 시작해서, 스크립트가 존재하는 모든 디렉토리를 찾아가게 된다. 즉 여러분들은 iptables같은 도구를 실행시키기 위해서는 `../userspace'로 시작하는 path를 사용해야 한다.

$VERBOSE를 설정해 놓으면 여러분의 스크립트는 보다 많은 정보를 출력하게된다. 즉, command line에서 `-v' 옵션을 준 것과 동일한 효과가 된다.

7.3. 유용한 도구들

``tools'' 디렉토리에는 쓸만한 testsuite 도구가 몇 개 있으며, 각 도구는 문제가 발생한 경우 non-zero exit status를 발생시키며 종료한다.

7.3.1. gen_ip

IP 패킷을 생성하는 함수로, IP 패킷을 표준 입력으로 출력한다. 여러분들은 표준 출력을 /dev/tap0와 /dev/tap1으로 전송하여 tap0와 tap1에 패킷을 보낼 수 있다. (/dev/tap0와 /dev/tap1이 존재하지 않으면 testsuite가 최초 실행 될 때 생성된다.)

FRAG=offset,length

패킷을 생성하고, offset과 length 만큼 패킷을 조각 낸다.

MF

패킷의 `More Fragments' 비트를 설정한다.

MAC=xx:xx:xx:xx:xx:xx

패킷에 소스 MAC 주소를 설정한다.

TOS=tos

패킷에 대하여 TOS 필드를 설정한다. 0-255.

다음은 강제 옵션이다.

source ip

패킷의 소스 IP 주소

dest ip

패킷의 목적지 주소

length

헤더를 포함한 패킷의 길이

protocol

패킷의 프로토콜 번호, (예: 17 = UDP)

각 전달인자는 프로토콜에 따라 달라진다. UDP(17)인 경우 소스와 목적지 포트 번호가 된다. ICMP(1)인 경우는 ICMP 메시지의 코드가 된다. 타입이 0 또는 8(ping-reply 또는 ping)인 경우는, 두개의 인자로 ID와 sequence 필드가 더 필요하다. TCP인 경우는 소스와 목적지 포트 그리고 플랙(``SYN'', ``SYN/ACK'', ``ACK'', ``RST'' 또는 ``FIN'')이 필요하다. 세개의 추가 옵션이 있는 데, ``OPT='' 다음에는 컴마로 분리된 옵션들이 오고, ``SYN='' 다음에는 시퀀스 번호, ``ACK='' 다음에도 시퀀스 번호가 호다. 마지막으로, ``DATA=''이라는 추가 옵션은 표준입력의 내용을 가득 채운 TCP 패킷의 부하를 의미한다.

7.3.2. rcv_ip

`rcv_ip'를 사용하면 IP 패킷을 볼 수 있으며, 가능한 get_ip로 들어오는 원래의 값에 가깝게 명령 행에 출력한다.(fragments는 제외)

이 함수는 패킷을 분석하는 데 매우 유용하며, 다음과 같은 강제 옵션이 두개 있다.

wait time

표준입력으로부터 패킷을 기다리는 최대 시간으로 초단위로 표시된다.

iterations

수신하고자 하는 패킷의 수

``DATA''라는 추가 옵션이 있으며, 패킷 헤더 다음에 패킷의 부하를 표준출력으로 출력한다.

다음 스크립트는 `rcv_ip'를 사용하는 표준을 보인 것이다.
# Set up job control, so we can use & in shell scripts.
set -m
# Wait two seconds for one packet from tap0
../tools/rcv_ip 2 1 < /dev/tap0 > $TMPFILE &
# Make sure that rcv_ip has started running.
sleep 1
# Send a ping packet
../tools/gen_ip $TAP1NET.2 $TAP0NET.2 100 1 8 0 55 57 > /dev/tap1 || exit 1
# Wait for rcv_ip,
if wait %../tools/rcv_ip; then :
else
    echo rcv_ip failed:
    cat $TMPFILE
    exit 1
fi
				

7.3.3. get_err

표준입력으로부터 패킷(예를 들면, gen_ip로 생성된 패킷)을 취하고, ICMP 에러로 변환시킨다.

세개의 전달인자로 소스 IP 주소, 타입과 코드를 받는다. 목적지 입력은 표준입력으로부터 받은 소스 IP 주소로 설정된다.

7.3.4. local_ip

표준입력으로부터 패킷을 얻어서 raw 소켓으로 전달한다. 로컬에서 생성된 패킷의 모양을 보여준다(ethertap 장치 중 하나로 입력된 패킷으로부터 분리하는 경우, 외부에서 생성된 패킷처럼 보인다).

7.4. 생각나는 대로 하는 충고

모든 도구는 읽고 쓰기를 한번 할 때 모든 것을 할 수 있다고 가정한다. 즉 ethertap 장치에 대해서는 사실이지만, pipe를 이용하여 약간 복잡한 작업을 할 때는 반드시 옳다고 말할 수는 없다.

패킷을 자르기 위해서 dd를 사용할 수 있다. dd는 일회성 작업으로 패킷을 출력하기 위해 사용할 수 있는 obs(output block size)라는 옵션이 있다.

첫 번째에 성공하도록 테스트하는 것이 좋다. 즉, 패킷이 성공적으로 블럭 되도록 테스트해야 한다. 처음에는 패킷이 정상적으로 통과하도록 테스트를 하고 나서, 일부 패킷이 블럭 되도록 테스트해야 한다. 그렇지 않으면, 엉뚱한 오류로 인해 패킷이 진행하는 것을 중지시킬 수 도 있다.

아무거나 막 보내고 나서 어떤 결과가 일어나는 확인하지 말고, 정확한 테스트를 할 수 있도록 해야한다. 정확한 테스트가 잘 못된 경우라면, 무엇이 잘 못 되었는지 알 수 있지만, 랜덤 테스트가 잘못 되면 전혀 도움이 되지 않는다.

아무런 메시지도 남기지 않고 테스트가 실패한 경우, 어떤 명령이 실행되고 있는지 알아보기 위해 스크립트의 맨 위에 `-x'를 추가 할 수 있다. 즉, `#!/bin/sh -x'.

테스트가 불규칙적으로 실패하면, 모든 외부 장치를 차단하거나 하는 식으로, 랜덤 네트웍 트래픽에 대하여 확인해 보는 것이 좋다. 예를 들면,Andrew Tridgell처럼 동일한 네트웍 상에 있으면, 윈도우의 브로드 캐스팅에 의해 수많은 간섭을 받게 된다.