ndn-cxxで実装したアプリケーションをndnSIMに移植する

モチベーション

ndn-cxxで実装したアプリケーションを、ノード数を増やした環境で実験に使いたい
→ ndnSIMに移植したい!

前提条件

実験環境 : Ubuntu 17.10 LTS
対象読者 : Named Data Networking(NDN)の研究をしている日本人, C++がそこそこに書ける日本人

公式にはなんて書いてあるの?

http://ndnsim.net/current/guide-to-simulate-real-apps.html
こちらにndn-cxxで書いたアプリケーションをndnSIMに移植する際のサンプルコードが書いてあります。
このコードはndnSIM/ns-3/src/ndnSIM/examples/ndn-cxx-simple/下と、ndnSIM/ns-3/src/ndnSIM/examples/ndn-cxx-simple.cppに入っています。
(ちなみに、先日の更新前まで上記リンク先で説明されていたndnSIM/ns-3/src/ndnSIM/examples/ndn-custom-apps.cppは全く使い物になりません)

普段ndnSIMを利用する際には自分で作ったアプリをscratch/下に置いて、ndnSIM/ns-3/下で

1
2
$ ./waf
$ ./build/scratch/hoge

と実行すれば良い””はず””なのですが、上記コード群をそのままscratch/下に持って来て実行しても、何故かうまくいきません。
しかもこのコードはノードが1つであり、実際に実験する際には複数ノードを使う場合がほとんどであるので、複数ノードの場合はどうするのか分からないという問題もあります……
ということで、同じ点で困っている日本人研究者のために記事を書き残しておきます。

とりあえず2ノード間通信を移植してみる

https://named-data.net/doc/ndn-cxx/current/examples.html
にあるサンプルコードをndnSIMに移植してみます。

まずはTrival Consumerを移植してみます。移植の際には名前空間に注意すること!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_HPP

#include <ndn-cxx/face.hpp>
#include <ndn-cxx/interest.hpp>
#include <ndn-cxx/security/key-chain.hpp>
#include <ndn-cxx/util/scheduler.hpp>

#include <iostream>

namespace app {

class CustomConsumer
{
public:
CustomConsumer(ndn::KeyChain& keyChain)
: m_keyChain(keyChain)
, m_scheduler(m_face.getIoService())
{
}

void
run()
{
ndn::Interest interest(ndn::Name("/example/testApp/randomData"));
interest.setInterestLifetime(ndn::time::milliseconds(1000));
interest.setMustBeFresh(true);

m_face.expressInterest(interest,
std::bind([] { std::cout << "Hello!" << std::endl; }),
std::bind([] { std::cout << "NACK!" << std::endl; }),
std::bind([] { std::cout << "Bye!.." << std::endl; }));

std::cout << "Sending: " << interest << std::endl;

m_face.processEvents(); // ok (will not block and do nothing)
// m_faceConsumer.getIoService().run(); // will crash
}

private:
void
onData(const ndn::Interest& interest, const ndn::Data& data)
{
std::cout << data << std::endl;
}

void
onNack(const ndn::Interest& interest, const ndn::lp::Nack& nack)
{
std::cout << "received Nack with reason " << nack.getReason() << " for interest" << interest << std::endl;
}

void
onTimeout(const ndn::Interest& interest)
{
std::cout << "Timeout " << interest << std::endl;
}

private:
ndn::KeyChain& m_keyChain;
ndn::Face m_face;
ndn::Scheduler m_scheduler;
};

} // namespace app

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_HPP

Consumerの処理をシナリオコードからstartさせるためのコードも必要になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_STARTER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_CONSUMER_STARTER_HPP

#include "consumer.hpp"

#include "ns3/ndnSIM/helper/ndn-stack-helper.hpp"
#include "ns3/application.h"

namespace ns3 {

// Class inheriting from ns3::Application
class CustomConsumerStarter : public Application
{
public:
static TypeId
GetTypeId()
{
static TypeId tid = TypeId("CustomConsumerStarter")
.SetParent<Application>()
.AddConstructor<CustomConsumerStarter>();

return tid;
}

protected:
// inherited from Application base class.
virtual void
StartApplication()
{
// Create an instance of the app, and passing the dummy version of KeyChain (no real signing)
m_instance.reset(new app::CustomConsumer(ndn::StackHelper::getKeyChain()));
m_instance->run(); // can be omitted
}

virtual void
StopApplication()
{
// Stop and destroy the instance of the app
m_instance.reset();
}

private:
std::unique_ptr<app::CustomConsumer> m_instance;
};

} // namespace ns3

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_STARTER_HPP

同様にしてTrival Producerも移植してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_HPP

#include <ndn-cxx/face.hpp>
#include <ndn-cxx/interest.hpp>
#include <ndn-cxx/security/key-chain.hpp>
#include <ndn-cxx/util/scheduler.hpp>

#include <iostream>

namespace app {

class CustomProducer
{
public:
CustomProducer(ndn::KeyChain& keyChain)
: m_keyChain(keyChain)
// , m_faceProducer(m_face.getIoService())
, m_scheduler(m_face.getIoService())
{
}

void
run()
{
m_face.setInterestFilter("/example/testApp",
std::bind(&CustomProducer::onInterest, this, _1, _2),
ndn::RegisterPrefixSuccessCallback(),
std::bind(&CustomProducer::onRegisterFailed, this, _1, _2));

m_face.processEvents(); // ok (will not block and do nothing)
// m_faceConsumer.getIoService().run(); // will crash
}

private:
void
onInterest(const ndn::InterestFilter& filter, const ndn::Interest& interest)
{
std::cout << "<< I: " << interest << std::endl;

ndn::Name dataName(interest.getName());
dataName
.append("testApp")
.appendVersion();

static const std::string content = "hogehogeclub";

std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>();
data->setName(dataName);
data->setFreshnessPeriod(ndn::time::seconds(10));
data->setContent(reinterpret_cast<const uint8_t*>(content.c_str()), content.size());

m_keyChain.sign(*data);

std::cout << ">> D: " << *data << std::endl;
m_face.put(*data);
}

void
onRegisterFailed(const ndn::Name& prefix, const std::string& reason)
{
std::cerr << "ERROR: Failed to register prefix \""
<< prefix <<"\" in local hub's daemon (" << reason << ")"
<< std::endl;

m_face.shutdown();
}

private:
ndn::KeyChain& m_keyChain;
ndn::Face m_face;
ndn::Scheduler m_scheduler;
};

} // namespace app

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_HPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#ifndef NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_STARTER_HPP
#define NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_CUSTOM_PRODUCER_STARTER_HPP

#include "producer.hpp"

#include "ns3/ndnSIM/helper/ndn-stack-helper.hpp"
#include "ns3/application.h"

namespace ns3 {

// Class inheriting from ns3::Application
class CustomProducerStarter : public Application
{
public:
static TypeId
GetTypeId()
{
static TypeId tid = TypeId("CustomProducerStarter")
.SetParent<Application>()
.AddConstructor<CustomProducerStarter>();

return tid;
}

protected:
// inherited from Application base class.
virtual void
StartApplication()
{
// Create an instance of the app, and passing the dummy version of KeyChain (no real signing)
m_instance.reset(new app::CustomProducer(ndn::StackHelper::getKeyChain()));
m_instance->run(); // can be omitted
}

virtual void
StopApplication()
{
// Stop and destroy the instance of the app
m_instance.reset();
}

private:
std::unique_ptr<app::CustomProducer> m_instance;
};

} // namespace ns3

#endif // NDNSIM_EXAMPLES_NDN_CXX_SIMPLE_REAL_APP_STARTER_HPP

最後にscratch/下にシナリオコードを書いてみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "ns3/sampleapps/consumer.hpp"
#include "ns3/sampleapps/consumer-starter.hpp"

#include "ns3/sampleapps/producer.hpp"
#include "ns3/sampleapps/producer-starter.hpp"

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

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED(CustomConsumerStarter);
NS_OBJECT_ENSURE_REGISTERED(CustomProducerStarter);

int
main(int argc, char* argv[])
{
CommandLine cmd;
cmd.Parse(argc, argv);

// Ptr<Node> node = CreateObject<Node>();
NodeContainer nodes;
nodes.Create(2);

PointToPointHelper p2p;
p2p.Install(nodes.Get(0), nodes.Get(1));

ndn::StackHelper ndnHelper;
ndnHelper.SetDefaultRoutes(true);
ndnHelper.InstallAll();

ndn::AppHelper consumerHelper("CustomConsumerStarter");
consumerHelper.Install(nodes.Get(0))
.Start(Seconds(6.5));

ndn::AppHelper producerHelper("CustomProducerStarter");
producerHelper.Install(nodes.Get(1))
.Start(Seconds(6.5));

Simulator::Stop(Seconds(20.0));

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

return 0;
}

} // namespace ns3

int
main(int argc, char* argv[])
{
return ns3::main(argc, argv);
}

ndn-cxxのコードを元に書き直したhppファイル群は、元々ndnSIMに入っているモジュール群と同じようにbuild/ns3/下に置くとscratch/下に置いたシナリオコードで上手くincludeすることが出来ます。
scratch/下にフォルダを作成してそこにhppファイル群を入れた場合、どう足掻いてもinclude出来ません!!!

今回はbuild/ns3/sampleapps/下にhppファイル群を置いて実行します。

実行結果

1
2
3
4
5
6
7
8
9
$ ./build/scratch/ndn-cxx-simple
Sending: /example/testApp/randomData?ndn.MustBeFresh=1&ndn.InterestLifetime=1000
<< I: /example/testApp/randomData?ndn.MustBeFresh=1&ndn.InterestLifetime=1000&ndn.Nonce=2956930911
>> D: Name: /example/testApp/randomData/testApp/%FD%19q
MetaInfo: ContentType: 0, FreshnessPeriod: 10000 milliseconds
Content: (size: 12)
Signature: (type: SignatureSha256WithRsa, value_length: 260)

Hello!

無事に実行できました。

終わりに

ndnSIMのhelperでは出来ることが非常に限られているので、実験の際にはndn-cxxのコードみたいに書いたhppファイル群を作成して、シナリオコードからincludeすると柔軟な条件で実験が出来たりします。
この記事がNDNを研究している日本人研究者の助けになれば幸いです。
質問があればコメント欄にどうぞ。

※ 今回書いたコードはこちらからも見れます https://github.com/wawawanet/ndn-cxx_to_ndnSIM

追記

ConsumerとProducerの間にノードを増やしても、ルーティング処理をndnSIMに任せておけばパケットが到達することを確認出来たので追記しておきます。
PointToPointHelperをインストールしてノードを結んでおいて、 ndn::StackHelperをSetDefaultRoutes(true)として全ノードにインストールしておけば、パケットは自動でルーティングされて届くみたいです。
ノードがちゃんと結ばれていないとNackが返ってくるようになっています。
(ルーティングの処理がブラックボックスすぎて、ルータに機能を追加したい場合はどうやるのか分からないですね……)