/ NETWORK

NS-3에서 Wi-Fi 네트워크 구현

저번 CSMA 포스팅에 이어,

이번 포스팅에서는 공식 예제 third.cc를 살펴보며 Wi-Fi 네트워크 환경이 어떻게 ns-3에서 구현되는지 알아본다.

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

#include "ns3/wifi-module.h"
#include "ns3/mobility-module.h"

#include "ns3/csma-module.h"
#include "ns3/internet-module.h"

먼저 Wi-Fi 관련 모듈 몇 개가 추가로 include됨을 확인 가능하다.

// Default Network Topology
//
//   Wifi 10.1.3.0
//                 AP
//  *    *    *    *
//  |    |    |    |    10.1.1.0
// n5   n6   n7   n0 -------------- n1   n2   n3   n4
//                   point-to-point  |    |    |    |
//                                   ================
//                                     LAN 10.1.2.0

ASCII 아트로 표현된 네트워크 구조를 보면, 왼쪽에 Wi-Fi 네트워크가 새로 생겼다.

second.cc와 유사한 부분은 생략하였다. 여기서는 새로 소개되는 클래스에 대해 설명한다.

NodeContainer wifiStaNodes;
wifiStaNodes.Create(nWifi);
NodeContainer wifiApNode = p2pNodes.Get(0);

Wi-Fi 네트워크에 속한 노드들이 담기게 될 NodeContainer가 선언되고, nWifi 개의 노드가 생성된다.

이 글에서는 nWifi가 선언되는 부분을 생략했지만, 저번 게시글에서 봤던 nCsma처럼 커맨드 라인 argument로 설정 가능한 변수이다.

nCsma때와 달리 AP 노드는 wifiStaNodes에 포함되지 않는 것을 볼 수 있다.

STAStation의 약자로, Wi-Fi 네트워크에 연결되는 AP가 아닌 일반적인 노드를 의미하기 때문이다.

그리고 AP가 될 n0wifiApNode 라는 NodeContainer에 담는다.

YansWifiChannelHelper channel = YansWifiChannelHelper::Default();
YansWifiPhyHelper phy = YansWifiPhyHelper::Default();

wifi 노드들 간 interconnection channel을 생성하고 wifi 디바이스를 만드는 헬퍼를 선언한다.

phy.SetChannel(channel.Create());

채널 오브젝트를 생성하고, 앞서 선언한 phy라는 PHY 계층 오브젝트 매니저에 해당 채널을 할당한다.

이로써 phy에 의해 생성된 모든 PHY 계층 오브젝트들은 같은 채널을 공유하게 된다.

WifiMacHelper mac;
Ssid ssid = Ssid("ns-3-ssid");

PHY 계층 설정이 일단 끝났으면 이제 MAC 계층 오브젝트를 생성하고, SSID를 설정한다.

WifiHelper wifi;

이제 노드들에 Wi-Fi 모델을 설치할 준비가 되었다.

NetDeviceContainer staDevices
mac.SetType("ns3::StaWifiMac",
            "Ssid", SsidValue(ssid),
            "ActiveProbing", BooleanValue(false));

이제 NetDeviceContainer를 선언하고, 헬퍼에 의해 생성될 MAC 계층 종류에 해당하는 TypeIdns3::StaWifiMac을 명시한다.

QosSupported Attribute는 WifiMacHelper 오브젝트가 기본값 true로 설정한다고 한다.

마지막으로, ActiveProbing Attribute는 false로 설정하여 이 헬퍼에 의해 생성된 MAC들이 probe 요청을 보내지 않도록 한다.

NetDeviceContainer staDevices;
staDevices = wifi.Install(phy, mac, wifiStaNodes);

이제 익숙한 .Install() 메소드로 STA 노드들에 Wi-Fi 네트워크 디바이스를 설치하고 채널을 공유하도록 한다.

이제 AP 노드를 설정해야 하니 아까 생성한 헬퍼의 타입을 다음과 같이 재설정한다.

mac.SetType("ns3::ApWifiMac",
            "Ssid", SsidValue(ssid));
NetDeviceContainer apDevices;
apDevices = wifi.Install(phy, mac, wifiApNode);

헬퍼 설정이 끝나면 아까 생성한 wifiApNode에 Wi-Fi 네트워크 디바이스를 설치한다.

이제 MobilityHelper를 이용하여 STA 노드들은 (스마트폰을 든 사람처럼)움직이고 AP 노드는 고정된 환경을 구현할 것이다.

MobilityHelper mobility;

mobility.SetPositionAllocator("ns3::GridPositionAllocator",
                              "MinX", DoubleValue(0.0),
                              "MinY", DoubleValue(0.0),
                              "DeltaX", DoubleValue(5.0),
                              "DeltaY", DoubleValue(10.0),
                              "GridWidth", UintegerValue(3),
                              "LayoutType", StringValue("RowFirst"));

위와 같이 움직일 공간과 속도를 설정하고,

mobility.SetMobilityModel("ns3::RandomWalk2dMobilityModel",
                          "Bounds", RectangleValue(Rectangle(-50, 50, -50, 50)));

위와 같이 일정한 경계에서 랜덤하게 움직이도록 설정한다.

mobility.Install(wifiStaNodes);

이 모델을 STA 노드에 Install() 메소드로 설치한다.

AP 노드는 고정된 위치를 유지하도록 해야 하니, 다음과 같이 ConstantPositionMobilityModel을 사용한다.

mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
mobility.Install(wifiApNode);

노드, 네트워크 디바이스, 채널이 생성되었고 mobility 모델까지 설정 완료되었으니, 이전 예제에서 했던 것과 마찬가지로 프로토콜 스택을 설치한다.

InternetStackHelper stack;
stack.Install(csmaNodes);
stack.Install(wifiApNode);
stack.Install(wifiStaNodes);

이제 각 네트워크의 IP주소 공간을 할당하고, 각각의 네트워크에 속한 노드에 IP 주소를 부여하도록 한다.

Ipv4AddressHelper address;

address.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer p2pInterfaces;
p2pInterfaces = address.Assign(p2pDevices);

address.SetBase("10.1.2.0", "255.255.255.0");
Ipv4InterfaceContainer csmaInterfaces;
csmaInterfaces = address.Assign(csmaDevices);

address.SetBase("10.1.3.0", "255.255.255.0");
address.Assign(staDevices);
address.Assign(apDevices);

위 코드에서 알 수 있듯이, P2P 네트워크에는 10.1.1.0 대역의 IP주소가 할당되고, CSMA 네트워크에는 10.1.2.0, Wi-Fi 네트워크에는 10.1.3.0대역이 할당되었다.

나중에 주소를 참고해야 하는 노드들의 경우 Ipv4InterfaceContainer를 선언하여 할당된 IP 주소를 저장하는 것을 확인 가능하다.

이제 second.cc 에서와 마찬가지로, ‘가장 오른쪽’ 노드에 에코 서버 어플리케이션을 설치한다. 코드 스니펫은 중복되므로 생략한다.

에코 클라이언트는 이번엔 Wi-Fi STA 노드 중 가장 마지막에 생성된 것에 설치한다.

UdpEchoClientHelper echoClient(csmaInterfaces.GetAddress(nCsma), 9);
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0)));
echoClient.SetAttribute("PacketSize", UintegerValue(1024));

ApplicationContainer clientApps =
    echoClient.Install(wifiStaNodes.Get(nWifi - 1));
clientApps.Start(Seconds(2.0));
clientApps.Stop(Seconds(10.0));

이제 second.cc에서 했던 것과 마찬가지로 이번에도 다음 Magical한 코드 한 줄로 라우팅 작업을 실행한다.

Ipv4GlobalRoutingHelper::PopulateRoutingTables();

이번에는 시뮬레이터가 저절로 동작을 멈추지 않는다. Wi-Fi AP가 멈추지 않고 계속 beacon 신호를 보내기 때문에, 그 동안 시뮬레이팅 역시 계속 진행된다. 서버 및 클라이언트 어플리케이션이 종료된 이후 시뮬레이터도 적절한 시각에 종료해 줘야 한다.

Simulator::Stop(Seconds(10.0));

이제 패킷 트레이스 파일을 생성해 보자. 다음과 같은 코드로 모든 3개 네트워크의 패킷을 캡처할 수 있다.

pointToPoint.EnablePcapAll("third");
phy.EnablePcap("third", apDevices.Get(0));
csma.EnablePcap("third", csmaDevices.Get(0), true);

Pcap 활성화 대상 노드가 P2P 네트워크에 속한 2개 노드인 것을 확인할 수 있다. 이것으로 Wi-Fi 네트워크와 CSMA 네트워크 내에 흐르는 모든 패킷을 모니터링 가능하다.

첫번째 줄은 P2P 네트워크에 속한 노드의 P2P 네트워크 인터페이스에 대해, 2, 3번째 줄은 각각 CSMA 네트워크와 Wi-Fi 네트워크 인터페이스에 대한 것임을 확인 가능하다.

Wi-Fi 네트워크의 경우 EnablePcap() 메소드를 YansWiFiPhyHelper 오브젝트에서 호출하는 것을 알 수 있다. WifiHelper가 아니라는 점에 주의하자.

마지막으로, 언제나처럼 시뮬레이터를 실행하고,Simulator::Destroy()로 클린업 해준다. 코드는 생략한다.

실행 결과

생성된 .pcap 파일들을 확인해 보면 링크 타입이 802.11인 것의 패킷 트레이스가 확인 가능하고, 실제 UDP 패킷이 오고 간 것도 확인 가능하다. 자세한 내용은 생략한다.

이제 Wi-Fi STA 노드들이 실제로 움직이는지 확인해 보자. 여기서 트레이싱 시스템이 활용된다. MobilityModel의 course change 트레이싱 소스를 후킹하는 것이다.

third.ccmain 함수 바로 앞에, 다음 코드를 추가해 보자:

void
CourseChange(std::string context, Ptr<const MobilityModel> model)
{
  Vector position = model->GetPosition();
  NS_LOG_UNCOND(context <<
              " x = " << position.x << ", y = " << position.y);
}

이 함수는 MobilityModel에서 위치 정보를 가져와서 무조건적으로(NS_LOG_UNCOD) x,y 좌표를 로깅한다.

우리는 STA 노드들의 위치가 바뀔 때마다 이 함수를 호출하고 싶다. Config::Connect 함수를 활용하면 된다. 다음 코드를 Simulator::Run 호출 직전에 추가한다.

std::ostringstream oss;
oss << "/NodeList/" << wifiStaNodes.Get(nWifi - 1)->GetId()
    << "/$ns3::MobilityModel/CourseChange";

Config::Connect(oss.str(), MakeCallback(&CourseChange));

먼저 연결을 원하는 이벤트의 namespace path를 포함하는 문자열을 생성한다. 일단 원하는 노드가 무엇인지 알아야 하므로, wifiStaNodes.Get(nWifi - 1)을 통해 마지막 노드를 가져온다. 그리고 GetId()를 통해 노드의 ID를 가져온다. 이 ID는 NodeList의 하위 항목으로 사용된다.

$ns3::MobilityModelns3::MobilityModel 타입의 aggregated object라고 불리는 것을 명시한다. $ prefix는 앞에 명시된 번호의 노드에 aggregated 되었다는 것을 명시한다. 마지막의 CourseChange는 후킹의 대상이 되는 이벤트 이름이다.

이제 Config::Connect를 통해 위에서 고른 노드의 트레이스 소스를 가리키는 namespace path를 전달하여 해당 트레이스 소스와 우리가 정의한 트레이스 싱크를 연결하도록 한다.

참고 문헌

NS-3 공식 문서