static void ipv4acd_reset(sd_ipv4acd *acd) { assert(acd); acd->timer_event_source = sd_event_source_unref(acd->timer_event_source); acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source); acd->fd = safe_close(acd->fd); ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true); }
static void ipv4acd_stop(sd_ipv4acd *ll) { assert(ll); ll->receive_message = sd_event_source_unref(ll->receive_message); ll->fd = safe_close(ll->fd); ll->timer = sd_event_source_unref(ll->timer); log_ipv4acd_debug(ll, "STOPPED"); ipv4acd_set_state (ll, IPV4ACD_STATE_INIT, true); }
int sd_ipv4acd_start(sd_ipv4acd *acd) { int r; assert_return(acd, -EINVAL); assert_return(acd->event, -EINVAL); assert_return(acd->ifindex > 0, -EINVAL); assert_return(acd->address != 0, -EINVAL); assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); if (r < 0) return r; safe_close(acd->fd); acd->fd = r; acd->defend_window = 0; acd->n_conflict = 0; r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd); if (r < 0) goto fail; r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority); if (r < 0) goto fail; (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message"); r = ipv4acd_set_next_wakeup(acd, 0, 0); if (r < 0) goto fail; ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); return 0; fail: ipv4acd_reset(acd); return r; }
static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { sd_ipv4acd *ll = userdata; int r = 0; assert(ll); switch (ll->state) { case IPV4ACD_STATE_INIT: ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_PROBE, true); if (ll->conflict >= MAX_CONFLICTS) { log_ipv4acd_notice(ll, "Max conflicts reached, delaying by %us", RATE_LIMIT_INTERVAL); r = ipv4acd_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); if (r < 0) goto out; ll->conflict = 0; } else { r = ipv4acd_set_next_wakeup(ll, 0, PROBE_WAIT); if (r < 0) goto out; } break; case IPV4ACD_STATE_WAITING_PROBE: case IPV4ACD_STATE_PROBING: /* Send a probe */ r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr); if (r < 0) { log_ipv4acd_error_errno(ll, r, "Failed to send ARP probe: %m"); goto out; } else { _cleanup_free_ char *address = NULL; union in_addr_union addr = { .in.s_addr = ll->address }; r = in_addr_to_string(AF_INET, &addr, &address); if (r >= 0) log_ipv4acd_debug(ll, "Probing %s", address); } if (ll->iteration < PROBE_NUM - 2) { ipv4acd_set_state(ll, IPV4ACD_STATE_PROBING, false); r = ipv4acd_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); if (r < 0) goto out; } else { ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_ANNOUNCE, true); r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); if (r < 0) goto out; } break; case IPV4ACD_STATE_ANNOUNCING: if (ll->iteration >= ANNOUNCE_NUM - 1) { ipv4acd_set_state(ll, IPV4ACD_STATE_RUNNING, false); break; } case IPV4ACD_STATE_WAITING_ANNOUNCE: /* Send announcement packet */ r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); if (r < 0) { log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m"); goto out; } else log_ipv4acd_debug(ll, "ANNOUNCE"); ipv4acd_set_state(ll, IPV4ACD_STATE_ANNOUNCING, false); r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); if (r < 0) goto out; if (ll->iteration == 0) { ll->conflict = 0; ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_BIND); } break; default: assert_not_reached("Invalid state."); } out: if (r < 0) sd_ipv4acd_stop(ll); return 1; } static void ipv4acd_on_conflict(sd_ipv4acd *ll) { _cleanup_free_ char *address = NULL; union in_addr_union addr = { .in.s_addr = ll->address }; int r; assert(ll); ll->conflict++; r = in_addr_to_string(AF_INET, &addr, &address); if (r >= 0) log_ipv4acd_debug(ll, "Conflict on %s (%u)", address, ll->conflict); ipv4acd_stop(ll); ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_CONFLICT); } static int ipv4acd_on_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { sd_ipv4acd *ll = userdata; struct ether_arp packet; int r; assert(ll); assert(fd >= 0); r = read(fd, &packet, sizeof(struct ether_arp)); if (r < (int) sizeof(struct ether_arp)) goto out; switch (ll->state) { case IPV4ACD_STATE_ANNOUNCING: case IPV4ACD_STATE_RUNNING: if (ipv4acd_arp_conflict(ll, &packet)) { usec_t ts; assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &ts) >= 0); /* Defend address */ if (ts > ll->defend_window) { ll->defend_window = ts + DEFEND_INTERVAL * USEC_PER_SEC; r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); if (r < 0) { log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m"); goto out; } else log_ipv4acd_debug(ll, "DEFEND"); } else ipv4acd_on_conflict(ll); } break; case IPV4ACD_STATE_WAITING_PROBE: case IPV4ACD_STATE_PROBING: case IPV4ACD_STATE_WAITING_ANNOUNCE: /* BPF ensures this packet indicates a conflict */ ipv4acd_on_conflict(ll); break; default: assert_not_reached("Invalid state."); } out: if (r < 0) sd_ipv4acd_stop(ll); return 1; }
static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { sd_ipv4acd *acd = userdata; int r = 0; assert(acd); switch (acd->state) { case IPV4ACD_STATE_STARTED: ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); if (acd->n_conflict >= MAX_CONFLICTS) { char ts[FORMAT_TIMESPAN_MAX]; log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); if (r < 0) goto fail; } else { r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); if (r < 0) goto fail; } break; case IPV4ACD_STATE_WAITING_PROBE: case IPV4ACD_STATE_PROBING: /* Send a probe */ r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); if (r < 0) { log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); goto fail; } else { _cleanup_free_ char *address = NULL; union in_addr_union addr = { .in.s_addr = acd->address }; (void) in_addr_to_string(AF_INET, &addr, &address); log_ipv4acd(acd, "Probing %s", strna(address)); } if (acd->n_iteration < PROBE_NUM - 2) { ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); if (r < 0) goto fail; } else { ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); if (r < 0) goto fail; } break; case IPV4ACD_STATE_ANNOUNCING: if (acd->n_iteration >= ANNOUNCE_NUM - 1) { ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false); break; } /* fall through */ case IPV4ACD_STATE_WAITING_ANNOUNCE: /* Send announcement packet */ r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); if (r < 0) { log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); goto fail; } else log_ipv4acd(acd, "ANNOUNCE"); ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0); if (r < 0) goto fail; if (acd->n_iteration == 0) { acd->n_conflict = 0; ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND); } break; default: assert_not_reached("Invalid state."); } return 0; fail: sd_ipv4acd_stop(acd); return 0; } static void ipv4acd_on_conflict(sd_ipv4acd *acd) { _cleanup_free_ char *address = NULL; union in_addr_union addr = { .in.s_addr = acd->address }; assert(acd); acd->n_conflict++; (void) in_addr_to_string(AF_INET, &addr, &address); log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); ipv4acd_reset(acd); ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); } static int ipv4acd_on_packet( sd_event_source *s, int fd, uint32_t revents, void *userdata) { sd_ipv4acd *acd = userdata; struct ether_arp packet; ssize_t n; int r; assert(s); assert(acd); assert(fd >= 0); n = recv(fd, &packet, sizeof(struct ether_arp), 0); if (n < 0) { if (errno == EAGAIN || errno == EINTR) return 0; log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m"); goto fail; } if ((size_t) n != sizeof(struct ether_arp)) { log_ipv4acd(acd, "Ignoring too short ARP packet."); return 0; } switch (acd->state) { case IPV4ACD_STATE_ANNOUNCING: case IPV4ACD_STATE_RUNNING: if (ipv4acd_arp_conflict(acd, &packet)) { usec_t ts; assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); /* Defend address */ if (ts > acd->defend_window) { acd->defend_window = ts + DEFEND_INTERVAL_USEC; r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); if (r < 0) { log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); goto fail; } else log_ipv4acd(acd, "DEFEND"); } else ipv4acd_on_conflict(acd); } break; case IPV4ACD_STATE_WAITING_PROBE: case IPV4ACD_STATE_PROBING: case IPV4ACD_STATE_WAITING_ANNOUNCE: /* BPF ensures this packet indicates a conflict */ ipv4acd_on_conflict(acd); break; default: assert_not_reached("Invalid state."); } return 0; fail: sd_ipv4acd_stop(acd); return 0; }