/* * ng_pptpgre.c * * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ * $Whistle: ng_pptpgre.c,v 1.7 1999/12/08 00:10:06 archie Exp $ */ /* * PPTP/GRE netgraph node type. * * This node type does the GRE encapsulation as specified for the PPTP * protocol (RFC 2637, section 4). This includes sequencing and * retransmission of frames, but not the actual packet delivery nor * any of the TCP control stream protocol. * * The "upper" hook of this node is suitable for attaching to a "ppp" * node link hook. The "lower" hook of this node is suitable for attaching * to a "ksocket" node on hook "inet/raw/gre". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* GRE packet format, as used by PPTP */ struct greheader { #if BYTE_ORDER == LITTLE_ENDIAN u_char recursion:3; /* recursion control */ u_char ssr:1; /* strict source route */ u_char hasSeq:1; /* sequence number present */ u_char hasKey:1; /* key present */ u_char hasRoute:1; /* routing present */ u_char hasSum:1; /* checksum present */ u_char vers:3; /* version */ u_char flags:4; /* flags */ u_char hasAck:1; /* acknowlege number present */ #elif BYTE_ORDER == BIG_ENDIAN u_char hasSum:1; /* checksum present */ u_char hasRoute:1; /* routing present */ u_char hasKey:1; /* key present */ u_char hasSeq:1; /* sequence number present */ u_char ssr:1; /* strict source route */ u_char recursion:3; /* recursion control */ u_char hasAck:1; /* acknowlege number present */ u_char flags:4; /* flags */ u_char vers:3; /* version */ #else #error BYTE_ORDER is not defined properly #endif u_int16_t proto; /* protocol (ethertype) */ u_int16_t length; /* payload length */ u_int16_t cid; /* call id */ u_int32_t data[0]; /* opt. seq, ack, then data */ }; /* The PPTP protocol ID used in the GRE 'proto' field */ #define PPTP_GRE_PROTO 0x880b /* Bits that must be set a certain way in all PPTP/GRE packets */ #define PPTP_INIT_VALUE ((0x2001 << 16) | PPTP_GRE_PROTO) #define PPTP_INIT_MASK 0xef7fffff /* Min and max packet length */ #define PPTP_MAX_PAYLOAD (0xffff - sizeof(struct greheader) - 8) /* All times are scaled by this (PPTP_TIME_SCALE time units = 1 sec.) */ #define PPTP_TIME_SCALE 1000 typedef u_int32_t pptptime_t; /* Acknowledgment timeout parameters and functions */ #define PPTP_XMIT_WIN 8 /* max xmit window */ #define PPTP_MIN_RTT (PPTP_TIME_SCALE / 10) /* 1/10 second */ #define PPTP_MAX_TIMEOUT (10 * PPTP_TIME_SCALE) /* 10 seconds */ #define PPTP_ACK_ALPHA(x) ((x) >> 3) /* alpha = 0.125 */ #define PPTP_ACK_BETA(x) ((x) >> 2) /* beta = 0.25 */ #define PPTP_ACK_CHI(x) ((x) << 2) /* chi = 4 */ #define PPTP_ACK_DELTA(x) ((x) << 1) /* delta = 2 */ /* We keep packet retransmit and acknowlegement state in this struct */ struct ng_pptpgre_ackp { int32_t ato; /* adaptive time-out value */ int32_t rtt; /* round trip time estimate */ int32_t dev; /* deviation estimate */ u_int16_t xmitWin; /* size of xmit window */ u_char sackTimerRunning;/* send ack timer is running */ u_int32_t winAck; /* seq when xmitWin will grow */ struct callout_handle sackTimer; /* send ack timer */ struct callout_handle rackTimer; /* recv ack timer */ pptptime_t timeSent[PPTP_XMIT_WIN]; }; /* When we recieve a packet, we wait to see if there's an outgoing packet we can piggy-back the ACK off of. These parameters determine the mimimum and maxmimum length of time we're willing to wait in order to do that. */ #define PPTP_MAX_ACK_DELAY ((int) (0.25 * PPTP_TIME_SCALE)) /* Node private data */ struct ng_pptpgre_private { hook_p upper; /* hook to upper layers */ hook_p lower; /* hook to lower layers */ struct ng_pptpgre_conf conf; /* configuration info */ struct ng_pptpgre_ackp ackp; /* packet transmit ack state */ u_int32_t recvSeq; /* last seq # we rcv'd */ u_int32_t xmitSeq; /* last seq # we sent */ u_int32_t recvAck; /* last seq # peer ack'd */ u_int32_t xmitAck; /* last seq # we ack'd */ struct timeval startTime; /* time node was created */ }; typedef struct ng_pptpgre_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_pptpgre_constructor; static ng_rcvmsg_t ng_pptpgre_rcvmsg; static ng_shutdown_t ng_pptpgre_rmnode; static ng_newhook_t ng_pptpgre_newhook; static ng_rcvdata_t ng_pptpgre_rcvdata; static ng_disconnect_t ng_pptpgre_disconnect; /* Helper functions */ static int ng_pptpgre_xmit(node_p node, struct mbuf *m, meta_p meta); static int ng_pptpgre_recv(node_p node, struct mbuf *m, meta_p meta); static void ng_pptpgre_start_recv_ack_timer(node_p node); static void ng_pptpgre_recv_ack_timeout(void *arg); static void ng_pptpgre_send_ack_timeout(void *arg); static void ng_pptpgre_reset(node_p node); static pptptime_t ng_pptpgre_time(node_p node); /* Parse type for struct ng_pptpgre_conf */ static const struct ng_parse_struct_info ng_pptpgre_conf_type_info = NG_PPTPGRE_CONF_TYPE_INFO; static const struct ng_parse_type ng_pptpgre_conf_type = { &ng_parse_struct_type, &ng_pptpgre_conf_type_info, }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_pptpgre_cmdlist[] = { { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_SET_CONFIG, "setconfig", &ng_pptpgre_conf_type, NULL }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GET_CONFIG, "getconfig", NULL, &ng_pptpgre_conf_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_pptpgre_typestruct = { NG_VERSION, NG_PPTPGRE_NODE_TYPE, NULL, ng_pptpgre_constructor, ng_pptpgre_rcvmsg, ng_pptpgre_rmnode, ng_pptpgre_newhook, NULL, NULL, ng_pptpgre_rcvdata, ng_pptpgre_rcvdata, ng_pptpgre_disconnect, ng_pptpgre_cmdlist }; NETGRAPH_INIT(pptpgre, &ng_pptpgre_typestruct); #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_pptpgre_constructor(node_p *nodep) { priv_p priv; int error; /* Allocate private structure */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_WAITOK); if (priv == NULL) return (ENOMEM); bzero(priv, sizeof(*priv)); /* Call generic node constructor */ if ((error = ng_make_node_common(&ng_pptpgre_typestruct, nodep))) { FREE(priv, M_NETGRAPH); return (error); } (*nodep)->private = priv; /* Initialize state */ callout_handle_init(&priv->ackp.sackTimer); callout_handle_init(&priv->ackp.rackTimer); /* Done */ return (0); } /* * Give our OK for a hook to be added. */ static int ng_pptpgre_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = node->private; hook_p *hookPtr; /* Check hook name */ if (strcmp(name, NG_PPTPGRE_HOOK_UPPER) == 0) hookPtr = &priv->upper; else if (strcmp(name, NG_PPTPGRE_HOOK_LOWER) == 0) hookPtr = &priv->lower; else return (EINVAL); /* See if already connected */ if (*hookPtr != NULL) return (EISCONN); /* OK */ *hookPtr = hook; return (0); } /* * Receive a control message. */ static int ng_pptpgre_rcvmsg(node_p node, struct ng_mesg *msg, const char *raddr, struct ng_mesg **rptr) { const priv_p priv = node->private; struct ng_mesg *resp = NULL; int error = 0; switch (msg->header.typecookie) { case NGM_PPTPGRE_COOKIE: switch (msg->header.cmd) { case NGM_PPTPGRE_SET_CONFIG: { struct ng_pptpgre_conf *const newConf = (struct ng_pptpgre_conf *) msg->data; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*newConf)) ERROUT(EINVAL); ng_pptpgre_reset(node); /* reset on configure */ priv->conf = *newConf; break; } case NGM_PPTPGRE_GET_CONFIG: NG_MKRESPONSE(resp, msg, sizeof(priv->conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&priv->conf, resp->data, sizeof(priv->conf)); break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } if (rptr) *rptr = resp; else if (resp) FREE(resp, M_NETGRAPH); done: FREE(msg, M_NETGRAPH); return (error); } /* * Receive incoming data on a hook. */ static int ng_pptpgre_rcvdata(hook_p hook, struct mbuf *m, meta_p meta) { const node_p node = hook->node; const priv_p priv = node->private; /* If not configured, reject */ if (!priv->conf.enabled) { NG_FREE_DATA(m, meta); return (ENXIO); } /* Treat as xmit or recv data */ if (hook == priv->upper) return ng_pptpgre_xmit(node, m, meta); if (hook == priv->lower) return ng_pptpgre_recv(node, m, meta); panic("%s: weird hook", __FUNCTION__); } /* * Destroy node */ static int ng_pptpgre_rmnode(node_p node) { const priv_p priv = node->private; /* Cancel timers */ ng_pptpgre_reset(node); /* Take down netgraph node */ node->flags |= NG_INVALID; ng_cutlinks(node); ng_unname(node); bzero(priv, sizeof(*priv)); FREE(priv, M_NETGRAPH); node->private = NULL; ng_unref(node); return (0); } /* * Hook disconnection */ static int ng_pptpgre_disconnect(hook_p hook) { const node_p node = hook->node; const priv_p priv = node->private; /* Zero out hook pointer */ if (hook == priv->upper) priv->upper = NULL; else if (hook == priv->lower) priv->lower = NULL; else panic("%s: unknown hook", __FUNCTION__); /* Go away if no longer connected to anything */ if (node->numhooks == 0) ng_rmnode(node); return (0); } /************************************************************************* TRANSMIT AND RECEIVE FUNCTIONS *************************************************************************/ /* * Transmit an outgoing frame, or just an ack if m is NULL. */ static int ng_pptpgre_xmit(node_p node, struct mbuf *m, meta_p meta) { const priv_p priv = node->private; struct ng_pptpgre_ackp *const a = &priv->ackp; u_char buf[sizeof(struct greheader) + 2 * sizeof(u_int32_t)]; struct greheader *const gre = (struct greheader *)buf; int grelen, error; /* Is our transmit window full? */ if (m != NULL && priv->xmitSeq - priv->recvAck >= a->xmitWin) { NG_FREE_DATA(m, meta); return (ENOBUFS); } /* Sanity check frame length */ if (m != NULL && m->m_pkthdr.len > PPTP_MAX_PAYLOAD) { NG_FREE_DATA(m, meta); return (EMSGSIZE); } /* Build GRE header */ ((u_int32_t *) gre)[0] = htonl(PPTP_INIT_VALUE); gre->length = (m != NULL) ? htons((u_short)m->m_pkthdr.len) : 0; gre->cid = htons(priv->conf.peerCid); /* Include sequence number if packet contains any data */ if (m != NULL) { gre->hasSeq = 1; a->timeSent[priv->xmitSeq - priv->recvAck] = ng_pptpgre_time(node); priv->xmitSeq++; gre->data[0] = htonl(priv->xmitSeq); if (priv->xmitSeq == priv->recvAck + 1) ng_pptpgre_start_recv_ack_timer(node); } /* Include acknowledgement (and stop send ack timer) if needed */ if (priv->xmitAck < priv->recvSeq) { gre->hasAck = 1; priv->xmitAck = priv->recvSeq; gre->data[gre->hasSeq] = htonl(priv->xmitAck); if (a->sackTimerRunning) { untimeout(ng_pptpgre_send_ack_timeout, node, a->sackTimer); a->sackTimerRunning = 0; } } /* Prepend GRE header to outgoing frame */ grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); if (m == NULL) { MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { NG_FREE_META(meta); return (ENOBUFS); } m->m_len = m->m_pkthdr.len = grelen; m->m_pkthdr.rcvif = NULL; } else { M_PREPEND(m, grelen, M_NOWAIT); if (m == NULL || (m->m_len < grelen && (m = m_pullup(m, grelen)) == NULL)) { NG_FREE_META(meta); return (ENOBUFS); } } bcopy(gre, mtod(m, u_char *), grelen); /* Deliver packet */ NG_SEND_DATA(error, priv->lower, m, meta); return (error); } /* * Handle an incoming packet. The packet includes the IP header. */ static int ng_pptpgre_recv(node_p node, struct mbuf *m, meta_p meta) { const priv_p priv = node->private; int iphlen, grelen, extralen; struct greheader *gre; struct ip *ip; int error = 0; /* Sanity check packet length */ if (m->m_pkthdr.len < sizeof(*ip) + sizeof(*gre)) { bad: NG_FREE_DATA(m, meta); return (EINVAL); } /* Safely pull up the complete IP+GRE headers */ if (m->m_len < sizeof(*ip) + sizeof(*gre) && (m = m_pullup(m, sizeof(*ip) + sizeof(*gre))) == NULL) { NG_FREE_META(meta); return (ENOBUFS); } ip = mtod(m, struct ip *); iphlen = ip->ip_hl << 2; if (m->m_len < iphlen + sizeof(*gre)) { if ((m = m_pullup(m, iphlen + sizeof(*gre))) == NULL) { NG_FREE_META(meta); return (ENOBUFS); } ip = mtod(m, struct ip *); } gre = (struct greheader *)((u_char *)ip + iphlen); grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); if (m->m_pkthdr.len < iphlen + grelen) goto bad; if (m->m_len < iphlen + grelen) { if ((m = m_pullup(m, iphlen + grelen)) == NULL) { NG_FREE_META(meta); return (ENOBUFS); } ip = mtod(m, struct ip *); gre = (struct greheader *)((u_char *)ip + iphlen); } /* Sanity check packet length and GRE header bits */ extralen = m->m_pkthdr.len - (iphlen + grelen + (u_int16_t)ntohs(gre->length)); if (extralen < 0) goto bad; if ((ntohl(*((u_int32_t *)gre)) & PPTP_INIT_MASK) != PPTP_INIT_VALUE) goto bad; if (ntohs(gre->cid) != priv->conf.cid) goto bad; /* Look for peer ack */ if (gre->hasAck) { struct ng_pptpgre_ackp *const a = &priv->ackp; const u_int32_t ack = ntohl(gre->data[gre->hasSeq]); const int index = ack - priv->recvAck - 1; const long sample = ng_pptpgre_time(node) - a->timeSent[index]; long diff; /* Sanity check ack value */ if (ack <= priv->recvAck) /* ack already timed out */ goto bad; if (ack > priv->xmitSeq) /* we never sent it! */ goto bad; priv->recvAck = ack; /* Update adaptive timeout stuff */ diff = sample - a->rtt; a->rtt += PPTP_ACK_ALPHA(diff); if (diff < 0) diff = -diff; a->dev += PPTP_ACK_BETA(diff - a->dev); a->ato = a->rtt + (u_int) (PPTP_ACK_CHI(a->dev)); if (a->ato > PPTP_MAX_TIMEOUT) a->ato = PPTP_MAX_TIMEOUT; ovbcopy(a->timeSent + index + 1, a->timeSent, sizeof(*a->timeSent) * (PPTP_XMIT_WIN - (index + 1))); if (ack >= a->winAck && a->xmitWin < PPTP_XMIT_WIN) { a->xmitWin++; a->winAck = ack + a->xmitWin; } /* (Re)start receive ACK timer as necessary */ ng_pptpgre_start_recv_ack_timer(node); } /* See if frame contains any data */ if (gre->hasSeq) { struct ng_pptpgre_ackp *const a = &priv->ackp; const u_int32_t seq = ntohl(gre->data[0]); /* Sanity check sequence number */ if (seq <= priv->recvSeq) /* out-of-order or dup */ goto bad; priv->recvSeq = seq; /* We need to acknowledge this packet; do it soon... */ if (!a->sackTimerRunning) { long ackTimeout; /* Take half of the estimated round trip time */ ackTimeout = (a->rtt >> 1); /* If too soon, just send one right now */ if (!priv->conf.enableDelayedAck) ng_pptpgre_xmit(node, NULL, NULL); else { /* send the ack later */ if (ackTimeout > PPTP_MAX_ACK_DELAY) ackTimeout = PPTP_MAX_ACK_DELAY; a->sackTimer = timeout( ng_pptpgre_send_ack_timeout, node, ackTimeout * hz / PPTP_TIME_SCALE); a->sackTimerRunning = 1; } } /* Trim mbuf down to internal payload */ m_adj(m, iphlen + grelen); if (extralen > 0) m_adj(m, -extralen); /* Deliver frame to upper layers */ NG_SEND_DATA(error, priv->upper, m, meta); } else NG_FREE_DATA(m, meta); /* no data to deliver */ return (error); } /************************************************************************* TIMER RELATED FUNCTIONS *************************************************************************/ /* * Set a timer for the peer's acknowledging our oldest unacknowledged * sequence number. If we get an ack for this sequence number before * the timer goes off, we cancel the timer. Resets currently running * recv ack timer, if any. */ static void ng_pptpgre_start_recv_ack_timer(node_p node) { const priv_p priv = node->private; struct ng_pptpgre_ackp *const a = &priv->ackp; int remain; /* Stop current recv ack timer, if any */ untimeout(ng_pptpgre_recv_ack_timeout, node, a->rackTimer); if (priv->recvAck == priv->xmitSeq) return; /* Compute how long until oldest unack'd packet times out, and reset the timer to that time. */ remain = (a->timeSent[0] + a->ato) - ng_pptpgre_time(node); if (remain < 0) remain = 0; a->rackTimer = timeout(ng_pptpgre_recv_ack_timeout, node, remain * hz / PPTP_TIME_SCALE); } /* * The peer has failed to acknowledge the oldest unacknowledged sequence * number within the time allotted. Update our adaptive timeout parameters * and reset/restart the recv ack timer. */ static void ng_pptpgre_recv_ack_timeout(void *arg) { const node_p node = arg; const priv_p priv = node->private; struct ng_pptpgre_ackp *const a = &priv->ackp; /* Update adaptive timeout stuff */ a->rtt = PPTP_ACK_DELTA(a->rtt); a->ato = a->rtt + PPTP_ACK_CHI(a->dev); if (a->ato > PPTP_MAX_TIMEOUT) a->ato = PPTP_MAX_TIMEOUT; priv->recvAck++; /* assume packet was lost */ a->winAck = priv->recvAck + a->xmitWin; /* reset win expand time */ ovbcopy(a->timeSent + 1, a->timeSent, /* shift xmit window times */ sizeof(*a->timeSent) * (PPTP_XMIT_WIN - 1)); a->xmitWin = (a->xmitWin + 1) / 2; /* shrink transmit window */ /* Restart timer if there are any more outstanding frames */ if (priv->recvAck != priv->xmitSeq) ng_pptpgre_start_recv_ack_timer(node); } /* * We've waited as long as we're willing to wait before sending an * acknowledgement to the peer for received frames. We had hoped to * be able to piggy back our acknowledgement on an outgoing data frame, * but apparently there haven't been any since. So send the ack now. */ static void ng_pptpgre_send_ack_timeout(void *arg) { const node_p node = arg; const priv_p priv = node->private; struct ng_pptpgre_ackp *const a = &priv->ackp; /* Send a frame with an ack but no payload */ a->sackTimerRunning = 0; ng_pptpgre_xmit(node, NULL, NULL); } /************************************************************************* MISC FUNCTIONS *************************************************************************/ /* * Reset state */ static void ng_pptpgre_reset(node_p node) { const priv_p priv = node->private; struct ng_pptpgre_ackp *const a = &priv->ackp; /* Reset adaptive timeout state */ a->ato = PPTP_MAX_TIMEOUT; a->rtt = priv->conf.peerPpd * PPTP_TIME_SCALE / 10; /* ppd in 10ths */ if (a->rtt < PPTP_MIN_RTT) a->rtt = PPTP_MIN_RTT; a->dev = 0; a->xmitWin = (priv->conf.recvWin + 1) / 2; if (a->xmitWin < 1) a->xmitWin = 1; if (a->xmitWin > PPTP_XMIT_WIN) a->xmitWin = PPTP_XMIT_WIN; a->winAck = a->xmitWin; /* Reset sequence numbers */ priv->recvSeq = 0; priv->recvAck = 0; priv->xmitSeq = 0; priv->xmitAck = 0; /* Reset start time */ getmicrotime(&priv->startTime); /* Stop timers */ untimeout(ng_pptpgre_send_ack_timeout, node, a->sackTimer); untimeout(ng_pptpgre_recv_ack_timeout, node, a->rackTimer); a->sackTimerRunning = 0; } /* * Return the current time scaled & translated to our internally used format. */ static pptptime_t ng_pptpgre_time(node_p node) { const priv_p priv = node->private; struct timeval tv; getmicrotime(&tv); if (tv.tv_sec < priv->startTime.tv_sec || (tv.tv_sec == priv->startTime.tv_sec && tv.tv_usec < priv->startTime.tv_usec)) return (0); timevalsub(&tv, &priv->startTime); tv.tv_sec *= PPTP_TIME_SCALE; tv.tv_usec /= 1000000 / PPTP_TIME_SCALE; return(tv.tv_sec + tv.tv_usec); }