Home | History | Annotate | Line # | Download | only in generic
      1 /*
      2  * wks.c -- Well-Known Services (WKS) RDATA parser
      3  *
      4  * Copyright (c) 2023, NLnet Labs. All rights reserved.
      5  *
      6  * SPDX-License-Identifier: BSD-3-Clause
      7  *
      8  */
      9 #ifndef WKS_H
     10 #define WKS_H
     11 
     12 // RFC1035 section 3.4.2:
     13 // The purpose of WKS RRs is to provide availability information for servers
     14 // for TCP and UDP.
     15 //
     16 // NSD and BIND use getprotobyname, which reads /etc/protocols (optimizations
     17 // may be in place for TCP and UDP). Note that BIND passes the protocol to
     18 // getservbyname for TCP and UDP only, NULL otherwise, which means any
     19 // protocol matches. Unfortunately, getprotobyname is NOT thread-safe.
     20 // getprotobyname_r exist on most BSDs and Linux, but not Windows.
     21 // The list of known protocols and services also differs between operating
     22 // systems and no list covers all IANA (links below) registered protocols
     23 // and services, which may cause compatibility issues. Furthermore, even
     24 // getprotobyname_r and getservbyname_r are marked locale, meaning the locale
     25 // object is read without any form of synchronization, which may be an issue
     26 // for a library.
     27 //
     28 // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
     29 // https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
     30 //
     31 // https://www.gnu.org/software/libc/manual/html_node/Protocols-Database.html
     32 // https://www.gnu.org/software/libc/manual/html_node/Services-Database.html
     33 // https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html
     34 // https://www.gnu.org/software/libc/manual/html_node/Other-Safety-Remarks.html
     35 //
     36 // WKS RRs are rarely used and a document to deprecate the RRTYPE (among
     37 // others) has been drafted (WKS removed from the second draft).
     38 //
     39 // https://datatracker.ietf.org/doc/html/draft-sury-deprecate-obsolete-resource-records-00
     40 // https://mailarchive.ietf.org/arch/msg/dnsop/YCVvXuM8HbJLF2SoXyDqyOGao34/
     41 //
     42 //
     43 // WKS RRs have been said to be deprecated in an informational document (NOT
     44 // a standard), although it wrongly claims WKS RRs are in fact deprecated.
     45 //
     46 // RFC1912 section 2.6.1 (informational):
     47 // WKS records are deprecated in [RFC 1123].  They serve no known useful
     48 // function, except internally among LISP machines.  Don't use them.
     49 //
     50 // https://datatracker.ietf.org/doc/html/rfc1912
     51 //
     52 // RFC1123 section 2.2 (standard):
     53 // An application SHOULD NOT rely on the ability to locate a WKS record
     54 // containing an accurate listing of all services at a particular host
     55 // address, since the WKS RR type is not often used by Internet sites.
     56 // To confirm that a service is present, simply attempt to use it.
     57 //
     58 // https://datatracker.ietf.org/doc/html/rfc1123
     59 //
     60 // RFC1127 section 2 (informational):
     61 // WKS Records Detracted   [AS 2.2, 5.2.12, 6.1.3.6]
     62 // Recommend against using WKS records from DNS.
     63 //
     64 // https://datatracker.ietf.org/doc/html/rfc1127
     65 //
     66 //
     67 // Rather than supporting any protocol registered by IANA, support a small
     68 // subset of mnemonics (TCP and UDP) as well as numeric values and add
     69 // support (or remove it entirely) for additional protocols on demand.
     70 
     71 #if BYTE_ORDER == LITTLE_ENDIAN
     72 # define TCP (0x0000000000706374llu)
     73 # define UDP (0x0000000000706475llu)
     74 #elif BYTE_ORDER == BIG_ENDIAN
     75 # define TCP (0x7463700000000000llu)
     76 # define UDP (0x7564700000000000llu)
     77 #else
     78 # error "byte order unknown"
     79 #endif
     80 
     81 static really_inline int32_t scan_protocol(
     82   const char *name, size_t length, uint8_t *protocol)
     83 {
     84   static const int8_t zero_masks[48] = {
     85     -1, -1, -1, -1, -1, -1, -1, -1,
     86     -1, -1, -1, -1, -1, -1, -1, -1,
     87     -1, -1, -1, -1, -1, -1, -1, -1,
     88     -1, -1, -1, -1, -1, -1, -1, -1,
     89      0,  0,  0,  0,  0,  0,  0,  0,
     90      0,  0,  0,  0,  0,  0,  0,  0
     91   };
     92 
     93   uint64_t key;
     94   uint64_t mask;
     95   const int8_t *zero_mask = &zero_masks[32 - (length & 0x1f)];
     96   memcpy(&mask, zero_mask, 8);
     97 
     98   memcpy(&key, name, sizeof(key)); // safe, input is padded
     99   key |= (key & 0x4040404040404040) >> 1; // convert to lower case
    100   key &= mask;
    101 
    102   if (key == TCP)
    103     return (void)(*protocol = 6), 1;
    104   else if (key == UDP)
    105     return (void)(*protocol = 17), 1;
    106   else
    107     return scan_int8(name, length, protocol);
    108 }
    109 
    110 typedef struct service service_t;
    111 struct service {
    112   struct {
    113     const char name[16];
    114     size_t length;
    115   } key;
    116   uint16_t port;
    117 };
    118 
    119 #define UNKNOWN_SERVICE() { { "", 0 }, 0 }
    120 #define SERVICE(name, port) { { name, sizeof(name) - 1 }, port }
    121 
    122 static const service_t services[64] = {
    123   UNKNOWN_SERVICE(),
    124   SERVICE("snmptrap", 162),
    125   SERVICE("pop3s", 995),
    126   SERVICE("pop3", 110),
    127   SERVICE("ldaps", 636),
    128   SERVICE("domain", 53),
    129   SERVICE("nntps", 563),
    130   SERVICE("nntp", 119),
    131   UNKNOWN_SERVICE(),
    132   UNKNOWN_SERVICE(),
    133   UNKNOWN_SERVICE(),
    134   UNKNOWN_SERVICE(),
    135   SERVICE("ftps-data", 989),
    136   UNKNOWN_SERVICE(),
    137   UNKNOWN_SERVICE(),
    138   SERVICE("imaps", 993),
    139   SERVICE("imap", 143),
    140   SERVICE("time", 37),
    141   UNKNOWN_SERVICE(),
    142   UNKNOWN_SERVICE(),
    143   SERVICE("kerberos", 88),
    144   UNKNOWN_SERVICE(),
    145   UNKNOWN_SERVICE(),
    146   SERVICE("ftp", 21),
    147   SERVICE("ntp", 123),
    148   SERVICE("whoispp", 63),
    149   SERVICE("ssh", 22),
    150   UNKNOWN_SERVICE(),
    151   SERVICE("nicname", 43),
    152   UNKNOWN_SERVICE(),
    153   UNKNOWN_SERVICE(),
    154   UNKNOWN_SERVICE(),
    155   SERVICE("ptp-general", 320),
    156   UNKNOWN_SERVICE(),
    157   UNKNOWN_SERVICE(),
    158   SERVICE("domain-s", 853),
    159   SERVICE("ftp-data", 20),
    160   SERVICE("ftps", 990),
    161   UNKNOWN_SERVICE(),
    162   UNKNOWN_SERVICE(),
    163   SERVICE("snmp", 161),
    164   UNKNOWN_SERVICE(),
    165   UNKNOWN_SERVICE(),
    166   UNKNOWN_SERVICE(),
    167   UNKNOWN_SERVICE(),
    168   SERVICE("bgmp", 264),
    169   SERVICE("echo", 7),
    170   UNKNOWN_SERVICE(),
    171   SERVICE("nnsp", 433),
    172   SERVICE("submission", 587),
    173   // submissions cannot be distinguished from submission by hash value because
    174   // the shared prefix is too long. include length to generate a unique key
    175   SERVICE("submissions", 465),
    176   UNKNOWN_SERVICE(),
    177   SERVICE("ptp-event", 319),
    178   UNKNOWN_SERVICE(),
    179   SERVICE("npp", 92),
    180   UNKNOWN_SERVICE(),
    181   SERVICE("https", 443),
    182   SERVICE("http", 80),
    183   UNKNOWN_SERVICE(),
    184   SERVICE("telnet", 23),
    185   SERVICE("tcpmux", 1),
    186   UNKNOWN_SERVICE(),
    187   SERVICE("lmtp", 24),
    188   SERVICE("smtp", 25)
    189 };
    190 
    191 #undef SERVICE
    192 #undef UNKNOWN_SERVICE
    193 
    194 // magic (139898079) generated using wks-hash.c
    195 static really_inline uint8_t service_hash(uint64_t input, size_t length)
    196 {
    197   // le64toh is required for big endian, no-op on little endian
    198   input = le64toh(input);
    199   uint32_t input32 = (uint32_t)((input >> 32) ^ input);
    200   return (((input32 * 139898079llu) >> 32) + length) & 0x3f;
    201 }
    202 
    203 nonnull((1,4))
    204 static really_inline int32_t scan_service(
    205   const char *data, size_t length, int32_t protocol, uint16_t *port)
    206 {
    207   uint8_t digit = (uint8_t)*data - '0';
    208   static const int8_t zero_masks[48] = {
    209     -1, -1, -1, -1, -1, -1, -1, -1,
    210     -1, -1, -1, -1, -1, -1, -1, -1,
    211     -1, -1, -1, -1, -1, -1, -1, -1,
    212     -1, -1, -1, -1, -1, -1, -1, -1,
    213      0,  0,  0,  0,  0,  0,  0,  0,
    214      0,  0,  0,  0,  0,  0,  0,  0
    215   };
    216 
    217   (void)protocol; // all supported services map to tcp and udp
    218 
    219   if (digit > 9) {
    220     uint64_t input0, input1;
    221     static const uint64_t upper_mask = 0xdfdfdfdfdfdfdfdfllu;
    222     static const uint64_t letter_mask = 0x4040404040404040llu;
    223     memcpy(&input0, data, 8);
    224     memcpy(&input1, data+8, 8);
    225     // convert to upper case, unconditionally transforms digits (0x30-0x39)
    226     // and dash (0x2d), but does not introduce clashes
    227     uint64_t key = input0 & upper_mask;
    228     // zero out non-relevant bytes
    229     uint64_t zero_mask0, zero_mask1;
    230     const int8_t *zero_mask = &zero_masks[32 - (length & 0xf)];
    231     memcpy(&zero_mask0, zero_mask, 8);
    232     memcpy(&zero_mask1, zero_mask+8, 8);
    233     uint8_t index = service_hash(key & zero_mask0, length);
    234     assert(index < 64);
    235 
    236     input0 |= (input0 & letter_mask) >> 1;
    237     input0 &= zero_mask0;
    238     input1 |= (input1 & letter_mask) >> 1;
    239     input1 &= zero_mask1;
    240 
    241     uint64_t name0, name1;
    242     memcpy(&name0, services[index].key.name, 8);
    243     memcpy(&name1, services[index].key.name+8, 8);
    244 
    245     *port = services[index].port;
    246     return (input0 == name0) &
    247 	   (input1 == name1) &
    248 	   (services[index].key.length == length);
    249   }
    250 
    251   return scan_int16(data, length, port);
    252 }
    253 
    254 #endif // WKS_H
    255