[Asterisk-cvs] asterisk/channels chan_sip.c,1.808,1.809
kpfleming
kpfleming
Mon Aug 22 18:53:01 CDT 2005
Update of /usr/cvsroot/asterisk/channels
In directory mongoose.digium.com:/tmp/cvs-serv10195/channels
Modified Files:
chan_sip.c
Log Message:
add preliminary support for SIP timers (issue #4359)
Index: chan_sip.c
===================================================================
RCS file: /usr/cvsroot/asterisk/channels/chan_sip.c,v
retrieving revision 1.808
retrieving revision 1.809
diff -u -d -r1.808 -r1.809
--- chan_sip.c 22 Aug 2005 21:13:37 -0000 1.808
+++ chan_sip.c 22 Aug 2005 22:55:06 -0000 1.809
@@ -111,8 +111,9 @@
#define DEFAULT_FREQ_OK 60 * 1000 /* How often to check for the host to be up */
#define DEFAULT_FREQ_NOTOK 10 * 1000 /* How often to check, if the host is down... */
-#define DEFAULT_RETRANS 2000 /* How frequently to retransmit */
-#define MAX_RETRANS 5 /* Try only 5 times for retransmissions */
+#define DEFAULT_RETRANS 1000 /* How frequently to retransmit */
+ /* 2 * 500 ms in RFC 3261 */
+#define MAX_RETRANS 7 /* Try only 7 times for retransmissions */
#define DEBUG_READ 0 /* Recieved data */
@@ -364,6 +365,7 @@
static int compactheaders = 0; /* send compact sip headers */
static int recordhistory = 0; /* Record SIP history. Off by default */
+static int dumphistory = 0; /* Dump history to verbose before destroying SIP dialog */
static char global_musicclass[MAX_MUSICCLASS] = ""; /* Global music on hold class */
#define DEFAULT_REALM "asterisk"
@@ -505,6 +507,7 @@
ast_group_t pickupgroup; /* Pickup group */
int lastinvite; /* Last Cseq of invite */
unsigned int flags; /* SIP_ flags */
+ int timer_t1; /* SIP timer T1, ms rtt */
unsigned int sipoptions; /* Supported SIP sipoptions on the other end */
int capability; /* Special capability (codec) */
int jointcapability; /* Supported capability at both ends (codecs ) */
@@ -609,6 +612,8 @@
unsigned int flags; /* non-zero if this is a response packet (e.g. 200 OK) */
struct sip_pvt *owner; /* Owner call */
int retransid; /* Retransmission ID */
+ int timer_a; /* SIP timer A, retransmission timer */
+ int timer_t1; /* SIP Timer T1, estimated RTT or 500 ms */
int packetlen; /* Length of packet */
char data[0];
};
@@ -823,6 +828,7 @@
static struct sip_auth *find_realm_authentication(struct sip_auth *authlist, char *realm); /* Find authentication for a specific realm */
static void append_date(struct sip_request *req); /* Append date to SIP packet */
static int determine_firstline_parts(struct sip_request *req);
+static void sip_dump_history(struct sip_pvt *dialog); /* Dump history to LOG_DEBUG at end of dialog, before destroying data */
/* Definition of this channel for channel registration */
static const struct ast_channel_tech sip_tech = {
@@ -1036,69 +1042,111 @@
/*--- retrans_pkt: Retransmit SIP message if no answer ---*/
static int retrans_pkt(void *data)
{
- struct sip_pkt *pkt=data, *prev, *cur;
- int res = 0;
+ struct sip_pkt *pkt=data, *prev, *cur = NULL;
char iabuf[INET_ADDRSTRLEN];
+ int reschedule = DEFAULT_RETRANS;
+
+ /* Lock channel */
ast_mutex_lock(&pkt->owner->lock);
+
if (pkt->retrans < MAX_RETRANS) {
+ char buf[80];
+
pkt->retrans++;
- if (sip_debug_test_pvt(pkt->owner)) {
+ if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
+ } else {
+ int siptimer_a;
+
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
+ if (!pkt->timer_a)
+ pkt->timer_a = 2 ;
+ else
+ pkt->timer_a = 2 * pkt->timer_a;
+
+ /* For non-invites, a maximum of 4 secs */
+ if (pkt->method != SIP_INVITE && pkt->timer_a > 4000)
+ pkt->timer_a = 4000;
+ siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
+
+ /* Reschedule re-transmit */
+ reschedule = siptimer_a;
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
+ }
+
+ if (pkt->owner && sip_debug_test_pvt(pkt->owner)) {
if (ast_test_flag(pkt->owner, SIP_NAT) & SIP_NAT_ROUTE)
ast_verbose("Retransmitting #%d (NAT) to %s:%d:\n%s\n---\n", pkt->retrans, ast_inet_ntoa(iabuf, sizeof(iabuf), pkt->owner->recv.sin_addr), ntohs(pkt->owner->recv.sin_port), pkt->data);
else
ast_verbose("Retransmitting #%d (no NAT) to %s:%d:\n%s\n---\n", pkt->retrans, ast_inet_ntoa(iabuf, sizeof(iabuf), pkt->owner->sa.sin_addr), ntohs(pkt->owner->sa.sin_port), pkt->data);
}
- append_history(pkt->owner, "ReTx", pkt->data);
+ snprintf(buf, sizeof(buf), "ReTx %d", reschedule);
+
+ append_history(pkt->owner, buf, pkt->data);
__sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
- res = 1;
- } else {
+ ast_mutex_unlock(&pkt->owner->lock);
+ return reschedule;
+ }
+ /* Too many retries */
+ if (pkt->owner && pkt->method != SIP_OPTIONS) {
ast_log(LOG_WARNING, "Maximum retries exceeded on call %s for seqno %d (%s %s)\n", pkt->owner->callid, pkt->seqno, (ast_test_flag(pkt, FLAG_FATAL)) ? "Critical" : "Non-critical", (ast_test_flag(pkt, FLAG_RESPONSE)) ? "Response" : "Request");
- append_history(pkt->owner, "MaxRetries", (ast_test_flag(pkt, FLAG_FATAL)) ? "(Critical)" : "(Non-critical)");
- pkt->retransid = -1;
- if (ast_test_flag(pkt, FLAG_FATAL)) {
- while(pkt->owner->owner && ast_mutex_trylock(&pkt->owner->owner->lock)) {
- ast_mutex_unlock(&pkt->owner->lock);
- usleep(1);
- ast_mutex_lock(&pkt->owner->lock);
- }
- if (pkt->owner->owner) {
- ast_set_flag(pkt->owner, SIP_ALREADYGONE);
- ast_queue_hangup(pkt->owner->owner);
- ast_mutex_unlock(&pkt->owner->owner->lock);
- } else {
- /* If no owner, destroy now */
- ast_set_flag(pkt->owner, SIP_NEEDDESTROY);
- }
+ } else {
+ if (pkt->method == SIP_OPTIONS && sipdebug)
+ ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) \n", pkt->owner->callid);
+ }
+ append_history(pkt->owner, "MaxRetries", (ast_test_flag(pkt, FLAG_FATAL)) ? "(Critical)" : "(Non-critical)");
+
+ pkt->retransid = -1;
+
+ if (ast_test_flag(pkt, FLAG_FATAL)) {
+ while(pkt->owner->owner && ast_mutex_trylock(&pkt->owner->owner->lock)) {
+ ast_mutex_unlock(&pkt->owner->lock);
+ usleep(1);
+ ast_mutex_lock(&pkt->owner->lock);
}
- /* In any case, go ahead and remove the packet */
- prev = NULL;
- cur = pkt->owner->packets;
- while(cur) {
- if (cur == pkt)
- break;
- prev = cur;
- cur = cur->next;
+ if (pkt->owner->owner) {
+ ast_set_flag(pkt->owner, SIP_ALREADYGONE);
+ ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet.\n", pkt->owner->callid);
+ ast_queue_hangup(pkt->owner->owner);
+ ast_mutex_unlock(&pkt->owner->owner->lock);
+ } else {
+ /* If no channel owner, destroy now */
+ ast_set_flag(pkt->owner, SIP_NEEDDESTROY);
}
- if (cur) {
- if (prev)
- prev->next = cur->next;
- else
- pkt->owner->packets = cur->next;
- ast_mutex_unlock(&pkt->owner->lock);
- free(cur);
- pkt = NULL;
- } else
- ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
}
+ /* In any case, go ahead and remove the packet */
+ prev = NULL;
+ cur = pkt->owner->packets;
+ while(cur) {
+ if (cur == pkt)
+ break;
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur) {
+ if (prev)
+ prev->next = cur->next;
+ else
+ pkt->owner->packets = cur->next;
+ ast_mutex_unlock(&pkt->owner->lock);
+ free(cur);
+ pkt = NULL;
+ } else
+ ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
if (pkt)
ast_mutex_unlock(&pkt->owner->lock);
- return res;
+ return 0;
}
/*--- __sip_reliable_xmit: transmit packet with retransmits ---*/
static int __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, char *data, int len, int fatal, int sipmethod)
{
struct sip_pkt *pkt;
+ int siptimer_a = DEFAULT_RETRANS;
+
pkt = malloc(sizeof(struct sip_pkt) + len + 1);
if (!pkt)
return -1;
@@ -1111,10 +1159,16 @@
pkt->seqno = seqno;
pkt->flags = resp;
pkt->data[len] = '\0';
+ pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
if (fatal)
ast_set_flag(pkt, FLAG_FATAL);
+ if (pkt->timer_t1)
+ siptimer_a = pkt->timer_t1 * 2;
+
/* Schedule retransmission */
- pkt->retransid = ast_sched_add(sched, DEFAULT_RETRANS, retrans_pkt, pkt);
+ pkt->retransid = ast_sched_add_variable(sched, siptimer_a, retrans_pkt, pkt, 1);
+ if (option_debug > 3 && sipdebug)
+ ast_log(LOG_DEBUG, "*** SIP TIMER: Initalizing retransmit timer on packet: Id #%d\n", pkt->retransid);
pkt->next = p->packets;
p->packets = pkt;
@@ -1185,6 +1239,7 @@
if ((cur->seqno == seqno) && ((ast_test_flag(cur, FLAG_RESPONSE)) == resp) &&
((ast_test_flag(cur, FLAG_RESPONSE)) ||
(!strncasecmp(msg, cur->data, strlen(msg)) && (cur->data[strlen(msg)] < 33)))) {
+ ast_mutex_lock(&p->lock);
if (!resp && (seqno == p->pendinginvite)) {
ast_log(LOG_DEBUG, "Acked pending invite %d\n", p->pendinginvite);
p->pendinginvite = 0;
@@ -1195,16 +1250,20 @@
prev->next = cur->next;
else
p->packets = cur->next;
- if (cur->retransid > -1)
+ if (cur->retransid > -1) {
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
ast_sched_del(sched, cur->retransid);
+ }
free(cur);
+ ast_mutex_unlock(&p->lock);
res = 0;
break;
}
prev = cur;
cur = cur->next;
}
- ast_log(LOG_DEBUG, "Stopping retransmission on '%s' of %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res ? "Not Found" : "Found");
+ ast_log(LOG_DEBUG, "Stopping retransmission on '%s' of %s %d: Match %s\n", p->callid, resp ? "Response" : "Request", seqno, res ? "Not Found" : "Found");
return res;
}
@@ -1215,7 +1274,7 @@
while(p->packets) {
if (cur == p->packets) {
- ast_log(LOG_WARNING, "Have a packet that doesn't want to give up!\n");
+ ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
return -1;
}
cur = p->packets;
@@ -1246,8 +1305,11 @@
((ast_test_flag(cur, FLAG_RESPONSE)) ||
(!strncasecmp(msg, cur->data, strlen(msg)) && (cur->data[strlen(msg)] < 33)))) {
/* this is our baby */
- if (cur->retransid > -1)
+ if (cur->retransid > -1) {
+ if (option_debug > 3 && sipdebug)
+ ast_log(LOG_DEBUG, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, msg);
ast_sched_del(sched, cur->retransid);
+ }
cur->retransid = -1;
res = 0;
break;
@@ -1718,6 +1780,9 @@
r->maxtime = peer->maxms;
r->callgroup = peer->callgroup;
r->pickupgroup = peer->pickupgroup;
+ /* Set timer T1 to RTT for this peer (if known by qualify=) */
+ if (peer->maxms && peer->lastms)
+ r->timer_t1 = peer->lastms;
if (ast_test_flag(r, SIP_DTMF) == SIP_DTMF_RFC2833)
r->noncodeccapability |= AST_RTP_DTMF;
else
@@ -1733,7 +1798,7 @@
/*--- create_addr: create address structure from peer name ---*/
/* Or, if peer not found, find it in the global DNS */
/* returns TRUE (-1) on failure, FALSE on success */
-static int create_addr(struct sip_pvt *r, char *opeer)
+static int create_addr(struct sip_pvt *dialog, char *opeer)
{
struct hostent *hp;
struct ast_hostent ahp;
@@ -1750,12 +1815,13 @@
*port = '\0';
port++;
}
- r->sa.sin_family = AF_INET;
+ dialog->sa.sin_family = AF_INET;
+ dialog->timer_t1 = 500; /* Default SIP retransmission timer T1 (RFC 3261) */
p = find_peer(peer, NULL, 1);
if (p) {
found++;
- if (create_addr_from_peer(r, p))
+ if (create_addr_from_peer(dialog, p))
ASTOBJ_UNREF(p, sip_destroy_peer);
}
if (!p) {
@@ -1780,10 +1846,10 @@
}
hp = ast_gethostbyname(hostn, &ahp);
if (hp) {
- ast_copy_string(r->tohost, peer, sizeof(r->tohost));
- memcpy(&r->sa.sin_addr, hp->h_addr, sizeof(r->sa.sin_addr));
- r->sa.sin_port = htons(portno);
- memcpy(&r->recv, &r->sa, sizeof(r->recv));
+ ast_copy_string(dialog->tohost, peer, sizeof(dialog->tohost));
+ memcpy(&dialog->sa.sin_addr, hp->h_addr, sizeof(dialog->sa.sin_addr));
+ dialog->sa.sin_port = htons(portno);
+ memcpy(&dialog->recv, &dialog->sa, sizeof(dialog->recv));
return 0;
} else {
ast_log(LOG_WARNING, "No such host: %s\n", peer);
@@ -1914,6 +1980,10 @@
if (sip_debug_test_pvt(p))
ast_verbose("Destroying call '%s'\n", p->callid);
+
+ if (dumphistory)
+ sip_dump_history(p);
+
if (p->stateid > -1)
ast_extension_state_del(p->stateid, NULL);
if (p->initid > -1)
@@ -1951,6 +2021,7 @@
p->history = p->history->next;
free(hist);
}
+
cur = iflist;
while(cur) {
if (cur == p) {
@@ -1969,17 +2040,19 @@
}
if (p->initid > -1)
ast_sched_del(sched, p->initid);
+
while((cp = p->packets)) {
p->packets = p->packets->next;
- if (cp->retransid > -1)
+ if (cp->retransid > -1) {
ast_sched_del(sched, cp->retransid);
+ }
free(cp);
}
- ast_mutex_destroy(&p->lock);
if (p->chanvars) {
ast_variables_destroy(p->chanvars);
p->chanvars = NULL;
}
+ ast_mutex_destroy(&p->lock);
free(p);
}
@@ -4680,6 +4753,11 @@
if (!r)
return 0;
+ if (recordhistory) {
+ char tmp[80];
+ snprintf(tmp, sizeof(tmp), "Account: %s@%s", r->username, r->hostname);
+ append_history(r->call, "RegistryRenew", tmp);
+ }
/* Since registry's are only added/removed by the the monitor thread, this
may be overkill to reference/dereference at all here */
if (sipdebug)
@@ -4722,7 +4800,6 @@
r->call = NULL;
ast_set_flag(p, SIP_NEEDDESTROY);
/* Pretend to ACK anything just in case */
- /* OEJ: Ack what??? */
__sip_pretend_ack(p);
}
/* If we have a limit, stop registration and give up */
@@ -4780,6 +4857,11 @@
ast_log(LOG_WARNING, "Unable to allocate registration call\n");
return 0;
}
+ if (recordhistory) {
+ char tmp[80];
+ snprintf(tmp, sizeof(tmp), "Account: %s@%s", r->username, r->hostname);
+ append_history(p, "RegistryInit", tmp);
+ }
/* Find address to hostname */
if (create_addr(p,r->hostname)) {
/* we have what we hope is a temporary network error,
@@ -4837,11 +4919,11 @@
/* set up a timeout */
if (auth == NULL) {
if (r->timeout > -1) {
- ast_log(LOG_WARNING, "Still have a registration timeout, %d\n", r->timeout);
+ ast_log(LOG_WARNING, "Still have a registration timeout, #%d - deleting it\n", r->timeout);
ast_sched_del(sched, r->timeout);
}
r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, r);
- ast_log(LOG_DEBUG, "Scheduled a registration timeout for %s : %d\n", r->hostname, r->timeout);
+ ast_log(LOG_DEBUG, "Scheduled a registration timeout for %s id #%d \n", r->hostname, r->timeout);
}
if (strchr(r->username, '@')) {
@@ -7596,7 +7678,7 @@
return RESULT_SUCCESS;
}
-/*--- sip_show_channel: Show details of one call ---*/
+/*--- sip_show_history: Show history details of one call ---*/
static int sip_show_history(int fd, int argc, char *argv[])
{
struct sip_pvt *cur;
@@ -7613,7 +7695,7 @@
ast_mutex_lock(&iflock);
cur = iflist;
while(cur) {
- if (!strncasecmp(cur->callid, argv[3],len)) {
+ if (!strncasecmp(cur->callid, argv[3], len)) {
ast_cli(fd,"\n");
if (cur->subscribed)
ast_cli(fd, " * Subscription\n");
@@ -7638,6 +7720,34 @@
return RESULT_SUCCESS;
}
+/*--- dump_history: Dump SIP history to debug log file at end of
+ lifespan for SIP dialog */
+void sip_dump_history(struct sip_pvt *dialog)
+{
+ int x;
+ struct sip_history *hist;
+
+ if (!dialog)
+ return;
+
+ ast_log(LOG_DEBUG, "\n---------- SIP HISTORY for '%s' \n", dialog->callid);
+ if (dialog->subscribed)
+ ast_log(LOG_DEBUG, " * Subscription\n");
+ else
+ ast_log(LOG_DEBUG, " * SIP Call\n");
+ x = 0;
+ hist = dialog->history;
+ while(hist) {
+ x++;
+ ast_log(LOG_DEBUG, " %d. %s\n", x, hist->event);
+ hist = hist->next;
+ }
+ if (!x)
+ ast_log(LOG_DEBUG, "Call '%s' has no history\n", dialog->callid);
+ ast_log(LOG_DEBUG, "\n---------- END SIP HISTORY for '%s' \n", dialog->callid);
+
+}
+
/*--- handle_request_info: Receive SIP INFO Message ---*/
/* Doesn't read the duration of the DTMF signal */
@@ -7904,6 +8014,11 @@
/* No old challenge */
return -1;
}
+ if (recordhistory) {
+ char tmp[80];
+ snprintf(tmp, sizeof(tmp), "Try: %d", p->authtries);
+ append_history(p, "RegistryAuth", tmp);
+ }
if (sip_debug_test_pvt(p) && p->registry)
ast_verbose("Responding to challenge, registration to domain/host name %s\n", p->registry->hostname);
return transmit_register(p->registry, SIP_REGISTER, digest, respheader);
@@ -8514,7 +8629,7 @@
else
expires_ms -= EXPIRY_GUARD_SECS * 1000;
if (sipdebug)
- ast_log(LOG_NOTICE, "Outbound Registration: Expiry for %s is %d sec (Scheduling reregistration in %d ms)\n", r->hostname, expires, expires_ms);
+ ast_log(LOG_NOTICE, "Outbound Registration: Expiry for %s is %d sec (Scheduling reregistration in %d s)\n", r->hostname, expires, expires_ms/1000);
r->refresh= (int) expires_ms / 1000;
@@ -8531,6 +8646,7 @@
struct sip_peer *peer;
int pingtime;
struct timeval tv;
+
if (resp != 100) {
int statechanged = 0;
int newstate = 0;
@@ -10113,12 +10229,13 @@
return 0;
}
if (peer->call > 0) {
- ast_log(LOG_NOTICE, "Still have a call...\n");
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Still have a QUALIFY dialog active, deleting\n");
sip_destroy(peer->call);
}
p = peer->call = sip_alloc(NULL, NULL, 0, SIP_OPTIONS);
if (!peer->call) {
- ast_log(LOG_WARNING, "Unable to allocate call for poking peer '%s'\n", peer->name);
+ ast_log(LOG_WARNING, "Unable to allocate dialog for poking peer '%s'\n", peer->name);
return -1;
}
memcpy(&p->sa, &peer->addr, sizeof(p->sa));
@@ -10938,6 +11055,8 @@
outboundproxyip.sin_family = AF_INET; /* Type of address: IPv4 */
videosupport = 0;
compactheaders = 0;
+ dumphistory = 0;
+ recordhistory = 0;
relaxdtmf = 0;
callevents = 0;
ourport = DEFAULT_SIP_PORT;
@@ -11053,13 +11172,17 @@
default_expiry = atoi(v->value);
if (default_expiry < 1)
default_expiry = DEFAULT_DEFAULT_EXPIRY;
- } else if (!strcasecmp(v->name, "sipdebug")){
+ } else if (!strcasecmp(v->name, "sipdebug")) {
sipdebug = ast_true(v->value);
- } else if (!strcasecmp(v->name, "registertimeout")){
+ } else if (!strcasecmp(v->name, "dumphistory")) {
+ dumphistory = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "recordhistory")) {
+ recordhistory = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "registertimeout")) {
global_reg_timeout = atoi(v->value);
if (global_reg_timeout < 1)
global_reg_timeout = DEFAULT_REGISTRATION_TIMEOUT;
- } else if (!strcasecmp(v->name, "registerattempts")){
+ } else if (!strcasecmp(v->name, "registerattempts")) {
global_regattempts_max = atoi(v->value);
} else if (!strcasecmp(v->name, "bindaddr")) {
if (!(hp = ast_gethostbyname(v->value, &ahp))) {
@@ -11099,8 +11222,6 @@
ast_parse_allow_disallow(&prefs, &global_capability, v->value, 0);
} else if (!strcasecmp(v->name, "register")) {
sip_register(v->value, v->lineno);
- } else if (!strcasecmp(v->name, "recordhistory")) {
- recordhistory = ast_true(v->value);
} else if (!strcasecmp(v->name, "tos")) {
if (ast_str2tos(v->value, &tos))
ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\n", v->lineno);
More information about the svn-commits
mailing list