Hitherto, some plug-ins have converted converted IP addresses to host-endianness or assumed that they already have been, and several have assumed that all IP addresses are IPv4. This can lead to garbled output if the expectations of plug-ins in a stack do not match. Convert all IP addresses to IPv6, using IPv4-inIPv6 for IPv4. Convert IPv4 addresses back for formatting. Move a couple of `ULOGD_RET_BOOL` cases for consistency. Reported-by: Robert O'Brien <robrien@xxxxxxxxxxxxxxxxxxxx> Signed-off-by: Jeremy Sowden <jeremy@xxxxxxxxxx> --- filter/raw2packet/ulogd_raw2packet_BASE.c | 24 ++++++++--- filter/ulogd_filter_IP2BIN.c | 35 ++++----------- filter/ulogd_filter_IP2HBIN.c | 13 +++--- filter/ulogd_filter_IP2STR.c | 5 +-- include/ulogd/ulogd.h | 52 +++++++++++++++++++++++ input/flow/ulogd_inpflow_NFCT.c | 24 +++++++---- output/ipfix/ulogd_output_IPFIX.c | 4 +- output/sqlite3/ulogd_output_SQLITE3.c | 24 ++++++++--- output/ulogd_output_GPRINT.c | 34 ++++++++++----- output/ulogd_output_OPRINT.c | 40 ++++++++++------- src/ulogd.c | 3 +- util/db.c | 18 ++++++-- 12 files changed, 186 insertions(+), 90 deletions(-) diff --git a/filter/raw2packet/ulogd_raw2packet_BASE.c b/filter/raw2packet/ulogd_raw2packet_BASE.c index 9117d27da09a..b81860222e54 100644 --- a/filter/raw2packet/ulogd_raw2packet_BASE.c +++ b/filter/raw2packet/ulogd_raw2packet_BASE.c @@ -629,6 +629,7 @@ static int _interp_icmp(struct ulogd_pluginstance *pi, struct icmphdr *icmph, uint32_t len) { struct ulogd_key *ret = pi->output.keys; + struct in6_addr gateway; if (len < sizeof(struct icmphdr)) return ULOGD_IRET_OK; @@ -645,7 +646,8 @@ static int _interp_icmp(struct ulogd_pluginstance *pi, struct icmphdr *icmph, break; case ICMP_REDIRECT: case ICMP_PARAMETERPROB: - okey_set_u32(&ret[KEY_ICMP_GATEWAY], ntohl(icmph->un.gateway)); + u32_to_ipv6(&gateway, icmph->un.gateway); + okey_set_u128(&ret[KEY_ICMP_GATEWAY], &gateway); break; case ICMP_DEST_UNREACH: if (icmph->code == ICMP_FRAG_NEEDED) { @@ -715,16 +717,19 @@ static int _interp_ahesp(struct ulogd_pluginstance *pi, void *protoh, static int _interp_iphdr(struct ulogd_pluginstance *pi, uint32_t len) { struct ulogd_key *ret = pi->output.keys; - struct iphdr *iph = - ikey_get_ptr(&pi->input.keys[INKEY_RAW_PCKT]); + struct iphdr *iph = ikey_get_ptr(&pi->input.keys[INKEY_RAW_PCKT]); + struct in6_addr saddr, daddr; void *nexthdr; if (len < sizeof(struct iphdr) || len <= (uint32_t)(iph->ihl * 4)) return ULOGD_IRET_OK; len -= iph->ihl * 4; - okey_set_u32(&ret[KEY_IP_SADDR], iph->saddr); - okey_set_u32(&ret[KEY_IP_DADDR], iph->daddr); + u32_to_ipv6(&saddr, iph->saddr); + u32_to_ipv6(&daddr, iph->daddr); + + okey_set_u128(&ret[KEY_IP_SADDR], &saddr); + okey_set_u128(&ret[KEY_IP_DADDR], &daddr); okey_set_u8(&ret[KEY_IP_PROTOCOL], iph->protocol); okey_set_u8(&ret[KEY_IP_TOS], iph->tos); okey_set_u8(&ret[KEY_IP_TTL], iph->ttl); @@ -896,6 +901,7 @@ static int _interp_arp(struct ulogd_pluginstance *pi, uint32_t len) struct ulogd_key *ret = pi->output.keys; const struct ether_arp *arph = ikey_get_ptr(&pi->input.keys[INKEY_RAW_PCKT]); + struct in6_addr spa, tpa; if (len < sizeof(struct ether_arp)) return ULOGD_IRET_OK; @@ -904,10 +910,14 @@ static int _interp_arp(struct ulogd_pluginstance *pi, uint32_t len) okey_set_u16(&ret[KEY_ARP_PTYPE], ntohs(arph->arp_pro)); okey_set_u16(&ret[KEY_ARP_OPCODE], ntohs(arph->arp_op)); + u32_to_ipv6(&spa, *(uint32_t *) arph->arp_spa); + u32_to_ipv6(&tpa, *(uint32_t *) arph->arp_tpa); + + okey_set_u128(&ret[KEY_ARP_SPA], &spa); + okey_set_u128(&ret[KEY_ARP_TPA], &tpa); + okey_set_ptr(&ret[KEY_ARP_SHA], (void *)&arph->arp_sha); - okey_set_ptr(&ret[KEY_ARP_SPA], (void *)&arph->arp_spa); okey_set_ptr(&ret[KEY_ARP_THA], (void *)&arph->arp_tha); - okey_set_ptr(&ret[KEY_ARP_TPA], (void *)&arph->arp_tpa); return ULOGD_IRET_OK; } diff --git a/filter/ulogd_filter_IP2BIN.c b/filter/ulogd_filter_IP2BIN.c index ee1238ff4940..0a7309c2f680 100644 --- a/filter/ulogd_filter_IP2BIN.c +++ b/filter/ulogd_filter_IP2BIN.c @@ -116,25 +116,12 @@ static struct ulogd_key ip2bin_keys[] = { static char ipbin_array[MAX_KEY-START_KEY][IPADDR_LENGTH]; -/** - * Convert IPv4 address (as 32-bit unsigned integer) to IPv6 address: - * add 96 bits prefix "::ffff:" to get IPv6 address "::ffff:a.b.c.d". - */ -static inline void uint32_to_ipv6(const uint32_t ipv4, struct in6_addr *ipv6) -{ - ipv6->s6_addr32[0] = 0x00000000; - ipv6->s6_addr32[1] = 0x00000000; - ipv6->s6_addr32[2] = htonl(0xffff); - ipv6->s6_addr32[3] = ipv4; -} - static int ip2bin(struct ulogd_key* inp, int index, int oindex) { char family = ikey_get_u8(&inp[KEY_OOB_FAMILY]); char convfamily = family; unsigned char *addr8; struct in6_addr *addr; - struct in6_addr ip4_addr; char *buffer; int i, written; @@ -162,18 +149,14 @@ static int ip2bin(struct ulogd_key* inp, int index, int oindex) } switch (convfamily) { - case AF_INET6: - addr = (struct in6_addr *)ikey_get_u128(&inp[index]); - break; - case AF_INET: - /* Convert IPv4 to IPv4 in IPv6 */ - addr = &ip4_addr; - uint32_to_ipv6(ikey_get_u32(&inp[index]), addr); - break; - default: - /* TODO handle error */ - ulogd_log(ULOGD_NOTICE, "Unknown protocol family\n"); - return ULOGD_IRET_ERR; + case AF_INET6: + case AF_INET: + addr = ikey_get_u128(&inp[index]); + break; + default: + /* TODO handle error */ + ulogd_log(ULOGD_NOTICE, "Unknown protocol family\n"); + return ULOGD_IRET_ERR; } buffer = ipbin_array[oindex]; @@ -184,7 +167,7 @@ static int ip2bin(struct ulogd_key* inp, int index, int oindex) addr8 = &addr->s6_addr[0]; for (i = 0; i < 4; i++) { written = sprintf(buffer, "%02x%02x%02x%02x", - addr8[0], addr8[1], addr8[2], addr8[3]); + addr8[0], addr8[1], addr8[2], addr8[3]); if (written != 2 * 4) { buffer[0] = 0; return ULOGD_IRET_ERR; diff --git a/filter/ulogd_filter_IP2HBIN.c b/filter/ulogd_filter_IP2HBIN.c index 087e824ae94b..5381dcd75ce9 100644 --- a/filter/ulogd_filter_IP2HBIN.c +++ b/filter/ulogd_filter_IP2HBIN.c @@ -156,14 +156,13 @@ static int interp_ip2hbin(struct ulogd_pluginstance *pi) for(i = START_KEY; i < MAX_KEY; i++) { if (pp_is_valid(inp, i)) { switch (convfamily) { - case AF_INET: - okey_set_u32(&ret[i-START_KEY], - ntohl(ikey_get_u32(&inp[i]))); - break; - case AF_INET6: - okey_set_ptr(&ret[i-START_KEY], - (struct in6_addr *)ikey_get_u128(&inp[i])); + case AF_INET: { + uint32_t ipv4; + + ipv4 = ipv6_to_u32(ikey_get_u128(&inp[i])); + okey_set_u32(&ret[i-START_KEY], ntohl(ipv4)); break; + } default: ; break; diff --git a/filter/ulogd_filter_IP2STR.c b/filter/ulogd_filter_IP2STR.c index 66324b0b3b22..8f7cd690f069 100644 --- a/filter/ulogd_filter_IP2STR.c +++ b/filter/ulogd_filter_IP2STR.c @@ -170,12 +170,11 @@ static int ip2str(struct ulogd_key *inp, int index, int oindex) switch (convfamily) { uint32_t ip; case AF_INET6: - inet_ntop(AF_INET6, - ikey_get_u128(&inp[index]), + inet_ntop(AF_INET6, ikey_get_u128(&inp[index]), ipstr_array[oindex], sizeof(ipstr_array[oindex])); break; case AF_INET: - ip = ikey_get_u32(&inp[index]); + ip = ipv6_to_u32(ikey_get_u128(&inp[index])); inet_ntop(AF_INET, &ip, ipstr_array[oindex], sizeof(ipstr_array[oindex])); break; diff --git a/include/ulogd/ulogd.h b/include/ulogd/ulogd.h index 092d9f521a70..64765cc571b7 100644 --- a/include/ulogd/ulogd.h +++ b/include/ulogd/ulogd.h @@ -14,10 +14,12 @@ #include <ulogd/linuxlist.h> #include <ulogd/conffile.h> #include <ulogd/ipfix_protocol.h> +#include <stdbool.h> #include <stdio.h> #include <signal.h> /* need this because of extension-sighandler */ #include <sys/types.h> #include <inttypes.h> +#include <netinet/in.h> #include <string.h> #include <config.h> @@ -202,6 +204,56 @@ static inline void *ikey_get_ptr(struct ulogd_key *key) return key->u.source->u.value.ptr; } +/** + * Convert IPv4 address (as 32-bit unsigned integer) to IPv6 address: + * add 96 bits prefix "::ffff:" to get IPv6 address "::ffff:a.b.c.d". + */ +static inline struct in6_addr * +u32_to_ipv6(struct in6_addr *ipv6, uint32_t ipv4) +{ + static const uint8_t IPV4_IN_IPV6_PREFIX[12] = { + [10] = 0xff, + [11] = 0xff, + }; + uint8_t *p = ipv6->s6_addr; + + memcpy(p, IPV4_IN_IPV6_PREFIX, sizeof(IPV4_IN_IPV6_PREFIX)); + p += sizeof(IPV4_IN_IPV6_PREFIX); + memcpy(p, &ipv4, sizeof(ipv4)); + + return ipv6; +} + +static inline struct in6_addr * +ipv4_to_ipv6(struct in6_addr *ipv6, const struct in_addr *ipv4) +{ + return u32_to_ipv6(ipv6, ipv4->s_addr); +} + +static inline uint32_t +ipv6_to_u32(const struct in6_addr *ipv6) +{ + return *((uint32_t *) ipv6->s6_addr + 3); +} + +static inline struct in_addr +ipv6_to_ipv4(const struct in6_addr *ipv6) +{ + return (struct in_addr) { .s_addr = (in_addr_t) ipv6_to_u32(ipv6) }; +} + +static inline bool +ipv4_in_ipv6(const struct in6_addr *ipv6) +{ + static const uint8_t IPV4_IN_IPV6_PREFIX[12] = { + [10] = 0xff, + [11] = 0xff, + }; + + return memcmp(IPV4_IN_IPV6_PREFIX, ipv6->s6_addr, + sizeof(IPV4_IN_IPV6_PREFIX)) == 0; +} + struct ulogd_pluginstance_stack; struct ulogd_pluginstance; diff --git a/input/flow/ulogd_inpflow_NFCT.c b/input/flow/ulogd_inpflow_NFCT.c index 899b7e3b8039..e4f2a18b8333 100644 --- a/input/flow/ulogd_inpflow_NFCT.c +++ b/input/flow/ulogd_inpflow_NFCT.c @@ -501,16 +501,22 @@ static int propagate_ct(struct ulogd_pluginstance *main_upi, okey_set_u8(&ret[NFCT_OOB_PROTOCOL], 0); /* FIXME */ switch (nfct_get_attr_u8(ct, ATTR_L3PROTO)) { - case AF_INET: - okey_set_u32(&ret[NFCT_ORIG_IP_SADDR], - nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC)); - okey_set_u32(&ret[NFCT_ORIG_IP_DADDR], - nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_DST)); - okey_set_u32(&ret[NFCT_REPLY_IP_SADDR], - nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC)); - okey_set_u32(&ret[NFCT_REPLY_IP_DADDR], - nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)); + case AF_INET: { + struct in6_addr addr; + + u32_to_ipv6(&addr, nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC)); + okey_set_u128(&ret[NFCT_ORIG_IP_SADDR], addr.s6_addr); + + u32_to_ipv6(&addr, nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_DST)); + okey_set_u128(&ret[NFCT_ORIG_IP_DADDR], addr.s6_addr); + + u32_to_ipv6(&addr, nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC)); + okey_set_u128(&ret[NFCT_REPLY_IP_SADDR], addr.s6_addr); + + u32_to_ipv6(&addr, nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)); + okey_set_u128(&ret[NFCT_REPLY_IP_DADDR], addr.s6_addr); break; + } case AF_INET6: okey_set_u128(&ret[NFCT_ORIG_IP_SADDR], nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC)); diff --git a/output/ipfix/ulogd_output_IPFIX.c b/output/ipfix/ulogd_output_IPFIX.c index 4863d008562e..1c281fe89c74 100644 --- a/output/ipfix/ulogd_output_IPFIX.c +++ b/output/ipfix/ulogd_output_IPFIX.c @@ -453,8 +453,8 @@ again: goto again; } - data->saddr.s_addr = ikey_get_u32(&pi->input.keys[InIpSaddr]); - data->daddr.s_addr = ikey_get_u32(&pi->input.keys[InIpDaddr]); + data->saddr = ipv6_to_ipv4(ikey_get_u128(&pi->input.keys[InIpSaddr])); + data->daddr = ipv6_to_ipv4(ikey_get_u128(&pi->input.keys[InIpDaddr])); data->packets = htonl((uint32_t) (ikey_get_u64(&pi->input.keys[InRawInPktCount]) + ikey_get_u64(&pi->input.keys[InRawOutPktCount]))); diff --git a/output/sqlite3/ulogd_output_SQLITE3.c b/output/sqlite3/ulogd_output_SQLITE3.c index 0a9ad67edcff..70eae85c22f0 100644 --- a/output/sqlite3/ulogd_output_SQLITE3.c +++ b/output/sqlite3/ulogd_output_SQLITE3.c @@ -145,6 +145,10 @@ sqlite3_interp(struct ulogd_pluginstance *pi) } switch (f->key->type) { + case ULOGD_RET_BOOL: + ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.b); + break; + case ULOGD_RET_INT8: ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.i8); break; @@ -160,11 +164,11 @@ sqlite3_interp(struct ulogd_pluginstance *pi) case ULOGD_RET_INT64: ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.i64); break; - + case ULOGD_RET_UINT8: ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.ui8); break; - + case ULOGD_RET_UINT16: ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.ui16); break; @@ -173,18 +177,26 @@ sqlite3_interp(struct ulogd_pluginstance *pi) ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.ui32); break; - case ULOGD_RET_IPADDR: case ULOGD_RET_UINT64: ret = sqlite3_bind_int64(priv->p_stmt, i, k_ret->u.value.ui64); break; - case ULOGD_RET_BOOL: - ret = sqlite3_bind_int(priv->p_stmt, i, k_ret->u.value.b); + case ULOGD_RET_IPADDR: { + struct in6_addr ipv6addr; + + memcpy(ipv6addr.s6_addr, k_ret->u.value.ui128, + sizeof(ipv6addr.s6_addr)); + if (ipv4_in_ipv6(&ipv6addr)) + ret = sqlite3_bind_int(priv->p_stmt, i, + ipv6_to_u32(&ipv6addr)); + else + ret = sqlite3_bind_null(priv->p_stmt, i); break; + } case ULOGD_RET_STRING: ret = sqlite3_bind_text(priv->p_stmt, i, k_ret->u.value.ptr, - strlen(k_ret->u.value.ptr), SQLITE_STATIC); + strlen(k_ret->u.value.ptr), SQLITE_STATIC); break; default: diff --git a/output/ulogd_output_GPRINT.c b/output/ulogd_output_GPRINT.c index eeeec6ac3eb0..7fa26ddee88b 100644 --- a/output/ulogd_output_GPRINT.c +++ b/output/ulogd_output_GPRINT.c @@ -27,6 +27,7 @@ #include <time.h> #include <errno.h> #include <inttypes.h> +#include <arpa/inet.h> #include <ulogd/ulogd.h> #include <ulogd/conffile.h> @@ -69,12 +70,6 @@ static struct config_keyset gprint_kset = { }, }; -#define NIPQUAD(addr) \ - ((unsigned char *)&addr)[0], \ - ((unsigned char *)&addr)[1], \ - ((unsigned char *)&addr)[2], \ - ((unsigned char *)&addr)[3] - static int gprint_interp(struct ulogd_pluginstance *upi) { struct gprint_priv *opi = (struct gprint_priv *) &upi->private; @@ -158,20 +153,39 @@ static int gprint_interp(struct ulogd_pluginstance *upi) rem -= ret; size += ret; break; - case ULOGD_RET_IPADDR: + case ULOGD_RET_IPADDR: { + int family; + struct in6_addr ipv6addr; + struct in_addr ipv4addr; + void *addr; + ret = snprintf(buf+size, rem, "%s=", key->name); if (ret < 0) break; rem -= ret; size += ret; - ret = snprintf(buf+size, rem, "%u.%u.%u.%u,", - NIPQUAD(key->u.value.ui32)); - if (ret < 0) + memcpy(ipv6addr.s6_addr, key->u.value.ui128, + sizeof(ipv6addr.s6_addr)); + + if (ipv4_in_ipv6(&ipv6addr)) { + family = AF_INET; + ipv4addr = ipv6_to_ipv4(&ipv6addr); + addr = &ipv4addr; + ret = INET_ADDRSTRLEN; + } else { + family = AF_INET6; + addr = &ipv6addr; + ret = INET6_ADDRSTRLEN; + } + + if (!inet_ntop(family, addr, buf + size, rem)) break; + rem -= ret; size += ret; break; + } default: /* don't know how to interpret this key. */ break; diff --git a/output/ulogd_output_OPRINT.c b/output/ulogd_output_OPRINT.c index 8617203237c3..1d97eebe2d85 100644 --- a/output/ulogd_output_OPRINT.c +++ b/output/ulogd_output_OPRINT.c @@ -24,6 +24,7 @@ #include <string.h> #include <errno.h> #include <inttypes.h> +#include <arpa/inet.h> #include <ulogd/ulogd.h> #include <ulogd/conffile.h> @@ -31,18 +32,6 @@ #define ULOGD_OPRINT_DEFAULT "/var/log/ulogd_oprint.log" #endif -#define NIPQUAD(addr) \ - ((unsigned char *)&addr)[0], \ - ((unsigned char *)&addr)[1], \ - ((unsigned char *)&addr)[2], \ - ((unsigned char *)&addr)[3] - -#define HIPQUAD(addr) \ - ((unsigned char *)&addr)[3], \ - ((unsigned char *)&addr)[2], \ - ((unsigned char *)&addr)[1], \ - ((unsigned char *)&addr)[0] - struct oprint_priv { FILE *of; }; @@ -86,10 +75,31 @@ static int oprint_interp(struct ulogd_pluginstance *upi) case ULOGD_RET_UINT64: fprintf(opi->of, "%" PRIu64 "\n", ret->u.value.ui64); break; - case ULOGD_RET_IPADDR: - fprintf(opi->of, "%u.%u.%u.%u\n", - HIPQUAD(ret->u.value.ui32)); + case ULOGD_RET_IPADDR: { + int family; + struct in6_addr ipv6addr; + struct in_addr ipv4addr; + void *addr; + char addrbuf[INET6_ADDRSTRLEN + 1] = ""; + + memcpy(ipv6addr.s6_addr, ret->u.value.ui128, + sizeof(ipv6addr.s6_addr)); + + if (ipv4_in_ipv6(&ipv6addr)) { + family = AF_INET; + ipv4addr = ipv6_to_ipv4(&ipv6addr); + addr = &ipv4addr; + } else { + family = AF_INET6; + addr = &ipv6addr; + } + + if (!inet_ntop(family, addr, addrbuf, sizeof(addrbuf))) + break; + + fprintf(opi->of, "%s\n", addrbuf); break; + } case ULOGD_RET_NONE: fprintf(opi->of, "<none>\n"); break; diff --git a/src/ulogd.c b/src/ulogd.c index b02f2602a895..eae384dea70d 100644 --- a/src/ulogd.c +++ b/src/ulogd.c @@ -182,13 +182,14 @@ int ulogd_key_size(struct ulogd_key *key) break; case ULOGD_RET_INT32: case ULOGD_RET_UINT32: - case ULOGD_RET_IPADDR: ret = 4; break; case ULOGD_RET_INT64: case ULOGD_RET_UINT64: ret = 8; break; + case ULOGD_RET_IPADDR: // We keep IPv4 addresses in IPv4-in-IPv6 + // internally. case ULOGD_RET_IP6ADDR: ret = 16; break; diff --git a/util/db.c b/util/db.c index c1d24365239f..7d21a812ab07 100644 --- a/util/db.c +++ b/util/db.c @@ -344,6 +344,9 @@ static void __format_query_db(struct ulogd_pluginstance *upi, char *start) } switch (res->type) { + case ULOGD_RET_BOOL: + sprintf(stmt_ins, "'%d',", res->u.value.b); + break; case ULOGD_RET_INT8: sprintf(stmt_ins, "%d,", res->u.value.i8); break; @@ -362,17 +365,24 @@ static void __format_query_db(struct ulogd_pluginstance *upi, char *start) case ULOGD_RET_UINT16: sprintf(stmt_ins, "%u,", res->u.value.ui16); break; - case ULOGD_RET_IPADDR: - /* fallthrough when logging IP as uint32_t */ case ULOGD_RET_UINT32: sprintf(stmt_ins, "%u,", res->u.value.ui32); break; case ULOGD_RET_UINT64: sprintf(stmt_ins, "%" PRIu64 ",", res->u.value.ui64); break; - case ULOGD_RET_BOOL: - sprintf(stmt_ins, "'%d',", res->u.value.b); + case ULOGD_RET_IPADDR: { + struct in6_addr ipv6addr; + + memcpy(ipv6addr.s6_addr, res->u.value.ui128, + sizeof(ipv6addr.s6_addr)); + if (ipv4_in_ipv6(&ipv6addr)) + sprintf(stmt_ins, "%" PRIu32 ",", + ipv6_to_u32(&ipv6addr)); + else + sprintf(stmt_ins, "NULL,"); break; + } case ULOGD_RET_STRING: *(stmt_ins++) = '\''; if (res->u.value.ptr) { -- 2.35.1