blob: a38735ba6b9d70a6f11ecd75f7131f4e3e1032f7 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Google LLC.
* Copyright (c) 2014-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements the Weave Mock Weave Border Gateway.
*
* This is used to instantiate a Tunnel Agent which opens a
* tunnel endpoint and forwards IPv6 packets between the
* Service connection and the tunnel endpoint.
*
*/
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <unistd.h>
#include <string.h>
#include <fstream>
#include "ToolCommon.h"
#include "CASEOptions.h"
#include <Weave/Support/logging/DecodedIPPacket.h>
#include <Weave/Profiles/ProfileCommon.h>
#include <Weave/Profiles/weave-tunneling/WeaveTunnelAgent.h>
#include <Weave/Profiles/weave-tunneling/WeaveTunnelCommon.h>
#include <InetLayer/InetInterface.h>
#include <Weave/Support/WeaveFaultInjection.h>
#include <Weave/Profiles/device-description/DeviceDescription.h>
#include <Weave/Profiles/vendor/nestlabs/device-description/NestProductIdentifiers.hpp>
#if WEAVE_CONFIG_ENABLE_TUNNELING
using namespace ::nl::Weave::Profiles::WeaveTunnel;
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
using namespace ::nl::Weave::Profiles::DeviceDescription;
#endif
#define TOOL_NAME "TestTunnelCASEPersistClient"
#define DEFAULT_BG_NODE_ID (0x18b4300000000001)
#define DEFAULT_TFE_NODE_ID (0x18b4300000000002)
#define BUFF_AVAILABLE_SIZE (1024)
#define PERSISTENT_TUNNEL_SESSION_PATH "./persistentTunnelCASE-BR"
static bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg);
static bool HandleNonOptionArgs(const char *progName, int argc, char *argv[]);
static void HandleLoadPersistedTunnelCASESession(nl::Weave::WeaveConnection *con);
static void HandleSessionPersistOnTunnelClosure(nl::Weave::WeaveConnection *con);
static WEAVE_ERROR RestorePersistedTunnelCASESession(nl::Weave::WeaveConnection *con);
static WEAVE_ERROR SuspendAndPersistTunnelCASESession(nl::Weave::WeaveConnection *con);
static bool IsPersistentTunnelSessionPresent(uint64_t peerNodeId);
static void
WeaveTunnelOnStatusNotifyHandlerCB(WeaveTunnelConnectionMgr::TunnelConnNotifyReasons reason,
WEAVE_ERROR aErr, void *appCtxt);
static WeaveTunnelAgent gTunAgent;
uint8_t gTunUpCount = 0;
bool gTestSucceeded = false;
static bool gTunnelLogging = false;
static IPAddress gDestAddr = IPAddress::Any;
static uint16_t gDestPort = 0;
static uint64_t gDestNodeId = DEFAULT_TFE_NODE_ID;
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
static bool gUseServiceDirForTunnel = false;
static ServiceDirectory::WeaveServiceManager gServiceMgr;
static uint8_t gServiceDirCache[500];
#endif
static uint8_t gRole = kClientRole_BorderGateway; //Default Value
enum
{
kToolOpt_ConnectTo = 1000,
kToolOpt_UseServiceDir,
};
static OptionDef gToolOptionDefs[] =
{
{ "connect-to", kArgumentRequired, kToolOpt_ConnectTo },
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
{ "service-dir", kNoArgument, kToolOpt_UseServiceDir },
#endif // WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
#if WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
{ "tunnel-log", kNoArgument, 'l' },
#endif // WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
{ }
};
static const char *const gToolOptionHelp =
" --connect-to <addr>[:<port>][%<interface>]\n"
" Connect to the tunnel service at the supplied address.\n"
"\n"
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
" --service-dir\n"
" Use service directory to lookup the address of the tunnel server.\n"
"\n"
#endif // WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
#if WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
" -l, --tunnel-log\n"
" Use detailed logging of Tunneled IP packet\n"
"\n"
#endif // WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
"";
static OptionSet gToolOptions =
{
HandleOption,
gToolOptionDefs,
"GENERAL OPTIONS",
gToolOptionHelp
};
static HelpOptions gHelpOptions(
TOOL_NAME,
"Usage: " TOOL_NAME " <options>\n",
WEAVE_VERSION_STRING "\n" WEAVE_TOOL_COPYRIGHT
);
static OptionSet *gToolOptionSets[] =
{
&gToolOptions,
&gNetworkOptions,
&gWeaveNodeOptions,
&gWRMPOptions,
&gCASEOptions,
&gDeviceDescOptions,
&gServiceDirClientOptions,
&gFaultInjectionOptions,
&gHelpOptions,
NULL
};
bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg)
{
switch (id)
{
case kToolOpt_ConnectTo:
{
const char *host;
uint16_t hostLen;
if (ParseHostAndPort(arg, strlen(arg), host, hostLen, gDestPort) != WEAVE_NO_ERROR)
{
PrintArgError("%s: Invalid value specified for --connect-to: %s\n", progName, arg);
return false;
}
char *hostCopy = strndup(host, hostLen);
bool isValidAddr = IPAddress::FromString(hostCopy, gDestAddr);
free(hostCopy);
if (!isValidAddr)
{
PrintArgError("%s: Invalid value specified for --connect-to (expected IP address): %s\n", progName, arg);
return false;
}
break;
}
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
case kToolOpt_UseServiceDir:
gUseServiceDirForTunnel = true;
break;
#endif
case 'l':
gTunnelLogging = true;
break;
default:
PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name);
return false;
}
return true;
}
bool HandleNonOptionArgs(const char *progName, int argc, char *argv[])
{
if (argc > 0)
{
if (argc > 1)
{
PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]);
return false;
}
if (!ParseNodeId(argv[0], gDestNodeId))
{
PrintArgError("%s: Invalid value specified for destination node-id: %s\n", progName, argv[0]);
return false;
}
}
return true;
}
#if WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
void TunneledPacketTransitHandler(const PacketBuffer &pkt, TunnelPktDirection pktDir, TunnelType tunnelType, bool &toDrop)
{
DecodedIPPacket decodedPkt;
char InOrOut[9];
char tunTypeStr[9];
// Decode the packet; skip the tunnel header and pass the IP packet
decodedPkt.PacketHeaderDecode(pkt.Start() + TUN_HDR_SIZE_IN_BYTES, pkt.DataLength() - TUN_HDR_SIZE_IN_BYTES);
strncpy(InOrOut, (pktDir == kDir_Outbound) ? "Outbound" : "Inbound", sizeof(InOrOut));
strncpy(tunTypeStr, (tunnelType == kType_TunnelPrimary) ? "primary" : (tunnelType == kType_TunnelBackup) ? "backup" : "shortcut", sizeof(tunTypeStr));
WeaveLogDetail(WeaveTunnel, "Tun: %s over %s", InOrOut, tunTypeStr);
// Log the header fields
LogPacket(decodedPkt, true);
// Inject a packet drop by the application.
WEAVE_FAULT_INJECT(FaultInjection::kFault_TunnelPacketDropByPolicy,
toDrop = true);
}
#endif // WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
void
WeaveTunnelOnStatusNotifyHandlerCB(WeaveTunnelConnectionMgr::TunnelConnNotifyReasons reason,
WEAVE_ERROR aErr, void *appCtxt)
{
WeaveLogDetail(WeaveTunnel, "WeaveTunnelAgent notification reason code is %d", reason);
if (reason == WeaveTunnelConnectionMgr::kStatus_TunPrimaryUp)
{
if (gTunUpCount < 1)
{
gTunUpCount++;
gTunAgent.StopServiceTunnel(WEAVE_ERROR_TUNNEL_FORCE_ABORT);
gTunAgent.StartServiceTunnel();
}
else
{
gTestSucceeded = true;
}
}
else
{
gTestSucceeded = false;
}
}
bool PersistedSessionKeyExists(const char *name)
{
return (access(name, F_OK) != -1);
}
WEAVE_ERROR
SuspendAndPersistTunnelCASESession(nl::Weave::WeaveConnection *con)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
std::ofstream persistentTunOfStream;
nl::Weave::WeaveSessionKey * persistedTunnelSessionKey = NULL;
const char * persistentTunnelSessionPath = PERSISTENT_TUNNEL_SESSION_PATH;
VerifyOrExit(con, err = WEAVE_ERROR_INVALID_ARGUMENT);
// If exist, this functions has already been called
VerifyOrExit(!PersistedSessionKeyExists(persistentTunnelSessionPath),
err = WEAVE_ERROR_SESSION_KEY_SUSPENDED);
uint8_t buf[BUFF_AVAILABLE_SIZE];
uint16_t dataLen;
err = FabricState.FindSessionKey(con->DefaultKeyId, con->PeerNodeId, false,
persistedTunnelSessionKey);
SuccessOrExit(err);
// Set the resumptionMsgIdValid flag
persistedTunnelSessionKey->SetResumptionMsgIdsValid(true);
// This call suspends the CASE session and returns a serialized byte stream
err = FabricState.SuspendSession(persistedTunnelSessionKey->MsgEncKey.KeyId,
persistedTunnelSessionKey->NodeId,
buf,
BUFF_AVAILABLE_SIZE,
dataLen);
SuccessOrExit(err);
// If success, set goodbit in the internal state flag
// In case of failure, set failbit.
persistentTunOfStream.open(persistentTunnelSessionPath, std::ofstream::binary | std::ios::trunc);
// If not open and associated with this stream object, directly fail
VerifyOrExit(persistentTunOfStream.is_open(),
err = WEAVE_ERROR_PERSISTED_STORAGE_FAIL);
// If fail, sets badbit or failbit in the internal state flags
persistentTunOfStream.write((char*)buf, dataLen);
// In case of failure, set failbit.
persistentTunOfStream.close();
// Check the stream's state flags
VerifyOrExit(persistentTunOfStream.good(),
err = WEAVE_ERROR_PERSISTED_STORAGE_FAIL);
printf("Suspending and persisting of tunnel CASE session successful\n");
exit:
if (err != WEAVE_NO_ERROR)
{
printf("Suspending and persisting of tunnel CASE Session failed with Weave error: %d\n", err);
}
return err;
}
WEAVE_ERROR
RestorePersistedTunnelCASESession(nl::Weave::WeaveConnection *con)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
std::ifstream persistentCASE;
const char * persistentTunnelSessionPath = PERSISTENT_TUNNEL_SESSION_PATH;
if (PersistedSessionKeyExists(persistentTunnelSessionPath))
{
printf("persistent tunnel CASE session exists\n");
uint8_t buf[BUFF_AVAILABLE_SIZE];
// In case of failure, set failbit.
persistentCASE.open(persistentTunnelSessionPath, std::ifstream::binary);
VerifyOrExit(persistentCASE.is_open(),
err = WEAVE_ERROR_PERSISTED_STORAGE_FAIL);
persistentCASE.seekg(0, persistentCASE.end);
uint16_t dataLen = persistentCASE.tellg();
persistentCASE.seekg(0, persistentCASE.beg);
VerifyOrExit(dataLen <= BUFF_AVAILABLE_SIZE,
err = WEAVE_ERROR_BUFFER_TOO_SMALL);
persistentCASE.read((char*) buf, dataLen);
// In case of failure, set failbit.
persistentCASE.close();
VerifyOrExit(!persistentCASE.fail(),
err = WEAVE_ERROR_PERSISTED_STORAGE_FAIL);
// delete persist storage before restore session
VerifyOrExit(std::remove(persistentTunnelSessionPath) == 0,
err = WEAVE_ERROR_PERSISTED_STORAGE_FAIL);
err = FabricState.RestoreSession(buf, dataLen, con);
SuccessOrExit(err);
printf("Restored persistent tunnel CASE session successfully\n");
}
else
{
printf("Persistent tunnel CASE Session doesn't exist\n");
}
exit:
if (persistentCASE.is_open())
{
persistentCASE.close();
}
if (err != WEAVE_NO_ERROR)
{
printf("Restore Persistent CASE Session Failed with weave err: %d\n", err);
}
return err;
}
void HandleLoadPersistedTunnelCASESession(nl::Weave::WeaveConnection *con)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
err = RestorePersistedTunnelCASESession(con);
if (err != WEAVE_NO_ERROR)
{
printf("Restoring Tunnel CASE Session failed with Weave error: %d\n", err);
}
}
void HandleSessionPersistOnTunnelClosure(nl::Weave::WeaveConnection *con)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
err = SuspendAndPersistTunnelCASESession(con);
if (err != WEAVE_NO_ERROR)
{
printf("Suspending and persisting Tunnel CASE Session failed with Weave error: %d\n", err);
}
}
bool IsPersistentTunnelSessionPresent(uint64_t peerNodeId)
{
const char * persistentTunnelSessionPath = PERSISTENT_TUNNEL_SESSION_PATH;
return (access(persistentTunnelSessionPath, F_OK) != -1);
}
#endif // WEAVE_CONFIG_ENABLE_TUNNELING
int main(int argc, char *argv[])
{
#if WEAVE_CONFIG_ENABLE_TUNNELING
WEAVE_ERROR err;
gWeaveNodeOptions.LocalNodeId = DEFAULT_BG_NODE_ID;
WeaveAuthMode authMode = kWeaveAuthMode_CASE_AnyCert;
nl::Weave::System::Stats::Snapshot before;
nl::Weave::System::Stats::Snapshot after;
const bool printStats = true;
InitToolCommon();
SetupFaultInjectionContext(argc, argv);
UseStdoutLineBuffering();
SetSignalHandler(DoneOnHandleSIGUSR1);
// Configure some alternate defaults for the device descriptor values.
gDeviceDescOptions.BaseDeviceDesc.ProductId = nl::Weave::Profiles::Vendor::Nestlabs::DeviceDescription::kNestWeaveProduct_Onyx;
strcpy(gDeviceDescOptions.BaseDeviceDesc.SerialNumber, "test-weave-tunnel-persist");
strcpy(gDeviceDescOptions.BaseDeviceDesc.SoftwareVersion, "test-weave-tunnel-persist/1.0");
gDeviceDescOptions.BaseDeviceDesc.DeviceFeatures = WeaveDeviceDescriptor::kFeature_LinePowered;
if (argc == 1)
{
gHelpOptions.PrintBriefUsage(stderr);
exit(EXIT_FAILURE);
}
if (!ParseArgsFromEnvVar(TOOL_NAME, TOOL_OPTIONS_ENV_VAR_NAME, gToolOptionSets, NULL, true) ||
!ParseArgs(TOOL_NAME, argc, argv, gToolOptionSets, HandleNonOptionArgs) ||
!ResolveWeaveNetworkOptions(TOOL_NAME, gWeaveNodeOptions, gNetworkOptions))
{
exit(EXIT_FAILURE);
}
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
if (gUseServiceDirForTunnel && gDestAddr != IPAddress::Any)
{
printf("ERROR: Please specify only one of --connect-to or --service-dir\n");
exit(EXIT_FAILURE);
}
if (!gUseServiceDirForTunnel && gDestAddr == IPAddress::Any)
{
printf("ERROR: Please specify how to find the tunnel server using either --connect-to or --service-dir\n");
exit(EXIT_FAILURE);
}
#else
if (gDestAddr == IPAddress::Any)
{
printf("ERROR: Please specify the address of the tunnel server using --connect-to\n");
exit(EXIT_FAILURE);
}
#endif
InitSystemLayer();
InitNetwork();
InitWeaveStack(false, true);
printf("Weave Node Configuration:\n");
printf(" Fabric Id: %" PRIX64 "\n", FabricState.FabricId);
printf(" Subnet Number: %X\n", FabricState.DefaultSubnet);
printf(" Node Id: %" PRIX64 "\n", FabricState.LocalNodeId);
nl::Weave::Stats::UpdateSnapshot(before);
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
err = gServiceMgr.init(&ExchangeMgr, gServiceDirCache, sizeof(gServiceDirCache),
GetRootServiceDirectoryEntry, kWeaveAuthMode_CASE_ServiceEndPoint,
NULL, NULL, OverrideServiceConnectArguments);
FAIL_ERROR(err, "gServiceMgr.Init failed");
#endif
#if WEAVE_CONFIG_ENABLE_SERVICE_DIRECTORY
if (gUseServiceDirForTunnel)
{
err = gTunAgent.Init(&Inet, &ExchangeMgr, gDestNodeId,
authMode, &gServiceMgr,
"weave-tun0", gRole);
}
else
#endif
{
err = gTunAgent.Init(&Inet, &ExchangeMgr, gDestNodeId, gDestAddr,
authMode,
"weave-tun0", gRole);
}
FAIL_ERROR(err, "TunnelAgent.Init failed");
gTunAgent.OnServiceTunStatusNotify = WeaveTunnelOnStatusNotifyHandlerCB;
if (gDestAddr != IPAddress::Any)
{
gTunAgent.SetDestination(gDestNodeId, gDestAddr, gDestPort);
}
#if WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
if (gTunnelLogging)
{
gTunAgent.OnTunneledPacketTransit = TunneledPacketTransitHandler;
}
else
{
gTunAgent.OnTunneledPacketTransit = NULL;
}
#endif // WEAVE_CONFIG_TUNNEL_ENABLE_TRANSIT_CALLBACK
#if WEAVE_CONFIG_PERSIST_CONNECTED_SESSION
gTunAgent.SetCallbacksForPersistedTunnelConnection(IsPersistentTunnelSessionPresent, HandleLoadPersistedTunnelCASESession);
#endif // WEAVE_CONFIG_PERSIST_CONNECTED_SESSION
FabricState.BoundConnectionClosedForSession = HandleSessionPersistOnTunnelClosure;
// Test case of Starting the Tunnel by negotiating the CASE session
err = gTunAgent.StartServiceTunnel();
FAIL_ERROR(err, "TunnelAgent.StartServiceTunnel failed");
while (!Done)
{
struct timeval sleepTime;
sleepTime.tv_sec = 0;
sleepTime.tv_usec = 100000;
ServiceNetwork(sleepTime);
if (gTestSucceeded)
{
Done = true;
}
else
{
continue;
}
}
if (gSigusr1Received) {
printf("SIGUSR1 received: proceed to exit gracefully\n");
}
gTunAgent.StopServiceTunnel(WEAVE_ERROR_TUNNEL_FORCE_ABORT);
gTunAgent.Shutdown();
ProcessStats(before, after, printStats, NULL);
PrintFaultInjectionCounters();
ShutdownWeaveStack();
ShutdownNetwork();
ShutdownSystemLayer();
#endif // WEAVE_CONFIG_ENABLE_TUNNELING
return EXIT_SUCCESS;
}