From 31abb0af3cd27fd8576adc74dc9ea68bd59d0cd1 Mon Sep 17 00:00:00 2001 From: Jeff Weston Date: Mon, 9 Sep 2019 12:30:12 -0400 Subject: [PATCH] 3.0b1 release --- README.md | 3 +- TODO.TXT | 10 +- VERSION.TXT | 7 +- examples/arposer.cpp | 447 +++ examples/detourExample.cpp | 14 +- examples/graphRider.cpp | 34 +- examples/jsonExample.cpp | 74 + examples/pcmd.cpp | 66 + examples/pipeExample.cpp | 2 +- examples/pipeExample.py | 33 + examples/protoExample.cpp | 217 +- examples/riposer.cpp | 403 +++ examples/threadExample.cpp | 16 +- examples/timerTest.cpp | 62 +- examples/ting.cpp | 22 +- examples/vifExample.cpp | 138 +- examples/vifLan.cpp | 2 +- examples/wxProtoExample.cpp | 2 +- include/manetGraph.h | 19 +- include/manetGraphML.h | 8 +- include/manetMsg.h | 2 +- include/protoAddress.h | 24 +- include/protoBitmask.h | 101 +- include/protoCap.h | 20 +- include/protoChannel.h | 112 +- include/protoCheck.h | 49 + include/protoDebug.h | 36 +- include/protoDefs.h | 28 +- include/protoDetour.h | 12 +- include/protoDispatcher.h | 492 ++-- include/protoFile.h | 5 +- include/protoJson.h | 357 +++ include/protoList.h | 33 +- include/protoNet.h | 107 +- include/protoPipe.h | 13 +- include/protoPkt.h | 25 +- include/protoPktARP.h | 104 +- include/protoPktETH.h | 11 +- include/protoPktIGMP.h | 186 ++ include/protoPktIP.h | 97 +- include/protoPktRIP.h | 132 + include/protoPktRTP.h | 2 +- include/protoPktTCP.h | 150 + include/protoQueue.h | 142 +- include/protoRouteMgr.h | 29 +- include/protoRouteTable.h | 2 +- include/protoSocket.h | 152 +- include/protoSpace.h | 2 +- include/protoString.h | 34 + include/protoTime.h | 5 +- include/protoTimer.h | 128 +- include/protoTree.h | 99 +- include/protoVersion.h | 2 +- include/protoVif.h | 21 +- makefiles/Makefile.common | 91 +- makefiles/Makefile.freebsd | 5 +- makefiles/Makefile.linux | 5 +- makefiles/Makefile.macosx | 2 +- makefiles/android/AndroidManifest.xml | 2 +- makefiles/android/README | 4 +- makefiles/android/jni/Android.mk | 25 +- makefiles/android/jni/Application.mk | 2 +- makefiles/android/project.properties | 2 +- makefiles/win32/ProtoLib-2008.sln | 50 + makefiles/win32/ProtoLib.sln | 72 +- makefiles/win32/Protokit.vcproj | 8 + makefiles/win32/Protokit.vcxproj | 273 ++ makefiles/win32/netExample/netExample.vcxproj | 19 +- .../win32/pipeExample/pipeExample.vcxproj | 141 + makefiles/win32/protoExample.vcxproj | 150 + .../win32/threadExample/threadExample.vcxproj | 138 + makefiles/win32/timerTest/timerTest.vcproj | 223 ++ makefiles/win32/vifExample/vifExample.vcproj | 3 +- makefiles/win32/wxProtoExample.sln | 45 +- makefiles/win32/wxProtoExample.vcxproj | 186 ++ makefiles/win64/ProtoLib.sln | 90 + makefiles/win64/Protokit.vcproj | 591 ++++ makefiles/win64/netExample/netExample.vcproj | 197 ++ .../win64/pipeExample/pipeExample.vcproj | 225 ++ makefiles/win64/protoExample.vcproj | 234 ++ .../win64/threadExample/threadExample.vcproj | 222 ++ makefiles/win64/vifExample/vifExample.vcproj | 225 ++ makefiles/wx/Makefile.common | 8 +- makefiles/wx/Makefile.macosx | 6 +- setup.py | 3 - src/bsd/bsdCap.cpp | 108 +- src/bsd/bsdDetour.cpp | 29 +- src/bsd/bsdNet.cpp | 284 +- src/bsd/bsdRouteMgr.cpp | 175 +- src/common/pcapCap.cpp | 106 +- src/common/protoAddress.cpp | 178 +- src/common/protoApp.cpp | 5 + src/common/protoBase64.cpp | 2 +- src/common/protoBitmask.cpp | 493 ++-- src/common/protoCap.cpp | 42 +- src/common/protoChannel.cpp | 387 ++- src/common/protoCheck.cpp | 172 ++ src/common/protoDebug.cpp | 50 +- src/common/protoDispatcher.cpp | 2413 ++++++++++++----- src/common/protoFile.cpp | 23 +- src/common/protoGraph.cpp | 2 +- src/common/protoJson.cpp | 1529 +++++++++++ src/common/protoLFSR.cpp | 9 +- src/common/protoList.cpp | 9 +- src/common/protoNet.cpp | 142 +- src/common/protoPipe.cpp | 618 +---- src/common/protoPktARP.cpp | 206 +- src/common/protoPktETH.cpp | 24 +- src/common/protoPktIGMP.cpp | 514 ++++ src/common/protoPktIP.cpp | 158 +- src/common/protoPktRIP.cpp | 283 ++ src/common/protoPktTCP.cpp | 172 ++ src/common/protoQueue.cpp | 22 +- src/common/protoRouteMgr.cpp | 166 +- src/common/protoSimSocket.cpp | 9 +- src/common/protoSocket.cpp | 1205 ++++++-- src/common/protoSpace.cpp | 6 +- src/common/protoString.cpp | 101 + src/common/protoTime.cpp | 11 +- src/common/protoTimer.cpp | 23 +- src/common/protoTree.cpp | 94 +- src/common/protoVif.cpp | 1 + src/java/ProtolibJni.dll-x64 | Bin 0 -> 177664 bytes src/java/protolib-jni.jar-x64 | Bin 0 -> 1417 bytes .../src/mil/navy/nrl/protolib/ProtoPipe.java | 2 +- src/linux/androidDetour.cpp | 797 ++++++ src/linux/linuxCap.cpp | 97 +- src/linux/linuxDetour.cpp | 88 +- src/linux/linuxNet.cpp | 996 ++++++- src/linux/linuxRouteMgr.cpp | 69 +- src/manet/manetGraph.cpp | 408 ++- src/manet/manetGraphML.cpp | 226 +- src/manet/manetMsg.cpp | 2 +- src/python/protokit.cpp | 22 +- src/sim/ns/ns233/ns233-Makefile.in | 2 +- src/sim/ns/ns234/ns234-Makefile.in | 2 +- src/sim/ns/ns235/README.TXT | 179 ++ src/sim/ns/ns235/cmu-trace.cc | 1609 +++++++++++ src/sim/ns/ns235/cmu-trace.h | 171 ++ src/sim/ns/ns235/ns-lib.tcl | 2307 ++++++++++++++++ src/sim/ns/ns235/ns235-Makefile.in | 702 +++++ src/sim/ns/ns235/packet.h | 853 ++++++ src/sim/ns/ns235/priqueue.cc | 196 ++ src/sim/ns/nsProtoSimAgent.h | 3 +- src/sim/ns/nsRouteMgr.cpp | 2 +- src/sim/ns/nsTCPProtoSocketAgent.cpp | 2 - src/sim/ns/nsTCPProtoSocketAgent.h | 2 - src/sim/ns/tcp/SimpleList.cpp | 1 - src/sim/ns/tcp/TCPSocketFactory.cpp | 1 - src/sim/ns/tcp/TCPSocketFactory.h | 1 - src/unix/bpfCap.cpp | 113 +- src/unix/unixNet.cpp | 520 ++-- src/unix/unixVif.cpp | 72 +- src/win32/win32Net.cpp | 850 ++++-- src/win32/win32NetMonitor.cpp | 269 ++ src/win32/win32Vif.cpp | 417 ++- waf | Bin 87674 -> 97478 bytes wscript | 11 +- 158 files changed, 24364 insertions(+), 3939 deletions(-) create mode 100644 examples/arposer.cpp create mode 100644 examples/jsonExample.cpp create mode 100644 examples/pcmd.cpp create mode 100644 examples/pipeExample.py create mode 100644 examples/riposer.cpp create mode 100644 include/protoCheck.h create mode 100644 include/protoJson.h create mode 100644 include/protoPktIGMP.h create mode 100644 include/protoPktRIP.h create mode 100644 include/protoPktTCP.h create mode 100644 include/protoString.h create mode 100644 makefiles/win32/ProtoLib-2008.sln create mode 100755 makefiles/win32/Protokit.vcxproj create mode 100755 makefiles/win32/pipeExample/pipeExample.vcxproj create mode 100755 makefiles/win32/protoExample.vcxproj create mode 100755 makefiles/win32/threadExample/threadExample.vcxproj create mode 100644 makefiles/win32/timerTest/timerTest.vcproj create mode 100755 makefiles/win32/wxProtoExample.vcxproj create mode 100644 makefiles/win64/ProtoLib.sln create mode 100755 makefiles/win64/Protokit.vcproj create mode 100755 makefiles/win64/netExample/netExample.vcproj create mode 100644 makefiles/win64/pipeExample/pipeExample.vcproj create mode 100644 makefiles/win64/protoExample.vcproj create mode 100644 makefiles/win64/threadExample/threadExample.vcproj create mode 100755 makefiles/win64/vifExample/vifExample.vcproj create mode 100644 src/common/protoCheck.cpp create mode 100644 src/common/protoJson.cpp create mode 100644 src/common/protoPktIGMP.cpp create mode 100644 src/common/protoPktRIP.cpp create mode 100644 src/common/protoPktTCP.cpp create mode 100644 src/common/protoString.cpp create mode 100755 src/java/ProtolibJni.dll-x64 create mode 100755 src/java/protolib-jni.jar-x64 create mode 100644 src/linux/androidDetour.cpp create mode 100644 src/sim/ns/ns235/README.TXT create mode 100644 src/sim/ns/ns235/cmu-trace.cc create mode 100644 src/sim/ns/ns235/cmu-trace.h create mode 100644 src/sim/ns/ns235/ns-lib.tcl create mode 100644 src/sim/ns/ns235/ns235-Makefile.in create mode 100644 src/sim/ns/ns235/packet.h create mode 100644 src/sim/ns/ns235/priqueue.cc create mode 100755 src/win32/win32NetMonitor.cpp diff --git a/README.md b/README.md index ecc2c43..e7e31d9 100644 --- a/README.md +++ b/README.md @@ -163,8 +163,7 @@ BSD (incl. MacOS), but not yet for WIN32 (This is work in progress). Also, more complete documentation, including Doxygen-based code documentation and a "Developer's Guide" with examples needs to be provided. -Brian Adamson 30 September 2003 - +Brian Adamson 26 June 2017 diff --git a/TODO.TXT b/TODO.TXT index c96c852..6b87af3 100644 --- a/TODO.TXT +++ b/TODO.TXT @@ -5,18 +5,14 @@ the future: 0) Documentation, documentation, documentation!!! Code documentation and "Developers' Guide" materials. -1) ProtoDisptacher use of "epoll()" (Linux), "kqueue()" (BSD), and "/dev/poll" - (Solaris) for enhanced performance over the current use of "select()". We - may also explore use of "pselect()" further if it makes sense. - -2) Generalize the Mutex stuff used in ProtoDispatcher for general purpose +1) Generalize the Mutex stuff used in ProtoDispatcher for general purpose thread synchronization purposes ... -3) Make code truly UNICODE compliant ... In particular, any functions which +2) Make code truly UNICODE compliant ... In particular, any functions which take/return strings (currently "char*") should instead use "tchar" types/functions as appropriate. Brian Adamson -9 June 2005 +26 June 2017 diff --git a/VERSION.TXT b/VERSION.TXT index bf3d62e..6e19dfc 100644 --- a/VERSION.TXT +++ b/VERSION.TXT @@ -10,8 +10,11 @@ to provide some additional description of significant changes made. Additionally, the developers will try to provide more timely releases of the Protolib source tree outside of the SVN repository. - - +Version 3.0b1 +============= + - ProtoDispatcher can now use "epoll()" (Linux), "kqueue()" (BSD), and + "/dev/poll" (Solaris), and "pselect()" for enhanced performance over + the current use of "select()" Version 2.1b1 ============= diff --git a/examples/arposer.cpp b/examples/arposer.cpp new file mode 100644 index 0000000..bb8c918 --- /dev/null +++ b/examples/arposer.cpp @@ -0,0 +1,447 @@ +#include "protoApp.h" +#include "protoCap.h" +#include "protoPktIP.h" +#include "protoPktETH.h" +#include "protoPktARP.h" +#include "protoNet.h" + +#include +#include +#include +#include // for "isspace()" + +/** + * @class ArposerApp + * + * @brief simple app that gratuitously responds to ARP requests + * with its own MAC address as the response + * + */ +class ArposerApp : public ProtoApp +{ + public: + ArposerApp(); + ~ArposerApp(); + + bool OnStartup(int argc, const char*const* argv); + bool ProcessCommands(int argc, const char*const* argv); + void OnShutdown(); + + + + private: + enum CmdType {CMD_INVALID, CMD_ARG, CMD_NOARG}; + static const char* const CMD_LIST[]; + static CmdType GetCmdType(const char* string); + bool OnCommand(const char* cmd, const char* val); + void Usage(); + + + + void PeekPkt(ProtoPktETH& ethPkt, bool inbound); + + void OnInboundPkt(ProtoChannel& theChannel, + ProtoChannel::Notification notifyType); + + ProtoCap* cap; + char if_name[256]; + + +}; // end class ArposerApp + +void ArposerApp::Usage() +{ + fprintf(stderr, "Usage: arposer interface [debug ][help]\n"); +} + +const char* const ArposerApp::CMD_LIST[] = +{ + "-help", // print help info an exit + "+interface", // name of interface on which to listen / respond + "+debug", // + NULL +}; + +/** + * This macro creates our ProtoApp derived application instance + */ +PROTO_INSTANTIATE_APP(ArposerApp) + +ArposerApp::ArposerApp() + : cap(NULL) +{ + if_name[0] = '\0'; +} + +ArposerApp::~ArposerApp() +{ +} + +ArposerApp::CmdType ArposerApp::GetCmdType(const char* cmd) +{ + if (!cmd) return CMD_INVALID; + unsigned int len = strlen(cmd); + bool matched = false; + CmdType type = CMD_INVALID; + const char* const* nextCmd = CMD_LIST; + while (*nextCmd) + { + if (!strncmp(cmd, *nextCmd+1, len)) + { + if (matched) + { + // ambiguous command (command should match only once) + return CMD_INVALID; + } + else + { + matched = true; + if ('+' == *nextCmd[0]) + type = CMD_ARG; + else + type = CMD_NOARG; + } + } + nextCmd++; + } + return type; +} // end ArposerApp::GetCmdType() + + +bool ArposerApp::OnStartup(int argc, const char*const* argv) +{ + // Seed rand() with time of day usec + // (comment out for repeatable results) + struct timeval currentTime; + ProtoSystemTime(currentTime); + srand((unsigned int)currentTime.tv_usec); + + if (!ProcessCommands(argc, argv)) + { + PLOG(PL_ERROR, "ArposerApp::OnStartup() error processing command line options\n"); + Usage(); + return false; + } + + // Check for valid parameters. + if (0 == strlen(if_name)) + { + PLOG(PL_ERROR, "ArposerApp::OnStartup() error: missng required 'interface' command!\n"); + Usage(); + return false; + } + + // Create our cap instance and initialize ... + if (!(cap = ProtoCap::Create())) + { + PLOG(PL_ERROR, "ArposerApp::OnStartup() new ProtoCap error: %s\n", GetErrorString()); + return false; + } + cap->SetNotifier(static_cast(&dispatcher)); + cap->SetListener(this,&ArposerApp::OnInboundPkt); + if (!cap->Open(if_name)) + { + PLOG(PL_ERROR,"ArposerApp::OnStartup() ProtoCap::Open(\"%s\") error\n", if_name); + Usage(); + return false; + } + + PLOG(PL_INFO, "arposer: running on interface: %s\n", if_name); + + return true; +} // end ArposerApp::OnStartup() + +void ArposerApp::OnShutdown() +{ + if (NULL != cap) + { + cap->Close(); + delete cap; + cap = NULL; + } + + PLOG(PL_INFO, "arposer: Done.\n"); + +} // end ArposerApp::OnShutdown() + +bool ArposerApp::ProcessCommands(int argc, const char*const* argv) +{ + // Dispatch command-line commands to our OnCommand() method + int i = 1; + while ( i < argc) + { + // Is it a arposer command? + switch (GetCmdType(argv[i])) + { + case CMD_INVALID: + { + PLOG(PL_ERROR, "ArposerApp::ProcessCommands() Invalid command:%s\n", + argv[i]); + Usage(); + return false; + } + case CMD_NOARG: + if (!OnCommand(argv[i], NULL)) + { + PLOG(PL_ERROR, "ArposerApp::ProcessCommands() ProcessCommand(%s) error\n", + argv[i]); + Usage(); + return false; + } + i++; + break; + case CMD_ARG: + if (!OnCommand(argv[i], argv[i+1])) + { + PLOG(PL_ERROR, "ArposerApp::ProcessCommands() ProcessCommand(%s, %s) error\n", + argv[i], argv[i+1]); + Usage(); + return false; + } + i += 2; + break; + } + } + return true; +} // end ArposerApp::ProcessCommands() + +bool ArposerApp::OnCommand(const char* cmd, const char* val) +{ + // (TBD) move command processing into Mgen class ??? + CmdType type = GetCmdType(cmd); + ASSERT(CMD_INVALID != type); + size_t len = strlen(cmd); + if ((CMD_ARG == type) && !val) + { + PLOG(PL_ERROR, "ArposerApp::ProcessCommand(%s) missing argument\n", cmd); + Usage(); + return false; + } + else if (!strncmp("help", cmd, len)) + { + Usage(); + exit(0); + } + else if (!strncmp("interface", cmd, len)) + { + strncpy(if_name, val, 255); + if_name[255] = '\0'; + } + else if (!strncmp("debug", cmd, len)) + { + SetDebugLevel(atoi(val)); + } + else + { + PLOG(PL_ERROR, "arposer error: invalid command\n"); + Usage(); + return false; + } + return true; +} // end ArposerApp::OnCommand() + + +void ArposerApp::PeekPkt(ProtoPktETH& ethPkt, bool inbound) +{ + switch (ethPkt.GetType()) + { + case ProtoPktETH::IP: + case ProtoPktETH::IPv6: + { + unsigned int payloadLength = ethPkt.GetPayloadLength(); + ProtoPktIP ipPkt; + // The void* cast here suppresses the alignment warning we get otherwise. This cast is OK + // because of how we set up the "alignedBuffer" elsewhere + if (!ipPkt.InitFromBuffer(payloadLength, (UINT32*)((void*)ethPkt.AccessPayload()), payloadLength)) + { + PLOG(PL_ERROR, "arposer::PeekPkt() error: bad %sbound IP packet\n", + inbound ? "in" : "out"); + break; + } + ProtoAddress dstAddr; + ProtoAddress srcAddr; + switch (ipPkt.GetVersion()) + { + case 4: + { + ProtoPktIPv4 ip4Pkt(ipPkt); + ip4Pkt.GetDstAddr(dstAddr); + ip4Pkt.GetSrcAddr(srcAddr); + break; + } + case 6: + { + ProtoPktIPv6 ip6Pkt(ipPkt); + ip6Pkt.GetDstAddr(dstAddr); + ip6Pkt.GetSrcAddr(srcAddr); + break; + } + default: + { + PLOG(PL_ERROR,"ArposerApp::PeekPkt() Error: Invalid IP pkt version.\n"); + break; + } + } + PLOG(PL_ALWAYS, "ArposerApp::PeekPkt() %sbound packet IP dst>%s ", + inbound ? "in" : "out", dstAddr.GetHostString()); + PLOG(PL_ALWAYS, "src>%s length>%d\n", srcAddr.GetHostString(), ipPkt.GetLength()); + break; + } + case ProtoPktETH::ARP: + { + ProtoPktARP arp; + // The void* cast here suppresses the alignment warning we get otherwise. This cast is OK + // because of how we set up the "alignedBuffer" elsewhere + if (!arp.InitFromBuffer((UINT32*)((void*)ethPkt.AccessPayload()), ethPkt.GetPayloadLength())) + { + PLOG(PL_ERROR, "ArposerApp::PeekPkt() received bad ARP packet?\n"); + break; + } + PLOG(PL_ALWAYS,"ArposerApp::PeekPkt() %sbound ARP ", + inbound ? "in" : "out"); + switch(arp.GetOpcode()) + { + case ProtoPktARP::ARP_REQ: + PLOG(PL_ALWAYS, "request "); + break; + case ProtoPktARP::ARP_REP: + PLOG(PL_ALWAYS, "reply "); + break; + default: + PLOG(PL_ALWAYS, "??? "); + break; + } + ProtoAddress addr; + arp.GetSenderHardwareAddress(addr); + PLOG(PL_ALWAYS, "from eth:%s ", addr.GetHostString()); + arp.GetSenderProtocolAddress(addr); + PLOG(PL_ALWAYS, "ip:%s ", addr.GetHostString()); + arp.GetTargetProtocolAddress(addr); + PLOG(PL_ALWAYS, "for ip:%s ", addr.GetHostString()); + if (ProtoPktARP::ARP_REP == arp.GetOpcode()) + { + + arp.GetTargetHardwareAddress(addr); + PLOG(PL_ALWAYS, "eth:%s ", addr.GetHostString()); + } + PLOG(PL_ALWAYS, "\n"); + break; + } + default: + PLOG(PL_ERROR, "ArposerApp::PeekPkt() unknown %s packet type\n", inbound ? "inbound" : "outbound"); + break; + } +} // end ArposerApp::PeekPkt() + + +/** + * @note We offset the buffer here by 2 bytes since + * Ethernet header is 14 bytes + * (i.e. not a multiple of 4 (sizeof(UINT32)) + * This gives us a properly aligned buffer for + * 32-bit aligned IP packets + */ +void ArposerApp::OnInboundPkt(ProtoChannel& theChannel, + ProtoChannel::Notification notifyType) +{ + ProtoTime currentTime; + if (ProtoChannel::NOTIFY_INPUT != notifyType) return; + while(1) + { + ProtoCap::Direction direction; + + const int BUFFER_MAX = 4096; + UINT32 alignedBuffer[BUFFER_MAX/sizeof(UINT32)]; + // offset by 2-bytes so IP content is 32-bit aligned + UINT16* ethBuffer = ((UINT16*)alignedBuffer) + 1; + unsigned int numBytes = BUFFER_MAX - 2; + + if (!cap->Recv((char*)ethBuffer, numBytes, &direction)) + { + PLOG(PL_ERROR, "ArposerApp::OnInboundPkt() ProtoCap::Recv() error\n"); + break; + } + + if (numBytes == 0) break; // no more packets to receive + + if ((ProtoCap::OUTBOUND != direction)) + { + // Map ProtoPktETH instance into buffer and init for processing + // (void* cast here is OK since ProtoPktETH is OK w/ UINT16* + ProtoPktETH ethPkt((UINT32*)((void*)ethBuffer), BUFFER_MAX - 2); + if (!ethPkt.InitFromBuffer(numBytes)) + { + PLOG(PL_ERROR, "ArposerApp::OnInboundPkt() error: bad Ether frame\n"); + continue; + } + + // In "MNE" environment, ignore packets from blocked MAC sources + ProtoAddress srcMacAddr; + ethPkt.GetSrcAddr(srcMacAddr); + + // Only handle ARP packets (skip others) + if (ProtoPktETH::ARP == ethPkt.GetType()) + { + + ProtoPktARP arp; + if (!arp.InitFromBuffer((UINT32*)((void*)ethPkt.AccessPayload()), ethPkt.GetPayloadLength())) + { + PLOG(PL_ERROR, "ArposerApp::PeekPkt() received bad ARP packet?\n"); + break; + } + // Skip ARP replies (only respond to ARP requests) + if (ProtoPktARP::ARP_REQ != arp.GetOpcode()) + continue; + + PLOG(PL_ALWAYS,"ArposerApp::PeekPkt() inbound ARP "); + switch(arp.GetOpcode()) + { + case ProtoPktARP::ARP_REQ: + PLOG(PL_ALWAYS, "request "); + break; + case ProtoPktARP::ARP_REP: + PLOG(PL_ALWAYS, "reply "); + break; + default: + PLOG(PL_ALWAYS, "??? "); + break; + } + ProtoAddress senderMAC; + arp.GetSenderHardwareAddress(senderMAC); + PLOG(PL_ALWAYS, "from eth:%s ", senderMAC.GetHostString()); + ProtoAddress senderIP; + arp.GetSenderProtocolAddress(senderIP); + PLOG(PL_ALWAYS, "ip:%s ", senderIP.GetHostString()); + ProtoAddress targetIP; + arp.GetTargetProtocolAddress(targetIP); + PLOG(PL_ALWAYS, "for ip:%s ", targetIP.GetHostString()); + if (ProtoPktARP::ARP_REP == arp.GetOpcode()) + { + ProtoAddress targetMAC; + arp.GetTargetHardwareAddress(targetMAC); + PLOG(PL_ALWAYS, "eth:%s ", targetMAC.GetHostString()); + } + PLOG(PL_ALWAYS, "\n"); + + // Build our ARP reply + arp.InitIntoBuffer((UINT32*)((void*)ethPkt.AccessPayload()), BUFFER_MAX - 16); + arp.SetOpcode(ProtoPktARP::ARP_REP); + arp.SetSenderHardwareAddress(cap->GetInterfaceAddr()); + arp.SetSenderProtocolAddress(targetIP); // target IP from above + arp.SetTargetHardwareAddress(senderMAC); // hw addr of requestor + arp.SetTargetProtocolAddress(senderIP); // IP addr of requestor + + // Send the reply + ethPkt.SetSrcAddr(cap->GetInterfaceAddr()); + ethPkt.SetDstAddr(srcMacAddr); + ethPkt.SetPayloadLength(arp.GetLength()); + + numBytes = ethPkt.GetLength(); + cap->Send((char*)ethBuffer, numBytes); // tbd - check result + } + + } + } // end while(1) + +} // end ArposerApp::OnInboundPkt + diff --git a/examples/detourExample.cpp b/examples/detourExample.cpp index dbf9597..4015007 100644 --- a/examples/detourExample.cpp +++ b/examples/detourExample.cpp @@ -254,13 +254,13 @@ void DetourExample::DetourEventHandler(ProtoChannel& theChannel, unsigned int numBytes = 8192; if (detour->Recv((char*)buffer, numBytes)) { - //TRACE("detour recv'd packet ...\n"); + TRACE("detour recv'd packet ...\n"); if (0 != numBytes) { allow = true; if (allow) { - //TRACE("detour allowing packet len:%u\n", numBytes); + TRACE("detour allowing packet len:%u\n", numBytes); ProtoPktIP ipPkt(buffer, numBytes); ipPkt.InitFromBuffer(numBytes); @@ -290,8 +290,8 @@ void DetourExample::DetourEventHandler(ProtoChannel& theChannel, { if (udpPkt.ChecksumIsValid(ipPkt)) { - //TRACE(" UDP with valid checksum %04x (computed: %04x)\n", - // udpPkt.GetChecksum(), udpPkt.ComputeChecksum(ipPkt)); + TRACE(" UDP with valid checksum %04x (computed: %04x)\n", + udpPkt.GetChecksum(), udpPkt.ComputeChecksum(ipPkt)); } else { @@ -306,11 +306,11 @@ void DetourExample::DetourEventHandler(ProtoChannel& theChannel, ProtoPktIPv4 ipv4Pkt(ipPkt); TRACE(" proto:%d\n", ipv4Pkt.GetProtocol()); + /* char base64Buffer[8192]; - unsigned int length = ProtoBase64::Encode((char*)buffer, numBytes, base64Buffer, 8192); - //TRACE("packet =\n %s\n", base64Buffer); - + TRACE("packet(base64)=\n %s\n", base64Buffer); + */ for (unsigned int i = 0; i < numBytes; i++) { if (0 == i) diff --git a/examples/graphRider.cpp b/examples/graphRider.cpp index f613eaa..de2e55e 100644 --- a/examples/graphRider.cpp +++ b/examples/graphRider.cpp @@ -417,23 +417,23 @@ int GraphRider::CalculateFullECDS(ManetGraph& UINT8 priorityN2 = useDegree ? ifaceN2->GetAdjacencyCount() : nodeN2.GetRtrPriority(); - /* - if (NULL == ifaceN1Max) - { - ifaceN1Max = ifaceN2; - } - else - { - Node& nodeN1Max = static_cast(ifaceN1Max->GetNode()); - UINT8 priorityN1Max = useDegree ? ifaceN1Max->GetAdjacencyCount() : nodeN1Max.GetRtrPriority(); - if ((priorityN2 > priorityN1Max) || - ((priorityN2 == priorityN1Max) && - (nodeN2.GetId() > nodeN1Max.GetId()))) - { - ifaceN1Max = ifaceN2; - } - } - * + // + //if (NULL == ifaceN1Max) + //{ + // ifaceN1Max = ifaceN2; + //} + //else + //{ + //Node& nodeN1Max = static_cast(ifaceN1Max->GetNode()); + //UINT8 priorityN1Max = useDegree ? ifaceN1Max->GetAdjacencyCount() : nodeN1Max.GetRtrPriority(); + //if ((priorityN2 > priorityN1Max) || + // ((priorityN2 == priorityN1Max) && + // (nodeN2.GetId() > nodeN1Max.GetId()))) + // { + // ifaceN1Max = ifaceN2; + // } + // } + diff --git a/examples/jsonExample.cpp b/examples/jsonExample.cpp new file mode 100644 index 0000000..e45333b --- /dev/null +++ b/examples/jsonExample.cpp @@ -0,0 +1,74 @@ +#include + +#include "protoJson.h" +#include "protoDebug.h" + +int main(int argc, char* argv[]) +{ + // Here we use the ProtoJson::Parser to parse JSON text from + // STDIN. If a complete JSON document is procesed, the resulting + // document is printed to STDOUT. + ProtoJson::Parser parser; + ProtoJson::Parser::Status status = ProtoJson::Parser::PARSE_MORE; + int result; + char buffer[1024]; + while (0 != (result = fread(buffer, sizeof(char), 1023, stdin))) + { + status = parser.ProcessInput(buffer, result); + // Note future extensions to parser could easily include + // ongoing "stream" process with PARSE_DONE returned for + // completed root level items. I.e., an indefinite stream + // of JSON content could be incrementally processed if + // desired. + if (ProtoJson::Parser::PARSE_ERROR == status) break; + } + + if (ProtoJson::Parser::PARSE_DONE == status) + { + + ProtoJson::Document* doc = parser.AccessDocument(); + + /* + // This adds a top level object to the loaded document to + // illustrate how a document can be built/augmented + ProtoJson::Object* object = new ProtoJson::Object(); + ProtoJson::Array* array = new ProtoJson::Array(); + for (unsigned int i = 0; i < 10; i++) + { + if (0 != (i & 1)) + { + // odd index array entries are integers + ProtoJson::Number* number = new ProtoJson::Number((int)(i*2)); + array->AppendValue(*number); + } + else + { + // even index array entries are floating + ProtoJson::Number* number = new ProtoJson::Number((double)i / 4.0); + array->AppendValue(*number); + } + } + object->InsertEntry("exampleArray", *array); + doc->AddItem(*object); + */ + + // Note the ProtoJson::Document::Print() method uses + // the Document::Iterator class to do a depth-first + // traversal of the JSON document content the parser loaded. + // In the future, this example may be expanded to provide + // more detailed illustration of the Document::Iterator _and_ + // methods to build ProtoJson::Document instances from + // scratch. + doc->Print(stdout); + } + else if (ProtoJson::Parser::PARSE_MORE == status) + { + TRACE("jsonExample: input document was not complete?!\n"); + } + else // PARSE_ERROR + { + TRACE("jsonExample: error parsing input document!\n"); + } + + TRACE("jsonExample::Done.\n"); +} // end main() diff --git a/examples/pcmd.cpp b/examples/pcmd.cpp new file mode 100644 index 0000000..4ae36aa --- /dev/null +++ b/examples/pcmd.cpp @@ -0,0 +1,66 @@ + +#include "protoPipe.h" +#include // for stderr output as needed +#include // for atoi() + +void Usage() +{ + fprintf(stderr, "Usage: pcmd commands ...\n"); +} + +int main(int argc, char* argv[]) +{ + if (argc < 3) + { + fprintf(stderr, "pcmd error: insufficient arguments!\n"); + Usage(); + return -1; + } + // Build command + char cmdBuffer[8192]; + unsigned int cmdLen = 0; + cmdBuffer[0] = '\0'; + for (int i = 2; i < argc; i++) + { + size_t argLen = strlen(argv[i]); + if ((cmdLen + argLen) > 8192) + { + fprintf(stderr, "pcmd error: command is too large!\n"); + return -1; + } + else + { + strcat(cmdBuffer, argv[i]); + cmdLen += argLen; + } + if ((i+1) < argc) + { + if (cmdLen < 8192) + { + strcat(cmdBuffer, " "); + cmdLen++; + } + else + { + fprintf(stderr, "pcmd error: command message is too large!\n"); + return -1; + } + } + } + + // Open pipe and send command + ProtoPipe msgPipe(ProtoPipe::MESSAGE); + if (!msgPipe.Connect(argv[1])) + { + fprintf(stderr, "pcmd error: error connecting to pipe \"%s\"\n", argv[1]); + return -1; + } + if (!msgPipe.Send(cmdBuffer, cmdLen)) + { + fprintf(stderr, "pcmd error: error sending to pipe \"%s\"\n", argv[1]); + return -1; + } + msgPipe.Close(); + + return 0; +} diff --git a/examples/pipeExample.cpp b/examples/pipeExample.cpp index b2a0820..a0532fe 100644 --- a/examples/pipeExample.cpp +++ b/examples/pipeExample.cpp @@ -73,7 +73,7 @@ PipeExample::PipeExample() PipeExample::~PipeExample() { - if (msg_buffer) + if (NULL != msg_buffer) { delete[] msg_buffer; msg_buffer = NULL; diff --git a/examples/pipeExample.py b/examples/pipeExample.py new file mode 100644 index 0000000..a4aef8c --- /dev/null +++ b/examples/pipeExample.py @@ -0,0 +1,33 @@ + +import protokit +import sys +import time +# Usage: python pipeExample {send | recv} + + +# Take your pick of which pipe type you want to try +# (Note that "STREAM" is currently limited to a single +# accepted connection) + +#pipeType = "MESSAGE" +pipeType = "STREAM" + +mode = sys.argv[1] +pipeName = sys.argv[2] + +protoPipe = protokit.Pipe(pipeType) + +if "recv" == mode: + protoPipe.Listen(pipeName) + if "STREAM" == pipeType: + protoPipe.Accept() + while True: + buf = protoPipe.Recv(8192) + print buf +else: + protoPipe.Connect(pipeName) + count = 0 + while True: + count += 1 + protoPipe.Send("Hello, ProtoPipe %s (%d) ..." % (pipeName, count)) + time.sleep(1.0) diff --git a/examples/protoExample.cpp b/examples/protoExample.cpp index 97586bb..bbbb7f2 100644 --- a/examples/protoExample.cpp +++ b/examples/protoExample.cpp @@ -18,56 +18,57 @@ class ProtoExample : #endif // if/else SIMULATE { -public: - ProtoExample(); - ~ProtoExample(); - - /** - * Override from ProtoApp or NsProtoSimAgent base - */ - bool OnStartup(int argc, const char*const* argv); - /** - * Override from ProtoApp or NsProtoSimAgent base - */ - bool ProcessCommands(int argc, const char*const* argv); - /** - * Override from ProtoApp or NsProtoSimAgent base - */ - void OnShutdown(); - /** - * Override from ProtoApp or NsProtoSimAgent base - */ - virtual bool HandleMessage(unsigned int len, const char* txBuffer,const ProtoAddress& srcAddr) {return true;} - -private: - enum CmdType {CMD_INVALID, CMD_ARG, CMD_NOARG}; - static CmdType GetCmdType(const char* string); - bool OnCommand(const char* cmd, const char* val); - void Usage(); - - bool OnTxTimeout(ProtoTimer& theTimer); - void OnUdpSocketEvent(ProtoSocket& theSocket, - ProtoSocket::Event theEvent); - void OnClientSocketEvent(ProtoSocket& theSocket, - ProtoSocket::Event theEvent); - void OnServerSocketEvent(ProtoSocket& theSocket, - ProtoSocket::Event theEvent); - static const char* const CMD_LIST[]; - static void SignalHandler(int sigNum); - - // ProtoTimer/ UDP socket demo members - ProtoTimer tx_timer; - ProtoAddress dst_addr; - - ProtoSocket udp_tx_socket; - ProtoSocket udp_rx_socket; - - // TCP socket demo members - ProtoSocket server_socket; - ProtoSocket client_socket; - unsigned int client_msg_count; - ProtoSocket::List connection_list; - bool use_html; + public: + ProtoExample(); + ~ProtoExample(); + + /** + * Override from ProtoApp or NsProtoSimAgent base + */ + bool OnStartup(int argc, const char*const* argv); + /** + * Override from ProtoApp or NsProtoSimAgent base + */ + bool ProcessCommands(int argc, const char*const* argv); + /** + * Override from ProtoApp or NsProtoSimAgent base + */ + void OnShutdown(); + /** + * Override from ProtoApp or NsProtoSimAgent base + */ + virtual bool HandleMessage(unsigned int len, const char* txBuffer,const ProtoAddress& srcAddr) {return true;} + + private: + enum CmdType {CMD_INVALID, CMD_ARG, CMD_NOARG}; + static CmdType GetCmdType(const char* string); + bool OnCommand(const char* cmd, const char* val); + void Usage(); + + void OnTxTimeout(ProtoTimer& theTimer); + void OnUdpSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + void OnClientSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + void OnServerSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + static const char* const CMD_LIST[]; + static void SignalHandler(int sigNum); + + // ProtoTimer/ UDP socket demo members + ProtoTimer tx_timer; + ProtoAddress dst_addr; + + ProtoSocket udp_tx_socket; + ProtoSocket udp_rx_socket; + + // TCP socket demo members + ProtoSocket server_socket; + ProtoSocket client_socket; + unsigned int client_msg_count; + ProtoSocket::List connection_list; + bool use_html; + }; // end class ProtoExample @@ -183,10 +184,18 @@ bool ProtoExample::OnStartup(int argc, const char*const* argv) } #ifndef SIMULATE + // Here's some code to test the ProtoSocket routines for network interface info ProtoAddress localAddress; char nameBuffer[256]; nameBuffer[255] = '\0'; + + // ljt test + ProtoAddress dst; + dst.ResolveFromString("fe80::426c:8fff:fe31:4f8e"); + if (dst.ResolveToName(nameBuffer,255)) + TRACE("protoExample: LJT ipv6 host name: %s\n", nameBuffer); + if (localAddress.ResolveLocalAddress()) { @@ -194,7 +203,7 @@ bool ProtoExample::OnStartup(int argc, const char*const* argv) if (localAddress.ResolveToName(nameBuffer, 255)) TRACE("protoExample: local default host name: %s\n", nameBuffer); else - TRACE("protoExample: unable to resolve local default IP address\n"); + TRACE("protoExample: unable to resolve local default IP address to name\n"); } else { @@ -242,7 +251,7 @@ bool ProtoExample::OnStartup(int argc, const char*const* argv) ProtoAddressList::Iterator iterator(addrList); while (iterator.GetNextAddress(ifaceAddr)) { - TRACE(" %s/%d", ifaceAddr.GetHostString(), ProtoNet::GetInterfaceAddressMask(nameBuffer, ifaceAddr)); + TRACE(" %s/%d", ifaceAddr.GetHostString(),ProtoNet::GetInterfaceAddressMask(nameBuffer, ifaceAddr)); if (ifaceAddr.IsLinkLocal()) TRACE(" (link local)"); } TRACE("\n"); @@ -255,11 +264,53 @@ bool ProtoExample::OnStartup(int argc, const char*const* argv) ProtoAddressList::Iterator iterator(addrList); while (iterator.GetNextAddress(ifaceAddr)) { + TRACE(" %s/%d", ifaceAddr.GetHostString(), ProtoNet::GetInterfaceAddressMask(nameBuffer, ifaceAddr)); - if (ifaceAddr.IsLinkLocal()) TRACE(" (link local)"); + + if (ifaceAddr.IsLinkLocal()) TRACE(" (link local)"); } TRACE("\n"); } + addrList.Destroy(); + + // Test our interface add routine + /* + char * ifaceName = "192.168.1.6"; + ProtoAddress tmpAddr; + tmpAddr.ConvertFromString("192.168.1.6"); + int maskLen = 24; + bool result = ProtoNet::AddInterfaceAddress(ifaceName, tmpAddr, maskLen); + */ + + +#ifndef WIN32 + // This code should work for Linux and BSD (incl. Mac OSX) + // Get IPv4 group memberships for interfaces + if (ProtoNet::GetGroupMemberships(nameBuffer, ProtoAddress::IPv4, addrList)) + { + TRACE(" IPv4 memberships:"); + ProtoAddressList::Iterator iterator(addrList); + ProtoAddress groupAddr; + while (iterator.GetNextAddress(groupAddr)) + { + TRACE(" %s", groupAddr.GetHostString()); + } + TRACE("\n"); + } + addrList.Destroy(); + if (ProtoNet::GetGroupMemberships(nameBuffer, ProtoAddress::IPv6, addrList)) + { + TRACE(" IPv6 memberships:"); + ProtoAddressList::Iterator iterator(addrList); + ProtoAddress groupAddr; + while (iterator.GetNextAddress(groupAddr)) + { + TRACE(" %s", groupAddr.GetHostString()); + } + TRACE("\n"); + } + addrList.Destroy(); +#endif // WIN32 } delete[] indexArray; } @@ -267,8 +318,6 @@ bool ProtoExample::OnStartup(int argc, const char*const* argv) { TRACE("protoExample: host has no network interfaces?!\n"); } - - // Here's some code to get the system routing table ProtoRouteTable routeTable; ProtoRouteMgr* routeMgr = ProtoRouteMgr::Create(); @@ -300,7 +349,7 @@ bool ProtoExample::OnStartup(int argc, const char*const* argv) entry->GetMetric()); } //ProtoAddress dst; - //dst.ResolveFromString("132.250.93.34"); + //dst.ResolveFromString("10.1.2.3"); //ProtoAddress gw; //routeMgr->SetRoute(dst, 32, gw, 4, 0); routeMgr->Close(); @@ -378,7 +427,7 @@ bool ProtoExample::OnCommand(const char* cmd, const char* val) } else if (!strncmp("send", cmd, len)) { - if (!udp_tx_socket.Open()) + if (!udp_tx_socket.Open(0, ProtoAddress::IPv4, false)) { PLOG(PL_ERROR, "ProtoExample::ProcessCommand(send) error opening udp_tx_socket\n"); return false; @@ -431,11 +480,24 @@ bool ProtoExample::OnCommand(const char* cmd, const char* val) UINT16 thePort; if (1 == sscanf(portPtr, "%hu", &thePort)) { - if (!udp_rx_socket.Open(thePort)) + + if (!udp_rx_socket.Open(0, ProtoAddress::IPv4, false)) { PLOG(PL_ERROR, "ProtoExample::ProcessCommand(recv) error opening udp_rx_socket\n"); return false; } + udp_rx_socket.EnableRecvDstAddr(); // for testing this feature + if (groupAddr.IsValid() && groupAddr.IsMulticast()) + { + + bool result = udp_rx_socket.SetReuse(true); + TRACE("set port reuse result %d...\n", result); + } + if (!udp_rx_socket.Bind(thePort)) + { + PLOG(PL_ERROR, "ProtoExample::ProcessCommand(recv) error binding udp_rx_socket\n"); + return false; + } if (groupAddr.IsValid() && groupAddr.IsMulticast()) { if (!(udp_rx_socket.JoinGroup(groupAddr))) @@ -468,12 +530,14 @@ bool ProtoExample::OnCommand(const char* cmd, const char* val) return false; } server.SetPort(atoi(ptr)); + TRACE("calling connect ...\n"); if (!client_socket.Connect(server)) { PLOG(PL_ERROR, "ProtoExample::ProcessCommand(connect) error connecting\n"); return false; } client_msg_count = 5; // (Send 5 messages, then disconnect) + TRACE(" (completed. starting output notification ...\n"); client_socket.StartOutputNotification(); } else @@ -510,7 +574,7 @@ bool ProtoExample::OnCommand(const char* cmd, const char* val) return true; } // end ProtoExample::OnCommand() -bool ProtoExample::OnTxTimeout(ProtoTimer& /*theTimer*/) +void ProtoExample::OnTxTimeout(ProtoTimer& /*theTimer*/) { const char* string = "Hello there UDP peer, how are you doing?"; unsigned int len = strlen(string) + 1; @@ -523,10 +587,10 @@ bool ProtoExample::OnTxTimeout(ProtoTimer& /*theTimer*/) } else if (len != numBytes) { - PLOG(PL_ERROR, "ProtoExample::OnClientSocketEvent() incomplete SendTo()\n"); + PLOG(PL_ERROR, "ProtoExample::OnTxTimeout() incomplete SendTo()\n"); TRACE(" (only sent %lu of %lu bytes)\n", numBytes, len); } - return true; + } // end ProtoExample::OnTxTimeout() void ProtoExample::OnUdpSocketEvent(ProtoSocket& theSocket, @@ -556,17 +620,24 @@ void ProtoExample::OnUdpSocketEvent(ProtoSocket& theSocket, { static unsigned long count = 0; TRACE("RECV) ...\n"); - ProtoAddress srcAddr; + ProtoAddress srcAddr, dstAddr; char buffer[1024]; unsigned int len = 1024; - if (theSocket.RecvFrom(buffer, len, srcAddr)) + // This RecvFrom() call w/ dstAddr may not be suppoted on WIN32 yet + if (theSocket.RecvFrom(buffer, len, srcAddr, dstAddr)) { buffer[len] = '\0'; if (len) - TRACE("ProtoExample::OnUdpSocketEvent(%lu) received \"%s\" from %s\n", - count++, buffer, srcAddr.GetHostString()); + { + char dstAddrString[32]; + dstAddr.GetHostString(dstAddrString, 32); + TRACE("ProtoExample::OnUdpSocketEvent(%lu) received \"%s\" from %s to dest %s\n", + count++, buffer, srcAddr.GetHostString(), dstAddrString); + } else + { TRACE("ProtoExample::OnUdpSocketEvent() received 0 bytes\n"); + } } else { @@ -609,8 +680,10 @@ void ProtoExample::OnClientSocketEvent(ProtoSocket& theSocket, TRACE("SEND) ...\n"); if (0 == client_msg_count) { + TRACE("protoExample: client message transmission completed.\n"); client_socket.StopOutputNotification(); // don't send any more - // TBD - start a timer and do shutdown after timeout??? + // Uncomment this to have the client cleanly disconnect after transmission + // (otherwise, linger for server response until user "Ctrl-C" exit) client_socket.Shutdown(); return; } @@ -624,9 +697,9 @@ void ProtoExample::OnClientSocketEvent(ProtoSocket& theSocket, } else if (len != numBytes) { - PLOG(PL_ERROR, "ProtoExample::OnClientSocketEvent() incomplete Send()\n"); - TRACE(" (only sent %lu of %lu bytes)\n", numBytes, len); - } + PLOG(PL_WARN, "ProtoExample::OnClientSocketEvent() incomplete Send()\n"); + TRACE(" (only sent %lu of %lu bytes (msgCount:%d))\n", numBytes, len, client_msg_count); + } else { TRACE("sent %u bytes to ProtoServer ...\n", len); @@ -694,13 +767,13 @@ void ProtoExample::OnServerSocketEvent(ProtoSocket& theSocket, { TRACE("ACCEPT) ...\n"); ProtoSocket* connectedSocket = new ProtoSocket(ProtoSocket::TCP); - connectedSocket->SetListener(this, &ProtoExample::OnServerSocketEvent); - - if (!connectedSocket) + if (NULL == connectedSocket) { PLOG(PL_ERROR, "ProtoExample::OnServerSocketEvent(ACCEPT) new ProtoSocket error\n"); break; } + connectedSocket->SetListener(this, &ProtoExample::OnServerSocketEvent); + if (!server_socket.Accept(connectedSocket)) { PLOG(PL_ERROR, "ProtoExample::OnServerSocketEvent(ACCEPT) error accepting connection\n"); diff --git a/examples/riposer.cpp b/examples/riposer.cpp new file mode 100644 index 0000000..41b09b8 --- /dev/null +++ b/examples/riposer.cpp @@ -0,0 +1,403 @@ +#include "protoApp.h" +#include "protoSocket.h" +#include "protoPktRIP.h" + +#include +#include +#include +#include // for "isspace()" + +/** + * @class RiposerApp + * + * @brief simple app that can transmit unsolicited RIP response + * messages to advertise a route + * + */ +class RiposerApp : public ProtoApp +{ + public: + RiposerApp(); + ~RiposerApp(); + + bool OnStartup(int argc, const char*const* argv); + bool ProcessCommands(int argc, const char*const* argv); + void OnShutdown(); + + static const char* RIP_ADDR; + static const UINT16 RIP_PORT; + static const double RIP_TIMEOUT; + + private: + enum CmdType {CMD_INVALID, CMD_ARG, CMD_NOARG}; + static const char* const CMD_LIST[]; + static CmdType GetCmdType(const char* string); + bool OnCommand(const char* cmd, const char* val); + void Usage(); + + bool OnRipTimeout(ProtoTimer& theTimer); + void OnRipSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + + ProtoTimer rip_timer; + ProtoSocket rip_socket; + ProtoAddress rip_addr; // dest group addr for RIP + char rip_iface[256]; + ProtoAddress route_addr; + unsigned int route_mask_len; + ProtoAddress route_next_hop; + +}; // end class RiposerApp + +const char* RiposerApp::RIP_ADDR = "224.0.0.9"; +const UINT16 RiposerApp::RIP_PORT = 520; +const double RiposerApp::RIP_TIMEOUT = 30.0; + +void RiposerApp::Usage() +{ + fprintf(stderr, "Usage: riposer [interface ][route /,]\n" + " [debug ][help]\n"); +} + +const char* const RiposerApp::CMD_LIST[] = +{ + "-help", // print help info an exit + "+interface", // name of interface on which to listen / respond + "+route", // /, + "+debug", // + NULL +}; + +/** + * This macro creates our ProtoApp derived application instance + */ +PROTO_INSTANTIATE_APP(RiposerApp) + +RiposerApp::RiposerApp() + : rip_socket(ProtoSocket::UDP) +{ + rip_iface[0] = '\0'; + rip_timer.SetListener(this, &RiposerApp::OnRipTimeout); + rip_timer.SetInterval(RIP_TIMEOUT); + rip_timer.SetRepeat(-1.0); // forever + rip_socket.SetNotifier(&GetSocketNotifier()); + rip_socket.SetListener(this, &RiposerApp::OnRipSocketEvent); + rip_addr.ConvertFromString(RIP_ADDR); // default RIP group addr/port + rip_addr.SetPort(RIP_PORT); +} + +RiposerApp::~RiposerApp() +{ +} + +RiposerApp::CmdType RiposerApp::GetCmdType(const char* cmd) +{ + if (!cmd) return CMD_INVALID; + unsigned int len = strlen(cmd); + bool matched = false; + CmdType type = CMD_INVALID; + const char* const* nextCmd = CMD_LIST; + while (*nextCmd) + { + if (!strncmp(cmd, *nextCmd+1, len)) + { + if (matched) + { + // ambiguous command (command should match only once) + return CMD_INVALID; + } + else + { + matched = true; + if ('+' == *nextCmd[0]) + type = CMD_ARG; + else + type = CMD_NOARG; + } + } + nextCmd++; + } + return type; +} // end RiposerApp::GetCmdType() + + +bool RiposerApp::OnStartup(int argc, const char*const* argv) +{ + // Seed rand() with time of day usec + // (comment out for repeatable results) + struct timeval currentTime; + ProtoSystemTime(currentTime); + srand((unsigned int)currentTime.tv_usec); + + if (!ProcessCommands(argc, argv)) + { + PLOG(PL_ERROR, "RiposerApp::OnStartup() error processing command line options\n"); + Usage(); + return false; + } + + // Open up the RIP UDP socket (port 520) and join the RIP_ADDR + if (!rip_socket.Open(rip_addr.GetPort())) + { + PLOG(PL_ERROR, "RiposerApp::OnStartup() error: unable to open rip socket\n"); + return false; + } + // Check if specific iface was set + const char* mcastIfaceName = NULL; + if (0 != strlen(rip_iface)) + mcastIfaceName = rip_iface; + if (!rip_socket.JoinGroup(rip_addr, mcastIfaceName)) + { + PLOG(PL_ERROR, "RiposerApp::OnStartup() error: unable to join rip multicast group\n"); + return false; + } + if (NULL != mcastIfaceName) + { + if (!rip_socket.SetMulticastInterface(mcastIfaceName)) + { + PLOG(PL_ERROR, "RiposerApp::OnStartup() error: unable to join set rip multicast interface\n"); + return false; + } + } + + if (route_addr.IsValid()) + { + // We've been instructed advertise a route, so startup "rip_timer" + OnRipTimeout(rip_timer); + rip_timer.SetInterval(RIP_TIMEOUT); + ActivateTimer(rip_timer); + } + + PLOG(PL_INFO, "riposer: running on interface: %s\n", mcastIfaceName ? mcastIfaceName : "default"); + + return true; +} // end RiposerApp::OnStartup() + +void RiposerApp::OnShutdown() +{ + if (rip_timer.IsActive()) + rip_timer.Deactivate(); + if (rip_socket.IsOpen()) + rip_socket.Close(); + + PLOG(PL_INFO, "riposer: Done.\n"); + +} // end RiposerApp::OnShutdown() + +bool RiposerApp::ProcessCommands(int argc, const char*const* argv) +{ + // Dispatch command-line commands to our OnCommand() method + int i = 1; + while ( i < argc) + { + // Is it a riposer command? + switch (GetCmdType(argv[i])) + { + case CMD_INVALID: + { + PLOG(PL_ERROR, "RiposerApp::ProcessCommands() Invalid command:%s\n", + argv[i]); + Usage(); + return false; + } + case CMD_NOARG: + if (!OnCommand(argv[i], NULL)) + { + PLOG(PL_ERROR, "RiposerApp::ProcessCommands() ProcessCommand(%s) error\n", + argv[i]); + Usage(); + return false; + } + i++; + break; + case CMD_ARG: + if (!OnCommand(argv[i], argv[i+1])) + { + PLOG(PL_ERROR, "RiposerApp::ProcessCommands() ProcessCommand(%s, %s) error\n", + argv[i], argv[i+1]); + Usage(); + return false; + } + i += 2; + break; + } + } + return true; +} // end RiposerApp::ProcessCommands() + +bool RiposerApp::OnCommand(const char* cmd, const char* val) +{ + // (TBD) move command processing into Mgen class ??? + CmdType type = GetCmdType(cmd); + ASSERT(CMD_INVALID != type); + size_t len = strlen(cmd); + if ((CMD_ARG == type) && !val) + { + PLOG(PL_ERROR, "RiposerApp::ProcessCommand(%s) missing argument\n", cmd); + Usage(); + return false; + } + else if (!strncmp("help", cmd, len)) + { + Usage(); + exit(0); + } + else if (!strncmp("interface", cmd, len)) + { + strncpy(rip_iface, val, 255); + rip_iface[255] = '\0'; + } + else if (!strncmp("route", cmd, len)) + { + // route /, + // 1) get + const char* txtPtr = val; + const char* delimiter = strchr(txtPtr, '/'); + if (NULL == delimiter) + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: missing \n"); + return false; + } + size_t txtLen = delimiter - txtPtr; + char tempTxt[256]; + if (txtLen > 255) txtLen = 255; + strncpy(tempTxt, txtPtr, txtLen); + tempTxt[txtLen] = '\0'; // null terminate + if (!route_addr.ConvertFromString(tempTxt)) + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: invalid \n"); + return false; + } + if (ProtoAddress::IPv4 != route_addr.GetType()) + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: non-IPv4 \n"); + return false; + } + // 2) get + txtPtr = delimiter + 1; + delimiter = strchr(txtPtr, ','); + if (NULL != delimiter) + txtLen = delimiter - txtPtr; + else + txtLen = strlen(txtPtr); + if (txtLen > 255) txtLen = 255; + strncpy(tempTxt, txtPtr, txtLen); + tempTxt[txtLen] = '\0'; + if (1 != sscanf(tempTxt, "%u", &route_mask_len)) + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: invalid \n"); + return false; + } + // 3) get + if (NULL != delimiter) + { + txtPtr = delimiter + 1; + if (!route_next_hop.ConvertFromString(txtPtr)) + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: invalid \n"); + return false; + } + if (ProtoAddress::IPv4 != route_next_hop.GetType()) + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: non-IPv4 \n"); + return false; + } + } + else + { + PLOG(PL_ERROR, "RiposerApp::OnCommand(route) error: missing \n"); + return false; + } + } + else if (!strncmp("debug", cmd, len)) + { + SetDebugLevel(atoi(val)); + } + else + { + PLOG(PL_ERROR, "riposer error: invalid command\n"); + Usage(); + return false; + } + return true; +} // end RiposerApp::OnCommand() + +bool RiposerApp::OnRipTimeout(ProtoTimer& theTimer) +{ + // Send unsolicited RIP response with our route advertisement + UINT32 buffer[256]; + ProtoPktRIP response(buffer, 256); // zero inits buffer + response.SetCommand(ProtoPktRIP::RESPONSE); + response.SetVersion(ProtoPktRIP::VERSION); + response.AddRouteEntry(route_addr, route_mask_len, route_next_hop); + unsigned int numBytes = response.GetLength(); + if (!rip_socket.SendTo(response.GetBuffer(), numBytes, rip_addr)) + { + PLOG(PL_ERROR, "RiposerApp::OnRipTimer() error: sendto() error: %s\n", GetErrorString()); + } + return true; +} // end RiposerApp::OnRipTimeout() + + +void RiposerApp::OnRipSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent) +{ + if (ProtoSocket::RECV == theEvent) + { + UINT32 buffer[8192]; + unsigned int len = 8192; + ProtoAddress srcAddr; + if (theSocket.RecvFrom((char*)buffer, len, srcAddr)) + { + if (0 == len) return; // false alarm + // TBD - print out more detail RIP packet information ... + TRACE("riposer: received a %u byte RIP packet from %s/%hu\n", + len, srcAddr.GetHostString(), srcAddr.GetPort()); + + ProtoPktRIP rip(buffer, len, len); + + + TRACE("riposer: received RIP message length:%d version:%d ", len, rip.GetVersion()); + switch (rip.GetCommand()) + { + case ProtoPktRIP::REQUEST: + TRACE("REQUEST\n"); + break; + case ProtoPktRIP::RESPONSE: + TRACE("RESPONSE\n"); + break; + default: + TRACE("INVALID command type:%s\n", rip.GetCommand()); + break; + } + unsigned int entryCount = rip.GetNumEntry(); + for (unsigned int i = 0; i < entryCount; i++) + { + ProtoPktRIP::RouteEntry entry; + rip.AccessRouteEntry(i, entry); + switch (entry.GetAddressFamily()) + { + case ProtoPktRIP::IPv4: + { + ProtoAddress addr; + entry.GetAddress(addr); + char routeAddr[64]; + addr.GetHostString(routeAddr, 64); + entry.GetNextHop(addr); + char nextHop[64]; + addr.GetHostString(nextHop, 64); + TRACE(" route entry %s/%u via %s\n", routeAddr, entry.GetMaskLength(), nextHop); + break; + } + case ProtoPktRIP::AUTH: + TRACE(" AUTH entry\n"); + break; + default: + TRACE(" UNKNOWN address family: %d\n", entry.GetAddressFamily()); + } + } + } + } +} // end RiposerApp::OnRipSocketEvent() + + diff --git a/examples/threadExample.cpp b/examples/threadExample.cpp index c477fcc..ba93f87 100644 --- a/examples/threadExample.cpp +++ b/examples/threadExample.cpp @@ -89,13 +89,13 @@ void Ticker::Stop() /** * IMPORTANT NOTE @note Remember this timeout handler - * is being called by the dispatcher thread, _not_ + * is being called by the dispatcher child thread, _not_ * the main thread since we're using ProtoDispatcher::StartThread() * to run the dispatcher */ bool Ticker::OnTimeout(ProtoTimer& theTimer) { - PLOG(PL_ERROR, "threadExample: Ticker::OnTimeout() ...\n"); + TRACE("threadExample: child thread Ticker::OnTimeout() ...\n"); return true; } // end Ticker::OnTimeout() @@ -103,6 +103,8 @@ int main(int argc, char* argv[]) { Ticker ticker; + // This "starts" the ticker thread although it just sits and + // waits until its "StartTimer()" method is called if (!ticker.Start()) { PLOG(PL_ERROR, "threadExample: Ticker::Start() error\n"); @@ -113,24 +115,24 @@ int main(int argc, char* argv[]) while (count--) { #ifdef WIN32 - Sleep(10000); + Sleep(3000); #else sleep(3); #endif // if/else WIN32 if (count) { - PLOG(PL_ERROR, "threadExample: 3 seconds have passed (starting ticker timer)\n"); + TRACE("threadExample: main thread 3 seconds have passed (starting ticker timer)\n"); ticker.StartTimer(); } else { - PLOG(PL_ERROR, "threadExample: 3 more seconds have passed (stopping ticker timer)\n"); + TRACE("threadExample: main thread 3 more seconds have passed (stopping ticker timer)\n"); ticker.Stop(); } } - PLOG(PL_ERROR, "threadExample: ticker stopped.\n"); - TRACE("threadExample: Done.\n"); + TRACE("threadExample: main thread ticker stopped.\n"); + TRACE("threadExample: main thread Done.\n"); return 0; } // end main() diff --git a/examples/timerTest.cpp b/examples/timerTest.cpp index 4b701a0..1073c8f 100644 --- a/examples/timerTest.cpp +++ b/examples/timerTest.cpp @@ -77,7 +77,7 @@ class TimerTestApp : public ProtoApp bool OnCommand(const char* cmd, const char* val); void Usage(); - bool OnTimeout(ProtoTimer& theTimer); + void OnTimeout(ProtoTimer& theTimer); ProtoTimer the_timer; @@ -91,7 +91,11 @@ class TimerTestApp : public ProtoApp Histogram histogram; + unsigned int report_count; + int timer_int_count; struct timeval last_time; + + FILE* out_file; bool use_nanosleep; @@ -101,16 +105,18 @@ class TimerTestApp : public ProtoApp void TimerTestApp::Usage() { - fprintf(stderr, "Usage: timerTest [help] interval \n"); + fprintf(stderr, "Usage: timerTest [help][precise][interval ][output ][timer_int_count <5secIntervalCount>]\n"); } const char* const TimerTestApp::CMD_LIST[] = { "-help", // print help info an exit "+interval", // - "-nanosleep", + "-nanosleep", // Linux only "-priority", "-precise", + "+output", + "+timer_int_count", // number of 5 second report intervals "+debug", // NULL }; @@ -121,7 +127,7 @@ const char* const TimerTestApp::CMD_LIST[] = PROTO_INSTANTIATE_APP(TimerTestApp) TimerTestApp::TimerTestApp() - : first_timeout(true) +: first_timeout(true), report_count(0), timer_int_count(4), out_file(stdout) { the_timer.SetListener(this, &TimerTestApp::OnTimeout); the_timer.SetInterval(1.0); @@ -166,6 +172,19 @@ TimerTestApp::CmdType TimerTestApp::GetCmdType(const char* cmd) bool TimerTestApp::OnStartup(int argc, const char*const* argv) { +#if defined(USE_TIMERFD) + TRACE("USE_TIMERFD\n"); +#endif +#if defined(USE_SELECT) + TRACE("USE_SELECT\n"); +#endif +#if defined(HAVE_PSELECT) + TRACE("HAVE_PSELECT\n"); +#endif +#if defined(USE_EPOLL) + TRACE("USE_EPOLL\n"); +#endif + if (!ProcessCommands(argc, argv)) { PLOG(PL_ERROR, "TimerTestApp::OnStartup() error processing command line options\n"); @@ -211,7 +230,12 @@ bool TimerTestApp::OnStartup(int argc, const char*const* argv) void TimerTestApp::OnShutdown() { - histogram.Print(stdout); + histogram.Print(out_file); + if (stdout != out_file) + { + fclose(out_file); + out_file = stdout; + } PLOG(PL_ERROR, "timerTest: Done.\n"); } // end TimerTestApp::OnShutdown() @@ -296,6 +320,26 @@ bool TimerTestApp::OnCommand(const char* cmd, const char* val) } the_timer.SetInterval((double)timerInterval); } + else if (!strncmp("timer_int_count", cmd, len)) + { + int inCount; + if (1 != sscanf(val, "%d", &inCount)) + { + PLOG(PL_ERROR, "TimerTestApp::OnCommand(count) error: invalid argument\n",GetErrorString()); + return false; + } + timer_int_count = inCount; + } + else if (!strncmp("output", cmd, len)) + { + FILE* file = fopen(val, "w+"); + if (NULL == file) + { + PLOG(PL_ERROR, "TimerTestApp::OnCommand(interval) fopen() error: %s\n", GetErrorString()); + return false; + } + out_file = file; + } else if (!strncmp("debug", cmd, len)) { SetDebugLevel(atoi(val)); @@ -310,14 +354,14 @@ bool TimerTestApp::OnCommand(const char* cmd, const char* val) } // end TimerTestApp::OnCommand() -bool TimerTestApp::OnTimeout(ProtoTimer& /*theTimer*/) +void TimerTestApp::OnTimeout(ProtoTimer& /*theTimer*/) { struct timeval currentTime; ProtoSystemTime(currentTime); if (first_timeout) { - elapsed_sec = 0.0; first_timeout = false; + elapsed_sec = 0.0; ave_sum = 0.0; squ_sum = 0.0; timeout_count = 0; @@ -349,12 +393,12 @@ bool TimerTestApp::OnTimeout(ProtoTimer& /*theTimer*/) TRACE("timer interval: ave>%lf min>%lf max>%lf var>%lf\n", ave, delta_min, delta_max, var); - elapsed_sec = 0.0; + if (timer_int_count == ++report_count) Stop(); + } } last_time = currentTime; - return true; } // end TimerTestApp::OnTimeout() diff --git a/examples/ting.cpp b/examples/ting.cpp index 6f9d18c..421245b 100644 --- a/examples/ting.cpp +++ b/examples/ting.cpp @@ -1193,7 +1193,8 @@ bool TingSession::SendMessage(TingMessage::Type msgType, double backoff) struct timeval currentTime; ProtoSystemTime(currentTime); msg.SetSentTime(currentTime); - if (!ting_socket.SendTo((const char*)msg.GetBuffer(), msg.GetLength(), destAddr)) + unsigned int numBytes = msg.GetLength(); + if (!ting_socket.SendTo((const char*)msg.GetBuffer(), numBytes, destAddr)) { PLOG(PL_ERROR, "TingApp::SendMessage() error: unable to send message!\n"); return false; @@ -1272,7 +1273,7 @@ void TingSession::Summarize() if (offsetCount > 0) { double offsetAve = offsetSum / (double)offsetCount; - fprintf(log_file, "offset: min>%lf ave>%lf max>%lf ", offsetMin, offsetAve, offsetMax); + fprintf(log_file, "offset: min>%f ave>%f max>%f ", offsetMin, offsetAve, offsetMax); } else { @@ -1281,7 +1282,7 @@ void TingSession::Summarize() if (rttCount > 0) { double rttAve = rttSum / (double)rttCount; - fprintf(log_file, "rtt: min>%lf ave>%lf max>%lf\n", rttMin, rttAve, rttMax); + fprintf(log_file, "rtt: min>%f ave>%f max>%f\n", rttMin, rttAve, rttMax); } else { @@ -1334,7 +1335,7 @@ void TingSession::Summarize() if (offsetCount > 0) { double offsetAve = offsetSum / (double)offsetCount; - fprintf(log_file, "offset: min>%lf ave>%lf max>%lf ", offsetMin, offsetAve, offsetMax); + fprintf(log_file, "offset: min>%f ave>%f max>%f ", offsetMin, offsetAve, offsetMax); } else { @@ -1343,7 +1344,7 @@ void TingSession::Summarize() if (rttCount > 0) { double rttAve = rttSum / (double)rttCount; - fprintf(log_file, "rtt: min>%lf ave>%lf max>%lf\n", rttMin, rttAve, rttMax); + fprintf(log_file, "rtt: min>%f ave>%f max>%f\n", rttMin, rttAve, rttMax); } else { @@ -1400,7 +1401,7 @@ void TingSession::Summarize() if (offsetCount > 0) { double offsetAve = offsetSum / (double)offsetCount; - fprintf(log_file, "RBS offset: min>%lf ave>%lf max>%lf ", offsetMin, offsetAve, offsetMax); + fprintf(log_file, "RBS offset: min>%f ave>%f max>%f ", offsetMin, offsetAve, offsetMax); } else { @@ -1433,7 +1434,7 @@ void TingSession::Summarize() if (rttCount > 0) { double rttAve = rttSum / (double)rttCount; - fprintf(log_file, "rtt: min>%lf ave>%lf max>%lf\n", rttMin, rttAve, rttMax); + fprintf(log_file, "rtt: min>%f ave>%f max>%f\n", rttMin, rttAve, rttMax); } else { @@ -1523,7 +1524,7 @@ void TingApp::Usage() bool TingApp::OnStartup(int argc, const char*const* argv) { - SetDebugLevel(2); + SetDebugLevel(8); // By default, listen on the default address/port group_address.ResolveFromString(DEFAULT_GROUP); @@ -1743,7 +1744,10 @@ bool TingApp::OnStartup(int argc, const char*const* argv) return false; } if (mcastOnly) + { ting_socket.SetReuse(true); // let's use separate ting to "initiate" checks + ting_socket.SetLoopback(true); + } if (!(ting_socket.Bind(ting_port))) { // Bind to random socket since as this may be a unicast "initiator" @@ -1891,7 +1895,7 @@ void TingApp::OnTingSocketEvent(ProtoSocket& theSocket, void TingApp::HandleMessage(const TingMessage& msg, const ProtoAddress& srcAddr, const struct timeval& recvTime) { - //TRACE("TingApp::HandleMessage() from %s/%hu ...\n", srcAddr.GetHostString(), srcAddr.GetPort()); + TRACE("TingApp::HandleMessage() from %s/%hu ...\n", srcAddr.GetHostString(), srcAddr.GetPort()); // Make sure the message round id is valid if (0 == msg.GetRoundId()) { diff --git a/examples/vifExample.cpp b/examples/vifExample.cpp index 798169e..e4c5526 100644 --- a/examples/vifExample.cpp +++ b/examples/vifExample.cpp @@ -11,8 +11,10 @@ #include #include #include -#include // for "isspace()" - +#include //for "isspace()" +#ifdef WIN32 +#include // For MAX_ADAPTER_NAME_LENGTH +#endif //WIN32 // rxlimit smoothing factors -- weights for new data #define RXLIMIT_SIZE_ALPHA 0.1 // for average packet length #define RXLIMIT_RATE_ALPHA 0.1 // for average rate measurement @@ -84,7 +86,11 @@ class VifExampleApp : public ProtoApp ProtoAddress vif_addr; ProtoAddress vif_hwaddr; unsigned int vif_mask_len; - char if_name[ProtoVif::VIF_NAME_MAX]; + char if_name[ProtoVif::VIF_NAME_MAX]; +#ifdef WIN32 + char if_friendly_name[MAX_ADAPTER_NAME_LENGTH]; + bool dhcp_enabled; +#endif //WIN32 Mode vif_mode; double txrate_limit; // in kbps double rxrate_limit; // in kbps @@ -150,7 +156,11 @@ const char* const VifExampleApp::CMD_LIST[] = PROTO_INSTANTIATE_APP(VifExampleApp) VifExampleApp::VifExampleApp() - : vif(NULL), cap(NULL), vif_mask_len(0), vif_mode(BRIDGE), txrate_limit(-1.0), rxrate_limit(-1.0), +: vif(NULL), cap(NULL), vif_mask_len(0), +#ifdef WIN32 + dhcp_enabled(false), +#endif //WIN32 + vif_mode(BRIDGE), txrate_limit(-1.0), rxrate_limit(-1.0), rxrate_limit_uppernominal(-1.0), rxrate_limit_lowernominal(-1.0), rxlimit_prev_pkt_time_initialized(false), rxlimit_avg_rate(0.0), rxlimit_drop_prob(0.0), rxlimit_avg_size(0.0), rxlimit_rxbytes(0), @@ -159,6 +169,9 @@ VifExampleApp::VifExampleApp() { vif_name[0] = '\0'; if_name[0] = '\0'; +#ifdef WIN32 + if_friendly_name[0] = '\0'; +#endif //WIN32 txrate_timer.SetListener(this, &VifExampleApp::OnTxRateTimeout); control_pipe.SetNotifier(&GetSocketNotifier()); @@ -218,13 +231,13 @@ bool VifExampleApp::OnStartup(int argc, const char*const* argv) // Check for valid parameters. if (0 == strlen(vif_name)) { - PLOG(PL_ERROR, "VifExampleApp::OnStartup() error: missng required 'vif' command!\n"); + PLOG(PL_ERROR, "VifExampleApp::OnStartup() error: missing required 'vif' command!\n"); Usage(); return false; } if (0 == strlen(if_name)) { - PLOG(PL_ERROR, "VifExampleApp::OnStartup() error: missng required 'interface' command!\n"); + PLOG(PL_ERROR, "VifExampleApp::OnStartup() error: missing required 'interface' command!\n"); Usage(); return false; } @@ -248,13 +261,14 @@ bool VifExampleApp::OnStartup(int argc, const char*const* argv) cap->SetNotifier(static_cast(&dispatcher)); cap->SetListener(this,&VifExampleApp::OnInboundPkt); - + if (!vif->Open(vif_name, vif_addr, vif_mask_len)) { PLOG(PL_ERROR,"VifExampleApp::OnStartup() ProtoVif::Open(\"%s\") error\n", vif_name); Usage(); return false; } + if (!cap->Open(if_name)) { PLOG(PL_ERROR,"VifExampleApp::OnStartup() ProtoCap::Open(\"%s\") error\n", if_name); @@ -274,6 +288,27 @@ bool VifExampleApp::OnStartup(int argc, const char*const* argv) Usage(); return false; } + // Seems we need to reopen vif after setting hardware address? + // Note that SetHardwareAddress is not currently working on windows 7 + // so this open is redundant on windows 7. TBD: Remove? +#ifdef WIN32 + /* + if (!vif->Open(vif_name, vif_addr, vif_mask_len)) + { + PLOG(PL_ERROR, "VifExampleApp::OnStartup() ProtoVif::Open(\"%s\") error\n", vif_name); + Usage(); + return false; + } + */ +#endif + // Snag the virtual interface hardware address + ProtoAddress tmp_hw_addr; + if (!ProtoNet::GetInterfaceAddress(vif_name, ProtoAddress::ETH, tmp_hw_addr)) + PLOG(PL_ERROR, "Win32Vif::Open(%s) error: unable to get ETH address!\n", vif_name); + + PLOG(PL_INFO,"Snagged>%s from vif>%s\n", tmp_hw_addr.GetHostString(), vif_name); + + } break; case CLONE: @@ -294,25 +329,64 @@ bool VifExampleApp::OnStartup(int argc, const char*const* argv) Usage(); return false; } + // TBD: Seems like we need to reopen after changing hardware address? + // Note that SetHardwareAddress is not currently working on windows 7 + // so this open is redundant on windows 7. +#ifdef WIN32 + /* + if (!vif->Open(vif_name, vif_addr, vif_mask_len)) + { + PLOG(PL_ERROR, "VifExampleApp::OnStartup() ProtoVif::Open(\"%s\") error\n", vif_name); + Usage(); + return false; + } + */ +#endif ProtoAddressList addrList; if (!ProtoNet::GetInterfaceAddressList(if_name, ProtoAddress::IPv4, addrList)) - { - // TBD - make this a warning? PLOG(PL_WARN, "VifExampleApp::OnStartup() warning: no interface IPv4 addresses?\n"); - - } if (!ProtoNet::GetInterfaceAddressList(if_name, ProtoAddress::IPv6, addrList)) - { - // TBD - make this a warning? PLOG(PL_WARN, "VifExampleApp::OnStartup() warning: no interface IPv6 addresses?\n"); - } +#ifdef WIN32 + // On WIN32 after we move the interface ip address to the vif + // interface subsequent calls using the original ifAddr ip address + // will get the vif adapter - use the friendly name to prevent this. + ProtoAddress ifAddr; + if (ifAddr.ConvertFromString(if_name)) + { + if (!ProtoNet::GetInterfaceFriendlyName(ifAddr, if_friendly_name, MAX_ADAPTER_NAME_LENGTH)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList(%s) error: no matching interface name found\n", if_name); + return false; + } + PLOG(PL_INFO, "ProtoNet::GetInterfaceAddressList() friendly if_name>%s\n", if_friendly_name); + } + + // Also squirrel away the dhcp enabled boolean so we can restore correctly + dhcp_enabled = ProtoNet::GetInterfaceAddressDhcp(if_name,ifAddr); +#endif //WIN32 + + // TBD - check for empty addrList? ProtoAddress addr; ProtoAddressList::Iterator iterator(addrList); +#ifndef WIN32 while (iterator.GetNextAddress(addr)) { + if (addr.IsLinkLocal()) continue; unsigned int maskLen = ProtoNet::GetInterfaceAddressMask(if_name, addr); + // TODO: continue? + if (maskLen == 0) + continue; + // Remove address from "interface" - if (!ProtoNet::RemoveInterfaceAddress(if_name, addr, maskLen)) +#ifdef WIN32 + // On WIN32 after we move the interface ip address to the vif + // address subsequent calls using the ifAddr will get the vif adapter - + // use the friendly name to prevent this. + if (!ProtoNet::RemoveInterfaceAddress(if_friendly_name, addr, maskLen)) +#else + if (!ProtoNet::RemoveInterfaceAddress(if_name,addr,maskLen)) +#endif //if/else WIN32 { PLOG(PL_ERROR, "VifExampleApp::OnStartup() error removing address %s from interface %s\n", addr.GetHostString(), if_name); Usage(); @@ -325,7 +399,12 @@ bool VifExampleApp::OnStartup(int argc, const char*const* argv) Usage(); return false; } +#ifdef WIN32 + // Set vif_addr to the new addr + vif_addr = addr; +#endif //WIN32 } +#endif // !WIN32 break; } } @@ -347,18 +426,37 @@ void VifExampleApp::OnShutdown() PLOG(PL_ERROR, "VifExampleApp::OnShutdown() error getting vif IPv4 addresses\n"); if (!ProtoNet::GetInterfaceAddressList(vif_name, ProtoAddress::IPv6, addrList)) PLOG(PL_ERROR, "VifExampleApp::OnShutdown() error getting vif IPv6 addresses\n"); + ProtoAddress addr; ProtoAddressList::Iterator iterator(addrList); +#ifndef WIN32 while (iterator.GetNextAddress(addr)) { - unsigned int maskLen = ProtoNet::GetInterfaceAddressMask(vif_name, addr); + if (addr.IsLinkLocal()) continue; + unsigned int maskLen = 0; +#ifdef WIN32 + // TODO: add option to GetInterfaceAddressMask to accept vif_name for lookup + maskLen = ProtoNet::GetInterfaceAddressMask(vif_addr.GetHostString(), addr); +#else + maskLen = ProtoNet::GetInterfaceAddressMask(vif_name, addr); +#endif //WIN32 + // TBD: continue? + if (maskLen == 0) + continue; // Remove address from vif if (!ProtoNet::RemoveInterfaceAddress(vif_name, addr, maskLen)) PLOG(PL_ERROR, "VifExampleApp::OnShutdown() error removing address %s from vif %s\n", addr.GetHostString(), vif_name); // Assign address to "interface" - if (!ProtoNet::AddInterfaceAddress(if_name, addr, maskLen)) - PLOG(PL_ERROR, "VifExampleApp::OnShutdown() error adding address %s to interface %s\n", addr.GetHostString(), if_name); +#ifdef WIN32 + // TODO: Make common function prototypes + if (!ProtoNet::AddInterfaceAddress(if_friendly_name, addr, maskLen, dhcp_enabled)) + PLOG(PL_ERROR, "VifExampleApp::OnShutdown() error adding address %s to interface %s\n", addr.GetHostString(), if_friendly_name); +#else + if (!ProtoNet::AddInterfaceAddress(if_name, addr, maskLen)) + PLOG(PL_ERROR, "VifExampleApp::OnShutdown() error adding address %s to interface %s\n", addr.GetHostString(), if_name); +#endif //WIN32 } +#endif // !WIN32 } vif->Close(); delete vif; @@ -751,6 +849,8 @@ void VifExampleApp::OnOutboundPkt(ProtoChannel& theChannel, { if (numBytes == 0) break; // no more packets to receive + TRACE("read %u bytes from vif ...\n", numBytes); + ProtoPktETH ethPkt((UINT32*)ethBuffer, BUFFER_MAX - 2); if (!ethPkt.InitFromBuffer(numBytes)) { @@ -832,7 +932,7 @@ void VifExampleApp::OnInboundPkt(ProtoChannel& theChannel, } if (numBytes == 0) break; // no more packets to receive - + if ((ProtoCap::OUTBOUND != direction)) { // Map ProtoPktETH instance into buffer and init for processing diff --git a/examples/vifLan.cpp b/examples/vifLan.cpp index 00a3a8a..66f7c32 100644 --- a/examples/vifLan.cpp +++ b/examples/vifLan.cpp @@ -137,7 +137,7 @@ bool VifLanApp::OnStartup(int argc, const char*const* argv) ProtoAddress invalidAddr; for (int i = 1; i < argc; i++) { - // Create virutal interface with given name + // Create virtual interface with given name ProtoVif* vif = ProtoVif::Create(); if (NULL == vif) { diff --git a/examples/wxProtoExample.cpp b/examples/wxProtoExample.cpp index 0dbdcff..6b6107e 100644 --- a/examples/wxProtoExample.cpp +++ b/examples/wxProtoExample.cpp @@ -70,7 +70,7 @@ END_EVENT_TABLE() wxProtoExampleApp::wxProtoExampleApp() - : counter(0), frame(NULL) + : wxProtoApp(false), counter(0), frame(NULL) { tx_timer.SetListener(this, &wxProtoExampleApp::OnTxTimeout); } diff --git a/include/manetGraph.h b/include/manetGraph.h index 20d153e..e91b59d 100644 --- a/include/manetGraph.h +++ b/include/manetGraph.h @@ -324,6 +324,9 @@ class NetGraph : public ProtoGraph Node& GetNode() const {return *node;} + + const Node* GetNodePtr() const + {return node;} bool ChangeNode(Node& theNode); @@ -450,7 +453,8 @@ class NetGraph : public ProtoGraph } void Adjust(Interface& iface, const Cost& newCost); - bool AdjustDownward(Interface& iface, const Cost& newCost); + bool AdjustDownward(Interface& iface, const Cost& newCost,const Interface* newPrevHop = NULL); + bool AdjustUpward(Interface& iface, const Cost& newCost); // Note use of this method does not maintain priority queue sorting order! bool Append(Interface& iface); // used for Dijkstra "tree walking" only @@ -544,6 +548,9 @@ class NetGraph : public ProtoGraph // Member variables ProtoSortedTree iface_list; // sorted by "Cost" value ItemFactory& item_factory; + + private: + using Vertice::SortedList::Remove; // gets rid of hidden overloaded virtual function warning }; // end NetGraph::Interface::PriorityQueue @@ -607,7 +614,7 @@ class NetGraph : public ProtoGraph {return AddInterface(iface, makeDefault);} bool Contains(const Interface& iface) const - {return iface_list.Contains(iface);} + {return (iface.GetNodePtr() == this);} Interface* GetAnyInterface() const {return static_cast(iface_list.GetRoot());} @@ -750,8 +757,12 @@ class NetGraph : public ProtoGraph Interface* GetNextInterface(); + bool PrevHopIsValid(Interface& currentIface); + void Update(Interface& startIface); + void Update(Interface& ifaceA, Interface& ifaceB); + // Override this method to filter which edges are included in traversal // (return "false" to disallow specific links) virtual bool AllowLink(const Interface& srcIface, const Link& link) @@ -793,6 +804,7 @@ class NetGraph : public ProtoGraph bool dijkstra_completed; bool in_update; bool traverse_nodes; + bool reset_required; }; // end class NetGraph::DijkstraTraversal @@ -839,6 +851,9 @@ class NetGraph : public ProtoGraph NODE_TYPE& GetNode() const {return static_cast(Interface::GetNode());} + + const NODE_TYPE* GetNodePtr() const + {return static_cast(Interface::GetNodePtr());} const COST_TYPE* GetCostTo(const InterfaceTemplate& dst) const { diff --git a/include/manetGraphML.h b/include/manetGraphML.h index 5287d16..9f39b0f 100644 --- a/include/manetGraphML.h +++ b/include/manetGraphML.h @@ -46,8 +46,8 @@ class ManetGraphMLParser }; bool Read(const char* path, NetGraph& graph); // load graph from GraphML file - bool Write(NetGraph& graph, const char* path); // make GraphML file from graph - + bool Write(NetGraph& graph, const char* path, char* buffer=NULL, unsigned int* len_ptr = NULL); // make GraphML file from graph + bool SetXMLName(const char* theName); bool SetAttributeKey(const char* theName,const char* theType, const char* theDomain = NULL, const char* oldIndex = NULL,const char* theDefault = NULL); @@ -168,8 +168,8 @@ class ManetGraphMLTemplate : public ManetGraphMLParser, public NetGraphTemplate< bool Read(const char* path) // load graph from GraphML file {return ManetGraphMLParser::Read(path, *this);} - bool Write(const char* path) // make GraphML file from graph - {return ManetGraphMLParser::Write(*this, path);} + bool Write(const char* path, char* buffer = NULL, unsigned int* len=NULL) // make GraphML file from graph + {return ManetGraphMLParser::Write(*this, path,buffer,len);} bool Connect(NetGraph::Interface& iface1,NetGraph::Interface& iface2,NetGraph::Cost& theCost,bool isDuplex) {return NetGraph::Connect(iface1,iface2,theCost,isDuplex);} virtual bool InsertInterface(NetGraph::Interface& theIface) diff --git a/include/manetMsg.h b/include/manetMsg.h index 488da8f..206a0b7 100644 --- a/include/manetMsg.h +++ b/include/manetMsg.h @@ -163,7 +163,7 @@ class ManetTlv : public ProtoPkt bool IsMultiValue() const { bool result = SemanticIsSet(MULTIVALUE); - if (result) ASSERT(HasValue() && !HasSingleIndex()); + ASSERT(!result || (HasValue() && !HasSingleIndex())); return result; } diff --git a/include/protoAddress.h b/include/protoAddress.h index b6bebbc..b846636 100644 --- a/include/protoAddress.h +++ b/include/protoAddress.h @@ -6,6 +6,7 @@ #include "protoDefs.h" #include "protoTree.h" +#include "protoDebug.h" #ifdef UNIX #include @@ -90,6 +91,7 @@ class ProtoAddress // Construction/initialization ProtoAddress(); ProtoAddress(const ProtoAddress& theAddr); + ProtoAddress(const char* theAddr); // must be a numeric address, not a host name! ~ProtoAddress(); bool IsValid() const {return (INVALID != type);} void Invalidate() @@ -128,6 +130,9 @@ class ProtoAddress bool IsSiteLocal() const; bool IsLinkLocal() const; bool IsUnspecified() const; + bool IsUnicast() const + {return (!IsMulticast() && !IsBroadcast() && !IsUnspecified());} + const char* GetHostString(char* buffer = NULL, unsigned int buflen = 0) const; @@ -168,6 +173,7 @@ class ProtoAddress unsigned int SetCommonTail(const ProtoAddress &theAddr); UINT8 GetPrefixLength() const; + void GeneratePrefixMask(ProtoAddress::Type theType, UINT8 prefixLen); void ApplyPrefixMask(UINT8 prefixLen); void ApplySuffixMask(UINT8 suffixLen); void GetSubnetAddress(UINT8 prefixLen, @@ -179,17 +185,16 @@ class ProtoAddress void GetEthernetMulticastAddress(const ProtoAddress& ipMcastAddr); // Name/address resolution - bool ResolveFromString(const char* string); + bool ResolveFromString(const char* text); bool ResolveToName(char* nameBuffer, unsigned int buflen) const; bool ResolveLocalAddress(char* nameBuffer = NULL, unsigned int buflen = 0); + // Expects IPv4, IPv6, or ETH address in numeric notation form + bool ConvertFromString(const char* text); // This expects a an "Ethernet" MAC address string // in colon-delimited hexadecmial format bool ResolveEthFromString(const char* text); - // Expects IPv4, IPv6, or ETH address in numeric notation form - bool ConvertFromString(const char* text); - // Miscellaneous // This function returns a 32-bit number which might _sometimes_ // be useful as an address-based end system identifier. @@ -224,11 +229,11 @@ class ProtoAddress #ifdef WIN32 static bool Win32Startup() { - WSADATA wsaData; + WSADATA wsaData; WORD wVersionRequested = MAKEWORD(2, 2); return (0 == WSAStartup(wVersionRequested, &wsaData)); } - static void Win32Cleanup() {WSACleanup();} + static void Win32Cleanup() {WSACleanup(); } #endif // WIN32 private: @@ -275,6 +280,11 @@ class ProtoAddressList } void Remove(const ProtoAddress& addr); + // Add (non-duplicative) / Remove addresses from input list + // (input list is not modified) + bool AddList(ProtoAddressList& addrList); + void RemoveList(ProtoAddressList& addrList); + bool IsEmpty() const {return (addr_tree.IsEmpty());} @@ -334,7 +344,7 @@ class ProtoAddressList ProtoTree addr_tree; }; // end class ProtoAddressList -extern const ProtoAddress PROTO_ADDR_NONE; +extern const ProtoAddress PROTO_ADDR_NONE; // invalid ProtoAddress (useful as a default) #endif // _PROTO_ADDRESS diff --git a/include/protoBitmask.h b/include/protoBitmask.h index c208243..ca04686 100644 --- a/include/protoBitmask.h +++ b/include/protoBitmask.h @@ -1,7 +1,8 @@ #ifndef _PROTO_BITMASK_ #define _PROTO_BITMASK_ -#include "protokit.h" // for PROTO_DEBUG stuff +#include "protoDefs.h" +#include "protoDebug.h" #include // for memset() #include // for fprintf() @@ -109,7 +110,7 @@ class ProtoBitmask void Display(FILE* stream); // Members - private: + //private: unsigned char* mask; UINT32 mask_len; UINT32 num_bits; @@ -139,10 +140,10 @@ class ProtoSlidingMask const char* GetMask() const {return (const char*)mask;} - bool Init(INT32 numBits, UINT32 rangeMask); - bool Resize(INT32 numBits); + bool Init(UINT32 numBits, UINT32 rangeMask); + bool Resize(UINT32 numBits); void Destroy(); - INT32 GetSize() const {return num_bits;} + UINT32 GetSize() const {return num_bits;} void Clear() { memset(mask, 0, mask_len); @@ -157,6 +158,7 @@ class ProtoSlidingMask start = 0; end = num_bits - 1; offset = index; + if (range_mask) offset &= range_mask; } bool IsSet() const {return (start < num_bits);} bool GetFirstSet(UINT32& index) const @@ -166,9 +168,11 @@ class ProtoSlidingMask } bool GetLastSet(UINT32& index) const { - INT32 n = end - start; - n = (n < 0) ? (n + num_bits) : n; - index = offset + n; + UINT32 n = (end < start) ? + (num_bits - (start - end)) : + (end - start); + index = offset + n; + if (range_mask) index &= range_mask; return IsSet(); } bool Test(UINT32 index) const; @@ -179,8 +183,8 @@ class ProtoSlidingMask bool Invert(UINT32 index) {return (Test(index) ? Unset(index): Set(index));} - bool SetBits(UINT32 index, INT32 count); - bool UnsetBits(UINT32 index, INT32 count); + bool SetBits(UINT32 index, UINT32 count); + bool UnsetBits(UINT32 index, UINT32 count); UINT32 GetRangeMask() const {return range_mask;} UINT32 GetRangeSign() const {return range_sign;} @@ -197,27 +201,66 @@ class ProtoSlidingMask bool Xor(const ProtoSlidingMask & b); // this = this ^ b void Display(FILE* stream); - void Debug(INT32 theCount); - - //private: + void Debug(UINT32 theCount); + // Calculate "circular" delta between two indices - INT32 Delta(UINT32 a, UINT32 b) const + // (If (0 == range_mask) it is an absolute 32-bit delta + INT32 Difference(UINT32 a, UINT32 b) const { - INT32 result = a - b; - return ((0 == (result & range_sign)) ? - (result & range_mask) : - (((result != range_sign) || (a < b)) ? - (result | ~range_mask) : result)); - } - - unsigned char* mask; - INT32 mask_len; - INT32 range_mask; - INT32 range_sign; - INT32 num_bits; - INT32 start; - INT32 end; - UINT32 offset; + UINT32 result = a - b; + if (0 != range_mask) + { + return ((0 == (result & range_sign)) ? + (result & range_mask) : + (((result != range_sign) || (a < b)) ? + (INT32)(result | ~range_mask) : (INT32)result)); + } + else + { + return (INT32)result; + } + } + + // Compare values. If a non-zero bit "mask" is given, the comparison + // is a "sliding window" (signed) over the bit space. Otherwise, it is + // simply and unsigned value comparison. + // Returns -1, 0, +1 for (a < b), (a == b), and (a > b), respectively + int Compare(UINT32 a, UINT32 b) const + { + if (0 != range_mask) + { + // "Sliding window" comparison + INT32 delta = Difference(a, b); + if (delta < 0) + return -1; + else if (0 == delta) + return 0; + else // if delta > 0 + return 1; + } + else if (a < b) + { + return -1; + } + else if (a == b) + { + return 0; + } + else //if (a > b) + { + return 1; + } + } + + private: + unsigned char* mask; + UINT32 mask_len; + UINT32 range_mask; + UINT32 range_sign; + UINT32 num_bits; + UINT32 start; + UINT32 end; + UINT32 offset; }; // end class ProtoSlidingMask #endif // _PROTO_BITMASK_ diff --git a/include/protoCap.h b/include/protoCap.h index d20c033..d7c6459 100644 --- a/include/protoCap.h +++ b/include/protoCap.h @@ -9,6 +9,7 @@ */ #include "protoChannel.h" +#include "protoAddress.h" class ProtoCap : public ProtoChannel { @@ -25,7 +26,7 @@ class ProtoCap : public ProtoChannel }; // These must be overridden for different implementations - // ProtoCap::Open() should also be called at the _end_ of derived + // ProtoCap::Open() MUST also be called at the _end_ of derived // implementations' Open() method virtual bool Open(const char* interfaceName = NULL) {return ProtoChannel::Open();} @@ -39,12 +40,18 @@ class ProtoCap : public ProtoChannel if_index = -1; } - int GetInterfaceIndex() const + unsigned int GetInterfaceIndex() const {return if_index;} - virtual bool Send(const char* buffer, unsigned int buflen) = 0; - virtual bool Forward(char* buffer, unsigned int buflen) = 0; + const ProtoAddress& GetInterfaceAddr() const + {return if_addr;} + virtual bool Recv(char* buffer, unsigned int& numBytes, Direction* direction = NULL) = 0; + virtual bool Send(const char* buffer, unsigned int& numBytes) = 0; + + bool Forward(char* buffer, unsigned int& numBytes); + + bool ForwardFrom(char* buffer, unsigned int& numBytes, const ProtoAddress& srcMacAddr); void SetUserData(const void* userData) {user_data = userData;} @@ -53,10 +60,11 @@ class ProtoCap : public ProtoChannel protected: ProtoCap(); - int if_index; + unsigned int if_index; // interface index (if applicable) + ProtoAddress if_addr; // interface MAC addr (if applicable) private: - const void* user_data; + const void* user_data; }; // end class ProtoCap diff --git a/include/protoChannel.h b/include/protoChannel.h index f46fa2f..a383499 100644 --- a/include/protoChannel.h +++ b/include/protoChannel.h @@ -34,23 +34,17 @@ class ProtoChannel #endif // if/else WIN32 static const Handle INVALID_HANDLE; - // Derived classes should _end_ their own "Open()" method + // Derived classes MUST _end_ their own "Open()" method // with a call to this bool Open() { StartInputNotification(); // enable input notifications by default return UpdateNotification(); } - // Derived classes should _begin_ their own "Close()" method + // Derived classes MUST _begin_ their own "Close()" method // with a call to this - void Close() - { - if (IsOpen()) - { - StopInputNotification(); - StopOutputNotification(); - } - } + void Close() ; + // (TBD) Should this be made virtual??? bool IsOpen() const { @@ -59,8 +53,7 @@ class ProtoChannel (INVALID_HANDLE != output_handle)); #else return (INVALID_HANDLE != descriptor); -#endif // if/else WIN32/UNIX - +#endif // if/else WIN32/UNIX } // Asynchronous I/O notification stuff @@ -89,35 +82,16 @@ class ProtoChannel bool SetNotifier(ProtoChannel::Notifier* theNotifier); bool SetBlocking(bool status); - bool StartOutputNotification() - { - notify_flags |= (int)NOTIFY_OUTPUT; - bool result = UpdateNotification(); -#ifdef WIN32 - output_ready = result; -#endif // WIN32 - return result; - } - void StopOutputNotification() - { - notify_flags &= ~((int)NOTIFY_OUTPUT); - if (notifier) notifier->UpdateChannelNotification(*this, notify_flags); - } - bool OutputNotification() {return (0 != (notify_flags & ((int)NOTIFY_OUTPUT)));} - - bool StartInputNotification() - { - notify_flags |= (int)NOTIFY_INPUT; - bool result = UpdateNotification(); - return result; - } - void StopInputNotification() - { - notify_flags &= ~((int)NOTIFY_INPUT); - if (notifier) notifier->UpdateChannelNotification(*this, notify_flags); - } - bool InputNotification() {return (0 != (notify_flags & ((int)NOTIFY_INPUT)));} - + bool StartInputNotification(); + void StopInputNotification(); + bool InputNotification() + {return (0 != (notify_flags & ((int)NOTIFY_INPUT)));} + + bool StartOutputNotification(); + void StopOutputNotification(); + bool OutputNotification() + {return (0 != (notify_flags & ((int)NOTIFY_OUTPUT)));} + bool UpdateNotification(); void OnNotify(ProtoChannel::Notification theNotification) @@ -127,15 +101,14 @@ class ProtoChannel #ifdef WIN32 - Handle GetInputHandle() {return input_handle;} - Handle GetOutputHandle() {return output_handle;} + Handle GetInputEventHandle() {return input_event_handle;} + Handle GetOutputEventHandle() {return output_event_handle;} bool IsOutputReady() {return output_ready;} bool IsInputReady() {return input_ready;} bool IsReady() {return (input_ready || output_ready);} #else - Handle GetInputHandle() {return descriptor;} - Handle GetOutputHandle() {return descriptor;} - Handle GetHandle() {return descriptor;} + Handle GetInputEventHandle() const {return descriptor;} + Handle GetOutputEventHandle() const {return descriptor;} #endif // if/else WIN32/UNIX // NOTE: For VC++ 6.x Debug builds "/ZI" or "/Z7" compile options must NOT be specified @@ -183,25 +156,44 @@ class ProtoChannel listenerType* listener; void(listenerType::*event_handler)(ProtoChannel&, Notification); }; - - Listener* listener; - Notifier* notifier; - int notify_flags; - - bool blocking_status; - + protected: #ifdef WIN32 - // ljt rework this!! - void SetInputHandle(Handle theInputHandle) - {input_handle = theInputHandle;} - HANDLE input_handle; - bool input_ready; + // On WIN32, the input/output handles may be different than the + // event handles (but they can be set the same by child classes + // if applicable. (ProtoDispatcher uses the event handles while + // read/write calls use the regular handles + HANDLE input_handle; // handle used for ReadFile + HANDLE input_event_handle; // event handle for notification HANDLE output_handle; - bool output_ready; + HANDLE output_event_handle; + bool input_ready; // input_ready helps us get level-triggered nofify behaviors + bool output_ready; // output_ready helps us get level-triggered nofify behaviors + // The followng stuff facilitates using Win32 overlapped I/O in ProtoChannel subclasses + bool InitOverlappedIO(); // must be called before overlapped i/o routines can be used + bool StartOverlappedRead(); // use to "kickstart" overlapped I/O aync notifications + bool OverlappedRead(char* buffer, unsigned int& numBytes); + bool OverlappedWrite(const char* buffer, unsigned int& numBytes); + enum {OVERLAPPED_BUFFER_SIZE = 8192}; + OVERLAPPED overlapped_read; + char* overlapped_read_buffer; + unsigned int overlapped_read_count; + unsigned int overlapped_read_index; + OVERLAPPED overlapped_write; + char* overlapped_write_buffer; + unsigned int overlapped_write_count; + unsigned int overlapped_write_index; #else int descriptor; -#endif // WIN32 +#endif // WIN32 + + private: +#ifdef UNIX + bool blocking_status; +#endif // UNIX + Listener* listener; + Notifier* notifier; + int notify_flags; }; // end class ProtoChannel diff --git a/include/protoCheck.h b/include/protoCheck.h new file mode 100644 index 0000000..fc7b12d --- /dev/null +++ b/include/protoCheck.h @@ -0,0 +1,49 @@ + +#ifndef _PROTO_CHECK +#define _PROTO_CHECK + +// This module provides compile-time optional "new" and "delete" operators +// for Protolib (or any code that #include "protoDefs.h" or this). These +// operator keep track of current memory allocations (of the new operator) +// and which source code file and line number from which the allocation was +// made. This can be useful for debugging purposes and should only be +// enabled for such purposes as there is a performance penalty when this +// is enabled. + +// At compile time, use -DUSE_PROTO_CHECK (or uncomment the line below) +// that defines that macro. + +// The current memory allocations can be "logged" via a call to +// ProtoCheckLogAllocations(). In the future some additional memory +// usage statistics (e.g. total size of allocations, peak, etc) +// might be added. This is _not_ a replacement for other tools such +// as "valgrind", etc but useful for Protolib-based code. + +#include // for FILE* + +// UNCOMMENT this next line to enable "ProtoCheck" (or use -DUSE_PROTO_CHECK for your compiler) +//#define USE_PROTO_CHECK + +#ifdef USE_PROTO_CHECK + +#include + +#ifndef _PROTO_CHECK_IMPL + +void* operator new(size_t size, const char* file, int line); + +void operator delete(void * p) throw(); + +void* operator new[](size_t size, const char* file, int line); + +void operator delete[](void * p) throw(); + +#define new new(__FILE__, __LINE__) + +#endif // !_PROTO_CHECK_IMPL + +#endif // USE_PROTO_CHECK + +void ProtoCheckLogAllocations(FILE* filePtr); + +#endif // _PROTO_CHECK diff --git a/include/protoDebug.h b/include/protoDebug.h index fc0ff46..dd5ea77 100644 --- a/include/protoDebug.h +++ b/include/protoDebug.h @@ -60,7 +60,6 @@ inline void CloseDebugWindow() {} #endif // if/else PROTO_DEBUG || PROTO_MSG - #if PROTO_DEBUG || PROTO_MSG // The following prototype and "SetAssertFunction()" allows the behavior of the PROTO_ASSERT macro @@ -77,12 +76,18 @@ void ProtoAssertHandler(bool condition, const char* conditionText, const char* f void PROTO_ABORT(const char *format, ...); #ifdef HAVE_ASSERT +#ifdef __ANDROID__ +#include +#define PROTO_ASSERT(X) {if (HasAssertFunction()) ProtoAssertHandler(X, #X, __FILE__, __LINE__); \ +else if (!((bool)(X))) __android_log_assert(#X, "protolib", "%s line:%d", __FILE__, __LINE__);} +#else #include #define PROTO_ASSERT(X) {if (HasAssertFunction()) ProtoAssertHandler(X, #X, __FILE__, __LINE__); else assert(X);} +#endif // if/else __ANDROID__ #else #define PROTO_ASSERT(X) \ {if (HasAssertFunction()) ProtoAssertHandler(X, #X, __FILE__, __LINE__); \ - else PROTO_ABORT("ASSERT(%s) failed at line %d in source file \"%s\"\n", #X, __LINE__, __FILE__);} + else if (!((bool)(X))) PROTO_ABORT("ASSERT(%s) failed at line %d in source file \"%s\"\n", #X, __LINE__, __FILE__);} #endif // if/else HAVE_ASSERT @@ -91,7 +96,8 @@ void PROTO_ABORT(const char *format, ...); #endif // TRACE void TRACE(const char *format, ...); -#else +#else // !PROTO_DEBUG + #define PROTO_ASSERT(X) #ifndef ABORT #define ABORT(X) @@ -125,4 +131,28 @@ inline const char* GetErrorString() #endif // if/else WIN32/UNIX } + +#ifdef WIN32 +typedef DWORD ProtoErrorCode; +#else +typedef int ProtoErrorCode; +#endif // if/else WIN32/UNIX + +inline const char* GetErrorString(ProtoErrorCode errorCode) +{ +#ifdef WIN32 + static char errorString[256]; + errorString[255] = '\0'; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) errorString, 255, NULL); + return errorString; +#else + return strerror(errorCode); +#endif // if/else WIN32/UNIX +} + #endif // _PROTO_DEBUG diff --git a/include/protoDefs.h b/include/protoDefs.h index 5b74d5e..9126b56 100644 --- a/include/protoDefs.h +++ b/include/protoDefs.h @@ -71,7 +71,7 @@ inline void ProtoSystemTime(struct timeval& theTime) // because of various issues with the performance counter stuff, // but on certain systems may be useful to achieve high clock // granularity. Uncomment the following line if desired: -//#define USE_PERFORMANCE_COUNTER 1 +#define USE_PERFORMANCE_COUNTER 1 #ifdef _WIN32_WCE #define USE_PERFORMANCE_COUNTER 1 @@ -93,12 +93,17 @@ inline LARGE_INTEGER ProtoGetPerformanceCounterFrequency() extern long proto_performance_counter_offset; if (0 != proto_performance_counter_frequency.QuadPart) { - SYSTEMTIME st; - GetSystemTime(&st); LARGE_INTEGER count; - QueryPerformanceCounter(&count); FILETIME ft; - SystemTimeToFileTime(&st, &ft); +#ifdef _WIN32_WCE + SYSTEMTIME st; + QueryPerformanceCounter(&count); + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ft); +#else + QueryPerformanceCounter(&count); + GetSystemTimeAsFileTime(&ft); +#endif // if/else _WIN32_WCE ULARGE_INTEGER systemTime = {ft.dwLowDateTime, ft.dwHighDateTime}; // Convert system time to Jan 1, 1970 epoch const ULARGE_INTEGER epochTime = {0xD53E8000, 0x019DB1DE}; @@ -138,12 +143,17 @@ inline void ProtoSystemTime(struct timeval& theTime) extern unsigned long proto_system_count_roll_sec; extern LARGE_INTEGER proto_system_count_last; // 1) Get current system time and performance counter count + LARGE_INTEGER count; + FILETIME ft; +#ifdef _WIN32_WCE SYSTEMTIME st; + QueryPerformanceCounter(&count); GetSystemTime(&st); - LARGE_INTEGER count; - QueryPerformanceCounter(&count); - FILETIME ft; SystemTimeToFileTime(&st, &ft); +#else + QueryPerformanceCounter(&count); + GetSystemTimeAsFileTime(&ft); +#endif // if/else _WIN32_WCE ULARGE_INTEGER usec = {ft.dwLowDateTime, ft.dwHighDateTime}; const ULARGE_INTEGER epochTime = {0xD53E8000, 0x019DB1DE}; usec.QuadPart -= epochTime.QuadPart; @@ -273,4 +283,6 @@ typedef uint32_t UINT32; #define MIN(X,Y) ((X // for _beginthreadex/_endthreadex #endif // !_WIN32_WCE -#else + +#define USE_WAITABLE_TIMER 1 + +#else // UNIX #include -#include // for read()pwd -#endif // if/else WIN32/UNIX +#include // for read(), etc + +// There are some compile-time options here for +// the async i/o mechanism ProtoDispatcher uses +// (e.g., "select()", "kqueue()", "epoll", etc) +#if defined(MACOSX) +// Makefile must pick either USE_KQUEUE or USE_SELECT +// or we default to USE_SELECT (TBD) +#if (!defined(USE_KQUEUE) && !defined(USE_SELECT)) +#warning "Neither USE_SELECT or USE_KQUEUE defined, setting USE_SELECT" +#define USE_SELECT 1 +#endif -#ifdef LINUX +#include +#include +#include + +#elif defined(LINUX) +// Makefile must pick USE_SELECT or USE_EPOLL or we +// default to USE_SELECT (TBD) +#if (!defined(USE_SELECT) && !defined(USE_EPOLL)) +#warning "Neither USE_SELECT or USE_EPOLL defined, setting USE_SELECT" +#define USE_SELECT 1 +#endif // !defined(USE_SELECT) & !defined(USE_EPOLL) +// Makefile must pick either USE_EPOLL or USE_SELECT +#ifdef USE_EPOLL +#include +#ifdef USE_SELECT +#error "Must define either USE_EPOLL or USE_SELECT, not both!" +#endif // USE_SELECT +#endif // USE_EPOLL #ifdef USE_TIMERFD #include -#endif // USE_TIMERFD -#endif // LINUX +#endif // USE_TIMERFD +#ifdef USE_EVENTFD +#include +#endif // USE_EVENTFD + +#else // "other" UNIX +#define USE_SELECT 1 // default Unix async i/o and timing mechanism + +#endif // if/else MACOSX / LINUX / OTHER + +#endif // if/else WIN32/UNIX + /** * @class ProtoDispatcher @@ -66,8 +108,6 @@ * in a thread, dispatching events to a parent thread). */ - - /*********************** ProtoDispatcher Update Notes @@ -151,10 +191,8 @@ ************************/ - - -/// Asynchronous event dispatcher class +/// Asynchronous event dispatcher and timer scheduler class class ProtoDispatcher : public ProtoTimerMgr, public ProtoSocket::Notifier, public ProtoChannel::Notifier @@ -166,6 +204,9 @@ class ProtoDispatcher : public ProtoTimerMgr, void Destroy(); // ProtoTimerMgr overrides + // Note the underlying ProtoTimerMgr will call ProtoDispatcher::UpdateSystemTimer() + // as needed which will will "wake up" (via ProtoDispatcher::SignalThread()) the + // dispatcher instance as needed void ActivateTimer(ProtoTimer& theTimer) { // TBD - should we SignalThread() the thread to wake it up? @@ -181,11 +222,8 @@ class ProtoDispatcher : public ProtoTimerMgr, } // Methods to manage generic input/output streams (pipes, files, devices, etc) -#ifdef WIN32 - typedef HANDLE Descriptor; // WIN32 uses "HANDLE" type for descriptors -#else - typedef int Descriptor; // UNIX uses "int" type for descriptors -#endif // if/selse WIN32 + typedef ProtoChannel::Handle Descriptor; // , UNIX "int" descriptors, Win32 "HANDLE" type + static const Descriptor INVALID_DESCRIPTOR; enum Event {EVENT_INPUT, EVENT_OUTPUT}; typedef void (Callback)(ProtoDispatcher::Descriptor descriptor, @@ -193,52 +231,24 @@ class ProtoDispatcher : public ProtoTimerMgr, const void* userData); bool InstallGenericInput(ProtoDispatcher::Descriptor descriptor, ProtoDispatcher::Callback* callback, - const void* clientData) - { - SignalThread(); - bool result = InstallGenericStream(descriptor, callback, clientData, Stream::INPUT); - UnsignalThread(); - return result; - } - void RemoveGenericInput(Descriptor descriptor) - { - SignalThread(); - GenericStream* stream = FindGenericStream(descriptor); - if (stream) - { - stream->UnsetFlag(Stream::INPUT); - if (!stream->HasFlags()) RemoveGenericStream(stream); - } - UnsignalThread(); - } + const void* clientData); + void RemoveGenericInput(Descriptor descriptor); bool InstallGenericOutput(ProtoDispatcher::Descriptor descriptor, ProtoDispatcher::Callback* callback, - const void* clientData) - { - return InstallGenericStream(descriptor, callback, clientData, Stream::OUTPUT); - } - void RemoveGenericOutput(Descriptor descriptor) - { - GenericStream* stream = FindGenericStream(descriptor); - if (stream) - { - stream->UnsetFlag(Stream::OUTPUT); - if (!stream->HasFlags()) RemoveGenericStream(stream); - } - } + const void* clientData); + void RemoveGenericOutput(Descriptor descriptor); // Methods to set up and explicitly control ProtoDispatcher operation /// Are there any pending timeouts/descriptors? bool IsPending() { - return (IsThreaded() || - (NULL != generic_stream_list) || - (NULL != socket_stream_list) || - (NULL != channel_stream_list) || - (ProtoTimerMgr::GetTimeRemaining() >= 0.0)); + return (IsThreaded() || + ProtoTimerMgr::IsActive() || + !stream_table.IsEmpty()); } + /** - * This one can safely be called on a dispatcher _before_ + * This can safely be called on a dispatcher _before_ * the "StartThread()" or "Run()" method is called without * affecting the current process priority */ @@ -256,6 +266,7 @@ class ProtoDispatcher : public ProtoTimerMgr, * (Note this will block forever if !IsPending()) */ void Wait(); + /// Dispatch round of events void Dispatch(); @@ -265,19 +276,34 @@ class ProtoDispatcher : public ProtoTimerMgr, bool IsRunning() const // TBD - deprecate IsRunning() method? {return run;} + /** * Controls whether time of day is polled for ultra-precise timing * If "precise timing" is set to "true", the time of day is polled * to achieve precise timer timeouts. However, this can consume * considerable CPU resources. The default state is "false". */ - void SetPreciseTiming(bool state) {precise_timing = state;} + void SetPreciseTiming(bool state) + {precise_timing = state;} + // For debugging purposes + void SetUserData(const void* userData) + {user_data = userData;} + const void* GetUserData() + {return user_data;} #ifdef WIN32 typedef CRITICAL_SECTION Mutex; + static void Init(Mutex& m) {InitializeCriticalSection(&m);} + static void Destroy(Mutex& m) {DeleteCriticalSection(&m);} + static void Lock(Mutex& m) {EnterCriticalSection(&m);} + static void Unlock(Mutex& m) {LeaveCriticalSection(&m);} #else typedef pthread_mutex_t Mutex; + static void Init(Mutex& m) {pthread_mutex_init(&m, NULL);} + static void Destroy(Mutex& m) {pthread_mutex_destroy(&m);} + static void Lock(Mutex& m) {pthread_mutex_lock(&m);} + static void Unlock(Mutex& m) {pthread_mutex_unlock(&m);} #endif // if/else WIN32/UNIX /** * @class Controller @@ -291,15 +317,15 @@ class ProtoDispatcher : public ProtoTimerMgr, { public: virtual ~Controller(); - void OnDispatch(); - + protected: + void OnDispatch(); // should be called by Controller thread in response to SignalDispatchReady() cue Controller(ProtoDispatcher& theDispatcher); private: friend class ProtoDispatcher; bool DoDispatch(); /// only called by ProtoDispatcher - virtual bool SignalDispatchReady() = 0; + virtual bool SignalDispatchReady() = 0; // cue to controller to make "OnDispatch() call void OnThreadStop() { if (use_lock_a) @@ -354,26 +380,8 @@ class ProtoDispatcher : public ProtoTimerMgr, prompt_client_data = clientData; } - /// Call this to force call of set PromptCallback in thread's context - bool PromptThread() - { - if (SuspendThread()) - { - prompt_set = true; // indicate prompt_callback should be called - if (!SignalThread()) - { - ResumeThread(); - return false; // suspend/signal thread - } - UnsignalThread(); - ResumeThread(); - return true; - } - else - { - return false; - } - } + /// Call this to force call of PromptCallback in thread's context + bool PromptThread(); #ifdef WIN32 @@ -382,45 +390,17 @@ class ProtoDispatcher : public ProtoTimerMgr, void Win32Cleanup(); #endif // WIN32 - private: + public: bool SignalThread(); void UnsignalThread(); - bool WasSignaled() - { - // Check for and reset "break" signal - if (IsThreaded()) - { -#ifdef WIN32 - if ((WAIT_OBJECT_0 <= wait_status) && (wait_status < (WAIT_OBJECT_0 + stream_count))) - { - GenericStream* theStream = - static_cast(stream_ptrs_array[wait_status - WAIT_OBJECT_0]); - if (break_event_stream == theStream) - { - ResetEvent(break_event); - return true; - } - } -#else - if ((wait_status > 0) && (FD_ISSET(break_pipe_fd[0], &input_set))) - { - // Reset by emptying pipe - char byte[32]; - while (read(break_pipe_fd[0], byte, 32) > 0); - return true; - } -#endif // if/else WIN32/UNIX - } - return false; - } - - + private: + bool WasSignaled(); /// Associated ProtoTimerMgrs will use this as needed bool UpdateSystemTimer(ProtoTimer::Command /*command*/, double /* delay*/) { // ProtoDispatcher::Dispatch() queries ProtoTimerMgr::GetTimeRemaining() instead - // This wakes up the dispatcher thread as needed. + // The signal/unsignal here just wakes up the thread so this happens SignalThread(); UnsignalThread(); return true; @@ -432,7 +412,9 @@ class ProtoDispatcher : public ProtoTimerMgr, /// Associated ProtoChannels will use this as needed bool UpdateChannelNotification(ProtoChannel& theChannel, int notifyFlags); - // Thread/Mutex stuff + + + // Thread/Mutex stuff (TBD - make this its own class ???) #ifdef WIN32 typedef DWORD WaitStatus; typedef DWORD ThreadId; @@ -444,11 +426,7 @@ class ProtoDispatcher : public ProtoTimerMgr, typedef unsigned int ExitStatus; static void DoThreadExit(ExitStatus exitStatus) {_endthreadex(exitStatus);} #endif // if/else _WIN32_WCE - static void Init(Mutex& m) {InitializeCriticalSection(&m);} - static void Destroy(Mutex& m) {DeleteCriticalSection(&m);} - static void Lock(Mutex& m) {EnterCriticalSection(&m);} - static void Unlock(Mutex& m) {LeaveCriticalSection(&m);} - ExitStatus GetExitStatus() + ExitStatus GetExitStatus() {return exit_status;} #else // Unix typedef int WaitStatus; @@ -458,47 +436,42 @@ class ProtoDispatcher : public ProtoTimerMgr, ExitStatus GetExitStatus() // note pthread uses a _pointer_ to the status value location {return &exit_status;} static void DoThreadExit(ExitStatus exitStatus) {pthread_exit(exitStatus);} - static void Init(Mutex& m) {pthread_mutex_init(&m, NULL);} - static void Destroy(Mutex& m) {pthread_mutex_destroy(&m);} - static void Lock(Mutex& m) {pthread_mutex_lock(&m);} - static void Unlock(Mutex& m) {pthread_mutex_unlock(&m);} #endif // if/else WIN32/UNIX bool IsMyself() {return (GetCurrentThread() == thread_id);} void DestroyThread(); - //static void DoThreadBreak(ProtoDispatcher::Descriptor descriptor, - // ProtoDispatcher::Event theEvent, - // const void* userData); + bool InstallBreak(); + bool SetBreak(); void RemoveBreak(); - /** * @class Stream * * @brief This class helps manage notification for * protoSockets and generic I/O descriptors */ - class Stream + class Stream : public ProtoTree::Item, public ProtoQueue::Item { public: - enum Type {GENERIC, SOCKET, CHANNEL}; + enum Type {GENERIC, SOCKET, CHANNEL, TIMER, EVENT}; enum Flag {NONE = 0x00, INPUT = 0x01, OUTPUT = 0x02, EXCEPTION = 0x04}; Type GetType() const {return type;} + bool IsInput() const {return FlagIsSet(INPUT);} bool IsOutput() const {return FlagIsSet(OUTPUT);} bool FlagIsSet(Flag theFlag) const {return (0 != (flags & theFlag));} void SetFlag(Flag theFlag) {flags |= theFlag;} void UnsetFlag(Flag theFlag) {flags &= ~theFlag;} void SetFlags(int theFlags) {flags = theFlags;} - bool HasFlags() {return (0 != flags);} + bool HasFlags() const {return (0 != flags);} + int GetFlags() const {return flags;} void ClearFlags() {flags = 0;} - Stream* GetNext() const {return next;} - void SetNext(Stream* stream) {next = stream;} - Stream* GetPrev() const {return prev;} - void SetPrev(Stream* stream) {prev = stream;} + virtual Descriptor GetInputHandle() const = 0; + virtual Descriptor GetOutputHandle() const = 0; + #ifdef WIN32 int GetIndex() const {return index;} void SetIndex(int theIndex) {index = theIndex;} @@ -509,34 +482,73 @@ class ProtoDispatcher : public ProtoTimerMgr, Stream(Type theType); private: + // ProtoTree::Item required overrides + virtual const char* GetKey() const = 0; + virtual unsigned int GetKeysize() const = 0; + Type type; int flags; #ifdef WIN32 int index; int outdex; #endif // WIN32 - Stream* prev; - Stream* next; }; // end class Stream - + + + // Declare indexed table of streams + // (will be indexed by ProtoChannel, ProtoSocket, etc pointer) + class StreamTable : public ProtoTreeTemplate {}; + + // Simple linked list of streams (we use for "ready_stream_list" + class StreamList : public ProtoSimpleQueueTemplate + { + public: + StreamList() : ProtoSimpleQueueTemplate(true) {} // use container pooling + ~StreamList() {} + }; // end class ProtoDispatcher::StreamList + + // The "UpdateStreamNotification()" method is implemented with system-specific code + // (i.e. for the separate WIN32, USE_SELECT, USE_KQUEUE, and USE_EPOLL cases) + enum NotificationCommand {DISABLE_ALL, ENABLE_INPUT, DISABLE_INPUT, ENABLE_OUTPUT, DISABLE_OUTPUT}; + bool UpdateStreamNotification(Stream& stream, NotificationCommand cmd); + + /** * @class SocketStream * * @brief This class helps manage notification for - * protoSockets and generic I/O descriptors + * ProtoSockets and generic I/O descriptors */ - - class SocketStream : public Stream + class SocketStream : public Stream { public: SocketStream(ProtoSocket& theSocket); ProtoSocket& GetSocket() {return *socket;} void SetSocket(ProtoSocket& theSocket) {socket = &theSocket;} +#ifdef WIN32 + Descriptor GetInputHandle() const + {return socket->GetInputEventHandle();} + Descriptor GetOutputHandle() const + {return socket->GetOutputEventHandle();} +#else + Descriptor GetInputHandle() const + {return socket->GetHandle();} + Descriptor GetOutputHandle() const + {return socket->GetHandle();} +#endif // if/else WIN32/UNIX + private: + const char* GetKey() const + {return (const char*)(&socket);} + unsigned int GetKeysize() const + {return (sizeof(ProtoSocket*) << 3);} + ProtoSocket* socket; }; // end class SocketStream SocketStream* GetSocketStream(ProtoSocket& theSocket); - void ReleaseSocketStream(SocketStream* socketStream); + void ReleaseSocketStream(SocketStream& socketStream); + + class SocketStreamPool : public ProtoTreeTemplate::ItemPool {}; /** * @class ChannelStream @@ -544,18 +556,88 @@ class ProtoDispatcher : public ProtoTimerMgr, * @brief This class helps manage notification for * protoSockets and generic I/O descriptors */ - class ChannelStream : public Stream { public: ChannelStream(ProtoChannel& theChannel); ProtoChannel& GetChannel() {return *channel;} void SetChannel(ProtoChannel& theChannel) {channel = &theChannel;} + + Descriptor GetInputHandle() const + {return channel->GetInputEventHandle();} + Descriptor GetOutputHandle() const + {return channel->GetOutputEventHandle();} private: + const char* GetKey() const + {return (const char*)(&channel);} + unsigned int GetKeysize() const + {return (sizeof(ProtoChannel*) << 3);} + ProtoChannel* channel; }; // end class ChannelStream ChannelStream* GetChannelStream(ProtoChannel& theChannel); - void ReleaseChannelStream(ChannelStream* channelStream); + void ReleaseChannelStream(ChannelStream& channelStream); + + class ChannelStreamPool : public ProtoTreeTemplate::ItemPool {}; + + + /** + * @class TimerStream + * + * @brief This class lets us have a "Stream*" pointer + * for the user data in "struct epoll_event" since we + * use the "event.data" for that purpose. + */ + class TimerStream : public Stream + { + public: + TimerStream(); + ~TimerStream(); + + void SetDescriptor(Descriptor theDescriptor) + {descriptor = theDescriptor;} + Descriptor GetDescriptor() const + {return descriptor;} + Descriptor GetInputHandle() const + {return descriptor;} + Descriptor GetOutputHandle() const + {return descriptor;} + private: + const char* GetKey() const {return NULL;} + unsigned int GetKeysize() const {return 0;} + + private: + Descriptor descriptor; + }; // end class TimerStream + + /** + * @class EventStream + * + * @brief This class lets us have a "Stream*" pointer + * for the user data in "struct epoll_event" since we + * use the "event.data" for that purpose. + */ + class EventStream : public Stream + { + public: + EventStream(); + ~EventStream(); + + void SetDescriptor(Descriptor theDescriptor) + {descriptor = theDescriptor;} + Descriptor GetDescriptor() const + {return descriptor;} + Descriptor GetInputHandle() const + {return descriptor;} + Descriptor GetOutputHandle() const + {return descriptor;} + private: + const char* GetKey() const {return NULL;} + unsigned int GetKeysize() const {return 0;} + + private: + Descriptor descriptor; + }; // end class EventStream /** * @class GenericStream @@ -563,7 +645,6 @@ class ProtoDispatcher : public ProtoTimerMgr, * @brief This class helps manage notification for * protoSockets and generic I/O descriptors */ - class GenericStream : public Stream { public: @@ -577,31 +658,58 @@ class ProtoDispatcher : public ProtoTimerMgr, } void OnEvent(Event theEvent) {if (callback) callback(descriptor, theEvent, client_data);} - private: - Descriptor descriptor; - Callback* callback; - const void* client_data; + + const char* GetDescriptorPtr() const + {return ((const char*)&descriptor);} + + Descriptor GetInputHandle() const + {return descriptor;} + Descriptor GetOutputHandle() const + {return descriptor;} + + //private: + const char* GetKey() const + {return (const char*)(&myself);} + unsigned int GetKeysize() const + {return (sizeof(GenericStream*) << 3);} + + GenericStream* myself; // used as StreamTable key + Descriptor descriptor; + Callback* callback; + const void* client_data; }; // end class GenericStream - bool InstallGenericStream(ProtoDispatcher::Descriptor descriptor, - Callback* callback, - const void* userData, - Stream::Flag flag); - void RemoveGenericStream(GenericStream* stream) - {ReleaseGenericStream(stream);} + // This class is used to keep a separate listing of GenericStreams indexed + // by their descriptors so we can find them quickly. + class GenericStreamTable : public ProtoIndexedQueueTemplate + { + public: + GenericStream* FindByDescriptor(Descriptor descriptor) const + {return Find((const char*)&descriptor, sizeof(Descriptor) << 3);} + + private: + // Required overrides for ProtoIndexedQueue subclasses + virtual const char* GetKey(const Item& item) const + {return static_cast(item).GetDescriptorPtr();} + virtual unsigned int GetKeysize(const Item& /*item*/) const + {return (sizeof(Descriptor) << 3);} + }; // end ProtoDispatcher::GenericStreamTable + + class GenericStreamPool : public ProtoTreeTemplate::ItemPool {}; + GenericStream* GetGenericStream(Descriptor descriptor); - GenericStream* FindGenericStream(Descriptor descriptor) const; - void ReleaseGenericStream(GenericStream* stream); - - + GenericStream* FindGenericStream(Descriptor descriptor) const + {return generic_stream_table.FindByDescriptor(descriptor);} + void ReleaseGenericStream(GenericStream& genericStream); + // Members - SocketStream* socket_stream_pool; - SocketStream* socket_stream_list; - ChannelStream* channel_stream_pool; - ChannelStream* channel_stream_list; - GenericStream* generic_stream_pool; - GenericStream* generic_stream_list; + StreamTable stream_table; // table of active streams by channel, socket, etc pointer + GenericStreamTable generic_stream_table; // Used to lookup stream by "descriptor" value + ChannelStreamPool channel_stream_pool; // land of inactive channel streams + SocketStreamPool socket_stream_pool; // land of inactive socket streams + GenericStreamPool generic_stream_pool; // land of inactive generic streams + volatile bool run; WaitStatus wait_status; int exit_code; @@ -610,6 +718,7 @@ class ProtoDispatcher : public ProtoTimerMgr, ThreadId thread_id; bool priority_boost; volatile bool thread_started; + volatile bool thread_signaled; Mutex suspend_mutex; Mutex signal_mutex; ThreadId thread_master; @@ -617,13 +726,15 @@ class ProtoDispatcher : public ProtoTimerMgr, unsigned int signal_count; Controller* controller; + // The "prompt" stuff here was a hack to be able to make a threaded + // ProtoDispatcher do some "work" (prompt_callback) in its thread bool prompt_set; PromptCallback* prompt_callback; const void* prompt_client_data; #ifdef USE_TIMERFD int timer_fd; #endif // USE_TIMERFD - + bool is_signaled; #ifdef WIN32 int Win32AddStream(Stream& theStream, HANDLE theHandle); void Win32RemoveStream(int index); @@ -634,27 +745,52 @@ class ProtoDispatcher : public ProtoTimerMgr, #else static unsigned int __stdcall DoThreadStart(void* lpParameter); #endif // if/else _WIN32_WCE - ExitStatus exit_status; - enum {DEFAULT_ITEM_ARRAY_SIZE = 32}; - HANDLE stream_handles_default[DEFAULT_ITEM_ARRAY_SIZE]; - Stream* stream_ptrs_default[DEFAULT_ITEM_ARRAY_SIZE]; - DWORD stream_array_size; - HANDLE* stream_handles_array; - Stream** stream_ptrs_array; - DWORD stream_count; - HWND msg_window; - HANDLE break_event; - GenericStream* break_event_stream; - bool socket_io_pending; - HANDLE actual_thread_handle; + ExitStatus exit_status; + enum {DEFAULT_STREAM_ARRAY_SIZE = 32}; + HANDLE* stream_handles_array; + Stream** stream_ptrs_array; + DWORD stream_array_size; + DWORD stream_count; + HWND msg_window; + HANDLE actual_thread_handle; + StreamList ready_stream_list; // list of input or output "ready" streams + EventStream break_stream; +#ifdef USE_WAITABLE_TIMER + TimerStream timer_stream; + bool timer_active; +#endif // USE_WAITABLE_TIMER #else // UNIX static void* DoThreadStart(void* arg); - int exit_status; - fd_set input_set; - fd_set output_set; - int break_pipe_fd[2]; + int exit_status; +#if (defined(USE_SELECT) || defined(USE_EPOLL)) + EventStream break_stream; +#ifndef USE_EVENTFD + int break_pipe_fd[2]; +#endif // !USE_EVENTFD +#endif // USE_SELECT || USE_EPOLL +#if defined(USE_SELECT) + fd_set input_set; + fd_set output_set; +#elif defined(USE_KQUEUE) + enum {KEVENT_ARRAY_SIZE = 64}; + bool KeventChange(uintptr_t ident, int16_t filter, uint16_t flags, void* udata); + struct kevent kevent_array[KEVENT_ARRAY_SIZE]; + int kevent_queue; +#elif defined(USE_EPOLL) + enum {EPOLL_ARRAY_SIZE = 64}; + bool EpollChange(int fd, int events, int op, void* udata); + struct epoll_event epoll_event_array[EPOLL_ARRAY_SIZE]; + int epoll_fd; +#else // UNIX +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // !USE_SELECT && !USE_KQUEUE +#ifdef USE_TIMERFD + TimerStream timer_stream; +#endif // USE_TIMERFD #endif // if/else WIN32/UNIX + const void* user_data; + }; // end class ProtoDispatcher #endif // _PROTO_DISPATCHER diff --git a/include/protoFile.h b/include/protoFile.h index c59df27..3e7ee92 100644 --- a/include/protoFile.h +++ b/include/protoFile.h @@ -135,7 +135,7 @@ class ProtoFile : public ProtoChannel #else //int fd; #endif // if/else _WIN32_WCE - int flags; + //int flags; #ifdef WIN32 __int64 offset; #else @@ -160,7 +160,7 @@ class ProtoDirectoryIterator bool GetPath(char* pathBuffer); // "buffer" here _MUST_ be PATH_MAX long! bool GetNextFile(char* buffer); - + void Recursive(bool stepIntoDirs = false); private: class ProtoDirectory { @@ -185,6 +185,7 @@ class ProtoDirectoryIterator ProtoDirectory* current; int path_len; + bool search_dirs; }; // end class ProtoDirectoryIterator diff --git a/include/protoJson.h b/include/protoJson.h new file mode 100644 index 0000000..184a71d --- /dev/null +++ b/include/protoJson.h @@ -0,0 +1,357 @@ +#ifndef _PROTO_JSON +#define _PROTO_JSON + +// This provides a means for loading JSON input (from a file or text stream, etc) into a data structure +// The plan is to use this for configuration files, etc for Protolib protocol implementations. + +// NOTES: +// 1) The parsing of ASCII text JSON files is pretty much complete. Work needs to be done to support UTF +// codings that can occur, etc. +// +// 2) Many backslash escapes are not handled or checked. +// +// 3) The ProtoJson::Parser here loads JSON input text into a "document" data structure. Object key,value pairs +// _are_ stored in dictionaries and Array elements are in indexed array structures. A ProtoJson::Document +// is defined will be created with methods for iteration and queries. The ProtoJson::Document::Print() method +// uses iteration over the document tree and can serve as an example. Eventually, helper methods for +// programmatically constructing the Document/Tree will be supported, too. +// +// 4) The initial goal is to support configuration files for Protolib protocol implementations using the JSON +// format since it is well documented, fairly structured, and more human readable/editable than some other formats. + +#include "protoTree.h" +#include "protoQueue.h" + +namespace ProtoJson +{ + class Item : public ProtoQueue::Item + { + public: + + virtual ~Item(); + + enum Type + { + INVALID = 0, // for error handling + ENTRY, // Object key,value entries + STRING, + NUMBER, + OBJECT, + ARRAY, + TRUE, + FALSE, + NONE // we don't use 'NULL' because that collides + }; + + static const char* GetTypeString(Type type); + + Type GetType() const + {return type;} + + unsigned int GetLevel() const + {return level;} + + bool IsValue() const + {return (type > ENTRY);} + + void SetParent(Item* theParent) // TBD - make this only accessible to 'friend' classes + {parent = theParent;} + + const Item* GetParent() const // TBD - don't need this, can use stack instead?? + {return parent;} + Item* AccessParent() // TBD - don't need this, can use stack instead?? + {return parent;} + + protected: + Item(Type theType, Item* theParent = NULL); + + private: + Type type; + Item* parent; // TBD - deprecate the "parent" member if we don't really need it + unsigned int level; // Item's depth in document tree (a convenience for printing, etc) + // (If we implemented a container for our Document::Iterator class + // instead of using the default ProtoQueue mechanism, the container + // could be used to cache the item's level during iteration) + }; // end class ProtoJson::Item + + typedef Item Value; // for clarity and convenience + + class ItemList : public ProtoSimpleQueueTemplate + { + public: + void AddItem(Item& item) + {Append(item);} + }; // end class ProtoJson::ItemList + + class String : public Value + { + public: + String(Item* theParent = NULL); + virtual ~String(); + + bool Set(const char* theText); + void SetTextPtr(char* textPtr); + + const char* GetText() const + {return text;} + + size_t GetLength() const + {return (NULL != text) ? strlen(text) : 0;} + + private: + char* text; + + }; // end class ProtoJson::String + + // may be an int or double + class Number : public Value + { + public: + Number(Item* theParent = NULL); + Number(int value, Item* theParent = NULL); + Number(double value, Item* theParent = NULL); + + virtual ~Number(); + + bool SetValue(const char* text); + void SetValue(double value) + { + floating = value; + is_float = true; + } + void SetValue(int value) + { + integer = value; + is_float = false; + } + bool IsFloat() const + {return is_float;} + double GetDouble() const + {return is_float ? floating : (double)integer;} + int GetInteger() const + {return is_float ? (int)floating : integer;} + private: + bool is_float; + union + { + double floating; + int integer; + }; + }; // end class ProtoJson::Number + + // This is used for JSON Object key,value pairs + class Entry : public ProtoJson::Item, public ProtoSortedTree::Item + { + public: + Entry(ProtoJson::Item* theParent = NULL); + virtual ~Entry(); + + bool SetKey(const char* text); + + const char* GetKey() const + {return key;} + unsigned int GetKeysize() const + {return keysize;} + + void SetValue(Value* theValue); // deletes old value, if any + + const Value* GetValue() const + {return value;} + Value* AccessValue() const + {return value;} + + private: + char* key; + unsigned int keysize; + Value* value; + }; // end class ProtoJson::Entry + + class Object : public Item, protected ProtoSortedTreeTemplate + { + public: + Object(ProtoJson::Item* theParent = NULL); + virtual ~Object(); + void Destroy(); + + + bool InsertEntry(const char* key, Value& value); + + bool InsertEntry(Entry& entry); + + Entry* FindEntry(const char* key) + {return Find(key, (strlen(key)+1)*8);} + + void RemoveEntry(Entry& entry) + { + Remove(entry); + entry.SetParent(NULL); + } + + class Iterator : protected ProtoSortedTreeTemplate::Iterator + { + public: + Iterator(Object& object, bool reverse = false); + ~Iterator(); + // If "key" is non-NULL, return matching entries only + Entry* GetNextEntry(const char* key = NULL); + Entry* GetPrevEntry(const char* key = NULL); + private: + char* match_key; + + }; // end class ProtoJson::Object::Iterator + + }; // end class ProtoJson::Object + + class Array : public Value + { + public: + Array(Item* theParent = NULL); + virtual ~Array(); + + void Destroy(); + + unsigned int GetLength() const + {return array_len;} + + bool AppendValue(Value& value); + + const Value* GetValue(unsigned int index) const; + Value* AccessValue(unsigned int index); + + // "index" MUST be in existing array range for now + void SetValue(unsigned int index, Value& value); + void ClearValue(unsigned int index); // remove/delete Value at index + + private: + Item** array_buf; + unsigned int array_len; + }; // end class ProtoJson::Array + + class TrueValue : public Value + { + public: + TrueValue(Item* theParent = NULL) : Item(TRUE, theParent) {} + virtual ~TrueValue() {} + }; // end class ProtoJson::TrueValue + + class FalseValue : public Value + { + public: + FalseValue(Item* theParent = NULL) : Item(FALSE, theParent) {} + virtual ~FalseValue() {} + }; // end class ProtoJson::FalseValue + + class NullValue : public Value + { + public: + NullValue(Item* theParent = NULL) : Item(NONE, theParent) {} + virtual ~NullValue() {} + }; // end class ProtoJson::NullValue + + class Document : public ItemList + { + public: + Document(); + ~Document(); + + bool AddItem(ProtoJson::Item& item); + void RemoveItem(ProtoJson::Item& item); + unsigned int GetItemCount() const + {return item_count;} + + void Print(FILE* filePtr); // TBD - add a PrintToBuffer() method (or ConvertToText()) + static void PrintValue(FILE* filePtr, const Value& value); + class Iterator + { + public: + Iterator(Document& document, bool depthFirst = true); + ~Iterator(); + + ProtoJson::Item* GetNextItem(); + + private: + ItemList::Iterator list_iterator; + bool depth_first; + ItemList pending_list; + }; + friend class Iterator; + private: + ItemList item_list; + unsigned int item_count; + + }; // end class ProtoJson::Document + + class Parser + { + public: + Parser(); + ~Parser(); + + void Destroy(); + + bool LoadDocument(const char *path); + + Document* AccessDocument() + {return current_document;} + + Document* DetachDocument(); + + enum Status + { + PARSE_ERROR, + PARSE_MORE, + PARSE_DONE + }; + + Status ProcessInput(const char* inputBuffer, unsigned int inputLength); + + private: + // Delimiters for parsing + static const char OBJECT_START; + static const char OBJECT_END; + static const char ARRAY_START; + static const char ARRAY_END; + static const char QUOTE; + static const char COLON; + static const char COMMA; + static const char ESCAPE; + static const char TRUE_START; + static const char FALSE_START; + static const char NULL_START; + + // Methods used in internal parsing + static Item::Type GetType(char c); // infers item type from first non-whitespace char of Value + bool AddValueToParent(Item* theParent, Item& value); + bool AddToString(String& string, const char* text, unsigned int length); + Status ProcessStringInput(const char* input, unsigned int length); + bool AddToTemp(const char* text, unsigned int length); + Status ProcessNumberInput(const char* input, unsigned int length); + bool FixedItemIsValid(Item::Type type); + Status ProcessFixedInput(const char* input, unsigned int length); + Status ProcessArrayInput(const char* input, unsigned int length); + Status ProcessEntryInput(const char* input, unsigned int length); + Status ProcessObjectInput(const char* input, unsigned int length); + Status ProcessValueInput(const char* input, unsigned int length); + + void PushStack(Item& item) + {parser_stack.Prepend(item);} + Item* PopStack() + {return parser_stack.RemoveHead();} + Item* PeekStack() + {return parser_stack.GetHead();} + + Document* current_document; + ItemList parser_stack; + Item* current_item; + unsigned int input_offset; + bool is_escaped; // for caching escape parse state + bool seek_colon; // for caching Object "key : value" parse state + char* temp_buffer; + unsigned int temp_buffer_max; + unsigned int temp_buffer_len; + + }; // end class ProtoJson::Parser + +} // end namespace ProtoJson + +#endif // _PROTO_JSON diff --git a/include/protoList.h b/include/protoList.h index 33aad03..69958da 100644 --- a/include/protoList.h +++ b/include/protoList.h @@ -9,19 +9,13 @@ * deriving your own classes you wish to store in a * ProtoList. * -* I have some ideas for some more sophisticated -* list mechanisms (like that I put into the ProtoGraph -* class) to provide for a list item base class that can -* be a member of multiple lists at once, automatically get -* removed from all of its lists when destroyed, etc. But, -* meanwhile this simple list puts no sophisticated demands -* on memory management other than that specified by the -* programmer. +* Note the "ProtoQueue" classes provide some more sophisticated +* options like items that can be listed in multiple lists and +* automated removal from the multiple lists upon deletion, etc */ #include "protoDefs.h" - /** * @class ProtoIterable * @@ -131,6 +125,11 @@ class ProtoList : private ProtoIterable { public: virtual ~Item(); + + const Item* GetNext() const + {return plist_next;} + const Item* GetPrev() const + {return plist_prev;} protected: Item(); @@ -165,10 +164,15 @@ class ProtoList : private ProtoIterable Item* GetPrevItem(); Item* PeekPrevItem() const; - void SetCursor(Item* cursor) - {item = cursor;} + bool SetCursor(Item* cursor) + { + item = cursor; + return true; // note list membership not validated + } void Reverse(); + bool IsReversed() const + {return reversed;} private: // Required override for ProtoIterable to make sure any @@ -179,9 +183,6 @@ class ProtoList : private ProtoIterable Item* item; bool reversed; - Iterator* ilist_prev; - Iterator* ilist_next; - }; // end class ProtoList::Iterator class ItemPool @@ -419,8 +420,12 @@ class ProtoStackTemplate : public ProtoStack ProtoStackTemplate() {} virtual ~ProtoStackTemplate() {} + void Push(ITEM_TYPE& item) + {ProtoStack::Push(item);} ITEM_TYPE* Pop() {return static_cast(ProtoStack::Pop());} + ITEM_TYPE* Peek() + {return static_cast(ProtoStack::Peek());} ITEM_TYPE* Get() {return static_cast(ProtoStack::Get());} ITEM_TYPE* GetHead() const diff --git a/include/protoNet.h b/include/protoNet.h index 7d6f5e9..ea9f7fb 100644 --- a/include/protoNet.h +++ b/include/protoNet.h @@ -1,8 +1,6 @@ #ifndef _PROTO_NET #define _PROTO_NET - - // The ProtoNet classes provide APIs for getting information // on the computer host's network interfaces, configured // addresses, etc. @@ -33,6 +31,13 @@ namespace ProtoNet * configuration. * */ + + enum InterfaceStatus + { + IFACE_UNKNOWN, + IFACE_UP, + IFACE_DOWN + }; class Monitor : public ProtoChannel { @@ -54,6 +59,7 @@ namespace ProtoNet // in another class // Use the GetNextEvent() to fetch the next network status update event + // (This should be called upon a ProtoChannel::NOTIFY_INPUT notification event) // from the system. Note that other functions may need to be invoked to // get details about a particular event (e.g., change in interface "flags", etc) @@ -74,6 +80,8 @@ namespace ProtoNet UNKNOWN_EVENT }; + enum {IFNAME_MAX = 255}; + Event(); ~Event(); @@ -83,6 +91,14 @@ namespace ProtoNet {iface_index = ifaceIndex;} void SetAddress(ProtoAddress& addr) {iface_addr = addr;} + void SetInterfaceName(const char* name) + { +#ifdef WIN32 + strncpy_s(iface_name, IFNAME_MAX, name, IFNAME_MAX); +#else + strncpy(iface_name, name, IFNAME_MAX); +#endif // if/else WIN32 + } Type GetType() const {return event_type;} @@ -92,12 +108,15 @@ namespace ProtoNet {return iface_addr;} ProtoAddress& AccessAddress() {return iface_addr;} + const char* GetInterfaceName() const + {return iface_name;} private: Type event_type; int iface_index; ProtoAddress iface_addr; + char iface_name[IFNAME_MAX+1]; }; // end class ProtoNet::Monitor::Event @@ -108,37 +127,89 @@ namespace ProtoNet }; // end class ProtoNet::Monitor + // These are the base ProtoNet methods that must be implemented in + // with specific operating system calls (i.e. in unixNet.cpp, win32Net.cpp, etc) + unsigned int GetInterfaceIndices(unsigned int* indexArray, unsigned int indexArraySize); - // These appends addresses of type "addrType" to the "addrList" - bool GetHostAddressList(ProtoAddress::Type addrType, - ProtoAddressList& addrList); - + // get iface name by index + unsigned int GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen); + + // get iface index by name + unsigned int GetInterfaceIndex(const char* interfaceName); + + // get all addrs of "addrType" for given given "ifName" bool GetInterfaceAddressList(const char* ifName, ProtoAddress::Type addrType, ProtoAddressList& addrList, unsigned int* ifIndex = NULL); + // get name that matches given ifAddr (may be an alias name) + // (returns name length so you can verify buflen was sufficient) + unsigned int GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen); +#ifdef WIN32 + unsigned int GetInterfaceFriendlyName(const ProtoAddress& ifaceAddress, char* buffer, unsigned int buflen); + unsigned int GetInterfaceFriendlyName(unsigned int ifaceIndex, char* buffer, unsigned int buflen); + bool GetInterfaceAddressDhcp(const char* ifName, const ProtoAddress& ifAddr); + // TODO: Fix functions to have one definition + bool AddInterfaceAddress(const char* ifaceName, const ProtoAddress& addr, unsigned int maskLen, bool dhcp_enabled=false); + bool GetInterfaceIpAddress(unsigned int index, ProtoAddress& ifAddr); +#else + bool AddInterfaceAddress(const char* ifaceName, const ProtoAddress& addr, unsigned int maskLen); + +#endif //WIN32 + unsigned int GetInterfaceAddressMask(const char* ifName, const ProtoAddress& ifAddr); + bool RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& addr, unsigned int maskLen = 0); + +#ifndef WIN32 // TBD - implement these for WIN32 + + + bool GetGroupMemberships(const char* ifaceName, ProtoAddress::Type addrType, ProtoAddressList& addrList); + +#endif // !WIN32 + + ///////////////////////////////////////////////////////// + // These are implemented in "protoNet.cpp" using the above + // 'base' functions. + + unsigned int GetInterfaceCount(); + + unsigned int GetInterfaceIndex(const ProtoAddress& ifAddr); + bool GetInterfaceAddress(const char* ifName, ProtoAddress::Type addrType, ProtoAddress& theAddress, unsigned int* ifIndex = NULL); - // Returns mask length for given interface address (0 if not valid iface or addr) - unsigned int GetInterfaceAddressMask(const char* ifName, const ProtoAddress& ifAddr); - + bool GetInterfaceAddress(unsigned int ifIndex, + ProtoAddress::Type addrType, + ProtoAddress& theAddress); - unsigned int GetInterfaceCount(); - unsigned int GetInterfaceIndices(unsigned int* indexArray, unsigned int indexArraySize); + bool GetHostAddressList(ProtoAddress::Type addrType, + ProtoAddressList& addrList); + + bool GetInterfaceAddressList(unsigned int ifIndex, + ProtoAddress::Type addrType, + ProtoAddressList& addrList); - unsigned int GetInterfaceIndex(const char* interfaceName); bool FindLocalAddress(ProtoAddress::Type addrType, ProtoAddress& theAddress); - bool GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen); - bool GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen); - - bool AddInterfaceAddress(const char* ifaceName, const ProtoAddress& addr, unsigned int maskLen); - bool RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& addr, unsigned int maskLen = 0); - + InterfaceStatus GetInterfaceStatus(const char* ifaceName); + InterfaceStatus GetInterfaceStatus(unsigned int ifaceIndex); + +#ifdef WIN32 + // TODO: make common function + bool AddInterfaceAddress(unsigned int ifaceIndex, const ProtoAddress& addr, unsigned int maskLen, bool dhcp_enabled=false); + +#else + bool AddInterfaceAddress(unsigned int ifaceIndex, const ProtoAddress& addr, unsigned int maskLen); +#endif // WIN32 + + bool RemoveInterfaceAddress(unsigned int ifaceIndex, const ProtoAddress& addr, unsigned int maskLen = 0); + + unsigned int GetInterfaceAddressMask(unsigned int ifIndex, const ProtoAddress& ifAddr); +#ifdef WIN32 + bool GetInterfaceAddressDhcp(unsigned int ifIndex, const ProtoAddress& ifAddr); +#endif // WIN32 } // end namespace ProtoNet diff --git a/include/protoPipe.h b/include/protoPipe.h index df86cca..3b70c95 100644 --- a/include/protoPipe.h +++ b/include/protoPipe.h @@ -17,8 +17,7 @@ class ProtoPipe : public ProtoSocket public: enum Type {MESSAGE, STREAM}; - ProtoPipe(); // JPH 11/2/2005 - added default constructor for nrlsmf.ex.cpp compilation - ProtoPipe(Type theType); + ProtoPipe(Type theType = MESSAGE); ~ProtoPipe(); Type GetType() {return ((UDP == GetProtocol()) ? MESSAGE : STREAM);} @@ -28,15 +27,10 @@ class ProtoPipe : public ProtoSocket bool Accept(ProtoPipe* thePipe = NULL); void Close(); -#if defined(WIN32) && !defined(_WIN32_WCE) - bool Send(const char* buffer, unsigned int& numBytes); - bool Recv(char* buffer, unsigned int& numBytes); -#else bool Send(const char* buffer, unsigned int& numBytes) {return ProtoSocket::Send(buffer, numBytes);} bool Recv(char* buffer, unsigned int& numBytes) {return ProtoSocket::Recv(buffer, numBytes);} -#endif // if/else WIN32/UNIX private: bool Open(const char* theName); @@ -44,7 +38,7 @@ class ProtoPipe : public ProtoSocket void Unlink(const char* theName); bool unlink_tried; #else -#ifndef _WIN32_WCE +#ifdef _NEVER_ virtual bool SetBlocking(bool blocking); HANDLE pipe_handle; bool is_mailslot; @@ -57,9 +51,8 @@ class ProtoPipe : public ProtoSocket char write_buffer[BUFFER_MAX]; OVERLAPPED read_overlapped; OVERLAPPED write_overlapped; -#else +#endif HANDLE named_event_handle; -#endif // if/else _WIN32_WCE #endif // if/else !WIN32 char path[PATH_MAX]; }; diff --git a/include/protoPkt.h b/include/protoPkt.h index 292af6e..23e4bbc 100644 --- a/include/protoPkt.h +++ b/include/protoPkt.h @@ -23,6 +23,11 @@ * building and parsing (For examples, see * ProtoPktIP, ProtoPktRTP, etc) */ + + // TBD - we should make this a template class so we can use different "buffer_ptr" types + // such as char*, UINT16*, etc depending upon the alignment requirements of the + // packet format specification. + class ProtoPkt { public: @@ -65,6 +70,7 @@ class ProtoPkt } const char* GetBuffer() const {return (char*)buffer_ptr;} + const UINT32* GetBuffer32() const {return buffer_ptr;} unsigned int GetBufferLength() const {return buffer_bytes;} unsigned int GetLength() const {return pkt_length;} @@ -79,6 +85,7 @@ class ProtoPkt // These helper methods are defined for setting multi-byte protocol // fields in one of two ways: 1) "cast and assign", or 2) memcpy() + // **IMPORTANT** Note the offsets are _byte_ offsets!!! #ifdef CAST_AND_ASSIGN static UINT16 GetUINT16(UINT16* ptr) const {return ntohs(*ptr);} @@ -123,23 +130,25 @@ class ProtoPkt UINT16 GetUINT16(unsigned int byteOffset) const { - UINT16* ptr = (UINT16*)((char*)buffer_ptr + byteOffset); - return GetUINT16(ptr); + UINT16 value; + memcpy(&value, (char*)buffer_ptr + byteOffset, 2); + return ntohs(value); } UINT32 GetUINT32(unsigned int byteOffset) const { - UINT32* ptr = (UINT32*)((char*)buffer_ptr + byteOffset); - return GetUINT32(ptr); + UINT32 value; + memcpy(&value, (char*)buffer_ptr + byteOffset, 4); + return ntohl(value); } void SetUINT16(unsigned int byteOffset, UINT16 value) { - UINT16* ptr = (UINT16*)((char*)buffer_ptr + byteOffset); - SetUINT16(ptr, value); + value = htons(value); + memcpy((char*)buffer_ptr + byteOffset, &value, 2); } void SetUINT32(unsigned int byteOffset, UINT32 value) { - UINT32* ptr = (UINT32*)((char*)buffer_ptr + byteOffset); - SetUINT32(ptr, value); + value = htonl(value); + memcpy((char*)buffer_ptr + byteOffset, &value, 4); } #endif // if/else CAST_AND_ASSIGN diff --git a/include/protoPktARP.h b/include/protoPktARP.h index 00deae9..3f11681 100644 --- a/include/protoPktARP.h +++ b/include/protoPktARP.h @@ -74,7 +74,7 @@ class ProtoPktARP : public ProtoPkt // Use these to build the ARP message - // (should be called in order of appearance here) + // (MUST be called in order of appearance here) bool InitIntoBuffer(UINT32* bufferPtr = 0, unsigned int numBytes = 0, bool freeOnDestruct = false); @@ -91,9 +91,9 @@ class ProtoPktARP : public ProtoPkt {((UINT16*)buffer_ptr)[OFFSET_HRD] = htons((UINT16)hwType);} void SetEtherType(ProtoPktETH::Type etherType) // protocol address type {((UINT16*)buffer_ptr)[OFFSET_PRO] = htons((UINT16)etherType);} - void SetHardwareAddrLen(UINT8 numBytes) const + void SetHardwareAddrLen(UINT8 numBytes) const {((UINT8*)buffer_ptr)[OFFSET_HLN] = numBytes;} - void SetProtocolAddrLen(UINT8 numBytes) const + void SetProtocolAddrLen(UINT8 numBytes) const {((UINT8*)buffer_ptr)[OFFSET_PLN] = numBytes;} enum @@ -121,4 +121,102 @@ class ProtoPktARP : public ProtoPkt }; // end class ProtoPktARP +// Data structure for both MAC->IP and IP->MAC lookups (dually indexed) +// (Note multiple IPs per MAC is allowed, but one MAC per IP) +class ProtoArpTable +{ + public: + ProtoArpTable(); + ~ProtoArpTable(); + + bool AddEntry(const ProtoAddress& ipAddr, const ProtoAddress& macAddr); + void RemoveEntryByIP(const ProtoAddress& ipAddr); + void RemoveEntryByMAC(const ProtoAddress& macAddr); + + bool GetMacAddress(const ProtoAddress& ipAddr, ProtoAddress& macAddr); + bool GetAddressList(const ProtoAddress& macAddr, ProtoAddressList addrList); + + void Destroy() + { + mac_list.Destroy(); + ip_list.Destroy(); + } + + private: + // Record of IP address listings for a given MAC address + class MacItem : public ProtoTree::Item + { + public: + MacItem(const ProtoAddress& macAddr); + ~MacItem(); + + const ProtoAddress& GetMacAddr() + {return mac_addr;} + ProtoAddressList& AccessAddressList() + {return ip_addr_list;} + + bool AddAddress(const ProtoAddress& ipAddr) + {return ip_addr_list.Insert(ipAddr);} + void RemoveAddress(const ProtoAddress& ipAddr) + {ip_addr_list.Remove(ipAddr);} + + private: + const char* GetKey() const + {return mac_addr.GetRawHostAddress();} + unsigned int GetKeysize() const + {return (8 * mac_addr.GetLength());} + + ProtoAddress mac_addr; + ProtoAddressList ip_addr_list; + }; // end class ProtoArpTable::MacItem + class MacList : public ProtoTreeTemplate + { + public: + MacItem* FindItem(const ProtoAddress& macAddr) + {return Find(macAddr.GetRawHostAddress(), 8 * macAddr.GetLength());} + }; // end class ProtoArpTable::IPList + + // MAC address indexed by IP address + class IPItem : public ProtoTree::Item + { + public: + IPItem(const ProtoAddress& ipAddr, MacItem* macItem) + : ip_addr(ipAddr), mac_item(macItem) {} + ~IPItem() {} + + const ProtoAddress& GetAddress()const + {return ip_addr;} + const ProtoAddress& GetMacAddr() const + {return mac_item->GetMacAddr();} + + MacItem* GetMacItem() + {return mac_item;} + + private: + const char* GetKey() const + {return ip_addr.GetRawHostAddress();} + unsigned int GetKeysize() const + {return (8 * ip_addr.GetLength());} + + ProtoAddress ip_addr; + MacItem* mac_item; + }; // end class ProtoArpTable::IPItem + + + // List of MAC address item indexed by IP address + class IPList : public ProtoTreeTemplate + { + public: + IPItem* FindItem(const ProtoAddress& ipAddr) + {return Find(ipAddr.GetRawHostAddress(), 8 * ipAddr.GetLength());} + }; // end class ProtoArpTable::IPList + + void DeleteIPItem(IPItem* ipItem); + void DeleteMacItem(MacItem* macItem); + + MacList mac_list; + IPList ip_list; + +}; // end class ProtoArpTable + #endif // _PROTO_PKT_ARP diff --git a/include/protoPktETH.h b/include/protoPktETH.h index 91bd248..64dcbde 100644 --- a/include/protoPktETH.h +++ b/include/protoPktETH.h @@ -94,6 +94,9 @@ class ProtoPktETH : public ProtoPkt const char* GetPayload() {return (((const char*)buffer_ptr)+OFFSET_PAYLOAD);} char* AccessPayload() {return (((char*)buffer_ptr)+OFFSET_PAYLOAD);} + bool InitIntoBuffer(UINT32* bufferPtr = NULL, + unsigned int bufferBytes = 0, + bool freeOnDestruct = false); void SetSrcAddr(ProtoAddress srcAddr) {memcpy(((char*)buffer_ptr)+OFFSET_SRC, srcAddr.GetRawHostAddress(), ADDR_LEN);} void SetDstAddr(ProtoAddress dstAddr) @@ -111,10 +114,10 @@ class ProtoPktETH : public ProtoPkt private: enum { - OFFSET_DST = 0, // 0 bytes - OFFSET_SRC = ADDR_LEN, // 6 bytes - OFFSET_TYPE = (2*ADDR_LEN)/2, // 6 UINT16 (12 bytes) - OFFSET_PAYLOAD = 2*(OFFSET_TYPE+1) // 14 bytes + OFFSET_DST = 0, // 6 bytes, zero offset + OFFSET_SRC = OFFSET_DST + ADDR_LEN, // 6 bytes, UINT8 offset + OFFSET_TYPE = (OFFSET_SRC + ADDR_LEN)/2, // 2 bytes, UINT16 offset + OFFSET_PAYLOAD = 2*(OFFSET_TYPE+1) // UINT8 offset }; }; // end class ProtoPktETH diff --git a/include/protoPktIGMP.h b/include/protoPktIGMP.h new file mode 100644 index 0000000..a6fbde6 --- /dev/null +++ b/include/protoPktIGMP.h @@ -0,0 +1,186 @@ +#ifndef _PROTO_PKT_IGMP +#define _PROTO_PKT_IGMP + +#include "protoPkt.h" +#include "protoAddress.h" + +class ProtoPktIGMP : public ProtoPkt +{ + public: + ProtoPktIGMP(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = false, + bool freeOnDestruct = false); + ~ProtoPktIGMP(); + + enum Type + { + QUERY = 0x11, + REPORT_V1 = 0x12, + REPORT_V2 = 0x16, + REPORT_V3 = 0x22, + LEAVE = 0x17 + }; + + static const UINT8 DEFAULT_QRV; // 2 per RFC 3376 + static const double DEFAULT_QQIC; // 125 seconds per RFC 3376 + static const double DEFAULT_MAX_RESP; // 10 seconds per RFC 3376 + + // IGMPv3 REPORT Group Record + class GroupRecord : public ProtoPkt + { + public: + GroupRecord(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = false, + bool freeOnDestruct = false); + ~GroupRecord(); + + enum Type + { + INVALID_TYPE = 0, + MODE_IS_INCLUDE = 1, + MODE_IS_EXCLUDE = 2, + CHANGE_TO_INCLUDE_MODE = 3, // include none == ASM leave, otherwise SSM join src list + CHANGE_TO_EXCLUDE_MODE = 4, // exclude none == ASM join group, otherwise SSM leave src list + ALLOW_NEW_SOURCES = 5, // source-specific join essentially? + BLOCK_OLD_SOURCES = 6 // source-specific leave essentially? + }; + + // Use these to parse Group Records + bool InitFromBuffer(UINT32* bufferPtr = NULL, + unsigned int bufferBytes = 0, + bool freeOnDestruct = false); + + Type GetType() const + {return (Type)GetUINT8(OFFSET_TYPE);} + unsigned int GetAuxDataLen() const + { + unsigned int len = GetUINT8(OFFSET_AUX_LEN); + return (len << 2); // returns length in bytes + } + UINT16 GetNumSources() const + {return GetUINT16(OFFSET_NUM_SRC);} + bool GetGroupAddress(ProtoAddress& groupAddr) + { + const char* ptr = (char*)buffer_ptr + OFFSET_GROUP; + groupAddr.SetRawHostAddress(ProtoAddress::IPv4, ptr, 4); + return groupAddr.IsMulticast(); + } + bool GetSourceAddress(UINT16 index, ProtoAddress& srcAddr) const; + const char* GetAuxData() const + {return ((char*)buffer_ptr + OffsetAuxData());} + + // Use these to create group records + bool InitIntoBuffer(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + void SetType(Type type) + {SetUINT8(OFFSET_TYPE, type);} + void SetGroupAddress(const ProtoAddress* groupAddr = NULL); + bool AppendSourceAddress(const ProtoAddress& srcAddr); + bool AppendAuxiliaryData(const char* data, UINT16 len); + + private: + enum + { + OFFSET_TYPE = 0, + OFFSET_AUX_LEN = OFFSET_TYPE + 1, + OFFSET_NUM_SRC = OFFSET_AUX_LEN + 1, + OFFSET_GROUP = OFFSET_NUM_SRC + 2, + OFFSET_SRC_LIST = OFFSET_GROUP + 4 + }; + UINT16 OffsetAuxData() const + {return (OFFSET_SRC_LIST + 4 * GetNumSources());} + }; // end class ProtoPktIGMP::GroupRecord + + // Use these to parse the IGMP message + bool InitFromBuffer(UINT16 pktLength, + UINT32* bufferPtr = NULL, + unsigned int buferBytes = 0, + bool freeOnDestruct = false); + + UINT8 GetVersion() const; + + Type GetType() const + {return (Type)GetUINT8(OFFSET_TYPE);} + double GetMaxResponseTime() const; // return value in seconds + UINT16 GetChecksum() const + {return GetUINT16(OFFSET_CHECKSUM);} + // For QUERY, REPORT_V1, REPORT_V2, LEAVE only + bool GetGroupAddress(ProtoAddress& groupAddr) const + { + const char* ptr = (char*)buffer_ptr + OFFSET_GROUP; + groupAddr.SetRawHostAddress(ProtoAddress::IPv4, ptr, 4); + return groupAddr.IsMulticast(); + } + // These 5 are for IGMPv3 QUERY messages only + bool GetSuppress() const + {return (GetVersion() < 3) ? false : (0 != (GetUINT8(OFFSET_S) & 0x08));} + UINT8 GetQRV() const + {return ((GetVersion() < 3) ? 0 : (GetUINT8(OFFSET_QRV) & 0x07));} + double GetQQIC() const; + UINT16 GetNumSources() const + {return (GetVersion() < 3) ? 0 : GetUINT16(OFFSET_NUM_SRC);} + bool GetSourceAddress(UINT16 index, ProtoAddress& srcAddr) const; + // These 2 are for IGMPv3 REPORT messages + UINT16 GetNumRecords() const + {return GetUINT16(OFFSET_NUM_REC);} + // This uses the prev groupRecord state to fetch next. If "groupRecord" + // passed in has a NULL buffer_ptr _or_ "first_ is true, then the + // first record is fetched. + bool GetNextGroupRecord(GroupRecord& groupRecord, bool first = false) const; + + + // Use these for building IGMP packets + bool InitIntoBuffer(Type type, + unsigned int version, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + + // This should be set to zero for IGMPv1 queries and report messages + void SetMaxResponseTime(double seconds, bool updateChecksum = true); + // set groupAddr pointer to NULL for general query + void SetGroupAddress(ProtoAddress* groupAddr = NULL, bool updateChecksum = true); + // These 4 are for IGMPv3 QUERY messages only + bool SetSuppress(bool state, bool updateChecksum = true); + bool SetQRV(UINT8 qrv, bool updateChecksum = true); + bool SetQQIC(double seconds, bool updateChecksum = true); + bool AppendSourceAddress(ProtoAddress& srcAddr, bool updateChecksum = true); + // These are for IGMPv3 REPORT messages only + bool AttachGroupRecord(GroupRecord& groupRecord); // inits "groupRecord" + bool AppendGroupRecord(const GroupRecord& groupRecord, bool updateChecksum = true); + + + // Return computed checksum in host byte order and set by default + UINT16 ComputeChecksum(bool set = true); + UINT16 FinalizeChecksum() + {return ComputeChecksum(true);} + + + private: + // shared offsets for IGMP query, report, and leave messages + enum + { + OFFSET_TYPE = 0, + OFFSET_MAX_RESP = OFFSET_TYPE + 1, + OFFSET_CHECKSUM = OFFSET_MAX_RESP + 1, + OFFSET_GROUP = OFFSET_CHECKSUM + 2, + OFFSET_RESERVED = OFFSET_GROUP + 4, + OFFSET_S = OFFSET_RESERVED, + OFFSET_QRV = OFFSET_S, + OFFSET_QQIC = OFFSET_QRV + 1, + OFFSET_NUM_SRC = OFFSET_QQIC + 1, // for IGMPv3 queries + OFFSET_NUM_REC = OFFSET_GROUP + 2, // for IGMPv3 reports + OFFSET_SRC_LIST = OFFSET_NUM_SRC + 2, // for IGMPv3 queries + OFFSET_REC_LIST = OFFSET_NUM_REC + 2 // for IGMPv3 reports + }; + // IGMPv3 group record field offsets + + +}; // end class ProtoPktIGMP + +// TBD - implement MLD (incl. MLDv2) ... ProtoPktMLD + +#endif // _PROTO_PKT_IGMP diff --git a/include/protoPktIP.h b/include/protoPktIP.h index f8e3f3c..cb968a0 100644 --- a/include/protoPktIP.h +++ b/include/protoPktIP.h @@ -57,18 +57,22 @@ class ProtoPktIP : public ProtoPkt HOPOPT = 0, // IPv6 hop-by-hop option ICMP = 1, // Internet Control Message Protocol IGMP = 2, // Internet Group Management Protocol - IPIP = 3, // IP in IP encapsulation + IPIP = 4, // IPv4 in IPv4 encapsulation TCP = 6, // Transmission Control Protocol UDP = 17, // User Datagram Protocol + IPV6 = 41, // Used for tunneling IPv6 packets over IPv4 or IPv6 RTG = 43, // IPv6 routing header FRAG = 44, // IPv6 fragment header + GRE = 47, // Generic Router Encapsulation ESP = 50, // Encapsulation security payload header - AUTH = 51, // authentication/ESP header + AUTH = 51, // authentication/ESP header + MOBILE = 55, // IP Moobility (Min Encap) ICMPv6 = 58, // ICMP for IPv6 MLD = 58, // IPv6 Multicast Listener Discovery NONE = 59, // IPPROTO_NONE - DSTOPT = 60, // IPv6 destination options header - MOBILE = 135, // IPv6 mobility header? + DSTOPT = 60, // IPv6 destination options header + OSPF = 89, // OSPF routing protocol + MOBILITY = 135, // IPv6 mobility extension header EXP1 = 253, // for experimental use EXP2 = 254, // for experimental use RESERVED = 255 @@ -159,6 +163,7 @@ class ProtoPktIPv4 : public ProtoPktIP enum Flag { + FLAG_NONE = 0x00, FLAG_RESERVED = 0x80, // reserved bit FLAG_DF = 0x40, // 0 = may fragment, 1 = don't fragment FLAG_MF = 0x20 // 0 = last fragment, 1 = more fragments @@ -312,7 +317,6 @@ class ProtoPktIPv4 : public ProtoPktIP void GetDstAddr(ProtoAddress& addr) const {addr.SetRawHostAddress(ProtoAddress::IPv4, (char*)(buffer_ptr+OFFSET_DST_ADDR), 4);} - /// Helper method to get pointer to ID portion of IPv4 header const char* GetIDPtr() const {return ((char*)buffer_ptr + (OFFSET_ID*2));} @@ -393,8 +397,10 @@ class ProtoPktIPv4 : public ProtoPktIP return ttl; } - /// Return checksum in host byte order + // Return checksum in host byte order UINT16 CalculateChecksum(bool set = true); + UINT16 FinalizeChecksum() + {return CalculateChecksum(true);} private: void SetHeaderLength(UINT8 hdrBytes) @@ -592,7 +598,7 @@ class ProtoPktIPv6 : public ProtoPktIP bool Pack(); - bool InitFromBuffer(Protocol extType, UINT32* bufferPtr, unsigned int numBytes, bool freeOnDestruct = false); + bool InitFromBuffer(Protocol extType, UINT32* bufferPtr = NULL, unsigned int numBytes = 0, bool freeOnDestruct = false); Protocol GetType() const {return ext_type;} Protocol GetNextHeader() const @@ -925,6 +931,83 @@ class ProtoPktESP : public ProtoPkt }; }; // end class ProtoPktESP + + +// This represents the RFC 2004 Minimal Forwarding Header and IP Payload +class ProtoPktMobile : public ProtoPkt +{ + public: + ProtoPktMobile(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = false, + bool freeOnDestruct = false); + ~ProtoPktMobile(); + + enum Flag {FLAG_SRC = 0x80}; + + // Use these to build an MOBILE Minimal Forwarding Header + bool InitIntoBuffer(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + + void SetProtocol(ProtoPktIP::Protocol protocol) + {SetUINT8(OFFSET_PROTOCOL, (UINT8)protocol);} + void SetFlag(Flag flag) + {((UINT8*)buffer_ptr)[OFFSET_FLAGS] |= flag;} + void ClearFlag(Flag flag) + {((UINT8*)buffer_ptr)[OFFSET_FLAGS] &= ~flag;} + void SetChecksum(UINT16 checksum) + {((UINT16*)buffer_ptr)[OFFSET_CHECKSUM] = htons(checksum);} + void SetDstAddr(const ProtoAddress& addr, bool calculateChecksum = false); + bool SetSrcAddr(const ProtoAddress& addr, bool calculateChecksum = false); + void SetPayload(const char* payload, UINT16 numBytes) + { + memcpy((char*)(buffer_ptr+OffsetPayload()), payload, numBytes); + if (FlagIsSet(FLAG_SRC)) + SetLength(12 + numBytes); + else + SetLength(8 + numBytes); + } + UINT16 CalculateChecksum(bool set = true); + + bool InitFromBuffer(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + + ProtoPktIP::Protocol GetProtocol() const + {return (ProtoPktIP::Protocol)GetUINT8(OFFSET_PROTOCOL);} + bool FlagIsSet(Flag flag) const + {return (0 != (flag & (((UINT8*)buffer_ptr)[OFFSET_FLAGS])));} + UINT16 GetChecksum() const + {return GetUINT16(OFFSET_CHECKSUM);} + void GetDstAddr(ProtoAddress& dst) const + {dst.SetRawHostAddress(ProtoAddress::IPv4, (char*)(buffer_ptr+OFFSET_DST_ADDR), 4);} + bool GetSrcAddr(ProtoAddress& src) const; + + UINT16 GetPayloadLength() const + {return GetLength() - 4*OffsetPayload();} + const UINT32* GetPayload() const + {return (buffer_ptr + OffsetPayload());} + UINT32* AccessPayload() + {return (buffer_ptr + OffsetPayload());} + + private: + enum + { + OFFSET_PROTOCOL = 0, // UINT8 offset + OFFSET_RESERVED = (OFFSET_PROTOCOL + 1), // UINT8 offset + OFFSET_FLAGS = OFFSET_RESERVED, // UINT8 offset + OFFSET_CHECKSUM = (OFFSET_PROTOCOL/2 + 1), // UINT16 offset + OFFSET_DST_ADDR = (OFFSET_CHECKSUM/2 + 1), // UINT32 offset + OFFSET_SRC_ADDR = (OFFSET_DST_ADDR + 1) // UINT32 offset + }; + + unsigned int OffsetPayload() const // (UINT32 offset + {return (FlagIsSet(FLAG_SRC) ? OFFSET_SRC_ADDR + 1 : OFFSET_SRC_ADDR);} + + +}; // end class ProtoPktMobile + /** * @class ProtoPktDPD * diff --git a/include/protoPktRIP.h b/include/protoPktRIP.h new file mode 100644 index 0000000..97f4d3a --- /dev/null +++ b/include/protoPktRIP.h @@ -0,0 +1,132 @@ +#ifndef _PROTO_PKT_RIP +#define _PROTO_PKT_RIP + +#include "protoPkt.h" +#include "protoAddress.h" + +class ProtoPktRIP : public ProtoPkt +{ + public: + ProtoPktRIP(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + unsigned int pktLength = 0, // inits from buffer if non-zero + bool freeOnDestruct = false); + ~ProtoPktRIP(); + + enum Command + { + INVALID = 0, + REQUEST = 1, + RESPONSE = 2 + }; + + enum {VERSION = 2}; + + enum AddressFamily + { + NONE = 0, + IPv4 = 2, // AF_INET + AUTH = 0xffff // Indicates Authentication entry + }; + + enum AuthType + { + AUTH_NONE = 0, + AUTH_PASS = 2 + }; + + class RouteEntry : public ProtoPkt + { + public: + RouteEntry(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = false, + bool freeOnDestruct = false); + ~RouteEntry(); + + // Use these to build a route entry + // (call them in order) + bool InitIntoBuffer(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + void SetAddressFamily(AddressFamily family) + {SetUINT16((unsigned int)(2*OFFSET_FAMILY), (UINT16)family);} + void SetRouteTag(UINT16 tag) + {SetUINT16(2*OFFSET_TAG, tag);} + bool SetAddress(const ProtoAddress& addr); + bool SetMask(const ProtoAddress& addr); + bool SetMaskLength(UINT8 maskLen); + bool SetNextHop(const ProtoAddress& addr); + void SetMetric(UINT32 metric) + {SetUINT32(4*OFFSET_METRIC, metric);} + + // Use these to parse a route entry + bool InitFromBuffer(unsigned int pktLength = 0, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + AddressFamily GetAddressFamily() const + {return (AddressFamily)(GetUINT16((unsigned int)(2*OFFSET_FAMILY)));} + UINT16 GetRouteTag() const + {return GetUINT16(2*OFFSET_TAG);} + bool GetAddress(ProtoAddress& addr) const; + bool GetMask(ProtoAddress& addr) const; + UINT8 GetMaskLength() const; + bool GetNextHop(ProtoAddress& addr) const; + UINT32 GetMetric() const + {return GetUINT32(4*OFFSET_METRIC);} + + private: + enum + { + OFFSET_FAMILY = 0, // UINT16 offset + OFFSET_TAG = OFFSET_FAMILY + 1, // UINT16 offset + OFFSET_ADDR = (OFFSET_TAG+1)/2, // UINT32 offset + OFFSET_MASK = OFFSET_ADDR + 1, // UINT32 offset + OFFSET_NHOP = OFFSET_MASK + 1, // UINT32 offset + OFFSET_METRIC = OFFSET_NHOP + 1 // UINT32 offset + }; + }; // end class ProtoPktRIP::RouteEntry + + bool InitIntoBuffer(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + void SetCommand(Command cmd) + {SetUINT8(OFFSET_COMMAND, (UINT8)cmd);} + void SetVersion(UINT8 version) + {SetUINT8(OFFSET_VERSION, version);} + + bool AddRouteEntry(const ProtoAddress& destAddr, + UINT32 maskLen, + const ProtoAddress& nextHop, + UINT32 metric = 1, + UINT16 routeTag = 0); + + bool InitFromBuffer(unsigned int pktLength = 0, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + Command GetCommand() const + {return (Command)(GetUINT8(OFFSET_COMMAND));} + UINT8 GetVersion() const + {return GetUINT8(OFFSET_VERSION);} + + unsigned int GetNumEntry() const; + // Note this does _not_ copy data, but wraps "entry" around + // the appropriate internal ProtoPktRIP buffer area + bool AccessRouteEntry(unsigned int index, RouteEntry& entry); + + + private: + enum + { + OFFSET_COMMAND = 0, // UINT8 offset + OFFSET_VERSION = OFFSET_COMMAND+1, // UINT8 offset + OFFSET_RESERVED = (OFFSET_VERSION+1)/2, // UINT16 offset + OFFSET_PAYLOAD = (OFFSET_RESERVED+1)/2 // UINT32 offset + }; + +}; // end class ProtoPktRIP + + +#endif // _PROTO_PKT_RIP diff --git a/include/protoPktRTP.h b/include/protoPktRTP.h index e36ed54..72d7e2d 100644 --- a/include/protoPktRTP.h +++ b/include/protoPktRTP.h @@ -198,7 +198,7 @@ class ProtoPktRTP : public ProtoPkt } void SetSequence(UINT16 sequence) // may want this class to maintain the sequence # rather than get it from outside later on - {reinterpret_cast(buffer_ptr)[OFFSET_SEQUENCE] = htons(sequence);} + {reinterpret_cast(buffer_ptr)[OFFSET_SEQUENCE] = htons(sequence);} void SetTimestamp(UINT32 timestamp) {buffer_ptr[OFFSET_TIMESTAMP] = htonl(timestamp);} diff --git a/include/protoPktTCP.h b/include/protoPktTCP.h new file mode 100644 index 0000000..43d4331 --- /dev/null +++ b/include/protoPktTCP.h @@ -0,0 +1,150 @@ +#ifndef _PROTO_PKT_TCP +#define _PROTO_PKT_TCP + +#include "protoPktIP.h" + +/** + * @class ProtoPktTCP + * + * @brief Parses TCP Packets + */ +class ProtoPktTCP : public ProtoPkt +{ + public: + ProtoPktTCP(UINT32* bufferPtr = 0, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~ProtoPktTCP(); + + enum Flag + { + FLAG_FIN = 0x0001, // finished, no more data + FLAG_SYN = 0x0002, // sync sequence numbers (first packet) + FLAG_RST = 0x0004, // connection reset + FLAG_PSH = 0x0008, // push data to receiving app + FLAG_ACK = 0x0010, // ack field is valid + FLAG_URG = 0x0020, // urgent field is valid + FLAG_ECE = 0x0040, // if SYN, ECN capable, else congestion indication + FLAG_CWR = 0x0080, // congestion window reduced + FLAG_NS = 0x0100 // ECN-nonce concealment protection + }; + + // Use these to parse the datagram + bool InitFromBuffer(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + bool InitFromPacket(ProtoPktIP& pkt); + UINT16 GetSrcPort() const + {return ntohs(((UINT16*)buffer_ptr)[OFFSET_SRC]);} + UINT16 GetDstPort() const + {return ntohs(((UINT16*)buffer_ptr)[OFFSET_DST]);} + UINT32 GetSequence() const + {return ntohl(buffer_ptr[OFFSET_SEQ]);} + UINT32 GetAckNumber() const + {return ntohl(buffer_ptr[OFFSET_ACK]);} + UINT16 GetFlags() const + {return (0x01ff & GetUINT16(2*OFFSET_FLAGS));} + bool FlagIsSet(Flag flag) const + {return (0 != (flag & GetFlags()));} + UINT16 GetWindowSize() const + {return GetUINT16(2*OFFSET_WINDOW);} + UINT16 GetChecksum() const + {return GetUINT16(2*OFFSET_CHECKSUM);} + UINT16 GetUrgentPointer() const + {return GetUINT16(2*OFFSET_URGENT);} + bool HasOptions() const + {return (OffsetPayload() > 5);} + UINT32* GetOptions() const + {return (buffer_ptr + OFFSET_OPTIONS);} + UINT16 GetPayloadLength() const + {return (GetLength() - (OffsetPayload() << 2));} + const UINT32* GetPayload() const + {return (buffer_ptr + OffsetPayload());} + UINT32* AccessPayload() + {return (buffer_ptr + OffsetPayload());} + UINT16 ComputeChecksum(ProtoPktIP& ipPkt) const; + bool ChecksumIsValid(ProtoPktIP& ipPkt) const + {return (GetChecksum() == ComputeChecksum(ipPkt));} + + // Use these to build the datagram + bool InitIntoBuffer(UINT32* bufferPtr = 0, + unsigned int numBytes = 0, + bool freeOnDestruct = false); + void SetSrcPort(UINT16 port) + {((UINT16*)buffer_ptr)[OFFSET_SRC] = htons(port);} + void SetDstPort(UINT16 port) + {((UINT16*)buffer_ptr)[OFFSET_DST] = htons(port);} + + void SetSequence(UINT32 seq) + {buffer_ptr[OFFSET_SEQ] = htonl(seq);} + void SetAckNumber(UINT32 ackNumber) + { + SetFlag(FLAG_ACK); + buffer_ptr[OFFSET_ACK] = htonl(ackNumber); + } + void SetFlags(UINT16 flags) + { + UINT16 field = 0xfe00 & GetUINT16(2*OFFSET_FLAGS); + SetUINT16(OFFSET_FLAGS*2, field | flags); + } + void ClearFlags() + { + UINT16 field = 0xfe00 & GetUINT16(2*OFFSET_FLAGS); + SetUINT16(OFFSET_FLAGS*2, field); + } + void SetFlag(Flag flag) + { + UINT16 field = GetUINT16(2*OFFSET_FLAGS); + SetUINT16(OFFSET_FLAGS*2, field | (UINT16)flag); + } + void ClearFlag(Flag flag) + { + UINT16 field = GetUINT16(2*OFFSET_FLAGS); + SetUINT16(OFFSET_FLAGS*2, field & ~(UINT16)flag); + } + void SetWindowSize(UINT16 windowSize) + {SetUINT16(OFFSET_WINDOW*2, windowSize);} + void SetChecksum(UINT16 checksum) + {SetUINT16(OFFSET_CHECKSUM*2, checksum);} + void SetUrgentPointer(UINT16 value) + { + SetFlag(FLAG_URG); + SetUINT16(OFFSET_URGENT*2, value); + } + void SetPayload(const char* payload, UINT16 numBytes) + { + memcpy((char*)(buffer_ptr+OffsetPayload()), payload, numBytes); + pkt_length = numBytes + (OffsetPayload() << 2); + } + // This must be called after payload is set + void FinalizeChecksum(ProtoPktIP& ipPkt) + {SetChecksum(ComputeChecksum(ipPkt));} + + private: + enum + { + OFFSET_SRC = 0, // source port number (UINT16 offset) + OFFSET_DST = OFFSET_SRC + 1, // destination port number (UINT16 offset) + OFFSET_SEQ = (OFFSET_DST + 1)/2, // sequence number (UINT32 offset) + OFFSET_ACK = OFFSET_SEQ + 1, // acknowledgment number (UINT32 offset) + OFFSET_DATA = (OFFSET_ACK+1)*4, // data offset (UINT8, upper 4 bits) + OFFSET_FLAGS = (OFFSET_ACK+1)*2, // flags (UINT16, lower 9 bits) + OFFSET_WINDOW = OFFSET_FLAGS + 1, // window size (UINT16 offset) + OFFSET_CHECKSUM = OFFSET_WINDOW + 1, // checksum (UINT16 offset) + OFFSET_URGENT = OFFSET_CHECKSUM +1, // urgent pointer (UINT16 offset) + OFFSET_OPTIONS = (OFFSET_URGENT+1)/2 // options (UINT32 offset) + }; + + unsigned int OffsetPayload() const // UINT32 offset value + {return (((( UINT8*)buffer_ptr)[OFFSET_DATA] >> 4) & 0x0f);} + void SetDataOffset(UINT8 offset) // as a UINT32 offset value + { + // Replace upper 4 bits of the data offset/reserved/ns byte + UINT8 field = ((UINT8*)buffer_ptr)[OFFSET_DATA] & 0x0f; + ((UINT8*)buffer_ptr)[OFFSET_DATA] = ((offset & 0x0f) << 4) | field; + } +}; // end class ProtoPktTCP + + +#endif // _PROTO_PKT_TCP diff --git a/include/protoQueue.h b/include/protoQueue.h index 1c5c383..f0381ef 100644 --- a/include/protoQueue.h +++ b/include/protoQueue.h @@ -33,13 +33,19 @@ class ProtoQueue virtual void Remove(Item& item) = 0; + // returns true if item is in other queue besides this one + bool IsInOtherQueue(Item& item) + {return item.IsInOtherQueue(*this);} + + bool Contains(Item& item) const + {return (NULL != item.GetContainer(*this));} + // Derived classes MUST implement the // Empty() method _and_ call it in // their destructor! virtual void Empty() = 0; - - /** + /** * @class Container * * @brief The "ProtoQueue::Container is a base class @@ -50,6 +56,9 @@ class ProtoQueue * needed for the given data structure type. */ + // TBD - can we make Container _privately_ derive from ProtoTree::Item + // to avoid the need for the "Entry" member (i.e. save some space) + class ContainerPool; class Container @@ -129,6 +138,17 @@ class ProtoQueue public: virtual ~Item(); + bool IsInQueue() const + {return (!container_list.IsEmpty());} + + Container* GetContainer(const ProtoQueue& queue) const + { + const ProtoQueue* ptr = &queue; + ProtoQueue::Container::Entry* entry = + static_cast(container_list.Find((const char*)&ptr, sizeof(ProtoQueue*) << 3)); + return ((NULL != entry) ? &entry->GetContainer() : NULL); + } + protected: Item(); @@ -142,13 +162,8 @@ class ProtoQueue void Cleanup(); private: - Container* GetContainer(const ProtoQueue& queue) const - { - const ProtoQueue* ptr = &queue; - ProtoQueue::Container::Entry* entry = - static_cast(container_list.Find((const char*)&ptr, sizeof(ProtoQueue*) << 3)); - return ((NULL != entry) ? &entry->GetContainer() : NULL); - } + bool IsInOtherQueue(const ProtoQueue& queue); + void Reference(ProtoQueue::Container& container) {container_list.Insert(container.AccessEntry());} void Dereference(ProtoQueue::Container& container) @@ -162,7 +177,7 @@ class ProtoQueue ProtoTree container_list; }; // end class ProtoQueue::Item - + /** * @class ProtoQueue::ContainerPool @@ -206,7 +221,6 @@ class ProtoQueue Container* GetContainer(const Item& item) const {return item.GetContainer(*this);} - Container* GetContainerFromPool() {return ((NULL != container_pool) ? container_pool->Get() : NULL);} @@ -264,18 +278,16 @@ class ProtoSimpleQueue : public ProtoQueue } Item* RemoveTail(); - bool IsEmpty() - { - return (GetHead()) ? false : true; - } + bool IsEmpty() const + {return (NULL != GetHead()) ? false : true;} void Empty(); // empties queue, but doesn't delete items void Destroy(); // empties queue, deleting items // TBD - add set operations - - /*const bool Union(ProtoSimpleQueue &unionQueue, const ProtoSimpleQueue &bQueue); + /* + const bool Union(ProtoSimpleQueue &unionQueue, const ProtoSimpleQueue &bQueue); const bool Intersection(ProtoSimpleQueue &intersectionQueue, const ProtoSimpleQueue &bQueue); const bool RelativeComplement(ProtoSimpleQueue &rcQueue, const ProtoSimpleQueue &bQueue);//(a - intersection of ab) const bool SymmetricDifference(ProtoSimpleQueue &rcQueue, const ProtoSimpleQueue &bQueue);//(union of relative complements) @@ -315,24 +327,23 @@ class ProtoSimpleQueue : public ProtoQueue Container* nextContainer = static_cast(ProtoList::Iterator::PeekPrevItem()); return ((NULL != nextContainer) ? nextContainer->GetItem() : NULL); } - }; // end class ProtoSimpleQueueTemplate::Iterator + }; // end class ProtoSimpleQueue::Iterator class Container : public ProtoQueue::Container, public ProtoList::Item { public: Container(); ~Container(); - }; // end class ProtoSimpleQueueTemplate::Container + }; // end class ProtoSimpleQueue::Container class ContainerPool : public ProtoQueue::ContainerPool { public: - ContainerPool(); - virtual ~ContainerPool(); - + void Put(Container& theContainer) + {ProtoQueue::ContainerPool::Put(theContainer);} Container* Get() {return static_cast(ProtoQueue::ContainerPool::Get());} - }; // end class ProtoSimpleQueueTemplate::ContainerPool + }; // end class ProtoSimpleQueue::ContainerPool private: // TBD - do we want to make CreateContainer() virtual so derived classes can do more? @@ -353,7 +364,7 @@ class ProtoSimpleQueueTemplate : public ProtoSimpleQueue ProtoSimpleQueueTemplate(bool usePool = false) : ProtoSimpleQueue(usePool) {} ProtoSimpleQueueTemplate(ContainerPool* containerPool) - : ProtoSimpleQueueTemplate(containerPool) {} + : ProtoSimpleQueue(containerPool) {} virtual ~ProtoSimpleQueueTemplate() {} ITEM_TYPE* GetHead() const @@ -408,7 +419,6 @@ MyItem* nextItem = it.GetNextItem() ... */ - class ProtoIndexedQueue : public ProtoQueue { public: @@ -467,6 +477,15 @@ class ProtoIndexedQueue : public ProtoQueue unsigned int GetKeysize() const; }; // end class ProtoIndexedQueue::Container + + class ContainerPool : public ProtoQueue::ContainerPool + { + public: + void Put(Container& theContainer) + {ProtoQueue::ContainerPool::Put(theContainer);} + Container* Get() + {return static_cast(ProtoQueue::ContainerPool::Get());} + }; // end class ProtoIndexedQueue::ContainerPool class Iterator : public ProtoTree::Iterator { @@ -474,8 +493,10 @@ class ProtoIndexedQueue : public ProtoQueue Iterator(ProtoIndexedQueue& theQueue, bool reverse = false); virtual ~Iterator(); - void Reset(bool reverse = false) - {ProtoTree::Iterator::Reset(reverse);} + void Reset(bool reverse = false, + const char* prefix = NULL, + unsigned int prefixSize = 0) + {ProtoTree::Iterator::Reset(reverse, prefix, prefixSize);} Item* GetNextItem() { @@ -524,10 +545,11 @@ class ProtoIndexedQueueTemplate : public ProtoIndexedQueue virtual unsigned int GetKeysize(const Item& item) const = 0; // Insert the "item" into the tree (will fail if item with equivalent key already in tree) - bool Insert(ITEM_TYPE& item) - {return ProtoIndexedQueue::Insert(item);} + //bool Insert(ITEM_TYPE& item) + // {return ProtoIndexedQueue::Insert(item);} // Remove the "item" from the tree + // LJT THIS HAD BEEN COMMENTED OUT void Remove(ITEM_TYPE& item) {return ProtoIndexedQueue::Remove(item);} @@ -552,8 +574,10 @@ class ProtoIndexedQueueTemplate : public ProtoIndexedQueue : ProtoIndexedQueue::Iterator(theQueue, reverse) {} ~Iterator() {} - void Reset(bool reverse = false) - {ProtoIndexedQueue::Iterator::Reset(reverse);} + void Reset(bool reverse = false, + const char* prefix = NULL, + unsigned int prefixSize = 0) + {ProtoIndexedQueue::Iterator::Reset(reverse, prefix, prefixSize);} ITEM_TYPE* GetNextItem() {return static_cast(ProtoIndexedQueue::Iterator::GetNextItem());} @@ -570,6 +594,9 @@ class ProtoIndexedQueueTemplate : public ProtoIndexedQueue : ProtoIndexedQueue(usePool) {} ProtoIndexedQueueTemplate(ContainerPool* containerPool) : ProtoIndexedQueue(containerPool) {} + + private: + using ProtoIndexedQueue::Remove; // gets rid of hidden overloaded virtual function warning }; // end class ProtoIndexedQueueTemplate @@ -590,10 +617,10 @@ class MyQueue public ProtoIndexedQueueTemplate // 2) MUST implement these required overrides to determine indexing (i.e. sorting) // (Note different ProtoIndexedQueue variants can implement these // differently to have different indexing / sorting behaviors) - const char* GetKey(Item& item) - {return static_cast(item).GetKey();} - unsigned int GetKeysize(Item& item) - {return static_cast(item).GetKeysize();} + const char* GetKey(const Item& item) const + {return static_cast(item).GetKey();} + unsigned int GetKeysize(const Item& item) const + {return static_cast(item).GetKeysize();} }; MyQueue queue; @@ -613,7 +640,7 @@ class ProtoSortedQueue : public ProtoQueue public: virtual ~ProtoSortedQueue(); - // Insert the "item" into the tree (will fail if item with equivalent key already in tree) + // Insert the "item" into the tree (multiple items with same key is OK) bool Insert(Item& item); // Remove the "item" from the tree @@ -622,13 +649,25 @@ class ProtoSortedQueue : public ProtoQueue bool IsEmpty() const {return item_tree.IsEmpty();} - // Find item with exact match to "key" and "keysize" (keysize is in bits) + // Find first item with exact match to "key" and "keysize" (keysize is in bits) Item* Find(const char* key, unsigned int keysize) const { Container* container = item_tree.Find(key, keysize); return ((NULL != container) ? container->GetItem() : NULL); } + Item* GetHead() const + { + Container* head = item_tree.GetHead(); + return ((NULL != head) ? head->GetItem() : NULL); + } + + Item* GetTail() const + { + Container* tail = item_tree.GetTail(); + return ((NULL != tail) ? tail->GetItem() : NULL); + } + void Empty(); // empties queue, but doesn't delete items void Destroy(); // empties queue, deleting items @@ -661,6 +700,15 @@ class ProtoSortedQueue : public ProtoQueue }; // end class ProtoSortedQueue::Container + class ContainerPool : public ProtoQueue::ContainerPool + { + public: + void Put(Container& theContainer) + {ProtoQueue::ContainerPool::Put(theContainer);} + Container* Get() + {return static_cast(ProtoQueue::ContainerPool::Get());} + }; // end class ProtoSortedQueue::ContainerPool + class Iterator : public ProtoSortedTree::Iterator { public: @@ -675,6 +723,12 @@ class ProtoSortedQueue : public ProtoQueue unsigned int keysize = 0) {ProtoSortedTree::Iterator::Reset(reverse, keyMin, keysize);} + void SetCursor(ProtoSortedQueue& theQueue, ProtoQueue::Item& theItem) + { + Container* container = static_cast(theItem.GetContainer(theQueue)); + ProtoSortedTree::Iterator::SetCursor(container); + } + Item* GetNextItem() { Container* nextContainer = static_cast(ProtoSortedTree::Iterator::GetNextItem()); @@ -735,12 +789,17 @@ class ProtoSortedQueueTemplate : public ProtoSortedQueue // Remove the "item" from the tree void Remove(ITEM_TYPE& item) - {ProtoSortedQueue::Remove(item);} + {ProtoSortedQueue::Remove(static_cast(item));} - // Find item with exact match to "key" and "keysize" (keysize is in bits) + // Find firat item with exact match to "key" and "keysize" (keysize is in bits) ITEM_TYPE* Find(const char* key, unsigned int keysize) const {return static_cast(ProtoSortedQueue::Find(key, keysize));} + ITEM_TYPE* GetHead() const + {return static_cast(ProtoSortedQueue::GetHead());} + ITEM_TYPE* GetTail() const + {return static_cast(ProtoSortedQueue::GetTail());} + class Iterator : public ProtoSortedQueue::Iterator { public: @@ -767,6 +826,9 @@ class ProtoSortedQueueTemplate : public ProtoSortedQueue : ProtoSortedQueue(usePool) {} ProtoSortedQueueTemplate(ContainerPool* containerPool) : ProtoSortedQueue(containerPool) {} - + + private: + using ProtoSortedQueue::Remove; // gets rid of hidden overloaded virtual function warning + }; // end class ProtoSortedQueueTemplate #endif // _PROTO_QUEUE diff --git a/include/protoRouteMgr.h b/include/protoRouteMgr.h index d8c9cfa..4dfecd7 100644 --- a/include/protoRouteMgr.h +++ b/include/protoRouteMgr.h @@ -25,8 +25,9 @@ class ProtoRouteMgr ZEBRA }; + static ProtoRouteMgr* Create(Type type = SYSTEM); - void Init(); //sets up initial state for saved table pointers. + virtual ~ProtoRouteMgr(); virtual bool Open(const void* userData = NULL) = 0; @@ -45,9 +46,23 @@ class ProtoRouteMgr * Any routes with differing parameters will be updated. * @param oldRouteTable The older set of routes which are to be diff'ed against. * @param newRouteTable The new/current routes which are to be added. + * @param settedRouteTable If non-null pointer is supplied the list will be populated with routes which were added/updated + * @param deletedRouteTable If non-null pointer is supplied the list will be populated with routes which were deleted * @return true upon success. */ - bool UpdateRoutes(ProtoRouteTable& oldRouteTable, ProtoRouteTable& newRouteTable); + bool UpdateRoutes(ProtoRouteTable& oldRouteTable, ProtoRouteTable& newRouteTable, ProtoRouteTable* settedRouteTable = NULL, ProtoRouteTable* deletedRouteTable = NULL); + /** + * @brief Entries in the route table will be updated to reflect the new route table*. + * Routes which existe in the old route table but not the new one will be removed. + * Routes which exist in the new route table but not the old will be added. + * Any routes with differing parameters will be updated. + * @param oldRouteTable The older set of routes which are to be diff'ed against. + * @param newRouteTable The new/current routes which are to be added. + * @param settedRouteTable The list will be populated with routes which should be added + * @param deletedRouteTable The list will be populated with routes which should be deleted + * @return true upon success. + */ + bool GetDiff(ProtoRouteTable& oldRouteTable, ProtoRouteTable& newRouteTable, ProtoRouteTable& settedRouteTable, ProtoRouteTable& deletedRouteTable); bool DeleteRoutes(ProtoRouteTable& routeTable); /** * @@ -62,7 +77,7 @@ class ProtoRouteMgr */ bool SaveAllRoutes(ProtoAddress::Type addrType); /** - * @brife will attempt to restore both IPv4 and IPv6 route tables + * @brief will attempt to restore both IPv4 and IPv6 route tables * @return true upon success of restoring EITHER IPv4 or IPv6 route tables. */ bool RestoreSavedRoutes(); @@ -73,6 +88,8 @@ class ProtoRouteMgr */ bool RestoreSavedRoutes(ProtoAddress::Type addrType); + void ClearSavedRoutes(); + virtual bool GetRoute(const ProtoAddress& dst, unsigned int prefixLen, ProtoAddress& gw, @@ -151,9 +168,13 @@ class ProtoRouteMgr virtual bool GetInterfaceAddressList(unsigned int ifIndex, ProtoAddress::Type addrType, ProtoAddressList& addrList) = 0; + + protected: + ProtoRouteMgr(); + private: - ProtoRouteTable* savedRoutesIPv6; ProtoRouteTable* savedRoutesIPv4; + ProtoRouteTable* savedRoutesIPv6; }; // end class ProtoRouteMgr diff --git a/include/protoRouteTable.h b/include/protoRouteTable.h index 61f2c29..4385394 100644 --- a/include/protoRouteTable.h +++ b/include/protoRouteTable.h @@ -112,7 +112,7 @@ class ProtoRouteTable void Init(const ProtoAddress& dstAddr, unsigned int prefixSize); ProtoAddress destination; - unsigned prefix_size; // in bits + unsigned int prefix_size; // in bits ProtoAddress gateway; unsigned int iface_index; int metric; diff --git a/include/protoSocket.h b/include/protoSocket.h index 6cc6ba7..7bbe09c 100644 --- a/include/protoSocket.h +++ b/include/protoSocket.h @@ -2,7 +2,6 @@ #define _PROTO_SOCKET #include "protoAddress.h" - #include "protoDebug.h" // temp #ifdef WIN32 @@ -16,9 +15,12 @@ typedef GUID *LPGUID; #endif #include // for SOCKET type, etc +#include // for WSARecvMsg() stuff #else // !WIN32 #include // for errno #include // for struct ifconf +#include // for read() +#include #endif // if/else WIN32/UNIX /** * @class ProtoSocket @@ -58,6 +60,13 @@ class ProtoSocket ECN_CE = 0x03 // ECN "Congestion Experienced" (old CE) }; + enum IPv6SupportStatus + { + IPV6_UNKNOWN, + IPV6_UNSUPPORTED, + IPV6_SUPPORTED + }; + #ifdef SIMULATE class Proxy {}; typedef Proxy* Handle; @@ -86,11 +95,24 @@ class ProtoSocket bool Accept(ProtoSocket* theSocket = NULL); bool Shutdown(); void Close(); - bool JoinGroup(const ProtoAddress& groupAddr, - const char* interfaceName = NULL); - bool LeaveGroup(const ProtoAddress& groupAddr, - const char* interfaceName = NULL); +// First cut at IGMPv3 SSM support (TBD - refine this and expand platform support) +// On Mac OSX, only version 10.7 and later support IGMPv3 +// and the "MCAST_JOIN_GROUP" macro definition is a "tell" for this +// (we _reallly_ need to go to a more sophisticated build system!) +#if (!defined(WIN32) && !defined(ANDROID) && (!defined(MACOSX))) || (defined(MACOSX) && defined(MCAST_JOIN_GROUP)) +#define _PROTOSOCKET_IGMPV3_SSM +#endif // !WIN32 && !ANDROID && (!MACOSX || MCAST_JOIN_GROUP) + + bool JoinGroup(const ProtoAddress& groupAddress, + const char* interfaceName = NULL, + const ProtoAddress* sourceAddress = NULL); + bool LeaveGroup(const ProtoAddress& groupAddress, + const char* interfaceName = NULL, + const ProtoAddress* sourceAddress = NULL); + + bool SetRawProtocol(Protocol theProtocol); + void SetState(State st){state = st;} // JPH 7/14/06 - for tcp development testing #ifdef WIN32 void SetClosing(bool status) {closing = status;} @@ -116,9 +138,9 @@ class ProtoSocket // These are valid for connected sockets only const ProtoAddress& GetSourceAddr() const {return source_addr;} const ProtoAddress& GetDestination() const {return destination;} - // I.T. to set the destination on a socket after an ACCEPT. - void SetDestination(const ProtoAddress& destin) - {destination=destin;} + // I.T. helper method to set the destination on a socket after an ACCEPT. + void SetDestination(const ProtoAddress& theDestination) + {destination = theDestination;} #endif // OPNET #ifdef WIN32 HANDLE GetInputEventHandle() {return input_event_handle;} @@ -145,13 +167,38 @@ class ProtoSocket } // Read/Write methods - bool SendTo(const char* buffer, unsigned int buflen, const ProtoAddress& dstAddr); + bool SendTo(const char* buffer, unsigned int &buflen, const ProtoAddress& dstAddr); bool RecvFrom(char* buffer, unsigned int& numBytes, ProtoAddress& srcAddr); + bool RecvFrom(char* buffer, unsigned int& numBytes, ProtoAddress& srcAddr, ProtoAddress& dstAddr); bool Send(const char* buffer, unsigned int& numBytes); bool Recv(char* buffer, unsigned int& numBytes); - +#if !defined(WIN32) && !defined(SIMULATE) + // This was for debugging?? Remove?? + bool Read(char* buffer, unsigned int &numBytes) + { + int result = read(handle, buffer, numBytes); + if (result < 0) + { + perror("read() error"); + numBytes = 0; + switch (errno) + { + case EAGAIN: + return true; + default: + return false; + } + } + else + { + numBytes = result; + return true; + } + } +#endif // !WIN32 // Attributes bool SetTTL(unsigned char ttl); + bool SetUnicastTTL(unsigned char ttl); bool SetLoopback(bool loopback); bool SetBroadcast(bool broadcast); bool SetFragmentation(bool enable); @@ -164,11 +211,14 @@ class ProtoSocket unsigned int GetTxBufferSize(); bool SetRxBufferSize(unsigned int bufferSize); unsigned int GetRxBufferSize(); + + void EnableRecvDstAddr(); // Helper methods #ifdef HAVE_IPV6 static bool HostIsIPv6Capable(); - static bool SetHostIPv6Capable(); + // Temporarily retained for backward compatability + static bool SetHostIPv6Capable() {return true;} bool SetFlowLabel(UINT32 label); #endif //HAVE_IPV6 @@ -214,53 +264,23 @@ class ProtoSocket public: virtual ~Notifier() {} virtual bool UpdateSocketNotification(ProtoSocket& theSocket, - int notifyFlags) {return true;} + int notifyFlags) {return true;} }; - Notifier* GetNotifier() const {return notifier;} bool SetNotifier(ProtoSocket::Notifier* theNotifier); - bool StartOutputNotification() - { - notify_output = true; - notify_output = UpdateNotification(); -#ifdef WIN32 - output_ready = true; -#endif // WIN32 - return notify_output; - } - void StopOutputNotification() - { - notify_output = false; - UpdateNotification(); - } - bool NotifyOutput() {return notify_output;} - - bool StartInputNotification() - { - notify_input = true; - notify_input = UpdateNotification(); -#ifdef WIN32 - input_ready = true; -#endif // WIN32 - return notify_input; - } - void StopInputNotification() - { - notify_input = false; - UpdateNotification(); - } - bool NotifyInput() {return notify_input;} - - bool StartExceptionNotification() - { - notify_exception = true; - notify_exception = UpdateNotification(); - return notify_exception; - } - void StopExceptionNotification() - { - notify_exception = false; - UpdateNotification(); - } + Notifier* GetNotifier() const {return notifier;} + + bool StartInputNotification(); + void StopInputNotification(); + bool InputNotification() const + {return notify_input;} + bool StartOutputNotification(); + void StopOutputNotification(); + bool OutputNotification() const + {return notify_output;} + bool StartExceptionNotification(); + void StopExceptionNotification(); + bool ExceptionNotification() const + {return notify_exception;} void OnNotify(ProtoSocket::Flag theFlag); @@ -328,7 +348,6 @@ class ProtoSocket private: - const List& list; const class Item* next; }; // end class ProtoSocketList::Iterator @@ -390,7 +409,8 @@ class ProtoSocket LISTENER_TYPE(listenerType* theListener, void(listenerType::*eventHandler)(ProtoSocket&, Event)) : listener(theListener), event_handler(eventHandler) {} - void on_event(ProtoSocket& theSocket, Event theEvent) {(listener->*event_handler)(theSocket, theEvent);} + void on_event(ProtoSocket& theSocket, Event theEvent) + {(listener->*event_handler)(theSocket, theEvent);} Listener* duplicate() {return (static_cast(new LISTENER_TYPE(listener, event_handler)));} private: @@ -402,11 +422,13 @@ class ProtoSocket Domain domain; Protocol protocol; + Protocol raw_protocol; // only applies to raw sockets State state; Handle handle; int port; UINT8 tos; // IPv4 TOS or IPv6 traffic class bool ecn_capable; + bool ip_recvdstaddr; // set "true" if RecvFrom() w/ destAddr is invoked #ifdef HAVE_IPV6 UINT32 flow_label; // IPv6 flow label #endif // HAVE_IPV6 @@ -423,8 +445,8 @@ class ProtoSocket #ifdef WIN32 HANDLE input_event_handle; HANDLE output_event_handle; - bool output_ready; - bool input_ready; + bool output_ready; // used to morph edge triggered Win32 sockets to level-triggered behavior + bool input_ready; // used to morph edge triggered Win32 sockets to level-triggered behavior bool closing; #endif // WIN32 Listener* listener; @@ -434,10 +456,16 @@ class ProtoSocket private: static const int IFBUFSIZ = 256; // for GetHostAddressList static const int IFIDXSIZ = 256; // " -#ifndef WIN32 + +#ifdef WIN32 + static LPFN_WSARECVMSG WSARecvMsg; +#else static int GetInterfaceList(struct ifconf& conf); // helper fn -#endif +#endif // if/else WIN32 + + static IPv6SupportStatus ipv6_support_status; }; // end class ProtoSocket + #endif // _PROTO_SOCKET diff --git a/include/protoSpace.h b/include/protoSpace.h index 12c284b..393c3d2 100644 --- a/include/protoSpace.h +++ b/include/protoSpace.h @@ -89,7 +89,7 @@ class ProtoSpace } #else ProtoTree::Endian GetEndian() const - return ProtoTree::ENDIAN_BIG; + {return ProtoTree::ENDIAN_BIG;} void SetNode(Node* theNode) {memcpy(key+sizeof(double), &theNode, sizeof(Node*));} Node* GetNode() const diff --git a/include/protoString.h b/include/protoString.h new file mode 100644 index 0000000..a960c15 --- /dev/null +++ b/include/protoString.h @@ -0,0 +1,34 @@ +#ifndef _PROTO_STRING + +// This module will include mostly "helper" classes for parsing +// and manipulating "char" strings. At the moment, there are +// _not_ plans to create yet another C++ "Strings" class. + +#include +#include +#include // for isspace() + + +// class ProtoTokenator provides an easy means to +// parse a string, item by item, with each item separated +// by a "delimiter" (white space by default) +// (The "strip" option removes leading/trailing white space) +class ProtoTokenator +{ + public: + ProtoTokenator(const char* text, char delimiter=' ', bool strip=true); + ~ProtoTokenator(); + + const char* const GetNextItem(); + + void Reset(); + + private: + char token; + bool strip; + const char* text_ptr; + const char* next_ptr; + char* prev_item; +}; // end class ProtoTokenator + +#endif // !_PROTO_STRING diff --git a/include/protoTime.h b/include/protoTime.h index 724678d..5416ad8 100644 --- a/include/protoTime.h +++ b/include/protoTime.h @@ -40,6 +40,9 @@ class ProtoTime bool IsZero() const {return ((0 == tval.tv_sec) && (0 == tval.tv_usec));} + void Zeroize() + {tval.tv_sec = tval.tv_usec = 0;} + const struct timeval& GetTimeVal() const {return tval;} @@ -88,7 +91,7 @@ class ProtoTime // Computes (t1 - t2) static double Delta(const ProtoTime& t1, const ProtoTime& t2); double operator-(const ProtoTime& t) - {return Delta(*this, t);} + {return Delta(*this, t);} private: // (TBD) for now we use struct timeval for convenience, but in future diff --git a/include/protoTimer.h b/include/protoTimer.h index 2e2cecc..7809094 100644 --- a/include/protoTimer.h +++ b/include/protoTimer.h @@ -63,26 +63,40 @@ class ProtoTimer * * TODO: Update text with new timer functionality * - * Special notice should be taken of the boolean return value of this function. - * When returning true the number of repeats will be decremented and if below 0 - * will deactivate the timer. If in this function you are setting/changing intervals - * or repetions of the timer which invoked the call, the function should return false - * to avoid standard exit actions. + * IMPORTAN: Special notice should be taken of the boolean return value of this function. + * When returning "true" the number of repeats will be decremented and if below 0 + * will deactivate the timer. If, in this function, you are rescheduling the timer, deleting it, + * or repetions of the timer which invoked the call, the function should return "false" + * to avoid standard exit actions (i.e. the timeout function has overridden usual timer handling). - * NOTE: For VC++ Debug builds, you _cannot_ use pre-compiled + * NOTE: For VC++ 6.0 Debug builds, you _cannot_ use pre-compiled * headers with this template code. Also, "/ZI" or "/Z7" compile options - * must NOT be specified. (or else VC++ experiences an "internal compiler error") + * must NOT be specified. (or else VC++ 6.0 experiences an "internal compiler error") * * @param theListener a pointer to the "listening" object * @param timeoutHandler a pointer to the listener's callback function. * */ - - template - bool SetListener(listenerType* theListener, bool(listenerType::*timeoutHandler)(ProtoTimer&)) + + + // This is our new "ProtoTimer::SetListener()" where the listener timeoutHandler + // has a "void" return type instead of the old "bool" since we don't care about + // the return type anymore. (yay!) + template + bool SetListener(LTYPE* theListener, void(LTYPE::*timeoutHandler)(ProtoTimer&)) + { + if (listener) delete listener; + listener = theListener ? new LISTENER_TYPE(theListener, timeoutHandler) : NULL; + return theListener ? (NULL != theListener) : true; + } + + // This is the old "ProtoTimer::SetListener()" we keep for backwards-compatibility + // WARNING: This _will_ be deprecated in the future + template + bool SetListener(LTYPE* theListener, bool(LTYPE::*timeoutHandler)(ProtoTimer&)) { if (listener) delete listener; - listener = theListener ? new LISTENER_TYPE(theListener, timeoutHandler) : NULL; + listener = theListener ? new OLD_LISTENER_TYPE(theListener, timeoutHandler) : NULL; return theListener ? (NULL != theListener) : true; } @@ -93,8 +107,10 @@ class ProtoTimer * * @param theInterval timer interval in seconds */ + // Our ProtoTime class currently only handles 1 usec granularity (for non-zero timeouts) + // so we enforce that constraint here. void SetInterval(double theInterval) - {interval = (theInterval < 0.0) ? 0.0 : theInterval;} + {interval = (theInterval < 0.0) ? 0.0 : ((theInterval < 1.0e-06) ? 1.0e-06 : theInterval);} double GetInterval() const {return interval;} /** * Timer repetition (0 = one shot, -1 = infinite repetition) @@ -158,7 +174,9 @@ class ProtoTimer * @retval Returns the result of the listener's callback function if * a listener exists for the timer. */ - bool DoTimeout() {return listener ? listener->on_timeout(*this) : true;} + void DoTimeout() + {if (NULL != listener) listener->on_timeout(*this);} + /** * @class Listener * @@ -170,14 +188,14 @@ class ProtoTimer /// virtual destructor virtual ~Listener() {} /// virtual on_timeout function - virtual bool on_timeout(ProtoTimer& theTimer) = 0; + virtual void on_timeout(ProtoTimer& theTimer) = 0; }; - /** + /** * @class LISTENER_TYPE * - * @brief Template for Listener classes. + * @brief Template for Listener classes. (This will be deprecated) */ - template + template class LISTENER_TYPE : public Listener { public: @@ -187,23 +205,74 @@ class ProtoTimer * @param theListener pointer to the "listening" object * @param timeoutHandler *pointer to the Listener's callback function. */ - LISTENER_TYPE(listenerType* theListener, bool(listenerType::*timeoutHandler)(ProtoTimer&)) + LISTENER_TYPE(LTYPE* theListener, void(LTYPE::*timeoutHandler)(ProtoTimer&)) : listener(theListener), timeout_handler(timeoutHandler) {} /** * @retval Returns the result of the Listeners timeout handler. */ - bool on_timeout(ProtoTimer& theTimer) - {return (listener->*timeout_handler)(theTimer);} + void on_timeout(ProtoTimer& theTimer) + {(listener->*timeout_handler)(theTimer);} /** * Duplicates the Listener member and returns a pointer to the new * object. */ Listener* duplicate() - {return (static_cast(new LISTENER_TYPE(listener, timeout_handler)));} + {return (static_cast(new LISTENER_TYPE(listener, timeout_handler)));} + private: + LTYPE* listener; + void (LTYPE::*timeout_handler)(ProtoTimer&); + }; // end class ProtoTimer::OLD_LISTENER_TYPE + + /** + * @class OldListener + * + * @brief OldListener base class (will be deprecated) + */ + class OldListener : public Listener + { + public: + /// virtual destructor + virtual ~OldListener() {} + /// virtual on_timeout function + void on_timeout(ProtoTimer& theTimer) + {old_on_timeout(theTimer);} private: - listenerType* listener; - bool (listenerType::*timeout_handler)(ProtoTimer&); + virtual bool old_on_timeout(ProtoTimer& theTimer) = 0; }; + + /** + * @class OLD_LISTENER_TYPE + * + * @brief Template for Listener classes. (This will be deprecated) + */ + template + class OLD_LISTENER_TYPE : public OldListener + { + public: + /** + * Listener contstructor + * + * @param theListener pointer to the "listening" object + * @param timeoutHandler *pointer to the Listener's callback function. + */ + OLD_LISTENER_TYPE(LTYPE* theListener, bool(LTYPE::*timeoutHandler)(ProtoTimer&)) + : listener(theListener), timeout_handler(timeoutHandler) {} + /** + * @retval Returns the result of the Listeners timeout handler. + */ + bool old_on_timeout(ProtoTimer& theTimer) + {return (listener->*timeout_handler)(theTimer);} + /** + * Duplicates the Listener member and returns a pointer to the new + * object. + */ + OldListener* duplicate() + {return (static_cast(new OLD_LISTENER_TYPE(listener, timeout_handler)));} + private: + LTYPE* listener; + bool (LTYPE::*timeout_handler)(ProtoTimer&); + }; // end class ProtoTimer::OLD_LISTENER_TYPE + Listener* listener; double interval; @@ -240,11 +309,17 @@ class ProtoTimerMgr virtual void ActivateTimer(ProtoTimer& theTimer); virtual void DeactivateTimer(ProtoTimer& theTimer); + /** + * @retval Returns "true" if there are any active timers + */ + bool IsActive() const + {return (NULL != short_head);} + /** * @retval Returns any time remaining for the active short timer or -1 */ double GetTimeRemaining() const - {return (short_head ? short_head->GetTimeRemaining() : -1.0);} + {return ((NULL != short_head) ? short_head->GetTimeRemaining() : -1.0);} /// Call this when the timer mgr's one-shot system timer fires void OnSystemTimeout(); @@ -263,7 +338,7 @@ class ProtoTimerMgr protected: /// System timer association/management definitions and routines virtual bool UpdateSystemTimer(ProtoTimer::Command command, - double delay) {return true;} + double delay) = 0;// {return true;} private: // Methods used internally @@ -328,7 +403,8 @@ class ProtoTimerMgr ProtoTimer* long_head; ProtoTimer* long_tail; ProtoTimer* short_head; - ProtoTimer* short_tail; + ProtoTimer* short_tail; + ProtoTimer* invoked_timer; // timer whose listener is being invoked }; // end class ProtoTimerMgr #endif // _PROTO_TIMER diff --git a/include/protoTree.h b/include/protoTree.h index 676ee27..8762cba 100644 --- a/include/protoTree.h +++ b/include/protoTree.h @@ -100,8 +100,10 @@ class ProtoTree : public ProtoIterable // Remove the "item" from the tree void Remove(Item& item); - bool Contains(const Item& item) const - {return (NULL != Find(item.GetKey(), item.GetKeysize()));} + // This should be implemented as shown here. I commented it out + // to detect if anything was using its old, incorrect implementation + //bool Contains(const Item& item) const + // {return (&item == Find(item.GetKey(), item.GetKeysize()));} // Find item with exact match to "key" and "keysize" (keysize is in bits) ProtoTree::Item* Find(const char* key, unsigned int keysize) const; @@ -109,6 +111,8 @@ class ProtoTree : public ProtoIterable ProtoTree::Item* FindString(const char* keyString) const {return Find(keyString, (unsigned int)(8*strlen(keyString)));} + // Find shortest item to which 'key' is a prefix, or secondly the item that + // is the largest prefix of 'key' (i.e. the closet prefix match) ProtoTree::Item* FindClosestMatch(const char* key, unsigned int keysize) const; // Find item which is largest prefix of the "key" (keysize is in bits) @@ -165,6 +169,22 @@ class ProtoTree : public ProtoIterable // Returns how deep in its tree this Item lies unsigned int GetDepth() const; + + // Debug helper for keys that are strings + const char* GetKeyText() const + { + static char text[256]; + unsigned int tlen = GetKeysize() >> 3; + if (tlen > 255) tlen = 255; +#ifdef WIN32 + strncpy_s(text, 256, GetKey(), tlen); +#else + strncpy(text, GetKey(), tlen); +#endif // if/else WIN32 + text[tlen] = '\0'; + return text; + } + protected: Item* GetParent() const {return parent;} Item* GetLeft() const {return left;} @@ -258,10 +278,10 @@ class ProtoTree : public ProtoIterable /** - * @class Iterator + * @class SimpleIterator * * @brief This can be used to iterate over the entire data set. Note - * it does _not_ iterator in lexical order, but also (beneficially) + * it does _not_ iterate in lexical order, but also (beneficially) * does _not_ make any virtual function calls (e.g. GetKey(), etc) * on the ProtoTree::Item members and is thus safe to call most all * of the time (i.e., such as during destructor calls) @@ -285,7 +305,10 @@ class ProtoTree : public ProtoIterable }; // end class ProtoTree::SimpleIterator - bool Bit(const char* key, unsigned int keysize, unsigned int index, Endian keyEndian) const; + static bool Bit(const char* key, unsigned int keysize, unsigned int index, Endian keyEndian); + + static bool ItemIsEqual(const Item& item, const char* key, unsigned int keysize); + static bool ItemsAreEqual(const Item& item1, const Item& item2); protected: // This finds the closest matching item with backpointer to "item" @@ -295,20 +318,16 @@ class ProtoTree : public ProtoIterable ProtoTree::Item* FindPrefixSubtree(const char* prefix, unsigned int prefixLen) const; - bool KeysAreEqual(const char* key1, - const char* key2, - unsigned int keysize, - Endian keyEndian) const; + static bool KeysAreEqual(const char* key1, + const char* key2, + unsigned int keysize, + Endian keyEndian); - bool ItemsAreEqual(const Item& item1, const Item& item2) const; - - bool ItemIsEqual(const Item& item, const char* key, unsigned int keysize) const; - - bool PrefixIsEqual(const char* key, - unsigned int keysize, - const char* prefix, - unsigned int prefixSize, - Endian keyEndian) const; + static bool PrefixIsEqual(const char* key, + unsigned int keysize, + const char* prefix, + unsigned int prefixSize, + Endian keyEndian); // Member variables Item* root; @@ -323,7 +342,13 @@ class ProtoTreeTemplate : public ProtoTree { public: ProtoTreeTemplate() {} - virtual ~ProtoTreeTemplate() {} + virtual ~ProtoTreeTemplate() {} + + bool Insert(ITEM_TYPE& item) + {return ProtoTree::Insert(item);} + + void Remove(ITEM_TYPE& item) + {ProtoTree::Remove(item);} // Find item with exact match to "key" and "keysize" (keysize is in bits) ITEM_TYPE* Find(const char* key, unsigned int keysize) const @@ -378,8 +403,11 @@ class ProtoTreeTemplate : public ProtoTree public: ItemPool() {} ~ItemPool() {} + + void Put(ITEM_TYPE& item) + {ProtoTree::ItemPool::Put(item);} - ITEM_TYPE Get() + ITEM_TYPE* Get() {return static_cast(ProtoTree::ItemPool::Get());} }; // end class ProtoTreeTemplate::ItemPool @@ -481,13 +509,25 @@ class ProtoSortedTree {return static_cast(item_tree.GetRoot());} // Random access methods (uses ProtoTree) + // Note that since a ProtoSortedTree can have multiple items + // with the same key, you should generally use the + // ProtoSortedTree::Iterator and set the keyMin/keysize + // parameters to find _all_ items for a given "key" + // (i.e., iterate until the next item key doesn't match) Item* Find(const char* key, unsigned int keysize) const {return item_tree.Find(key, keysize);} + + Item* FindString(const char* keyString) const + {return Find(keyString, (unsigned int)(8*strlen(keyString)));} + // Find item which _is_ largest prefix of the "key" (keysize is in bits) + Item* FindPrefix(const char* key, unsigned int keysize) const + {return item_tree.FindPrefix(key, keysize);} + void Remove(Item& item); - bool Contains(const Item& item) const - {return item_tree.Contains(item);} + //bool Contains(const Item& item) const + // {return item_tree.Contains(item);} void Empty(); // empties tree without deleting items contained @@ -529,11 +569,17 @@ class ProtoSortedTree /// Note if "reverse" is "true", then "keyMin" is really "keyMax" void Reset(bool reverse = false, const char* keyMin = NULL, unsigned int keysize = 0); + void SetCursor(Item* item) + {list_iterator.SetCursor(item);} + // This flips the reversal state, moving // cursor forward or backward one item void Reverse() {list_iterator.Reverse();} + bool IsReversed() const + {return list_iterator.IsReversed();} + private: /** * @class TempItem @@ -593,6 +639,15 @@ class ProtoSortedTreeTemplate : public ProtoSortedTree ITEM_TYPE* Find(const char* key, unsigned int keysize) const {return (static_cast(ProtoSortedTree::Find(key, keysize)));} + // Find item which _is_ largest prefix of the "key" (keysize is in bits) + ITEM_TYPE* FindPrefix(const char* key, unsigned int keysize) const + {return (static_cast(ProtoSortedTree::FindPrefix(key, keysize)));} + + ITEM_TYPE* GetHead() const + {return (static_cast(ProtoSortedTree::GetHead()));} + ITEM_TYPE* GetTail() const + {return (static_cast(ProtoSortedTree::GetTail()));} + class Iterator : public ProtoSortedTree::Iterator { public: diff --git a/include/protoVersion.h b/include/protoVersion.h index c9d5f84..8021d9a 100644 --- a/include/protoVersion.h +++ b/include/protoVersion.h @@ -1,4 +1,4 @@ #ifndef _PROTOLIB_VERSION #define _PROTOLIB_VERSION -#define PROTOLIB_VERSION "2.1b1" +#define PROTOLIB_VERSION "3.0b1" #endif // _PROTOLIB_VERSION diff --git a/include/protoVif.h b/include/protoVif.h index e1ca6b4..c4bec49 100644 --- a/include/protoVif.h +++ b/include/protoVif.h @@ -21,13 +21,16 @@ class ProtoVif : public ProtoChannel // Open/Close base class methods last/first, respectively // Note: on some OS's, you may not get your requested "vifName" virtual bool Open(const char* vifName, - const ProtoAddress& vifAddr, - unsigned int maskLen) + const ProtoAddress& ipAddr, // optional IP addr for interface + unsigned int maskLen) // maskLen for optional IP addr {return ProtoChannel::Open();} virtual void Close() {ProtoChannel::Close();} + const ProtoAddress& GetHardwareAddress() const + {return hw_addr;} + // This lets us set a MAC addr. // (vif MUST be open and iface is brought down/up to change) virtual bool SetHardwareAddress(const ProtoAddress& hwAddr) = 0; @@ -37,16 +40,24 @@ class ProtoVif : public ProtoChannel virtual bool Write(const char* buffer, unsigned int buflen) = 0; virtual bool Read(char* buffer, unsigned int& numBytes) = 0; - - const char* GetName() const + const char* GetName() const {return vif_name;} + void SetUserData(const void* userData) + {user_data = userData;} + const void* GetUserData() const + {return user_data;} + protected: ProtoVif(); // Implementations SHOULD fill this in with the whatever name // the virtual interface gets, whether equal to requested name or not - char vif_name[VIF_NAME_MAX+1]; + char vif_name[VIF_NAME_MAX+1]; + ProtoAddress hw_addr; // should be filled in with device hardware addr on Open() + + private: + const void* user_data; }; // end class ProtoVif diff --git a/makefiles/Makefile.common b/makefiles/Makefile.common index b57de65..4094d13 100644 --- a/makefiles/Makefile.common +++ b/makefiles/Makefile.common @@ -15,11 +15,13 @@ JAVA = ../src/java INCLUDES = $(TCL_INCL_PATH) $(SYSTEM_INCLUDES) -I../include -I/usr/include/libxml2 -CFLAGS = -g -DPROTO_DEBUG -DUNIX -D_FILE_OFFSET_BITS=64 -O $(SYSTEM_CFLAGS) -fPIC $(SYSTEM_HAVES) $(INCLUDES) +CFLAGS = -g -DPROTO_DEBUG -DUNIX -D_FILE_OFFSET_BITS=64 -O $(SYSTEM_CFLAGS) $(SYSTEM_HAVES) $(INCLUDES) +#CFLAGS = -g -DPROTO_DEBUG -DUNIX -D_FILE_OFFSET_BITS=64 $(SYSTEM_CFLAGS) -fPIC $(SYSTEM_HAVES) $(INCLUDES) LDFLAGS = $(SYSTEM_LDFLAGS) -LIBS = $(SYSTEM_LIBS) -lm -lxml2 -lpthread +#LIBS = $(SYSTEM_LIBS) -lm -lxml2 -lpthread +LIBS = $(SYSTEM_LIBS) -lm -lxml2 -pthread TARGETS = libprotokit.a libprotokit @@ -27,19 +29,21 @@ TARGETS = libprotokit.a libprotokit .cpp.o: $(CC) -c $(CFLAGS) -o $*.o $*.cpp +allExamples: arposer averageExample base64Example detourExample graphExample graphRider graphXMLExample jsonExample lfsrExample msg2MsgExample msgExample netExample pcmd pipe2SockExample pipeExample protoCapExample protoFileExample queueExample riposer serialExample simpleTcpExample sock2PipeExample threadExample timerTest ting vifExample vifLan protoExample + KIT_SRC = $(COMMON)/protoAddress.cpp $(COMMON)/protoApp.cpp $(COMMON)/protoBase64.cpp \ $(COMMON)/protoBitmask.cpp $(COMMON)/protoCap.cpp \ - $(COMMON)/protoChannel.cpp $(COMMON)/protoDebug.cpp \ + $(COMMON)/protoChannel.cpp $(COMMON)/protoCheck.cpp $(COMMON)/protoDebug.cpp \ $(COMMON)/protoDispatcher.cpp $(COMMON)/protoPipe.cpp \ - $(COMMON)/protoPkt.cpp $(COMMON)/protoPktARP.cpp \ - $(COMMON)/protoPktETH.cpp $(COMMON)/protoPktIP.cpp \ - $(COMMON)/protoPktRTP.cpp $(COMMON)/protoSocket.cpp \ + $(COMMON)/protoPkt.cpp $(COMMON)/protoPktARP.cpp $(COMMON)/protoPktETH.cpp \ + $(COMMON)/protoPktIGMP.cpp $(COMMON)/protoPktIP.cpp $(COMMON)/protoPktTCP.cpp \ + $(COMMON)/protoPktRIP.cpp $(COMMON)/protoPktRTP.cpp $(COMMON)/protoSocket.cpp \ $(COMMON)/protoRouteMgr.cpp $(COMMON)/protoRouteTable.cpp \ $(COMMON)/protoTime.cpp $(COMMON)/protoTimer.cpp \ $(COMMON)/protoTree.cpp $(COMMON)/protoList.cpp $(COMMON)/protoQueue.cpp \ $(COMMON)/protoVif.cpp $(COMMON)/protoCap.cpp \ $(COMMON)/protoSerial.cpp $(COMMON)/protoLFSR.cpp \ - $(COMMON)/protoNet.cpp $(COMMON)/protoFile.cpp \ + $(COMMON)/protoNet.cpp $(COMMON)/protoFile.cpp $(COMMON)/protoString.cpp \ $(SYSTEM_SRC) KIT_OBJ = $(KIT_SRC:.cpp=.o) @@ -80,6 +84,16 @@ protoExample: $(EXAMPLE_OBJ) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ +# Simple example command-line tcp client or server application + +TCP_EXAMPLE_SRC = $(EXAMPLES)/simpleTcpExample.cpp +TCP_EXAMPLE_OBJ = $(TCP_EXAMPLE_SRC:.cpp=.o) + +simpleTcpExample: $(TCP_EXAMPLE_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(TCP_EXAMPLE_OBJ) $(LDFLAGS) $(LIBS) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + # Simple example using our ProtoFile file I/O class to async listen to a file FILE_EXAMPLE_SRC = $(EXAMPLES)/protoFileExample.cpp FILE_EXAMPLE_OBJ = $(FILE_EXAMPLE_SRC:.cpp=.o) @@ -89,9 +103,26 @@ protoFileExample: $(FILE_EXAMPLE_OBJ) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ +# Simple ProtoCap example that promiscuously responds to ARP requests +ARPOSER_SRC = $(EXAMPLES)/arposer.cpp $(SYSTEM_SRC_EX) +ARPOSER_OBJ = $(ARPOSER_SRC:.cpp=.o) -# Simple example using our ProtoVif "virtual interface" class +arposer: $(ARPOSER_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(ARPOSER_OBJ) $(LDFLAGS) $(LIBS) $(SYSTEM_LIB_EX) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + +# Simple example that monitors RIP message and can advertise/inject a RIP route +RIPOSER_SRC = $(EXAMPLES)/riposer.cpp +RIPOSER_OBJ = $(RIPOSER_SRC:.cpp=.o) + +riposer: $(RIPOSER_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(RIPOSER_OBJ) $(LDFLAGS) $(LIBS) $(SYSTEM_LIB_EX) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + +# Simple example using our ProtoVif "virtual interface" class VIF_EXAMPLE_SRC = $(EXAMPLES)/vifExample.cpp $(COMMON)/protoVif.cpp $(SYSTEM_SRC_EX) VIF_EXAMPLE_OBJ = $(VIF_EXAMPLE_SRC:.cpp=.o) @@ -137,7 +168,7 @@ threadExample: $(THREAD_OBJ) libprotokit.a SOCK2PIPE_SRC = $(EXAMPLES)/sock2PipeExample.cpp SOCK2PIPE_OBJ = $(SOCK2PIPE_SRC:.cpp=.o) -sock2Pipe: $(SOCK2PIPE_OBJ) libprotokit.a +sock2PipeExample: $(SOCK2PIPE_OBJ) libprotokit.a $(CC) $(CFLAGS) -o $@ $(SOCK2PIPE_OBJ) $(LDFLAGS) $(LIBS) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ @@ -145,7 +176,7 @@ sock2Pipe: $(SOCK2PIPE_OBJ) libprotokit.a PIPE2SOCK_SRC = $(EXAMPLES)/pipe2SockExample.cpp PIPE2SOCK_OBJ = $(PIPE2SOCK_SRC:.cpp=.o) -pipe2Sock: $(PIPE2SOCK_OBJ) libprotokit.a +pipe2SockExample: $(PIPE2SOCK_OBJ) libprotokit.a $(CC) $(CFLAGS) -o $@ $(PIPE2SOCK_OBJ) $(LDFLAGS) $(LIBS) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ @@ -153,11 +184,19 @@ pipe2Sock: $(PIPE2SOCK_OBJ) libprotokit.a MSG2MSG_SRC = $(EXAMPLES)/msg2MsgExample.cpp MSG2MSG_OBJ = $(MSG2MSG_SRC:.cpp=.o) -msg2Msg: $(MSG2MSG_OBJ) libprotokit.a +msg2MsgExample: $(MSG2MSG_OBJ) libprotokit.a $(CC) $(CFLAGS) -o $@ $(MSG2MSG_OBJ) $(LDFLAGS) $(LIBS) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ +PCMD_SRC = $(EXAMPLES)/pcmd.cpp +PCMD_OBJ = $(PCMD_SRC:.cpp=.o) + +pcmd: $(PCMD_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(PCMD_OBJ) $(LDFLAGS) $(LIBS) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + PIPE_SRC = $(EXAMPLES)/pipeExample.cpp PIPE_OBJ = $(PIPE_SRC:.cpp=.o) @@ -174,7 +213,6 @@ netExample: $(NET_OBJ) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ - CAP_SRC = $(EXAMPLES)/protoCapExample.cpp $(SYSTEM_SRC_EX) CAP_OBJ = $(CAP_SRC:.cpp=.o) protoCapExample: $(CAP_OBJ) libprotokit.a @@ -236,6 +274,14 @@ graphExample: $(GRAPH_OBJ) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ +GRAPH_RIDER_SRC = $(EXAMPLES)/graphRider.cpp $(COMMON)/protoGraph.cpp \ + $(MANET)/manetGraph.cpp +GRAPH_RIDER_OBJ = $(GRAPH_RIDER_SRC:.cpp=.o) +graphRider: $(GRAPH_RIDER_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(GRAPH_RIDER_OBJ) $(LDFLAGS) $(LIBS) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + GRAPHXML_SRC = $(MANET)/manetGraphML.cpp $(EXAMPLES)/graphXMLExample.cpp $(MANET)/manetGraph.cpp \ $(COMMON)/protoGraph.cpp $(COMMON)/protoSpace.cpp @@ -262,12 +308,27 @@ msgExample: $(MSG_OBJ) libprotokit.a $(CC) $(CFLAGS) -o $@ $(MSG_SRC) $(LDFLAGS) $(LIBS) libprotokit.a mkdir -p ../bin cp $@ ../bin/$@ - + +JSON_SRC = $(EXAMPLES)/jsonExample.cpp $(COMMON)/protoJson.cpp +JSON_OBJ = $(JSON_SRC:.cpp=.o) + +jsonExample: $(JSON_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(JSON_SRC) $(LDFLAGS) $(LIBS) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + +STREE_SRC = $(EXAMPLES)/sortedTreeExample.cpp +STREE_OBJ = $(STREE_SRC:.cpp=.o) + +stree: $(STREE_OBJ) libprotokit.a + $(CC) $(CFLAGS) -o $@ $(STREE_SRC) $(LDFLAGS) $(LIBS) libprotokit.a + mkdir -p ../bin + cp $@ ../bin/$@ + clean: rm -f *.o $(COMMON)/*.o $(MANET)/*.o $(NS)/*.o ../src/*/*.o ../examples/*.o \ *.a *.$(SYSTEM_SOEXT) ../lib/*.a ../lib/*.../bin/* $(SYSTEM_SOEXT) \ - protoApp protoExample threadExample msg2Msg pipe2Sock sock2Pipe pipeExample protoCapExample vifExample \ - graphExample graphXMLExample serialExample lfsrExample gr ../bin/* + arposer averageExample base64Example detourExample graphExample graphRider graphXMLExample jsonExample lfsrExample msg2MsgExample msgExample netExample pcmd pipe2SockExample pipeExample protoCapExample protoApp protoExample protoFileExample queueExample riposer serialExample simpleTcpExample sock2PipeExample threadExample timerTest ting vifExample vifLan gr ../bin/* # DO NOT DELETE THIS LINE -- mkdep uses it. diff --git a/makefiles/Makefile.freebsd b/makefiles/Makefile.freebsd index e72e00d..5f86cb9 100644 --- a/makefiles/Makefile.freebsd +++ b/makefiles/Makefile.freebsd @@ -42,14 +42,15 @@ SYSTEM_LIBS = -pthread SYSTEM_HAVES = -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK -DHAVE_DIRFD $(DNETSEC) -SYSTEM_SRC = ../src/bsd/bsdRouteMgr.cpp +SYSTEM_SRC = ../src/bsd/bsdRouteMgr.cpp ../src/bsd/bsdNet.cpp ../src/unix/unixNet.cpp SYSTEM_SRC_EX = ../src/unix/bpfCap.cpp ../src/bsd/bsdDetour.cpp SYSTEM = freebsd -CC = g++ +CC = c++ SYSTEM_CFLAGS = -Wall -Wcast-align -pedantic -fPIC SYSTEM_SOFLAGS = -shared +SYSTEM_SOEXT = so RANLIB = ranlib AR = ar diff --git a/makefiles/Makefile.linux b/makefiles/Makefile.linux index a2b5ab1..eedcd70 100644 --- a/makefiles/Makefile.linux +++ b/makefiles/Makefile.linux @@ -39,8 +39,7 @@ SYSTEM_LIBS = -ldl -lrt # (We export these for other Makefiles as needed) # -SYSTEM_HAVES = -DLINUX -DHAVE_IPV6 -DHAVE_GETLOGIN -D_FILE_OFFSET_BITS=64 -DHAVE_LOCKF \ --DHAVE_OLD_SIGNALHANDLER -DHAVE_DIRFD -DHAVE_ASSERT -DNO_SCM_RIGHTS -DHAVE_SCHED -DUNIX -DUSE_TIMERFD +SYSTEM_HAVES = -DLINUX -DHAVE_GETLOGIN -D_FILE_OFFSET_BITS=64 -DHAVE_LOCKF -DHAVE_OLD_SIGNALHANDLER -DHAVE_DIRFD -DHAVE_ASSERT -DNO_SCM_RIGHTS -DHAVE_SCHED -DUNIX -DUSE_SELECT -DUSE_TIMERFD -DHAVE_PSELECT -DUSE_EVENTFD -DHAVE_IPV6 # (TBD) Move ProtoRouteMgr to ProtokitEx ?? SYSTEM_SRC = ../src/linux/linuxRouteMgr.cpp ../src/linux/linuxNet.cpp \ @@ -54,7 +53,7 @@ SYSTEM_LIB_EX = -lnetfilter_queue SYSTEM = linux CC = g++ -SYSTEM_CFLAGS = -Wall -Wcast-align -pedantic -fPIC +SYSTEM_CFLAGS = -Wall -Wcast-align -pedantic -fPIC SYSTEM_SOFLAGS = -shared SYSTEM_SOEXT = so RANLIB = ranlib diff --git a/makefiles/Makefile.macosx b/makefiles/Makefile.macosx index 3a11554..88bded3 100644 --- a/makefiles/Makefile.macosx +++ b/makefiles/Makefile.macosx @@ -37,7 +37,7 @@ SYSTEM_LIBS = -lresolv # SYSTEM_HAVES = -DMACOSX -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK \ --D_FILE_OFFSET_BITS=64 -DHAVE_DIRFD -DHAVE_PSELECT +-D_FILE_OFFSET_BITS=64 -DHAVE_DIRFD -DHAVE_PSELECT -DUSE_SELECT #Add this for MacOS 10.2 or 10.3 builds: -DSOCKLEN_T=int diff --git a/makefiles/android/AndroidManifest.xml b/makefiles/android/AndroidManifest.xml index f8e6f4b..a550feb 100644 --- a/makefiles/android/AndroidManifest.xml +++ b/makefiles/android/AndroidManifest.xml @@ -4,5 +4,5 @@ android:versionCode="1" android:versionName="1.0"> + android:targetSdkVersion="24"/> diff --git a/makefiles/android/README b/makefiles/android/README index 7027d05..d4c5d7c 100644 --- a/makefiles/android/README +++ b/makefiles/android/README @@ -6,7 +6,7 @@ Using protolib in your pure Java Android Application 1. First, update the library project to point to your Android SDK: - android update lib-project --target --path /path/to/protolib/makefiles/android + android update lib-project -p /path/to/protolib/makefiles/android -t Use "android list targets" to see the ids of the SDK targets you have installed. @@ -46,3 +46,5 @@ Using NORM in your C/C++/Java Android Application This is relative to the "NDK_MODULE_PATH" variable that must be defined in "Application.mk". + + diff --git a/makefiles/android/jni/Android.mk b/makefiles/android/jni/Android.mk index 5175dfc..fc0f0e6 100644 --- a/makefiles/android/jni/Android.mk +++ b/makefiles/android/jni/Android.mk @@ -8,11 +8,14 @@ LOCAL_C_INCLUDES := \ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) LOCAL_CFLAGS := \ - -DUNIX \ + -DUNIX -frtti \ + -DHAVE_IPV6 \ -DHAVE_DIRFD \ + -DPROTO_DEBUG \ -DHAVE_ASSERT \ -DHAVE_GETLOGIN \ - -DLINUX \ + -DUSE_SELECT \ + -DLINUX -DANDROID\ -D_FILE_OFFSET_BITS=64 \ -DHAVE_OLD_SIGNALHANDLER \ -DHAVE_SCHED \ @@ -33,23 +36,28 @@ LOCAL_SRC_FILES := \ ../../../src/common/protoChannel.cpp \ ../../../src/common/protoDebug.cpp \ ../../../src/common/protoDispatcher.cpp \ + ../../../src/common/protoGraph.cpp \ ../../../src/common/protoList.cpp \ ../../../src/common/protoNet.cpp \ ../../../src/common/protoPipe.cpp \ ../../../src/common/protoPkt.cpp \ ../../../src/common/protoPktETH.cpp \ ../../../src/common/protoPktIP.cpp \ + ../../../src/common/protoPktRIP.cpp \ + ../../../src/common/protoQueue.cpp \ ../../../src/common/protoRouteMgr.cpp \ ../../../src/common/protoRouteTable.cpp \ ../../../src/common/protoSocket.cpp \ + ../../../src/common/protoString.cpp \ ../../../src/common/protoTime.cpp \ ../../../src/common/protoTimer.cpp \ ../../../src/common/protoTree.cpp \ - ../../../src/linux/linuxCap.cpp \ + ../../../src/unix/unixNet.cpp \ ../../../src/linux/linuxNet.cpp \ + ../../../src/linux/linuxCap.cpp \ ../../../src/linux/linuxRouteMgr.cpp \ - ../../../src/unix/unixNet.cpp \ - ../../../src/unix/zebraRouteMgr.cpp + ../../../src/unix/zebraRouteMgr.cpp \ +# ../../../src/linux/androidDetour.cpp include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) @@ -57,3 +65,10 @@ LOCAL_MODULE := ProtolibJni LOCAL_STATIC_LIBRARIES := protolib LOCAL_SRC_FILES := ../../../src/java/protoPipeJni.cpp include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := riposer +LOCAL_STATIC_LIBRARIES := protolib +LOCAL_SRC_FILES := \ + ../../../examples/riposer.cpp +include $(BUILD_EXECUTABLE) diff --git a/makefiles/android/jni/Application.mk b/makefiles/android/jni/Application.mk index b448d58..4554c7e 100644 --- a/makefiles/android/jni/Application.mk +++ b/makefiles/android/jni/Application.mk @@ -1,2 +1,2 @@ APP_ABI := all -APP_PLATFORM := android-9 +APP_PLATFORM := android-14 diff --git a/makefiles/android/project.properties b/makefiles/android/project.properties index b2ed0b0..7d7d244 100644 --- a/makefiles/android/project.properties +++ b/makefiles/android/project.properties @@ -12,4 +12,4 @@ android.library=true # Project target. -target=android-16 +target=android-18 diff --git a/makefiles/win32/ProtoLib-2008.sln b/makefiles/win32/ProtoLib-2008.sln new file mode 100644 index 0000000..33daa28 --- /dev/null +++ b/makefiles/win32/ProtoLib-2008.sln @@ -0,0 +1,50 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "protoExample", "protoExample.vcproj", "{FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}" + ProjectSection(ProjectDependencies) = postProject + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pipeExample", "pipeExample\pipeExample.vcproj", "{E03C84C7-19D6-4A2E-94AA-8D303E081475}" + ProjectSection(ProjectDependencies) = postProject + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Protokit", "Protokit.vcproj", "{DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "threadExample", "threadExample\threadExample.vcproj", "{0894EFEA-B785-48A3-AA9F-EB3082E08E07}" + ProjectSection(ProjectDependencies) = postProject + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netExample", "netExample\netExample.vcproj", "{0C07D970-A202-4726-802C-DAB6A47D2C70}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|Win32.ActiveCfg = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|Win32.Build.0 = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|Win32.ActiveCfg = Release|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|Win32.Build.0 = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug|Win32.ActiveCfg = Debug|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.ActiveCfg = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.Build.0 = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.Build.0 = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|Win32.ActiveCfg = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|Win32.Build.0 = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|Win32.ActiveCfg = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|Win32.Build.0 = Release|Win32 + {0C07D970-A202-4726-802C-DAB6A47D2C70}.Debug|Win32.ActiveCfg = Debug|Win32 + {0C07D970-A202-4726-802C-DAB6A47D2C70}.Debug|Win32.Build.0 = Debug|Win32 + {0C07D970-A202-4726-802C-DAB6A47D2C70}.Release|Win32.ActiveCfg = Release|Win32 + {0C07D970-A202-4726-802C-DAB6A47D2C70}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/makefiles/win32/ProtoLib.sln b/makefiles/win32/ProtoLib.sln index 33daa28..3b6b2df 100644 --- a/makefiles/win32/ProtoLib.sln +++ b/makefiles/win32/ProtoLib.sln @@ -1,48 +1,72 @@ -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "protoExample", "protoExample.vcproj", "{FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}" - ProjectSection(ProjectDependencies) = postProject - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} - EndProjectSection +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "protoExample", "protoExample.vcxproj", "{FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pipeExample", "pipeExample\pipeExample.vcproj", "{E03C84C7-19D6-4A2E-94AA-8D303E081475}" - ProjectSection(ProjectDependencies) = postProject - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pipeExample", "pipeExample\pipeExample.vcxproj", "{E03C84C7-19D6-4A2E-94AA-8D303E081475}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Protokit", "Protokit.vcproj", "{DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "threadExample", "threadExample\threadExample.vcxproj", "{0894EFEA-B785-48A3-AA9F-EB3082E08E07}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "threadExample", "threadExample\threadExample.vcproj", "{0894EFEA-B785-48A3-AA9F-EB3082E08E07}" - ProjectSection(ProjectDependencies) = postProject - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netExample", "netExample\netExample.vcxproj", "{A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netExample", "netExample\netExample.vcproj", "{0C07D970-A202-4726-802C-DAB6A47D2C70}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Protokit", "Protokit.vcxproj", "{DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "timerTest", "timerTest\timerTest.vcxproj", "{A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vifExample", "vifExample\vifExample.vcxproj", "{82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|Win32.ActiveCfg = Debug|Win32 {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|Win32.Build.0 = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|x64.ActiveCfg = Debug|Win32 {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|Win32.ActiveCfg = Release|Win32 {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|Win32.Build.0 = Release|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|x64.ActiveCfg = Release|Win32 {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug|Win32.ActiveCfg = Debug|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug|x64.ActiveCfg = Debug|Win32 {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|Win32.ActiveCfg = Release|Win32 - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.ActiveCfg = Debug|Win32 - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.Build.0 = Debug|Win32 - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.ActiveCfg = Release|Win32 - {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.Build.0 = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|Win32.Build.0 = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|x64.ActiveCfg = Release|Win32 {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|Win32.ActiveCfg = Debug|Win32 {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|Win32.Build.0 = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|x64.ActiveCfg = Debug|Win32 {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|Win32.ActiveCfg = Release|Win32 {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|Win32.Build.0 = Release|Win32 - {0C07D970-A202-4726-802C-DAB6A47D2C70}.Debug|Win32.ActiveCfg = Debug|Win32 - {0C07D970-A202-4726-802C-DAB6A47D2C70}.Debug|Win32.Build.0 = Debug|Win32 - {0C07D970-A202-4726-802C-DAB6A47D2C70}.Release|Win32.ActiveCfg = Release|Win32 - {0C07D970-A202-4726-802C-DAB6A47D2C70}.Release|Win32.Build.0 = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|x64.ActiveCfg = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|Win32.ActiveCfg = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|Win32.Build.0 = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|x64.ActiveCfg = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|Win32.ActiveCfg = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|Win32.Build.0 = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|x64.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.ActiveCfg = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.Build.0 = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|x64.ActiveCfg = Debug|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|x64.Build.0 = Debug|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.Build.0 = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|x64.ActiveCfg = Release|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|x64.Build.0 = Release|x64 + {A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}.Debug|Win32.ActiveCfg = Debug|Win32 + {A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}.Debug|Win32.Build.0 = Debug|Win32 + {A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}.Debug|x64.ActiveCfg = Debug|Win32 + {A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}.Release|Win32.ActiveCfg = Release|Win32 + {A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}.Release|Win32.Build.0 = Release|Win32 + {A3F219C3-2F7A-44CB-A2AE-FF3BE04A8F86}.Release|x64.ActiveCfg = Release|Win32 + {82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}.Debug|Win32.ActiveCfg = Debug|Win32 + {82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}.Debug|Win32.Build.0 = Debug|Win32 + {82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}.Debug|x64.ActiveCfg = Debug|Win32 + {82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}.Release|Win32.ActiveCfg = Release|Win32 + {82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}.Release|Win32.Build.0 = Release|Win32 + {82A4737A-1986-44E6-8AE9-3BC41DBBB8D7}.Release|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/makefiles/win32/Protokit.vcproj b/makefiles/win32/Protokit.vcproj index acc404f..43b0c0b 100644 --- a/makefiles/win32/Protokit.vcproj +++ b/makefiles/win32/Protokit.vcproj @@ -391,6 +391,10 @@ RelativePath="..\..\src\common\protoPktRTP.cpp" > + + @@ -585,6 +589,10 @@ > + + diff --git a/makefiles/win32/Protokit.vcxproj b/makefiles/win32/Protokit.vcxproj new file mode 100755 index 0000000..9cf53f5 --- /dev/null +++ b/makefiles/win32/Protokit.vcxproj @@ -0,0 +1,273 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + + + + StaticLibrary + v120 + false + MultiByte + + + StaticLibrary + v120 + false + MultiByte + + + StaticLibrary + v120 + false + MultiByte + + + StaticLibrary + v120 + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>12.0.30501.0 + + + .\Debug\ + .\Debug\ + + + .\Release\ + .\Release\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + Disabled + ..\..\include;%(AdditionalIncludeDirectories) + _DEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CRT_SECURE_NO_WARNINGS;_LIB;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + .\Debug/Protokit.pch + .\Debug/ + .\Debug/ + .\Debug/ + Level3 + true + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + .\Debug\Protokit.lib + true + + + + + MaxSpeed + OnlyExplicitInline + ..\..\include;%(AdditionalIncludeDirectories) + NDEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CRT_SECURE_NO_WARNINGS;_LIB;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + .\Release/Protokit.pch + .\Release/ + .\Release/ + .\Release/ + Level3 + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + .\Release\Protokit.lib + true + + + + + X64 + + + Disabled + ..\..\include;%(AdditionalIncludeDirectories) + _DEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CRT_SECURE_NO_WARNINGS;_LIB;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + .\Debug/Protokit.pch + .\Debug/ + .\Debug/ + .\Debug/ + Level3 + true + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + .\Debug\Protokit.lib + true + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + ..\..\include;%(AdditionalIncludeDirectories) + NDEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CRT_SECURE_NO_WARNINGS;_LIB;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + .\Release/Protokit.pch + .\Release/ + .\Release/ + .\Release/ + Level3 + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + .\Release\Protokit.lib + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/makefiles/win32/netExample/netExample.vcxproj b/makefiles/win32/netExample/netExample.vcxproj index 72d6e8c..144fc81 100755 --- a/makefiles/win32/netExample/netExample.vcxproj +++ b/makefiles/win32/netExample/netExample.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -12,7 +12,7 @@ {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137} - v4.0 + v4.5 ManagedCProj netExample @@ -22,12 +22,14 @@ true false MultiByte + v120 Application false true Unicode + v120 @@ -51,8 +53,8 @@ Level3 Disabled - WIN32;HAVE_IPV6;HAVE_ASSERT;_WINDOWS;_DEBUG;PROTO_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\..\..\include;..\..\src\common;..\..\..\src\win32 + _DEBUG;PROTO_DEBUG;WIN32;HAVE_IPV6;HAVE_ASSERT;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions) + ..\..\..\include;..\..\src\common;..\..\..\src\win32;..\..\..\protolib\include;..\..\..\protolib\src\common EditAndContinue false false @@ -68,7 +70,7 @@ true - Iphlpapi.lib;ws2_32.lib;odbc32.lib;odbccp32.lib + Iphlpapi.lib;ws2_32.lib;%(AdditionalDependencies) .\Debug/netExample.exe Console .\Debug/netExample.pdb @@ -77,16 +79,17 @@ Level3 - WIN32;NDEBUG;%(PreprocessorDefinitions) + NDEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CONSOLE;%(PreprocessorDefinitions) + ..\..\..\include;%(AdditionalIncludeDirectories) true - - + iphlpapi.lib;ws2_32.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + diff --git a/makefiles/win32/pipeExample/pipeExample.vcxproj b/makefiles/win32/pipeExample/pipeExample.vcxproj new file mode 100755 index 0000000..08ec75a --- /dev/null +++ b/makefiles/win32/pipeExample/pipeExample.vcxproj @@ -0,0 +1,141 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {E03C84C7-19D6-4A2E-94AA-8D303E081475} + pipeExample + + + + Application + v120 + false + MultiByte + + + Application + v120 + false + MultiByte + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.61030.0 + + + .\Debug\ + .\Debug\ + false + + + .\Release\ + .\Release\ + false + + + + .\Debug/pipeExample.tlb + + + + Disabled + ..\..\..\include;%(AdditionalIncludeDirectories) + _DEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + .\Debug/pipeExample.pch + .\Debug/ + .\Debug/ + .\Debug/ + Level3 + true + EditAndContinue + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + ws2_32.lib;iphlpapi.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\Debug/pipeExample.exe + true + true + .\Debug/pipeExample.pdb + Console + false + + MachineX86 + + + + + .\Release/pipeExample.tlb + + + + MaxSpeed + OnlyExplicitInline + ..\..\..\include;%(AdditionalIncludeDirectories) + NDEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + .\Release/pipeExample.pch + .\Release/ + .\Release/ + .\Release/ + true + Level3 + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + ws2_32.lib;iphlpapi.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\Release/pipeExample.exe + true + .\Release/pipeExample.pdb + Console + false + + MachineX86 + + + + + + + + {de94f096-a09b-44b6-8efe-c7bf1f65c4c9} + + + + + + \ No newline at end of file diff --git a/makefiles/win32/protoExample.vcxproj b/makefiles/win32/protoExample.vcxproj new file mode 100755 index 0000000..bc37ba4 --- /dev/null +++ b/makefiles/win32/protoExample.vcxproj @@ -0,0 +1,150 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6} + protoExample + + + + Application + v120 + false + MultiByte + + + Application + v120 + false + MultiByte + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.61030.0 + + + .\Release\ + .\Release\ + false + + + .\Debug\ + .\Debug\ + false + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/protoExample.tlb + + + + MaxSpeed + OnlyExplicitInline + ..\..\include;%(AdditionalIncludeDirectories) + NDEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + .\Release/protoExample.pch + .\Release/ + .\Release/ + .\Release/ + false + Level3 + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Iphlpapi.lib;ws2_32.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\Release/protoExample.exe + true + .\Release/protoExample.pdb + Console + false + + MachineX86 + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/protoExample.tlb + + + + Disabled + ..\..\src\common;..\..\include;..\..\src\win32;%(AdditionalIncludeDirectories) + _DEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_WINDOWS;_CONSOLE;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + true + + .\Debug/protoExample.pch + .\Debug/ + .\Debug/ + .\Debug/ + true + Level3 + true + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + Iphlpapi.lib;ws2_32.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\Debug/protoExample.exe + true + true + .\Debug/protoExample.pdb + Console + false + + MachineX86 + + + + + + + + {de94f096-a09b-44b6-8efe-c7bf1f65c4c9} + + + + + + \ No newline at end of file diff --git a/makefiles/win32/threadExample/threadExample.vcxproj b/makefiles/win32/threadExample/threadExample.vcxproj new file mode 100755 index 0000000..6160b8c --- /dev/null +++ b/makefiles/win32/threadExample/threadExample.vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {0894EFEA-B785-48A3-AA9F-EB3082E08E07} + + + + Application + v120 + false + MultiByte + + + Application + v120 + false + MultiByte + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.61030.0 + + + .\Debug\ + .\Debug\ + false + + + .\Release\ + .\Release\ + false + + + + .\Debug/threadExample.tlb + + + + Disabled + ..\..\..\include;%(AdditionalIncludeDirectories) + _DEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + .\Debug/threadExample.pch + .\Debug/ + .\Debug/ + .\Debug/ + Level3 + true + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + iphlpapi.lib;ws2_32.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\Debug/threadExample.exe + true + true + .\Debug/threadExample.pdb + Console + false + + MachineX86 + + + + + .\Release/threadExample.tlb + + + + MaxSpeed + OnlyExplicitInline + ..\..\..\include;%(AdditionalIncludeDirectories) + NDEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;WIN32;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + .\Release/threadExample.pch + .\Release/ + .\Release/ + .\Release/ + Level3 + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + iphlpapi.lib;ws2_32.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\Release/threadExample.exe + true + .\Release/threadExample.pdb + Console + false + + MachineX86 + + + + + + + + {de94f096-a09b-44b6-8efe-c7bf1f65c4c9} + + + + + + \ No newline at end of file diff --git a/makefiles/win32/timerTest/timerTest.vcproj b/makefiles/win32/timerTest/timerTest.vcproj new file mode 100644 index 0000000..4f7b9f7 --- /dev/null +++ b/makefiles/win32/timerTest/timerTest.vcproj @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/vifExample/vifExample.vcproj b/makefiles/win32/vifExample/vifExample.vcproj index 19f6f34..ffb3098 100755 --- a/makefiles/win32/vifExample/vifExample.vcproj +++ b/makefiles/win32/vifExample/vifExample.vcproj @@ -41,7 +41,7 @@ + + + + Debug + Win32 + + + Release + Win32 + + + + {8F26E30C-25CF-4846-9BC0-CDF8C33E8DCE} + + + + + + Application + v120 + false + MultiByte + + + Application + v120 + false + MultiByte + + + + + + + + + + + + + <_ProjectFileVersion>12.0.30501.0 + + + .\Debug\ + .\Debug\ + false + + + .\Release\ + .\Release\ + false + + + + Disabled + ..\win32;..\common;%(AdditionalIncludeDirectories) + _DEBUG;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;__WINDOWS__;__WXMSW__;__WIN95__;__WIN32__;WINVER=0x0400;STRICT;WIN32;_WINDOWS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + .\Debug/wxProtoExample.pch + .\Debug/ + .\Debug/ + .\Debug/ + Level3 + true + EditAndContinue + Default + + + ws2_32.lib;iphlpapi.lib;odbc32.lib;odbccp32.lib;wxmsw26d_core.lib;wxbase26d.lib;wxtiffd.lib;wxjpegd.lib;wxpngd.lib;wxzlibd.lib;protokit.lib;%(AdditionalDependencies) + .\Debug/wxProtoExample.exe + true + ..\win32\Debug;..\wx\Debug;%(AdditionalLibraryDirectories) + true + .\Debug/wxProtoExample.pdb + Windows + MachineX86 + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Debug/wxProtoExample.tlb + + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + MaxSpeed + OnlyExplicitInline + ..\win32;..\common;..\wx;%(AdditionalIncludeDirectories) + NDEBUG;__WINDOWS;PROTO_DEBUG;HAVE_IPV6;HAVE_ASSERT;__WINDOWS__;__WXMSW__;__WIN95__;__WIN32__;WINVER=0x0400;STRICT;WIN32;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + + .\Release/wxProtoExample.pch + .\Release/ + .\Release/ + .\Release/ + true + Level3 + true + Default + + + ws2_32.lib;iphlpapi.lib;odbc32.lib;odbccp32.lib;comctl32.lib;rpcrt4.lib;wsock32.lib;winmm.lib;wxbase26.lib;wxmsw26_core.lib;wxmsw26_adv.lib;wxpng.lib;wxzlib.lib;wxjpeg.lib;wxtiff.lib;%(AdditionalDependencies) + .\Release/wxProtoExample.exe + true + libc.lib;libci.lib;msvcrtd.lib;%(IgnoreSpecificDefaultLibraries) + .\Release/wxProtoExample.pdb + Windows + MachineX86 + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/wxProtoExample.tlb + + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + Disabled + EnableFastChecks + MaxSpeed + true + + + Disabled + EnableFastChecks + MaxSpeed + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {de94f096-a09b-44b6-8efe-c7bf1f65c4c9} + false + + + + + + \ No newline at end of file diff --git a/makefiles/win64/ProtoLib.sln b/makefiles/win64/ProtoLib.sln new file mode 100644 index 0000000..e3ec5af --- /dev/null +++ b/makefiles/win64/ProtoLib.sln @@ -0,0 +1,90 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "protoExample", "protoExample.vcxproj", "{FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pipeExample", "pipeExample\pipeExample.vcxproj", "{E03C84C7-19D6-4A2E-94AA-8D303E081475}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "threadExample", "threadExample\threadExample.vcxproj", "{0894EFEA-B785-48A3-AA9F-EB3082E08E07}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netExample", "netExample\netExample.vcxproj", "{A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Protokit", "Protokit.vcxproj", "{DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug DLL|Win32 = Debug DLL|Win32 + Debug DLL|x64 = Debug DLL|x64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release DLL|Win32 = Release DLL|Win32 + Release DLL|x64 = Release DLL|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug DLL|Win32.ActiveCfg = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug DLL|x64.ActiveCfg = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|Win32.ActiveCfg = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|Win32.Build.0 = Debug|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|x64.ActiveCfg = Debug|x64 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Debug|x64.Build.0 = Debug|x64 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release DLL|Win32.ActiveCfg = Release|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release DLL|x64.ActiveCfg = Release|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|Win32.ActiveCfg = Release|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|Win32.Build.0 = Release|Win32 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|x64.ActiveCfg = Release|x64 + {FABA41A7-48C6-4D20-8DD5-5C7BACF26BC6}.Release|x64.Build.0 = Release|x64 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug DLL|Win32.ActiveCfg = Debug|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug DLL|x64.ActiveCfg = Debug|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug|Win32.ActiveCfg = Debug|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug|x64.ActiveCfg = Debug|x64 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Debug|x64.Build.0 = Debug|x64 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release DLL|Win32.ActiveCfg = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release DLL|x64.ActiveCfg = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|Win32.ActiveCfg = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|Win32.Build.0 = Release|Win32 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|x64.ActiveCfg = Release|x64 + {E03C84C7-19D6-4A2E-94AA-8D303E081475}.Release|x64.Build.0 = Release|x64 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug DLL|Win32.ActiveCfg = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug DLL|x64.ActiveCfg = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|Win32.ActiveCfg = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|Win32.Build.0 = Debug|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|x64.ActiveCfg = Debug|x64 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Debug|x64.Build.0 = Debug|x64 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release DLL|Win32.ActiveCfg = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release DLL|x64.ActiveCfg = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|Win32.ActiveCfg = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|Win32.Build.0 = Release|Win32 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|x64.ActiveCfg = Release|x64 + {0894EFEA-B785-48A3-AA9F-EB3082E08E07}.Release|x64.Build.0 = Release|x64 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug DLL|Win32.ActiveCfg = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug DLL|x64.ActiveCfg = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|Win32.ActiveCfg = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|Win32.Build.0 = Debug|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|x64.ActiveCfg = Debug|x64 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Debug|x64.Build.0 = Debug|x64 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release DLL|Win32.ActiveCfg = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release DLL|x64.ActiveCfg = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|Win32.ActiveCfg = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|Win32.Build.0 = Release|Win32 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|x64.ActiveCfg = Release|x64 + {A3ABFCBE-2EB2-49DB-9811-1A67BB7A1137}.Release|x64.Build.0 = Release|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug DLL|Win32.ActiveCfg = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug DLL|x64.ActiveCfg = Debug|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.ActiveCfg = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.Build.0 = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|x64.ActiveCfg = Debug|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|x64.Build.0 = Debug|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release DLL|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release DLL|x64.ActiveCfg = Release|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.Build.0 = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|x64.ActiveCfg = Release|x64 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/makefiles/win64/Protokit.vcproj b/makefiles/win64/Protokit.vcproj new file mode 100755 index 0000000..acc404f --- /dev/null +++ b/makefiles/win64/Protokit.vcproj @@ -0,0 +1,591 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win64/netExample/netExample.vcproj b/makefiles/win64/netExample/netExample.vcproj new file mode 100755 index 0000000..1fa161f --- /dev/null +++ b/makefiles/win64/netExample/netExample.vcproj @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win64/pipeExample/pipeExample.vcproj b/makefiles/win64/pipeExample/pipeExample.vcproj new file mode 100644 index 0000000..1b80a63 --- /dev/null +++ b/makefiles/win64/pipeExample/pipeExample.vcproj @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win64/protoExample.vcproj b/makefiles/win64/protoExample.vcproj new file mode 100644 index 0000000..9e2e97a --- /dev/null +++ b/makefiles/win64/protoExample.vcproj @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win64/threadExample/threadExample.vcproj b/makefiles/win64/threadExample/threadExample.vcproj new file mode 100644 index 0000000..4297a41 --- /dev/null +++ b/makefiles/win64/threadExample/threadExample.vcproj @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win64/vifExample/vifExample.vcproj b/makefiles/win64/vifExample/vifExample.vcproj new file mode 100755 index 0000000..19f6f34 --- /dev/null +++ b/makefiles/win64/vifExample/vifExample.vcproj @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/wx/Makefile.common b/makefiles/wx/Makefile.common index 3600ecb..612017f 100644 --- a/makefiles/wx/Makefile.common +++ b/makefiles/wx/Makefile.common @@ -23,9 +23,9 @@ TARGETS = wxProtoExample $(CC) -c $(CFLAGS) -o $*.o $*.cpp # wxProtoExample depends upon the NRL Protean Group's "Protokit" development library -LIBPROTO = $(PROTOLIB)/unix/libProtokit.a -$(PROTOLIB)/unix/libProtokit.a: - cd $(PROTOLIB)/unix; $(MAKE) -f Makefile.$(SYSTEM) libProtokit.a +LIBPROTO = $(PROTOLIB)/lib/libprotokit.a +$(PROTOLIB)/lib/libprotokit.a: + cd $(PROTOLIB)/makefiles; $(MAKE) -f Makefile.$(SYSTEM) libprotokit.a EXAMPLE_SRC = $(PROTOLIB)/examples/wxProtoExample.cpp $(PROTOLIB)/src/wx/wxProtoApp.cpp @@ -36,7 +36,7 @@ wxProtoExample: $(EXAMPLE_OBJ) $(LIBPROTO) $(SYSTEM_REZ) $@ clean: - rm -f *.o ../src//common/*.o ../unix/*.o ../unix/*.a wxProtoExample + rm -f *.o ../src/common/*.o ../makefiles/*.o ../makefiles/*.a wxProtoExample # DO NOT DELETE THIS LINE -- mkdep uses it. # DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. diff --git a/makefiles/wx/Makefile.macosx b/makefiles/wx/Makefile.macosx index 39590b0..cce3567 100644 --- a/makefiles/wx/Makefile.macosx +++ b/makefiles/wx/Makefile.macosx @@ -3,8 +3,8 @@ # # 1) Where to find the wxWidgets files -WX_FLAGS = `/usr/local/bin/wx-config --cxxflags` -WX_LIBS = `/usr/local/bin/wx-config --libs` +WX_FLAGS = `wx-config --cxxflags` +WX_LIBS = `wx-config --libs` # 2) System specific additional libraries, include paths, etc @@ -13,7 +13,7 @@ WX_LIBS = `/usr/local/bin/wx-config --libs` SYSTEM_FLAGS = SYSTEM_INCLUDES = -I/Developer/Headers/FlatCarbon SYSTEM_LIBS = -framework Carbon -framework System -lz -lresolv -SYSTEM_REZ = `/usr/local/bin/wx-config --rezflags` +SYSTEM_REZ = `/usr/local/bin/wx-config` SYSTEM_HAVES = -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK -DHAVE_DIRFD diff --git a/setup.py b/setup.py index fee95a0..1694740 100644 --- a/setup.py +++ b/setup.py @@ -15,13 +15,11 @@ srcFiles = [PYTHON + 'protokit.cpp'] - # Determine system-specific macro definitions, etc # (For now we support only "linux", "darwin" (MacOS), "freebsd", and "win32") system = platform.system().lower() - sys_macros = [('HAVE_ASSERT',None), ('HAVE_IPV6',None), ('PROTO_DEBUG', None)] sys_libs = ['protokit'] @@ -34,7 +32,6 @@ if system == 'darwin': sys_libs.append('resolv') - setup(name='protokit', version = '1.0', diff --git a/src/bsd/bsdCap.cpp b/src/bsd/bsdCap.cpp index e8480eb..b72ad73 100644 --- a/src/bsd/bsdCap.cpp +++ b/src/bsd/bsdCap.cpp @@ -33,7 +33,7 @@ #include // for open() #include // for close() -class BsdCap : public ProtoCap xxx +class BsdCap : public ProtoCap { public: BsdCap(); @@ -41,12 +41,8 @@ class BsdCap : public ProtoCap xxx bool Open(const char* interfaceName = NULL); void Close(); - bool Send(const char* buffer, unsigned int buflen); - bool Forward(char* buffer, unsigned int buflen); + bool Send(const char* buffer, unsigned int& numBytes); bool Recv(char* buffer, unsigned int& numBytes, Direction* direction = NULL); - - private: - char eth_addr[6]; }; // end class BsdCap /** @@ -106,15 +102,8 @@ bool BsdCap::Open(const char* interfaceName) } ProtoAddress macAddr; - if (ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, macAddr)) - { - memcpy(eth_addr, macAddr.GetRawHostAddress(), 6); - } - else - { - eth_addr.Invalidate(); + if (!ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, if_addr)) PLOG(PL_ERROR, "BsdCap::Open() warning: unable to get MAC address for interface \"%s\"\n", interfaceName); - } int ifIndex = ProtoSocket::GetInterfaceIndex(interfaceName); @@ -202,7 +191,7 @@ void BsdCap::Close() bool BsdCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) { if (direction) *direction = UNSPECIFIED; - while (1) + for (;;) { ssize_t result = read(descriptor, buffer, numBytes); if (result < 0) @@ -241,7 +230,7 @@ bool BsdCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) */ -bool BsdCap::Send(const char* buffer, unsigned int buflen) +bool BsdCap::Send(const char* buffer, unsigned int& numBytes) { // Make sure packet is a type that is OK for us to send // (Some packets seem to cause a PF_PACKET socket trouble) @@ -250,81 +239,34 @@ bool BsdCap::Send(const char* buffer, unsigned int buflen) type = ntohs(type); if (type <= 0x05dc) // assume it's 802.3 Length and ignore { - PLOG(PL_DEBUG, "BsdCap::Send() unsupported 802.3 frame (len = %04x)\n", type); - return false; + PLOG(PL_DEBUG, "BsdCap::Send() unsupported 802.3 frame (len = %04x)\n", type); + return false; } - - unsigned int put = 0; - while (put < buflen) + for (;;) { - struct sockaddr sockAddr; - + struct sockaddr sockAddr; // note the raw frame device here does not use the sockAddr ssize_t result = sendto(descriptor, buffer+put, buflen-put, 0, &sockAddr, sizeof(sockAddr)); - if (result > 0) - { - put += result; - } - else + if (result < 0) { - if (EINTR == errno) - { - continue; // try again - } - else + switch (errno) { - PLOG(PL_ERROR, "BsdCap::Send() error: %s\n", GetErrorString()); - return false; - } - } - } - return true; -} // end BsdCap::Send() -/** - * @brief Changes the mac addr to our own and writes packet to the bsd socket descriptor. - * - * 802.3 frames are not supported - * - * @param buffer - * @param buflen - * - * @return success or failure indicator - */ - -bool BsdCap::Forward(char* buffer, unsigned int buflen) -{ - // Make sure packet is a type that is OK for us to send - // (Some packets seem to cause a PF_PACKET socket trouble) - UINT16 type; - memcpy(&type, buffer+12, 2); - type = ntohs(type); - if (type <= 0x05dc) // assume it's 802.3 Length and ignore - { - PLOG(PL_DEBUG, "BsdCap::Forward() unsupported 802.3 frame (len = %04x)\n", type); - return false; - } - // Change the src MAC addr to our own - // (TBD) allow caller to specify dst MAC addr ??? - memcpy(buffer+6, eth_addr, 6); - unsigned int put = 0; - while (put < buflen) - { - ssize_t result = write(descriptor, buffer+put, buflen-put); - if (result > 0) - { - put += result; + case EINTR: + continue; // try again + case EWOULDBLOCK: + numBytes = 0; + case ENOBUFS: + // because this doesn't block write() + default: + PLOG(PL_ERROR, "BsdCap::Send() error: %s", GetErrorString()); + break; + } + return false; } else { - if (EINTR == errno) - { - continue; // try again - } - else - { - PLOG(PL_ERROR, "BsdCap::Forward() error: %s", GetErrorString()); - return false; - } + ASSERT(result == numBytes); + break; } } return true; -} // end BsdCap::Forward() +} // end BsdCap::Send() diff --git a/src/bsd/bsdDetour.cpp b/src/bsd/bsdDetour.cpp index 610ba3c..c013191 100644 --- a/src/bsd/bsdDetour.cpp +++ b/src/bsd/bsdDetour.cpp @@ -23,7 +23,8 @@ class BsdDetour : public ProtoDetour const ProtoAddress& srcFilterAddr = PROTO_ADDR_NONE, unsigned int srcFilterMask = 0, const ProtoAddress& dstFilterAddr = PROTO_ADDR_NONE, - unsigned int dstFilterMask = 0); + unsigned int dstFilterMask = 0, + int dscpValue = -1); void Close(); bool Recv(char* buffer, unsigned int& numBytes, @@ -115,7 +116,7 @@ bool BsdDetour::SetIPFirewall(Action action, const ProtoAddress& srcFilterAddr, unsigned int srcFilterMask, const ProtoAddress& dstFilterAddr, - unsigned int dstFilterMask) + unsigned int dstFilterMask) { // 1) IPv4 or IPv6 address family? const char* cmd; @@ -178,7 +179,7 @@ bool BsdDetour::SetIPFirewall(Action action, // cmd = "ipfw" or "ip6fw" const char* f = (ProtoAddress::IPv4 == srcFilterAddr.GetType()) ? "ip" : "ipv6"; - sprintf(rule, "%s add divert %hu %s ", cmd, DIVERT_PORT, f); + sprintf(rule, "%s add divert %hu %s ", cmd, (UINT16)DIVERT_PORT, f); if (0 != srcFilterMask) { strcat(rule, "from "); @@ -189,7 +190,7 @@ bool BsdDetour::SetIPFirewall(Action action, return false; } len = strlen(rule); - sprintf(rule+len, "/%hu ", srcFilterMask); + sprintf(rule+len, "/%u ", srcFilterMask); } else { @@ -206,7 +207,7 @@ bool BsdDetour::SetIPFirewall(Action action, return false; } len = strlen(rule); - sprintf(rule+len, "/%hu ", dstFilterMask); + sprintf(rule+len, "/%u ", dstFilterMask); } else { @@ -219,7 +220,7 @@ bool BsdDetour::SetIPFirewall(Action action, } else // (DELETE == action) { - sprintf(rule, "%s delete %hu\n", cmd, hookFlags - 1); + sprintf(rule, "%s delete %d\n", cmd, hookFlags - 1); hookFlags = 0; } @@ -268,7 +269,8 @@ bool BsdDetour::Open(int hookFlags, const ProtoAddress& srcFilterAddr, unsigned int srcFilterMask, const ProtoAddress& dstFilterAddr, - unsigned int dstFilterMask) + unsigned int dstFilterMask, + int /*dscpValue*/) // TBD - support DSCP { if (IsOpen()) Close(); @@ -409,14 +411,7 @@ bool BsdDetour::Open(int hookFlags, } unsigned int ifIndexArray[256]; unsigned int ifCount = ProtoSocket::GetInterfaceIndices(ifIndexArray, 256); - if (ifCount < 0) - { - PLOG(PL_ERROR, "BsdDetour::Open(): error: unable to retrieve list of network interface indices\n"); - delete rtMgr; - Close(); - return false; - } - else if (0 == ifCount) + if (0 == ifCount) { PLOG(PL_ERROR, "BsdDetour::Open(): warning: no network interface indices were found.\n"); } @@ -530,7 +525,7 @@ bool BsdDetour::Recv(char* buffer, struct sockaddr_storage sockAddr; socklen_t addrLen = sizeof(sockAddr); - size_t result = recvfrom(descriptor, buffer, numBytes, 0, + ssize_t result = recvfrom(descriptor, buffer, numBytes, 0, (struct sockaddr*)&sockAddr, &addrLen); if (result < 0) { @@ -587,7 +582,7 @@ bool BsdDetour::Allow(const char* buffer, unsigned int numBytes) { socklen_t addrSize = (ProtoAddress::IPv6 == pkt_addr.GetType()) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); - size_t result = sendto(descriptor, buffer, (size_t)numBytes, 0, + ssize_t result = sendto(descriptor, buffer, (size_t)numBytes, 0, &pkt_addr.GetSockAddr(), addrSize); if (result < 0) { diff --git a/src/bsd/bsdNet.cpp b/src/bsd/bsdNet.cpp index 1829b0b..f3b98c6 100644 --- a/src/bsd/bsdNet.cpp +++ b/src/bsd/bsdNet.cpp @@ -2,17 +2,23 @@ // This file contains BSD (and MacOS) specific implementations of // ProtoNet features. -// Note that the remainder of the Linux ProtoNet stuff is +// Note that the remainder of the ProtoNet stuff is // implemented in the "src/unix/unixNet.cpp" file // in the Protolib source tree and the common stuff is // in "src/common/protoNet.cpp" #include "protoNet.h" #include "protoDebug.h" +#include "protoTree.h" + +#include +#include +#include +#include +#include #include // for close() #include // for sprintf() -#include // for socket() #include // // for kernel event stuff #include // for ioctl() #include // for KEV_DL_SUBCLASS, etc @@ -20,6 +26,91 @@ #include // for KEV_INET_SUBCLASS, etc #include // for KEV_INET6_SUBCLASS, etc +bool ProtoNet::GetGroupMemberships(const char* ifaceName, ProtoAddress::Type addrType, ProtoAddressList& addrList) +{ + int family; + switch (addrType) + { + case ProtoAddress::IPv4: + family = AF_INET; + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + family = AF_INET6; + break; +#endif // HAVE_IPV6 + default: + PLOG(PL_ERROR, "UnixNet::GetInterfaceName() error: invalid address type\n"); + return 0; + } + // use getifmaddrs() to fetch memberships for given interface + struct ifmaddrs* ifmap; + if (0 == getifmaddrs(&ifmap)) + { + // Look for addrType address for given "interfaceName" + struct ifmaddrs* ptr = ifmap; + while (NULL != ptr) + { + if ((NULL != ptr->ifma_name) && + (NULL != ptr->ifma_addr) && + (family == ptr->ifma_addr->sa_family)) + { + if (ptr->ifma_name->sa_family != AF_LINK) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: invalid interface data from kernel!\n"); + freeifmaddrs(ifmap); + return false; + } + // This union hack avoids cast-align warning + typedef struct SockAddrPtr + { + union + { + struct sockaddr* sa; + struct sockaddr_dl* sd; + }; + } SockAddrPtr; + SockAddrPtr sap; + sap.sa = ptr->ifma_name; + char ifName[64]; + unsigned int nameLen = sap.sd->sdl_nlen; + if (nameLen > 63) nameLen = 63; + strncpy(ifName, sap.sd->sdl_data, nameLen); + ifName[nameLen] = '\0'; + if (0 == strcmp(ifaceName, ifName)) + { + ProtoAddress groupAddr; + if (!groupAddr.SetSockAddr(*(ptr->ifma_addr))) + { + PLOG(PL_WARN, "ProtoNet::GetGroupMemberships() error: invalid address family\n"); + ptr = ptr->ifma_next; + continue; + } + if (!addrList.Insert(groupAddr)) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: unable to add address to list!\n"); + freeifmaddrs(ifmap); + return false; + } + } + } + ptr = ptr->ifma_next; + } + if (NULL != ifmap) + freeifmaddrs(ifmap); + return true; + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() getifmaddrs() error: %s\n", GetErrorString()); + return false; + } +} // end ProtoNet::GetGroupMemberships() + +// IMPORTANT - The BsdNetMonitor implementation here is currently +// specific to Mac OSX. TBD - Provide implementation that +// works on other BSD platforms, too. +#ifdef MACOSX class BsdNetMonitor : public ProtoNet::Monitor { @@ -32,11 +123,53 @@ class BsdNetMonitor : public ProtoNet::Monitor bool GetNextEvent(Event& theEvent); private: - u_int32_t* msg_buffer; - u_int32_t msg_buffer_size; + // We keep a list of "up" interfaces that is populated upon "Open()" + // and modified as interfaces go up and down + + + class Interface : public ProtoTree::Item + { + public: + Interface(const char* name, unsigned int index); + ~Interface(); + + const char* GetName() const + {return iface_name;} + unsigned int GetIndex() const + {return iface_index;} + void SetIndex(unsigned int index) + {iface_index = index;} + + private: + const char* GetKey() const + {return iface_name;} + unsigned int GetKeysize() const + {return iface_name_bits;} + + char iface_name[IFNAMSIZ+1]; + unsigned int iface_name_bits; + unsigned int iface_index; + }; // end class BsdNetMonitor::Interface + class InterfaceList : public ProtoTreeTemplate {}; + + InterfaceList iface_list; + u_int32_t* msg_buffer; + u_int32_t msg_buffer_size; }; // end class BsdNetMonitor +BsdNetMonitor::Interface::Interface(const char* name, unsigned int index) + : iface_index(index) +{ + iface_name[IFNAMSIZ] = '\0'; + strncpy(iface_name, name, IFNAMSIZ); + iface_name_bits = strlen(iface_name) << 3; +} + +BsdNetMonitor::Interface::~Interface() +{ +} + // This is the implementation of the ProtoNet::Monitor::Create() // static method (our BSD-specific factory) @@ -52,6 +185,7 @@ BsdNetMonitor::BsdNetMonitor() BsdNetMonitor::~BsdNetMonitor() { + Close(); } bool BsdNetMonitor::Open() @@ -81,11 +215,57 @@ bool BsdNetMonitor::Open() Close(); return false; } + + // Populate our iface_list with "up" interfaces + unsigned int ifaceCount = ProtoNet::GetInterfaceCount(); + if (ifaceCount > 0) + { + unsigned int* indexArray = new unsigned int[ifaceCount]; + if (NULL == indexArray) + { + PLOG(PL_ERROR, "BsdNetMonitor::Open() new indexArray[%u] error: %s\n", ifaceCount, GetErrorString()); + Close(); + return false; + } + if (ProtoNet::GetInterfaceIndices(indexArray, ifaceCount) != ifaceCount) + { + PLOG(PL_ERROR, "BsdNetMonitor::Open() GetInterfaceIndices() error?!\n"); + delete[] indexArray; + Close(); + return false; + } + for (unsigned int i = 0; i < ifaceCount; i++) + { + char ifaceName[IFNAMSIZ+1]; + ifaceName[IFNAMSIZ] = '\0'; + if (!ProtoNet::GetInterfaceName(indexArray[i], ifaceName, IFNAMSIZ)) + { + PLOG(PL_ERROR, "BsdNetMonitor::Open() error: unable to get interface name for index: %u\n", indexArray[i]); + delete[] indexArray; + Close(); + return false; + } + Interface* iface = iface_list.FindString(ifaceName); + if (NULL != iface) + { + PLOG(PL_ERROR, "BsdNetMonitor::Open() warning: interface \"%s\" index:%u already listed as index:%u\n", + ifaceName, indexArray[i], iface->GetIndex()); + } + else if (NULL == (iface = new Interface(ifaceName, indexArray[i]))) + { + PLOG(PL_ERROR, "BsdNetMonitor::Open() new Interface[ error: %s\n", GetErrorString()); + Close(); + return false; + } + iface_list.Insert(*iface); + } + } return true; } // end BsdNetMonitor::Open() void BsdNetMonitor::Close() { + iface_list.Destroy(); if (IsOpen()) { ProtoNet::Monitor::Close(); @@ -96,6 +276,7 @@ void BsdNetMonitor::Close() { delete[] msg_buffer; msg_buffer_size = 0; + msg_buffer = NULL; } } // end BsdNetMonitor::Open() @@ -173,18 +354,80 @@ bool BsdNetMonitor::GetNextEvent(Event& theEvent) PLOG(PL_INFO, "BsdNetMonitor::GetNextEvent() warning: unhandled iface network event:%d\n", kmsg->event_code); break; } - // If it was a supported event, fill in ifaceIndex + // If it was a supported event, fill in ifaceIndex and ifaceName //if (Event::UNKNOWN_EVENT != theEvent.GetType()) { - - char ifName[IFNAMSIZ]; + // Do we already know this interface? + char ifName[IFNAMSIZ+1]; + ifName[IFNAMSIZ] = '\0'; sprintf(ifName, "%s%d", dat->if_name, dat->if_unit); + theEvent.SetInterfaceName(ifName); + Interface* iface =iface_list.FindString(ifName); + // Fetch the system index in case it's not consistent unsigned int ifIndex = ProtoNet::GetInterfaceIndex(ifName); if (0 == ifIndex) { - PLOG(PL_ERROR, "BsdNetMonitor::GetNextEvent() unable to get index for iface \"%s\"\n", ifName); - return false; - } + if (NULL != iface) + { + // If a known iface has lost its index, assume it's gone DOWN? + if (Event::IFACE_DOWN != theEvent.GetType()) + { + PLOG(PL_INFO, "BsdNetMonitor::GetNextEvent() known interface has lost it's index (assuming IFACE_DOWN)\n"); + theEvent.SetType(Event::IFACE_DOWN); + } + ifIndex = iface->GetIndex(); + } + else + { + // Unknown interface with no index, so ignore + PLOG(PL_INFO, "BsdNetMonitor::GetNextEvent() unable to get index for iface \"%s\"\n", ifName); + return GetNextEvent(theEvent); + } + } + else + { + if (NULL != iface) + { + if (ifIndex != iface->GetIndex()) + { + PLOG(PL_ERROR, "BsdNetMonitor::GetNextEvent() warning: index changed for known interface \"%s\"\n",ifName); + iface->SetIndex(ifIndex); + } + } + else if (Event::IFACE_DOWN != theEvent.GetType() && (ProtoNet::IFACE_UP == ProtoNet::GetInterfaceStatus(ifName))) + { + if (NULL == (iface = new Interface(ifName, ifIndex))) + { + PLOG(PL_ERROR, "BsdNetMonitor::Open() new Interface[ error: %s\n", GetErrorString()); + } + else + { + if (Event::IFACE_UP != theEvent.GetType()) + { + PLOG(PL_INFO, "BsdNetMonitor::GetNextEvent() event for unknown interface (assuming IFACE_UP)\n"); + theEvent.SetType(Event::IFACE_UP); + } + TRACE("Inserting iface \"%s\"\n", ifName); + iface_list.Insert(*iface); + } + } + } + if (Event::IFACE_STATE == theEvent.GetType()) + { + // See if state changed from UP to DOWN or vice versa + if (ProtoNet::IFACE_UP != ProtoNet::GetInterfaceStatus(ifName) && (NULL != iface)) + { + PLOG(PL_INFO, "BsdNetMonitor::GetNextEvent() status event for known, but DOWN, interface (assuming IFACE_DOWN)\n"); + theEvent.SetType(Event::IFACE_DOWN); + } + } + + if ((Event::IFACE_DOWN == theEvent.GetType()) && (NULL != iface)) + { + // Remove "DOWN"" iface from iface_list + iface_list.Remove(*iface); + delete iface; + } theEvent.SetInterfaceIndex(ifIndex); } break; @@ -196,6 +439,7 @@ bool BsdNetMonitor::GetNextEvent(Event& theEvent) switch (kmsg->event_code) { case KEV_INET_NEW_ADDR: + //case KEV_INET_CHANGED_ADDR: theEvent.SetType(Event::IFACE_ADDR_NEW); break; case KEV_INET_ADDR_DELETED: @@ -209,13 +453,15 @@ bool BsdNetMonitor::GetNextEvent(Event& theEvent) //if (Event::UNKNOWN_EVENT != theEvent.GetType()) { - char ifName[IFNAMSIZ]; + char ifName[IFNAMSIZ+1]; + ifName[IFNAMSIZ] = '\0'; sprintf(ifName, "%s%d", dat->link_data.if_name, dat->link_data.if_unit); + theEvent.SetInterfaceName(ifName); unsigned int ifIndex = ProtoNet::GetInterfaceIndex(ifName); if (0 == ifIndex) { - PLOG(PL_ERROR, "BsdNetMonitor::GetNextEvent() unable to get index for iface \"%s\"\n", ifName); - return false; + PLOG(PL_ERROR, "BsdNetMonitor::GetNextEvent() unable to get index for ip4 iface \"%s\"\n", ifName); + return GetNextEvent(theEvent); } theEvent.SetInterfaceIndex(ifIndex); theEvent.AccessAddress().SetRawHostAddress(ProtoAddress::IPv4, (char*)&(dat->ia_addr), 4); @@ -231,6 +477,8 @@ bool BsdNetMonitor::GetNextEvent(Event& theEvent) { case KEV_INET6_NEW_USER_ADDR: case KEV_INET6_NEW_LL_ADDR: + case KEV_INET6_NEW_RTADV_ADDR: + case KEV_INET6_CHANGED_ADDR: theEvent.SetType(Event::IFACE_ADDR_NEW); break; case KEV_INET6_ADDR_DELETED: @@ -244,13 +492,15 @@ bool BsdNetMonitor::GetNextEvent(Event& theEvent) //if (Event::UNKNOWN_EVENT != theEvent.GetType()) { - char ifName[IFNAMSIZ]; + char ifName[IFNAMSIZ+1]; + ifName[IFNAMSIZ] = '\0'; sprintf(ifName, "%s%d", dat->link_data.if_name, dat->link_data.if_unit); + theEvent.SetInterfaceName(ifName); unsigned int ifIndex = ProtoNet::GetInterfaceIndex(ifName); if (0 == ifIndex) { - PLOG(PL_ERROR, "BsdNetMonitor::GetNextEvent() unable to get index for iface \"%s\"\n", ifName); - return false; + PLOG(PL_ERROR, "BsdNetMonitor::GetNextEvent() unable to get index for ip6 iface \"%s\"\n", ifName); + return GetNextEvent(theEvent); } theEvent.SetInterfaceIndex(ifIndex); theEvent.AccessAddress().SetRawHostAddress(ProtoAddress::IPv6, (char*)&(dat->ia_addr), 16); @@ -264,3 +514,5 @@ bool BsdNetMonitor::GetNextEvent(Event& theEvent) } return true; } // end BsdNetMonitor::GetNextEvent() + +#endif // MACOSX (TBD - make this implementation work on other BSD systems, too) diff --git a/src/bsd/bsdRouteMgr.cpp b/src/bsd/bsdRouteMgr.cpp index e711293..c59e823 100644 --- a/src/bsd/bsdRouteMgr.cpp +++ b/src/bsd/bsdRouteMgr.cpp @@ -113,7 +113,7 @@ void BsdRouteMgr::Close() } } // end BsdRouteMgr::Close() - +/* #define ROUNDUP(a, size) (((a) & ((size)-1)) ? \ (1 + ((a) | ((size)-1))) \ : (a)) @@ -121,8 +121,18 @@ void BsdRouteMgr::Close() #define NEXT_SA(sa) sa = (struct sockaddr *) ((caddr_t) (sa) + ((sa)->sa_len ? \ ROUNDUP((sa)->sa_len, sizeof(u_long)) \ : sizeof(u_long))); - - +*/ + +// IMPORTANT NOTE: These "oldie but goodie macros above are wrong on a 64-bit machine!!! +// The ones below do the right thing. + +#define ROUNDUP(a, size) (((a) & ((size)-1)) ? \ + (1 + ((a) | ((size)-1))) \ + : (a)) + +#define NEXT_SA(sa) sa = (struct sockaddr *) ((caddr_t) (sa) + ((sa)->sa_len ? \ + ROUNDUP((sa)->sa_len, sizeof(UINT32)) \ + : sizeof(UINT32))); bool BsdRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, ProtoRouteTable& routeTable) @@ -164,7 +174,7 @@ bool BsdRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, len, strerror(errno)); return false; } - + if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { delete[] buf; @@ -176,7 +186,8 @@ bool BsdRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, char* next = buf; while (next < end) { - struct rt_msghdr* rtm = (struct rt_msghdr*)next; + // (void*) casts here to avoid cast-align mis-warning + struct rt_msghdr* rtm = (struct rt_msghdr*)((void*)next); struct sockaddr* addr = (struct sockaddr*)(rtm + 1); ProtoAddress dst, gw; dst.Invalidate(); @@ -202,21 +213,8 @@ bool BsdRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, } case RTAX_NETMASK: { - const unsigned char* ptr = NULL; - switch (addrType) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } - if (ptr) + const unsigned char* ptr = (const unsigned char*)(&addr->sa_data[2]); + if (NULL != ptr) { unsigned int maskSize = addr->sa_len ? (addr->sa_len - (int)(ptr - (unsigned char*)addr)) : 0; prefixLen = 0; @@ -233,8 +231,9 @@ bool BsdRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, while (0 != (bit & *ptr)) { bit >>= 1; - prefixLen += 1; + prefixLen += 1; } + break; } } } @@ -255,21 +254,37 @@ bool BsdRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, NEXT_SA(addr); } // end if(mask[i] is set) } // end for(i=0..RTAX_MAX) - if (dst.IsValid() && - (0 == (rtm->rtm_flags & RTF_WASCLONED))) + if (dst.IsValid()) { + bool setRoute = true; +#ifdef MACOSX + // Don't fetch cloned routes (TBD - investigate further) + if (0 != (rtm->rtm_flags & RTF_WASCLONED)) + setRoute = false; +#endif // MACOSX + if (0 == prefixLen) + { + // This makes sure default routes w/ valid gateway "trump" device default route + // (TBD - deal with multiple default route entries) + if ((NULL != routeTable.GetDefaultEntry()) && !gw.IsValid()) + setRoute = false; + } if (prefixLen < 0) prefixLen = dst.GetLength() << 3; if (0 == (rtm->rtm_flags & RTF_GATEWAY)) gw.Invalidate(); - if (!routeTable.SetRoute(dst, prefixLen, gw, rtm->rtm_index)) + + if (setRoute) { - PLOG(PL_ERROR, "BsdRouteMgr::GetAllRoutes() error creating table entry\n"); - delete[] buf; - return false; + if (!routeTable.SetRoute(dst, prefixLen, gw, rtm->rtm_index)) + { + PLOG(PL_ERROR, "BsdRouteMgr::GetAllRoutes() error creating table entry\n"); + delete[] buf; + return false; + } } } next += rtm->rtm_msglen; } - delete[] buf; + delete[] buf; return true; } // end BsdRouteMgr::GetAllRoutes() @@ -376,7 +391,7 @@ bool BsdRouteMgr::SetRoute(const ProtoAddress& dst, else { //TRACE("Adding RTAX_GATEWAY (IF)...\n"); - struct sockaddr_dl* sdl = (struct sockaddr_dl*)addr; + struct sockaddr_dl* sdl = (struct sockaddr_dl*)((void*)addr); sdl->sdl_len = sizeof(struct sockaddr_dl); sdl->sdl_family = AF_LINK; sdl->sdl_index = ifIndex; @@ -391,21 +406,7 @@ bool BsdRouteMgr::SetRoute(const ProtoAddress& dst, { if (prefixLen > 0) { - unsigned char* ptr = NULL; - switch (dst.GetType()) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } - ASSERT(ptr); + unsigned char* ptr = (unsigned char*)(&addr->sa_data[2]); unsigned int numBytes = prefixLen >> 3; memset(ptr, 0xff, numBytes); unsigned int remainder = prefixLen & 0x07; @@ -434,6 +435,7 @@ bool BsdRouteMgr::SetRoute(const ProtoAddress& dst, } // end if (rtm_addrs[i]) } // end for(i=0..RTAX_MAX) + // Send RTM_ADD request to routing socket int result = send(descriptor, rtm, rtm->rtm_msglen, 0); if ((result < 0) && (EEXIST == errno)) @@ -472,6 +474,7 @@ bool BsdRouteMgr::SetRoute(const ProtoAddress& dst, if ((seq == rtm->rtm_seq) && (pid == rtm->rtm_pid)) { + TRACE("matching response ...\n"); struct sockaddr* addr = (struct sockaddr*)(rtm + 1); for (int i = 0; i < RTAX_MAX; i++) { @@ -493,20 +496,7 @@ bool BsdRouteMgr::SetRoute(const ProtoAddress& dst, } case RTAX_NETMASK: { - const unsigned char* ptr = NULL; - switch (dst.GetType()) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } + const unsigned char* ptr = (const unsigned char*)(&addr->sa_data[2]); if (ptr) { unsigned int maskSize = addr->sa_len ? (addr->sa_len - (int)(ptr - (unsigned char*)addr)) : 0; @@ -550,6 +540,7 @@ bool BsdRouteMgr::SetRoute(const ProtoAddress& dst, { if (destination.IsValid()) { + //TRACE("BsdRouteMgr::SetRoute() successfully added route\n"); return true; } else @@ -663,7 +654,7 @@ bool BsdRouteMgr::DeleteRoute(const ProtoAddress& dst, else { //TRACE("Adding RTAX_GATEWAY (IF)...\n"); - struct sockaddr_dl* sdl = (struct sockaddr_dl*)addr; + struct sockaddr_dl* sdl = (struct sockaddr_dl*)((void*)addr); sdl->sdl_len = sizeof(struct sockaddr_dl); sdl->sdl_family = AF_LINK; sdl->sdl_index = ifIndex; @@ -676,21 +667,7 @@ bool BsdRouteMgr::DeleteRoute(const ProtoAddress& dst, break; case RTAX_NETMASK: { - unsigned char* ptr = NULL; - switch (dst.GetType()) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } - ASSERT(ptr); + unsigned char* ptr = (unsigned char*)(&addr->sa_data[2]); if (prefixLen > 0) { unsigned int numBytes = prefixLen >> 3; @@ -784,20 +761,7 @@ bool BsdRouteMgr::DeleteRoute(const ProtoAddress& dst, } case RTAX_NETMASK: { - const unsigned char* ptr = NULL; - switch (dst.GetType()) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } + const unsigned char* ptr = (const unsigned char*)(&addr->sa_data[2]); if (ptr) { unsigned int maskSize = addr->sa_len ? (addr->sa_len - (int)(ptr - (unsigned char*)addr)) : 0; @@ -937,21 +901,7 @@ bool BsdRouteMgr::GetRoute(const ProtoAddress& dst, break; case RTAX_NETMASK: { - unsigned char* ptr = NULL; - switch (dst.GetType()) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } - ASSERT(ptr); + unsigned char* ptr = (unsigned char*)(&addr->sa_data[2]); if (prefixLen > 0) { unsigned int numBytes = prefixLen >> 3; @@ -1038,7 +988,7 @@ bool BsdRouteMgr::GetRoute(const ProtoAddress& dst, //TRACE("RTAX_GWY: %s\n", gw.GetHostString()); break; case AF_LINK: - ifIndex = ((struct sockaddr_dl*)addr)->sdl_index; + ifIndex = ((struct sockaddr_dl*)((void*)addr))->sdl_index; //TRACE("RTAX_GWY: ifIndex:%d\n", ifIndex); break; default: @@ -1049,20 +999,7 @@ bool BsdRouteMgr::GetRoute(const ProtoAddress& dst, } case RTAX_NETMASK: { - const unsigned char* ptr = NULL; - switch (dst.GetType()) - { - case ProtoAddress::IPv4: - ptr = (unsigned char*)&(((struct sockaddr_in*)addr)->sin_addr); - break; -#ifdef HAVE_IPV6 - case ProtoAddress::IPv6: - ptr = (unsigned char*)&(((struct sockaddr_in6*)addr)->sin6_addr); - break; -#endif // HAVE_IPV6 - default: - break; - } + const unsigned char* ptr = (const unsigned char*)(&addr->sa_data[2]); if (ptr) { unsigned int maskSize = addr->sa_len ? (addr->sa_len - (int)(ptr - (unsigned char*)addr)) : 0; @@ -1182,7 +1119,7 @@ bool BsdRouteMgr::GetInterfaceAddressList(unsigned int ifIndex, char* next = buf; while (next < end) { - struct if_msghdr* ifm = (struct if_msghdr*)next; + struct if_msghdr* ifm = (struct if_msghdr*)((void*)next); switch (ifm->ifm_type) { case RTM_IFINFO: diff --git a/src/common/pcapCap.cpp b/src/common/pcapCap.cpp index 2a8a1e6..8a8762e 100644 --- a/src/common/pcapCap.cpp +++ b/src/common/pcapCap.cpp @@ -6,7 +6,7 @@ #include "protoCap.h" // for ProtoCap definition #include "protoSocket.h" #include "protoDebug.h" - +#include #ifdef WIN32 //#include #include @@ -30,13 +30,11 @@ class PcapCap : public ProtoCap bool Open(const char* interfaceName = NULL); bool IsOpen(){return (NULL != pcap_device);} void Close(); - bool Send(const char* buffer, unsigned int buflen); - bool Forward(char* buffer, unsigned int buflen); + bool Send(const char* buffer, unsigned int& numBytes); bool Recv(char* buffer, unsigned int& numBytes, Direction* direction = NULL); private: - pcap_t* pcap_device; - char eth_addr[6]; + pcap_t* pcap_device; }; // end class PcapCap @@ -95,13 +93,11 @@ bool PcapCap::Open(const char* interfaceName) return false; } } - ProtoAddress macAddr; - if (!ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, macAddr)) + if (!ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, if_addr)) { PLOG(PL_ERROR, "PcapCap::Open() error getting interface MAC address\n"); return false; } - memcpy(eth_addr, macAddr.GetRawHostAddress(), 6); Close(); // just in case char errbuf[PCAP_ERRBUF_SIZE+1]; errbuf[0] = '\0'; @@ -161,6 +157,7 @@ bool PcapCap::Open(const char* interfaceName) PLOG(PL_ERROR, "pcapExample: pcap_setnonblock() warning: %s\n", errbuf); #ifdef WIN32 input_handle = pcap_getevent(pcap_device); + input_event_handle = input_handle; #else descriptor = pcap_get_selectable_fd(pcap_device); #endif // if/else WIN32/UNIX @@ -208,8 +205,11 @@ bool PcapCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) unsigned int copyLen = (numBytes > hdr->caplen) ? hdr->caplen : numBytes; memcpy(buffer, data, copyLen); numBytes = copyLen; - if ((NULL != direction) && (0 == memcmp(eth_addr, buffer+6, 6))) - *direction = OUTBOUND; + // We may get false INBOUND when raw frames are sent with different src MAC addr + if ((NULL != direction) && (0 == memcmp(if_addr.GetRawHostAddress(), buffer + 6, 6))) + *direction = OUTBOUND; + else + *direction = INBOUND; return true; } case 0: // no pkt ready? @@ -226,7 +226,7 @@ bool PcapCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) } } // end PcapCap::Recv() -bool PcapCap::Send(const char* buffer, unsigned int buflen) +bool PcapCap::Send(const char* buffer, unsigned int& numBytes) { // Make sure packet is a type that is OK for us to send // (Some packets seem to cause a PF_PACKET socket trouble) @@ -240,74 +240,46 @@ bool PcapCap::Send(const char* buffer, unsigned int buflen) } #ifdef WIN32 - int pcapreturn = pcap_sendpacket(pcap_device,(unsigned char*)buffer, buflen); - TRACE("pcapreturn value in send %d\n",pcapreturn); -#else - unsigned int put = 0; - while (put < buflen) + int pcapreturn = pcap_sendpacket(pcap_device,(unsigned char*)buffer, numBytes); + if (0 != pcapreturn) { - ssize_t result = write(descriptor, buffer, buflen); - if (result > 0) + switch (errno) { - put += result; - } - else - { - if (EINTR == errno) - { - continue; // try again - } - else - { + case ENOBUFS: + case EWOULDBLOCK: + numBytes = 0; + default: PLOG(PL_ERROR, "PcapCap::Send() error: %s", GetErrorString()); - return false; - } + break; } + return false; } -#endif - return true; -} // end PcapCap::Send() - -bool PcapCap::Forward(char* buffer, unsigned int buflen) -{ - // Make sure packet is a type that is OK for us to send - // (Some packets seem to cause a PF_PACKET socket trouble) - UINT16 type; - memcpy(&type, buffer+12, 2); - type = ntohs(type); - if (type <= 0x05dc) // assume it's 802.3 Length and ignore - { - PLOG(PL_DEBUG, "PcapCap::Forward() unsupported 802.3 frame (len = %04x)\n", type); - return false; - } - // Change the src MAC addr to our own - // (TBD) allow caller to specify dst MAC addr ??? - memcpy(buffer+6, eth_addr, 6); -#ifdef WIN32 - int pcapreturn = pcap_sendpacket(pcap_device, (unsigned char*)buffer, buflen); - TRACE("this is the pcapreturn value in Forward %d\n",pcapreturn); #else - unsigned int put = 0; - while (put < buflen) + for (;;) { - ssize_t result = write(descriptor, buffer, buflen); - if (result > 0) + ssize_t result = write(descriptor, buffer, numBytes); + if (result < 0) { - put += result; + switch (errno) + { + case EINTR: + continue; // try again + case EWOULDBLOCK: + numBytes = 0; + case ENOBUFS: + // since this doesn't block write() or select(), etc + default: + PLOG(PL_ERROR, "PcapCap::Send() error: %s", GetErrorString()); + break; + } + return false; } else { - if (EINTR == errno) - { - continue; // try again - } - else - { - PLOG(PL_ERROR, "PcapCap::Forward() error: %s", GetErrorString()); - return false; - } + ASSERT(result == numBytes); + break; } } #endif return true; -} // end PcapCap::Forward() +} // end PcapCap::Send() diff --git a/src/common/protoAddress.cpp b/src/common/protoAddress.cpp index a8c3f0f..c60afba 100644 --- a/src/common/protoAddress.cpp +++ b/src/common/protoAddress.cpp @@ -11,6 +11,8 @@ * @file protoAddress.cpp * @brief Network address container class. */ + + #ifdef UNIX #include #include @@ -42,10 +44,18 @@ ProtoAddress::ProtoAddress() { memset(&addr, 0, sizeof(addr)); } + ProtoAddress::ProtoAddress(const ProtoAddress& theAddr) { *this = theAddr; } + + +ProtoAddress::ProtoAddress(const char* theAddr) +{ + ConvertFromString(theAddr); +} + ProtoAddress::~ProtoAddress() { } @@ -97,7 +107,7 @@ bool ProtoAddress::IsMulticast() const } #endif // HAVE_IPV6 case ETH: - // not ethernet broadcast also considered mcast here + // ethernet broadcast also considered mcast here return (0 != (0x01 & ((char*)&addr)[0])); #ifdef SIMULATE @@ -158,7 +168,7 @@ bool ProtoAddress::IsLoopback() const { case IPv4: { - // This was change since any 127.X.X.X is a loopback address + // This was changed since any 127.X.X.X is a loopback address // and many Linux configs have started using this fact for some purpose UINT32 addrVal = (UINT32)((struct sockaddr_in*)&addr)->sin_addr.s_addr; return (0x7f == (ntohl(addrVal) >> 24)); @@ -311,7 +321,7 @@ const char* ProtoAddress::GetHostString(char* buffer, unsigned int buflen) const unsigned long len = buflen; if (0 != WSAAddressToString((SOCKADDR*)&addr, sizeof(addr), NULL, (LPTSTR)buffer, &len)) PLOG(PL_ERROR, "ProtoAddress::GetHostString() WSAAddressToString() error\n"); - Win32Cleanup(); + Win32Cleanup(); #ifdef _UNICODE // Convert from unicode wcstombs(buffer, (wchar_t*)buffer, len); @@ -328,13 +338,18 @@ const char* ProtoAddress::GetHostString(char* buffer, unsigned int buflen) const char* ptr = strchr(buffer, '['); // nuke start bracket if (ptr) { + char * pch; size_t len = strlen(buffer); if (len > buflen) len = buflen; - memmove(buffer, ptr, len - (ptr-buffer)); - } + ptr++; + memmove(buffer, ptr, len - (ptr-buffer)); + } ptr = strrchr(buffer, '%'); // nuke if index, if applicable if (!ptr) ptr = strrchr(buffer, ']'); // nuke end bracket - if (ptr) ptr = '\0'; + ptr = (char *)memchr(buffer, ']', strlen(buffer)); + if (ptr) + buffer[ptr - buffer] = '\0'; + } #endif // HAVE_IPV6 return buffer ? buffer : "(null)"; @@ -479,7 +494,7 @@ void ProtoAddress::Reset(ProtoAddress::Type theType, bool zero) break; #endif // SIMULATE default: - PLOG(PL_ERROR, "ProtoAddress::Init() Invalid address type!\n"); + PLOG(PL_ERROR, "ProtoAddress::Reset() Invalid address type!\n"); break; } SetPort(0); @@ -518,7 +533,8 @@ bool ProtoAddress::SetSockAddr(const struct sockaddr& theAddr) #ifdef HAVE_IFDL case AF_LINK: { - struct sockaddr_dl* sdl = (struct sockaddr_dl*)&theAddr; + // The (void*) cast here avoid cast-align warning. TBD - may need to revisit this. + struct sockaddr_dl* sdl = (struct sockaddr_dl*)((void*)&theAddr); if (IFT_ETHER != sdl->sdl_type) { PLOG(PL_WARN, "ProtoNet::SetSockAddr() error: non-Ethertype link address!\n"); @@ -529,7 +545,7 @@ bool ProtoAddress::SetSockAddr(const struct sockaddr& theAddr) } #endif // HAVE_IFDL default: - PLOG(PL_WARN, "ProtoAddress::SetSockAddr() Invalid address type: %d\n", theAddr.sa_family); + PLOG(PL_ERROR, "ProtoAddress::SetSockAddr() warning: Invalid address type: %d\n", theAddr.sa_family); type = INVALID; length = 0; return false; @@ -621,7 +637,7 @@ const char* ProtoAddress::GetRawHostAddress() const PLOG(PL_ERROR, "ProtoAddress::RawHostAddress() Invalid address type!\n"); return NULL; } -} // end ProtoAddress::RawHostAddress() +} // end ProtoAddress::GetRawHostAddress() unsigned int ProtoAddress::SetCommonHead(const ProtoAddress& theAddr) { @@ -716,6 +732,49 @@ UINT8 ProtoAddress::GetPrefixLength() const return prefixLen; } // end ProtoAddress::GetPrefixLength() +void ProtoAddress::GeneratePrefixMask(ProtoAddress::Type type, UINT8 prefixLen) +{ + unsigned char* ptr; + switch (type) + { + case IPv4: + ptr = ((unsigned char*)&((struct sockaddr_in*)&addr)->sin_addr); + break; +#ifdef HAVE_IPV6 + case IPv6: + ptr = ((unsigned char*)&((struct sockaddr_in6*)&addr)->sin6_addr); + break; +#endif // HAVE_IPV6 + case ETH: + ptr = ((unsigned char*)&addr); + break; +#ifdef SIMULATE + case SIM: + ptr = ((unsigned char*)&((struct sockaddr_sim*)&addr)->addr); + break; +#endif // SIMULATE + default: + PLOG(PL_ERROR, "ProtoAddress::GeneratePrefixMask() Invalid address type!\n"); + return ; + } + Reset(type, true); // init to zero + if (prefixLen > GetLength()) + prefixLen = GetLength(); + while (0 != prefixLen) + { + if (prefixLen < 8) + { + *ptr = 0xff << (8 - prefixLen); + return; + } + else + { + *ptr++ = 0xff; + prefixLen -= 8; + } + } +} // end ProtoAddress::GeneratePrefixMask() + void ProtoAddress::ApplyPrefixMask(UINT8 prefixLen) { UINT8* ptr = NULL; @@ -1131,17 +1190,19 @@ bool ProtoAddress::ConvertFromString(const char* text) struct sockaddr_in sa; if (1 == inet_pton(AF_INET, text, &sa.sin_addr)) { + sa.sin_family = AF_INET; return SetSockAddr((struct sockaddr&)sa); } // Next try for an IPv6 addr struct sockaddr_in6 sa6; if (1 == inet_pton(AF_INET6, text, &sa6.sin6_addr)) { + sa6.sin6_family = AF_INET6; return SetSockAddr((struct sockaddr&)sa6); } // Finally, see if it's an Ethertype addr string return ResolveEthFromString(text); -#endif +#endif // if/else WIN32 #endif // if/else SIMULATE } // end ProtoAddress::ConvertFromString() @@ -1185,7 +1246,7 @@ bool ProtoAddress::ResolveFromString(const char* text) if(0 == getaddrinfo(text, NULL, NULL, &addrInfoPtr)) { #ifdef WIN32 - Win32Cleanup(); + Win32Cleanup(); #endif // WIN32 struct addrinfo* ptr = addrInfoPtr; bool result = false; @@ -1217,6 +1278,7 @@ bool ProtoAddress::ResolveFromString(const char* text) } else { + if (NULL != addrInfoPtr) freeaddrinfo(addrInfoPtr); PLOG(PL_WARN, "ProtoAddress::ResolveFromString() getaddrinfo() error: %s\n", GetErrorString()); #ifdef WIN32 // on WinNT 4.0, getaddrinfo() doesn't work, so we check the OS version @@ -1245,7 +1307,7 @@ bool ProtoAddress::ResolveFromString(const char* text) // 2) Use "gethostbyname()" to lookup IPv4 address struct hostent *hp = gethostbyname(text); #ifdef WIN32 - Win32Cleanup(); + Win32Cleanup(); #endif // WIN32 if(hp) { @@ -1257,7 +1319,7 @@ bool ProtoAddress::ResolveFromString(const char* text) } else { - PLOG(PL_ERROR, "ProtoAddress::ResolveFromString() gethostbyname() error: %s\n", + PLOG(PL_WARN, "ProtoAddress::ResolveFromString() gethostbyname() error: %s\n", GetErrorString()); return false; } @@ -1276,7 +1338,7 @@ bool ProtoAddress::ResolveFromString(const char* text) } #endif // !SIMULATE } // end ProtoAddress::ResolveFromString() - +#ifdef USE_GETHOSTBYADDR bool ProtoAddress::ResolveToName(char* buffer, unsigned int buflen) const { #ifdef WIN32 @@ -1313,11 +1375,12 @@ bool ProtoAddress::ResolveToName(char* buffer, unsigned int buflen) const return false; } // end switch(type) #ifdef WIN32 - Win32Cleanup(); + Win32Cleanup(); #endif // WIN32 if (hp) { + // Use the hp->h_name hostname by default size_t nameLen = 0; unsigned int dotCount = 0; strncpy(buffer, hp->h_name, buflen); @@ -1334,24 +1397,24 @@ bool ProtoAddress::ResolveToName(char* buffer, unsigned int buflen) const // Use first alias by default if (alias && *alias && buffer) { - strncpy(buffer, *alias, buflen); - nameLen = strlen(*alias); - nameLen = nameLen < buflen ? nameLen : buflen; - alias++; // Try to find the fully-qualified name // (longest alias with most '.') - while (*alias) + while (NULL != *alias) { unsigned int dc = 0; ptr = *alias; - while ((ptr = strchr(ptr, '.'))) + bool isArpa = false; + while (NULL != (ptr = strchr(ptr, '.'))) { ptr++; + // don't let ".arpa" aliases override + isArpa = (0 == strcmp(ptr, "arpa")); dc++; } size_t alen = strlen(*alias); bool override = (dc > dotCount) || ((dc == dotCount) && (alen >nameLen)); + if (isArpa) override = false; if (override) { strncpy(buffer, *alias, buflen); @@ -1366,13 +1429,61 @@ bool ProtoAddress::ResolveToName(char* buffer, unsigned int buflen) const } else { - PLOG(PL_ERROR, "ProtoAddress::ResolveToName() gethostbyaddr() error: %s\n", + PLOG(PL_WARN, "ProtoAddress::ResolveToName() gethostbyaddr() error: %s\n", GetErrorString()); GetHostString(buffer, buflen); return false; } } // end ProtoAddress::ResolveToName() +#else // Use newer getnameinfo rather than legacy gethostbyaddr +bool ProtoAddress::ResolveToName(char* buffer, unsigned int buflen) const +{ +#ifdef WIN32 + if (!Win32Startup()) + { + PLOG(PL_ERROR, "ProtoAddress::ResolveToName() Error initializing WinSock!\n"); + GetHostString(buffer, buflen); + return false; + } + DWORD retval = 0; +#else // WIN32 + int retval = 0; +#endif // endif WIN32 + switch (type) + { + case IPv4: + case IPv6: + retval = getnameinfo((struct sockaddr *) &addr, + sizeof(addr), + buffer, + buflen,NULL,0,NI_NAMEREQD); + break; +#ifdef SIMULATE + case SIM: + GetHostString(buffer, buflen); + return true; +#endif // SIMULATE + case ETH: + GetHostString(buffer, buflen); + return true; + default: + PLOG(PL_ERROR, "ProtoAddress::ResolveToName(): Invalid address type!\n"); + return false; + } // end switch(type) +#ifdef WIN32 + Win32Cleanup(); +#endif // WIN32 + if (retval != 0) + { + PLOG(PL_ERROR, "ProtoAddress::ResolveToName() error: %s\n", gai_strerror(retval)); + return false; + } + + return true; +} // end ProtoAddress::ResolveToName() +#endif // endif USE_GETHOSTBYADDR + bool ProtoAddress::ResolveLocalAddress(char* buffer, unsigned int buflen) { UINT16 thePort = GetPort(); // save port number @@ -1389,7 +1500,7 @@ bool ProtoAddress::ResolveLocalAddress(char* buffer, unsigned int buflen) #endif // WIN32 int result = gethostname(hostName, 255); #ifdef WIN32 - Win32Cleanup(); + Win32Cleanup(); #endif // WIN32 if (result) { @@ -1493,6 +1604,25 @@ void ProtoAddressList::Remove(const ProtoAddress& addr) } } // end ProtoAddressList::Remove() +bool ProtoAddressList::AddList(ProtoAddressList& addrList) +{ + ProtoAddressList::Iterator iterator(addrList); + ProtoAddress addr; + while (iterator.GetNextAddress(addr)) + { + if (!Insert(addr)) return false; + } + return true; +} // end ProtoAddressList::AddList() + +void ProtoAddressList::RemoveList(ProtoAddressList& addrList) +{ + ProtoAddressList::Iterator iterator(addrList); + ProtoAddress addr; + while (iterator.GetNextAddress(addr)) + Remove(addr); +} // end ProtoAddressList::RemoveList() + // Returns the root of the addr_tree bool ProtoAddressList::GetFirstAddress(ProtoAddress& firstAddr) const diff --git a/src/common/protoApp.cpp b/src/common/protoApp.cpp index 8317e0d..2a15546 100644 --- a/src/common/protoApp.cpp +++ b/src/common/protoApp.cpp @@ -318,6 +318,11 @@ int ProtoMain(int argc, char* argv[], bool pauseForUser) #endif // if/else _WIN32_WCE } //TRACE("ProtoMain() exiting with code %d...\n", exitCode); + +#ifdef USE_PROTO_CHECK + ProtoCheckLogAllocations(stderr); // TBD - output to proto debug log? +#endif // USE_PROTO_CHECK + return exitCode; // exitCode contains "signum" causing exit } // end ProtoMain(); diff --git a/src/common/protoBase64.cpp b/src/common/protoBase64.cpp index cdc5157..68aa080 100644 --- a/src/common/protoBase64.cpp +++ b/src/common/protoBase64.cpp @@ -181,7 +181,7 @@ unsigned int ProtoBase64::DetermineDecodedSize(const char* input, unsigned int n } // For every 4 "valid encoded bytes", 3 output bytes are generated unsigned int quads = validBytes / 4; - unsigned size = 3 * quads; + unsigned int size = 3 * quads; unsigned int remainder = validBytes % 4; // Note "remainder" _should_ only be 0, 2, or 3 if (remainder > 1) diff --git a/src/common/protoBitmask.cpp b/src/common/protoBitmask.cpp index f77fbf0..cff19a4 100644 --- a/src/common/protoBitmask.cpp +++ b/src/common/protoBitmask.cpp @@ -6,7 +6,6 @@ #include "protoBitmask.h" #include "protoDebug.h" -#include "protoDefs.h" /** * @file protoBitmask.cpp * @@ -384,7 +383,7 @@ bool ProtoBitmask::SetBits(UINT32 index, UINT32 count) { mask[maskIndex] |= 0x00ff >> bitIndex; count -= bitRemainder; - INT32 nbytes = count >> 3; + UINT32 nbytes = count >> 3; memset(&mask[++maskIndex], 0xff, nbytes); count &= 0x07; if (count) @@ -417,13 +416,13 @@ bool ProtoBitmask::UnsetBits(UINT32 index, UINT32 count) { mask[maskIndex] &= 0x00ff << bitRemainder; count -= bitRemainder; - INT32 nbytes = count >> 3; + UINT32 nbytes = count >> 3; memset(&mask[++maskIndex], 0, nbytes); count &= 0x07; if (count) mask[maskIndex+nbytes] &= 0xff >> count; } if ((first_set >= index) && (end > first_set)) - { + { first_set = end; if (!GetNextSet(first_set)) first_set = num_bits; } @@ -468,8 +467,14 @@ bool ProtoBitmask::Subtract(const ProtoBitmask& b) // this = ~this & b (this = b - this) +// (i.e., this is set to the bits uniquely set in 'b') bool ProtoBitmask::XCopy(const ProtoBitmask& b) { + if (!b.IsSet()) + { + Clear(); + return true; + } if (b.num_bits > num_bits) return false; unsigned int len = b.mask_len; unsigned int begin = b.first_set >> 3; @@ -553,16 +558,16 @@ ProtoSlidingMask::~ProtoSlidingMask() Destroy(); } -bool ProtoSlidingMask::Init(INT32 numBits, UINT32 rangeMask) +bool ProtoSlidingMask::Init(UINT32 numBits, UINT32 rangeMask) { if (mask) Destroy(); - if ((numBits <= 0) || ((UINT32)numBits > ((rangeMask>>1)+1))) + if ((0 != rangeMask) && (numBits > ((rangeMask>>1)+1))) return false; UINT32 len = (numBits + 7) >> 3; if ((mask = new unsigned char[len])) { range_mask = rangeMask; - range_sign = (rangeMask ^ (rangeMask >> 1)); + range_sign = rangeMask ? (rangeMask ^ (rangeMask >> 1)) : 0; mask_len = len; num_bits = numBits; Clear(); @@ -574,13 +579,17 @@ bool ProtoSlidingMask::Init(INT32 numBits, UINT32 rangeMask) } } // end ProtoSlidingMask::Init() -bool ProtoSlidingMask::Resize(INT32 numBits) +bool ProtoSlidingMask::Resize(UINT32 numBits) { // 1) Backup the current state ProtoSlidingMask tempMask = *this; // 2) Prune if needed. if (numBits < num_bits) - tempMask.UnsetBits(offset+numBits, num_bits-numBits); + { + UINT32 endex = offset + numBits; + if (range_mask) endex &= range_mask; + tempMask.UnsetBits(endex, num_bits-numBits); + } // 3) Re-init w/ new "numBits" mask = NULL; bool result = Init(numBits, range_mask); @@ -611,17 +620,17 @@ void ProtoSlidingMask::Destroy() bool ProtoSlidingMask::CanSet(UINT32 index) const { + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { // Determine position with respect to current start // and end, given the "offset" of the current start - INT32 pos = Delta(index, offset); - if (pos < 0) + if (Compare(index, offset) < 0) { // Precedes start. - pos += start; - if (pos < 0) pos += num_bits; - if (pos < 0) + INT32 deltaPos = start + Difference(index, offset);; + if (deltaPos < 0) deltaPos += num_bits; + if (deltaPos < 0) { // out of range return false; @@ -629,6 +638,7 @@ bool ProtoSlidingMask::CanSet(UINT32 index) const else { // Is pos between end & start? + UINT32 pos = (UINT32)deltaPos; if (end < start) { if ((pos <= end) || (pos >= start)) return false; @@ -640,7 +650,7 @@ bool ProtoSlidingMask::CanSet(UINT32 index) const return true; } } - else if (pos < num_bits) + else if ((UINT32)Difference(index, offset) < num_bits) { return true; } @@ -657,23 +667,25 @@ bool ProtoSlidingMask::CanSet(UINT32 index) const bool ProtoSlidingMask::Set(UINT32 index) { + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { // Determine position with respect to current start - // and end, given the "offset" of the current start - INT32 pos = Delta(index, offset); - if (pos < 0) + // and end, given the "offset" of the current start + UINT32 pos; + if (Compare(index, offset) < 0) { // Precedes start. - pos += start; - if (pos < 0) pos += num_bits; - if (pos < 0) + INT32 deltaPos = start + Difference(index, offset); + if (deltaPos < 0) deltaPos += num_bits; + if (deltaPos < 0) { // out of range return false; } else { + pos = (UINT32)deltaPos; // Is pos between end & start? if (end < start) { @@ -688,25 +700,29 @@ bool ProtoSlidingMask::Set(UINT32 index) offset = index; } } - else if (pos < num_bits) + else { - pos += start; - if (pos >= num_bits) pos -= num_bits; - if (end < start) + pos = Difference(index, offset); + if (pos < num_bits) { - if ((pos < start) && (pos > end)) end = pos; + pos += start; + if (pos >= num_bits) pos -= num_bits; + if (end < start) + { + if ((pos < start) && (pos > end)) end = pos; + } + else + { + if ((pos > end) || (pos < start)) end = pos; + } } else { - if ((pos > end) || (pos < start)) end = pos; + return false; // out of range } } - else - { - return false; // out of range - } ASSERT((pos >> 3) >= 0); - ASSERT((pos >> 3) < (INT32)mask_len); + ASSERT((pos >> 3) < mask_len); mask[(pos >> 3)] |= (0x80 >> (pos & 0x07)); } else @@ -720,97 +736,102 @@ bool ProtoSlidingMask::Set(UINT32 index) bool ProtoSlidingMask::Unset(UINT32 index) { - //ASSERT(CanSet(index)); + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { - INT32 pos = Delta(index, offset); - if (pos < 0) + if (Compare(index, offset) < 0) { return true; // out-of-range } - else if (pos < num_bits) + else { - // Is it in current range of set bits? - pos += start; - if (pos >= num_bits) pos -= num_bits; - if (end < start) + UINT32 pos = Difference(index, offset); + if (pos < num_bits) { - if ((pos > end) && (pos < start)) return true; + // Is it in current range of set bits? + pos += start; + if (pos >= num_bits) pos -= num_bits; + if (end < start) + { + if ((pos > end) && (pos < start)) return true; + } + else + { + if ((pos < start) || (pos > end)) return true; + } + // Yes, it was in range. + // Unset the corresponding bit + ASSERT((pos >> 3) >= 0); + ASSERT((pos >> 3) < mask_len); + mask[(pos >> 3)] &= ~(0x80 >> (pos & 0x07)); + if (start == end) + { + ASSERT(pos == start); + start = end = num_bits; + return true; + } + if (start == pos) + { + UINT32 next = index; + if (!GetNextSet(next)) + ASSERT(0); + ASSERT(Compare(next, offset) >= 0); + UINT32 delta = Difference(next, offset); + start += delta; + if (start >= num_bits) start -= num_bits; + offset = next; + } + if (pos == end) + { + UINT32 prev = index; + if (!GetPrevSet(prev)) + ASSERT(0); + ASSERT(Compare(prev, offset) >= 0); + UINT32 delta = Difference(prev, offset); + end = start + delta; + if (end >= num_bits) end -= num_bits; + } } else { - if ((pos < start) || (pos > end)) return true; - } - // Yes, it was in range. - // Unset the corresponding bit - ASSERT((pos >> 3) >= 0); - ASSERT((pos >> 3) < (INT32)mask_len); - mask[(pos >> 3)] &= ~(0x80 >> (pos & 0x07)); - if (start == end) - { - ASSERT(pos == start); - start = end = num_bits; - return true; - } - if (start == pos) - { - UINT32 next = index; - if (!GetNextSet(next)) ASSERT(0); - INT32 delta = Delta(next, offset); - ASSERT(delta >= 0); - start += delta; - if (start >= num_bits) start -= num_bits; - offset = next; - } - if (pos == end) - { - UINT32 prev = index; - if (!GetPrevSet(prev)) ASSERT(0); - INT32 delta = Delta(prev, offset); - ASSERT(delta >= 0); - end = start + delta; - if (end >= num_bits) end -= num_bits; + return true; // out-of-range } } - else - { - return true; // out-of-range - } } return true; } // end ProtoSlidingMask::Unset() -bool ProtoSlidingMask::SetBits(UINT32 index, INT32 count) +bool ProtoSlidingMask::SetBits(UINT32 index, UINT32 count) { - if (count < 0) return false; + ASSERT((0 == range_mask) || (index <= range_mask)); if (0 == count) return true; - INT32 firstPos, lastPos; + UINT32 firstPos, lastPos; if (IsSet()) { - INT32 last = (index + count - 1) & range_mask; + UINT32 last = (index + count - 1); + if (range_mask) last &= range_mask; if (!CanSet(index)) return false; if (!CanSet(last)) return false; // Calculate first set bit position - firstPos = Delta(index, offset); - if (firstPos < 0) + if (Compare(index, offset) < 0) { // precedes start - firstPos += start; - if (firstPos < 0) firstPos += num_bits; - start = firstPos; + INT32 deltaPos = start + Difference(index, offset); + if (deltaPos < 0) deltaPos += num_bits; + // The "CanSet()" checks above guarantee positive "deltaPos" here + start = firstPos = (UINT32)deltaPos; offset = index; } else { - firstPos += start; + firstPos = start + Difference(index, offset); if (firstPos >= num_bits) firstPos -= num_bits; } // Calculate last set bit position - lastPos = Delta(last , offset); - if (lastPos > 0) + if (Compare(last, offset) > 0) { // Is post start, post end? - lastPos += start; + lastPos = start + Difference(last, offset); if (lastPos >= num_bits) lastPos -= num_bits; if (end < start) { @@ -823,36 +844,35 @@ bool ProtoSlidingMask::SetBits(UINT32 index, INT32 count) } else { - lastPos += start; - if (lastPos < 0) lastPos += num_bits; + INT32 deltaPos = start + Difference(last, offset); + if (deltaPos < 0) deltaPos += num_bits; + // The "CanSet()" checks above guarantee positive "deltaPos" here + lastPos = (UINT32)deltaPos; } if (lastPos < firstPos) { // Set bits from firstPos to num_bits count = num_bits - firstPos; - INT32 maskIndex = firstPos >> 3; - int bitIndex = firstPos & 0x07; - int bitRemainder = 8 - bitIndex; - ASSERT(maskIndex >= 0); + UINT32 maskIndex = firstPos >> 3; + UINT32 bitIndex = firstPos & 0x07; + UINT32 bitRemainder = 8 - bitIndex; + ASSERT(maskIndex < mask_len); if (count <= bitRemainder) { - ASSERT(maskIndex < (INT32)mask_len); mask[maskIndex] |= (0x00ff >> bitIndex) & (0x00ff << (bitRemainder - count)); } else { - ASSERT(maskIndex < (INT32)mask_len); mask[maskIndex] |= 0x00ff >> bitIndex; count -= bitRemainder; - INT32 nbytes = count >> 3; - ASSERT((maskIndex+1+nbytes) <= (INT32)mask_len); + UINT32 nbytes = count >> 3; + ASSERT((maskIndex+1+nbytes) <= mask_len); memset(&mask[++maskIndex], 0xff, nbytes); count &= 0x07; if (count) { - ASSERT((maskIndex+nbytes) >= 0); - ASSERT((maskIndex+nbytes) < (INT32)mask_len); + ASSERT((maskIndex+nbytes) < mask_len); mask[maskIndex+nbytes] |= 0xff << (8-count); } } @@ -863,97 +883,100 @@ bool ProtoSlidingMask::SetBits(UINT32 index, INT32 count) { if (count > num_bits) return false; start = firstPos = 0; - end = lastPos = (INT32)(count - 1); + end = lastPos = (count - 1); offset = index; } // Set bits from firstPos to lastPos count = lastPos - firstPos + 1; - INT32 maskIndex = firstPos >> 3; - int bitIndex = firstPos & 0x07; - int bitRemainder = 8 - bitIndex; - ASSERT(maskIndex >= 0); + UINT32 maskIndex = firstPos >> 3; + UINT32 bitIndex = firstPos & 0x07; + UINT32 bitRemainder = 8 - bitIndex; + ASSERT(maskIndex < mask_len); if (count <= bitRemainder) { - ASSERT(maskIndex < (INT32)mask_len); mask[maskIndex] |= (0x00ff >> bitIndex) & (0x00ff << (bitRemainder - count)); } else { - ASSERT(maskIndex < (INT32)mask_len); mask[maskIndex] |= 0x00ff >> bitIndex; count -= bitRemainder; - INT32 nbytes = count >> 3; - ASSERT((maskIndex+1+nbytes) <= (INT32)mask_len); + UINT32 nbytes = count >> 3; + ASSERT((maskIndex+1+nbytes) <= mask_len); memset(&mask[++maskIndex], 0xff, nbytes); count &= 0x07; if (count) { - ASSERT((maskIndex+nbytes) >= 0); - ASSERT((maskIndex+nbytes) < (INT32)mask_len); + ASSERT((maskIndex+nbytes) < mask_len); mask[maskIndex+nbytes] |= 0xff << (8-count); } } return true; } // end ProtoSlidingMask::SetBits() -bool ProtoSlidingMask::UnsetBits(UINT32 index, INT32 count) +bool ProtoSlidingMask::UnsetBits(UINT32 index, UINT32 count) { + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { // Trim to fit as needed. - INT32 firstPos; - if (count <= 0) return true; + UINT32 firstPos; + if (0 == count) return true; if (count > num_bits) count = num_bits; - INT32 delta = Delta(index, offset); - if (delta >= num_bits) - { - return true; - } - else if (delta <= 0) + if (Compare(index, offset) < 0) { - firstPos = start; - count += delta; - if (count <= 0) return true; + // index precedes offset + firstPos = start; + UINT32 diff = Difference(offset, index); + if (diff >= count) + { + count = 0; + return true; + } + count -= diff; } else { - firstPos = start + delta; + UINT32 diff = Difference(index, offset); + if (diff >= num_bits) + return true; //beyond range + firstPos = start + diff; if (firstPos >= num_bits) firstPos -= num_bits; - } + } UINT32 lastSet; - if (!GetLastSet(lastSet)) ASSERT(0); - delta = Delta(((index+count-1) & range_mask), lastSet); - INT32 lastPos; - if (delta < 0) - { + if (!GetLastSet(lastSet)) + ASSERT(0); + UINT32 endex = index + count - 1; + if (range_mask) endex &= range_mask; + UINT32 lastPos; + if (Compare(endex, lastSet) < 0) + { lastPos = firstPos + count - 1; - if (lastPos >= num_bits) lastPos -= num_bits; + if (lastPos >= num_bits) lastPos -= num_bits; } else { lastPos = end; } - INT32 startPos; + UINT32 startPos; if (lastPos < firstPos) { // Clear bits from firstPos to num_bits count = num_bits - firstPos; - INT32 maskIndex = firstPos >> 3; - int bitIndex = firstPos & 0x07; - int bitRemainder = 8 - bitIndex; + UINT32 maskIndex = firstPos >> 3; + UINT32 bitIndex = firstPos & 0x07; + UINT32 bitRemainder = 8 - bitIndex; + ASSERT(maskIndex < mask_len); if (count <= bitRemainder) { - ASSERT(maskIndex < (INT32)mask_len); mask[maskIndex] &= (0x00ff << bitRemainder) | (0x00ff >> (bitIndex + count)); } else { - ASSERT(maskIndex < mask_len); mask[maskIndex] &= 0x00ff << bitRemainder; count -= bitRemainder; - INT32 nbytes = count >> 3; + UINT32 nbytes = count >> 3; ASSERT((maskIndex+1+nbytes) <= mask_len); memset(&mask[++maskIndex], 0, nbytes); count &= 0x07; @@ -971,21 +994,20 @@ bool ProtoSlidingMask::UnsetBits(UINT32 index, INT32 count) } // Unset bits from firstPos to lastPos count = lastPos - startPos + 1; - INT32 maskIndex = startPos >> 3; - int bitIndex = startPos & 0x07; - int bitRemainder = 8 - bitIndex; + UINT32 maskIndex = startPos >> 3; + UINT32 bitIndex = startPos & 0x07; + UINT32 bitRemainder = 8 - bitIndex; + ASSERT(maskIndex < mask_len); if (count <= bitRemainder) { - ASSERT(maskIndex < mask_len); mask[maskIndex] &= (0x00ff << bitRemainder) | (0x00ff >> (bitIndex + count)); } else { - ASSERT(maskIndex < mask_len); mask[maskIndex] &= 0x00ff << bitRemainder; count -= bitRemainder; - INT32 nbytes = count >> 3; + UINT32 nbytes = count >> 3; ASSERT((maskIndex+1+nbytes) <= mask_len); memset(&mask[++maskIndex], 0, nbytes); count &= 0x07; @@ -1019,11 +1041,12 @@ bool ProtoSlidingMask::UnsetBits(UINT32 index, INT32 count) bool ProtoSlidingMask::Test(UINT32 index) const { + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { - INT32 pos = Delta(index , offset); - if (pos >= 0) + if (Compare(index, offset) >= 0) { + UINT32 pos = Difference(index, offset); // Is it in range? if (pos >= num_bits) return false; pos += start; @@ -1044,15 +1067,15 @@ bool ProtoSlidingMask::Test(UINT32 index) const return false; } // end ProtoSlidingMask::Test() - bool ProtoSlidingMask::GetNextSet(UINT32& index) const { + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { UINT32 next = index; - INT32 pos = Delta(next, offset); - if (pos >= 0) + if (Compare(next, offset) >= 0) { + UINT32 pos = Difference(next, offset); // Is it in range? if (pos >= num_bits) return false; pos += start; @@ -1067,7 +1090,7 @@ bool ProtoSlidingMask::GetNextSet(UINT32& index) const if ((pos < start) || (pos > end)) return false; } // Seek next set bit - INT32 maskIndex = pos >> 3; + UINT32 maskIndex = pos >> 3; if (mask[maskIndex]) { int w = ProtoBitmask::WEIGHT[mask[maskIndex]]; @@ -1078,10 +1101,12 @@ bool ProtoSlidingMask::GetNextSet(UINT32& index) const if (loc >= remainder) { pos = (maskIndex << 3) + loc; - pos -= start; - if (pos < 0) pos += num_bits; + if (pos >= start) + pos -= start; + else + pos = num_bits - (start - pos); index = offset + pos; - index &= range_mask; + if (range_mask) index &= range_mask; return true; } } @@ -1094,25 +1119,29 @@ bool ProtoSlidingMask::GetNextSet(UINT32& index) const if (mask[maskIndex]) { pos = (maskIndex << 3) + ProtoBitmask::BITLOCS[mask[maskIndex]][0]; - pos -= start; - if (pos < 0) pos += num_bits; - index = offset + pos; - index &= range_mask; + if (pos >= start) + pos -= start; + else + pos = num_bits - (start - pos); + index = offset + pos; + if (range_mask) index &= range_mask; return true; } } maskIndex = 0; } - INT32 endIndex = end >> 3; + UINT32 endIndex = end >> 3; for (; maskIndex <= endIndex; maskIndex++) { if (mask[maskIndex]) { pos = (maskIndex << 3) + ProtoBitmask::BITLOCS[mask[maskIndex]][0]; - pos -= start; - if (pos < 0) pos += num_bits; + if (pos >= start) + pos -= start; + else + pos = num_bits - (start - pos); index = offset + pos; - index &= range_mask; + if (range_mask) index &= range_mask; return true; } } @@ -1128,12 +1157,13 @@ bool ProtoSlidingMask::GetNextSet(UINT32& index) const bool ProtoSlidingMask::GetPrevSet(UINT32& index) const { + ASSERT((0 == range_mask) || (index <= range_mask)); if (IsSet()) { UINT32 prev = index; - INT32 pos = Delta(prev, offset); - if (pos >= 0) + if (Compare(prev, offset) >= 0) { + UINT32 pos = Difference(prev, offset); // Is it in range? if (pos >= num_bits) { @@ -1159,7 +1189,7 @@ bool ProtoSlidingMask::GetPrevSet(UINT32& index) const } } // Seek prev set bits, starting with index - INT32 maskIndex = pos >> 3; + UINT32 maskIndex = pos >> 3; if (mask[maskIndex]) { int w = ProtoBitmask::WEIGHT[mask[maskIndex]] - 1; @@ -1170,10 +1200,12 @@ bool ProtoSlidingMask::GetPrevSet(UINT32& index) const if (loc <= remainder) { pos = (maskIndex << 3) + loc; - pos -= start; - if (pos < 0) pos += num_bits; + if (pos >= start) + pos -= start; + else + pos = num_bits - (start - pos); index = offset + pos; - index &= range_mask; + if (range_mask) index &= range_mask; return true; } } @@ -1181,33 +1213,38 @@ bool ProtoSlidingMask::GetPrevSet(UINT32& index) const maskIndex--; if (pos < start) { - for(; maskIndex >= 0; maskIndex--) + //for(; maskIndex >= 0; maskIndex--) + for(; maskIndex != 0xffffffff; maskIndex--) { if (mask[maskIndex]) { int w = ProtoBitmask::WEIGHT[mask[maskIndex]] - 1; pos = (maskIndex << 3) + ProtoBitmask::BITLOCS[mask[maskIndex]][w]; - pos -= start; - if (pos < 0) pos += num_bits; + if (pos >= start) + pos -= start; + else + pos = num_bits - (start - pos); index = offset + pos; - index &= range_mask; + if (range_mask) index &= range_mask; return true; } } maskIndex = mask_len - 1; } - INT32 startIndex = start >> 3; + UINT32 startIndex = start >> 3; for (; maskIndex >= startIndex; maskIndex--) { if (mask[maskIndex]) { int w = ProtoBitmask::WEIGHT[mask[maskIndex]] - 1; pos = (maskIndex << 3) + ProtoBitmask::BITLOCS[mask[maskIndex]][w]; - pos -= start; - if (pos < 0) pos += num_bits; + if (pos >= start) + pos -= start; + else + pos = num_bits - (start - pos); index = offset + pos; - index &= range_mask; + if (range_mask) index &= range_mask; return true; } } @@ -1220,8 +1257,9 @@ bool ProtoSlidingMask::Copy(const ProtoSlidingMask& b) { if (b.IsSet()) { - INT32 range = b.end - b.start; - if (range < 0) range += b.num_bits; + UINT32 range = (b.end >= b.start) ? + (b.end - b.start) : + (b.num_bits - (b.start - b.end)); if (range <= num_bits) { start = b.start & 0x07; @@ -1229,11 +1267,11 @@ bool ProtoSlidingMask::Copy(const ProtoSlidingMask& b) b.GetLastSet(bLastSet); UINT32 bFirstSet; b.GetFirstSet(bFirstSet); - end = bLastSet - bFirstSet + start; + end = Difference(bLastSet, bFirstSet) + start; offset = b.offset; // Copy start to mask_len - INT32 startIndex = b.start >> 3; - INT32 endIndex = b.end >> 3; + UINT32 startIndex = b.start >> 3; + UINT32 endIndex = b.end >> 3; if (b.end < b.start) { ASSERT((b.mask_len - startIndex) <= mask_len); @@ -1286,16 +1324,19 @@ bool ProtoSlidingMask::Add(const ProtoSlidingMask& b) UINT32 bLastSet; b.GetFirstSet(bLastSet); if (!CanSet(bLastSet)) return false; - INT32 range = b.end - b.start; - if (range < 0) range += b.num_bits; + + UINT32 range = (b.end >= b.start) ? + (b.end - b.start) : + (b.num_bits - (b.start - b.end)); UINT32 index; b.GetFirstSet(index); - for (INT32 i = 0; i < range; i++) + for (UINT32 i = 0; i <= range; i++) { // (TBD) Improve performance by getting/setting // ranges of set bits. if (b.Test(index)) Set(index); index++; + if (range_mask) index &= range_mask; } return true; } @@ -1311,18 +1352,17 @@ bool ProtoSlidingMask::Add(const ProtoSlidingMask& b) // This leaves us with bits uniquely set in "this" bool ProtoSlidingMask::Subtract(const ProtoSlidingMask& b) { - if (b.IsSet()) + if (IsSet() && b.IsSet()) { - if (IsSet()) + UINT32 index = offset; + UINT32 range = (end >= start) ? + (end - start) : + (num_bits - (start - end)); + for (UINT32 i = 0; i <= range; i++) { - UINT32 index = offset; - INT32 range = end - start; - if (range < 0) range += num_bits; - for (INT32 i = 0; i < range; i++) - { - if (Test(index) && b.Test(index)) Unset(index); - index++; - } + if (Test(index) && b.Test(index)) Unset(index); + index++; + if (range_mask) index &= range_mask; } } return true; @@ -1336,23 +1376,42 @@ bool ProtoSlidingMask::XCopy(const ProtoSlidingMask& b) { if (IsSet()) { + // Make sure b's range is compatible UINT32 bFirstSet; b.GetFirstSet(bFirstSet); if (!CanSet(bFirstSet)) return false; UINT32 bLastSet; b.GetFirstSet(bLastSet); if (!CanSet(bLastSet)) return false; - UINT32 index; - b.GetFirstSet(index); - INT32 range = b.end - b.start; - if (range < 0) range += b.num_bits; - for (INT32 i = 0; i < range; i++) + UINT32 firstSet; + GetFirstSet(firstSet); + UINT32 lastSet; + GetLastSet(lastSet); + // Clear any bits prior to 'bFirstSet' + if (Compare(firstSet, bFirstSet) < 0) + { + UINT32 numBits = Difference(bFirstSet, firstSet); + UnsetBits(firstSet, numBits); + } + // Clear any bits after 'bLastSet' + if (Compare(lastSet, bLastSet) > 0) + { + UINT32 numBits = Difference(lastSet, bLastSet); + UnsetBits(bLastSet, numBits); + } + // Perform XCopy() operation on the common range + UINT32 range = (b.end >= b.start) ? + (b.end - b.start) : + (b.num_bits - (b.start - b.end)); + UINT32 index = bFirstSet; + for (UINT32 i = 0; i <= range; i++) { if (Test(index)) Unset(index); else if (b.Test(index)) Set(index); - index++; + index++; + if (b.range_mask) index &= range_mask; } } else @@ -1375,12 +1434,14 @@ bool ProtoSlidingMask::Multiply(const ProtoSlidingMask& b) if (IsSet()) { UINT32 index = offset; - INT32 range = end - start; - if (range < 0) range += num_bits; - for (INT32 i = 0; i < range; i++) + UINT32 range = (end >= start) ? + (end - start) : + (num_bits - (start - end)); + for (UINT32 i = 0; i <= range; i++) { if (Test(index) && !b.Test(index)) Unset(index); - index++; + index++; + if (range_mask) index &= range_mask; } } } @@ -1405,12 +1466,14 @@ bool ProtoSlidingMask::Xor(const ProtoSlidingMask& b) if (!CanSet(bLastSet)) return false; UINT32 index; b.GetFirstSet(index); - INT32 range = b.end - b.start; - if (range < 0) range += b.num_bits; - for (INT32 i = 0; i < range; i++) + UINT32 range = (b.end >= b.start) ? + (b.end - b.start) : + (b.num_bits - (b.start - b.end)); + for (UINT32 i = 0; i <= range; i++) { if (b.Test(index)) Invert(index); index++; + if (range_mask) index &= range_mask; } } return true; @@ -1419,25 +1482,29 @@ bool ProtoSlidingMask::Xor(const ProtoSlidingMask& b) void ProtoSlidingMask::Display(FILE* stream) { UINT32 index = offset; - for (INT32 i = 0; i < num_bits; i++) + for (UINT32 i = 0; i < num_bits; i++) { - if (Test(index++)) fprintf(stream, "1"); else fprintf(stream, "0"); + if (Test(index)) fprintf(stream, "1"); else fprintf(stream, "0"); if (0x07 == (i & 0x07)) fprintf(stream, " "); if (0x3f == (i & 0x3f)) fprintf(stream, "\n"); + index++; + if (range_mask) index &= range_mask; } } // end ProtoSlidingMask::Display() -void ProtoSlidingMask::Debug(INT32 theCount) +void ProtoSlidingMask::Debug(UINT32 theCount) { UINT32 index = offset; theCount = MIN(theCount, num_bits); - PLOG(PL_ERROR, "ProtoSlidingMask::Debug() offset:%lu\n ", index); - INT32 i; + PLOG(PL_ERROR, "ProtoSlidingMask::Debug() offset:%lu\n ", (unsigned long)index); + UINT32 i; for (i = 0; i < theCount; i++) { - if (Test(index++)) PLOG(PL_ERROR, "1"); else PLOG(PL_ERROR, "0"); + if (Test(index)) PLOG(PL_ERROR, "1"); else PLOG(PL_ERROR, "0"); if (0x07 == (i & 0x07)) PLOG(PL_ERROR, " "); if (0x3f == (i & 0x3f)) PLOG(PL_ERROR, "\n "); + index++; + if (range_mask) index &= range_mask; } if (0x3f != (i & 0x3f)) PLOG(PL_ERROR, "\n"); } // end ProtoSlidingMask::Debug() diff --git a/src/common/protoCap.cpp b/src/common/protoCap.cpp index b49573f..cdc6fa6 100644 --- a/src/common/protoCap.cpp +++ b/src/common/protoCap.cpp @@ -10,6 +10,7 @@ */ #include "protoCap.h" +#include "protoDebug.h" // (TBD) How bad would it really be to inline these in the "ProtoCap" class definition? // (The we could get rid of this file) @@ -19,7 +20,7 @@ * */ ProtoCap::ProtoCap() - : if_index(-1), user_data(NULL) + : if_index(0), user_data(NULL) { // Enable input notification by default for ProtoCap StartInputNotification(); @@ -30,3 +31,42 @@ ProtoCap::~ProtoCap() { if (IsOpen()) Close(); } + + +/** + * @brief Changes the source mac addr to our own and writes packet to the pcap device + * + * 802.3 frames are not supported + * + * @param buffer + * @param buflen + * + * @return success or failure indicator + */ +bool ProtoCap::Forward(char* buffer, unsigned int& numBytes) +{ + // Change the src MAC addr to our own + // (TBD) allow caller to specify dst MAC addr ??? + memcpy(buffer+6, if_addr.GetRawHostAddress(), 6); + return Send(buffer, numBytes); +} // end ProtoCap::Forward() + + + +/** + * @brief Changes the source mac addr to specified srcMacAddr and writes packet pcap device + * + * 802.3 frames are not supported + * + * @param buffer + * @param buflen + * + * @return success or failure indicator + */ +bool ProtoCap::ForwardFrom(char* buffer, unsigned int& numBytes, const ProtoAddress& srcMacAddr) +{ + // Change the src MAC addr to our own + // (TBD) allow caller to specify dst MAC addr ??? + memcpy(buffer+6, srcMacAddr.GetRawHostAddress(), 6); + return Send(buffer, numBytes); +} // end ProtoCap::ForwardFrom() diff --git a/src/common/protoChannel.cpp b/src/common/protoChannel.cpp index 5723e9c..3e20c77 100644 --- a/src/common/protoChannel.cpp +++ b/src/common/protoChannel.cpp @@ -16,15 +16,18 @@ const ProtoChannel::Handle ProtoChannel::INVALID_HANDLE = -1; #endif // if/else WIN32/UNIX ProtoChannel::ProtoChannel() - : listener(NULL), notifier(NULL), notify_flags(0), blocking_status(true), #ifdef WIN32 - input_handle(INVALID_HANDLE), input_ready(false), - output_handle(INVALID_HANDLE), output_ready(false) + : input_handle(INVALID_HANDLE), input_event_handle(INVALID_HANDLE), input_ready(false), + output_handle(INVALID_HANDLE), output_event_handle(INVALID_HANDLE), output_ready(false), #else - descriptor(INVALID_HANDLE) + : descriptor(INVALID_HANDLE), blocking_status(true), #endif // if/else WIN32/UNIX + listener(NULL), notifier(NULL), notify_flags(0) { - +#ifdef WIN32 + overlapped_read_buffer = NULL; + overlapped_write_buffer = NULL; +#endif } ProtoChannel::~ProtoChannel() @@ -37,7 +40,6 @@ ProtoChannel::~ProtoChannel() } } - bool ProtoChannel::SetNotifier(ProtoChannel::Notifier* theNotifier) { if (notifier != theNotifier) @@ -45,10 +47,10 @@ bool ProtoChannel::SetNotifier(ProtoChannel::Notifier* theNotifier) if (IsOpen()) { // 1) Detach old notifier, if any - if (notifier) + if (NULL != notifier) { notifier->UpdateChannelNotification(*this, 0); - if (!theNotifier) + if (NULL == theNotifier) { // Reset channel to "blocking" if(!SetBlocking(true)) @@ -80,19 +82,118 @@ bool ProtoChannel::SetNotifier(ProtoChannel::Notifier* theNotifier) return true; } // end ProtoChannel::SetNotifier() +void ProtoChannel::Close() +{ + { + if (IsOpen()) + { + StopInputNotification(); + StopOutputNotification(); + } + } +#ifdef WIN32 + // This cleans up things if ProtoChannel overlapped i/o was used + if (NULL != overlapped_write_buffer) + { + CloseHandle(input_event_handle); + input_event_handle = NULL; + CloseHandle(output_event_handle); + output_event_handle = NULL; + delete[] overlapped_read_buffer; + overlapped_read_buffer = NULL; + delete[] overlapped_write_buffer; + overlapped_write_buffer = NULL; + } +#endif // WIN32 + +} // end ProtoChannel::Close() + -bool ProtoChannel::UpdateNotification() +bool ProtoChannel::StartInputNotification() +{ + if (!InputNotification()) + { +#ifdef WIN32 + // See if we're using overlapped i/o and kickstart if applicable + if ((NULL != overlapped_read_buffer) && (NULL != notifier)) + { + notify_flags |= (int)NOTIFY_INPUT; + if (!StartOverlappedRead()) // note it calls UpdateNotification() for us + { + notify_flags &= ~((int)NOTIFY_INPUT); + PLOG(PL_ERROR, "ProtoChannel::StartInputNotification() error: overlapped read startup failure!\n"); + return false; + } + return true; + } +#endif // WIN32 + if (0 != (notify_flags & (int)NOTIFY_INPUT)) return true; + notify_flags |= (int)NOTIFY_INPUT; + if (!UpdateNotification()) + { + notify_flags &= ~((int)NOTIFY_INPUT); + PLOG(PL_ERROR, "ProtoChannel::StartInputNotification() error: notification update failure!\n"); + return false; + } + } + return true; +} // end ProtoChannel::StartInputNotification() + +void ProtoChannel::StopInputNotification() { - if (notifier) + if (InputNotification()) + { + notify_flags &= ~((int)NOTIFY_INPUT); + UpdateNotification(); + } +} // end ProtoChannel::StopInputNotification() + +bool ProtoChannel::StartOutputNotification() +{ + if (!OutputNotification()) { - if (IsOpen() && !SetBlocking(false)) +#ifdef WIN32 + output_ready = true; +#endif // WIN32 + notify_flags |= (int)NOTIFY_OUTPUT; + if (!UpdateNotification()) { - PLOG(PL_ERROR, "ProtoChannel::UpdateNotification() SetBlocking() error\n"); - return false; + notify_flags &= ~((int)NOTIFY_OUTPUT); + PLOG(PL_ERROR, "ProtoChannel::StartOutputNotification() error: notification update failure!\n"); + return false; } - return notifier->UpdateChannelNotification(*this, notify_flags); } return true; +} // end ProtoChannel::StartInputNotification() + +void ProtoChannel::StopOutputNotification() +{ + if (OutputNotification()) + { + notify_flags &= ~((int)NOTIFY_OUTPUT); + UpdateNotification(); + } +} // end ProtoChannel::StopOutputNotification() + +bool ProtoChannel::UpdateNotification() +{ + if (NULL != notifier) + { + if (IsOpen()) + { + if (!SetBlocking(false)) + { + PLOG(PL_ERROR, "ProtoChannel::UpdateNotification() SetBlocking() error\n"); + return false; + } + return notifier->UpdateChannelNotification(*this, notify_flags); + } + return true; + } + else + { + return SetBlocking(true); + } } // end ProtoChannel::UpdateNotification() bool ProtoChannel::SetBlocking(bool blocking) @@ -119,6 +220,260 @@ bool ProtoChannel::SetBlocking(bool blocking) blocking_status = blocking; } #endif // UNIX - return true; //Note: taken care automatically under Win32 by WSAAsyncSelect(), etc -} // end ProtoChannel::SetBlocking(bool blocking) + return true; //Note: taken care automatically under Win32 by WSAAsyncSelect(), overlapped i/o, etc??? +} // end ProtoChannel::SetBlocking() + + + +#ifdef WIN32 + +bool ProtoChannel::InitOverlappedIO() +{ + // Set up event handles for overlapped i/o notifications + if (NULL == (input_event_handle = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + input_event_handle = INVALID_HANDLE_VALUE; + PLOG(PL_ERROR, "ProtoChannel::InitOverlappedIO() CreateEvent(input_event_handle) error: %s\n", ::GetErrorString()); + return false; + } + if (NULL == (output_event_handle = CreateEvent(NULL,TRUE,FALSE,NULL))) + { + output_event_handle = INVALID_HANDLE_VALUE; + PLOG(PL_ERROR, "ProtoChannel::InitOverlappedIO() CreateEvent(input_event_handle) error: %s\n", ::GetErrorString()); + CloseHandle(input_event_handle); + input_event_handle = NULL; + return false; + } + + // Initialize overlapped i/o structs + memset(&overlapped_read, 0, sizeof(overlapped_read)); + overlapped_read.hEvent = input_event_handle; + memset(&overlapped_write, 0, sizeof(overlapped_write)); + overlapped_write.hEvent = output_event_handle; + // Allocate buffers for overlapped read/write + if (NULL == overlapped_read_buffer) + { + if (NULL == (overlapped_read_buffer = new char[OVERLAPPED_BUFFER_SIZE])) + { + PLOG(PL_ERROR, "ProtoChannel::InitOverlappedIO() new overlapped_read_buffer error: %s\n", ::GetErrorString()); + CloseHandle(input_event_handle); + input_event_handle = NULL; + CloseHandle(output_event_handle); + output_event_handle = NULL; + return false; + } + } + if (NULL == overlapped_write_buffer) + { + if (NULL == (overlapped_write_buffer = new char[OVERLAPPED_BUFFER_SIZE])) + { + PLOG(PL_ERROR, "ProtoChannel::InitOverlappedIO() new overlapped_write_buffer error: %s\n", ::GetErrorString()); + CloseHandle(input_event_handle); + input_event_handle = NULL; + CloseHandle(output_event_handle); + output_event_handle = NULL; + delete[] overlapped_read_buffer; + overlapped_read_buffer = NULL; + return false; + } + } + return true; +} // end ProtoChannel::InitOverlappedIO() + +bool ProtoChannel::StartOverlappedRead() +{ + if ((NULL == overlapped_read_buffer) && !InitOverlappedIO()) + { + PLOG(PL_ERROR, "ProtoChannel::StartOverlappedRead() error: buffer allocation failed!\n"); + return false; + } + DWORD bytesRead; + if (0 != ReadFile(input_handle, overlapped_read_buffer, OVERLAPPED_BUFFER_SIZE, &bytesRead, &overlapped_read)) + { + // We got some data so use "input_ready" to get automatic notification + overlapped_read_count = bytesRead; + overlapped_read_index = 0; + input_ready = true; + } + else + { + switch(GetLastError()) + { + case ERROR_IO_PENDING: + overlapped_read_count = overlapped_read_index = 0; + input_ready = false; + break; + case ERROR_BROKEN_PIPE: + PLOG(PL_ERROR, "ProtoPipe::Listen() ReadFile() error: %s\n", ::GetErrorString()); + Close(); + return false; + } + } + return UpdateNotification(); +} // end ProtoChannel::StartOverlappedRead() + +bool ProtoChannel::OverlappedRead(char* buffer, unsigned int& numBytes) +{ + // Note when "input_ready" changes state we need to do an UpdateNotification() + bool wasInputReady = input_ready; + LPOVERLAPPED overlapPtr = NULL; + unsigned int want = numBytes; + unsigned int got = 0; + if (NULL != notifier) + { + // Only do overlapped I/O when a async i/o "notifier" is set + overlapPtr = &overlapped_read; + if (!input_ready) // an overlapped read operation is in place + { + DWORD bytesRead; + if (FALSE != GetOverlappedResult(input_handle, overlapPtr, &bytesRead, FALSE)) + { + unsigned int len = (bytesRead < want) ? bytesRead : want; + memcpy(buffer, overlapped_read_buffer, len); + got += len; + overlapped_read_index = (len < bytesRead) ? len : 0; + input_ready = true; // assume handle is ready for reading until a pending overlapped i/o + } + else + { + numBytes = 0; + switch (GetLastError()) + { + case ERROR_IO_INCOMPLETE: + // no data available yet + return true; + case ERROR_BROKEN_PIPE: + OnNotify(NOTIFY_NONE); + return false; + default: + PLOG(PL_ERROR, "ProtoPipe::Recv() GetOverlappedResult() error(%d): %s\n", + GetLastError(), ::GetErrorString()); + return false; + } + } + } + } + + // First, copy any data remaining in "overlapped_read_buffer"? + if ((overlapped_read_count > 0) && (got < want)) + { + unsigned int len = want - got; + if (len > overlapped_read_count) len = overlapped_read_count; + memcpy(buffer+got, overlapped_read_buffer+overlapped_read_index, len); + overlapped_read_count -= len; + overlapped_read_index += len; + got += len; + } + if (got < want) + { + // We need to try for more... + // Note if NULL != overlapPtr (from above), more overlapped read may be triggered + DWORD bytesRead; + unsigned int len = want - got; + if (len > OVERLAPPED_BUFFER_SIZE) len = OVERLAPPED_BUFFER_SIZE; + if (0 != ReadFile(input_handle, overlapped_read_buffer, len, &bytesRead, overlapPtr)) + { + memcpy(buffer+got, overlapped_read_buffer, bytesRead); + got += bytesRead; + input_ready = true; // assume handle is ready for reading until a pending overlapped i/o + } + else + { + switch(GetLastError()) + { + case ERROR_IO_PENDING: + overlapped_read_count = overlapped_read_index = 0; + input_ready = false; + break; + case ERROR_BROKEN_PIPE: + if (0 == got) + { + OnNotify(NOTIFY_NONE); + return false; + } + break; + default: + PLOG(PL_ERROR, "ProtoPipe::Recv() ReadFile(%d) error: %s\n", GetLastError(), ::GetErrorString()); + if (0 == got) return false; + break; + + } + } + } + numBytes = got; + if ((NULL != notifier) && (input_ready != wasInputReady)) + UpdateNotification(); + return true; + +} // end ProtoChannel::OverlappedRead() + +bool ProtoChannel::OverlappedWrite(const char* buffer, unsigned int& numBytes) +{ + // Note when "output_ready" changes state we need to do an UpdateNotification() + bool wasOutputReady = output_ready; + const char* bufferPtr = buffer; + LPOVERLAPPED overlapPtr = NULL; + + if (NULL != notifier) + { + // We only do actual overlapped i/o when an async notifier has been set + overlapPtr = &overlapped_write; + if (!output_ready) + { + DWORD bytesWritten; + if (FALSE == GetOverlappedResult(output_handle, overlapPtr, &bytesWritten, FALSE)) + { + switch (GetLastError()) + { + case ERROR_IO_INCOMPLETE: + // Still not output ready + numBytes = 0; + return true; + default: + PLOG(PL_ERROR, "ProtoChannel::OverlappedWrite() GetOverlappedResult() error: %s\n", GetErrorString()); + numBytes = 0; + return false; + } + } + } + // Copy data to overlapped i/o buffer, limiting "numBytes" as necessary + if (numBytes > OVERLAPPED_BUFFER_SIZE) numBytes = OVERLAPPED_BUFFER_SIZE; + + + memcpy(overlapped_write_buffer, buffer, numBytes); + bufferPtr = overlapped_write_buffer; + } + DWORD bytesWritten; + if (FALSE == WriteFile(output_handle, bufferPtr, numBytes, &bytesWritten, overlapPtr)) + { + switch (GetLastError()) + { + case ERROR_IO_PENDING: + // assume requested numBytes (trimmed) will be written + output_ready = false; + break; + case ERROR_BROKEN_PIPE: + numBytes = 0; + OnNotify(NOTIFY_NONE); + // no break here on purpose + default: + numBytes = 0; + PLOG(PL_ERROR,"Win32Vif::Write() WriteFile() error(%d): %s\n",GetLastError(),::GetErrorString()); + return false;; + } + } + else + { + numBytes = bytesWritten; + output_ready = true; + } + + if ((NULL != notifier) && (output_ready != wasOutputReady)) + UpdateNotification(); + + return true; +} // end ProtoChannel::OverlappedWrite() + +#endif // WIN32 + diff --git a/src/common/protoCheck.cpp b/src/common/protoCheck.cpp new file mode 100644 index 0000000..c027883 --- /dev/null +++ b/src/common/protoCheck.cpp @@ -0,0 +1,172 @@ + +#define _PROTO_CHECK_IMPL +#include "protoCheck.h" + +#include "protoDispatcher.h" // for ProtoDispatcher::Mutex to provide thread safety + +#include +#include + +#include // for fprintf() +#include // for strncpy() +#include // for std::map<> + +class ProtoCheckItem +{ + public: + ProtoCheckItem(); + ProtoCheckItem(size_t size, const char* file, int line); + ProtoCheckItem(const ProtoCheckItem& item); + ~ProtoCheckItem(); + + ProtoCheckItem& operator=(const ProtoCheckItem& item); + + size_t GetSize() const + {return mem_size;} + const char* GetFile() const + {return file_name;} + int GetLine() const + {return line_num;} + + static class ProtoChecker* proto_checker; + + private: + size_t mem_size; + char file_name[256]; // TBD PATH_MAX + int line_num; + +}; // end class ProtoCheckItem() + +typedef std::map ProtoCheckMap; + + +class ProtoChecker +{ + public: + ProtoChecker(); + ~ProtoChecker(); + + void AddItem(void* ptr, size_t size, const char* file, int line) + { + ProtoDispatcher::Lock(check_map_mutex); + check_map[ptr] = ProtoCheckItem(size, file, line); + ProtoDispatcher::Unlock(check_map_mutex); + } + void DeleteItem(void* ptr) + { + //fprintf(stderr, "proto check deleting item %p\n", ptr); + ProtoDispatcher::Lock(check_map_mutex); + check_map.erase(ptr); + ProtoDispatcher::Unlock(check_map_mutex); + } + + void LogAllocations(FILE* filePtr); + + private: + ProtoCheckMap check_map; + ProtoDispatcher::Mutex check_map_mutex; +}; + + +ProtoChecker* ProtoCheckItem::proto_checker = NULL; + +ProtoChecker::ProtoChecker() +{ +} + +ProtoChecker::~ProtoChecker() +{ +} + +void ProtoChecker::LogAllocations(FILE* filePtr) +{ + ProtoDispatcher::Lock(check_map_mutex); + for (ProtoCheckMap::iterator it=check_map.begin(); it!=check_map.end(); ++it) + { + const void* ptr = it->first; + ProtoCheckItem& item = it->second; + fprintf(filePtr, "ProtoCheck: alloc of %lu bytes for ptr %p from %s:%d\n", + (unsigned long)item.GetSize(), ptr, item.GetFile(), item.GetLine()); + } + ProtoDispatcher::Unlock(check_map_mutex); +} // end ProtoChecker::LogAllocations() + +void ProtoCheckLogAllocations(FILE* filePtr) +{ + if (NULL != ProtoCheckItem::proto_checker) + ProtoCheckItem::proto_checker->LogAllocations(filePtr); +} // end ProtoCheckLogAllocations() + +ProtoCheckItem::ProtoCheckItem() + : mem_size(0), line_num(0) +{ + file_name[0] = '\0'; +} + +ProtoCheckItem::ProtoCheckItem(size_t size, const char* file, int line) + : mem_size(size), line_num(line) +{ + file_name[255] = '\0'; + strncpy(file_name, file, 255); +} + +ProtoCheckItem::ProtoCheckItem(const ProtoCheckItem& item) + : mem_size(item.mem_size), line_num(item.line_num) +{ + file_name[255] = '\0'; + strncpy(file_name, item.file_name, 255); + +} + +ProtoCheckItem& ProtoCheckItem::operator=(const ProtoCheckItem& item) +{ + mem_size = item.mem_size; + strcpy(file_name, item.file_name); + line_num = item.line_num; + return *this; +} + +ProtoCheckItem::~ProtoCheckItem() +{ +} + + +#ifdef USE_PROTO_CHECK + +void* operator new(size_t size, const char* file, int line) +{ + void* p = malloc(size); + if (0 == p) + throw std::bad_alloc(); + if (NULL == ProtoCheckItem::proto_checker) + ProtoCheckItem::proto_checker = new ProtoChecker(); + ProtoCheckItem::proto_checker->AddItem(p, size, file, line); + return p; +} + +void operator delete(void* p) throw() +{ + if (NULL != ProtoCheckItem::proto_checker) + ProtoCheckItem::proto_checker->DeleteItem(p); + free(p); +} + +void* operator new[](size_t size, const char* file, int line) +{ + void* p = malloc(size); + if (0 == p) + throw std::bad_alloc(); + if (NULL == ProtoCheckItem::proto_checker) + ProtoCheckItem::proto_checker = new ProtoChecker(); + ProtoCheckItem::proto_checker->AddItem(p, size, file, line); + return p; +} + +void operator delete[](void* p) throw() +{ + if (NULL != ProtoCheckItem::proto_checker) + ProtoCheckItem::proto_checker->DeleteItem(p); + free(p); +} + +#endif // USE_PROTO_CHECK diff --git a/src/common/protoDebug.cpp b/src/common/protoDebug.cpp index 91dd467..7349ae4 100644 --- a/src/common/protoDebug.cpp +++ b/src/common/protoDebug.cpp @@ -24,6 +24,9 @@ #include #endif +#ifdef MACOSX +#include +#endif #if defined(PROTO_DEBUG) || defined(PROTO_MSG) // Note - the static debug_level, debug_log, etc variables are @@ -520,7 +523,7 @@ void DMSG(unsigned int level, const char *format, ...) if (debug_window.IsOpen() && ((stderr == debugLog) || (stdout == debugLog))) { char charBuffer[2048]; - charBuffer[2048] = '\0'; + charBuffer[2047] = '\0'; int count = _vsnprintf(charBuffer, 2047, format, args); #ifdef _UNICODE wchar_t wideBuffer[2048]; @@ -581,7 +584,7 @@ void PLOG(ProtoDebugLevel level, const char *format, ...) if (debug_window.IsOpen() && !debug_pipe.IsOpen() && ((stderr == debugLog) || (stdout == debugLog))) { char charBuffer[8192]; - charBuffer[8192] = '\0'; + charBuffer[8191] = '\0'; int count = _vsnprintf(charBuffer, 8191, format, args); #ifdef _UNICODE wchar_t wideBuffer[8192]; @@ -652,10 +655,11 @@ void PLOG(ProtoDebugLevel level, const char *format, ...) prio = ANDROID_LOG_DEBUG; break; case PL_TRACE: - prio = ANDROID_LOG_VERBOSE; case PL_DETAIL: /* explicit fallthrough */ case PL_MAX: /* explicit fallthrough */ case PL_ALWAYS: /* explicit fallthrough */ + prio = ANDROID_LOG_VERBOSE; + break; default: prio = ANDROID_LOG_DEFAULT; break; @@ -663,7 +667,21 @@ void PLOG(ProtoDebugLevel level, const char *format, ...) __android_log_vprint(prio, "protolib", format, args); #else fprintf(debugLog, "%s", header); - vfprintf(debugLog, format, args); + if (vfprintf(debugLog, format, args) < 0) + { + // perror() seems more resilient for some reason (at least on Mac OSX) + perror(""); + char buffer[8192]; + buffer[8191] = '\0'; + strcpy(buffer, header); + va_end(args); + va_start(args, format); + int count = vsnprintf(buffer + headerLen, 8191 - headerLen, format, args); + if ('\n' == buffer[headerLen + count - 1]) + buffer[headerLen + count - 1] = '\0'; + perror(buffer); + clearerr(debugLog); + } fflush(debugLog); #endif } @@ -786,7 +804,7 @@ void TRACE(const char *format, ...) if (debug_window.IsOpen() && ((stderr == debugLog) || (stdout == debugLog))) { char charBuffer[2048]; - charBuffer[2048] = '\0'; + charBuffer[2047] = '\0'; int count = _vsnprintf(charBuffer, 2047, format, args); #ifdef _UNICODE wchar_t wideBuffer[2048]; @@ -801,8 +819,26 @@ void TRACE(const char *format, ...) else #endif // _WIN32_WCE { - vfprintf(debugLog, format, args); +#ifdef __ANDROID__ + __android_log_vprint(ANDROID_LOG_ERROR, "protolib", format, args); +#else + if (vfprintf(debugLog, format, args) < 0) + { + // perror() seems more resilient for some reason (at least on Mac OSX) + perror(""); + char buffer[8192]; + buffer[8191] = '\0'; + va_end(args); + va_start(args, format); + int count = vsnprintf(buffer, 8191, format, args); + if ('\n' == buffer[count - 1]) + buffer[count - 1] = '\0'; + perror(buffer); + clearerr(debugLog); + } fflush(debugLog); + +#endif // if/else __ANDROID__ } va_end(args); } // end TRACE(); @@ -841,4 +877,4 @@ void ProtoAssertHandler(bool condition, const char* conditionText, const char* f if (NULL != assert_function) assert_function(condition, conditionText, fileName, lineNumber, assert_data); } // end ProtoAssertHandler() -#endif // if/else PROTO_DEBUG +#endif // PROTO_DEBUG diff --git a/src/common/protoDispatcher.cpp b/src/common/protoDispatcher.cpp index d6b424a..d260f06 100644 --- a/src/common/protoDispatcher.cpp +++ b/src/common/protoDispatcher.cpp @@ -29,13 +29,13 @@ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ********************************************************************/ - /** + +/** * @file protoDispatcher.cpp * * @brief This class provides a core around which Unix and Win32 applications using Protolib can be implemented */ - #include "protoDispatcher.h" #include @@ -62,11 +62,10 @@ const ProtoDispatcher::Descriptor ProtoDispatcher::INVALID_DESCRIPTOR = -1; ProtoDispatcher::Stream::Stream(Type theType) - : type(theType), flags(0), + : type(theType), flags(0) #ifdef WIN32 - index(-1), outdex(-1), + ,index(-1), outdex(-1) #endif // WIN32 - prev(NULL), next(NULL) { } @@ -80,35 +79,73 @@ ProtoDispatcher::ChannelStream::ChannelStream(ProtoChannel& theChannel) { } +ProtoDispatcher::TimerStream::TimerStream() + : Stream(TIMER), descriptor(INVALID_DESCRIPTOR) +{ +} + +ProtoDispatcher::TimerStream::~TimerStream() +{ +#ifdef UNIX + if (INVALID_DESCRIPTOR != descriptor) + { + close(descriptor); + descriptor = INVALID_DESCRIPTOR; + } +#endif // UNIX +} + +ProtoDispatcher::EventStream::EventStream() + : Stream(EVENT), descriptor(INVALID_DESCRIPTOR) +{ +} + +ProtoDispatcher::EventStream::~EventStream() +{ +#ifdef UNIX + if (INVALID_DESCRIPTOR != descriptor) + { + close(descriptor); + descriptor = INVALID_DESCRIPTOR; + } +#endif // UNIX +} + ProtoDispatcher::GenericStream::GenericStream(Descriptor theDescriptor) - : Stream(GENERIC), descriptor(theDescriptor), callback(NULL), client_data(NULL) + : Stream(GENERIC), myself(this), descriptor(theDescriptor), callback(NULL), client_data(NULL) { } ProtoDispatcher::ProtoDispatcher() - : socket_stream_pool(NULL), socket_stream_list(NULL), - channel_stream_pool(NULL), channel_stream_list(NULL), - generic_stream_pool(NULL), generic_stream_list(NULL), - run(false), wait_status(-1), exit_code(0), timer_delay(-1), precise_timing(false), - thread_id((ThreadId)(NULL)), priority_boost(false), thread_started(false), + : run(false), wait_status(-1), exit_code(0), timer_delay(-1), precise_timing(false), + thread_id((ThreadId)(NULL)), priority_boost(false), thread_started(false), thread_signaled(false), thread_master((ThreadId)(NULL)), suspend_count(0), signal_count(0), controller(NULL), prompt_set(false), prompt_callback(NULL), prompt_client_data(NULL) -#ifdef USE_TIMERFD - ,timer_fd(8) -#endif -#ifdef WIN32 - ,stream_array_size(DEFAULT_ITEM_ARRAY_SIZE), - stream_handles_array(stream_handles_default), - stream_ptrs_array(stream_ptrs_default), - stream_count(0), msg_window(NULL), - break_event(NULL), break_event_stream(NULL), - socket_io_pending(false) -{ +#if defined(WIN32) + ,stream_handles_array(NULL), stream_ptrs_array(NULL), + stream_array_size(0), stream_count(0), msg_window(NULL), + actual_thread_handle(NULL) + //#ifdef USE_TIMERFD - from merge not sure we need? + // ,timer_fd(8) + //#endif + +#ifdef USE_WAITABLE_TIMER + ,timer_active(false) +#endif // USE_WAITABLE_TIMER +#elif defined(USE_SELECT) + // no special initialization required +#elif defined(USE_KQUEUE) + ,kevent_queue(-1) +#elif defined(USE_EPOLL) + ,epoll_fd(-1) #else +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // !WIN32 && !USE_SELECT && !USE_KQUEUE { +#if !defined(USE_EVENTFD) && (defined(USE_SELECT) || defined(USE_EPOLL)) break_pipe_fd[0] = break_pipe_fd[1] = INVALID_DESCRIPTOR; -#endif // if/else WIN32/UNIX +#endif // UNIX } ProtoDispatcher::~ProtoDispatcher() @@ -119,42 +156,41 @@ ProtoDispatcher::~ProtoDispatcher() void ProtoDispatcher::Destroy() { Stop(); - while (channel_stream_list) - { - channel_stream_list->GetChannel().SetNotifier(NULL); - //ReleaseSocketStream(socket_stream_list); - } - while (channel_stream_pool) - { - ChannelStream* channelStream = channel_stream_pool; - channel_stream_pool = (ChannelStream*)channelStream->GetNext(); - delete channelStream; - } - while (socket_stream_list) - { - socket_stream_list->GetSocket().SetNotifier(NULL); - } - while (socket_stream_pool) - { - SocketStream* socketStream = socket_stream_pool; - socket_stream_pool = (SocketStream*)socketStream->GetNext(); - delete socketStream; - } - while (generic_stream_list) RemoveGenericStream(generic_stream_list); - while (generic_stream_pool) + + // Iterate through stream_table and disable notification + // (TBD - could we skip this and just call stream_list.Destroy()?) + Stream* stream; + StreamTable::Iterator iterator(stream_table); + while (NULL != (stream = iterator.GetNextItem())) { - GenericStream* stream = (GenericStream*)generic_stream_pool; - generic_stream_pool = (GenericStream*)stream->GetNext(); - delete stream; + switch (stream->GetType()) + { + case Stream::CHANNEL: + static_cast(stream)->GetChannel().SetNotifier(NULL); + break; + case Stream::SOCKET: + static_cast(stream)->GetSocket().SetNotifier(NULL); + break; + case Stream::GENERIC: + ReleaseGenericStream(static_cast(*stream)); + break; + case Stream::TIMER: + case Stream::EVENT: + // No timer or event streams are put in the stream_table (yet) + break; + } } + ASSERT(stream_table.IsEmpty()); + channel_stream_pool.Destroy(); + socket_stream_pool.Destroy(); + generic_stream_pool.Destroy(); + #ifdef WIN32 - if (DEFAULT_ITEM_ARRAY_SIZE != stream_array_size) + if (NULL != stream_handles_array) { delete[] stream_handles_array; - stream_handles_array = stream_handles_default; delete[] stream_ptrs_array; - stream_ptrs_array = stream_ptrs_default; - stream_array_size = DEFAULT_ITEM_ARRAY_SIZE; + stream_array_size = 0; } stream_count = 0; Win32Cleanup(); @@ -165,79 +201,115 @@ bool ProtoDispatcher::UpdateChannelNotification(ProtoChannel& theChannel, int notifyFlags) { SignalThread(); + // Find stream in our "wait" list, or add it to the list ... ChannelStream* channelStream = GetChannelStream(theChannel); - if (channelStream) + + if (NULL != channelStream) { - if (0 != notifyFlags) + + if ((channelStream->GetFlags() == notifyFlags) && (0 != notifyFlags)) { -#ifdef WIN32 - // Deal with input aspect - int index = channelStream->GetIndex(); - if (index < 0) + // no notification change needed, but maybe input/output ready change below + ASSERT(0 != notifyFlags); + } + else if (0 != notifyFlags) + { + // Determine what notification changes are needed for this stream and make them + if (0 != (notifyFlags & ProtoChannel::NOTIFY_INPUT)) { - // Not yet installed for input notification, so install if needed - if (0 != (notifyFlags & ProtoChannel::NOTIFY_INPUT)) + if (!channelStream->IsInput()) // Enable system-specific input notification { - if ((index = Win32AddStream(*channelStream, theChannel.GetInputHandle())) < 0) + if (!UpdateStreamNotification(*channelStream, ENABLE_INPUT)) { - PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error adding input stream\n"); - if (channelStream->GetOutdex() < 0) - ReleaseChannelStream(channelStream); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error: unable to ENABLE_INPUT!\n"); UnsignalThread(); return false; } - channelStream->SetIndex(index); } - } - else if (0 == (notifyFlags & ProtoChannel::NOTIFY_INPUT)) - { - Win32RemoveStream(index); - channelStream->SetIndex(-1); - } + } else { - // Update handle in case it has changed - stream_handles_array[index] = theChannel.GetInputHandle(); - } - // Deal with output aspect - int outdex = channelStream->GetOutdex(); - if (outdex < 0) - { - if (0 != (notifyFlags & ProtoChannel::NOTIFY_OUTPUT)) + if (channelStream->IsInput()) // Disable system-specific input notification { - if ((outdex = Win32AddStream(*channelStream, theChannel.GetOutputHandle())) < 0) + if (!UpdateStreamNotification(*channelStream, DISABLE_INPUT)) { - PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error adding output stream\n"); - if (channelStream->GetIndex() < 0) - ReleaseChannelStream(channelStream); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error: unable to DISABLE_INPUT!\n"); UnsignalThread(); return false; } - channelStream->SetOutdex(outdex); } - } - else if (0 == (notifyFlags & ProtoChannel::NOTIFY_OUTPUT)) + } + if (0 != (notifyFlags & ProtoChannel::NOTIFY_OUTPUT)) { - Win32RemoveStream(outdex); - channelStream->SetOutdex(-1); - } + if (!channelStream->IsOutput()) // Enable system-specific input notification + { + if (!UpdateStreamNotification(*channelStream, ENABLE_OUTPUT)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error: unable to ENABLE_OUTPUT!\n"); + UnsignalThread(); + return false; + } + } + } else { - // Update handle in case it has changed - stream_handles_array[outdex] = theChannel.GetOutputHandle(); - } -#endif // WIN32 - channelStream->SetFlags(notifyFlags); - UnsignalThread(); - return true; + if (channelStream->IsOutput()) // Disable system-specific output notification + { + if (!UpdateStreamNotification(*channelStream, DISABLE_OUTPUT)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error: unable to DISABLE_OUTPUT!\n"); + UnsignalThread(); + return false; + } + } + } + // TBD - support exception notification ??? } - else + else // 0 == notifyFlags { - ReleaseChannelStream(channelStream); // remove channel from our "wait" list + if (channelStream->HasFlags()) + { + if (!UpdateStreamNotification(*channelStream, DISABLE_ALL)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error: unable to DISABLE_ALL!\n"); + UnsignalThread(); + return false; + } + } + ReleaseChannelStream(*channelStream); UnsignalThread(); return true; + } // end if/else 0 != notifyFlags + +#ifdef WIN32 + // Add or remove channelStream from "ready_stream_list" as appropriate + // Note input/output readiness may have changed even if notification did not + if ((theChannel.IsInputReady() && channelStream->IsInput()) || + (theChannel.IsOutputReady() && channelStream->IsOutput())) + { + // Make sure our "ready" socket is in the "ready_stream_list" + if (!ready_stream_list.Contains(*channelStream)) + { + if (!ready_stream_list.Append(*channelStream)) + { + ReleaseChannelStream(*channelStream); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateChannelNotification() error: unable to append ready channel!\n"); + UnsignalThread(); + return false; + + } + } + } + else if (ready_stream_list.Contains(*channelStream)) + { + // Remove from "ready_socket_list" + ready_stream_list.Remove(*channelStream); } +#endif // WIN32 + + UnsignalThread(); + return true; } else { @@ -245,119 +317,161 @@ bool ProtoDispatcher::UpdateChannelNotification(ProtoChannel& theChannel, GetErrorString()); UnsignalThread(); return false; - } + } } // end ProtoDispatcher::UpdateChannelNotification() - bool ProtoDispatcher::UpdateSocketNotification(ProtoSocket& theSocket, int notifyFlags) { - SignalThread(); + SignalThread(); // TBD - check result? SocketStream* socketStream = GetSocketStream(theSocket); - if (socketStream) + if (NULL != socketStream) { - if (0 != notifyFlags) + if (socketStream->GetFlags() == notifyFlags) + { + // nothing to do up here, notification unchanged + // (but for WIN32, socket "readiness" may have changed) + // _unless_ it's a new (closed) socket or idle tcp socket + // TBD - should prob not be calling "UpdateSocketNotification()" in this case + if (0 == notifyFlags) + { + + ReleaseSocketStream(*socketStream); + UnsignalThread(); + return true; + } + } + else if (0 != notifyFlags) { #ifdef WIN32 - if (ProtoSocket::LOCAL != theSocket.GetDomain()) + // WIN32 Sockets are handled differently + int index = socketStream->GetIndex(); + if (index < 0) { - int index = socketStream->GetIndex(); - if (index < 0) - { - if ((index = Win32AddStream(*socketStream, theSocket.GetInputEventHandle())) < 0) - { - PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error adding handle\n"); - ReleaseSocketStream(socketStream); - UnsignalThread(); - return false; - } - socketStream->SetIndex(index); - } - long eventMask = 0; - if (0 != (notifyFlags & ProtoSocket::NOTIFY_INPUT)) - eventMask |= (FD_READ | FD_ACCEPT | FD_CLOSE); - if (0 != (notifyFlags & ProtoSocket::NOTIFY_OUTPUT)) - eventMask |= (FD_WRITE | FD_CONNECT | FD_CLOSE); - if (0 != (notifyFlags & ProtoSocket::NOTIFY_EXCEPTION)) - eventMask |= FD_ADDRESS_LIST_CHANGE; - // Note for IPv4/IPv6 sockets, the "input event handle" is used for both input & output - if (0 != WSAEventSelect(theSocket.GetHandle(), theSocket.GetInputEventHandle(), eventMask)) + if ((index = Win32AddStream(*socketStream, theSocket.GetInputEventHandle())) < 0) { - ReleaseSocketStream(socketStream); - PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() WSAEventSelect(0x%x) error: %s\n", - eventMask, ProtoSocket::GetErrorString()); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error adding handle\n"); + ReleaseSocketStream(*socketStream); UnsignalThread(); - return false; + return false; } + socketStream->SetIndex(index); } - else + long eventMask = 0; + if (0 != (notifyFlags & ProtoSocket::NOTIFY_INPUT)) + eventMask |= (FD_READ | FD_ACCEPT | FD_CLOSE); + if (0 != (notifyFlags & ProtoSocket::NOTIFY_OUTPUT)) + { + eventMask |= (FD_WRITE | FD_CONNECT | FD_CLOSE); + } + if (0 != (notifyFlags & ProtoSocket::NOTIFY_EXCEPTION)) + eventMask |= FD_ADDRESS_LIST_CHANGE; + // Note for IPv4/IPv6 sockets, the "input event handle" is used for both input & output + if (0 != WSAEventSelect(theSocket.GetHandle(), theSocket.GetInputEventHandle(), eventMask)) + { + ReleaseSocketStream(*socketStream); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() WSAEventSelect(0x%x) error: %s\n", + eventMask, ProtoSocket::GetErrorString()); + UnsignalThread(); + return false; + } + socketStream->SetFlags(notifyFlags); +#else // UNIX + // Determine what notification changes are needed for this stream and make them + if (0 != (notifyFlags & ProtoSocket::NOTIFY_INPUT)) { - // For "LOCAL" Win32 ProtoSockets (i.e. ProtoPipes) we manage separate input/output - // event handles for asynchronous I/O - int index = socketStream->GetIndex(); - if (index < 0) + if (!socketStream->IsInput()) // Enable system-specific input notification { - if (0 != (notifyFlags & ProtoSocket::NOTIFY_INPUT)) + if (!UpdateStreamNotification(*socketStream, ENABLE_INPUT)) { - if ((index = Win32AddStream(*socketStream, theSocket.GetInputEventHandle())) < 0) - { - PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error adding input stream\n"); - ReleaseSocketStream(socketStream); - UnsignalThread(); - return false; - } - socketStream->SetIndex(index); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error: unable to ENABLE_INPUT!\n"); + UnsignalThread(); + return false; } } - else if (0 == (notifyFlags & ProtoSocket::NOTIFY_INPUT)) - { - Win32RemoveStream(index); - socketStream->SetIndex(-1); - } - else - { - stream_handles_array[index] = theSocket.GetInputEventHandle(); - } - int outdex = socketStream->GetOutdex(); - if (outdex < 0) + } + else + { + if (socketStream->IsInput()) // Disable system-specific input notification { - if (0 != (notifyFlags & ProtoSocket::NOTIFY_OUTPUT)) + if (!UpdateStreamNotification(*socketStream, DISABLE_INPUT)) { - if ((outdex = Win32AddStream(*socketStream, theSocket.GetOutputEventHandle())) < 0) - { - PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error adding output stream\n"); - ReleaseSocketStream(socketStream); - UnsignalThread(); - return false; - } - socketStream->SetOutdex(index); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error: unable to DISABLE_INPUT!\n"); + UnsignalThread(); + return false; } } - else if (0 == (notifyFlags & ProtoSocket::NOTIFY_OUTPUT)) + } + if (0 != (notifyFlags & ProtoSocket::NOTIFY_OUTPUT)) + { + if (!socketStream->IsOutput()) // Enable system-specific input notification { - Win32RemoveStream(outdex); - socketStream->SetOutdex(-1); + if (!UpdateStreamNotification(*socketStream, ENABLE_OUTPUT)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error: unable to ENABLE_OUTPUT!\n"); + UnsignalThread(); + return false; + } } - else + } + else + { + if (socketStream->IsOutput()) // Disable system-specific output notification { - stream_handles_array[outdex] = theSocket.GetOutputEventHandle(); + if (!UpdateStreamNotification(*socketStream, DISABLE_OUTPUT)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error: unable to DISABLE_OUTPUT!\n"); + UnsignalThread(); + return false; + } } - } -#endif // WIN32 - socketStream->SetFlags(notifyFlags); + } + // TBD - support exception notification ??? +#endif // if/else WIN32/UNIX } - else + else // 0 == notifyFlags { + ASSERT(socketStream->HasFlags()); #ifdef WIN32 - if (socketStream->HasFlags() && ProtoSocket::LOCAL != theSocket.GetDomain()) + if (0 != WSAEventSelect(theSocket.GetHandle(), theSocket.GetInputEventHandle(), 0)) + PLOG(PL_WARN, "ProtoDispatcher::UpdateSocketNotification() WSAEventSelect(0) warning: %s\n", + ProtoSocket::GetErrorString()); +#endif // WIN32 + if (!UpdateStreamNotification(*socketStream, DISABLE_ALL)) { - if (0 != WSAEventSelect(theSocket.GetHandle(), theSocket.GetInputEventHandle(), 0)) - PLOG(PL_WARN, "ProtoDispatcher::UpdateSocketNotification() WSAEventSelect(0) warning: %s\n", - ProtoSocket::GetErrorString()); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error: unable to DISABLE_ALL!\n"); + UnsignalThread(); + return false; } -#endif // WIN32 - ReleaseSocketStream(socketStream); + ReleaseSocketStream(*socketStream); + UnsignalThread(); + return true; + } // end if/else 0 != notifyFlags +#ifdef WIN32 + // Add or remove socketStream from "ready_socket_list" as appropriate + // Note input/output readiness may have changed even if notification did not + if ((theSocket.IsInputReady() && socketStream->IsInput()) || + (theSocket.IsOutputReady() && socketStream->IsOutput())) + { + // Make sure our "ready" socket is in the "ready_stream_list" + if (!ready_stream_list.Contains(*socketStream)) + { + if (!ready_stream_list.Append(*socketStream)) + { + ReleaseSocketStream(*socketStream); + PLOG(PL_ERROR, "ProtoDispatcher::UpdateSocketNotification() error: unable to append ready socket!\n"); + UnsignalThread(); + return false; + + } + } + } + else if (ready_stream_list.Contains(*socketStream)) + { + // Remove from "ready_stream_list" + ready_stream_list.Remove(*socketStream); } +#endif // WIN32 UnsignalThread(); return true; } @@ -373,21 +487,15 @@ bool ProtoDispatcher::UpdateSocketNotification(ProtoSocket& theSocket, ProtoDispatcher::SocketStream* ProtoDispatcher::GetSocketStream(ProtoSocket& theSocket) { // First, search our list of active sockets - SocketStream* socketStream = socket_stream_list; - while (NULL != socketStream) - { - if (&theSocket == &socketStream->GetSocket()) - break; - else - socketStream = (SocketStream*)socketStream->GetNext(); - } + ProtoSocket* socketPtr = &theSocket; + SocketStream* socketStream = + static_cast(stream_table.Find((const char*)&socketPtr, sizeof(ProtoSocket*) << 3)); if (NULL == socketStream) { // Get one from the pool or create a new one - socketStream = socket_stream_pool; + socketStream = socket_stream_pool.Get(); if (NULL != socketStream) { - socket_stream_pool = (SocketStream*)socketStream->GetNext(); socketStream->ClearFlags(); socketStream->SetSocket(theSocket); } @@ -399,215 +507,230 @@ ProtoDispatcher::SocketStream* ProtoDispatcher::GetSocketStream(ProtoSocket& the return NULL; } } - // Prepend to "active" socket stream list - socketStream->SetPrev(NULL); - socketStream->SetNext(socket_stream_list); - if (socket_stream_list) - socket_stream_list->SetPrev(socketStream); - socket_stream_list = socketStream; + // Insert into "active" stream list + stream_table.Insert(*socketStream); } return socketStream; } // end ProtoDispatcher::GetSocketStream() -void ProtoDispatcher::ReleaseSocketStream(SocketStream* socketStream) -{ - socketStream->ClearFlags(); - SocketStream* prevStream = (SocketStream*)socketStream->GetPrev(); - SocketStream* nextStream = (SocketStream*)socketStream->GetNext(); - if (prevStream) - prevStream->SetNext(nextStream); - else - socket_stream_list = nextStream; - if (nextStream) nextStream->SetPrev(prevStream); - socketStream->SetNext(socket_stream_pool); - socket_stream_pool = socketStream; +void ProtoDispatcher::ReleaseSocketStream(SocketStream& socketStream) +{ #ifdef WIN32 - if (socketStream->GetIndex() >= 0) - { - Win32RemoveStream(socketStream->GetIndex()); - socketStream->SetIndex(-1); + if (ready_stream_list.Contains(socketStream)) + ready_stream_list.Remove(socketStream); + // Makes sure the channel input/output event HANDLE is removed from array + // (This removes it from list passed to MsgWaitForMultipleObjectsEx() call) + if (socketStream.GetIndex() >= 0) + { + Win32RemoveStream(socketStream.GetIndex()); + socketStream.SetIndex(-1); } - if (socketStream->GetOutdex() >= 0) + if (socketStream.GetOutdex() >= 0) { - Win32RemoveStream(socketStream->GetOutdex()); - socketStream->SetOutdex(-1); + Win32RemoveStream(socketStream.GetOutdex()); + socketStream.SetOutdex(-1); } #endif // WIN32 + if (socketStream.HasFlags()) + { + if (!UpdateStreamNotification(socketStream, DISABLE_ALL)) + PLOG(PL_ERROR, "ProtoDispatcher::ReleaseSocketStream() error: UpdateStreamNotification(DISABLE_ALL) failure!\n"); + socketStream.ClearFlags(); + } + stream_table.Remove(socketStream); + socket_stream_pool.Put(socketStream); } // end ProtoDispatcher::ReleaseSocketStream() ProtoDispatcher::ChannelStream* ProtoDispatcher::GetChannelStream(ProtoChannel& theChannel) { + // First, search our list of active channels - ChannelStream* channelStream = channel_stream_list; - while (channelStream) - { - if (&theChannel == &channelStream->GetChannel()) - break; - else - channelStream = (ChannelStream*)channelStream->GetNext(); - } - if (!channelStream) + ProtoChannel* channelPtr = &theChannel; + ChannelStream* channelStream = + static_cast(stream_table.Find((const char*)&channelPtr, sizeof(ProtoChannel*) << 3)); + + if (NULL == channelStream) { // Get one from the pool or create a new one - channelStream = channel_stream_pool; - if (channelStream) + channelStream = channel_stream_pool.Get(); + if (NULL != channelStream) { - channel_stream_pool = (ChannelStream*)channelStream->GetNext(); channelStream->ClearFlags(); channelStream->SetChannel(theChannel); } else { - if (!(channelStream = new ChannelStream(theChannel))) + if (NULL == (channelStream = new ChannelStream(theChannel))) { PLOG(PL_ERROR, "ProtoDispatcher::GetChannelStream() new ChannelStream error: %s\n", GetErrorString()); return NULL; } } - // Prepend to "active" channel stream list - channelStream->SetPrev(NULL); - channelStream->SetNext(channel_stream_list); - if (channel_stream_list) - channel_stream_list->SetPrev(channelStream); - channel_stream_list = channelStream; + // Insert into "active" stream table + stream_table.Insert(*channelStream); } return channelStream; } // end ProtoDispatcher::GetChannelStream() -void ProtoDispatcher::ReleaseChannelStream(ChannelStream* channelStream) +void ProtoDispatcher::ReleaseChannelStream(ChannelStream& channelStream) { - channelStream->ClearFlags(); - ChannelStream* prevStream = (ChannelStream*)channelStream->GetPrev(); - ChannelStream* nextStream = (ChannelStream*)channelStream->GetNext(); - if (prevStream) - prevStream->SetNext(nextStream); - else - channel_stream_list = nextStream; - if (nextStream) nextStream->SetPrev(prevStream); - channelStream->SetNext(channel_stream_pool); - channel_stream_pool = channelStream; #ifdef WIN32 - if (channelStream->GetIndex() >= 0) - { - Win32RemoveStream(channelStream->GetIndex()); - channelStream->SetIndex(-1); + if (ready_stream_list.Contains(channelStream)) + ready_stream_list.Remove(channelStream); + // Makes sure the channel input/output event HANDLE is removed from array + // (This removes it from list passed to MsgWaitForMultipleObjectsEx() call) + if (channelStream.GetIndex() >= 0) + { + Win32RemoveStream(channelStream.GetIndex()); + channelStream.SetIndex(-1); } - if (channelStream->GetOutdex() >= 0) + if (channelStream.GetOutdex() >= 0) { - Win32RemoveStream(channelStream->GetOutdex()); - channelStream->SetOutdex(-1); + Win32RemoveStream(channelStream.GetOutdex()); + channelStream.SetOutdex(-1); } #endif // WIN32 + stream_table.Remove(channelStream); + channelStream.ClearFlags(); + channel_stream_pool.Put(channelStream); } // end ProtoDispatcher::ReleaseChannelStream() -bool ProtoDispatcher::InstallGenericStream(ProtoDispatcher::Descriptor descriptor, - Callback* callback, - const void* userData, - Stream::Flag flag) -{ +bool ProtoDispatcher::InstallGenericInput(ProtoDispatcher::Descriptor descriptor, + ProtoDispatcher::Callback* callback, + const void* clientData) +{ + SignalThread(); GenericStream* stream = GetGenericStream(descriptor); - if (stream) + if (NULL != stream) { -#ifdef WIN32 - int index = stream->GetIndex(); - if (index < 0) + if (!stream->IsInput()) // Enable system-specific input notification { - if ((index = Win32AddStream(*stream, descriptor)) < 0) + if (!UpdateStreamNotification(*stream, ENABLE_INPUT)) { - PLOG(PL_ERROR, "ProtoDispatcher::InstallGenericStream() error adding stream\n"); - ReleaseGenericStream(stream); - return false; - } - stream->SetIndex(index); + PLOG(PL_ERROR, "ProtoDispatcher::InstallGenericInput() error: unable to ENABLE_INPUT!\n"); + if (!stream->HasFlags()) ReleaseGenericStream(*stream); + UnsignalThread(); + return false; + } } - else + } + stream->SetFlag(Stream::INPUT); + stream->SetCallback(callback, clientData); + UnsignalThread(); + return true;; +} // end ProtoDispatcher::InstallGenericInput() + +void ProtoDispatcher::RemoveGenericInput(Descriptor descriptor) +{ + SignalThread(); + GenericStream* stream = FindGenericStream(descriptor); + if (NULL != stream) + { + if (stream->IsInput()) { - stream_handles_array[index] = descriptor; + if (!UpdateStreamNotification(*stream, DISABLE_INPUT)) + PLOG(PL_ERROR, "ProtoDispatcher::RemoveGenericInput() error: UpdateStreamNotification(DISABLE_INPUT) failure!\n"); + stream->UnsetFlag(Stream::INPUT); } -#endif // WIN32 - stream->SetCallback(callback, userData); - stream->SetFlag(flag); - return true; - } - else + if (!stream->HasFlags()) ReleaseGenericStream(*stream); + } + UnsignalThread(); +} // end ProtoDispatcher::RemoveGenericInput() + +bool ProtoDispatcher::InstallGenericOutput(ProtoDispatcher::Descriptor descriptor, + ProtoDispatcher::Callback* callback, + const void* clientData) +{ + SignalThread(); + GenericStream* stream = FindGenericStream(descriptor); + if (NULL != stream) { - PLOG(PL_ERROR, "ProtoDispatcher::InstallGenericStream() error getting GenericStream\n"); - return false; + if (!stream->IsOutput()) // Enable system-specific output notification + { + if (!UpdateStreamNotification(*stream, ENABLE_OUTPUT)) + { + PLOG(PL_ERROR, "ProtoDispatcher::InstallGenericOutput() error: unable to ENABLE_OUTPUT!\n"); + if (!stream->HasFlags()) ReleaseGenericStream(*stream); + UnsignalThread(); + return false; + } + } } -} // end ProtoDispatcher::InstallGenericStream() + stream->SetFlag(Stream::OUTPUT); + stream->SetCallback(callback, clientData); + UnsignalThread(); + return true; +} // end ProtoDispatcher::InstallGenericOutput() + +void ProtoDispatcher::RemoveGenericOutput(Descriptor descriptor) +{ + SignalThread(); + GenericStream* stream = FindGenericStream(descriptor); + if (NULL != stream) + { + if (stream->IsOutput()) + { + if (!UpdateStreamNotification(*stream, DISABLE_OUTPUT)) + PLOG(PL_ERROR, "ProtoDispatcher::RemoveGenericOutput() error: UpdateStreamNotification(DISABLE_OUTPUT) failure!\n"); + stream->UnsetFlag(Stream::OUTPUT); + } + if (!stream->HasFlags()) ReleaseGenericStream(*stream); + } + UnsignalThread(); +} // end ProtoDispatcher::RemoveGenericOutput() ProtoDispatcher::GenericStream* ProtoDispatcher::GetGenericStream(Descriptor descriptor) { // First, search our list of active generic streams - GenericStream* stream = generic_stream_list; - while (stream) - { - if (descriptor == stream->GetDescriptor()) - break; - else - stream = (GenericStream*)stream->GetNext(); - } - if (!stream) + GenericStream* genericStream = generic_stream_table.FindByDescriptor(descriptor); + if (NULL == genericStream) { // Get one from the pool or create a new one - stream = generic_stream_pool; - if (stream) + genericStream = generic_stream_pool.Get(); + if (NULL != genericStream) { - generic_stream_pool = (GenericStream*)stream->GetNext(); - stream->ClearFlags(); - stream->SetDescriptor(descriptor); + genericStream->ClearFlags(); + genericStream->SetDescriptor(descriptor); } else { - if (!(stream = new GenericStream(descriptor))) + if (NULL == (genericStream = new GenericStream(descriptor))) { PLOG(PL_ERROR, "ProtoDispatcher::GetGenericStream() new GenericStream error: %s\n", GetErrorString()); return NULL; } } - // Add to "active" generic stream list - stream->SetPrev(NULL); - stream->SetNext(generic_stream_list); - if (generic_stream_list) generic_stream_list->SetPrev(stream); - generic_stream_list = stream; + // Insert into "active" stream table + stream_table.Insert(*genericStream); + // Insert into generic_stream_table (indexed by descriptor) + if (!generic_stream_table.Insert(*genericStream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::GetGenericStream() error: unable to add to table: %s\n", GetErrorString()); + ReleaseGenericStream(*genericStream); + return NULL; + } } - return stream; + return genericStream; } // end ProtoDispatcher::GetGenericStream() -ProtoDispatcher::GenericStream* ProtoDispatcher::FindGenericStream(Descriptor descriptor) const +void ProtoDispatcher::ReleaseGenericStream(GenericStream& genericStream) { - GenericStream* next = generic_stream_list; - while (next) + // First, disable system-specific notifications for stream + if (genericStream.IsInput()) { - if (next->GetDescriptor() == descriptor) - return next; - else - next = (GenericStream*)next->GetNext(); + if (!UpdateStreamNotification(genericStream, DISABLE_INPUT)) + PLOG(PL_ERROR, "ProtoDispatcher::ReleaseGenericStream() error: UpdateStreamNotification(DISABLE_INPUT) failure!\n"); } - return NULL; -} // end ProtoDispatcher::FindGenericStream() - -void ProtoDispatcher::ReleaseGenericStream(GenericStream* stream) -{ - stream->ClearFlags(); - // Move from "active" generic stream list to generic stream "pool" - GenericStream* prevStream = (GenericStream*)stream->GetPrev(); - GenericStream* nextStream = (GenericStream*)stream->GetNext(); - if (prevStream) - prevStream->SetNext(nextStream); - else - generic_stream_list = nextStream; - if (nextStream) nextStream->SetPrev(prevStream); - stream->SetNext(generic_stream_pool); - generic_stream_pool = stream; -#ifdef WIN32 - if (stream->GetIndex() >= 0) + if (genericStream.IsOutput()) { - Win32RemoveStream(stream->GetIndex()); - stream->SetIndex(-1); + if (!UpdateStreamNotification(genericStream, DISABLE_OUTPUT)) + PLOG(PL_ERROR, "ProtoDispatcher::ReleaseGenericStream() error: UpdateStreamNotification(DISABLE_OUTPUT) failure!\n"); } -#endif // WIN32 + genericStream.ClearFlags(); + stream_table.Remove(genericStream); + generic_stream_table.Remove(genericStream); + generic_stream_pool.Put(genericStream); } // end ProtoDispatcher::ReleaseGenericStream() @@ -642,14 +765,19 @@ bool ProtoDispatcher::BoostPriority() struct sched_param schp; memset(&schp, 0, sizeof(schp)); schp.sched_priority = sched_get_priority_max(SCHED_FIFO); - if (sched_setscheduler(0, SCHED_FIFO, &schp)) + if (0 != sched_setscheduler(0, SCHED_FIFO, &schp)) { schp.sched_priority = sched_get_priority_max(SCHED_OTHER); - if (sched_setscheduler(0, SCHED_OTHER, &schp)) + if (0 != sched_setscheduler(0, SCHED_OTHER, &schp)) { PLOG(PL_ERROR, "ProtoDispatcher::BoostPriority() error: sched_setscheduler() error: %s\n", GetErrorString()); return false; } + else + { + PLOG(PL_WARN, "ProtoDispatcher::BoostPriority() warning: unable to set SCHED_FIFO boost, SCHED_OTHER set.\n" + " (run as root or sudofor full SCHED_FIFO priority boost)\n"); + } } #else // (TBD) Do something differently if "pthread sched param"? @@ -673,14 +801,36 @@ int ProtoDispatcher::Run(bool oneShot) wait_status = -1; if (priority_boost) BoostPriority(); -#ifdef USE_TIMERFD - timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); - if (timer_fd < 0) +#ifdef USE_TIMERFD // LINUX-only + int tfd = timerfd_create(CLOCK_MONOTONIC, 0); + if (tfd < 0) { PLOG(PL_ERROR, "ProtoDispatcher::Run() timerfd_create() error: %s\n", GetErrorString()); return -1; // TBD - is there a more specific exitCode we should use instead??? } -#endif // USE_TIMERFD +#ifdef USE_EPOLL + if (!EpollChange(tfd, EPOLLIN, EPOLL_CTL_ADD, &timer_stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::Run() error EpolLChange(timer_fd) failed!\n"); + close(tfd); + return -1; // TBD - is there a more specific exitCode we should use instead??? + } +#endif + timer_stream.SetDescriptor(tfd); +#endif // USE_TIMERFD + +#ifdef USE_WAITABLE_TIMER // WIN32-only + HANDLE wtm = CreateWaitableTimer(NULL, TRUE, NULL); + if (INVALID_HANDLE_VALUE == wtm) + { + PLOG(PL_ERROR, "ProtoDispatcher::Run() CreateWaitableTimer() error: %s\n", GetErrorString()); + return -1; + } + timer_stream.SetDescriptor(wtm); + timer_stream.SetIndex(-1); // the timer_stream will added when activated + timer_active = false; +#endif // USE_WAITABLE_TIMER + // TBD - should we keep "run" true while in the do loop so "IsRunning()" // can be interpreted properly (or just deprecate IsRunning() ??? run = oneShot ? false : true; @@ -690,13 +840,23 @@ int ProtoDispatcher::Run(bool oneShot) { // Here we "latch" the next timeout _before_ // we open the suspend window to be safe - timer_delay = ProtoTimerMgr::GetTimeRemaining(); + timer_delay = ProtoTimerMgr::GetTimeRemaining(); if (IsThreaded()) { Lock(signal_mutex); Unlock(suspend_mutex); Wait(); Unlock(signal_mutex); + + // <-- this is where a dispatcher rests after SignalThread() call + + // what if another thread calls SignalThread() at this exact moment? + // and gets the suspend and signal mutexes ... + // a) WasSignaled() will be false on Unix (break_pipe fd not set) + // b) then Dispatch() will be called ... but only valid descriptors, etc are checked + // as Wait resets/builds the FD_SET + // c) next time WasSignaled() will be true, but no big deal + Lock(suspend_mutex); if (prompt_set) @@ -706,14 +866,19 @@ int ProtoDispatcher::Run(bool oneShot) prompt_callback(prompt_client_data); prompt_set = false; } - - if (WasSignaled()) + /* + if (WasSignaled()) // true" if was signaled via ProtoDispatcher::SignalThread() { + // We "continue" here in case i/o or timeout status + // was changed during signal suspend continue; } - else if (controller) + else */ + if (NULL != controller) { // Relinquish to controller thread + // Note this assumes the controller thread is the _only_ + // other thread vying for control of this dispatcher thread Unlock(suspend_mutex); controller->DoDispatch(); Lock(suspend_mutex); @@ -737,10 +902,25 @@ int ProtoDispatcher::Run(bool oneShot) } } while (run); #ifdef USE_TIMERFD - close(timer_fd); - timer_fd = INVALID_DESCRIPTOR; + // Note if USE_EPOLL, closing the "timer_fd" automatically deletes the event + close(timer_stream.GetDescriptor()); + timer_stream.SetDescriptor(INVALID_DESCRIPTOR); #endif // USE_TIMERFD - return exit_code; +#ifdef USE_WAITABLE_TIMER + if (timer_active) + { + CancelWaitableTimer(timer_stream.GetDescriptor()); + timer_active = false; + } + if (timer_stream.GetIndex() >= 0) + { + Win32RemoveStream(timer_stream.GetIndex()); + timer_stream.SetIndex(-1); + } + CloseHandle(timer_stream.GetDescriptor()); + timer_stream.SetDescriptor(INVALID_DESCRIPTOR); +#endif // USE_WAITABLE_TIMER + return exit_code; } // end ProtoDispatcher::Run() void ProtoDispatcher::Stop(int exitCode) @@ -824,55 +1004,75 @@ bool ProtoDispatcher::StartThread(bool priorityBoost, return true; } // end ProtoDispatcher::StartThread() + /** - * This brings the thread to a suspended state _outside_ - * of _its_ ProtoDispatcher::Wait() state + * This wakes up the thread, makes a call to the installed prompt_callback + * and the thread goes back to waiting. This is the way to have a ProtoDispatcher + * thread perform some "work" function. It currently does not have a means to + * signal when the work is completed other than this call (due to SuspendThread() call) + * will block until the work is done. TBD - provide a method to access/monitor/wait on + * the "suspend_mutex" that will be unlocked when the thread goes back to "sleep" */ -bool ProtoDispatcher::SignalThread() +bool ProtoDispatcher::PromptThread() { - SuspendThread(); - if (IsThreaded() && !IsMyself()) + // Make sure thread is asleep before setting prompt + if (SuspendThread()) { - if (signal_count > 0) + prompt_set = true; // indicate prompt_callback should be called + // Now explicitly wake the thread that the prompt is set + if (!SignalThread()) { - signal_count++; - return true; + ResumeThread(); + return false; // suspend/signal thread } - else - { -#ifdef WIN32 - if (0 == SetEvent(break_event)) + UnsignalThread(); + ResumeThread(); + return true; + } + else + { + return false; + } +} // end ProtoDispatcher::PromptThread() + + +/** + * This brings the thread to a suspended state _outside_ + * of _its_ ProtoDispatcher::Wait() state. A call to + * SignalThread() followed by UnsignalThread() will make + * the thread break from waiting and dispatch timers, pending I/O, etc + * The caller can safely do things in between signal/unsignal like + * add/remove descriptors, etc and this is used by internal ProtoDispatcher + * code to do this safely for threaded dispatcher instances. Any call to SignalThread() + * MUST be mirrored with an UnsignalThread() call and it is OK for it to be re-entrantly + * called (that is what the "signal_count" is for). + */ +bool ProtoDispatcher::SignalThread() +{ + SuspendThread(); + ThreadId currentThread = GetCurrentThread(); + if (IsThreaded() && (currentThread != thread_id)) + { + if (signal_count > 0) + { + signal_count++; + return true; + } + else + { + if (!SetBreak()) { - PLOG(PL_ERROR, "ProtoDispatcher::SignalThread() SetEvent(break_event) error\n"); + PLOG(PL_ERROR, "ProtoDispatcher::SignalThread() error: SetBreak() failed!\n"); ResumeThread(); return false; - } -#else - while (1) - { - char byte; - int result = write(break_pipe_fd[1], &byte, 1); - if (1 == result) - { - break; - } - else if (0 == result) - { - PLOG(PL_ERROR, "ProtoDispatcher::SignalThread() warning: write() returned zero\n"); - continue; - } - else - { - if (EINTR == errno) continue; // (TBD) also for EAGAIN ??? - PLOG(PL_ERROR, "ProtoDispatcher::SignalThread() write() error: %s\n", - GetErrorString()); - ResumeThread(); - return false; - } - } -#endif // if/else WIN32/UNIX + } + // Attempting to lock the "signal_mutex" blocks until the thread + // is "outside" its "ProtoDispatcher::Wait()" stage + // (thread can't reenter wait stage until "UnsignalThread()" is + // called and the "signal_mutex" is unlocked) Lock(signal_mutex); signal_count = 1; + thread_signaled = true; } } return true; @@ -880,7 +1080,8 @@ bool ProtoDispatcher::SignalThread() void ProtoDispatcher::UnsignalThread() { - if (IsThreaded() && !IsMyself() && (thread_master == GetCurrentThread())) + ThreadId currentThread = GetCurrentThread(); + if (IsThreaded() && (currentThread != thread_id) && (thread_master == currentThread)) { ASSERT(0 != signal_count); signal_count--; @@ -890,11 +1091,21 @@ void ProtoDispatcher::UnsignalThread() ResumeThread(); } // end ProtoDispatcher::UnsignalThread() +/* + * This makes sure the thread is asleep (i.e. waiting) or stopped + * until ResumeThread() is called (unlocks the suspend_mutex so + * thread can proceed) Note that SuspendThread() will block + * until the thread gets to the waiting state. Any call to SuspendThread() + * MUST be mirrored with an ResumeThread() call and it is OK for it to + * be re-entrantly called (that is what the "suspend_count" is for). + * + */ bool ProtoDispatcher::SuspendThread() { - if (IsThreaded() && !IsMyself()) + ThreadId currentThread = GetCurrentThread(); + if (IsThreaded() && (currentThread != thread_id)) { - if (GetCurrentThread() == thread_master) + if (currentThread == thread_master) { suspend_count++; return true; @@ -903,7 +1114,7 @@ bool ProtoDispatcher::SuspendThread() // (TBD) use a spin_count to limit iterations as safeguard while (!thread_started); Lock(suspend_mutex); // TBD - check result of "Lock()" - thread_master = GetCurrentThread(); + thread_master = currentThread; suspend_count = 1; } return true; @@ -911,9 +1122,10 @@ bool ProtoDispatcher::SuspendThread() void ProtoDispatcher::ResumeThread() { - if (IsThreaded() && !IsMyself()) + ThreadId currentThread = GetCurrentThread(); + if (IsThreaded() && (currentThread != thread_id)) { - if (GetCurrentThread() == thread_master) + if (currentThread == thread_master) { if (suspend_count > 1) { @@ -955,16 +1167,338 @@ void ProtoDispatcher::DestroyThread() // Unix-specific methods and implementation #ifdef UNIX -/** - * (TBD) It would be nice to use something like SIGUSR or something to break - * out of the select() (or pselect()) call in ProtoDispatcher::Wait() - * instead of a pipe() ... but to support threaded operation and do - * it right, we would need something like pthread-sigsetjmp() to deal - * with the "edge-triggered" nature issue of signals, and/or we - * might wish to consider kevent() for systems like BSD, etc - */ + + +#if defined(USE_SELECT) +// USE_SELECT implementation of ProtoDispatcher::UpdateStreamNotification() +bool ProtoDispatcher::UpdateStreamNotification(Stream& stream, NotificationCommand cmd) +{ + // ProtoDispatcher currently builds its "struct fdsets" by iterating through + // the "stream_list" each time ProtoDispatcher::Wait() is called + // TBD - we should use FD_CLR here to make sure our current input_set and + // output_set are in the proper state for when a stream's notification + // is disabled (e.g. due to deletion or other) + + switch (cmd) + { + case ENABLE_INPUT: + stream.SetFlag(Stream::INPUT); + break; + case DISABLE_INPUT: + stream.UnsetFlag(Stream::INPUT); + if (INVALID_DESCRIPTOR != stream.GetInputHandle()) + FD_CLR(stream.GetInputHandle(), &input_set); + break; + case ENABLE_OUTPUT: + stream.SetFlag(Stream::OUTPUT); + break; + case DISABLE_OUTPUT: + stream.UnsetFlag(Stream::OUTPUT); + if (INVALID_DESCRIPTOR != stream.GetOutputHandle()) + FD_CLR(stream.GetOutputHandle(), &output_set); + break; + case DISABLE_ALL: + stream.ClearFlags(); + if (INVALID_DESCRIPTOR != stream.GetInputHandle()) + FD_CLR(stream.GetInputHandle(), &input_set); + if (INVALID_DESCRIPTOR != stream.GetOutputHandle()) + FD_CLR(stream.GetOutputHandle(), &output_set); + break; + } + + return true; +} // end ProtoDispatcher::UpdateStreamNotification() [USE_SELECT] + +#elif defined(USE_KQUEUE) + +// USE_KQUEUE implementation of ProtoDispatcher::UpdateStreamNotification() +bool ProtoDispatcher::UpdateStreamNotification(Stream& stream, NotificationCommand cmd) +{ + switch (cmd) + { + case ENABLE_INPUT: + if (!KeventChange(stream.GetInputHandle(), EVFILT_READ, EV_ADD | EV_ENABLE, &stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(ENABLE_INPUT) KeventChange() error!\n"); + return false; + } + stream.SetFlag(Stream::INPUT); + break; + + case DISABLE_INPUT: + if (!KeventChange(stream.GetInputHandle(), EVFILT_READ, EV_DISABLE, &stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_INPUT) KeventChange() error!\n"); + return false; + } + stream.UnsetFlag(Stream::INPUT); + break; + + case ENABLE_OUTPUT: + if (!KeventChange(stream.GetOutputHandle(), EVFILT_WRITE, EV_ADD | EV_ENABLE, &stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_INPUT) KeventChange() error!\n"); + return false; + } + stream.SetFlag(Stream::OUTPUT); + break; + + case DISABLE_OUTPUT: + if (!KeventChange(stream.GetOutputHandle(), EVFILT_WRITE, EV_DISABLE, &stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_INPUT) KeventChange() error!\n"); + return false; + } + stream.UnsetFlag(Stream::OUTPUT); + break; + + case DISABLE_ALL: + if (stream.IsInput() && !KeventChange(stream.GetInputHandle(), EVFILT_READ, EV_DISABLE, &stream)) + { + PLOG(PL_WARN, "ProtoDispatcher::UpdateStreamNotification(DISABLE_ALL) KeventChange(EVFILT_READ) error!\n"); + //return false; + } + if (stream.IsOutput() && !KeventChange(stream.GetOutputHandle(), EVFILT_WRITE, EV_DISABLE, &stream)) + { + PLOG(PL_WARN, "ProtoDispatcher::UpdateStreamNotification(DISABLE_ALL) KeventChange(EVFILT_WRITE) error!\n"); + //return false; + } + stream.ClearFlags(); + // TBD - support exceptions ?? + break; + } + return true; +} // end ProtoDispatcher::UpdateStreamNotification() [USE_KQUEUE] + +bool ProtoDispatcher::KeventChange(uintptr_t ident, int16_t filter, uint16_t flags, void* udata) +{ + // This simply sets a new "struct kevent" in our "kevent_array" + if (-1 == kevent_queue) + { + if (-1 == (kevent_queue = kqueue())) + { + PLOG(PL_ERROR, "ProtoDispatcher::KeventChange() kqueue() error: %s\n", GetErrorString()); + return false; + } + } + struct kevent kvt; + kvt.ident = ident; + kvt.filter = filter; + kvt.flags = flags; + kvt.fflags = 0; + kvt.data = 0; + kvt.udata = udata; + + // On EV_DISABLE, go through the current kevent_array and make + // sure this "ident::filter" is nullified to avoid undesired + // notification attempts (i.e. disable current, pending notification, if any) + // (i.e., set the "filter" to harmless EVFILT_USER value) + if (0 != (flags & EV_DISABLE)) + { + struct kevent* kep = kevent_array; + for (int i = 0; i < wait_status; i++) + { + if ((ident == kep->ident) && (filter == kep->filter)) + { + kep->ident = 0; // different than our "break" EVFILT_USER if ever needed + kep->filter = EVFILT_USER; + } + } + } + // This call enables/disables future notifications as desired + if (kevent(kevent_queue, &kvt, 1, NULL, 0, NULL) < 0) + { + PLOG(PL_ERROR, "ProtoDispatcher::KeventChange() kevent() error: %s\n", GetErrorString()); + return false; + } + else + { + return true; + } +} // end ProtoDispatcher::KeventChange() + +#elif USE_EPOLL + +// USE_EPOLL implementation of ProtoDispatcher::UpdateStreamNotification() +bool ProtoDispatcher::UpdateStreamNotification(Stream& stream, NotificationCommand cmd) +{ + // Note for ProtoChannel it is possible that separate input and output descriptors may + // be used and so the code below checks for this condition + switch (cmd) + { + case ENABLE_INPUT: + ASSERT(!stream.IsInput()); + if (!((stream.IsOutput() && (stream.GetInputHandle() == stream.GetOutputHandle())) ? + EpollChange(stream.GetInputHandle(), EPOLLIN | EPOLLOUT, EPOLL_CTL_MOD, &stream) : + EpollChange(stream.GetInputHandle(), EPOLLIN, EPOLL_CTL_ADD, &stream))) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(ENABLE_INPUT) error: EpollChange() failed!\n"); + return false; + } + stream.SetFlag(Stream::INPUT); + break; + + case DISABLE_INPUT: + ASSERT(stream.IsInput()); + if (!((stream.IsOutput() && (stream.GetInputHandle() == stream.GetOutputHandle())) ? + EpollChange(stream.GetInputHandle(), EPOLLOUT, EPOLL_CTL_MOD, &stream) : + EpollChange(stream.GetInputHandle(), 0, EPOLL_CTL_DEL, NULL))) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_INPUT) error: EpollChange() failed!\n"); + return false; + } + stream.UnsetFlag(Stream::INPUT); + break; + + case ENABLE_OUTPUT: + ASSERT(!stream.IsOutput()); + if (!((stream.IsInput() && (stream.GetInputHandle() == stream.GetOutputHandle())) ? + EpollChange(stream.GetOutputHandle(), EPOLLIN | EPOLLOUT, EPOLL_CTL_MOD, &stream) : + EpollChange(stream.GetOutputHandle(), EPOLLOUT, EPOLL_CTL_ADD, &stream))) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(ENABLE_OUTPUT) error: EpollChange() failed!\n"); + return false; + } + stream.SetFlag(Stream::OUTPUT); + break; + + case DISABLE_OUTPUT: + ASSERT(stream.IsOutput()); + if (!((stream.IsInput() && (stream.GetInputHandle() == stream.GetOutputHandle())) ? + EpollChange(stream.GetOutputHandle(), EPOLLIN, EPOLL_CTL_MOD, &stream) : + EpollChange(stream.GetOutputHandle(), 0, EPOLL_CTL_DEL, NULL))) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_OUTPUT) error: EpollChange() failed!\n"); + return false; + } + stream.UnsetFlag(Stream::OUTPUT); + break; + + case DISABLE_ALL: + ASSERT(stream.IsInput() || stream.IsOutput()); + if (stream.GetInputHandle() == stream.GetOutputHandle()) + { + if (!EpollChange(stream.GetInputHandle(), 0, EPOLL_CTL_DEL, NULL)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_ALL) error: EpollChange() failed!\n"); + return false; + } + } + else + { + if (stream.IsInput()) + { + if (!EpollChange(stream.GetInputHandle(), 0, EPOLL_CTL_DEL, NULL)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_ALL) error: EpollChange(input) failed!\n"); + return false; + } + } + if (stream.IsOutput()) + { + if (!EpollChange(stream.GetOutputHandle(), 0, EPOLL_CTL_DEL, NULL)) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification(DISABLE_ALL) error: EpollChange(output) failed!\n"); + return false; + } + } + } + stream.ClearFlags(); + // TBD - support exceptions ?? + break; + } + return true; +} // end ProtoDispatcher::UpdateStreamNotification() [USE_EPOLL] + +bool ProtoDispatcher::EpollChange(int fd, int events, int op, void* udata) +{ + if (-1 == epoll_fd) + { + if (-1 == (epoll_fd = epoll_create1(0))) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification() epoll_create() error: %s\n", + GetErrorString()); + return false; + } + } + // Go through the current "epoll_event_array" and trim down or nullify + // applicable current pending events for this "fd" + // (So we don't get undesired notification dispatches) + switch (op) + { + case EPOLL_CTL_MOD: + { + struct epoll_event* evp = epoll_event_array; + for (int i = 0; i < wait_status; i++) + { + if (evp->data.ptr == udata) + evp->events &= events; // mask out events no longer wanted + evp++; + } + break; + } + case EPOLL_CTL_DEL: + { + struct epoll_event* evp = epoll_event_array; + for (int i = 0; i < wait_status; i++) + { + if (evp->data.ptr == udata) + { + evp->events = 0; // no events wanted + evp->data.ptr = NULL; // make sure no dereference is attempted + } + evp++; + } + break; + } + default: + break; + } // end switch(op) + // Set or disable future events for this descriptor + struct epoll_event event; + event.events = events; + event.data.ptr = udata; + if (-1 == epoll_ctl(epoll_fd, op, fd, &event)) + { +#ifdef USE_TIMERFD + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification() epoll_ctl() error: %s (timer_fd:%d epoll_fd:%d)\n", + GetErrorString(), timer_stream.GetDescriptor(), epoll_fd); +#else + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification() epoll_ctl() error: %s (epoll_fd:%d)\n", + GetErrorString(), epoll_fd); +#endif + return false; + } + return true; +} // end ProtoDispatcher::EpollChange() + +#else +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // !USE_SELECT && !USE_KQUEUE + bool ProtoDispatcher::InstallBreak() -{ +{ +#if defined(USE_SELECT) || defined(USE_EPOLL) +#ifdef USE_EVENTFD + // Create eventfd() descriptor + int efd = eventfd(0, 0); // TBD - should we set the flag "EFD_NONBLOCK"??? + if (-1 == efd) + { + PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() eventfd() error: %s\n", GetErrorString()); + return false; + } + break_stream.SetDescriptor(efd); +#ifdef USE_EPOLL + if (!EpollChange(efd, EPOLLIN, EPOLL_CTL_ADD, &break_stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() error: EpollChange() failed!\n"); + close(efd); + break_stream.SetDescriptor(INVALID_DESCRIPTOR); + return false; + } +#endif // USE_EPOLL +#else + // Create a "self pipe" for thread awakening purposes if (0 != pipe(break_pipe_fd)) { PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() pipe() error: %s\n", @@ -977,132 +1511,235 @@ bool ProtoDispatcher::InstallBreak() PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() fcntl(F_SETFL(O_NONBLOCK)) error: %s\n", GetErrorString()); return false; } + break_stream.SetDescriptor(break_pipe_fd[0]); +#ifdef USE_EPOLL + if (!EpollChange(break_pipe_fd[0], EPOLLIN, EPOLL_CTL_ADD, &break_stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() error: EpollChange() failed!\n"); + close(break_pipe_fd[0]); + close(break_pipe_fd[1]); + break_pipe_fd[0] = break_pipe_fd[1] = INVALID_DESCRIPTOR; + return false; + } +#endif // USE_EPOLL +#endif // if/else USE_EVENTFD +#elif defined(USE_KQUEUE) + if (!KeventChange(1, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, NULL)) + { + PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() KeventChange(EVFILT_USER) error: %s\n", GetErrorString()); + return false; + } +#else +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // !USE_SELECT && !USE_KQUEUE return true; } // end ProtoDispatcher::InstallBreak() +bool ProtoDispatcher::SetBreak() +{ +#if defined(USE_SELECT) || defined(USE_EPOLL) +#ifdef USE_EVENTFD + uint64_t value = 1; + if (write(break_stream.GetDescriptor(), &value, sizeof(uint64_t)) < 0) + { + PLOG(PL_ERROR, "ProtoDispatcher::SetBreak() write(eventfd) error: %s\n", GetErrorString()); + return false; + } +#else + while (1) + { + char byte = 0; + int result = write(break_pipe_fd[1], &byte, 1); + if (1 == result) + { + break; + } + else if (0 == result) + { + PLOG(PL_ERROR, "ProtoDispatcher::SetBreak() warning: write() returned zero\n"); + continue; + } + else + { + if (EINTR == errno) continue; // (TBD) also for EAGAIN ??? + PLOG(PL_ERROR, "ProtoDispatcher::SetBreak() write(break_pipe) error: %s\n", GetErrorString()); + return false; + } + } +#endif // end if/else USE_EVENTFD +#elif defined(USE_KQUEUE) + struct kevent kev; + EV_SET(&kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + if (-1 == kevent(kevent_queue, &kev, 1, NULL, 0, NULL)) + { + PLOG(PL_ERROR, "ProtoDispatcher::SetBreak() kevent() error: %s\n", GetErrorString()); + return false; + } +#else +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // !USE_SELECT && !USE_KQUEUE + return true; +} // end ProtoDispatcher::SetBreak() + void ProtoDispatcher::RemoveBreak() { - if (INVALID_DESCRIPTOR != break_pipe_fd[0]) +#if defined(USE_SELECT) || defined(USE_EPOLL) + if (INVALID_DESCRIPTOR != break_stream.GetDescriptor()) { - close(break_pipe_fd[0]); +#ifdef USE_EPOLL + if (!EpollChange(break_stream.GetDescriptor(), EPOLLIN, EPOLL_CTL_DEL, &break_stream)) + { + PLOG(PL_ERROR, "ProtoDispatcher::RemoveBreak() error: EpollChange() failed!\n"); + } +#endif // USE_EPOLL + // Close down the break_stream pipe or eventfd + close(break_stream.GetDescriptor()); + break_stream.SetDescriptor(INVALID_DESCRIPTOR); +#ifndef USE_EVENTFD + // We're using the break_pipe, so close() the extra descriptor close(break_pipe_fd[1]); - break_pipe_fd[0] = INVALID_DESCRIPTOR; + break_pipe_fd[0] = break_pipe_fd[1] = INVALID_DESCRIPTOR; +#endif } + +#elif defined(USE_KQUEUE) + struct kevent kev; + EV_SET(&kev, 1, EVFILT_USER, EV_DELETE, 0, 0, NULL); + if (-1 == kevent(kevent_queue, &kev, 1, NULL, 0, NULL)) + PLOG(PL_ERROR, "ProtoDispatcher::RemoveBreak() kevent() error: %s\n", GetErrorString()); +#else +#error "undefined async i/o mechanism" // to make sure we implement something +#endif } // end ProtoDispatcher::RemoveBreak() + /** - * Warning! This may block indefinitely iF !IsPending() ... + * Warning! This may block indefinitely if !IsPending() ... */ void ProtoDispatcher::Wait() { // (TBD) We could put some code here to protect this from // being called by the wrong thread? -#ifdef USE_TIMERFD -#define HAVE_PSELECT // so we can use the "struct timespec" created here -#endif // USE_TIMERFD +#if defined(USE_KQUEUE) || defined(HAVE_PSELECT) || defined(USE_TIMERFD) +#define USE_TIMESPEC 1 // so we can use the "struct timespec" created here +#endif // USE_KQUEUE || HAVE_PSELECT || USE_TIMERFD -#ifdef HAVE_PSELECT + +#ifdef USE_SELECT + int maxDescriptor = -1; + FD_ZERO(&input_set); + FD_ZERO(&output_set); +#endif // USE_SELECT + +#ifdef USE_TIMESPEC struct timespec timeout; - struct timespec* timeoutPtr; + struct timespec* timeoutPtr = NULL; #else struct timeval timeout; - struct timeval* timeoutPtr; -#endif // if/else HAVE_PSELECT + struct timeval* timeoutPtr = NULL; +#endif // if/else USE_TIMESPEC //double timerDelay = ProtoTimerMgr::GetTimeRemaining(); double timerDelay = timer_delay; + //TRACE("Wait() timerDelay:%lf\n", timerDelay); + if (timerDelay < 0.0) { - timeoutPtr = NULL; #ifdef USE_TIMERFD timeout.tv_sec = 0; timeout.tv_nsec = 0; -#endif // USE_TIMERFD +#endif //USE_TIMERFD + } else { +// We have observed some different thresholds on "precise timing" for +// for different systems and APIs +#if defined(MACOSX) +#define PRECISE_THRESHOLD 1.0e-05 // about 10 microseconds for MacOS whether select(), kevent(), etc +#elif defined(LINUX) +#ifdef USE_TIMERFD +#define PRECISE_THRESHOLD 2.0e-05 // about 20 microseconds for Linux timerfd usage +#else +#define PRECISE_THRESHOLD 2.0e-03 // about 2 msec for Linux select() +#endif // if/else USE_TIMERFD +#else +#define PRECISE_THRESHOLD 2.0e-03 // assume 2 msec for other Unix +#endif // if/else MACOSX / LINUX / OTHER + // If (true == precise_timing) essentially force polling for small delays // (Note this will consume CPU resources) - if (precise_timing && (timerDelay < 0.010)) timerDelay = 0.0; + if (precise_timing && (timerDelay < PRECISE_THRESHOLD)) timerDelay = 0.0; timeout.tv_sec = (unsigned long)timerDelay; -#ifdef HAVE_PSELECT +#ifdef USE_TIMESPEC timeout.tv_nsec = (unsigned long)(1.0e+09 * (timerDelay - (double)timeout.tv_sec)); #else timeout.tv_usec = (unsigned long)(1.0e+06 * (timerDelay - (double)timeout.tv_sec)); -#endif // if/else HAVE_PSELECT +#endif // if/else USE_TIMESPEC timeoutPtr = &timeout; - } - - FD_ZERO(&input_set); - FD_ZERO(&output_set); - int maxDescriptor = -1; - - #ifdef USE_TIMERFD - if ((0 != timeout.tv_nsec) || (0 != timeout.tv_sec)) - { - // Install the timerfd descriptor to our "input_set" - // configured with an appropriate one-shot timeout - struct itimerspec timerSpec; - timerSpec.it_interval.tv_sec = timerSpec.it_interval.tv_nsec = 0; // non-repeating timeout? - timerSpec.it_value = timeout; - if (0 == timerfd_settime(timer_fd, 0, &timerSpec, 0)) + if ((0 != timeout.tv_nsec) || (0 != timeout.tv_sec)) { - //TRACE("putting timer_fd %d input input_set with sec:%d nsec:%d\n", - // timer_fd, timerSpec.it_value.tv_sec, timerSpec.it_value.tv_nsec); - FD_SET(timer_fd, &input_set); - maxDescriptor = timer_fd; - timeoutPtr = NULL; // select() will be using "timer_fd" instead - } - else - { - PLOG(PL_ERROR, "ProtoDispatcher::Wait() timerfd_settime() error: %s\n", GetErrorString()); + // Install the timerfd descriptor to our "input_set" + // configured with an appropriate one-shot timeout + struct itimerspec timerSpec; + timerSpec.it_interval.tv_sec = timerSpec.it_interval.tv_nsec = 0; // non-repeating timeout? + timerSpec.it_value = timeout; + if (0 == timerfd_settime(timer_stream.GetDescriptor(), 0, &timerSpec, 0)) + { +#ifdef USE_SELECT + FD_SET(timer_stream.GetDescriptor(), &input_set); + if (timer_stream.GetDescriptor() > maxDescriptor) + maxDescriptor = timer_stream.GetDescriptor(); +#endif // USE_SELECT + timeoutPtr = NULL; // select() (or pselect()) will be using "timer_fd" instead + } + else + { + PLOG(PL_ERROR, "ProtoDispatcher::Wait() timerfd_settime() error: %s\n", GetErrorString()); + + } } - } #endif // USE_TIMERFD + } - +#if defined(USE_SELECT) // Monitor "break_pipe" if we are a threaded dispatcher // TBD - change this to use "eventfd()" on Linux if (IsThreaded()) { +#ifdef USE_EVENTFD + FD_SET(break_stream.GetDescriptor(), &input_set); + if (break_stream.GetDescriptor() > maxDescriptor) + maxDescriptor = break_stream.GetDescriptor(); +#else FD_SET(break_pipe_fd[0], &input_set); - maxDescriptor = break_pipe_fd[0]; - } - // Monitor socket streams ... - SocketStream* nextSocket = socket_stream_list; - while (nextSocket) - { - Descriptor descriptor = nextSocket->GetSocket().GetHandle(); - if (nextSocket->IsInput()) FD_SET(descriptor, &input_set); - if (nextSocket->IsOutput()) FD_SET(descriptor, &output_set); - if (descriptor > maxDescriptor) maxDescriptor = descriptor; - nextSocket = (SocketStream*)nextSocket->GetNext(); - } - // Monitor channel streams ... - ChannelStream* nextChannel = channel_stream_list; - while (nextChannel) - { - Descriptor descriptor = nextChannel->GetChannel().GetHandle(); - if (nextChannel->IsInput()) FD_SET(descriptor, &input_set); - if (nextChannel->IsOutput()) FD_SET(descriptor, &output_set); - if (descriptor > maxDescriptor) maxDescriptor = descriptor; - nextChannel = (ChannelStream*)nextChannel->GetNext(); - } - // Monitor generic streams ... - GenericStream* nextStream = generic_stream_list; - while (nextStream) - { - Descriptor descriptor = nextStream->GetDescriptor(); - if (nextStream->IsInput()) FD_SET(descriptor, &input_set); - if (nextStream->IsOutput()) FD_SET(descriptor, &output_set); - if (descriptor > maxDescriptor) maxDescriptor = descriptor; - nextStream = (GenericStream*)nextStream->GetNext(); + if (break_pipe_fd[0] > maxDescriptor) + maxDescriptor = break_pipe_fd[0]; +#endif // if/else USE_EVENTFD } - // (TBD) It might be nice to use "pselect()" here someday + // Iterate through and add streams to our FD_SETs + StreamTable::Iterator iterator(stream_table); + Stream* stream; + while (NULL != (stream = iterator.GetNextItem())) + { + if (stream->IsInput()) + { + Descriptor descriptor = stream->GetInputHandle(); + FD_SET(descriptor, &input_set); + if (descriptor > maxDescriptor) maxDescriptor = descriptor; + } + if (stream->IsOutput()) + { + Descriptor descriptor = stream->GetOutputHandle(); + FD_SET(descriptor, &output_set); + if (descriptor > maxDescriptor) maxDescriptor = descriptor; + } + // TBD - handle exception notification ??? + } #ifdef HAVE_PSELECT wait_status = pselect(maxDescriptor+1, (fd_set*)&input_set, @@ -1115,13 +1752,50 @@ void ProtoDispatcher::Wait() (fd_set*)&input_set, (fd_set*)&output_set, (fd_set*) NULL, - timeoutPtr); + (timeval*)timeoutPtr); #endif // if/else HAVE_PSELECT + +#elif defined(USE_EPOLL) + + // If no sockets were installed yet, create epoll_fd early + if (-1 == epoll_fd) + { + if (-1 == (epoll_fd = epoll_create1(0))) + { + PLOG(PL_ERROR, "ProtoDispatcher::Wait() epoll_create() error: %s\n", + GetErrorString()); + return; + } + } + + // Note if (NULL == timeoutPtr), then the timer_fd has been set up with the proper timeout value + // (otherwise we convert "timerDelay" to milliseconds) + wait_status = epoll_wait(epoll_fd, epoll_event_array, EPOLL_ARRAY_SIZE, (NULL != timeoutPtr) ? (int)(timerDelay*1000.0) : -1); + +#elif defined(USE_KQUEUE) + if (-1 == kevent_queue) + { + // Need to create our kqueue instance + if (-1 == (kevent_queue = kqueue())) + { + PLOG(PL_ERROR, "ProtoDispatcher::Wait() kqueue() error: %s\n", GetErrorString()); + // TBD - should we set "run" to false here??? + wait_status = -1; + return; + } + } + wait_status = kevent(kevent_queue, NULL, 0, kevent_array, KEVENT_ARRAY_SIZE, timeoutPtr); + // TBD - should we print a message here on error? (Dispatch() does this for us) +#else +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // !USE_SELECT && !USE_KQUEUE + } // end ProtoDispatcher::Wait() void ProtoDispatcher::Dispatch() { - +#if defined(USE_SELECT) + // Here the "wait_status" is the return value from the select() call switch (wait_status) { case -1: @@ -1136,78 +1810,332 @@ void ProtoDispatcher::Dispatch() break; default: - // (TBD) make this safer (we need a safe iterator for these)... - // Check socket streams ... - SocketStream* nextSocketStream = socket_stream_list; - while (nextSocketStream) - { - SocketStream* savedNext = (SocketStream*)nextSocketStream->GetNext(); - ProtoSocket& theSocket = nextSocketStream->GetSocket(); - Descriptor descriptor = theSocket.GetHandle(); - if (nextSocketStream->IsInput() && FD_ISSET(descriptor, &input_set)) - { - theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); - //break; - } - if (nextSocketStream->IsOutput() && FD_ISSET(descriptor, &output_set)) - { - theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); - //break; - } - nextSocketStream = savedNext; - } - // Check channel streams ... - ChannelStream* nextChannelStream = channel_stream_list; - while (nextChannelStream) + // Iterate through and check/dispatch streams + StreamTable::Iterator iterator(stream_table); + Stream* stream; + while (NULL != (stream = iterator.GetNextItem())) { - ChannelStream* savedNext = (ChannelStream*)nextChannelStream->GetNext(); - ProtoChannel& theChannel = nextChannelStream->GetChannel(); - Descriptor descriptor = theChannel.GetHandle(); - if (nextChannelStream->IsInput() && FD_ISSET(descriptor, &input_set)) + switch (stream->GetType()) { - theChannel.OnNotify(ProtoChannel::NOTIFY_INPUT); - //break; - } - if (nextChannelStream->IsOutput() && FD_ISSET(descriptor, &output_set)) - { - theChannel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); - //break; - } - nextChannelStream = savedNext; + case Stream::CHANNEL: + { + ProtoChannel& theChannel = static_cast(stream)->GetChannel(); + // A channel _might_ be implemented with separate input/output descriptors + if (stream->IsInput()) + { + if (FD_ISSET(theChannel.GetInputEventHandle(), &input_set)) + theChannel.OnNotify(ProtoChannel::NOTIFY_INPUT); + } + // Note that if the input notification handling caused the + // channel to even be deleted, because the stream is "pooled" + // and its flags zero, we're safe. If the channel is removed + // and the stream repurposed, we could throw a false notification + // here, but no big deal (same is true for socket handling + // immediately below). TBD - use FD_CLR in "UpdateChannelNotification()" + // to avoid this possibility + if (stream->IsOutput()) + { + if (FD_ISSET(theChannel.GetOutputEventHandle(), &output_set)) + theChannel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); + } + break; + } + case Stream::SOCKET: + { + // A socket has a single input/output descriptor + ProtoSocket& theSocket = static_cast(stream)->GetSocket(); + Descriptor descriptor = theSocket.GetHandle(); + if (stream->IsInput() && FD_ISSET(descriptor, &input_set)) + theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); + // TBD - what if stream and/or theSocket was deleted? + if (stream->IsOutput() && FD_ISSET(descriptor, &output_set)) + theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + break; + } + case Stream::GENERIC: + { + // A generic stream has a single input/output descriptor + Descriptor descriptor = static_cast(stream)->GetDescriptor(); + if (stream->IsInput() && FD_ISSET(descriptor, &input_set)) + static_cast(stream)->OnEvent(EVENT_INPUT); + if (stream->IsOutput() && FD_ISSET(descriptor, &output_set)) + static_cast(stream)->OnEvent(EVENT_OUTPUT); + break; + } + + case Stream::TIMER: + case Stream::EVENT: + { + // No timer or event stream is put in the stream_table + break; + } + } // end switch (stream->GetType()) } - // Check generic streams ... - GenericStream* nextStream = generic_stream_list; - while (nextStream) + if (break_stream.GetDescriptor() != INVALID_DESCRIPTOR && + FD_ISSET(break_stream.GetDescriptor(), &input_set)) { - GenericStream* savedNext = (GenericStream*)nextStream->GetNext(); - Descriptor descriptor = nextStream->GetDescriptor(); - if (nextStream->IsInput() && FD_ISSET(descriptor, &input_set)) - { - nextStream->OnEvent(EVENT_INPUT); - //break; - } - if (nextStream->IsOutput() && FD_ISSET(descriptor, &output_set)) - { - nextStream->OnEvent(EVENT_OUTPUT); - //break; - } - nextStream = savedNext; + // We were signaled so reset our break_pipe or eventfd +#ifdef USE_EVENTFD + // Reset "signal" by reading eventfd count + uint64_t counter = 0; + if (read(break_stream.GetDescriptor(), &counter, sizeof(counter)) < 0) + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() read(event_fd) error: %s\n", GetErrorString()); + +#else + // Reset "signal" by emptying break_pipe + char byte[32]; + while (read(break_pipe_fd[0], byte, 32) > 0); +#endif // if/else USE_EVENTFD } #ifdef USE_TIMERFD // TBD - should we put this code up at the top as the first check - // intead of checking all descriptors, all the time (maybe "epoll()" will help?) - if (FD_ISSET(timer_fd, &input_set)) + // instead of checking all descriptors, all the time (maybe "epoll()" will help?) + if (timer_stream.GetDescriptor() != INVALID_DESCRIPTOR && + FD_ISSET(timer_stream.GetDescriptor(), &input_set)) { - //TRACE("timer_fd was set\n"); - // clear the timer_fd status by reading from it uint64_t expirations = 0; - if (read(timer_fd, &expirations, sizeof(expirations)) < 0) + if (read(timer_stream.GetDescriptor(), &expirations, sizeof(expirations)) < 0) PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() read(timer_fd) error: %s\n", GetErrorString()); } #endif // USE_TIMERFD OnSystemTimeout(); break; - } // end switch(status) + } // end switch(wait_status) [USE_SELECT] + +#elif defined(USE_EPOLL) + + switch(wait_status) + { + case -1: + if (EINTR != errno) + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() epoll_wait() error: %s\n", GetErrorString()); + break; + + case 0: + // timeout only + OnSystemTimeout(); + break; + + default: + struct epoll_event* evp = epoll_event_array; + for (int i = 0; i < wait_status; i++) + { + // First check for an error condition + /*if (0 != (EPOLLERR & evp->events)) + { + // We don't check for EPOLLHUP since ProtoSocket handles that + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() epoll event error: %s\n", GetErrorString()); + } + else */ + if (NULL != evp->data.ptr) + { + // TBD - process EPOLLHUP events? + Stream* stream = (Stream*)evp->data.ptr; + switch (stream->GetType()) + { + case Stream::CHANNEL: + { + ProtoChannel& channel = static_cast(stream)->GetChannel(); + if (0 != (EPOLLIN & evp->events)) + { + if (stream->IsInput()) + channel.OnNotify(ProtoChannel::NOTIFY_INPUT); + } + if (0 != (EPOLLOUT & evp->events)) + { + if (stream->IsOutput()) + channel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); + } + if (0 != (EPOLLERR & evp->events)) + { + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() ProtoChannel epoll event error: %s\n", GetErrorString()); + // Throw a notification so error will be detected by app??? + if (stream->IsInput()) + channel.OnNotify(ProtoChannel::NOTIFY_INPUT); + else if (stream->IsOutput()) + channel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); + } + break; + } + case Stream::SOCKET: + { + ProtoSocket& socket = static_cast(stream)->GetSocket(); + if (0 != (EPOLLIN & evp->events)) + { + if (stream->IsInput()) + socket.OnNotify(ProtoSocket::NOTIFY_INPUT); + } + if (0 != (EPOLLOUT & evp->events)) + { + if (stream->IsOutput()) + socket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + } + if (0 != (EPOLLERR & evp->events)) + { + socket.OnNotify(ProtoSocket::NOTIFY_ERROR); + // Alternatively throw an input or output notification so app gets notified? + //if (stream->IsInput()) + // socket.OnNotify(ProtoSocket::NOTIFY_INPUT); + //else if (stream->IsOutput()) + // socket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + } + break; + } + case Stream::GENERIC: + { + if (0 != (EPOLLIN & evp->events)) + { + if (stream->IsInput()) + static_cast(stream)->OnEvent(EVENT_INPUT); + } + if (0 != (EPOLLOUT & evp->events)) + { + if (stream->IsOutput()) + static_cast(stream)->OnEvent(EVENT_OUTPUT); + } + if (0 != (EPOLLERR & evp->events)) + { + // Throw an input or output notification so app gets notified? + if (stream->IsInput()) + static_cast(stream)->OnEvent(EVENT_INPUT); + else if (stream->IsOutput()) + static_cast(stream)->OnEvent(EVENT_OUTPUT); + } + break; + } + case Stream::TIMER: + { +#ifdef USE_TIMERFD + // This _should_ only be our ProtoDispatcher::timer_stream + // Clear the timerfd with a read() call (timeout dispatched below) + uint64_t expirations = 0; + if (read(static_cast(stream)->GetDescriptor(), &expirations, sizeof(expirations)) < 0) + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() read(timer_fd) error: %s\n", GetErrorString()); +#endif // USE_TIMERFD + break; + } + case Stream::EVENT: + { +#ifdef USE_EVENTFD + uint64_t counter = 0; + if (read(static_cast(stream)->GetDescriptor(), &counter, sizeof(counter)) < 0) + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() read(event_fd) error: %s\n", GetErrorString()); +#else + // Reset "signal" by emptying pipe + char byte[32]; + while (read(break_pipe_fd[0], byte, 32) > 0); +#endif // if/else USE_EVENTFD + break; + } + } // end switch(stream->GetType()) [USE_EPOLL] + } + else + { + // This must be a nullified event so do nothing + } + evp++; + } // end for (i = 0..wait_status) + OnSystemTimeout(); + break; + } // end switch(wait_status) [USE_EPOLL] + +#elif defined(USE_KQUEUE) + // Here the "wait_status" is the return value from the kevent() call + switch (wait_status) + { + case -1: + if (EINTR != errno) + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() kevent() error: %s\n", GetErrorString()); + //OnSystemTimeout(); + break; + + case 0: + // timeout only + OnSystemTimeout(); + break; + + default: + { + // The return value is the number of events available + // (Note "wait_status" may change on the fly, here if + // notification results in stream removal) + struct kevent* kep = kevent_array; + for (int i = 0; i < wait_status; i++) + { + // First check if there was an error for the event + if (0 != (EV_ERROR & kep->flags)) + { + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() kqueue event error: %s\n", GetErrorString((int)(kep->data))); + kep++; + continue; + } + Stream* stream = (Stream*)kep->udata; + switch (kep->filter) + { + case EVFILT_READ: + ASSERT(NULL != stream); + if (!stream->IsInput()) break; + switch (stream->GetType()) + { + case Stream::CHANNEL: + static_cast(stream)->GetChannel().OnNotify(ProtoChannel::NOTIFY_INPUT); + break; + case Stream::SOCKET: + static_cast(stream)->GetSocket().OnNotify(ProtoSocket::NOTIFY_INPUT); + break; + case Stream::GENERIC: + static_cast(stream)->OnEvent(EVENT_INPUT); + break; + case Stream::TIMER: + case Stream::EVENT: + // No Stream::TIMER or Stream::EVENT is used eith EVFILT_READ for now + break; + } + break; + + case EVFILT_WRITE: + ASSERT(NULL != stream); + if (!stream->IsOutput()) break; + switch (stream->GetType()) + { + case Stream::CHANNEL: + static_cast(stream)->GetChannel().OnNotify(ProtoChannel::NOTIFY_OUTPUT); + break; + case Stream::SOCKET: + static_cast(stream)->GetSocket().OnNotify(ProtoSocket::NOTIFY_OUTPUT); + break; + case Stream::GENERIC: + static_cast(stream)->OnEvent(EVENT_OUTPUT); + break; + case Stream::TIMER: + case Stream::EVENT: + // No Stream::TIMER or Stream::EVENT is used with EVFILT_WRITE + break; + } + break; + + case EVFILT_USER: + // Nothing to be done since EVFILT_USER w/ EV_CLEAR resets itself + // (or was nullified event) + break; + + case EVFILT_TIMER: + // We found that "EVFILT_TIMER" was not as precise as the kevent() timeout, so we don't use it + default: + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() error: unexpected event filter type %d\n", kep->filter); + break; + + } // switch(kep->filter) + kep++; + } + OnSystemTimeout(); // called to service timers in case one or more is ready + break; + } + } // end switch(wait_status) [USE_KQUEUE] +#else // !USE_SELECT && !USE_KQUEUE +#error "undefined async i/o mechanism" // to make sure we implement something +#endif // end if/else USE_SELECT, USE_KQUEUE, ... + wait_status = 0; // reset "wait_status" since we're done } // end ProtoDispatcher::Dispatch() #endif // UNIX @@ -1217,35 +2145,46 @@ void ProtoDispatcher::Dispatch() bool ProtoDispatcher::InstallBreak() { + ASSERT(INVALID_DESCRIPTOR == break_stream.GetDescriptor()); // The break_event used to break the MsgWaitForMultipleObjectsEx() call // This needs to be manual reset? - if (!(break_event = CreateEvent(NULL, TRUE, FALSE, NULL))) + HANDLE brk = CreateEvent(NULL, TRUE, FALSE, NULL); + if (INVALID_HANDLE_VALUE == brk) { PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() CreateEvent() error\n"); return false; } - if (InstallGenericStream(break_event, NULL, NULL, Stream::INPUT)) - { - break_event_stream = GetGenericStream(break_event); + break_stream.SetDescriptor(brk); + int index = Win32AddStream(break_stream, brk); + if (index < 0) + { + PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() error: add break_stream failed!\n"); + CloseHandle(brk); + break_stream.SetDescriptor(INVALID_DESCRIPTOR); + return false; } - else + break_stream.SetIndex(index); + return true; +} // end ProtoDispatcher::InstallBreak() + +bool ProtoDispatcher::SetBreak() +{ + if (0 == SetEvent(break_stream.GetDescriptor())) { - PLOG(PL_ERROR, "ProtoDispatcher::InstallBreak() CreateEvent() error\n"); - CloseHandle(break_event); - break_event = NULL; + PLOG(PL_ERROR, "ProtoDispatcher::SetBreak() SetEvent(break_event) error: %s\n", GetErrorString()); return false; } return true; -} // end ProtoDispatcher::InstallBreak() +} // end ProtoDispatcher::SetBreak() void ProtoDispatcher::RemoveBreak() { - if (NULL != break_event) + if (INVALID_DESCRIPTOR != break_stream.GetDescriptor()) { - RemoveGenericStream(break_event_stream); - break_event_stream = NULL; - CloseHandle(break_event); - break_event = NULL; + Win32RemoveStream(break_stream.GetIndex()); + break_stream.SetIndex(-1); + CloseHandle(break_stream.GetDescriptor()); + break_stream.SetDescriptor(INVALID_DESCRIPTOR); } } // end ProtoDispatcher::RemoveBreak() @@ -1281,7 +2220,7 @@ bool ProtoDispatcher::Win32Init() return false; } - // Create msg_window to receive event messages + // Create hidden "msg_window" to receive event window messages HWND parent = NULL; #ifdef HWND_MESSAGE parent = HWND_MESSAGE; @@ -1297,7 +2236,7 @@ bool ProtoDispatcher::Win32Init() NULL, // menu handle or child identifier theInstance, // handle to application instance this); // window-creation data - if (msg_window) + if (NULL != msg_window) { ShowWindow(msg_window, SW_HIDE); return true; @@ -1312,17 +2251,84 @@ bool ProtoDispatcher::Win32Init() void ProtoDispatcher::Win32Cleanup() { - if (msg_window) + if (NULL != msg_window) { DestroyWindow(msg_window); msg_window = NULL; } } // end Win32Cleanup() + +// WIN32 implementation of ProtoDispatcher::UpdateStreamNotification() +bool ProtoDispatcher::UpdateStreamNotification(Stream& stream, NotificationCommand cmd) +{ + switch (cmd) + { + case ENABLE_INPUT: + { + ASSERT(stream.GetIndex() < 0); // input already enabled?! + int index = Win32AddStream(stream, stream.GetInputHandle()); + if (index < 0) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification() error adding input stream\n"); + return false; + } + stream.SetIndex(index); + stream.SetFlag(Stream::INPUT); + break; + } + case DISABLE_INPUT: + { + ASSERT(stream.GetIndex() >= 0); + Win32RemoveStream(stream.GetIndex()); + stream.SetIndex(-1); + stream.UnsetFlag(Stream::INPUT); + break; + } + case ENABLE_OUTPUT: + { + ASSERT(stream.GetOutdex() < 0); // output already enabled?! + int outdex = Win32AddStream(stream, stream.GetOutputHandle()); + if (outdex < 0) + { + PLOG(PL_ERROR, "ProtoDispatcher::UpdateStreamNotification() error adding output stream\n"); + return false; + } + stream.SetOutdex(outdex); + stream.SetFlag(Stream::OUTPUT); + break; + } + case DISABLE_OUTPUT: + { + ASSERT(stream.GetOutdex() >= 0); + Win32RemoveStream(stream.GetOutdex()); + stream.SetOutdex(-1); + stream.UnsetFlag(Stream::OUTPUT); + break; + } + case DISABLE_ALL: + { + if (stream.GetIndex() >= 0) + { + Win32RemoveStream(stream.GetIndex()); + stream.SetIndex(-1); + } + if (stream.GetOutdex() >= 0) + { + Win32RemoveStream(stream.GetOutdex()); + stream.SetIndex(-1); + } + stream.ClearFlags(); + break; + } + } + return true; +} // end ProtoDispatcher::UpdateStreamNotification() [WIN32] + int ProtoDispatcher::Win32AddStream(Stream& stream, HANDLE handle) { - int index = stream_count; - if (stream_count >= stream_array_size) + DWORD index = stream_count; + if (index >= stream_array_size) { if (!Win32IncreaseStreamArraySize()) { @@ -1351,25 +2357,31 @@ void ProtoDispatcher::Win32RemoveStream(int index) bool ProtoDispatcher::Win32IncreaseStreamArraySize() { - unsigned int newSize = 2 * stream_array_size; + unsigned int newSize = (0 != stream_array_size) ? (2 * stream_array_size) : DEFAULT_STREAM_ARRAY_SIZE; HANDLE* hPtr = new HANDLE[newSize]; Stream** iPtr = new Stream*[newSize]; - if (hPtr && iPtr) + if ((NULL != hPtr) && (NULL != iPtr)) { - memcpy(hPtr, stream_handles_array, stream_count*sizeof(HANDLE)); - memcpy(iPtr, stream_ptrs_array, stream_count*sizeof(Stream*)); - if (stream_handles_default != stream_handles_array) + if (0 != stream_count) + { + memcpy(hPtr, stream_handles_array, stream_count*sizeof(HANDLE)); + memcpy(iPtr, stream_ptrs_array, stream_count*sizeof(Stream*)); + } + if (NULL != stream_handles_array) + { delete[] stream_handles_array; - if (stream_ptrs_default != stream_ptrs_array) delete[] stream_ptrs_array; + } stream_handles_array = hPtr; stream_ptrs_array = iPtr; return true; } else { - if (hPtr) delete[] hPtr; - if (iPtr) delete[] iPtr; + PLOG(PL_ERROR, "ProtoDispatcher::Win32IncreaseStreamArraySize() new stream_array error: %s\n", + GetErrorString()); + if (NULL != hPtr) delete[] hPtr; + if (NULL != iPtr) delete[] iPtr; return false; } } // end ProtoDispatcher::Win32IncreaseStreamArraySize() @@ -1377,28 +2389,84 @@ bool ProtoDispatcher::Win32IncreaseStreamArraySize() void ProtoDispatcher::Wait() { double timerDelay = timer_delay; + // Don't wait if we have "ready" streams pending + if (!ready_stream_list.IsEmpty()) timerDelay = 0.0; + + +#ifdef USE_WAITABLE_TIMER + DWORD msec = INFINITE; + if (timerDelay < 0.0) + { + if (timer_active) + { + CancelWaitableTimer(timer_stream.GetDescriptor()); + timer_active = false; + } + if (timer_stream.GetIndex() >= 0) + { + Win32RemoveStream(timer_stream.GetIndex()); + timer_stream.SetIndex(-1); + } + } + else if (0.0 == timerDelay) + { + if (timer_active) + { + CancelWaitableTimer(timer_stream.GetDescriptor()); + timer_active = false; + } + if (timer_stream.GetIndex() >= 0) + { + Win32RemoveStream(timer_stream.GetIndex()); + timer_stream.SetIndex(-1); + } + msec = 0; + } + else + { + // The "negative" dueTime makes a "relative" waitable timer + LARGE_INTEGER dueTime; + dueTime.QuadPart = (LONGLONG)(-(timerDelay * 1.0e+07)); // convert to 100 nsec ticks + LONG period = 0; + if (0 == SetWaitableTimer(timer_stream.GetDescriptor(), &dueTime, 0, NULL, NULL, FALSE)) + { + PLOG(PL_ERROR, "ProtoDispatcher::Wait() SetWaitableTimer() error: %s\n", GetErrorString()); + msec = (DWORD)(1000.0 * timerDelay); + if ((timerDelay > 0.0) && (0 == msec) && !precise_timing) msec = 1; + timer_active = false; + } + else + { + if (timer_stream.GetIndex() < 0) + { + int index = Win32AddStream(timer_stream, timer_stream.GetDescriptor()); + if (index < 0) + { + PLOG(PL_ERROR, "ProtoDispatcher::Wait() error: unable to insert timer_stream\n"); + msec = (DWORD)(1000.0 * timerDelay); + if ((timerDelay > 0.0) && (0 == msec) && !precise_timing) msec = 1; + timer_active = false; + } + else + { + timer_stream.SetIndex(index); + timer_active = true; + } + } + else + { + timer_active = true; + } + } + } +#else DWORD msec = (timerDelay < 0.0) ? INFINITE : ((DWORD)(1000.0 * timerDelay)); - // If (false == precise_timing) enforce minimum delay of 1 msec - // (Note "precise_timing" will consume additional CPU resources) + // (Note the busy-wait "precise_timing" will consume additional CPU resources) if ((timerDelay > 0.0) && (0 == msec) && !precise_timing) msec = 1; - - // Don't wait if any sockets are still "OutputReady()" - // Monitor socket streams ... - SocketStream* nextSocket = socket_stream_list; - while (nextSocket) - { - ProtoSocket& theSocket = nextSocket->GetSocket(); - if (theSocket.IsInputReady() || - (theSocket.NotifyOutput() && theSocket.IsOutputReady())) - { - msec = 0; - socket_io_pending = true; - break; - } - nextSocket = (SocketStream*)nextSocket->GetNext(); - } - // Set some waitFlags +#endif // else/if USE_WAITABLE_TIMER + + // Set some waitFlags DWORD waitFlags = 0; // on WinNT 4.0, getaddrinfo() doesn't work, so we check the OS version // to decide what to do. Try "gethostbyaddr()" if it's an old OS (e.g. NT 4.0 or earlier) @@ -1437,26 +2505,43 @@ void ProtoDispatcher::Dispatch() PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() MsgWaitForMultipleObjectsEx() error: %s\n", errorString); break; } - - //case WAIT_TIMEOUT: // timeout condition default: - // Handle any sockets that are "io_pending" - // (WIN32 only does "edge triggering" on sockets) - if (socket_io_pending) - { - SocketStream* nextSocketStream = socket_stream_list; - while (nextSocketStream) + { + // Handle any "ready" sockets that are "io_pending" + // (e.g., WIN32 only does "edge triggering" on sockets) + // TBD - keep a separate list of io_pending socket streams? + StreamList::Iterator sit(ready_stream_list); + Stream* nextStream; + while (NULL != (nextStream = sit.GetNextItem())) + { + switch (nextStream->GetType()) { - ProtoSocket& theSocket = nextSocketStream->GetSocket(); - nextSocketStream = (SocketStream*)nextSocketStream->GetNext(); - - // (TBD) Make this safer (i.e. if notification destroys socket) - if (theSocket.IsInputReady()) - theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); - if (theSocket.NotifyOutput() && theSocket.IsOutputReady()) - theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + case Stream::CHANNEL: + { + ProtoChannel& theChannel = static_cast(nextStream)->GetChannel(); + // (TBD) Make this safer (i.e. if notification destroys channel) + if (theChannel.InputNotification()) + theChannel.OnNotify(ProtoChannel::NOTIFY_INPUT); + if (theChannel.OutputNotification()) + theChannel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); + break; + } + case Stream::SOCKET: + { + ProtoSocket& theSocket = static_cast(nextStream)->GetSocket(); + // (TBD) Make this safer (i.e. if notification destroys socket) + if (theSocket.InputNotification()) + theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); + if (theSocket.OutputNotification()) + theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + break; + } + default: + { + ASSERT(0); + break; + } } - socket_io_pending = false; } if (WAIT_TIMEOUT == wait_status) { @@ -1465,75 +2550,84 @@ void ProtoDispatcher::Dispatch() else if ((WAIT_OBJECT_0 <= wait_status) && (wait_status < (WAIT_OBJECT_0 + stream_count))) { unsigned int index = wait_status - WAIT_OBJECT_0; - Stream* stream = stream_ptrs_array[index]; - if (Stream::SOCKET == stream->GetType()) - { - ProtoSocket& theSocket = static_cast(stream)->GetSocket(); - if (ProtoSocket::LOCAL != theSocket.GetDomain()) - { - WSANETWORKEVENTS event; - if (0 == WSAEnumNetworkEvents(theSocket.GetHandle(), stream_handles_array[index], &event)) - { - if (0 != (event.lNetworkEvents & (FD_READ | FD_ACCEPT))) - theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); - - if (0 != (event.lNetworkEvents & FD_WRITE)) - theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); - - if (0 != (event.lNetworkEvents & FD_CLOSE)) - { - theSocket.SetClosing(true); - - if (0 == event.iErrorCode[FD_CLOSE_BIT]) - theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); - else - theSocket.OnNotify(ProtoSocket::NOTIFY_ERROR); - } - if (0 != (event.lNetworkEvents & FD_CONNECT)) - { - if (0 == event.iErrorCode[FD_CONNECT_BIT]) - theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); - else - theSocket.OnNotify(ProtoSocket::NOTIFY_ERROR); - } - if (0 != (event.lNetworkEvents & FD_ADDRESS_LIST_CHANGE)) - { - if (0 == event.iErrorCode[FD_ADDRESS_LIST_CHANGE_BIT]) - theSocket.OnNotify(ProtoSocket::NOTIFY_EXCEPTION); - else - theSocket.OnNotify(ProtoSocket::NOTIFY_ERROR); - } - } - else - { - PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() WSAEnumNetworkEvents() error\n"); - } - } - else - { - // "LOCAL" domain ProtoSockets are really ProtoPipes in Win32 - if (index == (unsigned int)stream->GetIndex()) - theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); - else // (index == stream->GetOutdex()) - theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); - } - } - else if (Stream::CHANNEL == stream->GetType()) - { - ProtoChannel& theChannel = static_cast(stream)->GetChannel(); - if (index == (unsigned int)stream->GetIndex()) - theChannel.OnNotify(ProtoChannel::NOTIFY_INPUT); - else // (index == stream->GetOutdex()) - theChannel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); - } - else - { - // (TBD) Can we test the handle for input/output readiness? - if (stream->IsInput()) - static_cast(stream)->OnEvent(EVENT_INPUT); - if (stream->IsOutput()) - static_cast(stream)->OnEvent(EVENT_OUTPUT); - } + Stream* stream = stream_ptrs_array[index]; + switch (stream->GetType()) + { + case Stream::SOCKET: + { + ProtoSocket& theSocket = static_cast(stream)->GetSocket(); + WSANETWORKEVENTS event; + if (0 == WSAEnumNetworkEvents(theSocket.GetHandle(), stream_handles_array[index], &event)) + { + if (0 != (event.lNetworkEvents & (FD_READ | FD_ACCEPT))) + theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); + + if (0 != (event.lNetworkEvents & FD_WRITE)) + theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + + if (0 != (event.lNetworkEvents & FD_CLOSE)) + { + theSocket.SetClosing(true); + if (0 == event.iErrorCode[FD_CLOSE_BIT]) + theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT); + else + theSocket.OnNotify(ProtoSocket::NOTIFY_ERROR); + } + if (0 != (event.lNetworkEvents & FD_CONNECT)) + { + if (0 == event.iErrorCode[FD_CONNECT_BIT]) + theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT); + else + theSocket.OnNotify(ProtoSocket::NOTIFY_ERROR); + } + if (0 != (event.lNetworkEvents & FD_ADDRESS_LIST_CHANGE)) + { + if (0 == event.iErrorCode[FD_ADDRESS_LIST_CHANGE_BIT]) + theSocket.OnNotify(ProtoSocket::NOTIFY_EXCEPTION); + else + theSocket.OnNotify(ProtoSocket::NOTIFY_ERROR); + } + } + else + { + PLOG(PL_ERROR, "ProtoDispatcher::Dispatch() WSAEnumNetworkEvents() error\n"); + } + break; + } // end case Stream::SOCKET + case Stream::CHANNEL: + { + ProtoChannel& theChannel = static_cast(stream)->GetChannel(); + if (index == (unsigned int)stream->GetIndex()) + theChannel.OnNotify(ProtoChannel::NOTIFY_INPUT); + else // (index == stream->GetOutdex()) + theChannel.OnNotify(ProtoChannel::NOTIFY_OUTPUT); + break; + } + case Stream::TIMER: + { +#ifdef USE_WAITABLE_TIMER + // Our one and only "timer_stream" + ResetEvent(timer_stream.GetDescriptor()); + timer_active = false; +#endif // USE_WAITABLE_TIMER + break; + } + case Stream::EVENT: + { + // our one and only "break" event + ResetEvent(break_stream.GetDescriptor()); + break; + } + case Stream::GENERIC: + { + // (TBD) Can we test the handle for input/output readiness? + if (stream->IsInput()) + static_cast(stream)->OnEvent(EVENT_INPUT); + if (stream->IsOutput()) + static_cast(stream)->OnEvent(EVENT_OUTPUT); + break; + } + } // end switch (stream->GetType()) } else if ((WAIT_OBJECT_0 + stream_count) == wait_status) { @@ -1562,6 +2656,7 @@ void ProtoDispatcher::Dispatch() // WAIT_ABANDONED or WAIT_IO_COMPLETION } break; + } // end case default } // end switch(status) OnSystemTimeout(); } // end ProtoDispatcher::Dispatch() @@ -1599,7 +2694,7 @@ LRESULT CALLBACK ProtoDispatcher::MessageHandler(HWND hwnd, UINT message, WPARAM #endif // WIN32 /** - * UNIX ProtoDispatcher::Controller implementation + * ProtoDispatcher::Controller implementation */ ProtoDispatcher::Controller::Controller(ProtoDispatcher& theDispatcher) : dispatcher(theDispatcher), use_lock_a(true) diff --git a/src/common/protoFile.cpp b/src/common/protoFile.cpp index e6c15bf..b10ce27 100644 --- a/src/common/protoFile.cpp +++ b/src/common/protoFile.cpp @@ -718,7 +718,7 @@ bool ProtoDirectoryIterator::GetNextFile(char* fileName) if (nameLen < PATH_MAX) fileName[nameLen] = '\0'; return true; } - else if (ProtoFile::DIRECTORY == type) + else if (ProtoFile::DIRECTORY == type && search_dirs) { ProtoDirectory *dir = new ProtoDirectory(ptr, current); @@ -786,7 +786,7 @@ bool ProtoDirectoryIterator::GetNextFile(char* fileName) if (nameLen < PATH_MAX) fileName[nameLen] = '\0'; return true; } - else if (ProtoFile::DIRECTORY == type) + else if (ProtoFile::DIRECTORY == type && search_dirs) { ProtoDirectory *dir = new ProtoDirectory(dp->d_name, current); if (dir && dir->Open()) @@ -827,7 +827,12 @@ bool ProtoDirectoryIterator::GetNextFile(char* fileName) } } // end ProtoDirectoryIterator::GetNextFile() (UNIX) #endif // if/else WIN32 - +void +ProtoDirectoryIterator::Recursive(bool stepIntoDirs) +{ + search_dirs = stepIntoDirs; + return; +} ProtoDirectoryIterator::ProtoDirectory::ProtoDirectory(const char* thePath, ProtoDirectory* theParent) : parent(theParent), @@ -1324,11 +1329,11 @@ ProtoFileList::DirectoryItem::~DirectoryItem() } bool ProtoFileList::DirectoryItem::GetNextFile(char* thePath, - bool reset, - bool updatesOnly, - time_t lastTime, - time_t thisTime, - time_t& bigTime) + bool reset, + bool updatesOnly, + time_t lastTime, + time_t thisTime, + time_t& bigTime) { if (reset) { @@ -1340,7 +1345,7 @@ bool ProtoFileList::DirectoryItem::GetNextFile(char* thePath, if (updates_only) { // Check to see if directory has been touched - time_t update_time = MdpFileGetUpdateTime(path); + time_t update_time = ProtoFile::GetUpdateTime(path); if (updateTime > bigTime) *bigTime = updateTime; if ((updateTime <= lastTime) || (updateTime > thisTime)) return false; diff --git a/src/common/protoGraph.cpp b/src/common/protoGraph.cpp index 50486db..8bf6b3a 100644 --- a/src/common/protoGraph.cpp +++ b/src/common/protoGraph.cpp @@ -752,7 +752,7 @@ ProtoGraph::Vertice* ProtoGraph::SimpleTraversal::GetNextVertice(unsigned int* l if (depth_first) { queue_pending.Prepend(*nextVertice); - // (TBD) depth tracking for Depth-first search + // (TBD) level tracking for Depth-first search } else { diff --git a/src/common/protoJson.cpp b/src/common/protoJson.cpp new file mode 100644 index 0000000..9ff2015 --- /dev/null +++ b/src/common/protoJson.cpp @@ -0,0 +1,1529 @@ +#include "protoJson.h" +#include "protoDebug.h" +#include // for tolower() +#include + +#include "protoCheck.h" + +ProtoJson::Item::Item(Type theType, Item* theParent) + : type(theType), parent(theParent), level((NULL == theParent) ? 0 : theParent->level + 1) +{ +} + +ProtoJson::Item::~Item() +{ +} + +const char* ProtoJson::Item::GetTypeString(Type type) +{ + switch (type) + { + case INVALID: + return "INVALID"; + case ENTRY: + return "ENTRY"; + case STRING: + return "STRING"; + case NUMBER: + return "NUMBER"; + case OBJECT: + return "OBJECT"; + case ARRAY: + return "ARRAY"; + case TRUE: + return "TRUE"; + case FALSE: + return "FALSE"; + case NONE: + return "NULL"; + } + ASSERT(0); + return NULL; +} // end ProtoJson::Parser::GetTypeString() + +ProtoJson::String::String(Item* theParent) + : Item(STRING, theParent), text(NULL) +{ +} + +ProtoJson::String::~String() +{ + if (NULL != text) + { + delete[] text; + text = NULL; + } +} + +bool ProtoJson::String::Set(const char* theText) +{ + if (NULL != text) delete[] text; + if (NULL == (text = new char[strlen(theText)+1])) + { + PLOG(PL_ERROR, "ProtoJson::String::Set() new char[] error: %s\n", GetErrorString()); + return false; + } + strcpy(text, theText); + return true; +} // end ProtoJson::String::Set() + +void ProtoJson::String::SetTextPtr(char* textPtr) +{ + if (NULL != text) delete[] text; + text = textPtr; +} // end ProtoJson::String::SetTextPtr() + +ProtoJson::Number::Number(Item* theParent) + : Item(NUMBER, theParent), is_float(false), integer(0) +{ +} + + +ProtoJson::Number::Number(int value, Item* theParent) + : Item(NUMBER, theParent), is_float(false), integer(value) +{ + SetValue(value); +} + +ProtoJson::Number::Number(double value, Item* theParent) + : Item(NUMBER, theParent), is_float(true), floating(value) +{ +} + +ProtoJson::Number::~Number() +{ +} + +// A null-terminated string MUST be supplied here +bool ProtoJson::Number::SetValue(const char* text) +{ + // If the text contains a '.' or 'e', or 'E', it could be a float + bool isFloat = false; + const char* ptr = text; + while (!isFloat && ('\0' != *ptr)) + { + switch (*ptr++) + { + case '.': + case 'E': + case 'e': + isFloat = true; + break; + default: + break; + } + } + if (isFloat) + { + double value; + if (1 != sscanf(text, "%lf", &value)) + { + PLOG(PL_ERROR, "ProtoJson::Number::SetValue() error: invalid floating point number text\n"); + return false; + } + SetValue(value); + } + else + { + int value; + if (1 != sscanf(text, "%d", &value)) + { + PLOG(PL_ERROR, "ProtoJson::Number::SetValue() error: invalid integer number text\n"); + return false; + } + SetValue(value); + } + return true; +} // end ProtoJson::Number::SetValue(text) + +ProtoJson::Array::Array(Item* theParent) + : Item(ARRAY, theParent), array_buf(NULL), array_len(0) +{ +} + +ProtoJson::Array::~Array() +{ + Destroy(); +} + +void ProtoJson::Array::Destroy() +{ + for (unsigned int i = 0; i < array_len; i++) + { + Item* item = array_buf[i]; + if (NULL != item) delete item; + } + if (NULL != array_buf) + { + delete[] array_buf; + array_buf = NULL; + } + array_len = 0; +} // end ProtoJson::Array::Destroy() + +bool ProtoJson::Array::AppendValue(Value& value) +{ + // TBD - should we do more clever memory mgmnt for better performance + // at expense of a little extra memory usage? + unsigned int len = array_len + 1; + Value** buf = new Value*[len]; + if (NULL == buf) + { + PLOG(PL_ERROR, "ProtoJson::Array::AppendValue() new array buffer error: %s\n", GetErrorString()); + return false; + } + if (NULL != array_buf) + { + memcpy(buf, array_buf, array_len*sizeof(Value*)); + delete[] array_buf; + } + buf[array_len] = &value; + array_buf = buf; + array_len = len; + value.SetParent(this); + return true; +} // end ProtoJson::Array::AppendValue() + +// "index" MUST be in existing array range for now +void ProtoJson::Array::SetValue(unsigned int index, Value& value) +{ + ClearValue(index); + if (index < array_len) + { + array_buf[index] = &value; + value.SetParent(this); + } + else + { + PLOG(PL_ERROR, "ProtoJson::Array::SetValue() error: out-of-bounds index!\n"); + } +} // end ProtoJson::Array::SetValue() + +void ProtoJson::Array::ClearValue(unsigned int index) +{ + if (index < array_len) + { + Value* value = array_buf[index]; + array_buf[index] = NULL; + delete value; + } +} // end ProtoJson::Array::ClearValue() + + +const ProtoJson::Value* ProtoJson::Array::GetValue(unsigned int index) const +{ + if (index < array_len) + { + return array_buf[index]; + } + else + { + PLOG(PL_WARN, "ProtoJson::Array::GetValue() warning: out-of-bounds index!\n"); + return NULL; + } +} // end ProtoJson::Array::GetValue() + +ProtoJson::Value* ProtoJson::Array::AccessValue(unsigned int index) +{ + if (index < array_len) + { + return array_buf[index]; + } + else + { + PLOG(PL_WARN, "ProtoJson::Array::GetValue() warning: out-of-bounds index!\n"); + return NULL; + } +} // end ProtoJson::Array::GetValue() + +ProtoJson::Object::Object(ProtoJson::Item* theParent) + : ProtoJson::Item(OBJECT, theParent) +{ +} + +ProtoJson::Object::~Object() +{ + Destroy(); +} + +void ProtoJson::Object::Destroy() +{ + ProtoSortedTreeTemplate::Destroy(); +} // end ProtoJson::Object::Destroy() + + bool ProtoJson::Object::InsertEntry(const char* key, Value& value) + { + Entry* entry = new Entry(); + if (entry->SetKey(key)) + { + entry->SetValue(&value); + return InsertEntry(*entry); + } + else + { + PLOG(PL_ERROR, "ProtoJson::Object::InsertEntry() new Entry() error: %s\n", GetErrorString()); + return false; + } + } // end ProtoJson::Object::InsertEntry() + +bool ProtoJson::Object::InsertEntry(Entry& entry) +{ + if (Insert(entry)) + { + entry.SetParent(this); + return true; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Object::InsertEntry() error: %s\n", GetErrorString()); + return false; + } +} // end ProtoJson::Object::InsertEntry() + +ProtoJson::Object::Iterator::Iterator(Object& object, bool reverse) + : ProtoSortedTreeTemplate::Iterator(object, reverse), match_key(NULL) + +{ +} + +ProtoJson::Object::Iterator::~Iterator() +{ + if (NULL != match_key) + { + delete[] match_key; + match_key = NULL; + } +} + +ProtoJson::Entry* ProtoJson::Object::Iterator::GetNextEntry(const char* key) +{ + + if (NULL != key) + { + // Check to see if it is a new or different key + bool initKey = (NULL == match_key) || (0 != strcmp(key, match_key)); + if (initKey) + { + if (NULL != match_key) delete[] match_key; + if (NULL == (match_key = new char[strlen(key) + 1])) + { + PLOG(PL_ERROR, "ProtoJson::Object::Iterator::GetNextEntry() new match_key error: %s\n", GetErrorString()); + return NULL; + } + strcpy(match_key, key); + unsigned int keysize = (strlen(key) + 1) * 8; + Reset(false, key, keysize); + } + } + return ProtoSortedTreeTemplate::Iterator::GetNextItem(); +} // end ProtoJson::Object::Iterator::GetNextEntry() + +ProtoJson::Entry* ProtoJson::Object::Iterator::GetPrevEntry(const char* key) +{ + + if (NULL != key) + { + // Check to see if it is a new or different key + bool initKey = (NULL == match_key) || (0 != strcmp(key, match_key)); + if (initKey) + { + if (NULL != match_key) delete[] match_key; + if (NULL == (match_key = new char[strlen(key) + 1])) + { + PLOG(PL_ERROR, "ProtoJson::Object::Iterator::GetPrevEntry() new match_key error: %s\n", GetErrorString()); + return NULL; + } + strcpy(match_key, key); + unsigned int keysize = (strlen(key) + 1) * 8; + Reset(true, key, keysize); + } + } + return ProtoSortedTreeTemplate::Iterator::GetPrevItem(); +} // end ProtoJson::Object::Iterator::GetPrevEntry() + +ProtoJson::Entry::Entry(ProtoJson::Item* theParent) + : ProtoJson::Item(ENTRY, theParent), key(NULL), keysize(0), value(NULL) +{ +} + +ProtoJson::Entry::~Entry() +{ + if (NULL != value) + { + delete value; + value = NULL; + } + if (NULL != key) + { + delete[] key; + key = NULL; + } + keysize = 0; +} + +bool ProtoJson::Entry::SetKey(const char* text) +{ + if (NULL != key) delete[] key; + keysize = strlen(text) + 1; // include null termination + if (NULL == (key = new char[keysize])) + { + keysize = 0; + return false; + } + strcpy(key, text); + keysize = keysize << 3; // convert bytes to bits + return true; +} // end ProtoJson::Entry::SetKey() + +void ProtoJson::Entry::SetValue(Value* theValue) +{ + if (NULL != value) delete value; + if (NULL != theValue) theValue->SetParent(this); + value = theValue; +} // end ProtoJson::Entry::SetValue() + + +ProtoJson::Document::Document() + : item_count(0) +{ +} + +ProtoJson::Document::~Document() +{ + item_list.Destroy(); + item_count = 0; +} + +bool ProtoJson::Document::AddItem(ProtoJson::Item& item) +{ + if (item_list.Append(item)) + { + item.SetParent(NULL); + item_count++; + return true; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Document::AddItem() error: %s\n", GetErrorString()); + return false; + } +} // end ProtoJson::Document::AddItem() + +void ProtoJson::Document::RemoveItem(ProtoJson::Item& item) +{ + if (item_list.Contains(item)) + { + item_list.Remove(item); + item_count--; + } +} // end ProtoJson::Document::RemoveItem() + + +void ProtoJson::Document::Print(FILE* filePtr) +{ + ItemList stack; + unsigned int stackDepth = 0; + const char* indent = " "; // 4 spaces + Iterator iterator(*this); + ProtoJson::Value* prevValue = NULL; + ProtoJson::Value* value; + + if (item_count > 1) + { + // If document has multiple top level items, we + // present them as a top level array + fprintf(filePtr, "[\n %s", indent); + stackDepth = 1; + } + while (NULL != (value = iterator.GetNextItem())) + { + if (NULL != prevValue) + { + ProtoJson::Value* savePrev = prevValue; + while (value->GetParent() != stack.GetHead()) + { + prevValue = stack.RemoveHead(); + stackDepth--; + switch (prevValue->GetType()) + { + case Value::OBJECT: + if(Value::OBJECT != savePrev->GetType()) + { + fprintf(filePtr, "\n"); + for (unsigned int i = 0; i < stackDepth; i++) + fprintf(filePtr, " %s", indent); + } + // else was an empty object + fprintf(filePtr, "}"); + break; + case Value::ARRAY: + if(Value::ARRAY != savePrev->GetType()) + { + fprintf(filePtr, "\n"); + for (unsigned int i = 0; i < stackDepth; i++) + fprintf(filePtr, " %s", indent); + } + // else was an empty array + fprintf(filePtr, "]"); + break; + default: + // should be an ENTRY? + break; + } + } + // Note this does _not_ comma delimit top level document items (is that correct???) + //if ((NULL != value->GetParent()) && (value->GetParent() == prevValue->GetParent())) + if (value->GetParent() == prevValue->GetParent()) + fprintf(filePtr, ","); + + + if ((Value::ENTRY != savePrev->GetType()) || + (Value::OBJECT == value->GetType()) || + (Value::ARRAY == value->GetType())) + { + fprintf(filePtr, "\n"); + for (unsigned int i = 0; i < stackDepth; i++) + fprintf(filePtr, " %s", indent); + } + } + + PrintValue(filePtr, *value); + + switch (value->GetType()) + { + case Value::OBJECT: + case Value::ARRAY: + case Value::ENTRY: + stack.Prepend(*value); + stackDepth++; + break; + default: + break; + } + + prevValue = value; + + } + while (NULL != prevValue) + { + switch (prevValue->GetType()) + { + case Value::OBJECT: + fprintf(filePtr, "\n"); + for (unsigned int i = 0; i < stackDepth; i++) + fprintf(filePtr, " %s", indent); + fprintf(filePtr, "}"); + break; + case Value::ARRAY: + fprintf(filePtr, "\n"); + for (unsigned int i = 0; i < stackDepth; i++) + fprintf(filePtr, " %s", indent); + fprintf(filePtr, "]"); + break; + default: + // should be an ENTRY? + break; + } + prevValue = stack.RemoveHead(); + if(NULL != prevValue) stackDepth--; + } + fprintf(filePtr, "\n"); + if (item_count > 1) fprintf(filePtr, "]\n"); + +} // end ProtoJson::Document::Print() + +void ProtoJson::Document::PrintValue(FILE* filePtr, const Value& value) +{ + switch (value.GetType()) + { + case Value::ENTRY: + { + const Entry& entry = static_cast(value); + fprintf(filePtr, "\"%s\" : ", entry.GetKey()); + break; + } + case Value::STRING: + { + const char* text = static_cast(value).GetText(); + fprintf(filePtr, "\"%s\"", (NULL != text) ? text : ""); + break; + } + case Value::NUMBER: + { + const Number& number = static_cast(value); + if (number.IsFloat()) + fprintf(filePtr, "%f", number.GetDouble()); + else + fprintf(filePtr, "%d", number.GetInteger()); + break; + } + case Value::OBJECT: + fprintf(filePtr, "{"); + break; + case Value::ARRAY: + fprintf(filePtr, "["); + break; + case Value::TRUE: + fprintf(filePtr, "true"); + break; + case Value::FALSE: + fprintf(filePtr, "false"); + break; + case Value::NONE: + fprintf(filePtr, "null"); + break; + default: + ASSERT(0); + break; + } +} // end ProtoJson::Document::PrintValue() + +ProtoJson::Document::Iterator::Iterator(Document& document, bool depthFirst) + : list_iterator(document.item_list), depth_first(depthFirst) +{ +} + +ProtoJson::Document::Iterator::~Iterator() +{ +} + +ProtoJson::Item* ProtoJson::Document::Iterator::GetNextItem() +{ + Value* currentItem = pending_list.RemoveHead(); + if (NULL == currentItem) + currentItem = list_iterator.GetNextItem(); + if (NULL != currentItem) + { + if (Value::ARRAY == currentItem->GetType()) + { + // Put array items into pending_list + Array* array = static_cast(currentItem); + unsigned arrayLength = array->GetLength(); + for (unsigned int i = 0; i < arrayLength; i++) + { + unsigned int index = arrayLength - i - 1; + Value* value = array->AccessValue(index); + if (NULL == value) + { + if (NULL == (value = new NullValue(value))) + { + PLOG(PL_ERROR, "ProtoJson::Document::Iterator::GetNextItem() new NullValue() error: %s\n", + GetErrorString()); + return NULL; + } + array->SetValue(index, *value); + } + bool result = depth_first ? pending_list.Prepend(*value) : pending_list.Append(*value); + if (!result) + { + PLOG(PL_ERROR, "ProtoJson::Document::Iterator::GetNextItem() error: unable to update pending_list\n"); + return NULL; + } + } + } + else if (Value::OBJECT == currentItem->GetType()) + { + // Put object entries into pending_list + Object* object = static_cast(currentItem); + Object::Iterator iterator(*object, true); // reverseto make pending_list order right + Entry* entry; + while (NULL != (entry = iterator.GetPrevEntry())) + { + bool result = depth_first ? pending_list.Prepend(*entry) : pending_list.Append(*entry); + if (!result) + { + PLOG(PL_ERROR, "ProtoJson::Document::Iterator::GetNextItem() error: unable to update pending_list\n"); + return NULL; + } + } + } + else if (Value::ENTRY == currentItem->GetType()) + { + // Put entry value into pending_list + Entry* entry = static_cast(currentItem); + Value* value = entry->AccessValue(); + if (NULL == value) + { + if (NULL == (value = new NullValue(entry))) + { + PLOG(PL_ERROR, "ProtoJson::Document::Iterator::GetNextItem() new NullValue() error: %s\n", + GetErrorString()); + return NULL; + } + entry->SetValue(value); + } + bool result = depth_first ? pending_list.Prepend(*value) : pending_list.Append(*value); + if (!result) + { + PLOG(PL_ERROR, "ProtoJson::Document::Iterator::GetNextItem() error: unable to update pending_list\n"); + return NULL; + } + } + } + return currentItem; +} // end ProtoJson::Document::Iterator::GetNextItem() + +// Delimiters for parsing +const char ProtoJson::Parser::OBJECT_START = '{'; +const char ProtoJson::Parser::OBJECT_END = '}'; +const char ProtoJson::Parser::ARRAY_START = '['; +const char ProtoJson::Parser::ARRAY_END = ']'; +const char ProtoJson::Parser::QUOTE ='\"'; +const char ProtoJson::Parser::COLON = ':'; +const char ProtoJson::Parser::COMMA = ','; +const char ProtoJson::Parser::ESCAPE = '\\'; +const char ProtoJson::Parser::TRUE_START = 't'; +const char ProtoJson::Parser::FALSE_START = 'f'; +const char ProtoJson::Parser::NULL_START = 'n'; + +ProtoJson::Parser::Parser() + : current_document(NULL), current_item(NULL), input_offset(0), + is_escaped(false), seek_colon(false), temp_buffer(NULL), + temp_buffer_max(0), temp_buffer_len(0) +{ +} + +ProtoJson::Parser::~Parser() +{ + Destroy(); +} + +void ProtoJson::Parser::Destroy() +{ + if (NULL != current_document) + { + current_document->Destroy(); + delete current_document; + current_document = NULL; + } + if (NULL != current_item) + { + delete current_item; + current_item = NULL; + } + input_offset = 0; + is_escaped = false; + seek_colon = false; + if (NULL != temp_buffer) + { + delete[] temp_buffer; + temp_buffer = NULL; + } + temp_buffer_len = temp_buffer_max = 0; +} // end ProtoJson::Parser::Destroy() + +bool ProtoJson::Parser::LoadDocument(const char *path) +{ + FILE* infile = fopen(path, "r"); + if (NULL == infile) + { + PLOG(PL_ERROR, "ProtoJson::Parser::LoadDocument() error opening file: %s\n", GetErrorString()); + return false; + } + Status status = PARSE_MORE; + int result; + char buffer[1024]; + while (0 != (result = fread(buffer, sizeof(char), 1024, stdin))) + { + status = ProcessInput(buffer, result); + if (PARSE_ERROR == status) + { + PLOG(PL_ERROR, "ProtoJson::Parser::LoadDocument() error: invalid JSON document!\n"); + return false; + } + } + if (PARSE_MORE == status) + { + PLOG(PL_ERROR, "ProtoJson::Parser::LoadDocument() error: incomplete JSON document!\n"); + return false; + } + else + { + return true; + } +} // end ProtoJson::Parser::LoadDocument() + + +ProtoJson::Document* ProtoJson::Parser::DetachDocument() +{ + Document* doc = current_document; + current_document = NULL; + return doc; +} // end ProtoJson::Parser::DetachDocument() + + +// This infers the Item type from the first character +// of the Item's textual representation +ProtoJson::Item::Type ProtoJson::Parser::GetType(char c) +{ + c = tolower(c); + switch (c) + { + case OBJECT_START: + return Item::OBJECT; + case ARRAY_START: + return Item::ARRAY; + case QUOTE: + return Item::STRING; + case TRUE_START: + return Item::TRUE; + case FALSE_START: + return Item::FALSE; + case NULL_START: + return Item::NONE; + default: + return Item::NUMBER; + } +} // end ProtoJson::Parser::GetType() + + + +bool ProtoJson::Parser::AddValueToParent(Item* parent, Item& value) +{ + // Values may be added to one of 3 parent types + // 1) if NULL == parent, "value" is a root level item and MUST be an ARRAY or OBJECT, or + // 2) Value items may be appended to ARRAYs, or + // 3) Value items may be assigned to an Object Entry + + ASSERT(value.IsValue()); + + if (NULL == parent) + { + ASSERT((Item::ARRAY == value.GetType()) || (Item::OBJECT == value.GetType())); + current_document->AddItem(value); + } + else + { + switch (parent->GetType()) + { + case Item::ARRAY: + if (!static_cast(parent)->AppendValue(value)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::AddValueToParent() error: unable to append array\n"); + return false; + } + break; + case Item::ENTRY: + static_cast(parent)->SetValue(&value); + break; + default: + PLOG(PL_ERROR, "ProtoJson::Parser::AddValueToParent() error: invalid parent type\n"); + ASSERT(0); + return false; + } + } + return true; +} // end AddValueToParent() + +bool ProtoJson::Parser::AddToString(String& string, const char* text, unsigned int length) +{ + unsigned int total = string.GetLength() + length; + char* buffer = new char[total + 1]; // include null terminator + if (NULL == buffer) + { + PLOG(PL_ERROR, "ProtoJson::Parser::AddToString() new buffer error: %s\n", GetErrorString()); + return false; + } + if (NULL != string.GetText()) + strcpy(buffer, string.GetText()); + if (0 != length) + memcpy(buffer + string.GetLength(), text, length); + buffer[total] = '\0'; + string.SetTextPtr(buffer); + return true; +} // end ProtoJson::Parser::AddToString() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessStringInput(const char* input, unsigned int length) +{ + if (0 == length) return PARSE_MORE; // need more input + bool start = (NULL == current_item) ? true : false; + String* string; + if (start) + { + // We're starting a new string, so create using top of stack as parent + Item* parent = PeekStack(); + if ((NULL == parent) || ((Item::ENTRY != parent->GetType()) && (Item::ARRAY != parent->GetType()))) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessStringInput() error: invalid JSON syntax\n"); + return PARSE_ERROR; + } + string = new String(parent); + if (NULL == string) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessStringInput() new String error: %s\n", GetErrorString()); + return PARSE_ERROR; + } + current_item = string; + } + else + { + ASSERT(Item::STRING == current_item->GetType()); + string = static_cast(current_item); + } + unsigned int i = 0;//start ? 1 : 0; // to skip leading quote if applicable + const char* startPtr = input + i; + for (; i < length; i++) + { + char c = input[i]; + if (is_escaped) + { + is_escaped = false; + continue; + } + else if (ESCAPE == c) + { + is_escaped = true; + continue; + } + else if (QUOTE == c) + { + // We've found the end of the string + if (AddToString(*string, startPtr, i)) + { + // consume string text and end QUOTE + input_offset += i + 1; + ASSERT(NULL != string->GetParent()); + if (!AddValueToParent(string->AccessParent(), *string)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessStringInput() error: unable to add to parent\n"); + return PARSE_ERROR; + } + current_item = NULL; + return PARSE_DONE; + } + else + { + return PARSE_ERROR; + } + } + } + // Incomplete string, need more input + if (AddToString(*string, startPtr, length)) + { + input_offset += length; + return PARSE_MORE; + } + else + { + return PARSE_ERROR; + } +} // end ProtoJson::Parser::ProcessStringInput() + + +bool ProtoJson::Parser::AddToTemp(const char* text, unsigned int length) +{ + // TBD - strip.convert escape sequences? + if (0 == length) return true; + unsigned int total = temp_buffer_len + length; + if (total > temp_buffer_max) + { + char* buffer = new char[total+1]; // include null terminator + if (NULL == buffer) + { + PLOG(PL_ERROR, "ProtoJson::Parser::AddToTemp() new buffer error: %s\n", GetErrorString()); + return false; + } + //if (NULL != temp_buffer) + if (0 != temp_buffer_len) + { + memcpy(buffer, temp_buffer, temp_buffer_len); + delete[] temp_buffer; + } + memcpy(buffer + temp_buffer_len, text, length); + buffer[total] = '\0'; + temp_buffer = buffer; + temp_buffer_len = temp_buffer_max = total; + } + else + { + memcpy(temp_buffer + temp_buffer_len, text, length); + temp_buffer[total] = '\0'; + temp_buffer_len = total; + } + return true; +} // end ProtoJson::Parser::AddToTemp() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessNumberInput(const char* input, unsigned int length) +{ + if (0 == length) return PARSE_MORE; // need more input + bool start = (NULL == current_item) ? true : false; + Number* number; + if (start) + { + // We're starting a new number, so create using top of stack as parent + Item* parent = PeekStack(); + if ((NULL == parent) || ((Item::ENTRY != parent->GetType()) && (Item::ARRAY != parent->GetType()))) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() error: invalid JSON syntax\n"); + return PARSE_ERROR; + } + number = new Number(parent); + if (NULL == number) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() new Number error: %s\n", GetErrorString()); + return PARSE_ERROR; + } + current_item = number; + temp_buffer_len = 0; + } + else + { + ASSERT(Item::NUMBER == current_item->GetType()); + number = static_cast(current_item); + } + for (unsigned int i = 0; i < length; i++) + { + char c = input[i]; + if (isspace(c) || (COMMA == c) || (OBJECT_END == c) || (ARRAY_END == c)) + { + // end of number string found + if (AddToTemp(input, i)) + { + input_offset += i; // consume number text + if (!number->SetValue(temp_buffer)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() error: invalid number text\n"); + return PARSE_ERROR; + } + temp_buffer_len = 0; // reset temp_buffer + if (!AddValueToParent(number->AccessParent(), *number)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() error: unable to add number to parent\n"); + return PARSE_ERROR; + } + current_item = NULL; + return PARSE_DONE; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() error: unable to update temp_buffer\n"); + return PARSE_ERROR; + } + } + } + // incomplete number text, need more input + if (AddToTemp(input, length)) + { + input_offset += length; + return PARSE_MORE; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() error: unable to update temp_buffer\n"); + return PARSE_ERROR; + } +} // end ProtoJson::Parser::ProcessNumberInput() + +bool ProtoJson::Parser::FixedItemIsValid(Item::Type type) +{ + char* ptr = temp_buffer; + // Convert temp_buffer to lower case for validation + while ('\0' != *ptr) tolower(*ptr++); + switch (type) + { + case Item::TRUE: + return (0 == strcmp(temp_buffer, "true")); + break; + case Item::FALSE: + return (0 == strcmp(temp_buffer, "false")); + break; + case Item::NONE: + return (0 == strcmp(temp_buffer, "null")); + break; + default: + return false; + } +} // end ProtoJson::Parser::FixedItemIsValid() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessFixedInput(const char* input, unsigned int length) +{ + // This is used for "true", "false", and "null" value fields (i.e. fixed text) + // Basically, the first character gives it away, but we validate + if (0 == length) return PARSE_MORE; // need more input + bool start = (NULL == current_item) ? true : false; + if (start) + { + // We're starting a new number, so create using top of stack as parent + Item* parent = PeekStack(); + if ((NULL == parent) || ((Item::ENTRY != parent->GetType()) && (Item::ARRAY != parent->GetType()))) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessNumberInput() error: invalid JSON syntax\n"); + return PARSE_ERROR; + } + // TBD if parent is an object, then this MUST be the object's value field + char c = tolower(*input); + switch (c) + { + case TRUE_START: + current_item = new ProtoJson::TrueValue(parent); + break; + case FALSE_START: + current_item = new ProtoJson::FalseValue(parent); + break; + case NULL_START: + current_item = new ProtoJson::NullValue(parent); + break; + default: + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessFixedInput(%5s) error: invalid text\n", input); + return PARSE_ERROR; + } + } + ASSERT((Item::TRUE == current_item->GetType()) || + (Item::FALSE == current_item->GetType()) || + (Item::NONE == current_item->GetType())); + for (unsigned int i = 0; i < length; i++) + { + char c = input[i]; + if (isspace(c) || (COMMA == c) || (OBJECT_END == c) || (ARRAY_END == c)) + { + // end of fixed value found + if (AddToTemp(input, i)) + { + // consume fixed item chars + input_offset += i; + if (!FixedItemIsValid(current_item->GetType())) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessFixedInput(%s) error: invalid fixed text\n", temp_buffer); + return PARSE_ERROR; + } + temp_buffer_len = 0; // reset temp_buffer + if (!AddValueToParent(current_item->AccessParent(), *current_item)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessFixedInput() error: unable to add \"%s\" value to parent\n", temp_buffer); + return PARSE_ERROR; + } + current_item = NULL; + return PARSE_DONE; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessFixedInput() error: unable to update temp_buffer\n"); + return PARSE_ERROR; + } + } + } + // Did not yet find end of fixed text + if (AddToTemp(input, length)) + { + return PARSE_MORE; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessFixedInput() error: unable to update temp_buffer\n"); + return PARSE_ERROR; + } +} // end ProtoJson::Parser::ProcessFixedInput() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessArrayInput(const char* input, unsigned int length) +{ + // Seeking array value items or ARRAY_END + if (0 == length) return PARSE_MORE; // need more input + bool start = (NULL == current_item) ? true : false; + Array* array; + if (start) + { + if (NULL == (array = new Array(PeekStack()))) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessArrayInput() new Array error: %s\n", GetErrorString()); + return PARSE_ERROR; + } + current_item = array; + } + else + { + ASSERT(Item::ARRAY == current_item->GetType()); + array = static_cast(current_item); + } + + for (unsigned int i = 0; i < length; i++) + { + char c = input[i]; + if (isspace(c)) + continue; + else if (COMMA == c) + // TBD - validate that a comma is indeed followed by a value??? + // skipping blank array fields is permissive, but maybe OK? + continue; + else if (ARRAY_END == c) + { + // consume white space + ARRAY_END + input_offset += i + 1; + // Attach completed array to parent + if (!AddValueToParent(array->AccessParent(), *array)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessArrayInput() error: unable to add array to parent\n"); + return PARSE_ERROR; + } + current_item = NULL; + return PARSE_DONE; + } + else + { + // Should be first character of an array value item + PushStack(*current_item); + current_item = NULL; + input_offset += i; + return ProcessValueInput(input + i, length - i); + } + } + input_offset += length; + return PARSE_MORE; // more input needed +} // end ProtoJson::Parser::ProcessArrayInput() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessEntryInput(const char* input, unsigned int length) +{ + // Seeking entry key, Value, or end-of-entry delimiter + if (0 == length) return PARSE_MORE; // need more input + bool start = (NULL == current_item) ? true : false; + Entry* entry; + Object* object; // parent of entry + if (start) + { + // An ENTRY's parent MUST always be an OBJECT + ASSERT((NULL != PeekStack()) && (Item::OBJECT == PeekStack()->GetType())); + object = static_cast(PeekStack()); + if (NULL == (entry = new Entry(object))) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessEntryInput() new Entry error: %s\n", GetErrorString()); + return PARSE_ERROR; + } + current_item = entry; + } + else + { + ASSERT(Item::ENTRY == current_item->GetType()); + entry = static_cast(current_item); + object = static_cast(entry->AccessParent()); + } + // Notes: + // 1) First, the Entry "key" is sought, cached in temp_buffer and set upon completion + // 2) Second, the key must be followed by a colon delimiter so we find that + // 3) Then, we push the Entry to stack and parse for it's Value field, possibly going down multiple levels + // 4) When the Value is complete, it adds itself to the Entry and returns PARSE_DONE to upper layer (which pops stack) + // 5) At this point, we look for the end-of-entry (either COMMA or END_OBJECT) + // + // The key detail here is the Value item knows to add itself to Entry (or Array) parents upon completion. + // That happens in the code for the different Value input processors + + + enum Mode + { + SEEK_KEY, // accumulating key string (starts here) + SEEK_COLON, + SEEK_VALUE, // got key, look for value + SEEK_TERM // got value, look for end-of-entry (termination) delimiter + }; + Mode mode; + if (NULL == entry->GetKey()) + mode = SEEK_KEY; + else if (seek_colon) + mode = SEEK_COLON; + else if (NULL == entry->GetValue()) + mode = SEEK_VALUE; + else + mode = SEEK_TERM; + for (unsigned int i = 0; i < length; i++) + { + char c = input[i]; + switch (mode) + { + case SEEK_KEY: + if (is_escaped) + { + is_escaped = false; + continue; + } + else if (ESCAPE == c) + { + is_escaped = true; + continue; + } + else if (c == QUOTE) + { + // non-escaped QUOTE is end of key string + if (!AddToTemp(input, i)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessEntryInput() error: unable to add to temp_buffer\n"); + return PARSE_ERROR; + } + if (!entry->SetKey(temp_buffer)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessEntryInput() error: unable to set entry key\n"); + return PARSE_ERROR; + } + temp_buffer_len = 0; // reset temp_buffer + seek_colon = true; + mode = SEEK_COLON; + } + break; + case SEEK_COLON: + if (COLON == c) + { + // we don't need to consume anything here since + // it's just a mode change with no return + seek_colon = false; + mode = SEEK_VALUE; + } + break; + case SEEK_VALUE: + if (isspace(c)) + { + continue; // skip white space + } + else + { + // upper level will re-enter and mode with be SEEK_TERM if value completed + input_offset += i; // consume skipped white space + PushStack(*entry); + current_item = NULL; + return ProcessValueInput(input + i, length - i); + } + break; + case SEEK_TERM: + if (isspace(c)) + { + continue; // skip white space + } + switch (c) + { + // Either of these indicates end-of-entry, but note we + // preserve the OBJECT_END for the upper layer object parsing + case COMMA: + input_offset++; // consume comma + case OBJECT_END: + input_offset += i; // consume white space + if (!object->InsertEntry(*entry)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessEntryInput() error: entry insertion failure\n"); + return PARSE_ERROR; + } + current_item = NULL; // entry is finished, upper layer will pop object off stack + return PARSE_DONE; + default: + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessEntryInput() error: invalid JSON input\n"); + return PARSE_ERROR; + } + break; + } // end switch (mode) + } // end for (...) + // if we make it here, we need more input + // incomplete entry text, need more input + if (AddToTemp(input, length)) + { + input_offset += length; + return PARSE_MORE; + } + else + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessEntryInput() error: unable to update temp_buffer\n"); + return PARSE_ERROR; + } + + //input_offset += length; + //return PARSE_MORE; +} // end ProtoJson::Parser::ProcessEntryInput() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessObjectInput(const char* input, unsigned int length) +{ + // Seeking object key,value items or OBJECT_END + if (0 == length) return PARSE_MORE; // need more input + bool start = (NULL == current_item) ? true : false; + Object* object; + if (start) + { + if (NULL == (object = new Object(PeekStack()))) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessObjectInput() new Object error: %s\n", GetErrorString()); + return PARSE_ERROR; + } + current_item = object; + } + else + { + ASSERT(Item::OBJECT == current_item->GetType()); + object = static_cast(current_item); + } + + for (unsigned int i = 0; i < length; i++) + { + char c = input[i]; + if (isspace(c)) + continue; + else if (COMMA == c) + // TBD - validate that a comma is indeed followed by a value??? + // skipping blank array fields is permissive, but maybe OK? + continue; + else if (OBJECT_END == c) + { + input_offset += i + 1; // consume white space + OBJECT_END + // Attach completed object to parent + if (!AddValueToParent(object->AccessParent(), *object)) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessObjectInput() error: unable to add object to parent\n"); + return PARSE_ERROR; + } + current_item = NULL; // current_item completed + return PARSE_DONE; + } + else if (QUOTE == c) + { + // Should be first character of an object key string + PushStack(*current_item); + current_item = NULL; + input_offset += i + 1; // consume whitespace + QUOTE + return ProcessEntryInput(input + i + 1, length - (i + 1)); + } + else + { + // Invalid JSON syntax + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessObjectInput() error: invalid Object content\n"); + return PARSE_ERROR; + } + } + input_offset += length; + return PARSE_MORE; // more input needed +} // end ProtoJson::Parser::ProcessObjectInput() + +ProtoJson::Parser::Status ProtoJson::Parser::ProcessValueInput(const char* input, unsigned int length) +{ + ASSERT(NULL == current_item); + if (0 == length) return PARSE_MORE; + // This is invoked to process ARRAY or OBJECT Item items + // The first char of the "input" should be beginning of some Item item + // (string, number, object, array, true, false, null) + Item::Type type = GetType(input[0]); + switch (type) + { + case Item::STRING: + // consume QUOTE (string start delimiter) + input_offset++; + return ProcessStringInput(input+1, length-1); + case Item::NUMBER: + return ProcessNumberInput(input, length); + break; + case Item::OBJECT: + // consume OBJECT_START delimiter + input_offset++; + return ProcessObjectInput(input+1, length-1); + break; + case Item::ARRAY: + // consume ARRAY_START delimiter + input_offset++; + return ProcessArrayInput(input+1, length-1); + break; + case Item::TRUE: + case Item::FALSE: + case Item::NONE: + return ProcessFixedInput(input, length); + default: + // Will not occur + ASSERT(0); + return PARSE_ERROR; + } +} // end Status ProtoJson::Parser::ProcessValueInput(() + +// This routine will become the heart of +// our ProtoJson parser that processes input +// to form a ProtoJson document/tree + +// This consumes all of the input provided with the following return values: +// 1) PARSE_ERROR indicating a JSON syntax error (or memory allocation error) +// 2) PARSE_MORE indicating more input is needed to have a complete, valid JSON document +// 3) PARSE_DONE indicating a completed JSON document/stanza +ProtoJson::Parser::Status ProtoJson::Parser::ProcessInput(const char* inputBuffer, unsigned int inputLength) +{ + if (NULL == current_document) + { + if (NULL == (current_document = new Document())) + { + PLOG(PL_ERROR, "ProtoJson::Parser::ProcessInput() new document error: %s\n", GetErrorString()); + return PARSE_ERROR; + } + } + // Top level call to process some JSON input + Status result = PARSE_MORE; + input_offset = 0; + while (input_offset < inputLength) + { + ProtoCheckLogAllocations(stdout); + + const char* input = inputBuffer + input_offset; + unsigned int length = inputLength - input_offset; + // TBD - should we pop the stack down in the lower parser layers instead? + if (NULL == current_item) current_item = PopStack(); + if (NULL == current_item) + { + bool empty = true; + result = PARSE_DONE; // default result for empty document + // Seeking top level ARRAY or OBJECT + for (unsigned int i = 0; i < length; i++) + { + char c = input[i]; + // Skip white space + if (isspace(c)) continue; + switch (c) + { + case ARRAY_START: + // consume skipped white space + ARRAY_START + empty = false; + input_offset += (i + 1); + result = ProcessArrayInput(input + (i + 1), length - (i + 1)); + break; + case OBJECT_START: + // consume skipped white space + OBJECT_START + empty = false; + input_offset += (i + 1); + result = ProcessObjectInput(input + (i + 1), length - (i + 1)); + break; + default: + // invalid input at top level + return PARSE_ERROR; + } + break; // an increment of non-whitespace parsing occurred + } + if (empty) input_offset += length; // consumes skipped white space + if (PARSE_ERROR == result) break; + } + else + { + switch (current_item->GetType()) + { + case Item::ENTRY: + result = ProcessEntryInput(input, length); + break; + case Item::STRING: + result = ProcessStringInput(input, length); + break; + case Item::NUMBER: + result = ProcessNumberInput(input, length); + break; + case Item::OBJECT: + result = ProcessObjectInput(input, length); + break; + case Item::ARRAY: + result = ProcessArrayInput(input, length); + break; + default: + result = ProcessFixedInput(input, length); + break; + } + if (PARSE_ERROR == result) break; + //ASSERT ((result != PARSE_MORE) || (input_offset < inputLength)); + } + } // end while (length > 0) + if (PARSE_DONE == result) + { + // Only really "DONE" if no pending current_item or stack, else need more input + if ((NULL != current_item) || (NULL != PeekStack())) + return PARSE_MORE; // more input needed + else + return PARSE_DONE; + } + else + { + return result; + } +} // end ProtoJson::Parser::ProcessInput() + + + diff --git a/src/common/protoLFSR.cpp b/src/common/protoLFSR.cpp index c063105..b6400db 100644 --- a/src/common/protoLFSR.cpp +++ b/src/common/protoLFSR.cpp @@ -252,7 +252,8 @@ UINT32 ProtoLFSR::PolynomialSearch(unsigned int m) seq[i] = new char[LEN >> 3]; if (NULL == seq[i]) { - PLOG(PL_ERROR, "ProtoLFSR::PolynomialSearch() new 'seq[%lu] error: %s", i, GetErrorString()); + PLOG(PL_ERROR, "ProtoLFSR::PolynomialSearch() new 'seq[%lu] error: %s", + (unsigned long)i, GetErrorString()); for (UINT32 j = 0; j < i; j++) delete[] seq[j]; delete[] seq; @@ -279,7 +280,7 @@ UINT32 ProtoLFSR::PolynomialSearch(unsigned int m) } unsigned int wtMin = 0xffffffff; - unsigned int offset = 0; + //unsigned int offset = 0; for (unsigned int i = 1; i < LEN-1; i++) { unsigned int wt = 0; @@ -291,7 +292,7 @@ UINT32 ProtoLFSR::PolynomialSearch(unsigned int m) if (wt < wtMin) { wtMin = wt; - offset = i; + //offset = i; } } if (wtMin > maxMin) @@ -387,7 +388,7 @@ bool ProtoLFSRX::SetPolynomial(const UINT32* polynomial, return true; } // end ProtoLFSRX::SetPolynomial() - + void ProtoLFSRX::Reset(UINT32* initialState) { byte_mode = false; diff --git a/src/common/protoList.cpp b/src/common/protoList.cpp index 9bcbb9d..0b85504 100644 --- a/src/common/protoList.cpp +++ b/src/common/protoList.cpp @@ -146,6 +146,13 @@ ProtoList::Item* ProtoList::RemoveHead() return item; } // end ProtoList::RemoveHead() +ProtoList::Item* ProtoList::RemoveTail() +{ + Item* item = tail; + if (NULL != item) Remove(*item); + return item; +} // end ProtoList::RemoveTail() + void ProtoList::Empty() { UpdateIterators(NULL, Iterator::EMPTY); @@ -206,7 +213,7 @@ void ProtoList::ItemPool::Destroy() ProtoList::Iterator::Iterator(ProtoList& theList, bool reverse) - : ProtoIterable::Iterator(theList), ilist_prev(NULL), ilist_next(NULL) + : ProtoIterable::Iterator(theList) { Reset(reverse); } diff --git a/src/common/protoNet.cpp b/src/common/protoNet.cpp index fba08c9..26b543b 100644 --- a/src/common/protoNet.cpp +++ b/src/common/protoNet.cpp @@ -1,15 +1,44 @@ #include "protoNet.h" #include "protoDebug.h" - - unsigned int ProtoNet::GetInterfaceCount() { return GetInterfaceIndices(NULL, 0); } // end ProtoNet::GetInterfaceCount() -// given addrType, returns addrList with addresses found added +unsigned int ProtoNet::GetInterfaceIndex(const ProtoAddress& ifAddr) +{ + + char ifName[256]; + ifName[255] = '\0'; + if (GetInterfaceName(ifAddr, ifName, 255)) + { + return GetInterfaceIndex(ifName); + } + else + { + return 0; + } +} // end ProtoNet::GetInterfaceIndex(by address) +bool ProtoNet::GetInterfaceAddressList(unsigned int ifIndex, + ProtoAddress::Type addrType, + ProtoAddressList& addrList) +{ + char ifName[256]; + ifName[255] = '\0'; + if (GetInterfaceName(ifIndex, ifName, 255)) + { + return GetInterfaceAddressList(ifName, addrType, addrList); + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: invalid interface index?!\n"); + return false; + } +} // end GetInterfaceAddressList(by index) + +// given addrType, returns addrList with addresses found added // TBD - we may want to move this implementation to "unixNet.cpp" and create a separate // Win32-specific implementation using the Windows "GetAdaptersAddresses()" call which // does. @@ -18,7 +47,7 @@ bool ProtoNet::GetHostAddressList(ProtoAddress::Type addrType, ProtoAddressList& addrList) { // First determine how many interfaces there are - unsigned int ifCount = GetInterfaceIndices(NULL, 0); + unsigned int ifCount = GetInterfaceCount(); if (0 == ifCount) { PLOG(PL_WARN, "ProtoNet::GetHostAddressList() warning: no interfaces?!\n"); @@ -35,24 +64,34 @@ bool ProtoNet::GetHostAddressList(ProtoAddress::Type addrType, ifCount = GetInterfaceIndices(ifIndices, ifCount); for (unsigned int i = 0; i < ifCount; i++) { - char ifName[256]; - ifName[255] = '\0'; - if (GetInterfaceName(ifIndices[i], ifName, 255)) + if (!GetInterfaceAddressList(ifIndices[i], addrType, addrList)) { - if (!GetInterfaceAddressList(ifName, addrType, addrList)) - { - PLOG(PL_DEBUG, "ProtoNet::GetHostAddressList() error: unable to get addresses for iface index %d\n", ifIndices[i]); - } + PLOG(PL_DEBUG, "ProtoNet::GetHostAddressList() error: unable to get addresses for iface index %d\n", ifIndices[i]); } - else - { - PLOG(PL_DEBUG, "ProtoNet::GetHostAddressList() error: unable to get name for iface index %d\n", ifIndices[i]); - } } delete[] ifIndices; return true; // all interfaces found & list returned in addrList } // end ProtoNet::GetHostAddressList() +// given "addrType", searches through interface list, returns first non-loopback address found +// TBD - should we _try_ to find a non-link-local addr as well? +#ifndef WIN32 +// FindLocalAddress defined in win32Net.cpp +bool ProtoNet::FindLocalAddress(ProtoAddress::Type addrType, ProtoAddress& theAddress) +{ + ProtoAddressList addrList; + if (GetHostAddressList(addrType, addrList)) + { + ProtoAddressList::Iterator iterator(addrList); + while (iterator.GetNextAddress(theAddress)) + { + if (!theAddress.IsLoopback()) + return true; + } + } + return false; +} // end ProtoNet::FindLocalAddress() +#endif bool ProtoNet::GetInterfaceAddress(const char* ifName, ProtoAddress::Type addrType, ProtoAddress& theAddress, @@ -63,6 +102,77 @@ bool ProtoNet::GetInterfaceAddress(const char* ifName, return addrList.GetFirstAddress(theAddress); } // end ProtoNet::GetInterfaceAddress() +bool ProtoNet::GetInterfaceAddress(unsigned int ifIndex, + ProtoAddress::Type addrType, + ProtoAddress& theAddress) +{ + ProtoAddressList addrList; + GetInterfaceAddressList(ifIndex, addrType, addrList); + return addrList.GetFirstAddress(theAddress); +} // end ProtoNet::GetInterfaceAddress() + +#ifndef WIN32 +unsigned int ProtoNet::GetInterfaceAddressMask(unsigned int ifIndex, const ProtoAddress& ifAddr) +{ +#ifndef WIN32 + char ifName[256]; + ifName[255] = '\0'; + if (GetInterfaceName(ifIndex, ifName, 255)) + { + return GetInterfaceAddressMask(ifName, ifAddr); + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressMask() error: invalid interface index?!\n"); + return 0; + } +#else + PLOG(PL_ERROR,"ProtoNet::GetInterfaceAddressMask() error: function not implemented for WIN32\n"); + return 0; +#endif +} // end ProtoNet::GetInterfaceAddressMask() + +bool ProtoNet::AddInterfaceAddress(unsigned int ifIndex, const ProtoAddress& addr, unsigned int maskLen) +{ +#ifndef WIN32 + char ifName[256]; + ifName[255] = '\0'; + if (GetInterfaceName(ifIndex, ifName, 255)) + { + return AddInterfaceAddress(ifName, addr, maskLen); + } + else + { + PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress() error: invalid interface index?!\n"); + return false; + } +#else + PLOG(PL_ERROR,"ProtoNet::AddInterfaceAddress() error: function not implemented in WIN32\n"); + return false; +#endif +} // end ProtoNet::AddInterfaceAddress() + +bool ProtoNet::RemoveInterfaceAddress(unsigned int ifIndex, const ProtoAddress& addr, unsigned int maskLen) +{ +#ifndef WIN32 + char ifName[256]; + ifName[255] = '\0'; + if (GetInterfaceName(ifIndex, ifName, 255)) + { + return RemoveInterfaceAddress(ifName, addr, maskLen); + } + else + { + PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress() error: invalid interface index?!\n"); + return false; + } +#else + PLOG(PL_ERROR,"ProtoNet::RemoveInterfaceAddress() error: function not implemented in WIN32\n"); + return false; +#endif +} // end ProtoNet::RemoveInterfaceAddress() +#endif // !WIN32 + ProtoNet::Monitor::Monitor() { // Enable input notification by default @@ -77,6 +187,8 @@ ProtoNet::Monitor::~Monitor() ProtoNet::Monitor::Event::Event() : event_type(UNKNOWN_EVENT), iface_index(0) { + strcpy(iface_name, "???"); + iface_name[IFNAME_MAX] = '\0'; } diff --git a/src/common/protoPipe.cpp b/src/common/protoPipe.cpp index 1316804..57e5cca 100644 --- a/src/common/protoPipe.cpp +++ b/src/common/protoPipe.cpp @@ -1,7 +1,9 @@ /** * @file protoPipe.cpp * -* @brief Provides a cross-platform interprocess communication mechanism for Protolib using Unix-domain sockets (UNIX) or named pipe/mailslot mechanisms (WIN32). This class extends the "ProtoSocket" class to support "LOCAL" domain interprocess communications +* @brief Provides a cross-platform interprocess communication mechanism for +* Protolib using Unix-domain sockets (UNIX) or similar locally bound sockets mechanisms (WIN32). +* This class extends the "ProtoSocket" class to support "LOCAL" domain interprocess communications */ #include "protoPipe.h" #include "protoDebug.h" @@ -23,15 +25,10 @@ ProtoPipe::ProtoPipe(Type theType) : ProtoSocket((MESSAGE == theType) ? UDP : TCP), #ifdef WIN32 -#ifndef _WIN32_WCE - pipe_handle(INVALID_HANDLE_VALUE), is_mailslot(false), - read_count(0), read_index(0) -#else named_event_handle(INVALID_HANDLE_VALUE) -#endif // if/else !WIN32_WCE -#else +#else // UNIX unlink_tried(false) -#endif // WIN32 +#endif // if/else WIN32/UNIX { domain = LOCAL; path[0] = '\0'; @@ -46,57 +43,40 @@ ProtoPipe::~ProtoPipe() void ProtoPipe::Close() { - ProtoSocket::Close(); -#ifndef _WIN32_WCE - if (INVALID_HANDLE_VALUE != pipe_handle) - { - CloseHandle(pipe_handle); - pipe_handle = INVALID_HANDLE_VALUE; - } - if (INVALID_HANDLE_VALUE != input_event_handle) - { - CloseHandle(input_event_handle); - input_event_handle = INVALID_HANDLE_VALUE; - } - if (INVALID_HANDLE_VALUE != output_event_handle) - { - CloseHandle(output_event_handle); - output_event_handle = INVALID_HANDLE_VALUE; - } - output_ready = input_ready = false; - is_mailslot = false; -#else // (_WIN32_WCE defined) - // Remove registry entry if we're a listener - if ('\0' != path[0]) + if (IsOpen()) { + ProtoSocket::Close(); + // Remove registry entry if we're a listener + if ('\0' != path[0]) + { #ifdef _UNICODE - wchar_t wideBuffer[PATH_MAX]; - mbstowcs(wideBuffer, path, strlen(path)+1); - LPCTSTR namePtr = wideBuffer; + wchar_t wideBuffer[PATH_MAX]; + mbstowcs(wideBuffer, path, strlen(path)+1); + LPCTSTR namePtr = wideBuffer; #else - LPCSTR namePtr = path; + LPCSTR namePtr = path; #endif // if/else _UNICODE - HKEY hKey; - if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, - _T("Software\\Protokit"), - 0, KEY_SET_VALUE, &hKey)) - { - if (ERROR_SUCCESS != RegDeleteValue(hKey, namePtr)) - PLOG(PL_ERROR, "ProtoPipe::Close() RegDeleteValue() error: %s\n", ::GetErrorString()); - RegCloseKey(hKey); + HKEY hKey; + if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, + _T("Software\\Protokit"), + 0, KEY_SET_VALUE, &hKey)) + { + if (ERROR_SUCCESS != RegDeleteValue(hKey, namePtr)) + PLOG(PL_ERROR, "ProtoPipe::Close() RegDeleteValue() error: %s\n", ::GetErrorString()); + RegCloseKey(hKey); + } + else + { + PLOG(PL_ERROR, "ProtoPipe::Close() RegOpenKeyEx() error: %s\n", ::GetErrorString()); + } } - else + if (INVALID_HANDLE_VALUE != named_event_handle) { - PLOG(PL_ERROR, "ProtoPipe::Close() RegOpenKeyEx() error: %s\n", ::GetErrorString()); - } - } - if (INVALID_HANDLE_VALUE != named_event_handle) - { - CloseHandle(named_event_handle); - named_event_handle = INVALID_HANDLE_VALUE; - } -#endif // if/else !_WIN32_WCE -} // end ProtoPipe::Close(); + CloseHandle(named_event_handle); + named_event_handle = INVALID_HANDLE_VALUE; + } + } // end if IsOpen() +} // end ProtoPipe::Close() /** * Setup event handles and overlapped structures. Set up pipes. @@ -105,153 +85,11 @@ void ProtoPipe::Close() bool ProtoPipe::Listen(const char* theName) { -#ifndef _WIN32_WCE - if (IsOpen()) Close(); - - // Setup event handles and overlapped structs - if (NULL == (input_event_handle = CreateEvent(NULL, TRUE, FALSE, NULL))) - { - input_event_handle = INVALID_HANDLE_VALUE; - PLOG(PL_ERROR, "ProtoPipe::Listen() CreateEvent(input_event_handle) error: %s\n", ::GetErrorString()); - Close(); - return false; - } - if (NULL == (output_event_handle = CreateEvent(NULL, TRUE, FALSE, NULL))) - { - output_event_handle = INVALID_HANDLE_VALUE; - PLOG(PL_ERROR, "ProtoPipe::Listen() CreateEvent(output_event_handle) error: %s\n", ::GetErrorString()); - Close(); - return false; - } - memset(&read_overlapped, 0, sizeof(read_overlapped)); - read_overlapped.hEvent = input_event_handle; - memset(&write_overlapped, 0, sizeof(write_overlapped)); - write_overlapped.hEvent = output_event_handle; - - if (TCP == protocol) - { - // Create a named pipe for "STREAM" type ProtoPipe -#ifdef _UNICODE - const wchar_t pipePrefix[] = L"\\\\.\\pipe\\"; - wchar_t convertedName[PATH_MAX]; - wchar_t pipeName[PATH_MAX]; - - size_t ppSize = wcslen(pipePrefix); // prefix char count - // convert char name to wide char - size_t theNameLen = mbstowcs(convertedName, theName, PATH_MAX); - - wcscpy(pipeName, pipePrefix); // assemble the pipe name - wcsncat(pipeName, convertedName, PATH_MAX - ppSize - theNameLen); -#else - char pipeName[PATH_MAX]; - strcpy(pipeName, "\\\\.\\pipe\\"); - strncat(pipeName, theName, PATH_MAX-strlen(pipeName)); -#endif - pipe_handle = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, - 0, 0, 0, 0); - if (INVALID_HANDLE_VALUE == pipe_handle) - { - PLOG(PL_ERROR, "ProtoPipe::Listen() CreateNamedPipe() error: %s\n", ::GetErrorString()); - return false; - } - if (NULL != notifier) - { - if (0 == ConnectNamedPipe(pipe_handle, &read_overlapped)) - { - switch (GetLastError()) - { - case ERROR_IO_PENDING: - case ERROR_PIPE_LISTENING: - break; - default: - PLOG(PL_ERROR, "ProtoPipe::Listen() ConnectNamedPipe() error: %s\n", ::GetErrorString()); - Close(); - return false; - } - } - else - { - PLOG(PL_ERROR, "ProtoPipe::Listen() ConnectNamedPipe() connected?: %s\n", ::GetErrorString()); - Close(); - return false; - } - } - state = LISTENING; - } - else // (UDP == protocol) - { - // Create a mail slot for "MESSAGE" type ProtoPipe -#ifdef _UNICODE - const wchar_t pipePrefix[] = L"\\\\.\\mailslot\\"; - wchar_t convertedName[PATH_MAX]; - wchar_t pipeName[PATH_MAX]; - - size_t ppSize = wcslen(pipePrefix); // prefix char count - // convert char name to wide char - size_t theNameLen = mbstowcs(convertedName, theName, PATH_MAX); - - wcscpy(pipeName, pipePrefix); // assemble the pipe name - wcsncat(pipeName, convertedName, PATH_MAX - ppSize - theNameLen); -#else - char pipeName[PATH_MAX]; - strcpy(pipeName, "\\\\.\\mailslot\\"); - strncat(pipeName, theName, PATH_MAX-strlen(pipeName)); -#endif - pipe_handle = CreateMailslot(pipeName, - 8192, // 8192-byte maximum message size to emulate UDP - MAILSLOT_WAIT_FOREVER, // no time-out for operations - NULL); // no security attributes - if (INVALID_HANDLE_VALUE == pipe_handle) - { - PLOG(PL_ERROR, "ProtoPipe::Listen() CreateMailSlot() error: %s\n", ::GetErrorString()); - return false; - } - is_mailslot = true; - // Start overlapped notifications - if (NULL != notifier) - { - if (notify_output) output_ready = true; - // Initiate overlapped I/O ... - DWORD bytesRead; - if (0 != ReadFile(pipe_handle, read_buffer, BUFFER_MAX, &bytesRead, &read_overlapped)) - { - input_ready = true; - read_count = bytesRead; - read_index = 0; - } - else - { - switch(GetLastError()) - { - case ERROR_IO_PENDING: - read_count = read_index = 0; - input_ready = false; - break; - case ERROR_BROKEN_PIPE: - PLOG(PL_ERROR, "ProtoPipe::Listen() ReadFile() error: %s\n", ::GetErrorString()); - Close(); - return false; - } - } - } - state = IDLE; - } - - port = 0; // to fake binding - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoSocket::Listen() error updating notification\n"); - Close(); - return false; - } -#else - // This "pipe" implementation uses the registry to find "local" sockets + // This "pipe" implementation uses the WIN32 registry to find "local" sockets // which are used to provide interprocess "pipe" connectivity if (NULL != named_event_handle) CloseHandle(named_event_handle); - // 1) Try to open name event of using given name + // 1) Try to open named event for "theName" char pipeName[MAX_PATH]; strcpy(pipeName, "Global\\protoPipe-"); strncat(pipeName, theName, MAX_PATH - strlen(pipeName)); @@ -262,8 +100,7 @@ bool ProtoPipe::Listen(const char* theName) #else LPCTSTR namePtr = pipeName; #endif // if/else _UNICODE - - named_event_handle = CreateEvent(NULL, TRUE, TRUE, namePtr); + named_event_handle = CreateEvent(NULL, TRUE, TRUE, namePtr); if (NULL == named_event_handle) { named_event_handle = INVALID_HANDLE_VALUE; @@ -283,6 +120,8 @@ bool ProtoPipe::Listen(const char* theName) // Make a registry entry so "connecting" pipes can find the port number HKEY hKey; DWORD dwAction; + long theError = 0; + if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, _T("Software\\Protokit"), 0L, @@ -311,8 +150,8 @@ bool ProtoPipe::Listen(const char* theName) RegCloseKey(hKey); } else - { - PLOG(PL_ERROR, "ProtoPipe::Listen() RegCreateKeyEx() error: %s\n", ::GetErrorString()); + { + PLOG(PL_ERROR, "ProtoPipe::Listen() RegCreateKeyEx() error: %s (Must be run as administrator)\n", ::GetErrorString()); Close(); return false; } @@ -323,236 +162,19 @@ bool ProtoPipe::Listen(const char* theName) Close(); return false; } -#endif // if/else !_WIN32_WCE + // Save our named event path name strncpy(path, theName, PATH_MAX); return true; } // ProtoPipe::Listen() bool ProtoPipe::Accept(ProtoPipe* thePipe) { -#ifndef _WIN32_WCE - if (UDP == protocol) - { - PLOG(PL_ERROR, "ProtoPipe::Accept() invald operation on MESSAGE pipe\n"); - return false; - } - if (NULL != notifier) - { - DWORD numBytes; - if (FALSE == GetOverlappedResult(pipe_handle, &read_overlapped, &numBytes, FALSE)) - { - PLOG(PL_ERROR, "ProtoPipe::Accept() GetOverlappedResult() error (%d): %s\n", GetLastError(), ::GetErrorString()); - // (TBD) close/reopen named pipe ??? - return false; - } - if (notify_output) output_ready = true; - // Initiate overlapped I/O ... - DWORD bytesRead; - if (0 != ReadFile(pipe_handle, read_buffer, BUFFER_MAX, &bytesRead, &read_overlapped)) - { - input_ready = true; - read_count = bytesRead; - read_index = 0; - } - else - { - switch(GetLastError()) - { - case ERROR_IO_PENDING: - read_count = read_index = 0; - input_ready = false; - break; - case ERROR_BROKEN_PIPE: - PLOG(PL_ERROR, "ProtoPipe::Accept() ReadFile() error: %s\n", ::GetErrorString()); - return false; - } - } - } - else - { - if (0 == ConnectNamedPipe(pipe_handle, NULL)) - { - PLOG(PL_ERROR, "ProtoPipe::Accept() ConnectNamedPipe() error: %s\n", ::GetErrorString()); - return false; - } - } - if (thePipe && (thePipe != this)) - { - // 1) Clear current notification - state = CLOSED; - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoPipe::Accept() error updating notification\n"); - Close(); - return false; - } - - // 2) Copy "this" to "thePipe"; - *thePipe = *this; - // 3) Manually reset "this" to fully closed state and re-open - pipe_handle = NULL; - input_ready = false; - output_ready = false; - char pipeName[PATH_MAX]; - strncpy(pipeName, path, PATH_MAX); - if (!Listen(pipeName)) - { - PLOG(PL_ERROR, "ProtoPipe::Accept() error re-opening server pipe\n"); - thePipe->Close(); - return false; - } - // 4) Set up notification for thePipe - thePipe->state = CONNECTED; - if (!thePipe->UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoPipe::Accept() error setting up new pipe notification\n"); - thePipe->Close(); - return false; - } - } - else - { - state = CONNECTED; - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoSocket::Accept() error updating notification\n"); - Close(); - return false; - } - } - return true; -#else return ProtoSocket::Accept(static_cast(thePipe)); -#endif // if/else !_WIN32_WCE } // end ProtoPipe::Accept() -#ifndef _WIN32_WCE -bool ProtoPipe::SetBlocking(bool blocking) -{ - if (TCP != protocol && is_mailslot) - { - DWORD timeout = MAILSLOT_WAIT_FOREVER; //blocking ? MAILSLOT_WAIT_FOREVER : 0; - if (0 == SetMailslotInfo(pipe_handle, timeout)) - { - PLOG(PL_ERROR, "ProtoPipe::SetBlocking() SetMailslotInfo() error: %s\n", ::GetErrorString()); - return false; - } - } - return true; -} // end ProtoPipe::SetBlocking() -#endif // !_WIN32_WCE bool ProtoPipe::Connect(const char* theName) { -#ifndef _WIN32_WCE - if (TCP == protocol) - { - // Connect to named pipe for STREAM type ProtoPipes -#ifdef _UNICODE - const wchar_t pipePrefix[] = L"\\\\.\\pipe\\"; - wchar_t convertedName[PATH_MAX]; - wchar_t pipeName[PATH_MAX]; - - size_t ppSize = wcslen(pipePrefix); // prefix char count - // convert char name to wide char - size_t theNameLen = mbstowcs(convertedName, theName, PATH_MAX); - - wcscpy(pipeName, pipePrefix); // assemble the pipe name - wcsncat(pipeName, convertedName, PATH_MAX - ppSize - theNameLen); -#else - char pipeName[PATH_MAX]; - strcpy(pipeName, "\\\\.\\pipe\\"); - strncat(pipeName, theName, PATH_MAX - strlen(pipeName)); -#endif - if (INVALID_HANDLE_VALUE == (pipe_handle = CreateFile(pipeName, GENERIC_WRITE | GENERIC_READ, 0, - NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, - NULL))) - { - PLOG(PL_DEBUG, "ProtoPipe::Connect() CreateFile() error: %s\n", ::GetErrorString()); - return false; - } - state = CONNECTED; - } - else - { - // Write to mailslot for MESSAGE type ProtoPipes -#ifdef _UNICODE - const wchar_t pipePrefix[] = L"\\\\.\\mailslot\\"; - wchar_t convertedName[PATH_MAX]; - wchar_t pipeName[PATH_MAX]; - - size_t ppSize = wcslen(pipePrefix); // prefix char count - // convert char name to wide char - size_t theNameLen = mbstowcs(convertedName, theName, PATH_MAX); - - wcscpy(pipeName, pipePrefix); // assemble the pipe name - wcsncat(pipeName, convertedName, PATH_MAX - ppSize - theNameLen); -#else - char pipeName[PATH_MAX]; - strcpy(pipeName, "\\\\.\\mailslot\\"); - strncat(pipeName, theName, PATH_MAX - strlen(pipeName)); -#endif - if (INVALID_HANDLE_VALUE == (pipe_handle = CreateFile(pipeName, GENERIC_WRITE | GENERIC_READ, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, NULL))) - { - PLOG(PL_DEBUG, "ProtoPipe::Connect() CreateFile() error: %s\n", ::GetErrorString()); - return false; - } - state = IDLE; - } - port = 0; - if (NULL == (input_event_handle = CreateEvent(NULL, TRUE, FALSE, NULL))) - { - input_event_handle = INVALID_HANDLE_VALUE; - PLOG(PL_ERROR, "ProtoPipe::Connect() CreateEvent(input_event_handle) error: %s\n", ::GetErrorString()); - Close(); - return false; - } - if (NULL == (output_event_handle = CreateEvent(NULL, TRUE, FALSE, NULL))) - { - output_event_handle = INVALID_HANDLE_VALUE; - PLOG(PL_ERROR, "ProtoPipe::Connect() CreateEvent(output_event_handle) error: %s\n", ::GetErrorString()); - Close(); - return false; - } - memset(&read_overlapped, 0, sizeof(read_overlapped)); - read_overlapped.hEvent = input_event_handle; - memset(&write_overlapped, 0, sizeof(write_overlapped)); - write_overlapped.hEvent = output_event_handle; - if (NULL != notifier) - { - if (notify_output) output_ready = true; - // Initiate overlapped I/O ... - DWORD bytesRead; - if (0 != ReadFile(pipe_handle, read_buffer, BUFFER_MAX, &bytesRead, &read_overlapped)) - { - input_ready = true; - read_count = bytesRead; - read_index = 0; - } - else - { - switch(GetLastError()) - { - case ERROR_IO_PENDING: - read_count = read_index = 0; - input_ready = false; - break; - case ERROR_BROKEN_PIPE: - PLOG(PL_ERROR, "ProtoPipe::Accept() ReadFile() error: %s\n", ::GetErrorString()); - Close(); - return false; - } - } - } - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoSocket::Connect() error updating notification\n"); - Close(); - return false; - } -#else // 1) Open the named event given the pipe name // This "pipe" implementation uses the registry to find "local" sockets // which are used to provide interprocess "pipe" connectivity @@ -641,150 +263,10 @@ bool ProtoPipe::Connect(const char* theName) PLOG(PL_ERROR, "ProtoPipe::Connect() error (pipe not found): %s\n", ::GetErrorString()); return false; } -#endif // if/else !_WIN32_WCE return true; } // end ProtoPipe::Connect() -#ifndef _WIN32_WCE -bool ProtoPipe::Send(const char* buffer, unsigned int& numBytes) -{ - DWORD bytesWritten; - LPOVERLAPPED overlappedPtr = NULL; - const char* bufPtr = buffer; - if (NULL != notifier) - { - if (notify_output && !output_ready) - { - if (FALSE != GetOverlappedResult(pipe_handle, &write_overlapped, &bytesWritten, FALSE)) - { - output_ready = true; - overlappedPtr = &write_overlapped; - bufPtr = write_buffer; - if (numBytes > BUFFER_MAX) numBytes = BUFFER_MAX; - memcpy(write_buffer, buffer, numBytes); - } - else - { - PLOG(PL_ERROR, "ProtoPipe::Send() GetOverlappedResult() error: %d\n", ::GetErrorString()); - return false; - } - - } - } - if (0 != WriteFile(pipe_handle, bufPtr, numBytes, &bytesWritten, overlappedPtr)) - { - numBytes = bytesWritten; - if (0 == numBytes) output_ready = false; - return true; - } - else - { - numBytes = 0; - switch (GetLastError()) - { - case ERROR_IO_PENDING: - return true; - case ERROR_BROKEN_PIPE: - OnNotify(NOTIFY_NONE); - break; - default: - PLOG(PL_ERROR, "ProtoPipe::Send() WriteFile() error(%d): %s\n", GetLastError(), ::GetErrorString()); - break; - } - return false; - } -} // end ProtoPipe::Send() - -bool ProtoPipe::Recv(char* buffer, unsigned int& numBytes) -{ - DWORD want = numBytes; - unsigned int got = 0; - LPOVERLAPPED overlapPtr = NULL; - if (NULL != notifier) - { - if (!input_ready) - { - // We had a pending overlapped read operation - DWORD bytesRead; - if (FALSE != GetOverlappedResult(pipe_handle, &read_overlapped, &bytesRead, FALSE)) - { - - unsigned int len = (bytesRead < want) ? bytesRead : want; - memcpy(buffer, read_buffer, len); - got += len; - read_index = (len < bytesRead) ? len : 0; - input_ready = true; - } - else - { - numBytes = 0; - switch (GetLastError()) - { - case ERROR_BROKEN_PIPE: - OnNotify(NOTIFY_NONE); - return false; - default: - PLOG(PL_ERROR, "ProtoPipe::Recv() GetOverlappedResult() error(%d): %s\n", GetLastError(), ::GetErrorString()); - return false; - } - } - } - overlapPtr = &read_overlapped; - } - - // Do we have any data remaining in our "read_buffer"? - if ((read_count > 0) && (got < want)) - { - unsigned int len = want - got; - if (len > read_count) len = read_count; - memcpy(buffer+got, read_buffer+read_index, len); - read_count -= len; - read_index += len; - got += len; - } - - // Read more as needed, triggering overlapped I/O - if (got < want) - { - DWORD bytesRead; - unsigned int len = want - got; - if (len > BUFFER_MAX) len = BUFFER_MAX; - if (0 != ReadFile(pipe_handle, read_buffer, len, &bytesRead, overlapPtr)) - { - memcpy(buffer+got, read_buffer, bytesRead); - got += bytesRead; - } - else - { - switch(GetLastError()) - { - case ERROR_IO_PENDING: - read_count = read_index = 0; - input_ready = false; - break; - case ERROR_BROKEN_PIPE: - if (0 == got) - { - OnNotify(NOTIFY_NONE); - return false; - } - break; - default: - PLOG(PL_ERROR, "ProtoPipe::Recv() ReadFile(%d) error: %s\n", GetLastError(), ::GetErrorString()); - if (0 == got) return false; - break; - - } - } - } - numBytes = got; - return true; -} // end ProtoPipe::Recv() - -#endif // !_WIN32_WCE - -#else - +#else // UNIX /** * This method opens a Unix-domain socket to serve as the ProtoPipe */ @@ -797,7 +279,11 @@ bool ProtoPipe::Open(const char* theName) char pipeName[PATH_MAX]; if(*theName!='/') { +#ifdef __ANDROID__ + strcpy(pipeName, "/data/local/tmp/"); +#else strcpy(pipeName, "/tmp/"); +#endif // if/else __ANDROID__ } strncat(pipeName, theName, PATH_MAX-strlen(pipeName)); struct sockaddr_un sockAddr; @@ -851,7 +337,11 @@ void ProtoPipe::Unlink(const char* theName) char pipeName[PATH_MAX]; if(*theName!='/') { +#ifdef __ANDROID__ + strcpy(pipeName, "/data/local/tmp/"); +#else strcpy(pipeName, "/tmp/"); +#endif // if/else __ANDROID__ } strncat(pipeName, theName, PATH_MAX - strlen(pipeName)); unlink(pipeName); @@ -925,7 +415,11 @@ bool ProtoPipe::Connect(const char* theName) if (!IsOpen()) { char pipeName[PATH_MAX]; +#ifdef __ANDROID__ + strcpy(pipeName, "/data/local/tmp/protoSocketXXXXXX"); +#else strcpy(pipeName, "/tmp/protoSocketXXXXXX"); +#endif // if/else __ANDROID__ int fd = mkstemp(pipeName); if (fd < 0) { @@ -967,7 +461,11 @@ bool ProtoPipe::Connect(const char* theName) serverAddr.sun_family = AF_UNIX; if(*theName!='/') { +#ifdef __ANDROID__ + strcpy(serverAddr.sun_path, "/data/local/tmp/"); +#else strcpy(serverAddr.sun_path, "/tmp/"); +#endif // if/else __ANDROID__ } strncat(serverAddr.sun_path, theName, PATH_MAX - strlen(serverAddr.sun_path)); #ifdef SCM_RIGHTS // 4.3BSD Reno and later diff --git a/src/common/protoPktARP.cpp b/src/common/protoPktARP.cpp index ec2136b..10d4832 100644 --- a/src/common/protoPktARP.cpp +++ b/src/common/protoPktARP.cpp @@ -10,16 +10,21 @@ ProtoPktARP::ProtoPktARP(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) { + if (initFromBuffer) + InitFromBuffer(); + else + InitIntoBuffer(); } ProtoPktARP::~ProtoPktARP() { } -bool ProtoPktARP::InitFromBuffer(UINT32* bufferPtr, - unsigned int numBytes, - bool freeOnDestruct) +bool ProtoPktARP::InitFromBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) { unsigned int minLength = OFFSET_SNDR_HRD_ADDR; if (NULL != bufferPtr) @@ -128,6 +133,26 @@ bool ProtoPktARP::GetTargetProtocolAddress(ProtoAddress& addr) const return true; } // end ProtoPktARP::GetTargetProtocolAddress() +bool ProtoPktARP::InitIntoBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + unsigned int minLength = OFFSET_SNDR_HRD_ADDR; + if (NULL != bufferPtr) + { + if (numBytes < minLength) + return false; + else + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + } + else if (GetBufferLength() < minLength) + { + return false; + } + SetLength(minLength); + return true; +} // end ProtoPktARP::InitIntoBuffer() + bool ProtoPktARP::SetSenderHardwareAddress(const ProtoAddress& addr) { // TBD - support more types and verify that target/sender hw addr type are equal! @@ -139,6 +164,7 @@ bool ProtoPktARP::SetSenderHardwareAddress(const ProtoAddress& addr) SetHardwareType(ETHERNET); SetHardwareAddrLen(addr.GetLength()); memcpy(((char*)buffer_ptr) + OffsetSenderHardwareAddr(), addr.GetRawHostAddress(), addr.GetLength()); + SetLength(GetLength() + addr.GetLength()); return true; } // end ProtoPktARP::SetSenderHardwareAddress() @@ -160,6 +186,7 @@ bool ProtoPktARP::SetSenderProtocolAddress(const ProtoAddress& addr) SetEtherType(etherType); SetProtocolAddrLen(addr.GetLength()); memcpy(((char*)buffer_ptr) + OffsetSenderProtocolAddr(), addr.GetRawHostAddress(), addr.GetLength()); + SetLength(GetLength() + addr.GetLength()); return true; } // end ProtoPktARP::SetSenderProtocolAddress() @@ -174,6 +201,7 @@ bool ProtoPktARP::SetTargetHardwareAddress(const ProtoAddress& addr) SetHardwareType(ETHERNET); SetHardwareAddrLen(addr.GetLength()); memcpy(((char*)buffer_ptr) + OffsetTargetHardwareAddr(), addr.GetRawHostAddress(), addr.GetLength()); + SetLength(GetLength() + addr.GetLength()); return true; } // end ProtoPktARP::SetTargetHardwareAddress() @@ -195,5 +223,177 @@ bool ProtoPktARP::SetTargetProtocolAddress(const ProtoAddress& addr) SetEtherType(etherType); SetProtocolAddrLen(addr.GetLength()); memcpy(((char*)buffer_ptr) + OffsetTargetProtocolAddr(), addr.GetRawHostAddress(), addr.GetLength()); + SetLength(GetLength() + addr.GetLength()); return true; } // end ProtoPktARP::SetTargetProtocolAddress() + + +///////////////////////////////////////////////// +// ProtoArpTable implementation + +ProtoArpTable::MacItem::MacItem(const ProtoAddress& macAddr) + : mac_addr(macAddr) +{ +} + +ProtoArpTable::MacItem::~MacItem() +{ + ip_addr_list.Destroy(); +} + + +ProtoArpTable::ProtoArpTable() +{ +} + +ProtoArpTable::~ProtoArpTable() +{ + mac_list.Destroy(); + ip_list.Destroy(); +} + +bool ProtoArpTable::AddEntry(const ProtoAddress& ipAddr, const ProtoAddress& macAddr) +{ + // One MAC addr entry per IP address (do we already know this IP address) + IPItem* ipItem = ip_list.FindItem(ipAddr); + if (NULL == ipItem) + { + // It's a new IP address, is it for an existing MAC addr? + MacItem* macItem = mac_list.FindItem(macAddr); + if (NULL == macItem) + { + if (NULL == (macItem = new MacItem(macAddr))) + { + PLOG(PL_ERROR, "ProtoArpTable::AddEntry() new MacItem error: %s\n", GetErrorString()); + return false; + } + if (!mac_list.Insert(*macItem)) + { + PLOG(PL_ERROR, "ProtoArpTable::AddEntry() error: unable to insert MacItem\n"); + delete macItem; + return false; + } + } + // Create new IPItem + if (NULL == (ipItem = new IPItem(ipAddr, macItem))) + { + PLOG(PL_ERROR, "ProtoArpTable::AddEntry() new IPItem error: %s\n", GetErrorString()); + } + else if (!ip_list.Insert(*ipItem)) // attempt insertion + { + PLOG(PL_ERROR, "ProtoArpTable::AddEntry() unab IPItem error: %s\n", GetErrorString()); + delete ipItem; + ipItem = NULL; + } + else if (!macItem->AddAddress(ipAddr)) // add IP address to macItem + { + PLOG(PL_ERROR, "ProtoArpTable::AddEntry() error: unable to add IP address\n"); + ip_list.Remove(*ipItem); + delete ipItem; + ipItem = NULL; + } + if (NULL == ipItem) + { + // something went wrong + macItem->RemoveAddress(ipAddr); + if (macItem->AccessAddressList().IsEmpty()) + { + mac_list.Remove(*macItem); + delete macItem; + } + return false; + } + } + else + { + // It's an existing IPItem, update stuff if different MAC addr + if (!macAddr.HostIsEqual(ipItem->GetMacAddr())) + { + // Remove the record for this IP address and re-add with new MAC + DeleteIPItem(ipItem); + return AddEntry(ipAddr, macAddr); + } + } + return true; +} // end ProtoArpTable::AddEntry() + +void ProtoArpTable::DeleteIPItem(IPItem* ipItem) +{ + if (NULL != ipItem) + { + ip_list.Remove(*ipItem); + MacItem* macItem = ipItem->GetMacItem(); + macItem->RemoveAddress(ipItem->GetAddress()); + if (macItem->AccessAddressList().IsEmpty()) + delete macItem; + delete ipItem; + } +} // end ProtoArpTable:RemoveIPItem() + +void ProtoArpTable::DeleteMacItem(MacItem* macItem) +{ + if (NULL != macItem) + { + ProtoAddressList::Iterator iterator(macItem->AccessAddressList()); + ProtoAddress ipAddr; + while (iterator.GetNextAddress(ipAddr)) + { + IPItem* ipItem = ip_list.FindItem(ipAddr); + ASSERT(NULL != ipItem); + ip_list.Remove(*ipItem); + delete ipItem; + } + delete macItem; + } +} // end ProtoArpTable::DeleteMacItem() + +void ProtoArpTable::RemoveEntryByIP(const ProtoAddress& ipAddr) +{ + IPItem* ipItem = ip_list.FindItem(ipAddr); + if (NULL != ipItem) DeleteIPItem(ipItem); +} // end ProtoArpTable::RemoveEntryByIP() + + +void ProtoArpTable::RemoveEntryByMAC(const ProtoAddress& macAddr) +{ + MacItem* macItem = mac_list.FindItem(macAddr); + if (NULL != macItem) DeleteMacItem(macItem); +} // end ProtoArpTable::RemoveEntryByMAC() + +bool ProtoArpTable::GetMacAddress(const ProtoAddress& ipAddr, ProtoAddress& macAddr) +{ + IPItem* ipItem = ip_list.FindItem(ipAddr); + if (NULL == ipItem) + { + macAddr.Invalidate(); + return false; + } + else + { + macAddr = ipItem->GetMacAddr(); + return true; + } +} // end ProtoArpTable::GetMacAddress() + +bool ProtoArpTable::GetAddressList(const ProtoAddress& macAddr, ProtoAddressList addrList) +{ + MacItem* macItem = mac_list.FindItem(macAddr); + if (NULL == macItem) + { + PLOG(PL_WARN, "ProtoArpTable::GetAddressList() warning: unknown MAC address\n"); + return false; + } + ProtoAddressList::Iterator iterator(macItem->AccessAddressList()); + ProtoAddress ipAddr; + while (iterator.GetNextAddress(ipAddr)) + { + if (!addrList.Insert(ipAddr)) + { + PLOG(PL_ERROR, "ProtoArpTable::GetAddressList() error: %s\n", GetErrorString()); + return false; + } + } + return true; +} // end ProtoArpTable::GetIPAddresses() + + diff --git a/src/common/protoPktETH.cpp b/src/common/protoPktETH.cpp index 92b84f1..4301af8 100644 --- a/src/common/protoPktETH.cpp +++ b/src/common/protoPktETH.cpp @@ -5,7 +5,9 @@ */ #include "protoPktETH.h" -ProtoPktETH::ProtoPktETH(UINT32* bufferPtr, unsigned int numBytes, bool freeOnDestruct) +ProtoPktETH::ProtoPktETH(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) { } @@ -13,3 +15,23 @@ ProtoPktETH::ProtoPktETH(UINT32* bufferPtr, unsigned int numBytes, bool freeOnDe ProtoPktETH::~ProtoPktETH() { } + +bool ProtoPktETH::InitIntoBuffer(UINT32* bufferPtr, + unsigned int bufferBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (bufferBytes < 14) + return false; + else + AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct); + } + else if (GetBufferLength() < 14) + { + return false; + } + memset(buffer_ptr, 0, 14); + SetLength(14); + return true; +} // end ProtoPktETH::InitIntoBuffer() diff --git a/src/common/protoPktIGMP.cpp b/src/common/protoPktIGMP.cpp new file mode 100644 index 0000000..19ceb13 --- /dev/null +++ b/src/common/protoPktIGMP.cpp @@ -0,0 +1,514 @@ +#include "protoPktIGMP.h" + +const UINT8 ProtoPktIGMP::DEFAULT_QRV = 2; // default query robustness value +const double ProtoPktIGMP::DEFAULT_QQIC = 125.0; // default query interval (seconds) +const double ProtoPktIGMP::DEFAULT_MAX_RESP = 10.0; // default query response time (seconds) + +ProtoPktIGMP::ProtoPktIGMP(UINT32* bufferPtr, + unsigned int numBytes, + bool initFromBuffer, + bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +ProtoPktIGMP::~ProtoPktIGMP() +{ +} + +bool ProtoPktIGMP::InitFromBuffer(UINT16 pktLength, + UINT32* bufferPtr, + unsigned int bufferBytes, + bool freeOnDestruct) +{ + unsigned int minLength = OFFSET_RESERVED; + if (NULL != bufferPtr) + { + if (bufferBytes < minLength) // IGMPv2 msg size + return false; + else + AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct); + } + if (GetBufferLength() < pktLength) + { + PLOG(PL_ERROR, "ProtoPktIGMP::InitFromBuffer() error: insufficient buffer size\n"); + return false; + } + SetLength(pktLength); + if (GetVersion() > 2) + { + minLength = OFFSET_SRC_LIST; + if (pktLength < minLength) + { + PLOG(PL_ERROR, "ProtoPktIGMP::InitFromBuffer() error: invalid IGMPv3 packet\n"); + return false; + } + minLength += 4*GetNumSources(); + if (pktLength < minLength) + { + PLOG(PL_ERROR, "ProtoPktIGMP::InitFromBuffer() error: truncated IGMPv3 packet\n"); + return false; + } + } + return true; +} // end ProtoPktIGMP::InitFromBuffer() + +UINT8 ProtoPktIGMP::GetVersion() const +{ + switch (GetType()) + { + case REPORT_V1: + return 1; + case REPORT_V2: + return 2; + case REPORT_V3: + return 3; + case QUERY: + case LEAVE: + if (GetLength() > OFFSET_RESERVED) + return 3; + else + return 2; + default: + return 0; + } +} // end ProtoPktIGMP::GetVersion() + +double ProtoPktIGMP::GetMaxResponseTime() const +{ + UINT8 value = GetUINT8(OFFSET_MAX_RESP); + if ((value < 128) || (GetVersion() < 3)) + { + // value is in units of 0.1 seconds + return (0.1*(double)value); + } + else + { + unsigned int exp = (value & 0x70) >> 4; + unsigned int mant = value & 0x0f; + unsigned int maxRespTime = (mant | 0x10) << (exp + 3); + return (0.1*(double)maxRespTime); + } +} // end ProtoPktIGMP::GetMaxResponseTime() + +double ProtoPktIGMP::GetQQIC() const +{ + UINT8 qqic = GetUINT8(OFFSET_QQIC); + if (qqic < 128) + { + return (double)qqic; + } + else + { + unsigned int exp = (qqic & 0x70) >> 4; + unsigned int mant = qqic & 0x0f; + return (double)((mant | 0x10) << (exp + 3)); + } +} // end ProtoPktIGMP::GetQQIC() + +bool ProtoPktIGMP::GetSourceAddress(UINT16 index, ProtoAddress& srcAddr) const +{ + if (index < GetNumSources()) + { + const char* ptr = (const char*)buffer_ptr + OFFSET_SRC_LIST + index*4; + srcAddr.SetRawHostAddress(ProtoAddress::IPv4, ptr, 4); + return true; + } + else + { + srcAddr.Invalidate(); + return false; + } +} // end ProtoPktIGMP::GetSourceAddress() + +bool ProtoPktIGMP::GetNextGroupRecord(ProtoPktIGMP::GroupRecord& groupRecord, bool first) const +{ + if (0 == GetNumRecords()) return false; + UINT32* recordPtr; + unsigned int bufferSpace; + if (first || (NULL == groupRecord.GetBuffer())) + { + // Point to first record + recordPtr = buffer_ptr + (OFFSET_REC_LIST >> 2); + bufferSpace = GetLength() - OFFSET_REC_LIST; + } + else + { + recordPtr = groupRecord.AccessBuffer() + (groupRecord.GetLength() >> 2); + // Make sure it's in scope of this IGMP message size. + size_t offset = 4*(recordPtr - buffer_ptr); + if (offset > GetLength()) + return false; // out of bounds + bufferSpace = GetLength() - offset; + } + if (0 == bufferSpace) + return false; + else + return groupRecord.InitFromBuffer(recordPtr, bufferSpace); +} // end ProtoPktIGMP::GetGroupRecord() + +bool ProtoPktIGMP::InitIntoBuffer(Type type, + unsigned int version, + UINT32* bufferPtr, + unsigned int bufferBytes, + bool freeOnDestruct) +{ + switch (type) + { + case REPORT_V1: + version = 1; + break; + case REPORT_V2: + case LEAVE: + version = 2; + break; + case REPORT_V3: + version = 3; + break; + default: + break; + } + UINT16 minLength = (version < 3) ? OFFSET_RESERVED : OFFSET_SRC_LIST; + if (NULL != bufferPtr) + { + if (bufferBytes < minLength) + return false; + else + AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct); + } + if (GetBufferLength() < minLength) return false; + memset(buffer_ptr, 0, minLength); + SetUINT8(OFFSET_TYPE, type); + SetLength(minLength); + if (QUERY == type) + { + SetMaxResponseTime(DEFAULT_MAX_RESP); + if (3 == version) + { + SetQRV(DEFAULT_QRV); + SetQQIC(DEFAULT_QQIC); + } + } + return true; + +} // end ProtoPktIGMP::InitIntoBuffer() + +void ProtoPktIGMP::SetMaxResponseTime(double seconds, bool updateChecksum) +{ + seconds *= 10.0; // convert to 1/10 sec units + UINT8 code; + if (seconds < 128) + { + code = (unsigned int)seconds; + } + else if (seconds > 31743.0) + { + code = 255; + } + else + { + unsigned int value = (unsigned int)seconds; + unsigned exp = 0; + value >>= 3; + while (value > 31) + { + exp++; + value >>= 1; + } + exp <<= 4; + code = (0x80 | exp | (value & 0x0f)); + + } + SetUINT8(OFFSET_MAX_RESP, code); + if (updateChecksum) ComputeChecksum(); +} // end ProtoPktIGMP::SetMaxResponseTime() + +void ProtoPktIGMP::SetGroupAddress(ProtoAddress* groupAddr, bool updateChecksum) +{ + char* ptr = ((char*)buffer_ptr) + OFFSET_GROUP; + if ((NULL == groupAddr) || (ProtoAddress::IPv4 != groupAddr->GetType()) || (!groupAddr->IsMulticast())) + memset(ptr, 0, 4); + else + memcpy(ptr, groupAddr->GetRawHostAddress(), 4); + if (updateChecksum) ComputeChecksum(); +} // end ProtoPktIGMP::SetGroupAddress() + + +bool ProtoPktIGMP::SetSuppress(bool state, bool updateChecksum) +{ + if (GetBufferLength() <= OFFSET_RESERVED) + { + PLOG(PL_ERROR, "ProtoPktIGMP::SetSuppress() error: insufficient buffer space\n"); + return false; + } + UINT8 code = GetUINT8(OFFSET_S); + if (state) + code |= 0x08; + else + code = (code & ~0x08) & 0x0f; + SetUINT8(OFFSET_S, code); + if (updateChecksum) ComputeChecksum(); + return true; +} // end ProtoPktIGMP::SetSuppress() + +bool ProtoPktIGMP::SetQRV(UINT8 qrv, bool updateChecksum) +{ + if (GetBufferLength() <= OFFSET_RESERVED) + { + PLOG(PL_ERROR, "ProtoPktIGMP::SetQRV() error: insufficient buffer space\n"); + return false; + } + UINT8 code = GetUINT8(OFFSET_QRV); + code &= 0x08; // clear old qrv + qrv &= 0x07; // mask new qrv + code |= qrv; // set new qrv + SetUINT8(OFFSET_QRV, code); + if (updateChecksum) ComputeChecksum(); + return true; +} // end ProtoPktIGMP::SetQRV() + +bool ProtoPktIGMP::SetQQIC(double seconds, bool updateChecksum) +{ + if (GetBufferLength() <= OFFSET_RESERVED) + { + PLOG(PL_ERROR, "ProtoPktIGMP::SetQQIC() error: insufficient buffer space\n"); + return false; + } + UINT8 code; + if (seconds < 128) + { + code = (unsigned int)seconds; + } + else if (seconds > 31743.0) + { + code = 255; + } + else + { + unsigned int value = (unsigned int)seconds; + unsigned exp = 0; + value >>= 3; + while (value > 31) + { + exp++; + value >>= 1; + } + exp <<= 4; + code = (0x80 | exp | (value & 0x0f)); + } + SetUINT8(OFFSET_QQIC, code); + if (updateChecksum) ComputeChecksum(); + return true; +} // end ProtoPktIGMP::SetQQIC() + +bool ProtoPktIGMP::AppendSourceAddress(ProtoAddress& srcAddr, bool updateChecksum) +{ + if ((ProtoAddress::IPv4 != srcAddr.GetType()) || !srcAddr.IsUnicast()) + { + PLOG(PL_ERROR, "ProtoPktIGMP::AppendSourceAddress() error: invalid source address\n"); + return false; + } + // Is there room for another source address? + UINT16 numSrc = GetNumSources(); + unsigned int offset = OFFSET_SRC_LIST + 4*numSrc; + ASSERT(offset == GetLength()); + unsigned int newLength = offset + 4; + if (newLength > GetBufferLength()) + { + PLOG(PL_ERROR, "ProtoPktIGMP::AppendSourceAddress() error: insufficient buffer space\n"); + return false; + } + memcpy(((char*)buffer_ptr) + offset, srcAddr.GetRawHostAddress(), 4); + SetUINT16(OFFSET_NUM_SRC, numSrc + 1); + SetLength(newLength); + if (updateChecksum) ComputeChecksum(); + return true; +} // end ProtoPktIGMP::AppendSourceAddress() + +// This inits the groupRecord into buffer space at the +// end of the IGMP message being built (Need to call AppendGroupRecord() to commit it) +// (avoids need for additional allocation and copying when building up message) +bool ProtoPktIGMP::AttachGroupRecord(GroupRecord& groupRecord) +{ + UINT32 currentLength = GetLength(); + ASSERT(0 == (currentLength & 0x00000003)); // should be multiple of 4 + UINT32* bufferPtr = buffer_ptr + (currentLength >> 2); + unsigned int bufferSpace = GetBufferLength() - currentLength; + if (!groupRecord.InitIntoBuffer(bufferPtr, bufferSpace)) + { + PLOG(PL_ERROR, "ProtoPktIGMP::AttachGroupRecord() error: insufficient buffer space\n"); + return false; + } + return true; +} // end ProtoPktIGMP::AttachGroupRecord() + +bool ProtoPktIGMP::AppendGroupRecord(const GroupRecord& groupRecord, bool updateChecksum) +{ + UINT32 currentLength = GetLength(); + ASSERT(0 == (currentLength & 0x00000003)); // should be multiple of 4 + unsigned int bufferSpace = GetBufferLength() - currentLength; + if (bufferSpace < groupRecord.GetLength()) + { + PLOG(PL_ERROR, "ProtoPktIGMP::AppendGroupRecord() error: insufficient buffer space\n"); + return false; + } + char* ptr = ((char*)buffer_ptr + currentLength); + // Check if we need to copy since this is _not_ an attached group record + if (ptr != groupRecord.GetBuffer()) + memcpy(ptr, groupRecord.GetBuffer(), groupRecord.GetLength()); + SetLength(currentLength + groupRecord.GetLength()); + if (updateChecksum) ComputeChecksum(); + return true; +} // end ProtoPktIGMP::AppendGroupRecord() + +UINT16 ProtoPktIGMP::ComputeChecksum(bool set) +{ + UINT32 sum = 0; + const UINT16* ptr = (const UINT16*)GetBuffer32(); + // Compute before checksum + unsigned int end = OFFSET_CHECKSUM/2; + for (unsigned int i = 0; i < end; i++) + sum += (UINT16)ntohs(ptr[i]); + unsigned int start = end + 1; + end = GetLength() / 2; + for (unsigned int i = start; i < end; i++) + sum += (UINT16)ntohs(ptr[i]); + + // Carry as needed + while (0 != (sum >> 16)) + sum = (sum & 0x0000ffff) + (sum >> 16); + // Complement + sum = ~sum; + // ZERO check/correct as needed + if (0 == sum) sum = 0x0000ffff; + if (set) SetUINT16(OFFSET_CHECKSUM, (UINT16)sum); + return (UINT16)sum; +} // end ProtoPktIGMP::ComputeChecksum() + +ProtoPktIGMP::GroupRecord::GroupRecord(UINT32* bufferPtr, + unsigned int numBytes, + bool initFromBuffer, + bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) + InitFromBuffer(); + else + InitIntoBuffer(); +} + +ProtoPktIGMP::GroupRecord::~GroupRecord() +{ +} + +bool ProtoPktIGMP::GroupRecord::InitFromBuffer(UINT32* bufferPtr, + unsigned int bufferBytes, + bool freeOnDestruct) +{ + unsigned int minLength = OFFSET_SRC_LIST; + if (NULL != bufferPtr) + { + if (bufferBytes < minLength) // IGMPv2 msg size + return false; + else + AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct); + } + minLength += GetAuxDataLen(); + minLength += 4*GetNumSources(); + if (GetBufferLength() < minLength) + { + PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::InitFromBuffer() error: truncated IGMPv3 group record?!\n"); + return false; + } + SetLength(minLength); + return true; +} // end ProtoPktIGMP::GroupRecord::InitFromBuffer() + + +bool ProtoPktIGMP::GroupRecord::GetSourceAddress(UINT16 index, ProtoAddress& srcAddr) const +{ + if (index >= GetNumSources()) + { + PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::GetSourceAddress() error: index out of range\n"); + srcAddr.Invalidate(); + return false; + } + const char* ptr = (char*)buffer_ptr + OFFSET_SRC_LIST + (index << 2); + srcAddr.SetRawHostAddress(ProtoAddress::IPv4, ptr, 4); + return true; +} // end ProtoPktIGMP::GroupRecord::GetSourceAddress() + +bool ProtoPktIGMP::GroupRecord::InitIntoBuffer(UINT32* bufferPtr, + unsigned int bufferBytes, + bool freeOnDestruct) +{ + UINT16 minLength = OFFSET_SRC_LIST; + if (NULL != bufferPtr) + { + if (bufferBytes < minLength) + return false; + else + AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct); + } + if (GetBufferLength() < minLength) return false; + memset(buffer_ptr, 0, OFFSET_SRC_LIST); + return true; +} // end ProtoPktIGMP::GroupRecord::InitIntoBuffer() + +void ProtoPktIGMP::GroupRecord::SetGroupAddress(const ProtoAddress* groupAddr) +{ + char* ptr = ((char*)buffer_ptr) + OFFSET_GROUP; + if ((NULL == groupAddr) || (ProtoAddress::IPv4 != groupAddr->GetType()) || (!groupAddr->IsMulticast())) + memset(ptr, 0, 4); + else + memcpy(ptr, groupAddr->GetRawHostAddress(), 4); +} // end ProtoPktIGMP::GroupRecord::SetGroupAddress() + +bool ProtoPktIGMP::GroupRecord::AppendSourceAddress(const ProtoAddress& srcAddr) +{ + if ((ProtoAddress::IPv4 != srcAddr.GetType()) || !srcAddr.IsUnicast()) + { + PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendSourceAddress() error: invalid source address\n"); + return false; + } + // Is there room for another source address? + UINT16 numSrc = GetNumSources(); + unsigned int offset = OFFSET_SRC_LIST + 4*numSrc; + ASSERT(offset == GetLength()); + unsigned int newLength = offset + 4; + if (newLength > GetBufferLength()) + { + PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendSourceAddress() error: insufficient buffer space\n"); + return false; + } + memcpy(((char*)buffer_ptr) + offset, srcAddr.GetRawHostAddress(), 4); + SetUINT16(OFFSET_NUM_SRC, numSrc + 1); + SetLength(newLength); + return true; +} // end ProtoPktIGMP::GroupRecord::AppendSourceAddress() + +bool ProtoPktIGMP::GroupRecord::AppendAuxiliaryData(const char* data, UINT16 len) +{ + unsigned int currentLength = GetLength(); + unsigned int bufferSpace = GetBufferLength() - currentLength; + if (0 != (len % 0xfffc)) + { + // aux data len must by multiple of 4 bytes (32-bit words) + PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendAuxiliaryData() error: invalid data length\n"); + return false; + } + if (bufferSpace < len) + { + PLOG(PL_ERROR, "ProtoPktIGMP::GroupRecord::AppendAuxiliaryData() error: insufficient buffer space\n"); + return false; + } + char* ptr = ((char*)buffer_ptr) + currentLength; + memcpy(ptr, data, len); + SetUINT8(OFFSET_AUX_LEN,len >> 2); + SetLength(currentLength + len); + return true; +} // end ProtoPktIGMP::GroupRecord::AppendAuxiliaryData() + + + diff --git a/src/common/protoPktIP.cpp b/src/common/protoPktIP.cpp index 467e097..ca48938 100644 --- a/src/common/protoPktIP.cpp +++ b/src/common/protoPktIP.cpp @@ -152,24 +152,25 @@ bool ProtoPktIPv4::InitFromBuffer(UINT32* bufferPtr, unsigned int numBytes, bool ProtoPkt::SetLength(0); if (GetBufferLength() > (OFFSET_VERSION+1)) { - if (4 != (((UINT8*)buffer_ptr)[0] >> 4)) + if (4 == (((UINT8*)buffer_ptr)[0] >> 4)) { - return false; // not an IPv4 packet! - } - else if (GetBufferLength() > (OFFSET_LEN + 2)) - { - // this validates that the embedded length is <= buffer_bytes - return ProtoPkt::InitFromBuffer(GetTotalLength()); + if (GetBufferLength() > (OFFSET_LEN + 2) && + ProtoPkt::InitFromBuffer(GetTotalLength())) + { + return true; + } + else + { + PLOG(PL_ERROR, "ProtoPktIPv4::InitFromBuffer() error: insufficient buffer space!\n"); + } } else { - return false; + PLOG(PL_WARN, "ProtoPktIPv4::InitFromBuffer() error: invalid protocol version!\n"); } } - else - { - return false; - } + if (NULL != bufferPtr) DetachBuffer(); + return false; } // end ProtoPktIPv4::InitFromBuffer() @@ -190,6 +191,10 @@ bool ProtoPktIPv4::InitIntoBuffer(UINT32* bufferPtr, unsigned int bufferBytes, b SetHeaderLength(20); SetChecksum(0); ((UINT16*)buffer_ptr)[OFFSET_FRAGMENT] = 0; // init flags & frag to ZERO + ((UINT8*)buffer_ptr)[OFFSET_TOS] = 0; + ((UINT8*)buffer_ptr)[OFFSET_FLAGS] = 0; + ((UINT8*)buffer_ptr)[OFFSET_TTL] = 255; + ((UINT8*)buffer_ptr)[OFFSET_PROTOCOL] = 0; // (TBD) Set some header fields to default values? (e.g. fragment, flags) // (TBD) should we set total length to 20 here? return true; @@ -579,7 +584,6 @@ bool ProtoPktIPv6::InitFromBuffer(UINT32* bufferPtr, unsigned int numBytes, bool if (6 != (((UINT8*)buffer_ptr)[0] >> 4)) { PLOG(PL_ERROR, "ProtoPktIPv6::InitFromBuffer() error: invalid version number\n"); - return false; // not an IPv6 packet! } else if (GetBufferLength() > (2*OFFSET_LENGTH + 2)) { @@ -591,20 +595,19 @@ bool ProtoPktIPv6::InitFromBuffer(UINT32* bufferPtr, unsigned int numBytes, bool else { PLOG(PL_ERROR, "ProtoPktIPv6::InitFromBuffer() error: invalid packet length?\n"); - return false; } } else { PLOG(PL_ERROR, "ProtoPktIPv6::InitFromBuffer() error: insufficient buffer space (2)\n"); - return false; } } else { PLOG(PL_ERROR, "ProtoPktIPv6::InitFromBuffer() error: insufficient buffer space (1)\n"); - return false; } + if (NULL != bufferPtr) DetachBuffer(); + return false; } // end ProtoPktIPv6::InitFromBuffer() @@ -857,7 +860,7 @@ ProtoPktIPv6::Extension::Extension(Protocol extType, : ProtoPkt(bufferPtr, numBytes, freeOnDestruct), ext_type(NONE), opt_pending(false), opt_packed(false) { if (initFromBuffer) - InitFromBuffer(extType, bufferPtr, numBytes, freeOnDestruct); + InitFromBuffer(extType); else InitIntoBuffer(extType); } @@ -1151,7 +1154,7 @@ bool ProtoPktIPv6::Extension::Iterator::GetNextExtension(Extension& extension) if ((6 != ipv6_pkt.GetVersion()) || (offset >= ipv6_pkt.GetLength())) return false; if (IsExtension(next_hdr)) { - if (extension.InitFromBuffer(next_hdr, (UINT32*)ipv6_pkt.GetBuffer()+(offset >> 2), ipv6_pkt.GetLength()-offset)) + if (extension.InitFromBuffer(next_hdr, (UINT32*)ipv6_pkt.GetBuffer32()+(offset >> 2), ipv6_pkt.GetLength()-offset)) { next_hdr = extension.GetNextHeader(); offset += extension.GetLength(); @@ -1664,6 +1667,123 @@ bool ProtoPktDPD::SetTaggerId(const ProtoAddress& ipAddr) } } // ProtoPktDPD::SetTaggerId(by address) + + +// begin ProtoPktMobile implementation +ProtoPktMobile::ProtoPktMobile(UINT32* bufferPtr, + unsigned int numBytes, + bool initFromBuffer, + bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (initFromBuffer) + InitFromBuffer(); + else + InitIntoBuffer(); + } +} + +ProtoPktMobile::~ProtoPktMobile() +{ +} + +bool ProtoPktMobile::InitIntoBuffer(UINT32* bufferPtr, unsigned int bufferBytes, bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (bufferBytes < 8) + return false; + else + AttachBuffer(bufferPtr, bufferBytes, freeOnDestruct); + } + else if (GetBufferLength() < 8) + { + return false; + } + SetProtocol(ProtoPktIP::RESERVED); + SetUINT8(OFFSET_RESERVED, 0); + SetChecksum(0); + SetLength(8); + return true; +} // end ProtoPktMobile::InitIntoBuffer() + + +void ProtoPktMobile::SetDstAddr(const ProtoAddress& addr, bool calculateChecksum) +{ + memcpy((char*)(buffer_ptr+OFFSET_DST_ADDR), addr.GetRawHostAddress(), 4); // (TBD) leverage alignment? + if (calculateChecksum) CalculateChecksum(); // (TBD) is it worth it to incrementally update +} // end ProtoPktMobile::SetDstAddr() + +bool ProtoPktMobile::SetSrcAddr(const ProtoAddress& addr, bool calculateChecksum) +{ + if (GetBufferLength() < 12) return false; + memcpy((char*)(buffer_ptr+OFFSET_SRC_ADDR), addr.GetRawHostAddress(), 4); // (TBD) leverage alignment? + if (calculateChecksum) CalculateChecksum(); // (TBD) is it worth it to incrementally update + SetFlag(FLAG_SRC); + SetLength(12); + return true; +} // end ProtoPktMobile::SetDstAddr() + +// Return header checksum in host byte order +UINT16 ProtoPktMobile::CalculateChecksum(bool set) +{ + UINT32 sum = 0; + UINT16 savedSum = GetChecksum(); + SetChecksum(0); + UINT16* ptr = (UINT16*)buffer_ptr; + // Calculate checksum, skipping checksum field + unsigned int headerLen = FlagIsSet(FLAG_SRC) ? 12/2 : 8/2; + for (unsigned int i = 0; i < headerLen; i++) + sum += ntohs(ptr[i]); + while (sum >> 16) + sum = (sum & 0x0000ffff) + (sum >> 16); + sum = ~sum; + if (set) + SetChecksum(sum); + else + SetChecksum(savedSum); + return sum; +} // ProtoPktMobile::CalculateChecksum() + +bool ProtoPktMobile::InitFromBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + UINT16 minBytes = 8; + if (buffer_bytes > OFFSET_FLAGS) + { + if (FlagIsSet(FLAG_SRC)) + minBytes = 12; + } + if (buffer_bytes < minBytes) + { + pkt_length = 0; + if (NULL != bufferPtr) DetachBuffer(); + return false; + } + pkt_length = numBytes; + return true; +} // end ProtoPktMobile::InitFromBuffer() + +bool ProtoPktMobile::GetSrcAddr(ProtoAddress& src) const +{ + if (FlagIsSet(FLAG_SRC)) + { + src.SetRawHostAddress(ProtoAddress::IPv4, (char*)(buffer_ptr+OFFSET_DST_ADDR), 4); + return true; + } + else + { + src.Invalidate(); + return false; + } +} // end ProtoPktMobile::GetSrcAddr() + + bool ProtoPktDPD::SetPktId(const char* pktId, UINT8 pktIdLength) { unsigned int taggerIdLength = GetTaggerIdLength(); @@ -1821,7 +1941,7 @@ UINT16 ProtoPktUDP::ComputeChecksum(ProtoPktIP& ipPkt) const return 0; } // 2) UDP header part, sans "checksum" field - const UINT16* ptr = (const UINT16*)GetBuffer(); + const UINT16* ptr = (const UINT16*)GetBuffer32(); unsigned int i; for (i = 0; i < OFFSET_CHECKSUM; i++) sum += (UINT16)ntohs(ptr[i]); diff --git a/src/common/protoPktRIP.cpp b/src/common/protoPktRIP.cpp new file mode 100644 index 0000000..33ea0a6 --- /dev/null +++ b/src/common/protoPktRIP.cpp @@ -0,0 +1,283 @@ + +#include "protoPktRIP.h" +#include // for memset(), etc + +ProtoPktRIP::ProtoPktRIP(UINT32* bufferPtr, + unsigned int numBytes, + unsigned int pktLength, + bool freeOnDestruct) +: ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (0 != pktLength) + InitFromBuffer(pktLength); + else + InitIntoBuffer(); +} + +ProtoPktRIP::~ProtoPktRIP() +{ +} + +bool ProtoPktRIP::InitIntoBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (numBytes < (4*OFFSET_PAYLOAD)) + return false; + else + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + } + else if (GetBufferLength() < (4*OFFSET_PAYLOAD)) + { + return false; + } + SetCommand(INVALID); + SetVersion(2); + SetLength(4*OFFSET_PAYLOAD); + return true; +} // end ProtoPktRIP::InitIntoBuffer() + +bool ProtoPktRIP::AddRouteEntry(const ProtoAddress& destAddr, + UINT32 maskLen, + const ProtoAddress& nextHop, + UINT32 metric, + UINT16 routeTag) +{ + // We only support IPv4 for now + if (ProtoAddress::IPv4 != destAddr.GetType() || + ProtoAddress::IPv4 != nextHop.GetType()) + { + PLOG(PL_ERROR, "ProtoPktRIP::AddRouteEntry() error: invalid address type\n"); + return false; + } + if (maskLen > 32) + { + PLOG(PL_ERROR, "ProtoPktRIP::AddRouteEntry() error: invalid mask length\n"); + return false; + } + if (metric > 16) + { + PLOG(PL_ERROR, "ProtoPktRIP::AddRouteEntry() error: invalid metric value\n"); + return false; + } + // Is there space for another entry? + unsigned space = GetBufferLength() - GetLength(); + if (space < 20) // RIP route entries are 20 bytes + { + PLOG(PL_WARN, "ProtoPktRIP::AddRouteEntry() warning: insufficient buffer space\n"); + return false; + } + unsigned int offset = GetLength() / 4; + ProtoPktRIP::RouteEntry entry(AccessBuffer() + offset, 20); + entry.SetAddressFamily(IPv4); + entry.SetRouteTag(routeTag); + entry.SetAddress(destAddr); + entry.SetMaskLength(maskLen); + entry.SetNextHop(nextHop); + entry.SetMetric(metric); + SetLength(GetLength() + 20); + return true; +} // end ProtoPktRIP::AddRouteEntry() + +bool ProtoPktRIP::InitFromBuffer(unsigned int pktLength, + UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (numBytes < pktLength) // UDP header size + return false; + else + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + } + if (GetBufferLength() < pktLength) + { + PLOG(PL_ERROR, "ProtoPktRIP::InitFromBuffer() error: insufficient buffer size\n"); + return false; + } + if (pktLength < (4*OFFSET_PAYLOAD)) + { + PLOG(PL_ERROR, "ProtoPktRIP::InitFromBuffer() error: truncated packet? (pktLen:%u offset:%u\n", pktLength, 4*OFFSET_PAYLOAD); + return false; + } + SetLength(pktLength); + return true; +} // end ProtoPktRIP::InitFromBuffer() + +unsigned int ProtoPktRIP::GetNumEntry() const +{ + unsigned int pktLen = GetLength(); + if (pktLen < (4*OFFSET_PAYLOAD)) + return 0; + else + pktLen -= 4*OFFSET_PAYLOAD; + return pktLen / 20; // RIP is 20 bytes per entry +} // end ProtoPktRIP::GetNumEntry() + +bool ProtoPktRIP::AccessRouteEntry(unsigned int index, RouteEntry& entry) +{ + if (index > (GetNumEntry() - 1)) + { + PLOG(PL_ERROR, "ProtoPktRIP::AccessRouteEntry() error: invalid route entry index\n"); + return false; + } + // Compute UINT32* pointer (20 byte entry is 5 UINT32's) + UINT32* entryBuffer = AccessBuffer() + OFFSET_PAYLOAD + 5*index; + return entry.InitFromBuffer(20, entryBuffer, 20); +} // end ProtoPktRIP::AccessRouteEntry() + +ProtoPktRIP::RouteEntry::RouteEntry(UINT32* bufferPtr, + unsigned int numBytes, + bool initFromBuffer, + bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) + InitFromBuffer(numBytes); + else + InitIntoBuffer(); +} + +ProtoPktRIP::RouteEntry::~RouteEntry() +{ +} + +bool ProtoPktRIP::RouteEntry::InitIntoBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (numBytes < 20) + return false; + else + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + } + else if (GetBufferLength() < 20) + { + return false; + } + memset(AccessBuffer(), 0, 20); + SetLength(20); + return true; +} // end ProtoPktRIP::RouteEntry::InitIntoBuffer() + +bool ProtoPktRIP::RouteEntry::SetAddress(const ProtoAddress& addr) +{ + if (ProtoAddress::IPv4 != addr.GetType()) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::SetAddress() error: invalid address type\n"); + return false; + } + memcpy(AccessBuffer() + OFFSET_ADDR, addr.GetRawHostAddress(), 4); + return true; +} // end ProtoPktRIP::RouteEntry::SetAddress() + +bool ProtoPktRIP::RouteEntry::SetMask(const ProtoAddress& addr) +{ + if (ProtoAddress::IPv4 != addr.GetType()) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::SetMask() error: invalid mask address\n"); + return false; + } + memcpy(AccessBuffer() + OFFSET_MASK, addr.GetRawHostAddress(), 4); + return true; +} // end ProtoPktRIP::RouteEntry::SetMaskLength() + +bool ProtoPktRIP::RouteEntry::SetMaskLength(UINT8 maskLen) +{ + if (maskLen > 32) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::SetMaskLength() error: invalid mask length\n"); + return false; + } + ProtoAddress maskAddr; + maskAddr.GeneratePrefixMask(ProtoAddress::IPv4, maskLen); + return SetMask(maskAddr); +} + +bool ProtoPktRIP::RouteEntry::SetNextHop(const ProtoAddress& nextHop) +{ + if (ProtoAddress::IPv4 != nextHop.GetType()) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::SetAddress() error: invalid address type\n"); + return false; + } + memcpy(AccessBuffer() + OFFSET_NHOP, nextHop.GetRawHostAddress(), 4); + return true; +} // end ProtoPktRIP::RouteEntry::SetNextHop() + +bool ProtoPktRIP::RouteEntry::InitFromBuffer(unsigned int pktLength, + UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (numBytes < pktLength) // UDP header size + return false; + else + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + } + if (GetBufferLength() < pktLength) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::InitFromBuffer() error: insufficient buffer size\n"); + return false; + } + if (pktLength < 20) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::InitFromBuffer() error: truncated packet?\n"); + return false; + } + SetLength(20); + return true; +} // end ProtoPktRIP::RouteEntry::InitFromBuffer() + +bool ProtoPktRIP::RouteEntry::GetAddress(ProtoAddress& addr) const +{ + if (IPv4 != GetAddressFamily()) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::GetAddress() error: invalid address family: %d\n", GetAddressFamily()); + return false; + } + addr.SetRawHostAddress(ProtoAddress::IPv4, (char*)(GetBuffer32() + OFFSET_ADDR), 4); + return true; +} // end ProtoPktRIP::RouteEntry::GetAddress() + +bool ProtoPktRIP::RouteEntry::GetMask(ProtoAddress& addr) const +{ + if (IPv4 != GetAddressFamily()) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::GetMask() error: invalid address family\n"); + return 0; + } + addr.SetRawHostAddress(ProtoAddress::IPv4, (char*)(GetBuffer32() + OFFSET_MASK), 4); + return true; +} // end ProtoPktRIP::RouteEntry::GetMask() + +UINT8 ProtoPktRIP::RouteEntry::GetMaskLength() const +{ + ProtoAddress maskAddr; + if (!GetMask(maskAddr)) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::GetMaskLength() error: invalid address family\n"); + return 0; + } + return maskAddr.GetPrefixLength(); +} // end ProtoPktRIP::RouteEntry::GetMaskLength() + +bool ProtoPktRIP::RouteEntry::GetNextHop(ProtoAddress& addr) const +{ + if (IPv4 != GetAddressFamily()) + { + PLOG(PL_ERROR, "ProtoPktRIP::RouteEntry::GetNextHop() error: invalid address family\n"); + return 0; + } + addr.SetRawHostAddress(ProtoAddress::IPv4, (char*)(GetBuffer32() + OFFSET_NHOP), 4); + return true; +} // end ProtoPktRIP::RouteEntry::GetNextHop() + + diff --git a/src/common/protoPktTCP.cpp b/src/common/protoPktTCP.cpp new file mode 100644 index 0000000..513ed03 --- /dev/null +++ b/src/common/protoPktTCP.cpp @@ -0,0 +1,172 @@ +#include "protoPktTCP.h" + +ProtoPktTCP::ProtoPktTCP(UINT32* bufferPtr, + unsigned int numBytes, + bool initFromBuffer, + bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (initFromBuffer) + InitFromBuffer(); + else + InitIntoBuffer(); + } +} + +ProtoPktTCP::~ProtoPktTCP() +{ +} + +bool ProtoPktTCP::InitFromPacket(ProtoPktIP& ipPkt) +{ + switch (ipPkt.GetVersion()) + { + case 4: + { + // (TBD) support IPv4 extended headers + ProtoPktIPv4 ip4Pkt(ipPkt); + if (ProtoPktIP::TCP == ip4Pkt.GetProtocol()) + { + return InitFromBuffer(ip4Pkt.AccessPayload(), ip4Pkt.GetPayloadLength(), false); + } + else + { + return false; // not a TCP packet + } + break; + } + case 6: + { + ProtoPktIPv6 ip6Pkt(ipPkt); + if (ip6Pkt.HasExtendedHeader()) + { + unsigned int extHeaderLength = 0; + ProtoPktIPv6::Extension::Iterator extIterator(ip6Pkt); + ProtoPktIPv6::Extension ext; + while (extIterator.GetNextExtension(ext)) + { + extHeaderLength += ext.GetLength(); + if (ProtoPktIP::TCP == ext.GetNextHeader()) + { + UINT32* tcpBuffer = ip6Pkt.AccessPayload() + (extHeaderLength >> 2); + unsigned int tcpLength = ip6Pkt.GetPayloadLength() - extHeaderLength; + return InitFromBuffer(tcpBuffer, tcpLength, false); + } + } + return false; // not a TCP packet + } + else if (ProtoPktIP::TCP == ip6Pkt.GetNextHeader()) + { + return InitFromBuffer(ip6Pkt.AccessPayload(), ip6Pkt.GetPayloadLength(), false); + } + else + { + return false; // not a TCP packet + } + break; + } + default: + PLOG(PL_ERROR, "ProtoPktTCP::InitFromPacket() error: bad IP packet version: %d\n", ipPkt.GetVersion()); + return false; + } + return true; +} // end ProtoPktTCP::InitFromPacket() + +bool ProtoPktTCP::InitFromBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + UINT16 totalLen = GetPayloadLength() + (OffsetPayload() << 2); + if (totalLen > buffer_bytes) + { + pkt_length = 0; + if (NULL != bufferPtr) DetachBuffer(); + return false; + } + else + { + // (TBD) We could validate the checksum, too? + pkt_length = totalLen; + return true; + } +} // end bool ProtoPktTCP::InitFromBuffer() + +bool ProtoPktTCP::InitIntoBuffer(UINT32* bufferPtr, + unsigned int numBytes, + bool freeOnDestruct) +{ + if (NULL != bufferPtr) + { + if (numBytes < 20) // TCP header size + return false; + else + AttachBuffer(bufferPtr, numBytes, freeOnDestruct); + } + if (GetBufferLength() < 20) return false; + SetDataOffset(5); + ClearFlags(); + SetChecksum(0); + return true; +} // end ProtoPktTCP::InitIntoBuffer() + +UINT16 ProtoPktTCP::ComputeChecksum(ProtoPktIP& ipPkt) const +{ + UINT32 sum = 0; + // 1) Calculate pseudo-header part + switch(ipPkt.GetVersion()) + { + case 4: + { + ProtoPktIPv4 ipv4Pkt(ipPkt); + // a) src/dst addr pseudo header portion + const UINT16* ptr = (const UINT16*)ipv4Pkt.GetSrcAddrPtr(); + int addrEndex = ProtoPktIPv4::ADDR_LEN; // note src + dst = 2 addresses + for (int i = 0; i < addrEndex; i++) + sum += (UINT16)ntohs(ptr[i]); + // b) protocol & "total length" pseudo header portions + sum += (UINT16)ipv4Pkt.GetProtocol(); + sum += (UINT16)GetLength(); // TCP length + break; + } + case 6: + { + ProtoPktIPv6 ipv6Pkt(ipPkt); + // a) src/dst addr pseudo header portion + const UINT16* ptr = (const UINT16*)ipv6Pkt.GetSrcAddrPtr(); + int addrEndex = ProtoPktIPv6::ADDR_LEN; // note src + dst = 2 addresses + for (int i = 0; i < addrEndex; i++) + sum += (UINT16)ntohs(ptr[i]); + sum += (UINT16)GetLength(); // TCP length + sum += (UINT16)ipv6Pkt.GetNextHeader(); + break; + } + default: + return 0; + } + // 2) TCP header part, sans "checksum" field + const UINT16* ptr = (const UINT16*)GetBuffer32(); + unsigned int i; + for (i = 0; i < OFFSET_CHECKSUM; i++) + sum += (UINT16)ntohs(ptr[i]); + // 3) TCP payload part (note adjustment for odd number of payload bytes) + unsigned int dataEndex = GetLength(); + if (0 != (dataEndex & 0x01)) + sum += (UINT16)(((UINT16)((UINT8*)ptr)[dataEndex-1]) << 8); + dataEndex >>= 1; // convert from bytes to UINT16 index + for (i = (OFFSET_CHECKSUM+1); i < dataEndex; i++) + sum += (UINT16)ntohs(ptr[i]); + + // 4) Carry as needed + while (0 != (sum >> 16)) + sum = (sum & 0x0000ffff) + (sum >> 16); + + sum = ~sum; + + // 5) ZERO check/correct as needed + if (0 == sum) sum = 0x0000ffff; + return (UINT16)sum; +} // end ProtoPktTCP::CalculateChecksum() diff --git a/src/common/protoQueue.cpp b/src/common/protoQueue.cpp index cfcce66..5973151 100644 --- a/src/common/protoQueue.cpp +++ b/src/common/protoQueue.cpp @@ -86,6 +86,18 @@ void ProtoQueue::Item::Cleanup() } } // end ProtoQueue::Item::Cleanup() +bool ProtoQueue::Item::IsInOtherQueue(const ProtoQueue& queue) +{ + ProtoTree::Iterator iterator(container_list); + const ProtoQueue::Container::Entry* entry; + while (NULL != (entry = static_cast(iterator.GetNextItem()))) + { + if (&queue != entry->GetContainer().GetQueue()) + return true; + } + return false; +} // end ProtoQueue::IsInOtherQueue() + ProtoQueue::ContainerPool::ContainerPool() { } @@ -235,14 +247,6 @@ ProtoSimpleQueue::Container::~Container() Cleanup(); } -ProtoSimpleQueue::ContainerPool::ContainerPool() -{ -} - -ProtoSimpleQueue::ContainerPool::~ContainerPool() -{ -} - ProtoIndexedQueue::ProtoIndexedQueue(bool usePool) : ProtoQueue(usePool) { @@ -264,6 +268,8 @@ bool ProtoIndexedQueue::Insert(Item& theItem) if (NULL == theContainer) theContainer = CreateContainer(); if (NULL == theContainer) return false; Associate(theItem, *theContainer); + //return item_tree.Insert(*theContainer); + //bunny Brian should we always return true here seems wrong...I'll let you do the update above or not. item_tree.Insert(*theContainer); return true; } // end ProtoIndexedQueue::Insert() diff --git a/src/common/protoRouteMgr.cpp b/src/common/protoRouteMgr.cpp index c29f1dc..938c9ad 100644 --- a/src/common/protoRouteMgr.cpp +++ b/src/common/protoRouteMgr.cpp @@ -5,17 +5,24 @@ */ #include "protoRouteMgr.h" #include "protoDebug.h" + +ProtoRouteMgr::ProtoRouteMgr() + : savedRoutesIPv4(NULL), savedRoutesIPv6(NULL) +{ +} + ProtoRouteMgr::~ProtoRouteMgr() { - if(NULL != savedRoutesIPv4) - delete savedRoutesIPv4; - if(NULL != savedRoutesIPv6) - delete savedRoutesIPv6; + if(NULL != savedRoutesIPv4) delete savedRoutesIPv4; + if(NULL != savedRoutesIPv6) delete savedRoutesIPv6; } + + bool ProtoRouteMgr::DeleteAllRoutes() { return DeleteAllRoutes(ProtoAddress::IPv4) && DeleteAllRoutes(ProtoAddress::IPv6); } + bool ProtoRouteMgr::DeleteAllRoutes(ProtoAddress::Type addrType, unsigned int maxIterations) { ProtoRouteTable rt; @@ -96,13 +103,86 @@ bool ProtoRouteMgr::SetRoutes(ProtoRouteTable& routeTable) return result; } // end ProtoRouteMgr::SetRoutes() bool -ProtoRouteMgr::UpdateRoutes(ProtoRouteTable& oldRouteTable, ProtoRouteTable& newRouteTable) +ProtoRouteMgr::GetDiff(ProtoRouteTable& oldRouteTable, ProtoRouteTable& newRouteTable, ProtoRouteTable& settedRouteTable, ProtoRouteTable& deletedRouteTable) +{ + //this can be sped up by only going through a single list instead of both and adding routes directly instead of in sets. TBD + ProtoRouteTable updateRoutesMetric; //this was added so we only get a diff on changed routes via the settedRoute table + + settedRouteTable.Init(); + deletedRouteTable.Init(); + ProtoRouteTable::Iterator it_old(oldRouteTable); + ProtoRouteTable::Iterator it_new(newRouteTable); + ProtoRouteTable::Entry* entry; + + ProtoAddress gwAddrLookup; + unsigned int ifaceIndexLookup; + int metricLookup; + + while(NULL != (entry = it_old.GetNextEntry())) + { + ProtoAddress dstAddr = entry->GetDestination(); + ProtoAddress gwAddr = entry->GetGateway(); + unsigned int prefixLen = entry->GetPrefixSize(); + unsigned int ifaceIndex = entry->GetInterfaceIndex(); + int metric = entry->GetMetric(); + if(!newRouteTable.FindRoute(dstAddr,prefixLen,gwAddrLookup,ifaceIndexLookup,metricLookup)) + { + if(!deletedRouteTable.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + { + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table\n"); + return false; + } + } + } + while(NULL != (entry = it_new.GetNextEntry())) + { + ProtoAddress dstAddr = entry->GetDestination(); + ProtoAddress gwAddr = entry->GetGateway(); + unsigned int prefixLen = entry->GetPrefixSize(); + unsigned int ifaceIndex = entry->GetInterfaceIndex(); + int metric = entry->GetMetric(); + if(!oldRouteTable.FindRoute(dstAddr,prefixLen,gwAddrLookup,ifaceIndexLookup,metricLookup)) + { + if(!settedRouteTable.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + { + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in new section\n"); + return false; + } + } else if((gwAddrLookup != gwAddr) || (ifaceIndexLookup != ifaceIndex)) { + if(!settedRouteTable.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + { + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in change section\n"); + return false; + } + } else if(metricLookup != metric) { + if(!updateRoutesMetric.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + { + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in change section\n"); + return false; + } + } + } + return true; +} +bool +ProtoRouteMgr::UpdateRoutes(ProtoRouteTable& oldRouteTable, ProtoRouteTable& newRouteTable, ProtoRouteTable* settedRouteTable, ProtoRouteTable* deletedRouteTable) { //this can be sped up by only going through a single list instead of both and adding routes directly instead of in sets. TBD ProtoRouteTable removeRoutes; ProtoRouteTable updateRoutes; - removeRoutes.Init(); - updateRoutes.Init(); + ProtoRouteTable updateRoutesMetric; //this was added so we only get a diff on changed routes via the settedRoute table + ProtoRouteTable* removeRoutesPtr = &removeRoutes; + ProtoRouteTable* updateRoutesPtr = &updateRoutes; + if( NULL != settedRouteTable) + { + updateRoutesPtr = settedRouteTable; + } + if( NULL != deletedRouteTable) + { + removeRoutesPtr = deletedRouteTable; + } + removeRoutesPtr->Init(); + updateRoutesPtr->Init(); ProtoRouteTable::Iterator it_old(oldRouteTable); ProtoRouteTable::Iterator it_new(newRouteTable); ProtoRouteTable::Entry* entry; @@ -120,7 +200,7 @@ ProtoRouteMgr::UpdateRoutes(ProtoRouteTable& oldRouteTable, ProtoRouteTable& new int metric = entry->GetMetric(); if(!newRouteTable.FindRoute(dstAddr,prefixLen,gwAddrLookup,ifaceIndexLookup,metricLookup)) { - if(!removeRoutes.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + if(!removeRoutesPtr->SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) { PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table\n"); return false; @@ -136,32 +216,37 @@ ProtoRouteMgr::UpdateRoutes(ProtoRouteTable& oldRouteTable, ProtoRouteTable& new int metric = entry->GetMetric(); if(!oldRouteTable.FindRoute(dstAddr,prefixLen,gwAddrLookup,ifaceIndexLookup,metricLookup)) { - if(!updateRoutes.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + if(!updateRoutesPtr->SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) { PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in new section\n"); return false; } - } else { - if((gwAddrLookup != gwAddr) || - (ifaceIndexLookup != ifaceIndex) || - (metricLookup != metric)) + } else if((gwAddrLookup != gwAddr) || (ifaceIndexLookup != ifaceIndex)) { + if(!updateRoutesPtr->SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) { - if(!updateRoutes.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) - { - PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in change section\n"); - return false; - } + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in change section\n"); + return false; + } + } else if(metricLookup != metric) { + if(!updateRoutesMetric.SetRoute(dstAddr,prefixLen,gwAddr,ifaceIndex,metric)) + { + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed add an old route to the removeRoutes table in change section\n"); + return false; } } } - if(!DeleteRoutes(removeRoutes)) { + if(!DeleteRoutes(*removeRoutesPtr)) { PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed delete old routes\n"); return false; } - if(!SetRoutes(updateRoutes)) { + if(!SetRoutes(*updateRoutesPtr)) { PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed update routes\n"); return false; } + if(!SetRoutes(updateRoutesMetric)) { + PLOG(PL_ERROR,"ProtoRouteMgr::UpdateRoutes() failed update routes metric only\n"); + return false; + } return true; } /** @@ -222,13 +307,12 @@ bool ProtoRouteMgr::DeleteRoutes(ProtoRouteTable& routeTable) return result; } // end ProtoRouteMgr::DeleteRoutes() -bool -ProtoRouteMgr::SaveAllRoutes() +bool ProtoRouteMgr::SaveAllRoutes() { return SaveAllRoutes(ProtoAddress::IPv4) || SaveAllRoutes(ProtoAddress::IPv6); -} -bool -ProtoRouteMgr::SaveAllRoutes(ProtoAddress::Type addrType) +} // end ProtoRouteMgr::SaveAllRoutes() + +bool ProtoRouteMgr::SaveAllRoutes(ProtoAddress::Type addrType) { switch (addrType) { @@ -271,9 +355,9 @@ ProtoRouteMgr::SaveAllRoutes(ProtoAddress::Type addrType) return false; } return true; -} -bool -ProtoRouteMgr::RestoreSavedRoutes() +} // ProtoRouteMgr::SaveAllRoutes() + +bool ProtoRouteMgr::RestoreSavedRoutes() { bool returnvalue = false; if(NULL != savedRoutesIPv6) @@ -289,9 +373,9 @@ ProtoRouteMgr::RestoreSavedRoutes() PLOG(PL_ERROR,"ProtoRouteMgr::RestoreSavedRoutes() couldn't restore routes, did you save any first?"); } return returnvalue; -} -bool -ProtoRouteMgr::RestoreSavedRoutes(ProtoAddress::Type addrType) +} // end ProtoRouteMgr::RestoreSavedRoutes() + +bool ProtoRouteMgr::RestoreSavedRoutes(ProtoAddress::Type addrType) { switch(addrType) { @@ -316,4 +400,22 @@ ProtoRouteMgr::RestoreSavedRoutes(ProtoAddress::Type addrType) return false; } return true; -} +} // end ProtoRouteMgr::RestoreSavedRoutes() + +void ProtoRouteMgr::ClearSavedRoutes() +{ + // the ProtoRouteMgr base class constructor now inits + // these pointers to NULL as it should, so the Init() + // method here is probably unecessary, but now can be + // used to "clear" any saved route info + if (NULL != savedRoutesIPv4) + { + delete savedRoutesIPv4; + savedRoutesIPv4 = NULL; + } + if (NULL != savedRoutesIPv6) + { + delete savedRoutesIPv6; + savedRoutesIPv6 = NULL; + } +} // end ProtoRouteMgr::ClearSavedRoutes() diff --git a/src/common/protoSimSocket.cpp b/src/common/protoSimSocket.cpp index efdaa0f..913d141 100644 --- a/src/common/protoSimSocket.cpp +++ b/src/common/protoSimSocket.cpp @@ -274,7 +274,8 @@ bool ProtoSocket::RecvFrom(char* buffer, } // end ProtoSocket::RecvFrom() bool ProtoSocket::JoinGroup(const ProtoAddress& groupAddr, - const char* /*interfaceName*/) + const char* /*interfaceName*/, + const ProtoAddress* /*sourceAddr*/) { if (!IsOpen()) { @@ -288,7 +289,8 @@ bool ProtoSocket::JoinGroup(const ProtoAddress& groupAddr, } // end ProtoSocket::JoinGroup() bool ProtoSocket::LeaveGroup(const ProtoAddress& groupAddr, - const char* /*interfaceName*/) + const char* /*interfaceName*/, + const ProtoAddress* /*sourceAddr*/) { if (IsOpen()) { @@ -300,6 +302,7 @@ bool ProtoSocket::LeaveGroup(const ProtoAddress& groupAddr, } } // end ProtoSocket::LeaveGroup() + // (NOTE: These functions are being moved to ProtoRouteMgr // Helper functions for group joins & leaves /*bool ProtoSocket::GetInterfaceAddress(const char* interfaceName, @@ -580,6 +583,6 @@ ProtoSocket::List::Item::Item(ProtoSocket* theSocket) } ProtoSocket::List::Iterator::Iterator(const ProtoSocket::List& theList) - : list(theList), next(theList.head) + : next(theList.head) { } diff --git a/src/common/protoSocket.cpp b/src/common/protoSocket.cpp index 7c26ebe..b68b857 100644 --- a/src/common/protoSocket.cpp +++ b/src/common/protoSocket.cpp @@ -1,30 +1,37 @@ + +/** +* @file protoSocket.cpp +* +* @brief Network socket container class that provides consistent interface for use of operating system (or simulation environment) transport sockets. +*/ + #ifdef UNIX #include #include // for atoi() #ifdef HAVE_IPV6 #ifdef MACOSX -#include +#define __APPLE_USE_RFC_3542 1 // needed to invoke IPV6_PKTINFO #endif // MACOSX -#include +#include #endif // HAVE_IPV6 #include #include #include +#include #include #include #ifndef SIOCGIFHWADDR #if defined(SOLARIS) || defined(IRIX) #include // for SIOCGIFADDR ioctl -#include // for rest_init() +#include // for res_init() #include #include #else #include #include #include -#include #endif // if/else (SOLARIS || IRIX) #endif // !SIOCGIFHWADDR @@ -34,12 +41,6 @@ #include #include // for extra socket options #include -/** -* @file protoSocket.cpp -* -* @brief Network socket container class that provides consistent interface for use of operating system (or simulation environment) transport sockets. -*/ - #include #include #endif // WIN32 @@ -51,12 +52,13 @@ #define IPV6_FLOWINFO_SEND 33 // from linux/in6.h #endif // !IPV6_FLOWINFO_SEND -#include "protoSocket.h" -#include "protoDebug.h" - #include #include +#include "protoSocket.h" +#include "protoDebug.h" +#include "protoNet.h" // needed for Android getifaddrs() + // Hack for using with NRL IPSEC implementation #ifdef HAVE_NETSEC #include @@ -77,23 +79,23 @@ extern int netsec_requestlen; #ifdef WIN32 const ProtoSocket::Handle ProtoSocket::INVALID_HANDLE = INVALID_SOCKET; +LPFN_WSARECVMSG ProtoSocket::WSARecvMsg = NULL; #else const ProtoSocket::Handle ProtoSocket::INVALID_HANDLE = -1; #endif // if/else WIN32 +ProtoSocket::IPv6SupportStatus ProtoSocket::ipv6_support_status = IPV6_UNKNOWN; + ProtoSocket::ProtoSocket(ProtoSocket::Protocol theProtocol) - : domain(IPv4), protocol(theProtocol), state(CLOSED), - handle(INVALID_HANDLE), port(-1), tos(0), ecn_capable(false), + : domain(IPv4), protocol(theProtocol), raw_protocol(RAW), state(CLOSED), + handle(INVALID_HANDLE), port(-1), tos(0), ecn_capable(false), ip_recvdstaddr(false), #ifdef HAVE_IPV6 flow_label(0), #endif // HAVE_IPV6 - notifier(NULL), notify_output(false), - notify_input(true), - notify_exception(false), + notifier(NULL), notify_output(false), notify_input(true), notify_exception(false), #ifdef WIN32 input_event_handle(NULL), output_event_handle(NULL), - output_ready(false), input_ready(false), - closing(false), + input_ready(false), output_ready(false), closing(false), #endif // WIN32 listener(NULL), user_data(NULL) { @@ -110,7 +112,6 @@ ProtoSocket::~ProtoSocket() } } - bool ProtoSocket::SetBlocking(bool blocking) { #ifdef UNIX @@ -134,6 +135,57 @@ bool ProtoSocket::SetBlocking(bool blocking) return true; //Note: taken care automatically under Win32 by WSAAsyncSelect(), etc } // end ProtoSocket::SetBlocking(bool blocking) +bool ProtoSocket::StartInputNotification() +{ + if (!notify_input) + { + notify_input = true; + notify_input = UpdateNotification(); + } + return notify_input; +} // end ProtoSocket::StartInputNotification() + +void ProtoSocket::StopInputNotification() +{ + if (notify_input) + { + notify_input = false; + UpdateNotification(); + } +} // end ProtoSocket::StopInputNotification() + +bool ProtoSocket::StartOutputNotification() +{ + if (!notify_output) + { + notify_output = true; + notify_output = UpdateNotification(); + } + return notify_output; +} // end ProtoSocket::StartOutputNotification() + +void ProtoSocket::StopOutputNotification() +{ + if (notify_output) + { + notify_output = false; + UpdateNotification(); + } +} // end ProtoSocket::StopOutputNotification() + +bool ProtoSocket::StartExceptionNotification() +{ + notify_exception = true; + notify_exception = UpdateNotification(); + return notify_exception; +} // end ProtoSocket::StartExceptionNotification() + +void ProtoSocket::StopExceptionNotification() +{ + notify_exception = false; + UpdateNotification(); +} // ProtoSocket::StopExceptionNotification() + bool ProtoSocket::SetNotifier(ProtoSocket::Notifier* theNotifier) { if (notifier != theNotifier) @@ -141,10 +193,10 @@ bool ProtoSocket::SetNotifier(ProtoSocket::Notifier* theNotifier) if (IsOpen()) { // 1) Detach old notifier, if any - if (notifier) + if (NULL != notifier) { notifier->UpdateSocketNotification(*this, 0); - if (!theNotifier) + if (NULL == theNotifier) { // Reset socket to "blocking" if(!SetBlocking(true)) @@ -154,11 +206,16 @@ bool ProtoSocket::SetNotifier(ProtoSocket::Notifier* theNotifier) else { // Set socket to "non-blocking" - if(!SetBlocking(false)) + if(!SetBlocking(false)) { PLOG(PL_ERROR, "ProtoSocket::SetNotifier() SetBlocking(false) error\n", GetErrorString()); return false; } +#ifdef WIN32 + // Reset input/output ready to initial state? + input_ready = false; + output_ready = true; +#endif // WIN32 } // 2) Set and update new notifier (if applicable) notifier = theNotifier; @@ -176,7 +233,6 @@ bool ProtoSocket::SetNotifier(ProtoSocket::Notifier* theNotifier) return true; } // end ProtoSocket::SetNotifier() - ProtoAddress::Type ProtoSocket::GetAddressType() { switch (domain) @@ -213,11 +269,8 @@ bool ProtoSocket::Open(UINT16 thePort, { if (!HostIsIPv6Capable()) { - if (!SetHostIPv6Capable()) - { - PLOG(PL_ERROR, "ProtoSocket::Open() system not IPv6 capable?!\n"); - return false; - } + PLOG(PL_ERROR,"ProtoSocket::Open() system not IPv6 capable?\n"); + return false; } domain = IPv6; } @@ -240,7 +293,7 @@ bool ProtoSocket::Open(UINT16 thePort, socketType = SOCK_RAW; break; default: - PLOG(PL_ERROR,"ProtoSocket::Open Error: Unsupported protocol\n"); + PLOG(PL_ERROR,"ProtoSocket::Open() error: Unsupported protocol\n"); return false; } @@ -251,7 +304,7 @@ bool ProtoSocket::Open(UINT16 thePort, int family = AF_INET; #endif // if/else HAVE_IPV6 // Startup WinSock - if (!ProtoAddress::Win32Startup()) + if (!ProtoAddress::Win32Startup()) { PLOG(PL_ERROR, "ProtoSocket::Open() WSAStartup() error: %s\n", GetErrorString()); return false; @@ -274,7 +327,20 @@ bool ProtoSocket::Open(UINT16 thePort, protocolType = IPPROTO_TCP; break; case RAW: - protocolType = IPPROTO_RAW; + switch (raw_protocol) + { + case RAW: + protocolType = IPPROTO_RAW; + break; + case UDP: + protocolType = IPPROTO_UDP; + break; + case TCP: + protocolType = IPPROTO_TCP; + break; + default: + protocolType = IPPROTO_RAW; + } break; } @@ -327,20 +393,20 @@ bool ProtoSocket::Open(UINT16 thePort, { PLOG(PL_ERROR, "ProtoSocket: WSAEnumProtocols() error2!\n"); delete[] protocolInfo; - ProtoAddress::Win32Cleanup(); + ProtoAddress::Win32Cleanup(); return false; } } else { PLOG(PL_ERROR, "ProtoSocket: Error allocating memory!\n"); - ProtoAddress::Win32Cleanup(); + ProtoAddress::Win32Cleanup(); return false; } } else { - PLOG(PL_ERROR, "ProtoSocket::Open() WSAEnumProtocols() error1!\n"); + PLOG(PL_WARN, "ProtoSocket::Open() WSAEnumProtocols() error1!\n"); } // Use WSASocket() to open right kind of socket @@ -350,10 +416,11 @@ bool ProtoSocket::Open(UINT16 thePort, if (UDP == protocol) flags |= (WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF); #endif // !_WIN32_WCE handle = WSASocket(family, socketType, 0, infoPtr, 0, flags); - if (protocolInfo) delete[] protocolInfo; + if (NULL != protocolInfo) delete[] protocolInfo; if (INVALID_HANDLE == handle) { - PLOG(PL_ERROR, "ProtoSocket::WSASocket() error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "ProtoSocket::Open() WSASocket() error: %s\n", GetErrorString()); + ProtoAddress::Win32Cleanup(); return false; } if (NULL == (input_event_handle = WSACreateEvent())) @@ -362,12 +429,35 @@ bool ProtoSocket::Open(UINT16 thePort, Close(); return false; } + input_ready = false; + output_ready = true; +#else +#ifdef HAVE_IPV6 + int family = (IPv6 == domain) ? AF_INET6: AF_INET; #else - int family = (IPv6 == domain) ? PF_INET6: PF_INET; - int socketProtocol = (SOCK_RAW == socketType) ? IPPROTO_RAW : 0; + int family = AF_INET; +#endif // if/else HAVE_IPV6 + int socketProtocol = 0; // use default socket protocol type if not RAW + if (SOCK_RAW == socketType) + { + switch (raw_protocol) + { + case RAW: + socketProtocol = IPPROTO_RAW; + break; + case UDP: + socketProtocol = IPPROTO_UDP; + break; + case TCP: + socketProtocol = IPPROTO_TCP; + break; + default: + socketProtocol = IPPROTO_RAW; + } + } if (INVALID_HANDLE == (handle = socket(family, socketType, socketProtocol))) { - PLOG(PL_ERROR, "ProtoSocket: socket() error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "ProtoSocket::Open() socket() error: %s\n", GetErrorString()); return false; } // (TBD) set IP_HDRINCL option for raw socket @@ -376,7 +466,7 @@ bool ProtoSocket::Open(UINT16 thePort, #ifdef NETSEC if (net_security_setrequest(handle, 0, netsec_request, netsec_requestlen)) { - PLOG(PL_ERROR, "ProtoSocket: net_security_setrequest() error: %s\n", + PLOG(PL_ERROR, "ProtoSocket::Open() net_security_setrequest() error: %s\n", GetErrorString()); Close(); return false; @@ -410,12 +500,14 @@ bool ProtoSocket::Open(UINT16 thePort, else { port = -1; - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoSocket::Open() error installing async notification\n"); - Close(); - return false; - } + // TBD - the UpdateNotification() call here may be unnecessary??? + // (newly opened, unbound socket needs no notification?) + if (!UpdateNotification()) + { + PLOG(PL_ERROR, "ProtoSocket::Open() error installing async notification\n"); + Close(); + return false; + } } // Do the following in case TOS or "ecn_capable" was set _before_ // ProtoSocket::Open() was called. Note that the "IPv6" flow label @@ -424,12 +516,31 @@ bool ProtoSocket::Open(UINT16 thePort, #ifdef WIN32 closing = false; #endif //WIN32 + ip_recvdstaddr = false; // make sure this is reset return true; } // end ProtoSocket::Open() +bool ProtoSocket::SetRawProtocol(Protocol theProtocol) +{ + bool wasOpen = false; + UINT16 portSave = 0; + if (IsOpen()) + { + portSave = GetPort(); + Close(); + wasOpen = true; + } + protocol = RAW; + raw_protocol = theProtocol; + if (wasOpen) + return Open(portSave); + else + return true; +} // end ProtoSocket::SetRawProtocol() + bool ProtoSocket::UpdateNotification() { - if (NULL != notifier) + if (NULL != notifier) { if (IsOpen() && !SetBlocking(false)) { @@ -437,7 +548,7 @@ bool ProtoSocket::UpdateNotification() return false; } int notifyFlags = NOTIFY_NONE; - if (listener) + if (NULL != listener) { switch (protocol) { @@ -473,7 +584,8 @@ bool ProtoSocket::UpdateNotification() notifyFlags = NOTIFY_INPUT; break; case CONNECTED: - notifyFlags = NOTIFY_INPUT; + if (notify_input) + notifyFlags = NOTIFY_INPUT; if (notify_output) notifyFlags |= NOTIFY_OUTPUT; break; @@ -566,6 +678,7 @@ void ProtoSocket::OnNotify(ProtoSocket::Flag theFlag) } else if (NOTIFY_ERROR == theFlag) { + TRACE("ProtoSocket NOTIFY_ERROR notification\n"); switch(state) { case CONNECTING: @@ -593,16 +706,12 @@ void ProtoSocket::OnNotify(ProtoSocket::Flag theFlag) ASSERT(INVALID_EVENT != event); if (listener) listener->on_event(*this, event); } // end ProtoSocket::OnNotify() - bool ProtoSocket::Bind(UINT16 thePort, const ProtoAddress* localAddress) { - if (IsBound()) - Close(); - if (IsOpen() && - (NULL != localAddress) && - (GetAddressType() != localAddress->GetType())) + if (IsBound()) Close(); + if (IsOpen() && (NULL != localAddress) && (GetAddressType() != localAddress->GetType())) { - Close(); + Close(); // requires a different address family, so close for reopen } if (!IsOpen()) { @@ -642,7 +751,7 @@ bool ProtoSocket::Bind(UINT16 thePort, const ProtoAddress* localAddress) if (NULL != localAddress) { ((struct sockaddr_in*)&socketAddr)->sin_addr = - ((const struct sockaddr_in*)(&localAddress->GetSockAddr()))->sin_addr; + ((const struct sockaddr_in*)(&localAddress->GetSockAddrStorage()))->sin_addr; } else { @@ -678,17 +787,13 @@ bool ProtoSocket::Bind(UINT16 thePort, const ProtoAddress* localAddress) #endif // UNIX #ifdef HAVE_IPV6 -#if defined(WIN32) +#ifdef WIN32 OSVERSIONINFO osvi; BOOL osVistaOrLater; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); - osVistaOrLater = osvi.dwMajorVersion > 5; - if (osVistaOrLater) { // On Windows Vista & above dual stack sockets are supported so @@ -701,12 +806,12 @@ bool ProtoSocket::Bind(UINT16 thePort, const ProtoAddress* localAddress) return false; } } -#endif +#endif // WIN32 #endif // HAVE_IPV6 // Bind the socket to the given port if (bind(handle, (struct sockaddr*)&socketAddr, addrSize) < 0) { - PLOG(PL_ERROR, "ProtoSocket::Bind() bind() error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "ProtoSocket::Bind(%hu) bind() error: %s\n", thePort, GetErrorString()); return false; } @@ -717,7 +822,7 @@ bool ProtoSocket::Bind(UINT16 thePort, const ProtoAddress* localAddress) PLOG(PL_ERROR, "ProtoSocket::Bind() getsockname() error: %s\n", GetErrorString()); return false; } - + source_addr.SetSockAddr((struct sockaddr&)socketAddr); switch(((struct sockaddr*)&socketAddr)->sa_family) { case AF_INET: @@ -734,6 +839,11 @@ bool ProtoSocket::Bind(UINT16 thePort, const ProtoAddress* localAddress) PLOG(PL_ERROR, "ProtoSocket::Bind() error: getsockname() returned unknown address type\n"); return false; } +#ifdef WIN32 + // reset input/output readiness since it is a newly bound socket + input_ready = false; + output_ready = true; +#endif // WIN32 return UpdateNotification(); } // end ProtoSocket::Bind() @@ -754,9 +864,11 @@ bool ProtoSocket::Shutdown() #ifdef WIN32 if (SOCKET_ERROR == shutdown(handle, SD_SEND)) #else - if (0 != shutdown(handle, 1)) + if (0 != shutdown(handle, SHUT_WR)) #endif // if/else WIN32/UNIX { + // TBD - should we not bother to re-enable output notification here? + // (i.e. since the user intended to shutdown the socket?!) if (notifyOutput) { notify_output = true; @@ -779,19 +891,15 @@ bool ProtoSocket::Shutdown() void ProtoSocket::Close() { - if (IsOpen()) { - if (IsConnected()) {Disconnect();}; + if (IsConnected()) Disconnect(); state = CLOSED; UpdateNotification(); #ifdef WIN32 if (NULL != input_event_handle) { - if (LOCAL == protocol) - CloseHandle(input_event_handle); - else - WSACloseEvent(input_event_handle); + WSACloseEvent(input_event_handle); input_event_handle = NULL; } #endif // if WIN32 @@ -799,20 +907,18 @@ void ProtoSocket::Close() { #ifdef WIN32 if (SOCKET_ERROR == closesocket(handle)) - PLOG(PL_ERROR, "ProtoSocket::Close() warning: closesocket() error: %s\n", GetErrorString()); - ProtoAddress::Win32Cleanup(); - output_ready = false; + PLOG(PL_WARN, "ProtoSocket::Close() warning: closesocket() error: %s\n", GetErrorString()); + ProtoAddress::Win32Cleanup(); + input_ready = output_ready = false; #else close(handle); #endif // if/else WIN32/UNIX - handle = INVALID_HANDLE; + handle = INVALID_HANDLE; } port = -1; } - } // end Close() - bool ProtoSocket::Connect(const ProtoAddress& theAddress) { if (IsConnected()) Disconnect(); @@ -827,13 +933,6 @@ bool ProtoSocket::Connect(const ProtoAddress& theAddress) #else socklen_t addrSize = sizeof(struct sockaddr_in); #endif // if/else HAVE_IPV6 - state = CONNECTING; - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoSocket::Connect() error updating notification\n"); - state = IDLE; - return false; - } #ifdef WIN32 int result = WSAConnect(handle, &theAddress.GetSockAddr(), addrSize, NULL, NULL, NULL, NULL); @@ -842,13 +941,12 @@ bool ProtoSocket::Connect(const ProtoAddress& theAddress) if (WSAEWOULDBLOCK != WSAGetLastError()) { PLOG(PL_ERROR, "ProtoSocket::Connect() WSAConnect() error: (%s)\n", GetErrorString()); - state = IDLE; - UpdateNotification(); return false; - } - output_ready = false; + } + output_ready = false; // not yet connected + state = CONNECTING; } -#else +#else // UNIX #ifdef HAVE_IPV6 if (flow_label && (ProtoAddress::IPv6 == theAddress.GetType())) ((struct sockaddr_in6*)(&theAddress.GetSockAddrStorage()))->sin6_flowinfo = flow_label; @@ -859,21 +957,21 @@ bool ProtoSocket::Connect(const ProtoAddress& theAddress) if (EINPROGRESS != errno) { PLOG(PL_ERROR, "ProtoSocket::Connect() connect() error: %s\n", GetErrorString()); - state = IDLE; - UpdateNotification(); return false; } + state = CONNECTING; } -#endif // if/else WIN32 +#endif // if/else WIN32/UNIX else { state = CONNECTED; - if (!UpdateNotification()) - { - PLOG(PL_ERROR, "ProtoSocket::Connect() error updating notification\n"); - state = IDLE; - return false; - } + } + if (!UpdateNotification()) + { + PLOG(PL_ERROR, "ProtoSocket::Connect() error updating notification\n"); + state = IDLE; + UpdateNotification(); + return false; } // Use getsockname() to get local "port" and "source_addr" #ifdef HAVE_IPV6 @@ -921,10 +1019,10 @@ void ProtoSocket::Disconnect() { if (IsConnected() || IsConnecting()) { - state = IDLE; + state = IDLE; + UpdateNotification(); //source_addr.Invalidate(); //destination.Invalidate(); - UpdateNotification(); struct sockaddr nullAddr; memset(&nullAddr, 0 , sizeof(struct sockaddr)); #ifdef UNIX @@ -934,8 +1032,8 @@ void ProtoSocket::Disconnect() if (connect(handle, &nullAddr, sizeof(struct sockaddr))) { if (EAFNOSUPPORT != errno) - PLOG(PL_ERROR, "ProtoSocket::Disconnect() connect() error: %s)\n", GetErrorString()); - + PLOG(PL_WARN, "ProtoSocket::Disconnect() connect() error: %s)\n", GetErrorString()); + // (TBD) should we Close() and re-Open() the socket here? } } else @@ -945,16 +1043,17 @@ void ProtoSocket::Disconnect() // (but can't easily recall all socket options, so best not I guess) //UINT16 savePort = GetPort(); - // Macosx & linux trigger server resets differently... + // MACOSX & LINUX trigger server resets differently... #ifdef MACOSX Close(); #else - nullAddr.sa_family = AF_UNSPEC; - if (connect(handle,&nullAddr,sizeof(struct sockaddr))) - { - if (EAFNOSUPPORT != errno) - PLOG(PL_ERROR,"ProtoSocket::Disconnect() connect() error (%s)\n",GetErrorString()); - } + nullAddr.sa_family = AF_UNSPEC; + if (connect(handle,&nullAddr,sizeof(struct sockaddr))) + { + if (EAFNOSUPPORT != errno) + PLOG(PL_WARN, "ProtoSocket::Disconnect() connect() error (%s)\n", GetErrorString()); + Close(); + } #endif //Open(savePort); } @@ -962,9 +1061,9 @@ void ProtoSocket::Disconnect() if (SOCKET_ERROR == WSAConnect(handle, &nullAddr, sizeof(struct sockaddr), NULL, NULL, NULL, NULL)) { - PLOG(PL_WARN, "ProtoSocket::Disconnect() WSAConnect() error: (%s)\n", GetErrorString()); + //PLOG(PL_WARN, "ProtoSocket::Disconnect() WSAConnect() error: %s\n", GetErrorString()); + Close(); // if windows doesn't like the null-connect disconnect trick either } - if (TCP == protocol) output_ready = false; #endif // WIN32 } } // end ProtoSocket::Disconnect() @@ -987,17 +1086,16 @@ bool ProtoSocket::Listen(UINT16 thePort) return false; } } - if (UDP == protocol) - state = CONNECTED; - else - state = LISTENING; + if (TCP == protocol) + state = LISTENING; + else + return true; // UDP sockets don't "listen" if (!UpdateNotification()) { state = IDLE; PLOG(PL_ERROR, "ProtoSocket::Listen() error updating notification\n"); return false; } - if (UDP == protocol) return true; #ifdef WIN32 if (SOCKET_ERROR == listen(handle, 5)) #else @@ -1012,6 +1110,11 @@ bool ProtoSocket::Listen(UINT16 thePort) bool ProtoSocket::Accept(ProtoSocket* newSocket) { + if (TCP != protocol) + { + PLOG(PL_ERROR, "ProtoSocket::Accept() error non-TCP socket!\n"); + return false; + } ProtoSocket& theSocket = (NULL != newSocket) ? *newSocket : *this; // Clone server socket if (this != &theSocket) @@ -1019,7 +1122,7 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) // TBD - should override ProtoSocket copy operator to delete old listener??? if (NULL != theSocket.listener) delete theSocket.listener; theSocket = *this; - theSocket.listener = NULL; // this->listener is duplicated later (or should we save our old one) + theSocket.listener = NULL; // this->listener is duplicated later (or should we save our old one?) } #ifdef HAVE_IPV6 struct sockaddr_in6 socketAddr; @@ -1046,18 +1149,21 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) switch (WSAGetLastError()) { case WSAEWOULDBLOCK: - input_ready = false; - break; + if (input_ready) + { + input_ready = false; + UpdateNotification(); + } + break; default: - PLOG(PL_ERROR, "ProtoSocket::Accept() accept() error: %s\n", GetErrorString()); - break; + PLOG(PL_ERROR, "ProtoSocket::Accept() accept() error: %s\n", GetErrorString()); + break; } #endif PLOG(PL_ERROR, "ProtoSocket::Accept() accept() error: %s\n", GetErrorString()); if (this != &theSocket) { #ifdef WIN32 - closesocket(theHandle); ProtoAddress::Win32Cleanup(); #endif // WIN32 theSocket.handle = INVALID_HANDLE; @@ -1065,6 +1171,7 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) } return false; } + // Don't think we need make "input_ready" true on accept? if (LOCAL != domain) theSocket.destination.SetSockAddr((struct sockaddr&)socketAddr); // Get the socket name so we know our port number @@ -1076,7 +1183,7 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) { #ifdef WIN32 closesocket(theHandle); - ProtoAddress::Win32Cleanup(); + ProtoAddress::Win32Cleanup(); #endif // WIN32 theSocket.handle = INVALID_HANDLE; theSocket.state = CLOSED; @@ -1107,12 +1214,12 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) { #ifdef WIN32 closesocket(theHandle); - ProtoAddress::Win32Cleanup(); + ProtoAddress::Win32Cleanup(); #endif // WIN32 theSocket.handle = INVALID_HANDLE; theSocket.state = CLOSED; } - return false;; + return false; } // end switch() if (this == &theSocket) { @@ -1120,6 +1227,8 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) UpdateNotification(); #ifdef WIN32 closesocket(theSocket.handle); + input_ready = false; // will be notified when there's something to read + output_ready = true; // assume new socket ready for writing #else close(theSocket.handle); #endif // if/else WIN32/UNIX @@ -1127,13 +1236,15 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) else { #ifdef WIN32 - theSocket.input_event_handle = NULL; + // Need a new event handle for this new, unopened socket if (NULL == (theSocket.input_event_handle = WSACreateEvent())) { PLOG(PL_ERROR, "ProtoSocket::Accept() WSACreateEvent error: %s\n", GetErrorString()); theSocket.Close(); return false; } + theSocket.input_ready = false; // will be notified when there's something to read + theSocket.output_ready = true; // assume new socket ready for writing #endif // WIN32 // TBD - keep old listener / notifier and just do an UpdateNotification() here if (NULL != listener) @@ -1156,7 +1267,7 @@ bool ProtoSocket::Accept(ProtoSocket* newSocket) } } } // end if/else (this == &theSocket) - theSocket.handle = theHandle; + theSocket.handle = theHandle; // the socket gets the new handle/descriptor from accept() theSocket.state = CONNECTED; theSocket.UpdateNotification(); return true; @@ -1178,27 +1289,47 @@ bool ProtoSocket::Send(const char* buffer, switch (WSAGetLastError()) { case WSAEINTR: - return true; + return true; case WSAEWOULDBLOCK: - output_ready = false; - return true; + if (output_ready) + { + output_ready = false; + UpdateNotification(); // because no longer "output ready" + } + return true; case WSAENETRESET: case WSAECONNABORTED: case WSAECONNRESET: case WSAESHUTDOWN: case WSAENOTCONN: - output_ready = false; - OnNotify(NOTIFY_ERROR); - break; + if (output_ready) + { + output_ready = false; + UpdateNotification(); // because no longer "output ready" + } + OnNotify(NOTIFY_ERROR); + break; default: - PLOG(PL_ERROR, "ProtoSocket::Send() WSASend() error: %s\n", GetErrorString()); - break; + PLOG(PL_ERROR, "ProtoSocket::Send() WSASend() error: %s\n", GetErrorString()); + break; } return false; } else { - output_ready = true; + if (bytesSent < numBytes) + { + if (output_ready) + { + output_ready = false; + UpdateNotification(); // because no longer "output ready" + } + } + else if (!output_ready) + { + output_ready = true; + UpdateNotification(); + } numBytes = (unsigned int)bytesSent; return true; } @@ -1219,6 +1350,9 @@ bool ProtoSocket::Send(const char* buffer, case ENOTCONN: OnNotify(NOTIFY_ERROR); break; + case ENOBUFS: + PLOG(PL_DEBUG, "ProtoSocket::Send() send() error: %s\n", GetErrorString()); + return false; default: PLOG(PL_ERROR, "ProtoSocket::Send() send() error: %s\n", GetErrorString()); break; @@ -1256,10 +1390,13 @@ bool ProtoSocket::Recv(char* buffer, { case WSAEINTR: case WSAEWOULDBLOCK: - - input_ready = false; - return true; // not really an error, just no bytes read - break; + if (input_ready) + { + input_ready = false; + UpdateNotification(); // because no longer "input ready" + } + return true; // not really an error, just no bytes read + break; case WSAENETRESET: case WSAECONNABORTED: case WSAECONNRESET: @@ -1276,7 +1413,18 @@ bool ProtoSocket::Recv(char* buffer, } else { + if ((bytesReceived < numBytes) && (TCP == protocol)) + { + input_ready = false; + UpdateNotification(); // because no longer "input ready" + } + else if (!input_ready) + { + input_ready = true; + UpdateNotification(); + } numBytes = bytesReceived; + // TBD - Should we do a NOTIFY_NONE here like we do for UNIX? return true; } #else @@ -1313,8 +1461,14 @@ bool ProtoSocket::Recv(char* buffer, #endif // if/else WIN32/UNIX } // end ProtoSocket::Recv() +// Note the function prototype and behavior here is changing slightly +// from the prior / original ProtoSocket::SendTo() behavior such that +// the EWOULDBLOCK condition no longer returns "false" but instead returns +// "true", but with setting the referenced "buflen" value to zero. +// This lets us differentiate this condition, mainly as a cue for when +// (and when not to) enable socket output notification for async i/o mgmnt.s bool ProtoSocket::SendTo(const char* buffer, - unsigned int buflen, + unsigned int& buflen, const ProtoAddress& dstAddr) { if (!IsOpen()) @@ -1331,66 +1485,92 @@ bool ProtoSocket::SendTo(const char* buffer, if (!Send(buffer, numBytes)) { - PLOG(PL_WARN, "ProtoSocket::SendTo() error: Send() error\n"); + PLOG(PL_DEBUG, "ProtoSocket::SendTo() error: Send() error\n"); + buflen = 0; return false; } - else if (numBytes != buflen) + if (numBytes != buflen) { - - PLOG(PL_ERROR, "ProtoSocket::SendTo() error: Send() incomplete\n"); - return false; + PLOG(PL_DEBUG, "ProtoSocket::SendTo() error: Send() incomplete\n"); + buflen = 0; + return true; } else { return true; } } - else - { - socklen_t addrSize; + else + { + socklen_t addrSize; #ifdef HAVE_IPV6 - if (flow_label && (ProtoAddress::IPv6 == dstAddr.GetType())) - ((struct sockaddr_in6*)(&dstAddr.GetSockAddrStorage()))->sin6_flowinfo = flow_label; - if (ProtoAddress::IPv6 == dstAddr.GetType()) - addrSize = sizeof(struct sockaddr_in6); - else + if (flow_label && (ProtoAddress::IPv6 == dstAddr.GetType())) + ((struct sockaddr_in6*)(&dstAddr.GetSockAddrStorage()))->sin6_flowinfo = flow_label; + if (ProtoAddress::IPv6 == dstAddr.GetType()) + addrSize = sizeof(struct sockaddr_in6); + else #endif //HAVE_IPV6 - addrSize = sizeof(struct sockaddr_in); + addrSize = sizeof(struct sockaddr_in); #ifdef WIN32 WSABUF wsaBuf; wsaBuf.len = buflen; wsaBuf.buf = (char*)buffer; DWORD numBytes; - if (SOCKET_ERROR == WSASendTo(handle, &wsaBuf, 1, &numBytes, 0, - &dstAddr.GetSockAddr(), addrSize, NULL, NULL)) + &dstAddr.GetSockAddr(), addrSize, NULL, NULL)) + { + buflen = 0; + switch (WSAGetLastError()) + { + case WSAEINTR: + return true; + case WSAEWOULDBLOCK: + output_ready = false; + return true; + default: + break; + } + PLOG(PL_ERROR, "ProtoSocket::SendTo() WSASendTo() error: %s\n", GetErrorString()); + return false; + } + else + { + //ASSERT(numBytes == buflen); + return true; + } #else - int result = sendto(handle, buffer, (size_t)buflen, 0, - &dstAddr.GetSockAddr(), addrSize); - + ssize_t result = sendto(handle, buffer, (size_t)buflen, 0, &dstAddr.GetSockAddr(), addrSize); if (result < 0) -#endif // if/else WIN32/UNIX { -#ifdef WIN32 - if (WSAEWOULDBLOCK == WSAGetLastError()) - output_ready = false; -#endif - PLOG(PL_WARN, "ProtoSocket::SendTo() sendto() error: %s\n", GetErrorString()); + buflen = 0; + switch (errno) + { + case EINTR: + case EAGAIN: + return true; + case ENOBUFS: + PLOG(PL_DEBUG, "ProtoSocket::SendTo() sendto() error: %s\n", GetErrorString()); + return false; + default: + break; + } + PLOG(PL_ERROR, "ProtoSocket::SendTo() sendto() error: %s\n", GetErrorString()); return false; } else { + //ASSERT(result == buflen); return true; } - } +#endif // if/else WIN32/UNIX + } } // end ProtoSocket::SendTo() - bool ProtoSocket::RecvFrom(char* buffer, unsigned int& numBytes, ProtoAddress& sourceAddr) { - if (!IsBound()) + if (!IsBound()) { PLOG(PL_ERROR, "ProtoSocket::RecvFrom() error: socket not bound\n"); numBytes = 0; @@ -1407,16 +1587,19 @@ bool ProtoSocket::RecvFrom(char* buffer, { numBytes = 0; #ifdef WIN32 - PLOG(PL_WARN, "ProtoSocket::RecvFrom() recvfrom() error: %s\n", GetErrorString()); switch (WSAGetLastError()) { case WSAEINTR: case WSAEWOULDBLOCK: - input_ready = false; + if (input_ready) + { + input_ready = false; + UpdateNotification(); // because no longer "input ready" + } return true; break; default: - PLOG(PL_ERROR, "ProtoSocket::Recv() recv() error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() recvfrom() error: %s\n", GetErrorString()); break; } #else @@ -1436,6 +1619,13 @@ bool ProtoSocket::RecvFrom(char* buffer, } else { +#ifdef WIN32 + if (!input_ready) + { + input_ready = true; + UpdateNotification(); + } +#endif // WIN32 numBytes = result; sourceAddr.SetSockAddr(*((struct sockaddr*)&sockAddr)); if (!sourceAddr.IsValid()) @@ -1447,30 +1637,302 @@ bool ProtoSocket::RecvFrom(char* buffer, } } // end ProtoSocket::RecvFrom() - +void ProtoSocket::EnableRecvDstAddr() +{ + if (!ip_recvdstaddr) + { + int enable = 1; +#ifdef IP_RECVDSTADDR + if (setsockopt(handle, IPPROTO_IP, IP_RECVDSTADDR, (char*)&enable, sizeof(enable)) < 0) + PLOG(PL_WARN, "ProtoSocket::EnableRecvDstAddr() setsocktopt(IP_RECVDSTADDR) error: %s\n", GetErrorString()); +#else + if (setsockopt(handle, IPPROTO_IP, IP_PKTINFO, (char*)&enable, sizeof(enable)) < 0) + PLOG(PL_WARN, "ProtoSocket::EnableRecvDstAddr() setsocktopt(IP_PKTINFO) error: %s\n", GetErrorString()); +#endif // if/else IP_RECVDSTADDR #ifdef HAVE_IPV6 -#ifndef IPV6_ADD_MEMBERSHIP -#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP -#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP -#endif // !IPV6_ADD_MEMBERSHIP -#endif // HAVE_IPV6 +#ifdef IPV6_RECVDSTADDR + if (setsockopt(handle, IPPROTO_IPV6, IPV6_RECVDSTADDR, (char*)&enable, sizeof(enable)) < 0) + PLOG(PL_WARN, "ProtoSocket::EnableRecvDstAddr() setsocktopt(IPV6_RECVDSTADDR) error: %s\n", GetErrorString()); +#else + if (setsockopt(handle, IPPROTO_IPV6, IPV6_RECVPKTINFO, (char*)&enable, sizeof(enable)) < 0) + PLOG(PL_WARN, "ProtoSocket::EnableRecvDstAddr() setsocktopt(IPV6_PKTINFO) error: %s\n", GetErrorString()); +#endif // if/else IPV6_RECVDSTADDR +#endif // HAVE_IPV6 + ip_recvdstaddr = true; + } +#ifdef WIN32 + // On Windows, you have to "load" the WsaRecvMsg() function pointer + if (NULL == WSARecvMsg) + { + // On first call, we need to fetch the WSARecvMsg() function pointer + GUID WSARecvMsg_GUID = WSAID_WSARECVMSG; + DWORD NumberOfBytes; + if (SOCKET_ERROR == WSAIoctl(handle, SIO_GET_EXTENSION_FUNCTION_POINTER, + &WSARecvMsg_GUID, sizeof(WSARecvMsg_GUID), + &WSARecvMsg, sizeof WSARecvMsg, + &NumberOfBytes, NULL, NULL)) + { + PLOG(PL_ERROR, "ProtoSocket::EnableRecvDstAddr() error: WSARecvMsg() not supported on this platform!\n"); + WSARecvMsg = NULL; + } + } +#endif // WIN32 +} // end ProtoSocket::EnableRecvDstAddr() -/** - * @note On WinNT 4.0 (or earlier?), we seem to need WSAJoinLeaf() for multicast to work - * Thus NT 4.0 probably doesn't support IPv6 multicast??? - * So, here we use WSAJoinLeaf() iff the OS is NT and version 4.0 or earlier. - */ -bool ProtoSocket::JoinGroup(const ProtoAddress& groupAddress, - const char* interfaceName) +#ifndef WIN32 +// Variant RecvFrom() that uses recvmsg() to get destAddr information +bool ProtoSocket::RecvFrom(char* buffer, + unsigned int& numBytes, + ProtoAddress& sourceAddr, + ProtoAddress& destAddr) { - if (!IsOpen() && !Open(0, groupAddress.GetType(), false)) + if (!IsBound()) { - PLOG(PL_ERROR, "ProtoSocket::JoinGroup() error: socket not open\n"); - return false; - } -#ifdef WIN32 - // on WinNT 4.0 (or earlier?), we seem to need WSAJoinLeaf() for multicast to work - // Thus NT 4.0 probably doesn't support IPv6 multicast??? + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() error: socket not bound\n"); + numBytes = 0; + } + if (!ip_recvdstaddr) EnableRecvDstAddr(); // should enable ahead of time to make sure you don't miss any + +#ifdef HAVE_IPV6 + struct sockaddr_storage sockAddr; +#else + struct sockaddr sockAddr; +#endif // if/else HAVE_IPV6 + socklen_t addrLen = sizeof(sockAddr); + + char cdata[64]; + struct msghdr msg; // TBD - should we bzero() our "cdata" and "msg" here??? + struct iovec iov[1]; + iov[0].iov_base = buffer; + iov[0].iov_len = numBytes; + msg.msg_name = &sockAddr; + msg.msg_namelen = addrLen; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_control = cdata; + msg.msg_controllen = 64;//sizeof(cdata); + msg.msg_flags = 0; + destAddr.Invalidate(); + int result = recvmsg(handle, &msg, 0); + if (result < 0) + { + numBytes = 0; +#ifdef WIN32 + switch (WSAGetLastError()) + { + case WSAEINTR: + case WSAEWOULDBLOCK: + //PLOG(PL_WARN, "ProtoSocket::RecvFrom() recvmsg() error: %s\n", GetErrorString()); + input_ready = false; + return true; + break; + default: + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() recvmsg() error: %s\n", GetErrorString()); + break; + } +#else + switch (errno) + { + case EINTR: + case EAGAIN: + //PLOG(PL_WARN, "ProtoSocket::Recv() recv() error: %s\n", GetErrorString()); + return true; + break; + default: + PLOG(PL_ERROR, "ProtoSocket::Recv() recv() error: %s\n", GetErrorString()); + break; + } +#endif // UNIX + return false; + } + else + { + numBytes = result; + sourceAddr.SetSockAddr(*((struct sockaddr*)&sockAddr)); + if (!sourceAddr.IsValid()) + { + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() Unsupported address type!\n"); + return false; + } + // Get destAddr info + for (struct cmsghdr* cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NXTHDR(&msg, cmptr)) + { + if (cmptr->cmsg_level == IPPROTO_IP) + { +#ifdef IP_RECVDSTADDR + if ((cmptr->cmsg_level == IPPROTO_IP) && (cmptr->cmsg_type == IP_RECVDSTADDR)) + { + destAddr.SetRawHostAddress(ProtoAddress::IPv4, (char*)CMSG_DATA(cmptr), 4); + } +#else + if (cmptr->cmsg_type == IP_PKTINFO) + { + struct in_pktinfo* pktInfo = (struct in_pktinfo*)((void*)CMSG_DATA(cmptr)); + destAddr.SetRawHostAddress(ProtoAddress::IPv4, (char*)(&pktInfo->ipi_addr), 4); + } +#endif // if/else IP_RECVDSTADDR + } +#ifdef HAVE_IPV6 + if (cmptr->cmsg_level == IPPROTO_IPV6) + { +#ifdef IPV6_RECVDSTADDR + if (cmptr->cmsg_type == IPV6_RECVDSTADDR) + { + destAddr.SetRawHostAddress(ProtoAddress::IPv6, (char*)CMSG_DATA(cmptr), 16); + } +#else + if (cmptr->cmsg_type == IPV6_PKTINFO) + { + struct in6_pktinfo* pktInfo = (struct in6_pktinfo*)((void*)CMSG_DATA(cmptr)); + destAddr.SetRawHostAddress(ProtoAddress::IPv6, (char*)(&pktInfo->ipi6_addr), 16); + } +#endif // if/else IPV6_RECVDSTADDR + } +#endif // HAVE_IPV6 + } + return true; + } +} // end ProtoSocket::RecvFrom(w/ destAddr) +#else +// WIN32 implementation +bool ProtoSocket::RecvFrom(char* buffer, + unsigned int& numBytes, + ProtoAddress& sourceAddr, + ProtoAddress& destAddr) +{ + if (!IsBound()) + { + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() error: socket not bound\n"); + numBytes = 0; + } + if (!ip_recvdstaddr) EnableRecvDstAddr(); // should enable ahead of time to make sure you don't miss any + destAddr.Invalidate(); // will be filled in if possible + + if (NULL == WSARecvMsg) + { + PLOG(PL_WARN, "ProtoSocket::RecvFrom() warning: WSARecvMsg() not supported\n"); + return RecvFrom(buffer, numBytes, sourceAddr); + } + struct sockaddr sockAddr; + socklen_t addrLen = sizeof(sockAddr); + + // Buffer to receive control data (destAddr info) + char cdata[256]; + // Buffer to receive data + WSABUF dbuf[1]; + dbuf[0].len = numBytes; + dbuf[0].buf = buffer; + + WSAMSG msg; + msg.name = &sockAddr; + msg.namelen = addrLen; + msg.lpBuffers = dbuf; + msg.dwBufferCount = 1; + msg.Control.len = 64; + msg.Control.buf = cdata; + msg.dwFlags = 0; + destAddr.Invalidate(); + + DWORD bytesRecvd; + if (SOCKET_ERROR == WSARecvMsg(handle, &msg, &bytesRecvd, NULL, NULL)) + { + numBytes = 0; + switch (WSAGetLastError()) + { + case WSAEINTR: + case WSAEWOULDBLOCK: + //PLOG(PL_WARN, "ProtoSocket::RecvFrom() WSARecvMsg() error: %s\n", GetErrorString()); + input_ready = false; + return true; + break; + default: + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() WSARecvMsg() error: %s\n", GetErrorString()); + break; + } + return false; + } + else + { + numBytes = bytesRecvd; + sourceAddr.SetSockAddr(*((struct sockaddr*)&sockAddr)); + if (!sourceAddr.IsValid()) + { + PLOG(PL_ERROR, "ProtoSocket::RecvFrom() error: Unsupported address type!\n"); + return false; + } + // Get destAddr info + for (WSACMSGHDR* cmptr = WSA_CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = WSA_CMSG_NXTHDR(&msg, cmptr)) + { + if (cmptr->cmsg_level == IPPROTO_IP) + { +#ifdef IP_RECVDSTADDR + if ((cmptr->cmsg_level == IPPROTO_IP) && (cmptr->cmsg_type == IP_RECVDSTADDR)) + { + destAddr.SetRawHostAddress(ProtoAddress::IPv4, (char*)WSA_CMSG_DATA(cmptr), 4); + } +#else + if (cmptr->cmsg_type == IP_PKTINFO) + { + struct in_pktinfo* pktInfo = (struct in_pktinfo*)((void*)WSA_CMSG_DATA(cmptr)); + destAddr.SetRawHostAddress(ProtoAddress::IPv4, (char*)(&pktInfo->ipi_addr), 4); + } +#endif // if/else IP_RECVDSTADDR + } +#ifdef HAVE_IPV6 + if (cmptr->cmsg_level == IPPROTO_IPV6) + { +#ifdef IPV6_RECVDSTADDR + if (cmptr->cmsg_type == IPV6_RECVDSTADDR) + { + destAddr.SetRawHostAddress(ProtoAddress::IPv6, (char*)WSA_CMSG_DATA(cmptr), 16); + } +#else + if (cmptr->cmsg_type == IPV6_PKTINFO) + { + struct in6_pktinfo* pktInfo = (struct in6_pktinfo*)((void*)WSA_CMSG_DATA(cmptr)); + destAddr.SetRawHostAddress(ProtoAddress::IPv6, (char*)(&pktInfo->ipi6_addr), 16); + } +#endif // if/else IPV6_RECVDSTADDR + } +#endif // HAVE_IPV6 + } + return true; + } +} // end ProtoSocket::RecvFrom(w/ destAddr) [WIN32] + +#endif // if/else !WIN32 + +#ifdef HAVE_IPV6 +#ifndef IPV6_ADD_MEMBERSHIP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif // !IPV6_ADD_MEMBERSHIP +#endif // HAVE_IPV6 + +/** + * @note On WinNT 4.0 (or earlier?), we seem to need WSAJoinLeaf() for multicast to work + * Thus NT 4.0 probably doesn't support IPv6 multicast??? + * So, here we use WSAJoinLeaf() iff the OS is NT and version 4.0 or earlier. + */ + // Full SSM support is still work in progress (only supported on Linux and Mac OSX for moment) +bool ProtoSocket::JoinGroup(const ProtoAddress& groupAddress, + const char* interfaceName, // optional interface name for group join + const ProtoAddress* sourceAddress) // optional source address for SSM support +{ + if (!IsOpen() && !Open(0, groupAddress.GetType(), false)) + { + PLOG(PL_ERROR, "ProtoSocket::JoinGroup() error: unable to open socket\n"); + return false; + } +#ifdef WIN32 + if (NULL != sourceAddress) + { + // WIN32 SSM support will be added in near future + PLOG(PL_ERROR, "ProtoSocket::JoinGroup() error: Source-specific Multicast (SSM) for WIN32 not yet supported\n"); + return false; + } + // on WinNT 4.0 (or earlier?), we seem to need WSAJoinLeaf() for multicast to work + // Thus NT 4.0 probably doesn't support IPv6 multicast??? // So, here we use WSAJoinLeaf() iff the OS is NT and version 4.0 or earlier. bool useJoinLeaf = false; OSVERSIONINFO vinfo; @@ -1501,12 +1963,18 @@ bool ProtoSocket::JoinGroup(const ProtoAddress& groupAddress, #ifdef HAVE_IPV6 if (ProtoAddress::IPv6 == groupAddress.GetType()) { + if (NULL != sourceAddress) + { + // IPv6 SSM support will be added in near future + PLOG(PL_ERROR, "ProtoSocket::JoinGroup() error: Source-specific Multicast (SSM) for IPv6 not yet supported\n"); + return false; + } if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)&groupAddress.GetSockAddrStorage())->sin6_addr)) { struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = IN6_V4MAPPED_ADDR(&(((struct sockaddr_in6*)&groupAddress.GetSockAddrStorage())->sin6_addr)); - if (interfaceName) + if (NULL != interfaceName) { ProtoAddress interfaceAddress; #if defined(WIN32) && (WINVER < 0x0500) @@ -1537,65 +2005,120 @@ bool ProtoSocket::JoinGroup(const ProtoAddress& groupAddress, { struct ipv6_mreq mreq; mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)&groupAddress.GetSockAddrStorage())->sin6_addr; - if (interfaceName) + if (NULL != interfaceName) mreq.ipv6mr_interface = GetInterfaceIndex(interfaceName); else mreq.ipv6mr_interface = 0; result = setsockopt(handle, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); - } + } } else #endif // HAVE_IPV6 { - struct ip_mreq mreq; + // IPv4 group join + if (NULL == sourceAddress) + { + // non-SSM + + struct ip_mreq mreq; #ifdef HAVE_IPV6 - mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddrStorage())->sin_addr; + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddrStorage())->sin_addr; #else - mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddr())->sin_addr; + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddr())->sin_addr; #endif // end if/else HAVE_IPV6 - if (interfaceName) - { - ProtoAddress interfaceAddress; -#if defined(WIN32) && (WINVER < 0x0500) - if (interfaceAddress.ResolveFromString(interfaceName)) + if (NULL != interfaceName) { - mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); - } - else if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress)) + ProtoAddress interfaceAddress; +#if defined(WIN32) && (WINVER < 0x0500) + if (interfaceAddress.ResolveFromString(interfaceName)) + { + mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + } + else if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress)) #else - if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress)) + if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress)) #endif // if/else defined(WIN32) && (WINVER < 0x0500) - { - mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + { + mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + } + else + { + PLOG(PL_ERROR, "ProtoSocket::JoinGroup() error: invalid interface name\n"); + return false; + } } else { - PLOG(PL_ERROR, "ProtoSocket::JoinGroup() invalid interface name\n"); - return false; + mreq.imr_interface.s_addr = INADDR_ANY; } + result = setsockopt(handle, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); } else { - mreq.imr_interface.s_addr = INADDR_ANY; + // SSM join +#ifdef _PROTOSOCKET_IGMPV3_SSM + struct ip_mreq_source mreq; +#ifdef HAVE_IPV6 + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddrStorage())->sin_addr; + mreq.imr_sourceaddr = ((struct sockaddr_in*)&sourceAddress->GetSockAddrStorage())->sin_addr; +#else + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddr())->sin_addr; + mreq.imr_sourceaddr = ((struct sockaddr_in*)&sourceAddress->GetSockAddr())->sin_addr; +#endif // if/else HAVE_IPV6 + if (NULL != interfaceName) + { + ProtoAddress interfaceAddress; + if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress)) + { + mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + } + else + { + PLOG(PL_ERROR, "ProtoSocket::JoinGroup() invalid interface name\n"); + return false; + } + } + else + { + mreq.imr_interface.s_addr = INADDR_ANY; + } + result = setsockopt(handle, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); + if (result < 0) + { + PLOG(PL_ERROR, "ProtoSocket::JoinGroup() setsockopt(IP_ADD_SOURCE_MEMBERSHIP) error: %s\n", strerror( errno )); + return false; + } + return true; +#else + PLOG(PL_ERROR, "ProtoSocket:JoinGroup() error: SSM support not included in this build\n"); + return false; +#endif } - result = setsockopt(handle, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); } if (result < 0) { - PLOG(PL_ERROR, "ProtoSocket: Error joining multicast group: %s\n", GetErrorString()); + PLOG(PL_ERROR, "ProtoSocket:JoinGroup() setsockopt(add membership) error: %s\n", GetErrorString()); return false; } return true; } // end ProtoSocket::JoinGroup() bool ProtoSocket::LeaveGroup(const ProtoAddress& groupAddress, - const char* interfaceName) + const char* interfaceName, + const ProtoAddress* sourceAddress) { if (!IsOpen()) return true; int result; #ifdef HAVE_IPV6 if (ProtoAddress::IPv6 == groupAddress.GetType()) { + // IPv6 leave + if (NULL != sourceAddress) + { + // IPv6 SSM support will be added in near future + PLOG(PL_ERROR, "ProtoSocket::LeaveGroup() error: Source-specific Multicast (SSM) for IPv6 not yet supported\n"); + return false; + } if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)&groupAddress.GetSockAddrStorage())->sin6_addr)) { struct ip_mreq mreq; @@ -1634,31 +2157,72 @@ bool ProtoSocket::LeaveGroup(const ProtoAddress& groupAddress, else #endif //HAVE_IPV6 { - struct ip_mreq mreq; + // IPv4 leave + if (NULL == sourceAddress) + { + // non-SSM + struct ip_mreq mreq; #ifdef HAVE_IPV6 - mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddrStorage())->sin_addr; + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddrStorage())->sin_addr; #else - mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddr())->sin_addr; + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddr())->sin_addr; #endif // end if/else HAVE_IPV6 - if (interfaceName) - { - ProtoAddress interfaceAddress; - if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, - interfaceAddress)) + if (NULL != interfaceName) { - mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + ProtoAddress interfaceAddress; + if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, + interfaceAddress)) + { + mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + } + else + { + PLOG(PL_ERROR, "ProtoSocket::LeaveGroup() invalid interface name\n"); + return false; + } } else { - PLOG(PL_ERROR, "ProtoSocket::LeaveGroup() invalid interface name\n"); - return false; + mreq.imr_interface.s_addr = INADDR_ANY; } + result = setsockopt(handle, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); } else { - mreq.imr_interface.s_addr = INADDR_ANY; + // SSM +#ifdef _PROTOSOCKET_IGMPV3_SSM + struct ip_mreq_source mreq; +#ifdef HAVE_IPV6 + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddrStorage())->sin_addr; + mreq.imr_sourceaddr = ((struct sockaddr_in*)&sourceAddress->GetSockAddrStorage())->sin_addr; +#else + mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress.GetSockAddr())->sin_addr; + mreq.imr_sourceaddr = ((struct sockaddr_in*)&sourceAddress->GetSockAddr())->sin_addr; +#endif // if/else HAVE_IPV6 + if (interfaceName) + { + ProtoAddress interfaceAddress; + if (GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, + interfaceAddress)) + { + mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + } + else + { + PLOG(PL_ERROR, "ProtoSocket::LeaveGroup() invalid interface name\n"); + return false; + } + } + else + { + mreq.imr_interface.s_addr = INADDR_ANY; + } + result = setsockopt(handle, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); +#else + PLOG(PL_ERROR, "ProtoSocket:LeaveGroup() error: SSM support not included in this build\n"); + return false; +#endif } - result = setsockopt(handle, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq)); } if (result < 0) { @@ -1671,40 +2235,45 @@ bool ProtoSocket::LeaveGroup(const ProtoAddress& groupAddress, } } // end ProtoSocket::LeaveGroup() +// Set MulticastTTL Function name retained for backwards compatability +// See new ProtoSocket::SetUnicastTTL bool ProtoSocket::SetTTL(unsigned char ttl) { #if defined(WIN32) && !defined(_WIN32_WCE) DWORD dwTTL = (DWORD)ttl; DWORD dwBytesXfer; - int optVal = ttl; - int optLen = sizeof(int); - // We set unicast ttl too - if (setsockopt(handle,IPPROTO_IP,IP_TTL,(char*)&optVal,optLen) == SOCKET_ERROR) - PLOG(PL_ERROR, "ProtoSocket: setsockopt(IP_TTL) error: %s\n", GetErrorString()); + int optVal = ttl; + int optLen = sizeof(int); if (WSAIoctl(handle, SIO_MULTICAST_SCOPE, &dwTTL, sizeof(dwTTL), NULL, 0, &dwBytesXfer, NULL, NULL)) #else - int result; + int result = 0; #ifdef HAVE_IPV6 if (IPv6 == domain) { - socklen_t hops = (socklen_t) ttl; - #ifdef MACOSX - result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, - &hops, sizeof(&hops)); + // v6 multicast TTL socket option must be an int + int hops = (int) ttl; - if (result == 0) - result = setsockopt(handle, IPPROTO_IPV6, IPV6_UNICAST_HOPS, - &hops, sizeof(&hops)); + if (protocol != ProtoSocket::TCP) + result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hops, sizeof(hops)); + + if (result == 0) + result = setsockopt(handle, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &hops, sizeof(hops)); #else - result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, - &hops, sizeof(hops)); - if (result == 0) - result = setsockopt(handle, IPPROTO_IPV6, IPV6_UNICAST_HOPS, - &hops, sizeof(hops)); + socklen_t hops = (socklen_t) ttl; + + if (protocol != ProtoSocket::TCP) + result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hops, sizeof(hops)); + + if (result == 0) + result = setsockopt(handle, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &hops, sizeof(hops)); #endif // MACOSX } else @@ -1716,17 +2285,17 @@ bool ProtoSocket::SetTTL(unsigned char ttl) socklen_t hops = (socklen_t)ttl; #endif // if/else _WIN32_WCE/UNIX #ifdef MACOSX - result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_TTL, - (char*)&hops, sizeof(&hops)); + if (protocol != ProtoSocket::TCP) + result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_TTL, + (char*)&hops, sizeof(hops)); + if (result == 0) result = setsockopt(handle,IPPROTO_IP, IP_TTL, - (char*)&hops, sizeof(&hops)); + (char*)&hops, sizeof(hops)); #else - result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_TTL, - (char*)&hops, sizeof(hops)); - if (result == 0) - result = setsockopt(handle,IPPROTO_IP, IP_TTL, + if (protocol != ProtoSocket::TCP) + result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&hops, sizeof(hops)); #endif } @@ -1743,6 +2312,63 @@ bool ProtoSocket::SetTTL(unsigned char ttl) } } // end ProtoSocket::SetTTL() +bool ProtoSocket::SetUnicastTTL(unsigned char ttl) +{ +#if defined(WIN32) && !defined(_WIN32_WCE) + DWORD dwTTL = (DWORD)ttl; + DWORD dwBytesXfer; + int optVal = ttl; + int optLen = sizeof(int); + if (setsockopt(handle,IPPROTO_IP,IP_TTL,(char*)&optVal,optLen) == SOCKET_ERROR) + { + PLOG(PL_ERROR, "ProtoSocket: setsockopt(IP_TTL) error: %s\n", GetErrorString()); + return false; + } +#else + int result = 0; +#ifdef HAVE_IPV6 + if (IPv6 == domain) + { + socklen_t hops = (socklen_t) ttl; + +#ifdef MACOSX + result = setsockopt(handle, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &hops, sizeof(&hops)); +#else + result = setsockopt(handle, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &hops, sizeof(hops)); +#endif // MACOSX + } + else +#endif // HAVE_IPV6 + { +#ifdef _WIN32_WCE + int hops = (int)ttl; +#else + socklen_t hops = (socklen_t)ttl; +#endif // if/else _WIN32_WCE/UNIX +#ifdef MACOSX + result = setsockopt(handle,IPPROTO_IP, IP_TTL, + (char*)&hops, sizeof(&hops)); + +#else + result = setsockopt(handle,IPPROTO_IP, IP_TTL, + (char*)&hops, sizeof(hops)); +#endif + } + if (result < 0) + { + + PLOG(PL_ERROR, "ProtoSocket: setsockopt(IP_MULTICAST_TTL) error: %s\n", GetErrorString()); + return false; + } +#endif // if/else WIN32/UNIX | _WIN32_WCE + else + { + return true; + } +} // end ProtoSocket::SetUnicastTTL() + bool ProtoSocket::SetTOS(UINT8 theTOS) { if (!IsOpen()) @@ -1766,7 +2392,7 @@ bool ProtoSocket::SetTOS(UINT8 theTOS) if (setsockopt(handle, SOL_IP, IP_TOS, (char*)&tosBits, sizeof(tosBits)) < 0) #else int tosBits = theTOS; - int result; + int result = -1; #ifdef HAVE_IPV6 if (IPv6 == domain) { @@ -1863,7 +2489,7 @@ bool ProtoSocket::SetFragmentation(bool enable) // For IPv6 sockets, we can try "IPV6_DONTFRAG" as a backup // (clear DF to allow fragmenation to occur) int df = enable ? 0 : 1; - if (setsockopt(handle, IPPROTO_IP6, IPV6_DONTFRAG, &df, sizeof(df)) < 0) + if (setsockopt(handle, IPPROTO_IPV6, IPV6_DONTFRAG, &df, sizeof(df)) < 0) { PLOG(PL_ERROR, "ProtoSocket::SetFragmentation() setsockopt(IPV6_DONTFRAG) error: %s\n", GetErrorString()); return false; @@ -1891,7 +2517,7 @@ bool ProtoSocket::SetFragmentation(bool enable) return false; } #else - PLOG(PL_ERROR, "ProtoSocket::SetFragmentation() error: IP_MTU_DISCOVER or IP_DONTFRAG socket option not supported!\n"); + PLOG(PL_WARN, "ProtoSocket::SetFragmentation() warning: IP_MTU_DISCOVER or IP_DONTFRAG socket option not supported!\n"); return false; #endif // if/else IP_MTU_DISCOVER / IP_DONTFRAG / none #endif // if/else WIN32 / UNIX @@ -2039,7 +2665,7 @@ bool ProtoSocket::SetReuse(bool state) #ifdef SO_REUSEPORT // not defined on Linux for some reason? #ifdef WIN32 - BOOL reusePort = (BOOL)reusePort; + BOOL reusePort = (BOOL)resuse; if (setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, (char*)&reusePort, sizeof(reusePort)) < 0) #else if (setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, (char*)&reuse, sizeof(reuse)) < 0) @@ -2063,50 +2689,29 @@ bool ProtoSocket::HostIsIPv6Capable() } SOCKET handle = socket(AF_INET6, SOCK_DGRAM, 0); closesocket(handle); - ProtoAddress::Win32Cleanup(); + ProtoAddress::Win32Cleanup(); if(INVALID_SOCKET == handle) return false; else return true; #else -#ifdef RES_USE_INET6 - if (0 == (_res.options & RES_INIT)) res_init(); - if (0 == (_res.options & RES_USE_INET6)) - return false; - else -#endif // RES_USE_INET6 - return true; -#endif // if/else WIN32 -} // end ProtoSocket::HostIsIPv6Capable() - -bool ProtoSocket::SetHostIPv6Capable() -{ -#ifdef WIN32 - if (!ProtoAddress::Win32Startup()) + if (IPV6_UNKNOWN == ipv6_support_status) { - PLOG(PL_ERROR, "ProtoSocket::SetHostIPv6Capable() WSAStartup() error: %s\n", GetErrorString()); - return false; + ProtoAddressList addrList; + ProtoNet::GetHostAddressList(ProtoAddress::IPv6, addrList); + if (addrList.IsEmpty()) + { + ipv6_support_status = IPV6_UNSUPPORTED; + } + else + { + ipv6_support_status = IPV6_SUPPORTED; + } } - SOCKET handle = socket(AF_INET6, SOCK_DGRAM, 0); - closesocket(handle); - ProtoAddress::Win32Cleanup(); - if(INVALID_SOCKET == handle) - return false; - else - return true; -#else -#ifdef RES_USE_INET6 - if (0 == (_res.options & RES_INIT)) - res_init(); - if (0 == (_res.options & RES_USE_INET6)) - _res.options |= RES_USE_INET6; - if (0 == (_res.options & RES_USE_INET6)) - return false; - else -#endif // RES_USE_INET6 - return true; + return (IPV6_SUPPORTED == ipv6_support_status); #endif // if/else WIN32 -} // end ProtoSocket::SetHostIPv6Capable() +} // end ProtoSocket::HostIsIPv6Capable() + #endif // HAVE_IPV6 @@ -2149,7 +2754,7 @@ bool ProtoSocket::SetRxBufferSize(unsigned int bufferSize) PLOG(PL_ERROR, "ProtoSocket::SetRxBufferSize() error: socket closed\n"); return false; } - unsigned int oldBufferSize = GetTxBufferSize(); + unsigned int oldBufferSize = GetRxBufferSize(); if (setsockopt(handle, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, sizeof(bufferSize)) < 0) { setsockopt(handle, SOL_SOCKET, SO_RCVBUF, (char*)&oldBufferSize, sizeof(oldBufferSize)); @@ -2290,7 +2895,7 @@ ProtoSocket::List::Item::Item(ProtoSocket* theSocket) } ProtoSocket::List::Iterator::Iterator(const ProtoSocket::List& theList) - : list(theList), next(theList.head) + : next(theList.head) { } @@ -2349,12 +2954,12 @@ unsigned int ProtoSocket::GetInterfaceIndices(unsigned int* indexArray, unsigned bool ProtoSocket::GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen) { - return ProtoNet::GetInterfaceName(index, buffer, buflen); + return (0 != ProtoNet::GetInterfaceName(index, buffer, buflen)); } // end ProtoSocket::GetInterfaceName(by index) bool ProtoSocket::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) { - return ProtoNet::GetInterfaceName(ifAddr, buffer, buflen); + return (0 != ProtoNet::GetInterfaceName(ifAddr, buffer, buflen)); } // end ProtoSocket::GetInterfaceName(by address) diff --git a/src/common/protoSpace.cpp b/src/common/protoSpace.cpp index a4c0508..513ceb0 100644 --- a/src/common/protoSpace.cpp +++ b/src/common/protoSpace.cpp @@ -327,7 +327,11 @@ ProtoSpace::Node* ProtoSpace::Iterator::GetNextNode(double* distance) if (inBox) { ord_tree.RemoveHead(); - if (NULL != distance) *distance = sqrt(nextOrd->GetValue()); + if (NULL != distance) + { + *distance = sqrt(nextOrd->GetValue()); + //TRACE("ProtoSpace returning node01x distance %lf\n", *distance); + } space.ReturnOrdinateToPool(*nextOrd); return nextNode; } diff --git a/src/common/protoString.cpp b/src/common/protoString.cpp new file mode 100644 index 0000000..5d5bcaf --- /dev/null +++ b/src/common/protoString.cpp @@ -0,0 +1,101 @@ +#include "protoString.h" + +ProtoTokenator::ProtoTokenator(const char* text, char delimiter, bool stripWhiteSpace) + : token(delimiter), strip(stripWhiteSpace), + text_ptr(text), prev_item(NULL) +{ + Reset(); +} + +ProtoTokenator::~ProtoTokenator() +{ + if (NULL != prev_item) + { + delete[] prev_item; + prev_item = NULL; + } +} + +void ProtoTokenator::Reset() +{ + next_ptr = text_ptr; + if (isspace(token)) + { + // advance to end of any leading white space + while (NULL != next_ptr) + { + if ('\0' == *next_ptr) + next_ptr = NULL; + else if (isspace(*next_ptr)) + next_ptr++; + else + break; + } + } +} // end ProtoTokenator::Reset() + +const char* const ProtoTokenator::GetNextItem() +{ + if (strip) + { + // Strip any leading white space + while (NULL != next_ptr) + { + if ('\0' == *next_ptr) + next_ptr = NULL; + else if (isspace(*next_ptr)) + next_ptr++; + else + break; + } + } + if (NULL == next_ptr) return NULL; + const char* ptr; + if (isspace(token)) + { + ptr = next_ptr; + while (NULL != ptr) + { + if ('\0' == *ptr) + ptr = NULL; + else if (isspace(*ptr)) + break; + else + ptr++; + } + } + else + { + ptr = strchr(next_ptr, token); + } + size_t itemLen = (NULL == ptr) ? strlen(next_ptr) : (ptr++ - next_ptr); + if (NULL != prev_item) delete[] prev_item; + if (strip) + { + const char* tailPtr = next_ptr + itemLen - 1; + while ((0 != itemLen) && (isspace(*tailPtr--))) + itemLen--; + } + if (NULL == (prev_item = new char[itemLen+1])) + { + perror("ProtoTokenator::GetNextItem() new char[] error"); + return NULL; + } + strncpy(prev_item, next_ptr, itemLen); + prev_item[itemLen] = '\0'; + if (isspace(token)) + { + // advance to end of white space + while (NULL != ptr) + { + if ('\0' == *ptr) + ptr = NULL; + else if (isspace(*ptr)) + ptr++; + else + break; + } + } + next_ptr = ptr; + return prev_item; +} // end ProtoTokenator::GetNextItem() diff --git a/src/common/protoTime.cpp b/src/common/protoTime.cpp index a95f19e..4f5ff40 100644 --- a/src/common/protoTime.cpp +++ b/src/common/protoTime.cpp @@ -25,11 +25,8 @@ ProtoTime::ProtoTime(const struct timeval& timeVal) ProtoTime::ProtoTime(double seconds) { - //TRACE("enter ProtoTime::ProtoTime(%lf seconds) ctor ...\n", seconds); tval.tv_sec = (unsigned long)seconds; tval.tv_usec = (unsigned long)(1.0e+06 * (seconds - ((double)tval.tv_sec))); - - //TRACE(" tval.tv_sec = %lu\n", tval.tv_sec); } ProtoTime::ProtoTime(unsigned long sec, unsigned long usec) @@ -50,6 +47,7 @@ void ProtoTime::operator+=(const ProtoTime& t) } } // end ProtoTime::operator+=() +/* (COMMENTED OUT BECAUSE NOT SURE IF CORRECT AND DON'T THINK IT'S USED) void ProtoTime::operator-=(double sec) { unsigned long secInt = (unsigned long)sec; @@ -77,6 +75,7 @@ void ProtoTime::operator-=(double sec) tval.tv_sec -= secInt; } } // end ProtoTime::operator-=() +*/ double ProtoTime::GetOffsetValue() const { @@ -104,12 +103,14 @@ double ProtoTime::Delta(const ProtoTime& t1, const ProtoTime& t2) // precise timing (needed for WinCE) bool proto_performance_counter_init = false; LARGE_INTEGER proto_performance_counter_frequency = {0, 0}; -#ifdef _WIN32_WCE +#ifdef USE_PERFORMANCE_COUNTER +//#ifdef _WIN32_WCE long proto_performance_counter_offset = 0; long proto_system_time_last_sec = 0; unsigned long proto_system_count_roll_sec = 0; LARGE_INTEGER proto_system_count_last = {0, 0}; -#endif // _WIN32_WCE +#endif // USE_PERFORMANCE_COUNTER +//#endif // _WIN32_WCE #endif // WIN32 diff --git a/src/common/protoTimer.cpp b/src/common/protoTimer.cpp index 2a78fe6..baa25d6 100644 --- a/src/common/protoTimer.cpp +++ b/src/common/protoTimer.cpp @@ -128,7 +128,7 @@ double ProtoTimer::GetTimeRemaining() const */ ProtoTimerMgr::ProtoTimerMgr() : update_pending(false), timeout_scheduled(false), - long_head(NULL), long_tail(NULL), short_head(NULL), short_tail(NULL) + long_head(NULL), long_tail(NULL), short_head(NULL), short_tail(NULL), invoked_timer(NULL) { pulse_timer.SetListener(this, &ProtoTimerMgr::OnPulseTimeout); pulse_timer.SetInterval(1.0); @@ -154,6 +154,7 @@ const double ProtoTimerMgr::PRECISION_TIME_THRESHOLD = 8.0; */ void ProtoTimerMgr::OnSystemTimeout() { + //TRACE("enter OnSystemTimeout() short_head interval:%lf ...\n", short_head ? short_head->GetInterval() : -1.0); timeout_scheduled = false; bool updateStatus = update_pending; update_pending = true; @@ -163,12 +164,15 @@ void ProtoTimerMgr::OnSystemTimeout() while (next) { double delta = ProtoTime::Delta(next->timeout, now); + //TRACE(" delta = %lf\n", delta); // We limit to within a microsecond of accuracy on // real-world systems to avoid overzealous attempts // at scheduling if (delta < 1.0e-06) { - if(next->DoTimeout()) + invoked_timer = next; + next->DoTimeout(); + if(invoked_timer== next) { if (next->IsActive()) { @@ -182,6 +186,8 @@ void ProtoTimerMgr::OnSystemTimeout() } } } + // else timer got deleted or otherwise rescheduled, etc + invoked_timer = NULL; next = short_head; } else @@ -255,15 +261,17 @@ void ProtoTimerMgr::ActivateTimer(ProtoTimer& theTimer) void ProtoTimerMgr::ReactivateTimer(ProtoTimer& theTimer, const ProtoTime& now) { - double timerInterval = theTimer.interval; + double timerInterval = theTimer.GetInterval(); if (PRECISION_TIME_THRESHOLD > timerInterval) { + //TRACE("incrementing timer timeout %lu:%lu by %lf\n", theTimer.timeout.sec(), theTimer.timeout.usec(), timerInterval); theTimer.timeout += timerInterval; + //TRACE(" new timeout %lu:%lu\n", theTimer.timeout.sec(), theTimer.timeout.usec()); double delta = ProtoTime::Delta(theTimer.timeout, now); - if (delta < -1.0) + if (delta < -0.01 ) { GetCurrentProtoTime(theTimer.timeout); - PLOG(PL_ERROR, "ProtoTimerMgr: Warning! real time failure interval:%lf (delta:%lf)\n", + PLOG(PL_DEBUG, "ProtoTimerMgr: Warning! real time failure interval:%lf (delta:%lf)\n", timerInterval, delta); } InsertShortTimer(theTimer); @@ -291,6 +299,9 @@ void ProtoTimerMgr::DeactivateTimer(ProtoTimer& theTimer) { if (theTimer.is_precise) { + // See if timer being removed is currently being "invoked" + if (&theTimer == invoked_timer) + invoked_timer = NULL; RemoveShortTimer(theTimer); } else @@ -384,7 +395,7 @@ void ProtoTimerMgr::InsertShortTimer(ProtoTimer& theTimer) return; } } - ASSERT(breakCount < 5000); + //ASSERT(breakCount < 5000); } if (NULL != (theTimer.prev = short_tail)) short_tail->next = &theTimer; diff --git a/src/common/protoTree.cpp b/src/common/protoTree.cpp index 5c3c3ff..cca3fc5 100644 --- a/src/common/protoTree.cpp +++ b/src/common/protoTree.cpp @@ -125,7 +125,7 @@ bool ProtoTree::PrefixIsEqual(const char* key, unsigned int keysize, const char* prefix, unsigned int prefixSize, - Endian keyEndian) const + Endian keyEndian) { if (prefixSize > keysize) return false; unsigned int fullByteCount = (prefixSize >> 3); @@ -178,7 +178,7 @@ bool ProtoTree::PrefixIsEqual(const char* key, bool ProtoTree::KeysAreEqual(const char* key1, const char* key2, unsigned int keysize, - Endian keyEndian) const + Endian keyEndian) { unsigned int fullByteCount = keysize >> 3; unsigned int remBitCount = keysize & 0x07; @@ -212,7 +212,7 @@ bool ProtoTree::KeysAreEqual(const char* key1, return true; } // end ProtoTree::KeysAreEqual() -bool ProtoTree::ItemsAreEqual(const Item& item1, const Item& item2) const +bool ProtoTree::ItemsAreEqual(const Item& item1, const Item& item2) { unsigned int keysize = item1.GetKeysize(); if (item2.GetKeysize() != keysize) return false; @@ -226,13 +226,13 @@ bool ProtoTree::ItemsAreEqual(const Item& item1, const Item& item2) const return KeysAreEqual(item1.GetKey(), item2.GetKey(), keysize, keyEndian); } // end ProtoTree::ItemsAreEqual() -bool ProtoTree::ItemIsEqual(const Item& item, const char* key, unsigned int keysize) const +bool ProtoTree::ItemIsEqual(const Item& item, const char* key, unsigned int keysize) { if (item.GetKeysize() != keysize) return false; return KeysAreEqual(item.GetKey(), key, keysize, item.GetEndian()); } // end ProtoTree::ItemIsEqual() -bool ProtoTree::Bit(const char* key, unsigned int keysize, unsigned int index, Endian keyEndian) const +bool ProtoTree::Bit(const char* key, unsigned int keysize, unsigned int index, Endian keyEndian) { if (index < keysize) { @@ -404,7 +404,7 @@ bool ProtoTree::Insert(ProtoTree::Item& item) } else { - // Subtree is empty, so make "item" the subtree root + // tree is empty, so make "item" the tree root root = &item; item.parent = (Item*)NULL; item.left = item.right = &item; @@ -605,7 +605,7 @@ ProtoTree::Item* ProtoTree::FindClosestMatch(const char* key, unsigned int keysize) const { Item* x = root; - if (x) + if (NULL != x) { Endian keyEndian = x->GetEndian(); Item* p; @@ -613,7 +613,7 @@ ProtoTree::Item* ProtoTree::FindClosestMatch(const char* key, { p = x; x = Bit(key, keysize, x->bit, keyEndian) ? x->right : x->left; - } while (x->parent == p); + } while ((x->parent == p) && (x->bit < keysize)); return x; } else @@ -656,7 +656,7 @@ ProtoTree::Item* ProtoTree::FindPrefixSubtree(const char* prefix, { // (TBD) Retest this code more with new "size-agnostic" ProtoTree implementation Item* x = root; - if (x) + if (NULL != x) { Endian keyEndian = x->GetEndian(); Item* p; @@ -740,7 +740,6 @@ void ProtoTree::Iterator::Reset(bool reverse, if (reverse) { - TRACE("resetting iterator to reversed state ...\n"); // This code is basically the same as ProtoTree::GetLastItem() if (NULL != tree->root) { @@ -893,7 +892,11 @@ ProtoTree::Item* ProtoTree::Iterator::GetPrevItem() // This iterator has been moving forward // so we need to turn it around. reversed = true; + // temporarily suspend prefix matching (if applicable) to allow turn-around + unsigned savePrefixSize = prefix_size; + prefix_size = 0; GetPrevItem(); + prefix_size = savePrefixSize; } Item* item = prev; Endian keyEndian = item->GetEndian(); @@ -1204,6 +1207,8 @@ void ProtoTree::Iterator::Update(ProtoIterable::Item* theItem, Action theAction) } case REMOVE: { + // NOTE - This doesn't work quite right for prefix iterators + // (mid-iteration removal of items can break comprehensive prefix iteration) // Save our current iterator state Item* oldPrev = prev; Item* oldNext = next; @@ -1223,12 +1228,14 @@ void ProtoTree::Iterator::Update(ProtoIterable::Item* theItem, Action theAction) if (NULL == oldNext) { // tree is now empty? - ASSERT(NULL == prefix_item); - prev = next = NULL; + //ASSERT(NULL == prefix_item); + if (NULL == prefix_item) + prev = next = NULL; + else + Reset(reversed, prefix_item->GetKey(), prefix_size); } else { - TRACE("update restoring cursor to oldNext ...\n"); SetCursor(*oldNext); } } @@ -1242,13 +1249,23 @@ void ProtoTree::Iterator::Update(ProtoIterable::Item* theItem, Action theAction) if (NULL == oldPrev) { // tree is now empty? - ASSERT(NULL == prefix_item); - prev = next = NULL; + //ASSERT(NULL == prefix_item); + if (NULL == prefix_item) + prev = next = NULL; + else + Reset(reversed, prefix_item->GetKey(), prefix_size); } else { - SetCursor(*oldPrev); - prev = oldPrev; + if (NULL == prefix_item) + { + SetCursor(*oldPrev); + prev = oldPrev; + } + else + { + Reset(reversed, prefix_item->GetKey(), prefix_size); + } } } } @@ -1260,8 +1277,11 @@ void ProtoTree::Iterator::Update(ProtoIterable::Item* theItem, Action theAction) if (NULL == oldPrev) { // tree is now empty? - ASSERT(NULL == prefix_item); - prev = next = NULL; + //ASSERT(NULL == prefix_item); + if (NULL == prefix_item) + prev = next = NULL; + else + Reset(reversed, prefix_item->GetKey(), prefix_size); } else { @@ -1273,13 +1293,23 @@ void ProtoTree::Iterator::Update(ProtoIterable::Item* theItem, Action theAction) if (NULL == oldNext) { // tree is now empty? - ASSERT(NULL == prefix_item); - prev = next = NULL; + //ASSERT(NULL == prefix_item); + if (NULL == prefix_item) + prev = next = NULL; + else + Reset(reversed, prefix_item->GetKey(), prefix_size); } else { - SetCursor(*oldNext); - next = oldNext; + if (NULL == prefix_item) + { + SetCursor(*oldNext); + next = oldNext; + } + else + { + Reset(reversed, prefix_item->GetKey(), prefix_size); + } } } } @@ -1674,9 +1704,12 @@ ProtoSortedTree::Iterator::~Iterator() void ProtoSortedTree::Iterator::Reset(bool reverse, const char* keyMin, unsigned int keysize) { + list_iterator.Reset(reverse); // put the iterator in the right direction if ((NULL != keyMin) && list_iterator.IsValid() && !tree.IsEmpty()) { - Item* match = tree.Find(keyMin, keysize); //static_cast(tree.GetItemTree().Find(keyMin, keysize)); + // refine if a "keyMin" start point was provided + // (note for "reverse" == true, "keyMin" is really a "keyMax" + Item* match = tree.Find(keyMin, keysize); if (NULL == match) { // There was no exact match to "keyMin", so look for next item (or prev if reverse == true) @@ -1696,22 +1729,9 @@ void ProtoSortedTree::Iterator::Reset(bool reverse, const char* keyMin, unsigned match = tree.item_list.GetHead(); else match = static_cast(tree.item_list.GetNextItem(*prev)); - /* - // Rewind to first matching item in linked list - Item* prevItem = match->GetPrev(); - while ((NULL != prevItem) && (!prevItem->IsInTree())) - { - match = prevItem; - prevItem = prevItem->GetPrev(); - } - */ } list_iterator.SetCursor(match); } - else - { - list_iterator.Reset(reverse); - } } // end ProtoSortedTree::Iterator::Reset() ProtoSortedTree::Iterator::TempItem::TempItem(const char* theKey, unsigned int theKeysize, ProtoTree::Endian keyEndian) diff --git a/src/common/protoVif.cpp b/src/common/protoVif.cpp index aa0adab..fbf239d 100644 --- a/src/common/protoVif.cpp +++ b/src/common/protoVif.cpp @@ -6,6 +6,7 @@ #include "protoVif.h" ProtoVif::ProtoVif() + : user_data(NULL) { vif_name[0] = '\0'; vif_name[VIF_NAME_MAX] = '\0'; // to guarantee null termination diff --git a/src/java/ProtolibJni.dll-x64 b/src/java/ProtolibJni.dll-x64 new file mode 100755 index 0000000000000000000000000000000000000000..164dd871181e3ad2a22594f34b574592f8fda599 GIT binary patch literal 177664 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 *{2H`Zym0V^gVKGv_?q(*UT=2O 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 LsA)GaE*TgAM;f#v literal 0 HcmV?d00001 diff --git a/src/java/src/mil/navy/nrl/protolib/ProtoPipe.java b/src/java/src/mil/navy/nrl/protolib/ProtoPipe.java index 7910e5a..8373256 100644 --- a/src/java/src/mil/navy/nrl/protolib/ProtoPipe.java +++ b/src/java/src/mil/navy/nrl/protolib/ProtoPipe.java @@ -39,6 +39,6 @@ public native void write(byte[] b, int off, int len) throws IOException, private native void doFinalize(); - protected void finalize() throws Throwable {doFinalize();} + protected void finalize() throws Throwable {doFinalize(); super.finalize();} } diff --git a/src/linux/androidDetour.cpp b/src/linux/androidDetour.cpp new file mode 100644 index 0000000..193b2f9 --- /dev/null +++ b/src/linux/androidDetour.cpp @@ -0,0 +1,797 @@ +#include "protoDetour.h" +#include "protoCap.h" // used for packet injection +#include "protoSocket.h" +#include "protoTree.h" // for mapping ifName -> ifIndex + +#include +#include +#include +#include // for getpid() +#include +#include +#include +#include +#include +#include +#include // for fcntl(), etc +#include // for ETH_P_IP +#include // for ARPHRD_ETHER + +/** NOTES: + * + * 1) Must run "modprobe ip_queue" before running + * programs using this class + * + */ + +// This is the "old" LinuxDetour code that uses the "ip_queue" +// iptables hook instead of the newer "ipnetfilter_queue" code + +class LinuxDetour : public ProtoDetour +{ + public: + LinuxDetour(); + ~LinuxDetour(); + + bool Open(int hookFlags = 0, + const ProtoAddress& srcFilterAddr = PROTO_ADDR_NONE, + unsigned int srcFilterMask = 0, + const ProtoAddress& dstFilterAddr = PROTO_ADDR_NONE, + unsigned int dstFilterMask = 0, + int dscpValue = -1); + void Close(); + + virtual bool Recv(char* buffer, + unsigned int& numBytes, + Direction* direction = NULL, // optionally learn INBOUND/OUTBOUND direction of pkt + ProtoAddress* srcMac = NULL, // optionally learn previous hop source MAC addr + unsigned int* ifIndex = NULL); // optionally learn which iface (INBOUND only) + + bool Allow(const char* buffer, unsigned int numBytes); + bool Drop(); + bool Inject(const char* buffer, unsigned int numBytes); + + virtual bool SetMulticastInterface(const char* interfaceName); + + private: + enum Action + { + INSTALL, + DELETE + }; + + + // used for name -> index lookup + class IfNameItem : public ProtoTree::Item + { + public: + IfNameItem(); + ~IfNameItem(); + bool Init(const char* ifName, unsigned int ifIndex); + + const char* GetIfName() const {return if_name;} + unsigned int GetIfIndex() const {return if_index;} + + // Required ProtoTree::Item overrides + const char* GetKey() const {return if_name;} + unsigned int GetKeysize() const {return if_name_size;} + + private: + char* if_name; + unsigned int if_name_size; // in bits + unsigned int if_index; + }; // end class LinuxDetour::IfNameItem + + bool SetIPTables(Action action, + int hookFlags , + const ProtoAddress& srcFilterAddr, + unsigned int srcFilterMask, + const ProtoAddress& dstFilterAddr, + unsigned int dstFilterMask, + int dscpValue); + + int pid; + int seq; + struct nlmsghdr nlh; + struct ipq_packet_msg pkt_msg; + int raw_fd; // for packet injection + int hook_flags; + ProtoAddress src_filter_addr; + unsigned int src_filter_mask; + ProtoAddress dst_filter_addr; + unsigned int dst_filter_mask; + int dscp_value; + ProtoTree if_name_tree; + +}; // end class LinuxDetour + +ProtoDetour* ProtoDetour::Create() +{ + return static_cast(new LinuxDetour()); +} // end ProtoDetour::Create(), (void*)nl_head + + +LinuxDetour::LinuxDetour() + : seq(0), raw_fd(-1), hook_flags(0), dscp_value(-1) +{ + +} + +LinuxDetour::~LinuxDetour() +{ + Close(); +} + + +LinuxDetour::IfNameItem::IfNameItem() + : if_name(NULL), if_index(0) +{ +} + +bool LinuxDetour::IfNameItem::Init(const char* ifName, unsigned int ifIndex) +{ + if (NULL != if_name) delete[] if_name; + size_t nameLen = strlen(ifName); + if (nameLen > IFNAMSIZ) nameLen = IFNAMSIZ; + nameLen++; + if (NULL == (if_name = new char[nameLen])) + { + PLOG(PL_ERROR, "LinuxDetour::IfNameItem::Init() new if_name error: %s\n", GetErrorString()); + if_name_size = 0; + return false; + } + strcpy(if_name, ifName); + if_name_size = (nameLen - 1) << 3; + if_index = ifIndex; + return true; +} // end LinuxDetour::IfNameItem::Init() + +LinuxDetour::IfNameItem::~IfNameItem() +{ + if (NULL != if_name) + { + delete[] if_name; + if_name = NULL; + } +} + +bool LinuxDetour::SetIPTables(Action action, + int hookFlags , + const ProtoAddress& srcFilterAddr, + unsigned int srcFilterMask, + const ProtoAddress& dstFilterAddr, + unsigned int dstFilterMask, + int dscpValue) +{ + // 1) IPv4 or IPv6 address family? + // (Note we now use the "mangle" table for our stuf) + const char* cmd; + if (srcFilterAddr.GetType() != dstFilterAddr.GetType()) + { + PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: inconsistent src/dst filter addr families\n"); + return false; + } + else if (ProtoAddress::IPv4 == srcFilterAddr.GetType()) + { + cmd = "/sbin/iptables -t mangle"; // IPv4 iptables + } + else if (ProtoAddress::IPv6 == srcFilterAddr.GetType()) + { + cmd = "/sbin/ip6tables -t mangle"; + } + else + { + PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: unspecified filter addr family\n"); + return false; + } + // 2) INSTALL ("-A") or DELETE ("-D") the rule + const char* mode = (INSTALL == action) ? "-A" : "-D"; + // 3) For which firewall hooks? + while (0 != hookFlags) + { + const char* target; + if (0 != (hookFlags & OUTPUT)) + { + target = "OUTPUT"; + hookFlags &= ~OUTPUT; + } + else if (0 != (hookFlags & INPUT)) + { + target = "PREROUTING"; // PREROUTING precedes INPUT in mangle table + hookFlags &= ~INPUT; + } + else if (0 != (hookFlags & FORWARD)) + { + target = "FORWARD"; + hookFlags &= ~FORWARD; + } + else + { + break; // all flags have been processed + } + + // Make and install "iptables" firewall rules + const size_t RULE_MAX = 511; + char rule[RULE_MAX+1]; + // cmd = "iptables" or "ip6tables" + // mode = "-I" or "-D" + // target = "INPUT", "OUTPUT", or "FORWARD" + sprintf(rule, "%s %s %s -j QUEUE ", cmd, mode, target); + if (0 != srcFilterMask) + { + strcat(rule, "-s "); + size_t len = strlen(rule); + if (!srcFilterAddr.GetHostString(rule+len, RULE_MAX - len)) + { + PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: bad source addr filter\n"); + return false; + } + len = strlen(rule); + sprintf(rule+len, "/%hu ", srcFilterMask); + } + if (0 != dstFilterMask) + { + strcat(rule, "-d "); + size_t len = strlen(rule); + if (!dstFilterAddr.GetHostString(rule+len, RULE_MAX - len)) + { + PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: bad destination addr filter\n"); + return false; + } + len = strlen(rule); + sprintf(rule+len, "/%hu ", dstFilterMask); + } + // Add DSCP filter command, if applicable + if (dscpValue >= 0) + { + // TBD - error check dscpValue??? + size_t len = strlen(rule); + sprintf(rule+len, "-m dscp --dscp %d ", dscpValue); + dscp_value = dscpValue; + } + + + // Add redirection so we can get stderr result + strcat(rule, " 2>&1"); + FILE* p = popen(rule, "r"); + if (NULL != p) + { + char errorMsg[256]; + fread(errorMsg, 1, 256, p); + char* ptr = strchr(errorMsg, '\n'); + if (NULL != ptr) *ptr = '\0'; + errorMsg[255] = '\0'; + if (0 != pclose(p)) + { + PLOG(PL_ERROR, "LinuxDetour::SetIPTables() \"%s\" error: %s\n", + rule, errorMsg); + return false; + } + } + else + { + PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: popen(%s): %s\n", + rule, GetErrorString()); + return false; + } + } // end while (0 != hookFlags) + return true; +} // end LinuxDetour::SetIPTables() + +bool LinuxDetour::Open(int hookFlags, + const ProtoAddress& srcFilterAddr, + unsigned int srcFilterMask, + const ProtoAddress& dstFilterAddr, + unsigned int dstFilterMask, + int dscpValue) +{ + if (IsOpen()) Close(); + + // 0) Open raw socket for optional packet injection use + //if (0 > (raw_fd = socket(domain, SOCK_RAW, IPPROTO_RAW))) + if (0 > (raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))) // or can we IPv6 on an AF_INET/HDRINCL socket? + { + PLOG(PL_ERROR, "LinuxDetour::Open() socket(IPPROTO_RAW) error: %s\n", + GetErrorString()); + return false; + } + //if (AF_INET == domain) + { + // Note: no IP_HDRINCL for IPv6 raw sockets ? + int enable = 1; + if (setsockopt(raw_fd, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable))) + { + PLOG(PL_ERROR, "LinuxDetour::Open() setsockopt(IP_HDRINCL) error: %s\n", + GetErrorString()); + Close(); + return false; + } + } + // Set to non-blocking for our purposes (TBD) Add a SetBlocking() method + if(-1 == fcntl(raw_fd, F_SETFL, fcntl(raw_fd, F_GETFL, 0) | O_NONBLOCK)) + { + PLOG(PL_ERROR, "LinuxDetour::Open() fcntl(F_SETFL(O_NONBLOCK)) error: %s\n", GetErrorString()); + Close(); + return false; + } + + // Check for "inject-only" mode and "rewire appropriately + if (0 != (hookFlags & INJECT)) + { + descriptor = raw_fd; + raw_fd = -1; + ProtoDetour::Open(); + StopInputNotification(); + return true; + } + + int domain; + if (srcFilterAddr.GetType() != dstFilterAddr.GetType()) + { + PLOG(PL_ERROR, "LinuxDetour::Open() error: inconsistent src/dst filter addr families\n"); + Close(); + return false; + } + else if (ProtoAddress::IPv4 == srcFilterAddr.GetType()) + + { + domain = AF_INET; + } + else if (ProtoAddress::IPv6 == srcFilterAddr.GetType()) + { + domain = AF_INET6; + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Open() error: unspecified filter addr family\n"); + Close(); + return false; + } + + // Make sure the "ip_queue" (or "ip6_queue") modules are loaded + // (We make this non-fatal in the case the operating system has + // these compiled-in and they are not used as loadable modules). + FILE* p; + if (AF_INET == domain) + p = popen("/sbin/modprobe ip_queue 2>&1", "r"); + else + p = popen("/sbin/modprobe ip6_queue 2>&1", "r"); + if (NULL != p) + { + // Read any error information fed back + char errorMsg[256]; + fread(errorMsg, 1, 256, p); + errorMsg[255] = '\0'; + if (0 != pclose(p)) + PLOG(PL_ERROR, "LinuxDetour::Open() warning: \"/sbin/modprobe %s\" error: %s", + (AF_INET == domain) ? "ip_queue" : "ip6_queue", errorMsg); + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Open() warning: popen(/sbin/modprobe) error: %s\n", + GetErrorString()); + } + + // Save parameters for firewall rule removal + hook_flags = hookFlags; + src_filter_addr = srcFilterAddr; + src_filter_mask = srcFilterMask; + dst_filter_addr = dstFilterAddr; + dst_filter_mask = dstFilterMask; + // Set up iptables (or ip6tables) if non-zero "hookFlags" are provided + if (0 != hookFlags) + { + if (!SetIPTables(INSTALL, hookFlags, + srcFilterAddr, srcFilterMask, + dstFilterAddr, dstFilterMask, dscpValue)) + { + PLOG(PL_ERROR, "LinuxDetour::Open() error: couldn't install firewall rules\n"); + Close(); + return false; + } + } + + // 1) Open netlink firewall socket + int protocol; + if (AF_INET == domain) + protocol = NETLINK_FIREWALL; + else + protocol = NETLINK_IP6_FW; + if ((descriptor = socket(PF_NETLINK, SOCK_RAW, protocol)) < 0) + { + PLOG(PL_ERROR, "LinuxDetour::Open() socket(NETLINK_FIREWALL) error: %s\n", + GetErrorString()); + Close(); + return false; + } + // 2) save our process id + pid = getpid(); + + // 3) Send a mode message + struct + { + struct nlmsghdr nlh; + ipq_mode_msg mode; + } req; + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req)); + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_type = IPQM_MODE; + req.nlh.nlmsg_pid = pid; + req.mode.value = IPQ_COPY_PACKET; + req.mode.range = 0; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(struct sockaddr_nl)); + addr.nl_family = AF_NETLINK; + addr.nl_pid = 0; + addr.nl_groups = 0; + + if (sendto(descriptor, &req, req.nlh.nlmsg_len, 0, + (struct sockaddr*)&addr, sizeof(struct sockaddr_nl)) < 0) + { + PLOG(PL_ERROR, "LinuxDetour::Open() sendto() error: %s\n", GetErrorString()); + Close(); + return false; + } + + if (!ProtoDetour::Open()) + { + PLOG(PL_ERROR, "LinuxDetour::Open() ProtoChannel::Open() error\n"); + Close(); + return false; + } + + // Get array of ifIndices to iterate over and create ifName->ifIndex ProtoTree + unsigned int ifIndexArray[256]; + unsigned int ifCount = ProtoSocket::GetInterfaceIndices(ifIndexArray, 256); + if (ifCount < 0) + { + PLOG(PL_ERROR, "LinuxDetour::Open() warning: unable to retrieve list of network interface indices\n"); + } + else if (0 == ifCount) + { + PLOG(PL_ERROR, "LinuxDetour::Open() warning: no network interface indices were found.\n"); + } + else if (ifCount > 256) + { + PLOG(PL_ERROR, "LinuxDetour::Open() warning: found network interfaces indices exceeding maximum count of 256.\n"); + ifCount = 256; + } + for (unsigned int i = 0; i < ifCount; i++) + { + char ifName[IFNAMSIZ+1]; + ifName[IFNAMSIZ] = '\0'; + if (ProtoSocket::GetInterfaceName(ifIndexArray[i], ifName, IFNAMSIZ)) + { + IfNameItem* item = static_cast(if_name_tree.Find(ifName, strlen(ifName) << 3)); + if (NULL != item) + { + PLOG(PL_ERROR, "LinuxDetour::Open() warning: duplicate ifName?!\n"); + } + else + { + if (NULL == (item = new IfNameItem())) + { + PLOG(PL_ERROR, "LinuxDetour::Open() new IfNameItem error: %s\n", GetErrorString()); + Close(); + return false; + } + if (!item->Init(ifName, ifIndexArray[i])) + { + PLOG(PL_ERROR, "LinuxDetour::Open() new IfNameItem error: %s\n", GetErrorString()); + delete item; + Close(); + return false; + } + if_name_tree.Insert(*item); + } + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Open() warning: failed to get ifName for ifIndex:%d\n", ifIndexArray[i]); + } + } + return true; +} // end LinuxDetour::Open() + +void LinuxDetour::Close() +{ + // Empty our ifName->ifIndex tree + ProtoTree::Item* rootItem; + while (NULL != (rootItem = if_name_tree.GetRoot())) + { + if_name_tree.Remove(*rootItem); + delete rootItem; + } + + if (raw_fd >= 0) + { + close(raw_fd); + raw_fd = -1; + } + if (0 != hook_flags) + { + SetIPTables(DELETE, hook_flags, + src_filter_addr, src_filter_mask, + dst_filter_addr, dst_filter_mask, dscp_value); + hook_flags = 0; + } + if (descriptor >= 0) + { + ProtoDetour::Close(); + close(descriptor); + descriptor = INVALID_HANDLE; + } +} // end LinuxDetour::Close() + +bool LinuxDetour::Recv(char* buffer, + unsigned int& numBytes, + Direction* direction, + ProtoAddress* srcMac, + unsigned int* ifIndex) +{ + if (NULL != srcMac) srcMac->Invalidate(); + struct iovec iov[3]; + iov[0].iov_base = &nlh; + iov[0].iov_len = sizeof(nlh); + iov[1].iov_base = &pkt_msg; + iov[1].iov_len = sizeof(pkt_msg); + iov[2].iov_base = buffer; + iov[2].iov_len = numBytes; + struct sockaddr_nl addr; + struct msghdr msg; + msg.msg_name = (void*)&addr; + msg.msg_namelen = sizeof(struct sockaddr_nl); + msg.msg_iov = iov; + msg.msg_iovlen = 3; + msg.msg_control = NULL; + msg.msg_controllen = 0; + size_t result = recvmsg(descriptor, &msg, 0); + if (result < 0) + { + numBytes = 0; + if ((EAGAIN == errno) || (EINTR == errno)) + { + return true; + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Recv() recvfrom() error: %s\n", GetErrorString()); + return false; + } + } + else if (IPQM_PACKET != nlh.nlmsg_type) + { + numBytes = 0; + return true; + } + else + { + if (NULL != direction) + { + switch (pkt_msg.hook) + { + case NF_IP_LOCAL_OUT: + //case NF_IP6_LOCAL_OUT: + *direction = OUTBOUND; // locally-generated packet + break; + default: + *direction = INBOUND; // packet from somewhere else + break; // (assume it is INBOUND) + } + } + if ((NULL != srcMac) && + (ARPHRD_ETHER == pkt_msg.hw_type) && + (ETH_ALEN == pkt_msg.hw_addrlen)) + { + srcMac->SetRawHostAddress(ProtoAddress::ETH, (char*)pkt_msg.hw_addr, ETH_ALEN); + } + numBytes = pkt_msg.data_len; + if (NULL != ifIndex) + { + // Attempt to fill in "ifIndex" + if (NF_IP_LOCAL_OUT == pkt_msg.hook) + { + // OUTBOUND packet so set "ifIndex" to pkt_msg.outdev_name + size_t nameLen = strlen(pkt_msg.outdev_name); + if (nameLen > IFNAMSIZ) nameLen = IFNAMSIZ; + IfNameItem* item = static_cast(if_name_tree.Find(pkt_msg.outdev_name, nameLen << 3)); + if (NULL != item) + { + *ifIndex = item->GetIfIndex(); + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Recv() warning: OUTBOUND packet to unknown ifName: %s\n", pkt_msg.outdev_name); + *ifIndex = 0; + } + } + else + { + // Assume INBOUND packet so set "ifIndex" to pkt_msg.indev_name + size_t nameLen = strlen(pkt_msg.indev_name); + if (nameLen > IFNAMSIZ) nameLen = IFNAMSIZ; + IfNameItem* item = static_cast(if_name_tree.FindClosestMatch(pkt_msg.indev_name, nameLen << 3)); + if (NULL != item) + { + *ifIndex = item->GetIfIndex(); + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Recv() warning: INBOUND packet from unknown ifName: %s\n", pkt_msg.indev_name); + *ifIndex = 0; + } + } + } + return true; + } +} // end LinuxDetour::Recv() + +bool LinuxDetour::Allow(const char* buffer, unsigned int numBytes) +{ + struct + { + struct nlmsghdr nlh; + struct ipq_verdict_msg verdict; + } req; + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_type = IPQM_VERDICT; + req.nlh.nlmsg_pid = pid; + req.verdict.value = NF_ACCEPT; + req.verdict.id = pkt_msg.packet_id; // filled in from last Recv() + req.verdict.data_len = numBytes; + + struct iovec iov[3]; + iov[0].iov_base = &req.nlh; + iov[0].iov_len = sizeof(struct nlmsghdr); + iov[1].iov_base = &req.verdict; + iov[1].iov_len = sizeof(struct ipq_verdict_msg); + iov[2].iov_base = (void*)buffer; + iov[2].iov_len = numBytes; + req.nlh.nlmsg_len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(struct sockaddr_nl)); + addr.nl_family = AF_NETLINK; + addr.nl_pid = 0; + addr.nl_groups = 0; + + struct msghdr msg; + msg.msg_name = (void*)&addr; + msg.msg_namelen = sizeof(struct sockaddr_nl); + msg.msg_iov = iov; + msg.msg_iovlen = 3; + msg.msg_control = NULL; + msg.msg_controllen = 0; + + if (sendmsg(descriptor, &msg, 0) < 0) + { + PLOG(PL_ERROR, "LinuxDetour::Allow() sendmsg() error: %s\n", GetErrorString()); + return false; + } + return true; +} // end LinuxDetour::Allow() + +bool LinuxDetour::Drop() +{ + struct + { + struct nlmsghdr nlh; + struct ipq_verdict_msg verdict; + } req; + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_type = IPQM_VERDICT; + req.nlh.nlmsg_pid = pid; + req.verdict.value = NF_DROP; + req.verdict.id = pkt_msg.packet_id; // filled in from last Recv() + req.verdict.data_len = 0; + + struct iovec iov[2]; + iov[0].iov_base = &req.nlh; + iov[0].iov_len = sizeof(struct nlmsghdr); + iov[1].iov_base = &req.verdict; + iov[1].iov_len = sizeof(struct ipq_verdict_msg); + req.nlh.nlmsg_len = iov[0].iov_len + iov[1].iov_len; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(struct sockaddr_nl)); + addr.nl_family = AF_NETLINK; + addr.nl_pid = 0; + addr.nl_groups = 0; + + struct msghdr msg; + msg.msg_name = (void*)&addr; + msg.msg_namelen = sizeof(struct sockaddr_nl); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + msg.msg_control = NULL; + msg.msg_controllen = 0; + + if (sendmsg(descriptor, &msg, 0) < 0) + { + PLOG(PL_ERROR, "LinuxDetour::Drop() sendmsg() error: %s\n", GetErrorString()); + return false; + } + return true; +} // end LinuxDetour::Drop() + +bool LinuxDetour::Inject(const char* buffer, unsigned int numBytes) +{ + unsigned char version = buffer[0]; + version >>= 4; + ProtoAddress dstAddr; + socklen_t addrLen; + if (4 == version) + { + dstAddr.SetRawHostAddress(ProtoAddress::IPv4, buffer+16, 4); + addrLen = sizeof(struct sockaddr); + } + else if (6 == version) + { + PLOG(PL_ERROR, "LinuxDetour::Inject() IPv6 injection not yet supported!\n"); + return false; + } + else + { + PLOG(PL_ERROR, "LinuxDetour::Inject() unknown IP version!\n"); + return false; + } + int fd = (raw_fd < 0) ? descriptor : raw_fd; + size_t result = sendto(fd, buffer, numBytes, 0, + &dstAddr.GetSockAddr(), addrLen); + if (result != numBytes) + { + PLOG(PL_ERROR, "LinuxDetour::Inject() sendto() error: %s\n", GetErrorString()); + return false; + } + return true; +} // end LinuxDetour::Inject() + +bool LinuxDetour::SetMulticastInterface(const char* interfaceName) +{ + int fd = (raw_fd < 0) ? descriptor : raw_fd; + if (fd < 0) + { + PLOG(PL_ERROR, "LinuxDetour::SetMulticastInterface() error: detour not open\n"); + return false; + } + + if (interfaceName) + { + int result; +#ifdef HAVE_IPV6 + if (ProtoAddress::IPv6 == src_filter_addr.GetType()) + { + unsigned int interfaceIndex = ProtoSocket::GetInterfaceIndex(interfaceName); + result = setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, + (char*)&interfaceIndex, sizeof(interfaceIndex)); + } + else +#endif // HAVE_IPV6 + { + struct in_addr localAddr; + ProtoAddress interfaceAddress; + if (ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress)) + { + localAddr.s_addr = htonl(interfaceAddress.IPv4GetAddress()); + } + else + { + PLOG(PL_ERROR, "LinuxDetour::SetMulticastInterface() invalid interface name\n"); + return false; + } + result = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char*)&localAddr, + sizeof(localAddr)); + } + if (result < 0) + { + PLOG(PL_ERROR, "LinuxDetour: setsockopt(IP_MULTICAST_IF) error: %s\n", + GetErrorString()); + return false; + } + } + return true; +} // end LinuxDetour::SetMulticastInterface() diff --git a/src/linux/linuxCap.cpp b/src/linux/linuxCap.cpp index 5dfc2e1..393d428 100644 --- a/src/linux/linuxCap.cpp +++ b/src/linux/linuxCap.cpp @@ -28,12 +28,9 @@ class LinuxCap : public ProtoCap bool Open(const char* interfaceName = NULL); void Close(); - bool Send(const char* buffer, unsigned int buflen); - bool Forward(char* buffer, unsigned int buflen); + bool Send(const char* buffer, unsigned int& numBytes); bool Recv(char* buffer, unsigned int& numBytes, Direction* direction = NULL); - - private: - struct sockaddr_ll iface_addr; + }; // end class LinuxCap ProtoCap* ProtoCap::Create() @@ -101,24 +98,24 @@ bool LinuxCap::Open(const char* interfaceName) GetErrorString()); - ProtoAddress ethAddr; - if (!ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, ethAddr)) + if (!ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, if_addr)) { PLOG(PL_ERROR, "LinuxCap::Open() error getting interface MAC address\n"); Close(); return false; } - // Init our interface address structure - memset((char*)&iface_addr, 0, sizeof(iface_addr)); - iface_addr.sll_protocol = htons(ETH_P_ALL); - iface_addr.sll_ifindex = ifIndex; - iface_addr.sll_family = AF_PACKET; - memcpy(iface_addr.sll_addr, ethAddr.GetRawHostAddress(), 6); - iface_addr.sll_halen = ethAddr.GetLength(); + // Init our interface address structure + struct sockaddr_ll ifaceAddr; + memset((char*)&ifaceAddr, 0, sizeof(ifaceAddr)); + ifaceAddr.sll_protocol = htons(ETH_P_ALL); + ifaceAddr.sll_ifindex = ifIndex; + ifaceAddr.sll_family = AF_PACKET; + memcpy(ifaceAddr.sll_addr, if_addr.GetRawHostAddress(), 6); + ifaceAddr.sll_halen = if_addr.GetLength(); // bind() the socket to the specified interface - if (bind(descriptor, (struct sockaddr*)&iface_addr, sizeof(iface_addr)) < 0) + if (bind(descriptor, (struct sockaddr*)&ifaceAddr, sizeof(ifaceAddr)) < 0) { PLOG(PL_ERROR, "LinuxCap::Open() bind error: %s\n", GetErrorString()); Close(); @@ -146,7 +143,7 @@ void LinuxCap::Close() } } // end LinuxCap::Close() -bool LinuxCap::Send(const char* buffer, unsigned int buflen) +bool LinuxCap::Send(const char* buffer, unsigned int& numBytes) { // Make sure packet is a type that is OK for us to send // (Some packets seem to cause a PF_PACKET socket trouble) @@ -158,38 +155,33 @@ bool LinuxCap::Send(const char* buffer, unsigned int buflen) PLOG(PL_DEBUG, "LinuxCap::Send() unsupported 802.3 frame (len = %04x)\n", type); return false; } - size_t result = write(descriptor, buffer, buflen); - if (result != buflen) - { - PLOG(PL_ERROR, "LinuxCap::Send() write() error: %s\n", GetErrorString()); - return false; - } - return true; -} // end LinuxCap::Send() - -bool LinuxCap::Forward(char* buffer, unsigned int buflen) -{ - // Make sure packet is a type that is OK for us to send - // (Some packet types seem to cause PF_PACKET socket trouble) - UINT16 type; - memcpy(&type, buffer+12, 2); - type = ntohs(type); - if (type <= 0x05dc) // assume it's 802.3 Length and ignore + for(;;) { - PLOG(PL_DEBUG, "LinuxCap::Forward() unsupported 802.3 frame (len = %04x)\n", type); - return false; + ssize_t result = write(descriptor, buffer, numBytes); + if (result < 0) + { + switch (errno) + { + case EINTR: + continue; // try again + case EWOULDBLOCK: + numBytes = 0; + case ENOBUFS: + // because this doesn't block write() + default: + PLOG(PL_WARN, "LinuxCap::Send() error: %s\n", GetErrorString()); + break; + } + return false; + } + else + { + ASSERT(result == numBytes); + break; + } } - // Change the src MAC addr to our own - // (TBD) allow caller to specify dst MAC addr ??? - memcpy(buffer+6, iface_addr.sll_addr, 6); - size_t result = write(descriptor, buffer, buflen); - if (result != buflen) - { - PLOG(PL_ERROR, "LinuxCap::Send() write() error: %s\n", GetErrorString()); - return false; - } return true; -} // end LinuxCap::Forward() +} // end LinuxCap::Send() bool LinuxCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) { @@ -200,15 +192,20 @@ bool LinuxCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) if (result < 0) { numBytes = 0; - if ((EAGAIN != errno) && (EINTR != errno)) - PLOG(PL_ERROR, "LinuxCap::Recv() error: %s\n", GetErrorString()); - else - return true; + switch (errno) + { + case EINTR: + case EAGAIN: + return true; + default: + PLOG(PL_ERROR, "LinuxCap::Recv() error: %s\n", GetErrorString()); + break; + } return false; } else { - if (direction) + if (NULL != direction) { if (pktAddr.sll_pkttype == PACKET_OUTGOING) *direction = OUTBOUND; diff --git a/src/linux/linuxDetour.cpp b/src/linux/linuxDetour.cpp index fea5968..c44a6bc 100644 --- a/src/linux/linuxDetour.cpp +++ b/src/linux/linuxDetour.cpp @@ -4,6 +4,7 @@ #include "protoCap.h" // used for packet injection #include "protoNet.h" +#include // for atoi(), getenv() #include #include // for close() #include // for NF_IP_LOCAL_OUT, etc @@ -15,6 +16,8 @@ #include // for ETH_P_IP #include // for ARPHRD_ETHER +#include // for LINUX_VERSION_CODE + /** NOTES: * * 1) This newer implementation of LinuxDetour uses netfilter_queue @@ -40,7 +43,8 @@ class LinuxDetour : public ProtoDetour const ProtoAddress& srcFilterAddr = PROTO_ADDR_NONE, unsigned int srcFilterMask = 0, const ProtoAddress& dstFilterAddr = PROTO_ADDR_NONE, - unsigned int dstFilterMask = 0); + unsigned int dstFilterMask = 0, + int dscpValue = -1); void Close(); virtual bool Recv(char* buffer, @@ -61,14 +65,18 @@ class LinuxDetour : public ProtoDetour INSTALL, DELETE }; - + + // Simple has used to randomize pid into base nfq_num + UINT32 JenkinsHash(UINT32 value); + bool SetIPTables(UINT16 nfqNum, Action action, int hookFlags , const ProtoAddress& srcFilterAddr, unsigned int srcFilterMask, const ProtoAddress& dstFilterAddr, - unsigned int dstFilterMask); + unsigned int dstFilterMask, + int dscpValue); int raw_fd; // for packet injection int hook_flags; @@ -76,6 +84,7 @@ class LinuxDetour : public ProtoDetour unsigned int src_filter_mask; ProtoAddress dst_filter_addr; unsigned int dst_filter_mask; + int dscp_value; enum {NFQ_BUFFER_SIZE = 8192}; @@ -84,7 +93,12 @@ class LinuxDetour : public ProtoDetour nfq_data* nfqData, void* userData); - + // This is initialized with a value set by the + // "PROTO_NFQ_NUM_BASE" or uses a hash of the + // process id otherwise + static bool nfq_num_init; + static UINT16 nfq_num_next; // + struct nfq_handle* nfq_handle; struct nfq_q_handle* nfq_queue; UINT16 nfq_num; // based on pid @@ -104,15 +118,25 @@ ProtoDetour* ProtoDetour::Create() return static_cast(new LinuxDetour()); } // end ProtoDetour::Create(), (void*)nl_head +bool LinuxDetour::nfq_num_init = true; +UINT16 LinuxDetour::nfq_num_next = 0; LinuxDetour::LinuxDetour() - : raw_fd(-1), hook_flags(0), + : raw_fd(-1), hook_flags(0), dscp_value(-1), nfq_handle(NULL), nfq_queue(NULL), nfq_pkt_id(0), nfq_pkt_data(NULL), nfq_pkt_len(0), nfq_direction(UNSPECIFIED), nfq_ifindex(0) { - + if (nfq_num_init) + { + char* cp = getenv("PROTO_NFQ_NUM_BASE"); + if (NULL != cp) + nfq_num_next = (UINT16)atoi(cp); + else + nfq_num_next = (UINT16)JenkinsHash(getpid()); // TBD - implement a semaphore for this? + nfq_num_init = false; + } } LinuxDetour::~LinuxDetour() @@ -120,13 +144,25 @@ LinuxDetour::~LinuxDetour() Close(); } +UINT32 LinuxDetour::JenkinsHash(UINT32 a) +{ + a = (a+0x7ed55d16) + (a<<12); + a = (a^0xc761c23c) ^ (a>>19); + a = (a+0x165667b1) + (a<<5); + a = (a+0xd3a2646c) ^ (a<<9); + a = (a+0xfd7046c5) + (a<<3); + a = (a^0xb55a4f09) ^ (a>>16); + return a; +} // end LinuxDetour::JenkinsHash() + bool LinuxDetour::SetIPTables(UINT16 nfqNum, Action action, int hookFlags , const ProtoAddress& srcFilterAddr, unsigned int srcFilterMask, const ProtoAddress& dstFilterAddr, - unsigned int dstFilterMask) + unsigned int dstFilterMask, + int dscpValue) { // 1) IPv4 or IPv6 address family? // (Note we now use the "mangle" table for our stuf) @@ -206,6 +242,15 @@ bool LinuxDetour::SetIPTables(UINT16 nfqNum, len = strlen(rule); sprintf(rule+len, "/%hu ", dstFilterMask); } + + // Add DSCP filter command, if applicable + if (dscpValue >= 0) + { + // TBD - error check dscpValue??? + size_t len = strlen(rule); + sprintf(rule+len, "-m dscp --dscp %d ", dscpValue); + } + // Add redirection so we can get stderr result strcat(rule, " 2>&1"); FILE* p = popen(rule, "r"); @@ -243,7 +288,8 @@ bool LinuxDetour::Open(int hookFlags, const ProtoAddress& srcFilterAddr, unsigned int srcFilterMask, const ProtoAddress& dstFilterAddr, - unsigned int dstFilterMask) + unsigned int dstFilterMask, + int dscpValue) { if (IsOpen()) Close(); @@ -311,7 +357,8 @@ bool LinuxDetour::Open(int hookFlags, // Make sure the "nfnetlink_queue" modules are loaded // (We make this non-fatal in the case the operating system has // these compiled-in and they are not used as loadable modules). - FILE* p = popen("/sbin/modprobe -l nfnetlink_queue 2>&1", "r"); + // FILE* p = popen("/sbin/modprobe -l nfnetlink_queue 2>&1", "r"); + FILE* p = popen("find /lib/modules/$(uname -r) -iname nfnetlink_queue.ko\\* 2>&1", "r"); if (NULL != p) { // Read any error information fed back @@ -334,7 +381,7 @@ bool LinuxDetour::Open(int hookFlags, GetErrorString()); } - nfq_num = (UINT16) getpid(); // TBD - is there a better way?? + nfq_num = nfq_num_next++; // TBD - is there a better way?? // Save parameters for firewall rule removal hook_flags = hookFlags; @@ -342,12 +389,14 @@ bool LinuxDetour::Open(int hookFlags, src_filter_mask = srcFilterMask; dst_filter_addr = dstFilterAddr; dst_filter_mask = dstFilterMask; + dscp_value = dscpValue; // Set up iptables (or ip6tables) if non-zero "hookFlags" are provided if (0 != hookFlags) { if (!SetIPTables(nfq_num, INSTALL, hookFlags, srcFilterAddr, srcFilterMask, - dstFilterAddr, dstFilterMask)) + dstFilterAddr, dstFilterMask, + dscpValue)) { PLOG(PL_ERROR, "LinuxDetour::Open() error: couldn't install firewall rules\n"); Close(); @@ -416,7 +465,7 @@ void LinuxDetour::Close() { SetIPTables(nfq_num, DELETE, hook_flags, src_filter_addr, src_filter_mask, - dst_filter_addr, dst_filter_mask); + dst_filter_addr, dst_filter_mask, dscp_value); hook_flags = 0; } if (descriptor >= 0) @@ -542,15 +591,18 @@ int LinuxDetour::NfqCallback(nfq_q_handle* nfqQueue, linuxDetour->nfq_direction = INBOUND; // Finally record packet length and cache pointer to IP packet data - // The "#ifdef aligned_be64" hack here is because the Linux "libnetfilter_queue/libnetfilter_queue.h" - // changed the prototype of the "nfq_get_payload()" function to use an "unsigned char*" pointer - // circa 2011. Hopefully this will stabilize and this hack will no longer be needed - // (or we use a more sophisticated build system to test for this function prototype disparity) -#ifdef aligned_be64 + + // A change to the nfq_get_payload() prototype seemed to kick in around Linux header files + // version 3.6? (This will probably need to be fine tuned for the right version threshold.) + +#define LINUX_VERSION_MAJOR (LINUX_VERSION_CODE/65536) +#define LINUX_VERSION_MINOR ((LINUX_VERSION_CODE - (LINUX_VERSION_MAJOR*65536)) / 256) + +#if ((LINUX_VERSION_MAJOR > 3) || ((LINUX_VERSION_MAJOR == 3) && (LINUX_VERSION_MINOR > 5))) linuxDetour->nfq_pkt_len = nfq_get_payload(nfqData, (unsigned char**)(&linuxDetour->nfq_pkt_data)); #else linuxDetour->nfq_pkt_len = nfq_get_payload(nfqData, &linuxDetour->nfq_pkt_data); -#endif // if/else aligned_be64 +#endif // return 0; } // end LinuxDetour::NfqCallback() diff --git a/src/linux/linuxNet.cpp b/src/linux/linuxNet.cpp index 343e8eb..5221369 100644 --- a/src/linux/linuxNet.cpp +++ b/src/linux/linuxNet.cpp @@ -1,6 +1,7 @@ #include "protoNet.h" #include "protoList.h" +#include "protoString.h" // for ProtoTokenator #include "protoDebug.h" #include @@ -9,12 +10,907 @@ #include #include #include // for getpid() +#include // Note that the remainder of the Linux ProtoNet stuff is // implemented in the "src/unix/unixNet.cpp" file // in the Protolib source tree and the common stuff is // in "src/common/protoNet.cpp" + +// This class wraps around a netlink socket to provide methods for +// sending/receiving netlink messages for different purposes. +class ProtoNetlink +{ + public: + ProtoNetlink(); + ~ProtoNetlink(); + + bool Open(); + void Close(); + + UINT32 GetPortId() const + {return port_id;} + + bool SendRequest(void* req, size_t len); + bool RecvResponse(UINT32 seq, struct nlmsghdr** bufferHandle, int* msgSize); + + private: + int descriptor; + UINT32 port_id; // aka netlink pid (not process id) +}; // end class ProtoNetlink + + +ProtoNetlink::ProtoNetlink() + : descriptor(-1) +{ +} + +ProtoNetlink::~ProtoNetlink() +{ + Close(); +} + +bool ProtoNetlink::Open() +{ + Close(); // in case already open + descriptor = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (descriptor < 0) + { + PLOG(PL_ERROR, "ProtoNetlink::Open() socket() error: %s\n", GetErrorString()); + return false; + } + + // Here we use "bind()" to get a unique netlink port id (pid) from the kernel + // (with localAddr.nl_pid passed into bind() set as '0', kernel assigns us one + struct sockaddr_nl localAddr; + memset(&localAddr, 0, sizeof(localAddr)); + localAddr.nl_family = AF_NETLINK; + if (0 > bind(descriptor, (struct sockaddr*) &localAddr, sizeof(localAddr))) + { + PLOG(PL_ERROR, "ProtoNetlink::Open() bind() error: %s\n", GetErrorString()); + Close(); + return false; + } + // Get socket name so we know our port number (i.e. netlink pid) + socklen_t addrLen = sizeof(localAddr); + if (getsockname(descriptor, (struct sockaddr*)&localAddr, &addrLen) < 0) + { + PLOG(PL_ERROR, "ProtoNetlink::Open() getsockname() error: %s\n", GetErrorString()); + Close(); + return false; + } + if (AF_NETLINK != localAddr.nl_family) + { + PLOG(PL_ERROR, "ProtoNetlink::Open() error: invalid socket type?!\n"); + Close(); + return false; + } + port_id = localAddr.nl_pid; + return true; +} // end ProtoNetlink::Open() + +void ProtoNetlink::Close() +{ + if (descriptor >= 0) + { + close(descriptor); + descriptor = -1; + } +} // end ProtoNetlink::Close() + +bool ProtoNetlink::SendRequest(void* req, size_t len) +{ + // init iovec structure for sendmsg() + struct iovec io; + io.iov_base = req; + io.iov_len = len; + // init netlink sockaddr (addressed to "kernel") + struct sockaddr_nl kernel; + memset(&kernel, 0, sizeof(kernel)); + kernel.nl_family = AF_NETLINK; + // init msghdr struct for sendmsg() + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_name = &kernel; + msg.msg_namelen = sizeof(kernel); + // now sendmsg (handling EINTR) + for (;;) + { + if (0 > sendmsg(descriptor, &msg, 0)) + { + if (EINTR == errno) continue; + PLOG(PL_ERROR, "ProtoNetlink::SendRequest() sendmsg() error: %s\n", GetErrorString()); + return false; + } + else + { + return true; + } + } +} // end ProtoNetlink::SendRequest() + +bool ProtoNetlink::RecvResponse(UINT32 seq, struct nlmsghdr** bufferHandle, int* msgSize) +{ + if (descriptor < 0) + { + PLOG(PL_ERROR, "ProtoNetlink::RecvResponse() error: netlink socket not open\n"); + return false; + } + if ((NULL == bufferHandle) || (NULL == msgSize)) + { + PLOG(PL_ERROR, "ProtoNetlink::RecvResponse() error: invalid parameters\n"); + return false; + } + *bufferHandle = NULL; + *msgSize = 0; + // TBD - we need to check this behavior regarding determining buffer size ... + // (The "buffer increase" strategy doesn't work so initial size must be big enough for now. + // I need to see what strategy works (e.g. multi-part receives, or re-send request, etc) + size_t bufsize = 4096/sizeof(struct nlmsghdr); // initial size (will be increased if needed + struct nlmsghdr* buffer = NULL; + for (;;) + { + if (NULL != buffer) + delete[] buffer; + if (NULL == (buffer = new struct nlmsghdr[bufsize])) + { + PLOG(PL_ERROR, "ProtoNetlink::RecvResponse() new nlmsghdr[] error: %s\n", GetErrorString()); + return false; + } + for (;;) + { + // init iovec struct + struct iovec io; + io.iov_base = buffer; + io.iov_len = bufsize*sizeof(struct nlmsghdr); + struct sockaddr_nl addr; + // init msghdr struct for recvmsg() + struct msghdr msg; + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_name = &addr; + msg.msg_namelen = sizeof(addr); + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + ssize_t result = recvmsg(descriptor, &msg, 0); + if (result < 0) + { + if (EINTR == errno) continue; + PLOG(PL_ERROR, "ProtoNetlink::RecvResponse() recvmsg() error: %s\n", GetErrorString()); + delete[] buffer; + return false; + } + else if (0 != (MSG_TRUNC & msg.msg_flags)) + { + // buffer was too, small + bufsize *= 2; + break; + } + else + { + int saveResult = result; + // We got a complete response, parse and make sure it's OK + for(struct nlmsghdr* hdr = buffer; NLMSG_OK(hdr, (unsigned int)result); hdr = NLMSG_NEXT(hdr, result)) + { + // Is it the response I'm looking for? + if ((hdr->nlmsg_pid != port_id) || (seq != hdr->nlmsg_seq)) + continue; // not for me + if (NLMSG_ERROR == hdr->nlmsg_type) + { + PLOG(PL_ERROR, "ProtoNetlink::RecvResponse() error: NLMSG_ERROR\n"); + delete[] buffer; + return false; + } + } + // A non-error response was received + *bufferHandle = buffer; + *msgSize = saveResult; + return true; + } + } + } +} // end ProtoNetlink::RecvResponse() + +#ifdef ANDROID +// Although these function would also work for Linux, we just have these defined for +// Android where the getifaddrs() function is not available. The non-Android Linux +// versions of these are implemented in the more general "unixNet.cpp" module using getifaddrs() + +unsigned int ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) +{ + // For now on Android, we'll get our list of interface indices and check each until we find + // one with a matching address and then get its name. The reason we have a different approach + // for this for Linux is to handle interface alias names, but not sure that applies to Android anyway? + unsigned int ifCount = GetInterfaceCount(); + if (0 == ifCount) + { + PLOG(PL_WARN, "ProtoNet::GetInterfaceName() warning: no interfaces?!\n"); + return 0; + } + // Then allocate a buffer of the appropriate size + unsigned int* ifIndices = new unsigned int[ifCount]; + if (NULL == ifIndices) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() new ifIndices[] error: %s\n", GetErrorString()); + return false; + } + // Now call with a buffer to get this list of indices + ifCount = GetInterfaceIndices(ifIndices, ifCount); + for (unsigned int i = 0; i < ifCount; i++) + { + unsigned int ifIndex = ifIndices[i]; + char ifName[IFNAMSIZ+1]; + ifName[IFNAMSIZ] = '\0'; + if (0 == GetInterfaceName(ifIndex, ifName, IFNAMSIZ+1)) + { + PLOG(PL_WARN, "ProtoNet::GetInterfaceName() no name for interface index %u?\n", ifIndex); + continue; + } + ProtoAddressList addrList; + if (GetInterfaceAddressList(ifName, ifAddr.GetType(), addrList)) + { + ProtoAddressList::Iterator iterator(addrList); + ProtoAddress addr; + while (iterator.GetNextAddress(addr)) + { + if (addr.HostIsEqual(ifAddr)) + { + delete[] ifIndices; + strncpy(buffer, ifName, buflen); + return strlen(ifName); + } + } + } + } + delete[] ifIndices; + return 0; +} // end ProtoNet::GetInterfaceName() + +bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, + ProtoAddress::Type addressType, + ProtoAddressList& addrList, + unsigned int* interfaceIndex) +{ + if (ProtoAddress::ETH == addressType) + { + struct ifreq req; + memset(&req, 0, sizeof(struct ifreq)); + strncpy(req.ifr_name, interfaceName, IFNAMSIZ); + int socketFd = socket(PF_INET, SOCK_DGRAM, 0); + if (socketFd < 0) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() socket() error: %s\n", GetErrorString()); + return false; + } + // Get hardware (MAC) address instead of IP address + if (ioctl(socketFd, SIOCGIFHWADDR, &req) < 0) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() ioctl(SIOCGIFHWADDR) error: %s\n", + GetErrorString()); + close(socketFd); + return false; + } + else + { + close(socketFd); + if (NULL != interfaceIndex) + *interfaceIndex = req.ifr_ifindex; + ProtoAddress ethAddr; + if (!ethAddr.SetRawHostAddress(ProtoAddress::ETH, + (const char*)&req.ifr_hwaddr.sa_data, + IFHWADDRLEN)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: invalid ETH addr?\n"); + return false; + } + if (!addrList.Insert(ethAddr)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: unable to add ETH addr to list.\n"); + return false; + } + return true; + } + } + + unsigned int ifIndex = GetInterfaceIndex(interfaceName); + if (0 == ifIndex) + { + // Perhaps "interfaceName" is an address string? + ProtoAddress ifAddr; + if (ifAddr.ConvertFromString(interfaceName)) + ifIndex = GetInterfaceIndex(ifAddr); + } + if (0 == ifIndex) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: invalid interface name\n"); + return false; + } + if (NULL != interfaceIndex) *interfaceIndex = ifIndex; + // Instantiate a netlink socket and open it + ProtoNetlink nlink; + if (!nlink.Open()) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: unable to open netlink socket\n"); + return false; + } + + // Construct request for interface addresses + struct + { + struct nlmsghdr msg; + struct ifaddrmsg ifa; + } req; + memset(&req, 0, sizeof(req)); + + // fixed sequence number for single request + UINT32 seq = 1; + + // netlink message header + req.msg.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.msg.nlmsg_type = RTM_GETADDR; + req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH ; + req.msg.nlmsg_seq = seq; // fixed sequence number for single request + req.msg.nlmsg_pid = nlink.GetPortId(); + + // route dump request for given addressType and interface index + unsigned int addrLength = 0; + unsigned char addrFamily = AF_UNSPEC; + switch (addressType) + { + case ProtoAddress::IPv4: + + addrFamily = req.ifa.ifa_family = AF_INET; + req.ifa.ifa_prefixlen = 32; + addrLength = 4; + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + + addrFamily = req.ifa.ifa_family = AF_INET6; + req.ifa.ifa_prefixlen = 128; + addrLength = 16; + break; +#endif // HAVE_IPV6 + default: + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() invalid address type!\n"); + return false; + } + req.ifa.ifa_flags = 0;//IFA_F_SECONDARY;//0;//IFA_F_PERMANENT; + req.ifa.ifa_scope = RT_SCOPE_UNIVERSE; + req.ifa.ifa_index = ifIndex; + if (!nlink.SendRequest(&req, sizeof(req))) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: unable to send netlink request\n"); + return false; + } + // Request sent, now receive response(s) until done + bool done = false; + ProtoAddressList localAddrList; // keep link/site local addresses separate and add to "addrList" at end + while (!done) + { + struct nlmsghdr* buffer; + int msgLen; + if (nlink.RecvResponse(seq, &buffer, &msgLen)) + { + struct nlmsghdr* msg = buffer; + // Parse response, adding matching addresses for "addressType" and "ifIndex" + for (; 0 != NLMSG_OK(msg, (unsigned int)msgLen); msg = NLMSG_NEXT(msg, msgLen)) + { + // only pay attention to matching netlink responses received + if ((msg->nlmsg_pid != nlink.GetPortId()) || (msg->nlmsg_seq != seq)) + continue; + switch (msg->nlmsg_type) + { + case NLMSG_NOOP: + //TRACE("recvd NLMSG_NOOP ...\n"); + break; + case NLMSG_ERROR: + { + struct nlmsgerr* errorMsg = (struct nlmsgerr*)NLMSG_DATA(msg); + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() recvd NLMSG_ERROR error seq:%d code:%d...\n", + msg->nlmsg_seq, errorMsg->error); + delete[] buffer; + return false; + } + case NLMSG_DONE: + //TRACE("recvd NLMSG_DONE ...\n"); + done = true; + break; + case RTM_NEWADDR: + { + //TRACE("recvd RTM_NEWADDR ... \n"); + struct ifaddrmsg* ifa = (struct ifaddrmsg*)NLMSG_DATA(msg); + struct rtattr* rta = IFA_RTA(ifa); + int rtaLen = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + for (; RTA_OK(rta, rtaLen); rta = RTA_NEXT(rta, rtaLen)) + { + switch (rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + { + if ((ifa->ifa_index == ifIndex) && (ifa->ifa_family == addrFamily)) + { + switch (ifa->ifa_scope) + { + case RT_SCOPE_UNIVERSE: + { + ProtoAddress theAddress; + theAddress.SetRawHostAddress(addressType, (char*)RTA_DATA(rta), addrLength); + if (theAddress.IsValid()) + { + if (!addrList.Insert(theAddress)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: couldn't add to addrList\n"); + done = true; + } + } + break; + } + case RT_SCOPE_SITE: + case RT_SCOPE_LINK: + case RT_SCOPE_HOST: + { + // Keep site-local addresses in separate list and add at end. + ProtoAddress theAddress; + theAddress.SetRawHostAddress(addressType, (char*)RTA_DATA(rta), addrLength); + if (theAddress.IsValid()) + { + if (!localAddrList.Insert(theAddress)) + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: couldn't add to localAddrList\n"); + } + break; + } + default: + // ignore other address types for now + break; + } + } + break; + } + case IFA_BROADCAST: + { + //ProtoAddress addr; + //addr.SetRawHostAddress(addrType, (char*)RTA_DATA(rta), addrLength); + //TRACE("IFA_BROADCAST: %s\n", addr.GetHostString()); + break; + } + default: + //TRACE("ProtoNet::GetInterfaceAddressList() unhandled rtattr type:%d len:%d\n", + // rta->rta_type, RTA_PAYLOAD(rta)); + break; + + } // end switch(rta_type) + } // end for(RTA_NEXT()) + break; + } + default: + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() matching reply type:%d len:%d bytes\n", + msg->nlmsg_type, msg->nlmsg_len); + break; + } // end switch(nlmsg_type) + } + ASSERT(NULL != buffer); + delete[] buffer; + } // end if (nlink.RecvResponse()) + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error:invalid netlink response\n"); + done = true; + } + } // end while(!done) + ProtoAddressList::Iterator iterator(localAddrList); + ProtoAddress localAddr; + while (iterator.GetNextAddress(localAddr)) + { + if (!addrList.Insert(localAddr)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: couldn't add localAddr to addrList\n"); + break; + } + } + nlink.Close(); + return true; +} // end ProtoNet::GetInterfaceAddressList() + + +unsigned int ProtoNet::GetInterfaceAddressMask(const char* ifaceName, const ProtoAddress& theAddr) +{ + unsigned int ifIndex = GetInterfaceIndex(ifaceName); + if (0 == ifIndex) + { + // Perhaps "interfaceName" is an address string? + ProtoAddress ifAddr; + if (ifAddr.ConvertFromString(ifaceName)) + ifIndex = GetInterfaceIndex(ifAddr); + } + if (0 == ifIndex) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: invalid interface name\n"); + return false; + } + // Instantiate a netlink socket and open it + ProtoNetlink nlink; + if (!nlink.Open()) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: unable to open netlink socket\n"); + return false; + } + // Construct request for interface addresses + struct + { + struct nlmsghdr msg; + struct ifaddrmsg ifa; + } req; + memset(&req, 0, sizeof(req)); + + // fixed sequence number for single request + UINT32 seq = 1; + + // netlink message header + req.msg.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.msg.nlmsg_type = RTM_GETADDR; + req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH ; + req.msg.nlmsg_seq = seq; // fixed sequence number for single request + req.msg.nlmsg_pid = nlink.GetPortId(); + + // route dump request for given addressType and interface index + unsigned char addrFamily = AF_UNSPEC; + switch (theAddr.GetType()) + { + case ProtoAddress::IPv4: + + addrFamily = req.ifa.ifa_family = AF_INET; + req.ifa.ifa_prefixlen = 32; + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + + addrFamily = req.ifa.ifa_family = AF_INET6; + req.ifa.ifa_prefixlen = 128; + break; +#endif // HAVE_IPV6 + default: + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() invalid address type!\n"); + return false; + } + req.ifa.ifa_flags = 0;//IFA_F_SECONDARY;//0;//IFA_F_PERMANENT; + req.ifa.ifa_scope = RT_SCOPE_UNIVERSE; + req.ifa.ifa_index = ifIndex; + if (!nlink.SendRequest(&req, sizeof(req))) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error: unable to send netlink request\n"); + return false; + } + // Request sent, now receive response(s) until done + bool done = false; + ProtoAddressList localAddrList; // keep link/site local addresses separate and add to "addrList" at end + while (!done) + { + struct nlmsghdr* buffer = NULL; + int msgLen; + if (nlink.RecvResponse(seq, &buffer, &msgLen)) + { + struct nlmsghdr* msg = buffer; + // Parse response, adding matching addresses for "addressType" and "ifIndex" + for (; 0 != NLMSG_OK(msg, (unsigned int)msgLen); msg = NLMSG_NEXT(msg, msgLen)) + { + // only pay attention to matching netlink responses received + if ((msg->nlmsg_pid != nlink.GetPortId()) || (msg->nlmsg_seq != seq)) + continue; + switch (msg->nlmsg_type) + { + case NLMSG_NOOP: + //TRACE("recvd NLMSG_NOOP ...\n"); + break; + case NLMSG_ERROR: + { + struct nlmsgerr* errorMsg = (struct nlmsgerr*)NLMSG_DATA(msg); + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() recvd NLMSG_ERROR error seq:%d code:%d...\n", + msg->nlmsg_seq, errorMsg->error); + delete[] buffer; + return false; + } + case NLMSG_DONE: + //TRACE("recvd NLMSG_DONE ...\n"); + done = true; + break; + case RTM_NEWADDR: + { + //TRACE("recvd RTM_NEWADDR ... \n"); + struct ifaddrmsg* ifa = (struct ifaddrmsg*)NLMSG_DATA(msg); + struct rtattr* rta = IFA_RTA(ifa); + int rtaLen = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + for (; RTA_OK(rta, rtaLen); rta = RTA_NEXT(rta, rtaLen)) + { + switch (rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + case IFA_BROADCAST: + { + if ((ifa->ifa_index == ifIndex) && (ifa->ifa_family == addrFamily)) + { + ProtoAddress addr; + addr.SetRawHostAddress(theAddr.GetType(), (char*)RTA_DATA(rta), theAddr.GetLength()); + if (addr.HostIsEqual(theAddr)) + { + // We have a match, return its prefix length + unsigned int prefixLen = ifa->ifa_prefixlen; + nlink.Close(); + delete[] buffer; + return prefixLen; + } + } + break; + } + default: + //TRACE("ProtoNet::GetInterfaceAddressList() unhandled rtattr type:%d len:%d\n", + // rta->rta_type, RTA_PAYLOAD(rta)); + break; + + } // end switch(rta_type) + } // end for(RTA_NEXT()) + break; + } + default: + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() matching reply type:%d len:%d bytes\n", + msg->nlmsg_type, msg->nlmsg_len); + break; + } // end switch(nlmsg_type) + } + ASSERT(NULL != buffer); + delete[] buffer; + } // end if (nlink.RecvResponse() + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() error:invalid netlink response\n"); + done = true; + } + } // end while(!done) + nlink.Close(); + return 0; +} // end ProtoNet::GetInterfaceAddressMask() + +#endif // ANDROID + +static unsigned int Readline(FILE* filePtr, char* buffer, unsigned int buflen) +{ + unsigned int index = 0; + while (index < buflen) + { + if (0 == fread(buffer+index, 1 , 1, filePtr)) + return index; + else if ('\n' == buffer[index]) + return (index + 1); + else + index++; + } + return (index+1); // return value > buflen indicates line exceeds buflen +} // end Readline() + + +bool ProtoNet::GetGroupMemberships(const char* ifaceName, ProtoAddress::Type addrType, ProtoAddressList& addrList) +{ + if (ProtoAddress::IPv4 == addrType) + { + // TBD - is there a netlink interface for this instead? + FILE* filePtr = fopen("/proc/net/igmp", "r"); + if (NULL == filePtr) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() fopen(/proc/net/igmp) error: %s\n", GetErrorString()); + return false; + } + bool seekingIface = true; + unsigned int numGroups = 0; + unsigned int skipCount = 1; // skip the first line (header line) + unsigned len; + char buffer[256]; + while (0 != (len = Readline(filePtr, buffer, 255))) + { + if (len > 255) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: /proc/net/igmp line length exceeds max expected!\n"); + fclose(filePtr); + return false; + } + if (0 != skipCount) + { + skipCount--; + continue; + } + buffer[len+1] = '\0'; // apply NULL terminator + if (seekingIface) + { + ProtoTokenator tk(buffer); + unsigned int index = 0; + const char* next; + while (NULL != (next = tk.GetNextItem())) + { + if (1 == index) + { + // Interface name is second item on line + if (0 == strcmp(ifaceName, next)) + seekingIface = false; // we found it + } + else if (3 == index) + { + unsigned int count; + int result; + if (1 != (result = sscanf(next, "%u", &count))) + { + if (seekingIface) + { + skipCount = 0; + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: invalid 'Count' %s for iface %s?\n", + next, ifaceName); + fclose(filePtr); + return false; + } + } + else if (seekingIface) + { + skipCount = count; + } + else + { + if (0 == count) + { + fclose(filePtr); + return true; + } + numGroups = count; + } + } + index++; + } + if (index < 3) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: no 'Count' for iface %s?\n", ifaceName); + fclose(filePtr); + return false; + } + } + else + { + ASSERT(0 != numGroups); + ProtoTokenator tk(buffer); + const char* groupText = tk.GetNextItem(); // first token is group addr in hex + UINT32 groupInt; // note it's already in Network Byte Order (Big Endian) + if (1 != sscanf(groupText, "%x", &groupInt)) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: invalid group addr for iface %s?\n", ifaceName); + fclose(filePtr); + return false; + } + ProtoAddress groupAddr; + groupAddr.SetRawHostAddress(ProtoAddress::IPv4, (const char*)&groupInt, 4); + if (!addrList.Insert(groupAddr)) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: unable to insert IPv4 group addr to list!\n"); + fclose(filePtr); + return false; + } + numGroups--; + if (0 == numGroups) + { + fclose(filePtr); + return true; + } + } + } + fclose(filePtr); + if (0 != numGroups) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: missing group listings for interface %s\n", ifaceName); + return false; + } + else if (seekingIface) + { + PLOG(PL_WARN, "ProtoNet::GetGroupMemberships() warning: requested interface %s not listed?!\n", ifaceName); + } + else + { + ASSERT(0); // shouldn't get here + } + return true; + } + else if (ProtoAddress::IPv6 == addrType) + { + // TBD - is there a netlink interface for this instead? + FILE* filePtr = fopen("/proc/net/igmp6", "r"); + if (NULL == filePtr) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() fopen(/proc/net/igmp) error: %s\n", GetErrorString()); + return false; + } + unsigned len; + char buffer[256]; + while (0 != (len = Readline(filePtr, buffer, 255))) + { + // Format of lines are
... + ProtoTokenator tk(buffer); + const char* next; + bool match = false; + unsigned int index = 0; + while (NULL != (next = tk.GetNextItem())) + { + if (1 == index) + { + if (0 == strcmp(next, ifaceName)) + match = true; + else + break; + } + else if (2 == index) + { + ASSERT(match); + break; // 'next' points to address string + } + index++; + } // end while (tk.GetNextItem()) + if (match) + { + if (NULL == next) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: missing IPv6 group addr %s for interface %s\n", + next, ifaceName); + fclose(filePtr); + return false; + } + if (strlen(next) < 32) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: incomplete IPv6 group addr %s for interface %s\n", + next, ifaceName); + fclose(filePtr); + return false; + } + char addr[16]; // will contain IPv6 group addr + for (int i = 0; i < 32; i += 8) + { + //char wordText[9]; + //wordText[8] = '\0'; + //strncpy(wordText, next + i, 8); + UINT32 word; + if (1 != sscanf(next + i, "%8x", &word)) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: invalid IPv6 group addr %s for interface %s\n", + next, ifaceName); + fclose(filePtr); + return false; + } + word = htonl(word); + memcpy(addr + i/2, &word, 4); + } + ProtoAddress groupAddr; + groupAddr.SetRawHostAddress(ProtoAddress::IPv6, addr, 16); + if (!addrList.Insert(groupAddr)) + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: unable to insert IPv6 group addr to list!\n"); + fclose(filePtr); + return false; + } + } + } // end while (Readline()) + return true; + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetGroupMemberships() error: invalid address typs\n"); + return false; + } +} // end ProtoNet::GetGroupMemberships() + + + class LinuxNetMonitor : public ProtoNet::Monitor { public: @@ -35,15 +931,56 @@ class LinuxNetMonitor : public ProtoNet::Monitor EventItem(); ~EventItem(); }; // end class LinuxNetMonitor::EventItem - - class EventList : public ProtoListTemplate {}; - - EventList event_list; - EventList event_pool; + class EventList : public ProtoListTemplate {}; + + class Interface : public ProtoTree::Item + { + public: + Interface(unsigned int index, const char* name); + ~Interface(); + + void SetName(const char* ifName) + {strncpy(iface_name, ifName, IFNAMSIZ);} + const char* GetName() const + {return iface_name;} + unsigned int GetIndex() const + {return iface_index;} + + private: + const char* GetKey() const + {return ((const char*)&iface_index);} + unsigned int GetKeysize() const + {return (sizeof(unsigned int) << 3);} + + char iface_name[IFNAMSIZ+1]; + unsigned int iface_name_bits; + unsigned int iface_index; + }; // end class LinuxNetMonitor::Interface + class InterfaceList : public ProtoTreeTemplate + { + public: + Interface* FindInterface(unsigned int ifIndex) + {return Find((char*)&ifIndex, sizeof(unsigned int) << 3);} + }; + + InterfaceList iface_list; + EventList event_list; + EventList event_pool; }; // end class LinuxNetMonitor +LinuxNetMonitor::Interface::Interface(unsigned int index, const char* name) + : iface_index(index) +{ + iface_name[IFNAMSIZ] = '\0'; + SetName(name); +} + +LinuxNetMonitor::Interface::~Interface() +{ +} + // This is the implementation of the ProtoNet::Monitor::Create() // static method (our Linux-specific factory) ProtoNet::Monitor* ProtoNet::Monitor::Create() @@ -74,7 +1011,7 @@ bool LinuxNetMonitor::Open() // network interface status update messages struct sockaddr_nl localAddr; localAddr.nl_family = AF_NETLINK; - localAddr.nl_pid = getpid(); + localAddr.nl_pid = 0; // system will assign us a unique netlink port id localAddr.nl_groups |= RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;// | RTMGRP_IPV6_IFINFO; if (0 > bind(descriptor, (struct sockaddr*) &localAddr, sizeof(localAddr))) { @@ -83,7 +1020,6 @@ bool LinuxNetMonitor::Open() Close(); return false; } - if (!ProtoNet::Monitor::Open()) { Close(); @@ -115,7 +1051,7 @@ bool LinuxNetMonitor::GetNextEvent(Event& theEvent) EventItem* eventItem = event_list.RemoveHead(); if (NULL == eventItem) { - // There was not any existing events in our list, so + // There were not any existing events in our list, so // get more from netlink char buffer[4096]; struct nlmsghdr* nlh = (struct nlmsghdr*)buffer; @@ -141,6 +1077,7 @@ bool LinuxNetMonitor::GetNextEvent(Event& theEvent) unsigned int msgLen = (unsigned int)result; for (;(NLMSG_OK(nlh, msgLen)) && (nlh->nlmsg_type != NLMSG_DONE); nlh = NLMSG_NEXT(nlh, msgLen)) { + eventItem = NULL; switch (nlh->nlmsg_type) { case RTM_NEWLINK: @@ -156,10 +1093,15 @@ bool LinuxNetMonitor::GetNextEvent(Event& theEvent) } struct ifinfomsg* ifi = (struct ifinfomsg*)NLMSG_DATA(nlh); if (RTM_NEWLINK == nlh->nlmsg_type) + { eventItem->SetType(Event::IFACE_UP); + } else + { eventItem->SetType(Event::IFACE_DOWN); + } eventItem->SetInterfaceIndex(ifi->ifi_index); + // TBD - look through the message RTA's (if any) for the iface name? event_list.Append(*eventItem); break; } @@ -224,6 +1166,44 @@ bool LinuxNetMonitor::GetNextEvent(Event& theEvent) //TRACE("OTHER message type %d\n", nlh->nlmsg_type); break; } // end switch(nlh->nlmsg_type) + + if (NULL != eventItem) + { + // We enqueued and event, we need to set its interface name if possible + // 1) Do we already know this interface? + unsigned int ifIndex = eventItem->GetInterfaceIndex(); + Interface* iface = iface_list.FindInterface(ifIndex); + if ((NULL == iface) || (Event::IFACE_DOWN != eventItem->GetType())) + { + // Get the interface name (if it's new or possibly changed + char ifName[IFNAMSIZ+1]; + ifName[IFNAMSIZ] = '\0'; + if (ProtoNet::GetInterfaceName(ifIndex, ifName, IFNAMSIZ)) + { + eventItem->SetInterfaceName(ifName); + if ((NULL == iface) && (Event::IFACE_DOWN != eventItem->GetType())) + { + if (NULL != (iface = new Interface(ifIndex, ifName))) + iface_list.Insert(*iface); + else + PLOG(PL_ERROR, "LinuxNetMonitor::GetNextEvent() new Interface error: %s\n", GetErrorString()); + } + } + else + { + if (NULL != iface) eventItem->SetInterfaceName(iface->GetName()); + PLOG(PL_ERROR, "LinuxNetMonitor::GetNextEvent() warning: unable to get interface name for index %d\n", ifIndex); + } + } + if ((Event::IFACE_DOWN == eventItem->GetType()) && (NULL != iface)) + { + eventItem->SetInterfaceName(iface->GetName()); + iface_list.Remove(*iface); + delete iface; + } + } + + } // end for NLMSG_OK ... eventItem = event_list.RemoveHead(); } // end if (NULL == eventItem) diff --git a/src/linux/linuxRouteMgr.cpp b/src/linux/linuxRouteMgr.cpp index 12a747e..f34e3c7 100644 --- a/src/linux/linuxRouteMgr.cpp +++ b/src/linux/linuxRouteMgr.cpp @@ -72,8 +72,8 @@ class LinuxRouteMgr : public ProtoRouteMgr bool NetlinkCheckResponse(UINT32 seq); int descriptor; - pid_t pid; - UINT32 sequence; + UINT32 port_id; // netlink port id + UINT32 sequence; // netlink request/response sequence number }; // end class LinuxRouteMgr @@ -85,28 +85,19 @@ ProtoRouteMgr* ProtoRouteMgr::Create(Type theType) { case ZEBRA: returnMgr = (ProtoRouteMgr*)(new ZebraRouteMgr); - returnMgr->Init(); break; case SYSTEM: returnMgr = (ProtoRouteMgr*)(new LinuxRouteMgr); - returnMgr->Init(); break; default: return NULL; } return returnMgr; } // end ProtoRouteMgr::Create() -void -ProtoRouteMgr::Init() -{ - savedRoutesIPv4 = NULL; - savedRoutesIPv6 = NULL; - return; -} + LinuxRouteMgr::LinuxRouteMgr() - : descriptor(-1), sequence(0) + : descriptor(-1), port_id(0), sequence(0) { - Init(); } LinuxRouteMgr::~LinuxRouteMgr() @@ -148,8 +139,27 @@ bool LinuxRouteMgr::Open(const void* /*userData*/) } else { - pid = (UINT32)getpid(); - return true; + // Here we use "bind()" to get a unique netlink port id (pid) from the kernel + // (with localAddr.nl_pid passed into bind() set as '0', kernel assigns us one + struct sockaddr_nl localAddr; + memset(&localAddr, 0, sizeof(localAddr)); + localAddr.nl_family = AF_NETLINK; + if (0 > bind(descriptor, (struct sockaddr*) &localAddr, sizeof(localAddr))) + { + PLOG(PL_ERROR, "ProtoNetlink::Open() bind() error: %s\n", GetErrorString()); + Close(); + return false; + } + // Get socket name so we know our port number (i.e. netlink pid) + socklen_t addrLen = sizeof(localAddr); + if (getsockname(descriptor, (struct sockaddr*)&localAddr, &addrLen) < 0) + { + PLOG(PL_ERROR, "ProtoNetlink::Open() getsockname() error: %s\n", GetErrorString()); + Close(); + return false; + } + port_id = localAddr.nl_pid; + return true; } } // end LinuxRouteMgr::Open() @@ -203,7 +213,7 @@ bool LinuxRouteMgr::NetlinkCheckResponse(UINT32 seq) // Justin Below is a semi-hack to get SMF to function correctly in core namespaces. // If when the msg->nlmsg_pid gets fixed this ifdef can be removed #ifndef CORE_NAMESPACES - if ((msg->nlmsg_pid == (UINT32)pid) && (msg->nlmsg_seq == seq)) + if ((msg->nlmsg_pid == port_id) && (msg->nlmsg_seq == seq)) #else //DMSG(0,"J Namspaces comment in NetlinkCheckResponse\n"); if (msg->nlmsg_seq == seq) @@ -217,7 +227,7 @@ bool LinuxRouteMgr::NetlinkCheckResponse(UINT32 seq) if (0 != errorMsg->error) { PLOG(PL_ERROR, "LinuxRouteMgr::NetlinkCheckResponse() recvd NLMSG_ERROR " - "error seq:%d code:%d...\n", msg->nlmsg_seq, errorMsg->error); + "error seq:%d code:%d...\n", msg->nlmsg_seq, errorMsg->error); return false; } else @@ -228,7 +238,7 @@ bool LinuxRouteMgr::NetlinkCheckResponse(UINT32 seq) } default: PLOG(PL_ERROR, "LinuxRouteMgr::NetlinkCheckResponse() recvd unexpected " - "matching message type:%d\n", msg->nlmsg_type); + "matching message type:%d\n", msg->nlmsg_type); // Assume success ??? return true; break; @@ -272,7 +282,7 @@ bool LinuxRouteMgr::SetRoute(const ProtoAddress& dst, req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK; UINT32 seq = sequence++; req.msg.nlmsg_seq = seq; - req.msg.nlmsg_pid = pid; + req.msg.nlmsg_pid = port_id; // route add request switch (dst.GetType()) @@ -448,7 +458,7 @@ bool LinuxRouteMgr::DeleteRoute(const ProtoAddress& dst, req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_MATCH; UINT32 seq = sequence++; req.msg.nlmsg_seq = seq; - req.msg.nlmsg_pid = pid; + req.msg.nlmsg_pid = port_id; // route delete request switch (dst.GetType()) @@ -598,7 +608,7 @@ bool LinuxRouteMgr::GetRoute(const ProtoAddress& dst, req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH; UINT32 seq = sequence++; req.msg.nlmsg_seq = seq; - req.msg.nlmsg_pid = pid; + req.msg.nlmsg_pid = port_id; // route dump request switch (dst.GetType()) @@ -696,7 +706,8 @@ bool LinuxRouteMgr::GetRoute(const ProtoAddress& dst, else if (msgLen == bufferSize) { bufferSize *= 2; - truncated = true; + truncated = true; + // TBD - should use struct msghdr.msg_flags MSG_TRUNC flag to detect this isntead } struct nlmsghdr* msg = (struct nlmsghdr*)buffer; @@ -706,7 +717,7 @@ bool LinuxRouteMgr::GetRoute(const ProtoAddress& dst, //Justin Below is a semi-hack to get SMF to function correctly in core namespaces. //If when the msg->nlmsg_pid gets fixed this ifdef can be removed #ifndef CORE_NAMESPACES - if ((msg->nlmsg_pid == (UINT32)pid) && (msg->nlmsg_seq == seq)) + if ((msg->nlmsg_pid == port_id) && (msg->nlmsg_seq == seq)) #else if (msg->nlmsg_seq == seq) #endif // if/else !CORE_NAMESPACES @@ -893,7 +904,7 @@ bool LinuxRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_MULTI; UINT32 seq = sequence++; req.msg.nlmsg_seq = seq; - req.msg.nlmsg_pid = pid; + req.msg.nlmsg_pid = port_id; // route dump request switch (addrType) @@ -961,7 +972,7 @@ bool LinuxRouteMgr::GetAllRoutes(ProtoAddress::Type addrType, // Justin Below is a semi-hack to get SMF to function correctly in core namespaces. // If when the msg->nlmsg_pid gets fixed this #ifdef can be removed #ifndef CORE_NAMESPACES - if ((msg->nlmsg_pid == (UINT32)pid) && (msg->nlmsg_seq == seq)) + if ((msg->nlmsg_pid == port_id) && (msg->nlmsg_seq == seq)) #else if (msg->nlmsg_seq == seq) #endif // if/else !CORE_NAMESPACES @@ -1157,7 +1168,7 @@ bool LinuxRouteMgr::GetInterfaceAddressList(unsigned int ifIndex, req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH ; UINT32 seq = sequence++; req.msg.nlmsg_seq = seq; - req.msg.nlmsg_pid = pid; + req.msg.nlmsg_pid = port_id; // route dump request unsigned int addrLength = 0; @@ -1202,8 +1213,8 @@ bool LinuxRouteMgr::GetInterfaceAddressList(unsigned int ifIndex, while(!done) { // DMSG(0,"Justin in while comment in getinterfaceaddress is ipv4\n"); - char buffer[1024]; - int msgLen = recv(descriptor, buffer, 1024, 0); + char buffer[4096]; + int msgLen = recv(descriptor, buffer, 4096, 0); if (msgLen < 0) { PLOG(PL_ERROR, "LinuxRouteMgr::GetInterfaceAddressList() recv() error: %s\n", strerror(errno)); @@ -1215,7 +1226,7 @@ bool LinuxRouteMgr::GetInterfaceAddressList(unsigned int ifIndex, //Justin Below is a semi-hack to get SMF to function correctly in core namespaces. //If when the msg->nlmsg_pid gets fixed this #if can be removed #ifndef CORE_NAMESPACES - if ((msg->nlmsg_pid == (UINT32)pid) && (msg->nlmsg_seq == seq)) + if ((msg->nlmsg_pid == port_id) && (msg->nlmsg_seq == seq)) #else if (msg->nlmsg_seq == seq) #endif // if/else !CORE_NAMESPACES diff --git a/src/manet/manetGraph.cpp b/src/manet/manetGraph.cpp index e99ca1b..ffc77d5 100644 --- a/src/manet/manetGraph.cpp +++ b/src/manet/manetGraph.cpp @@ -396,7 +396,7 @@ void NetGraph::Interface::PriorityQueue::Adjust(Interface& iface, const Cost& ne InsertItem(*item); } // end ProtoGraph::Vertice::PriorityQueue::Adjust() -bool NetGraph::Interface::PriorityQueue::AdjustDownward(Interface& iface, const Cost& newCost) +bool NetGraph::Interface::PriorityQueue::AdjustDownward(Interface& iface, const Cost& newCost, const Interface* newPrevHop) { Item* item = static_cast(GetQueueState(iface)); ASSERT(item != NULL); @@ -406,13 +406,32 @@ bool NetGraph::Interface::PriorityQueue::AdjustDownward(Interface& iface, const item->SetCost(newCost); InsertItem(*item); return true; - } + } else { return false; } } // end ProtoGraph::Vertice::PriorityQueue::AdjustDownward() +bool NetGraph::Interface::PriorityQueue::AdjustUpward(Interface& iface, const Cost& newCost) +{ + Item* item = static_cast(GetQueueState(iface)); + ASSERT(item != NULL); + if (newCost > item->GetCost()) + { + //bunny TBD this is wrong! we don't know for certain that this is the correct "path" + //we need to search for alternative shorter paths (upstream only!) and update the cost and route table information + RemoveItem(*item); + item->SetCost(newCost); + InsertItem(*item); + return true; + } + else + { + return false; + } +} // end ProtoGraph::Vertice::PriorityQueue::AdjustUpward() + bool NetGraph::Interface::PriorityQueue::Append(Interface& iface) { Item* item = item_factory.GetItem(); @@ -1003,7 +1022,7 @@ NetGraph::DijkstraTraversal::DijkstraTraversal(NetGraph& theGraph, start_iface((NULL != startIface) ? startIface : startNode.GetDefaultInterface()), queue_pending(static_cast(*this)), queue_visited(static_cast(*this)), - trans_iface(NULL), current_level(0), dijkstra_completed(false), in_update(false), traverse_nodes(false) + trans_iface(NULL), current_level(0), dijkstra_completed(false), in_update(false), traverse_nodes(false), reset_required(false) { // ASSERT(&start_iface->GetNode() == &startNode); } @@ -1061,10 +1080,12 @@ bool NetGraph::DijkstraTraversal::Reset(Interface* startIface) return false; } } + //TRACE("dijkstra_completed = false bunny1\n"); dijkstra_completed = false; } else { + //TRACE("dijkstra_completed = true bunny2\n"); dijkstra_completed = true; } return true; @@ -1081,6 +1102,7 @@ NetGraph::Interface* NetGraph::DijkstraTraversal::GetNextInterface() queue_pending.TransferInterface(*currentIface, queue_visited); const Cost* currentCost = queue_visited.GetCost(*currentIface); ASSERT(NULL != currentCost); + //TRACE(" %s being updated with cost %f\n",currentIface->GetAddress().GetHostString(),((ManetGraph::Cost*)currentCost)->GetValue()); AdjacencyIterator linkIterator(*currentIface); Link* nextLink; while ((nextLink = linkIterator.GetNextAdjacencyLink())) @@ -1099,32 +1121,120 @@ NetGraph::Interface* NetGraph::DijkstraTraversal::GetNextInterface() do { bool saveState = true; - if (nextDst->IsInQueue(queue_pending)) + if(!in_update) { - // We have found another path to this pending iface. - // If it is a shorter path update the cost to lower value - if (!queue_pending.AdjustDownward(*nextDst, nextCost)) - saveState = false; - } - else if (!nextDst->IsInQueue(queue_visited)) - { - // This is the first path found to this iface, - // so enqueue it in our "queue_pending" queue - if (!queue_pending.Insert(*nextDst, nextCost)) + if (nextDst->IsInQueue(queue_pending)) { - PLOG(PL_ERROR, "NetGraph::DijkstraTraversal::GetNextInterface() error: couldn't enqueue iface\n"); - return NULL; + // We have found another path to this pending iface. + // If it is a shorter path update the cost to lower value + if (!queue_pending.AdjustDownward(*nextDst, nextCost, currentIface)) + saveState = false; } - } - else if (in_update) + else if (!nextDst->IsInQueue(queue_visited)) + { + // This is the first path found to this iface, + // so enqueue it in our "queue_pending" queue + if (!queue_pending.Insert(*nextDst, nextCost)) + { + PLOG(PL_ERROR, "NetGraph::DijkstraTraversal::GetNextInterface() error: couldn't enqueue iface\n"); + return NULL; + } + } + else + { + saveState = false; + } + } + else //in_update { + //visited queue nodes may not have been updated yet...we need a different way to check. + Interface* prevHopIface; + bool isInVisited = false; + bool isNewIface = false; + Interface::PriorityQueue* queuePtr = &queue_pending; + if(nextDst->IsInQueue(queue_visited)) + { + isInVisited = true; + queuePtr = &queue_visited; + } else if (!nextDst->IsInQueue(queue_pending)) { + //new interface so just go ahead and add it + isNewIface = true; + } + + if(isNewIface) + { + if (!queue_pending.Insert(*nextDst, nextCost)) + { + PLOG(PL_ERROR, "NetGraph::DijkstraTraversal::GetNextInterface() error: couldn't enqueue iface\n"); + return NULL; + } + + } + else if (queuePtr->AdjustDownward(*nextDst, nextCost, currentIface)) //try and move it down + { + //TRACE(" %s moved downward\n",nextDst->GetAddress().GetHostString()); + if(isInVisited) + { + //TRACE(" was in visited queue adding it to the pending queue so children can be updated\n"); + queue_visited.TransferInterface(*nextDst, queue_pending); //it moved down so put it in the pending queue to updated it's children... + } + } + else if(NULL != (prevHopIface = queuePtr->GetPrevHop(*nextDst))) + { + //need to check if we were the previous hop + Interface* bunnyTemp = queuePtr->GetNextHop(*currentIface); + //if(bunnyTemp) + // TRACE("bunny next hop current is %s\n",bunnyTemp->GetAddress().GetHostString()); + //TRACE("checking next nbr from current iface %s \n",currentIface->GetAddress().GetHostString()); + //TRACE("prev hop %s ",prevHopIface->GetAddress().GetHostString()); + //TRACE("of dst %s iface\n",nextDst->GetAddress().GetHostString()); + bool linkWasUsed = false; + if(traverse_nodes) + { + if(currentIface->GetNode().Contains(*prevHopIface)) + linkWasUsed = true; + } + else + { + if(currentIface == prevHopIface) + linkWasUsed = true; + } + if(linkWasUsed) + { + //TRACE("link was used was it longer? if yes then\n"); + if(queuePtr->AdjustUpward(*nextDst, nextCost)) + { + //TRACE("link we are bailing out and setting the required reset bool true!\n"); + //we need to verify that this is the currently shortest path and if not fix it! + //New function required or expand AdjustUpward to take care of this. + + //using class variable to tell update to do a full reset... + //Fixing AdjustingUpward and putting the some queue update stuff will then we can remove this + reset_required = true; + saveState = false; + return NULL; + //queue_visited.TransferInterface(*nextDst, queue_pending); + //we need to check neighbors to see if they used this path and update them if they did. + } + else + { + //TRACE("link was same length as before not saving state\n"); + saveState = false; + } + } else { + //TRACE("link wasn't used not saving\n"); + saveState = false; + } + } else { + //TRACE("no prev hop\n"); + //no previous hop this can happen if currentIface is on the root node + saveState = false; + } // I _think_ this is currently broken? It seems we would need to shuffle nodes // from "queue_visited" to "queue_pending" here to work properly? ... Note that // it depends heavily on "Update()" being called with a proper "startIface" // This checks for shorter path to a "visited" iface // (must be in "Update()" mode! (i.e. "in_update == true") - if (!queue_visited.AdjustDownward(*nextDst, nextCost)) - saveState = false; /*{ // Update the routing tree state if (currentIface == start_iface) @@ -1134,18 +1244,45 @@ NetGraph::Interface* NetGraph::DijkstraTraversal::GetNextInterface() } saveState = false;*/ } - else - { - saveState = false; - } // Save our routing tree state as we go if(saveState) { //if (currentIface == start_iface) if(start_iface->GetNode().Contains(*currentIface)) + { + //leaving debug statements here but commented out for the time being JD + if(nextDst==NULL) + { + //TRACE("nextDst is null!\n"); + } + else + { + //TRACE("nextDst is %s\n",nextDst->GetAddress().GetHostString()); + } + if(nextLink==NULL) + { + //TRACE("nextLink is null!\n"); + } else { + //TRACE("nextLink src is %s dst is ",nextLink->GetSrc()->GetAddress().GetHostString()); + //TRACE("%s\n",nextLink->GetDst()->GetAddress().GetHostString()); + } + if(currentIface==NULL) + { + //TRACE("currentIface is null!\n"); + } else { + //TRACE("currentIface is %s\n",currentIface->GetAddress().GetHostString()); + } queue_pending.SetRouteInfo(*nextDst, nextLink, currentIface); + } else - queue_pending.SetRouteInfo(*nextDst, queue_visited.GetNextHopLink(*currentIface), currentIface); + { + if(queue_visited.Contains(*currentIface)) + { + queue_pending.SetRouteInfo(*nextDst, queue_visited.GetNextHopLink(*currentIface), currentIface); + } else { + queue_pending.SetRouteInfo(*nextDst, queue_pending.GetNextHopLink(*currentIface), currentIface); + } + } } if(traverse_nodes) nextDst = ifaceIterator.GetNextInterface(); @@ -1156,25 +1293,120 @@ NetGraph::Interface* NetGraph::DijkstraTraversal::GetNextInterface() } else { + //TRACE("dijkstra_completed = true bunny2\n"); dijkstra_completed = true; } return currentIface; } // end NetGraph::DijkstraTraversal::GetNextInterface() +bool NetGraph::DijkstraTraversal::PrevHopIsValid(Interface& currentIface) +{ + ASSERT(start_iface != NULL); + if(!traverse_nodes) + { + if(¤tIface == start_iface) + return true; + } else { + if(currentIface.GetNode().Contains(*start_iface)) + return true; + } + const Cost* currCostPtr = GetCost(currentIface); + if(currCostPtr == NULL) + return false; + Interface* prevHopIface = NULL; + if(NULL != (prevHopIface = queue_visited.GetPrevHop(currentIface))) + { + const Cost* prevCostPtr = GetCost(*prevHopIface); + if(prevCostPtr == NULL) + return false; + + if(!traverse_nodes) + { + Link* linkPtr = prevHopIface->GetLinkTo(currentIface); + if(linkPtr != NULL) + { + if (AllowLink(*prevHopIface, *linkPtr)) + { + Cost& sumCost = AccessCostTemp(); + sumCost = linkPtr->GetCost(); + sumCost += *prevCostPtr; + if(sumCost > *currCostPtr) + { + //cost has increased and is no longer valid + return false; + } else if(sumCost < *currCostPtr) { + //we could return false here but the link has only gotten "better" so should still be a valid prev hop just with the wrong value + return true; + } else { + return true; + } + } + } else { + //no link exists from my previous hop returning null + return false; + } + } else { + ManetNode::NeighborIterator nbIt(prevHopIface->GetNode()); + Link* linkPtr = NULL; + while(NULL != (linkPtr = nbIt.GetNextNeighborLink())) + { + Interface* srcPtr = linkPtr->GetSrc(); + ASSERT(NULL != srcPtr); + if (!AllowLink(*srcPtr, *linkPtr)) continue; + Interface* dstPtr = linkPtr->GetDst(); + if(dstPtr!=NULL) + { + if(currentIface.GetNode().Contains(*dstPtr)) + { + Cost& sumCost = AccessCostTemp(); + sumCost = linkPtr->GetCost(); + sumCost += *prevCostPtr; + if(sumCost <= *currCostPtr) + return true; + if (sumCost == *currCostPtr) + return true; + } + } + } + return false; + } + + } else { + //previous hop is null returning false; + return false; + } + return true; +} //end NetGraph::Interface* NetGraph::DijkstraTraversal::PreviousHopIsValid() + void NetGraph::DijkstraTraversal::Update(Interface& startIface) { - in_update = true; - // (TBD) fix this for new Dijkstra approach + //TRACE(" calling update on interface %s\n",startIface.GetAddress().GetHostString()); if (!dijkstra_completed) { + //TRACE("bunny doing a full reset...\n"); + Reset(); // Complete dijkstra traversal while (NULL != GetNextInterface()); + return; + } + if(!PrevHopIsValid(startIface)) + { + //we might be able to find and update the "star cluster" for the next best paths but it might not be worth it + //TRACE(" previous hop isn't valid....doing a full reset...\n"); + Reset(); + // Complete dijkstra traversal + while (NULL != GetNextInterface()); + return; } - ASSERT(queue_pending.IsEmpty()); - - if (startIface.IsInQueue(queue_visited)) + + //set up the initial state for walking through the graph stub. + in_update = true; + Interface* origStartIfacePtr = start_iface; + start_iface=&startIface; + if(startIface.IsInQueue(queue_visited)) { + //TRACE(" Interface was in the visited queue so setting up pending queue to update dijkstra state\n"); if(traverse_nodes) { Node::InterfaceIterator ifaceIterator(startIface.GetNode()); @@ -1189,21 +1421,99 @@ void NetGraph::DijkstraTraversal::Update(Interface& startIface) } else { - // Need to reset entire Dijkstra + // Need to reset entire Dijkstra (as a new interface has been added and we don't know from where) // (TBD - maybe something smarter can be done here) - Reset(&startIface); + // could be solved in a similar way to the star cluster as a single isolated branch/leaf is a subset of that problem + //TRACE(" Interface to update isn't in the visited queue doing a full reset\n"); + reset_required = true; } - - while (NULL != GetNextInterface()); + //walk through and update the graph stub + if(!reset_required) + { + //TRACE(" Going to walk through the graph\n"); + } + Interface *nextIface = GetNextInterface(); + if(reset_required) + nextIface = NULL; + while (NULL != nextIface) + { + //TRACE(" %s is the next iface\n",nextIface->GetAddress().GetHostString()); + if(reset_required) + nextIface = NULL; + else + nextIface = GetNextInterface(); + } + //finished walking through stub check to make sure a reset isn't required + //TRACE(" finishing up the state and checking to see if a reset is required\n"); in_update = false; + start_iface=origStartIfacePtr; + if(reset_required) + { + //TRACE(" Needed a full reset\n"); + Reset(); + while (NULL != GetNextInterface()); + } + //if (!dijkstra_completed) + // TRACE("Getting out without dijkstra being finished!\n"); } // end NetGraph::DijkstraTraversal::Update() // Call this to setup re-traverse of tree computed via Dijkstra +void NetGraph::DijkstraTraversal::Update(Interface& ifaceA, Interface& ifaceB) +{ + const NetGraph::Cost* aCostPtr = GetCost(ifaceA); + const NetGraph::Cost* bCostPtr = GetCost(ifaceB); + if((aCostPtr != NULL) && (bCostPtr != NULL)) + { + if(*aCostPtr>*bCostPtr) + { + Link* linkPtr = ifaceB.GetLinkTo(ifaceA); + if(NULL != linkPtr) + { + //there is a link so lets try and update B without doing a full update + if (!AllowLink(ifaceB, *linkPtr)) + { + //the link isn't allowed and if the link was on the shortest path this will cause a full rest otherwise nothing will be updated + Update(ifaceA); + } else { + Update(ifaceB); + } + } else { + //the link doesn't exists and if the link was on shortest path this will cause a full reset otherwise nothing will be updated + Update(ifaceA); + } + } + else + { + //a's cost is less than b's + Link* linkPtr = ifaceA.GetLinkTo(ifaceB); + if(NULL != linkPtr) + { + if (!AllowLink(ifaceA, *linkPtr)) + { + //the link isn't allowed and if the link was on the shortest path this will cause a full reset otherwise nothing will be updated + Update(ifaceB); + } else { + //there is a link so lets try and update A without doing a full update + Update(ifaceA); + } + } else { + //the link doesn't exists and if the link was on shortest path this will cause a full rest otherwise nothing will be updated + Update(ifaceB); + } + } + } else if(aCostPtr != NULL) { + Update(ifaceA); + } else if (bCostPtr != NULL) { + Update(ifaceB); + } +} + bool NetGraph::DijkstraTraversal::TreeWalkReset() { // If Dijkstra was not completed, run full Dijkstra if (!dijkstra_completed) { + //TRACE("In reset...\n"); Reset(); while (NULL != GetNextInterface()); } @@ -1227,17 +1537,33 @@ NetGraph::Interface* NetGraph::DijkstraTraversal::TreeWalkNext(unsigned int* lev if (NULL != currentIface) { // Find selected links - AdjacencyIterator linkIterator(*currentIface); + Link* nextLink; Link* firstLink = NULL; - while ((nextLink = linkIterator.GetNextAdjacencyLink())) + if(traverse_nodes) { - Interface* nextDst = nextLink->GetDst(); - ASSERT(NULL != nextDst); - if (currentIface == GetPrevHop(*nextDst)) + NetGraph::Node::NeighborIterator linkIterator(currentIface->GetNode()); + while ((nextLink = linkIterator.GetNextNeighborLink())) { - if (NULL == firstLink) firstLink = nextLink; - queue_pending.Append(*nextDst); + Interface* nextDst = nextLink->GetDst(); + ASSERT(NULL != nextDst); + if (currentIface == GetPrevHop(*nextDst)) + { + if (NULL == firstLink) firstLink = nextLink; + queue_pending.Append(*nextDst); + } + } + } else { + AdjacencyIterator linkIterator(*currentIface); + while ((nextLink = linkIterator.GetNextAdjacencyLink())) + { + Interface* nextDst = nextLink->GetDst(); + ASSERT(NULL != nextDst); + if (currentIface == GetPrevHop(*nextDst)) + { + if (NULL == firstLink) firstLink = nextLink; + queue_pending.Append(*nextDst); + } } } // Track depth as walk progresses ... diff --git a/src/manet/manetGraphML.cpp b/src/manet/manetGraphML.cpp index b3f20fe..7c3d604 100644 --- a/src/manet/manetGraphML.cpp +++ b/src/manet/manetGraphML.cpp @@ -91,8 +91,7 @@ bool ManetGraphMLParser::AttributeKey::Init(const char* theIndex,const char* the } -bool -ManetGraphMLParser::AttributeKey::Set(const char* theIndex,const char* theName,const char* theType,const char* theDomain, const char* theOldIndex,const char*theDefault) +bool ManetGraphMLParser::AttributeKey::Set(const char* theIndex,const char* theName,const char* theType,const char* theDomain, const char* theOldIndex,const char*theDefault) { if(NULL != index) delete[] index; @@ -144,8 +143,8 @@ ManetGraphMLParser::AttributeKey::Set(const char* theIndex,const char* theName,c } return true; } -bool -ManetGraphMLParser::AttributeKey::SetType(const char* theType) + +bool ManetGraphMLParser::AttributeKey::SetType(const char* theType) { if((!strcmp(theType,"bool")) || (!strcmp(theType,"Bool")) || @@ -549,7 +548,7 @@ bool ManetGraphMLParser::GetLookup(char* theLookup,unsigned int maxlen,NetGraph: { if(strlen(GetString(node))>maxlen) { - PLOG(PL_ERROR,"ManetGraphMLParser::GetLookup(node) ndoe string is longer than max leng\n"); + PLOG(PL_ERROR,"ManetGraphMLParser::GetLookup(node) node string is longer than max leng\n"); return false; } sprintf(theLookup,"node:%s",GetString(node)); @@ -689,9 +688,10 @@ bool ManetGraphMLParser::ReadXMLNode(xmlTextReader* readerPtr, char* parentXMLNodeID, bool& isDuplex) { - const xmlChar *name, *value; - int count, depth, type, isempty; - + //const xmlChar *name, *value; + //int count, depth, type, isempty; + const xmlChar *name; + int type; type = xmlTextReaderNodeType(readerPtr); if(XML_READER_TYPE_END_ELEMENT==type) return true; @@ -1060,7 +1060,7 @@ bool ManetGraphMLParser::Read(const char* path, NetGraph& graph) } // end ManetGraphMLParser::Read() -bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) +bool ManetGraphMLParser::Write(NetGraph& graph, const char* path, char* buffer, unsigned int* len_ptr) { PLOG(PL_INFO,"ManetGraphMLParser::Write: Enter!\n"); /* Create a new XmlWriter for DOM, with no compression. */ @@ -1109,9 +1109,17 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) /* We are done with the header so now we go through the actual graph and add each node and edge */ /* We are adding each node */ returnvalue = xmlTextWriterStartElement(writerPtr, BAD_CAST "graph"); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error starting XML graph element\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error starting XML graph element\n"); + return false; + } returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "id",BAD_CAST XMLName); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error setting XML graph attribute id\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error setting XML graph attribute id\n"); + return false; + } returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "edgedefault",BAD_CAST "directed"); if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error setting graph attribute directed\n"); return false;} @@ -1122,16 +1130,22 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) { //check to see if this is a default "node" interface //if(!iface->IsPort()) - if(iface == iface->GetNode().GetDefaultInterface()) + if (iface == iface->GetNode().GetDefaultInterface()) { //Node& node = static_cast(iface->GetNode()); returnvalue = xmlTextWriterStartElement(writerPtr, BAD_CAST "node"); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error adding XML node\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error adding XML node\n"); + return false; + } if(iface->GetAddress().IsValid()) { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "id", BAD_CAST iface->GetAddress().GetHostString()); //printf("writing node %s\n",iface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "id", BAD_CAST iface->GetName()); //printf("writing node %s\n",iface->GetName()); } @@ -1167,16 +1181,26 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) // if(portIface->IsPort()) { returnvalue = xmlTextWriterStartElement(writerPtr, BAD_CAST "port"); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error adding XML node\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error adding XML node\n"); + return false; + } if(portIface->GetAddress().IsValid()) { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "name", BAD_CAST portIface->GetAddress().GetHostString()); //printf("writing interface %s\n",iface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "name", BAD_CAST portIface->GetName()); //printf("writing node %s\n",iface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error adding setting node id\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error adding setting node id\n"); + return false; + } //update attributes to the port/interface using the virutal function if(!UpdateInterfaceAttributes(*portIface)) { @@ -1196,12 +1220,20 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) return false; }*/ returnvalue = xmlTextWriterEndElement(writerPtr); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error ending node element\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error ending node element\n"); + return false; + } } } //close up the node node element returnvalue = xmlTextWriterEndElement(writerPtr); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error ending node element\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: Error ending node element\n"); + return false; + } } } //adding the directional links to the xml @@ -1216,23 +1248,38 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) while (NULL != (nbrIface = iteratorN1.GetNextAdjacency())) { returnvalue = xmlTextWriterStartElement(writerPtr, BAD_CAST "edge"); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding edge\n"); return false;} - if(iface->GetAddress().IsValid()) { + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding edge\n"); return false; + } + if(iface->GetAddress().IsValid()) + { //printf("writing connection %s ->",iface->GetAddress().GetHostString()); returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "source", BAD_CAST iface->GetAddress().GetHostString()); - } else { + }else + { //printf("writing connection %s ->",iface->GetName()); returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "source", BAD_CAST iface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); + return false; + } if(nbrIface->GetAddress().IsValid()) { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "target", BAD_CAST nbrIface->GetAddress().GetHostString()); ////printf("%s\n",nbrIface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "target", BAD_CAST nbrIface->GetName()); ////printf("%s\n",nbrIface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); + return false; + } NetGraph::Link* link = iface->GetLinkTo(*nbrIface); if(NULL == link) { @@ -1252,7 +1299,11 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) return false; } returnvalue = xmlTextWriterEndElement(writerPtr); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error ending node element\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error ending node element\n"); + return false; + } } } else //it is a port interface so we need to find the "node" interface @@ -1279,32 +1330,63 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) return false; } returnvalue = xmlTextWriterStartElement(writerPtr, BAD_CAST "edge"); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding edge\n"); return false;} - - if(nodeIface->GetAddress().IsValid()) { + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding edge\n"); + return false; + } + if(nodeIface->GetAddress().IsValid()) + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "source", BAD_CAST nodeIface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "source", BAD_CAST nodeIface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); return false;} - if(nbrNodeIface->GetAddress().IsValid()) { + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); + return false; + } + if(nbrNodeIface->GetAddress().IsValid()) + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "target", BAD_CAST nbrNodeIface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "target", BAD_CAST nbrNodeIface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); return false;} - if(iface->GetAddress().IsValid()) { + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); + return false; + } + if(iface->GetAddress().IsValid()) + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "sourceport", BAD_CAST iface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "sourceport", BAD_CAST iface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); return false;} - if(nbrIface->GetAddress().IsValid()) { + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); + return false; + } + if(nbrIface->GetAddress().IsValid()) + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "targetport", BAD_CAST nbrIface->GetAddress().GetHostString()); - } else { + } + else + { returnvalue = xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "targetport", BAD_CAST nbrIface->GetName()); } - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error adding setting source attribute\n"); + return false; + } NetGraph::Link* link = iface->GetLinkTo(*nbrIface); if(NULL == link) @@ -1325,7 +1407,11 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) return false; } returnvalue = xmlTextWriterEndElement(writerPtr); - if (returnvalue < 0) { PLOG(PL_ERROR,"ManetGraphMLParser::Write Error ending node element\n"); return false;} + if (returnvalue < 0) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write Error ending node element\n"); + return false; + } } } } @@ -1335,8 +1421,30 @@ bool ManetGraphMLParser::Write(NetGraph& graph, const char* path) xmlFreeTextWriter(writerPtr); - xmlSaveFormatFileEnc(path, docPtr, MY_GRAPHML_ENCODING,1); - + if(NULL != path) + { + xmlSaveFormatFileEnc(path, docPtr, MY_GRAPHML_ENCODING,1); + } + if(NULL != buffer && NULL != len_ptr) + { + //xmlOutputBuffer xmlbuff; + //xmlSaveFormatFileTo(&xmlbuff,docPtr,MY_GRAPHML_ENCODING,1); + //if (xmlbuff.written > (int)*len_ptr){ + // DMSG(0,"bunny in buffer section\n"); + + xmlChar* tempout; + int size; + xmlDocDumpFormatMemoryEnc(docPtr,&tempout,&size,MY_GRAPHML_ENCODING,1); + if(size > (int)*len_ptr) + { + PLOG(PL_ERROR,"ManetGraphMLParser::Write: size of document %d is larger than the allocated space of %d\n",size, (int)*len_ptr); + return false; + } + *len_ptr = size; + memcpy(buffer,tempout,size); + xmlFree(tempout); + xmlCleanupParser(); + } xmlFreeDoc(docPtr); return true; @@ -1354,7 +1462,9 @@ ManetGraphMLParser::AttributeList::FindAttribute(const char *theLookup,const cha { attr = NULL; //we didn't find the entry - } else { + } + else + { if(strcmp(attr->GetIndex(),theIndex)) { //this attribute does not have the index we are looking for @@ -1366,8 +1476,8 @@ ManetGraphMLParser::AttributeList::FindAttribute(const char *theLookup,const cha } return attr; } -bool -ManetGraphMLParser::WriteLocalKeys(xmlTextWriter* writerPtr) + +bool ManetGraphMLParser::WriteLocalKeys(xmlTextWriter* writerPtr) { int rv = 0; IndexKeylist::Iterator it(indexkeylist); @@ -1428,8 +1538,8 @@ ManetGraphMLParser::WriteLocalKeys(xmlTextWriter* writerPtr) return false; return true; } -bool -ManetGraphMLParser::WriteLocalNodeAttributes(xmlTextWriter* writerPtr,NetGraph::Node& theNode) + +bool ManetGraphMLParser::WriteLocalNodeAttributes(xmlTextWriter* writerPtr,NetGraph::Node& theNode) { PLOG(PL_DETAIL,"ManetGraphMLParser::WriteLocalNodeAttributes: Enter\n"); bool rv = true; @@ -1446,7 +1556,9 @@ ManetGraphMLParser::WriteLocalNodeAttributes(xmlTextWriter* writerPtr,NetGraph:: //PLOG(PL_DETAIL,"ManetGraphMLParser::WriteLocalNodeAttributes():mykey=\"%s\",lookup=\"%s\",key=\"%s\",value=\"%s\"\n",key,attr->GetLookup(),attr->GetIndex(),attr->GetValue()); attr = NULL; //attr = it.GetNextItem(); - } else { + } + else + { rv += xmlTextWriterStartElement(writerPtr, BAD_CAST "data"); rv += xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "key",BAD_CAST attr->GetIndex()); rv += xmlTextWriterWriteString(writerPtr, BAD_CAST attr->GetValue()); @@ -1457,8 +1569,8 @@ ManetGraphMLParser::WriteLocalNodeAttributes(xmlTextWriter* writerPtr,NetGraph:: } return rv; } -bool -ManetGraphMLParser::WriteLocalInterfaceAttributes(xmlTextWriter* writerPtr,NetGraph::Interface& theInterface) + +bool ManetGraphMLParser::WriteLocalInterfaceAttributes(xmlTextWriter* writerPtr,NetGraph::Interface& theInterface) { bool rv = true; char key[255];//this should be dynamic or checks added TBD @@ -1472,7 +1584,9 @@ ManetGraphMLParser::WriteLocalInterfaceAttributes(xmlTextWriter* writerPtr,NetGr if(strcmp(attr->GetLookup(),key)) { attr = NULL; - } else { + } + else + { rv += xmlTextWriterStartElement(writerPtr, BAD_CAST "data"); rv += xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "key",BAD_CAST attr->GetIndex()); rv += xmlTextWriterWriteString(writerPtr, BAD_CAST attr->GetValue()); @@ -1482,8 +1596,8 @@ ManetGraphMLParser::WriteLocalInterfaceAttributes(xmlTextWriter* writerPtr,NetGr } return rv; } -bool -ManetGraphMLParser::WriteLocalLinkAttributes(xmlTextWriter* writerPtr,NetGraph::Link& theLink) + +bool ManetGraphMLParser::WriteLocalLinkAttributes(xmlTextWriter* writerPtr,NetGraph::Link& theLink) { PLOG(PL_DETAIL,"ManetGraphMLParser::WriteLocalLinkAttributes()\n"); bool rv = true; @@ -1501,7 +1615,9 @@ ManetGraphMLParser::WriteLocalLinkAttributes(xmlTextWriter* writerPtr,NetGraph:: if(strcmp(attr->GetLookup(),key)) { attr = NULL; - } else { + } + else + { rv += xmlTextWriterStartElement(writerPtr, BAD_CAST "data"); rv += xmlTextWriterWriteAttribute(writerPtr, BAD_CAST "key",BAD_CAST attr->GetIndex()); rv += xmlTextWriterWriteString(writerPtr, BAD_CAST attr->GetValue()); diff --git a/src/manet/manetMsg.cpp b/src/manet/manetMsg.cpp index d7f8bd9..275e150 100644 --- a/src/manet/manetMsg.cpp +++ b/src/manet/manetMsg.cpp @@ -672,7 +672,7 @@ ManetTlv* ManetAddrBlock::AppendTlv(UINT8 type) { // Check for "head-only" or "tail-only" address block if ((0 == GetHeadLength()) && (0 == GetTailLength())) - return false; + return NULL; else SetAddressCount(1); } diff --git a/src/python/protokit.cpp b/src/python/protokit.cpp index df74391..c573b11 100644 --- a/src/python/protokit.cpp +++ b/src/python/protokit.cpp @@ -98,24 +98,31 @@ extern "C" { Py_RETURN_NONE; } - static PyObject* Pipe_Listen(Pipe *self, PyObject *args) { + static PyObject* Pipe_Listen(Pipe *self, PyObject *args) + { const char *name; - if (!PyArg_ParseTuple(args, "s", &name)) return NULL; - if (!self->thisptr->Listen(name)) { PyErr_SetString(ProtoError, "Could not start listener."); return NULL; } - self->isConnected = true; Py_RETURN_NONE; } + // Thisc currently only allows a single connection to be accepted + // TBD - provide option to accept multiple connections static PyObject* Pipe_Accept(Pipe *self, PyObject *args) { - PyErr_SetString(PyExc_NotImplementedError, ""); - return NULL; + + if (!self->thisptr->Accept()) + { + PyErr_SetString(ProtoError, "ProtoPipe::Accept() error"); + return NULL; + } + //PyErr_SetString(PyExc_NotImplementedError, ""); + //return NULL; + Py_RETURN_NONE; } static PyObject* Pipe_Close(Pipe *self) { @@ -244,8 +251,7 @@ extern "C" { if (PyType_Ready(&PipeType) < 0) return; - m = Py_InitModule3("protokit", protokit_methods, - "Python wrapper for Protokit"); + m = Py_InitModule3("protokit", protokit_methods, "Python wrapper for Protokit"); if (m == NULL) return; diff --git a/src/sim/ns/ns233/ns233-Makefile.in b/src/sim/ns/ns233/ns233-Makefile.in index bdfb22b..2f57e24 100644 --- a/src/sim/ns/ns233/ns233-Makefile.in +++ b/src/sim/ns/ns233/ns233-Makefile.in @@ -74,7 +74,7 @@ OBJ_PROTOLIB_CPP = \ protolib/src/common/protoSimSocket.o protolib/src/common/protoAddress.o \ protolib/src/common/protoTimer.o protolib/examples/protoExample.o \ protolib/src/common/protoDebug.o protolib/src/common/protoTree.o \ - protolib/src/common/protoList.o \ + protolib/src/common/protoList.o protolib/src/common/protoRouteMgr.o \ protolib/src/sim/ns/nsRouteMgr.o protolib/src/common/protoRouteTable.o \ protolib/src/common/protoBitmask.o protolib/src/sim/ns/nsProtoManetKernel.o \ protolib/src/common/protoTime.o protolib/src/sim/ns/tcp/TCPData.o \ diff --git a/src/sim/ns/ns234/ns234-Makefile.in b/src/sim/ns/ns234/ns234-Makefile.in index 582a7f5..5b5b175 100644 --- a/src/sim/ns/ns234/ns234-Makefile.in +++ b/src/sim/ns/ns234/ns234-Makefile.in @@ -74,7 +74,7 @@ OBJ_PROTOLIB_CPP = \ $(PROTOLIB)/src/common/protoSimSocket.o $(PROTOLIB)/src/common/protoAddress.o \ $(PROTOLIB)/src/common/protoTimer.o $(PROTOLIB)/examples/protoExample.o \ $(PROTOLIB)/src/common/protoDebug.o $(PROTOLIB)/src/common/protoTree.o \ - $(PROTOLIB)/src/common/protoList.o \ + $(PROTOLIB)/src/common/protoList.o $(PROTOLIB)/src/common/protoRouteMgr.o \ $(PROTOLIB)/src/sim/ns/nsRouteMgr.o $(PROTOLIB)/src/common/protoRouteTable.o \ $(PROTOLIB)/src/common/protoPkt.o $(PROTOLIB)/src/manet/manetMsg.o \ $(PROTOLIB)/src/common/protoBitmask.o $(PROTOLIB)/src/sim/ns/nsProtoManetKernel.o \ diff --git a/src/sim/ns/ns235/README.TXT b/src/sim/ns/ns235/README.TXT new file mode 100644 index 0000000..f1a682a --- /dev/null +++ b/src/sim/ns/ns235/README.TXT @@ -0,0 +1,179 @@ + (NOTE: The included "ns235-Makefile.in" can be used + but expects NRL PROTOLIB, NORM, OLSR, and MGEN ns-2 + extensions to be used.) + +QUICK INSTALL +(using the supplied ns235-Makefile.in) + +1) Download source code of the following NRL programs PROTOLIB, + NORM, OLSR, and MGEN. + + a) Using svn: + 1. make current directory /ns-allinone-2.35/ns-2.35/ + 2. type "svn checkout --username https://pf.itd.nrl.navy.mil/svnroot/protolib/trunk protolib" + 3. type "svn checkout --username https://pf.itd.nrl.navy.mil/svnroot/norm/trunk norm" + 3. type "svn checkout --username https://pf.itd.nrl.navy.mil/svnroot/nrlolsr/trunk nrlolsr" + 3. type "svn checkout --username https://pf.itd.nrl.navy.mil/svnroot/mgen/trunk mgen" + b) Using cvs: (depreciated older code) + 1. make current directory /ns-allinone-2.35/ns-2.35/ + 2. type "cvs -d :pserver:anonymous@pf.itd.nrl.navy.mil:/cvsroot/protolib co protolib" + 3. type "cvs -d :pserver:anonymous@pf.itd.nrl.navy.mil:/cvsroot/norm co norm" + 4. type "cvs -d :pserver:anonymous@pf.itd.nrl.navy.mil:/cvsroot/olsr co nrlolsr" + 5. type "cvs -d :pserver:anonymous@pf.itd.nrl.navy.mil:/cvsroot/mgen co mgen" + c) Using tarballs: + 1. make current directory /ns-allinone-2.35/ns-2.35/ + 2. download norm, nrlolsr, mgen (nightly build tar file use is encouraged) + 3. type "tar -xvzf .tgz + d) Using symbolic links (Note: only works if you have already downloaded the nrl code) + 1. make current directory /ns-allinone-2.35/ns-2.35/ + 2. type "ln -s protolib + 3. type "ln -s norm + 4. type "ln -s nrlolsr + 5. type "ln -s mgen + (Note: using this method will create ns specific .o files in the source trees which can + cause issues if building these programs for other envirnments) + +2) Copy the files within this directory to the appropriate places in the ns 2.35 code tree + a) make current directory /ns-allinone-2.35/ns-2.35/ + (Note: you may want to make backups of the files being written over before performing the following commands) + b) type "cp protolib/src/sim/ns/ns234/cmu-trace.h trace/" + c) type "cp protolib/src/sim/ns/ns234/cmu-trace.cc trace/" + d) type "cp protolib/src/sim/ns/ns234/packet.h common/" + e) type "cp protolib/src/sim/ns/ns234/packet.cc common/" + f) type "cp protolib/src/sim/ns/ns234/priqueue.cc queue/" + g) type "cp protolib/src/sim/ns/ns234/ns-lib.tcl tcl/lib/" + +3) Using the supplied makefile build your protolib enabled ns2.35 build + a) Make current directory /ns-allinone-2.35/ns-2.35/ + (Note: you may want to make backups of the files being written over before performing the following commands) + b) type "cp protolib/src/sim/ns/ns234/ns234-Makefile.in Makefile.in" + c) type "./configure" + d) type "make clean" + e) type "make ns" + +========================= +LONG INSTALL INSTRUCTIONS +(provided mainly as hints for those building on top of a +modified ns code tree in which the quick install did not work) + +To use PROTOLIB with ns, you will need to at least modify +the ns "Makefile.in" to build the PROTOLIB code into ns. +To do this, use the following steps: + + +1) Make a link to the PROTOLIB source directory in the ns + source directory. (I use "protolib" for the link name + in the steps below). + +2) Provide paths to the PROTOLIB include files by setting + + PROTOLIB_INCLUDES = -Iprotolib/common -Iprotolib/ns + + and adding $(PROTOLIB_INCLUDES) to the "INCLUDES" macro + already defined in the ns "Makefile.in" + +3) Define compile-time CFLAGS needed for the PROTOLIB code + by setting + + PROTOLIB_FLAGS = -DUNIX -DNS2 -DPROTO_DEBUG -DHAVE_ASSERT + + and adding $(PROTOLIB_FLAGS) to the "CFLAGS" macro + already defined in the ns "Makefile.in" + +4) Add the list of PROTOLIB object files to get compiled + and linked during the ns build. For UDP and TCP support, set + +OBJ_PROTOLIB_CPP = \ + protolib/ns/nsProtoSimAgent.o protolib/ns/nsRouteMgr.o \ + protolib/ns/nsProtoSimAgent.o protolib/common/protoSimAgent.o \ + protolib/common/protoSimSocket.o protolib/common/protoAddress.o \ + protolib/common/protoTimer.o protolib/common/protoExample.o \ + protolib/common/protoDebug.o protolib/common/protoTree.o \ + protolib/ns/nsRouteMgr.o protolib/common/protoRouteTable.o \ + protolib/common/protoBitmask.o protolib/ns/nsProtolibMK.o \ + protolib/common/protoTime.o + + and then add $(OBJ_PROTOLIB_CPP) to the list in the + "OBJ" macro already defined in the ns "Makefile.in" + + Note: "nsProtoAgent.cpp" contains a starter ns agent + which uses the PROTOLIB ProtocolTimer and UdpSocket + classes. + +5) Add the the rule for .cpp files to ns-2 "Makefile.in": + + .cpp.o: @rm -f $@ $(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cpp + + and add to the ns-2 Makefile.in "SRC" macro definition: + + $(OBJ_CPP:.o=.cpp) + +6) Change your ns-2.35/common/packet.h file to include info on ProtolibMK: + + a) add: + + "#define HDR_PROTOLIBMK(p) (hdr_protolibmk::access(p))" + near the top of the file with the other similar #defines. + + b) Before the end of the statics for packet_t, change the + current PT_NTYPE line to + + "static const packet_t PT_PROTOLIBMK=73;" + + and change the last entry to + + "PT_NTYPE = 74; // This MUST be the LAST one" + + c) add "name_[PT_PROTOLIBMK]= "ProtolibMK";" in the p_info() section + +7) Change your ns-2.35/trace/cmu-trace.h file adding ProtolibMK hooks + add "void format_protolibmk(Packet *p, int offset);" at the bottom with + the other "format_xxx" functions. + +8) Change your ns-2.35/trace/cmu-trace.cc file adding ProtolibManetKernel hooks: + + a) add "#include " at the end of the #includes + + b) add empty function: + "void CMUTrace::format_protolibmk(Packet *p, int offset) {return;}" + + c) add to the switch(ch->ptype()) (default one) statement: + + "case PT_PROTOLIBMK: + format_protolibmk(p, offset); + break;" + +9) Change your ns-2.35/tcl/lib/ns-lib.tcl file: + + a) add to the "switch -exact $routingAgent" statement: + + "ProtolibMK { + set ragent [$self create-protolibmk-agent $node] + }" + + b) add along with other Simulator create functions: + + "Simulator instproc create-protolibmk-agent {node} { + # create a dummie wireless agent + # it will foward packets up to protolib manet + # and just act as a wedge into ns + # used by protolib wireless manets + set ragent [new Agent/ProtolibMK [$node node-addr]] + $node set ragent_ $ragent + return $ragent + }" + +10) Last edit! Change ns-2.35/queue/priqueue.cc file, add + + "case PT_PROTOLIBMK:" + + to the switch statment in the if(Prefer_Routing_Protocols) conditional (at top) + + +6) Run "./configure" in the ns source directory to create + a new Makefile and then type "make ns" to rebuild ns. + + +Brian Adamson +Justin Dean +1/14/2014 diff --git a/src/sim/ns/ns235/cmu-trace.cc b/src/sim/ns/ns235/cmu-trace.cc new file mode 100644 index 0000000..fad3850 --- /dev/null +++ b/src/sim/ns/ns235/cmu-trace.cc @@ -0,0 +1,1609 @@ +/* -*- Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */ +/* + * Copyright (c) 1997 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Ported from CMU/Monarch's code, appropriate copyright applies. + * nov'98 -Padma. + * + * $Header: /cvsroot/nsnam/ns-2/trace/cmu-trace.cc,v 1.98 2011/10/02 22:32:35 tom_henderson Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include // DSR +#include +#include +#include +#include +#include //TORA +#include // IMEP +#include //AODV +#include +#include +#include +#include +#include +#include +#include +// +#include "wpan/p802_15_4pkt.h" +#include "wpan/p802_15_4trace.h" +#include "wpan/p802_15_4nam.h" +// + +#include "diffusion/diff_header.h" // DIFFUSION -- Chalermek + + +PacketTracer::PacketTracer() : next_(0) +{ +} +void PacketTracer::setNext(PacketTracer *next) +{ + next_ = next; +} + +PacketTracer::~PacketTracer() +{ +} + +PacketTracer *PacketTracer::getNext() +{ + return next_; +} + +int PacketTracer::format_unknow(Packet *p, int offset, BaseTrace *pt, int newtrace) +{ + return (format(p, offset, pt, newtrace) || (next_ && next_->format_unknow(p, offset, pt, newtrace))); +} + +PacketTracer *CMUTrace::pktTrc_ = 0; + +void CMUTrace::addPacketTracer(PacketTracer *pt) +{ + if(!pt) + return; + pt->setNext(pktTrc_); + pktTrc_ = pt; +} + + +//#define LOG_POSITION + +//extern char* pt_names[]; + +static class CMUTraceClass : public TclClass { +public: + CMUTraceClass() : TclClass("CMUTrace") { } + TclObject* create(int, const char*const* argv) { + return (new CMUTrace(argv[4], *argv[5])); + } +} cmutrace_class; + + +// +double CMUTrace::bradius = 0.0; +double CMUTrace::radius_scaling_factor_ = 0.0; +double CMUTrace::duration_scaling_factor_ = 0.0; +// + +CMUTrace::CMUTrace(const char *s, char t) : Trace(t) +{ + bzero(tracename, sizeof(tracename)); + strncpy(tracename, s, MAX_ID_LEN); + + if(strcmp(tracename, "RTR") == 0) { + tracetype = TR_ROUTER; + } + else if(strcmp(tracename, "TRP") == 0) { + tracetype = TR_ROUTER; + } + else if(strcmp(tracename, "PHY") == 0) { + tracetype = TR_PHY; + } + else if(strcmp(tracename, "MAC") == 0) { + tracetype = TR_MAC; + } + else if(strcmp(tracename, "IFQ") == 0) { + tracetype = TR_IFQ; + } + else if(strcmp(tracename, "AGT") == 0) { + tracetype = TR_AGENT; + } + else { + fprintf(stderr, "CMU Trace Initialized with invalid type\n"); + exit(1); + } +// change wrt Mike's code +// assert(type_ == DROP || type_ == SEND || type_ == RECV); + assert(type_ == DROP || type_ == SEND || type_ == RECV + || ((type_ == EOT) && (tracetype == TR_MAC))); + + + + newtrace_ = 0; + for (int i=0 ; i < MAX_NODE ; i++) + nodeColor[i] = 3 ; + node_ = 0; +} + +void +CMUTrace::format_mac_common(Packet *p, const char *why, int offset) +{ + struct hdr_cmn *ch = HDR_CMN(p); + struct hdr_ip *ih = HDR_IP(p); + struct hdr_mac802_11 *mh; + struct hdr_smac *sh; + char mactype[SMALL_LEN]; + + strcpy(mactype, Simulator::instance().macType()); + if (strcmp (mactype, "Mac/SMAC") == 0) + sh = HDR_SMAC(p); + else + mh = HDR_MAC802_11(p); + + double x = 0.0, y = 0.0, z = 0.0; + + char op = (char) type_; + Node* thisnode = Node::get_node_by_address(src_); + double energy = -1; + if (thisnode) { + if (thisnode->energy_model()) { + energy = thisnode->energy_model()->energy(); + } + } + + // hack the IP address to convert pkt format to hostid format + // for now until port ids are removed from IP address. -Padma. + + int src = Address::instance().get_nodeaddr(ih->saddr()); + + if(tracetype == TR_ROUTER && type_ == SEND) { + if(src_ != src) + op = FWRD; + } + + // use tagged format if appropriate + if (pt_->tagged()) { + int next_hop = -1 ; + Node* nextnode = Node::get_node_by_address(ch->next_hop_); + if (nextnode) next_hop = nextnode->nodeid(); + + node_->getLoc(&x, &y, &z); + + if (op == DROP) op = 'd'; + if (op == SEND) op = '+'; + if (op == FWRD) op = 'h'; + + sprintf(pt_->buffer() + offset, + "%c "TIME_FORMAT" -s %d -d %d -p %s -k %3s -i %d " + "-N:loc {%.2f %.2f %.2f} -N:en %f ", + + op, // event type + Scheduler::instance().clock(), // time + src_, // this node + next_hop, // next hop + packet_info.name(ch->ptype()), // packet type + tracename, // trace level + ch->uid(), // event id + x, y, z, // location + energy); // energy + + offset = strlen(pt_->buffer()); + if (strcmp (mactype, "Mac/SMAC") == 0) { + format_smac(p, offset); + } else { + format_mac(p, offset); + } + return; + } + + + // Use new ns trace format to replace the old cmu trace format) + if (newtrace_) { + + node_->getLoc(&x, &y, &z); + // consistence + if ( op == DROP ) { op = 'd';} + + // basic trace infomation + basic exenstion + + sprintf(pt_->buffer() + offset, + "%c -t %.9f -Hs %d -Hd %d -Ni %d -Nx %.2f -Ny %.2f -Nz %.2f -Ne %f -Nl %3s -Nw %s ", + op, // event type + Scheduler::instance().clock(), // time + src_, // this node + ch->next_hop_, // next hop + src_, // this node + x, // x coordinate + y, // y coordinate + z, // z coordinate + energy, // energy, -1 = not existing + tracename, // trace level + why); // reason + + // mac layer extension + + offset = strlen(pt_->buffer()); + if (strcmp(mactype, "Mac/SMAC") == 0) { + format_smac(p, offset); + } else { + format_mac(p, offset); + } + return; + } + + +#ifdef LOG_POSITION + x = 0.0, y = 0.0, z = 0.0; + node_->getLoc(&x, &y, &z); +#endif + sprintf(pt_->buffer() + offset, +#ifdef LOG_POSITION + "%c %.9f %d (%6.2f %6.2f) %3s %4s %d %s %d ", +#else + "%c %.9f _%d_ %3s %4s %d %s %d", +#endif + op, + Scheduler::instance().clock(), + src_, // this node +#ifdef LOG_POSITION + x, + y, +#endif + tracename, + why, + + ch->uid(), // identifier for this event + + ((ch->ptype() == PT_MAC) ? ( + (mh->dh_fc.fc_type == MAC_Type_Control) ? ( + (mh->dh_fc.fc_subtype == MAC_Subtype_RTS) ? "RTS" : + (mh->dh_fc.fc_subtype == MAC_Subtype_CTS) ? "CTS" : + (mh->dh_fc.fc_subtype == MAC_Subtype_ACK) ? "ACK": + // + (mh->dh_fc.fc_subtype == MAC_Subtype_Beacon) ? "BCN" : //Beacon + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_AssoReq) ? "CM1" : //CMD: Association request + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_AssoRsp) ? "CM2" : //CMD: Association response + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_DAssNtf) ? "CM3" : //CMD: Disassociation notification + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_DataReq) ? "CM4" : //CMD: Data request + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_PIDCNtf) ? "CM5" : //CMD: PAN ID conflict notification + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_OrphNtf) ? "CM6" : //CMD: Orphan notification + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_BconReq) ? "CM7" : //CMD: Beacon request + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_CoorRea) ? "CM8" : //CMD: Coordinator realignment + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_GTSReq) ? "CM9" : //CMD: GTS request + "UNKN") : + (mh->dh_fc.fc_type == MAC_Type_Management) ? ( + (mh->dh_fc.fc_subtype == MAC_Subtype_80211_Beacon) ? "BCN" : + (mh->dh_fc.fc_subtype == MAC_Subtype_AssocReq) ? "ACRQ" : + (mh->dh_fc.fc_subtype == MAC_Subtype_AssocRep) ? "ACRP" : + (mh->dh_fc.fc_subtype == MAC_Subtype_Auth) ? "AUTH" : + (mh->dh_fc.fc_subtype == MAC_Subtype_ProbeReq) ? "PRRQ" : + (mh->dh_fc.fc_subtype == MAC_Subtype_ProbeRep) ? "PRRP" : + "UNKN") : + "UNKN") : + (ch->ptype() == PT_SMAC) ? ( + (sh->type == RTS_PKT) ? "RTS" : + (sh->type == CTS_PKT) ? "CTS" : + (sh->type == ACK_PKT) ? "ACK" : + (sh->type == SYNC_PKT) ? "SYNC" : + "UNKN") : + packet_info.name(ch->ptype())), + ch->size()); + + offset = strlen(pt_->buffer()); + + if(tracetype == TR_PHY) { + format_phy(p, offset); + offset = strlen(pt_->buffer()); + return; + } + + if (strncmp (mactype, "Mac/SMAC", 8) == 0) { + format_smac(p, offset); + } else { + format_mac(p, offset); + } + + offset = strlen(pt_->buffer()); + + if (thisnode) { + if (thisnode->energy_model()) { + // log detailed energy consumption + // total energy and breakdown in idle, sleep, transmit and receive modes + sprintf(pt_->buffer() + offset, + "[energy %f ei %.3f es %.3f et %.3f er %.3f] ", + thisnode->energy_model()->energy(), + thisnode->energy_model()->ei(), + thisnode->energy_model()->es(), + thisnode->energy_model()->et(), + thisnode->energy_model()->er()); + } + } +} + +void +CMUTrace::format_phy(Packet *, int offset) +{ + sprintf(pt_->buffer() + offset, " "); +} + + +void +CMUTrace::format_mac(Packet *p, int offset) +{ + struct hdr_mac802_11 *mh = HDR_MAC802_11(p); + struct hdr_cmn *ch = HDR_CMN(p); + // This function assumes in some places that mh->dh_body points + // to an ethertype, which may not be true and causes some portability + // problems, so we zero the printing of this field in some cases + bool print_ether_type = true; + if ( (ch->ptype() == PT_MAC) && + ( (mh->dh_fc.fc_type == MAC_Type_Control) || + (mh->dh_fc.fc_type == MAC_Type_Management))) { + print_ether_type = false; + } + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-M:dur %x -M:s %x -M:d %x -M:t %x ", + mh->dh_duration, // MAC: duration + + // change wrt Mike's code + //ETHER_ADDR(mh->dh_da), // MAC: source + //ETHER_ADDR(mh->dh_sa), // MAC: destination + ETHER_ADDR(mh->dh_ra), // MAC: source + ETHER_ADDR(mh->dh_ta), // MAC: destination + + + print_ether_type ? GET_ETHER_TYPE(mh->dh_body) : 0); // MAC: type + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-Ma %x -Md %x -Ms %x -Mt %x ", + mh->dh_duration, + + // change wrt Mike's code + //ETHER_ADDR(mh->dh_da), + //ETHER_ADDR(mh->dh_sa), + + ETHER_ADDR(mh->dh_ra), + ETHER_ADDR(mh->dh_ta), + + print_ether_type ? GET_ETHER_TYPE(mh->dh_body) : 0); + } else { + sprintf(pt_->buffer() + offset, + " [%x %x %x %x] ", + //*((u_int16_t*) &mh->dh_fc), + mh->dh_duration, + + // change wrt Mike's code + //ETHER_ADDR(mh->dh_da), + //ETHER_ADDR(mh->dh_sa), + ETHER_ADDR(mh->dh_ra), + ETHER_ADDR(mh->dh_ta), + print_ether_type ? GET_ETHER_TYPE(mh->dh_body) : 0); + } +} + +void +CMUTrace::format_smac(Packet *p, int offset) +{ + struct hdr_smac *sh = HDR_SMAC(p); + sprintf(pt_->buffer() + offset, + " [%.2f %d %d] ", + sh->duration, + sh->dstAddr, + sh->srcAddr); +} + + +void +CMUTrace::format_ip(Packet *p, int offset) +{ + struct hdr_cmn *ch = HDR_CMN(p); + struct hdr_ip *ih = HDR_IP(p); + + // hack the IP address to convert pkt format to hostid format + // for now until port ids are removed from IP address. -Padma. + int src = Address::instance().get_nodeaddr(ih->saddr()); + int dst = Address::instance().get_nodeaddr(ih->daddr()); + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-IP:s %d -IP:sp %d -IP:d %d -IP:dp %d -p %s -e %d " + "-c %d -i %d -IP:ttl %d ", + src, // packet src + ih->sport(), // src port + dst, // packet dest + ih->dport(), // dst port + packet_info.name(ch->ptype()), // packet type + ch->size(), // packet size + ih->flowid(), // flow id + ch->uid(), // unique id + ih->ttl_ // ttl + ); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-Is %d.%d -Id %d.%d -It %s -Il %d -If %d -Ii %d -Iv %d ", + src, // packet src + ih->sport(), // src port + dst, // packet dest + ih->dport(), // dst port + packet_info.name(ch->ptype()), // packet type + ch->size(), // packet size + ih->flowid(), // flow id + ch->uid(), // unique id + ih->ttl_); // ttl + } else { + sprintf(pt_->buffer() + offset, "------- [%d:%d %d:%d %d %d] ", + src, ih->sport(), + dst, ih->dport(), + ih->ttl_, (ch->next_hop_ < 0) ? 0 : ch->next_hop_); + } +} + +// Note: HDLC format (format_hdlc()) has moved to satellite tracing + +void +CMUTrace::format_arp(Packet *p, int offset) +{ + struct hdr_arp *ah = HDR_ARP(p); + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-arp:op %s -arp:ms %d -arp:s %d -arp:md %d -arp:d %d ", + ah->arp_op == ARPOP_REQUEST ? "REQUEST" : "REPLY", + ah->arp_sha, + ah->arp_spa, + ah->arp_tha, + ah->arp_tpa); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-P arp -Po %s -Pms %d -Ps %d -Pmd %d -Pd %d ", + ah->arp_op == ARPOP_REQUEST ? "REQUEST" : "REPLY", + ah->arp_sha, + ah->arp_spa, + ah->arp_tha, + ah->arp_tpa); + } else { + + sprintf(pt_->buffer() + offset, + "------- [%s %d/%d %d/%d]", + ah->arp_op == ARPOP_REQUEST ? "REQUEST" : "REPLY", + ah->arp_sha, + ah->arp_spa, + ah->arp_tha, + ah->arp_tpa); + } +} + +void +CMUTrace::format_dsr(Packet *p, int offset) +{ + hdr_sr *srh = hdr_sr::access(p); + int last_err_index = 0; + int last_reply_index = 0; + + if (srh->num_route_errors() > 1) { + last_err_index = srh->num_route_errors() - 1; + } + if (srh->route_reply_len() > 1) { + last_reply_index = srh->route_reply_len() - 1; + } + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-dsr:h %d -dsr:q %d -dsr:s %d -dsr:p %d -dsr:n %d " + "-dsr:l %d -dsr:e {%d %d} -dsr:w %d -dsr:m %d -dsr:c %d " + "-dsr:b {%d %d} ", + srh->num_addrs(), + srh->route_request(), + srh->rtreq_seq(), + srh->route_reply(), + srh->rtreq_seq(), + srh->route_reply_len(), + srh->reply_addrs()[0].addr, + srh->reply_addrs()[last_reply_index].addr, + srh->route_error(), + srh->num_route_errors(), + srh->down_links()[last_err_index].tell_addr, + srh->down_links()[last_err_index].from_addr, + srh->down_links()[last_err_index].to_addr); + return; + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-P dsr -Ph %d -Pq %d -Ps %d -Pp %d -Pn %d -Pl %d -Pe %d->%d -Pw %d -Pm %d -Pc %d -Pb %d->%d ", + srh->num_addrs(), // how many nodes travered + + srh->route_request(), + srh->rtreq_seq(), + + srh->route_reply(), + srh->rtreq_seq(), + srh->route_reply_len(), + // the dest of the src route + srh->reply_addrs()[0].addr, + srh->reply_addrs()[last_reply_index].addr, + + srh->route_error(), + srh->num_route_errors(), + srh->down_links()[last_err_index].tell_addr, + srh->down_links()[last_err_index].from_addr, + srh->down_links()[last_err_index].to_addr); + + return; + } + sprintf(pt_->buffer() + offset, + "%d [%d %d] [%d %d %d %d->%d] [%d %d %d %d->%d]", + srh->num_addrs(), + + srh->route_request(), + srh->rtreq_seq(), + + srh->route_reply(), + srh->rtreq_seq(), + srh->route_reply_len(), + // the dest of the src route + srh->reply_addrs()[0].addr, + srh->reply_addrs()[last_reply_index].addr, + + srh->route_error(), + srh->num_route_errors(), + srh->down_links()[last_err_index].tell_addr, + srh->down_links()[last_err_index].from_addr, + srh->down_links()[last_err_index].to_addr); +} + +void +CMUTrace::format_msg(Packet *, int) +{ +} + +void +CMUTrace::format_tcp(Packet *p, int offset) +{ + struct hdr_cmn *ch = HDR_CMN(p); + struct hdr_tcp *th = HDR_TCP(p); + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-tcp:s %d -tcp:a %d -tcp:f %d -tcp:o %d ", + th->seqno_, + th->ackno_, + ch->num_forwards(), + ch->opt_num_forwards()); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-Pn tcp -Ps %d -Pa %d -Pf %d -Po %d ", + th->seqno_, + th->ackno_, + ch->num_forwards(), + ch->opt_num_forwards()); + + } else { + sprintf(pt_->buffer() + offset, + "[%d %d] %d %d", + th->seqno_, + th->ackno_, + ch->num_forwards(), + ch->opt_num_forwards()); + } +} + +/* Armando L. Caro Jr. 6/5/2002 + * (with help from Florina Almenrez ) + */ +void +CMUTrace::format_sctp(Packet* p,int offset) +{ + struct hdr_cmn *ch = HDR_CMN(p); + struct hdr_sctp *sh = HDR_SCTP(p); + //struct hdr_ip *ih = HDR_IP(p); + char cChunkType; + + for(u_int i = 0; i < sh->NumChunks(); i++) { + switch(sh->SctpTrace()[i].eType) { + case SCTP_CHUNK_INIT: + case SCTP_CHUNK_INIT_ACK: + case SCTP_CHUNK_COOKIE_ECHO: + case SCTP_CHUNK_COOKIE_ACK: + cChunkType = 'I'; // connection initialization + break; + + case SCTP_CHUNK_DATA: + cChunkType = 'D'; + break; + + case SCTP_CHUNK_SACK: + cChunkType = 'S'; + break; + + case SCTP_CHUNK_FORWARD_TSN: + cChunkType = 'R'; + break; + + case SCTP_CHUNK_HB: + cChunkType = 'H'; + break; + + case SCTP_CHUNK_HB_ACK: + cChunkType = 'B'; + break; + default: + // quiet compiler + cChunkType = ' '; + assert (false); + break; + } + + if( newtrace_ ) { + sprintf(pt_->buffer() + offset, + "-Pn sctp -Pnc %d -Pct %c " + "-Ptsn %d -Psid %d -Pssn %d " + "-Pf %d -Po %d ", + sh->NumChunks(), + cChunkType, + sh->SctpTrace()[i].uiTsn, + sh->SctpTrace()[i].usStreamId, + sh->SctpTrace()[i].usStreamSeqNum, + ch->num_forwards(), + ch->opt_num_forwards()); + } + else { + sprintf(pt_->buffer() + offset, + "[%d %c %d %d %d] %d %d", + sh->NumChunks(), + cChunkType, + sh->SctpTrace()[i].uiTsn, + sh->SctpTrace()[i].usStreamId, + sh->SctpTrace()[i].usStreamSeqNum, + ch->num_forwards(), + ch->opt_num_forwards()); + } + } +} + +void +CMUTrace::format_rtp(Packet *p, int offset) +{ + struct hdr_cmn *ch = HDR_CMN(p); + struct hdr_rtp *rh = HDR_RTP(p); + struct hdr_ip *ih = HDR_IP(p); + Node* thisnode = Node::get_node_by_address(src_); + + //hacking, needs to change later, + int dst = Address::instance().get_nodeaddr(ih->daddr()); + + if (dst == src_){ + // I just received a cbr data packet + if (thisnode->energy_model() && + thisnode->energy_model()->powersavingflag()) { + thisnode->energy_model()->set_node_state(EnergyModel::INROUTE); + } + } + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-cbr:s %d -cbr:f %d -cbr:o %d ", + rh->seqno_, + ch->num_forwards(), + ch->opt_num_forwards()); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-Pn cbr -Pi %d -Pf %d -Po %d ", + rh->seqno_, + ch->num_forwards(), + ch->opt_num_forwards()); + } else { + sprintf(pt_->buffer() + offset, + "[%d] %d %d", + rh->seqno_, + ch->num_forwards(), + ch->opt_num_forwards()); + } +} + +void +CMUTrace::format_imep(Packet *p, int offset) +{ + struct hdr_imep *im = HDR_IMEP(p); + +#define U_INT16_T(x) *((u_int16_t*) &(x)) + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-imep:a %c -imep:h %c -imep:o %c -imep:l %04x ", + (im->imep_block_flags & BLOCK_FLAG_ACK) ? 'A' : '-', + (im->imep_block_flags & BLOCK_FLAG_HELLO) ? 'H' : '-', + (im->imep_block_flags & BLOCK_FLAG_OBJECT) ? 'O' : '-', + U_INT16_T(im->imep_length)); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-P imep -Pa %c -Ph %c -Po %c -Pl 0x%04x ] ", + (im->imep_block_flags & BLOCK_FLAG_ACK) ? 'A' : '-', + (im->imep_block_flags & BLOCK_FLAG_HELLO) ? 'H' : '-', + (im->imep_block_flags & BLOCK_FLAG_OBJECT) ? 'O' : '-', + U_INT16_T(im->imep_length)); + } else { + sprintf(pt_->buffer() + offset, + "[%c %c %c 0x%04x] ", + (im->imep_block_flags & BLOCK_FLAG_ACK) ? 'A' : '-', + (im->imep_block_flags & BLOCK_FLAG_HELLO) ? 'H' : '-', + (im->imep_block_flags & BLOCK_FLAG_OBJECT) ? 'O' : '-', + U_INT16_T(im->imep_length)); + } +#undef U_INT16_T +} + + +void +CMUTrace::format_tora(Packet *p, int offset) +{ + struct hdr_tora *th = HDR_TORA(p); + struct hdr_tora_qry *qh = HDR_TORA_QRY(p); + struct hdr_tora_upd *uh = HDR_TORA_UPD(p); + struct hdr_tora_clr *ch = HDR_TORA_CLR(p); + + switch(th->th_type) { + + case TORATYPE_QRY: + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-tora:t %x -tora:d %d -tora:c QUERY", + qh->tq_type, qh->tq_dst); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-P tora -Pt 0x%x -Pd %d -Pc QUERY ", + qh->tq_type, qh->tq_dst); + + } else { + + sprintf(pt_->buffer() + offset, "[0x%x %d] (QUERY)", + qh->tq_type, qh->tq_dst); + } + break; + + case TORATYPE_UPD: + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-tora:t %x -tora:d %d -tora:a %f -tora:o %d " + "-tora:r %d -tora:e %d -tora:i %d -tora:c UPDATE", + uh->tu_type, + uh->tu_dst, + uh->tu_tau, + uh->tu_oid, + uh->tu_r, + uh->tu_delta, + uh->tu_id); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-P tora -Pt 0x%x -Pd %d (%f %d %d %d %d) -Pc UPDATE ", + uh->tu_type, + uh->tu_dst, + uh->tu_tau, + uh->tu_oid, + uh->tu_r, + uh->tu_delta, + uh->tu_id); + } else { + sprintf(pt_->buffer() + offset, + "-Pt 0x%x -Pd %d -Pa %f -Po %d -Pr %d -Pe %d -Pi %d -Pc UPDATE ", + uh->tu_type, + uh->tu_dst, + uh->tu_tau, + uh->tu_oid, + uh->tu_r, + uh->tu_delta, + uh->tu_id); + } + break; + + case TORATYPE_CLR: + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-tora:t %x -tora:d %d -tora:a %f -tora:o %d " + "-tora:c CLEAR ", + ch->tc_type, + ch->tc_dst, + ch->tc_tau, + ch->tc_oid); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, + "-P tora -Pt 0x%x -Pd %d -Pa %f -Po %d -Pc CLEAR ", + ch->tc_type, + ch->tc_dst, + ch->tc_tau, + ch->tc_oid); + } else { + sprintf(pt_->buffer() + offset, "[0x%x %d %f %d] (CLEAR)", + ch->tc_type, + ch->tc_dst, + ch->tc_tau, + ch->tc_oid); + } + break; + } +} + +void +CMUTrace::format_aodv(Packet *p, int offset) +{ + struct hdr_aodv *ah = HDR_AODV(p); + struct hdr_aodv_request *rq = HDR_AODV_REQUEST(p); + struct hdr_aodv_reply *rp = HDR_AODV_REPLY(p); + + + switch(ah->ah_type) { + case AODVTYPE_RREQ: + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-aodv:t %x -aodv:h %d -aodv:b %d -aodv:d %d " + "-aodv:ds %d -aodv:s %d -aodv:ss %d " + "-aodv:c REQUEST ", + rq->rq_type, + rq->rq_hop_count, + rq->rq_bcast_id, + rq->rq_dst, + rq->rq_dst_seqno, + rq->rq_src, + rq->rq_src_seqno); + } else if (newtrace_) { + + sprintf(pt_->buffer() + offset, + "-P aodv -Pt 0x%x -Ph %d -Pb %d -Pd %d -Pds %d -Ps %d -Pss %d -Pc REQUEST ", + rq->rq_type, + rq->rq_hop_count, + rq->rq_bcast_id, + rq->rq_dst, + rq->rq_dst_seqno, + rq->rq_src, + rq->rq_src_seqno); + + + } else { + + sprintf(pt_->buffer() + offset, + "[0x%x %d %d [%d %d] [%d %d]] (REQUEST)", + rq->rq_type, + rq->rq_hop_count, + rq->rq_bcast_id, + rq->rq_dst, + rq->rq_dst_seqno, + rq->rq_src, + rq->rq_src_seqno); + } + break; + + case AODVTYPE_RREP: + case AODVTYPE_HELLO: + case AODVTYPE_RERR: + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-aodv:t %x -aodv:h %d -aodv:d %d -adov:ds %d " + "-aodv:l %f -aodv:c %s ", + rp->rp_type, + rp->rp_hop_count, + rp->rp_dst, + rp->rp_dst_seqno, + rp->rp_lifetime, + rp->rp_type == AODVTYPE_RREP ? "REPLY" : + (rp->rp_type == AODVTYPE_RERR ? "ERROR" : + "HELLO")); + } else if (newtrace_) { + + sprintf(pt_->buffer() + offset, + "-P aodv -Pt 0x%x -Ph %d -Pd %d -Pds %d -Pl %f -Pc %s ", + rp->rp_type, + rp->rp_hop_count, + rp->rp_dst, + rp->rp_dst_seqno, + rp->rp_lifetime, + rp->rp_type == AODVTYPE_RREP ? "REPLY" : + (rp->rp_type == AODVTYPE_RERR ? "ERROR" : + "HELLO")); + } else { + + sprintf(pt_->buffer() + offset, + "[0x%x %d [%d %d] %f] (%s)", + rp->rp_type, + rp->rp_hop_count, + rp->rp_dst, + rp->rp_dst_seqno, + rp->rp_lifetime, + rp->rp_type == AODVTYPE_RREP ? "REPLY" : + (rp->rp_type == AODVTYPE_RERR ? "ERROR" : + "HELLO")); + } + break; + + default: +#ifdef WIN32 + fprintf(stderr, + "CMUTrace::format_aodv: invalid AODV packet type\n"); +#else + fprintf(stderr, + "%s: invalid AODV packet type\n", __FUNCTION__); +#endif + abort(); + } +} + +// AOMDV patch +void +CMUTrace::format_aomdv(Packet *p, int offset) +{ + struct hdr_aomdv *ah = HDR_AOMDV(p); + struct hdr_aomdv_request *rq = HDR_AOMDV_REQUEST(p); + struct hdr_aomdv_reply *rp = HDR_AOMDV_REPLY(p); + + + switch(ah->ah_type) { + case AOMDVTYPE_RREQ: + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-aomdv:t %x -aomdv:h %d -aomdv:b %d -aomdv:d %d " + "-aomdv:ds %d -aomdv:s %d -aomdv:ss %d " + "-aomdv:c REQUEST ", + rq->rq_type, + rq->rq_hop_count, + rq->rq_bcast_id, + rq->rq_dst, + rq->rq_dst_seqno, + rq->rq_src, + rq->rq_src_seqno); + } else if (newtrace_) { + + sprintf(pt_->buffer() + offset, + "-P aomdv -Pt 0x%x -Ph %d -Pb %d -Pd %d -Pds %d -Ps %d -Pss %d -Pc REQUEST ", + rq->rq_type, + rq->rq_hop_count, + rq->rq_bcast_id, + rq->rq_dst, + rq->rq_dst_seqno, + rq->rq_src, + rq->rq_src_seqno); + + + } else { + + sprintf(pt_->buffer() + offset, + "[0x%x %d %d [%d %d] [%d %d]] (REQUEST)", + rq->rq_type, + rq->rq_hop_count, + rq->rq_bcast_id, + rq->rq_dst, + rq->rq_dst_seqno, + rq->rq_src, + rq->rq_src_seqno); + } + break; + + case AOMDVTYPE_RREP: + case AOMDVTYPE_HELLO: + case AOMDVTYPE_RERR: + + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, + "-aomdv:t %x -aomdv:h %d -aomdv:d %d -admov:ds %d " + "-aomdv:l %f -aomdv:c %s ", + rp->rp_type, + rp->rp_hop_count, + rp->rp_dst, + rp->rp_dst_seqno, + rp->rp_lifetime, + rp->rp_type == AOMDVTYPE_RREP ? "REPLY" : + (rp->rp_type == AOMDVTYPE_RERR ? "ERROR" : + "HELLO")); + } else if (newtrace_) { + + sprintf(pt_->buffer() + offset, + "-P aomdv -Pt 0x%x -Ph %d -Pd %d -Pds %d -Pl %f -Pc %s ", + rp->rp_type, + rp->rp_hop_count, + rp->rp_dst, + rp->rp_dst_seqno, + rp->rp_lifetime, + rp->rp_type == AOMDVTYPE_RREP ? "REPLY" : + (rp->rp_type == AOMDVTYPE_RERR ? "ERROR" : + "HELLO")); + } else { + sprintf(pt_->buffer() + offset, + "[0x%x %d [%d %d] %f] (%s) [%d %d]", + rp->rp_type, + rp->rp_hop_count, + rp->rp_dst, + rp->rp_dst_seqno, + rp->rp_lifetime, + rp->rp_type == AODVTYPE_RREP ? "REPLY" : + (rp->rp_type == AODVTYPE_RERR ? "ERROR" : + "HELLO"), + rp->rp_bcast_id, + rp->rp_first_hop + ); + } + break; + + default: +#ifdef WIN32 + fprintf(stderr, + "CMUTrace::format_aomdv: invalid AODV packet type\n"); +#else + fprintf(stderr, + "%s: invalid AOMDV packet type\n", __FUNCTION__); +#endif + abort(); + } +} + +void +CMUTrace::format_mdart(Packet *p, int offset) +{ + struct hdr_mdart *rh = HDR_MDART(p); + struct hdr_mdart_hello *rhHello = HDR_MDART_HELLO(p); + struct hdr_mdart_darq *rhDarq = HDR_MDART_DARQ(p); + struct hdr_mdart_darp *rhDarp = HDR_MDART_DARP(p); + struct hdr_mdart_daup *rhDaup = HDR_MDART_DAUP(p); + // struct hdr_mdart_encp *rhEncp = HDR_MDART_ENCP(p); + switch(rh->type_) { + case MDART_TYPE_HELLO: + { + bitset tempSetSrcAdd_ (rhHello->srcAdd_); + string tempSrcAdd_ = tempSetSrcAdd_.to_string(); + bitset tempSetDstAdd_ (rhHello->dstAdd_); + string tempDstAdd_ = tempSetDstAdd_.to_string(); + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, "-mdart:t %x -mdart:dAdd %d -mdart:sAdd %d -mdart:sId %d", rhHello->type_, rhHello->dstAdd_, rhHello->srcAdd_, rhHello->srcId_); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, "-type HELLO -srcId %d -srcAdd %s dstAdd %s -seqNum %d", rhHello->srcId_, tempSrcAdd_.c_str(), tempDstAdd_.c_str(), rhHello->seqNum_); + } else { + sprintf(pt_->buffer() + offset, "[0x%x [%d %d] [%d]]", rhHello->type_, rhHello->dstAdd_, rhHello->srcAdd_, rhHello->srcId_); + } + break; + } + case MDART_TYPE_DARQ: + { + bitset tempSetSrcAdd_ (rhDarq->srcAdd_); + string tempSrcAdd_ = tempSetSrcAdd_.to_string(); + bitset tempSetForAdd_ (rhDarq->forAdd_); + string tempForAdd_ = tempSetForAdd_.to_string(); + bitset tempSetDstAdd_ (rhDarq->dstAdd_); + string tempDstAdd_ = tempSetDstAdd_.to_string(); + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, "-mdart:t %x -mdart:dstAdd %d -mdart:srcAdd %d -mdart:dstId %d -mdart:srcId %d -mdart:forAdd %d -mdart:forId %d -mdart:rId %d -mdart:pId %u -mdart:c DARQ", rhDarq->type_, rhDarq->dstAdd_, rhDarq->srcAdd_, rhDarq->dstId_, rhDarq->srcId_, rhDarq->forAdd_, rhDarq->forId_, rhDarq->reqId_, rhDarq->seqNum_);//, rhDarq->reqpktId_); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, "-type DARQ -srcId %d -srcAdd %s -forId %d -forAdd %s -dstId %d dstAdd %s -reqId %d -seqNum %d", rhDarq->srcId_, tempSrcAdd_.c_str(), rhDarq->forId_, tempForAdd_.c_str(), rhDarq->dstId_, tempDstAdd_.c_str(), rhDarq->reqId_, rhDarq->seqNum_); + } else { + sprintf(pt_->buffer() + offset, "[0x%x [%d %d] [%d %d] [%d %d] [%d] %u] (DARQ)", rhDarq->type_, rhDarq->dstAdd_, rhDarq->srcAdd_, rhDarq->dstId_, rhDarq->srcId_, rhDarq->forAdd_, rhDarq->forId_, rhDarq->reqId_, rhDarq->seqNum_);//, rhDarq->reqpktId_); + } + break; + } + case MDART_TYPE_DARP: + { + bitset tempSetSrcAdd_ (rhDarp->srcAdd_); + string tempSrcAdd_ = tempSetSrcAdd_.to_string(); + bitset tempSetForAdd_ (rhDarp->forAdd_); + string tempForAdd_ = tempSetForAdd_.to_string(); + bitset tempSetDstAdd_ (rhDarp->dstAdd_); + string tempDstAdd_ = tempSetDstAdd_.to_string(); + bitset tempSetReqAdd_ (rhDarp->reqAdd_); + string tempReqAdd_ = tempSetReqAdd_.to_string(); + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, "-mdart:t %x -mdart:dAdd %d -mdart:sAdd %d -mdart:dId %d -mdart:sId %d -mdart:fAdd %d -mdart:fId %d -mdart:rAdd %d -mdart:rId %d -mdart:pAId %d mdart:c DARP", rhDarp->type_, rhDarp->dstAdd_, rhDarp->srcAdd_, rhDarp->dstId_, rhDarp->srcId_, rhDarp->forAdd_, rhDarp->forId_, rhDarp->reqAdd_, rhDarp->reqId_, rhDarp->seqNum_);//, rhDarp->reqpktId_); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, "-type DARP -srcId %d -srcAdd %s -forId %d -forAdd %s -dstId %d dstAdd %s -reqId %d -reqAdd %s -seqNum %d", rhDarp->srcId_, tempSrcAdd_.c_str(), rhDarp->forId_, tempForAdd_.c_str(), rhDarp->dstId_, tempDstAdd_.c_str(), rhDarp->reqId_, tempReqAdd_.c_str(), rhDarp->seqNum_); + } else { + sprintf(pt_->buffer() + offset, "[0x%x [%d %d] [%d %d] [%d %d] [%d %d] %u] (DARP)", rhDarp->type_, rhDarp->dstAdd_, rhDarp->srcAdd_, rhDarp->dstId_, rhDarp->srcId_, rhDarp->forAdd_, rhDarp->forId_, rhDarp->reqAdd_, rhDarp->reqId_, rhDarp->seqNum_);//, rhDarp->reqpktId_); + } + break; + } + case MDART_TYPE_DAUP: + { + bitset tempSetSrcAdd_ (rhDaup->srcAdd_); + string tempSrcAdd_ = tempSetSrcAdd_.to_string(); + bitset tempSetForAdd_ (rhDaup->forAdd_); + string tempForAdd_ = tempSetForAdd_.to_string(); + bitset tempSetDstAdd_ (rhDaup->dstAdd_); + string tempDstAdd_ = tempSetDstAdd_.to_string(); + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, "-mdart:t %x -mdart:dAdd %d -mdart:sAdd %d -mdart:dId %d -mdart:sId %d -mdart:fAdd %d -mdart:fId %d -mdart:pAId %u -mdart:c DAUP", rhDaup->type_, rhDaup->dstAdd_, rhDaup->srcAdd_, rhDaup->dstId_, rhDaup->srcId_, rhDaup->forAdd_, rhDaup->forId_, rhDaup->seqNum_); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, "-type DAUP -srcId %d -srcAdd %s -forId %d -forAdd %s -dstId %d dstAdd %s -seqNum %d", rhDaup->srcId_, tempSrcAdd_.c_str(), rhDaup->forId_, tempForAdd_.c_str(), rhDaup->dstId_, tempDstAdd_.c_str(), rhDaup->seqNum_); + } else { + sprintf(pt_->buffer() + offset, "[0x%x [%d %d] [%d %d] [%d %d] %u] (DAUP)", rhDaup->type_, rhDaup->dstAdd_, rhDaup->srcAdd_, rhDaup->dstId_, rhDaup->srcId_, rhDaup->forAdd_, rhDaup->forId_, rhDaup->seqNum_); + } + break; + } + case MDART_TYPE_DABR: + { + bitset tempSetSrcAdd_ (rhDarq->srcAdd_); + string tempSrcAdd_ = tempSetSrcAdd_.to_string(); + bitset tempSetDstAdd_ (rhDarq->dstAdd_); + string tempDstAdd_ = tempSetDstAdd_.to_string(); + if (pt_->tagged()) { + sprintf(pt_->buffer() + offset, "-mdart:t %x -mdart:dAdd %d -mdart:sAdd %d -mdart:dId %d -mdart:sId %d -mdart:fAdd %d -mdart:fId %d -mdart:pAId %u -mdart:c DAUP", rhDaup->type_, rhDaup->dstAdd_, rhDaup->srcAdd_, rhDaup->dstId_, rhDaup->srcId_, rhDaup->forAdd_, rhDaup->forId_, rhDaup->seqNum_); + } else if (newtrace_) { + sprintf(pt_->buffer() + offset, "-type DABR -srcId %d -srcAdd %s dstAdd %s", rhDarq->srcId_, tempSrcAdd_.c_str(), tempDstAdd_.c_str()); + } else { + sprintf(pt_->buffer() + offset, "[0x%x [%d %d] [%d %d] [%d %d] %u] (DAUP)", rhDaup->type_, rhDaup->dstAdd_, rhDaup->srcAdd_, rhDaup->dstId_, rhDaup->srcId_, rhDaup->forAdd_, rhDaup->forId_, rhDaup->seqNum_); + } + break; + } + } +} + +void +CMUTrace::format_protolibmk(Packet *p, int offset) {return;} + +void +CMUTrace::nam_format(Packet *p, int offset) +{ + Node* srcnode = 0 ; + Node* nextnode = 0 ; + struct hdr_cmn *ch = HDR_CMN(p); + struct hdr_ip *ih = HDR_IP(p); + char op = (char) type_; + char colors[32]; + int next_hop = -1 ; + +// change wrt Mike's code + assert(type_ != EOT); + + + + // + + //Actually we only need to handle MAC layer for nam (but should display dropping for other layers) + //if (strcmp(tracename,"MAC") != 0) + //if ((op != 'D')&&(op != 'd')) + // return; + + struct hdr_mac802_11 *mh = HDR_MAC802_11(p); + char ptype[11]; + strcpy(ptype, + ((ch->ptype() == PT_MAC) ? ( + (mh->dh_fc.fc_subtype == MAC_Subtype_RTS) ? "RTS" : + (mh->dh_fc.fc_subtype == MAC_Subtype_CTS) ? "CTS" : + (mh->dh_fc.fc_subtype == MAC_Subtype_ACK) ? "ACK" : + (mh->dh_fc.fc_subtype == MAC_Subtype_Beacon) ? "BCN" : //Beacon + (mh->dh_fc.fc_subtype == MAC_Subtype_AssocReq) ? "ACRQ" : + (mh->dh_fc.fc_subtype == MAC_Subtype_AssocRep) ? "ACRP" : + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_AssoReq) ? "CM1" : //CMD: Association request + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_AssoRsp) ? "CM2" : //CMD: Association response + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_DAssNtf) ? "CM3" : //CMD: Disassociation notification + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_DataReq) ? "CM4" : //CMD: Data request + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_PIDCNtf) ? "CM5" : //CMD: PAN ID conflict notification + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_OrphNtf) ? "CM6" : //CMD: Orphan notification + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_BconReq) ? "CM7" : //CMD: Beacon request + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_CoorRea) ? "CM8" : //CMD: Coordinator realignment + (mh->dh_fc.fc_subtype == MAC_Subtype_Command_GTSReq) ? "CM9" : //CMD: GTS request + "UNKN" + ) : packet_info.name(ch->ptype()))); + // + int dst = Address::instance().get_nodeaddr(ih->daddr()); + + nextnode = Node::get_node_by_address(ch->next_hop_); + if (nextnode) next_hop = nextnode->nodeid(); + + srcnode = Node::get_node_by_address(src_); + + double energy = -1; + double initenergy = -1; + + //default value for changing node color with respect to energy depletion + double l1 = 0.5; + double l2 = 0.2; + + if (srcnode) { + if (srcnode->energy_model()) { + energy = srcnode->energy_model()->energy(); + initenergy = srcnode->energy_model()->initialenergy(); + l1 = srcnode->energy_model()->level1(); + l2 = srcnode->energy_model()->level2(); + } + } + + int energyLevel = 0 ; + double energyLeft = (double)(energy/initenergy) ; + + if ((energyLeft <= 1 ) && (energyLeft >= l1 )) energyLevel = 3; + if ((energyLeft >= l2 ) && (energyLeft < l1 )) energyLevel = 2; + if ((energyLeft > 0 ) && (energyLeft < l2 )) energyLevel = 1; + + if (energyLevel == 0) + strcpy(colors,"-c black -o red"); + else if (energyLevel == 1) + strcpy(colors,"-c red -o yellow"); + else if (energyLevel == 2) + strcpy(colors,"-c yellow -o green"); + else if (energyLevel == 3) + strcpy(colors,"-c green -o black"); + + // A simple hack for scadds demo (fernandez's visit) -- Chalermek + int pkt_color = 0; + if (ch->ptype()==PT_DIFF) { + hdr_cdiff *dfh= HDR_CDIFF(p); + if (dfh->mess_type != DATA) { + pkt_color = 1; + } + } + + // + if (Nam802_15_4::Nam_Status) + { + ATTRIBUTELINK *attr; + int t_src,t_dst; + if (ch->ptype() == PT_MAC) + { + t_src = p802_15_4macSA(p); + t_dst = p802_15_4macDA(p);; + } + else + { + t_src = HDR_IP(p)->saddr(); + t_dst = HDR_IP(p)->daddr(); + } + attr = findAttrLink(HDR_CMN(p)->ptype(),t_src,t_dst); + if (attr == NULL) + attr = findAttrLink(HDR_CMN(p)->ptype()); + if (attr != NULL) + HDR_LRWPAN(p)->attribute = attr->attribute; + else + HDR_LRWPAN(p)->attribute = 0; + if (HDR_LRWPAN(p)->attribute >= 32) + pkt_color = HDR_LRWPAN(p)->attribute; + } + // + + // convert to nam format + if (op == 's') op = 'h' ; + if (op == 'D') op = 'd' ; + if (op == 'h') { + sprintf(pt_->nbuffer(), + "+ -t %.9f -s %d -d %d -p %s -e %d -c 2 -a %d -i %d -k %3s ", + Scheduler::instance().clock(), + src_, // this node + next_hop, + ptype, //packet_info.name(ch->ptype()), + ch->size(), + pkt_color, + ch->uid(), + tracename); + + offset = strlen(pt_->nbuffer()); + pt_->namdump(); + sprintf(pt_->nbuffer() , + "- -t %.9f -s %d -d %d -p %s -e %d -c 2 -a %d -i %d -k %3s", + Scheduler::instance().clock(), + src_, // this node + next_hop, + ptype, //packet_info.name(ch->ptype()), + ch->size(), + pkt_color, + ch->uid(), + tracename); + + offset = strlen(pt_->nbuffer()); + pt_->namdump(); + } + + // if nodes are too far from each other + // nam won't dump SEND event 'cuz it's + // gonna be dropped later anyway + // this value 250 is pre-calculated by using + // two-ray ground refelction model with fixed + // transmission power 3.652e-10 +// if ((type_ == SEND) && (distance > 250 )) return ; + + if(tracetype == TR_ROUTER && type_ == RECV && dst != -1 ) return ; + if(type_ == RECV && dst == -1 )dst = src_ ; //broadcasting event + + if (energy != -1) { //energy model being turned on + if (src_ >= MAX_NODE) { + fprintf (stderr, "node id must be < %d\n", + MAX_NODE); + exit(0); + } + if (nodeColor[src_] != energyLevel ) { //only dump it when node + sprintf(pt_->nbuffer() , //color change + "n -t %.9f -s %d -S COLOR %s", + Scheduler::instance().clock(), + src_, // this node + colors); + offset = strlen(pt_->nbuffer()); + pt_->namdump(); + nodeColor[src_] = energyLevel ; + } + } + + sprintf(pt_->nbuffer() , + "%c -t %.9f -s %d -d %d -p %s -e %d -c 2 -a %d -i %d -k %3s", + op, + Scheduler::instance().clock(), + src_, // this node + next_hop, + ptype, //packet_info.name(ch->ptype()), + ch->size(), + pkt_color, + ch->uid(), + tracename); + +// +if (Nam802_15_4::Nam_Status) +{ + if ((strcmp(tracename, "AGT") != 0) || ((u_int32_t)(ih->daddr()) == IP_BROADCAST)) // + //(doesn't really matter -- seems agent level has no effect on nam) + if (next_hop == -1 && op == 'h') { + // print extra fields for broadcast packets + + // bradius is calculated assuming 2-ray ground reflectlon + // model using default settings of Phy/WirelessPhy and + // Antenna/OmniAntenna + if (bradius == 0.0) calculate_broadcast_parameters(); + + double radius = bradius*radius_scaling_factor_; + + // duration is calculated based on the radius and + // the speed of light (299792458 m/s) + double duration = (bradius/299792458.0)*duration_scaling_factor_; + // + if (Nam802_15_4::Nam_Status) + if (duration < 0.000000001) + duration = 0.000000001; + // + sprintf(pt_->nbuffer() + strlen(pt_->nbuffer()), + " -R %.2f -D %.2f", + radius, + duration); + } +} +// + + offset = strlen(pt_->nbuffer()); + pt_->namdump(); +} + +void CMUTrace::format(Packet* p, const char *why) +{ + hdr_cmn *ch = HDR_CMN(p); + int offset = 0; + + /* + * Log the MAC Header + */ + format_mac_common(p, why, offset); + + if (pt_->namchannel()) + nam_format(p, offset); + offset = strlen(pt_->buffer()); + switch(ch->ptype()) { + case PT_MAC: + case PT_SMAC: + break; + case PT_ARP: + format_arp(p, offset); + break; + default: + format_ip(p, offset); + offset = strlen(pt_->buffer()); + switch(ch->ptype()) { + case PT_AODV: + format_aodv(p, offset); + break; + // AOMDV patch + case PT_AOMDV: + format_aomdv(p, offset); + break; + case PT_TORA: + format_tora(p, offset); + break; + case PT_IMEP: + format_imep(p, offset); + break; + case PT_DSR: + format_dsr(p, offset); + break; + case PT_MESSAGE: + case PT_UDP: + format_msg(p, offset); + break; + case PT_TCP: + case PT_ACK: + format_tcp(p, offset); + break; + case PT_SCTP: + /* Armando L. Caro Jr. 6/5/2002 + */ + format_sctp(p, offset); + break; + case PT_CBR: + format_rtp(p, offset); + break; + case PT_DIFF: + break; + case PT_GAF: + case PT_PING: + break; + default: + + if(pktTrc_ && pktTrc_->format_unknow(p, offset, pt_, newtrace_)) + break; + + /* + fprintf(stderr, "%s - invalid packet type (%s).\n", + __PRETTY_FUNCTION__, packet_info.name(ch->ptype())); + exit(1); + */ + break; //zheng: add + } + } +} + +int +CMUTrace::command(int argc, const char*const* argv) +{ + + if(argc == 3) { + if(strcmp(argv[1], "node") == 0) { + node_ = (MobileNode*) TclObject::lookup(argv[2]); + if(node_ == 0) + return TCL_ERROR; + return TCL_OK; + } + if (strcmp(argv[1], "newtrace") == 0) { + newtrace_ = atoi(argv[2]); + return TCL_OK; + } + } + return Trace::command(argc, argv); +} + +/*ARGSUSED*/ +void +CMUTrace::recv(Packet *p, Handler *h) +{ + if (!node_energy()) { + Packet::free(p); + return; + } + assert(initialized()); + /* + * Agent Trace "stamp" the packet with the optimal route on + * sending. + */ + if (tracetype == TR_AGENT && type_ == SEND) { + God::instance()->stampPacket(p); + } +#if 0 + /* + * When the originator of a packet drops the packet, it may or may + * not have been stamped by GOD. Stamp it before logging the + * information. + */ + if(src_ == src && type_ == DROP) { + God::instance()->stampPacket(p); + } +#endif + format(p, "---"); + pt_->dump(); + //namdump(); + if(target_ == 0) + Packet::free(p); + else + send(p, h); +} + +void +CMUTrace::recv(Packet *p, const char* why) +{ + assert(initialized() && type_ == DROP); + if (!node_energy()) { + Packet::free(p); + return; + } +#if 0 + /* + * When the originator of a packet drops the packet, it may or may + * not have been stamped by GOD. Stamp it before logging the + * information. + */ + if(src_ == ih->saddr()) { + God::instance()->stampPacket(p); + } +#endif + format(p, why); + pt_->dump(); + //namdump(); + Packet::free(p); +} + +int CMUTrace::node_energy() +{ + Node* thisnode = Node::get_node_by_address(src_); + double energy = 1; + if (thisnode) { + if (thisnode->energy_model()) { + energy = thisnode->energy_model()->energy(); + } + } + if (energy > 0) return 1; + return 0; +} + +// +void CMUTrace::calculate_broadcast_parameters() { + // Calculate the maximum distance at which a packet can be received + // based on the two-ray reflection model using the current default + // values for Phy/WirelessPhy and Antenna/OmniAntenna. + + double P_t, P_r, G_t, G_r, h, L; + Tcl& tcl = Tcl::instance(); + + tcl.evalc("Phy/WirelessPhy set Pt_"); + P_t = atof(tcl.result()); + tcl.evalc("Phy/WirelessPhy set RXThresh_"); + P_r = atof(tcl.result()); + tcl.evalc("Phy/WirelessPhy set L_"); + L = atof(tcl.result()); + tcl.evalc("Antenna/OmniAntenna set Gt_"); + G_t = atof(tcl.result()); + tcl.evalc("Antenna/OmniAntenna set Gr_"); + G_r = atof(tcl.result()); + tcl.evalc("Antenna/OmniAntenna set Z_"); + h = atof(tcl.result()); + bradius = pow(P_t*G_r*G_t*pow(h,4.0)/(P_r*L), 0.25); + // + //the above calculation is not accurate for short distance + double PI,freq,lambda,crossover_dist; + PI = 3.14159265359; + tcl.evalc("Phy/WirelessPhy set freq_"); + freq = atof(tcl.result()); + lambda = 3.0e8/freq; + crossover_dist = (4 * PI * h * h) / lambda; + if (bradius < crossover_dist) //need re-calculation + bradius = pow(P_t * G_r * G_t * pow(lambda, 2.0)/(P_r * L), 0.5)/(4 * PI); + // + + // Also get the scaling factors + tcl.evalc("CMUTrace set radius_scaling_factor_"); + radius_scaling_factor_ = atof(tcl.result()); + tcl.evalc("CMUTrace set duration_scaling_factor_"); + duration_scaling_factor_ = atof(tcl.result()); +} +// diff --git a/src/sim/ns/ns235/cmu-trace.h b/src/sim/ns/ns235/cmu-trace.h new file mode 100644 index 0000000..b778e35 --- /dev/null +++ b/src/sim/ns/ns235/cmu-trace.h @@ -0,0 +1,171 @@ +/* -*- Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- + * + * Copyright (c) 1997 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Header: /cvsroot/nsnam/ns-2/trace/cmu-trace.h,v 1.29 2010/05/09 22:28:41 tom_henderson Exp $ + */ + +/* Ported from CMU/Monarch's code, nov'98 -Padma.*/ + +#ifndef __cmu_trace__ +#define __cmu_trace__ + +#include "trace.h" +#include "god.h" + +#ifndef __PRETTY_FUNCTION__ +#define __PRETTY_FUNCTION__ ("") +#endif /* !__PRETTY_FUNCTION__ */ + +/* ====================================================================== + Global Defines + ====================================================================== */ +#define DROP 'D' +#define RECV 'r' +#define SEND 's' +#define FWRD 'f' + +// change wrt Mike's code +#define EOT 'x' + + + +#define TR_ROUTER 0x01 +#define TR_MAC 0x02 +#define TR_IFQ 0x04 +#define TR_AGENT 0x08 + +#define TR_PHY 0x10 + +#define DROP_END_OF_SIMULATION "END" +#define DROP_MAC_COLLISION "COL" +#define DROP_MAC_DUPLICATE "DUP" +#define DROP_MAC_PACKET_ERROR "ERR" +#define DROP_MAC_RETRY_COUNT_EXCEEDED "RET" +#define DROP_MAC_INVALID_STATE "STA" +#define DROP_MAC_BUSY "BSY" +#define DROP_MAC_INVALID_DST "DST" +#define DROP_MAC_SLEEP "SLP" // smac sleep state + +#define DROP_RTR_NO_ROUTE "NRTE" // no route +#define DROP_RTR_ROUTE_LOOP "LOOP" // routing loop +#define DROP_RTR_TTL "TTL" // ttl reached zero +#define DROP_RTR_QFULL "IFQ" // queue full +#define DROP_RTR_QTIMEOUT "TOUT" // packet expired +#define DROP_RTR_MAC_CALLBACK "CBK" // MAC callback +#define DROP_RTR_SALVAGE "SAL" + +#define DROP_IFQ_QFULL "IFQ" // no buffer space in IFQ +#define DROP_IFQ_ARP_FULL "ARP" // dropped by ARP +#define DROP_IFQ_FILTER "FIL" + +#define DROP_OUTSIDE_SUBNET "OUT" // dropped by base stations if received rtg updates from nodes outside its domain. + +#define MAX_ID_LEN 3 +#define MAX_NODE 4096 + +/** + * This class allows a dynamic library to define the tracing format + * for newly defined packet types + * + */ +class PacketTracer +{ + public: + PacketTracer(); + virtual ~PacketTracer(); + void setNext(PacketTracer *next); + PacketTracer *getNext(); + int format_unknow(Packet *p, int offset, BaseTrace *pt_, int newtrace); + protected: + virtual int format(Packet *p, int offset, BaseTrace *pt_, int newtrace) = 0; //return 0 if the packet is unknown + PacketTracer *next_; +}; + + +class CMUTrace : public Trace { +public: + CMUTrace(const char *s, char t); + void recv(Packet *p, Handler *h); + void recv(Packet *p, const char* why); + + static void addPacketTracer(PacketTracer *pt); + + +private: + char tracename[MAX_ID_LEN + 1]; + int nodeColor[MAX_NODE]; + int tracetype; + MobileNode *node_; + int newtrace_; + + // + static double bradius; + static double radius_scaling_factor_; + static double duration_scaling_factor_; + static void calculate_broadcast_parameters(); + // + + int initialized() { return node_ && 1; } + int node_energy(); + int command(int argc, const char*const* argv); + void format(Packet *p, const char *why); + + void nam_format(Packet *p, int offset); + + void format_phy(Packet *p, int offset); + + void format_mac_common(Packet *p, const char *why, int offset); + void format_mac(Packet *p, int offset); + void format_smac(Packet *p, int offset); + void format_ip(Packet *p, int offset); + + void format_arp(Packet *p, int offset); + void format_hdlc(Packet *p, int offset); + void format_dsr(Packet *p, int offset); + void format_msg(Packet *p, int offset); + void format_tcp(Packet *p, int offset); + void format_sctp(Packet *p, int offset); + void format_rtp(Packet *p, int offset); + void format_tora(Packet *p, int offset); + void format_imep(Packet *p, int offset); + void format_aodv(Packet *p, int offset); + void format_aomdv(Packet *p, int offset); + void format_mdart(Packet *p, int offset); + void format_protolibmk(Packet *p, int offset); + + // This holds all the tracers added at run-time + static PacketTracer *pktTrc_; + +}; + +#endif /* __cmu_trace__ */ diff --git a/src/sim/ns/ns235/ns-lib.tcl b/src/sim/ns/ns235/ns-lib.tcl new file mode 100644 index 0000000..cfb72b7 --- /dev/null +++ b/src/sim/ns/ns235/ns-lib.tcl @@ -0,0 +1,2307 @@ +# -*- Mode:tcl; tcl-indent-level:8; tab-width:8; indent-tabs-mode:t -*- +# +# Copyright (c) 1996 Regents of the University of California. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the MASH Research +# Group at the University of California Berkeley. +# 4. Neither the name of the University nor of the Research Group may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +# @(#) $Header: /cvsroot/nsnam/ns-2/tcl/lib/ns-lib.tcl,v 1.279 2010/05/09 22:28:41 tom_henderson Exp $ + + +# +# Word of warning to developers: +# this code (and all it sources) is compiled into the +# ns executable. You need to rebuild ns or explicitly +# source this code to see changes take effect. +# + + + +proc warn {msg} { + global warned_ + if {![info exists warned_($msg)]} { + puts stderr "warning: $msg" + set warned_($msg) 1 + } +} + +if {[info commands debug] == ""} { + proc debug args { + warn {Script debugging disabled. Reconfigure with --with-tcldebug, and recompile.} + } +} + +proc assert args { + if [catch "expr $args" ret] { + set ret [eval expr $args] + } + if {! $ret} { + error "assertion failed: $args" + } +} + +proc find-max list { + set max 0 + foreach val $list { + if {$val > $max} { + set max $val + } + } + return $max +} + +proc bw_parse { bspec } { + if { [scan $bspec "%f%s" b unit] == 1 } { + set unit bps + } + regsub {[/p]s(ec)?$} $unit {} unit + if [string match {*B} $unit] { + set b [expr $b*8] + set unit "[string trimright $unit B]b" + } + switch $unit { + b { return $b } + kb { return [expr $b*1000] } + Mb { return [expr $b*1000000] } + Gb { return [expr $b*1000000000] } + default { + puts "error: bw_parse: unknown unit `$unit'" + exit 1 + } + } +} + +proc time_parse { spec } { + if { [scan $spec "%f%s" t unit] == 1 } { + set unit s + } + regsub {sec$} $unit {s} unit + switch $unit { + s { return $t } + ms { return [expr $t*1e-3] } + us { return [expr $t*1e-6] } + ns { return [expr $t*1e-9] } + ps { return [expr $t*1e-12] } + default { + puts "error: time_parse: unknown unit `$unit'" + exit 1 + } + } +} + +proc delay_parse { spec } { + return [time_parse $spec] +} + + +# +# Create the core OTcl class called "Simulator". +# This is the principal interface to the simulation engine. +# +#Class Simulator + +# +# XXX Whenever you modify the source list below, please also change the +# OTcl script dependency list in Makefile.in +# +source ns-autoconf.tcl +source ns-address.tcl +source ns-node.tcl +source ns-rtmodule.tcl +source ns-hiernode.tcl +source ns-mobilenode.tcl +source ns-bsnode.tcl +source ns-link.tcl +source ns-source.tcl +source ns-compat.tcl +source ns-packet.tcl +source ns-queue.tcl +source ns-trace.tcl +source ns-random.tcl +source ns-agent.tcl +source ns-route.tcl +source ns-errmodel.tcl +source ns-intserv.tcl +source ns-cmutrace.tcl +source ns-mip.tcl +source ns-sat.tcl +#source ns-nix.tcl +source ns-diffusion.tcl +source ../rtp/session-rtp.tcl +source ../interface/ns-iface.tcl +source ../lan/ns-mac.tcl + +# Added by Sushmita to support event tracing for mac-simple and 802.11 +source ../lan/ns-mac-simple.tcl +source ../lan/ns-mac-802_11.tcl + +source ../lan/ns-ll.tcl +source ../lan/vlan.tcl +source ../lan/abslan.tcl +source ../mcast/timer.tcl +source ../mcast/ns-mcast.tcl +source ns-srcrt.tcl +source ../mcast/McastProto.tcl +source ../mcast/DM.tcl +source ../ctr-mcast/CtrMcast.tcl +source ../ctr-mcast/CtrMcastComp.tcl +source ../ctr-mcast/CtrRPComp.tcl +source ../mcast/BST.tcl +source ../mcast/srm.tcl +source ../mcast/srm-ssm.tcl +# These files removed due to licensing conflicts +# source ../mcast/mftp_snd.tcl +# source ../mcast/mftp_rcv.tcl +# source ../mcast/mftp_rcv_stat.tcl +source ../mcast/McastMonitor.tcl +source ../rlm/rlm.tcl +source ../rlm/rlm-ns.tcl +source ../session/session.tcl +source ../webcache/http-server.tcl +source ../webcache/http-cache.tcl +source ../webcache/http-agent.tcl +source ../webcache/http-mcache.tcl +source ../webcache/webtraf.tcl +source ../webcache/empweb.tcl +source ns-namsupp.tcl +source ../mobility/dsdv.tcl +source ../mobility/dsr.tcl +source ../mobility/com.tcl + +source ../plm/plm.tcl +source ../plm/plm-ns.tcl +source ../plm/plm-topo.tcl + +# MPLS +source ../mpls/ns-mpls-simulator.tcl +source ../mpls/ns-mpls-node.tcl +source ../mpls/ns-mpls-ldpagent.tcl +source ../mpls/ns-mpls-classifier.tcl + +source ns-default.tcl +source ../emulate/ns-emulate.tcl + +#pushback +source ns-pushback.tcl + +# PGM +#source ../pgm/ns-pgm.tcl + +#LMS +source ../mcast/ns-lms.tcl + +# STL dependent modules get included +# ONLY when STL is found + +if {[ns-hasSTL] == 1} { +source ns-nix.tcl +source ../pgm/ns-pgm.tcl +source ../rtglib/ns-rtProtoLS.tcl +source ../delaybox/delaybox.tcl +source ../packmime/packmime.tcl +} + +source ns-qsnode.tcl + +# Obsolete modules +#source ns-wireless-mip.tcl +#source ns-nam.tcl + +Simulator instproc init args { + + # Debojyoti added this for asim + + $self instvar useasim_ + $self instvar slinks_ + $self instvar nconn_ + $self instvar sflows_ + $self instvar nsflows_ + + set slinks_(0:0) 0 + set nconn_ 0 + set conn_ "" + # for short flows stuff + set sflows_ "" + set nsflows_ 0 + set useasim_ 0 + + $self create_packetformat + $self use-scheduler Calendar + #$self use-scheduler List + $self set nullAgent_ [new Agent/Null] + $self set-address-format def + if {[lindex $args 0] == "-multicast"} { + $self multicast $args + } + eval $self next $args +} + +Simulator instproc nullagent {} { + $self instvar nullAgent_ + return $nullAgent_ +} + +Simulator instproc use-scheduler type { + $self instvar scheduler_ + if [info exists scheduler_] { + if { [$scheduler_ info class] == "Scheduler/$type" } { + return + } else { + delete $scheduler_ + } + } + set scheduler_ [new Scheduler/$type] + $scheduler_ now +} + +Simulator instproc delay_parse { spec } { + return [time_parse $spec] +} + +Simulator instproc bw_parse { spec } { + return [bw_parse $spec] +} + +# +# A simple method to wrap any object around +# a trace object that dumps to stdout +# +Simulator instproc dumper obj { + set t [$self alloc-trace hop stdout] + $t target $obj + return $t +} + +# New node structure +# +# Add APT to support multi-interface: user can specified multiple channels +# when config nod. Still need modifications in routing agents to make +# multi-interfaces really work. -chen xuan 07/21/00 +# +# Define global node configuration +# $ns_ node-config -addressType flat/hierarchical +# -adhocRouting DSDV/DSR/TORA +# -llType +# -macType +# -propType +# -ifqType +# -ifqLen +# -phyType +# -antType +# -channel +# -channelType +# -topologyInstance +# -wiredRouting ON/OFF +# -mobileIP ON/OFF +# -energyModel "EnergyModel" +# -initialEnergy (in Joules) +# -rxPower (in W) +# -txPower (in W) +# -idlePower (in W) +# +# -sleepPower (in W) +# -sleepTime (in sec indicating when the node can start sleeping) +# -agentTrace ON +# -routerTrace ON +# -macTrace OFF +# -phyTrace OFF +# -toraDebug OFF +# -movementTrace OFF +# change wrt Mike's code +# -eotTrace OFF +# -diffusionFilter "GradientFilter/OnePhasePullFilter/GeoRoutingFilter/RmstFilter/SourceRouteFilter/LogFilter/TagFilter" + + +Simulator instproc addressType {val} { $self set addressType_ $val } +Simulator instproc adhocRouting {val} { $self set routingAgent_ $val } +Simulator instproc llType {val} { $self set llType_ $val } +Simulator instproc macType {val} { $self set macType_ $val } +Simulator instproc propType {val} { $self set propType_ $val } +Simulator instproc propInstance {val} { $self set propInstance_ $val } +Simulator instproc ifqType {val} { $self set ifqType_ $val } +Simulator instproc ifqLen {val} { $self set ifqlen_ $val } +Simulator instproc phyType {val} { $self set phyType_ $val } +Simulator instproc antType {val} { $self set antType_ $val } +Simulator instproc channel {val} {$self set channel_ $val} +Simulator instproc channelType {val} {$self set channelType_ $val} +Simulator instproc topoInstance {val} {$self set topoInstance_ $val} +Simulator instproc wiredRouting {val} {$self set wiredRouting_ $val} +Simulator instproc mobileIP {val} {$self set mobileIP_ $val} +Simulator instproc energyModel {val} { $self set energyModel_ $val } +Simulator instproc initialEnergy {val} { $self set initialEnergy_ $val } +Simulator instproc txPower {val} { $self set txPower_ $val } +Simulator instproc rxPower {val} { $self set rxPower_ $val } +Simulator instproc idlePower {val} { $self set idlePower_ $val } +# +Simulator instproc sleepPower {val} { $self set sleepPower_ $val } +Simulator instproc sleepTime {val} { $self set sleepTime_ $val } +Simulator instproc transitionPower {val} { $self set transitionPower_ $val } +Simulator instproc transitionTime {val} { $self set transitionTime_ $val } +# +Simulator instproc IncomingErrProc {val} { $self set inerrProc_ $val } +Simulator instproc OutgoingErrProc {val} { $self set outerrProc_ $val } +Simulator instproc FECProc {val} { $self set FECProc_ $val } +Simulator instproc agentTrace {val} { $self set agentTrace_ $val } +Simulator instproc routerTrace {val} { $self set routerTrace_ $val } +Simulator instproc macTrace {val} { $self set macTrace_ $val } +Simulator instproc phyTrace {val} { $self set phyTrace_ $val } +Simulator instproc movementTrace {val} { $self set movementTrace_ $val } +Simulator instproc toraDebug {val} {$self set toraDebug_ $val } +Simulator instproc satNodeType {val} {$self set satNodeType_ $val} +Simulator instproc downlinkBW {val} {$self set downlinkBW_ $val} +Simulator instproc stopTime {val} {$self set stopTime_ $val} + +# This method is needed so that new Routing Agents can be implemented in a dynamic +# library and used without having to modify Simulator::create-wireless-node +Simulator instproc rtAgentFunction {val} {$self set rtAgentFunction_ $val} + + +# change wrt Mike's code +Simulator instproc eotTrace {val} { $self set eotTrace_ $val } +Simulator instproc diffusionFilter {val} {$self set diffFilter_ $val} + +Simulator instproc MPLS { val } { + if { $val == "ON" } { + Node enable-module "MPLS" + } else { + Node disable-module "MPLS" + } +} + + +Simulator instproc PGM { val } { + if { $val == "ON" } { + Node enable-module "PGM" + } else { + Node disable-module "PGM" + } +} +Simulator instproc LMS { val } { + if { $val == "ON" } { + Node enable-module "LMS" + } else { + Node disable-module "LMS" + } +} + +Simulator instproc get-nodetype {} { + $self instvar addressType_ routingAgent_ wiredRouting_ + set val "" + + if { [info exists addressType_] && $addressType_ == "hierarchical" } { + set val Hier + } + if { [info exists routingAgent_] && $routingAgent_ != "" } { + set val Mobile + } + if { [info exists wiredRouting_] && $wiredRouting_ == "ON" } { + set val Base + } + if { [info exists wiredRouting_] && $wiredRouting_ == "OFF"} { + set val Base + } + if { [Simulator set mobile_ip_] } { + if { $val == "Base" && $wiredRouting_ == "ON" } { + set val MIPBS + } + if { $val == "Base" && $wiredRouting_ == "OFF" } { + set val MIPMH + } + } + return $val +} + +Simulator instproc node-config args { + # Object::init-vars{} is defined in ~tclcl/tcl-object.tcl. + # It initializes all default variables in the following way: + # 1. Look for pairs of {-cmd val} in args + # 2. If "$self $cmd $val" is not valid then put it in a list of + # arguments to be returned to the caller. + # + # Since we do not handle undefined {-cmd val} pairs, we ignore + # return value from init-vars{}. + set args [eval $self init-vars $args] + + $self instvar addressType_ routingAgent_ propType_ macTrace_ \ + routerTrace_ agentTrace_ movementTrace_ channelType_ channel_ \ + chan topoInstance_ propInstance_ mobileIP_ \ + rxPower_ txPower_ idlePower_ sleepPower_ sleepTime_ transitionPower_ \ + transitionTime_ satNodeType_ eotTrace_ phyTrace_ + + if [info exists phyTrace_] { + Simulator set PhyTrace_ $phyTrace_ + } + + + if [info exists macTrace_] { + Simulator set MacTrace_ $macTrace_ + } + if [info exists routerTrace_] { + Simulator set RouterTrace_ $routerTrace_ + } + if [info exists agentTrace_] { + Simulator set AgentTrace_ $agentTrace_ + } + if [info exists movementTrace_] { + Simulator set MovementTrace_ $movementTrace_ + } + + # change wrt Mike's code + if [info exists eotTrace_] { + Simulator set EotTrace_ $eotTrace_ + } + + # hacking for matching old cmu add-interface + # not good style, for back-compability ONLY + # + # Only create 1 instance of prop + if {[info exists propInstance_]} { + if {[info exists propType_] && [Simulator set propInstCreated_] == 0} { + warn "Both propType and propInstance are set. propType is ignored." + } + } else { + if {[info exists propType_]} { + set propInstance_ [new $propType_] + Simulator set propInstCreated_ 1 + } + } + + # Add multi-interface support: + # User can only specify either channelType_ (single_interface as + # before) or channel_ (multi_interface) + # If both variables are specified, error! + if {[info exists channelType_] && [info exists channel_]} { + error "Can't specify both channel and channelType, error!" + } elseif {[info exists channelType_] && ![info exists satNodeType_]} { + # Single channel, single interface + warn "Please use -channel as shown in tcl/ex/wireless-mitf.tcl" + if {![info exists chan]} { + set chan [new $channelType_] + } + } elseif {[info exists channel_]} { + # Multiple channel, multiple interfaces + set chan $channel_ + } + if [info exists topoInstance_] { + $propInstance_ topography $topoInstance_ + } + # set address type, hierarchical or expanded + if {[string compare $addressType_ ""] != 0} { + $self set-address-format $addressType_ + } + # set mobileIP flag + if { [info exists mobileIP_] && $mobileIP_ == "ON"} { + Simulator set mobile_ip_ 1 + } else { + if { [info exists mobileIP_] } { + Simulator set mobile_ip_ 0 + } + } +} + +# Default behavior is changed: consider nam as not initialized if +# no shape OR color parameter is given +Simulator instproc node args { + $self instvar Node_ routingAgent_ wiredRouting_ satNodeType_ + if { [Simulator info vars EnableMcast_] != "" } { + warn "Flag variable Simulator::EnableMcast_ discontinued.\n\t\ + Use multicast methods as:\n\t\t\ + % set ns \[new Simulator -multicast on]\n\t\t\ + % \$ns multicast" + $self multicast + Simulator unset EnableMcast_ + } + if { [Simulator info vars NumberInterfaces_] != "" } { + warn "Flag variable Simulator::NumberInterfaces_ discontinued.\n\t\ + Setting this variable will not affect simulations." + Simulator unset NumberInterfaces_ + } + + # Satellite node + if { [info exists satNodeType_] } { + set node [eval $self create-satnode] + #simulator's nodelist in C++ space + if {[info exists wiredRouting_] && $wiredRouting_ == "ON"} { + # add node to simulator's nodelist in C++ space + $self add-node $node [$node id] + # Want to keep global state of wiredRouting info + SatRouteObject set wiredRouting_ true + } + return $node + } + + # wireless-ready node + if { [info exists routingAgent_] && ($routingAgent_ != "") } { + set node [eval $self create-wireless-node $args] + # for base node + if {[info exists wiredRouting_] && $wiredRouting_ == "ON"} { + set Node_([$node id]) $node + #simulator's nodelist in C++ space + $self add-node $node [$node id] + } + return $node + } + + + # Enable-mcast is now done automatically inside Node::init{} + # + # XXX node_factory_ is deprecated, HOWEVER, since it's still used by + # mobile IP, algorithmic routing, manual routing, and backward + # compability tests of hierarchical routing, we should keep it around + # before all related code are wiped out. + set node [eval new [Simulator set node_factory_] $args] + set Node_([$node id]) $node + + #add to simulator's nodelist in C++ space + $self add-node $node [$node id] + + #set the nodeid in c++ Node - ratul + $node nodeid [$node id] + + $node set ns_ $self + $self check-node-num + return $node +} + +# XXX This is stupid hack. When old code (not using node-config) is used, +# create-wireless-node{} will not be called, and IMEPFlag_ will remain empty +# (as set in ns-default.tcl), then Node/MobileNode will use global proc +# cmu-trace to create trace objects; otherwise mobility-trace{} will be +# triggered. +Simulator instproc imep-support {} { + return [Simulator set IMEPFlag_] +} + +# XXX This should be moved into the node initialization procedure instead +# of standing here in ns-lib.tcl. +Simulator instproc create-wireless-node args { + $self instvar routingAgent_ wiredRouting_ propInstance_ llType_ \ + macType_ ifqType_ ifqlen_ phyType_ chan antType_ \ + energyModel_ initialEnergy_ txPower_ rxPower_ \ + idlePower_ sleepPower_ sleepTime_ transitionPower_ transitionTime_ \ + topoInstance_ level1_ level2_ inerrProc_ outerrProc_ FECProc_ rtAgentFunction_ + + Simulator set IMEPFlag_ OFF + + # create node instance + set node [eval $self create-node-instance $args] + + # basestation address setting + if { [info exist wiredRouting_] && $wiredRouting_ == "ON" } { + $node base-station [AddrParams addr2id [$node node-addr]] + } + if {$rtAgentFunction_ != ""} { + set ragent [$self $rtAgentFunction_ $node] + } else { + switch -exact $routingAgent_ { + DSDV { + set ragent [$self create-dsdv-agent $node] + } + DSR { + $self at 0.0 "$node start-dsr" + } + AODV { + set ragent [$self create-aodv-agent $node] + } + AOMDV { + set ragent [$self create-aomdv-agent $node] + } + MDART { + set ragent [$self create-mdart-agent $node] + } + PUMA { + set ragent [$self create-puma-agent $node] + } + TORA { + Simulator set IMEPFlag_ ON + set ragent [$self create-tora-agent $node] + } + ProtolibMK { + set ragent [$self create-protolibmk-agent $node] + } + DIFFUSION/RATE { + eval $node addr $args + set ragent [$self create-diffusion-rate-agent $node] + } + DIFFUSION/PROB { + eval $node addr $args + set ragent [$self create-diffusion-probability-agent $node] + } + Directed_Diffusion { + eval $node addr $args + set ragent [$self create-core-diffusion-rtg-agent $node] + } + FLOODING { + eval $node addr $args + set ragent [$self create-flooding-agent $node] + } + OMNIMCAST { + eval $node addr $args + set ragent [$self create-omnimcast-agent $node] + } + DumbAgent { + set ragent [$self create-dumb-agent $node] + } + ManualRtg { + set ragent [$self create-manual-rtg-agent $node] + } + default { + eval $node addr $args + puts "Wrong node routing agent!" + exit + } + } + } + + # errProc_ and FECProc_ are an option unlike other + # parameters for node interface + if ![info exist inerrProc_] { + set inerrProc_ "" + } + if ![info exist outerrProc_] { + set outerrProc_ "" + } + if ![info exist FECProc_] { + set FECProc_ "" + } + + + + # Add main node interface + $node add-interface $chan $propInstance_ $llType_ $macType_ \ + $ifqType_ $ifqlen_ $phyType_ $antType_ $topoInstance_ \ + $inerrProc_ $outerrProc_ $FECProc_ + # Attach agent + if {$routingAgent_ != "DSR"} { + $node attach $ragent [Node set rtagent_port_] + } + if {$routingAgent_ == "DIFFUSION/RATE" || + $routingAgent_ == "DIFFUSION/PROB" || + $routingAgent_ == "FLOODING" || + $routingAgent_ == "OMNIMCAST" || + $routingAgent_ == "Directed_Diffusion" } { + $ragent port-dmux [$node demux] + $node instvar ll_ + $ragent add-ll $ll_(0) + } + if { $routingAgent_ == "DumbAgent" } { + $ragent port-dmux [$node demux] + } + + + # Bind routing agent and mip agent if existing basestation + # address setting + if { [info exist wiredRouting_] && $wiredRouting_ == "ON" } { + if { $routingAgent_ != "DSR" } { + $node mip-call $ragent + } + } + # + # This Trace Target is used to log changes in direction + # and velocity for the mobile node. + # + set tracefd [$self get-ns-traceall] + if {$tracefd != "" } { + $node nodetrace $tracefd + $node agenttrace $tracefd + } + set namtracefd [$self get-nam-traceall] + if {$namtracefd != "" } { + $node namattach $namtracefd + } + if [info exists energyModel_] { + if [info exists level1_] { + set l1 $level1_ + } else { + set l1 0.5 + } + if [info exists level2_] { + set l2 $level2_ + } else { + set l2 0.2 + } + $node addenergymodel [new $energyModel_ $node \ + $initialEnergy_ $l1 $l2] + } + if [info exists txPower_] { + $node setPt $txPower_ + } + if [info exists rxPower_] { + $node setPr $rxPower_ + } + if [info exists idlePower_] { + $node setPidle $idlePower_ + } +# + if [info exists sleepPower_] { + $node setPsleep $sleepPower_ + } + if [info exists sleepTime_] { + $node setTSleep $sleepTime_ + } + if [info exists transitionPower_] { + $node setPtransition $transitionPower_ + } + if [info exists transitionTime_] { + $node setTtransition $transitionTime_ + } +# + $node topography $topoInstance_ + + return $node +} + +Simulator instproc create-node-instance args { + $self instvar routingAgent_ + # DSR is a special case + if {$routingAgent_ == "DSR"} { + set nodeclass [$self set-dsr-nodetype] + } else { + set nodeclass Node/MobileNode + } + return [eval new $nodeclass $args] +} + +Simulator instproc set-dsr-nodetype {} { + $self instvar wiredRouting_ + set nodetype SRNodeNew + # MIP mobilenode + if [Simulator set mobile_ip_] { + set nodetype SRNodeNew/MIPMH + } + # basestation dsr node + if { [info exists wiredRouting_] && $wiredRouting_ == "ON"} { + set nodetype Node/MobileNode/BaseStationNode + } + return $nodetype +} + +Simulator instproc create-tora-agent { node } { + set ragent [new Agent/TORA [$node id]] + $node set ragent_ $ragent + return $ragent +} + +Simulator instproc create-dsdv-agent { node } { + # Create a dsdv routing agent for this node + set ragent [new Agent/DSDV] + # Setup address (supports hier-addr) for dsdv agent + # and mobilenode + set addr [$node node-addr] + $ragent addr $addr + $ragent node $node + if [Simulator set mobile_ip_] { + $ragent port-dmux [$node demux] + } + $node addr $addr + $node set ragent_ $ragent + $self at 0.0 "$ragent start-dsdv" ;# start updates + return $ragent +} + +Simulator instproc create-protolibmk-agent { node } { + # create a dummie wireless agent + # it will forward packets up to protolib manet + # and just act as a wedge into ns + # used by protolib wireless manets + set ragent [new Agent/ProtolibMK [$node node-addr]] + $node set ragent_ $ragent + return $ragent +} + +Simulator instproc create-dumb-agent { node } { + + # create a simple wireless agent + # that only forwards packets + # used for testing single hop brdcast/unicast mode + # for wireless macs + + set ragent [new Agent/DumbAgent] + $node set ragent_ $ragent + + return $ragent +} + +Simulator instproc create-manual-rtg-agent { node } { + + # create a simple wireless agent + # that only forwards packets + # used for testing single hop brdcast/unicast mode + # for wireless macs + + set ragent [new Agent/ManualRtgAgent] + $node set ragent_ $ragent + $node attach $ragent [Node set rtagent_port_] + + return $ragent +} + +Simulator instproc create-aodv-agent { node } { + # Create AODV routing agent + set ragent [new Agent/AODV [$node node-addr]] + $self at 0.0 "$ragent start" ;# start BEACON/HELLO Messages + $node set ragent_ $ragent + return $ragent +} + +# AOMDV patch +Simulator instproc create-aomdv-agent { node } { + set ragent [new Agent/AOMDV [$node node-addr]] + $self at 0.0 "$ragent start" + $node set ragent_ $ragent + return $ragent +} + +Simulator instproc create-puma-agent { node } { + # Create PUMA routing agent + set ragent [new Agent/PUMA [$node node-addr]] + $self at 0.0 "$ragent start" + $node set ragent_ $ragent + return $ragent +} + +Simulator instproc create-mdart-agent { node } { + # Create M-DART routing agent + set ragent [new Agent/MDART [$node node-addr]] + $self at 0.0 "$ragent start" ;# start BEACON/HELLO Messages + $node set ragent_ $ragent + return $ragent +} + +Simulator instproc use-newtrace {} { + Simulator set WirelessNewTrace_ 1 +} + +Simulator instproc use-taggedtrace { {tag ON} } { + Simulator set TaggedTrace_ $tag +} + +Simulator instproc hier-node haddr { + error "hier-nodes should be created with [$ns_ node $haddr]" +} + +Simulator instproc now {} { + $self instvar scheduler_ + return [$scheduler_ now] +} + +Simulator instproc at args { + $self instvar scheduler_ + return [eval $scheduler_ at $args] +} + +Simulator instproc at-now args { + $self instvar scheduler_ + return [eval $scheduler_ at-now $args] +} + +Simulator instproc cancel args { + $self instvar scheduler_ + return [eval $scheduler_ cancel $args] +} + +Simulator instproc after {ival args} { + eval $self at [expr [$self now] + $ival] $args +} + +# +# check if total num of nodes exceed 2 to the power n +# where +# +Simulator instproc check-node-num {} { + if {[Node set nn_] > [expr pow(2, [AddrParams nodebits])]} { + error "Number of nodes exceeds node-field-size of [AddrParams nodebits] bits" + } +} + +# +# Check if number of items at each hier level (num of nodes, or clusters or +# domains) exceed size of that hier level field size (in bits). should be +# modified to support n-level of hierarchies +# +Simulator instproc chk-hier-field-lengths {} { + AddrParams instvar domain_num_ cluster_num_ nodes_num_ + if [info exists domain_num_] { + if {[expr $domain_num_ - 1]> [AddrParams NodeMask 1]} { + error "\# of domains exceed dom-field-size " + } + } + if [info exists cluster_num_] { + set maxval [expr [find-max $cluster_num_] - 1] + if {$maxval > [expr pow(2, [AddrParams NodeMask 2])]} { + error "\# of clusters exceed clus-field-size " + } + } + if [info exists nodes_num_] { + set maxval [expr [find-max $nodes_num_] -1] + if {$maxval > [expr pow(2, [AddrParams NodeMask 3])]} { + error "\# of nodess exceed node-field-size" + } + } +} + + +Simulator instproc check-smac {} { + $self instvar macType_ + if { [info exist macType_] && $macType_ == "Mac/SMAC" } { + if { [$macType_ set syncFlag_] } { + puts "\nNOTE: SMAC is running with sleep-wakeup cycles on. Please make sure to run yr applications AFTER the nodes get sync'ed which is about 40sec for the default settings.\n" + } + } +} + + +Simulator instproc run {} { + # NIXVECTOR? + # global runstart + # set runstart [clock seconds] + $self check-smac ;# print warning if in sleep/wakeup cycle + $self check-node-num + $self rtmodel-configure ;# in case there are any + [$self get-routelogic] configure + $self instvar scheduler_ Node_ link_ started_ + + set started_ 1 + + # + # Reset every node, which resets every agent. + # + + foreach nn [array names Node_] { + $Node_($nn) reset + # GFR Additions for NixVector Routing + if { [Simulator set nix-routing] } { + $Node_($nn) populate-objects + } + } + + # + # Also reset every queue + # + + foreach qn [array names link_] { + set q [$link_($qn) queue] + $q reset + } + + # Do all nam-related initialization here + $self init-nam + + # NIXVECTOR xxx? + # global simstart + # set simstart [clock seconds] + return [$scheduler_ run] +} + +# johnh xxx? +Simulator instproc log-simstart { } { + # GFR Modification to log actual start + global simstart + puts "Starting Actual Simulation" + set simstart [clock seconds] +} + +Simulator instproc halt {} { + $self instvar scheduler_ + #puts "time: [clock format [clock seconds] -format %X]" + $scheduler_ halt +} + +Simulator instproc dumpq {} { + $self instvar scheduler_ + $scheduler_ dumpq +} + +Simulator instproc is-started {} { + $self instvar started_ + return [info exists started_] +} + +Simulator instproc clearMemTrace {} { + $self instvar scheduler_ + $scheduler_ clearMemTrace +} + +Simulator instproc simplex-link { n1 n2 bw delay qtype args } { + $self instvar link_ queueMap_ nullAgent_ useasim_ + set sid [$n1 id] + set did [$n2 id] + + # Debo + if { $useasim_ == 1 } { + set slink_($sid:$did) $self + } + + if [info exists queueMap_($qtype)] { + set qtype $queueMap_($qtype) + } + # construct the queue + set qtypeOrig $qtype + switch -exact $qtype { + ErrorModule { + if { [llength $args] > 0 } { + set q [eval new $qtype $args] + } else { + set q [new $qtype Fid] + } + } + intserv { + set qtype [lindex $args 0] + set q [new Queue/$qtype] + } + default { + if { [llength $args] == 0} { + set q [new Queue/$qtype] + } else { + set q [new Queue/$qtype $args] + } + } + } + # Now create the link + switch -exact $qtypeOrig { + RTM { + set c [lindex $args 1] + set link_($sid:$did) [new CBQLink \ + $n1 $n2 $bw $delay $q $c] + } + CBQ - + CBQ/WRR { + # assume we have a string of form "linktype linkarg" + if {[llength $args] == 0} { + # default classifier for cbq is just Fid type + set c [new Classifier/Hash/Fid 33] + } else { + set c [lindex $args 0] + } + set link_($sid:$did) [new CBQLink \ + $n1 $n2 $bw $delay $q $c] + } + FQ { + set link_($sid:$did) [new FQLink $n1 $n2 $bw $delay $q] + } + intserv { + #XX need to clean this up + set link_($sid:$did) [new IntServLink \ + $n1 $n2 $bw $delay $q \ + [concat $qtypeOrig $args]] + } + default { + set link_($sid:$did) [new SimpleLink \ + $n1 $n2 $bw $delay $q] + } + } + if {$qtype == "RED/Pushback"} { + set pushback 1 + } else { + set pushback 0 + } + $n1 add-neighbor $n2 $pushback + + #XXX yuck + if {[string first "RED" $qtype] != -1 || + [string first "PI" $qtype] != -1 || + [string first "Vq" $qtype] != -1 || + [string first "REM" $qtype] != -1 || + [string first "GK" $qtype] != -1 || + [string first "RIO" $qtype] != -1 || + [string first "XCP" $qtype] != -1} { + $q link [$link_($sid:$did) set link_] + } + + set trace [$self get-ns-traceall] + if {$trace != ""} { + $self trace-queue $n1 $n2 $trace + } + set trace [$self get-nam-traceall] + if {$trace != ""} { + $self namtrace-queue $n1 $n2 $trace + } + + # Register this simplex link in nam link list. Treat it as + # a duplex link in nam + $self register-nam-linkconfig $link_($sid:$did) +} + +# +# This is used by Link::orient to register/update the order in which links +# should created in nam. This is important because different creation order +# may result in different layout. +# +# A poor hack. :( Any better ideas? +# +Simulator instproc register-nam-linkconfig link { + $self instvar linkConfigList_ link_ + if [info exists linkConfigList_] { + # Check whether the reverse simplex link is registered, + # if so, don't register this link again. + # We should have a separate object for duplex link. + set i1 [[$link src] id] + set i2 [[$link dst] id] + if [info exists link_($i2:$i1)] { + set pos [lsearch $linkConfigList_ $link_($i2:$i1)] + if {$pos >= 0} { + set a1 [$link_($i2:$i1) get-attribute \ + "ORIENTATION"] + set a2 [$link get-attribute "ORIENTATION"] + if {$a1 == "" && $a2 != ""} { + # If this duplex link has not been + # assigned an orientation, do it. + set linkConfigList_ [lreplace \ + $linkConfigList_ $pos $pos] + } else { + return + } + } + } + # Remove $link from list if it's already there + set pos [lsearch $linkConfigList_ $link] + if {$pos >= 0} { + set linkConfigList_ \ + [lreplace $linkConfigList_ $pos $pos] + } + } + lappend linkConfigList_ $link +} + +# +# GT-ITM may occasionally generate duplicate links, so we need this check +# to ensure duplicated links do not appear in nam trace files. +# +Simulator instproc remove-nam-linkconfig {i1 i2} { + $self instvar linkConfigList_ link_ + if ![info exists linkConfigList_] { + return + } + set pos [lsearch $linkConfigList_ $link_($i1:$i2)] + if {$pos >= 0} { + set linkConfigList_ [lreplace $linkConfigList_ $pos $pos] + return + } + set pos [lsearch $linkConfigList_ $link_($i2:$i1)] + if {$pos >= 0} { + set linkConfigList_ [lreplace $linkConfigList_ $pos $pos] + } +} + +# Armando L. Caro Jr. 10/22/2001 +# +# we create a simplex link (NOT duplex) from the core to the interface. we can +# use arbitrary params (bw, delay, etc) since we'll never actually transmit +# data on these links. they are only used for routing (ie, to determine which +# interface a packet should go out from) +# +Simulator instproc multihome-add-interface { core if } { + $self instvar link_ + set coreId [$core id] + set ifId [$if id] + + # arbitrary values (doesn't matter since link will NEVER be used!) + set bw 1Mb + set delay 100ms + set type DropTail + + if [info exists link_($coreId:$ifId)] { + $self remove-nam-linkconfig $coreId $ifId + } + eval $self simplex-link $core $if $bw $delay $type + # Modified by GFR for nix-vector routing + if { [Simulator set nix-routing] } { + # Inform nodes of neighbors + $n1 set-neighbor [$core id] + $n2 set-neighbor [$if id] + } + + + $core instvar multihome_interfaces_ num_interfaces_ + set interface_ {} + + # interface node + lappend interface_ $if + + # link from interface node to core node + lappend interface_ [$link_($coreId:$ifId) set head_] + + lappend multihome_interfaces_ $interface_ +} + +Simulator instproc duplex-link { n1 n2 bw delay type args } { + $self instvar link_ + set i1 [$n1 id] + set i2 [$n2 id] + if [info exists link_($i1:$i2)] { + $self remove-nam-linkconfig $i1 $i2 + } + eval $self simplex-link $n1 $n2 $bw $delay $type $args + eval $self simplex-link $n2 $n1 $bw $delay $type $args + # Modified by GFR for nix-vector routing + if { [Simulator set nix-routing] } { + # Inform nodes of neighbors + $n1 set-neighbor [$n2 id] + $n2 set-neighbor [$n1 id] + } +} + +Simulator instproc duplex-intserv-link { n1 n2 bw pd sched signal adc args } { + eval $self duplex-link $n1 $n2 $bw $pd intserv $sched $signal $adc $args +} + +Simulator instproc simplex-link-op { n1 n2 op args } { + $self instvar link_ + eval $link_([$n1 id]:[$n2 id]) $op $args +} + +Simulator instproc duplex-link-op { n1 n2 op args } { + $self instvar link_ + eval $link_([$n1 id]:[$n2 id]) $op $args + eval $link_([$n2 id]:[$n1 id]) $op $args +} + +Simulator instproc flush-trace {} { + $self instvar alltrace_ + if [info exists alltrace_] { + foreach trace $alltrace_ { + $trace flush + } + } +} + +Simulator instproc namtrace-all file { + $self instvar namtraceAllFile_ + if {$file != ""} { + set namtraceAllFile_ $file + } else { + unset namtraceAllFile_ + } +} + +Simulator instproc energy-color-change {level1 level2} { + $self instvar level1_ level2_ + set level1_ $level1 + set level2_ $level2 +} + +Simulator instproc namtrace-all-wireless {file optx opty} { + $self instvar namtraceAllFile_ + + # indicate that we need a W event written to the trace + $self set namNeedsW_ 1 + if { $optx != "" && $opty != "" } { + $self set namWx_ $optx + $self set namWy_ $opty + } + + $self namtrace-all $file +} + +Simulator instproc nam-end-wireless {stoptime} { + $self instvar namtraceAllFile_ + + if {$namtraceAllFile_ != ""} { + $self puts-nam-config "W -t $stoptime" + } +} + +Simulator instproc namtrace-some file { + $self instvar namtraceSomeFile_ + set namtraceSomeFile_ $file +} + +# Support for event-tracing + +Simulator instproc eventtrace-all {{file ""}} { + $self instvar eventTraceAll_ eventtraceAllFile_ traceAllFile_ + set eventTraceAll_ 1 + if {$file != ""} { + set eventtraceAllFile_ $file + } else { + set eventtraceAllFile_ $traceAllFile_ + } + +} + + + +Simulator instproc initial_node_pos {nodep size} { + $self instvar addressType_ + $self instvar energyModel_ + + if [info exists energyModel_] { + set nodeColor "green" + } else { + set nodeColor "black" + } + if { [info exists addressType_] && $addressType_ == "hierarchical" } { + # Hierarchical addressing + $self puts-nam-config "n -t * -a [$nodep set address_] \ +-s [$nodep id] -x [$nodep set X_] -y [$nodep set Y_] -Z [$nodep set Z_] \ +-z $size -v circle -c $nodeColor" + } else { + # Flat addressing + $self puts-nam-config "n -t * -s [$nodep id] \ +-x [$nodep set X_] -y [$nodep set Y_] -Z [$nodep set Z_] -z $size \ +-v circle -c $nodeColor" + } +} + +Simulator instproc trace-all file { + $self instvar traceAllFile_ + set traceAllFile_ $file +} + +Simulator instproc get-nam-traceall {} { + $self instvar namtraceAllFile_ + if [info exists namtraceAllFile_] { + return $namtraceAllFile_ + } else { + return "" + } +} + +Simulator instproc get-ns-traceall {} { + $self instvar traceAllFile_ + if [info exists traceAllFile_] { + return $traceAllFile_ + } else { + return "" + } +} + +# If exists a traceAllFile_, print $str to $traceAllFile_ +Simulator instproc puts-ns-traceall { str } { + $self instvar traceAllFile_ + if [info exists traceAllFile_] { + puts $traceAllFile_ $str + } +} + +# If exists a traceAllFile_, print $str to $traceAllFile_ +Simulator instproc puts-nam-traceall { str } { + $self instvar namtraceAllFile_ + if [info exists namtraceAllFile_] { + puts $namtraceAllFile_ $str + } elseif [info exists namtraceSomeFile_] { + puts $namtraceSomeFile_ $str + } +} + +# namConfigFile is used for writing color/link/node/queue/annotations. +# XXX It cannot co-exist with namtraceAll. +Simulator instproc namtrace-config { f } { + $self instvar namConfigFile_ + set namConfigFile_ $f +} + +Simulator instproc get-nam-config {} { + $self instvar namConfigFile_ + if [info exists namConfigFile_] { + return $namConfigFile_ + } else { + return "" + } +} + +# Used only for writing nam configurations to trace file(s). This is different +# from puts-nam-traceall because we may want to separate configuration +# informations and actual tracing information +Simulator instproc puts-nam-config { str } { + $self instvar namtraceAllFile_ namConfigFile_ + + if [info exists namConfigFile_] { + puts $namConfigFile_ $str + } elseif [info exists namtraceAllFile_] { + puts $namtraceAllFile_ $str + } elseif [info exists namtraceSomeFile_] { + puts $namtraceSomeFile_ $str + } +} + +Simulator instproc color { id name } { + $self instvar color_ + set color_($id) $name +} + +Simulator instproc get-color { id } { + $self instvar color_ + return $color_($id) +} + +# you can pass in {} as a null file +Simulator instproc create-trace { type file src dst {op ""} } { + $self instvar alltrace_ + set p [new Trace/$type] + $p tagged [Simulator set TaggedTrace_] + if [catch {$p set src_ [$src id]}] { + $p set src_ $src + } + if [catch {$p set dst_ [$dst id]}] { + $p set dst_ $dst + } + lappend alltrace_ $p + if {$file != ""} { + $p ${op}attach $file + } + return $p +} + + +Simulator instproc create-eventtrace {type owner } { + $self instvar alltrace_ + $self instvar eventTraceAll_ eventtraceAllFile_ namtraceAllFile_ + + if ![info exists eventTraceAll_] return + + if { $eventTraceAll_ == 1 } { + + set et [new BaseTrace/$type] + $owner cmd eventtrace $et + + lappend alltrace_ $et + $et attach $eventtraceAllFile_ + if [info exists namtraceAllFile_] { + $et namattach $namtraceAllFile_ + } + } +} + + +Simulator instproc namtrace-queue { n1 n2 {file ""} } { + $self instvar link_ namtraceAllFile_ + if {$file == ""} { + if ![info exists namtraceAllFile_] return + set file $namtraceAllFile_ + } + $link_([$n1 id]:[$n2 id]) nam-trace $self $file + + # Added later for queue specific tracing events other than enque, + # deque and drop as of now nam does not understand special events. + # Changes will have to be made to nam for it to understand events + # like early drops if they are prefixed differently than "d". - ratul + set queue [$link_([$n1 id]:[$n2 id]) queue] + $queue attach-nam-traces $n1 $n2 $file +} + +Simulator instproc trace-queue { n1 n2 {file ""} } { + $self instvar link_ traceAllFile_ + if {$file == ""} { + if ![info exists traceAllFile_] return + set file $traceAllFile_ + } + $link_([$n1 id]:[$n2 id]) trace $self $file + + # Added later for queue specific tracing events other than enque, + # deque and drop - ratul + set queue [$link_([$n1 id]:[$n2 id]) queue] + $queue attach-traces $n1 $n2 $file +} + +# +# arrange for queue length of link between nodes n1 and n2 +# to be tracked and return object that can be queried +# to learn average q size etc. XXX this API still rough +# +Simulator instproc monitor-queue { n1 n2 qtrace { sampleInterval 0.1 } } { + $self instvar link_ + return [$link_([$n1 id]:[$n2 id]) init-monitor $self $qtrace $sampleInterval] +} + +Simulator instproc queue-limit { n1 n2 limit } { + $self instvar link_ + [$link_([$n1 id]:[$n2 id]) queue] set limit_ $limit + if {[[$link_([$n1 id]:[$n2 id]) queue] info class] == "Queue/XCP"} { + [$link_([$n1 id]:[$n2 id]) queue] queue-limit $limit + } +} + +Simulator instproc drop-trace { n1 n2 trace } { + $self instvar link_ + [$link_([$n1 id]:[$n2 id]) queue] drop-target $trace +} + +Simulator instproc cost {n1 n2 c} { + $self instvar link_ + $link_([$n1 id]:[$n2 id]) cost $c +} + +# Armando L. Caro Jr. 10/22/2001 +Simulator instproc multihome-attach-agent { core agent } { + $agent set-multihome-core [$core entry] + + foreach interface [$core set multihome_interfaces_] { + set ifNode [lindex $interface 0] + set coreLink [lindex $interface 1] + + # attach agent to the node for each interface + $ifNode attach $agent + set addr [$agent set agent_addr_] + set port [$agent set agent_port_] + set entry [$ifNode entry] + + # give the interface info to the agent + $agent add-multihome-interface $addr $port $entry $coreLink + + $agent instvar multihome_bindings_ + set binding_ {} + lappend binding_ $addr + lappend binding_ $port + lappend multihome_bindings_ $binding_ + } +} + +Simulator instproc attach-agent { node agent } { + $node attach $agent + # $agent set nodeid_ [$node id] + + # Armando L. Caro Jr. 10/22/2001 + # + # list of tuples (addr, port) + # This is NEEDED so that single homed agents can play with multihomed + # ones! + # multihoming only for SCTP agents -Padma H. + if {[lindex [split [$agent info class] "/"] 1] == "SCTP"} { + $agent instvar multihome_bindings_ + set binding_ {} + set addr [$agent set agent_addr_] + set port [$agent set agent_port_] + lappend binding_ $addr + lappend binding_ $port + lappend multihome_bindings_ $binding_ + } +} + +Simulator instproc attach-tbf-agent { node agent tbf } { + $node attach $agent + $agent attach-tbf $tbf +} + + +Simulator instproc detach-agent { node agent } { + + # Debo added this + $self instvar conn_ nconn_ sflows_ nsflows_ useasim_ + + if {$useasim_ == 1} { + set list "" + set s [$node id] + set d [[$self get-node-by-addr [$agent set dst_addr_]] id] + foreach x $conn_ { + set t [split $x ":"] + if {[string compare [lindex $t 0]:[lindex $t 1] $s:$d] != 0} { + lappend list_ $x + } + } + set conn_ list + set nconn_ [expr $nconn_ -1] + # --------------------------------------- + } + + $self instvar nullAgent_ + $node detach $agent $nullAgent_ +} + +# +# Helper proc for setting delay on an existing link +# +Simulator instproc delay { n1 n2 delay {type simplex} } { + $self instvar link_ + set sid [$n1 id] + set did [$n2 id] + if [info exists link_($sid:$did)] { + set d [$link_($sid:$did) link] + $d set delay_ $delay + } + if {$type == "duplex"} { + if [info exists link_($did:$sid)] { + set d [$link_($did:$sid) link] + $d set delay_ $delay + } + } +} + +# +# Helper proc for setting bandwidth on an existing link +# +Simulator instproc bandwidth { n1 n2 bandwidth {type simplex} } { + $self instvar link_ + set sid [$n1 id] + set did [$n2 id] + if [info exists link_($sid:$did)] { + set d [$link_($sid:$did) link] + $d set bandwidth_ $bandwidth + } + if {$type == "duplex"} { + if [info exists link_($did:$sid)] { + set d [$link_($did:$sid) link] + $d set bandwidth_ $bandwidth + } + } +} + + +#XXX need to check that agents are attached to nodes already +Simulator instproc connect {src dst} { + + $self instvar conn_ nconn_ sflows_ nsflows_ useasim_ + + # Armando L. Caro Jr. + # does the agent type support multihoming?? + # @@@ do we need to worry about $useasim_ below?? (wasn't in 2.1b8) + if {[lindex [split [$src info class] "/"] 1] == "SCTP"} { + $self multihome-connect $src $dst + } + + $self simplex-connect $src $dst + $self simplex-connect $dst $src + + + # Debo + + if {$useasim_ == 1} { + set sid [$src nodeid] + set sport [$src set agent_port_] + set did [$dst nodeid] + set dport [$dst set agent_port_] + + if {[lindex [split [$src info class] "/"] 1] == "TCP"} { + lappend conn_ $sid:$did:$sport:$dport + incr nconn_ + # set $nconn_ [expr $nconn_ + 1] + # puts "Set a connection with id $nconn_ between $sid and $did" + } + } + + return $src +} + +# Armando L. Caro Jr. 10/12/2001 +Simulator instproc multihome-connect {src dst} { + + set destNum 0 + foreach binding [$src set multihome_bindings_] { + incr destNum + set addr [lindex $binding 0] + set port [lindex $binding 1] + $dst add-multihome-destination $addr $port + } + if {$destNum == 0} { + # src isn't multihomed, so make sure we do an + # add-multihome-destination + $dst add-multihome-destination \ + [$src set agent_addr_] [$src set agent_port_] + } + + set destNum 0 + foreach binding [$dst set multihome_bindings_] { + incr destNum + set addr [lindex $binding 0] + set port [lindex $binding 1] + $src add-multihome-destination $addr $port + } + if {$destNum == 0} { + # dst isn't multihomed, so make sure we do an + # add-multihome-destination + $src add-multihome-destination \ + [$dst set agent_addr_] [$dst set agent_port_] + } +} + +Simulator instproc simplex-connect { src dst } { + $src set dst_addr_ [$dst set agent_addr_] + $src set dst_port_ [$dst set agent_port_] + + + # Polly Huang: to support abstract TCP simulations + if {[lindex [split [$src info class] "/"] 1] == "AbsTCP"} { + $self at [$self now] "$self rtt $src $dst" + $dst set class_ [$src set class_] + } + + return $src +} + +# +# Here are a bunch of helper methods. +# + +Simulator proc instance {} { + set ns [Simulator info instances] + if { $ns != "" } { + return $ns + } + foreach sim [Simulator info subclass] { + set ns [$sim info instances] + if { $ns != "" } { + return $ns + } + } + error "Cannot find instance of simulator" +} + +Simulator instproc get-number-of-nodes {} { + return [$self array size Node_] +} + +Simulator instproc get-node-by-id id { + $self instvar Node_ + return $Node_($id) +} + +# Given an node's address, Return the node-id +Simulator instproc get-node-id-by-addr address { + $self instvar Node_ + set n [Node set nn_] + for {set q 0} {$q < $n} {incr q} { + set nq $Node_($q) + if {[string compare [$nq node-addr] $address] == 0} { + return $q + } + } + error "get-node-id-by-addr:Cannot find node with given address" +} + +# Given an node's address, return the node +Simulator instproc get-node-by-addr address { + return [$self get-node-by-id [$self get-node-id-by-addr $address]] +} + +Simulator instproc all-nodes-list {} { + $self instvar Node_ + set nodes "" + foreach n [lsort -dictionary [array names Node_]] { + lappend nodes $Node_($n) + } + return $nodes +} + +Simulator instproc link { n1 n2 } { + $self instvar Node_ link_ + if { ![catch "$n1 info class Node"] } { + set n1 [$n1 id] + } + if { ![catch "$n2 info class Node"] } { + set n2 [$n2 id] + } + if [info exists link_($n1:$n2)] { + return $link_($n1:$n2) + } + return "" +} + +# Creates connection. First creates a source agent of type s_type and binds +# it to source. Next creates a destination agent of type d_type and binds +# it to dest. Finally creates bindings for the source and destination agents, +# connects them, and returns the source agent. +Simulator instproc create-connection {s_type source d_type dest pktClass} { + set s_agent [new Agent/$s_type] + set d_agent [new Agent/$d_type] + $s_agent set fid_ $pktClass + $d_agent set fid_ $pktClass + $self attach-agent $source $s_agent + $self attach-agent $dest $d_agent + $self connect $s_agent $d_agent + + return $s_agent +} + +# Creates a highspeed connection. Similar to create-connection +# above except the sink agent requires additional work -- Sylvia +Simulator instproc create-highspeed-connection {s_type source d_type dest pktClass} { + set s_agent [new Agent/$s_type] + set d_agent [new Agent/$d_type] + $d_agent resize_buffers + $s_agent set fid_ $pktClass + $d_agent set fid_ $pktClass + $self attach-agent $source $s_agent + $self attach-agent $dest $d_agent + $self connect $s_agent $d_agent + + return $s_agent +} + +# Creates connection. First creates a source agent of type s_type and binds +# it to source. Next creates a destination agent of type d_type and binds +# it to dest. Finally creates bindings for the source and destination agents, +# connects them, and returns a list of source agent and destination agent. +Simulator instproc create-connection-list {s_type source d_type dest pktClass} { + set s_agent [new Agent/$s_type] + set d_agent [new Agent/$d_type] + $s_agent set fid_ $pktClass + $d_agent set fid_ $pktClass + $self attach-agent $source $s_agent + $self attach-agent $dest $d_agent + $self connect $s_agent $d_agent + + return [list $s_agent $d_agent] +} + +# Creates connection. First creates a source agent of type s_type and binds +# it to source. Next creates a destination agent of type d_type and binds +# it to dest. Finally creates bindings for the source and destination agents, +# connects them, and returns the source agent. +# The destination agent is set to listen, for full-tcp. +Simulator instproc create-connection-listen {s_type source d_type dest pktClass} { + set s_agent [new Agent/$s_type] + set d_agent [new Agent/$d_type] + $s_agent set fid_ $pktClass + $d_agent set fid_ $pktClass + $self attach-agent $source $s_agent + $self attach-agent $dest $d_agent + $self connect $s_agent $d_agent + $d_agent listen + + return $s_agent +} + +# This seems to be an obsolete procedure. +Simulator instproc create-tcp-connection {s_type source d_type dest pktClass} { + set s_agent [new Agent/$s_type] + set d_agent [new Agent/$d_type] + $s_agent set fid_ $pktClass + $d_agent set fid_ $pktClass + $self attach-agent $source $s_agent + $self attach-agent $dest $d_agent + return "$s_agent $d_agent" +} + +# +# Other classifier methods overload the instproc-likes to track +# and return the installed objects. +# +Classifier instproc install {slot val} { + $self set slots_($slot) $val + $self cmd install $slot $val +} + +Classifier instproc installNext val { + set slot [$self cmd installNext $val] + $self set slots_($slot) $val + set slot +} + +Classifier instproc adjacents {} { + $self array get slots_ +} + +Classifier instproc in-slot? slot { + $self instvar slots_ + set ret "" + if {[info exists slots_($slot)]} { + set ret $slots_($slot) + } + set ret +} + +# For debugging +Classifier instproc dump {} { + $self instvar slots_ offset_ shift_ mask_ + puts "classifier $self" + puts "\t$offset_ offset" + puts "\t$shift_ shift" + puts "\t$mask_ mask" + puts "\t[array size slots_] slots" + foreach i [lsort -integer [array names slots_]] { + set iv $slots_($i) + puts "\t\tslot $i: $iv ([$iv info class])" + } +} + +Classifier instproc no-slot slot { + puts stderr "--- Classfier::no-slot{} default handler (tcl/lib/ns-lib.tcl) ---" + puts stderr "\t$self: no target for slot $slot" + puts stderr "\t$self type: [$self info class]" + puts stderr "content dump:" + $self dump + puts stderr "---------- Finished standard no-slot{} default handler ----------" + # Clear output before we bail out + [Simulator instance] flush-trace + exit 1 +} + +Classifier/Hash instproc dump args { + eval $self next $args + $self instvar default_ + puts "\t$default_ default" +} + +Classifier/Hash instproc init nbuck { + # We need to make sure that port shift/mask values are there + # so we set them after they get their default values + $self next $nbuck + $self instvar shift_ mask_ + set shift_ [AddrParams NodeShift 1] + set mask_ [AddrParams NodeMask 1] +} + +Classifier/Port/Reserve instproc init args { + eval $self next + $self reserve-port 2 +} + +Simulator instproc makeflowmon { cltype { clslots 29 } } { + set flowmon [new QueueMonitor/ED/Flowmon] + set cl [new Classifier/Hash/$cltype $clslots] + + $cl proc unknown-flow { src dst fid } { + set fdesc [new QueueMonitor/ED/Flow] + set dsamp [new Samples] + $fdesc set-delay-samples $dsamp + set slot [$self installNext $fdesc] + $self set-hash auto $src $dst $fid $slot + } + + $cl proc no-slot slotnum { + # + # note: we can wind up here when a packet passes + # through either an Out or a Drop Snoop Queue for + # a queue that the flow doesn't belong to anymore. + # Since there is no longer hash state in the + # hash classifier, we get a -1 return value for the + # hash classifier's classify() function, and there + # is no node at slot_[-1]. What to do about this? + # Well, we are talking about flows that have already + # been moved and so should rightly have their stats + # zero'd anyhow, so for now just ignore this case.. + # puts "classifier $self, no-slot for slotnum $slotnum" + } + $flowmon classifier $cl + return $flowmon +} + +# attach a flow monitor to a link +# 3rd argument dictates whether early drop support is to be used + +Simulator instproc attach-fmon {lnk fm { edrop 0 } } { + set isnoop [new SnoopQueue/In] + set osnoop [new SnoopQueue/Out] + set dsnoop [new SnoopQueue/Drop] + $lnk attach-monitors $isnoop $osnoop $dsnoop $fm + if { $edrop != 0 } { + set edsnoop [new SnoopQueue/EDrop] + $edsnoop set-monitor $fm + [$lnk queue] early-drop-target $edsnoop + $edsnoop target [$self set nullAgent_] + } + [$lnk queue] drop-target $dsnoop +} + +# Added by Yun Wang + +Simulator instproc maketbtagger { cltype { clslots 29 } } { + + set tagger [new QueueMonitor/ED/Tagger] + set cl [new Classifier/Hash/$cltype $clslots] + + $cl proc unknown-flow { src dst fid } { + set fdesc [new QueueMonitor/ED/Flow/TB] + set dsamp [new Samples] + $fdesc set-delay-samples $dsamp + set slot [$self installNext $fdesc] + $self set-hash auto $src $dst $fid $slot + } + + $cl proc set-rate { src dst fid hashbucket rate depth init} { + set fdesc [new QueueMonitor/ED/Flow/TB] + set dsamp [new Samples] + $fdesc set-delay-samples $dsamp + $fdesc set target_rate_ $rate + $fdesc set bucket_depth_ $depth + # Initialize the bucket as full + $fdesc set tbucket_ $init + set slot [$self installNext $fdesc] + $self set-hash $hashbucket $src $dst $fid $slot + } + + $cl proc no-slot slotnum { + # + # note: we can wind up here when a packet passes + # through either an Out or a Drop Snoop Queue for + # a queue that the flow doesn't belong to anymore. + # Since there is no longer hash state in the + # hash classifier, we get a -1 return value for the + # hash classifier's classify() function, and there + # is no node at slot_[-1]. What to do about this? + # Well, we are talking about flows that have already + # been moved and so should rightly have their stats + # zero'd anyhow, so for now just ignore this case.. + # puts "classifier $self, no-slot for slotnum $slotnum" + } + $tagger classifier $cl + return $tagger +} + +# Added by Yun Wang + +Simulator instproc maketswtagger { cltype { clslots 29 } } { + + set tagger [new QueueMonitor/ED/Tagger] + set cl [new Classifier/Hash/$cltype $clslots] + + $cl proc unknown-flow { src dst fid hashbucket } { + set fdesc [new QueueMonitor/ED/Flow/TSW] + set dsamp [new Samples] + $fdesc set-delay-samples $dsamp + set slot [$self installNext $fdesc] + $self set-hash $hashbucket $src $dst $fid $slot + } + + $cl proc no-slot slotnum { + # + # note: we can wind up here when a packet passes + # through either an Out or a Drop Snoop Queue for + # a queue that the flow doesn't belong to anymore. + # Since there is no longer hash state in the + # hash classifier, we get a -1 return value for the + # hash classifier's classify() function, and there + # is no node at slot_[-1]. What to do about this? + # Well, we are talking about flows that have already + # been moved and so should rightly have their stats + # zero'd anyhow, so for now just ignore this case.. + # puts "classifier $self, no-slot for slotnum $slotnum" + } + $tagger classifier $cl + return $tagger +} + +# attach a Tagger to a link +# Added by Yun Wang + +Simulator instproc attach-tagger {lnk fm} { + set isnoop [new SnoopQueue/Tagger] + $lnk attach-taggers $isnoop $fm +} + +# Imported from session.tcl. It is deleted there. +### to insert loss module to regular links in detailed Simulator +Simulator instproc lossmodel {lossobj from to} { + set link [$self link $from $to] + $link errormodule $lossobj +} + +# This function generates losses that can be visualized by nam. +Simulator instproc link-lossmodel {lossobj from to} { + set link [$self link $from $to] + $link insert-linkloss $lossobj +} + + +#### Polly Huang: Simulator class instproc to support abstract tcp simulations + +Simulator instproc rtt { src dst } { + $self instvar routingTable_ delay_ + set srcid [[$src set node_] id] + set dstid [[$dst set node_] id] + set delay 0 + set tmpid $srcid + while {$tmpid != $dstid} { + set nextid [$routingTable_ lookup $tmpid $dstid] + set tmpnode [$self get-node-by-id $tmpid] + set nextnode [$self get-node-by-id $nextid] + set tmplink [[$self link $tmpnode $nextnode] link] + set delay [expr $delay + [expr 2 * [$tmplink set delay_]]] + set delay [expr $delay + [expr 8320 / [$tmplink set bandwidth_]]] + set tmpid $nextid + } + $src rtt $delay + return $delay +} + +Simulator instproc abstract-tcp {} { + $self instvar TahoeAckfsm_ RenoAckfsm_ TahoeDelAckfsm_ RenoDelAckfsm_ dropper_ + $self set TahoeAckfsm_ [new FSM/TahoeAck] + $self set RenoAckfsm_ [new FSM/RenoAck] + $self set TahoeDelAckfsm_ [new FSM/TahoeDelAck] + $self set RenoDelAckfsm_ [new FSM/RenoDelAck] + $self set nullAgent_ [new DropTargetAgent] +} + +# Chalermek: For Diffusion, Flooding, and Omnicient Multicast + +Simulator instproc create-diffusion-rate-agent {node} { + global opt + set diff [new Agent/Diffusion/RateGradient] + + $node set diffagent_ $diff + $node set ragent_ $diff + + $diff on-node $node + + if [info exist opt(enablePos)] { + if {$opt(enablePos) == "true"} { + $diff enable-pos + } else { + $diff disable-pos + } + } + + if [info exist opt(enableNeg)] { + if {$opt(enableNeg) == "true"} { + $diff enable-neg + } else { + $diff disable-neg + } + } + + if [info exist opt(suppression)] { + if {$opt(suppression) == "true"} { + $diff enable-suppression + } else { + $diff disable-suppression + } + } + + if [info exist opt(subTxType)] { + $diff set-sub-tx-type $opt(subTxType) + } + + if [info exist opt(orgTxType)] { + $diff set-org-tx-type $opt(orgTxType) + } + + if [info exist opt(posType)] { + $diff set-pos-type $opt(posType) + } + + if [info exist opt(posNodeType)] { + $diff set-pos-node-type $opt(posNodeType) + } + + if [info exist opt(negWinType)] { + $diff set-neg-win-type $opt(negWinType) + } + + if [info exist opt(negThrType)] { + $diff set-neg-thr-type $opt(negThrType) + } + + if [info exist opt(negMaxType)] { + $diff set-neg-max-type $opt(negMaxType) + } + + $self put-in-list $diff + $self at 0.0 "$diff start" + + return $diff +} + +Simulator instproc create-diffusion-probability-agent {node} { + global opt + set diff [new Agent/Diffusion/ProbGradient] + + $node set diffagent_ $diff + $node set ragent_ $diff + + $diff on-node $node + + if [info exist opt(enablePos)] { + if {$opt(enablePos) == "true"} { + $diff enable-pos + } else { + $diff disable-pos + } + } + if [info exist opt(enableNeg)] { + if {$opt(enableNeg) == "true"} { + $diff enable-neg + } else { + $diff disable-neg + } + } + + $self put-in-list $diff + $self at 0.0 "$diff start" + + return $diff +} + +# Diffusioncore agent (in diffusion) maps to the wireless routing agent +# in ns +Simulator instproc create-core-diffusion-rtg-agent {node} { + $self instvar stopTime_ diffFilter_ + Node instvar ragent_ dmux_ + set ragent [new Agent/DiffusionRouting [$node id]] + $node set ragent_ $ragent + # at stop-time core-diffusion dumps stats data + # see diffusion.cc for details + if { [info exists stopTime_] } { + $ragent stop-time $stopTime_ + } + if { ![info exists diffFilter_] } { + puts stderr "Error: No filter defined for diffusion!\n" + exit 1 + } + $node create-diffusionApp-agent $diffFilter_ + return $ragent +} + +Simulator instproc create-flooding-agent {node} { + set flood [new Agent/Flooding] + + $node set ragent_ $flood + + $flood on-node $node + + $self put-in-list $flood + $self at 0.0 "$flood start" + + return $flood +} + +Simulator instproc create-omnimcast-agent {node} { + set omni [new Agent/OmniMcast] + + $node set ragent_ $omni + + $omni on-node $node + + $self put-in-list $omni + $self at 0.0 "$omni start" + + return $omni +} + +# XXX These are very simulation-specific methods, why should they belong here? +Simulator instproc put-in-list {agent} { + $self instvar lagent + lappend lagent $agent +} + +Simulator instproc terminate-all-agents {} { + $self instvar lagent + foreach i $lagent { + $i terminate + } +} + +Simulator instproc prepare-to-stop {} { + $self instvar lagent + foreach i $lagent { + $i stop + } +} + diff --git a/src/sim/ns/ns235/ns235-Makefile.in b/src/sim/ns/ns235/ns235-Makefile.in new file mode 100644 index 0000000..d53ecaf --- /dev/null +++ b/src/sim/ns/ns235/ns235-Makefile.in @@ -0,0 +1,702 @@ +# Copyright (c) 1994, 1995, 1996 +# The Regents of the University of California. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that: (1) source code distributions +# retain the above copyright notice and this paragraph in its entirety, (2) +# distributions including binary code include the above copyright notice and +# this paragraph in its entirety in the documentation or other materials +# provided with the distribution, and (3) all advertising materials mentioning +# features or use of this software display the following acknowledgement: +# ``This product includes software developed by the University of California, +# Lawrence Berkeley Laboratory and its contributors.'' Neither the name of +# the University nor the names of its contributors may be used to endorse +# or promote products derived from this software without specific prior +# written permission. +# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# @(#) $Header: 2002/10/09 15:34:11 + +# +# Various configurable paths (remember to edit Makefile.in, not Makefile) +# + +# Top level hierarchy +prefix = @prefix@ +# Pathname of directory to install the binary +BINDEST = @prefix@/bin +# Pathname of directory to install the man page +MANDEST = @prefix@/man + +BLANK = # make a blank space. DO NOT add anything to this line + +# The following will be redefined under Windows (see WIN32 lable below) +CC = @CC@ +CPP = @CXX@ +LINK = $(CPP) +LINK_SHLIB = @SHLIB_LD@ +MKDEP = ./conf/mkdep +TCLSH = @V_TCLSH@ +TCL2C = @V_TCL2CPP@ +AR = ar rc $(BLANK) + +RANLIB = @V_RANLIB@ +INSTALL = @INSTALL@ +LN = ln +TEST = test +RM = rm -f +MV = mv +PERL = @PERL@ + +# for diffusion +#DIFF_INCLUDES = "./diffusion3/main ./diffusion3/lib ./diffusion3/nr ./diffusion3/ns" + +CCOPT = @V_CCOPT@ +STATIC = @V_STATIC@ +#LDFLAGS = $(STATIC) +LDFLAGS = @LDFLAGS@ +LDOUT = -o $(BLANK) + +DEFINE = -DTCP_DELAY_BIND_ALL -DNO_TK @V_DEFINE@ @V_DEFINES@ @DEFS@ -DNS_DIFFUSION -DSMAC_NO_SYNC -DCPP_NAMESPACE=@CPP_NAMESPACE@ -DUSE_SINGLE_ADDRESS_SPACE -Drng_test + +###################### Definitions for NRL Extensions ######################### + +########################### PROTOLIB Section ####################### + +# These flags work on Linux +PROTOLIB_CFLAGS = -DUNIX -DNS2 -DPROTO_DEBUG -DSIMULATE -DHAVE_ASSERT -DHAVE_DIRFD +PROTOLIB = ./protolib +PROTOLIB_INCLUDES = -I$(PROTOLIB)/include -I$(PROTOLIB)/src/sim/ns +OBJ_PROTOLIB_CPP = \ + protolib/src/sim/ns/nsProtoSimAgent.o protolib/src/common/protoSimAgent.o \ + protolib/src/common/protoSimSocket.o protolib/src/common/protoAddress.o \ + protolib/src/common/protoTimer.o protolib/examples/protoExample.o \ + protolib/src/common/protoDebug.o protolib/src/common/protoTree.o \ + protolib/src/common/protoList.o protolib/src/common/protoRouteMgr.o \ + protolib/src/sim/ns/nsRouteMgr.o protolib/src/common/protoRouteTable.o \ + protolib/src/sim/ns/nsProtoManetKernel.o protolib/src/common/protoBitmask.o \ + protolib/src/common/protoTime.o protolib/src/sim/ns/tcp/TCPData.o \ + protolib/src/sim/ns/tcp/SimpleList.o protolib/src/sim/ns/tcp/TCPSocketApp.o \ + protolib/src/sim/ns/tcp/TCPEvent.o protolib/src/sim/ns/tcp/TCPSocketAgent.o \ + protolib/src/sim/ns/tcp/TCPServerSocketAgent.o protolib/src/sim/ns/tcp/TCPSocketFactory.o \ + protolib/src/sim/ns/tcp/TCPSocketExample.o protolib/src/sim/ns/nsTCPProtoSocketAgent.o + +############################## Other NRL Stuff ######################## + +# MGEN +MGEN_INCLUDES = -Imgen/include -Imgen/src/sim/ns -Imgen/src/sim +OBJ_MGEN_CPP = \ + mgen/src/sim/ns/nsMgenAgent.o \ + mgen/src/sim/mgenSimSinkTransport.o \ + mgen/src/common/mgen.o mgen/src/common/mgenEvent.o \ + mgen/src/common/mgenTransport.o \ + mgen/src/common/mgenFlow.o mgen/src/common/mgenMsg.o \ + mgen/src/common/mgenPattern.o \ + mgen/src/common/mgenSequencer.o mgen/src/common/mgenPayload.o + + +# NORM +NORM_INCLUDES = -Inorm/src/common -Inorm/include +OBJ_NORM_CPP = \ + norm/src/sim/ns/nsNormAgent.o norm/src/common/normSimAgent.o \ + norm/src/common/normMessage.o norm/src/common/normSession.o \ + norm/src/common/normNode.o norm/src/common/normObject.o \ + norm/src/common/normSegment.o norm/src/common/normEncoder.o \ + norm/src/common/normEncoderRS8.o norm/src/common/normEncoderRS16.o \ + norm/src/common/normEncoderMDP.o norm/src/common/galois.o \ + norm/src/common/normFile.o + +# NRLOLSR +NRLOLSR_INCLUDES = -Inrlolsr/common -Inrlolsr/ns +OBJ_NRLOLSR_CPP = \ + nrlolsr/common/nbr_queue.o nrlolsr/common/nrlolsr.o \ + nrlolsr/common/olsr_packet_types.o nrlolsr/ns/nrlolsrAgent.o \ + nrlolsr/ns/smfDupTree.o nrlolsr/ns/smfWindow.o + + +NRL_CFLAGS = $(PROTOLIB_CFLAGS) +NRL_INCLUDES = $(PROTOLIB_INCLUDES) \ + $(MGEN_INCLUDES) \ + $(NORM_INCLUDES) \ + $(NRLOLSR_INCLUDES) +OBJ_NRL_CPP = $(OBJ_PROTOLIB_CPP) \ + $(OBJ_MGEN_CPP) \ + $(OBJ_NORM_CPP) \ + $(OBJ_NRLOLSR_CPP) + +############################################################################### + +INCLUDES = \ + -I. @V_INCLUDE_X11@ \ + -I. \ + @V_INCLUDES@ \ + -I./tcp -I./sctp -I./common -I./link -I./queue \ + -I./adc -I./apps -I./mac -I./mobile -I./trace \ + -I./routing -I./tools -I./classifier -I./mcast \ + -I./diffusion3/lib/main -I./diffusion3/lib \ + -I./diffusion3/lib/nr -I./diffusion3/ns \ + -I./diffusion3/filter_core -I./asim/ -I./qs \ + -I./diffserv -I./satellite \ + -I./wpan \ + $(NRL_INCLUDES) + + +LIB = \ + @V_LIBS@ \ + @V_LIB_X11@ \ + @V_LIB@ \ + -lm @LIBS@ +# -L@libdir@ \ + +CFLAGS += $(CCOPT) $(DEFINE) $(NRL_CFLAGS) + +# Explicitly define compilation rules since SunOS 4's make doesn't like gcc. +# Also, gcc does not remove the .o before forking 'as', which can be a +# problem if you don't own the file but can write to the directory. +.SUFFIXES: .cc # $(.SUFFIXES) + +.cc.o: + @rm -f $@ + $(CPP) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cc + +.c.o: + @rm -f $@ + $(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $*.c + +.cpp.o: + @rm -f $@ + $(CPP) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cpp + + +GEN_DIR = gen/ +LIB_DIR = lib/ +NS = ns +NSLIB = @NSLIB@ +NSX = nsx +NSE = nse +NSTK = nstk + +# To allow conf/makefile.win overwrite this macro +# We will set these two macros to empty in conf/makefile.win since VC6.0 +# does not seem to support the STL in gcc 2.8 and up. +OBJ_STL = diffusion3/lib/nr/nr.o diffusion3/lib/dr.o \ + diffusion3/filters/diffusion/one_phase_pull.o \ + diffusion3/filters/diffusion/two_phase_pull.o \ + diffusion3/lib/diffapp.o \ + diffusion3/ns/diffagent.o diffusion3/ns/diffrtg.o \ + diffusion3/ns/difftimer.o \ + diffusion3/filter_core/filter_core.o \ + diffusion3/filter_core/iolog.o \ + diffusion3/filter_core/iostats.o \ + diffusion3/lib/main/attrs.o \ + diffusion3/lib/main/events.o \ + diffusion3/lib/main/iodev.o \ + diffusion3/lib/main/iohook.o \ + diffusion3/lib/main/timers.o \ + diffusion3/lib/main/message.o \ + diffusion3/lib/main/tools.o \ + diffusion3/apps/gear_examples/gear_common.o \ + diffusion3/apps/gear_examples/gear_receiver.o \ + diffusion3/apps/gear_examples/gear_sender.o \ + diffusion3/apps/rmst_examples/rmst_sink.o \ + diffusion3/apps/rmst_examples/rmst_source.o \ + diffusion3/apps/ping/1pp_ping_sender.o \ + diffusion3/apps/ping/1pp_ping_receiver.o \ + diffusion3/apps/ping/2pp_ping_sender.o \ + diffusion3/apps/ping/2pp_ping_receiver.o \ + diffusion3/apps/ping/ping_common.o \ + diffusion3/apps/ping/push_receiver.o \ + diffusion3/apps/ping/push_sender.o \ + diffusion3/filters/gear/gear_attr.o \ + diffusion3/filters/gear/gear.o \ + diffusion3/filters/gear/gear_tools.o \ + diffusion3/filters/misc/log.o \ + diffusion3/filters/misc/srcrt.o \ + diffusion3/filters/misc/tag.o \ + diffusion3/filters/rmst/rmst.o \ + diffusion3/filters/rmst/rmst_filter.o \ + delaybox/delaybox.o \ + packmime/packmime_HTTP.o packmime/packmime_HTTP_rng.o \ + packmime/packmime_OL.o packmime/packmime_OL_ranvar.o\ + packmime/packmime_ranvar.o \ + tmix/tmix.o tmix/tmixAgent.o tmix/tmix_delaybox.o + +NS_TCL_LIB_STL = tcl/lib/ns-diffusion.tcl \ + tcl/delaybox/delaybox.tcl \ + tcl/packmime/packmime.tcl \ + tcl/tmix/tmix.tcl \ + tcl/tmix/tmix_delaybox.tcl + + +# WIN32: uncomment the following line to include specific make for VC++ +# !include + +OBJ_CC = \ + tools/random.o tools/rng.o tools/ranvar.o common/misc.o common/timer-handler.o \ + common/scheduler.o common/object.o common/packet.o \ + common/ip.o routing/route.o common/connector.o common/ttl.o \ + trace/trace.o trace/trace-ip.o \ + classifier/classifier.o classifier/classifier-addr.o \ + classifier/classifier-hash.o \ + classifier/classifier-virtual.o \ + classifier/classifier-mcast.o \ + classifier/classifier-bst.o \ + classifier/classifier-mpath.o mcast/replicator.o \ + classifier/classifier-mac.o \ + classifier/classifier-qs.o \ + classifier/classifier-port.o src_rtg/classifier-sr.o \ + src_rtg/sragent.o src_rtg/hdr_src.o adc/ump.o \ + qs/qsagent.o qs/hdr_qs.o \ + apps/app.o apps/telnet.o tcp/tcplib-telnet.o \ + tools/trafgen.o trace/traffictrace.o tools/pareto.o \ + tools/expoo.o tools/cbr_traffic.o \ + adc/tbf.o adc/resv.o adc/sa.o tcp/saack.o \ + tools/measuremod.o adc/estimator.o adc/adc.o adc/ms-adc.o \ + adc/timewindow-est.o adc/acto-adc.o \ + adc/pointsample-est.o adc/salink.o adc/actp-adc.o \ + adc/hb-adc.o adc/expavg-est.o\ + adc/param-adc.o adc/null-estimator.o \ + adc/adaptive-receiver.o apps/vatrcvr.o adc/consrcvr.o \ + common/agent.o common/message.o apps/udp.o \ + common/session-rtp.o apps/rtp.o tcp/rtcp.o \ + common/ivs.o \ + common/messpass.o common/tp.o common/tpm.o apps/worm.o \ + tcp/tcp.o tcp/tcp-sink.o tcp/tcp-reno.o \ + tcp/tcp-newreno.o \ + tcp/tcp-vegas.o tcp/tcp-rbp.o tcp/tcp-full.o tcp/rq.o \ + baytcp/tcp-full-bay.o baytcp/ftpc.o baytcp/ftps.o \ + tcp/scoreboard.o tcp/scoreboard-rq.o tcp/tcp-sack1.o tcp/tcp-fack.o \ + tcp/scoreboard1.o tcp/tcp-linux.o tcp/linux/ns-linux-util.o \ + tcp/tcp-asym.o tcp/tcp-asym-sink.o tcp/tcp-fs.o \ + tcp/tcp-asym-fs.o \ + tcp/tcp-int.o tcp/chost.o tcp/tcp-session.o \ + tcp/nilist.o \ + sctp/sctp.o apps/sctp_app1.o\ + sctp/sctp-timestamp.o sctp/sctp-hbAfterRto.o \ + sctp/sctp-multipleFastRtx.o sctp/sctp-mfrHbAfterRto.o \ + sctp/sctp-mfrTimestamp.o \ + sctp/sctp-cmt.o \ + sctp/sctpDebug.o \ + dccp/dccp_sb.o \ + dccp/dccp_opt.o \ + dccp/dccp_ackv.o \ + dccp/dccp_packets.o \ + dccp/dccp.o \ + dccp/dccp_tcplike.o \ + dccp/dccp_tfrc.o \ + tools/integrator.o tools/queue-monitor.o \ + tools/flowmon.o tools/loss-monitor.o \ + queue/queue.o queue/drop-tail.o \ + adc/simple-intserv-sched.o queue/red.o \ + queue/semantic-packetqueue.o queue/semantic-red.o \ + tcp/ack-recons.o \ + queue/sfq.o queue/fq.o queue/drr.o queue/srr.o queue/cbq.o \ + queue/jobs.o queue/marker.o queue/demarker.o \ + link/hackloss.o queue/errmodel.o queue/fec.o\ + link/delay.o tcp/snoop.o \ + gaf/gaf.o \ + link/dynalink.o routing/rtProtoDV.o common/net-interface.o \ + mcast/ctrMcast.o mcast/mcast_ctrl.o mcast/srm.o \ + common/sessionhelper.o queue/delaymodel.o \ + mcast/srm-ssm.o mcast/srm-topo.o \ + routing/alloc-address.o routing/address.o \ + $(LIB_DIR)int.Vec.o $(LIB_DIR)int.RVec.o \ + $(LIB_DIR)dmalloc_support.o \ + webcache/http.o webcache/tcp-simple.o webcache/pagepool.o \ + webcache/inval-agent.o webcache/tcpapp.o webcache/http-aux.o \ + webcache/mcache.o webcache/webtraf.o \ + webcache/webserver.o \ + webcache/logweb.o \ + empweb/empweb.o \ + empweb/empftp.o \ + realaudio/realaudio.o \ + mac/lanRouter.o classifier/filter.o \ + common/pkt-counter.o \ + common/Decapsulator.o common/Encapsulator.o \ + common/encap.o \ + mac/channel.o mac/mac.o mac/ll.o mac/mac-802_11.o \ + mac/mac-802_11Ext.o \ + mac/mac-802_3.o mac/mac-tdma.o mac/smac.o \ + mobile/mip.o mobile/mip-reg.o mobile/gridkeeper.o \ + mobile/propagation.o mobile/tworayground.o \ + mobile/nakagami.o \ + mobile/antenna.o mobile/omni-antenna.o \ + mobile/shadowing.o mobile/shadowing-vis.o mobile/dumb-agent.o \ + common/bi-connector.o common/node.o \ + common/mobilenode.o \ + mac/arp.o mobile/god.o mobile/dem.o \ + mobile/topography.o mobile/modulation.o \ + queue/priqueue.o queue/dsr-priqueue.o \ + mac/phy.o mac/wired-phy.o mac/wireless-phy.o \ + mac/wireless-phyExt.o \ + mac/mac-timers.o trace/cmu-trace.o mac/varp.o \ + mac/mac-simple.o \ + satellite/sat-hdlc.o \ + dsdv/dsdv.o dsdv/rtable.o queue/rtqueue.o \ + routing/rttable.o \ + imep/imep.o imep/dest_queue.o imep/imep_api.o \ + imep/imep_rt.o imep/rxmit_queue.o imep/imep_timers.o \ + imep/imep_util.o imep/imep_io.o \ + tora/tora.o tora/tora_api.o tora/tora_dest.o \ + tora/tora_io.o tora/tora_logs.o tora/tora_neighbor.o \ + dsr/dsragent.o dsr/hdr_sr.o dsr/mobicache.o dsr/path.o \ + dsr/requesttable.o dsr/routecache.o dsr/add_sr.o \ + dsr/dsr_proto.o dsr/flowstruct.o dsr/linkcache.o \ + dsr/simplecache.o dsr/sr_forwarder.o \ + aodv/aodv_logs.o aodv/aodv.o \ + aodv/aodv_rtable.o aodv/aodv_rqueue.o \ + aomdv/aomdv_logs.o aomdv/aomdv.o \ + aomdv/aomdv_rtable.o aomdv/aomdv_rqueue.o \ + puma/puma.o \ + mdart/mdart_adp.o mdart/mdart_dht.o mdart/mdart_ndp.o \ + mdart/mdart_neighbor.o mdart/mdart_queue.o mdart/mdart_table.o \ + mdart/mdart.o \ + common/ns-process.o \ + satellite/satgeometry.o satellite/sathandoff.o \ + satellite/satlink.o satellite/satnode.o \ + satellite/satposition.o satellite/satroute.o \ + satellite/sattrace.o \ + rap/raplist.o rap/rap.o rap/media-app.o rap/utilities.o \ + common/fsm.o tcp/tcp-abs.o \ + diffusion/diffusion.o diffusion/diff_rate.o diffusion/diff_prob.o \ + diffusion/diff_sink.o diffusion/flooding.o diffusion/omni_mcast.o \ + diffusion/hash_table.o diffusion/routing_table.o diffusion/iflist.o \ + tcp/tfrc.o tcp/tfrc-sink.o mobile/energy-model.o apps/ping.o tcp/tcp-rfc793edu.o \ + queue/rio.o queue/semantic-rio.o tcp/tcp-sack-rh.o tcp/scoreboard-rh.o \ + plm/loss-monitor-plm.o plm/cbr-traffic-PP.o \ + linkstate/hdr-ls.o \ + mpls/classifier-addr-mpls.o mpls/ldp.o mpls/mpls-module.o \ + routing/rtmodule.o classifier/classifier-hier.o \ + routing/addr-params.o \ + nix/hdr_nv.o nix/classifier-nix.o \ + nix/nixnode.o \ + routealgo/rnode.o \ + routealgo/bfs.o \ + routealgo/rbitmap.o \ + routealgo/rlookup.o \ + routealgo/routealgo.o \ + nix/nixvec.o \ + nix/nixroute.o \ + diffserv/dsred.o diffserv/dsredq.o \ + diffserv/dsEdge.o diffserv/dsCore.o \ + diffserv/dsPolicy.o diffserv/ew.o diffserv/dewp.o \ + queue/red-pd.o queue/pi.o queue/vq.o queue/rem.o \ + queue/gk.o \ + pushback/rate-limit.o pushback/rate-limit-strategy.o \ + pushback/ident-tree.o pushback/agg-spec.o \ + pushback/logging-data-struct.o \ + pushback/rate-estimator.o \ + pushback/pushback-queue.o pushback/pushback.o \ + common/parentnode.o trace/basetrace.o \ + common/simulator.o asim/asim.o \ + common/scheduler-map.o common/splay-scheduler.o \ + linkstate/ls.o linkstate/rtProtoLS.o \ + pgm/classifier-pgm.o pgm/pgm-agent.o pgm/pgm-sender.o \ + pgm/pgm-receiver.o mcast/rcvbuf.o \ + mcast/classifier-lms.o mcast/lms-agent.o mcast/lms-receiver.o \ + mcast/lms-sender.o \ + queue/delayer.o \ + xcp/xcpq.o xcp/xcp.o xcp/xcp-end-sys.o \ + wpan/p802_15_4csmaca.o wpan/p802_15_4fail.o \ + wpan/p802_15_4hlist.o wpan/p802_15_4mac.o \ + wpan/p802_15_4nam.o wpan/p802_15_4phy.o \ + wpan/p802_15_4sscs.o wpan/p802_15_4timer.o \ + wpan/p802_15_4trace.o wpan/p802_15_4transac.o \ + apps/pbc.o \ + @V_STLOBJ@ + + +# don't allow comments to follow continuation lines + +# mac-csma.o mac-multihop.o\ +# sensor-nets/landmark.o mac-simple-wireless.o \ +# sensor-nets/tags.o sensor-nets/sensor-query.o \ +# sensor-nets/flood-agent.o \ + +# what was here before is now in emulate/ +OBJ_C = \ + tcp/linux/tcp_naivereno.o\ + tcp/linux/src/tcp_cong.o\ + tcp/linux/src/tcp_highspeed.o tcp/linux/src/tcp_bic.o tcp/linux/src/tcp_htcp.o tcp/linux/src/tcp_scalable.o tcp/linux/src/tcp_cubic.o\ + tcp/linux/src/tcp_westwood.o tcp/linux/src/tcp_vegas.o tcp/linux/src/tcp_hybla.o\ + tcp/linux/src/tcp_illinois.o tcp/linux/src/tcp_yeah.o \ + tcp/linux/src/tcp_veno.o tcp/linux/src/tcp_compound.o tcp/linux/src/tcp_lp.o\ + tcp/linux/ns-linux-c.o tcp/linux/ns-linux-param.o + +OBJ_COMPAT = $(OBJ_GETOPT) common/win32.o +#XXX compat/win32x.o compat/tkConsole.o + +OBJ_EMULATE_CC = \ + emulate/net-ip.o \ + emulate/net.o \ + emulate/tap.o \ + emulate/ether.o \ + emulate/internet.o \ + emulate/ping_responder.o \ + emulate/arp.o \ + emulate/icmp.o \ + emulate/net-pcap.o \ + emulate/nat.o \ + emulate/iptap.o \ + emulate/tcptap.o + +OBJ_EMULATE_C = \ + emulate/inet.o + +OBJ_GEN = $(GEN_DIR)version.o $(GEN_DIR)ns_tcl.o $(GEN_DIR)ptypes.o + +OBJ_CPP = $(OBJ_NRL_CPP) + +SRC = $(OBJ_C:.o=.c) $(OBJ_CC:.o=.cc) $(OBJ_CPP:.o=.cpp) \ + $(OBJ_EMULATE_C:.o=.c) $(OBJ_EMULATE_CC:.o=.cc) \ + common/tclAppInit.cc common/tkAppInit.cc + +OBJ = $(OBJ_C) $(OBJ_CC) $(OBJ_GEN) $(OBJ_COMPAT) $(OBJ_CPP) + +CLEANFILES = ns nse nsx ns.dyn $(OBJ) $(OBJ_EMULATE_CC) \ + $(OBJ_EMULATE_C) common/tclAppInit.o common/main-monolithic.o \ + common/tkAppInit.o nstk \ + $(GEN_DIR)* $(NS).core core core.$(NS) core.$(NSX) core.$(NSE) \ + common/ptypes2tcl common/ptypes2tcl.o + +SUBDIRS=\ + indep-utils/cmu-scen-gen/setdest \ + indep-utils/webtrace-conv/dec \ + indep-utils/webtrace-conv/epa \ + indep-utils/webtrace-conv/nlanr \ + indep-utils/webtrace-conv/ucb + +BUILD_NSE = @build_nse@ + +all: $(NS) $(BUILD_NSE) $(NSTK) all-recursive Makefile + + +all-recursive: + for i in $(SUBDIRS); do ( cd $$i; $(MAKE) all; ) done + + + + +ifeq ($(NSLIB),libns.dll) + +# This is for cygwin + +NS_CPPFLAGS = -DNSLIBNAME=\"$(NSLIB)\" +NS_LIBS = @DL_LIBS@ + +$(NSLIB): $(OBJ) common/tclAppInit.o + $(LINK) -shared $(LDFLAGS) \ + $(LDOUT)$@ \ + -Wl,--export-all-symbols \ + -Wl,--enable-auto-import \ + -Wl,--out-implib=$@.a \ + -Wl,--whole-archive $^ \ + -Wl,--no-whole-archive @V_IMPORT_LIBS@ + +$(NS): $(NSLIB) common/main-modular.cc + $(LINK) $(NS_CPPFLAGS) $(LDFLAGS) $(LDOUT)$@ common/main-modular.cc $(NS_LIBS) + +else + +# default for all systems but cygwin + +$(NS): $(OBJ) common/tclAppInit.o common/main-monolithic.o + $(LINK) $(LDFLAGS) $(LDOUT)$@ $^ $(LIB) + +endif + + + +Makefile: Makefile.in + @echo "Makefile.in is newer than Makefile." + @echo "You need to re-run configure." + false + +$(NSE): $(OBJ) common/tclAppInit.o common/main-monolithic.o $(OBJ_EMULATE_CC) $(OBJ_EMULATE_C) + $(LINK) $(LDFLAGS) $(LDOUT)$@ $^ $(LIB) + +$(NSTK): $(OBJ) common/tkAppInit.o + $(LINK) $(LDFLAGS) $(LDOUT)$@ $^ $(LIB) + +ns.dyn: $(OBJ) common/tclAppInit.o common/main-monolithic.o + $(LINK) $(LDFLAGS) -o $@ $^ $(LIB) + +PURIFY = purify -cache-dir=/tmp +ns-pure: $(OBJ) common/tclAppInit.o common/main-monolithic.o + $(PURIFY) $(LINK) $(LDFLAGS) -o $@ $^ $(LIB) + +NS_TCL_LIB = \ + tcl/lib/ns-compat.tcl \ + tcl/lib/ns-default.tcl \ + tcl/lib/ns-errmodel.tcl \ + tcl/lib/ns-lib.tcl \ + tcl/lib/ns-link.tcl \ + tcl/lib/ns-mobilenode.tcl \ + tcl/lib/ns-sat.tcl \ + tcl/lib/ns-cmutrace.tcl \ + tcl/lib/ns-node.tcl \ + tcl/lib/ns-rtmodule.tcl \ + tcl/lib/ns-hiernode.tcl \ + tcl/lib/ns-packet.tcl \ + tcl/lib/ns-queue.tcl \ + tcl/lib/ns-source.tcl \ + tcl/lib/ns-nam.tcl \ + tcl/lib/ns-trace.tcl \ + tcl/lib/ns-agent.tcl \ + tcl/lib/ns-random.tcl \ + tcl/lib/ns-namsupp.tcl \ + tcl/lib/ns-address.tcl \ + tcl/lib/ns-intserv.tcl \ + tcl/lib/ns-autoconf.tcl \ + tcl/rtp/session-rtp.tcl \ + tcl/lib/ns-mip.tcl \ + tcl/rtglib/dynamics.tcl \ + tcl/rtglib/route-proto.tcl \ + tcl/rtglib/algo-route-proto.tcl \ + tcl/rtglib/ns-rtProtoLS.tcl \ + tcl/interface/ns-iface.tcl \ + tcl/mcast/BST.tcl \ + tcl/mcast/ns-mcast.tcl \ + tcl/mcast/McastProto.tcl \ + tcl/mcast/DM.tcl \ + tcl/mcast/srm.tcl \ + tcl/mcast/srm-adaptive.tcl \ + tcl/mcast/srm-ssm.tcl \ + tcl/mcast/timer.tcl \ + tcl/mcast/McastMonitor.tcl \ + tcl/mobility/dsdv.tcl \ + tcl/mobility/dsr.tcl \ + tcl/ctr-mcast/CtrMcast.tcl \ + tcl/ctr-mcast/CtrMcastComp.tcl \ + tcl/ctr-mcast/CtrRPComp.tcl \ + tcl/rlm/rlm.tcl \ + tcl/rlm/rlm-ns.tcl \ + tcl/session/session.tcl \ + tcl/lib/ns-route.tcl \ + tcl/emulate/ns-emulate.tcl \ + tcl/lan/vlan.tcl \ + tcl/lan/abslan.tcl \ + tcl/lan/ns-ll.tcl \ + tcl/lan/ns-mac.tcl \ + tcl/webcache/http-agent.tcl \ + tcl/webcache/http-server.tcl \ + tcl/webcache/http-cache.tcl \ + tcl/webcache/http-mcache.tcl \ + tcl/webcache/webtraf.tcl \ + tcl/webcache/empweb.tcl \ + tcl/webcache/empftp.tcl \ + tcl/plm/plm.tcl \ + tcl/plm/plm-ns.tcl \ + tcl/plm/plm-topo.tcl \ + tcl/mpls/ns-mpls-classifier.tcl \ + tcl/mpls/ns-mpls-ldpagent.tcl \ + tcl/mpls/ns-mpls-node.tcl \ + tcl/mpls/ns-mpls-simulator.tcl \ + tcl/lib/ns-pushback.tcl \ + tcl/lib/ns-srcrt.tcl \ + tcl/mcast/ns-lms.tcl \ + tcl/lib/ns-qsnode.tcl \ + @V_NS_TCL_LIB_STL@ + +$(GEN_DIR)ns_tcl.cc: $(NS_TCL_LIB) + $(TCLSH) bin/tcl-expand.tcl tcl/lib/ns-lib.tcl @V_NS_TCL_LIB_STL@ | $(TCL2C) et_ns_lib > $@ + +$(GEN_DIR)version.c: VERSION + $(RM) $@ + $(TCLSH) bin/string2c.tcl version_string < VERSION > $@ + +$(GEN_DIR)ptypes.cc: common/ptypes2tcl common/packet.h + ./common/ptypes2tcl > $@ + +common/ptypes2tcl: common/ptypes2tcl.o + $(LINK) $(LDFLAGS) $(LDOUT)$@ common/ptypes2tcl.o + +common/ptypes2tcl.o: common/ptypes2tcl.cc common/packet.h + +dirs: + for d in $(DESTDIR)$(MANDEST)/man1; do \ + if [ ! -d $$d ]; then \ + mkdir -p $$d ;\ + fi;\ + done + + +install: dirs force install-ns install-man + +install-ns: force + $(INSTALL) -m 755 ns $(DESTDIR)$(BINDEST) + +install-man: force + $(INSTALL) -m 644 ns.1 $(DESTDIR)$(MANDEST)/man1 + +install-recursive: force + for i in $(SUBDIRS); do ( cd $$i; $(MAKE) install; ) done + +clean: clean-recursive + $(RM) $(CLEANFILES) + +clean-recursive: + for i in $(SUBDIRS); do ( cd $$i; $(MAKE) clean; ) done + +nrlclean: + $(RM) $(OBJ_CPP) + +AUTOCONF_GEN = tcl/lib/ns-autoconf.tcl +distclean: distclean-recursive + $(RM) $(CLEANFILES) Makefile config.cache config.log config.status \ + autoconf.h gnuc.h os-proto.h $(AUTOCONF_GEN); \ + $(MV) .configure .configure- ;\ + echo "Moved .configure to .configure-" + +distclean-recursive: + for i in $(SUBDIRS); do ( cd $$i; $(MAKE) clean; $(RM) Makefile; ) done + +tags: force + ctags -wtd *.cc *.h webcache/*.cc webcache/*.h dsdv/*.cc dsdv/*.h \ + dsr/*.cc dsr/*.h webcache/*.cc webcache/*.h lib/*.cc lib/*.h \ + ../Tcl/*.cc ../Tcl/*.h + +TAGS: force + etags *.cc *.h webcache/*.cc webcache/*.h dsdv/*.cc dsdv/*.h \ + dsr/*.cc dsr/*.h webcache/*.cc webcache/*.h lib/*.cc lib/*.h \ + ../Tcl/*.cc ../Tcl/*.h + +tcl/lib/TAGS: force + ( \ + cd tcl/lib; \ + $(TCLSH) ../../bin/tcl-expand.tcl ns-lib.tcl | grep '^### tcl-expand.tcl: begin' | awk '{print $$5}' >.tcl_files; \ + etags --lang=none -r '/^[ \t]*proc[ \t]+\([^ \t]+\)/\1/' `cat .tcl_files`; \ + etags --append --lang=none -r '/^\([A-Z][^ \t]+\)[ \t]+\(instproc\|proc\)[ \t]+\([^ \t]+\)[ \t]+/\1::\3/' `cat .tcl_files`; \ + ) + +depend: $(SRC) + $(MKDEP) $(CFLAGS) $(INCLUDES) $(SRC) 2>&1 > /dev/null + +srctar: + @cwd=`pwd` ; dir=`basename $$cwd` ; \ + name=ns-`cat VERSION | tr A-Z a-z` ; \ + tar=ns-src-`cat VERSION`.tar.gz ; \ + list="" ; \ + for i in `cat FILES` ; do list="$$list $$name/$$i" ; done; \ + echo \ + "(rm -f $$tar; cd .. ; ln -s $$dir $$name)" ; \ + (rm -f $$tar; cd .. ; ln -s $$dir $$name) ; \ + echo \ + "(cd .. ; tar cfhz $$tar [lots of files])" ; \ + (cd .. ; tar cfhz - $$list) > $$tar ; \ + echo \ + "rm ../$$name; chmod 444 $$tar" ; \ + rm ../$$name; chmod 444 $$tar + +force: + +test: force + ./validate + +# Create makefile.vc for Win32 development by replacing: +# "# !include ..." -> "!include ..." +makefile.vc: Makefile.in + $(PERL) bin/gen-vcmake.pl < Makefile.in > makefile.vc +# $(PERL) -pe 's/^# (\!include)/\!include/o' < Makefile.in > makefile.vc diff --git a/src/sim/ns/ns235/packet.h b/src/sim/ns/ns235/packet.h new file mode 100644 index 0000000..8208513 --- /dev/null +++ b/src/sim/ns/ns235/packet.h @@ -0,0 +1,853 @@ +/* -*- Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */ +/* + * Copyright (c) 1997 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#) $Header: /cvsroot/nsnam/ns-2/common/packet.h,v 1.107 2010/05/09 22:28:41 tom_henderson Exp $ (LBL) + */ + +#ifndef ns_packet_h +#define ns_packet_h + +#include +#include + +#include "config.h" +#include "scheduler.h" +#include "object.h" +#include "lib/bsd-list.h" +#include "packet-stamp.h" +#include "ns-process.h" + +// Used by wireless routing code to attach routing agent +#define RT_PORT 255 /* port that all route msgs are sent to */ + +#define HDR_CMN(p) (hdr_cmn::access(p)) +#define HDR_ARP(p) (hdr_arp::access(p)) +#define HDR_MAC(p) (hdr_mac::access(p)) +#define HDR_MAC802_11(p) ((hdr_mac802_11 *)hdr_mac::access(p)) +#define HDR_MAC_TDMA(p) ((hdr_mac_tdma *)hdr_mac::access(p)) +#define HDR_SMAC(p) ((hdr_smac *)hdr_mac::access(p)) +#define HDR_LL(p) (hdr_ll::access(p)) +#define HDR_HDLC(p) ((hdr_hdlc *)hdr_ll::access(p)) +#define HDR_IP(p) (hdr_ip::access(p)) +#define HDR_RTP(p) (hdr_rtp::access(p)) +#define HDR_TCP(p) (hdr_tcp::access(p)) +#define HDR_SCTP(p) (hdr_sctp::access(p)) +#define HDR_SR(p) (hdr_sr::access(p)) +#define HDR_TFRC(p) (hdr_tfrc::access(p)) +#define HDR_TORA(p) (hdr_tora::access(p)) +#define HDR_IMEP(p) (hdr_imep::access(p)) +#define HDR_CDIFF(p) (hdr_cdiff::access(p)) /* chalermak's diffusion*/ +//#define HDR_DIFF(p) (hdr_diff::access(p)) /* SCADD's diffusion ported into ns */ +#define HDR_LMS(p) (hdr_lms::access(p)) +#define HDR_PROTOLIBMK(p) (hdr_protolibmk::access(p)) + +/* --------------------------------------------------------------------*/ + +/* + * modified ns-2.33, adding support for dynamic libraries + * + * packet_t is changed from enum to unsigned int in order to allow + * dynamic definition of new packet types within dynamic libraries. + * Pre-defined packet types are implemented as static const. + * + */ + +typedef unsigned int packet_t; + +static const packet_t PT_TCP = 0; +static const packet_t PT_UDP = 1; +static const packet_t PT_CBR = 2; +static const packet_t PT_AUDIO = 3; +static const packet_t PT_VIDEO = 4; +static const packet_t PT_ACK = 5; +static const packet_t PT_START = 6; +static const packet_t PT_STOP = 7; +static const packet_t PT_PRUNE = 8; +static const packet_t PT_GRAFT = 9; +static const packet_t PT_GRAFTACK = 10; +static const packet_t PT_JOIN = 11; +static const packet_t PT_ASSERT = 12; +static const packet_t PT_MESSAGE = 13; +static const packet_t PT_RTCP = 14; +static const packet_t PT_RTP = 15; +static const packet_t PT_RTPROTO_DV = 16; +static const packet_t PT_CtrMcast_Encap = 17; +static const packet_t PT_CtrMcast_Decap = 18; +static const packet_t PT_SRM = 19; + /* simple signalling messages */ +static const packet_t PT_REQUEST = 20; +static const packet_t PT_ACCEPT = 21; +static const packet_t PT_CONFIRM = 22; +static const packet_t PT_TEARDOWN = 23; +static const packet_t PT_LIVE = 24; // packet from live network +static const packet_t PT_REJECT = 25; + +static const packet_t PT_TELNET = 26; // not needed: telnet use TCP +static const packet_t PT_FTP = 27; +static const packet_t PT_PARETO = 28; +static const packet_t PT_EXP = 29; +static const packet_t PT_INVAL = 30; +static const packet_t PT_HTTP = 31; + + /* new encapsulator */ +static const packet_t PT_ENCAPSULATED = 32; +static const packet_t PT_MFTP = 33; + + /* CMU/Monarch's extnsions */ +static const packet_t PT_ARP = 34; +static const packet_t PT_MAC = 35; +static const packet_t PT_TORA = 36; +static const packet_t PT_DSR = 37; +static const packet_t PT_AODV = 38; +static const packet_t PT_IMEP = 39; + + // RAP packets +static const packet_t PT_RAP_DATA = 40; +static const packet_t PT_RAP_ACK = 41; + +static const packet_t PT_TFRC = 42; +static const packet_t PT_TFRC_ACK = 43; +static const packet_t PT_PING = 44; + +static const packet_t PT_PBC = 45; + // Diffusion packets - Chalermek +static const packet_t PT_DIFF = 46; + + // LinkState routing update packets +static const packet_t PT_RTPROTO_LS = 47; + + // MPLS LDP header +static const packet_t PT_LDP = 48; + + // GAF packet +static const packet_t PT_GAF = 49; + + // ReadAudio traffic +static const packet_t PT_REALAUDIO = 50; + + // Pushback Messages +static const packet_t PT_PUSHBACK = 51; + + #ifdef HAVE_STL + // Pragmatic General Multicast +static const packet_t PT_PGM = 52; + #endif //STL + // LMS packets +static const packet_t PT_LMS = 53; +static const packet_t PT_LMS_SETUP = 54; + +static const packet_t PT_SCTP = 55; +static const packet_t PT_SCTP_APP1 = 56; + + // SMAC packet +static const packet_t PT_SMAC = 57; + // XCP packet +static const packet_t PT_XCP = 58; + + // HDLC packet +static const packet_t PT_HDLC = 59; + + // Bell Labs Traffic Trace Type (PackMime OL) +static const packet_t PT_BLTRACE = 60; + + // AOMDV packet +static const packet_t PT_AOMDV = 61; + + // PUMA packet +static const packet_t PT_PUMA = 62; + + // DCCP packets +static const packet_t PT_DCCP = 63; +static const packet_t PT_DCCP_REQ = 64; +static const packet_t PT_DCCP_RESP = 65; +static const packet_t PT_DCCP_ACK = 66; +static const packet_t PT_DCCP_DATA = 67; +static const packet_t PT_DCCP_DATAACK = 68; +static const packet_t PT_DCCP_CLOSE = 69; +static const packet_t PT_DCCP_CLOSEREQ = 70; +static const packet_t PT_DCCP_RESET = 71; + + // M-DART packets +static const packet_t PT_MDART = 72; + +// NRL ProtoManetKernel packet +static const packet_t PT_PROTOLIBMK = 73; + + // insert new packet types here +static packet_t PT_NTYPE = 74; // This MUST be the LAST one + +enum packetClass +{ + UNCLASSIFIED, + ROUTING, + DATApkt + }; + + +/* + * ns-2.33 adding support for dynamic libraries + * + * The PacketClassifier class is needed to make + * p_info::data_packet(packet_t) work also with dynamically defined + * packet types. + * + */ +class PacketClassifier +{ + public: + PacketClassifier(): next_(0){} + virtual ~PacketClassifier() {} + void setNext(PacketClassifier *next){next_ = next;} + PacketClassifier *getNext(){return next_;} + packetClass classify(packet_t type) + { + packetClass c = getClass(type); + if(c == UNCLASSIFIED && next_) + c = next_->classify(type); + return c; + } + + protected: + //return 0 if the packet is unknown + virtual packetClass getClass(packet_t type) = 0; + PacketClassifier *next_; +}; + +class p_info { +public: + p_info() + { + initName(); + } + const char* name(packet_t p) const { + if ( p <= p_info::nPkt_ ) return name_[p]; + return 0; + } + static bool data_packet(packet_t type) { + return ( (type) == PT_TCP || \ + (type) == PT_TELNET || \ + (type) == PT_CBR || \ + (type) == PT_AUDIO || \ + (type) == PT_VIDEO || \ + (type) == PT_ACK || \ + (type) == PT_SCTP || \ + (type) == PT_SCTP_APP1 || \ + (type) == PT_HDLC \ + ); + } + static packetClass classify(packet_t type) { + if (type == PT_DSR || + type == PT_MESSAGE || + type == PT_TORA || + type == PT_PUMA || + type == PT_AODV || + type == PT_MDART || + type == PT_PROTOLIBMK) + return ROUTING; + if (type == PT_TCP || + type == PT_TELNET || + type == PT_CBR || + type == PT_AUDIO || + type == PT_VIDEO || + type == PT_ACK || + type == PT_SCTP || + type == PT_SCTP_APP1 || + type == PT_HDLC) + return DATApkt; + if (pc_) + return pc_->classify(type); + return UNCLASSIFIED; + } + static void addPacketClassifier(PacketClassifier *pc) + { + if(!pc) + return; + pc->setNext(pc_); + pc_ = pc; + } + static void initName() + { + if(nPkt_ >= PT_NTYPE+1) + return; + char **nameNew = new char*[PT_NTYPE+1]; + for(unsigned int i = (unsigned int)PT_SMAC+1; i < nPkt_; i++) + { + nameNew[i] = name_[i]; + } + if(!nPkt_) + delete [] name_; + name_ = nameNew; + nPkt_ = PT_NTYPE+1; + + + name_[PT_TCP]= "tcp"; + name_[PT_UDP]= "udp"; + name_[PT_CBR]= "cbr"; + name_[PT_AUDIO]= "audio"; + name_[PT_VIDEO]= "video"; + name_[PT_ACK]= "ack"; + name_[PT_START]= "start"; + name_[PT_STOP]= "stop"; + name_[PT_PRUNE]= "prune"; + name_[PT_GRAFT]= "graft"; + name_[PT_GRAFTACK]= "graftAck"; + name_[PT_JOIN]= "join"; + name_[PT_ASSERT]= "assert"; + name_[PT_MESSAGE]= "message"; + name_[PT_RTCP]= "rtcp"; + name_[PT_RTP]= "rtp"; + name_[PT_RTPROTO_DV]= "rtProtoDV"; + name_[PT_CtrMcast_Encap]= "CtrMcast_Encap"; + name_[PT_CtrMcast_Decap]= "CtrMcast_Decap"; + name_[PT_SRM]= "SRM"; + + name_[PT_REQUEST]= "sa_req"; + name_[PT_ACCEPT]= "sa_accept"; + name_[PT_CONFIRM]= "sa_conf"; + name_[PT_TEARDOWN]= "sa_teardown"; + name_[PT_LIVE]= "live"; + name_[PT_REJECT]= "sa_reject"; + + name_[PT_TELNET]= "telnet"; + name_[PT_FTP]= "ftp"; + name_[PT_PARETO]= "pareto"; + name_[PT_EXP]= "exp"; + name_[PT_INVAL]= "httpInval"; + name_[PT_HTTP]= "http"; + name_[PT_ENCAPSULATED]= "encap"; + name_[PT_MFTP]= "mftp"; + name_[PT_ARP]= "ARP"; + name_[PT_MAC]= "MAC"; + name_[PT_TORA]= "TORA"; + name_[PT_DSR]= "DSR"; + name_[PT_AODV]= "AODV"; + name_[PT_MDART]= "MDART"; + name_[PT_IMEP]= "IMEP"; + + name_[PT_RAP_DATA] = "rap_data"; + name_[PT_RAP_ACK] = "rap_ack"; + + name_[PT_TFRC]= "tcpFriend"; + name_[PT_TFRC_ACK]= "tcpFriendCtl"; + name_[PT_PING]="ping"; + + name_[PT_PBC] = "PBC"; + + /* For diffusion : Chalermek */ + name_[PT_DIFF] = "diffusion"; + + // Link state routing updates + name_[PT_RTPROTO_LS] = "rtProtoLS"; + + // MPLS LDP packets + name_[PT_LDP] = "LDP"; + + // for GAF + name_[PT_GAF] = "gaf"; + + // RealAudio packets + name_[PT_REALAUDIO] = "ra"; + + //pushback + name_[PT_PUSHBACK] = "pushback"; + +#ifdef HAVE_STL + // for PGM + name_[PT_PGM] = "PGM"; +#endif //STL + + // LMS entries + name_[PT_LMS]="LMS"; + name_[PT_LMS_SETUP]="LMS_SETUP"; + + name_[PT_SCTP]= "sctp"; + name_[PT_SCTP_APP1] = "sctp_app1"; + + // smac + name_[PT_SMAC]="smac"; + + // HDLC + name_[PT_HDLC]="HDLC"; + + // XCP + name_[PT_XCP]="xcp"; + + // Bell Labs (PackMime OL) + name_[PT_BLTRACE]="BellLabsTrace"; + + // AOMDV patch + name_[PT_AOMDV]= "AOMDV"; + + // PUMA + name_[PT_PUMA]="PUMA"; + + // DCCP + name_[PT_DCCP]="DCCP"; + name_[PT_DCCP_REQ]="DCCP_Request"; + name_[PT_DCCP_RESP]="DCCP_Response"; + name_[PT_DCCP_ACK]="DCCP_Ack"; + name_[PT_DCCP_DATA]="DCCP_Data"; + name_[PT_DCCP_DATAACK]="DCCP_DataAck"; + name_[PT_DCCP_CLOSE]="DCCP_Close"; + name_[PT_DCCP_CLOSEREQ]="DCCP_CloseReq"; + name_[PT_DCCP_RESET]="DCCP_Reset"; + + // NRL ProtoManetKernel + name_[PT_PROTOLIBMK]= "ProtolibMK"; + + name_[PT_NTYPE]= "undefined"; + } + static int addPacket(char *name); + static packet_t getType(const char *name) + { + for(unsigned int i = 0; i < nPkt_; i++) + { + if(strcmp(name, name_[i]) == 0) + return i; + } + return PT_NTYPE; + + } +private: + static char** name_; + static unsigned int nPkt_; + static PacketClassifier *pc_; +}; +extern p_info packet_info; /* map PT_* to string name */ +//extern char* p_info::name_[]; + +#define DATA_PACKET(type) ( (type) == PT_TCP || \ + (type) == PT_TELNET || \ + (type) == PT_CBR || \ + (type) == PT_AUDIO || \ + (type) == PT_VIDEO || \ + (type) == PT_ACK || \ + (type) == PT_SCTP || \ + (type) == PT_SCTP_APP1 \ + ) + +//#define OFFSET(type, field) ((long) &((type *)0)->field) +#define OFFSET(type, field) ( (char *)&( ((type *)256)->field ) - (char *)256) + +class PacketData : public AppData { +public: + PacketData(int sz) : AppData(PACKET_DATA) { + datalen_ = sz; + if (datalen_ > 0) + data_ = new unsigned char[datalen_]; + else + data_ = NULL; + } + PacketData(PacketData& d) : AppData(d) { + datalen_ = d.datalen_; + if (datalen_ > 0) { + data_ = new unsigned char[datalen_]; + memcpy(data_, d.data_, datalen_); + } else + data_ = NULL; + } + virtual ~PacketData() { + if (data_ != NULL) + delete []data_; + } + unsigned char* data() { return data_; } + + virtual int size() const { return datalen_; } + virtual AppData* copy() { return new PacketData(*this); } +private: + unsigned char* data_; + int datalen_; +}; + +//Monarch ext +typedef void (*FailureCallback)(Packet *,void *); + +class Packet : public Event { +private: + unsigned char* bits_; // header bits +// unsigned char* data_; // variable size buffer for 'data' +// unsigned int datalen_; // length of variable size buffer + AppData* data_; // variable size buffer for 'data' + static void init(Packet*); // initialize pkt hdr + bool fflag_; +protected: + static Packet* free_; // packet free list + int ref_count_; // free the pkt until count to 0 +public: + Packet* next_; // for queues and the free list + static int hdrlen_; + + Packet() : bits_(0), data_(0), ref_count_(0), next_(0) { } + inline unsigned char* bits() { return (bits_); } + inline Packet* copy() const; + inline Packet* refcopy() { ++ref_count_; return this; } + inline int& ref_count() { return (ref_count_); } + static inline Packet* alloc(); + static inline Packet* alloc(int); + inline void allocdata(int); + // dirty hack for diffusion data + inline void initdata() { data_ = 0;} + static inline void free(Packet*); + inline unsigned char* access(int off) const { + if (off < 0) + abort(); + return (&bits_[off]); + } + // This is used for backward compatibility, i.e., assuming user data + // is PacketData and return its pointer. + inline unsigned char* accessdata() const { + if (data_ == 0) + return 0; + assert(data_->type() == PACKET_DATA); + return (((PacketData*)data_)->data()); + } + // This is used to access application-specific data, not limited + // to PacketData. + inline AppData* userdata() const { + return data_; + } + inline void setdata(AppData* d) { + if (data_ != NULL) + delete data_; + data_ = d; + } + inline int datalen() const { return data_ ? data_->size() : 0; } + + // Monarch extn + + static void dump_header(Packet *p, int offset, int length); + + // the pkt stamp carries all info about how/where the pkt + // was sent needed for a receiver to determine if it correctly + // receives the pkt + PacketStamp txinfo_; + + /* + * According to cmu code: + * This flag is set by the MAC layer on an incoming packet + * and is cleared by the link layer. It is an ugly hack, but + * there's really no other way because NS always calls + * the recv() function of an object. + * + */ + u_int8_t incoming; + + //monarch extns end; +}; + +/* + * static constant associations between interface special (negative) + * values and their c-string representations that are used from tcl + */ +class iface_literal { +public: + enum iface_constant { + UNKN_IFACE= -1, /* + * iface value for locally originated packets + */ + ANY_IFACE= -2 /* + * hashnode with iif == ANY_IFACE_ + * matches any pkt iface (imported from TCL); + * this value should be different from + * hdr_cmn::UNKN_IFACE (packet.h) + */ + }; + iface_literal(const iface_constant i, const char * const n) : + value_(i), name_(n) {} + inline int value() const { return value_; } + inline const char * name() const { return name_; } +private: + const iface_constant value_; + /* strings used in TCL to access those special values */ + const char * const name_; +}; + +static const iface_literal UNKN_IFACE(iface_literal::UNKN_IFACE, "?"); +static const iface_literal ANY_IFACE(iface_literal::ANY_IFACE, "*"); + +/* + * Note that NS_AF_* doesn't necessarily correspond with + * the constants used in your system (because many + * systems don't have NONE or ILINK). + */ +enum ns_af_enum { NS_AF_NONE, NS_AF_ILINK, NS_AF_INET }; + +enum ModulationScheme {BPSK = 0, QPSK = 1, QAM16 = 2, QAM64 = 3}; + +struct hdr_cmn { + enum dir_t { DOWN= -1, NONE= 0, UP= 1 }; + packet_t ptype_; // packet type (see above) + int size_; // simulated packet size + int uid_; // unique id + int error_; // error flag + int errbitcnt_; // # of corrupted bits jahn + int fecsize_; + double ts_; // timestamp: for q-delay measurement + int iface_; // receiving interface (label) + dir_t direction_; // direction: 0=none, 1=up, -1=down + // source routing + char src_rt_valid; + double ts_arr_; // Required by Marker of JOBS + + //Monarch extn begins + nsaddr_t prev_hop_; // IP addr of forwarding hop + nsaddr_t next_hop_; // next hop for this packet + int addr_type_; // type of next_hop_ addr + nsaddr_t last_hop_; // for tracing on multi-user channels + + // AOMDV patch + int aomdv_salvage_count_; + + // called if pkt can't obtain media or isn't ack'd. not called if + // droped by a queue + FailureCallback xmit_failure_; + void *xmit_failure_data_; + + /* + * MONARCH wants to know if the MAC layer is passing this back because + * it could not get the RTS through or because it did not receive + * an ACK. + */ + int xmit_reason_; +#define XMIT_REASON_RTS 0x01 +#define XMIT_REASON_ACK 0x02 + + // filled in by GOD on first transmission, used for trace analysis + int num_forwards_; // how many times this pkt was forwarded + int opt_num_forwards_; // optimal #forwards + // Monarch extn ends; + + // tx time for this packet in sec + double txtime_; + inline double& txtime() { return(txtime_); } + + static int offset_; // offset for this header + inline static int& offset() { return offset_; } + inline static hdr_cmn* access(const Packet* p) { + return (hdr_cmn*) p->access(offset_); + } + + /* per-field member functions */ + inline packet_t& ptype() { return (ptype_); } + inline int& size() { return (size_); } + inline int& uid() { return (uid_); } + inline int& error() { return error_; } + inline int& errbitcnt() {return errbitcnt_; } + inline int& fecsize() {return fecsize_; } + inline double& timestamp() { return (ts_); } + inline int& iface() { return (iface_); } + inline dir_t& direction() { return (direction_); } + // monarch_begin + inline nsaddr_t& next_hop() { return (next_hop_); } + inline int& addr_type() { return (addr_type_); } + inline int& num_forwards() { return (num_forwards_); } + inline int& opt_num_forwards() { return (opt_num_forwards_); } + //monarch_end + + ModulationScheme mod_scheme_; + inline ModulationScheme& mod_scheme() { return (mod_scheme_); } +}; + + +class PacketHeaderClass : public TclClass { +protected: + PacketHeaderClass(const char* classname, int hdrsize); + virtual int method(int argc, const char*const* argv); + void field_offset(const char* fieldname, int offset); + inline void bind_offset(int* off) { offset_ = off; } + inline void offset(int* off) {offset_= off;} + int hdrlen_; // # of bytes for this header + int* offset_; // offset for this header +public: + virtual void bind(); + virtual void export_offsets(); + TclObject* create(int argc, const char*const* argv); +}; + + +inline void Packet::init(Packet* p) +{ + bzero(p->bits_, hdrlen_); +} + +inline Packet* Packet::alloc() +{ + Packet* p = free_; + if (p != 0) { + assert(p->fflag_ == FALSE); + free_ = p->next_; + assert(p->data_ == 0); + p->uid_ = 0; + p->time_ = 0; + } else { + p = new Packet; + p->bits_ = new unsigned char[hdrlen_]; + if (p == 0 || p->bits_ == 0) + abort(); + } + init(p); // Initialize bits_[] + (HDR_CMN(p))->next_hop_ = -2; // -1 reserved for IP_BROADCAST + (HDR_CMN(p))->last_hop_ = -2; // -1 reserved for IP_BROADCAST + p->fflag_ = TRUE; + (HDR_CMN(p))->direction() = hdr_cmn::DOWN; + /* setting all direction of pkts to be downward as default; + until channel changes it to +1 (upward) */ + p->next_ = 0; + return (p); +} + +/* + * Allocate an n byte data buffer to an existing packet + * + * To set application-specific AppData, use Packet::setdata() + */ +inline void Packet::allocdata(int n) +{ + assert(data_ == 0); + data_ = new PacketData(n); + if (data_ == 0) + abort(); +} + +/* allocate a packet with an n byte data buffer */ +inline Packet* Packet::alloc(int n) +{ + Packet* p = alloc(); + if (n > 0) + p->allocdata(n); + return (p); +} + +#include "dccp/dccp_packets.h" + +inline void Packet::free(Packet* p) +{ + hdr_dccp *dccph; + if (p->fflag_) { + if (p->ref_count_ == 0) { + + //free DCCP options on dropped packets + switch (HDR_CMN(p)->ptype_){ + case PT_DCCP: + case PT_DCCP_REQ: + case PT_DCCP_RESP: + case PT_DCCP_ACK: + case PT_DCCP_DATA: + case PT_DCCP_DATAACK: + case PT_DCCP_CLOSE: + case PT_DCCP_CLOSEREQ: + case PT_DCCP_RESET: + dccph = hdr_dccp::access(p); + if (dccph->options_ != NULL){ + delete (dccph->options_); + } + break; + default: + ; + } + + /* + * A packet's uid may be < 0 (out of a event queue), or + * == 0 (newed but never gets into the event queue. + */ + assert(p->uid_ <= 0); + // Delete user data because we won't need it any more. + if (p->data_ != 0) { + delete p->data_; + p->data_ = 0; + } + init(p); + p->next_ = free_; + free_ = p; + p->fflag_ = FALSE; + } else { + --p->ref_count_; + } + } +} + +inline Packet* Packet::copy() const +{ + hdr_dccp *dccph, *dccph_p; + Packet* p = alloc(); + memcpy(p->bits(), bits_, hdrlen_); + + //copy DCCP options_, since it is a pointer + switch (HDR_CMN(this)->ptype_){ + case PT_DCCP: + case PT_DCCP_REQ: + case PT_DCCP_RESP: + case PT_DCCP_ACK: + case PT_DCCP_DATA: + case PT_DCCP_DATAACK: + case PT_DCCP_CLOSE: + case PT_DCCP_CLOSEREQ: + case PT_DCCP_RESET: + dccph = hdr_dccp::access(this); + dccph_p = hdr_dccp::access(p); + if (dccph->options_ != NULL) + dccph_p->options_ = new DCCPOptions(*dccph->options_); + break; + default: + ; + } + + if (data_) + p->data_ = data_->copy(); + p->txinfo_.init(&txinfo_); + + return (p); +} + +inline void +Packet::dump_header(Packet *p, int offset, int length) +{ + assert(offset + length <= p->hdrlen_); + struct hdr_cmn *ch = HDR_CMN(p); + + fprintf(stderr, "\nPacket ID: %d\n", ch->uid()); + + for(int i = 0; i < length ; i+=16) { + fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + p->bits_[offset + i], p->bits_[offset + i + 1], + p->bits_[offset + i + 2], p->bits_[offset + i + 3], + p->bits_[offset + i + 4], p->bits_[offset + i + 5], + p->bits_[offset + i + 6], p->bits_[offset + i + 7], + p->bits_[offset + i + 8], p->bits_[offset + i + 9], + p->bits_[offset + i + 10], p->bits_[offset + i + 11], + p->bits_[offset + i + 12], p->bits_[offset + i + 13], + p->bits_[offset + i + 14], p->bits_[offset + i + 15]); + } +} + +#endif diff --git a/src/sim/ns/ns235/priqueue.cc b/src/sim/ns/ns235/priqueue.cc new file mode 100644 index 0000000..e41d10f --- /dev/null +++ b/src/sim/ns/ns235/priqueue.cc @@ -0,0 +1,196 @@ +/* -*- Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */ +/* + * Copyright (c) 1997 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the Computer Systems + * Engineering Group at Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* Ported from CMU/Monarch's code, nov'98 -Padma.*/ + +/* -*- c++ -*- + priqueue.cc + + A simple priority queue with a remove packet function + $Id: priqueue.cc,v 1.9 2010/05/09 22:28:41 tom_henderson Exp $ + */ + +#include +#include +#include +#include +#include + +#include "priqueue.h" + +typedef int (*PacketFilter)(Packet *, void *); + +PriQueue_List PriQueue::prhead = { 0 }; + +static class PriQueueClass : public TclClass { +public: + PriQueueClass() : TclClass("Queue/DropTail/PriQueue") {} + TclObject* create(int, const char*const*) { + return (new PriQueue); + } +} class_PriQueue; + + +PriQueue::PriQueue() : DropTail() +{ + bind("Prefer_Routing_Protocols", &Prefer_Routing_Protocols); + LIST_INSERT_HEAD(&prhead, this, link); +} + +int +PriQueue::command(int argc, const char*const* argv) +{ + if (argc == 2 && strcasecmp(argv[1], "reset") == 0) + { + Terminate(); + //FALL-THROUGH to give parents a chance to reset + } + return DropTail::command(argc, argv); +} + +void +PriQueue::recv(Packet *p, Handler *h) +{ + struct hdr_cmn *ch = HDR_CMN(p); + + if(Prefer_Routing_Protocols) { + + switch(ch->ptype()) { + case PT_DSR: + case PT_MESSAGE: + case PT_TORA: + case PT_AODV: + case PT_PROTOLIBMK: + case PT_AOMDV: + case PT_MDART: + recvHighPriority(p, h); + break; + + default: + Queue::recv(p, h); + } + } + else { + Queue::recv(p, h); + } +} + + +void +PriQueue::recvHighPriority(Packet *p, Handler *) + // insert packet at front of queue +{ + q_->enqueHead(p); + if (q_->length() >= qlim_) + { + Packet *to_drop = q_->lookup(q_->length()-1); + q_->remove(to_drop); + drop(to_drop); + } + + if (!blocked_) { + /* + * We're not blocked. Get a packet and send it on. + * We perform an extra check because the queue + * might drop the packet even if it was + * previously empty! (e.g., RED can do this.) + */ + p = deque(); + if (p != 0) { + blocked_ = 1; + target_->recv(p, &qh_); + } + } +} + +void +PriQueue::filter(PacketFilter filter, void * data) + // apply filter to each packet in queue, + // - if filter returns 0 leave packet in queue + // - if filter returns 1 remove packet from queue +{ + int i = 0; + while (i < q_->length()) + { + Packet *p = q_->lookup(i); + if (filter(p,data)) + { + q_->remove(p); // decrements q len + } + else i++; + } +} + +Packet* +PriQueue::filter(nsaddr_t id) +{ + Packet *p = 0; + Packet *pp = 0; + struct hdr_cmn *ch; + + for(p = q_->head(); p; p = p->next_) { + ch = HDR_CMN(p); + if(ch->next_hop() == id) + break; + pp = p; + } + + /* + * Deque Packet + */ + if(p) { + if(pp == 0) + q_->remove(p); + else + q_->remove(p, pp); + } + return p; +} + +/* + * Called at the end of the simulation to purge the IFQ. + */ +void +PriQueue::Terminate() +{ + Packet *p; + while((p = deque())) { + drop(p, DROP_END_OF_SIMULATION); + //drop(p); + + } +} + + + + diff --git a/src/sim/ns/nsProtoSimAgent.h b/src/sim/ns/nsProtoSimAgent.h index 6e60180..cd77c0d 100644 --- a/src/sim/ns/nsProtoSimAgent.h +++ b/src/sim/ns/nsProtoSimAgent.h @@ -193,7 +193,8 @@ class NsProtoSimAgent : public Agent, public TimerHandler, public ProtoSimAgent }; // end class NsProtoSimAgent::UdpSocketAgent - ProtoSimAgent::SocketProxy::List socket_proxy_list; + //ProtoSimAgent::SocketProxy::List socket_proxy_list; + NSSocketProxy::List socket_proxy_list; Scheduler* scheduler; }; // end class NsProtoSimAgent diff --git a/src/sim/ns/nsRouteMgr.cpp b/src/sim/ns/nsRouteMgr.cpp index 297a307..f80d768 100644 --- a/src/sim/ns/nsRouteMgr.cpp +++ b/src/sim/ns/nsRouteMgr.cpp @@ -13,7 +13,7 @@ class NsRouteMgr : public ProtoRouteMgr NsRouteMgr(); ~NsRouteMgr(); virtual bool Open(const void* userData = NULL); - virtual bool IsOpen() const {return (NULL != node_id);} + virtual bool IsOpen() const {return (0 != node_id);} virtual void Close(); virtual bool GetAllRoutes(ProtoAddress::Type addrType, diff --git a/src/sim/ns/nsTCPProtoSocketAgent.cpp b/src/sim/ns/nsTCPProtoSocketAgent.cpp index 8b5e796..0ac36dd 100644 --- a/src/sim/ns/nsTCPProtoSocketAgent.cpp +++ b/src/sim/ns/nsTCPProtoSocketAgent.cpp @@ -1,8 +1,6 @@ /* * TCPProtoSocketAgent.cpp * - * Copyright 2007 __MyCompanyName__. All rights reserved. - * */ #include "nsTCPProtoSocketAgent.h" diff --git a/src/sim/ns/nsTCPProtoSocketAgent.h b/src/sim/ns/nsTCPProtoSocketAgent.h index 88a3871..fb7e837 100644 --- a/src/sim/ns/nsTCPProtoSocketAgent.h +++ b/src/sim/ns/nsTCPProtoSocketAgent.h @@ -1,8 +1,6 @@ /* * TCPProtoSocketAgent.h * - * Copyright 2007 __MyCompanyName__. All rights reserved. - * */ #ifndef TCPProtoSocketAgent_h diff --git a/src/sim/ns/tcp/SimpleList.cpp b/src/sim/ns/tcp/SimpleList.cpp index b1eb0d9..c3dd472 100644 --- a/src/sim/ns/tcp/SimpleList.cpp +++ b/src/sim/ns/tcp/SimpleList.cpp @@ -3,7 +3,6 @@ * AgentJ * * Created by Ian Taylor on 17/07/2008. - * Copyright 2008 __MyCompanyName__. All rights reserved. * */ diff --git a/src/sim/ns/tcp/TCPSocketFactory.cpp b/src/sim/ns/tcp/TCPSocketFactory.cpp index b2255ca..3f16483 100644 --- a/src/sim/ns/tcp/TCPSocketFactory.cpp +++ b/src/sim/ns/tcp/TCPSocketFactory.cpp @@ -3,7 +3,6 @@ * FullerTCP * * Created by Ian Taylor on 07/03/2007. - * Copyright 2007 __MyCompanyName__. All rights reserved. * */ diff --git a/src/sim/ns/tcp/TCPSocketFactory.h b/src/sim/ns/tcp/TCPSocketFactory.h index 5a362c5..fd97b88 100644 --- a/src/sim/ns/tcp/TCPSocketFactory.h +++ b/src/sim/ns/tcp/TCPSocketFactory.h @@ -3,7 +3,6 @@ * FullerTCP * * Created by Ian Taylor on 07/03/2007. - * Copyright 2007 __MyCompanyName__. All rights reserved. * */ diff --git a/src/unix/bpfCap.cpp b/src/unix/bpfCap.cpp index 0bb4e1b..2cd4134 100644 --- a/src/unix/bpfCap.cpp +++ b/src/unix/bpfCap.cpp @@ -37,16 +37,14 @@ class BpfCap : public ProtoCap bool Open(const char* interfaceName = NULL); void Close(); - bool Send(const char* buffer, unsigned int buflen); - bool Forward(char* buffer, unsigned int buflen); + bool Send(const char* buffer, unsigned int& numBytes); bool Recv(char* buffer, unsigned int& numBytes, Direction* direction = NULL); private: char* bpf_buffer; unsigned int bpf_buflen; unsigned int bpf_captured; - unsigned int bpf_index; - char eth_addr[6]; + unsigned int bpf_index; }; // end class BpfCap @@ -99,9 +97,7 @@ bool BpfCap::Open(const char* interfaceName) } ProtoAddress macAddr; - if (ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, macAddr)) - memcpy(eth_addr, macAddr.GetRawHostAddress(), 6); - else + if (!ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::ETH, if_addr)) PLOG(PL_ERROR, "BpfCap::Open() warning: unable to get MAC address for interface \"%s\"\n", interfaceName); int ifIndex = ProtoSocket::GetInterfaceIndex(interfaceName); @@ -142,6 +138,7 @@ bool BpfCap::Open(const char* interfaceName) unsigned int buflen; if ((ioctl(fd, BIOCGBLEN, (caddr_t)&buflen) < 0) || buflen < 32768) buflen = 32768; + for ( ; buflen != 0; buflen >>= 1) { ioctl(fd, BIOCSBLEN, (caddr_t)&buflen); @@ -156,7 +153,8 @@ bool BpfCap::Open(const char* interfaceName) close(fd); return false; } - } + } + if (0 == buflen) { PLOG(PL_ERROR, "BpfCap::Open() unable to set bpf buffer\n"); @@ -262,10 +260,10 @@ void BpfCap::Close() bool BpfCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) { - if (NULL != direction) *direction = UNSPECIFIED; + if (NULL != direction) *direction = INBOUND; if (bpf_index >= bpf_captured) { - while (1) + for (;;) { ssize_t result = read(descriptor, bpf_buffer, bpf_buflen); if (result < 0) @@ -296,7 +294,8 @@ bool BpfCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) // Determine next packet (if applicable) if (bpf_captured > bpf_index) { - struct bpf_hdr* bpfHdr = (struct bpf_hdr*)(bpf_buffer + bpf_index); + // The (void*) cast here avoids alignment warnings from the compiler + struct bpf_hdr* bpfHdr = (struct bpf_hdr*)((void*)(bpf_buffer + bpf_index)); if (numBytes >= bpfHdr->bh_caplen) { memcpy(buffer, bpf_buffer+bpf_index+bpfHdr->bh_hdrlen, bpfHdr->bh_caplen); @@ -313,9 +312,9 @@ bool BpfCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) { numBytes = 0; } - if ((NULL != direction) && (0 == memcmp(eth_addr, buffer+6, 6))) + // TBD - make sure this doesn't screw up inbound multicast/broadcast traffic + if ((NULL != direction) && (0 == memcmp(if_addr.GetRawHostAddress(), buffer+6, 6))) *direction = OUTBOUND; - return true; } // end BpfCap::Recv() @@ -330,7 +329,7 @@ bool BpfCap::Recv(char* buffer, unsigned int& numBytes, Direction* direction) * @return success or failure indicator */ -bool BpfCap::Send(const char* buffer, unsigned int buflen) +bool BpfCap::Send(const char* buffer, unsigned int& numBytes) { // Make sure packet is a type that is OK for us to send // (Some packets seem to cause a PF_PACKET socket trouble) @@ -339,79 +338,33 @@ bool BpfCap::Send(const char* buffer, unsigned int buflen) type = ntohs(type); if (type <= 0x05dc) // assume it's 802.3 Length and ignore { - PLOG(PL_DEBUG, "BpfCap::Send() unsupported 802.3 frame (len = %04x)\n", type); - return false; + PLOG(PL_ERROR, "BpfCap::Send() unsupported 802.3 frame (len = %04x)\n", type); + return false; } - - unsigned int put = 0; - while (put < buflen) + for (;;) { - ssize_t result = write(descriptor, buffer, buflen); - if (result > 0) - { - put += result; - } - else + ssize_t result = write(descriptor, buffer, numBytes); + if (result < 0) { - if (EINTR == errno) + switch (errno) { - continue; // try again - } - else - { - PLOG(PL_ERROR, "BpfCap::Send() error: %s\n", GetErrorString()); - return false; - } - } - } - return true; -} // end BpfCap::Send() -/** - * @brief Changes the mac addr to our own and writes packet to the bpf descriptor. - * - * 802.3 frames are not supported - * - * @param buffer - * @param buflen - * - * @return success or failure indicator - */ - -bool BpfCap::Forward(char* buffer, unsigned int buflen) -{ - // Make sure packet is a type that is OK for us to send - // (Some packets seem to cause a PF_PACKET socket trouble) - UINT16 type; - memcpy(&type, buffer+12, 2); - type = ntohs(type); - if (type <= 0x05dc) // assume it's 802.3 Length and ignore - { - PLOG(PL_DEBUG, "BpfCap::Forward() unsupported 802.3 frame (len = %04x)\n", type); - return false; - } - // Change the src MAC addr to our own - // (TBD) allow caller to specify dst MAC addr ??? - memcpy(buffer+6, eth_addr, 6); - unsigned int put = 0; - while (put < buflen) - { - ssize_t result = write(descriptor, buffer, buflen); - if (result > 0) - { - put += result; + case EINTR: + continue; // try again + case EWOULDBLOCK: + numBytes = 0; + case ENOBUFS: + // because this doesn't block write() + default: + PLOG(PL_ERROR, "BpfCap::Send() error: %s", GetErrorString()); + break; + } + return false; } else { - if (EINTR == errno) - { - continue; // try again - } - else - { - PLOG(PL_ERROR, "BpfCap::Forward() error: %s\n", GetErrorString()); - return false; - } + ASSERT(result == (ssize_t)numBytes); + break; } } return true; -} // end BpfCap::Forward() +} // end BpfCap::Send() diff --git a/src/unix/unixNet.cpp b/src/unix/unixNet.cpp index fd675ec..73d05fd 100644 --- a/src/unix/unixNet.cpp +++ b/src/unix/unixNet.cpp @@ -39,84 +39,68 @@ #endif // if/else (SOLARIS || IRIX) #endif // !SIOCGIFHWADDR -#ifdef HAVE_IPV6 -// Although getifaddrs() is probably ok for non-ip6, Android doesn't like it -// so we used the HAVE_IPV6 cue for this as well. Proper Protolib Android support to come in the future! +#ifndef ANDROID +// Android doesn't have getifaddrs(), so we have some netlink stuff +// in "linuxNet.cpp" to implement specific some functions for Android #include -#endif // HAVE_IPV6 - +#endif // !ANDROID // Implementation of ProtoNet functions for Unix systems. These are the mechanisms that are common to // most Unix systems. Some additional ProtoNet mechanisms that have Linux- or MacOS-specific code are // implemented in "src/linux/linuxNet.cpp" or "src/macos/macosNet.cpp", etc. - -#ifndef HAVE_IPV6 -// Internal helper function for older (non-IPv6) systems, -// returns number of interfaces, interface records in "struct ifconf" -// conf.ifc_buf must be deleted by calling program when done -// note that use of SIOCGIFCONF is deprecated -static int GetInterfaceList(struct ifconf& conf) +#ifndef ANDROID // Android version implemented in linuxNet.cpp because no Android getifaddrs() +unsigned int ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) { - int sockFd = socket(PF_INET, SOCK_DGRAM, 0); - if (sockFd < 0) + int family; + switch (ifAddr.GetType()) { - PLOG(PL_ERROR, "ProtoNet::GetInterfaceList() socket() error: %s\n", GetErrorString()); - return 0; + case ProtoAddress::IPv4: + family = AF_INET; + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + family = AF_INET6; + break; +#endif // HAVE_IPV6 + default: + PLOG(PL_ERROR, "UnixNet::GetInterfaceName() error: invalid address type\n"); + return 0; } - - int ifNum = 32; // first guess for max # of interfaces -#ifdef SIOCGIFNUM // Solaris has this, others might - if (ioctl(sock, SIOCGIFNUM, &ifNum) >= 0) ifNum++; -#endif // SIOCGIFNUM - // We loop here until we get a fully successful SIOGIFCONF - // This returns us a list of all interfaces - int bufLen; - conf.ifc_buf = NULL; - do + struct ifaddrs* ifap; + if (0 == getifaddrs(&ifap)) { - if (NULL != conf.ifc_buf) delete[] conf.ifc_buf; // remove previous buffer - bufLen = ifNum * sizeof(struct ifreq); - conf.ifc_len = bufLen; - conf.ifc_buf = new char[bufLen]; - if ((NULL == conf.ifc_buf)) - { - PLOG(PL_ERROR, "ProtoNet::GetInterfaceList() new conf.ifc_buf error: %s\n", GetErrorString()); - conf.ifc_len = 0; - break; - } - if ((ioctl(sockFd, SIOCGIFCONF, &conf) < 0)) + // Look for addrType address for given "interfaceName" + struct ifaddrs* ptr = ifap; + unsigned int namelen = 0; + while (ptr) { - PLOG(PL_WARN, "ProtoNet::GetInterfaceList() ioctl(SIOCGIFCONF) warning: %s\n", GetErrorString()); - conf.ifc_len = 0; // reset for fall-through below + if ((NULL != ptr->ifa_addr) && (family == ptr->ifa_addr->sa_family)) + { + ProtoAddress theAddr; + theAddr.SetSockAddr(*(ptr->ifa_addr)); + if (theAddr.HostIsEqual(ifAddr)) + { + namelen = strlen(ptr->ifa_name); + if (namelen > IFNAMSIZ) namelen = IFNAMSIZ; + if (NULL == buffer) break; + unsigned int maxlen = (buflen > IFNAMSIZ) ? IFNAMSIZ : buflen; + strncpy(buffer, ptr->ifa_name, maxlen); + break; + } + } + ptr = ptr->ifa_next; } - ifNum *= 2; // last guess not big enough, so make bigger & try again - } while (conf.ifc_len >= bufLen); - close(sockFd); // done with socket (whether error or not) - return (conf.ifc_len / sizeof(struct ifreq)); // number of interfaces (or 0) - -// above follows Stevens book & may be the most general for all *nix platforms -// below is simpler, works (at least) on Fedora Linux -// found it on http://codingrelic.geekhold.com/ -// -// conf.ifc_len = 0; -// conf.ifc_buf = NULL; -// int sockFd; -// -// if (((sockFd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) || // get socket -// (ioctl(sockFd, SIOCGIFCONF, &conf) < 0) || // get # of records -// ((conf.ifc_buf = new char[conf.ifc_len]) == NULL) || // make if bfr -// (ioctl(sockFd, SIOCGIFCONF, &conf) < 0)) // fill bfr with if records -// { -// PLOG(PL_ERROR, "ProtoNet::GetInterfaceList() error: %s\n", -// GetErrorString()); -// conf.ifc_len = 0; // reset for below -// } -// -// close(sockFd); // done with socket, error or no -// -// return (conf.ifc_len / sizeof(struct ifreq)); // # records (or 0) -} // end ProtoNet::GetInterfaceList() -#endif // !HAVE_IPV6 + freeifaddrs(ifap); + if (0 == namelen) + PLOG(PL_ERROR, "UnixNet::GetInterfaceName() error: unknown interface address\n"); + return namelen; + } + else + { + PLOG(PL_ERROR, "UnixNet::GetInterfaceName() getifaddrs() error: %s\n", GetErrorString()); + return 0; + } +} // end ProtoNet::GetInterfaceName(by address) bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, ProtoAddress::Type addressType, @@ -341,21 +325,21 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, } return true; #else - // For now, assume we're BSD and use ifaddrs() + // For now, assume we're BSD and use getifaddrs() close(socketFd); // don't need the socket struct ifaddrs* ifap; if (0 == getifaddrs(&ifap)) { // Look for AF_LINK address for given "interfaceName" struct ifaddrs* ptr = ifap; - while (ptr) + while (NULL != ptr) { - if (ptr->ifa_addr && (AF_LINK == ptr->ifa_addr->sa_family)) + if ((NULL != ptr->ifa_addr) && (AF_LINK == ptr->ifa_addr->sa_family)) { if (!strcmp(interfaceName, ptr->ifa_name)) { - // (TBD) should we confirm sdl->sdl_type == IFT_ETHER? - struct sockaddr_dl* sdl = (struct sockaddr_dl*)ptr->ifa_addr; + // note the (void*) cast here gets rid of cast-align mis-warning + struct sockaddr_dl* sdl = (struct sockaddr_dl*)((void*)ptr->ifa_addr); if (IFT_ETHER != sdl->sdl_type) { freeifaddrs(ifap); @@ -402,7 +386,7 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, // Look for addrType address for given "interfaceName" struct ifaddrs* ptr = ifap; bool foundIface = false; - while (ptr) + while (NULL != ptr) { char ifname[IFNAMSIZ+1]; ifname[IFNAMSIZ] = '\0'; @@ -419,7 +403,7 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, #endif // LINUX if (0 == strcmp(interfaceName, ifname)) { - if (req.ifr_addr.sa_family == ptr->ifa_addr->sa_family) + if ((NULL != ptr->ifa_addr) && (ptr->ifa_addr->sa_family == req.ifr_addr.sa_family)) { ProtoAddress ifAddr; if (!ifAddr.SetSockAddr(*(ptr->ifa_addr))) @@ -527,7 +511,6 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, return true; } // end ProtoNet::GetInterfaceAddressList() -#ifdef HAVE_IPV6 unsigned int ProtoNet::GetInterfaceAddressMask(const char* ifaceName, const ProtoAddress& theAddr) { int family; @@ -549,9 +532,9 @@ unsigned int ProtoNet::GetInterfaceAddressMask(const char* ifaceName, const Prot // Look for addrType address for given "interfaceName" struct ifaddrs* ptr = ifap; bool foundIface = false; - while (ptr) + while (NULL != ptr) { - if (ptr->ifa_addr->sa_family != family) + if ((NULL == ptr->ifa_addr) || (ptr->ifa_addr->sa_family != family)) { ptr = ptr->ifa_next; continue; @@ -626,55 +609,57 @@ unsigned int ProtoNet::GetInterfaceAddressMask(const char* ifaceName, const Prot { PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressMask() getifaddrs() error: %s\n"); } - return false; + return 0; } // end ProtoNet::GetInterfaceAddressMask() -#endif // HAVE_IPV6 +#endif // !ANDROID -unsigned int ProtoNet::GetInterfaceIndex(const char* interfaceName) +#if defined(SIOCGIFINDEX) && (defined(ANDROID) || !defined(HAVE_IPV6)) +// Internal helper function for systems without getifaddrs() (e.g. Android or older stuff) +static int GetInterfaceList(struct ifconf& conf) { - unsigned int index = 0; -#ifdef HAVE_IPV6 - index = if_nametoindex(interfaceName); -#else -#ifdef SIOCGIFINDEX int sockFd = socket(PF_INET, SOCK_DGRAM, 0); if (sockFd < 0) { - PLOG(PL_WARN, "ProtoNet::GetInterfaceIndex() socket() error: %s\n", - GetErrorString()); + PLOG(PL_ERROR, "ProtoNet::GetInterfaceList() socket() error: %s\n", GetErrorString()); return 0; } - struct ifreq req; - strncpy(req.ifr_name, interfaceName, IFNAMSIZ); - if (ioctl(sockFd, SIOCGIFINDEX, &req) < 0) - PLOG(PL_WARN, "ProtoNet::GetInterfaceIndex() ioctl(SIOCGIFINDEX) error: %s\n", - GetErrorString()); - else - index = req.ifr_ifindex; - close(sockFd); -#else - PLOG(PL_ERROR, "ProtoNet::GetInterfaceIndex() error: interface indices not supported\n"); - return 0; -#endif // if/else SIOCGIFINDEX -#endif // if/else HAVE_IPV6 - if (0 == index) + + int ifNum = 32; // first guess for max # of interfaces +#ifdef SIOCGIFNUM // Solaris has this, others might + if (ioctl(sock, SIOCGIFNUM, &ifNum) >= 0) ifNum++; +#endif // SIOCGIFNUM + // We loop here until we get a fully successful SIOGIFCONF + // This returns us a list of all interfaces + int bufLen; + conf.ifc_buf = NULL; + do { - // Perhaps "interfaceName" was an address string? - ProtoAddress ifAddr; - if (ifAddr.ResolveFromString(interfaceName)) + if (NULL != conf.ifc_buf) delete[] conf.ifc_buf; // remove previous buffer + bufLen = ifNum * sizeof(struct ifreq); + conf.ifc_len = bufLen; + conf.ifc_buf = new char[bufLen]; + if ((NULL == conf.ifc_buf)) { - char nameBuffer[IFNAMSIZ+1]; - if (GetInterfaceName(ifAddr, nameBuffer, IFNAMSIZ+1)) - return GetInterfaceIndex(nameBuffer); + PLOG(PL_ERROR, "ProtoNet::GetInterfaceList() new conf.ifc_buf error: %s\n", GetErrorString()); + conf.ifc_len = 0; + break; } - } - return index; -} // end ProtoNet::GetInterfaceIndex() + if ((ioctl(sockFd, SIOCGIFCONF, &conf) < 0)) + { + PLOG(PL_WARN, "ProtoNet::GetInterfaceList() ioctl(SIOCGIFCONF) warning: %s\n", GetErrorString()); + conf.ifc_len = 0; // reset for fall-through below + } + ifNum *= 2; // last guess not big enough, so make bigger & try again + } while (conf.ifc_len >= bufLen); + close(sockFd); // done with socket (whether error or not) + return (conf.ifc_len / sizeof(struct ifreq)); // number of interfaces (or 0) +} // end ProtoNet::GetInterfaceList() +#endif // SIOCGIFINDEX && (ANDROID || !HAVE_IPV6) unsigned int ProtoNet::GetInterfaceIndices(unsigned int* indexArray, unsigned int indexArraySize) { unsigned int indexCount = 0; -#ifdef HAVE_IPV6 +#if defined(HAVE_IPV6) && !defined(ANDROID) struct if_nameindex* ifdx = if_nameindex(); if (NULL == ifdx) return 0; // no interfaces found struct if_nameindex* ifPtr = ifdx; @@ -687,7 +672,7 @@ unsigned int ProtoNet::GetInterfaceIndices(unsigned int* indexArray, unsigned in ifPtr++; } if_freenameindex(ifdx); -#else // !HAVE_IPV6 +#else // !HAVE_IPV6 || ANDROID #ifdef SIOCGIFINDEX struct ifconf conf; conf.ifc_buf = NULL; // initialize @@ -703,72 +688,63 @@ unsigned int ProtoNet::GetInterfaceIndices(unsigned int* indexArray, unsigned in #else // !SIOCGIFINDEX PLOG(PL_ERROR, "ProtoNet::GetInterfaceIndices() error: interface indices not supported\n"); #endif // if/else SIOCGIFINDEX -#endif // if/else HAVE_IPV6 +#endif // if/else HAVE_IPV6 && !ANDROID return indexCount; } // end ProtoNet::GetInterfaceIndices() -// given addrType, searches through interface list, returns first non-loopback address found -// TBD - should we _try_ to find a non-link-local addr as well? -bool ProtoNet::FindLocalAddress(ProtoAddress::Type addrType, ProtoAddress& theAddress) +unsigned int ProtoNet::GetInterfaceIndex(const char* interfaceName) { - bool foundLocal = false; // default + unsigned int index = 0; #ifdef HAVE_IPV6 - struct if_nameindex* ifdx = if_nameindex(); - if (NULL == ifdx) return false; - struct if_nameindex* ifPtr = ifdx; // first interface - - while (0 != ifPtr->if_index) - { - if (GetInterfaceAddress(ifPtr->if_name, addrType, theAddress)) - { - // Should we _try_ to find a non-link-local addr? - if (!theAddress.IsLoopback()) - { - foundLocal = true; - break; - } - } - ifPtr++; - } - if_freenameindex(ifdx); -#else // !HAVE_IPV6 - struct ifconf conf; - conf.ifc_buf = NULL; // initialize - int ifCount = GetInterfaceList(conf); - for (int i = 0; i < ifCount; i++) - { - if (GetInterfaceAddress(conf.ifc_req[i].ifr_name, addrType, theAddress)) - { - if (!theAddress.IsLoopback()) - { - // Should we _try_ to find a non-link-local addr? - foundLocal = true; - break; - } - } - } - delete[] conf.ifc_buf; -#endif // if/else HAVE_IPV6 - - // (TBD) set loopback addr if nothing else? - if (!foundLocal) - PLOG(PL_WARN, "ProtoNet::FindLocalAddress() no %s addr assigned\n", - (ProtoAddress::IPv6 == addrType) ? "IPv6" : "IPv4"); - return foundLocal; -} // end ProtoNet::FindLocalAddress() + index = if_nametoindex(interfaceName); +#else +#ifdef SIOCGIFINDEX + int sockFd = socket(PF_INET, SOCK_DGRAM, 0); + if (sockFd < 0) + { + PLOG(PL_WARN, "ProtoNet::GetInterfaceIndex() socket() error: %s\n", + GetErrorString()); + return 0; + } + struct ifreq req; + strncpy(req.ifr_name, interfaceName, IFNAMSIZ); + if (ioctl(sockFd, SIOCGIFINDEX, &req) < 0) + PLOG(PL_WARN, "ProtoNet::GetInterfaceIndex() ioctl(SIOCGIFINDEX) error: %s\n", + GetErrorString()); + else + index = req.ifr_ifindex; + close(sockFd); +#else + PLOG(PL_ERROR, "ProtoNet::GetInterfaceIndex() error: interface indices not supported\n"); + return 0; +#endif // if/else SIOCGIFINDEX +#endif // if/else HAVE_IPV6 + if (0 == index) + { + // Perhaps "interfaceName" was an address string? + ProtoAddress ifAddr; + if (ifAddr.ResolveFromString(interfaceName)) + { + char nameBuffer[IFNAMSIZ+1]; + if (GetInterfaceName(ifAddr, nameBuffer, IFNAMSIZ+1)) + return GetInterfaceIndex(nameBuffer); + } + } + return index; +} // end ProtoNet::GetInterfaceIndex() -bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen) +unsigned int ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen) { #ifdef HAVE_IPV6 char ifName[IFNAMSIZ+1]; if (NULL != if_indextoname(index, ifName)) { strncpy(buffer, ifName, buflen); - return true; + return strlen(ifName); } else { - return false; + return 0; } #else #ifdef SIOCGIFNAME @@ -777,7 +753,7 @@ bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int b { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() socket() error: %s\n", GetErrorString()); - return false; + return 0; } struct ifreq req; req.ifr_ifindex = index; @@ -786,170 +762,72 @@ bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int b PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() ioctl(SIOCGIFNAME) error: %s\n", GetErrorString()); close(sockFd); - return false; + return 0; } close(sockFd); - if (buflen > IFNAMSIZ) + if (NULL != buffer) { - buffer[IFNAMSIZ] = '\0'; - buflen = IFNAMSIZ; + if (buflen > IFNAMSIZ) + { + buffer[IFNAMSIZ] = '\0'; + buflen = IFNAMSIZ; + } + strncpy(buffer, req.ifr_name, buflen); } - strncpy(buffer, req.ifr_name, buflen); - return true; + return strnlen(req.ifr_name, IFNAMSIZ); #else PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() error: getting name by index not supported\n"); - return false; + return 0; #endif // if/else SIOCGIFNAME #endif // if/else HAVE_IPV6 } // end ProtoNet::GetInterfaceName(by index) -bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) -{ -#ifdef HAVE_IPV6 - // Go through list, looking for matching address - struct if_nameindex* ifdx = if_nameindex(); - struct if_nameindex* ptr = ifdx; - if (NULL == ifdx) return false; // no interfaces? - while (0 != ptr->if_index) +ProtoNet::InterfaceStatus ProtoNet::GetInterfaceStatus(const char* ifaceName) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { - ProtoAddressList addrList; - if (GetInterfaceAddressList(ptr->if_name, ifAddr.GetType(), addrList)) - { - ProtoAddressList::Iterator iterator(addrList); - ProtoAddress theAddress; - while (iterator.GetNextAddress(theAddress)) - { - if (ifAddr.HostIsEqual(theAddress)) - { - if (buflen > IFNAMSIZ) - { - buffer[IFNAMSIZ] = '\0'; - buflen = IFNAMSIZ; - } - strncpy(buffer, ptr->if_name, buflen); - if_freenameindex(ifdx); - return true;; - } - } - } - ptr++; + PLOG(PL_ERROR, "ProtoNet::GetInterfaceStatus() socket() error: %s\n", GetErrorString()); + return IFACE_UNKNOWN; } - if_freenameindex(ifdx); -#else - // First, find out how many interfaces there are - unsigned int indexCount = GetInterfaceIndices(NULL, 0); - if (indexCount > 0) + struct ifreq req; + memset(&req, 0, sizeof(req)); + strncpy(req.ifr_name, ifaceName, IFNAMSIZ); + if (ioctl(fd, SIOCGIFFLAGS, &req) < 0) { - unsigned int* indexArray = new unsigned int[indexCount]; - if (NULL == indexArray) - { - PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() new indexArray error: %S\n", - GetErrorString()); - return false; - } - indexCount = GetInterfaceIndices(indexArray, indexCount); - for (unsigned int i = 0; i < indexCount; i++) - { - char ifName[IFNAMSIZ+1]; - ifName[IFNAMSIZ] = '\0'; - if (GetInterfaceName(indexArray[i], ifName, IFNAMSIZ)) - { - ProtoAddressList addrList; - if (GetInterfaceAddressList(ifName, ifAddr.GetType(), addrList)) - { - ProtoAddressList::Iterator iterator(addrList); - ProtoAddress theAddress; - while (iterator.GetNextAddress(theAddress)) - { - if (ifAddr.HostIsEqual(theAddress)) - { - if (buflen > IFNAMSIZ) - { - buffer[IFNAMSIZ] = '\0'; - buflen = IFNAMSIZ; - } - strncpy(buffer, ifName, buflen); - delete[] indexArray; - return true;; - } - } - } - } - else - { - PLOG(PL_WARN, "ProtoNet::GetInterfaceName() warning: GetInterfaceName(%d) failure\n", - indexArray[i]); - } - } - delete[] indexArray; + PLOG(PL_ERROR, "ProtoNet::GetInterfaceStatus() ioctl(SIOCGIFFLAGS) error: %s\n", GetErrorString()); + close(fd); + return IFACE_UNKNOWN; } -#endif // if/else HAVE_IPV6 - return false; -} // end ProtoNet::GetInterfaceName(by address) - + close(fd); + + if (0 != (req.ifr_flags & IFF_UP)) + return IFACE_UP; + else + return IFACE_DOWN; + +} // end ProtoNet::GetInterfaceStatus(by name) -#ifdef LINUX -#ifdef HAVE_IPV6 -static unsigned int GetInterfaceAlias(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) +ProtoNet::InterfaceStatus ProtoNet::GetInterfaceStatus(unsigned int ifaceIndex) { - int family; - switch (ifAddr.GetType()) + char ifaceName[IFNAMSIZ+1]; + ifaceName[IFNAMSIZ] = '\0'; + if (!GetInterfaceName(ifaceIndex, ifaceName, IFNAMSIZ)) { - case ProtoAddress::IPv4: - family = AF_INET; - break; - case ProtoAddress::IPv6: - family = AF_INET6; - break; - default: - PLOG(PL_ERROR, "UnixNet::GetInterfaceAliasName() error: invalid address type\n"); - return 0; + PLOG(PL_ERROR, "ProtoNet::InterfaceIsUp() socket() error: %s\n", GetErrorString()); + return IFACE_UNKNOWN; } - struct ifaddrs* ifap; - if (0 == getifaddrs(&ifap)) - { - // Look for addrType address for given "interfaceName" - struct ifaddrs* ptr = ifap; - unsigned int namelen = 0; - while (ptr) - { - if (family == ptr->ifa_addr->sa_family) - { - ProtoAddress theAddr; - theAddr.SetSockAddr(*(ptr->ifa_addr)); - if (theAddr.HostIsEqual(ifAddr)) - { - namelen = strlen(ptr->ifa_name); - if (namelen > IFNAMSIZ) namelen = IFNAMSIZ; - if (NULL == buffer) break; - unsigned int maxlen = (buflen > IFNAMSIZ) ? IFNAMSIZ : buflen; - strncpy(buffer, ptr->ifa_name, maxlen); - break; - } - } - ptr = ptr->ifa_next; - } - freeifaddrs(ifap); - if (0 == namelen) - PLOG(PL_ERROR, "UnixNet::GetInterfaceAliasName() error: unknown interface address\n"); - return namelen; - } - else - { - PLOG(PL_ERROR, "UnixNet::GetInterfaceAliasName() getifaddrs() error: %s\n", GetErrorString()); - return false; - } -} // end GetInterfaceAlias() -#endif // HAVE_IPV6 -#endif // LINUX (although this could work for others as well) - + return GetInterfaceStatus(ifaceName); +} // end ProtoNet::GetInterfaceStatus(by index) -#ifdef HAVE_IPV6 // TBD - implement with proper system APIs instead of "ifconfig" command bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& ifaceAddr, unsigned int maskLen) { char cmd[1024]; #ifdef LINUX +#ifdef __ANDROID__ + sprintf(cmd, "ip addr add %s/%u dev %s", ifaceAddr.GetHostString(), maskLen, ifaceName); +#else switch (ifaceAddr.GetType()) { case ProtoAddress::IPv4: @@ -969,7 +847,7 @@ bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& if { char ifname[IFNAMSIZ+1]; ifname[IFNAMSIZ] = '\0'; - if (!GetInterfaceAlias(addr, ifname, IFNAMSIZ)) + if (!GetInterfaceName(addr, ifname, IFNAMSIZ)) { PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress() error: unable to get interface name for addr %s\n", addr.GetHostString()); @@ -1014,7 +892,10 @@ bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& if if (!GetInterfaceAddress(aliasName, ProtoAddress::IPv4, ifAddr)) { // This alias is available, so set address - sprintf(cmd, "/sbin/ifconfig %s %s/%d", aliasName, ifaceAddr.GetHostString(), maskLen); + if (32 == maskLen) + sprintf(cmd, "/sbin/ifconfig %s %s broadcast 0.0.0.0 netmask 255.255.255.255", aliasName, ifaceAddr.GetHostString()); + else + sprintf(cmd, "/sbin/ifconfig %s %s/%u", aliasName, ifaceAddr.GetHostString(), maskLen); break; } index++; @@ -1029,13 +910,16 @@ bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& if else { // Set primary address for interface ifaceName - sprintf(cmd, "/sbin/ifconfig %s %s/%d", ifaceName, ifaceAddr.GetHostString(), maskLen); + if (32 == maskLen) + sprintf(cmd, "/sbin/ifconfig %s %s broadcast 0.0.0.0 netmask 255.255.255.255", ifaceName, ifaceAddr.GetHostString()); + else + sprintf(cmd, "/sbin/ifconfig %s %s/%u", ifaceName, ifaceAddr.GetHostString(), maskLen); } break; } case ProtoAddress::IPv6: { - sprintf(cmd, "/sbin/ifconfig %s add %s/%d", ifaceName, ifaceAddr.GetHostString(), maskLen); + sprintf(cmd, "/sbin/ifconfig %s add %s/%u", ifaceName, ifaceAddr.GetHostString(), maskLen); break; } default: @@ -1044,15 +928,18 @@ bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& if return false; } } +#endif // if/else __ANDROID__ #else switch (ifaceAddr.GetType()) { case ProtoAddress::IPv4: - sprintf(cmd, "/sbin/ifconfig %s %s/%d alias", ifaceName, ifaceAddr.GetHostString(), maskLen); + sprintf(cmd, "/sbin/ifconfig %s %s/%u alias", ifaceName, ifaceAddr.GetHostString(), maskLen); break; +#ifdef HAVE_IPV6 case ProtoAddress::IPv6: - sprintf(cmd, "/sbin/ifconfig %s inet6 %s/%d alias", ifaceName, ifaceAddr.GetHostString(), maskLen); + sprintf(cmd, "/sbin/ifconfig %s inet6 %s/%u alias", ifaceName, ifaceAddr.GetHostString(), maskLen); break; +#endif // HAVE_IPV6 default: PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress() error: invalid address type\n"); return false; @@ -1069,8 +956,10 @@ bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& if bool ProtoNet::RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& ifaceAddr, unsigned int maskLen) { char cmd[1024]; - #ifdef LINUX +#ifdef __ANDROID__ + sprintf(cmd, "ip addr del %s/%u dev %s", ifaceAddr.GetHostString(), maskLen, ifaceName); +#else switch (ifaceAddr.GetType()) { case ProtoAddress::IPv4: @@ -1078,7 +967,7 @@ bool ProtoNet::RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& // On linux we need to find the right interface alias char ifname[IFNAMSIZ+1]; ifname[IFNAMSIZ] = '\0'; - if (!GetInterfaceAlias(ifaceAddr, ifname, IFNAMSIZ)) + if (!GetInterfaceName(ifaceAddr, ifname, IFNAMSIZ)) { PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress() error: unknown interface address\n"); return false; @@ -1095,6 +984,7 @@ bool ProtoNet::RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& } break; } +#ifdef HAVE_IPV6 case ProtoAddress::IPv6: { // delete IPv6 address @@ -1104,22 +994,25 @@ bool ProtoNet::RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& sprintf(cmd, "/sbin/ifconfig %s del %s", ifaceName, ifaceAddr.GetHostString()); break; } +#endif // HAVE_IPV8 default: { PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress() error: invalid address type\n"); return false; } } - +#endif // if/else __ANDROID__ #else // BSD, MacOSX, etc switch (ifaceAddr.GetType()) { case ProtoAddress::IPv4: sprintf(cmd, "/sbin/ifconfig %s %s -alias", ifaceName, ifaceAddr.GetHostString()); break; +#ifdef HAVE_IPV6 case ProtoAddress::IPv6: sprintf(cmd, "/sbin/ifconfig %s inet6 %s -alias", ifaceName, ifaceAddr.GetHostString()); break; +#endif // HAVE_IPV6 default: PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress() error: invalid address type\n"); return false; @@ -1133,4 +1026,3 @@ bool ProtoNet::RemoveInterfaceAddress(const char* ifaceName, const ProtoAddress& return true; } // end ProtoNet::RemoveInterfaceAddress() -#endif // HAVE_IPV6 diff --git a/src/unix/unixVif.cpp b/src/unix/unixVif.cpp index b205c14..351a45c 100644 --- a/src/unix/unixVif.cpp +++ b/src/unix/unixVif.cpp @@ -1,4 +1,5 @@ #include "protoVif.h" +#include "protoNet.h" #include "protoDebug.h" #ifdef UNIX @@ -23,7 +24,7 @@ class UnixVif : public ProtoVif UnixVif(); ~UnixVif(); - bool Open(const char* vifName, const ProtoAddress& vifAddr, unsigned int maskLen); + bool Open(const char* vifName, const ProtoAddress& ipAddr, unsigned int maskLen); void Close(); @@ -50,17 +51,27 @@ UnixVif::~UnixVif() { } -bool UnixVif::Open(const char* vifName, const ProtoAddress& vifAddr, unsigned int maskLen) +bool UnixVif::Open(const char* vifName, const ProtoAddress& ipAddr, unsigned int maskLen) { -#ifdef LINUX - // 1) Open up the TUN/TAP device - const char* devName = "/dev/net/tun"; - if ((descriptor = open(devName, O_RDWR)) < 0) + Close(); // in case already open +#ifdef LINUX +#ifdef __ANDROID__ + const char* devName = "/dev//tun"; +#else + // 0) Prefer the flow control enabled TUN/TAP first + const char* devName = "/dev/net/tun_flowctl"; +#endif // if/else __ANDROID__ + descriptor = open(devName, O_RDWR); + if (descriptor < 0) { - PLOG(PL_ERROR,"UnixVif::Open(%s) error: open(\"%s\") failed: %s\n", vifName, devName, GetErrorString()); - return false; + // 1) Open up the TUN/TAP device + const char* devName = "/dev/net/tun"; + if ((descriptor = open(devName, O_RDWR)) < 0) + { + PLOG(PL_ERROR,"UnixVif::Open(%s) error: open(\"%s\") failed: %s\n", vifName, devName, GetErrorString()); + return false; + } } - // 2) Set up a TAP virtual interface with given "vifName" struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); @@ -76,6 +87,15 @@ bool UnixVif::Open(const char* vifName, const ProtoAddress& vifAddr, unsigned in Close(); return false; } + /* doesn't do what i want! + // This enables flow control in the Linux tap device? + int sndbuf = 500*1500; // 500 packets worth? + if (ioctl(descriptor, TUNSETSNDBUF, &sndbuf) < 0) + { + PLOG(PL_ERROR, "UnixVif::Open(%s) error: ioctl(TUNSETIFF) failed: %s\n", vifName, GetErrorString()); + } + TRACE("set tap sndbuf to %d\n", sndbuf); + */ strncpy(vif_name, vifName, VIF_NAME_MAX); #endif // LINUX @@ -104,21 +124,41 @@ bool UnixVif::Open(const char* vifName, const ProtoAddress& vifAddr, unsigned in // 3) Configure the interface via "ifconfig" command // (TBD) Do this with an ioctl() call instead?? char cmd[1024]; - if (vifAddr.IsValid()) //IP address specified - sprintf(cmd, "/sbin/ifconfig %s %s/%d up", vif_name, vifAddr.GetHostString(), maskLen); +#ifdef __ANDROID__ + // Bring interface up and give it an address + sprintf(cmd, "ip link set %s up", vif_name); +#else + if (ipAddr.IsValid()) //IP address specified + sprintf(cmd, "/sbin/ifconfig %s %s/%d up", vif_name, ipAddr.GetHostString(), maskLen); else // IP address NOT specified, use system scripts sprintf(cmd, "/sbin/ifconfig %s up", vif_name); +#endif // if/else __ANDROID__ if (system(cmd) < 0) { PLOG(PL_ERROR, "UnixVif::Open(%s) error: \"%s\n\" failed: %s\n", vifName, cmd, GetErrorString()); Close(); return false; } +#ifdef __ANDROID__ + // On Android, addr is assigned as a separate step + if (ipAddr.IsValid()) + { + if (!ProtoNet::AddInterfaceAddress(vif_name, ipAddr, maskLen)) + { + PLOG(PL_ERROR, "UnixVif::Open(%s) error: unable to assign IP address!\n", vifName); + Close(); + return false; + } + } +#endif // __ANDROID__ + // 4) Snag the virtual interface hardware address + if (!ProtoNet::GetInterfaceAddress(vif_name, ProtoAddress::ETH, hw_addr)) + PLOG(PL_ERROR, "UnixVif::Open(%s) error: unable to get ETH address!\n", vif_name); - // 4) Make a call to "ProtoChannel::Open()" to install event dispatching if applicable + // 5) Make a call to "ProtoChannel::Open()" to install event dispatching if applicable if (!ProtoChannel::Open()) { - PLOG(PL_ERROR, "UnixVif::Open(%s) error: couldn't install ProtoChannel\n", vifName); + PLOG(PL_ERROR, "UnixVif::Open(%s) error: couldn't install ProtoChannel\n", vif_name); Close(); return false; } @@ -193,6 +233,12 @@ bool UnixVif::SetHardwareAddress(const ProtoAddress& ethAddr) return false; } #endif // MACOSX + + + // 4) Snag the virtual interface hardware address + if (!ProtoNet::GetInterfaceAddress(vif_name, ProtoAddress::ETH, hw_addr)) + PLOG(PL_ERROR, "UnixVif::SetHardwareAddress() error: unable to get ETH address for virtual interface \"%s\"!\n", vif_name); + return true; } // end UnixVif::SetHardwareAddress() diff --git a/src/win32/win32Net.cpp b/src/win32/win32Net.cpp index 8e40add..a6e2e1e 100644 --- a/src/win32/win32Net.cpp +++ b/src/win32/win32Net.cpp @@ -5,6 +5,8 @@ #include #include // for extra socket options +// wcstombs +#include /** * @file win32Net.cpp * @@ -108,7 +110,16 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, } while (addrEntry) { - if (0 == strncmp(interfaceName, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN)) + // Were we given a friendly name or the adapter name? + char friendlyName[MAX_INTERFACE_NAME_LEN]; + //PLOG(PL_INFO,"PWCHAR string: %ls \n", addrEntry->FriendlyName); + //PLOG(PL_INFO,"adapterName> %s \n", addrEntry->AdapterName); + //PLOG(PL_INFO,"interfaceName> %s \n\n", interfaceName); + int ret = wcstombs(friendlyName, addrEntry->FriendlyName, MAX_INTERFACE_NAME_LEN); + if (ret == MAX_INTERFACE_NAME_LEN) friendlyName[MAX_INTERFACE_NAME_LEN - 1] = '\0'; + if ((0 == strncmp(interfaceName, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN)) + | + (0 == strncmp(interfaceName, friendlyName, MAX_INTERFACE_NAME_LEN))) { // A match was found! if (ProtoAddress::ETH == addressType) @@ -117,6 +128,7 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, { ProtoAddress ethAddr; ethAddr.SetRawHostAddress(ProtoAddress::ETH, (char*)&addrEntry->PhysicalAddress, 6); + PLOG(PL_INFO, "Win32Net::GetInterfaceAddressList() ethAddr>%s\n", ethAddr.GetHostString()); if (NULL != interfaceIndex) { if (0 != addrEntry->IfIndex) @@ -153,7 +165,8 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, } ProtoAddress ifAddr; ifAddr.SetSockAddr(*(ipAddr->Address.lpSockaddr)); - // Defer link local address to last + + // Defer link local address to last if (ifAddr.IsLinkLocal()) { if (localAddrList.Insert(ifAddr)) @@ -183,7 +196,7 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, } delete[] addrBuffer; if (!foundAddr) - PLOG(PL_WARN, "ProtoNet::GetInterfaceAddressList(%s) warning: no matching interface found\n", interfaceName); + PLOG(PL_WARN, "ProtoNet::GetInterfaceAddressList(%s) warning: no matching interface found. Checking for ip address\n", interfaceName); } else #endif // if (WINVER >= 0x0501) @@ -414,10 +427,10 @@ bool ProtoNet::GetInterfaceAddressList(const char* interfaceName, if (foundAddr) return true; // As a last resort, check if the "interfaceName" is actually an address string ProtoAddress ifAddr; - if (ifAddr.ResolveFromString(interfaceName)) + if (ifAddr.ConvertFromString(interfaceName)) { - char ifName[256]; - if (!GetInterfaceName(ifAddr, ifName, 256)) + char ifName[MAX_ADAPTER_NAME_LENGTH + 4]; + if (!GetInterfaceName(ifAddr, ifName, MAX_ADAPTER_NAME_LENGTH + 4)) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList(%s) error: no matching interface name found\n", interfaceName); return false; @@ -588,6 +601,7 @@ bool ProtoNet::FindLocalAddress(ProtoAddress::Type addrType, ProtoAddress& theAd } } // end ProtoNet::FindLocalAddress() + unsigned int ProtoNet::GetInterfaceIndex(const char* interfaceName) { ProtoAddress theAddress; @@ -603,7 +617,8 @@ unsigned int ProtoNet::GetInterfaceIndex(const char* interfaceName) } } // end ProtoNet::GetInterfaceIndex() -bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen) + +unsigned int ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen) { ULONG bufferLength = 0; if (ERROR_BUFFER_OVERFLOW == GetAdaptersInfo(NULL, &bufferLength)) @@ -612,35 +627,36 @@ bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int b if (NULL == infoBuffer) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(by index) new infoBuffer error: %s\n", ::GetErrorString()); - return false; + return 0; } IP_ADAPTER_INFO* adapterInfo = (IP_ADAPTER_INFO*)infoBuffer; if (NO_ERROR != GetAdaptersInfo(adapterInfo, &bufferLength)) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(by index) GetAdaptersInfo() error: %s\n", ::GetErrorString()); delete[] infoBuffer; - return false; + return 0; } while (NULL != adapterInfo) { if (index == adapterInfo->Index) { - strncpy_s(buffer, buflen, adapterInfo->AdapterName, MAX_ADAPTER_NAME_LENGTH); + size_t nameLen = strnlen_s(adapterInfo->AdapterName, MAX_ADAPTER_NAME_LENGTH); + strncpy_s(buffer, buflen, adapterInfo->AdapterName, nameLen); delete[] infoBuffer; - return true; + return nameLen; } adapterInfo = adapterInfo->Next; } - // Assume index==1 is loopback? + // Assume index == 1 is loopback? if (1 == index) { strncpy_s(buffer, buflen, "lo", 3); - return true; + return 2; } else { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(by index) no matching interface found!\n"); - return false; + return 0; } } else if (0 != index) @@ -657,20 +673,21 @@ bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int b buflen = buflen < MAX_INTERFACE_NAME_LEN ? buflen : MAX_INTERFACE_NAME_LEN; wcstombs(buffer, ifRow.wszName, buflen); #else + size_t nameLen = strnlen_s((char*)ifRow.bDescr, ifRow.dwDescrLen); strncpy_s(buffer, buflen, (char*)ifRow.bDescr, ifRow.dwDescrLen); #endif // if/else _UNICODE - return true; + return nameLen; } else { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(by index) GetIfEntry(%d) error: %s\n", index, ::GetErrorString()); - return false; + return 0; } } else { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(%d) error: invalid index\n", index); - return false; + return 0; } } // end ProtoNet::GetInterfaceName(by index) @@ -680,7 +697,7 @@ bool ProtoNet::GetInterfaceName(unsigned int index, char* buffer, unsigned int b * using the "GetAdaptersAddresses()" call, and * 2) Supports IPv4 only on older operating systems using "GetIPaddrTable()" */ -bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) +unsigned int ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) { /* (TBD) Do the approaches below provide the loopback address? if (ifAddr.IsLoopback()) @@ -688,7 +705,6 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign strncpy_s(buffer, buflen, "lo", 3); return true; }*/ - // Try the "GetAdaptersAddresses()" approach first ULONG afFamily = AF_UNSPEC; @@ -718,20 +734,20 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign if (ERROR_NO_DATA == result) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(%s) error: no matching network adapters found.\n", ifAddr.GetHostString()); - return false; + return 0; } char* addrBuffer = new char[bufferSize]; if (NULL == addrBuffer) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() new addrBuffer error: %s\n", ::GetErrorString()); - return false; + return 0; } IP_ADAPTER_ADDRESSES* addrEntry = (IP_ADAPTER_ADDRESSES*)addrBuffer; if (ERROR_SUCCESS != GetAdaptersAddresses(afFamily, addrFlags, NULL, addrEntry, &bufferSize)) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() GetAdaptersAddresses() error: %s\n", ::GetErrorString()); delete[] addrBuffer; - return false; + return 0; } while (NULL != addrEntry) { @@ -744,9 +760,10 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign if (tempAddress.HostIsEqual(ifAddr)) { // Copy the interface name + size_t nameLen = strnlen_s(addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN); strncpy_s(buffer, buflen, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN); delete[] addrBuffer; - return true; + return nameLen; } } } @@ -761,9 +778,10 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign tempAddress.SetSockAddr(*(ipAddr->Address.lpSockaddr)); if (tempAddress.HostIsEqual(ifAddr)) { + size_t nameLen = strnlen_s(addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN); strncpy_s(buffer, buflen, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN); delete[] addrBuffer; - return true; + return nameLen; } } ipAddr = ipAddr->Next; @@ -783,14 +801,14 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign if (NULL == infoBuffer) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(%s) new infoBuffer error: %s\n", ifAddr.GetHostString(), ::GetErrorString()); - return false; + return 0; } IP_ADAPTER_INFO* adapterInfo = (IP_ADAPTER_INFO*)infoBuffer; if (NO_ERROR != GetAdaptersInfo(adapterInfo, &bufferLength)) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName(%s) GetAdaptersInfo() error: %s\n", ifAddr.GetHostString(), ::GetErrorString()); delete[] infoBuffer; - return false; + return 0; } while (NULL != adapterInfo) { @@ -807,9 +825,10 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign } if (tempAddr.IsValid() && tempAddr.HostIsEqual(ifAddr)) { + size_t nameLen = strnlen_s(adapterInfo->AdapterName, MAX_INTERFACE_NAME_LEN); strncpy_s(buffer, buflen, adapterInfo->AdapterName, MAX_ADAPTER_NAME_LENGTH); delete[] infoBuffer; - return true; + return nameLen; } adapterInfo = adapterInfo->Next; } @@ -822,7 +841,7 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign if (NO_ERROR != GetNumberOfInterfaces(&ifCount)) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() GetNumberOfInterfaces() error: %s\n", ::GetErrorString()); - return false; + return 0; } for (DWORD i = 1; i <= ifCount; i++) { @@ -842,11 +861,12 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign // We use the "bDescr" field because the "wszName" field doesn't seem to work #ifdef _UNICODE buflen = buflen < MAX_INTERFACE_NAME_LEN ? buflen : MAX_INTERFACE_NAME_LEN; - wcstombs(buffer, ifRow.wszName, buflen); + size_t nameLen = wcstombs(buffer, ifRow.wszName, buflen); #else + size_t nameLen = strnlen_s((char*)ifRow.bDescr, ifRow.dwDescrLen); strncpy_s(buffer, buflen, (char*)ifRow.bDescr, ifRow.dwDescrLen); #endif // if/else _UNICODE - return true; + return nameLen; } } } @@ -865,7 +885,7 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign if (NULL == tableBuffer) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() new tableBuffer error: %s\n", ::GetErrorString()); - return false; + return 0; } MIB_IPADDRTABLE* addrTable = (MIB_IPADDRTABLE*)tableBuffer; if (ERROR_SUCCESS == GetIpAddrTable(addrTable, &bufferSize, FALSE)) @@ -882,17 +902,20 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign if (NO_ERROR != GetIfEntry(&ifEntry)) { PLOG(PL_ERROR, "ProtoNet::GetInterfaceName() GetIfEntry(%d) error: %s\n", i, ::GetErrorString()); - return false; + return 0; } // We use the "bDescr" field because the "wszName" field doesn't seem to work #ifdef _UNICODE buflen = buflen < MAX_INTERFACE_NAME_LEN ? buflen : MAX_INTERFACE_NAME_LEN; - wcstombs(buffer, ifEntry.wszName, buflen); + size_t nameLen = wcstombs(buffer, ifEntry.wszName, buflen); + unsigned int tmpbuflen = buflen; #else + size_t nameLen = strnlen_s((char*)ifEntry.bDescr, ifEntry.dwDescrLen); strncpy_s(buffer, buflen, (char*)ifEntry.bDescr, ifEntry.dwDescrLen); + unsigned int tmpbuflen = buflen; #endif // if/else _UNICODE delete[] tableBuffer; - return true; + return nameLen; } } } @@ -913,266 +936,625 @@ bool ProtoNet::GetInterfaceName(const ProtoAddress& ifAddr, char* buffer, unsign { PLOG(PL_WARN, "ProtoNet::GetInterfaceName() warning GetAdaptersAddresses() error: %s\n", ::GetErrorString()); } - return false; + return 0; } // end ProtoNet::GetInterfaceName(by addr) -class Win32NetMonitor : public ProtoNet::Monitor +ProtoNet::InterfaceStatus ProtoNet::GetInterfaceStatus(const char* ifaceName) +{ + // On WIN32, if we can't get an interface index, we assume IFACE_DOUWN + unsigned int ifaceIndex = GetInterfaceIndex(ifaceName); + if (0 != ifaceIndex) + return IFACE_UP; + else + return IFACE_DOWN; +} // end ProtoNet::GetInterfaceStatus(by name) + +ProtoNet::InterfaceStatus ProtoNet::GetInterfaceStatus(unsigned int ifaceIndex) +{ + // On WIN32, if we can't get an interface name, we assume IFACE_DOWN + char ifaceName[MAX_ADAPTER_NAME_LENGTH + 4]; + if (GetInterfaceName(ifaceIndex, ifaceName, MAX_ADAPTER_NAME_LENGTH + 4)) + return IFACE_UP; + else + return IFACE_DOWN; +} // end ProtoNet::GetInterfaceStatus(by index) +unsigned int ProtoNet::GetInterfaceAddressMask(unsigned int ifaceIndex, const ProtoAddress& ifAddr) +{ + char ifName[MAX_ADAPTER_NAME_LENGTH + 4]; + unsigned int buflen; + if (GetInterfaceName(ifaceIndex, ifName, MAX_ADAPTER_NAME_LENGTH + 4)) + { + return GetInterfaceAddressMask(ifName, ifAddr); + } + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressMask() no matching interface found for ifaceIndex %s", ifaceIndex); + return 0; +} + +unsigned int ProtoNet::GetInterfaceAddressMask(const char* ifName, const ProtoAddress& ifAddr) { -public: - Win32NetMonitor(); - ~Win32NetMonitor(); - bool Open(); - void Close(); - bool GetNextEvent(Event& theEvent); - bool GetEvent(PMIB_IPINTERFACE_ROW row, - MIB_NOTIFICATION_TYPE notificationType); - static bool FindIPAddr(NET_IFINDEX InterfaceIndex); - const char* GetNotificationType(int type); - HANDLE GetEventHandle() { return input_handle; } + // ifName can be a friendly adapater address, an adapter name, or and + // adapter ip address. A separate function looks up by index. + + // We could use GetAdaptersInfo and get mask from adaterInfo->IpAddressList.IpMask.String + // would need to convert dotted decimal mask to cidr -private: - // We cache mib changes to a linked list for - // retrieval by the GetNextEvent() method - class EventItem : public Event, public ProtoList::Item + // TBD: Implement option 2 ljt? + + // 1) Supports IPv4 and IPv6 on newer Windows Operating systems (WinXP and Win2003) + // using the "GetAdaptersAddresses()" call, and + // 2) Supports IPv4 only on older operating systems using "GetIPaddrTable()" + // Then, try the "GetAdaptersAddresses()" approach first + + ULONG afFamily = AF_UNSPEC; + switch (ifAddr.GetType()) { - public: - EventItem(); - ~EventItem(); - }; // end class Win32NetMontior::EventItem + case ProtoAddress::IPv4: + afFamily = AF_INET; + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + afFamily = AF_INET6; + break; +#endif // HAVE_IPV6 + default: + break; + } + ULONG bufferLength = 0; - class EventList : public ProtoListTemplate {}; - EventList event_list; - EventList event_pool; +#if (WINVER >= 0x0501) + // On NT4, Win2K and earlier, GetAdaptersAddresses() isn't to be found + // in the iphlpapi.dll ... + DWORD addrFlags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST; + ULONG bufferSize = 0; + DWORD result = GetAdaptersAddresses(afFamily, addrFlags, NULL, NULL, &bufferSize); + if ((ERROR_BUFFER_OVERFLOW == result) || + (ERROR_NO_DATA == result)) + { + if (ERROR_NO_DATA == result) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressMask(%s) error: no matching interface adapters found.\n", ifAddr.GetHostString()); + return 0; + } + + char* addrBuffer = new char[bufferSize]; + if (NULL == addrBuffer) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressMask() new addrBuffer error: %s\n", ::GetErrorString()); + return 0; + } + IP_ADAPTER_ADDRESSES* addrEntry = (IP_ADAPTER_ADDRESSES*)addrBuffer; + if (ERROR_SUCCESS != GetAdaptersAddresses(afFamily, addrFlags, NULL, addrEntry, &bufferSize)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressList() GetAdaptersAddresses() error: %s\n", ::GetErrorString()); + delete[] addrBuffer; + return 0; + } + while (addrEntry) + { + // Were we given a friendly name or the adapter name? + char friendlyName[MAX_INTERFACE_NAME_LEN]; + int ret = wcstombs(friendlyName, addrEntry->FriendlyName, MAX_INTERFACE_NAME_LEN); + if (ret == MAX_INTERFACE_NAME_LEN) friendlyName[MAX_INTERFACE_NAME_LEN - 1] = '\0'; - typedef CRITICAL_SECTION Mutex; - Mutex lock; - - static void Init(Mutex& m) {InitializeCriticalSection(&m);} - static void Destroy(Mutex& m) {DeleteCriticalSection(&m);} - static void Lock(Mutex& m) {EnterCriticalSection(&m);} - static void Unlock(Mutex& m) {LeaveCriticalSection(&m);} + if ((0 == strncmp(ifName, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN)) + || (0 == strncmp(ifName, friendlyName, MAX_INTERFACE_NAME_LEN))) + { + // A match was found! + if (ProtoAddress::ETH != ifAddr.GetType()) + { + IP_ADAPTER_UNICAST_ADDRESS* ipAddr = addrEntry->FirstUnicastAddress; + while (NULL != ipAddr) + { + if ((afFamily == AF_UNSPEC) || + (afFamily == ipAddr->Address.lpSockaddr->sa_family)) + { + ProtoAddress tmpAddr; + tmpAddr.SetSockAddr(*(ipAddr->Address.lpSockaddr)); + if (ifAddr == tmpAddr) + { + unsigned int mask = ipAddr->OnLinkPrefixLength; + delete[] addrBuffer; + return mask; + } + } + ipAddr = ipAddr->Next; + } + } + } + addrEntry = addrEntry->Next; + } + delete[] addrBuffer; + PLOG(PL_WARN, "ProtoNet::GetInterfaceAddressMask(%s) warning: no matching interface found. Checking for ip address.\n", ifAddr.GetHostString()); + } + + // See if "ifName" is actually an address + ProtoAddress tmpAddr; + if (tmpAddr.ConvertFromString(ifName)) + { + char ifName[MAX_ADAPTER_NAME_LENGTH + 4]; + if (!GetInterfaceName(tmpAddr, ifName, MAX_ADAPTER_NAME_LENGTH + 4)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressMask(%s) error: no matching interface found\n", ifAddr.GetHostString()); + return false; + } + return GetInterfaceAddressMask(ifName, tmpAddr); + } + - HANDLE notification_handle; // handle to subsequently stop notifications + return 0; +#endif // if (WINVER >= 0x0501) + // TBD: Implement support for earlier versions of windows? -}; // end class Win32NetMonitor + PLOG(PL_WARN, "ProtoNet::GetInterfaceAddressList(%s) warning: no matching interface found (WINVER < 0x0501?)\n", ifAddr.GetHostString()); -/// This is the implementation of the ProtoNet::Monitor::Create() -/// static method (our win32-specific factory) -ProtoNet::Monitor* ProtoNet::Monitor::Create() -{ - return static_cast(new Win32NetMonitor); -} // end ProtoNet::Monitor::Create() + return 0; +}; // ProtoNet::GetInterfaceAddressMask -Win32NetMonitor::Win32NetMonitor() +bool ProtoNet::AddInterfaceAddress(unsigned int ifaceIndex, const ProtoAddress& addr, unsigned int maskLen, bool dhcp_enabled) { - Init(lock); -} + /* + NOTE: dhcp interface flag is inconsisten when interface had both ipv4 and ipv6 addresses. + Do not use in this case. + */ -Win32NetMonitor::~Win32NetMonitor() + unsigned int buflen; + char ifName[MAX_ADAPTER_NAME_LENGTH]; + if (GetInterfaceFriendlyName(ifaceIndex, ifName, MAX_ADAPTER_NAME_LENGTH )) + { + return AddInterfaceAddress(ifName, addr, maskLen,dhcp_enabled); + } + PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress() no matching interface found for ifaceIndex %s", ifaceIndex); + return false; +}; +bool ProtoNet::AddInterfaceAddress(const char* ifaceName, const ProtoAddress& ifaceAddr, unsigned int maskLen, bool dhcp_enabled) { -} + /* + NOTE: dhcp interface flag is inconsisten when interface had both ipv4 and ipv6 addresses. + Do not use in this case. + */ + char cmd[1024]; + switch (ifaceAddr.GetType()) + { + case ProtoAddress::IPv4: + if (dhcp_enabled) + sprintf(cmd, "netsh interface ipv4 set address \"%s\" dhcp", ifaceName); + else + sprintf(cmd, "netsh interface ipv4 add address \"%s\" addr=%s", ifaceName, ifaceAddr.GetHostString()); -// Static callback function for NotifyIpInterfaceChange API -static void WINAPI IpInterfaceChangeCallback(PVOID callerContext, - PMIB_IPINTERFACE_ROW row, - MIB_NOTIFICATION_TYPE notificationType) -{ - Win32NetMonitor* monitor = (Win32NetMonitor*)callerContext; - if (!monitor) + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + sprintf(cmd, "netsh interface ipv6 set address interface=\"%s\" address=%s", ifaceName, ifaceAddr.GetHostString()); + break; +#endif //HAVE_IPV6 + default: { - PLOG(PL_ERROR,"IpInterfaceChangeCallback() Error: No callerContext.\n"); - return; + PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress() error: invalid address type\n"); + return false; + } } - if (row) + PLOG(PL_INFO, "cmd>%s \n", cmd); + if (LONG ret = system(cmd)) { - // Get complete information for MIP_IPINTERFACE_ROW - GetIpInterfaceEntry(row); - // Add an event to our list for the notification - if (!monitor->GetEvent(row,notificationType)) - { - PLOG(PL_ERROR,"MonitorEventHandler() GetEvent error\n"); - return; - } + PLOG(PL_ERROR, "ProtoNet::AddInterfaceAddress(%s) add %s failed failed. netsh call returned: %u", ifaceName, ifaceAddr.GetHostString(), ret); + return false; + } + return true; - } - if (!SetEvent(monitor->GetEventHandle())) - PLOG(PL_ERROR,"win32Net::MonitorEventHandler() Error setting event handle.\n"); +} // end ProtoNet::AddInterfaceAddress() -} -bool Win32NetMonitor::Open() +bool ProtoNet::RemoveInterfaceAddress(unsigned int ifaceIndex, const ProtoAddress& addr, unsigned int maskLen) { - // Not using a manual reset event? - if (NULL == (input_handle = CreateEvent(NULL,FALSE,FALSE,NULL))) - { - input_handle = INVALID_HANDLE_VALUE; - PLOG(PL_ERROR,"Win32Monitor::Open() CreateEvent(event_handle) error: %s\n", ::GetErrorString()); - Close(); - return false; - } - // Initiate notifications ... - notification_handle = NULL; - if (!NotifyIpInterfaceChange( - AF_UNSPEC, // AF_INET - (PIPINTERFACE_CHANGE_CALLBACK)IpInterfaceChangeCallback, - this, - false, // initialNofification - ¬ification_handle) == NO_ERROR) + char ifName[MAX_ADAPTER_NAME_LENGTH]; + unsigned int buflen; + if (GetInterfaceName(ifaceIndex, ifName, MAX_ADAPTER_NAME_LENGTH)) { - PLOG(PL_ERROR,"Win32NetMonitor::Open() NotifyIpInterfaceChange failed\n"); - return false; + return RemoveInterfaceAddress(ifName, addr, maskLen); } + PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress() no matching interface found for ifaceIndex %s", ifaceIndex); + return false; +}; +bool ProtoNet::RemoveInterfaceAddress(const char* ifaceAddress, const ProtoAddress& ifaceAddr, unsigned int maskLen) +{ + // Get the adapter name from the iface address + ProtoAddress ifAddr; + char ifName[MAX_ADAPTER_NAME_LENGTH]; + if (ifAddr.ConvertFromString(ifaceAddress)) + { + if (!GetInterfaceFriendlyName(ifAddr, ifName, MAX_ADAPTER_NAME_LENGTH)) + { + PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress(%s) error: no matching interface name found\n", ifaceAddress); + return false; + } + PLOG(PL_INFO, "ProtoNet::RemoveInterfaceAddress() ifaceName>%s\n", ifName); + + } + else + { + // Is it already a friendly name? + sprintf(ifName, "%s", ifaceAddress); + PLOG(PL_INFO, "ProtoNet::RemoveInterfaceAddress() ifaceName>%s\n", ifName); + } + char cmd[1024]; + // First assign a static address to the interface so we can then delete it - if (!ProtoNet::Monitor::Open()) + switch (ifaceAddr.GetType()) { - Close(); + case ProtoAddress::IPv4: + sprintf(cmd, "netsh interface ipv4 set address name=\"%s\" static %s", ifName, ifaceAddr.GetHostString()); + break; + case ProtoAddress::IPv6: + // ipv6 seems to behave differently - is this necessary? + sprintf(cmd, "netsh interface ipv6 set address interface=\"%s\" %s", ifName, ifaceAddr.GetHostString()); + break; + default: + PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress() error: invalid address type\n"); return false; } - + PLOG(PL_INFO, "cmd>%s \n", cmd); + if (LONG ret = system(cmd)) + { + PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress(%s) Change to static ip %s failed. netsh call returned: %u\n", ifName, ifaceAddr.GetHostString(), ret); + return false; + } + switch (ifaceAddr.GetType()) + { + case ProtoAddress::IPv4: + sprintf(cmd, "netsh interface ipv4 delete address \"%s\" addr=%s", ifName, ifaceAddr.GetHostString()); + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + sprintf(cmd, "netsh interface ipv6 delete address interface=\"%s\" address=%s", ifName, ifaceAddr.GetHostString()); + break; +#endif //HAVE_IPV6 + default: + { + PLOG(PL_ERROR, "ProtoNet::RemoveInterfaceAddress() error: invalid address type\n"); + return false; + } + } + PLOG(PL_INFO, "cmd>%s \n", cmd); + if (LONG ret = system(cmd)) + { + PLOG(PL_ERROR,"ProtoNet::RemoveInterfaceAddress(%s) Open %s failed. netsh call returned: %u\n", ifName,ifaceAddr.GetHostString(), ret); + return false; + } + return true; -} +} // end ProtoNet::RemoveInterfaceAddress() -void Win32NetMonitor::Close() +/** +* Only available on WINVER > 0x0501 +*/ +unsigned int ProtoNet::GetInterfaceFriendlyName(const ProtoAddress& ifAddr, char* buffer, unsigned int buflen) { - if (IsOpen()) +#if (WINVER >= 0x0501) + + ULONG afFamily = AF_UNSPEC; + switch (ifAddr.GetType()) { - ProtoNet::Monitor::Close(); - input_handle = INVALID_HANDLE; + case ProtoAddress::IPv4: + afFamily = AF_INET; + break; +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + afFamily = AF_INET6; + break; +#endif // HAVE_IPV6 + default: + break; } - if (notification_handle != INVALID_HANDLE) - CancelMibChangeNotify2(notification_handle); - - event_list.Destroy(); - event_pool.Destroy(); - - Unlock(lock); - Destroy(lock); -} + ULONG bufferLength = 0; + // On NT4 and earlier, GetAdaptersAddresses() isn't to be found + // in the iphlpapi.dll ... + DWORD addrFlags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST; + ULONG bufferSize = 0; + DWORD result = GetAdaptersAddresses(afFamily, addrFlags, NULL, NULL, &bufferSize); + if ((ERROR_BUFFER_OVERFLOW == result) || + (ERROR_NO_DATA == result)) + { + if (ERROR_NO_DATA == result) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName(%s) error: no matching network adapters found.\n", ifAddr.GetHostString()); + return 0; + } + char* addrBuffer = new char[bufferSize]; + if (NULL == addrBuffer) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName() new addrBuffer error: %s\n", ::GetErrorString()); + return 0; + } + IP_ADAPTER_ADDRESSES* addrEntry = (IP_ADAPTER_ADDRESSES*)addrBuffer; + if (ERROR_SUCCESS != GetAdaptersAddresses(afFamily, addrFlags, NULL, addrEntry, &bufferSize)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName() GetAdaptersAddresses() error: %s\n", ::GetErrorString()); + delete[] addrBuffer; + return 0; + } + while (NULL != addrEntry) + { + // TODO LJT remove this + if (ProtoAddress::ETH == ifAddr.GetType()) + { + if (6 == addrEntry->PhysicalAddressLength) + { + ProtoAddress tempAddress; + tempAddress.SetRawHostAddress(ProtoAddress::ETH, (char*)&addrEntry->PhysicalAddress, 6); + if (tempAddress.HostIsEqual(ifAddr)) + { + // Copy the interface name + size_t nameLen = strnlen_s(addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN); + strncpy_s(buffer, buflen, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN); + delete[] addrBuffer; + return nameLen; + } + } + } + else + { + IP_ADAPTER_UNICAST_ADDRESS* ipAddr = addrEntry->FirstUnicastAddress; + while (NULL != ipAddr) + { + if (afFamily == ipAddr->Address.lpSockaddr->sa_family) + { + ProtoAddress tempAddress; + tempAddress.SetSockAddr(*(ipAddr->Address.lpSockaddr)); + if (tempAddress.HostIsEqual(ifAddr)) + { + // Convert the PWCHAR friendly name + char friendlyName[MAX_INTERFACE_NAME_LEN]; + PLOG(PL_INFO, "PWCHAR string: %ls \n", addrEntry->FriendlyName); + int ret = wcstombs(friendlyName, addrEntry->FriendlyName, MAX_INTERFACE_NAME_LEN); + if (ret == MAX_INTERFACE_NAME_LEN) friendlyName[MAX_INTERFACE_NAME_LEN - 1] = '\0'; -const char* Win32NetMonitor::GetNotificationType(int type) - { - static const char* names[] = { - "ParameterNotification", - "AddInstance", - "DeleteInstance", - "InitialNotification" - }; + size_t nameLen = strnlen_s(friendlyName, MAX_INTERFACE_NAME_LEN); + strncpy_s(buffer, buflen, friendlyName, MAX_INTERFACE_NAME_LEN); + delete[] addrBuffer; + return nameLen; + } + } + ipAddr = ipAddr->Next; + } + if (NULL != ipAddr) break; + } + addrEntry = addrEntry->Next; + } // end while(addrEntry) + delete[] addrBuffer; + PLOG(PL_WARN, "ProtoNet::GetInterfaceFriendlyName(%s) warning: no matching interface found\n", ifAddr.GetHostString()); + return 0; + } +#else + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName() not available on WINVER < 0x0501\n"); + return 0; +#endif // if (WINVER >= 0x0501) - const char* name = ""; - if (type >=0 && type < sizeof(names)) { - name = names[type]; - } - return name; - } -bool Win32NetMonitor::GetNextEvent(Event& theEvent) +} // end ProtoNet::GetInterfaceFriendlyName(by addr) +/** +* Only available on WINVER > 0x0501 +*/ +unsigned int ProtoNet::GetInterfaceFriendlyName(unsigned int index, char* buffer, unsigned int buflen) { - // 0) Initialize event instance - theEvent.SetType(Event::UNKNOWN_EVENT); - theEvent.SetInterfaceIndex(0); - theEvent.AccessAddress().Invalidate(); +#if (WINVER >= 0x0501) - // 1) Get next event from list - Lock(lock); - EventItem* eventItem = event_list.RemoveHead(); - if (eventItem == NULL) + ULONG bufferLength = 0; + if (ERROR_BUFFER_OVERFLOW == GetAdaptersInfo(NULL, &bufferLength)) { - Unlock(lock); - theEvent.SetType(Event::NULL_EVENT); - return true; + char * infoBuffer = new char[bufferLength]; + if (NULL == infoBuffer) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName(by index) new infoBuffer error: %s\n", GetErrorString()); + return 0; + } + + IP_ADAPTER_INFO* adapterInfo = (IP_ADAPTER_INFO*)infoBuffer; + if (NO_ERROR != GetAdaptersInfo(adapterInfo, &bufferLength)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName(by index) GetAdaptersInfo() error: %s\n", ::GetErrorString()); + delete[] infoBuffer; + return 0; + } + ProtoAddress ifAddr; + while (NULL != adapterInfo) + { + if (index == adapterInfo->Index) + { + ifAddr.ResolveFromString(adapterInfo->IpAddressList.IpAddress.String); + return GetInterfaceFriendlyName(ifAddr, buffer, MAX_ADAPTER_NAME_LENGTH); + } + adapterInfo = adapterInfo->Next; + } + // Assume index == 1 is loopback? + if (1 == index) + { + strncpy_s(buffer, buflen, "lo", 3); + return 2; + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFirendlyName(by index) no matching interface found!\n"); + return 0; + } + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName(%d) error: invalid index\n", index); + return 0; } - theEvent = static_cast(*eventItem); - event_pool.Append(*eventItem); - Unlock(lock); - return true; -} -bool Win32NetMonitor::GetEvent(PMIB_IPINTERFACE_ROW row, - MIB_NOTIFICATION_TYPE notificationType) +#else + PLOG(PL_ERROR, "ProtoNet::GetInterfaceFriendlyName(by index) not available on WINVER < 0x0501\n"); + return 0; +#endif // if (WINVER >= 0x0501) + +} // end ProtoNet::GetInterfaceFriendlyName(by index) + +bool ProtoNet::GetInterfaceAddressDhcp(unsigned int ifaceIndex, const ProtoAddress& ifAddr) { - EventItem* eventItem = event_pool.RemoveHead(); - if (NULL == eventItem) eventItem = new EventItem(); - if (NULL == eventItem) + char ifName[MAX_ADAPTER_NAME_LENGTH + 4]; + unsigned int buflen; + if (GetInterfaceName(ifaceIndex, ifName, MAX_ADAPTER_NAME_LENGTH + 4)) { - PLOG(PL_ERROR,"Win32NetMonitor::GetEvent() new EventItem error: %s\n", GetErrorString()); - return false; + return GetInterfaceAddressDhcp(ifName, ifAddr); } + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressDhcp() no matching interface found for ifaceIndex %s", ifaceIndex); + return false; +} - eventItem->SetInterfaceIndex(row->InterfaceIndex); - - switch (notificationType) - { - case 0: - //eventItem->SetType(Event::IFACE_STATE); - // not interested in windows state changes at the moment - eventItem->SetType(Event::UNKNOWN_EVENT); - break; - case 1: - eventItem->SetType(Event::IFACE_UP); - break; - case 2: - eventItem->SetType(Event::IFACE_DOWN); +bool ProtoNet::GetInterfaceAddressDhcp(const char* ifName, const ProtoAddress& ifAddr) +{ + // 1) IP_ADAPTER_ADDRESSES only seems to support Dhcpv4Enabled - not sure + // how to detect ipv6 ljt + ULONG afFamily = AF_UNSPEC; + switch (ifAddr.GetType()) + { + case ProtoAddress::IPv4: + afFamily = AF_INET; break; - case 3: - eventItem->SetType(Event::UNKNOWN_EVENT); +#ifdef HAVE_IPV6 + case ProtoAddress::IPv6: + afFamily = AF_INET6; break; +#endif // HAVE_IPV6 default: - eventItem->SetType(Event::UNKNOWN_EVENT); - PLOG(PL_ERROR,"Win32NetMonitor::GetEvent() warning: unhandled network event: %d\n",notificationType); break; } + ULONG bufferLength = 0; - - // Iterate through addresses looking for our interface index +#if (WINVER >= 0x0501) + // On NT4, Win2K and earlier, GetAdaptersAddresses() isn't to be found + // in the iphlpapi.dll ... + DWORD addrFlags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST; ULONG bufferSize = 0; - ULONG index = 0; - if (ERROR_INSUFFICIENT_BUFFER == GetIpAddrTable(NULL, &bufferSize, FALSE)) - { - char* tableBuffer = new char[bufferSize]; - if (NULL == tableBuffer) - { - PLOG(PL_ERROR, "Win32NetMonitor::GetEvent() new tableBuffer error: %s\n", ::GetErrorString()); - return false; - } - MIB_IPADDRTABLE* addrTable = (MIB_IPADDRTABLE*)tableBuffer; - if (ERROR_SUCCESS == GetIpAddrTable(addrTable, &bufferSize, FALSE)) + DWORD result = GetAdaptersAddresses(afFamily, addrFlags, NULL, NULL, &bufferSize); + if ((ERROR_BUFFER_OVERFLOW == result) || + (ERROR_NO_DATA == result)) + { + if (ERROR_NO_DATA == result) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressDhcp(%s) error: no matching interface adapters found.\n", ifAddr.GetHostString()); + return 0; + } + + char* addrBuffer = new char[bufferSize]; + if (NULL == addrBuffer) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressDhcp() new addrBuffer error: %s\n", ::GetErrorString()); + return 0; + } + IP_ADAPTER_ADDRESSES* addrEntry = (IP_ADAPTER_ADDRESSES*)addrBuffer; + if (ERROR_SUCCESS != GetAdaptersAddresses(afFamily, addrFlags, NULL, addrEntry, &bufferSize)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddressDhcp() GetAdaptersAddresses() error: %s\n", ::GetErrorString()); + delete[] addrBuffer; + return 0; + } + while (addrEntry) + { + if (0 == strncmp(ifName, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN)) { - for (DWORD i = 0; i < addrTable->dwNumEntries; i++) + // A match was found! + if (ProtoAddress::ETH != ifAddr.GetType()) { + IP_ADAPTER_UNICAST_ADDRESS* ipAddr = addrEntry->FirstUnicastAddress; + while (NULL != ipAddr) + { + if ((afFamily == AF_UNSPEC) || + (afFamily == ipAddr->Address.lpSockaddr->sa_family)) + { + ProtoAddress tmpAddr; + tmpAddr.SetSockAddr(*(ipAddr->Address.lpSockaddr)); + if (ifAddr == tmpAddr) + { + unsigned int mask = ipAddr->OnLinkPrefixLength; + delete[] addrBuffer; + // Apparantly windows doesn't automatically convert 0/1 to bool properly?? + if (addrEntry->Dhcpv4Enabled) + { + return true; + } + else + { + return false; + } + return addrEntry->Dhcpv4Enabled; + } + } + ipAddr = ipAddr->Next; + } + } + } + addrEntry = addrEntry->Next; + } + delete[] addrBuffer; + PLOG(PL_WARN, "ProtoNet::GetInterfaceAddressDhcp(%s) warning: no matching interface found. Checking for ip address.\n", ifAddr.GetHostString()); + } - MIB_IPADDRROW* entry = &(addrTable->table[i]); - if (entry->dwIndex == row->InterfaceIndex) - { - if (row->Family == AF_INET) - eventItem->AccessAddress().SetRawHostAddress(ProtoAddress::IPv4, (char*)&entry->dwAddr,4); - else - eventItem->AccessAddress().SetRawHostAddress(ProtoAddress::IPv6, (char*)&entry->dwAddr,16); - // TBD Ignore link local addr new/delete events? - - } + // See if "ifName" is actually an address + ProtoAddress tmpAddr; + if (tmpAddr.ConvertFromString(ifName)) + { + char ifName[MAX_ADAPTER_NAME_LENGTH + 4]; + if (!GetInterfaceName(tmpAddr, ifName, MAX_ADAPTER_NAME_LENGTH + 4)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceAddresDhcp(%s) error: no matching interface found\n", ifAddr.GetHostString()); + return false; + } + return GetInterfaceAddressDhcp(ifName, tmpAddr); + } - } - } - } - else - { - PLOG(PL_WARN, "Win32NetMonitor::GetEvent(%u) warning GetIpAddrTable() error: %s\n", row->InterfaceIndex, GetErrorString()); - } + return false; +#endif // if (WINVER >= 0x0501) + // TBD: Implement support for earlier versions of windows? + PLOG(PL_WARN, "ProtoNet::GetInterfaceAddressDhcp(%s) warning: no matching interface found (WINVER < 0x0501?)\n", ifAddr.GetHostString()); - Lock(lock); - event_list.Append(*eventItem); - Unlock(lock); - return true; + return false; +}; // ProtoNet::GetInterfaceAddressMask -} // end Win32NetMonitor::GetNextEvent(); -Win32NetMonitor::EventItem::EventItem() +bool ProtoNet::GetInterfaceIpAddress(unsigned int index,ProtoAddress& ifAddr) { -} +#if (WINVER >= 0x0501) // TBD Should work on older? -Win32NetMonitor::EventItem::~EventItem() -{ -} - + ULONG bufferLength = 0; + if (ERROR_BUFFER_OVERFLOW == GetAdaptersInfo(NULL, &bufferLength)) + { + char * infoBuffer = new char[bufferLength]; + if (NULL == infoBuffer) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceIpAddress(by index) new infoBuffer error: %s\n", GetErrorString()); + return false; + } + + IP_ADAPTER_INFO* adapterInfo = (IP_ADAPTER_INFO*)infoBuffer; + if (NO_ERROR != GetAdaptersInfo(adapterInfo, &bufferLength)) + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceIpAddress(by index) GetAdaptersInfo() error: %s\n", ::GetErrorString()); + delete[] infoBuffer; + return false; + } + while (NULL != adapterInfo) + { + if (index == adapterInfo->Index) + { + ifAddr.ResolveFromString(adapterInfo->IpAddressList.IpAddress.String); + return true; + } + adapterInfo = adapterInfo->Next; + } + } + else + { + PLOG(PL_ERROR, "ProtoNet::GetInterfaceIpAddress(%d) error: invalid index\n", index); + return false; + } + +#else + PLOG(PL_ERROR, "ProtoNet::GetInterfaceIpAddress(by index) not available on WINVER < 0x0501\n"); + return false; +#endif // if (WINVER >= 0x0501) + +} // end ProtoNet::GetInterfaceIpAddress(by index) diff --git a/src/win32/win32NetMonitor.cpp b/src/win32/win32NetMonitor.cpp new file mode 100755 index 0000000..d2c608b --- /dev/null +++ b/src/win32/win32NetMonitor.cpp @@ -0,0 +1,269 @@ +#include "protoNet.h" +#include + +// When compiled with the v100_xp platorm on vista and above the winver +// check will fail(NotifyIpInterfaceChange is not available below Vista) + +#if (WINVER >= 0x0600) // Win32NetMonitor only works for Windows Vista and higher +class Win32NetMonitor : public ProtoNet::Monitor +{ +public: + Win32NetMonitor(); + ~Win32NetMonitor(); + + bool Open(); + void Close(); + bool GetNextEvent(Event& theEvent); + bool GetEvent(PMIB_IPINTERFACE_ROW row, + MIB_NOTIFICATION_TYPE notificationType); + static bool FindIPAddr(NET_IFINDEX InterfaceIndex); + const char* GetNotificationType(int type); + HANDLE GetEventHandle() { return input_handle; } + +private: + // We cache mib changes to a linked list for + // retrieval by the GetNextEvent() method + class EventItem : public Event, public ProtoList::Item + { + public: + EventItem(); + ~EventItem(); + }; // end class Win32NetMontior::EventItem + + class EventList : public ProtoListTemplate {}; + EventList event_list; + EventList event_pool; + + typedef CRITICAL_SECTION Mutex; + Mutex lock; + + static void Init(Mutex& m) {InitializeCriticalSection(&m);} + static void Destroy(Mutex& m) {DeleteCriticalSection(&m);} + static void Lock(Mutex& m) {EnterCriticalSection(&m);} + static void Unlock(Mutex& m) {LeaveCriticalSection(&m);} + + HANDLE notification_handle; // handle to subsequently stop notifications + +}; // end class Win32NetMonitor + +/// This is the implementation of the ProtoNet::Monitor::Create() +/// static method (our win32-specific factory) +ProtoNet::Monitor* ProtoNet::Monitor::Create() +{ + return static_cast(new Win32NetMonitor); +} // end ProtoNet::Monitor::Create() + +Win32NetMonitor::Win32NetMonitor() +{ + Init(lock); +} + +Win32NetMonitor::~Win32NetMonitor() +{ +} + +// Static callback function for NotifyIpInterfaceChange API +static void WINAPI IpInterfaceChangeCallback(PVOID callerContext, + PMIB_IPINTERFACE_ROW row, + MIB_NOTIFICATION_TYPE notificationType) +{ + Win32NetMonitor* monitor = (Win32NetMonitor*)callerContext; + if (!monitor) + { + PLOG(PL_ERROR,"IpInterfaceChangeCallback() Error: No callerContext.\n"); + return; + } + + if (row) + { + // Get complete information for MIP_IPINTERFACE_ROW + GetIpInterfaceEntry(row); + // Add an event to our list for the notification + if (!monitor->GetEvent(row,notificationType)) + { + PLOG(PL_ERROR,"MonitorEventHandler() GetEvent error\n"); + return; + } + + } + if (!SetEvent(monitor->GetEventHandle())) + PLOG(PL_ERROR,"win32Net::MonitorEventHandler() Error setting event handle.\n"); +} + +bool Win32NetMonitor::Open() +{ + // Not using a manual reset event? + if (NULL == (input_handle = CreateEvent(NULL,FALSE,FALSE,NULL))) + { + input_handle = INVALID_HANDLE_VALUE; + PLOG(PL_ERROR,"Win32Monitor::Open() CreateEvent(event_handle) error: %s\n", ::GetErrorString()); + Close(); + return false; + } + // Initiate notifications ... + notification_handle = NULL; + if (!NotifyIpInterfaceChange( + AF_UNSPEC, // AF_INET + (PIPINTERFACE_CHANGE_CALLBACK)IpInterfaceChangeCallback, + this, + false, // initialNofification + ¬ification_handle) == NO_ERROR) + { + PLOG(PL_ERROR,"Win32NetMonitor::Open() NotifyIpInterfaceChange failed\n"); + return false; + } + + if (!ProtoNet::Monitor::Open()) + { + Close(); + return false; + } + + return true; +} + +void Win32NetMonitor::Close() +{ + if (IsOpen()) + { + ProtoNet::Monitor::Close(); + input_handle = INVALID_HANDLE; + } + if (notification_handle != INVALID_HANDLE) + CancelMibChangeNotify2(notification_handle); + + event_list.Destroy(); + event_pool.Destroy(); + + Unlock(lock); + Destroy(lock); +} + +const char* Win32NetMonitor::GetNotificationType(int type) + { + static const char* names[] = { + "ParameterNotification", + "AddInstance", + "DeleteInstance", + "InitialNotification" + }; + + const char* name = ""; + if (type >=0 && type < sizeof(names)) { + name = names[type]; + } + return name; + } +bool Win32NetMonitor::GetNextEvent(Event& theEvent) +{ + // 0) Initialize event instance + theEvent.SetType(Event::UNKNOWN_EVENT); + theEvent.SetInterfaceIndex(0); + theEvent.AccessAddress().Invalidate(); + + // 1) Get next event from list + Lock(lock); + EventItem* eventItem = event_list.RemoveHead(); + if (eventItem == NULL) + { + Unlock(lock); + theEvent.SetType(Event::NULL_EVENT); + return true; + } + theEvent = static_cast(*eventItem); + event_pool.Append(*eventItem); + Unlock(lock); + return true; +} + +bool Win32NetMonitor::GetEvent(PMIB_IPINTERFACE_ROW row, + MIB_NOTIFICATION_TYPE notificationType) +{ + EventItem* eventItem = event_pool.RemoveHead(); + if (NULL == eventItem) eventItem = new EventItem(); + if (NULL == eventItem) + { + PLOG(PL_ERROR,"Win32NetMonitor::GetEvent() new EventItem error: %s\n", GetErrorString()); + return false; + } + + eventItem->SetInterfaceIndex(row->InterfaceIndex); + + switch (notificationType) + { + case 0: + //eventItem->SetType(Event::IFACE_STATE); + // not interested in windows state changes at the moment + eventItem->SetType(Event::UNKNOWN_EVENT); + break; + case 1: + eventItem->SetType(Event::IFACE_UP); + break; + case 2: + eventItem->SetType(Event::IFACE_DOWN); + break; + case 3: + eventItem->SetType(Event::UNKNOWN_EVENT); + break; + default: + eventItem->SetType(Event::UNKNOWN_EVENT); + PLOG(PL_ERROR,"Win32NetMonitor::GetEvent() warning: unhandled network event: %d\n",notificationType); + break; + } + + + // Iterate through addresses looking for our interface index + ULONG bufferSize = 0; + ULONG index = 0; + if (ERROR_INSUFFICIENT_BUFFER == GetIpAddrTable(NULL, &bufferSize, FALSE)) + { + char* tableBuffer = new char[bufferSize]; + if (NULL == tableBuffer) + { + PLOG(PL_ERROR, "Win32NetMonitor::GetEvent() new tableBuffer error: %s\n", ::GetErrorString()); + return false; + } + MIB_IPADDRTABLE* addrTable = (MIB_IPADDRTABLE*)tableBuffer; + if (ERROR_SUCCESS == GetIpAddrTable(addrTable, &bufferSize, FALSE)) + { + for (DWORD i = 0; i < addrTable->dwNumEntries; i++) + { + + MIB_IPADDRROW* entry = &(addrTable->table[i]); + if (entry->dwIndex == row->InterfaceIndex) + { + if (row->Family == AF_INET) + eventItem->AccessAddress().SetRawHostAddress(ProtoAddress::IPv4, (char*)&entry->dwAddr,4); + else + eventItem->AccessAddress().SetRawHostAddress(ProtoAddress::IPv6, (char*)&entry->dwAddr,16); + // TBD Ignore link local addr new/delete events? + + } + + } + + } + } + else + { + PLOG(PL_WARN, "Win32NetMonitor::GetEvent(%u) warning GetIpAddrTable() error: %s\n", row->InterfaceIndex, GetErrorString()); + } + + + Lock(lock); + event_list.Append(*eventItem); + Unlock(lock); + return true; + +} // end Win32NetMonitor::GetNextEvent(); + +Win32NetMonitor::EventItem::EventItem() +{ +} + +Win32NetMonitor::EventItem::~EventItem() +{ +} + +#endif // (WINVER >= 0x00600) + diff --git a/src/win32/win32Vif.cpp b/src/win32/win32Vif.cpp index 3792640..ba9f17e 100755 --- a/src/win32/win32Vif.cpp +++ b/src/win32/win32Vif.cpp @@ -1,54 +1,58 @@ #include "protoVif.h" +#include "protoNet.h" #include "protoDebug.h" #include "windows.h" -#include "common.h" // ljt why isn't it finding this in the #include #include - -#include -// Where did these defines go?? Is this the right location? ljt -/* =============================================== - This file is included both by OpenVPN and - the TAP-Win32 driver and contains definitions - common to both. - =============================================== */ - -/* ============= - TAP IOCTLs - ============= */ - -#define TAP_CONTROL_CODE(request,method) \ - CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) - -#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE (6, METHOD_BUFFERED) -#define USERMODEDEVICEDIR "\\\\.\\Global\\" -#define NETWORK_CONNECTIONS_KEY \ +#include + +#include +/* =============================================== + This file is included both by OpenVPN and + the TAP-Win32 driver and contains definitions + common to both. + =============================================== */ + +/* ============= + TAP IOCTLs + ============= */ + +#define TAP_CONTROL_CODE(request,method) \ + CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) + +#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE (6, METHOD_BUFFERED) +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define NETWORK_CONNECTIONS_KEY \ "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" #define TAPSUFFIX ".tap" class Win32Vif : public ProtoVif { - public: - Win32Vif(); - ~Win32Vif(); + public: + Win32Vif(); + ~Win32Vif(); - bool InitTap(char* vifName, const ProtoAddress& vifAddr, unsigned int maskLen); - bool ConfigureTap(char* adapterid, char* vifName, const ProtoAddress& vifAddr,unsigned int maskLen); + bool InitTap(const char* vifName, const ProtoAddress& vifAddr, unsigned int maskLen); + bool ConfigureTap(char* adapterid, const char* vifName, const ProtoAddress& vifAddr,unsigned int maskLen); bool FindIPAddr(ProtoAddress vifAddr); - bool Open(const char* vifName, const ProtoAddress& vifAddr, unsigned int maskLen); - void Close(); - + bool SetHardwareAddress(const ProtoAddress& ethAddr); + void SetMAC(char * AdapterName, char * NewMAC); + void ResetAdapter(char * AdapterName); + bool IsValidMAC(char * str); + bool SetARP(bool status); bool Write(const char* buffer, unsigned int numBytes); bool Read(char* buffer, unsigned int& numBytes); - + private: + #ifndef _WIN32_WCE // These members facilitate Win32 overlapped I/O // which we use for async I/O HANDLE tap_handle; + char adapter_id[1024]; enum {BUFFER_MAX = 8192}; char read_buffer[BUFFER_MAX]; unsigned int read_count; @@ -73,8 +77,7 @@ Win32Vif::Win32Vif() Win32Vif::~Win32Vif() { } - -bool Win32Vif::ConfigureTap(char* adapterid,char* vifName,ProtoAddress vifAddr,unsigned int maskLen) +bool Win32Vif::ConfigureTap(char* adapterid,const char* vifName,const ProtoAddress& vifAddr,unsigned int maskLen) { ULONG status; HKEY key; @@ -83,7 +86,6 @@ bool Win32Vif::ConfigureTap(char* adapterid,char* vifName,ProtoAddress vifAddr,u char regpath[1024]; char nameData[1024]; long len = sizeof(nameData); - /* Find out more about this adapter */ sprintf(regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid); @@ -115,27 +117,32 @@ bool Win32Vif::ConfigureTap(char* adapterid,char* vifName,ProtoAddress vifAddr,u return false;; } RegCloseKey(key); - - /* Configure the interface */ - char cmd[256]; - char subnetMask[16]; - - ProtoAddress subnetMaskAddr; - subnetMaskAddr.ResolveFromString("255.255.255.255"); - - ProtoAddress tmpAddr; - subnetMaskAddr.GetSubnetAddress(maskLen, tmpAddr); - sprintf(subnetMask," %s ", tmpAddr.GetHostString()); - - sprintf(cmd, - "netsh interface ip set address \"%s\" static %s %s ", - nameData, vifAddr.GetHostString(), subnetMask); - - if (LONG ret = system(cmd)) - { - PLOG(PL_ERROR,"Win32Vif::ConfigureTap(%s) Open failed. netsh call returned: %u", nameData, ret); - return false; - } + char cmd[256]; + if (vifAddr.IsValid()) + { + + /* Configure the interface */ + char subnetMask[16]; + + ProtoAddress subnetMaskAddr; + subnetMaskAddr.ResolveFromString("255.255.255.255"); + + ProtoAddress tmpAddr; + subnetMaskAddr.GetSubnetAddress(maskLen, tmpAddr); + sprintf(subnetMask, " %s ", tmpAddr.GetHostString()); + + sprintf(cmd, + "netsh interface ip set address \"%s\" static %s %s ", + nameData, vifAddr.GetHostString(), subnetMask); + PLOG(PL_INFO, "cmd>%s \n", cmd); + if (LONG ret = system(cmd)) + { + PLOG(PL_ERROR, "Win32Vif::ConfigureTap(%s) Open failed. netsh call returned: %u", nameData, ret); + return false; + } + } + else + PLOG(PL_INFO, "Win32Vif::ConfigureTap() no valid ip addr provided. Replace mode?\n"); // Rename the connection if (strcmp(nameData, vifName) != 0) @@ -148,8 +155,9 @@ bool Win32Vif::ConfigureTap(char* adapterid,char* vifName,ProtoAddress vifAddr,u nameData, vifName, ret); return false; } + sprintf(vif_name, "%s", vifName); } - PLOG(PL_INFO,"Win32Vif::ConfigureTap() Tap opened on interface: %s\n",vifName); + PLOG(PL_INFO,"Win32Vif::ConfigureTap() Tap configured on interface: %s\n",vifName); /* set driver media status to 'connected' */ status = TRUE; @@ -186,7 +194,7 @@ bool Win32Vif::InitTap(const char* vifName, const ProtoAddress& vifAddr, unsigne /* find the adapter with TAPSUFFIX */ for (int enum_index = 0; ; enum_index++) { - len = sizeof(adapterid); + len = sizeof(adapter_id); if (RegEnumKeyEx(key, enum_index, adapterid, (LPDWORD)&len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) { @@ -200,6 +208,22 @@ bool Win32Vif::InitTap(const char* vifName, const ProtoAddress& vifAddr, unsigne 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0); + if (tap_handle == INVALID_HANDLE_VALUE) + { + //PLOG(PL_INFO, "error>%d tapName>%s\n", GetLastError(),tapname); + /* TODO: Format windows error message - put in function + LPVOID lpMsgBuf; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR)&lpMsgBuf, 0, NULL); + // Display the string. + MessageBox(NULL, (LPCTSTR)lpMsgBuf, (LPCTSTR)"Error", MB_OK | MB_ICONINFORMATION); + */ + } + if (tap_handle != INVALID_HANDLE_VALUE) { break; @@ -207,23 +231,29 @@ bool Win32Vif::InitTap(const char* vifName, const ProtoAddress& vifAddr, unsigne } RegCloseKey(key); - PLOG(PL_INFO,"win32Vif::InitTap() found tap adapter %s\n",tapname); + PLOG(PL_INFO,"Win32Vif::InitTap() found tap adapter %s\n",tapname); if (!ConfigureTap(adapterid,vifName,vifAddr,maskLen)) return false; + // Squirrel away our adapter id for now - we neet it to set hardware + // info. TODO: develop functions to get adapter id from vif name? + // NOTE: Set hardware address is not currently working under windows 7 + // remove this code? + sprintf(adapter_id, "%s", adapterid); + if (NULL == (input_handle = CreateEvent(NULL,TRUE,FALSE,NULL))) { input_handle = INVALID_HANDLE_VALUE; PLOG(PL_ERROR,"Win32Vif::InitTap() CreateEvent(input_handle) error: %s\n", ::GetErrorString()); - Close(); // ljt?? + Close(); return false; } if (NULL == (output_handle = CreateEvent(NULL,TRUE,FALSE,NULL))) { output_handle = INVALID_HANDLE_VALUE; PLOG(PL_ERROR,"Win32Vif::InitTap() CreateEvent(output_handle) error: %s\n",::GetErrorString()); - Close(); // ljt?? + Close(); return false; } @@ -280,6 +310,13 @@ bool Win32Vif::Open(const char* vifName, const ProtoAddress& vifAddr, unsigned i return false; } strncpy(vif_name, vifName, VIF_NAME_MAX); + + // Snag the virtual interface hardware address + if (!ProtoNet::GetInterfaceAddress(vifName, ProtoAddress::ETH, hw_addr)) + PLOG(PL_ERROR, "Win32Vif::Open(%s) error: unable to get ETH address!\n", vifName); + + PLOG(PL_INFO,"Win32Vif::Open() vif>%s mac>%s\n", vifName, hw_addr.GetHostString()); + if (!ProtoChannel::Open()) { PLOG(PL_ERROR, "Win32Vif::Open(%s) error: couldn't install ProtoChannel\n", vifName); @@ -316,8 +353,6 @@ void Win32Vif::Close() bool Win32Vif::Write(const char* buffer, unsigned int numBytes) { - // ljt is this quite right?? - DWORD bytesWritten = 0; OVERLAPPED overlappedPtr; @@ -460,6 +495,7 @@ bool Win32Vif::Read(char* buffer, unsigned int& numBytes) bool Win32Vif::FindIPAddr(ProtoAddress vifAddr) { + // Iterate through addresses looking for an address match ULONG bufferSize = 0; ULONG index = 0; @@ -468,7 +504,7 @@ bool Win32Vif::FindIPAddr(ProtoAddress vifAddr) char* tableBuffer = new char[bufferSize]; if (NULL == tableBuffer) { - PLOG(PL_ERROR, "ProtoSocket::GetInterfaceName() new tableBuffer error: %s\n", ::GetErrorString()); + PLOG(PL_ERROR, "Win32Vif::FindIPAddr() new tableBuffer error: %s\n", ::GetErrorString()); return false; } MIB_IPADDRTABLE* addrTable = (MIB_IPADDRTABLE*)tableBuffer; @@ -486,7 +522,7 @@ bool Win32Vif::FindIPAddr(ProtoAddress vifAddr) index = entry->dwIndex; if (NO_ERROR != GetIfEntry(&ifEntry)) { - PLOG(PL_ERROR, "ProtoSocket::GetInterfaceName() GetIfEntry(%d) error: %s\n", i, ::GetErrorString()); + PLOG(PL_ERROR, "Win32Vif::FindIPAddr() GetIfEntry(%d) error: %s\n", i, ::GetErrorString()); return false; } delete[] tableBuffer; @@ -496,7 +532,7 @@ bool Win32Vif::FindIPAddr(ProtoAddress vifAddr) } else { - PLOG(PL_WARN, "ProtoSocket::GetInterfaceName(%s) warning GetIpAddrTable() error: %s\n", vifAddr.GetHostString(), GetErrorString()); + PLOG(PL_WARN, "Win32Vif::FindIPAddr(%s) warning GetIpAddrTable() error: %s\n", vifAddr.GetHostString(), GetErrorString()); } delete[] tableBuffer; } @@ -521,10 +557,263 @@ bool Win32Vif::FindIPAddr(ProtoAddress vifAddr) &NTEContext, &NTEInstance)) != NO_ERROR) { - PLOG(PL_ERROR,"Win32Vif::Open() AddIPAddress call failed with %d\n",status); + PLOG(PL_ERROR,"Win32Vif::FindIPAddr() AddIPAddress call failed with %d\n",status); return false; } return true; } return false; } // end Win32Vif::FindIPAddr() + +void Win32Vif::SetMAC(char * AdapterName, char * NewMAC) +{ + /* NOTE: Under development do not use.*/ + PLOG(PL_ERROR, "Win32Vif::SetMAC() Unable to set vif mac address. Not currently working on windows.\n"); + return; + + ULONG status; + //HKEY key; + DWORD nameType; + const char nameString[] = "Name"; + char regpath[1024]; + char nameData[1024]; + long len = sizeof(nameData); + + HKEY hListKey = NULL; + HKEY hKey = NULL; + + // TODO: Use our definitions + RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, + 0, KEY_READ, &hListKey); + if (!hListKey) { + PLOG(PL_ERROR,"Win32Vif::SetMac() Failed to open adapter list key\n"); + return; + } + FILETIME writtenTime; + char keyNameBuf[512], keyNameBuf2[512]; + DWORD keyNameBufSiz = 512; + DWORD crap; + int i = 0; + bool found = false; + while (RegEnumKeyEx(hListKey, i++, keyNameBuf, &keyNameBufSiz, 0, NULL, NULL, &writtenTime) + == ERROR_SUCCESS) { + _snprintf(keyNameBuf2, 512, "%s\\Connection", keyNameBuf); + + hKey = NULL; + RegOpenKeyEx(hListKey, keyNameBuf2, 0, KEY_READ, &hKey); + if (hKey) { + keyNameBufSiz = 512; + if (RegQueryValueEx(hKey, "Name", 0, &crap, (LPBYTE)keyNameBuf2, &keyNameBufSiz) + == ERROR_SUCCESS && strcmp(keyNameBuf, AdapterName) == 0) { + PLOG(PL_INFO,"Win32Vif::SetMac() Found Adapter ID is %s\n", keyNameBuf); + found = true; + break; + + } + RegCloseKey(hKey); + } + keyNameBufSiz = 512; + } + RegCloseKey(hListKey); + if (!found) { + PLOG(PL_ERROR,"Win32Vif::SetMac() Could not find adapter name '%s'.\nPlease make sure this is the name you gave it in Network Connections.\n", AdapterName); + return; + } + + RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, + 0, KEY_READ, &hListKey); + if (!hListKey) { + PLOG(PL_ERROR,"Win32Vif::SetMac() Failed to open adapter list key in Phase 2\n"); + return; + } + i = 0; + char buf[512]; + while (RegEnumKeyEx(hListKey, i++, keyNameBuf2, &keyNameBufSiz, 0, NULL, NULL, &writtenTime) + == ERROR_SUCCESS) { + hKey = NULL; + RegOpenKeyEx(hListKey, keyNameBuf2, 0, KEY_READ | KEY_SET_VALUE, &hKey); + if (hKey) { + keyNameBufSiz = 512; + //PLOG(PL_INFO,"Win32Vif::SetMac() phase2>%s\n", buf); + if ((RegQueryValueEx(hKey, "NetCfgInstanceId", 0, &crap, (LPBYTE)buf, &keyNameBufSiz) + == ERROR_SUCCESS) ) { //&& (strcmp(buf, keyNameBuf) == 0)) { + + if (strcmp(buf, keyNameBuf) != 0) + { + //PLOG(PL_INFO,"Win32Vif::SetMac() not equal buf>%s keynamebuf>%s\n", buf, keyNameBuf); + continue; + } + + char mac[60] = ""; + unsigned long macsize = sizeof(mac); + DWORD type; + char tmpMAC[60] = ""; + if (RegQueryValueEx(hKey, "NetworkAddress", 0, &type, (LPBYTE)tmpMAC, &macsize) + == ERROR_SUCCESS) + PLOG(PL_INFO,"Win32Vif::SetMac() interface original mac> %s \n", tmpMAC); + + + if (RegSetValueEx(hKey, "NetworkAddress", 0, REG_SZ, (LPBYTE)NewMAC, strlen(NewMAC) + 1) + == ERROR_SUCCESS) + PLOG(PL_INFO,"Win32Vif::SetMac() Updating adapter index %s (%s=%s) newMac>%s \n", keyNameBuf2, buf, keyNameBuf, NewMAC); + else + { + + PLOG(PL_ERROR, "Win32Vif::SetMac() set mac error>%d tapName>%s\n", GetLastError(),keyNameBuf); + + LPVOID lpMsgBuf; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR)&lpMsgBuf, 0, NULL); + // Display the string. + MessageBox(NULL, (LPCTSTR)lpMsgBuf, (LPCTSTR)"Error", MB_OK | MB_ICONINFORMATION); + + } + if (RegQueryValueEx(hKey, "NetworkAddress", 0, &type, (LPBYTE)NewMAC, &macsize) + == ERROR_SUCCESS) + PLOG(PL_INFO,"Win32Vif::SetMac() querying reset interface mac> %s \n", NewMAC); + + break; + } + RegCloseKey(hKey); + } + keyNameBufSiz = 512; + } + RegCloseKey(hListKey); + +} +bool Win32Vif::IsValidMAC(char * str) { + + PLOG(PL_INFO, "Win32Vif::IsValidMAC() Not implemented\n"); + return true; + + // stub + if (strlen(str) != 18) return false; + for (int i = 0; i < 18; i++) { + if (str[i] == '-') + continue; + if ((str[i] < '0' || str[i] > '9') + && (str[i] < 'a' || str[i] > 'f') + && (str[i] < 'A' || str[i] > 'F')) { + return false; + } + } + return true; +} + +bool Win32Vif::SetHardwareAddress(const ProtoAddress& ethAddr) +{ + /* NOTE: Under development do not use.*/ + PLOG(PL_ERROR, "Win32Vif::SetHardwareAddress() Unable to set vif hardware address. Not currently working on windows.\n"); + return true; + + // LJT: I made a stab at setting this but setting the macAddr + // doesn't work. It gets set in the registry but never assigned to the + // actual vif. Taking too much time when usefuleness is unknown. + // leaving code as a hint as a potential technique + PLOG(PL_INFO, "Win32Vif::SetHardwareAddress(mac) value: %s\n", ethAddr.GetHostString()); + + if (ProtoAddress::ETH != ethAddr.GetType()) + { + PLOG(PL_ERROR, "Win32Vif::SetHardwareAddress() error: invalid address type!\n"); + return false; + } + const UINT8* addr = (const UINT8*)ethAddr.GetRawHostAddress(); + char mac[64]; + + sprintf(mac, " %02x-%02x-%02x-%02x-%02x-%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + + // IsValidMAC is not fully implemented + if (IsValidMAC(mac)) + { + SetMAC(adapter_id, mac); + ResetAdapter(vif_name); + } + + return true; +} // end Win32Vif::SetHardwareAddress() + +bool Win32Vif::SetARP(bool status) +{ + PLOG(PL_ERROR, "Win32Vif::SetARP() unimplemented function on WIN32"); + return false; + + // end Win32Vif::SetARP() +} +void Win32Vif::ResetAdapter(char * AdapterName) { + + /* NOTE: Under development do not use.*/ + PLOG(PL_INFO, "Win32Vif::ResetAdapter() unimplemented function on WIN32"); + return; + + struct _GUID guid = { 0xBA126AD1, 0x2166, 0x11D1, 0 }; + memcpy(guid.Data4, "\xB1\xD0\x00\x80\x5F\xC1\x27\x0E", 8); + //unsigned short * buf = new unsigned short[strlen(AdapterName) + 1]; + char * buf = new char[strlen(AdapterName) + 1]; + void(__stdcall *NcFreeNetConProperties) (NETCON_PROPERTIES *); + HMODULE NetShell_Dll = LoadLibrary("Netshell.dll"); + if (!NetShell_Dll) { + PLOG(PL_ERROR,"Win32Vif::ResetAdapter() Couldn't load Netshell.dll\n"); + return; + } + NcFreeNetConProperties = (void(__stdcall *)(struct tagNETCON_PROPERTIES *))GetProcAddress(NetShell_Dll, "NcFreeNetconProperties"); + if (!NcFreeNetConProperties) { + PLOG(PL_ERROR,"Win32Vif::ResetAdapter() Couldn't load required DLL function\n"); + return; + } + + for (unsigned int i = 0; i <= strlen(AdapterName); i++) { + buf[i] = AdapterName[i]; + } + CoInitialize(0); + INetConnectionManager * pNCM = NULL; + HRESULT hr = ::CoCreateInstance(guid, + NULL, + CLSCTX_ALL, + __uuidof(INetConnectionManager), + (void**)&pNCM); + if (!pNCM) + PLOG(PL_ERROR,"Win32Vif::ResetAdapter() Failed to instantiate required object\n"); + else { + IEnumNetConnection * pENC; + pNCM->EnumConnections(NCME_DEFAULT, &pENC); + if (!pENC) { + PLOG(PL_ERROR,"Win32Vif::ResetAdapater() Could not enumerate Network Connections\n"); + } + else { + INetConnection * pNC; + ULONG fetched; + NETCON_PROPERTIES * pNCP; + do { + pENC->Next(1, &pNC, &fetched); + if (fetched && pNC) { + pNC->GetProperties(&pNCP); + if (pNCP) { + //(0 == strncmp(interfaceName, addrEntry->AdapterName, MAX_INTERFACE_NAME_LEN)) + //if (wcscmp(pNCP->pszwName, buf) == 0) { + // Convert to char * + char * tmpBuf = new char[strlen(buf) + 1]; + wcstombs(tmpBuf, pNCP->pszwName, strlen(tmpBuf)); + if (0 == strncmp(buf, tmpBuf, strlen(buf))) + { + PLOG(PL_INFO, "Win32Vif::ResetAdapater() Resetting interface %s", tmpBuf); + pNC->Disconnect(); + pNC->Connect(); + } + NcFreeNetConProperties(pNCP); + } + } + } while (fetched); + pENC->Release(); + } + pNCM->Release(); + } + + FreeLibrary(NetShell_Dll); + CoUninitialize(); +} +//TODO: Validate mac diff --git a/waf b/waf index e1e34d431afba28d52add07a6094226a1a74374b..f6e96169d3883a30306aacc5a9c0553348be6f6c 100755 GIT binary patch delta 94573 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 z z8YLLpf-A?O4~Ezm7Y7EKdF6l|!ku=*rkV_r0}mH*8WtgTNZ67lH+}_!`$SrNCPTPq zF1>CYohN8hV|tX^aW`W}Ix7}l#@J*IhL%B<9t7UhP1pJ-Xbris;0)lCOpDhOwk8kAO5fio0T}b(R>f6C!7?cRFx(m$t%wiEqq> zAJK(j=fsmc?NY0rpD+v?SYUFOyA2JFL~|t#VHO`oBV~-FVh6~H4)XcQW6ZI}mSR$+ zv#Hb2q{A^`3w*7L2?@JJ#mR&NP%}{Ct((rYzAF}8(u~(fy79Qdj z*KrkL&C8=6sj3oD6tT{gbnE3K2JYEhanMn7%~Dh$y+$k(D;{!5(mnF*B5NBym~dKD zKzgjFR(0F6)0S|@Z(0}Mxwnc>quA={329N0#@R#(&UyUW83Vdfrdij&`7z`K%#0~( zO$!ctqS{q#e!|Zx80Uwg0VYX3VXvuip`+(wB8KPguc3;2{F&|GH%YQl+ zanpy^=^yv;JT^DgABn2e6XSb50RQo^g~&2UTOas&NiW5xm8AEK%1-m3?A#L{M9$BZ zJ%{A-Ro0;JH*;<8py%V*x&w49TD9CdG$JT%!`?HyZ^1vb-2WZ;%Pc{|kmSDilhk7d zk%!+N#_f3fJoO-kJdknb7#pwEL3CowQSxvRUO52Tp5ihfQTV)5cn>~92v>D+29TBB z0DjPmW=)BaCPliXCuaCDy}^W$FuC4MaI`$gFZ+!$pHm|k1&VfO?pfbnL|teHMDcNZ zA;*q#hm%l!D5;F_y|)rbJ+&kjtJX_-5Kf>6$7%JfEyYNAOD;x8o%M67w9jq0uz z*`RxH)M}G!BG}bt-+8u(AU!Q+?51Z{fkEbuP*y0 zN;Cq4T?WdfUtOje?d!ceDvf3E{+6LsYUBJg#I#ctc3#GmVvY4rjd!M$&n6p)uxLWx z#Sky?@!a(5W%dSzZ%~z3QF7uSv~T2So6v|b*#k*OemDL^pq)!fGxa8zl9~J9Gl1Rf2mN5rI6o$(^E&rXz79K-0>umz)^yczF&H75p!Kmd8!LG zQ&b9q+!PR0skvchdc~o3e&N6M<72KI^opj9x!f54SB~yl*U97o0F}~YdqDe;+umP znPSPrvTD^%hD>Xa%QHoUW0R&3C~E7Y0km4ZjB{J3oQ(Kg*y|r|5O`T*a>`-SlFWyH zDc1MOl0-=6Fot3k^>K{DYZT#}E7Ez=b(EnH2KgfvyQybBwJ&eJrlUdiWJZ^mG*K-ULGZ)HxIghR4^31*tc`L@u}fE5oFKC09KjWM+%Q`>5lc=q1I{O zG^L1xOjc=RY@0H5cF!M8QfMfwn*}a)=(-p#v)8+Zw@6ZDHu0llc6ov(mUj&I$3JmT z^G>{Ck8-&~B?kZ27Zegz3t*4L|Ec+kb zR<>o$xJ;#@O7Cl`1)Ps=Js`2;_;cY`X1XSV3C-R2r*7xRJ+94JNru>U47P35-ls(q zby%I=L+4p^b|WT--RuS2=$*@|t~v8r!Xz@u6X*>GlYA3kNXrr~8~v0^kIPY4S}=&_ zo~LMO`~wq|*7N;g*Kej@=-290qlZkp)I5Z(WL*VW@&{5ZRPbUR$KC$A4S|>=x`2xk zm&3i=)*(XA5o_&>b~UY-EkLNZ-VpTo;UoEMe1pJm;SoLke$7>v;$waDL8oBL!6d8C z;i3}XeJSfK+AM~qsg2d+*J2;Ow!CC?R~5AG@-DXFM4ydrZe3Qq3IQR>o9#0=s(E1O z%g2>7+b%<)Zg^FHm1wD-o#O4n9KDZlVUmlnAw?$FY?4js=7DEcoIvjN |r5U5+s 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 Nj-yNf3L`u{Jqo|