NS Tutorial VII – Ping protocol 제작시 문제점

 NS Tutorial VII. A new protocol for ns 섹션을 그대로 따라하면 문제점이 발생한다.

 아마도…Tutorial을 쓴 시점이랑 지금의 ns2 버전이랑 맞지 않아서 생기는 문제인 것 같다.

 ping.cc 와 ping.h 을 나름대로 바꿔보았다.

 이름만 바꾸었고 나머지 부분은 모두 ping.cc, ping.h와 동일하다.

 주의해서 볼 부부은

hdr_ip* hdrip = hdr_ip::access(pkt);

hdr_pong* hdr = hdr_pong::access(pkt);

부분이다.

pong.h (Language : c)
  1.  
  2. #ifndef ns_pong_h
  3. #define ns_pong_h
  4.  
  5. #include “agent.h”
  6. #include “tclcl.h”
  7. #include “packet.h”
  8. #include “address.h”
  9. #include “ip.h”
  10.  
  11. // pong header의 구조를 선언
  12. // ret : 보내는 것(0)인지 받는 것(1)인지를 구분하기 위한 용도로 사용됨.
  13. // send_time : round-trip-time을 구하기 위한 용도로 사용됨.
  14. struct hdr_pong {
  15.     char ret;
  16.     double send_time;
  17.    
  18.     // Header acces methods
  19.     static int offset_; // required by PacketHeaderManager
  20.     // inline 함수는 처리 오버헤드를 거의 없애는 방법으로 사용됨.
  21.     // compile된 size는 커지지만 성능이 높아짐. 따라서 간단한 함수는 inline으로 정의하는 것이 효율적임
  22.     inline static int& offset() { return offset_; }
  23.     inline static hdr_pong* access(const Packet* p) {
  24.         return (hdr_pong*)p->access(offset_);
  25.     }
  26. };
  27.  
  28. // Agent를 상속받아 PongAgent 생성자와 사용될 메소드를 선언
  29. // PongAgnet() : 생성자
  30. // command() : sned일 때 사용되는 메소드
  31. // recv() : receive일 때 사용되는 메소드, 이것은 자동으로 호출됨
  32. // off_pong_ : pong header의 offset을 나타내기 위해 사용됨.
  33. class PongAgent : public Agent {
  34. public:
  35.     PongAgent();
  36.     int command(int argc, const char*const* argv);
  37.     void recv(Packet*, Handler*);
  38. };
  39.  
  40. #endif

pong.cc (Language : c)
  1.  
  2. #include “pong.h”
  3.  
  4. int hdr_pong::offset_;
  5.  
  6. // PacketHeaderClass를 상속받아 PongHeaderClass를 선언
  7. // 이미 많은 protocol header들이 PacketHeaderClass를 상속받아 정의되어 있다.
  8. // 동일한 방법으로 PacketHeader 아래에 Pong header를 둔다.
  9. // pong.h 에서 정의한 hdr_pong의 offset를 연결한다.
  10. static class PongHeaderClass : public PacketHeaderClass {
  11. public:
  12.     PongHeaderClass() : PacketHeaderClass(“PacketHeader/Pong”, sizeof(hdr_pong)) {
  13.         bind_offset(&hdr_pong::offset_);
  14.     }
  15. } class_ponghdr;
  16.  
  17. // TclClass를 상속받아 PongClass를 선언
  18. // Agent 하위에 Pong을 두기 위해 “Agent/Pong”을 지정
  19. // 이 부분은 tcl 파일을 작성할 때 “set p0 [new Agent/Pong]”와 같이 사용됨
  20. // PongAgent를 TclObject로 생성하여 반환
  21. static class PongClass : public TclClass {
  22. public:
  23.     PongClass() : TclClass(“Agent/Pong”) { }
  24.     TclObject* create(int, const char*const*) {
  25.         return (new PongAgent);
  26.     }
  27. } class_pong;
  28.  
  29. // PongAgent의 생성자를 구체적으로 정의하는 부분
  30. // 생성자에서는 C++과 Tcl을 바인드하도록 함.
  31. PongAgent::PongAgent() : Agent(PT_PONG) {
  32.     bind(“packetSize_”, &size_);
  33. }
  34.  
  35. // PongAgent의 command 메소드를 정의
  36. // 궁극적으로 이 부분은 pong packet을 send하는 부분이다.
  37. // Packet을 생성하고, pong header의 시작지점으로 이동하여,
  38. // ret를 0으로 설정하고, 시간을 설정한 다음, Packet을 전송한다.
  39. int PongAgent::command(int argc, const char*const* argv) {
  40.     if(argc == 2) {
  41.         if(strcmp(argv[1], “send”) == 0) {
  42.        
  43.             // 새로운 packet을 준비
  44.             // Creaqte a new packet
  45.             Packet* pkt = allocpkt();
  46.            
  47.             // packet의 pong header 시작 지점으로 이동
  48.             // Access the Pong header for the new packet
  49.             hdr_pong* hdr = hdr_pong::access(pkt);
  50.  
  51.             // pong header의 ret를 0으로 설정
  52.             // Set the ‘ret’ field to 0, so the receiving node
  53.             // knows that it to generate an echo packet
  54.             hdr->ret = 0;
  55.            
  56.             // pong header의 send time을 현재의 시간으로 설정
  57.             // Store the current time in the ‘send_time’ field
  58.             hdr->send_time = Scheduler::instance().clock();
  59.            
  60.             // 만들어진 packet을 전송
  61.             // Send the packet
  62.             send(pkt, 0);
  63.            
  64.             // Return TCL_OK, so the calling function knows that
  65.             // the command has been procoessed
  66.             return (TCL_OK);
  67.         }
  68.     }
  69.    
  70.     // If the command hasn’t been proceesed by PongAgent()::command,
  71.     // call the command() function for the base class
  72.     return (Agent::command(argc, argv));
  73. }
  74.  
  75. // PongAgent의 recv() 메소드를 정의
  76. // Pong Packet을 수신하였을 때 수행하는 부분을 정의
  77. void PongAgent::recv(Packet* pkt, Handler*) {
  78.     // ip header의 시작 지점으로 이동
  79.     // Access the IP header for the received packet
  80.     hdr_ip* hdrip = hdr_ip::access(pkt);
  81.    
  82.     // pong header의 시작 지점으로 이동
  83.     // Access the pong header for the received packet
  84.     hdr_pong* hdr = hdr_pong::access(pkt);
  85.    
  86.     // ret = 0 이면, 즉 처음 수신한 packet일 때 처리하는 부분
  87.     if(hdr->ret == 0) {
  88.         // packet에 있는 시간을 stime에 할당
  89.         double stime = hdr->send_time;
  90.        
  91.         // packet을 폐기
  92.         Packet::free(pkt);
  93.        
  94.         // 응답을 위한 packet을 생성
  95.         Packet* pktret = allocpkt();
  96.        
  97.         // packet의 pong header 시작지점으로 이동
  98.         hdr_pong* hdrret = hdr_pong::access(pktret);
  99.        
  100.         // packet의 ret를 1로 설정
  101.         hdrret->ret = 1;
  102.        
  103.         // packet의 send_time에 packet을 수신한 시간을 설정
  104.         hdrret->send_time = stime;
  105.        
  106.         // packet을 전송
  107.         send(pktret, 0);
  108.     } else {        // ret = 1 이면, 즉 보낸 packet이 다시 되돌아왔을 때 처리하는 부분
  109.         // 출력을 위한 변수 out을 선언
  110.         char out[100];
  111.        
  112.         // out 변수에 내용을 기록
  113.         // “이름(Agent/Pong) recv from(Source address) rtt(round-trip-time)”의 형태를 갖추어 기록
  114.         sprintf(out, “%s recv %d %3.1f”,
  115.        
  116.         // %s에 해당하는 부분(노드 이름)
  117.         name(),
  118.        
  119.         // %d에 해당하는 부분(노드 주소)
  120.         hdrip->src_.addr_ >> Address::instance().NodeShift_[1],
  121.        
  122.         // %3.1f에 해당하는 부분(응답시간)
  123.         // (현재시간 – send_time * 1000) –> round-trip-time
  124.         (Scheduler::instance().clock() – hdr->send_time) * 1000);
  125.        
  126.         // tcl instance를 생성
  127.         Tcl& tcl =Tcl::instance();
  128.        
  129.         // tcl instance로 out을 반환
  130.         tcl.eval(out);
  131.        
  132.         // packet을 폐기
  133.         Packet::free(pkt);
  134.     }
  135. }

 그리고 VII.3 Necessary changes 의

{ SRMEXT off_srm_ext_ }
{ Ping off_ping_ }} {
set cl PacketHeader/[lindex $pair 9]

 부분은 안해줘도 된다.

 해당 소스를 찾아보면 ‘tcl/lib/ns-packet.tcl’ 파일을 찾아보면

# XXX Old code. Do NOT delete for now. – Aug 30, 2000

라고 적혀있는 것을 볼 수 있다.

 즉, 건드리지 않는 것이 좋다. : )

NS-2 설치 후 라이브러리 경로 및 실행파일 경로 추가

 정상적인 NS-2 설치에 성공했다면 설치 직후에 다음과 같은 메시지가 보여진다(allinone-2.31 버전 기준)

Please put /usr/local/ns-allinone-2.31/bin:/usr/local/ns-allinone-2.31/tcl8.4.14/unix:/usr/local/ns-allinone-2.31/tk8.4.14/unix
into your PATH environment; so that you’ll be able to run itm/tclsh/wish/xgraph.

IMPORTANT NOTICES:

(1) You MUST put /usr/local/ns-allinone-2.31/otcl-1.13, /usr/local/ns-allinone-2.31/lib,
    into your LD_LIBRARY_PATH environment variable.
    If it complains about X libraries, add path to your X libraries
    into LD_LIBRARY_PATH.
    If you are using csh, you can set it like:
                setenv LD_LIBRARY_PATH <paths>
    If you are using sh, you can set it like:
                export LD_LIBRARY_PATH=<paths>

(2) You MUST put /usr/local/ns-allinone-2.31/tcl8.4.14/library into your TCL_LIBRARY environmental
    variable. Otherwise ns/nam will complain during startup.

After these steps, you can now run the ns validation suite with
cd ns-2.31; ./validate

For trouble shooting, please first read ns problems page
http://www.isi.edu/nsnam/ns/ns-problems.html. Also search the ns mailing list archive
for related posts.

 여기서 해당 라이브러리와 실행파일 경로를 path에 지정해야 다음부터 편하게 쓸수 있는데 두가지 방법이 있다.

 하나는
LD_LIBRARY_PATH 환경변수에 직접 경로를 추가하는 방법과
다른 하나는 /etc/ld.so.conf 파일에 직접 경로를 입력하고 ldconfig 명령어를 돌려주는 방법이 있다.

 어느것을 하든 관계는 없지만 만약 환경변수에 경로를 추가한다면 .profile 혹은 ,bashrc 파일에 입력하여 재부팅 혹은 재로그인 시 자동으로 인식되게끔 하는 조치가 필요하다.

Implementing a New Manet Unicast Routing Protocol in NS2 Version 0.2 번역판-4

7 레이어-2 프로토콜들로부터 정보를 받기

몇 개의 라우팅 프로토콜들은 패킷이 레이어-2로부터 보내질 수 없을 때 반응에 흥미가 있을 것이다. 이것은 우리가 아래에 설명한 것처럼, 우리의 라우팅 에이전트에 의해서 쉽게 달성될 수 있다.

그것은 어떻게 작동하는가? 패킷의 공통의 헤더는 그 패킷이 그 레이어-2 에이전트에 의해서 보내질 수 없다면 호출될 것인 함수를 네가 명시할 수 있는 필드를 가진다. 그 함수를 protoname_mac_failed_callback()이라 부르자. 우리는 그런 레이어-2 실패에 대해 반응을 담당하는 역할을 하는 라우팅 에이전트 내에 또 다른 것을 호출하는 이런 함수를 사용할 것이다. 우리는 이런 두 번째 함수를 mac_failed()로 부를 것이다. 그래서 우리는 protoname/protoname.h의 줄 9만 단지 변경해야 한다

protoname/protoname.h

1: class Protoname : public Agent {

2: /* … */

3:

4: public:

5:

6: Protoname(nsaddr_t);

7: int command(int, const char*const*);

8: void recv(Packet*, Handler*);

9: void mac_failed(Packet*);

10: };

11: #endif

protoname/protoname.cc 파일은 더 많은 변화들을 요구한다. 무엇보다도 우리는 공통의 헤더 안쪽에 등록되는 그 함수를 구현해야 한다. 그 함수는 Protoname 클래스의 mac_failed() 함수로 단순히 호출할 것이다. 너는 아래에 구현을 볼 수 있다.

protoname/protoname.cc

1: static void

2: protoname_mac_failed_callback(Packet *p, void *arg) {

3: ((Protoname*)arg)->mac_failed(p);

4: }

mac_failed()에 의해서 구현된 기능성은 protoname 규격에 매우 의존한다. 예를 들어, 코드의 다음 조각은 디버그 메시지를 프린트하고 (줄 6-9) 패킷을 버린다. (줄 11)

protoname/protoname.cc

1: void

2: Protoname::mac_failed(Packet* p) {

3: struct hdr_ip* ih = HDR_IP(p);

4: struct hdr_cmn* ch = HDR_CMN(p);

5:

6: debug(“%f: Node %d MAC layer cannot send a packet to node %dn”,

7: CURRENT_TIME,

8: ra_addr(),

9: ch->next_hop());

10:

11: drop(p, DROP_RTR_MAC_CALLBACK);

12:

13: /* … do something … */

14: }

만약 우리가 라우팅 패킷이 레이어-2 프로토콜들에 의해서 보내지지 않을 때를 알기 원한다면 우리는 send_protoname_pkt()를 변경할 필요가 있다. 유사하게 만약 우리가 데이터 패킷들에 주의하기를 원한다면 forward_data()는 가볍게 마찬가지로 변경되어야 한다. 양쪽의 경우들에서 우리는 그 패킷의 공통의 헤더를 업데이트할 때 다음의 두 개의 줄들을 단지 더해야 한다.

protoname/protoname.cc

1: ch->xmit_failure_ = protoname_mac_failed_callback;

2: ch->xmit_failure_data_ = (void*)this;

protoname_mac_failed_callback()은 무슨 경우들에서 호출될 것인가? NS-2.27에서 우리는 두 개의 다른 상황들을 설립할 수 있다.

mac/arp.cc 노드가 목적지 주소를 (ARP를 통해서) 결정하기를 원하지만 재시도들의 최대 숫자가 초과될 때

mac/mac-802_11.cc 두 개의 가능성들이 있다. 첫 번째 것은 RTS가 보내졌지만 대응하는 CTS가 받아들여지지 않았고 재시도의 최대 숫자가 초과될 때 일어날 수 있다. 두 번째 것은 데이터 패킷이 전달되었지만 절대 ACK를 받지 못했고 (받아들여진 ACK가 없다) 재시도들의 최대 숫자가 초과될 때 발생한다.

8 유선이-붙은-무선 환경들을 위한 지원

이제까지 우리는 평평한 manet들, 즉, 무선-유일한 시나리오들에 대해서만 관련이 있었다. 이번 절에서 우리는 하이브리드 manet들 (NS2 용어를 따르면, 유선이-붙은-무선 시나리오들)을 다루기 위한 기본적인 개념들을 소개할 것이다. 유선이-붙은-무선 스크립트들은 계층적인 주소를 사용할 필요가 있고, 그래서 너는 주소의 이런 유형에서 필요한 지식을 얻기 위해서 15장과 29장 [2]을 읽어야 한다.

최소의 변화들과 함께 우리는 유선이-붙은-무선 시뮬레이션들에서 우리의 프로토콜을 사용할 수 있다. 이런 것들에서는 고정된 노드들, 무선 노드들 그리고 베이스 스테이션들이 있다. 베이스 스테이션은 유선과 무선 도메인들 사이에 게이트웨이이고, 모든 무선 노드는 자신이 어느 베이스 스테이션에 결합되는지를 알 필요가 있다. 우리가 유선이-붙은-무선 지원을 제공하기 위해서 실행할 필요가 있는 모든 것은 각각의 노드를 위해 일치하는 베이스 스테이션을 설정하는 것이다.

유선이-붙은-무선 시나리오들을 기술하는 시뮬레이션 스크립트들은 각각의 모바일 노드에서 시간적으로 앞선 동작을 수행하는데, 즉, 모든 모바일 노드는 베이스 스테이션 (노드 API베이스-스테이션 함수)에 부착된다. 그러나 우리는 여러 개의 베이스 스테이션들이 사용되는 시나리오들에 관심이 있고, 우리는 모바일 노드들이 그들의 결합된 베이스 스테이션들을 동적으로 바꾸기를 원한다는 것을 상상해라. 우리가 다수의 베이스 스테이션들이 허락되는 하이브리드 ad hoc 네트워크들을 지원하는 라우팅 프로토콜을 코드하기를 원한다면 이것은 유용하다. 만약 이것이 너의 경우라면, 그 절을 계속해서 읽어라.

다음의 코드에서 보여지는 것처럼 protoname/protoname.h를 다시 편집하자. 줄들 1과 11은 더해지고, 반면에 나머지는 변하지 않는다.

protoname/protoname.h

1: #include <mobilenode.h>

2:

3: /* … */

4:

5: class Protoname : public Agent {

6:

7: /* … */

8:

9: protected:

10:

11: MobileNode* node_;

12:

13: /* … */

14: };

우리는 라우팅 에이전트가 부착되는 노드를 의미하는 (common/mobilenode.h에서 정의된) 모바일노드 오브젝트에 대한 참조를 더했다. 이런 참조를 얻기 위해서 우리는 Protoname 건설자 안쪽에 다음의 줄 4를 더할 필요가 있다.

protoname/protoname.cc

1: Protoname::Protoname(nsaddr_t id) : Agent(PT_PROTONAME), pkt_timer_(this) {

2: bind_bool(“accessible_var_”, &accessible_var_);

3: ra_addr_ = id;

4: node_ = (MobileNode*)Node::get_node_by_address(id);

5: }

모바일노드 클래스는 우리가 관심이 있는 두 개의 함수들을 소유한다. 우선 첫째로 모바일 노드가 부착되는 베이스 스테이션의 식별자를 되돌려주는, base_stn()이다. 두 번째는 그 모바일 노드를 위한 적합한 베이스 스테이션을 설립할 수 있는 set_base_stn()이다. 그래서 우리는 유선이-붙은-무선 시뮬레이션들을 이런 두 개의 함수들을 사용함으로써 다룰 수 있다. 예를 들어, 다음의 코드는 그 모바일 노드 자체가 베이스 스테이션인지를 체크하고; 만약 아니라면 그때 그것은 베이스 스테이션을 할당 받는다.

protoname/protoname.cc

1: if (node_->base_stn() == ra_addr()) {

2: // I’m a base station

3: /* … */

4: }

5: else {

6: // I’m not a base station

7: node_->set_base_stn(base_stn_addr);

8: }

앞의 예제는 결합된 베이스 스테이션을 동적으로 바꾸는 법을 보여준다. 이런 바꿈들을 수행하기 위해서 사용되는 접근들은 프로토콜 자체에 의존한다.

참조

[1] Marc Greis. Tutorial for the Network Simulator ”ns”.

http://www.isi.edu/nsnam/ns/tutorial/index.html.

[2] The VINT Project. The ns Manual, December 2003.

http://www.isi.edu/nsnam/ns/ns-documentation.html.


[1] 이것은 manet 라우팅 프로토콜들에서 일반적인 특징이지만, 그러나 실제 목적은 랜덤 숫자들을 얻는 예제를 제공하는 것이다.

[2] 실제 이것은 사실이 아니다. 사실 데이터 패킷들은 자신들의 일치하는 에이전트에게 직접 배달되고, 그래서 포트 분류자는 우리의 라우팅 에이전트에서 필요하지 않다. 그러나 우리는 이런 설명을 유지하는데 왜냐하면 NS-2.27은 라우팅 에이전트가 자신의 API의 부분으로써 port-dmux 동작 (4.3.2절을 보라)을 받아들이는 것을 요구하기 때문이다.

[3] IP 헤더에 따라서 패킷의 Time To Live

Implementing a New Manet Unicast Routing Protocol in NS2 Version 0.2 번역판-3

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

Implementing a New Manet Unicast Routing Protocol in NS2 Version 0.2 번역판-2

4.3.2 command()

코드의 다음 조각은 조금 더 복잡하다. 그것은 우리의 에이전트가 에이전트 클래스로부터 물려받는 command() 방법의 구현으로 구성된다.

protoname/protoname.cc

1: int

2: Protoname::command(int argc, const char*const* argv) {

3: if (argc == 2) {

4: if (strcasecmp(argv[1], “start”) == 0) {

5: pkt_timer_.resched(0.0);

6: return TCL_OK;

7: }

8: else if (strcasecmp(argv[1], “print_rtable”) == 0) {

9: if (logtarget_ != 0) {

10: sprintf(logtarget_->pt_->buffer(), “P %f _%d_ Routing Table”,

11: CURRENT_TIME,

12: ra_addr());

13: logtarget_->pt_->dump();

14: rtable_.print(logtarget_);

15: }

16: else {

17: fprintf(stdout, “%f _%d_ If you want to print this routing table ”

18: “you must create a trace file in your tcl script”,

19: CURRENT_TIME,

20: ra_addr());

21: }

22: return TCL_OK;

23: }

24: }

25: else if (argc == 3) {

26: // Obtains corresponding dmux to carry packets to upper layers

27: if (strcmp(argv[1], “port-dmux”) == 0) {

28: dmux_ = (PortClassifier*)TclObject::lookup(argv[2]);

29: if (dmux_ == 0) {

30: fprintf(stderr, “%s: %s lookup of %s failedn”,

31: __FILE__,

32: argv[1],

33: argv[2]);

34: return TCL_ERROR;

35: }

36: return TCL_OK;

37: }

38: // Obtains corresponding tracer

39: else if (strcmp(argv[1], “log-target”) == 0 ||

40: strcmp(argv[1], “tracetarget”) == 0) {

41: logtarget_ = (Trace*)TclObject::lookup(argv[2]);

42: if (logtarget_ == 0)

43: return TCL_ERROR;

44: return TCL_OK;

45: }

46: }

47: // Pass the command to the base class

48: return Agent::command(argc, argv);

49: }

argv[0]은 선포되는 방법 (항상 “cmd”, 3장 [2]를 보라)의 이름을 포함하고, argv[1]은 요청되는 동작이며, argv[2..argc-1]은 통과되었던 인수들의 나머지이다. 이런 함수 내에서 우리가 Tcl로부터 접근하기 쉽게 만들기를 원하는 임의의 다른 동작뿐만 아니라 몇 가지 의무적인 동작들을 코드해야 한다. 한 예로써 우리는 라우팅 표의 내용들을 트레이스 파일로 덤프하는 print_rtable로 불리는 동작을 코드할 것이다.

우리는 네가 그것들을 어떻게 처리하는 지 볼 수 있도록, 2개 또는 3개의 인수들을 가진 경우들에서만 우리의 코드의 초점을 맞춘다. 각각의 경우는 TCL_OK (만약 모든 것이 괜찮으면) 또는 TCL_ERROR (만약 임의의 에러가 발생되었다면) 둘 중의 하나를 되돌려주면서 그것의 실행을 끝내야 한다.

줄들 4-7은 우리가 항상 구현해야 하는 의무적인 명령어를 기술한다: start. 이런 명령어의 예상되는 행동은 그것의 실행을 시작하기 위한 에이전트를 구성하는 것이다. 우리의 경우에서 그것은 자신의 패킷 전송 타이머를 시작한다. 우리는 라우팅 에이전트가 자신의 동작을 시작하기 위해서 수행해야 하는 모든 요구되는 행동들을 여기에서 구현해야 한다.

줄들 8-23은 우리의 print_rtable 명령어를 구현한다. 우리는 logtarget_이 초기화되는 지를 첫 번째로 체크한다. (줄 9) 그리고 나서 우리는 줄들 10-13에 보여지는 것처럼 표를 트레이스 파일 안으로 덤프한다. 코드의 이런 조각을 이해하기 위해서 네가 trace/trace.h 헤더 파일을 조사하는 것은 유용할 것이다. 트레이스 클래스가 정의되는 곳이 있다. 그것은 BaseTrace 클래스의 pt_와 관계가 있다. 이런 마지막 클래스는 출력이 버퍼되는 변수를 얻기 위해 사용되는 buffer()와 그 버퍼에서 출력 파일로 쏟아져 나오기 위해 사용되는 dump() 함수들 각각 구현한다. 마지막으로, 줄 14는 자신 소유의 내용을 트레이스 파일 안에 쓰기 위한 우리의 라우팅 표의 print() 함수를 호출한다. 아래의 TCL 코드는 시뮬레이션 스크립트로부터 정해진 시간에 print_rtable 동작을 실행하는 법을 보여준다. ns_시뮬레이터의 인스턴스를 포함하고 node_ns_에 의해서 생성된 노드임을 가정한다. 우리는 인수로써 255를 통과시키는 중인데 왜냐하면 이것은 라우팅 에이전트가 부착되는 포트의 숫자이기 때문이다.

simulation.tcl

1: $ns_ at 15.0 “[$node_ agent 255] print_rtable”

구현하기 위한 또 다른 의무적인 명령어는 port-dmux이다. 그것의 구현은 줄들 27-37에서 제공된다. [2]의 3장에서 설명되는 것처럼, NS는 그것의 이름이 주어지는 그것들 각각에 빠른 접속을 제공하기 위해서 해시 표 안에 모든 콤파일된 오브젝트 (C++ 오브젝트)에 대한 참조를 저장한다. 우리는 그것의 이름이 주어지는 포트분류자 오브젝트를 획득하기 위해서 줄 28에서 그 편의를 이용한다.

유사하게, 그것의 이름이 주어지는 트레이스 오브젝트를 단순히 획득하는 tracetarget (우리는 게다가 그것이 log-target으로 불려지도록 허락한다는 것을 주목해라)이라 불리는 또 다른 의무적인 동작이 있다.

만약 우리가 요청되는 명령어를 처리하는 법을 알지 못한다면, 우리가 줄 48에서 실행한 것처럼, 이런 책임을 기반 클래스에 위임한다.

4.3.3 recv()

다음 함수는 recv()이고 우리가 아는 것처럼 라우팅 에이전트가 패킷을 받을 때마다 언제나 선포된다. 모든 패킷common/packet.h에서 정의된 hdr_cmn이라 불리는 공통의 헤더를 가진다. 이런 헤더에 접속하기 위해서 우리 소유의 패킷 유형을 위해서 우리가 전에 정의했던 것과 같은 매크로가 있고, 우리는 그것을 줄 3에서 사용한다. 줄 4는 같은 것을 실행하지만 ip.h 안에 기술된, hdr_ip, IP 헤더를 얻기 위한 것이다.

1: void

2: Protoname::recv(Packet* p, Handler* h) {

3: struct hdr_cmn* ch = HDR_CMN(p);

4: struct hdr_ip* ih = HDR_IP(p);

5:

6: if (ih->saddr() == ra_addr()) {

7: // If there exists a loop, must drop the packet

8: if (ch->num_forwards() > 0) {

9: drop(p, DROP_RTR_ROUTE_LOOP);

10: return;

11: }

12: // else if this is a packet I am originating, must add IP header

13: else if (ch->num_forwards() == 0)

14: ch->size() += IP_HDR_LEN;

15: }

16:

17: // If it is a protoname packet, must process it

18: if (ch->ptype() == PT_PROTONAME)

19: recv_protoname_pkt(p);

20: // Otherwise, must forward the packet (unless TTL has reached zero)

21: else {

22: ih->ttl_–;

23: if (ih->ttl_ == 0) {

24: drop(p, DROP_RTR_TTL);

25: return;

26: }

27: forward_data(p);

28: }

29: }

우리가 해야 하는 첫 번째 것은 우리가 우리 자신에게 전송했던 패킷을 받는 중이 아님을 체크하는 것이다. 만약 그것이 그 경우라면, 우리가 줄들 8-11에서 실행한 것처럼, 패킷을 버려야 하고 되돌려 보내야 한다. 게다가, 만약 그 패킷이 (노드의 상위 레이어들에 의해서) 노드 내에서 발생되었다면 우리는 라우팅 프로토콜이 (바이트 단위로) 더하는 중인 그 오버헤드를 패킷의 길이에 더해야 한다. 줄들 13-14에서 보여지는 것처럼, 우리는 protoname이 IP 위에서 동작한다고 가정한다.

받아들여진 패킷이 유형 PT_PROTONAME일 때 그때 우리는 그것을 처리하기 위해서 recv_protoname_pkt()를 호출할 것이다. (줄들 18-19) 만약 TTL[3]이 0에 도달하지 않았다면, 만약 그것이 데이터 패킷이라면 그때 우리는 그것을 (만약 그것이 다른 노드로 예정된 것이라면) 전송해야 하거나 (만약 그것이 브로드캐스트 패킷이었거나 우리 자신에게로 예정되었던 것이라면) 상위 레이어들에게 그것을 전달하기 위해서 전송해야 한다. 줄들 21-28은 forward_data() 함수의 사용하는 것을 단지 기술했던 것을 실행한다.

너는 drop() 함수가 패킷을 버리기 위해서 사용된다는 것을 깨달았을 것이다. 그것의 인수들은 패킷 자체로 향하는 포인터이고 그것을 버리기 위한 이유를 주는 상수이다. 이런 상수들이 여러 개가 존재한다. 너는 파일 trace/cmu-trace.h에서 그것들을 볼 수 있다.

4.3.4 recv_protoname_pkt()

라우팅 에이전트가 protoname 패킷을 받았고, 선포되기 위한 recv_protoname_pkt()를 만들었다고 가정하자. 이런 함수의 구현은 구체적인 프로토콜에 따라서 많이 변할 것이지만, 우리는 다음 예제에서 일반적인 설계를 볼 수 있다.

줄들 3-4는 평소대로 IP 헤더와 protoname 패킷 헤더를 얻는다. 그 후에 우리는 줄들 8-9에서 소스와 목적지 포트들이 RT_PORT임을 확인한다. 이 상수는 common/packet.h안에 정의되고 그것은 255와 같다. 이 포트는 라우팅 에이전트를 붙이기 위해서 예약된다.

그 후에, protoname 패킷은 우리의 라우팅 프로토콜의 규격에 따라서 처리되어야 한다.

마지막으로 우리는 줄 14에서 우리가 실행한 것처럼 자원들을 해제해야 한다.

1: void

2: Protoname::recv_protoname_pkt(Packet* p) {

3: struct hdr_ip* ih = HDR_IP(p);

4: struct hdr_protoname_pkt* ph = HDR_PROTONAME_PKT(p);

5:

6: // All routing messages are sent from and to port RT_PORT,

7: // so we check it.

8: assert(ih->sport() == RT_PORT);

9: assert(ih->dport() == RT_PORT);

10:

11: /* … processing of protoname packet … */

12:

13: // Release resources

14: Packet::free(p);

15: }

4.3.5 send_protoname_pkt()

우리는 4.2절에서 우리의 맞춤 타이머가 그것이 만료가 될 때마다 send_protoname_pkt() 함수를 어떻게 호출하는 지를 보았다. 우리는 아래에 이런 함수의 견본 구현을 보여준다. 물론 각각의 프로토콜은 무언가를 다르게 요구하고 이것은 단지 예제일 뿐이다.

protoname/protoname.cc

1: void

2: Protoname::send_protoname_pkt() {

3: Packet* p = allocpkt();

4: struct hdr_cmn* ch = HDR_CMN(p);

5: struct hdr_ip* ih = HDR_IP(p);

6: struct hdr_protoname_pkt* ph = HDR_PROTONAME_PKT(p);

7:

8: ph->pkt_src() = ra_addr();

9: ph->pkt_len() = 7;

10: ph->pkt_seq_num() = seq_num_++;

11:

12: ch->ptype() = PT_PROTONAME;

15

13: ch->direction() = hdr_cmn::DOWN;

14: ch->size() = IP_HDR_LEN + ph->pkt_len();

15: ch->error() = 0;

16: ch->next_hop() = IP_BROADCAST;

17: ch->addr_type() = NS_AF_INET;

18:

19: ih->saddr() = ra_addr();

20: ih->daddr() = IP_BROADCAST;

21: ih->sport() = RT_PORT;

22: ih->dport() = RT_PORT;

23: ih->ttl() = IP_DEF_TTL;

24:

25: Scheduler::instance().schedule(target_, p, JITTER);

26: }

패킷을 보내기 위해서 우리는 첫 번째 그것을 할당할 필요가 있다. 우리는 그것을 위해서 allocpkt() 함수를 사용한다. 이 함수는 모든 에이전트들을 위해서 정의된다. 그리고 나서 우리는 평소대로 공통의, IP와 protoname 패킷 헤더들을 얻는다. (줄들 3-6) 우리의 목표는 우리가 원하는 값들로 모든 이런 헤더들을 채우는 것이다.

Protoname 패킷 헤더는 줄들 8-10에서 채워진다. 우리의 단순한 예제에서 우리는 단지 에이전트의 소스 주소, 메시지의 (바이트 단위로) 길이 그리고 시퀀스 넘버를 필요로 한다. 이런 필드들은 protoname의 패킷 규격에 완전히 의존적이다.

NS에서 공통의 헤더는 여러 개의 필드들을 가진다. 우리는 우리가 흥미가 있는 그런 것들에만 초점을 맞춘다. (줄들 12-17) 우리는 패킷 유형을 protoname 패킷으로 설정할 필요가 있다. (줄 12) 우리는 줄 13에서 패킷 방향을 또한 할당한다. 우리가 패킷을 보내는 중이기 때문에, 그것은 hdr_cmn::DOWN 상수에 의해서 의미되는, 아래로 가는 중이다. 패킷의 크기는 줄 14에서 주어진다. 그것은 바이트 단위이고 이것은 NS2 계산들을 위해 사용되는 값이다. 우리가 의도하는 것은 너의 hdr_protoname_pkt struct의 실제 크기는 문제가 되지 않는다는 것이다. 전달 딜레이와 같은 것들을 계산하기 위해서 NS2는 네가 여기 안에 넣는 값을 사용할 것이다. 공통의 헤더와 함께 계속해서, 줄 15에서 우리는 전달에서 어떤 에러를 갖지 않는다고 결정한다. 줄 16은 패킷이 보내져야 하는 쪽으로 다음 홉을 할당한다. 이것은 매우 중요한 필드이고, 우리의 프로토콜에서 그것은 IP_BROADCAST로써 설립되는데 왜냐하면 우리는 그 이웃하는 노드들 모두가 이런 제어 패킷을 받기를 원하기 때문이다. 그 상수는 common/ip.h에서 정의되고 너는 다른 매크로들을 위해서 거기서 체크할 수 있다. 우리가 채우는 그 마지막 필드는 주소 유형이다. 그것은 NS_AF_NONE, NS_AF_ILINK 또는 NS_AF_INET이 될 수 있다. (common/packet.h를 보라) 우리는 NS_AF_INET을 선택하는데 왜냐하면 우리는 인터넷 프로토콜을 구현하는 중이기 때문이다.

이제 우리는 IP 헤더의 구성을 계속한다. 그것은 줄들 19-23에서 볼 수 있는 것처럼 매우 단순하다. common/ip.h에서 정의되고 IP 패킷들을 위한 초기 TTL 값을 의미하는 IP_DEF_TTL로 불리는 새로운 상수가 있다. 그 IP 헤더는 IPv6 시뮬레이션들을 위해 사용되는 다른 필드들을 가지지만, 우리는 우리의 예제를 위해서 그것들이 필요하지 않다.

이제 우리는 패킷을 보내는 것을 단지 계속할 수 있다. 패킷들은 이벤트들이고 ([2]의 12장을 보라) 그래서 그것들은 스케쥴될 필요가 있다. 사실, 패킷을 보내는 것은 그것을 정해진 시간에서 스케쥴하는 것과 동등하다. 줄 25는 몇 개의 지터를 소개하는 패킷을 전송하는 법을 보여준다. 그 패킷 클래스는 커넥터 클래스로부터 물려받는데, 이것은 target_으로 불리는 TclObject와 관계가 있다. 이것은 그 이벤트를 처리할 것인 핸들러이고 인수로써 schedule() 함수에게로 통과된다.

4.3.6 reset_protoname_pkt_timer()

우리의 패킷 전송 타이머는 그것 자체를 다시 스케쥴하기 위한 다른 콜백 (4.2절)을 수행한다. 그것은 함수 reset_protoname_pkt_timer()에서 실행된다. 우리는 pkt_timer_가 5초 후에 기한 만료가 되도록 다시 스케쥴되는, 다음 예제에서 그것을 보여준다.

protoname/protoname.cc

1: void

2: Protoname::reset_protoname_pkt_timer() {

3: pkt_timer_.resched((double)5.0);

4: }

4.3.7 forward_date()

지금까지 우리는 protoname 패킷들에 주로 초점을 맞춰왔지만, 데이터 패킷들을 다룰 시간이다. forward_data() 함수는 패킷이 상위-레이어 에이전트들에게 배달되어야 하는지 또는 다른 노드에게 전송되어야 하는 지를 결정한다. 우리는 줄들 6-10에서 첫 번째 경우를 체크한다. 그것이 안으로 들어오는 패킷이고 목적지 주소가 노드 자체이거나 브로드캐스트라면, 그때 우리는 안으로 들어오는 패킷을 받아들이기 위해서 (만약 우리가 그것이 포트분류자 오브젝트임을 기억한다면) 노드의 dmux_를 사용한다.

만약 그렇지 않으면, 우리는 패킷을 전송해야 한다. 이것은 우리가 줄들 12-28에서 실행한 것처럼 공통의 헤더를 적당히 설정함으로써 달성된다. 만약 그 패킷이 브로드캐스트 패킷이라면, 그때 다음 홉은 적절히 채워질 것이다. 만약 아니라면, 우리는 다음 홉을 알아내기 위해서 우리의 라우팅 표를 사용한다. (줄 17) 우리의 구현은 목적지 주소로 향하는 루트가 없을 때 IP_BROADCAST를 되돌려준다. 그런 경우에서 우리는 디버그 메시지를 프린트하고 (줄들 19-22) 패킷을 버린다. (줄 23) 만약 모든 것이 괜찮다면 그때 우리는 줄 29에서 실행한 것처럼 패킷을 전송할 것이다.

protoname/protoname.cc

1: void

2: Protoname::forward_data(Packet* p) {

3: struct hdr_cmn* ch = HDR_CMN(p);

4: struct hdr_ip* ih = HDR_IP(p);

5:

6: if (ch->direction() == hdr_cmn::UP &&

7: ((u_int32_t)ih->daddr() == IP_BROADCAST || ih->daddr() == ra_addr())) {

8: dmux_->recv(p, 0.0);

9: return;

10: }

11: else {

12: ch->direction() = hdr_cmn::DOWN;

13: ch->addr_type() = NS_AF_INET;

14: if ((u_int32_t)ih->daddr() == IP_BROADCAST)

15: ch->next_hop() = IP_BROADCAST;

16: else {

17: nsaddr_t next_hop = rtable_.lookup(ih->daddr());

18: if (next_hop == IP_BROADCAST) {

19: debug(“%f: Agent %d can not forward a packet destined to %dn”,

20: CURRENT_TIME,

21: ra_addr(),

22: ih->daddr());

23: drop(p, DROP_RTR_NO_ROUTE);

24: return;

25: }

26: else

27: ch->next_hop() = next_hop;

28: }

29: Scheduler::instance().schedule(target_, p, 0.0);

30: }

31: }