Consolidate TCP option parsing
Some checks are pending
nmap multiplatform autobuilds / build (arm64, gcc, ubuntu-latest-gcc-arm64, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, freebsd-15-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, macos-15-clang, macos-15) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, macos-26-clang, macos-26) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, netbsd-10-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, openbsd-7-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, solaris-11-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, ubuntu-latest-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (egcc, openbsd-7-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, freebsd-15-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, netbsd-10-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, solaris-11-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, ubuntu-latest-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (msvc, windows-latest-msvc, windows-latest) (push) Waiting to run

This commit is contained in:
dmiller 2026-06-24 14:47:12 +00:00
parent 486fa2ebdd
commit c86a45ef6e
6 changed files with 367 additions and 294 deletions

View file

@ -813,6 +813,27 @@ const u8 *TCPHeader::getOptions(size_t *optslen) const {
return this->h.options;
} /* End of getOptions() */
struct tcpopt_atindex_ctx {
unsigned int index;
unsigned int found;
nping_tcp_opt_t result;
tcpopt_atindex_ctx() : index(0), found(0) {
memset(&result, 0, sizeof(result));
}
};
static bool tcpopt_atindex(u8 op, u8 oplen, const u8 *data, void *ctx)
{
tcpopt_atindex_ctx *args = static_cast<tcpopt_atindex_ctx *>(ctx);
if (args->index == args->found) {
args->result.type = op;
args->result.len = oplen;
args->result.value = data + 2;
return false;
}
args->found += 1;
return true;
}
/* Returns the index-th option in the TCP header. On success it returns a
* structure filled with option information. If there is no index-th option,
@ -824,61 +845,13 @@ const u8 *TCPHeader::getOptions(size_t *optslen) const {
* would be set to zero and the "value" field should NOT be accessed, as it
* will not contain reliable information. */
nping_tcp_opt_t TCPHeader::getOption(unsigned int index) const {
nping_tcp_opt_t *curr_opt=NULL;
u8 *curr_pnt=(u8 *)this->h.options;
int bytes_left=this->length - TCP_HEADER_LEN;
assert((this->length - TCP_HEADER_LEN) == this->tcpoptlen);
unsigned int optsfound=0;
nping_tcp_opt_t result;
memset(&result, 0, sizeof(nping_tcp_opt_t));
while(bytes_left>0){
/* Use the opts structure as a template to access current option. It is
* OK to use it because we only access the first two elements. */
curr_opt=(nping_tcp_opt_t *)curr_pnt;
/* If we are right in the option that the caller wants, just return it */
if(optsfound==index){
result.type=curr_opt->type;
if(result.type==TCPOPT_EOL || result.type==TCPOPT_NOOP)
result.len=1;
else
result.len=curr_opt->len;
result.value=(u8 *)curr_pnt+2;
return result;
}
/* Otherwise, we have to parse it, so we can skip it and access the next
* option */
switch(curr_opt->type){
/* EOL or NOOP
+-+-+-+-+-+-+-+-+
| X |
+-+-+-+-+-+-+-+-+ */
case TCPOPT_EOL:
goto out;
case TCPOPT_NOOP:
curr_pnt++; /* Skip one octet */
bytes_left--;
break;
/* TLV encoded option */
default:
/* If we don't have as many octets as the option advertises, the
* option is bogus. Return failure. */
if(bytes_left<curr_opt->len)
return result;
curr_pnt+=curr_opt->len;
bytes_left-=curr_opt->len;
break;
}
optsfound++;
TCPOptions opts;
tcpopt_atindex_ctx ctx;
if (opts.fromTCPHeader(*this)) {
ctx.index = index;
opts.foreachOpt(tcpopt_atindex, &ctx);
}
out:
return result;
return ctx.result;
}
@ -935,3 +908,71 @@ const char *TCPHeader::optcode2str(u8 optcode){
} /* End of optcode2str() */
bool TCPOptions::fromTCPPacket(const u8 *tcppkt, int tcplen)
{
tcpopts = NULL;
optslen = 0;
if (tcplen < TCP_HEADER_LEN)
return false;
u8 data_offset = tcppkt[12] >> 4;
if (data_offset < 5)
return false;
tcpopts = tcppkt + TCP_HEADER_LEN;
optslen = MIN(4 * data_offset, tcplen) - TCP_HEADER_LEN;
if (optslen == 0)
tcpopts = NULL;
return true;
}
bool TCPOptions::fromBuffer(const u8 *tcpoptions, int optionslen)
{
if (!tcpoptions || optionslen <= 0)
return false;
tcpopts = tcpoptions;
optslen = optionslen;
return true;
}
bool TCPOptions::fromTCPHeader(const TCPHeader &T)
{
size_t len = 0;
tcpopts = T.getOptions(&len);
if (len > INT_MAX) {
tcpopts = NULL;
return false;
}
optslen = len;
return tcpopts != NULL;
}
bool TCPOptions::foreachOpt(tcpopt_callback cb, void *ctx) const
{
const u8 *p = tcpopts;
int len = optslen;
while (len > 0) {
int op = p[0];
int oplen = 1;
switch (op) {
case 0: /* TCPOPT_EOL */
case 1: /* TCPOPT_NOP */
break;
default: /* TLV option */
if (len < 2)
return false;
oplen = p[1];
if (oplen < 2)
return false; /* No infinite loops, please */
if (oplen > len)
return false; /* Not enough space */
break;
}
if (!cb(op, oplen, p, ctx))
return true;
len -= oplen;
p += oplen;
}
return len == 0;
}

View file

@ -123,7 +123,7 @@
struct nping_tcp_opt {
u8 type; /* Option type code. */
u8 len; /* Option length. */
u8 *value; /* Option value */
const u8 *value; /* Option value */
}__attribute__((__packed__));
typedef struct nping_tcp_opt nping_tcp_opt_t;
@ -257,4 +257,26 @@ class TCPHeader : public TransportLayerElement {
}; /* End of class TCPHeader */
/* This callback will be called for each option in the buffer.
* Return true to continue processing options.
* Return false to stop processing options. */
typedef bool (*tcpopt_callback)(u8 op, u8 oplen, const u8 *data, void *ctx);
class TCPOptions {
public:
/* Note: this class parses in-place and does not make a copy of the data. The
* data pointed to by tcppkt must remain allocated as long as the TCPOptions
* object is in use. */
TCPOptions() : tcpopts(NULL), optslen(0) {}
bool fromTCPPacket(const u8 *tcppkt, int tcplen);
bool fromBuffer(const u8 *tcpoptions, int optionslen);
bool fromTCPHeader(const TCPHeader &T);
/* Returns true if no errors were encountered, even if the callback returns false. */
bool foreachOpt(tcpopt_callback cb, void *ctx) const;
private:
const u8 *tcpopts;
int optslen;
};
#endif /* __TCPHEADER_H__ */

View file

@ -120,6 +120,7 @@ typedef unsigned __int8 u_int8_t;
#endif
#include "netutil.h"
#include "TCPHeader.h"
#if HAVE_NET_IF_H
#ifndef NET_IF_H /* This guarding is needed for at least some versions of OpenBSD */
@ -1392,6 +1393,42 @@ const char *proto2ascii_uppercase(u8 proto) {
return proto2ascii_case(proto, 1);
}
struct tcpopt_info_ctx {
char *p;
char *end;
bool valid;
tcpopt_info_ctx() : p(NULL), end(NULL), valid(true) {}
bool check_length(size_t len) const {
return (end - p) >= len;
}
void put_str(const char *str) {
if (p >= end)
return;
int r = Snprintf(p, end - p, "%s", str);
if (r < 0)
p = end;
else {
p += r;
if (p > end)
p = end;
}
}
void fmt_num(const char *fmt, unsigned int n) {
if (p >= end)
return;
int r = Snprintf(p, end - p, fmt, n);
if (r < 0)
p = end;
else {
p += r;
if (p > end)
p = end;
}
}
};
static bool tcpopt_info(u8 opcode, u8 len, const u8 *data, void *ctx);
/* Get an ASCII information about a tcp option which is pointed by
optp, with a length of len. The result is stored in the result
buffer. The result may look like "<mss 1452,sackOK,timestamp
@ -1399,130 +1436,93 @@ const char *proto2ascii_uppercase(u8 proto) {
void tcppacketoptinfo(u8 *optp, int len, char *result, int bufsize) {
assert(optp);
assert(result);
char *p, ch;
u8 *q;
int opcode;
u16 tmpshort;
u32 tmpword1, tmpword2;
unsigned int i=0;
assert(bufsize > 0);
memset(result, 0, bufsize);
p = result;
*p = '\0';
q = optp;
ch = '<';
TCPOptions opts;
if (bufsize < 2 || !opts.fromBuffer(optp, len))
return;
tcpopt_info_ctx ctx;
ctx.p = result;
ctx.end = result + bufsize - 1;
ctx.put_str("<");
while (len > 0 && bufsize > 2) {
Snprintf(p, bufsize, "%c", ch);
bufsize--;
p++;
opcode = *q++;
if (!opcode) { /* End of List */
Snprintf(p, bufsize, "eol");
bufsize -= strlen(p);
p += strlen(p);
len--;
} else if (opcode == 1) { /* No Op */
Snprintf(p, bufsize, "nop");
bufsize -= strlen(p);
p += strlen(p);
len--;
} else if (opcode == 2) { /* MSS */
if (len < 4)
break; /* MSS has 4 bytes */
q++;
memcpy(&tmpshort, q, 2);
Snprintf(p, bufsize, "mss %hu", (unsigned short) ntohs(tmpshort));
bufsize -= strlen(p);
p += strlen(p);
q += 2;
len -= 4;
} else if (opcode == 3) { /* Window Scale */
if (len < 3)
break; /* Window Scale option has 3 bytes */
q++;
Snprintf(p, bufsize, "wscale %u", *q);
bufsize -= strlen(p);
p += strlen(p);
q++;
len -= 3;
} else if (opcode == 4) { /* SACK permitted */
if (len < 2)
break; /* SACK permitted option has 2 bytes */
Snprintf(p, bufsize, "sackOK");
bufsize -= strlen(p);
p += strlen(p);
q++;
len -= 2;
} else if (opcode == 5) { /* SACK */
unsigned sackoptlen = *q;
if ((unsigned) len < sackoptlen)
break;
/* This would break parsing, so it's best to just give up */
if (sackoptlen < 2)
break;
q++;
if ((sackoptlen - 2) == 0 || ((sackoptlen - 2) % 8 != 0)) {
Snprintf(p, bufsize, "malformed sack");
bufsize -= strlen(p);
p += strlen(p);
} else {
Snprintf(p, bufsize, "sack %d ", (sackoptlen - 2) / 8);
bufsize -= strlen(p);
p += strlen(p);
for (i = 0; i < sackoptlen - 2; i += 8) {
memcpy(&tmpword1, q + i, 4);
memcpy(&tmpword2, q + i + 4, 4);
Snprintf(p, bufsize, "{%u:%u}", tmpword1, tmpword2);
bufsize -= strlen(p);
p += strlen(p);
}
}
q += sackoptlen - 2;
len -= sackoptlen;
} else if (opcode == 8) { /* Timestamp */
if (len < 10)
break; /* Timestamp option has 10 bytes */
q++;
memcpy(&tmpword1, q, 4);
memcpy(&tmpword2, q + 4, 4);
Snprintf(p, bufsize, "timestamp %lu %lu", (unsigned long) ntohl(tmpword1),
(unsigned long) ntohl(tmpword2));
bufsize -= strlen(p);
p += strlen(p);
q += 8;
len -= 10;
}
ch = ',';
}
if (len > 0) {
*result = '\0';
if (!opts.foreachOpt(tcpopt_info, &ctx) || !ctx.valid) {
Snprintf(result, bufsize, "<Invalid TCP options>");
return;
}
Snprintf(p, bufsize, ">");
char *q = ctx.p - 1;
if (*q != ',')
q++;
*q++ = '>';
*q++ = '\0';
return;
}
static bool tcpopt_info(u8 opcode, u8 len, const u8 *data, void *ctx)
{
tcpopt_info_ctx *args = static_cast<tcpopt_info_ctx *>(ctx);
if (!args->check_length(4))
return false;
const u8 *q = data + 2;
switch (opcode) {
case 0: /* End of List */
args->put_str("eol,");
break;
case 1: /* No Op */
args->put_str("nop,");
break;
case 2: /* MSS */
if (len < 4) {
args->valid = false;
break; /* MSS has 4 bytes */
}
args->fmt_num("mss %u,", (q[0] << 8) + q[1]);
case 3: /* Window Scale */
if (len < 3) {
args->valid = false;
break; /* Window Scale option has 3 bytes */
}
args->fmt_num("wscale %u,", q[0]);
break;
case 4: /* SACK permitted */
if (len < 2) {
args->valid = false;
break; /* SACK permitted option has 2 bytes */
}
args->put_str("sackOK,");
break;
case 5: /* SACK */
if (len < 2) {
args->valid = false;
break;
}
if ((len - 2) == 0 || ((len - 2) % 8 != 0)) {
args->put_str("malformed sack,");
} else {
args->fmt_num("sack %u ", (len - 2) / 8);
for (int i = 0; i < len - 2; i += 8) {
args->fmt_num("{%u:", (q[i] << 24) + (q[i+1] << 16) + (q[i+2] << 8) + q[i+3]);
args->fmt_num("%u}", (q[i+4] << 24) + (q[i+5] << 16) + (q[i+6] << 8) + q[i+7]);
}
args->put_str(",");
}
break;
case 8: /* Timestamp */
if (len < 10) {
args->valid = false;
break; /* Timestamp option has 10 bytes */
}
args->fmt_num("timestamp %u ", (q[0] << 24) + (q[1] << 16) + (q[2] << 8) + q[3]);
args->fmt_num("%u,", (q[4] << 24) + (q[5] << 16) + (q[6] << 8) + q[7]);
break;
default:
break;
}
return args->valid;
}
/* A trivial function used with qsort to sort the routes by netmask and metric */

View file

@ -2684,7 +2684,7 @@ bool HostOsScan::processTOpsResp(HostOsScanStats *hss, const u8 *tcp, int tcplen
if (hss->FP_TOps || hss->TOps_AVs[replyNo])
return false;
int opsParseResult = get_tcpopt_string(tcp, tcplen, this->tcpMss, ops_buf, sizeof(ops_buf));
int opsParseResult = get_tcpopt_string(tcp, tcplen, ops_buf, sizeof(ops_buf));
if (opsParseResult <= 0) {
if (opsParseResult < 0 && o.debugging)
@ -2740,7 +2740,7 @@ bool HostOsScan::processTEcnResp(HostOsScanStats *hss, const struct ip *ip, cons
test.setAVal("W", hss->target->FPR->cp_hex(ntohs(tcp->th_win)));
/* Now for the TCP options ... */
int opsParseResult = get_tcpopt_string(tcppkt, tcplen, this->tcpMss, ops_buf, sizeof(ops_buf));
int opsParseResult = get_tcpopt_string(tcppkt, tcplen, ops_buf, sizeof(ops_buf));
if (opsParseResult <= 0) {
if (opsParseResult < 0 && o.debugging)
@ -2880,7 +2880,7 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, cons
char ops_buf[256];
/* Now for the TCP options ... */
int opsParseResult = get_tcpopt_string(tcppkt, tcplen, this->tcpMss, ops_buf, sizeof(ops_buf));
int opsParseResult = get_tcpopt_string(tcppkt, tcplen, ops_buf, sizeof(ops_buf));
if (opsParseResult <= 0) {
if (opsParseResult < 0 && o.debugging)
error("Option parse error for T%d response from %s.", replyNo, hss->target->targetipstr());
@ -3136,86 +3136,103 @@ bool HostOsScan::processTIcmpResp(HostOsScanStats *hss, const struct ip *ip, con
return true;
}
int HostOsScan::get_tcpopt_string(const u8 *tcp, int tcplen, int mss, char *result, int maxlen) const {
struct tcpopt_string_ctx {
char *p;
const u8 *q;
u16 tmpshort;
u32 tmpword;
int length;
int opcode;
char *end;
bool valid;
tcpopt_string_ctx() : p(NULL), end(NULL), valid(true) {}
bool check_length(size_t len) const {
return (end - p) >= len;
}
void put(char c) {
assert(end > p);
*p++ = c;
}
void put_hex(unsigned int u) {
int w = sprintf(p, "%X", u);
p += w;
}
};
p = result;
struct tcp_hdr hdr;
memcpy(&hdr, tcp, sizeof(hdr));
length = MIN(hdr.th_off * 4, tcplen) - sizeof(struct tcp_hdr);
q = tcp + sizeof(struct tcp_hdr);
static bool tcpopt_tostring(u8 op, u8 oplen, const u8 *data, void *ctx)
{
tcpopt_string_ctx *args = static_cast<tcpopt_string_ctx *>(ctx);
if (!args->check_length(1))
return false;
const u8 *q = data + 2;
switch (op) {
case 0: /* End of List */
args->put('L');
break;
case 1: /* No Op */
args->put('N');
break;
case 2: /* MSS */
if (oplen < 4) {
args->valid = false;
break; /* MSS has 4 bytes */
}
args->put('M');
if (!args->check_length(4))
return false;
args->put_hex((q[0] << 8) + q[1]);
break;
case 3:/* Window Scale */
if (oplen < 3) {
args->valid = false;
break; /* Window Scale option has 3 bytes */
}
args->put('W');
if (!args->check_length(2))
return false;
args->put_hex(q[0]);
break;
case 4:/* SACK permitted */
if (oplen < 2) {
args->valid = false;
break; /* SACK permitted option has 2 bytes */
}
args->put('S');
break;
case 8: /* Timestamp */
if (oplen < 10) {
args->valid = false;
break; /* Timestamp option has 10 bytes */
}
args->put('T');
if (!args->check_length(2))
return false;
args->put((q[0] || q[1] || q[2] || q[3]) ? '1' : '0');
args->put((q[4] || q[5] || q[6] || q[7]) ? '1' : '0');
break;
default:
break;
}
return args->valid;
}
int HostOsScan::get_tcpopt_string(const u8 *tcp, int tcplen, char *result, int maxlen) const {
assert(tcp);
assert(result);
assert(maxlen > 0);
memset(result, 0, maxlen);
/*
* Example parsed result: M5B4ST11NW2
* MSS, Sack Permitted, Timestamp with both value not zero, Nop, WScale with value 2
*/
/* Be aware of the max increment value for p in parsing,
* now is 5 = strlen("Mxxxx") <-> MSS Option
*/
while (length > 0 && (p - result) < (maxlen - 5)) {
opcode = *q++;
if (!opcode) { /* End of List */
*p++ = 'L';
length--;
} else if (opcode == 1) { /* No Op */
*p++ = 'N';
length--;
} else if (opcode == 2) { /* MSS */
if (length < 4)
break; /* MSS has 4 bytes */
*p++ = 'M';
q++;
memcpy(&tmpshort, q, 2);
/* if (ntohs(tmpshort) == mss) */
/* *p++ = 'E'; */
sprintf(p, "%hX", ntohs(tmpshort));
p += strlen(p); /* max movement of p is 4 (0xFFFF) */
q += 2;
length -= 4;
} else if (opcode == 3) { /* Window Scale */
if (length < 3)
break; /* Window Scale option has 3 bytes */
*p++ = 'W';
q++;
snprintf(p, length, "%hhX", *((u8*)q));
p += strlen(p); /* max movement of p is 2 (max WScale value is 0xFF) */
q++;
length -= 3;
} else if (opcode == 4) { /* SACK permitted */
if (length < 2)
break; /* SACK permitted option has 2 bytes */
*p++ = 'S';
q++;
length -= 2;
} else if (opcode == 8) { /* Timestamp */
if (length < 10)
break; /* Timestamp option has 10 bytes */
*p++ = 'T';
q++;
memcpy(&tmpword, q, 4);
if (tmpword)
*p++ = '1';
else
*p++ = '0';
q += 4;
memcpy(&tmpword, q, 4);
if (tmpword)
*p++ = '1';
else
*p++ = '0';
q += 4;
length -= 10;
}
}
TCPOptions opts;
if (!opts.fromTCPPacket(tcp, tcplen))
return -1;
tcpopt_string_ctx ctx;
ctx.p = result;
ctx.end = result + maxlen - 1;
if (length > 0) {
if (!opts.foreachOpt(tcpopt_tostring, &ctx) || !ctx.valid) {
/* We could reach here for one of the two reasons:
* 1. At least one option is not correct. (Eg. Should have 4 bytes but only has 3 bytes left).
* 2. The option string is too long.
@ -3223,9 +3240,7 @@ int HostOsScan::get_tcpopt_string(const u8 *tcp, int tcplen, int mss, char *resu
*result = '\0';
return -1;
}
*p = '\0';
return p - result;
return ctx.p - result;
}

View file

@ -429,7 +429,7 @@ private:
void makeTOpsFP(HostOsScanStats *hss);
void makeTWinFP(HostOsScanStats *hss);
int get_tcpopt_string(const u8 *tcp, int tcplen, int mss, char *result, int maxlen) const;
int get_tcpopt_string(const u8 *tcp, int tcplen, char *result, int maxlen) const;
int rawsd; /* Raw socket descriptor */
netutil_eth_t *ethsd; /* Ethernet handle */

View file

@ -73,6 +73,7 @@
#include "utils.h"
#include "nmap_error.h"
#include "libnetutil/netutil.h"
#include "libnetutil/TCPHeader.h"
#include "struct_ip.h"
@ -1625,6 +1626,33 @@ int recvtime(int sd, char *buf, int len, int seconds, int *timedout) {
return -1;
}
struct getTS_args {
u32 *timestamp;
u32 *echots;
int found;
getTS_args() : timestamp(NULL), echots(NULL), found(0) {}
};
static bool tcpopt_ts_cb(u8 op, u8 oplen, const u8 *data, void *ctx)
{
if (op == 8 /* TCPOPT_TIMESTAMP */ && oplen == 10) {
getTS_args *args = static_cast<getTS_args *>(ctx);
const u8 *p = data + 2;
/* Legitimate ts option */
if (args->timestamp) {
*args->timestamp = (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3];
}
if (args->echots) {
*args->echots = (p[4] << 24) + (p[5] << 16) + (p[6] << 8) + p[7];
}
// done!
args->found = 1;
return false;
}
// Keep looking
return true;
}
/* Examines the given tcp packet and obtains the TCP timestamp option
information if available. Note that the CALLER must ensure that
"tcp" contains a valid header (in particular the th_off must be the
@ -1636,52 +1664,19 @@ int recvtime(int sd, char *buf, int len, int seconds, int *timedout) {
correct way to check for errors is to look at the return value
since a zero ts or echots could possibly be valid. */
int gettcpopt_ts(const u8 *tcppkt, int tcplen, u32 *timestamp, u32 *echots) {
const u8 *p;
int len = 0;
int op;
int oplen;
struct tcp_hdr tcp;
assert(tcplen >= sizeof(tcp));
memcpy(&tcp, tcppkt, sizeof(tcp));
/* first we find where the tcp options start ... */
p = tcppkt + 20;
len = MIN(4 * tcp.th_off, tcplen) - 20;
while (len > 0 && *p != 0 /* TCPOPT_EOL */ ) {
op = *p++;
if (op == 0 /* TCPOPT_EOL */ )
break;
if (op == 1 /* TCPOPT_NOP */ ) {
len--;
continue;
}
oplen = *p++;
if (oplen < 2)
break; /* No infinite loops, please */
if (oplen > len)
break; /* Not enough space */
if (op == 8 /* TCPOPT_TIMESTAMP */ && oplen == 10) {
/* Legitimate ts option */
if (timestamp) {
memcpy((char *) timestamp, p, 4);
*timestamp = ntohl(*timestamp);
}
p += 4;
if (echots) {
memcpy((char *) echots, p, 4);
*echots = ntohl(*echots);
}
return 1;
}
len -= oplen;
p += oplen - 2;
}
/* Didn't find anything */
if (timestamp)
*timestamp = 0;
if (echots)
*echots = 0;
return 0;
TCPOptions opts;
if (!opts.fromTCPPacket(tcppkt, tcplen))
return 0;
getTS_args args;
args.timestamp = timestamp;
args.echots = echots;
opts.foreachOpt(tcpopt_ts_cb, &args);
return args.found;
}