/* * xfrd-notify.c - notify sending routines * * Copyright (c) 2006, NLnet Labs. All rights reserved. * * See LICENSE for the license. * */ #include "config.h" #include #include #include #include #include "xfrd-notify.h" #include "xfrd.h" #include "xfrd-tcp.h" #include "packet.h" #define XFRD_NOTIFY_RETRY_TIMOUT 3 /* seconds between retries sending NOTIFY */ /* start sending notifies */ static void notify_enable(struct notify_zone* zone, struct xfrd_soa* new_soa); /* setup the notify active state */ static void setup_notify_active(struct notify_zone* zone); /* handle zone notify send */ static void xfrd_handle_notify_send(int fd, short event, void* arg); static int xfrd_notify_send_udp(struct notify_zone* zone, int index); static void notify_send_disable(struct notify_zone* zone) { zone->notify_send_enable = 0; event_del(&zone->notify_send_handler); if(zone->notify_send_handler.ev_fd != -1) { close(zone->notify_send_handler.ev_fd); zone->notify_send_handler.ev_fd = -1; } } static void notify_send6_disable(struct notify_zone* zone) { zone->notify_send6_enable = 0; event_del(&zone->notify_send6_handler); if(zone->notify_send6_handler.ev_fd != -1) { close(zone->notify_send6_handler.ev_fd); zone->notify_send6_handler.ev_fd = -1; } } void notify_disable(struct notify_zone* zone) { zone->notify_current = 0; /* if added, then remove */ if(zone->notify_send_enable) { notify_send_disable(zone); } if(zone->notify_send6_enable) { notify_send6_disable(zone); } if(xfrd->notify_udp_num == XFRD_MAX_UDP_NOTIFY) { /* find next waiting and needy zone */ while(xfrd->notify_waiting_first) { /* snip off */ struct notify_zone* wz = xfrd->notify_waiting_first; assert(wz->is_waiting); wz->is_waiting = 0; xfrd->notify_waiting_first = wz->waiting_next; if(wz->waiting_next) wz->waiting_next->waiting_prev = NULL; if(xfrd->notify_waiting_last == wz) xfrd->notify_waiting_last = NULL; /* see if this zone needs notify sending */ if(wz->notify_current) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: notify off waiting list.", zone->apex_str) ); setup_notify_active(wz); return; } } } xfrd->notify_udp_num--; } void init_notify_send(rbtree_type* tree, region_type* region, struct zone_options* options) { struct notify_zone* not = (struct notify_zone*) region_alloc(region, sizeof(struct notify_zone)); memset(not, 0, sizeof(struct notify_zone)); not->apex = options->node.key; not->apex_str = options->name; not->node.key = not->apex; not->options = options; /* if master zone and have a SOA */ not->current_soa = (struct xfrd_soa*)region_alloc(region, sizeof(struct xfrd_soa)); memset(not->current_soa, 0, sizeof(struct xfrd_soa)); not->notify_send_handler.ev_fd = -1; not->notify_send6_handler.ev_fd = -1; not->is_waiting = 0; not->notify_send_enable = 0; not->notify_send6_enable = 0; tsig_create_record_custom(¬->notify_tsig, NULL, 0, 0, 4); not->notify_current = 0; rbtree_insert(tree, (rbnode_type*)not); } void xfrd_del_notify(xfrd_state_type* xfrd, const dname_type* dname) { /* find it */ struct notify_zone* not = (struct notify_zone*)rbtree_delete( xfrd->notify_zones, dname); if(!not) return; /* waiting list */ if(not->is_waiting) { if(not->waiting_prev) not->waiting_prev->waiting_next = not->waiting_next; else xfrd->notify_waiting_first = not->waiting_next; if(not->waiting_next) not->waiting_next->waiting_prev = not->waiting_prev; else xfrd->notify_waiting_last = not->waiting_prev; not->is_waiting = 0; } /* event */ if(not->notify_send_enable || not->notify_send6_enable) { notify_disable(not); } /* del tsig */ tsig_delete_record(¬->notify_tsig, NULL); /* free it */ region_recycle(xfrd->region, not->current_soa, sizeof(xfrd_soa_type)); /* the apex is recycled when the zone_options.node.key is removed */ region_recycle(xfrd->region, not, sizeof(*not)); } static int reply_pkt_is_ack(struct notify_zone* zone, buffer_type* packet, int index) { if((OPCODE(packet) != OPCODE_NOTIFY) || (QR(packet) == 0)) { log_msg(LOG_ERR, "xfrd: zone %s: received bad notify reply opcode/flags from %s", zone->apex_str, zone->pkts[index].dest->ip_address_spec); return 0; } /* we know it is OPCODE NOTIFY, QUERY_REPLY and for this zone */ if(ID(packet) != zone->pkts[index].notify_query_id) { log_msg(LOG_ERR, "xfrd: zone %s: received notify-ack with bad ID from %s", zone->apex_str, zone->pkts[index].dest->ip_address_spec); 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->pkts[index].dest->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->pkts[index].dest->ip_address_spec)); return 1; } /* compare sockaddr and acl_option addr and port numbers */ static int cmp_addr_equal(struct sockaddr* a, socklen_t a_len, struct acl_options* dest) { if(dest) { unsigned int destport = ((dest->port == 0)? (unsigned)atoi(TCP_PORT):dest->port); #ifdef INET6 struct sockaddr_storage* a1 = (struct sockaddr_storage*)a; if(a1->ss_family == AF_INET6 && dest->is_ipv6) { struct sockaddr_in6* a2 = (struct sockaddr_in6*)a; if(a_len < sizeof(struct sockaddr_in6)) return 0; /* too small */ if(ntohs(a2->sin6_port) != destport) return 0; /* different port number */ if(memcmp(&a2->sin6_addr, &dest->addr.addr6, sizeof(struct in6_addr)) != 0) return 0; /* different address */ return 1; } if(a1->ss_family == AF_INET6 || dest->is_ipv6) return 0; /* different address family */ else { #endif /* INET6 */ struct sockaddr_in* a3 = (struct sockaddr_in*)a; if(a_len < sizeof(struct sockaddr_in)) return 0; /* too small */ if(ntohs(a3->sin_port) != destport) return 0; /* different port number */ if(memcmp(&a3->sin_addr, &dest->addr.addr, sizeof(struct in_addr)) != 0) return 0; /* different address */ return 1; #ifdef INET6 } #endif } return 0; } static void notify_pkt_done(struct notify_zone* zone, int index) { zone->pkts[index].dest = NULL; zone->pkts[index].notify_retry = 0; zone->pkts[index].send_time = 0; zone->pkts[index].notify_query_id = 0; zone->notify_pkt_count--; } static void notify_pkt_retry(struct notify_zone* zone, int index) { if(++zone->pkts[index].notify_retry >= zone->options->pattern->notify_retry) { log_msg(LOG_ERR, "xfrd: zone %s: max notify send count reached, %s unreachable", zone->apex_str, zone->pkts[index].dest->ip_address_spec); notify_pkt_done(zone, index); return; } if(!xfrd_notify_send_udp(zone, index)) { notify_pkt_retry(zone, index); } } static void xfrd_handle_notify_reply(struct notify_zone* zone, buffer_type* packet, struct sockaddr* src, socklen_t srclen) { int i; for(i=0; ipkts[i].dest) continue; /* based on destination */ if(!cmp_addr_equal(src, srclen, zone->pkts[i].dest)) continue; if(reply_pkt_is_ack(zone, packet, i)) { /* is done */ notify_pkt_done(zone, i); return; } else { /* retry */ notify_pkt_retry(zone, i); return; } } } static int xfrd_notify_send_udp(struct notify_zone* zone, int index) { buffer_type* packet = xfrd_get_temp_buffer(); if(!zone->pkts[index].dest) return 0; /* send NOTIFY to secondary. */ xfrd_setup_packet(packet, TYPE_SOA, CLASS_IN, zone->apex, qid_generate()); zone->pkts[index].notify_query_id = ID(packet); OPCODE_SET(packet, OPCODE_NOTIFY); AA_SET(packet); if(zone->current_soa->serial != 0) { /* add current SOA to answer section */ ANCOUNT_SET(packet, 1); xfrd_write_soa_buffer(packet, zone->apex, zone->current_soa); } if(zone->pkts[index].dest->key_options) { xfrd_tsig_sign_request(packet, &zone->notify_tsig, zone->pkts[index].dest); } buffer_flip(packet); if((zone->pkts[index].dest->is_ipv6 && zone->notify_send6_handler.ev_fd == -1) || (!zone->pkts[index].dest->is_ipv6 && zone->notify_send_handler.ev_fd == -1)) { /* open fd */ int fd = xfrd_send_udp(zone->pkts[index].dest, packet, zone->options->pattern->outgoing_interface); if(fd == -1) { log_msg(LOG_ERR, "xfrd: zone %s: could not send notify #%d to %s", zone->apex_str, zone->pkts[index].notify_retry, zone->pkts[index].dest->ip_address_spec); return 0; } if(zone->pkts[index].dest->is_ipv6) zone->notify_send6_handler.ev_fd = fd; else zone->notify_send_handler.ev_fd = fd; } else { /* send on existing fd */ #ifdef INET6 struct sockaddr_storage to; #else struct sockaddr_in to; #endif /* INET6 */ int fd; socklen_t to_len = xfrd_acl_sockaddr_to( zone->pkts[index].dest, &to); if(zone->pkts[index].dest->is_ipv6) fd = zone->notify_send6_handler.ev_fd; else fd = zone->notify_send_handler.ev_fd; if(sendto(fd, buffer_current(packet), buffer_remaining(packet), 0, (struct sockaddr*)&to, to_len) == -1) { log_msg(LOG_ERR, "xfrd notify: sendto %s failed %s", zone->pkts[index].dest->ip_address_spec, strerror(errno)); return 0; } } zone->pkts[index].send_time = time(NULL); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: sent notify #%d to %s", zone->apex_str, zone->pkts[index].notify_retry, zone->pkts[index].dest->ip_address_spec)); return 1; } static void notify_timeout_check(struct notify_zone* zone) { time_t now = time(NULL); int i; for(i=0; ipkts[i].dest) continue; if(now >= zone->pkts[i].send_time + XFRD_NOTIFY_RETRY_TIMOUT) { notify_pkt_retry(zone, i); } } } static void notify_start_pkts(struct notify_zone* zone) { int i; if(!zone->notify_current) return; /* no more acl to send to */ for(i=0; ipkts[i].dest==NULL && zone->notify_current) { zone->pkts[i].dest = zone->notify_current; zone->notify_current = zone->notify_current->next; zone->pkts[i].notify_retry = 0; zone->pkts[i].notify_query_id = 0; zone->pkts[i].send_time = 0; zone->notify_pkt_count++; if(!xfrd_notify_send_udp(zone, i)) { notify_pkt_retry(zone, i); } } } } static void notify_setup_event(struct notify_zone* zone) { if(zone->notify_send_handler.ev_fd != -1) { int fd = zone->notify_send_handler.ev_fd; if(zone->notify_send_enable) { event_del(&zone->notify_send_handler); } zone->notify_timeout.tv_sec = XFRD_NOTIFY_RETRY_TIMOUT; memset(&zone->notify_send_handler, 0, sizeof(zone->notify_send_handler)); event_set(&zone->notify_send_handler, fd, EV_READ | EV_TIMEOUT, xfrd_handle_notify_send, zone); if(event_base_set(xfrd->event_base, &zone->notify_send_handler) != 0) log_msg(LOG_ERR, "notify_send: event_base_set failed"); if(event_add(&zone->notify_send_handler, &zone->notify_timeout) != 0) log_msg(LOG_ERR, "notify_send: event_add failed"); zone->notify_send_enable = 1; } if(zone->notify_send6_handler.ev_fd != -1) { int fd = zone->notify_send6_handler.ev_fd; if(zone->notify_send6_enable) { event_del(&zone->notify_send6_handler); } zone->notify_timeout.tv_sec = XFRD_NOTIFY_RETRY_TIMOUT; memset(&zone->notify_send6_handler, 0, sizeof(zone->notify_send6_handler)); event_set(&zone->notify_send6_handler, fd, EV_READ | EV_TIMEOUT, xfrd_handle_notify_send, zone); if(event_base_set(xfrd->event_base, &zone->notify_send6_handler) != 0) log_msg(LOG_ERR, "notify_send: event_base_set failed"); if(event_add(&zone->notify_send6_handler, &zone->notify_timeout) != 0) log_msg(LOG_ERR, "notify_send: event_add failed"); zone->notify_send6_enable = 1; } } static void xfrd_handle_notify_send(int fd, short event, void* arg) { struct notify_zone* zone = (struct notify_zone*)arg; buffer_type* packet = xfrd_get_temp_buffer(); if(zone->is_waiting) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: notify waiting, skipped, %s", zone->apex_str)); return; } if((event & EV_READ)) { struct sockaddr_storage src; socklen_t srclen = (socklen_t)sizeof(src); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: read notify ACK", zone->apex_str)); assert(fd != -1); if(xfrd_udp_read_packet(packet, fd, (struct sockaddr*)&src, &srclen)) { /* find entry, send retry or make entry NULL */ xfrd_handle_notify_reply(zone, packet, (struct sockaddr*)&src, srclen); } } if((event & EV_TIMEOUT)) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: notify timeout", zone->apex_str)); /* timeout, try again */ } /* see which pkts have timeouted, retry or NULL them */ notify_timeout_check(zone); /* start new packets if we have empty space */ notify_start_pkts(zone); /* see if we are done */ if(!zone->notify_current && !zone->notify_pkt_count) { /* we are done */ DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: no more notify-send acls. stop notify.", zone->apex_str)); notify_disable(zone); return; } notify_setup_event(zone); } static void setup_notify_active(struct notify_zone* zone) { zone->notify_pkt_count = 0; memset(zone->pkts, 0, sizeof(zone->pkts)); zone->notify_current = zone->options->pattern->notify; zone->notify_timeout.tv_sec = 0; zone->notify_timeout.tv_usec = 0; if(zone->notify_send_enable) notify_send_disable(zone); memset(&zone->notify_send_handler, 0, sizeof(zone->notify_send_handler)); event_set(&zone->notify_send_handler, -1, EV_TIMEOUT, xfrd_handle_notify_send, zone); if(event_base_set(xfrd->event_base, &zone->notify_send_handler) != 0) log_msg(LOG_ERR, "notifysend: event_base_set failed"); if(evtimer_add(&zone->notify_send_handler, &zone->notify_timeout) != 0) log_msg(LOG_ERR, "notifysend: evtimer_add failed"); zone->notify_send_enable = 1; } static void notify_enable(struct notify_zone* zone, struct xfrd_soa* new_soa) { if(!zone->options->pattern->notify) { return; /* no notify acl, nothing to do */ } if(new_soa == NULL) memset(zone->current_soa, 0, sizeof(xfrd_soa_type)); else memcpy(zone->current_soa, new_soa, sizeof(xfrd_soa_type)); if(zone->is_waiting) return; if(xfrd->notify_udp_num < XFRD_MAX_UDP_NOTIFY) { setup_notify_active(zone); xfrd->notify_udp_num++; return; } /* put it in waiting list */ zone->notify_current = zone->options->pattern->notify; zone->is_waiting = 1; zone->waiting_next = NULL; zone->waiting_prev = xfrd->notify_waiting_last; if(xfrd->notify_waiting_last) { xfrd->notify_waiting_last->waiting_next = zone; } else { xfrd->notify_waiting_first = zone; } xfrd->notify_waiting_last = zone; DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: notify on waiting list.", zone->apex_str)); } void xfrd_notify_start(struct notify_zone* zone, struct xfrd_state* xfrd) { xfrd_zone_type* xz; if(zone->is_waiting || zone->notify_send_enable || zone->notify_send6_enable) return; xz = (xfrd_zone_type*)rbtree_search(xfrd->zones, zone->apex); if(xz && xz->soa_nsd_acquired) notify_enable(zone, &xz->soa_nsd); else notify_enable(zone, NULL); } void xfrd_send_notify(rbtree_type* tree, const dname_type* apex, struct xfrd_soa* new_soa) { /* lookup the zone */ struct notify_zone* zone = (struct notify_zone*) rbtree_search(tree, apex); assert(zone); if(zone->notify_send_enable || zone->notify_send6_enable) notify_disable(zone); notify_enable(zone, new_soa); } void notify_handle_master_zone_soainfo(rbtree_type* tree, const dname_type* apex, struct xfrd_soa* new_soa) { /* lookup the zone */ struct notify_zone* zone = (struct notify_zone*) rbtree_search(tree, apex); if(!zone) return; /* got SOAINFO but zone was deleted meanwhile */ /* check if SOA changed */ if( (new_soa == NULL && zone->current_soa->serial == 0) || (new_soa && new_soa->serial == zone->current_soa->serial)) return; if(zone->notify_send_enable || zone->notify_send6_enable) notify_disable(zone); notify_enable(zone, new_soa); } void close_notify_fds(rbtree_type* tree) { struct notify_zone* zone; RBTREE_FOR(zone, struct notify_zone*, tree) { if(zone->notify_send_enable || zone->notify_send6_enable) notify_send_disable(zone); } }