Changeset 11130 in ntrip for trunk/BNC/tests


Ignore:
Timestamp:
Oct 28, 2026, 9:32:30 AM (10 days ago)
Author:
stuerze
Message:

Some fixes from security point of view

Location:
trunk/BNC/tests
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/tests/README.md

    r11127 r11130  
    3131| `tools/ssr_harness.sh` | builds `tools/ssr_harness.cpp` with the SSR decoders under ASan/UBSan and runs it |
    3232| `tools/ssl_check.py` | TLS checks of the Ntrip 2s client and upload (not part of `run_tests.py`) |
     33| `tools/net_check.py` | UDP/RTP sender filter and upload status checks on loopback (not part of `run_tests.py`) |
    3334| `work/` | scratch output, not versioned |
    3435
    … …  
    100101  coefficients are C(d,o) = (100*d+o)/200 TECU and S = -C, both `.ssr` files
    101102  are identical apart from the station ID. These frames overflowed the
    102   coefficient and name arrays; the 4-layer VTEC crashed BNC.
     103  coefficient and name arrays; the 4-layer VTEC crashed BNC. Also two RTCM
     104  1033: a complete one and one whose receiver descriptor length runs beyond
     105  the message (rejected; before, stale buffer content was logged).
     106
     107* `rawfile_hostile`: raw file with hostile chunk headers from
     108  `tools/mkcrafted.py --rawfile`: the station ID `../ESCAPE0` must give
     109  `Output/.._ESCAPE0_*.ssr` (no file outside the output directory), a chunk
     110  header announcing 2000000000 bytes is rejected with a log message.
    103111
    104112## Sanitizer check of the SSR decoders
    … …  
    115123also covers the new RTCM-SSR metadata (message 60 in
    116124`crafted_limits.raw`: 7 entries with additional parameters of 40 and 0 bits,
    117 and 31 entries, which must be rejected), which `RTCM3Decoder.cpp` does not
    118 dispatch at present. Every decoded VTEC and metadata message is encoded
    119 again with the `Make*` function of the same class and decoded once more; the
    120 result must be identical. Exit code 0 only without sanitizer errors and
     125and 31 entries, which must be rejected) and satellite antenna messages
     126(BDS with mask bits in all four 16-bit quarters, GPS with shared blocks),
     127which `RTCM3Decoder.cpp` does not dispatch at present. Every decoded VTEC, metadata and antenna message is
     128encoded again with the `Make*` function of the same class and decoded once
     129more; the result must be identical, for the antenna messages also the bytes. Exit code 0 only without sanitizer errors and
    121130differences.
    122131
    … …  
    135144certificate for another host name (rejected) and a client certificate
    136145required by the caster, with and without `sslIgnoreErrors`. Takes about 3 min.
     146
     147## Network check
     148
     149```
     150tests/tools/net_check.py [--bnc PATH]
     151```
     152
     153Runs BNC live against fake servers on loopback addresses (no root needed).
     154A second sender on another address sends spoofed datagrams to BNC's port: in
     155the modes 'U' (Ntrip 2 UDP) and 'R' (RTSP/RTP) and in 'UN' with a sender
     156address in the URL, BNC's raw output must contain only the server's data;
     157'UN' with 127.0.0.1 accepts any sender, as before. An Ntrip 1 caster sending an endless
     158response header is cut off, a normal Ntrip 1 stream still works. An upload
     159answered with `HTTP/1.1 401` and a header containing `200` must be reported
     160as broken. Takes about 2 min.
    137161
    138162## Adding a replay case
  • trunk/BNC/tests/tools/bncraw.py

    r11127 r11130  
    197197
    198198
     199def frame(payload):
     200    """RTCM3 frame (preamble, length, payload, CRC24Q) of a payload."""
     201    head = bytes([0xD3, (len(payload) >> 8) & 0x03, len(payload) & 0xFF])
     202    return head + payload + crc24q(head + payload).to_bytes(3, "big")
     203
     204
    199205def cmd_slice(args):
    200206    stations = set(args.stations.split(",")) if args.stations else None
     207    types = args.types.split(",") if args.types else None
     208    parsers = {}
    201209
    202210    def selected():
    … …  
    208216            if args.end and t >= args.end:
    209217                continue
     218            if types:
     219                # keep only the RTCM3 messages of the given types (or type prefixes)
     220                frames = parsers.setdefault(sta, Rtcm3Framer()).feed(data)
     221                kept = b"".join(frame(pl) for pl in frames
     222                                if any(msg_type(pl).startswith(tt) for tt in types))
     223                if not kept:
     224                    continue
     225                data = kept
    210226            yield t, sta, fmt, data
    211227
    … …  
    245261    p.add_argument("--end", help="ISO time, exclusive")
    246262    p.add_argument("--stations", help="comma separated staIDs to keep")
     263    p.add_argument("--types", help="comma separated RTCM3 message types or prefixes to keep, "
     264                                   "e.g. 1019,4076_02 (GPS ephemerides and IGS-SSR GPS messages)")
    247265    p.set_defaults(func=cmd_slice)
    248266
  • trunk/BNC/tests/tools/fakecaster.py

    r11127 r11130  
    1515additionally requires a client certificate signed by that CA. With --serve
    1616a GET request (BNC as Ntrip client) is answered with the RTCM bytes of the
    17 given raw file. Failed TLS handshakes are reported on stdout.
     17given raw file. With --reply an upload is answered with this text instead
     18of the 200 status (e.g. a rejection), and the connection is closed. Failed
     19TLS handshakes are reported on stdout.
    1820"""
    1921
    … …  
    5557        self.total = 0
    5658        self.handshakes = 0
     59        self.rejected = 0
    5760        self.served = 0
    5861
    … …  
    6770
    6871
    69 def serve(conn, store, timeout, tls=None, served=None):
     72def serve(conn, store, timeout, tls=None, served=None, reply=None):
    7073    conn.settimeout(timeout)
    7174    buf = b""
    … …  
    8992                         b"Content-Type: gnss/data\r\nConnection: close\r\n\r\n" + served)
    9093            store.served += len(served)
     94            return
     95        if reply is not None:
     96            conn.sendall(reply)
     97            store.rejected += 1
    9198            return
    9299        if header.startswith(b"SOURCE"):
    … …  
    127134    ap.add_argument("--tls-client-ca", help="require a client certificate of this CA")
    128135    ap.add_argument("--serve", help="raw file whose RTCM bytes are sent on GET")
     136    ap.add_argument("--reply", help="answer to uploads instead of 200 ('\\r\\n' escapes allowed)")
    129137    args = ap.parse_args()
    130138
    … …  
    136144            tls.verify_mode = ssl.CERT_REQUIRED
    137145            tls.load_verify_locations(args.tls_client_ca)
     146    reply = None
     147    if args.reply is not None:
     148        reply = args.reply.encode("latin-1").decode("unicode_escape").encode("latin-1")
    138149    served = None
    139150    if args.serve:
    … …  
    155166        except socket.timeout:
    156167            break
    157         t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served),
     168        t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served, reply),
    158169                             daemon=True)
    159170        t.start()
    … …  
    166177    if served is not None:
    167178        print("served %d bytes" % store.served, flush=True)
     179    if reply is not None:
     180        print("%d upload(s) answered with --reply" % store.rejected, flush=True)
    168181    return 0
    169182
  • trunk/BNC/tests/tools/mkcrafted.py

    r11125 r11130  
    66  CRAFT0IGS0  IGS-SSR VTEC (4076/201), decoded by SsrCorrIgs, followed by
    77              RTCM 1300/1301/1302 with 31-character names (BNC's RTCM scan
    8               lists them only for streams that also decode successfully)
     8              lists them only for streams that also decode successfully);
     9              in the first epoch also two RTCM 1033 (antenna and receiver
     10              descriptors): a complete one and one whose receiver descriptor
     11              length (200) runs beyond the end of the message
    912  CRAFT0RTC0  RTCM-SSR VTEC (1264), decoded by SsrCorrRtcm
    1013  CRAFT0NEW0  new RTCM-SSR metadata (60), not dispatched by BNC at present;
    … …  
    1215                - 7 entries (maximum), additional parameter of 40 and 0 bits
    1316                - 31 entries (5-bit count, more than META_MAXNUMENTRIES=7)
     17              and satellite antenna messages (DF+010-DF+021):
     18                - BDS (83): C01, C17, C33, C49, C64 (mask bits in all four
     19                  16-bit quarters of DF394), individual blocks
     20                - GPS (80): G01, G02, G32 sharing one block and one frequency
     21                  block; nadir angle corrections incl. the invalid value
    1422
    1523VTEC epochs (every 5 s):
    … …  
    2230by eye; the heights are 100 km * layer number.
    2331
     32With --rawfile, a raw file with hostile chunk headers is written instead
     33(test of the raw file reader, data/crafted_rawfile.raw):
     34  ../ESCAPE0  station ID with a path: RTCM-SSR VTEC whose .ssr file must be
     35              written inside the output directory
     36  CRAFT0RTC0  RTCM-SSR VTEC, 5 s later
     37  then a chunk header announcing 2000000000 bytes, which must be rejected
     38
    2439Usage: mkcrafted.py <out.raw>
     40       mkcrafted.py --rawfile <out.raw>
    2541"""
    2642
    … …  
    140156
    141157
     158def descriptors(strings, truncate=False):
     159    """RTCM 1033: antenna, setup ID, serial, receiver, firmware, serial."""
     160    w = BitWriter()
     161    w.u(1033, 12)
     162    w.u(0, 12)               # reference station ID
     163    ant, antser, rec, firm, recser = strings
     164    w.text(ant, 8)
     165    w.u(0, 8)                # antenna setup ID
     166    w.text(antser, 8)
     167    if truncate:
     168        w.u(200, 8)          # receiver descriptor length beyond the message
     169        for c in rec.encode("ascii"):
     170            w.u(c, 8)
     171    else:
     172        w.text(rec, 8)
     173        w.text(firm, 8)
     174        w.text(recser, 8)
     175    return frame(w.payload())
     176
     177
    142178def metadata(entries):
    143179    """entries: list of None (default model) or number of additional bits."""
    … …  
    163199
    164200
     201def antenna(msg, sats, shared, nadir):
     202    """Satellite antenna message; sats: PRNs (1..64) of the mask."""
     203    w = BitWriter()
     204    w.u(msg, 12)
     205    w.u(4711, 16)            # provider ID
     206    w.u(5, 6)                # satellite antenna IOD
     207    w.u(1, 1)                # phase center information
     208    w.u(0, 1)                # group delay information
     209    w.u(1, 1)                # nadir angle dependent corrections follow
     210    w.u(3, 5)                # maximum off-nadir angle: corrections 0..3 deg
     211    w.u(1, 4)                # range extension: 3 + 1 = 4 bits
     212    w.u(shared, 1)           # satellite set indicator
     213    mask = 0
     214    for prn in sats:
     215        mask |= 1 << (64 - prn)
     216    w.u(mask, 64)
     217    for n in range(1 if shared else len(sats)):
     218        w.u(shared, 1)       # frequency set indicator
     219        w.u(0b101000, 6)     # frequencies 0 and 2
     220        for f in range(1 if shared else 2):
     221            w.u(1, 1)        # nadir correction follows
     222            w.s(100 * n + 10 * f + 1, 12)    # mm
     223            for deg in range(4):
     224                w.s(nadir[(n + f + deg) % len(nadir)], 4)   # mm, -8: invalid
     225    return frame(w.payload())
     226
     227
     228def rawfile(path):
     229    chunks = []
     230    for i, sta in enumerate(["../ESCAPE0", "CRAFT0RTC0"]):
     231        t = T0 + datetime.timedelta(seconds=5 * i)
     232        chunks.append((t.isoformat(), sta, "RTCM_3", vtec_rtcm(t, [(2, 2)])))
     233    write_chunks(path, chunks)
     234    t = T0 + datetime.timedelta(seconds=10)
     235    with open(path, "ab") as o:
     236        o.write(("%s CRAFT0RTC0 RTCM_3 2000000000\n" % t.isoformat()).encode())
     237        o.write(vtec_rtcm(t, [(2, 2)]) + b"\n")
     238
     239
    165240def main():
     241    if len(sys.argv) == 3 and sys.argv[1] == "--rawfile":
     242        rawfile(sys.argv[2])
     243        return
    166244    if len(sys.argv) != 2:
    167245        sys.exit(__doc__)
    … …  
    175253        t = T0 + datetime.timedelta(seconds=5 * i)
    176254        stamp = t.isoformat()
    177         chunks.append((stamp, "CRAFT0IGS0", "RTCM_3", vtec_igs(t, layers)
    178                        + service_crs() + helmert() + rtcm_crs()))
     255        data = vtec_igs(t, layers) + service_crs() + helmert() + rtcm_crs()
     256        if i == 0:
     257            data += descriptors(["LEIAR25.R4      LEIT", "12345", "SEPT POLARX5",
     258                                 "5.5.0", "3001234"])
     259            data += descriptors(["TRUNCATED ANTENNA", "9", "RECEIVER", "", ""],
     260                                truncate=True)
     261        chunks.append((stamp, "CRAFT0IGS0", "RTCM_3", data))
    179262        chunks.append((stamp, "CRAFT0RTC0", "RTCM_3", vtec_rtcm(t, layers)))
    180263    stamp = (T0 + datetime.timedelta(seconds=15)).isoformat()
    181264    chunks.append((stamp, "CRAFT0NEW0", "RTCM_3",
    182                    metadata([40, 0] + [None] * 5) + metadata([None] * 31)))
     265                   metadata([40, 0] + [None] * 5) + metadata([None] * 31)
     266                   + antenna(83, [1, 17, 33, 49, 64], 0, [-7, 0, 7, 3])
     267                   + antenna(80, [1, 2, 32], 1, [5, -8, -1, 2])))
    183268    write_chunks(sys.argv[1], chunks)
    184269
  • trunk/BNC/tests/tools/ssr_harness.cpp

    r11127 r11130  
    77// reported by AddressSanitizer instead of silently corrupting the next member.
    88//
    9 // Every decoded VTEC (all formats) and metadata message (new RTCM-SSR) is
    10 // encoded again with the Make* function of the same class and decoded once
    11 // more; the result must be identical (exit code 1 otherwise).
     9// Every decoded VTEC (all formats), metadata and satellite antenna message
     10// (new RTCM-SSR) is encoded again with the Make* function of the same class
     11// and decoded once more; the result must be identical, for the antenna
     12// messages also the bytes of the frame (exit code 1 otherwise).
    1213//
    1314// Usage: ssr_harness <file>   (BNC raw file or plain RTCM3 byte stream)
    … …  
    9697  int irc = t1.decode(ssr, frame, size);
    9798  printf("  %-8s irc %3d", name, irc);
     99  SsrCorrRtcmNew* rtcmNew = dynamic_cast<SsrCorrRtcmNew*>(ssr);
    98100  if (irc >= 0 && t1.v->NumLayers) {
    99101    double sum = 0.0;
    … …  
    125127      }
    126128    }
    127     SsrCorrRtcmNew* rtcmNew = dynamic_cast<SsrCorrRtcmNew*>(ssr);
    128129    if (rtcmNew) {
    129130      char buffer[CLOCKORBIT_BUFFERSIZE];
    … …  
    134135      nFailed += !ok;
    135136    }
     137  }
     138  for (unsigned s = 0; rtcmNew && irc >= 0 && s < CLOCKORBIT_SATNUM; s++) {
     139    if (!t1.a->SatelliteMask[s]) {
     140      continue;
     141    }
     142    printf("  ANT type %u mask %016llx", t1.a->messageType[s],
     143           (unsigned long long)t1.a->SatelliteMask[s]);
     144    char buffer[CLOCKORBIT_BUFFERSIZE];
     145    size_t len = rtcmNew->MakeAntenna(t1.a, t1.a->messageType[s], buffer, sizeof(buffer));
     146    t_target t2;
     147    bool sameBytes = len == size && memcmp(buffer, frame, size) == 0;
     148    bool ok = len > 0 && t2.decode(ssr, (const unsigned char*)buffer, len) >= 0 &&
     149              memcmp(t1.a, t2.a, sizeof(*t1.a)) == 0;
     150    printf("  re-encoded: %s, %s", ok ? "identical" : "DIFFERENT",
     151           sameBytes ? "same bytes" : "OTHER BYTES");
     152    nFailed += !ok || !sameBytes;
    136153  }
    137154  printf("\n");
Note: See TracChangeset for help on using the changeset viewer.