Commit cbdbca38 authored by Youness Alaoui's avatar Youness Alaoui
Browse files

Adding pseudotcp implementation, copied from libjingle 0.4

parent bdff4556
......@@ -57,6 +57,8 @@ libagent_la_SOURCES = \
discovery.h \
interfaces.c \
interfaces.h \
pseudotcp.h \
pseudotcp.c \
$(BUILT_SOURCES)
libagent_la_LIBADD = \
......@@ -68,4 +70,4 @@ libagent_la_DEPENDENCIES = \
$(top_builddir)/socket/libsocket.la \
$(top_builddir)/stun/libstun.la
pkginclude_HEADERS = agent.h candidate.h debug.h address.h interfaces.h
pkginclude_HEADERS = agent.h candidate.h debug.h address.h interfaces.h pseudotcp.h
/*
* This file is part of the Nice GLib ICE library.
*
* (C) 2010 Collabora Ltd.
* Contact: Youness Alaoui
* (C) 20010 Nokia Corporation. All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Youness Alaoui, Collabora Ltd.
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the LGPL.
*/
/* Reproducing license from libjingle for copied code */
/*
* libjingle
* Copyright 2004--2005, Google Inc.
*
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
#include "pseudotcp.h"
G_DEFINE_TYPE (PseudoTcpSocket, pseudo_tcp_socket, G_TYPE_OBJECT);
// The following logging is for detailed (packet-level) pseudotcp analysis only.
#define _DBG_NONE 0
#define _DBG_NORMAL 1
#define _DBG_VERBOSE 2
#define _DEBUGMSG _DBG_NONE
#define DEBUG(fmt, ...) g_debug ("PseudoTcpSocket %p: " fmt, self, ## __VA_ARGS__)
//////////////////////////////////////////////////////////////////////
// Network Constants
//////////////////////////////////////////////////////////////////////
// Standard MTUs
const guint16 PACKET_MAXIMUMS[] = {
65535, // Theoretical maximum, Hyperchannel
32000, // Nothing
17914, // 16Mb IBM Token Ring
8166, // IEEE 802.4
//4464, // IEEE 802.5 (4Mb max)
4352, // FDDI
//2048, // Wideband Network
2002, // IEEE 802.5 (4Mb recommended)
//1536, // Expermental Ethernet Networks
//1500, // Ethernet, Point-to-Point (default)
1492, // IEEE 802.3
1006, // SLIP, ARPANET
//576, // X.25 Networks
//544, // DEC IP Portal
//512, // NETBIOS
508, // IEEE 802/Source-Rt Bridge, ARCNET
296, // Point-to-Point (low delay)
//68, // Official minimum
0, // End of list marker
};
#define MAX_PACKET 65535
// Note: we removed lowest level because packet overhead was larger!
#define MIN_PACKET 296
// (+ up to 40 bytes of options?)
#define IP_HEADER_SIZE 20
#define ICMP_HEADER_SIZE 8
#define UDP_HEADER_SIZE 8
// TODO: Make JINGLE_HEADER_SIZE transparent to this code?
// when relay framing is in use
#define JINGLE_HEADER_SIZE 64
//////////////////////////////////////////////////////////////////////
// Global Constants and Functions
//////////////////////////////////////////////////////////////////////
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 0 | Conversation Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 4 | Sequence Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 8 | Acknowledgment Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | | |U|A|P|R|S|F| |
// 12 | Control | |R|C|S|S|Y|I| Window |
// | | |G|K|H|T|N|N| |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 16 | Timestamp sending |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 20 | Timestamp receiving |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 24 | data |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//////////////////////////////////////////////////////////////////////
#define MAX_SEQ 0xFFFFFFFF
#define HEADER_SIZE 24
#define PACKET_OVERHEAD (HEADER_SIZE + UDP_HEADER_SIZE + \
IP_HEADER_SIZE + JINGLE_HEADER_SIZE)
// MIN_RTO = 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second")
#define MIN_RTO 250
#define DEF_RTO 3000 /* 3 seconds (RFC1122, Sec 4.2.3.1) */
#define MAX_RTO 60000 /* 60 seconds */
#define ACK_DELAY 100 /* 100 milliseconds */
/*
#define FLAG_FIN 0x01
#define FLAG_SYN 0x02
#define FLAG_ACK 0x10
*/
#define FLAG_CTL 0x02
#define FLAG_RST 0x04
#define CTL_CONNECT 0
//#define CTL_REDIRECT 1
#define CTL_EXTRA 255
#define CTRL_BOUND 0x80000000
// If there are no pending clocks, wake up every 4 seconds
#define DEFAULT_TIMEOUT 4000
// If the connection is closed, once per minute
#define CLOSED_TIMEOUT (60 * 1000)
//////////////////////////////////////////////////////////////////////
// Helper Functions
//////////////////////////////////////////////////////////////////////
static guint32
min (guint32 first, guint32 second)
{
return (first < second? first:second);
}
static guint32
max (guint32 first, guint32 second)
{
return (first > second? first:second);
}
static guint32
bound(guint32 lower, guint32 middle, guint32 upper)
{
return min (max (lower, middle), upper);
}
static guint32
get_current_time(void)
{
GTimeVal tv;
g_get_current_time (&tv);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
static gboolean
time_is_between(guint32 later, guint32 middle, guint32 earlier)
{
if (earlier <= later) {
return ((earlier <= middle) && (middle <= later));
} else {
return !((later < middle) && (middle < earlier));
}
}
static gint32
time_diff(guint32 later, guint32 earlier)
{
guint32 LAST = 0xFFFFFFFF;
guint32 HALF = 0x80000000;
if (time_is_between(earlier + HALF, later, earlier)) {
if (earlier <= later) {
return (long)(later - earlier);
} else {
return (long)(later + (LAST - earlier) + 1);
}
} else {
if (later <= earlier) {
return -(long) (earlier - later);
} else {
return -(long)(earlier + (LAST - later) + 1);
}
}
}
//////////////////////////////////////////////////////////////////////
// PseudoTcp
//////////////////////////////////////////////////////////////////////
typedef enum {
SD_NONE,
SD_GRACEFUL,
SD_FORCEFUL
} Shutdown;
typedef enum {
sfNone,
sfDelayedAck,
sfImmediateAck
} SendFlags;
enum {
// Note: can't go as high as 1024 * 64, because of uint16 precision
kRcvBufSize = 1024 * 60,
// Note: send buffer should be larger to make sure we can always fill the
// receiver window
kSndBufSize = 1024 * 90
};
typedef struct {
guint32 conv, seq, ack;
guint8 flags;
guint16 wnd;
const gchar * data;
guint32 len;
guint32 tsval, tsecr;
} Segment;
typedef struct {
guint32 seq, len;
guint8 xmit;
gboolean bCtrl;
} SSegment;
typedef struct {
guint32 seq, len;
} RSegment;
struct _PseudoTcpSocketPrivate {
PseudoTcpCallbacks callbacks;
Shutdown shutdown;
gint error;
// TCB data
PseudoTcpState state;
guint32 conv;
gboolean bReadEnable, bWriteEnable, bOutgoing;
guint32 last_traffic;
// Incoming data
GList *rlist;
gchar rbuf[kRcvBufSize];
guint32 rcv_nxt, rcv_wnd, rlen, lastrecv;
// Outgoing data
GList *slist;
gchar sbuf[kSndBufSize];
guint32 snd_nxt, snd_wnd, slen, lastsend, snd_una;
// Maximum segment size, estimated protocol level, largest segment sent
guint32 mss, msslevel, largest, mtu_advise;
// Retransmit timer
guint32 rto_base;
// Timestamp tracking
guint32 ts_recent, ts_lastack;
// Round-trip calculation
guint32 rx_rttvar, rx_srtt, rx_rto;
// Congestion avoidance, Fast retransmit/recovery, Delayed ACKs
guint32 ssthresh, cwnd;
guint8 dup_acks;
guint32 recover;
guint32 t_ack;
};
/* properties */
enum
{
PROP_CONVERSATION = 1,
PROP_CALLBACKS,
PROP_STATE,
LAST_PROPERTY
};
static void pseudo_tcp_socket_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec);
static void pseudo_tcp_socket_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec);
static void pseudo_tcp_socket_dispose (GObject *object);
static guint32 queue(PseudoTcpSocket *self, const gchar * data,
guint32 len, gboolean bCtrl);
static PseudoTcpWriteResult packet(PseudoTcpSocket *self, guint32 seq,
guint8 flags, const gchar * data, guint32 len);
static gboolean parse(PseudoTcpSocket *self,
const guint8 * buffer, guint32 size);
static gboolean process(PseudoTcpSocket *self, Segment *seg);
static gboolean transmit(PseudoTcpSocket *self, const GList *seg, guint32 now);
static void attempt_send(PseudoTcpSocket *self, SendFlags sflags);
static void closedown(PseudoTcpSocket *self, guint32 err);
static void adjustMTU(PseudoTcpSocket *self);
static void
pseudo_tcp_socket_class_init (PseudoTcpSocketClass *cls)
{
GObjectClass *object_class = G_OBJECT_CLASS (cls);
object_class->get_property = pseudo_tcp_socket_get_property;
object_class->set_property = pseudo_tcp_socket_set_property;
object_class->dispose = pseudo_tcp_socket_dispose;
g_object_class_install_property (object_class, PROP_CONVERSATION,
g_param_spec_uint ("conversation", "TCP Conversation ID",
"The TCP Conversation ID",
0, G_MAXUINT32, 0,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CALLBACKS,
g_param_spec_pointer ("callbacks", "PseudoTcp socket callbacks",
"Structure with the callbacks to call when PseudoTcp events happen",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_STATE,
g_param_spec_uint ("state", "PseudoTcp State",
"The current state (enum PseudoTcpState) of the PseudoTcp socket",
TCP_LISTEN, TCP_CLOSED, TCP_LISTEN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
static void
pseudo_tcp_socket_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PseudoTcpSocket *self = PSEUDO_TCP_SOCKET (object);
switch (property_id) {
case PROP_CONVERSATION:
g_value_set_uint (value, self->priv->conv);
break;
case PROP_CALLBACKS:
g_value_set_pointer (value, (gpointer) &self->priv->callbacks);
break;
case PROP_STATE:
g_value_set_uint (value, self->priv->state);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pseudo_tcp_socket_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PseudoTcpSocket *self = PSEUDO_TCP_SOCKET (object);
switch (property_id) {
case PROP_CONVERSATION:
self->priv->conv = g_value_get_uint (value);
break;
case PROP_CALLBACKS:
{
PseudoTcpCallbacks *c = g_value_get_pointer (value);
self->priv->callbacks = *c;
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pseudo_tcp_socket_dispose (GObject *object)
{
PseudoTcpSocket *self = PSEUDO_TCP_SOCKET (object);
PseudoTcpSocketPrivate *priv = self->priv;
if (priv == NULL)
return;
/*TODO: free slist/rlist/?*/
g_free (priv);
self->priv = NULL;
if (G_OBJECT_CLASS (pseudo_tcp_socket_parent_class)->dispose)
G_OBJECT_CLASS (pseudo_tcp_socket_parent_class)->dispose (object);
}
static void
pseudo_tcp_socket_init (PseudoTcpSocket *obj)
{
/* Use g_new0, and do not use g_object_set_private because the size of
* our private data is too big (150KB+) and the g_slice_allow cannot allocate
* it. So we handle the private ourselves */
PseudoTcpSocketPrivate *priv = g_new0 (PseudoTcpSocketPrivate, 1);
guint32 now = get_current_time();
obj->priv = priv;
priv->shutdown = SD_NONE;
priv->error = 0;
priv->state = TCP_LISTEN;
priv->conv = 0;
priv->rcv_wnd = sizeof(priv->rbuf);
priv->snd_nxt = priv->slen = 0;
priv->snd_wnd = 1;
priv->snd_una = priv->rcv_nxt = priv->rlen = 0;
priv->bReadEnable = TRUE;
priv->bWriteEnable = FALSE;
priv->t_ack = 0;
priv->msslevel = 0;
priv->largest = 0;
priv->mss = MIN_PACKET - PACKET_OVERHEAD;
priv->mtu_advise = MAX_PACKET;
priv->rto_base = 0;
priv->cwnd = 2 * priv->mss;
priv->ssthresh = sizeof(priv->rbuf);
priv->lastrecv = priv->lastsend = priv->last_traffic = now;
priv->bOutgoing = FALSE;
priv->dup_acks = 0;
priv->recover = 0;
priv->ts_recent = priv->ts_lastack = 0;
priv->rx_rto = DEF_RTO;
priv->rx_srtt = priv->rx_rttvar = 0;
}
gint
pseudo_tcp_socket_connect(PseudoTcpSocket *self)
{
PseudoTcpSocketPrivate *priv = self->priv;
gchar buffer[1];
if (priv->state != TCP_LISTEN) {
priv->error = EINVAL;
return -1;
}
priv->state = TCP_SYN_SENT;
DEBUG ("State: TCP_SYN_SENT");
buffer[0] = CTL_CONNECT;
queue(self, buffer, 1, TRUE);
attempt_send(self, sfNone);
return 0;
}
PseudoTcpSocket *pseudo_tcp_socket_new (guint32 conversation,
PseudoTcpCallbacks *callbacks)
{
return g_object_new (PSEUDO_TCP_SOCKET_TYPE,
"conversation", conversation,
"callbacks", callbacks,
NULL);
}
void
pseudo_tcp_socket_notify_mtu(PseudoTcpSocket *self, guint16 mtu)
{
PseudoTcpSocketPrivate *priv = self->priv;
priv->mtu_advise = mtu;
if (priv->state == TCP_ESTABLISHED) {
adjustMTU(self);
}
}
void
pseudo_tcp_socket_notify_clock(PseudoTcpSocket *self)
{
PseudoTcpSocketPrivate *priv = self->priv;
guint32 now = get_current_time ();
if (priv->state == TCP_CLOSED)
return;
// Check if it's time to retransmit a segment
if (priv->rto_base &&
(time_diff(priv->rto_base + priv->rx_rto, now) <= 0)) {
if (g_list_length (priv->slist) == 0) {
g_assert_not_reached ();
} else {
// Note: (priv->slist.front().xmit == 0)) {
// retransmit segments
guint32 nInFlight;
guint32 rto_limit;
#if _DEBUGMSG >= _DBG_NORMAL
DEBUG ("timeout retransmit (rto: %d) (rto_base: %d) (now: %d) "
"(dup_acks: %d)", priv->rx_rto, priv->rto_base, now,
(guint) priv->dup_acks);
#endif // _DEBUGMSG
if (!transmit(self, priv->slist, now)) {
closedown(self, ECONNABORTED);
return;
}
nInFlight = priv->snd_nxt - priv->snd_una;
priv->ssthresh = max(nInFlight / 2, 2 * priv->mss);
//LOG(LS_INFO) << "priv->ssthresh: " << priv->ssthresh << " nInFlight: " << nInFlight << " priv->mss: " << priv->mss;
priv->cwnd = priv->mss;
// Back off retransmit timer. Note: the limit is lower when connecting.
rto_limit = (priv->state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO;
priv->rx_rto = min(rto_limit, priv->rx_rto * 2);
priv->rto_base = now;
}
}
// Check if it's time to probe closed windows
if ((priv->snd_wnd == 0)
&& (time_diff(priv->lastsend + priv->rx_rto, now) <= 0)) {
if (time_diff(now, priv->lastrecv) >= 15000) {
closedown(self, ECONNABORTED);
return;
}
// probe the window
packet(self, priv->snd_nxt - 1, 0, 0, 0);
priv->lastsend = now;
// back off retransmit timer
priv->rx_rto = min(MAX_RTO, priv->rx_rto * 2);
}
// Check if it's time to send delayed acks
if (priv->t_ack && (time_diff(priv->t_ack + ACK_DELAY, now) <= 0)) {
packet(self, priv->snd_nxt, 0, 0, 0);
}
}
gboolean
pseudo_tcp_socket_notify_packet(PseudoTcpSocket *self,
const gchar * buffer, guint32 len)
{
if (len > MAX_PACKET) {
//LOG_F(WARNING) << "packet too large";
return FALSE;
}
return parse(self, (guint8 *) buffer, len);
}
gboolean
pseudo_tcp_socket_get_next_clock(PseudoTcpSocket *self,