From 157fec86d91f48964f4ccb70e416eccb9c465f41 Mon Sep 17 00:00:00 2001 From: Natale Patriciello Date: Fri, 16 Oct 2015 10:43:36 -0700 Subject: [PATCH] Added a GeneralTest class for TCP The class provides a general infrastructure for a TCP-related test, setting up the environment (two sockets connected through SimpleNetDevice over SimpleChannel) and provides the basic management of parameters (like segment size). --- src/internet/model/tcp-socket-base.h | 5 +- src/internet/test/tcp-general-test.cc | 884 ++++++++++++++++++++++++++ src/internet/test/tcp-general-test.h | 668 +++++++++++++++++++ src/internet/wscript | 1 + 4 files changed, 1557 insertions(+), 1 deletion(-) create mode 100644 src/internet/test/tcp-general-test.cc create mode 100644 src/internet/test/tcp-general-test.h diff --git a/src/internet/model/tcp-socket-base.h b/src/internet/model/tcp-socket-base.h index d39193ce2..dab67a37b 100644 --- a/src/internet/model/tcp-socket-base.h +++ b/src/internet/model/tcp-socket-base.h @@ -245,6 +245,9 @@ public: * \return the object TypeId */ static TypeId GetTypeId (void); + + friend class TcpGeneralTest; + /** * Create an unbound TCP socket */ @@ -559,7 +562,7 @@ protected: * * \param flags the packet's flags */ - void SendEmptyPacket (uint8_t flags); + virtual void SendEmptyPacket (uint8_t flags); /** * \brief Send reset and tear down this socket diff --git a/src/internet/test/tcp-general-test.cc b/src/internet/test/tcp-general-test.cc new file mode 100644 index 000000000..58aaf9e9d --- /dev/null +++ b/src/internet/test/tcp-general-test.cc @@ -0,0 +1,884 @@ +/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2015 Natale Patriciello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "ns3/test.h" +#include "ns3/node-container.h" +#include "ns3/tcp-socket-base.h" +#include "ns3/simple-net-device-helper.h" +#include "ns3/ipv4-address-helper.h" +#include "ns3/internet-stack-helper.h" +#include "ns3/log.h" +#include "tcp-general-test.h" + +namespace ns3 { + +NS_LOG_COMPONENT_DEFINE ("TcpGeneralTest"); + +TcpGeneralTest::TcpGeneralTest (const std::string &desc, + uint32_t pktSize, uint32_t pktCount, + const Time& pktInterval, + const Time& propagationDelay, const Time& startTime, + uint32_t initialSlowStartThresh, uint32_t initialCwnd, + uint32_t segmentSize, + TypeId congestionControl, + uint32_t mtu) + : TestCase (desc), + m_congControlTypeId (congestionControl), + m_propagationDelay (propagationDelay), + m_startTime (startTime), + m_mtu (mtu), + m_pktSize (pktSize), + m_pktCount (pktCount), + m_interPacketInterval (pktInterval), + m_initialSlowStartThresh (initialSlowStartThresh), + m_initialCwnd (initialCwnd), + m_segmentSize (segmentSize) +{ + NS_LOG_FUNCTION (this << desc); +} + +TcpGeneralTest::~TcpGeneralTest () +{ + NS_LOG_FUNCTION_NOARGS (); +} + +void +TcpGeneralTest::ReceivePacket (Ptr socket) +{ + NS_LOG_FUNCTION (this << socket); + Ptr packet; + Address from; + + while ((packet = socket->RecvFrom (from))) + { + if (packet->GetSize () == 0) + { //EOF + break; + } + } +} + +void +TcpGeneralTest::SendPacket (Ptr socket, uint32_t pktSize, + uint32_t pktCount, Time pktInterval ) +{ + NS_LOG_FUNCTION (this << " " << pktSize << " " << pktCount << " " << + pktInterval.GetSeconds ()); + if (pktCount > 0) + { + socket->Send (Create (pktSize)); + Simulator::Schedule (pktInterval, &TcpGeneralTest::SendPacket, this, + socket, pktSize, pktCount - 1, pktInterval); + } + else + { + socket->Close (); + } +} + +void +TcpGeneralTest::DoTeardown (void) +{ + FinalChecks (); + + Simulator::Destroy (); + NS_LOG_INFO ("Done."); +} + +void +TcpGeneralTest::DoRun (void) +{ + NS_LOG_INFO ("Create nodes."); + NodeContainer nodes; + nodes.Create (2); + + InternetStackHelper internet; + internet.Install (nodes); + + Packet::EnablePrinting (); + + Ptr channel = CreateChannel (); + + SimpleNetDeviceHelper helperChannel; + helperChannel.SetNetDevicePointToPointMode (true); + + NetDeviceContainer net = helperChannel.Install (nodes, channel); + + Ptr receiverEM = CreateReceiverErrorModel (); + Ptr senderEM = CreateSenderErrorModel (); + + Ptr senderDev = DynamicCast (net.Get (0)); + Ptr receiverDev = DynamicCast (net.Get (1)); + + senderDev->SetMtu (m_mtu); + senderDev->GetQueue ()->TraceConnect ("Drop", "SENDER", + MakeCallback (&TcpGeneralTest::QueueDropCb, this)); + senderDev->TraceConnect ("PhyRxDrop", "sender", + MakeCallback (&TcpGeneralTest::PhyDropCb, this)); + + receiverDev->SetMtu (m_mtu); + receiverDev->GetQueue ()->TraceConnect ("Drop", "RECEIVER", + MakeCallback (&TcpGeneralTest::QueueDropCb, this)); + receiverDev->TraceConnect ("PhyRxDrop", "RECEIVER", + MakeCallback (&TcpGeneralTest::PhyDropCb, this)); + + senderDev->SetReceiveErrorModel (senderEM); + receiverDev->SetReceiveErrorModel (receiverEM); + + Ipv4AddressHelper ipv4; + ipv4.SetBase ("10.1.1.0", "255.255.255.0"); + Ipv4InterfaceContainer i = ipv4.Assign (net); + Ipv4Address serverAddress = i.GetAddress (1); + //Ipv4Address clientAddress = i.GetAddress (0); + + NS_LOG_INFO ("Create sockets."); + //Receiver socket on n1 + m_receiverSocket = CreateReceiverSocket (nodes.Get (1)); + + m_receiverSocket->SetRecvCallback (MakeCallback (&TcpGeneralTest::ReceivePacket, this)); + m_receiverSocket->SetAcceptCallback ( + MakeNullCallback, const Address &> (), + MakeCallback (&TcpGeneralTest::HandleAccept, this)); + m_receiverSocket->SetCloseCallbacks (MakeCallback (&TcpGeneralTest::NormalCloseCb, this), + MakeCallback (&TcpGeneralTest::ErrorCloseCb, this)); + m_receiverSocket->SetRcvAckCb (MakeCallback (&TcpGeneralTest::RcvAckCb, this)); + m_receiverSocket->SetProcessedAckCb (MakeCallback (&TcpGeneralTest::ProcessedAckCb, this)); + m_receiverSocket->SetRetransmitCb (MakeCallback (&TcpGeneralTest::RtoExpiredCb, this)); + m_receiverSocket->SetForkCb (MakeCallback (&TcpGeneralTest::ForkCb, this)); + m_receiverSocket->TraceConnectWithoutContext ("Tx", + MakeCallback (&TcpGeneralTest::TxPacketCb, this)); + m_receiverSocket->TraceConnectWithoutContext ("Rx", + MakeCallback (&TcpGeneralTest::RxPacketCb, this)); + + InetSocketAddress local = InetSocketAddress (Ipv4Address::GetAny (), 4477); + m_receiverSocket->Bind (local); + m_receiverSocket->Listen (); + m_receiverSocket->ShutdownSend (); + + m_senderSocket = CreateSenderSocket (nodes.Get (0)); + m_senderSocket->SetCloseCallbacks (MakeCallback (&TcpGeneralTest::NormalCloseCb, this), + MakeCallback (&TcpGeneralTest::ErrorCloseCb, this)); + m_senderSocket->SetRcvAckCb (MakeCallback (&TcpGeneralTest::RcvAckCb, this)); + m_senderSocket->SetProcessedAckCb (MakeCallback (&TcpGeneralTest::ProcessedAckCb, this)); + m_senderSocket->SetRetransmitCb (MakeCallback (&TcpGeneralTest::RtoExpiredCb, this)); + m_senderSocket->TraceConnectWithoutContext ("CongestionWindow", + MakeCallback (&TcpGeneralTest::CWndTrace, this)); + m_senderSocket->TraceConnectWithoutContext ("AckState", + MakeCallback (&TcpGeneralTest::AckStateTrace, this)); + m_senderSocket->TraceConnectWithoutContext ("Tx", + MakeCallback (&TcpGeneralTest::TxPacketCb, this)); + m_senderSocket->TraceConnectWithoutContext ("Rx", + MakeCallback (&TcpGeneralTest::RxPacketCb, this)); + + InetSocketAddress remoteAddr = InetSocketAddress (serverAddress, 4477); + + m_senderSocket->Connect (remoteAddr); + + Simulator::ScheduleWithContext (nodes.Get (0)->GetId (), + m_startTime, &TcpGeneralTest::SendPacket, this, + m_senderSocket, m_pktSize, m_pktCount, m_interPacketInterval); + + NS_LOG_INFO ("Run Simulation."); + Simulator::Run (); +} + +void +TcpGeneralTest::HandleAccept (Ptr socket, const Address& from) +{ + (void) from; + socket->SetRecvCallback (MakeCallback (&TcpGeneralTest::ReceivePacket, this)); + socket->SetCloseCallbacks (MakeCallback (&TcpGeneralTest::NormalCloseCb, this), + MakeCallback (&TcpGeneralTest::ErrorCloseCb, this)); + +} + +Ptr +TcpGeneralTest::CreateChannel () +{ + Ptr ch = CreateObject (); + + ch->SetAttribute ("Delay", TimeValue (m_propagationDelay)); + + return ch; +} + +Ptr +TcpGeneralTest::CreateSocket (Ptr node, TypeId socketType, + TypeId congControl) +{ + ObjectFactory rttFactory; + ObjectFactory congestionAlgorithmFactory; + ObjectFactory socketFactory; + + rttFactory.SetTypeId (RttMeanDeviation::GetTypeId ()); + congestionAlgorithmFactory.SetTypeId (congControl); + socketFactory.SetTypeId (socketType); + + Ptr rtt = rttFactory.Create (); + Ptr socket = socketFactory.Create (); + Ptr algo = congestionAlgorithmFactory.Create (); + + socket->SetNode (node); + socket->SetTcp (node->GetObject ()); + socket->SetRtt (rtt); + socket->SetCongestionControlAlgorithm (algo); + + socket->SetAttribute ("InitialSlowStartThreshold", UintegerValue (m_initialSlowStartThresh)); + socket->SetAttribute ("SegmentSize", UintegerValue (m_segmentSize)); + socket->SetAttribute ("InitialCwnd", UintegerValue (m_initialCwnd)); + + return socket; +} + +Ptr +TcpGeneralTest::CreateSenderErrorModel () +{ + return 0; +} + +Ptr +TcpGeneralTest::CreateReceiverErrorModel () +{ + return 0; +} + +Ptr +TcpGeneralTest::CreateSenderSocket (Ptr node) +{ + return CreateSocket (node, TcpSocketMsgBase::GetTypeId (), m_congControlTypeId); +} + +Ptr +TcpGeneralTest::CreateReceiverSocket (Ptr node) +{ + return CreateSocket (node, TcpSocketMsgBase::GetTypeId (), m_congControlTypeId); +} + +void +TcpGeneralTest::QueueDropCb ( std::string context, Ptr p) +{ + if (context.compare ("SENDER") == 0) + { + QueueDrop (SENDER); + } + else if (context.compare ("RECEIVER") == 0) + { + QueueDrop (RECEIVER); + } + else + { + NS_FATAL_ERROR ("Packet dropped in a queue, but queue not recognized"); + } +} + +void +TcpGeneralTest::PhyDropCb (std::string context, Ptr p) +{ + if (context.compare ("SENDER") == 0) + { + PhyDrop (SENDER); + } + else if (context.compare ("RECEIVER") == 0) + { + PhyDrop (RECEIVER); + } + else + { + NS_FATAL_ERROR ("Packet dropped in a queue, but queue not recognized"); + } +} + +void +TcpGeneralTest::NormalCloseCb (Ptr socket) +{ + if (socket->GetNode () == m_receiverSocket->GetNode ()) + { + NormalClose (RECEIVER); + } + else if (socket->GetNode () == m_senderSocket->GetNode ()) + { + NormalClose (SENDER); + } + else + { + NS_FATAL_ERROR ("Closed socket, but not recognized"); + } +} + +void +TcpGeneralTest::RtoExpiredCb (const Ptr tcb, + const Ptr tcp) +{ + if (tcp->GetNode () == m_receiverSocket->GetNode ()) + { + RTOExpired (tcb, RECEIVER); + } + else if (tcp->GetNode () == m_senderSocket->GetNode ()) + { + RTOExpired (tcb, SENDER); + } + else + { + NS_FATAL_ERROR ("Closed socket, but not recognized"); + } +} + +void +TcpGeneralTest::ErrorCloseCb (Ptr socket) +{ + if (socket->GetNode () == m_receiverSocket->GetNode ()) + { + ErrorClose (RECEIVER); + } + else if (socket->GetNode () == m_senderSocket->GetNode ()) + { + ErrorClose (SENDER); + } + else + { + NS_FATAL_ERROR ("Closed socket, but not recognized"); + } +} + +void +TcpGeneralTest::RcvAckCb (const Ptr p, const TcpHeader& h, + const Ptr tcp) +{ + if (tcp->GetNode () == m_receiverSocket->GetNode ()) + { + RcvAck (tcp->m_tcb, h, RECEIVER); + } + else if (tcp->GetNode () == m_senderSocket->GetNode ()) + { + RcvAck (tcp->m_tcb, h, SENDER); + } + else + { + NS_FATAL_ERROR ("Received ACK but socket not recognized"); + } +} + +void +TcpGeneralTest::TxPacketCb (const Ptr p, + const TcpHeader &h, const Ptr tcp) +{ + if (tcp->GetNode () == m_receiverSocket->GetNode ()) + { + Tx (p, h, RECEIVER); + } + else if (tcp->GetNode () == m_senderSocket->GetNode ()) + { + Tx (p, h, SENDER); + } + else + { + NS_FATAL_ERROR ("Received ACK but socket not recognized"); + } +} + +void +TcpGeneralTest::RxPacketCb (const Ptr p, const TcpHeader &h, + const Ptr tcp) +{ + if (tcp->GetNode () == m_receiverSocket->GetNode ()) + { + Rx (p, h, RECEIVER); + } + else if (tcp->GetNode () == m_senderSocket->GetNode ()) + { + Rx (p, h, SENDER); + } + else + { + NS_FATAL_ERROR ("Received ACK but socket not recognized"); + } +} + +void +TcpGeneralTest::ProcessedAckCb (Ptr p, const TcpHeader& h, + Ptr tcp) +{ + if (tcp->GetNode () == m_receiverSocket->GetNode ()) + { + ProcessedAck (tcp->m_tcb, h, RECEIVER); + } + else if (tcp->GetNode () == m_senderSocket->GetNode ()) + { + ProcessedAck (tcp->m_tcb, h, SENDER); + } + else + { + NS_FATAL_ERROR ("Received ACK but socket not recognized"); + } +} + +void +TcpGeneralTest::ForkCb (Ptr tcp) +{ + NS_LOG_FUNCTION (this << tcp); + + m_receiverSocket = tcp; +} + +uint32_t +TcpGeneralTest::GetReTxThreshold (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_retxThresh; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_retxThresh; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +uint32_t +TcpGeneralTest::GetDupAckCount (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_dupAckCount; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_dupAckCount; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +uint32_t +TcpGeneralTest::GetDelAckCount (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_delAckMaxCount; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_delAckMaxCount; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +Time +TcpGeneralTest::GetDelAckTimeout(SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->GetDelAckTimeout (); + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->GetDelAckTimeout (); + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +uint32_t +TcpGeneralTest::GetSegSize (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->GetSegSize (); + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->GetSegSize (); + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +Time +TcpGeneralTest::GetRto (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_rto.Get (); + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_rto.Get (); + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +Time +TcpGeneralTest::GetMinRto (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_minRto; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_minRto; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +Time +TcpGeneralTest::GetConnTimeout (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_cnTimeout; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_cnTimeout; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +Ptr +TcpGeneralTest::GetRttEstimator (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_rtt; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_rtt; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +Time +TcpGeneralTest::GetClockGranularity (SocketWho who) +{ + if (who == SENDER) + { + return DynamicCast (m_senderSocket)->m_clockGranularity; + } + else if (who == RECEIVER) + { + return DynamicCast (m_receiverSocket)->m_clockGranularity; + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +void +TcpGeneralTest::SetRcvBufSize (SocketWho who, uint32_t size) +{ + if (who == SENDER) + { + m_senderSocket->SetRcvBufSize (size); + } + else if (who == RECEIVER) + { + m_receiverSocket->SetRcvBufSize (size); + } + else + { + NS_FATAL_ERROR ("Not defined"); + } +} + +NS_OBJECT_ENSURE_REGISTERED (TcpSocketMsgBase); + +TypeId +TcpSocketMsgBase::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::TcpSocketMsgBase") + .SetParent () + .SetGroupName ("Internet") + .AddConstructor () + ; + return tid; +} + +Ptr +TcpSocketMsgBase::Fork (void) +{ + return CopyObject (this); +} + +void +TcpSocketMsgBase::SetRcvAckCb (AckManagementCallback cb) +{ + NS_ASSERT (!cb.IsNull ()); + m_rcvAckCb = cb; +} + +void +TcpSocketMsgBase::SetProcessedAckCb (AckManagementCallback cb) +{ + NS_ASSERT (!cb.IsNull ()); + m_processedAckCb = cb; +} + +void +TcpSocketMsgBase::SetRetransmitCb (RetrCallback cb) +{ + NS_ASSERT (!cb.IsNull ()); + m_retrCallback = cb; +} + +void +TcpSocketMsgBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) +{ + NS_ASSERT (!(m_rcvAckCb.IsNull () || m_processedAckCb.IsNull ())); + m_rcvAckCb (packet, tcpHeader, this); + + TcpSocketBase::ReceivedAck (packet, tcpHeader); + + m_processedAckCb (packet, tcpHeader, this); +} + +void +TcpSocketMsgBase::Retransmit () +{ + TcpSocketBase::Retransmit (); + + m_retrCallback (m_tcb, this); +} + +void +TcpSocketMsgBase::SetForkCb (Callback > cb) +{ + NS_ASSERT (!cb.IsNull ()); + m_forkCb = cb; +} + +void +TcpSocketMsgBase::CompleteFork (Ptr p, const TcpHeader &tcpHeader, + const Address &fromAddress, const Address &toAddress) +{ + TcpSocketBase::CompleteFork (p, tcpHeader, fromAddress, toAddress); + + if (!m_forkCb.IsNull ()) + { + m_forkCb (this); + } +} + +NS_OBJECT_ENSURE_REGISTERED (TcpSocketSmallAcks); + +TypeId +TcpSocketSmallAcks::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::TcpSocketSmallAcks") + .SetParent () + .SetGroupName ("Internet") + .AddConstructor () + ; + return tid; +} + +/** + * \brief Send empty packet, copied/pasted from TcpSocketBase + * + * The rationale for copying/pasting is that we need to edit a little the + * code inside. Since there isn't a well-defined division of duties, + * we are forced to do this. + */ +void +TcpSocketSmallAcks::SendEmptyPacket (uint8_t flags) +{ + Ptr p = Create (); + TcpHeader header; + SequenceNumber32 s = m_nextTxSequence; + + /* + * Add tags for each socket option. + * Note that currently the socket adds both IPv4 tag and IPv6 tag + * if both options are set. Once the packet got to layer three, only + * the corresponding tags will be read. + */ + if (IsManualIpTos ()) + { + SocketIpTosTag ipTosTag; + ipTosTag.SetTos (GetIpTos ()); + p->AddPacketTag (ipTosTag); + } + + if (IsManualIpv6Tclass ()) + { + SocketIpv6TclassTag ipTclassTag; + ipTclassTag.SetTclass (GetIpv6Tclass ()); + p->AddPacketTag (ipTclassTag); + } + + if (IsManualIpTtl ()) + { + SocketIpTtlTag ipTtlTag; + ipTtlTag.SetTtl (GetIpTtl ()); + p->AddPacketTag (ipTtlTag); + } + + if (IsManualIpv6HopLimit ()) + { + SocketIpv6HopLimitTag ipHopLimitTag; + ipHopLimitTag.SetHopLimit (GetIpv6HopLimit ()); + p->AddPacketTag (ipHopLimitTag); + } + + if (m_endPoint == 0 && m_endPoint6 == 0) + { + NS_LOG_WARN ("Failed to send empty packet due to null endpoint"); + return; + } + if (flags & TcpHeader::FIN) + { + flags |= TcpHeader::ACK; + } + else if (m_state == FIN_WAIT_1 || m_state == LAST_ACK || m_state == CLOSING) + { + ++s; + } + + bool hasSyn = flags & TcpHeader::SYN; + bool hasFin = flags & TcpHeader::FIN; + bool isAck = flags == TcpHeader::ACK; + + header.SetFlags (flags); + header.SetSequenceNumber (s); + + // Actual division in small acks. + if (hasSyn || hasFin) + { + header.SetAckNumber (m_rxBuffer->NextRxSequence ()); + } + else + { + SequenceNumber32 ackSeq; + + ackSeq = m_lastAckedSeq + m_bytesToAck; + + if (m_bytesLeftToBeAcked == 0 && m_rxBuffer->NextRxSequence () > m_lastAckedSeq) + { + m_bytesLeftToBeAcked = m_rxBuffer->NextRxSequence ().GetValue () - 1 - m_bytesToAck; + } + else if (m_bytesLeftToBeAcked > 0 && m_rxBuffer->NextRxSequence () > m_lastAckedSeq) + { + m_bytesLeftToBeAcked -= m_bytesToAck; + } + + NS_LOG_LOGIC ("Acking up to " << ackSeq << " remaining bytes: " << m_bytesLeftToBeAcked); + + header.SetAckNumber (ackSeq); + m_lastAckedSeq = ackSeq; + } + + // end of division in small acks + + if (m_endPoint != 0) + { + header.SetSourcePort (m_endPoint->GetLocalPort ()); + header.SetDestinationPort (m_endPoint->GetPeerPort ()); + } + else + { + header.SetSourcePort (m_endPoint6->GetLocalPort ()); + header.SetDestinationPort (m_endPoint6->GetPeerPort ()); + } + AddOptions (header); + header.SetWindowSize (AdvertisedWindowSize ()); + + // RFC 6298, clause 2.4 + m_rto = Max (m_rtt->GetEstimate () + Max (m_clockGranularity, m_rtt->GetVariation () * 4), m_minRto); + + if (hasSyn) + { + if (m_synCount == 0) + { // No more connection retries, give up + NS_LOG_LOGIC ("Connection failed."); + m_rtt->Reset (); //According to recommendation -> RFC 6298 + CloseAndNotify (); + return; + } + else + { // Exponential backoff of connection time out + int backoffCount = 0x1 << (m_synRetries - m_synCount); + m_rto = m_cnTimeout * backoffCount; + m_synCount--; + } + } + if (m_endPoint != 0) + { + m_tcp->SendPacket (p, header, m_endPoint->GetLocalAddress (), + m_endPoint->GetPeerAddress (), m_boundnetdevice); + } + else + { + m_tcp->SendPacket (p, header, m_endPoint6->GetLocalAddress (), + m_endPoint6->GetPeerAddress (), m_boundnetdevice); + } + + m_txTrace (p, header, this); + + if (flags & TcpHeader::ACK) + { // If sending an ACK, cancel the delay ACK as well + m_delAckEvent.Cancel (); + m_delAckCount = 0; + } + if (m_retxEvent.IsExpired () && (hasSyn || hasFin) && !isAck ) + { // Retransmit SYN / SYN+ACK / FIN / FIN+ACK to guard against lost + NS_LOG_LOGIC ("Schedule retransmission timeout at time " + << Simulator::Now ().GetSeconds () << " to expire at time " + << (Simulator::Now () + m_rto.Get ()).GetSeconds ()); + m_retxEvent = Simulator::Schedule (m_rto, &TcpSocketSmallAcks::SendEmptyPacket, this, flags); + } + + // send another ACK if bytes remain + if (m_bytesLeftToBeAcked > 0 && m_rxBuffer->NextRxSequence () > m_lastAckedSeq) + { + SendEmptyPacket (flags); + } +} + +Ptr +TcpSocketSmallAcks::Fork (void) +{ + return CopyObject (this); +} + +} // namespace ns3 diff --git a/src/internet/test/tcp-general-test.h b/src/internet/test/tcp-general-test.h new file mode 100644 index 000000000..606a87c5b --- /dev/null +++ b/src/internet/test/tcp-general-test.h @@ -0,0 +1,668 @@ +/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2015 Natale Patriciello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef TCPGENERALTEST_H +#define TCPGENERALTEST_H + +#include "ns3/simple-net-device.h" +#include "ns3/error-model.h" +#include "ns3/tcp-socket-base.h" +#include "ns3/test.h" + +namespace ns3 { + +/** + * \brief Class for inserting callbacks special points of the flow of TCP sockets + * + * This subclass is born to extend TcpSocketBase, inserting callbacks in certain + * points of the flow, to be used in testing to check certain values or flow + * directions. + * + * There isn't the necessity to fill TcpSocketBase of TracedCallbacks; the rationale + * is to maintain the base class as clean as possible. + * + * To be fair with testing, this class should NOT modify the behavior of TcpSocketBase. + * + * \see SetRcvAckCb + * \see SetProcessedAckCb + * \see SetRetransmitCb + */ +class TcpSocketMsgBase : public TcpSocketBase +{ +public: + static TypeId GetTypeId (void); + + TcpSocketMsgBase () : TcpSocketBase () + { + } + + TcpSocketMsgBase (const TcpSocketMsgBase &other) : TcpSocketBase (other) + { + m_rcvAckCb = other.m_rcvAckCb; + m_processedAckCb = other.m_processedAckCb; + m_retrCallback = other.m_retrCallback; + m_forkCb = other.m_forkCb; + } + + typedef Callback, const TcpHeader&, + Ptr > AckManagementCallback; + typedef Callback, + Ptr > RetrCallback; + + /** + * \brief Set the callback invoked when an ACK is received (at the beginning + * of the processing) + * + * \param cb callback + */ + void SetRcvAckCb (AckManagementCallback cb); + + /** + * \brief Set the callback invoked when an ACK is received and processed + * (at the end of the processing) + * + * \param cb callback + */ + void SetProcessedAckCb (AckManagementCallback cb); + + /** + * \brief Set the callback invoked after the processing of a retransmit timeout + * + * \param cb callback + */ + void SetRetransmitCb (RetrCallback cb); + + /** + * \brief Set the callback invoked after the forking + * \param cb callback + */ + void SetForkCb (Callback > cb); + +protected: + virtual void ReceivedAck (Ptr packet, const TcpHeader& tcpHeader); + virtual void Retransmit (void); + virtual Ptr Fork (void); + virtual void CompleteFork (Ptr p, const TcpHeader& tcpHeader, + const Address& fromAddress, const Address& toAddress); + +private: + AckManagementCallback m_rcvAckCb; + AckManagementCallback m_processedAckCb; + RetrCallback m_retrCallback; + Callback > m_forkCb; +}; + + +/** + * \brief A TCP socket which sends ACKs smaller than the segment received. + * + * Usually, a TCP socket which receives the sequence number "x" replies with + * an ACK to "x+1". What happen if a malicious socket sends smaller ACKs + * (e.g. two ACKs, one for "x/2", and the other for "x+1") ? A TCP implementation + * should avoid to artificially increase the congestion window, thinking of + * having ACKed 2 segments instead of 1. + * + * Set the number of bytes that should be acked in each ACK packet with + * SetBytesToAck. + * + * \see TcpSlowStartAttackerTest + */ +class TcpSocketSmallAcks : public TcpSocketMsgBase +{ +public: + static TypeId GetTypeId (void); + + TcpSocketSmallAcks () + : TcpSocketMsgBase (), + m_bytesToAck (125), + m_bytesLeftToBeAcked (0), + m_lastAckedSeq (1) + { + } + + TcpSocketSmallAcks (const TcpSocketSmallAcks &other) : TcpSocketMsgBase (other), + m_bytesToAck (other.m_bytesToAck), + m_bytesLeftToBeAcked (other.m_bytesLeftToBeAcked), + m_lastAckedSeq (other.m_lastAckedSeq) + { + } + + void SetBytesToAck (uint32_t bytes) + { + m_bytesToAck = bytes; + } + +protected: + virtual void SendEmptyPacket (uint8_t flags); + Ptr Fork (void); + + uint32_t m_bytesToAck; + uint32_t m_bytesLeftToBeAcked; + SequenceNumber32 m_lastAckedSeq; +}; + +/** +* \brief General infrastructure for TCP testing +* +* The class provides a simple setup for a connection testing. Implement +* or modify the virtual methods in order to install a specified +* channel, a specified socket and a specified error model on this simulation. +* Default values are a null error model, and as a channel a SimpleChannel with +* the propagation delay set through the constructor. +* +* Check DoRun documentation for more information on the environment setup. +* +* Apart from setting up the environment for testing, subclassing permits also +* to track and check what is happening inside the two connected sockets. Thanks +* to TcpSocketMsgBase, there are many information provided to children: +* +* - Tracing of states inside the state machines (TCP and ACK ones, through +* functions AckStateTrace and TcpStateTrace) +* - cWnd tracing (through CWndTrace) +* - Socket closing: error state, or normal state (NormalClose and ErrorClose) +* - Packet drop, inside queue or over the link (QueueDrop, PhyDrop) +* - Ack received (RcvAck) and Ack processed (ProcessedAck). The first is used to +* signal that an ACK has been received; after the processing of it, the Second +* is called +* - A packet is transmitted to IP layer or received from IP layer (Tx and Rx) +* - The RTO expires (RTOExpired) +* +* The default version of such methods is empty; implement their behavior differently, +* based on what you want to test. Default is empty to avoid the need to implement +* useless pure virtual function. +* +* If you need values from TcpSocketBase, thanks to the friend relationship between +* this class and TcpSocketBase itself you can get it. Remember that friendship +* isn't passed by inheritance, so the way to go is to write a Getters (like +* GetSegSize) and call it in the subclass. +* +* \see DoRun +* \see TcpSocketMsgBase +*/ +class TcpGeneralTest : public TestCase +{ +public: + /** + * \brief TcpGeneralTest constructor + * + * \param pktSize application packet size + * \param pktCount count of application packet to generate + * \param pktInterval (application) interval between each generated packet + * \param propagationDelay propagation delay of the channel + * \param startTime time of the first application-generated packet + * \param mtu MTU of the environment + * \param desc description of the test + */ + TcpGeneralTest (const std::string &desc, + uint32_t pktSize = 500, + uint32_t pktCount = 10, + const Time& pktInterval = Seconds (0.01), + const Time& propagationDelay = Seconds (0.5), + const Time& startTime = Seconds (10), + uint32_t initialSlowStartThresh = 0xffff, + uint32_t initialCwnd = 1, + uint32_t segmentSize = 500, + TypeId congestionControl = TcpNewReno::GetTypeId (), + uint32_t mtu = 1500); + ~TcpGeneralTest (); + + /** + * \brief Used as parameter of methods, specifies on what node + * the caller is interested (e.g. GetSegSize). + */ + enum SocketWho + { + SENDER, //!< Sender node + RECEIVER //!< Receiver node + }; + +protected: + /** + * \brief Create and return the channel installed between the two socket + * + * \return A SimpleChannel subclass + */ + virtual Ptr CreateChannel (); + + /** + * \brief Create and return the error model to install in the sender node + * + * \return sender error model + */ + virtual Ptr CreateSenderErrorModel (); + + /** + * \brief Create and return the error model to install in the receiver node + * + * \return receiver error model + */ + virtual Ptr CreateReceiverErrorModel (); + + /** + * \brief Create and install the socket to install on the receiver + * \param node receiver node pointer + * \return the socket to be installed in the receiver + */ + virtual Ptr CreateReceiverSocket (Ptr node); + + /** + * \brief Create and install the socket to install on the sender + * \param node sender node pointer + * \return the socket to be installed in the sender + */ + virtual Ptr CreateSenderSocket (Ptr node); + + /** + * \brief Create a socket + * + * \param node associated node + * \param congControl congestion control + * \return a pointer to the newer created socket + */ + virtual Ptr CreateSocket (Ptr node, TypeId socketType, + TypeId congControl); + + /** + * \brief Get the pointer to a previously created sender socket + * \return ptr to sender socket or 0 + */ + Ptr GetSenderSocket () + { + return m_senderSocket; + } + + /** + * \brief Get the pointer to a previously created receiver socket + * \return ptr to receiver socket or 0 + */ + Ptr GetReceiverSocket () + { + return m_receiverSocket; + } + + /** + * \brief Execute the tcp test + * + * As environment, two socket are connected through a SimpleChannel. Each device + * has an MTU of 1500 bytes, and the application starts to send packet at + * 10s of simulated time, through SendPacket. + */ + virtual void DoRun (void); + + /** + * \brief Teardown the TCP test + */ + virtual void DoTeardown (void); + + /** + * \brief Packet received + * + * The method processes the packet (application-layer) + * \param socket socket which has received the packet + */ + virtual void ReceivePacket (Ptr socket); + + /** + * \brief Send packets to other endpoint + * + * \param socket Socket + * \param pktSize size of the packet + * \param pktCount number of packets to send + * \param pktInterval interval between packet (application-level) + */ + void SendPacket (Ptr socket, uint32_t pktSize, + uint32_t pktCount, Time pktInterval); + + /** + * \brief Get the segment size of the node specified + * + * \param who node to get the parameter from + * + * \return segment size of the specified node + */ + uint32_t GetSegSize (SocketWho who); + + /** + * \brief Get the retransmission threshold + * \param who node to get the parameter from + * \return retransmission threshold + */ + uint32_t GetReTxThreshold (SocketWho who); + + /** + * \brief Get the number of dupack received + * \param who node to get the parameter from + * \return number of dupack + */ + uint32_t GetDupAckCount (SocketWho who); + + /** + * \brief Get the number of delayed ack (if present) + * \param who node to get the parameter from + * \return number of ack we will wait before sending an ACK + */ + uint32_t GetDelAckCount (SocketWho who); + + /** + * \brief Get the timeout of delayed ack (if present) + * \param who node to get the parameter from + * \return time we will wait before sending an ACK + */ + Time GetDelAckTimeout (SocketWho who); + + /** + * \brief Get the retransmission time + * + * \param who node to get the parameter from + * \return calculated RTO time + */ + Time GetRto (SocketWho who); + + /** + * \brief Get the minimun RTO attribute + * + * \param who node to get the parameter from + * \return minimum RTO time + */ + Time GetMinRto (SocketWho who); + + /** + * \brief Get the retransmission time for the SYN segments + * + * \param who node to get the parameter from + * \return SYN segments RTO time + */ + Time GetConnTimeout (SocketWho who); + + /** + * \brief Get the Rtt estimator of the socket + * + * \param who node to get the parameter from + * \return Rtt estimator + */ + Ptr GetRttEstimator (SocketWho who); + + /** + * \brief Get the clock granularity attribute + * + * \param who node to get the parameter from + * \return clock granularity + */ + Time GetClockGranularity (SocketWho who); + + /** + * \brief Forcefully set a defined size for rx buffer + * + * \param who socket to force + * \param size size of the rx buffer + */ + void SetRcvBufSize (SocketWho who, uint32_t size); + + /** + * \brief State on Ack state machine changes + * \param oldValue old value + * \param newValue new value + */ + virtual void AckStateTrace (const TcpSocketState::TcpAckState_t oldValue, + const TcpSocketState::TcpAckState_t newValue) + { + } + + /** + * \brief Congestion window changes + * + * \param oldValue old value + * \param newValue new value + */ + virtual void CWndTrace (uint32_t oldValue, uint32_t newValue) + { + } + + /** + * \brief Socket closed normally + * \param who the socket closed (SENDER or RECEIVER) + */ + virtual void NormalClose (SocketWho who) + { + } + + /** + * \brief Socket closed with an error + * + * \param who the socket closed (SENDER or RECEIVER) + */ + virtual void ErrorClose (SocketWho who) + { + /** \todo indicate the error */ + } + + /** + * \brief Drop on the queue + * \param who where the drop occurred (SENDER or RECEIVER) + */ + virtual void QueueDrop (SocketWho who) + { + } + + /** + * \brief Link drop + * \param who where the drop occurred (SENDER or RECEIVER) + */ + virtual void PhyDrop (SocketWho who) + { + } + + /** + * \brief Received ack + * + * Invoked when an ACK is received (no processing is done yet) + * + * \param tcb Transmission Control Block + * \param h the header of segment + * \param who the socket which has received the ACK (SENDER or RECEIVER) + */ + virtual void RcvAck (const Ptr tcb, + const TcpHeader& h, SocketWho who) + { + } + + /** + * \brief Processed ack + * + * Invoked after the processing of the ACK + * + * \param tcb Transmission Control Block + * \param h the header of segment + * \param who the socket which has processed the ACK (SENDER or RECEIVER) + */ + virtual void ProcessedAck (const Ptr tcb, + const TcpHeader& h, SocketWho who) + { + } + + /** + * \brief Packet transmitted down to IP layer + * + * \param p packet + * \param h header + * \param who the socket which has received the packet (SENDER or RECEIVER) + */ + virtual void Tx (const Ptr p, const TcpHeader&h, SocketWho who) + { + } + + /** + * \brief Packet received from IP layer + * + * \param p packet + * \param h header + * \param who the socket which has received the packet (SENDER or RECEIVER) + */ + virtual void Rx (const Ptr p, const TcpHeader&h, SocketWho who) + { + } + + /** + * \brief Rto has expired + * + * \param tcb Transmission control block + * \param who where the RTO has expired (SENDER or RECEIVER) + */ + virtual void RTOExpired (const Ptr tcb, SocketWho who) + { + } + + /** + * \brief Performs the (eventual) final checks through test asserts + * + */ + virtual void FinalChecks () + { + } + + /** + * \brief Get the channel Propagation Delay + * \return propagation delay of the channel + */ + Time GetPropagationDelay () const + { + return m_propagationDelay; + } + + /** + * \brief Get the data start time + * \return start time of data packets + */ + Time GetStartTime () const + { + return m_startTime; + } + + /** + * \brief Get the MTU of the environment + * \return MTU of the environment + */ + uint32_t GetMtu () const + { + return m_mtu; + } + + /** + * \brief Get the application packet size + * \return application packet size + */ + uint32_t GetPktSize () const + { + return m_pktSize; + } + + /** + * \brief Get the number of application packets + * \return count of application packets to be generated + */ + uint32_t GetPktCount () const + { + return m_pktCount; + } + + /** + * \brief Get the interval to wait for each packet sent down from application + * to TCP + * \return interval between packet + */ + Time GetPktInterval () const + { + return m_interPacketInterval; + } + + /** + * \brief Get the initial slow start threshold + * \return initial slow start threshold + */ + uint32_t GetInitialSsThresh () const + { + return m_initialSlowStartThresh; + } + + /** + * \brief Get the initial congestion window + * \return initial cwnd + */ + uint32_t GetInitialCwnd () const + { + return m_initialCwnd; + } + + + TypeId m_congControlTypeId; //!< Congestion control + +private: + // Member variables, accessible through getters + // giving write access to subclass can be dangerous + Time m_propagationDelay; //!< Propagation delay of the channel + + Time m_startTime; //!< Data transmission time + uint32_t m_mtu; //!< MTU of the environment + + uint32_t m_pktSize; //!< Size of the application packet + uint32_t m_pktCount; //!< Count of the application packet + Time m_interPacketInterval; //!< Time between sending application packet + // down to tcp socket + uint32_t m_initialSlowStartThresh; //!< Initial slow start threshold + uint32_t m_initialCwnd; //!< Initial congestion window + uint32_t m_segmentSize; //!< Segment size + + Ptr m_senderSocket; //!< Pointer to sender socket + Ptr m_receiverSocket; //!< Pointer to receiver socket + +private: + // De-multiplexing callbacks. + void NormalCloseCb (Ptr socket); + void ErrorCloseCb (Ptr socket); + void QueueDropCb (std::string context, Ptr p); + void PhyDropCb (std::string context, Ptr p); + void RcvAckCb (Ptr p, const TcpHeader& h, + Ptr tcp); + void ProcessedAckCb (Ptr p, const TcpHeader& h, + Ptr tcp); + void TxPacketCb (const Ptr p, const TcpHeader& h, + const Ptr tcp); + void RxPacketCb (const Ptr p, const TcpHeader& h, + const Ptr tcp); + void RtoExpiredCb (const Ptr tcb, + const Ptr tcp); + void ForkCb (Ptr tcp); + void HandleAccept (Ptr socket, const Address& from); +}; + +/** + * \brief Convenience function to retrieve the ACK state from a TCB + * + * \param tcb Transmission control block + * \return the state of the ACK state machine + */ +static inline TcpSocketState::TcpAckState_t +GetAckStateFrom (Ptr tcb) +{ + return tcb->m_ackState.Get (); +} + +} // namespace ns3 + +#endif // TCPGENERALTEST_H + diff --git a/src/internet/wscript b/src/internet/wscript index 7ca5248d2..c0a79c860 100644 --- a/src/internet/wscript +++ b/src/internet/wscript @@ -231,6 +231,7 @@ def build(bld): 'test/tcp-wscaling-test.cc', 'test/tcp-option-test.cc', 'test/tcp-header-test.cc', + 'test/tcp-general-test.cc', 'test/udp-test.cc', 'test/ipv6-address-generator-test-suite.cc', 'test/ipv6-dual-stack-test-suite.cc',