ns3 (1)

햄스터·2025년 4월 19일

NetworkProject

목록 보기
1/5

ns-3은 네트워크를 시뮬레이션 하기 위해 쓰는 도구입니다.
실제 네트워크 장비들을 코드로 모사하여 시뮬레이션 할 수 있게 도와줍니다.

ns-3을 이해하기 위해 알아야 하는 필수적인 용어들 먼저 알아봅시다.


우선 ns3은 다음과 같은 구조가 완전 기본 구조입니다.

사용자가 만든 Application이 API와 연결되어,
TCP, UDP, IP 등 Protocol 계층을 거쳐서,
NetDevice로 이동하고,
NetDevice에선 Channel로 다른 NetDevice에게 데이터를 전송하며,
데이터는 NetDevice에서 수신하여, Protocol Stack을 따라 Application으로 올라갑니다.

네트워크 때 배웠던 OSI 7 계층과 아주 흡사합니다.
거의 같죠.

Application Layer, Transport Layer, Data Link Layer가 관측됩니다.

즉,

하나의 Node를 구성하기 위해서 여러가지 내부 설정들을 직접 코딩해주면 되는 겁니다.

Node

실제 세계에 있는 네트워크의 가장 기본적인 구성요소입니다.
내 컴퓨터가 될 수도 있고, Switch(Router)가 될 수도 있고, 뭐 그렇죠.

아무튼 그 기초가 되는 Abstraction을 ns3에서는
Node class로 구현합니다.

네트워크 시뮬레이션에서 Host를 추가하고 싶건, Router를 추가하고 싶건 일단 Node 하나는 있어야 합니다.

Application

내가 '시뮬레이션'하고 싶은 Activity를 정의합니다.
일정한 시간 간격으로 Packet을 보내고 싶거나, (OnOffApplication)
UDP 프로토콜로 무언가를 일정하게 쏘고 싶거나, (UDPEchoClientApplication) 등등이요.
Application Class로 구현합니다.

Channel

실제 세계에서 node와 node간에는 데이터를 주고받는 길이 있죠?
무선채널 (WiFi)일 수도 있고, 선으로 직접 연결되어 있는 경우도 있죠.

그걸 시뮬레이션합니다. (CsmaChannel, PointToPointChannel, WiFiChannel ..)

Channel Class로 구현합니다.

Net Device

네트워크카드, 즉 NIC를 구현한 구현체입니다.
특정 노드가 있고, Channel이 있으면 그 둘을 연결하는 무언가가 필요하겠죠.

현실세계에선 NIC가 그 역할을 하고,
ns-3에선 NetDevice가 그 역할을 합니다.
(그러니까 NetDevice 없으면 Channel이 의미가 없겠죠?)

CsmaNetDevice, WifiNetDevice 등이 있구요.
NetDevice Class로 구현합니다.

Topology Helper

이 수많은 내부 구성요소들을,
일일히 코딩하고, 연결하고, 테스트하고, 그러고 있어야 할까요?

NetDevice를 만들고, 주소를 추가하고, device를 Node에 추가하고,
Protocol Stack을 Configure하고, NetDevice를 Channel에 연결하고..

너무 귀찮지 않나요?

이 귀찮은 작업을 쉽게쉽게 하게 해주는게 Helper들입니다.

NodeContainer

Node를 만들고, 관리하게 도와줍니다.

PointToPointHelper

P2PNetDevice와, P2PChannel의 환경설정과, 연결을 도와주고,
Channel과 NetDevice에 Delay, DataRate 등을 설정할 수 있게 해줍니다.

NetDeviceContainer

node에 NetDevice를 설치하고, Channel을 만들기 위해 씁니다.

InternetStackHelper

각 NodeContainer 내의 Node에 TCP, UDP, IP 등 Internet Stack을 설치하는 것을 돕습니다.

Ipv4AddressHelper

IP 주소 할당과 주소의 지정을 돕습니다.


자, 이제 배울만큼 배웠으니 실제 시뮬레이션 예제를 봅시다.

다음과 같은 간단한 네트워크 시뮬레이션을 해보려 합니다.
UDP로 Packet을 2초에 Client가 발송하면,
듣고있던 Server는 echo back 해줍니다.

구현한 코드를 좀 봅시다.

#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("FirstScriptExample")

int main(int argc, char* argv[])
{
	CommandLine cmd;
    cmd.Parse(argc, argv);
    
    Time::SetResolution(Time::NS)
    LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
    LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
    
    NodeContainer nodes;
    nodes.Create(2);
    
    PointToPointHelper p2p;
    p2p.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
    p2p.SetChannelAttribute("Delay", StringValue("2ms"));
    
    NetDeviceContainer devices;
    devices = p2p.Install(nodes);
    
    InternetStackHelper stack;
    stack.Install(nodes);
    
    Ipv4AddressHelper address;
    address.SetBase("10.1.1.0", "255.255.255.0")
    
    Ipv4InterfaceContainer interfaces = address.Assign(devices);
    
    UdpEchoServerHelper echoServer(9);
    
    ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));
    serverApps.Start(Seconds(1.0));
    severApps.Stop(Seconds(10.0));
    
    UdoEchoClientHelper echoClient (interfaces.GetAddress(1), 9);
    echoClient.SetAttribute("MaxPackets", UintegerValue (1));
    echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0));
    echoClient.SetAttribute("PacketSize", UintegerValue (1024));
    
    ApplicationContainer clientApps = echoClient.Install(nodes.Get(0));
    clientApps.Start(Seconds(2.0));
    clientApps.Stop(Seconds(10.0));
    
    Simulator::Run();
    Simulator::Destroy();
    
    return 0;
    

이 코드를 뜯어봅시다.

Node 설치

NodeContainer nodes;
nodes.Create(2);

기본이 될 Node를 2개 만들어줍니다.
0-index-based입니다.

NodeContainer에는, Create 말고도 몇 가지 기능이 더 있습니다.

  • Add : Node를 추가할 수 있습니다.
    Ptr<Node> newNode
    nodes.Add(newNode) 처럼 사용합니다.

  • Get : i번째 node를 얻어냅니다.
    Ptr<Node> node_i = nodes.Get(i)처럼 사용합니다.

  • GetN : node의 개수를 얻어냅니다.
    int nodeCount = nodes.GetN()처럼 사용합니다.

Channel 설치

PointToPointHelper p2p;
p2p.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
p2p.SetChannelAttribute("Delay", StringValue("2ms"));

두 Node 사이를 P2P Channel로 이어줍니다.
그냥 채널을 전부 코딩하지 말고, PointToPointHelper를 사용합니다.
SetDeviceAttribute, SetChannelAttribute로 해당 link의 설정을 할 수 있습니다.

NetDevice 설치

NetDeviceContainer devices;
devices = p2p.Install(nodes);

채널과 Node만 동떨어져 있으면 아무 일도 일어나지 않습니다.
둘을 이어줄 NetDevice를 만들어줍니다.

NetDeviceContainer도 여러 함수를 지원합니다.

  • Add : Container에 NetDevice를 추가합니다.
    devices.Add(Ptr<NetDevice> newDevice)처럼 사용합니다.
    *Get, GetN : 위의 NodeContainer와 역할이 같습니다.
  • NetDeviceContainer Install(NodeContainer) :
    NetDeviceContainer = Channel.Install(NodeContainer)로 사용합니다.
    Channel과 Node, NetDevice를 이어주는 방법입니다.
    가령, devices = p2p.Install(nodes)를 하고 나면,
    nodes에 있는 node들은 p2p NetDevice를 단 채로, p2p Channel로 이어집니다.

IP 설정

InternetStackHelper stack;
stack.Install(nodes);
    
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0")
    
Ipv4InterfaceContainer interfaces = address.Assign(devices);    

연결만 해선 아무일도 일어나지 않죠.
서로의 주소를 알고, 인터넷 스택을 설치해야 통신이 가능합니다.
10.1.1.0 ~ 255.255.255.0까지 NetDevice들에게 ip를 부여해줍시다.

address의 부여를 Nodes가 아닌 NetDeviceContainer에 해주는 것이 포인트.
SetBase의 시작이 10.1.1.0이라면, node0과 node1은 각각
10.1.1.1, 10.1.1.2를 받게 됩니다.

Server쪽 Application 설치

UdpEchoServerHelper echoServer(9);
    
ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));
serverApps.Start(Seconds(1.0));
severApps.Stop(Seconds(10.0));

서버 쪽에선 UDPEchoServer 애플리케이션을 설치합니다.
UDP 패킷을 받으면 그대로 Echo-back 해주는 애플리케이션입니다.
포트 9번에 설치하고, 1~10초까지 실행되며, nodes.Get(1), 즉 1번Node에 설치해줍니다.

Client쪽 Application 설치

UdoEchoClientHelper echoClient (interfaces.GetAddress(1), 9);
echoClient.SetAttribute("MaxPackets", UintegerValue (1));
echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0));
echoClient.SetAttribute("PacketSize", UintegerValue (1024));
    
ApplicationContainer clientApps = echoClient.Install(nodes.Get(0));
clientApps.Start(Seconds(2.0));
clientApps.Stop(Seconds(10.0));

똑같습니다. Client쪽에도 UDPEchoClient 애플리케이션을 설치합니다.
단, Server는 echo-back만 해주면 되지만, Client는 직접 쏴야 하는 실정이라,
MaxPackets, Interval, PacketSize 등을 전부 설정해줍니다.

또, 어디로 보내야 할지도 Client는 알아야 하지요.
echoServer와는 다르게, 보낼 대상의 address, Port까지 처음에 Configure해줍니다.

전부 끝나면,

다음과 같은 네트워크 설정이 완료됩니다.

실행

Simulator::Run()
Simulator::Destroy()

전체 시뮬레이터를 시작하고, 스케줄 된 Application을 돌린 후, 정지합니다.
Simulator::Stop(Seconds(30.0))으로 30초에 전체 시뮬레이터를 정지시키는 작업도 가능합니다.

Simulator::Destroy()가 불리면 전체 노드의 object가 삭제되고, 메모리가 해제됩니다.

실행결과

./waf --run fileName 하면 실행이 가능한데,

다음과 같은 결과를 볼 수 있습니다.
client는 port 9번에게 보냈고, Server는 Client에게 돌려보냅니다.
Client는 port를 딱히 지정하지 않았죠? 그래서 자동지정된 49153번이 쓰였습니다.


이젠 Logging Module을 배워봅시다.

Log Component

앞서 NS_LOG_COMPONENT_DEFINE("blahblah")같은 코드를 봤습니다.
무슨 코드일까요?

ns3에서는 LOG COMPONENT를 정의할 수 있습니다.
ns3의 기본 빌드에서는 로그가 나오지 않기 때문에,
./waf configure --build-profile = debug를 먼저 해 줘야 로그가 찍힙니다.

특정 상황에 로그를 찍고 싶다면, Verbosity Level을 지정하여 LOG를 찍습니다.
가령,

  if (Ipv4Address::IsMatchingType (m_peerAddress))
    {
      NS_LOG_INFO ("At time " << Simulator::Now ().GetSeconds () << "s client sent " << m_size << " bytes to " <<
                   Ipv4Address::ConvertFrom (m_peerAddress) << " port " << m_peerPort);
    }

같은 코드를 보면 알 수 있습니다.
INFO 레벨의 로그로 찍고 있죠.
로그 레벨은 이렇게 존재합니다.

이렇게 정의해주고 나면, main function 내에서
어떤 LOG의 어떤 Level을 켤지 정할 수 있습니다.

이렇게 Enable하면, ns3 내에 정의된 FirstScriptExample, UdpEchoClientApplication, UdpEchoServerApplication 이름의 LOG에서 INFO로 되어있는 로그들을 찍을 수 있게 됩니다.

굳이 이렇게 main에 쓰지 않아도 여러가지 방법으로 로그를 켤 수 있습니다.

How to LOG? (1)

$ export NS_LOG=UdpEchoClientApplication=level_all
$ ./waf --run fileName

이렇게 터미널에서 export하면,
UdpEchoClientApplication이라는 이름으로 정의된 log에서,
모든 레벨의 LOG를 전부 찍겠다

라는 뜻입니다.

실제로 많은 로그들이 다 찍힙니다.

How to LOG? (2)

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_time'
$ ./waf --run fileName

저 작은따옴표가 중요합니다. (빼먹기 쉬움.)
모든 레벨의 log를 찍되, prefix로 시간을 넣겠다 입니다

로그에 시간이 추가로 찍히죠?

이 prefix도 다음과 같은 종류들이 사용이 가능합니다.

터미널에 쓸때는 prefix_func, prefix_time, prefix_node, prefix_level, prefix_all처럼 사용 가능합니다.

HOW to LOG? (3)

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_time'
$ ./waf --run fileName > log.out 2>&1

log는 기본적으로 stdout이면서 stderr이기도 합니다.
따라서 stderr (2)를 stdout (1)과 함께,
log.out에 redirection시켜서 저장하는 방법입니다.

How to LOG? (4)

$export NS_LOG=

다음 코드로 터미널에서 설정해놓은 default log level을 초기화 가능합니다.
이제 시뮬레이션마다 원하는 로그를 찍어낼 수 있겠네요.

profile
햄스터가 세상을 지배한다.

0개의 댓글