static int xfrd_handle_notify_reply(struct notify_zone_t* zone, buffer_type* packet) { if((OPCODE(packet) != OPCODE_NOTIFY) || (QR(packet) == 0)) { log_msg(LOG_ERR, "xfrd: zone %s: received bad notify reply opcode/flags", zone->apex_str); return 0; } /* we know it is OPCODE NOTIFY, QUERY_REPLY and for this zone */ if(ID(packet) != zone->notify_query_id) { log_msg(LOG_ERR, "xfrd: zone %s: received notify-ack with bad ID", zone->apex_str); return 0; } /* could check tsig, but why. The reply does not cause processing. */ if(RCODE(packet) != RCODE_OK) { log_msg(LOG_ERR, "xfrd: zone %s: received notify response error %s from %s", zone->apex_str, rcode2str(RCODE(packet)), zone->notify_current->ip_address_spec); if(RCODE(packet) == RCODE_IMPL) return 1; /* rfc1996: notimpl notify reply: consider retries done */ return 0; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: host %s acknowledges notify", zone->apex_str, zone->notify_current->ip_address_spec)); return 1; }
/* sets new last_server */ void reply_query(int fd, int family, time_t now) { /* packet from peer server, extract data for cache, and send to original requester */ struct dns_header *header; union mysockaddr serveraddr; struct frec *forward; socklen_t addrlen = sizeof(serveraddr); ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); size_t nn; struct server *server; /* packet buffer overwritten */ daemon->srv_save = NULL; /* Determine the address of the server replying so that we can mark that as good */ serveraddr.sa.sa_family = family; #ifdef HAVE_IPV6 if (serveraddr.sa.sa_family == AF_INET6) serveraddr.in6.sin6_flowinfo = 0; #endif /* spoof check: answer must come from known server, */ for (server = daemon->servers; server; server = server->next) if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR)) && sockaddr_isequal(&server->addr, &serveraddr)) break; header = (struct dns_header *)daemon->packet; if (!server || n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) return; server = forward->sentto; if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) && !option_bool(OPT_ORDER) && forward->forwardall == 0) /* for broken servers, attempt to send to another one. */ { unsigned char *pheader; size_t plen; int is_sign; /* recreate query from reply */ pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign); if (!is_sign) { header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); if ((nn = resize_packet(header, (size_t)n, pheader, plen))) { header->hb3 &= ~(HB3_QR | HB3_TC); forward_query(-1, NULL, NULL, 0, header, nn, now, forward); return; } } } if ((forward->sentto->flags & SERV_TYPE) == 0) { if (RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) server = NULL; else { struct server *last_server; /* find good server by address if possible, otherwise assume the last one we sent to */ for (last_server = daemon->servers; last_server; last_server = last_server->next) if (!(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR)) && sockaddr_isequal(&last_server->addr, &serveraddr)) { server = last_server; break; } } if (!option_bool(OPT_ALL_SERVERS)) daemon->last_server = server; } /* If the answer is an error, keep the forward record in place in case we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when everything is broken */ if (forward->forwardall == 0 || --forward->forwardall == 1 || (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) { int check_rebind = !(forward->flags & FREC_NOREBIND); if (!option_bool(OPT_NO_REBIND)) check_rebind = 0; if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED))) { header->id = htons(forward->orig_id); header->hb4 |= HB4_RA; /* recursion if available */ send_from(forward->fd, option_bool(OPT_NOWILD), daemon->packet, nn, &forward->source, &forward->dest, forward->iface); } free_frec(forward); /* cancel */ } }
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, int checking_disabled) { unsigned char *pheader, *sizep; int munged = 0, is_sign; size_t plen; /* If upstream is advertising a larger UDP packet size than we allow, trim it so that we don't get overlarge requests for the client. We can't do this for signed packets. */ if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)) && !is_sign) { unsigned short udpsz; unsigned char *psave = sizep; GETSHORT(udpsz, sizep); if (udpsz > daemon->edns_pktsz) PUTSHORT(daemon->edns_pktsz, psave); } /* RFC 4035 sect 4.6 para 3 */ if (!is_sign && !option_bool(OPT_DNSSEC)) header->hb4 &= ~HB4_AD; if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) return n; /* Complain loudly if the upstream server is non-recursive. */ if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && ntohs(header->ancount) == 0 && server && !(server->flags & SERV_WARNED_RECURSIVE)) { prettyprint_addr(&server->addr, daemon->namebuff); my_syslog(LOG_WARNING, _("nameserver %s refused to do a recursive query"), daemon->namebuff); if (!option_bool(OPT_LOG)) server->flags |= SERV_WARNED_RECURSIVE; } if (daemon->bogus_addr && RCODE(header) != NXDOMAIN && check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) { munged = 1; SET_RCODE(header, NXDOMAIN); header->hb3 &= ~HB3_AA; } else { if (RCODE(header) == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) && check_for_local_domain(daemon->namebuff, now)) { /* if we forwarded a query for a locally known name (because it was for an unknown type) and the answer is NXDOMAIN, convert that to NODATA, since we know that the domain exists, even if upstream doesn't */ munged = 1; header->hb3 |= HB3_AA; SET_RCODE(header, NOERROR); } if (extract_addresses(header, n, daemon->namebuff, now, is_sign, check_rebind, checking_disabled)) { my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; } } /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ if (munged) { header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); } /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide sections of the packet. Find the new length here and put back pseudoheader if it was removed. */ return resize_packet(header, n, pheader, plen); }
/* parse the received packet. returns xfrd packet result code. */ static enum xfrd_packet_result xfrd_parse_received_xfr_packet(xfrd_zone_t* zone, buffer_type* packet, xfrd_soa_t* soa) { size_t rr_count; size_t qdcount = QDCOUNT(packet); size_t ancount = ANCOUNT(packet), ancount_todo; int done = 0; /* has to be axfr / ixfr reply */ if(!buffer_available(packet, QHEADERSZ)) { log_msg(LOG_INFO, "packet too small"); return xfrd_packet_bad; } /* only check ID in first response message. Could also check that * AA bit and QR bit are set, but not needed. */ DEBUG(DEBUG_XFRD,2, (LOG_INFO, "got query with ID %d and %d needed", ID(packet), zone->query_id)); if(ID(packet) != zone->query_id) { log_msg(LOG_ERR, "xfrd: zone %s received bad query id from %s, " "dropped", zone->apex_str, zone->master->ip_address_spec); return xfrd_packet_bad; } /* check RCODE in all response messages */ if(RCODE(packet) != RCODE_OK) { log_msg(LOG_ERR, "xfrd: zone %s received error code %s from " "%s", zone->apex_str, rcode2str(RCODE(packet)), zone->master->ip_address_spec); if (RCODE(packet) == RCODE_IMPL || RCODE(packet) == RCODE_FORMAT) { return xfrd_packet_notimpl; } return xfrd_packet_bad; } /* check TSIG */ if(zone->master->key_options) { if(!xfrd_xfr_process_tsig(zone, packet)) { DEBUG(DEBUG_XFRD,1, (LOG_ERR, "dropping xfr reply due " "to bad TSIG")); return xfrd_packet_bad; } } buffer_skip(packet, QHEADERSZ); /* skip question section */ for(rr_count = 0; rr_count < qdcount; ++rr_count) { if (!packet_skip_rr(packet, 1)) { log_msg(LOG_ERR, "xfrd: zone %s, from %s: bad RR in " "question section", zone->apex_str, zone->master->ip_address_spec); return xfrd_packet_bad; } } if(zone->msg_rr_count == 0 && ancount == 0) { if(zone->tcp_conn == -1 && TC(packet)) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: TC flagged")); return xfrd_packet_tcp; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: too short xfr packet: no " "answer")); return xfrd_packet_bad; } ancount_todo = ancount; if(zone->msg_rr_count == 0) { /* parse the first RR, see if it is a SOA */ if(!packet_skip_dname(packet) || !xfrd_parse_soa_info(packet, soa)) { DEBUG(DEBUG_XFRD,1, (LOG_ERR, "xfrd: zone %s, from %s: " "no SOA begins answer" " section", zone->apex_str, zone->master->ip_address_spec)); return xfrd_packet_bad; } if(zone->soa_disk_acquired != 0 && zone->state != xfrd_zone_expired /* if expired - accept anything */ && compare_serial(ntohl(soa->serial), ntohl(zone->soa_disk.serial)) < 0) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s ignoring old serial from %s", zone->apex_str, zone->master->ip_address_spec)); VERBOSITY(1, (LOG_INFO, "xfrd: zone %s ignoring old serial from %s", zone->apex_str, zone->master->ip_address_spec)); return xfrd_packet_bad; } if(zone->soa_disk_acquired != 0 && zone->soa_disk.serial == soa->serial) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s got " "update indicating " "current serial", zone->apex_str)); /* (even if notified) the lease on the current soa is renewed */ zone->soa_disk_acquired = xfrd_time(); if(zone->soa_nsd.serial == soa->serial) zone->soa_nsd_acquired = xfrd_time(); xfrd_set_zone_state(zone, xfrd_zone_ok); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s is ok", zone->apex_str)); if(zone->soa_notified_acquired == 0) { /* not notified or anything, so stop asking around */ zone->round_num = -1; /* next try start a new round */ xfrd_set_timer_refresh(zone); return xfrd_packet_newlease; } /* try next master */ return xfrd_packet_bad; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "IXFR reply has ok serial (have \ %u, reply %u).", ntohl(zone->soa_disk.serial), ntohl(soa->serial))); /* serial is newer than soa_disk */ if(ancount == 1) { /* single record means it is like a notify */ (void)xfrd_handle_incoming_notify(zone, soa); } else if(zone->soa_notified_acquired && zone->soa_notified.serial && compare_serial(ntohl(zone->soa_notified.serial), ntohl(soa->serial)) < 0) { /* this AXFR/IXFR notifies me that an even newer serial exists */ zone->soa_notified.serial = soa->serial; } zone->msg_new_serial = ntohl(soa->serial); zone->msg_rr_count = 1; zone->msg_is_ixfr = 0; if(zone->soa_disk_acquired) zone->msg_old_serial = ntohl(zone->soa_disk.serial); else zone->msg_old_serial = 0; ancount_todo = ancount - 1; } if(zone->tcp_conn == -1 && TC(packet)) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s received TC from %s. retry tcp.", zone->apex_str, zone->master->ip_address_spec)); return xfrd_packet_tcp; } if(zone->tcp_conn == -1 && ancount < 2) { /* too short to be a real ixfr/axfr data transfer: need at */ /* least two RRs in the answer section. */ /* The serial is newer, so try tcp to this master. */ DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: udp reply is short. Try " "tcp anyway.")); return xfrd_packet_tcp; } if(!xfrd_xfr_check_rrs(zone, packet, ancount_todo, &done, soa)) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s sent bad xfr " "reply.", zone->apex_str)); return xfrd_packet_bad; } if(zone->tcp_conn == -1 && done == 0) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: udp reply incomplete")); return xfrd_packet_bad; } if(done == 0) return xfrd_packet_more; if(zone->master->key_options) { if(zone->tsig.updates_since_last_prepare != 0) { log_msg(LOG_INFO, "xfrd: last packet of reply has no " "TSIG"); return xfrd_packet_bad; } } return xfrd_packet_transfer; }
#define OPCODE_SHIFT 11 #define Z_SHIFT 4 static char* print_header(struct header *h, char *buf, size_t max) { snprintf(buf, max, "id:0x.... %s op:%i %s %s %s %s z:%i r:%s qd:%i an:%i ns:%i ar:%i", (h->flags & QR_MASK) ? "QR":" ", (int)(OPCODE(h->flags) >> OPCODE_SHIFT), (h->flags & AA_MASK) ? "AA":" ", (h->flags & TC_MASK) ? "TC":" ", (h->flags & RD_MASK) ? "RD":" ", (h->flags & RA_MASK) ? "RA":" ", ((h->flags & Z_MASK) >> Z_SHIFT), rcodetostr(RCODE(h->flags)), h->qdcount, h->ancount, h->nscount, h->arcount); return buf; } static void dump_packet(const void *data, size_t len) { char buf[1024]; struct packed p; struct header h; struct query q; struct rr rr; int i, an, ns, ar, n;
static void handle_tcp_reading(netio_type *netio, netio_handler_type *handler, netio_event_types_type event_types) { struct tcp_handler_data *data = (struct tcp_handler_data *) handler->user_data; ssize_t received; if (event_types & NETIO_EVENT_TIMEOUT) { /* Connection timed out. */ cleanup_tcp_handler(netio, handler); return; } if (data->nsd->tcp_query_count > 0 && data->query_count >= data->nsd->tcp_query_count) { /* No more queries allowed on this tcp connection. */ cleanup_tcp_handler(netio, handler); return; } assert(event_types & NETIO_EVENT_READ); if (data->bytes_transmitted == 0) { query_reset(data->query, TCP_MAX_MESSAGE_LEN, 1); } /* * Check if we received the leading packet length bytes yet. */ if (data->bytes_transmitted < sizeof(uint16_t)) { received = read(handler->fd, (char *) &data->query->tcplen + data->bytes_transmitted, sizeof(uint16_t) - data->bytes_transmitted); if (received == -1) { if (errno == EAGAIN || errno == EINTR) { /* * Read would block, wait until more * data is available. */ return; } else { #ifdef ECONNRESET if (verbosity >= 2 || errno != ECONNRESET) #endif /* ECONNRESET */ log_msg(LOG_ERR, "failed reading from tcp: %s", strerror(errno)); cleanup_tcp_handler(netio, handler); return; } } else if (received == 0) { /* EOF */ cleanup_tcp_handler(netio, handler); return; } data->bytes_transmitted += received; if (data->bytes_transmitted < sizeof(uint16_t)) { /* * Not done with the tcplen yet, wait for more * data to become available. */ return; } assert(data->bytes_transmitted == sizeof(uint16_t)); data->query->tcplen = ntohs(data->query->tcplen); /* * Minimum query size is: * * Size of the header (12) * + Root domain name (1) * + Query class (2) * + Query type (2) */ if (data->query->tcplen < QHEADERSZ + 1 + sizeof(uint16_t) + sizeof(uint16_t)) { VERBOSITY(2, (LOG_WARNING, "packet too small, dropping tcp connection")); cleanup_tcp_handler(netio, handler); return; } if (data->query->tcplen > data->query->maxlen) { VERBOSITY(2, (LOG_WARNING, "insufficient tcp buffer, dropping connection")); cleanup_tcp_handler(netio, handler); return; } buffer_set_limit(data->query->packet, data->query->tcplen); } assert(buffer_remaining(data->query->packet) > 0); /* Read the (remaining) query data. */ received = read(handler->fd, buffer_current(data->query->packet), buffer_remaining(data->query->packet)); if (received == -1) { if (errno == EAGAIN || errno == EINTR) { /* * Read would block, wait until more data is * available. */ return; } else { #ifdef ECONNRESET if (verbosity >= 2 || errno != ECONNRESET) #endif /* ECONNRESET */ log_msg(LOG_ERR, "failed reading from tcp: %s", strerror(errno)); cleanup_tcp_handler(netio, handler); return; } } else if (received == 0) { /* EOF */ cleanup_tcp_handler(netio, handler); return; } data->bytes_transmitted += received; buffer_skip(data->query->packet, received); if (buffer_remaining(data->query->packet) > 0) { /* * Message not yet complete, wait for more data to * become available. */ return; } assert(buffer_position(data->query->packet) == data->query->tcplen); /* Account... */ #ifdef BIND8_STATS # ifndef INET6 STATUP(data->nsd, ctcp); # else if (data->query->addr.ss_family == AF_INET) { STATUP(data->nsd, ctcp); } else if (data->query->addr.ss_family == AF_INET6) { STATUP(data->nsd, ctcp6); } # endif #endif /* BIND8_STATS */ /* We have a complete query, process it. */ /* tcp-query-count: handle query counter ++ */ data->query_count++; buffer_flip(data->query->packet); data->query_state = server_process_query(data->nsd, data->query); if (data->query_state == QUERY_DISCARDED) { /* Drop the packet and the entire connection... */ STATUP(data->nsd, dropped); #if defined(BIND8_STATS) && defined(USE_ZONE_STATS) if (data->query->zone) { ZTATUP(data->query->zone, dropped); } #endif cleanup_tcp_handler(netio, handler); return; } #ifdef BIND8_STATS if (RCODE(data->query->packet) == RCODE_OK && !AA(data->query->packet)) { STATUP(data->nsd, nona); ZTATUP(data->query->zone, nona); } # ifdef USE_ZONE_STATS # ifndef INET6 ZTATUP(data->query->zone, ctcp); # else if (data->query->addr.ss_family == AF_INET) { ZTATUP(data->query->zone, ctcp); } else if (data->query->addr.ss_family == AF_INET6) { ZTATUP(data->query->zone, ctcp6); } # endif # endif /* USE_ZONE_STATS */ #endif /* BIND8_STATS */ query_add_optional(data->query, data->nsd); /* Switch to the tcp write handler. */ buffer_flip(data->query->packet); data->query->tcplen = buffer_remaining(data->query->packet); data->bytes_transmitted = 0; handler->timeout->tv_sec = data->nsd->tcp_timeout; handler->timeout->tv_nsec = 0L; timespec_add(handler->timeout, netio_current_time(netio)); handler->event_types = NETIO_EVENT_WRITE | NETIO_EVENT_TIMEOUT; handler->event_handler = handle_tcp_writing; }
static void handle_udp(netio_type *ATTR_UNUSED(netio), netio_handler_type *handler, netio_event_types_type event_types) { struct udp_handler_data *data = (struct udp_handler_data *) handler->user_data; int received, sent; struct query *q = data->query; if (!(event_types & NETIO_EVENT_READ)) { return; } /* Account... */ #ifdef BIND8_STATS if (data->socket->addr->ai_family == AF_INET) { STATUP(data->nsd, qudp); } else if (data->socket->addr->ai_family == AF_INET6) { STATUP(data->nsd, qudp6); } #endif /* Initialize the query... */ query_reset(q, UDP_MAX_MESSAGE_LEN, 0); received = recvfrom(handler->fd, buffer_begin(q->packet), buffer_remaining(q->packet), 0, (struct sockaddr *)&q->addr, &q->addrlen); if (received == -1) { if (errno != EAGAIN && errno != EINTR) { log_msg(LOG_ERR, "recvfrom failed: %s", strerror(errno)); STATUP(data->nsd, rxerr); /* No zone statup */ } } else { buffer_skip(q->packet, received); buffer_flip(q->packet); /* Process and answer the query... */ if (server_process_query(data->nsd, q) != QUERY_DISCARDED) { #ifdef BIND8_STATS if (RCODE(q->packet) == RCODE_OK && !AA(q->packet)) { STATUP(data->nsd, nona); ZTATUP(q->zone, nona); } # ifdef USE_ZONE_STATS if (data->socket->addr->ai_family == AF_INET) { ZTATUP(q->zone, qudp); } else if (data->socket->addr->ai_family == AF_INET6) { ZTATUP(q->zone, qudp6); } # endif #endif /* Add EDNS0 and TSIG info if necessary. */ query_add_optional(q, data->nsd); buffer_flip(q->packet); sent = sendto(handler->fd, buffer_begin(q->packet), buffer_remaining(q->packet), 0, (struct sockaddr *) &q->addr, q->addrlen); if (sent == -1) { log_msg(LOG_ERR, "sendto failed: %s", strerror(errno)); STATUP(data->nsd, txerr); ZTATUP(q->zone, txerr); } else if ((size_t) sent != buffer_remaining(q->packet)) { log_msg(LOG_ERR, "sent %d in place of %d bytes", sent, (int) buffer_remaining(q->packet)); #ifdef BIND8_STATS } else { /* Account the rcode & TC... */ STATUP2(data->nsd, rcode, RCODE(q->packet)); ZTATUP2(q->zone, rcode, RCODE(q->packet)); if (TC(q->packet)) { STATUP(data->nsd, truncated); ZTATUP(q->zone, truncated); } #endif /* BIND8_STATS */ } #ifdef BIND8_STATS } else { STATUP(data->nsd, dropped); # ifdef USE_ZONE_STATS if (q->zone) { ZTATUP(q->zone, dropped); } # endif #endif } } }
/** classify the query in a number of different types, each has separate * ratelimiting, so that positive queries are not impeded by others */ static uint16_t rrl_classify(query_type* query, const uint8_t** d, size_t* d_len) { if(RCODE(query->packet) == RCODE_NXDOMAIN) { if(query->zone && query->zone->apex) { *d = dname_name(domain_dname(query->zone->apex)); *d_len = domain_dname(query->zone->apex)->name_size; } return rrl_type_nxdomain; } if(RCODE(query->packet) != RCODE_OK) { if(query->zone && query->zone->apex) { *d = dname_name(domain_dname(query->zone->apex)); *d_len = domain_dname(query->zone->apex)->name_size; } return rrl_type_error; } if(query->delegation_domain) { *d = dname_name(domain_dname(query->delegation_domain)); *d_len = domain_dname(query->delegation_domain)->name_size; return rrl_type_referral; } if(query->qtype == TYPE_ANY) { if(query->qname) { *d = dname_name(query->qname); *d_len = query->qname->name_size; } return rrl_type_any; } if(query->qtype == TYPE_RRSIG) { if(query->qname) { *d = dname_name(query->qname); *d_len = query->qname->name_size; } return rrl_type_rrsig; } if(query->wildcard_domain) { *d = dname_name(domain_dname(query->wildcard_domain)); *d_len = domain_dname(query->wildcard_domain)->name_size; return rrl_type_wildcard; } if(ANCOUNT(query->packet) == 0) { if(query->zone && query->zone->apex) { *d = dname_name(domain_dname(query->zone->apex)); *d_len = domain_dname(query->zone->apex)->name_size; } return rrl_type_nodata; } if(query->qtype == TYPE_DNSKEY) { if(query->qname) { *d = dname_name(query->qname); *d_len = query->qname->name_size; } return rrl_type_dnskey; } /* positive */ if(query->qname) { *d = dname_name(query->qname); *d_len = query->qname->name_size; } return rrl_type_positive; }