반응형

OS: Ubuntu 18.04
Simulator: ns-3.30

 

ns-3에서 무선 채널을 통해 두 노드가 서로 unicast로 패킷을 보낼 때, 시뮬레이션 타임을 기준으로 완전히 똑같은 시간에 패킷을 보내면 (즉, ns3::Socket::Send 함수를 똑같은 시간에 사용하면) 충돌이 발생한다.
이것을 방지하려면 패킷을 보낼 때 의도적으로 약간의 지터(jitter)를 부여하면 되는데, 예를 들면 두 노드가 ns3::Socket::Send를 call할 때, 수십~수백 마이크로초(microseconds) 정도의 차이만 있어도 무선 채널에서 충돌로 인한 패킷 유실을 피할 수 있다.

이를 위해 각 노드에서 Socket::Send 함수를 call 하는 부분 앞에 매번 지터를 아래와 같이 넣어 주었다.

Ptr<UniformRandomVariable> rng = CreateObject<UniformRandomVariable> ();
uint32_t jitter = rng->GetInteger(0, 1000);
Simulator::Schedule(jitter, &패킷_보내는_함수, 함수 파라미터, ...);

문제는 위와 같이 지터를 넣어 주는데도 영문도 모르게 패킷이 전달이 안되는 것이다.

분명히 동일한 시간에 패킷을 보내지만 않으면 서로 모두 전달이 되는데도 불구하고, bootstrap처럼 자동으로 주기적으로 heartbeat 메세지를 보내도록 설정하면, 패킷이 이유 없이 사라지는 것이었다. 보내는 노드에서 Socket::Send 함수를 call 한 기록만 있고 패킷을 받은 노드가 하나도 없는 상황...

 

UniformRandomVariable 클래스를 다시 확인해 보니, GetInteger 함수는 "다음(next)" 랜덤 값을 반환한다고 되어 있다. 그 말은, 랜덤 숫자의 배열을 미리 생성해 놓고, 맨 첫번째 인덱스부터 시작해서 GetInteger 함수를 call할 때마다 순서대로 하나씩 반환한다는 뜻이다.

내가 동시에 모든 노드가 시뮬레이션 시작 시간에 동시에 heartbeat 메세지를 생성해서 broadcast하도록 스케줄링을 걸었고, 그 스케줄링 함수 안에 Socket::Send 함수 앞에 GetInteger를 썼다. 내가 랜덤에 시드 값을 지정하지 않았기 때문에, 모든 노드가 같은 타이밍에 GetInteger를 맨 처음 불러왔다면, 모두가 똑같은 jitter 숫자를 가져온다는 뜻이므로, 랜덤을 잘못 적용한 셈이다. ㅠㅠ

 

각 노드별로 노드 ID 숫자를 seed로 입력해 주었더니, 모두가 처음으로 받아 오는 GetInteger 값이 다 다른 숫자가 되었다. 이제서야 진짜 랜덤으로 작동한 것이다.

#include "ns3/random-variable-stream.h"

// ...(중략)

SeedManager::SetSeed(node_id + 1); // ns-3에서 node id는 0부터 시작하고, 

                                                                            // Seed 값에 0이 들어가면 런타임 에러가 나서 1을 더해 줬다.

Ptr<UniformRandomVariable> rng = CreateObject<UniformRandomVariable> ();

 

 

반응형
블로그 이미지

Bryan_

,
반응형

OS: Ubuntu 16.04

ns-3 version: 3.26


ns-3에서 기존의 리눅스에서 돌아가던 코드를 포팅하는 과정에서, UDP (또는 TCP) 소켓을 열고 패킷을 보내는 코드를 그대로 옮겨 오면서 ns-3 클래스 중 하나인 Ptr<Socket>을 직접 만들어서 패킷을 보내도록 만들어 두었다.


그런데, 이상하게 1초마다 보내는 패킷의 개수를 대폭 늘리기만 하면 (일부러 링크가 saturated 되도록 만들고 경로 변경을 유도하게 만들어야 했다), 시뮬레이션 타임이 끝나기 전에 이유 없이 시뮬레이션이 아주 느려지면서 몇 시간씩 걸리는 것이었다. 에러 메세지는 하나도 없었고, 메모리 문제라도 있었으면 Segmentation fault라도 떠서 디버그를 했을 텐데 그런 문제도 없었고, 실제로 몇 시간씩 걸리더라도 메모리 사용량이 그에 비례해서 늘어나지도 않고 오로지 CPU 사용량만 100%를 찍고 있었다.



결론부터 얘기하자면, 원인은 Socket 오브젝트의 잘못된 사용이었는데, 소켓을 열고 나서 하나도 닫지 않아서 발생한 문제였다. 나는 ns3::Socket 오브젝트를 생성하는 함수를 따로 만들고, 그걸 원하는 조건에서 보내면서 나에게 필요한 통계 처리를 하기 위해서 아래와 같은 간단한 함수를 정의해서 코드 이곳저곳에서 사용하였다.


void sendPacket (Ptr<Socket> socket, Ptr<Packet> packet) {

        socket->Send(packet);

        // 특정 통계 내는 작업

}



이미 언급했듯이 위의 함수에서 결정적인 실수를 하나 했는데, sendPacket 함수를 call 하기 전에 매번 socket을 새로 생성하도록 해 놓고는 잊고 socket을 한 번도 닫지 않은 것이었다. 소켓을 하나만 열고 재활용을 한다면 위의 함수가 문제가 없지만, 매번 소켓을 새로 생성한다면 위의 함수에서 socket->Close(); 를 반드시 추가해야 하는데 그러지 못했었다.


즉, 시뮬레이션 타임이 끝날 때까지 내가 직접 열어 놓은 모든 소켓이 하나도 안 닫히고 끝까지 열려 있었고 시뮬레이션 규모가 작을 때에는 그게 문제가 되지 않다가, 보내는 패킷 개수를 대폭 늘렸더니 에러는 없지만 시뮬레이션을 마치는 데 걸리는 실제 시간이 너무 오래 걸리는 것이었다. 내 컴퓨터에서 40초짜리 point-to-point 네트워크 환경에 대한 시뮬레이션을 돌리는 데 6-7시간이 걸렸다.


처음에 원인을 모를 때에는 내가 file I/O를 너무 많이 열어 놔서 그런가 하는 생각에 특정 로그를 기록하기 위해 열어 놓은 모든 파일 포인터를 다 주석처리해 보기도 했고, 콘솔 화면에 찍히는 메세지를 다 없애 보기도 했고, 랜덤 숫자 생성을 너무 자주 하는 건가 해서 그 부분까지 랜덤을 쓰지 않도록 했는데도 여전히 특정 시간대에서 거의 진행을 하지 못하고 한참을 멈춰 있는 것이었다.


그리고 그 때의 CPU 사용량은 코어 1개를 100% 사용하고 있었는데, 내가 parallel processor (mpi)를 쓰지 않았기 때문에 ns-3가 내부적으로 엄청나게 많은 정체불명의 연산을 하고 있다는 추측밖에 들지 않았다. 그 때에 메모리 사용량이 같이 늘어났다면 내가 뭔가 데이터 구조를 잘못 관리했다거나, 버그가 있어서 데이터가 무한정 늘어난다거나 하는 추측이라도 했을 텐데 메모리 사용량도 늘어나지 않았다.


시뮬레이션 전체 시간 40초 중에서 유독 33초 전까지는 1분도 안돼서 진행을 하다가, 그 뒤부터 갑자기 원인 불명으로 느려지면서 6시간을 소비하는 것이 이상해서, 보내는 패킷의 개수를 줄였더니 전체 시뮬레이션이 1분 만에 잘 끝나는 것이었다.


생각해 보니, 약 15~20초 사이에 4개의 트래픽을 생성하고, 매 초마다 250~300개 정도의 패킷을 보내도록 했더니 33~34초 정도에서 항상 멈추는 것이 확인되었고, 그 중에서 똑같은 링크를 통해서 3개의 트래픽이 동시에 지나가는 영역이 있는데, 시뮬레이션 시간으로 약 33초 정도면 그 링크를 지나가는 패킷이 약 15000~20000개쯤 되는 것 같았다.


그러면 ns-3가 실제 TCP/IP 프로토콜과 transport 작동을 그대로 구현했기 때문에 소켓을 하나 열면 보내는 쪽에서는 50001~65535 중에서 아직 다른 소켓에 bind되지 않은 하나의 랜덤 숫자를 찾아서 소켓을 열고, 패킷을 보내는 방식으로 작동할 것이므로, 15535개 이상의 소켓을 열기 시작하면 bind할 포트 번호를 찾지 못할 것이다.


아...

이런 상황에서 실제 컴퓨터는 소켓이 bind를 못한다던지 하는 오류를 냈을 텐데 ns-3는 아무 오류도 없이 bind될 소켓을 찾느라 계속 기다리기만 했던 것이었다. 그런데 bind할 소켓이 하나도 없었을 텐데 실제 시간으로 6시간쯤 걸려서 결국 패킷을 다 보내기는 했다는 것도 신기하네. Timeout 같은 게 설정되어 있어서 그랬을까?


앞으로는 socket->Send를 쓸 때에는 꼭 socket->Close를 잊지 말아야겠다.



반응형
블로그 이미지

Bryan_

,
반응형

OS: Ubuntu 16.04 (amd64)

Version: ns-3.26

gcc: 4.8.4



*결론부터 말하면, 소스 파일 확장자를 반드시 .cc 로 맞추자.



ns-3 에서 바로 시뮬레이션 코드를 테스트할 때 scratch 디렉토리에 소스코드를 집어넣으면 되고, 이 때 객체지향 컨셉을 적당히 쓰기 위해 하나의 클래스를 독립된 헤더와 소스 파일로 만드는 경우가 있다.


헤더와 소스 파일을 만들 때, [클래스 이름].h, [클래스 이름].cc 로 맞춰서 만들어야 하는데, 실수로 소스 파일 확장자를 cc가 아닌 cpp 또는 그외 다른 확장자로 쓰게 되면 ns-3의 waf 스크립트에서 소스 파일만 인식을 못해서 빌드 과정에서 "undefined reference to X" 에러가 발생한다.


문제는 이클립스 CPP 환경에서 ns-3를 프로젝트로 로드해 와서 scratch 폴더 내부에 클래스를 생성할 때에는 소스 파일의 확장자가 .cc이든 .cpp이든 아무 경고 메세지가 발생하지 않는다. 나도 이클립스 CPP에서 New > Class 메뉴를 통해서 자동으로 클래스를 만들면서 소스 파일 확장자가 .cpp로 만들어지는 것을 미처 인지하지 못해서 이 링크 에러를 보게 되었다.


ns-3.26/build/scratch 디렉토리를 살펴 보니 아예 내가 만든 클래스에 대한 오브젝트 파일(.o) 파일이 존재하지 않았다. 그 원인은 ns-3.26/wscript 파일에서 소스 파일을 자동으로 불러올 때 .cc 파일만 인식하게 되어 있기 때문이다.


(ns-3.26/wscript 파일 일부)

...(앞부분 생략)...

elif filename.endswith(".cc"):

name = filename[:-len(".cc")]

...(이하 생략)



wscript 파일을 고쳐서 cpp 파일도 자동 인식하도록 만들어도 되지만, 일단 ns-3에서 scratch 디렉토리에 대해서 미리 정해져 있는 컨벤션이고, 나중에 시뮬레이션 코드만 다른 컴퓨터의 다른 ns-3 버전에서 돌려야 할 때 같은 문제가 또 발생하게 되므로, 그냥 소스 파일을 .cc로 맞추는 것이 안전한 방법이 되겠다.



반응형
블로그 이미지

Bryan_

,
반응형

네트워크 시뮬레이터로로 예전부터 1년반쯤 전까지는 QualNet을 써 오다가, 작년부터는 줄곧 ns-3만 쓰고 있는데, 둘 사이에 장단점을 간단히 비교해 볼 수 있을 것 같았다.


주의사항: 주관적인 관점이라서 다른 사용자 입장에서는 의견이 다를 수 있음.




<비용>

QualNet : ns-3 (압승)


*QualNet

 - 대학교에서 University license로 기본 라이브러리만 구매하면 약 1000만원

 - 기본 제공이 안되는 라이브러리 추가 시 약 100~250만원 정도 예상 (LTE, Zigbee 같은 것들이 여기에 해당 ㅜㅜ)

 - 한 번 라이선스 구입하면 무제한 사용

 - 대신 버전 업그레이드 기간에는 제한이 있음. 문제는 시간이 지날 수록 최신 버전을 써야 할 필요가 생긴다는 것. 2010년쯤에 구입했던 5.1 버전에는 기본 제공하는 와이파이 표준이 802.11a/b/g 밖에 없어서 802.11n이나 ac를 테스트할 수가 없었다. 구 버전 사용자가 최신 버전을 쓰려면 본사에 1000달러를 내면 된다고 한다.

 - 멀티코어를 잘 지원해서 시뮬레이션 성능을 높일 수는 있는데, 라이선스 하나에 코어 2개가 최대임. 코어 수 늘리려면 라이선스 더 사야 함(...)


*ns-3

 - 무료 (오픈소스)

 - 버전 업그레이드 되면 그냥 새로 다운받아서 쓰면 됨

 - 오픈소스라서 실시간으로 수정되는 코드를 버전 관리 시스템에서 바로 받아쓸 수도 있다. (물론 그만큼 버그에는 취약. 공식 릴리즈 공지되는 버전만 써도 무방)




<그래픽 유저 인터페이스(GUI)>


QualNet (압승) : ns-3


*QualNet

 - ns-3에 비해 월등히 좋음.

 - 마우스 클릭만 가지고 가장 기본적인 시뮬레이션 실행이 가능한 수준 ㄷㄷ.

 - 파워포인트에서 도형 배치하고 선 긋듯이 노드들 배치하고, 트래픽은 보내는 곳과 받는 곳 사이에 선을 drag & drop으로 죽 그어 주면 만들어짐. 그 선을 더블클릭해서 패킷 사이즈, 보내는 양, 시작시간 등등 다 설정하면 됨. 각 노드도 더블클릭해서 각각의 네트워크 인터페이스, 프로토콜, IP주소, 그외 각종 설정들 다 바꿀 수 있음.

 - 그림 그리기(?)를 끝내고 시뮬레이션 시작(play 버튼)을 누르면 노드와 노드 사이에 패킷 전달되는 과정, 노드의 움직임, 무선일 경우에는 전송 범위까지 실시간으로 시뮬레이션 시간에 맞춰서 다 표시됨. 보여주는 정보의 종류를 조정할 수 있고, 재생되는 속도도 조정 가능. (물론 최고속도는 컴퓨터의 하드웨어 성능에 비례함)

 - 시뮬레이션 끝나면 결과를 그래프로 볼 수 있는데, L1/L2/L3/L4/어플리케이션 계층 각각에 대해서 통계치를 다 볼 수 있음. 예를 들어 PHY 계층에서 signal 발생시킨 수와 에러율 확인, MAC 계층에서 프레임 전송 수와 실패 재전송 수, 라우팅 계층의 경로 탐색 시도 수, 응용 계층에서의 실제 throughput 이 모든 것을 모든 노드에 대해서 다 확인 가능.

 - 물론 GUI만으로 논문 실험에 쓸 만한 자기만의 방법을 설정해줄 수는 없고, 그 부분은 어쩔 수 없이 코드 수정을 해야 함.


*ns-3

 - GUI가 큰 의미가 없음 (...)

 - 애초에 ns-3에는 시뮬레이션 환경 자체를 설정하는 GUI가 존재하지 않음.

 - C++ 코드로 일단 뭐가 됐든 시뮬레이션을 돌려 본 뒤에야 자신이 만든 네트워크 토폴로지가 어떻게 생겼는지 GUI에서 확인 가능. 왜냐하면 ns-3에서 제공하는 NetAnim 이라는 GUI는 실시간으로 시뮬레이션을 시각화하는 것이 아니고, 시뮬레이션 결과 파일을 읽어들여서 재생하는 역할이기 때문. (스타크래프트의 지난 경기 리플레이로 보는 것과 차이가 없음)

 - 앞으로도 눈으로 보는 대로 시뮬레이션 환경을 만들 수 있도록 돕는 GUI가 나올 확률은 낮음. ㅜㅜ 왜냐하면 퀄넷은 시뮬레이션 환경 자체는 별도의 스크립트 파일로 처리하고, 실제 L1/L2/L3/L4 계층의 행동은 C/C++ 코드로 처리하기 때문에 GUI에서 스크립트 파일을 만들어 주는 것이 가능한 반면에, ns-3는 그냥 모두 다 C++로 코딩해야 되기 때문에.

 - 그나마 ns-3 입장에서 위안이 되는 점은, 토폴로지를 눈으로 확인할 필요가 없고 환경을 조금씩 바꾸면서 수많은 반복 실험을 해야할 때가 되면 GUI를 쓸 필요 없이 커맨드 라인에서 배치(batch)를 돌리게 되니까 퀄넷이나 ns-3나 별 차이가 없게 될 것이라는 점. (퍽이나 ㅠㅠ)




<시뮬레이션 성능(scalability, running time 측면)>


QualNet (우세) : ns-3


*QualNet

 - 애초에 퀄넷이 처음부터 내세우는 장점이 scalability이고, 기본적으로 모든 코드에 MPI 적용이 되도록 만들었기 때문에 (사용자가 새로 만드는 코드까지 전부) 멀티 프로세서 지원의 편의 측면에서 ns-3를 압도함.

 - QualNet 판매하는 회사 이름도 심지어 Scalable Networks임 (...)

 - GUI에서는 시뮬레이션 시작 버튼 옆에 프로세서 개수 칸이 있는데 그냥 숫자 써주면 끝.

 - 커맨드 라인에서는 "-mp 2"라고 쓰면 알아서 듀얼코어 써서 돌림.

 - 그냥 싱글코어로 돌려도 꽤 빠른 편임.

 - 다만 GUI에서 실시간으로 시뮬레이션 진행 중인 애니메이션 화면을 봐 가면서 실행하면 당연히 느림. (...) GUI에서 눈으로 확인이 되었으면 커맨드 라인에서 돌려야 함.

 - 유일한 단점이 있다면, 코어 개수도 현질을 해야 3개 이상 쓸 수 있다는 점.

 - 또한 라이선스 때문에 한번에 여러 머신에서 여러 프로세스를 돌리는 것이 불가능함. (라이선스 서버 1개가 라이선스 1개를 프로세스 1개에 할당하는 방식이고, 그 동안에는 다른 프로세스가 실행될 수 없는 구조 ㅠㅠ 동시에 여러 머신에서 시뮬레이션을 돌리려면 머신 개수만큼 라이선스가 있어야 함.)


*ns-3

 - ns-3도 퀄넷처럼 이벤트 기반 처리 방식으로 시뮬레이션을 실행하고, MPI 라이브러리를 통해서 멀티코어를 사용할 수 있기 때문에, 구조적인 측면에서는 퀄넷과 큰 차이가 없음. 하지만 사용의 편의가 떨어지는 게 문제.

 - ns-3에서 멀티코어를 쓰려면 사용자가 수동으로 mpi 관련 라이브러리를 미리 설치해야 하고, 자신의 시뮬레이션 코드에 MPI를 쓰겠다는 설정을 또 별도로 작성해 줘야 함. 단순히 argument에 숫자 추가만 하면 되는 QualNet에 비해 쓰기 어려울 수밖에 없음. (인터넷에도 MPI 적용이 안돼서 질문하는 글이 많음)

 - 모든 코드가 다 MPI가 되는 것도 아님. 특히 와이파이 같은 무선 쪽은 MPI가 아직 안돼서 무조건 싱글코어로 돌려야 함. 이게 특히 심각해지는 부분이 ns-3가 가상 머신들을 연동해서 돌아갈 때.


 - 다만 ns-3가 퀄넷에 비해 유리한 상황이 있기는 한데, 똑같은 시뮬레이션 인스턴스를 1개가 아니라 조건이 조금씩 다른 수백~수천 개의 시나리오를 순차적으로 처리해야 할 때. 퀄넷은 라이선스 1개당 1개 프로세스만 존재할 수 있지만, ns-3는 오픈소스니까 그런 거 없다. 원하는 만큼 클러스터에 복제해서 원하는 만큼 얼마든지 프로세스를 돌릴 수 있음. 즉, 개별 시뮬레이션 처리 시간이 오래 걸리는 문제를 동시에 여러 머신과 프로세스를 써서 얼마든지 전체 시뮬레이션 시간을 줄일 수 있음.




<외부 프로그램과의 확장성>


QualNet : ns-3 (우세)


*QualNet

 - 외부 프로그램에서 패킷을 만들어서 퀄넷의 시뮬레이션 네트워크에 주입하는 것이 가능한데, 방법이 편하지 않음. 외부 프로그램과 퀄넷 사이에 패킷을 서로 전달해 주는 코드를 하나 더 만들어야 함. (이것도 C언어로)


*ns-3

 - 리눅스 컨테이너(lxc)를 써서 VM을 만들어서 그 VM들 사이의 네트워크를 ns-3가 시뮬레이션할 수 있음!!

 - ns-3 시뮬레이션 코드 위에 아예 실제로 작동하는 프로그램 소스코드를 그대로 컴파일해서 돌리는 것(Direct Code Execution; DCE)도 가능!!


 - 그러나 위 2개 다 아직 한계가 있음 ㅠㅠ

 - VM 방식은 호스트 머신(리눅스)에 브릿지 인터페이스에 tap을 붙여서 ns-3와 연결되는데, 여기서 약간씩 딜레이가 발생함. VM들은 자기들만의 clock을 갖고 있는데 ns-3에 약간이나마 늦게 패킷이 흘러들어오면 그걸 ns-3 프로세스가 시뮬레이션으로 처리하고 다시 VM들에게 돌려주는 과정에서 각 VM은 이미 자기 시간이 흘러가고 있음. 결국 실제 환경에 비해서 delay가 커질 수밖에 없는 구조.

 - 게다가 설상가상으로, 와이파이 같은 무선 네트워크 환경에 저렇게 VM을 붙이면 ns-3가 아직(2017.04.15 기준) 무선 환경을 멀티코어로 처리하지 못하기 때문에 무선 환경의 신호 세기, 간섭, fading, propagation 이런 것들을 다 싱글코어에서 계산해야 됨(......)

 - 그래서 VM 10개를 만들고 와이파이 애드혹 네트워크를 시뮬레이션했더니, 각자 1초에 하나씩 hello 메세지를 broadcast하기만 하는데도 그 상태로 ping을 날리면 20초가 넘어가는(...) 도저히 실험 불가능한 상태가 됨. 지못미 ㅠㅠ

 - VM에 대한 대안으로 나온 것이 DCE인데, 이것도 사실 C++로 개발된 프로그램만 취급함. C++ 소스코드를 가져와서 ns-3에서 호환될 수 있게 g++ 옵션을 조금 추가해서 빌드하면 되는데, 그렇다고 모든 시중의 C++ 프로그램이 다 빌드되는 것도 아님. ns-3 커뮤니티 얘네들은 자랑스럽게 iperf를 소스코드 수정 없이 그대로 빌드해서 갖다쓸 수 있다고 자랑하는데, tcpdump 같이 더 심각한 일을 하는 소스코드는 아예 컴파일 불가능 ㅜㅜ

 - 내가 직접 소켓 프로그래밍으로 C++ 프로그램을 만들어서 돌리려고 해도 생각보다 지원 안되는 코드가 많아서 코딩에도 제약이 있음. 이게 뭐야...


 - 결론적으로, 분명히 확장성이 좋아 보이는데 결국 실제로 제대로 써 보려고 달려들면 퀄넷이나 ns-3나 안되는 건 마찬가지임 ㅠㅠ




<통계(Statistics)>


QualNet : ns-3 (무승부)


시뮬레이션 결과에 대한 통계를 내 주는 부분은 양쪽 다 방식도 다르고 장단점도 분명해서 어느 한 쪽이 유리하다고 볼 수는 없다.


*QualNet

 - 위의 GUI 부분에도 언급되어 있듯이, 물리 계층부터 응용 계층까지 각 계층에서 낼 수 있는 모든 통계를 항상 만들어 줌. GUI에서 그래프를 그려줄 때 참고하는 파일이 .stat 파일이고, 시뮬레이션 1개를 실행하고 나면 자동으로 생성되기 때문에 이 .stat 파일을 직접 파싱해서 원하는 통계치를 계산하는 것도 가능.

 - 모든 계층에 대한 통계가 다 나오는 점이 의외로 디버그에 유용할 때도 있음. 가령 응용 계층에서 패킷을 모두 전송 실패했는데 물리 계층에서 받는 signal이 분명히 기록되어 있다면 중간의 라우팅 계층 같은 부분에서 기대와 다르게 패킷을 drop했을 수 있으므로, 라우팅 계층의 logic을 살펴보는 식의 접근이 가능함.


*ns-3

 - 시뮬레이션 환경에서 생성하는 노드 각각에 대해서 .pcap 파일을 자동으로 만들도록 설정할 수 있는데 (코드에 pcap output을 만들어 달라고 1줄 추가하면 됨), 이 pcap 파일을 Wireshark에서 바로 보거나 그래프를 볼 수도 있고, tcpdump에서 약간의 규칙을 적용해서 원하는 정보를 원하는 포맷으로 만들어 쓸 수 있음.

 - pcap 파일을 기반으로 패킷을 분석하고 통계를 내는 부분은 오픈소스 도구들도 여럿 존재하기 때문에 퀄넷의 자체 포맷(stat)에 비해 유리한 점이 있음.




반응형
블로그 이미지

Bryan_

,
반응형

Host OS: Ubuntu 16.04 (amd64)

Host Spec (VM): Quad core 2.6GHz (Intel Zeon, Haswell), 4GB RAM

LXC template: ubuntu



지난 여름에는 라즈베리파이 4개를 가지고 무선 메쉬 네트워크를 만들고, 각각의 라즈베리파이가 AP 역할을 함으로써 서로 다른 AP에 물린 클라이언트들 사이에 통신이 되도록 하였고, 이를 중개하는 multi-constrained 라우팅 프로토콜을 Java로 개발해서 테스트를 했었다.


사실 성능을 생각하면 C로 개발해야 하지만 무지막지하게 늘어나는 개발 시간 때문에 그나마 익숙하게 다룰 수 있는 Java로 개발을 했었고, 이제 와서 보니 이게 ns-3에서 시뮬레이션을 돌릴 때 결국 C나 파이썬으로 포팅해야 되는 어려움으로 돌아오고 말았다. ㄷㄷ


하지만 ns-3는 real-world program을 돌릴 수 있는 몇 가지 방법을 제시하고 있었고, 그 중에서 Linux Container (LXC)를 이용한 방법이 있어서 튜토리얼을 따라하며 환경을 구축해 보았다.


실제로 예제를 그대로 따라해서 2개의 노드가 IEEE 802.11g로 서로 애드혹 네트워크로 연결되고 각각의 노드에서 ping, ssh 등을 테스트하는 데에는 아무 문제가 없었다.

여기서 더 나가서 내가 개발한 Java 기반의 라우팅 프로토콜을 실행시키는 과정이 생각보다 오래 걸렸지만, 이것도 각 컨테이너를 인터넷에 먼저 연결시키고 apt-get으로 필요한 패키지를 다 설치하고 나니 어쨌든 내가 라즈베리파이에서 돌리던 모양 그대로 실행시킬 수 있었다.


여기까지는 일단 그대로 실행이 되는지부터 봐야 해서 노드 3개까지만 만들어서 해 본 거였고, 이제 본격적으로 시뮬레이션을 돌리기 위해서 노드 생성과 Java 프로그램 실행을 자동화하는 스크립팅 작업을 했는데 이것 또한 시간이 은근히 많이 걸렸다. ㅜㅜ


우여곡절 끝에 이제 내가 마음대로 원하는 만큼의 노드(컨테이너)를 자동으로 생성/실행할 수 있게 되었고, 네트워크 토폴로지는 ns-3에서 역시나 마음대로 설정해 주면 되었다.


드디어 대망의 첫 실험...

자신있게 10개의 노드를 명령어 한 줄로 간지나게(?) 자동으로 뙇 생성하고, 토폴로지는 4-4-2 그리드 형태로 만든 다음, 실험 시작을 누르고 나서 시범적으로 9번 노드에서 이웃 노드인 10번 노드한테 ping을 날려 봤더니...


root@node9:/# ping 10.0.0.10

PING 10.0.0.10 (10.0.0.10) 56(84) bytes of data.

64 bytes from 10.0.0.10: icmp_seq=1 ttl=64 time=3665 ms

64 bytes from 10.0.0.10: icmp_seq=2 ttl=64 time=4356 ms

64 bytes from 10.0.0.10: icmp_seq=3 ttl=64 time=5375 ms

64 bytes from 10.0.0.10: icmp_seq=4 ttl=64 time=7245 ms

64 bytes from 10.0.0.10: icmp_seq=5 ttl=64 time=8166 ms

64 bytes from 10.0.0.10: icmp_seq=6 ttl=64 time=9149 ms

64 bytes from 10.0.0.10: icmp_seq=7 ttl=64 time=10225 ms

64 bytes from 10.0.0.10: icmp_seq=8 ttl=64 time=11106 ms

64 bytes from 10.0.0.10: icmp_seq=9 ttl=64 time=12252 ms

64 bytes from 10.0.0.10: icmp_seq=10 ttl=64 time=13146 ms

64 bytes from 10.0.0.10: icmp_seq=11 ttl=64 time=14062 ms

64 bytes from 10.0.0.10: icmp_seq=12 ttl=64 time=15261 ms

64 bytes from 10.0.0.10: icmp_seq=13 ttl=64 time=16784 ms

64 bytes from 10.0.0.10: icmp_seq=14 ttl=64 time=18056 ms

64 bytes from 10.0.0.10: icmp_seq=15 ttl=64 time=19286 ms

64 bytes from 10.0.0.10: icmp_seq=16 ttl=64 time=20347 ms

64 bytes from 10.0.0.10: icmp_seq=17 ttl=64 time=21576 ms

64 bytes from 10.0.0.10: icmp_seq=18 ttl=64 time=22208 ms

64 bytes from 10.0.0.10: icmp_seq=19 ttl=64 time=22970 ms

64 bytes from 10.0.0.10: icmp_seq=20 ttl=64 time=23673 ms

64 bytes from 10.0.0.10: icmp_seq=21 ttl=64 time=24549 ms

64 bytes from 10.0.0.10: icmp_seq=22 ttl=64 time=24565 ms

64 bytes from 10.0.0.10: icmp_seq=23 ttl=64 time=24311 ms

64 bytes from 10.0.0.10: icmp_seq=24 ttl=64 time=24205 ms

64 bytes from 10.0.0.10: icmp_seq=25 ttl=64 time=23996 ms

64 bytes from 10.0.0.10: icmp_seq=26 ttl=64 time=23627 ms

64 bytes from 10.0.0.10: icmp_seq=27 ttl=64 time=23452 ms

64 bytes from 10.0.0.10: icmp_seq=28 ttl=64 time=23325 ms

64 bytes from 10.0.0.10: icmp_seq=29 ttl=64 time=23048 ms

64 bytes from 10.0.0.10: icmp_seq=30 ttl=64 time=22669 ms

64 bytes from 10.0.0.10: icmp_seq=31 ttl=64 time=22337 ms

64 bytes from 10.0.0.10: icmp_seq=32 ttl=64 time=21869 ms

64 bytes from 10.0.0.10: icmp_seq=33 ttl=64 time=21127 ms

64 bytes from 10.0.0.10: icmp_seq=34 ttl=64 time=20186 ms

64 bytes from 10.0.0.10: icmp_seq=35 ttl=64 time=19246 ms

64 bytes from 10.0.0.10: icmp_seq=36 ttl=64 time=18327 ms

64 bytes from 10.0.0.10: icmp_seq=37 ttl=64 time=17383 ms

64 bytes from 10.0.0.10: icmp_seq=38 ttl=64 time=16449 ms

64 bytes from 10.0.0.10: icmp_seq=39 ttl=64 time=15512 ms

64 bytes from 10.0.0.10: icmp_seq=40 ttl=64 time=14573 ms

64 bytes from 10.0.0.10: icmp_seq=41 ttl=64 time=13607 ms

64 bytes from 10.0.0.10: icmp_seq=42 ttl=64 time=12676 ms

64 bytes from 10.0.0.10: icmp_seq=43 ttl=64 time=11745 ms

64 bytes from 10.0.0.10: icmp_seq=44 ttl=64 time=10816 ms

64 bytes from 10.0.0.10: icmp_seq=45 ttl=64 time=9876 ms

64 bytes from 10.0.0.10: icmp_seq=46 ttl=64 time=8953 ms

64 bytes from 10.0.0.10: icmp_seq=47 ttl=64 time=8012 ms

64 bytes from 10.0.0.10: icmp_seq=48 ttl=64 time=7037 ms

64 bytes from 10.0.0.10: icmp_seq=49 ttl=64 time=6042 ms

64 bytes from 10.0.0.10: icmp_seq=50 ttl=64 time=5064 ms

64 bytes from 10.0.0.10: icmp_seq=51 ttl=64 time=4069 ms

64 bytes from 10.0.0.10: icmp_seq=52 ttl=64 time=3074 ms

64 bytes from 10.0.0.10: icmp_seq=53 ttl=64 time=2080 ms

64 bytes from 10.0.0.10: icmp_seq=54 ttl=64 time=1087 ms

64 bytes from 10.0.0.10: icmp_seq=55 ttl=64 time=102 ms

64 bytes from 10.0.0.10: icmp_seq=56 ttl=64 time=5.94 ms

64 bytes from 10.0.0.10: icmp_seq=57 ttl=64 time=5.57 ms

64 bytes from 10.0.0.10: icmp_seq=58 ttl=64 time=5.82 ms

64 bytes from 10.0.0.10: icmp_seq=59 ttl=64 time=5.49 ms

64 bytes from 10.0.0.10: icmp_seq=60 ttl=64 time=5.55 ms

^C

--- 10.0.0.10 ping statistics ---

60 packets transmitted, 60 received, 0% packet loss, time 59063ms

rtt min/avg/max/mdev = 5.498/13466.053/24565.805/8177.166 ms, pipe 25

root@node9:/# 



Aㅏ...

저기 중간에 점도 찍히지 않고 당당하게 5자리를 찍어주는 저 결과는 뭥미?

24205는 뭔가요? 저게 진정 와이파이 RTT인가여? 털썩...


각 컨테이너에서 아무 프로그램도 돌고 있지 않을 때는 이웃노드 간에 ping을 날리면 약 5ms가 되어야 정상인데, 보는 것처럼 데이터 패킷도 아닌 ICMP 패킷 하나가 바로 옆으로 가는 데 24초를 넘어섰다. ㄷㄷㄷ 저것도 계속 RTT가 커지기만 하길래 24초쯤 되는 상황에서 돌아가는 모든 Java 프로그램을 죽였더니, 한참이 지나고 나서야 ping이 정상으로 돌아온 것이다.


내가 Java 프로그램을 잘못 만들었구나 자책하려고 했지만, Host 머신에서 CPU 사용량을 확인해 보니 ns-3 프로세스가 혼자 97% ~ 110% 사이를 왔다갔다 하고 있었고, 정작 내가 개발한 Java 프로세스들은 별로 CPU를 차지하고 있지 않았다. (라즈베리파이2에서도 3-9% 수준이긴 했음)


일차적으로 생각해 본 원인은 ns-3 프로세스가 L2 이하를 시뮬레이션 시키고 있는데, 10개의 노드 각각이 라우팅을 위해서 Hello 패킷을 비롯한 컨트롤 패킷을 매 초마다 발생시키니까 싱글 코어만으로는 계산하는 양이 감당이 안돼서 응답이 늦어지는 바람에 ping이 20초를 넘어서는 저런 사단이 난 것 같았다.


아~망했어요 ㅠㅠ

나는 ns-3 코어가 적어도 100노드 정도의 scalability는 책임질 수 있을 거라 생각했는데... 이건 뭐 컨테이너가 제아무리 가벼워도 각자 발생시키는 패킷의 양이 늘어나면 ns-3 코어가 감당을 못하는 상황이라니...


현재까지는 ns-3에 멀티코어 활용하도록 고친다던지, 아예 컨테이너도 없이 real-world 프로그램을 직접 네트워크와 연동하는 방법(DCE라고 하는데... 결국 컨테이너로 해결을 못하면 이것도 공부해야 함...)은 아직 확인하지 못했다.


내가 ns-3에 멀티코어 활용 방법을 아직 몰라서 이런 것이라고 믿고 싶다. ㅜㅜ 

그런데 왠지 보니까 기본적으로 멀티코어를 활용하도록 설계했을 법도 한데 안한 점과 구글 검색(ns-3 multi core)에서 첫 페이지에서 그런 옵션을 켜는 법이라던지 하는 간단한 해결방법이 보이지 않는 것을 보면... 


야~신난다 @_@

대안으로 나온 것이 컨테이너도 없이 DCE라고 직접 프로그램 코드를 바로 실행하는 방법도 있지만 이건 C++만 된다고 한다. DCE에 Java 프로그램을 붙이는 방법이 있는지 찾아보고, 이마저도 없으면 쌩으로 Java를 모두 C++ 또는 파이썬으로 포팅하는 대규모 토건사업이 펼쳐지겠지 ㄹㅇㅁㄴㄹㅇㅁㄹㅁㅈ 일자리 창출 인정? =_=


정신줄 그만 놓고, 해결 방법을 찾아봐야겠다. ㅜㅜ


반응형
블로그 이미지

Bryan_

,