Browse Source

limit stream's outbound speed

pull/2076/head
orignal 5 months ago
parent
commit
0428b5ece1
  1. 5
      libi2pd/Destination.cpp
  2. 4
      libi2pd/Destination.h
  3. 45
      libi2pd/Streaming.cpp
  4. 7
      libi2pd/Streaming.h
  5. 1
      libi2pd_client/ClientContext.cpp

5
libi2pd/Destination.cpp

@ -180,7 +180,6 @@ namespace client
i2p::tunnel::tunnels.StopTunnelPool (m_Pool); i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
} }
SaveTags (); SaveTags ();
m_Service.stop (); // make sure we don't process more messages after this point. TODO: implement it better
CleanUp (); // GarlicDestination CleanUp (); // GarlicDestination
} }
@ -980,6 +979,7 @@ namespace client
bool isPublic, const std::map<std::string, std::string> * params): bool isPublic, const std::map<std::string, std::string> * params):
LeaseSetDestination (service, isPublic, params), LeaseSetDestination (service, isPublic, params),
m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED),
m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0), m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0),
m_DatagramDestination (nullptr), m_RefCounter (0), m_DatagramDestination (nullptr), m_RefCounter (0),
m_ReadyChecker(service) m_ReadyChecker(service)
@ -1048,6 +1048,9 @@ namespace client
auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY);
if (it != params->end ()) if (it != params->end ())
m_StreamingAckDelay = std::stoi(it->second); m_StreamingAckDelay = std::stoi(it->second);
it = params->find (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED);
if (it != params->end ())
m_StreamingOutboundSpeed = std::stoi(it->second);
it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS);
if (it != params->end ()) if (it != params->end ())
m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true

4
libi2pd/Destination.h

@ -84,6 +84,8 @@ namespace client
// streaming // streaming
const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay"; const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay";
const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds
const char I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED[] = "i2p.streaming.maxOutboundSpeed"; // bytes/sec
const int DEFAULT_MAX_OUTBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings"; const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings";
const int DEFAULT_ANSWER_PINGS = true; const int DEFAULT_ANSWER_PINGS = true;
@ -259,6 +261,7 @@ namespace client
bool IsAcceptingStreams () const; bool IsAcceptingStreams () const;
void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor); void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor);
int GetStreamingAckDelay () const { return m_StreamingAckDelay; } int GetStreamingAckDelay () const { return m_StreamingAckDelay; }
int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; }
bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; } bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; }
// datagram // datagram
@ -296,6 +299,7 @@ namespace client
std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey; std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey;
int m_StreamingAckDelay; int m_StreamingAckDelay;
int m_StreamingOutboundSpeed;
bool m_IsStreamingAnswerPings; bool m_IsStreamingAnswerPings;
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts; std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;

45
libi2pd/Streaming.cpp

@ -70,12 +70,14 @@ namespace stream
std::shared_ptr<const i2p::data::LeaseSet> remote, int port): m_Service (service), std::shared_ptr<const i2p::data::LeaseSet> remote, int port): m_Service (service),
m_SendStreamID (0), m_SequenceNumber (0), m_SendStreamID (0), m_SequenceNumber (0),
m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1),
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsWinDropped (true), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsSendTime (true), m_IsWinDropped (true),
m_IsTimeOutResend (false), m_LocalDestination (local), m_IsTimeOutResend (false), m_LocalDestination (local),
m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service),
m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port),
m_RTT (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_RTO (INITIAL_RTO), m_RTT (INITIAL_RTT), m_WindowSize (INITIAL_WINDOW_SIZE), m_RTO (INITIAL_RTO),
m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_PrevRTT (INITIAL_RTT), m_Jitter (0), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_OutboundSpeed (local.GetOwner ()->GetStreamingOutboundSpeed ()), m_PrevRTTSample (INITIAL_RTT),
m_PrevRTT (INITIAL_RTT), m_Jitter (0),
m_PacingTime (INITIAL_PACING_TIME), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) m_PacingTime (INITIAL_PACING_TIME), m_NumResendAttempts (0), m_MTU (STREAMING_MTU)
{ {
RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); RAND_bytes ((uint8_t *)&m_RecvStreamID, 4);
@ -85,11 +87,12 @@ namespace stream
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): Stream::Stream (boost::asio::io_service& service, StreamingDestination& local):
m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_Service (service), m_SendStreamID (0), m_SequenceNumber (0),
m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1),
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsWinDropped (true), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsSendTime (true), m_IsWinDropped (true),
m_IsTimeOutResend (false), m_LocalDestination (local), m_IsTimeOutResend (false), m_LocalDestination (local),
m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service),
m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT),
m_WindowSize (INITIAL_WINDOW_SIZE), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_WindowSize (INITIAL_WINDOW_SIZE), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_OutboundSpeed (local.GetOwner ()->GetStreamingOutboundSpeed ()),
m_PrevRTTSample (INITIAL_RTT), m_PrevRTT (INITIAL_RTT), m_Jitter (0), m_PrevRTTSample (INITIAL_RTT), m_PrevRTT (INITIAL_RTT), m_Jitter (0),
m_PacingTime (INITIAL_PACING_TIME), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) m_PacingTime (INITIAL_PACING_TIME), m_NumResendAttempts (0), m_MTU (STREAMING_MTU)
{ {
@ -492,10 +495,9 @@ namespace stream
m_IsWinDropped = true; // don't drop window twice m_IsWinDropped = true; // don't drop window twice
} }
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
int pacTime = std::round (m_RTT*1000/m_WindowSize); UpdatePacingTime ();
m_PacingTime = std::max (MIN_PACING_TIME, pacTime);
m_PrevRTT = m_RTT * 1.1 + m_Jitter; m_PrevRTT = m_RTT * 1.1 + m_Jitter;
//
bool wasInitial = m_RTO == INITIAL_RTO; bool wasInitial = m_RTO == INITIAL_RTO;
m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter)); // TODO: implement it better m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter)); // TODO: implement it better
@ -515,7 +517,7 @@ namespace stream
m_RoutingSession->SetSharedRoutingPath ( m_RoutingSession->SetSharedRoutingPath (
std::make_shared<i2p::garlic::GarlicRoutingPath> ( std::make_shared<i2p::garlic::GarlicRoutingPath> (
i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0, 0})); i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0, 0}));
if (m_SentPackets.empty ()) if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ())
{ {
m_ResendTimer.cancel (); m_ResendTimer.cancel ();
m_SendTimer.cancel (); m_SendTimer.cancel ();
@ -599,7 +601,7 @@ namespace stream
{ {
ScheduleSend (); ScheduleSend ();
int numMsgs = m_WindowSize - m_SentPackets.size (); int numMsgs = m_WindowSize - m_SentPackets.size ();
if (numMsgs <= 0) return; // window is full if (numMsgs <= 0 || !m_IsSendTime) return; // window is full
else numMsgs = 1; else numMsgs = 1;
bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet
std::vector<Packet *> packets; std::vector<Packet *> packets;
@ -701,6 +703,7 @@ namespace stream
m_SentPackets.insert (it); m_SentPackets.insert (it);
} }
SendPackets (packets); SendPackets (packets);
m_IsSendTime = false;
if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ()) if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ())
SendClose (); SendClose ();
if (isEmpty) if (isEmpty)
@ -1045,6 +1048,7 @@ namespace stream
{ {
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
{ {
m_IsSendTime = true;
if (m_IsNAcked) // || m_WindowSize < int(m_SentPackets.size ())) // resend one packet if (m_IsNAcked) // || m_WindowSize < int(m_SentPackets.size ())) // resend one packet
ResendPacket (); ResendPacket ();
// delay-based CC // delay-based CC
@ -1053,10 +1057,8 @@ namespace stream
m_WindowSize >>= 1; // /2 m_WindowSize >>= 1; // /2
m_IsWinDropped = true; // don't drop window twice m_IsWinDropped = true; // don't drop window twice
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
int pacTime = std::round (m_RTT*1000/m_WindowSize); UpdatePacingTime ();
m_PacingTime = std::max (MIN_PACING_TIME, pacTime);
} }
//
else if (m_WindowSize > int(m_SentPackets.size ())) // send one packet else if (m_WindowSize > int(m_SentPackets.size ())) // send one packet
SendBuffer (); SendBuffer ();
else // pass else // pass
@ -1081,6 +1083,7 @@ namespace stream
{ {
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
{ {
m_IsSendTime = true;
if (m_RTO > INITIAL_RTO) m_RTO = INITIAL_RTO; if (m_RTO > INITIAL_RTO) m_RTO = INITIAL_RTO;
m_SendTimer.cancel (); // if no ack's in RTO, disable fast retransmit m_SendTimer.cancel (); // if no ack's in RTO, disable fast retransmit
m_IsTimeOutResend = true; m_IsTimeOutResend = true;
@ -1118,7 +1121,7 @@ namespace stream
} }
// select tunnels if necessary and send // select tunnels if necessary and send
if (packets.size () > 0) if (packets.size () > 0 && m_IsSendTime)
{ {
if (m_IsNAcked) m_NumResendAttempts = 1; if (m_IsNAcked) m_NumResendAttempts = 1;
else if (m_IsTimeOutResend) m_NumResendAttempts++; else if (m_IsTimeOutResend) m_NumResendAttempts++;
@ -1130,10 +1133,8 @@ namespace stream
m_WindowSize >>= 1; // /2 m_WindowSize >>= 1; // /2
m_IsWinDropped = true; // don't drop window twice m_IsWinDropped = true; // don't drop window twice
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
int pacTime = std::round (m_RTT*1000/m_WindowSize); UpdatePacingTime ();
m_PacingTime = std::max (MIN_PACING_TIME, pacTime);
} }
//
} }
else if (m_IsTimeOutResend) else if (m_IsTimeOutResend)
{ {
@ -1141,8 +1142,7 @@ namespace stream
m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change
m_WindowSize = INITIAL_WINDOW_SIZE; m_WindowSize = INITIAL_WINDOW_SIZE;
m_IsWinDropped = true; m_IsWinDropped = true;
int pacTime = std::round (m_RTT*1000/m_WindowSize); UpdatePacingTime ();
m_PacingTime = std::max (MIN_PACING_TIME, pacTime);
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
if (m_NumResendAttempts & 1) if (m_NumResendAttempts & 1)
{ {
@ -1159,6 +1159,7 @@ namespace stream
} }
} }
SendPackets (packets); SendPackets (packets);
m_IsSendTime = false;
if (m_IsNAcked) ScheduleSend (); if (m_IsNAcked) ScheduleSend ();
} }
else else
@ -1296,6 +1297,16 @@ namespace stream
if (m_RoutingSession) if (m_RoutingSession)
m_RoutingSession->SetSharedRoutingPath (nullptr); // TODO: count failures m_RoutingSession->SetSharedRoutingPath (nullptr); // TODO: count failures
} }
void Stream::UpdatePacingTime ()
{
m_PacingTime = std::round (m_RTT*1000/m_WindowSize);
if (m_OutboundSpeed)
{
auto minTime = (1000000LL*STREAMING_MTU)/m_OutboundSpeed;
if (m_PacingTime < minTime) m_PacingTime = minTime;
}
}
StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip): StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip):
m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),

7
libi2pd/Streaming.h

@ -53,8 +53,6 @@ namespace stream
const size_t MAX_PACKET_SIZE = 4096; const size_t MAX_PACKET_SIZE = 4096;
const size_t COMPRESSION_THRESHOLD_SIZE = 66; const size_t COMPRESSION_THRESHOLD_SIZE = 66;
const int MAX_NUM_RESEND_ATTEMPTS = 10; const int MAX_NUM_RESEND_ATTEMPTS = 10;
const int MAX_STREAM_SPEED = 1730000000; // 1 - 1730000000 // in bytes/sec // no more than 1.73 Gbytes/s
const int MIN_PACING_TIME = 1000000 * STREAMING_MTU / MAX_STREAM_SPEED; // in microseconds
const int INITIAL_WINDOW_SIZE = 10; const int INITIAL_WINDOW_SIZE = 10;
const int MIN_WINDOW_SIZE = 1; const int MIN_WINDOW_SIZE = 1;
const int MAX_WINDOW_SIZE = 128; const int MAX_WINDOW_SIZE = 128;
@ -241,6 +239,8 @@ namespace stream
void ScheduleAck (int timeout); void ScheduleAck (int timeout);
void HandleAckSendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode);
void UpdatePacingTime ();
private: private:
boost::asio::io_service& m_Service; boost::asio::io_service& m_Service;
@ -251,6 +251,7 @@ namespace stream
StreamStatus m_Status; StreamStatus m_Status;
bool m_IsAckSendScheduled; bool m_IsAckSendScheduled;
bool m_IsNAcked; bool m_IsNAcked;
bool m_IsSendTime;
bool m_IsWinDropped; bool m_IsWinDropped;
bool m_IsTimeOutResend; bool m_IsTimeOutResend;
StreamingDestination& m_LocalDestination; StreamingDestination& m_LocalDestination;
@ -269,7 +270,7 @@ namespace stream
SendBufferQueue m_SendBuffer; SendBufferQueue m_SendBuffer;
double m_RTT; double m_RTT;
int m_WindowSize, m_RTO, m_AckDelay, m_PrevRTTSample, m_PrevRTT, m_Jitter; int m_WindowSize, m_RTO, m_AckDelay, m_OutboundSpeed, m_PrevRTTSample, m_PrevRTT, m_Jitter;
uint64_t m_PacingTime; uint64_t m_PacingTime;
int m_NumResendAttempts; int m_NumResendAttempts;
size_t m_MTU; size_t m_MTU;

1
libi2pd_client/ClientContext.cpp

@ -471,6 +471,7 @@ namespace client
options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY);
options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY);
options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY);
options[I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, DEFAULT_MAX_OUTBOUND_SPEED);
options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false);
options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE);
std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4");

Loading…
Cancel
Save