mirror of
https://github.com/nmap/nmap.git
synced 2026-08-04 14:49:29 +00:00
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
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:
parent
486fa2ebdd
commit
c86a45ef6e
6 changed files with 367 additions and 294 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue