// Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//
#include <PINGENG.H>
#include <e32hal.h>
#include <icmp6_hdr.h>
#include <in_chk.h>
#include <commdbconnpref.h>
#include <connpref.h>
const TInt KDefaultPings = 4;
const TInt KDefaultInterval = 1000000;
const TInt KDefaultWait = 10000000;
const TInt KDefaultPingSize = 32;
const TInt KDefaultBacklog = 512;
const TInt KMaxTimeOnQue=KDefaultBacklog*KDefaultInterval;
const TInt KMaxSendTime=60000000;
const TUint KIcmpHeaderSize = 8;
const TUint KMinIpHeaderSize = 20;
class CPingTimer : public CTimer
{
friend class CPingEng;
protected:
CPingTimer(CPingEng& aParent);
void RunL();
private:
CPingEng& iParent;
};
class CPingSender : public CActive
{
friend class CPingEng;
protected:
CPingSender(CPingEng& aParent);
~CPingSender();
void RunL();
void DoCancel();
private:
CPingEng& iParent;
};
class CPingReceiver : public CActive
{
friend class CPingEng;
protected:
CPingReceiver(CPingEng& aParent);
~CPingReceiver();
void RunL();
void DoCancel();
private:
CPingEng& iParent;
};
class HPingHeader : public TInet6HeaderICMP_Echo
{
public:
static HPingHeader* NewL(TInt aSize = KIcmpHeaderSize, TUint aIPVersion = KAfInet);
~HPingHeader();
TBool VerifyRecvEcho(TInt aId);
TBool VerifyNonEcho(TInt aId);
void SetVersion(TUint aIPVersion);
void FormatSend(TUint aId, TUint aSeqNum);
TInt MaxLength();
TInt DataLength();
TPtr8* Grab();
TPtrC8 IcmpContents();
private:
void ConstructL(TInt aSize, TUint aIPVersion);
TBool SetHeader(TUint aOffset = 0);
HBufC8* iData;
TPtr8* iDataPtr;
TUint iIPVersion;
TInt iSize;
};
enum TPingEngPanic
{
ETimerPriorityGreaterThanSender, // 0
ESenderPrirityGreaterThanReceiver // 1
};
LOCAL_C void Panic(TPingEngPanic aPanic)
//
// Panic the user
//
{
User::Panic(_L("PingEng"), aPanic);
}
EXPORT_C TPingOptions::TPingOptions()
//
// Default ping options
//
{
iNumberOfPings=KDefaultPings;
iInterval = KDefaultInterval;
iWait = KDefaultWait;
iPingSize=KDefaultPingSize;
iResolveAddress=EFalse;
iPrompt=EFalse;
iConnIap=0;
iConnSnap=0;
iPreload=0;
iBacklog=KDefaultBacklog;
}
EXPORT_C CPingEng* CPingEng::NewL(MPingNotificationHandler& aUi)
//
// Create a new ping engine
//
{
CPingEng* p= new(ELeave) CPingEng(aUi);
CleanupStack::PushL(p);
p->ConstructL();
CleanupStack::Pop(p);
return p;
}
CPingEng::CPingEng(MPingNotificationHandler& aUi)
: iUi(aUi)
//
// Declare a name
//
{
__DECLARE_NAME(_S("CPingEng"));
}
EXPORT_C CPingEng::~CPingEng()
//
// Destroy wot ping created
{
iSocket.Close();
iResolver.Close();
iConnect.Close();
iSocketServ.Close();
delete iReceiver;
delete iSender;
delete iTimer;
delete iRecvData;
delete iSendData;
EmptyPingRecordQue();
}
void CPingEng::EmptyPingRecordQue()
{
while (!iQue.IsEmpty())
{
DeletePingRecord(iQue.First());
}
}
void CPingEng::DeletePingRecord(TPingRecord* aRecord)
{
iQue.Remove(*aRecord);
delete aRecord;
iNoInQue--;
}
void CPingEng::ConstructL()
//
// Construct and heap objects
//
{
iQue.SetOffset(_FOFF(TPingRecord, iLink));
iTimer = new (ELeave) CPingTimer(*this);
iTimer->ConstructL();
iSender = new (ELeave) CPingSender(*this);
iReceiver = new (ELeave) CPingReceiver(*this);
User::LeaveIfError(iSocketServ.Connect());
}
EXPORT_C void CPingEng::SetPriorities(TInt aTimerPriority, TInt aSenderPriority, TInt aReceiverPriority)
//
// Set various active object priorities
//
{
__ASSERT_ALWAYS(aTimerPriority < aSenderPriority, Panic(ETimerPriorityGreaterThanSender));
__ASSERT_ALWAYS(aSenderPriority < aReceiverPriority, Panic(ESenderPrirityGreaterThanReceiver));
iTimer->SetPriority(aTimerPriority);
iSender->SetPriority(aSenderPriority);
iReceiver->SetPriority(aReceiverPriority);
}
EXPORT_C void CPingEng::StartL(const TPingOptions& aOptions)
//
// Start a ping
//
{
// Reset All Variables
iNrTransmitted=0;
iNrReceived=0;
iNrDuplicates=0;
iMinTime=KMaxTInt;
iMaxTime=0;
iSumTime=0;
iId=User::TickCount()&KMaxTUint16;
iNameEntry().iName.SetLength(0);
iNameEntry().iFlags=0;
iIsLoopback = EFalse;
iOptions=aOptions;
if(iOptions.iDestname.Length()>0)
{
TInetAddr& addr = (TInetAddr&)iNameEntry().iAddr;
const TBool addressInputValid = addr.Input(iOptions.iDestname) == KErrNone;
if (addressInputValid)
{
iIsLoopback = addr.IsLoopback();
}
iConnect.Close();
// Make a connection only if it is required.
//
if (!iIsLoopback)
{
// This branch will unfortunately be taken if the call to TInetAddr.Input() fails.
User::LeaveIfError(iConnect.Open(iSocketServ, KConnectionTypeDefault));
if(iOptions.iConnSnap)
{
TConnSnapPref connPref(iOptions.iConnSnap);
User::LeaveIfError(iConnect.Start(connPref));
}
else
{
TCommDbConnPref commDbPref;
if(iOptions.iConnIap)
{
commDbPref.SetIapId(iOptions.iConnIap);
}
if(!iOptions.iPrompt)
{
commDbPref.SetDialogPreference(ECommDbDialogPrefDoNotPrompt);
}
else
{
commDbPref.SetDialogPreference(ECommDbDialogPrefPrompt);
}
User::LeaveIfError(iConnect.Start(commDbPref));
}
}
TInt err;
if (addressInputValid)
{
if(iOptions.iResolveAddress)
{
if((err=iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp, iConnect))==KErrNone)
{
iState=ELookingUp;
iResolver.GetByAddress(addr, iNameEntry, iSender->iStatus);
iSender->SetActive();
iTimer->After(KMaxSendTime);
}
else
{
DoError(err);
}
}
else
{
iSender->iStatus=KErrNone;
iState=ELookingUp;
SendCompleteL();
}
}
else
{
if((err=iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp, iConnect))==KErrNone)
{
iState=ELookingUp;
iResolver.GetByName(iOptions.iDestname, iNameEntry, iSender->iStatus);
iSender->SetActive();
iTimer->After(KMaxSendTime);
}
else
{
DoError(err);
}
}
}
else
{
DoError(KErrBadName);
}
}
EXPORT_C void CPingEng::CancelAndFinished()
//
// Cancel from the UI
//
{
if(iState!=EStopped || iTimer->IsActive())
{
Cancel();
iUi.Finished(iNameEntry(), iNrTransmitted, iNrReceived, iNrDuplicates, iMinTime, iMaxTime, iSumTime, KErrCancel);
}
}
EXPORT_C void CPingEng::Cancel()
//
// Cancel a ping in progress
//
{
iSender->Cancel();
iReceiver->Cancel();
iTimer->Cancel();
iSocket.Close();
iConnect.Close();
iResolver.Close();
EmptyPingRecordQue();
iState=EStopped;
}
void CPingEng::DoError(TInt aError)
//
// Generate an error from somewhere
//
{
Cancel();
iTimer->SetActive();
TRequestStatus* p = &iTimer->iStatus;
User::RequestComplete(p, aError);
}
void CPingEng::TimerComplete()
//
// Timer event completed
//
{
if(iTimer->iStatus==KErrNone)
{
if((iOptions.iNumberOfPings && iNrTransmitted>=iOptions.iNumberOfPings)
|| iSender->IsActive())
{
Cancel();
iUi.Finished(iNameEntry(), iNrTransmitted, iNrReceived, iNrDuplicates, iMinTime, iMaxTime, iSumTime, KErrTimedOut);
}
else
{
PurgeQue();
NextSend();
}
}
else
{
iUi.Finished(iNameEntry(), iNrTransmitted, iNrReceived, iNrDuplicates, iMinTime, iMaxTime, iSumTime, iTimer->iStatus.Int());
}
}
void CPingEng::SendCompleteL()
//
// A send operation has completed
//
{
if(iState==ELookingUp)
{
iResolver.Close();
}
if(iSender->iStatus!=KErrNone)
{
DoError(iSender->iStatus.Int());
return;
}
iTimer->Cancel();
if(iState==ELookingUp) // Intiate the ping process
{
delete iSendData;
iSendData = NULL;
delete iRecvData;
iRecvData = NULL;
TInt res=KErrNone;
// Set up socket and recieve/send packets depending on address type.
switch(iNameEntry().iAddr.Family())
{
case KAfInet:
{
iSendData = HPingHeader::NewL(iOptions.iPingSize);
iRecvData = HPingHeader::NewL(iOptions.iPingSize);
if(!iRecvData || !iSendData)
{
DoError(KErrNoMemory);
return;
}
if (iIsLoopback)
{
res=iSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInetIcmp);
}
else
{
res=iSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInetIcmp, iConnect);
}
break;
}
case KAfInet6:
{
iSendData = HPingHeader::NewL(iOptions.iPingSize, KAfInet6);
iRecvData = HPingHeader::NewL(iOptions.iPingSize, KAfInet6);
if(!iRecvData || !iSendData)
{
DoError(KErrNoMemory);
return;
}
if (iIsLoopback)
{
res=iSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInet6Icmp);
}
else
{
res=iSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInet6Icmp, iConnect);
}
break;
}
default:
DoError(KErrGeneral);
return;
}
if(res==KErrNone)
{
res=iSocket.SetOpt(KSORecvBuf, KSOLSocket, iRecvData->MaxLength());
}
if(res==KErrNone)
{
res=iSocket.SetOpt(KSOSendBuf, KSOLSocket, iSendData->MaxLength());
}
if(res!=KErrNone)
{
DoError(res);
return;
}
// Start the receiver
iSocket.RecvFrom(*(iRecvData->Grab()), iSrcAddr, 0, iReceiver->iStatus);
iReceiver->SetActive();
// Tell the UI
iUi.Pinging(iNameEntry(),iOptions.iPingSize);
iState=ESending;
NextSend();
}
else // Start appropriate timer
{
iUi.Sent();
if(!iOptions.iNumberOfPings || iNrTransmitted<iOptions.iNumberOfPings)
{
if(iOptions.iPreload || !iOptions.iInterval.Int())
{
if(iOptions.iPreload>0)
{
--iOptions.iPreload;
}
iTimer->iStatus=KErrNone;
TimerComplete();
}
else
{
iTimer->After(iOptions.iInterval);
}
}
else if(iNrReceived)
{
iTimer->After(iMaxTime>500000 ? (iMaxTime*5) : 1000000);
}
else
{
iTimer->After(iOptions.iWait);
}
}
}
void CPingEng::NextSend()
//
// Initiate the next send
//
{
TPingRecord* r = new TPingRecord(iNrTransmitted);
if(!r)
{
DoError(KErrNoMemory);
return;
}
iQue.AddLast(*r);
iNoInQue++;
if(iNoInQue > iOptions.iBacklog)
{
DeletePingRecord(iQue.First());
}
iSendData->FormatSend(iId, iNrTransmitted++);
iSocket.SendTo(*(iSendData->Grab()), iNameEntry().iAddr, iSendFlags, iSender->iStatus);
iSender->SetActive();
iTimer->After(KMaxSendTime);
}
void CPingEng::SendDoCancel()
//
// A send operation requires cancelling
//
{
if(iState==ELookingUp)
{
iResolver.Cancel();
}
else if(iState==ESending)
{
iSocket.CancelSend();
}
}
void CPingEng::RecvComplete()
//
// A recv operation has completed
//
{
if(iReceiver->iStatus==KErrNone)
{
if(iRecvData->VerifyRecvEcho(iId))
{
TSglQueIter<TPingRecord> iter(iQue);
TPingRecord* p=0;
while((p=iter++)!=0)
{
if(iRecvData->Sequence() == p->iSeqNr)
{
++iNrReceived;
TTime now;
now.UniversalTime();
TTimeIntervalMicroSeconds32 word = I64LOW(now.MicroSecondsFrom(p->iSendTime).Int64());
iMaxTime = word.Int()>iMaxTime ? word.Int() : iMaxTime;
iMinTime = word.Int()<iMinTime ? word.Int() : iMinTime;
iSumTime += word.Int();
iUi.Reply(iSrcAddr, iRecvData->DataLength(), p->iSeqNr, word);
DeletePingRecord(p);
if(iQue.IsEmpty() && iOptions.iNumberOfPings && iNrReceived>=iOptions.iNumberOfPings
&& iNrTransmitted>=iOptions.iNumberOfPings)
{
Cancel();
iUi.Finished(iNameEntry(), iNrTransmitted, iNrReceived, iNrDuplicates, iMinTime, iMaxTime, iSumTime, KErrNone);
return;
}
break;
}
}
if(p==0)
{
++iNrDuplicates;
}
}
else if(iRecvData->VerifyNonEcho(iId))
{
if(iNameEntry().iAddr.Family()==KAfInet)
{
iUi.Icmp4Message(iSrcAddr, iRecvData->Type(), iRecvData->Code(), iRecvData->IcmpContents());
}
else
{
iUi.Icmp6Message(iSrcAddr, iRecvData->Type(), iRecvData->Code());
}
}
iSocket.RecvFrom(*(iRecvData->Grab()), iSrcAddr, 0, iReceiver->iStatus);
iReceiver->SetActive();
}
else
{
DoError(iReceiver->iStatus.Int());
}
}
void CPingEng::PurgeQue()
//
// Purge the que of things that linger about for too long
//
{
TTime shortTimeAgo;
shortTimeAgo.UniversalTime();
shortTimeAgo -= TTimeIntervalMicroSeconds32(Max(KMaxTimeOnQue, iOptions.iWait.Int()));
TSglQueIter<TPingRecord> iter(iQue);
TPingRecord* p;
while((p=iter++)!=0)
{
if(p->iSendTime < shortTimeAgo)
{
DeletePingRecord(p);
}
else
{
break;
}
}
}
void CPingEng::RecvDoCancel()
//
// A send operation requires cancelling
//
{
iSocket.CancelRecv();
}
CPingTimer::CPingTimer(CPingEng& aParent)
//
// To time events
//
: CTimer(EPingTimerPriority), iParent(aParent)
{
CActiveScheduler::Add(this);
__DECLARE_NAME(_S("CPingTimer"));
}
void CPingTimer::RunL()
//
// Timer is complete
//
{
iParent.TimerComplete();
}
CPingSender::CPingSender(CPingEng& aParent)
//
// C'tor
//
: CActive(EPingSenderPriority), iParent(aParent)
{
CActiveScheduler::Add(this);
__DECLARE_NAME(_S("CPingSender"));
}
CPingSender::~CPingSender()
//
// D'tor cancels
//
{
Cancel();
}
void CPingSender::RunL()
//
// Upcall to parent
//
{
iParent.SendCompleteL();
}
void CPingSender::DoCancel()
//
// Get parent to cancel send
//
{
iParent.SendDoCancel();
}
CPingReceiver::CPingReceiver(CPingEng& aParent)
//
// C'tor
//
: CActive(EPingReceiverPriority), iParent(aParent)
{
CActiveScheduler::Add(this);
__DECLARE_NAME(_S("CPingReceiver"));
}
CPingReceiver::~CPingReceiver()
//
// D'tor cancels
//
{
Cancel();
}
void CPingReceiver::RunL()
//
// Upcall to parent
//
{
iParent.RecvComplete();
}
void CPingReceiver::DoCancel()
//
// Get parent to cancel send
//
{
iParent.RecvDoCancel();
}
TPingRecord::TPingRecord(TUint aSeqNr)
//
// Create new ping record
//
{
iSeqNr = aSeqNr&KMaxTUint16;
iSendTime.UniversalTime();
}
HPingHeader::~HPingHeader()
//
// D'tor deletes
//
{
delete iData;
delete iDataPtr;
}
HPingHeader* HPingHeader::NewL(TInt aSize, TUint aIPVersion)
//
// Create a new ping header
//
{
HPingHeader* h = new(ELeave) HPingHeader();
CleanupStack::PushL(h);
h->ConstructL(aSize, aIPVersion);
CleanupStack::Pop(h);
return h;
}
void HPingHeader::ConstructL(TInt aSize, TUint aIPVersion)
{
iData = HBufC8::NewL(aSize);
iDataPtr = new(ELeave) TPtr8(iData->Des());
iSize = aSize < KIcmpHeaderSize ? KIcmpHeaderSize : aSize;
iData->Des().FillZ();
SetVersion(aIPVersion);
}
void HPingHeader::SetVersion(TUint aIPVersion)
//
// To set IP version of packet
//
{
iIPVersion = aIPVersion;
}
TInt HPingHeader::MaxLength()
//
// To get packet data maximum length
//
{
return iData->Des().MaxLength();
}
TInt HPingHeader::DataLength()
//
// To get packet data length
//
{
return iData->Des().Length();
}
TPtrC8 HPingHeader::IcmpContents()
//
// To get the icmp contents from packet data
//
{
return iData->Des().Mid(4, iData->Length()-4);
}
TPtr8* HPingHeader::Grab()
//
// To get data far a send or receive operation
//
{
iDataPtr->Copy(iData->Des());
return iDataPtr;
}
void HPingHeader::FormatSend(TUint aId, TUint aSeqNum)
//
// Format an ICMP packet to send
//
{
TUint type;
TUint code;
TChecksum sum;
// Configure version
if(iIPVersion == KAfInet)
{
type = KIPv4PingTypeEchoRequest;
code = KIPv4PingCodeEcho;
}
else
{
type = KIPv6PingTypeEchoRequest;
code = KIPv6PingCodeEcho;
}
// Fill header
SetType(static_cast<TUint8>(type));
SetCode(static_cast<TUint8>(code));
SetIdentifier(static_cast<TUint16>(aId));
SetSequence(static_cast<TUint16>(aSeqNum));
// Zero checksum
SetChecksum(0);
// Copy the header to the data descriptor
iData->Des().Copy(reinterpret_cast<TUint8*>(this), KIcmpHeaderSize);
// Adjust the length for the appropriate ping size
iData->Des().SetLength(iSize);
// Fill ping data
if (iSize > KIcmpHeaderSize)
{
TInt i = 0;
TUint8 dataChar = 'a';
TInt dataLen = iSize - KIcmpHeaderSize;
TPtr8 dataPtr = iData->Des().MidTPtr(KIcmpHeaderSize, dataLen);
for (i = 0; i < dataLen; i++ )
{
dataPtr[i] = dataChar;
if (dataChar == 'z')
{
dataChar = 'a';
}
else
{
dataChar++;
}
}
}
// Compute checksum
sum.Add(reinterpret_cast<const TUint16*>(iData->Ptr()), iSize);
SetChecksum(sum.Sum());
}
TBool HPingHeader::SetHeader(TUint aOffset)
//
// Set the header from an Icmp reply
//
{
const TUint8* buffData;
// Check size
if(DataLength() < (TInt)KIcmpHeaderSize)
{
return EFalse;
}
buffData = iData->Des().Ptr();
if(!buffData)
{
return EFalse;
}
// Fill TInet6HeaderICMP_Echo from the buffer
for(int k=0;k<(TInt)KIcmpHeaderSize;k++)
{
i[k] = *(buffData + k + aOffset);
}
return ETrue;
}
TBool HPingHeader::VerifyRecvEcho(TInt aId)
//
// Verifiy header is valid echo reply
//
{
TBool ret = ETrue;
TUint typeCheck;
TUint codeCheck;
// Fill TInet6HeaderICMP_Echo from packet data
ret = SetHeader();
// Look at IP version
if(iIPVersion == KAfInet)
{
typeCheck = KIPv4PingTypeEchoReply;
codeCheck = KIPv4PingCodeEcho;
}
else
{
typeCheck = KIPv6PingTypeEchoReply;
codeCheck = KIPv6PingCodeEcho;
}
// Wrong packet type or code
if(ret && (Type() != typeCheck || Code() != codeCheck))
{
ret = EFalse;
}
// Wrong packet identifier
if(ret && Identifier() != aId)
{
ret = EFalse;
}
return ret;
}
TBool HPingHeader::VerifyNonEcho(TInt aId)
//
// Verify header which is not echo reply
//
{
// Fill TInet6HeaderICMP_Echo from packet data
TBool ret = SetHeader();
// Look at IP version
if(ret && iIPVersion == KAfInet) // IP4
{
switch(Type())
{
case KIPv4PingTypeUnreachable:
case KIPv4PingTypeSourceQuench:
case KIPv4PingTypeRedirect:
case KIPv4PingTypeTimeExceeded:
case KIPv4PingTypeBadParameter:
break;
default:
ret = EFalse;
}
if(ret && (DataLength() < (TInt)KIcmpHeaderSize))
{
ret = EFalse;
}
if(ret)
{
ret = SetHeader(KIcmpHeaderSize + KMinIpHeaderSize);
if(ret && (Type() != KIPv4PingTypeEchoRequest || Identifier() != aId))
{
ret = EFalse;
}
}
}
else
{ // IP6
switch(Type())
{
case KIPv6PingTypeUnreachable:
case KIPv6PingTypePacketTooBig:
case KIPv6PingTypeTimeExeeded:
case KIPv6PingTypeParamProblem:
break;
default:
ret = EFalse;
}
}
return ret;
}