5 라우팅 표
너는 라우팅 표를 필요로 하지 않을지라도, 만약 너의 프로토콜이 그것을 사용한다면 그때 이 절을 읽어라. 우리는 다른 클래스로써 또는 임의의 다른 데이터 구조 (예를 들어, 해쉬 표)로써 라우팅 표를 구현할 수 있다. 우리는 라우팅 표가 가지기로 되어 있는 그 기능성을 캡슐화하는 클래스를 보일 예정이다. 내부의 정보는 프로토콜에서 프로토콜까지 많이 변할 것이다. 라우팅 표에서 각각의 입력을 위해서 목적지 주소들, 다음 홉 주소들, 루트들과 결합된 거리들 또는 비용, 시퀀스 넘버들, 수명들 기타 등등을 저장하기를 원할 것이다. 물론 우리의 예제는 매우 단순한 라우팅 표와 그것을 프린트하기 위한 방법을 묘사한다. 우리가 각각의 입력에 저장할 유일한 정보는 목적지와 다음 홉 주소들이다. 우리는 저장 구조로써 해시 표 (map)을 사용한다. 이런 경우는 너무 단순해서 새로운 클래스를 구현할 수 없지만, 우리는 예제로써 그것을 실행할 것이다. 코드의 다음 조각은 protoname/protoname_rtable.h에 일치한다.
protoname/protoname_rtable.h
1: #ifndef __protoname_rtable_h__
2: #define __protoname_rtable_h__
3:
4: #include <trace.h>
5: #include <map>
6:
7: typedef std::map<nsaddr_t, nsaddr_t> rtable_t;
8:
9: class protoname_rtable {
10:
11: rtable_t rt_;
12:
13: public:
14:
15: protoname_rtable();
16: void print(Trace*);
17: void clear();
18: void rm_entry(nsaddr_t);
19: void add_entry(nsaddr_t, nsaddr_t);
20: nsaddr_t lookup(nsaddr_t);
21: u_int32_t size();
22: };
23:
24: #endif
이런 함수들의 구현은 매우 쉽다. 사실 그 건설자는 매우 단순해서 그것 안에 실행할 것이 아무것도 없다.
protoname/protoname_rtable.cc
1: protoname_rtable::protoname_rtable() { }
그 print() 함수는 노드의 라우팅 표의 내용들을 트레이스 파일로 덤프할 것이다. 그것을 실행하기 위해서 우리는 절 4.3에서 언급했던 트레이스 클래스를 사용한다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::print(Trace* out) {
3: sprintf(out->pt_->buffer(), “Ptdesttnext”);
4: out->pt_->dump();
5: for (rtable_t::iterator it = rt_.begin(); it != rt_.end(); it++) {
6: sprintf(out->pt_->buffer(), “Pt%dt%d”,
7: (*it).first,
8: (*it).second);
9: out->pt_->dump();
10: }
11: }
다음의 함수는 라우팅 표 안에 모든 입력들을 제거한다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::clear() {
3: rt_.clear();
4: }
그것의 목적지 주소가 주어진 입력을 제거하기 위해서 우리는 rm_entry() 함수를 구현한다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::rm_entry(nsaddr_t dest) {
3: rt_.erase(dest);
4: }
아래의 코드는 그것의 목적지와 다음 홉 주소들이 주어진 라우팅 표 안에 새로운 입력을 더하기 위해서 사용된다.
protoname/protoname_rtable.cc
1: void
2: protoname_rtable::add_entry(nsaddr_t dest, nsaddr_t next) {
3: rt_[dest] = next;
4: }
Lookup()은 그것의 목적지 주소가 주어진 입력의 다음 홉 주소를 되돌려준다. 만약 그런 입력이 존재하지 않는다면, (즉, 그 목적지를 위한 루트가 없다면) 그 함수는 IP_BROADCAST를 되돌려준다. 물론 이 상수를 사용하기 위해서 common/ip.h를 포함한다.
protoname/protoname_rtable.cc
1: nsaddr_t
2: protoname_rtable::lookup(nsaddr_t dest) {
3: rtable_t::iterator it = rt_.find(dest);
4: if (it == rt_.end())
5: return IP_BROADCAST;
6: else
7: return (*it).second;
8: }
마지막으로, size()는 라우팅 표 안에 입력들의 수를 되돌려준다.
protoname/protoname_rtable.cc
1: u_int32_t
2: protoname_rtable::size() {
3: return rt_.size();
4: }
6 요구되는 변화들
우리는 거의 끝냈다. 우리는 NS2 안쪽에 프로토콜 protoname을 위한 라우팅 에이전트를 구현해왔다. 그러나 우리의 코드를 시뮬레이터 안쪽에 통합하기 위해서 우리가 실행할 필요가 있는 몇 가지 변화들이 있다.
6.1 패킷 유형 선언
만약 우리가 기억한다면 우리는 우리의 새로운 패킷 유형, PT_PROTONAME을 가리키기 위한 상수를 사용해야 했었다. 우리는 파일 common/packet.h 안쪽에 그것을 정의할 것이다.
모든 패킷 유형들이 리스트된, packet_h 목록을 찾아보자. 우리는 코드의 다음 조각에서 보이는 것처럼 PT_PROTONAME을 이런 목록에 더할 것이다. (줄 6)
common/packet.h
1: enum packet_t {
2: PT_TCP,
3: PT_UDP,
4: PT_CBR,
5: /* … much more packet types … */
6: PT_PROTONAME,
7: PT_NTYPE // This MUST be the LAST one
8: };
바로 아래에 같은 파일에 p_info 클래스의 정의가 있다. 건설자 안쪽에 우리는 우리의 패킷 유형을 위한 본문의 이름을 제공할 것이다. (줄 6)
common/packet.h
1: p_info() {
2: name_[PT_TCP]= “tcp”;
3: name_[PT_UDP]= “udp”;
4: name_[PT_CBR]= “cbr”;
5: /* … much more names … */
6: name_[PT_PROTONAME]= “protoname”;
7: }
6.2 트레이싱 지원
우리가 알다시피 시뮬레이션의 목적은 실행 동안에 무엇이 일어났는지 기술하는 트레이스 파일을 얻는 것이다. 트레이스들에 친밀함을 느끼기 위해서 23장 [2]를 읽어봐라. 트레이스 오브젝트는 패킷이 받아들여지거나, 보내지거나, 또는 버려질 때마다 매번 패킷의 요구되는 정보를 쓰기 위해서 사용된다. 우리의 패킷 유형에 관한 정보를 기록하기 위해서 우리는 CMUTrace 클래스 안쪽에 format_protoname() 함수를 구현한다. 무선 시뮬레이션들을 위한 트레이스 지원은 CMUTrace 오브젝트들에 의해서 제공되고 그것은 16장 [2]에서 기술된다.
trace/cmu-trace.h 파일을 편집해보자. 우리는 다음의 예제의 줄 수 6 안에서처럼 우리의 새로운 함수를 더해야 한다.
trace/cmu-trace.h
1: class CMUTrace : public Trace {
2: /* … definitions … */
3: private:
4: /* … */
5: void format_aodv(Packet *p, int offset);
6: void format_protoname(Packet *p, int offset);
7: };
(trace/cmu-trace.cc로부터 추출된) 코드의 다음 조각은 트레이스들의 다른 유형들을 보여준다.
trace/cmu-trace.cc
1: #include <protoname/protoname_pkt.h>
2:
3: /* … */
4:
5: void
6: CMUTrace::format_protoname(Packet *p, int offset)
7: {
8: struct hdr_protoname_pkt* ph = HDR_PROTONAME_PKT(p);
9:
10: if (pt_->tagged()) {
11: sprintf(pt_->buffer() + offset,
12: “-protoname:o %d -protoname:s %d -protoname:l %d “,
13: ph->pkt_src(),
14: ph->pkt_seq_num(),
15: ph->pkt_len());
16: }
17: else if (newtrace_) {
18: sprintf(pt_->buffer() + offset,
19: “-P protoname -Po %d -Ps %d -Pl %d “,
20: ph->pkt_src(),
21: ph->pkt_seq_num(),
22: ph->pkt_len());
23: }
24: else {
25: sprintf(pt_->buffer() + offset,
26: “[protoname %d %d %d] “,
27: ph->pkt_src(),
28: ph->pkt_seq_num(),
29: ph->pkt_len());
30: }
31: }
우리는 위의 코드로부터 3가지 다른 트레이스 포맷들이 있다는 것을 추론할 수 있다: 태그가 붙은 트레이스들, 새로운 포맷 트레이스들 그리고 고전적인 트레이스들. 문법에 이어서 각각은, 비록 다르지만, 네가 말할 수 있는 것처럼 매우 쉽고 직관적이다. 태그가 붙은 것과 새로운 트레이스 포맷들 양쪽에는 프린트되는 중인 정보의 각각의 필드를 식별하기 위해서 사용되는 표지들이 존재한다. 우리는 소스 주소로써 “o” (기원), 시퀀스 넘버로써 “s”, 일치하는 패킷의 길이로써 “l”을 사용하기로 결정해왔다.
이것을 최근에 생성된 함수로 호출하기 위해서 우리는 trace/cmu-trace.cc 안에 format()을 바꿔야 한다.
trace/cmu-trace.cc
1: void
2: CMUTrace::format(Packet* p, const char *why)
3: {
4: /* … */
5: case PT_PING:
6: break;
7:
8: case PT_PROTONAME:
9: format_protoname(p, offset);
10: break;
11:
12: default:
13: /* … */
14: }
6.3 Tcl 라이브러리
이제 우리는 Tcl 파일들에서 몇 개의 변화들을 실행할 필요가 있다. 실제로 우리는 우리의 패킷 유형을 더하고, 묶여진 속성들을 위한 초기 값들을 주고 우리의 protoname 라우팅 프로토콜을 실행하는 무선 노드들을 생성하기 위한 요구되는 인프라스트럭처를 제공할 예정이다.
tcl/lib/ns-packet.tcl에서 우리는 다음의 코드의 위치를 정해야 하고 protoname을 목록에 더해야 한다. (우리가 줄 2에서 실행한 것처럼)
tcl/lib/ns-packet.tcl
1: foreach prot {
2: Protoname
3: AODV
4: ARP
5: # …
6: NV
7: } {
8: add-packet-header $prot
9: }
묶여진 속성들을 위한 초기 값들은 tcl/lib/ns-packet.tcl 안쪽에 주어져야 한다. 우리는 파일의 끝으로 가야하고 다음의 코드와 같이 무언가를 써야 한다:
tcl/lib/ns-default.tcl
1: # …
2: # Defaults defined for Protoname
3: Agent/Protoname set accessible_var_ true
마지막으로 우리는 tcl/lib/ns-lib.tcl을 변경해야 한다. 우리는 노드를 생성하기 위한 프로시저들을 더할 필요가 있다. 우리의 흥미는 라우팅 프로토콜로써 protoname을 가진 무선 노드를 생성하는 것을 중심으로 행해질 것이다.
프로시저 노드는 create-wireless-node 프로시저에게로 호출한다. 다른 업무들 중에서, 이 마지막 것은, 하나의 노드를 위한 그 라우팅 에이전트를 설정하려는 의도이다. 우리는 우리의 protoname 프로토콜의 인스턴스를 생성하기 위해서 이런 프로시저를 교묘히 바꿀 필요가 있다.
tcl/lib/ns-lib.tcl
1: Simulator instproc create-wireless-node args {
2: # …
3: switch -exact $routingAgent_ {
4: Protoname {
5: set ragent [$self create-protoname-agent $node]
6: }
7: # …
8: }
9: # …
10: }
그리고 나서 create-protoname-agent는 다음의 예제에서 보여지는 것처럼 아래에 코드될 것이다.
tcl/lib/ns-lib.tcl
1: Simulator instproc create-protoname-agent { node } {
2: # Create Protoname routing agent
3: set ragent [new Agent/Protoname [$node node-addr]]
4: $self at 0.0 “$ragent start”
5: $node set ragent_ $ragent
6: return $ragent
7: }
줄 3은 노드의 주소를 가진 새로운 protoname 에이전트를 생성한다. 이런 에이전트는 시뮬레이션의 처음에 시작되도록 스케쥴되고, (줄 4) 줄 5에서 노드의 라우팅 에이전트로써 할당된다.
6.4 우선순위 큐
네가 너의 시뮬레이션들에서 우선순위 큐들을 사용할 것은 매우 적당하다. 이 큐 유형은 높은 우선순위 패킷들로써 라우팅 패킷들을 취급하고, 그것들을 큐의 처음에 삽입한다. 그러나 우리는 protoname 패킷들이 라우팅 패킷들이고 그래서 높은 우선순위로써 취급된다고 PriQueue 클래스에게 말할 필요가 있다.
우리는 queue/priqueue.cc 파일에 recv() 함수를 변경해야 한다. 코드의 다음 조각에서 줄 13은 우리가 실행할 필요가 있는 유일한 변경이다.
queue/priqueue.cc
1: void
2: PriQueue::recv(Packet *p, Handler *h)
3: {
4: struct hdr_cmn *ch = HDR_CMN(p);
5:
6: if (Prefer_Routing_Protocols) {
7:
8: switch(ch->ptype()) {
9: case PT_DSR:
10: case PT_MESSAGE:
11: case PT_TORA:
12: case PT_AODV:
13: case PT_PROTONAME:
14: recvHighPriority(p, h);
15: break;
16:
17: default:
18: Queue::recv(p, h);
19: }
20: }
21: else {
22: Queue::recv(p, h);
23: }
24: }
6.5 Makefile
이제 모든 것이 구현되고 우리는 단지 그것을 컴파일할 필요가 있다! 그렇게 하기 위해서 우리는 다음의 코드 (줄 4)에서처럼 OBJ_CC 변수 안쪽에 우리의 오브젝트 파일들을 더함으로써 Makefile 파일을 편집할 것이다.
Makefile
1: OBJ_CC =
2: tools/random.o tools/rng.o tools/ranvar.o common/misc.o common/timer-handler.o
3: # …
4: protoname/protoname.o protoname/protoname_rtable.o
5: # …
6: $(OBJ_STL)
우리가 common/packet.h를 변경했지만 common/packet.cc는 변경하지 않았기 때문에 우리는 다시 컴파일되는 것을 위해서 이 마지막 파일을 “touch”해야 한다. 그런 후에 우리는 make를 실행할 수 있고 우리 소유의 라우팅 프로토콜을 즐길 수 있다. (또는 아마 모든 편집 문제들을 해결할 수 있다!)
[ns-2.27]$ touch common/packet.cc
[ns-2.27]$ make