| /* |
| * Self-announce |
| * (c) 2017-2019 Red Hat, Inc. |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| * See the COPYING file in the top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu-common.h" |
| #include "net/announce.h" |
| #include "net/net.h" |
| #include "qapi/clone-visitor.h" |
| #include "qapi/qapi-visit-net.h" |
| #include "qapi/qapi-commands-net.h" |
| #include "trace.h" |
| |
| static GData *named_timers; |
| |
| int64_t qemu_announce_timer_step(AnnounceTimer *timer) |
| { |
| int64_t step; |
| |
| step = timer->params.initial + |
| (timer->params.rounds - timer->round - 1) * |
| timer->params.step; |
| |
| if (step < 0 || step > timer->params.max) { |
| step = timer->params.max; |
| } |
| timer_mod(timer->tm, qemu_clock_get_ms(timer->type) + step); |
| |
| return step; |
| } |
| |
| /* |
| * If 'free_named' is true, then remove the timer from the list |
| * and free the timer itself. |
| */ |
| void qemu_announce_timer_del(AnnounceTimer *timer, bool free_named) |
| { |
| bool free_timer = false; |
| if (timer->tm) { |
| timer_free(timer->tm); |
| timer->tm = NULL; |
| } |
| qapi_free_strList(timer->params.interfaces); |
| timer->params.interfaces = NULL; |
| if (free_named && timer->params.has_id) { |
| AnnounceTimer *list_timer; |
| /* |
| * Sanity check: There should only be one timer on the list with |
| * the id. |
| */ |
| list_timer = g_datalist_get_data(&named_timers, timer->params.id); |
| assert(timer == list_timer); |
| free_timer = true; |
| g_datalist_remove_data(&named_timers, timer->params.id); |
| } |
| trace_qemu_announce_timer_del(free_named, free_timer, timer->params.id); |
| g_free(timer->params.id); |
| timer->params.id = NULL; |
| |
| if (free_timer) { |
| g_free(timer); |
| } |
| } |
| |
| /* |
| * Under BQL/main thread |
| * Reset the timer to the given parameters/type/notifier. |
| */ |
| void qemu_announce_timer_reset(AnnounceTimer *timer, |
| AnnounceParameters *params, |
| QEMUClockType type, |
| QEMUTimerCB *cb, |
| void *opaque) |
| { |
| /* |
| * We're under the BQL, so the current timer can't |
| * be firing, so we should be able to delete it. |
| */ |
| qemu_announce_timer_del(timer, false); |
| |
| QAPI_CLONE_MEMBERS(AnnounceParameters, &timer->params, params); |
| timer->round = params->rounds; |
| timer->type = type; |
| timer->tm = timer_new_ms(type, cb, opaque); |
| } |
| |
| #ifndef ETH_P_RARP |
| #define ETH_P_RARP 0x8035 |
| #endif |
| #define ARP_HTYPE_ETH 0x0001 |
| #define ARP_PTYPE_IP 0x0800 |
| #define ARP_OP_REQUEST_REV 0x3 |
| |
| static int announce_self_create(uint8_t *buf, |
| uint8_t *mac_addr) |
| { |
| /* Ethernet header. */ |
| memset(buf, 0xff, 6); /* destination MAC addr */ |
| memcpy(buf + 6, mac_addr, 6); /* source MAC addr */ |
| *(uint16_t *)(buf + 12) = htons(ETH_P_RARP); /* ethertype */ |
| |
| /* RARP header. */ |
| *(uint16_t *)(buf + 14) = htons(ARP_HTYPE_ETH); /* hardware addr space */ |
| *(uint16_t *)(buf + 16) = htons(ARP_PTYPE_IP); /* protocol addr space */ |
| *(buf + 18) = 6; /* hardware addr length (ethernet) */ |
| *(buf + 19) = 4; /* protocol addr length (IPv4) */ |
| *(uint16_t *)(buf + 20) = htons(ARP_OP_REQUEST_REV); /* opcode */ |
| memcpy(buf + 22, mac_addr, 6); /* source hw addr */ |
| memset(buf + 28, 0x00, 4); /* source protocol addr */ |
| memcpy(buf + 32, mac_addr, 6); /* target hw addr */ |
| memset(buf + 38, 0x00, 4); /* target protocol addr */ |
| |
| /* Padding to get up to 60 bytes (ethernet min packet size, minus FCS). */ |
| memset(buf + 42, 0x00, 18); |
| |
| return 60; /* len (FCS will be added by hardware) */ |
| } |
| |
| static void qemu_announce_self_iter(NICState *nic, void *opaque) |
| { |
| AnnounceTimer *timer = opaque; |
| uint8_t buf[60]; |
| int len; |
| bool skip; |
| |
| if (timer->params.has_interfaces) { |
| strList *entry = timer->params.interfaces; |
| /* Skip unless we find our name in the requested list */ |
| skip = true; |
| |
| while (entry) { |
| if (!strcmp(entry->value, nic->ncs->name)) { |
| /* Found us */ |
| skip = false; |
| break; |
| } |
| entry = entry->next; |
| } |
| } else { |
| skip = false; |
| } |
| |
| trace_qemu_announce_self_iter(timer->params.has_id ? timer->params.id : "_", |
| nic->ncs->name, |
| qemu_ether_ntoa(&nic->conf->macaddr), skip); |
| |
| if (!skip) { |
| len = announce_self_create(buf, nic->conf->macaddr.a); |
| |
| qemu_send_packet_raw(qemu_get_queue(nic), buf, len); |
| |
| /* if the NIC provides it's own announcement support, use it as well */ |
| if (nic->ncs->info->announce) { |
| nic->ncs->info->announce(nic->ncs); |
| } |
| } |
| } |
| static void qemu_announce_self_once(void *opaque) |
| { |
| AnnounceTimer *timer = (AnnounceTimer *)opaque; |
| |
| qemu_foreach_nic(qemu_announce_self_iter, timer); |
| |
| if (--timer->round) { |
| qemu_announce_timer_step(timer); |
| } else { |
| qemu_announce_timer_del(timer, true); |
| } |
| } |
| |
| void qemu_announce_self(AnnounceTimer *timer, AnnounceParameters *params) |
| { |
| qemu_announce_timer_reset(timer, params, QEMU_CLOCK_REALTIME, |
| qemu_announce_self_once, timer); |
| if (params->rounds) { |
| qemu_announce_self_once(timer); |
| } else { |
| qemu_announce_timer_del(timer, true); |
| } |
| } |
| |
| void qmp_announce_self(AnnounceParameters *params, Error **errp) |
| { |
| AnnounceTimer *named_timer; |
| if (!params->has_id) { |
| params->id = g_strdup(""); |
| params->has_id = true; |
| } |
| |
| named_timer = g_datalist_get_data(&named_timers, params->id); |
| |
| if (!named_timer) { |
| named_timer = g_new0(AnnounceTimer, 1); |
| g_datalist_set_data(&named_timers, params->id, named_timer); |
| } |
| |
| qemu_announce_self(named_timer, params); |
| } |