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
에 포함되지 않는 것을 볼 수 있다.
STA는 Station의 약자로, Wi-Fi 네트워크에 연결되는 AP가 아닌 일반적인 노드를 의미하기 때문이다.
그리고 AP가 될 n0
를 wifiApNode
라는 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 계층 종류에 해당하는 TypeId
인 ns3::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.cc
의 main
함수 바로 앞에, 다음 코드를 추가해 보자:
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::MobilityModel
은 ns3::MobilityModel
타입의 aggregated object라고 불리는 것을 명시한다. $
prefix는 앞에 명시된 번호의 노드에 aggregated 되었다는 것을 명시한다. 마지막의 CourseChange
는 후킹의 대상이 되는 이벤트 이름이다.
이제 Config::Connect
를 통해 위에서 고른 노드의 트레이스 소스를 가리키는 namespace path를 전달하여 해당 트레이스 소스와 우리가 정의한 트레이스 싱크를 연결하도록 한다.