/* * xfrd-notify.c - notify sending routines * * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. * * See LICENSE for the license. * */ #include #include #include #include #include #include "xfrd-notify.h" #include "xfrd.h" #include "xfrd-tcp.h" #include "packet.h" #define XFRD_NOTIFY_RETRY_TIMOUT 15 /* seconds between retries sending NOTIFY */ /* start sending notifies */ static void notify_enable(struct notify_zone_t* zone, struct xfrd_soa* new_soa); /* stop sending notifies */ static void notify_disable(struct notify_zone_t* zone); /* setup the notify active state */ static void setup_notify_active(struct notify_zone_t* zone); /* returns if the notify send is done for the notify_current acl */ static int xfrd_handle_notify_reply(struct notify_zone_t* zone, buffer_type* packet); /* handle zone notify send */ static void xfrd_handle_notify_send(netio_type *netio, netio_handler_type *handler, netio_event_types_type event_types); static void xfrd_notify_next(struct notify_zone_t* zone); static void xfrd_notify_send_udp(struct notify_zone_t* zone, buffer_type* packet); static void notify_disable(struct notify_zone_t* zone) { zone->notify_current = 0; zone->notify_send_handler.timeout = NULL; if(zone->notify_send_handler.fd != -1) { close(zone->notify_send_handler.fd); zone->notify_send_handler.fd = -1; } 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_t* wz = xfrd->notify_waiting_first; assert(wz->is_waiting); wz->is_waiting = 0; xfrd->notify_waiting_first = wz->waiting_next; 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_t* tree, netio_type* netio, region_type* region, const dname_type* apex, zone_options_t* options, zone_type* dbzone) { struct notify_zone_t* not = (struct notify_zone_t*) region_alloc(region, sizeof(struct notify_zone_t)); memset(not, 0, sizeof(struct notify_zone_t)); not->apex = apex; 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)); if(dbzone && dbzone->soa_rrset && dbzone->soa_rrset->rrs) { xfrd_copy_soa(not->current_soa, dbzone->soa_rrset->rrs); } not->is_waiting = 0; not->notify_send_handler.fd = -1; not->notify_send_handler.timeout = 0; not->notify_send_handler.user_data = not; not->notify_send_handler.event_types = NETIO_EVENT_READ|NETIO_EVENT_TIMEOUT; not->notify_send_handler.event_handler = xfrd_handle_notify_send; netio_add_handler(netio, ¬->notify_send_handler); tsig_create_record_custom(¬->notify_tsig, region, 0, 0, 4); not->notify_current = 0; rbtree_insert(tree, (rbnode_t*)not); } 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; } static void xfrd_notify_next(struct notify_zone_t* zone) { /* advance to next in acl */ zone->notify_current = zone->notify_current->next; zone->notify_retry = 0; if(zone->notify_current == 0) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: no more notify-send acls. stop notify.", zone->apex_str)); notify_disable(zone); return; } } static void xfrd_notify_send_udp(struct notify_zone_t* zone, buffer_type* packet) { if(zone->notify_send_handler.fd != -1) close(zone->notify_send_handler.fd); zone->notify_send_handler.fd = -1; /* Set timeout for next reply */ zone->notify_timeout.tv_sec = xfrd_time() + XFRD_NOTIFY_RETRY_TIMOUT; /* send NOTIFY to secondary. */ xfrd_setup_packet(packet, TYPE_SOA, CLASS_IN, zone->apex); zone->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->notify_current->key_options) { xfrd_tsig_sign_request(packet, &zone->notify_tsig, zone->notify_current); } buffer_flip(packet); zone->notify_send_handler.fd = xfrd_send_udp(zone->notify_current, packet, zone->options->outgoing_interface); if(zone->notify_send_handler.fd == -1) { log_msg(LOG_ERR, "xfrd: zone %s: could not send notify #%d to %s", zone->apex_str, zone->notify_retry, zone->notify_current->ip_address_spec); return; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: sent notify #%d to %s", zone->apex_str, zone->notify_retry, zone->notify_current->ip_address_spec)); } static void xfrd_handle_notify_send(netio_type* ATTR_UNUSED(netio), netio_handler_type *handler, netio_event_types_type event_types) { struct notify_zone_t* zone = (struct notify_zone_t*)handler->user_data; buffer_type* packet = xfrd_get_temp_buffer(); assert(zone->notify_current); if(zone->is_waiting) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: notify waiting, skipped, %s", zone->apex_str)); assert(zone->notify_send_handler.fd == -1); return; } if(event_types & NETIO_EVENT_READ) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: read notify ACK", zone->apex_str)); assert(handler->fd != -1); if(xfrd_udp_read_packet(packet, zone->notify_send_handler.fd)) { if(xfrd_handle_notify_reply(zone, packet)) xfrd_notify_next(zone); } } else if(event_types & NETIO_EVENT_TIMEOUT) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: notify timeout", zone->apex_str)); /* timeout, try again */ } /* see if notify is still enabled */ if(zone->notify_current) { zone->notify_retry++; if(zone->notify_retry > zone->options->notify_retry) { log_msg(LOG_ERR, "xfrd: zone %s: max notify send count reached, %s unreachable", zone->apex_str, zone->notify_current->ip_address_spec); xfrd_notify_next(zone); } } if(zone->notify_current) { /* try again */ xfrd_notify_send_udp(zone, packet); } } static void setup_notify_active(struct notify_zone_t* zone) { zone->notify_retry = 0; zone->notify_current = zone->options->notify; zone->notify_send_handler.timeout = &zone->notify_timeout; zone->notify_timeout.tv_sec = xfrd_time(); zone->notify_timeout.tv_nsec = 0; } static void notify_enable(struct notify_zone_t* zone, struct xfrd_soa* new_soa) { if(!zone->options->notify) { return; /* no notify acl, nothing to do */ } if(new_soa == NULL) memset(zone->current_soa, 0, sizeof(xfrd_soa_t)); else memcpy(zone->current_soa, new_soa, sizeof(xfrd_soa_t)); 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->notify; zone->is_waiting = 1; zone->waiting_next = NULL; if(xfrd->notify_waiting_last) { xfrd->notify_waiting_last->waiting_next = zone; } else { xfrd->notify_waiting_first = zone; } xfrd->notify_waiting_last = zone; zone->notify_send_handler.timeout = NULL; DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s: notify on waiting list.", zone->apex_str)); } void xfrd_send_notify(rbtree_t* tree, const dname_type* apex, struct xfrd_soa* new_soa) { /* lookup the zone */ struct notify_zone_t* zone = (struct notify_zone_t*) rbtree_search(tree, apex); assert(zone); notify_enable(zone, new_soa); } void notify_handle_master_zone_soainfo(rbtree_t* tree, const dname_type* apex, struct xfrd_soa* new_soa) { /* lookup the zone */ struct notify_zone_t* zone = (struct notify_zone_t*) rbtree_search(tree, apex); assert(zone); /* check if SOA changed */ if( (new_soa == NULL && zone->current_soa->serial == 0) || (new_soa && new_soa->serial == zone->current_soa->serial)) return; notify_enable(zone, new_soa); } void close_notify_fds(rbtree_t* tree) { struct notify_zone_t* zone; RBTREE_FOR(zone, struct notify_zone_t*, tree) { if(zone->notify_send_handler.fd != -1) { close(zone->notify_send_handler.fd); zone->notify_send_handler.fd = -1; } } }