| /* |
| * wmediumd, wireless medium simulator for mac80211_hwsim kernel module |
| * Copyright (c) 2011 cozybit Inc. |
| * Copyright (C) 2020 Intel Corporation |
| * |
| * Author: Javier Lopez <[email protected]> |
| * Javier Cardona <[email protected]> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| */ |
| |
| #include <netlink/netlink.h> |
| #include <netlink/genl/genl.h> |
| #include <netlink/genl/ctrl.h> |
| #include <netlink/genl/family.h> |
| #include <assert.h> |
| #include <stdint.h> |
| #include <getopt.h> |
| #include <signal.h> |
| #include <math.h> |
| #include <sys/timerfd.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #include <endian.h> |
| #include <usfstl/loop.h> |
| #include <usfstl/sched.h> |
| #include <usfstl/schedctrl.h> |
| #include <usfstl/vhost.h> |
| #include <usfstl/uds.h> |
| |
| #include "wmediumd.h" |
| #include "ieee80211.h" |
| #include "config.h" |
| #include "api.h" |
| |
| USFSTL_SCHEDULER(scheduler); |
| |
| static void wmediumd_deliver_frame(struct usfstl_job *job); |
| |
| enum { |
| HWSIM_VQ_TX, |
| HWSIM_VQ_RX, |
| HWSIM_NUM_VQS, |
| }; |
| |
| static inline int div_round(int a, int b) |
| { |
| return (a + b - 1) / b; |
| } |
| |
| static inline int pkt_duration(int len, int rate) |
| { |
| /* preamble + signal + t_sym * n_sym, rate in 100 kbps */ |
| return 16 + 4 + 4 * div_round((16 + 8 * len + 6) * 10, 4 * rate); |
| } |
| |
| int w_logf(struct wmediumd *ctx, u8 level, const char *format, ...) |
| { |
| va_list(args); |
| va_start(args, format); |
| if (ctx->log_lvl >= level) { |
| return vprintf(format, args); |
| } |
| return -1; |
| } |
| |
| int w_flogf(struct wmediumd *ctx, u8 level, FILE *stream, const char *format, ...) |
| { |
| va_list(args); |
| va_start(args, format); |
| if (ctx->log_lvl >= level) { |
| return vfprintf(stream, format, args); |
| } |
| return -1; |
| } |
| |
| static void wqueue_init(struct wqueue *wqueue, int cw_min, int cw_max) |
| { |
| INIT_LIST_HEAD(&wqueue->frames); |
| wqueue->cw_min = cw_min; |
| wqueue->cw_max = cw_max; |
| } |
| |
| void station_init_queues(struct station *station) |
| { |
| wqueue_init(&station->queues[IEEE80211_AC_BK], 15, 1023); |
| wqueue_init(&station->queues[IEEE80211_AC_BE], 15, 1023); |
| wqueue_init(&station->queues[IEEE80211_AC_VI], 7, 15); |
| wqueue_init(&station->queues[IEEE80211_AC_VO], 3, 7); |
| } |
| |
| static inline bool frame_has_a4(struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| |
| return (hdr->frame_control[1] & (FCTL_TODS | FCTL_FROMDS)) == |
| (FCTL_TODS | FCTL_FROMDS); |
| } |
| |
| static inline bool frame_is_mgmt(struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| |
| return (hdr->frame_control[0] & FCTL_FTYPE) == FTYPE_MGMT; |
| } |
| |
| static inline bool frame_is_data(struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| |
| return (hdr->frame_control[0] & FCTL_FTYPE) == FTYPE_DATA; |
| } |
| |
| static inline bool frame_is_data_qos(struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| |
| return (hdr->frame_control[0] & (FCTL_FTYPE | STYPE_QOS_DATA)) == |
| (FTYPE_DATA | STYPE_QOS_DATA); |
| } |
| |
| static inline bool frame_is_probe_req(struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| |
| return (hdr->frame_control[0] & (FCTL_FTYPE | STYPE_PROBE_REQ)) == |
| (FTYPE_MGMT | STYPE_PROBE_REQ); |
| } |
| |
| |
| static inline bool frame_has_zero_rates(const struct frame *frame) |
| { |
| for (int i = 0; i < frame->tx_rates_count; i++) { |
| if (frame->tx_rates[i].idx < 0) |
| break; |
| |
| if (frame->tx_rates[i].count > 0) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static inline void fill_tx_rates(struct frame *frame) |
| { |
| if (frame->tx_rates_count <= 0) { |
| return; |
| } |
| |
| int max_index = get_max_index(); |
| |
| /* Starting from OFDM rate (See per.c#rateset) */ |
| const int basic_rate_start = 4; /* 6 mbps */ |
| |
| int i; |
| int rate_count = min(max_index - basic_rate_start + 1, frame->tx_rates_count); |
| |
| for (i = 0; i < rate_count; i++) { |
| frame->tx_rates[i].idx = basic_rate_start + rate_count - i - 1; |
| frame->tx_rates[i].count = 4; |
| } |
| |
| for (; i < frame->tx_rates_count; i++) { |
| frame->tx_rates[i].idx = -1; |
| frame->tx_rates[i].count = 0; |
| } |
| } |
| |
| static inline u8 *frame_get_qos_ctl(struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| |
| if (frame_has_a4(frame)) |
| return (u8 *)hdr + 30; |
| else |
| return (u8 *)hdr + 24; |
| } |
| |
| static enum ieee80211_ac_number frame_select_queue_80211(struct frame *frame) |
| { |
| u8 *p; |
| int priority; |
| |
| if (!frame_is_data(frame)) |
| return IEEE80211_AC_VO; |
| |
| if (!frame_is_data_qos(frame)) |
| return IEEE80211_AC_BE; |
| |
| p = frame_get_qos_ctl(frame); |
| priority = *p & QOS_CTL_TAG1D_MASK; |
| |
| return ieee802_1d_to_ac[priority]; |
| } |
| |
| static double dBm_to_milliwatt(int decibel_intf) |
| { |
| #define INTF_LIMIT (31) |
| int intf_diff = NOISE_LEVEL - decibel_intf; |
| |
| if (intf_diff >= INTF_LIMIT) |
| return 0.001; |
| |
| if (intf_diff <= -INTF_LIMIT) |
| return 1000.0; |
| |
| return pow(10.0, -intf_diff / 10.0); |
| } |
| |
| static double milliwatt_to_dBm(double value) |
| { |
| return 10.0 * log10(value); |
| } |
| |
| static int set_interference_duration(struct wmediumd *ctx, int src_idx, |
| int duration, int signal) |
| { |
| int i; |
| |
| if (!ctx->intf) |
| return 0; |
| |
| if (signal >= CCA_THRESHOLD) |
| return 0; |
| |
| for (i = 0; i < ctx->num_stas; i++) { |
| ctx->intf[ctx->num_stas * src_idx + i].duration += duration; |
| // use only latest value |
| ctx->intf[ctx->num_stas * src_idx + i].signal = signal; |
| } |
| |
| return 1; |
| } |
| |
| static int get_signal_offset_by_interference(struct wmediumd *ctx, int src_idx, |
| int dst_idx) |
| { |
| int i; |
| double intf_power; |
| |
| if (!ctx->intf) |
| return 0; |
| |
| intf_power = 0.0; |
| for (i = 0; i < ctx->num_stas; i++) { |
| if (i == src_idx || i == dst_idx) |
| continue; |
| if (drand48() < ctx->intf[i * ctx->num_stas + dst_idx].prob_col) |
| intf_power += dBm_to_milliwatt( |
| ctx->intf[i * ctx->num_stas + dst_idx].signal); |
| } |
| |
| if (intf_power <= 1.0) |
| return 0; |
| |
| return (int)(milliwatt_to_dBm(intf_power) + 0.5); |
| } |
| |
| static bool is_multicast_ether_addr(const u8 *addr) |
| { |
| return 0x01 & addr[0]; |
| } |
| |
| static struct station *get_station_by_addr(struct wmediumd *ctx, u8 *addr) |
| { |
| struct station *station; |
| |
| list_for_each_entry(station, &ctx->stations, list) { |
| if (memcmp(station->addr, addr, ETH_ALEN) == 0) |
| return station; |
| } |
| return NULL; |
| } |
| |
| static bool station_has_addr(struct station *station, const u8 *addr) |
| { |
| unsigned int i; |
| |
| if (memcmp(station->addr, addr, ETH_ALEN) == 0) |
| return true; |
| |
| for (i = 0; i < station->n_addrs; i++) { |
| if (memcmp(station->addrs[i].addr, addr, ETH_ALEN) == 0) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static struct station *get_station_by_used_addr(struct wmediumd *ctx, u8 *addr) |
| { |
| struct station *station; |
| |
| list_for_each_entry(station, &ctx->stations, list) { |
| if (station_has_addr(station, addr)) |
| return station; |
| } |
| return NULL; |
| } |
| |
| static void wmediumd_wait_for_client_ack(struct wmediumd *ctx, |
| struct client *client) |
| { |
| client->wait_for_ack = true; |
| |
| while (client->wait_for_ack) |
| usfstl_loop_wait_and_handle(); |
| } |
| |
| static void wmediumd_remove_client(struct wmediumd *ctx, struct client *client); |
| |
| static void wmediumd_notify_frame_start(struct usfstl_job *job) |
| { |
| struct frame *frame = container_of(job, struct frame, start_job); |
| struct wmediumd *ctx = job->data; |
| struct client *client, *tmp; |
| struct { |
| struct wmediumd_message_header hdr; |
| struct wmediumd_tx_start start; |
| } __attribute__((packed)) msg = { |
| .hdr.type = WMEDIUMD_MSG_TX_START, |
| .hdr.data_len = sizeof(msg.start), |
| .start.freq = frame->freq, |
| }; |
| |
| if (ctx->ctrl) |
| usfstl_sched_ctrl_sync_to(ctx->ctrl); |
| |
| list_for_each_entry_safe(client, tmp, &ctx->clients, list) { |
| if (!(client->flags & WMEDIUMD_CTL_NOTIFY_TX_START)) |
| continue; |
| |
| if (client == frame->src) |
| msg.start.cookie = frame->cookie; |
| else |
| msg.start.cookie = 0; |
| |
| /* must be API socket since flags cannot otherwise be set */ |
| assert(client->type == CLIENT_API_SOCK); |
| |
| if (write(client->loop.fd, &msg, sizeof(msg)) < sizeof(msg)) { |
| usfstl_loop_unregister(&client->loop); |
| wmediumd_remove_client(ctx, client); |
| continue; |
| } |
| |
| wmediumd_wait_for_client_ack(ctx, client); |
| } |
| } |
| |
| static void log2pcap(struct wmediumd *ctx, struct frame *frame, uint64_t ts) |
| { |
| struct { |
| uint8_t it_version; |
| uint8_t it_pad; |
| uint16_t it_len; |
| uint32_t it_present; |
| struct { |
| uint16_t freq, flags; |
| } channel; |
| uint8_t signal; |
| } __attribute__((packed)) radiotap_hdr = { |
| .it_len = htole16(sizeof(radiotap_hdr)), |
| .it_present = htole32(1 << 3 /* channel */ | |
| 1 << 5 /* signal dBm */), |
| .channel.freq = htole16(frame->freq), |
| .signal = frame->signal, |
| }; |
| struct { |
| uint32_t type, blocklen, ifidx, ts_hi, ts_lo, caplen, pktlen; |
| } __attribute__((packed)) blockhdr = { |
| .type = 6, |
| .ts_hi = ts / (1ULL << 32), |
| .ts_lo = ts, |
| .caplen = frame->data_len + sizeof(radiotap_hdr), |
| .pktlen = frame->data_len + sizeof(radiotap_hdr), |
| }; |
| static const uint8_t pad[3]; |
| uint32_t sz, align; |
| |
| sz = blockhdr.caplen + sizeof(blockhdr) + sizeof(uint32_t); |
| blockhdr.blocklen = (sz + 3) & ~3; |
| align = blockhdr.blocklen - sz; |
| |
| fwrite(&blockhdr, sizeof(blockhdr), 1, ctx->pcap_file); |
| fwrite(&radiotap_hdr, sizeof(radiotap_hdr), 1, ctx->pcap_file); |
| fwrite(frame->data, frame->data_len, 1, ctx->pcap_file); |
| fwrite(pad, align, 1, ctx->pcap_file); |
| fwrite(&blockhdr.blocklen, sizeof(blockhdr.blocklen), 1, ctx->pcap_file); |
| fflush(ctx->pcap_file); |
| } |
| |
| static void queue_frame(struct wmediumd *ctx, struct station *station, |
| struct frame *frame) |
| { |
| struct ieee80211_hdr *hdr = (void *)frame->data; |
| u8 *dest = hdr->addr1; |
| uint64_t target; |
| struct wqueue *queue; |
| struct frame *tail; |
| struct station *tmpsta, *deststa; |
| int send_time; |
| int cw; |
| double error_prob; |
| bool is_acked = false; |
| bool noack = false; |
| int i, j; |
| int rate_idx; |
| int ac; |
| |
| /* TODO configure phy parameters */ |
| int slot_time = 9; |
| int sifs = 16; |
| int difs = 2 * slot_time + sifs; |
| |
| int retries = 0; |
| |
| int ack_time_usec = pkt_duration(14, index_to_rate(0, frame->freq)) + |
| sifs; |
| |
| /* |
| * To determine a frame's expiration time, we compute the |
| * number of retries we might have to make due to radio conditions |
| * or contention, and add backoff time accordingly. To that, we |
| * add the expiration time of the previous frame in the queue. |
| */ |
| |
| ac = frame_select_queue_80211(frame); |
| queue = &station->queues[ac]; |
| |
| /* try to "send" this frame at each of the rates in the rateset */ |
| send_time = 0; |
| cw = queue->cw_min; |
| |
| int snr = SNR_DEFAULT; |
| |
| if (is_multicast_ether_addr(dest)) { |
| deststa = NULL; |
| } else { |
| deststa = get_station_by_used_addr(ctx, dest); |
| if (deststa) { |
| snr = ctx->get_link_snr(ctx, station, deststa) - |
| get_signal_offset_by_interference(ctx, |
| station->index, deststa->index); |
| snr += ctx->get_fading_signal(ctx); |
| } |
| } |
| frame->signal = snr + NOISE_LEVEL; |
| |
| noack = is_multicast_ether_addr(dest); |
| |
| /* |
| * TODO(b/211353765) Remove this when fundamenal solution is applied |
| * |
| * Temporary workaround for relaying probe_req frame. |
| */ |
| if (frame_is_probe_req(frame) && frame_has_zero_rates(frame)) { |
| fill_tx_rates(frame); |
| } |
| |
| double choice = drand48(); |
| |
| for (i = 0; i < frame->tx_rates_count && !is_acked; i++) { |
| |
| rate_idx = frame->tx_rates[i].idx; |
| |
| /* no more rates in MRR */ |
| if (rate_idx < 0) |
| break; |
| |
| error_prob = ctx->get_error_prob(ctx, snr, rate_idx, |
| frame->freq, frame->data_len, |
| station, deststa); |
| for (j = 0; j < frame->tx_rates[i].count; j++) { |
| send_time += difs + pkt_duration(frame->data_len, |
| index_to_rate(rate_idx, frame->freq)); |
| |
| retries++; |
| |
| /* skip ack/backoff/retries for noack frames */ |
| if (noack) { |
| is_acked = true; |
| break; |
| } |
| |
| /* TODO TXOPs */ |
| |
| /* backoff */ |
| if (j > 0) { |
| send_time += (cw * slot_time) / 2; |
| cw = (cw << 1) + 1; |
| if (cw > queue->cw_max) |
| cw = queue->cw_max; |
| } |
| |
| send_time += ack_time_usec; |
| |
| if (choice > error_prob) { |
| is_acked = true; |
| break; |
| } |
| |
| if (!use_fixed_random_value(ctx)) |
| choice = drand48(); |
| } |
| } |
| |
| if (is_acked) { |
| frame->tx_rates[i-1].count = j + 1; |
| for (; i < frame->tx_rates_count; i++) { |
| frame->tx_rates[i].idx = -1; |
| frame->tx_rates[i].count = -1; |
| } |
| frame->flags |= HWSIM_TX_STAT_ACK; |
| } |
| |
| /* |
| * delivery time starts after any equal or higher prio frame |
| * (or now, if none). |
| */ |
| target = scheduler.current_time; |
| for (i = 0; i <= ac; i++) { |
| list_for_each_entry(tmpsta, &ctx->stations, list) { |
| tail = list_last_entry_or_null(&tmpsta->queues[i].frames, |
| struct frame, list); |
| if (tail && target < tail->job.start) |
| target = tail->job.start; |
| } |
| } |
| |
| if (ctx->pcap_file) { |
| log2pcap(ctx, frame, target); |
| |
| if (is_acked && !noack) { |
| struct { |
| struct frame frame; |
| uint16_t fc; |
| uint16_t dur; |
| uint8_t ra[6]; |
| } __attribute__((packed, aligned(8))) ack = { |
| .fc = htole16(0xd4), |
| .dur = htole16(ack_time_usec), |
| }; |
| |
| memcpy(&ack.frame, frame, sizeof(ack.frame)); |
| ack.frame.data_len = 10; |
| memcpy(ack.ra, frame->data + 10, 6); |
| |
| log2pcap(ctx, &ack.frame, |
| target + send_time - ack_time_usec); |
| } |
| } |
| |
| target += send_time; |
| |
| frame->duration = send_time; |
| frame->src = station->client; |
| |
| if (ctx->need_start_notify) { |
| frame->start_job.start = target - send_time; |
| frame->start_job.callback = wmediumd_notify_frame_start; |
| frame->start_job.data = ctx; |
| frame->start_job.name = "frame-start"; |
| usfstl_sched_add_job(&scheduler, &frame->start_job); |
| } |
| |
| frame->job.start = target; |
| frame->job.callback = wmediumd_deliver_frame; |
| frame->job.data = ctx; |
| frame->job.name = "frame"; |
| usfstl_sched_add_job(&scheduler, &frame->job); |
| list_add_tail(&frame->list, &queue->frames); |
| } |
| |
| static void wmediumd_send_to_client(struct wmediumd *ctx, |
| struct client *client, |
| struct nl_msg *msg) |
| { |
| struct wmediumd_message_header hdr; |
| size_t len; |
| int ret; |
| |
| switch (client->type) { |
| case CLIENT_NETLINK: |
| ret = nl_send_auto_complete(ctx->sock, msg); |
| if (ret < 0) |
| w_logf(ctx, LOG_ERR, "%s: nl_send_auto failed\n", __func__); |
| break; |
| case CLIENT_VHOST_USER: |
| len = nlmsg_total_size(nlmsg_datalen(nlmsg_hdr(msg))); |
| usfstl_vhost_user_dev_notify(client->dev, HWSIM_VQ_RX, |
| (void *)nlmsg_hdr(msg), len); |
| break; |
| case CLIENT_API_SOCK: |
| len = nlmsg_total_size(nlmsg_datalen(nlmsg_hdr(msg))); |
| hdr.type = WMEDIUMD_MSG_NETLINK; |
| hdr.data_len = len; |
| |
| if (write(client->loop.fd, &hdr, sizeof(hdr)) < sizeof(hdr)) |
| goto disconnect; |
| |
| if (write(client->loop.fd, (void *)nlmsg_hdr(msg), len) < len) |
| goto disconnect; |
| |
| wmediumd_wait_for_client_ack(ctx, client); |
| break; |
| } |
| |
| return; |
| |
| disconnect: |
| usfstl_loop_unregister(&client->loop); |
| wmediumd_remove_client(ctx, client); |
| } |
| |
| static void wmediumd_remove_client(struct wmediumd *ctx, struct client *client) |
| { |
| struct frame *frame, *tmp; |
| struct wqueue *queue; |
| struct station *station; |
| int ac; |
| |
| list_for_each_entry(station, &ctx->stations, list) { |
| if (station->client == client) |
| station->client = NULL; |
| } |
| |
| list_for_each_entry(station, &ctx->stations, list) { |
| for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { |
| queue = &station->queues[ac]; |
| list_for_each_entry_safe(frame, tmp, &queue->frames, |
| list) { |
| if (frame->src == client) { |
| list_del(&frame->list); |
| usfstl_sched_del_job(&frame->job); |
| free(frame); |
| } |
| } |
| } |
| } |
| |
| if (!list_empty(&client->list)) |
| list_del(&client->list); |
| list_add(&client->list, &ctx->clients_to_free); |
| |
| if (client->flags & WMEDIUMD_CTL_NOTIFY_TX_START) |
| ctx->need_start_notify--; |
| |
| client->wait_for_ack = false; |
| } |
| |
| /* |
| * Report transmit status to the kernel. |
| */ |
| static void send_tx_info_frame_nl(struct wmediumd *ctx, struct frame *frame) |
| { |
| struct nl_msg *msg; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) { |
| w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n"); |
| return; |
| } |
| |
| if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id, |
| 0, NLM_F_REQUEST, HWSIM_CMD_TX_INFO_FRAME, |
| VERSION_NR) == NULL) { |
| w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__); |
| goto out; |
| } |
| |
| if (nla_put(msg, HWSIM_ATTR_ADDR_TRANSMITTER, ETH_ALEN, |
| frame->sender->hwaddr) || |
| nla_put_u32(msg, HWSIM_ATTR_FLAGS, frame->flags) || |
| nla_put_u32(msg, HWSIM_ATTR_SIGNAL, frame->signal) || |
| nla_put(msg, HWSIM_ATTR_TX_INFO, |
| frame->tx_rates_count * sizeof(struct hwsim_tx_rate), |
| frame->tx_rates) || |
| nla_put_u64(msg, HWSIM_ATTR_COOKIE, frame->cookie)) { |
| w_logf(ctx, LOG_ERR, "%s: Failed to fill a payload\n", __func__); |
| goto out; |
| } |
| |
| if (ctx->ctrl) |
| usfstl_sched_ctrl_sync_to(ctx->ctrl); |
| wmediumd_send_to_client(ctx, frame->src, msg); |
| |
| out: |
| nlmsg_free(msg); |
| } |
| |
| /* |
| * Send a data frame to the kernel for reception at a specific radio. |
| */ |
| static void send_cloned_frame_msg(struct wmediumd *ctx, struct client *src, |
| struct station *dst, u8 *data, int data_len, |
| int rate_idx, int signal, int freq, |
| uint64_t cookie) |
| { |
| struct client *client, *tmp; |
| struct nl_msg *msg, *cmsg = NULL; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) { |
| w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n"); |
| return; |
| } |
| |
| if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id, |
| 0, NLM_F_REQUEST, HWSIM_CMD_FRAME, |
| VERSION_NR) == NULL) { |
| w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__); |
| goto out; |
| } |
| |
| if (nla_put(msg, HWSIM_ATTR_ADDR_RECEIVER, ETH_ALEN, |
| dst->hwaddr) || |
| nla_put(msg, HWSIM_ATTR_FRAME, data_len, data) || |
| nla_put_u32(msg, HWSIM_ATTR_RX_RATE, 1) || |
| nla_put_u32(msg, HWSIM_ATTR_FREQ, freq) || |
| nla_put_u32(msg, HWSIM_ATTR_SIGNAL, signal)) { |
| w_logf(ctx, LOG_ERR, "%s: Failed to fill a payload\n", __func__); |
| goto out; |
| } |
| |
| w_logf(ctx, LOG_DEBUG, "cloned msg dest " MAC_FMT " (radio: " MAC_FMT ") len %d\n", |
| MAC_ARGS(dst->addr), MAC_ARGS(dst->hwaddr), data_len); |
| |
| if (ctx->ctrl) |
| usfstl_sched_ctrl_sync_to(ctx->ctrl); |
| |
| list_for_each_entry_safe(client, tmp, &ctx->clients, list) { |
| if (client->flags & WMEDIUMD_CTL_RX_ALL_FRAMES) { |
| if (src == client && !cmsg) { |
| struct nlmsghdr *nlh = nlmsg_hdr(msg); |
| |
| cmsg = nlmsg_inherit(nlh); |
| nlmsg_append(cmsg, nlmsg_data(nlh), nlmsg_datalen(nlh), 0); |
| assert(nla_put_u64(cmsg, HWSIM_ATTR_COOKIE, cookie) == 0); |
| } |
| wmediumd_send_to_client(ctx, client, |
| src == client ? cmsg : msg); |
| } else if (!dst->client || dst->client == client) { |
| wmediumd_send_to_client(ctx, client, msg); |
| } |
| } |
| |
| out: |
| nlmsg_free(msg); |
| if (cmsg) |
| nlmsg_free(cmsg); |
| } |
| |
| static void wmediumd_deliver_frame(struct usfstl_job *job) |
| { |
| struct wmediumd *ctx = job->data; |
| struct frame *frame = container_of(job, struct frame, job); |
| struct ieee80211_hdr *hdr = (void *) frame->data; |
| struct station *station; |
| u8 *dest = hdr->addr1; |
| u8 *src = frame->sender->addr; |
| |
| list_del(&frame->list); |
| |
| if (frame->flags & HWSIM_TX_STAT_ACK) { |
| /* rx the frame on the dest interface */ |
| list_for_each_entry(station, &ctx->stations, list) { |
| if (memcmp(src, station->addr, ETH_ALEN) == 0) |
| continue; |
| |
| if (is_multicast_ether_addr(dest)) { |
| int snr, rate_idx, signal; |
| double error_prob; |
| |
| /* |
| * we may or may not receive this based on |
| * reverse link from sender -- check for |
| * each receiver. |
| */ |
| snr = ctx->get_link_snr(ctx, frame->sender, |
| station); |
| snr += ctx->get_fading_signal(ctx); |
| signal = snr + NOISE_LEVEL; |
| if (signal < CCA_THRESHOLD) |
| continue; |
| |
| if (set_interference_duration(ctx, |
| frame->sender->index, frame->duration, |
| signal)) |
| continue; |
| |
| snr -= get_signal_offset_by_interference(ctx, |
| frame->sender->index, station->index); |
| rate_idx = frame->tx_rates[0].idx; |
| error_prob = ctx->get_error_prob(ctx, |
| (double)snr, rate_idx, frame->freq, |
| frame->data_len, frame->sender, |
| station); |
| |
| if (drand48() <= error_prob) { |
| w_logf(ctx, LOG_INFO, "Dropped mcast from " |
| MAC_FMT " to " MAC_FMT " at receiver\n", |
| MAC_ARGS(src), MAC_ARGS(station->addr)); |
| continue; |
| } |
| |
| send_cloned_frame_msg(ctx, frame->sender->client, |
| station, |
| frame->data, |
| frame->data_len, |
| 1, signal, |
| frame->freq, |
| frame->cookie); |
| |
| } else if (station_has_addr(station, dest)) { |
| if (set_interference_duration(ctx, |
| frame->sender->index, frame->duration, |
| frame->signal)) |
| continue; |
| |
| send_cloned_frame_msg(ctx, frame->sender->client, |
| station, |
| frame->data, |
| frame->data_len, |
| 1, frame->signal, |
| frame->freq, |
| frame->cookie); |
| } |
| } |
| } else |
| set_interference_duration(ctx, frame->sender->index, |
| frame->duration, frame->signal); |
| |
| send_tx_info_frame_nl(ctx, frame); |
| |
| free(frame); |
| } |
| |
| static void wmediumd_intf_update(struct usfstl_job *job) |
| { |
| struct wmediumd *ctx = job->data; |
| int i, j; |
| |
| for (i = 0; i < ctx->num_stas; i++) |
| for (j = 0; j < ctx->num_stas; j++) { |
| if (i == j) |
| continue; |
| // probability is used for next calc |
| ctx->intf[i * ctx->num_stas + j].prob_col = |
| ctx->intf[i * ctx->num_stas + j].duration / |
| (double)10000; |
| ctx->intf[i * ctx->num_stas + j].duration = 0; |
| } |
| |
| job->start += 10000; |
| usfstl_sched_add_job(&scheduler, job); |
| } |
| |
| static |
| int nl_err_cb(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg) |
| { |
| struct genlmsghdr *gnlh = nlmsg_data(&nlerr->msg); |
| struct wmediumd *ctx = arg; |
| |
| w_flogf(ctx, LOG_ERR, stderr, "nl: cmd %d, seq %d: %s\n", gnlh->cmd, |
| nlerr->msg.nlmsg_seq, strerror(abs(nlerr->error))); |
| |
| return NL_SKIP; |
| } |
| |
| /* |
| * Handle events from the kernel. Process CMD_FRAME events and queue them |
| * for later delivery with the scheduler. |
| */ |
| static void _process_messages(struct nl_msg *msg, |
| struct wmediumd *ctx, |
| struct client *client) |
| { |
| struct nlattr *attrs[HWSIM_ATTR_MAX+1]; |
| /* netlink header */ |
| struct nlmsghdr *nlh = nlmsg_hdr(msg); |
| /* generic netlink header*/ |
| struct genlmsghdr *gnlh = nlmsg_data(nlh); |
| |
| struct station *sender; |
| struct frame *frame; |
| struct ieee80211_hdr *hdr; |
| u8 *src, *hwaddr, *addr; |
| void *new; |
| unsigned int i; |
| |
| genlmsg_parse(nlh, 0, attrs, HWSIM_ATTR_MAX, NULL); |
| |
| switch (gnlh->cmd) { |
| case HWSIM_CMD_FRAME: |
| if (attrs[HWSIM_ATTR_ADDR_TRANSMITTER]) { |
| hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); |
| |
| unsigned int data_len = |
| nla_len(attrs[HWSIM_ATTR_FRAME]); |
| char *data = (char *)nla_data(attrs[HWSIM_ATTR_FRAME]); |
| unsigned int flags = |
| nla_get_u32(attrs[HWSIM_ATTR_FLAGS]); |
| unsigned int tx_rates_len = |
| nla_len(attrs[HWSIM_ATTR_TX_INFO]); |
| struct hwsim_tx_rate *tx_rates = |
| (struct hwsim_tx_rate *) |
| nla_data(attrs[HWSIM_ATTR_TX_INFO]); |
| u64 cookie = nla_get_u64(attrs[HWSIM_ATTR_COOKIE]); |
| u32 freq; |
| |
| freq = attrs[HWSIM_ATTR_FREQ] ? |
| nla_get_u32(attrs[HWSIM_ATTR_FREQ]) : 2412; |
| |
| hdr = (struct ieee80211_hdr *)data; |
| src = hdr->addr2; |
| |
| if (data_len < 6 + 6 + 4) |
| return; |
| |
| sender = get_station_by_addr(ctx, hwaddr); |
| if (!sender) { |
| sender = get_station_by_used_addr(ctx, src); |
| if (!sender) { |
| w_flogf(ctx, LOG_ERR, stderr, |
| "Unable to find sender station by src=" MAC_FMT " nor hwaddr=" MAC_FMT "\n", |
| MAC_ARGS(src), MAC_ARGS(hwaddr)); |
| return; |
| } |
| memcpy(sender->hwaddr, hwaddr, ETH_ALEN); |
| } |
| |
| if (!sender->client) |
| sender->client = client; |
| |
| frame = calloc(1, sizeof(*frame) + data_len); |
| if (!frame) |
| return; |
| |
| memcpy(frame->data, data, data_len); |
| frame->data_len = data_len; |
| frame->flags = flags; |
| frame->cookie = cookie; |
| frame->freq = freq; |
| frame->sender = sender; |
| frame->tx_rates_count = |
| tx_rates_len / sizeof(struct hwsim_tx_rate); |
| memcpy(frame->tx_rates, tx_rates, |
| min(tx_rates_len, sizeof(frame->tx_rates))); |
| queue_frame(ctx, sender, frame); |
| } |
| break; |
| case HWSIM_CMD_ADD_MAC_ADDR: |
| if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER] || |
| !attrs[HWSIM_ATTR_ADDR_RECEIVER]) |
| break; |
| hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); |
| addr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_RECEIVER]); |
| sender = get_station_by_addr(ctx, hwaddr); |
| if (!sender) |
| break; |
| for (i = 0; i < sender->n_addrs; i++) { |
| if (memcmp(sender->addrs[i].addr, addr, ETH_ALEN) == 0) |
| return; |
| } |
| new = realloc(sender->addrs, ETH_ALEN * (sender->n_addrs + 1)); |
| if (!new) |
| break; |
| sender->addrs = new; |
| memcpy(sender->addrs[sender->n_addrs].addr, addr, ETH_ALEN); |
| sender->n_addrs += 1; |
| break; |
| case HWSIM_CMD_DEL_MAC_ADDR: |
| if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER] || |
| !attrs[HWSIM_ATTR_ADDR_RECEIVER]) |
| break; |
| hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); |
| addr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_RECEIVER]); |
| sender = get_station_by_addr(ctx, hwaddr); |
| if (!sender) |
| break; |
| for (i = 0; i < sender->n_addrs; i++) { |
| if (memcmp(sender->addrs[i].addr, addr, ETH_ALEN)) |
| continue; |
| sender->n_addrs -= 1; |
| memmove(sender->addrs[i].addr, |
| sender->addrs[sender->n_addrs].addr, |
| ETH_ALEN); |
| break; |
| } |
| break; |
| } |
| } |
| |
| static int process_messages_cb(struct nl_msg *msg, void *arg) |
| { |
| struct wmediumd *ctx = arg; |
| |
| _process_messages(msg, ctx, &ctx->nl_client); |
| return 0; |
| } |
| |
| static void wmediumd_vu_connected(struct usfstl_vhost_user_dev *dev) |
| { |
| struct wmediumd *ctx = dev->server->data; |
| struct client *client; |
| |
| client = calloc(1, sizeof(*client)); |
| dev->data = client; |
| client->type = CLIENT_VHOST_USER; |
| client->dev = dev; |
| list_add(&client->list, &ctx->clients); |
| } |
| |
| static void wmediumd_vu_handle(struct usfstl_vhost_user_dev *dev, |
| struct usfstl_vhost_user_buf *buf, |
| unsigned int vring) |
| { |
| struct nl_msg *nlmsg; |
| char data[4096]; |
| size_t len; |
| |
| len = iov_read(data, sizeof(data), buf->out_sg, buf->n_out_sg); |
| |
| if (!nlmsg_ok((const struct nlmsghdr *)data, len)) |
| return; |
| nlmsg = nlmsg_convert((struct nlmsghdr *)data); |
| if (!nlmsg) |
| return; |
| |
| _process_messages(nlmsg, dev->server->data, dev->data); |
| |
| nlmsg_free(nlmsg); |
| } |
| |
| static void wmediumd_vu_disconnected(struct usfstl_vhost_user_dev *dev) |
| { |
| struct client *client = dev->data; |
| |
| dev->data = NULL; |
| wmediumd_remove_client(dev->server->data, client); |
| } |
| |
| static int process_set_snr_message(struct wmediumd *ctx, struct wmediumd_set_snr *set_snr) { |
| struct station *node1 = get_station_by_addr(ctx, set_snr->node1_mac); |
| struct station *node2 = get_station_by_addr(ctx, set_snr->node2_mac); |
| |
| if (node1 == NULL || node2 == NULL) { |
| return -1; |
| } |
| |
| ctx->snr_matrix[ctx->num_stas * node2->index + node1->index] = set_snr->snr; |
| ctx->snr_matrix[ctx->num_stas * node1->index + node2->index] = set_snr->snr; |
| |
| return 0; |
| } |
| |
| static int process_reload_config_message(struct wmediumd *ctx, |
| struct wmediumd_reload_config *reload_config) { |
| char *config_path; |
| int result = 0; |
| |
| config_path = reload_config->config_path; |
| |
| if (validate_config(config_path)) { |
| clear_config(ctx); |
| load_config(ctx, config_path, NULL); |
| } else { |
| result = -1; |
| } |
| |
| return result; |
| } |
| |
| static int process_reload_current_config_message(struct wmediumd *ctx) { |
| char *config_path; |
| int result = 0; |
| |
| config_path = strdup(ctx->config_path); |
| |
| if (validate_config(config_path)) { |
| clear_config(ctx); |
| load_config(ctx, config_path, NULL); |
| } else { |
| result = -1; |
| } |
| |
| free(config_path); |
| |
| return result; |
| } |
| |
| static int process_get_stations_message(struct wmediumd *ctx, ssize_t *response_len, unsigned char **response_data) { |
| struct station *station; |
| int station_count = 0; |
| |
| list_for_each_entry(station, &ctx->stations, list) { |
| if (station->client != NULL) { |
| ++station_count; |
| } |
| } |
| |
| *response_len = sizeof(uint32_t) + sizeof(struct wmediumd_station_info) * station_count; |
| struct wmediumd_station_infos *station_infos = malloc(*response_len); |
| |
| station_infos->count = station_count; |
| int station_index = 0; |
| |
| list_for_each_entry(station, &ctx->stations, list) { |
| if (station->client != NULL) { |
| struct wmediumd_station_info *station_info = &station_infos->stations[station_index]; |
| memcpy(station_info->addr, station->addr, ETH_ALEN); |
| memcpy(station_info->hwaddr, station->hwaddr, ETH_ALEN); |
| |
| station_info->x = station->x; |
| station_info->y = station->y; |
| |
| station_info->tx_power = station->tx_power; |
| |
| station_index++; |
| } |
| } |
| |
| *response_data = (unsigned char *)station_infos; |
| |
| return 0; |
| } |
| |
| static const struct usfstl_vhost_user_ops wmediumd_vu_ops = { |
| .connected = wmediumd_vu_connected, |
| .handle = wmediumd_vu_handle, |
| .disconnected = wmediumd_vu_disconnected, |
| }; |
| |
| static void close_pcapng(struct wmediumd *ctx) { |
| if (ctx->pcap_file == NULL) { |
| return; |
| } |
| |
| fflush(ctx->pcap_file); |
| fclose(ctx->pcap_file); |
| |
| ctx->pcap_file = NULL; |
| } |
| |
| static void init_pcapng(struct wmediumd *ctx, const char *filename); |
| |
| static void wmediumd_api_handler(struct usfstl_loop_entry *entry) |
| { |
| struct client *client = container_of(entry, struct client, loop); |
| struct wmediumd *ctx = entry->data; |
| struct wmediumd_message_header hdr; |
| enum wmediumd_message response = WMEDIUMD_MSG_ACK; |
| struct wmediumd_message_control control = {}; |
| struct nl_msg *nlmsg; |
| unsigned char *data; |
| ssize_t response_len = 0; |
| unsigned char *response_data = NULL; |
| ssize_t len; |
| |
| len = read(entry->fd, &hdr, sizeof(hdr)); |
| if (len != sizeof(hdr)) |
| goto disconnect; |
| |
| /* safety valve */ |
| if (hdr.data_len > 1024 * 1024) |
| goto disconnect; |
| |
| data = malloc(hdr.data_len); |
| if (!data) |
| goto disconnect; |
| |
| len = read(entry->fd, data, hdr.data_len); |
| if (len != hdr.data_len) |
| goto disconnect; |
| |
| switch (hdr.type) { |
| case WMEDIUMD_MSG_REGISTER: |
| if (!list_empty(&client->list)) { |
| response = WMEDIUMD_MSG_INVALID; |
| break; |
| } |
| list_add(&client->list, &ctx->clients); |
| break; |
| case WMEDIUMD_MSG_UNREGISTER: |
| if (list_empty(&client->list)) { |
| response = WMEDIUMD_MSG_INVALID; |
| break; |
| } |
| list_del_init(&client->list); |
| break; |
| case WMEDIUMD_MSG_NETLINK: |
| if (ctx->ctrl) |
| usfstl_sched_ctrl_sync_from(ctx->ctrl); |
| |
| if (!nlmsg_ok((const struct nlmsghdr *)data, len)) { |
| response = WMEDIUMD_MSG_INVALID; |
| break; |
| } |
| |
| nlmsg = nlmsg_convert((struct nlmsghdr *)data); |
| if (!nlmsg) |
| break; |
| |
| _process_messages(nlmsg, ctx, client); |
| |
| nlmsg_free(nlmsg); |
| break; |
| case WMEDIUMD_MSG_SET_CONTROL: |
| /* copy what we get and understand, leave the rest zeroed */ |
| memcpy(&control, data, |
| min(sizeof(control), hdr.data_len)); |
| |
| if (client->flags & WMEDIUMD_CTL_NOTIFY_TX_START) |
| ctx->need_start_notify--; |
| if (control.flags & WMEDIUMD_CTL_NOTIFY_TX_START) |
| ctx->need_start_notify++; |
| |
| client->flags = control.flags; |
| break; |
| case WMEDIUMD_MSG_GET_STATIONS: |
| if (process_get_stations_message(ctx, &response_len, &response_data) < 0) { |
| response = WMEDIUMD_MSG_INVALID; |
| } |
| response = WMEDIUMD_MSG_STATIONS_LIST; |
| break; |
| case WMEDIUMD_MSG_SET_SNR: |
| if (process_set_snr_message(ctx, (struct wmediumd_set_snr *)data) < 0) { |
| response = WMEDIUMD_MSG_INVALID; |
| } |
| break; |
| case WMEDIUMD_MSG_RELOAD_CONFIG: |
| if (process_reload_config_message(ctx, |
| (struct wmediumd_reload_config *)data) < 0) { |
| response = WMEDIUMD_MSG_INVALID; |
| } |
| break; |
| case WMEDIUMD_MSG_RELOAD_CURRENT_CONFIG: |
| if (process_reload_current_config_message(ctx) < 0) { |
| response = WMEDIUMD_MSG_INVALID; |
| } |
| break; |
| case WMEDIUMD_MSG_START_PCAP: |
| init_pcapng(ctx, ((struct wmediumd_start_pcap *)data)->pcap_path); |
| break; |
| case WMEDIUMD_MSG_STOP_PCAP: |
| close_pcapng(ctx); |
| break; |
| case WMEDIUMD_MSG_ACK: |
| assert(client->wait_for_ack == true); |
| assert(hdr.data_len == 0); |
| client->wait_for_ack = false; |
| /* don't send a response to a response, of course */ |
| return; |
| default: |
| response = WMEDIUMD_MSG_INVALID; |
| break; |
| } |
| |
| /* return a response */ |
| hdr.type = response; |
| hdr.data_len = response_len; |
| len = write(entry->fd, &hdr, sizeof(hdr)); |
| if (len != sizeof(hdr)) |
| goto disconnect; |
| |
| if (response_data != NULL) { |
| if (response_len != 0) { |
| len = write(entry->fd, response_data, response_len); |
| |
| if (len != response_len) { |
| free(response_data); |
| goto disconnect; |
| } |
| } |
| |
| free(response_data); |
| response_data = NULL; |
| } |
| |
| return; |
| disconnect: |
| usfstl_loop_unregister(&client->loop); |
| wmediumd_remove_client(ctx, client); |
| } |
| |
| static void wmediumd_api_connected(int fd, void *data) |
| { |
| struct wmediumd *ctx = data; |
| struct client *client; |
| |
| client = calloc(1, sizeof(*client)); |
| client->type = CLIENT_API_SOCK; |
| client->loop.fd = fd; |
| client->loop.data = ctx; |
| client->loop.handler = wmediumd_api_handler; |
| usfstl_loop_register(&client->loop); |
| INIT_LIST_HEAD(&client->list); |
| } |
| |
| /* |
| * Register with the kernel to start receiving new frames. |
| */ |
| static int send_register_msg(struct wmediumd *ctx) |
| { |
| struct nl_sock *sock = ctx->sock; |
| struct nl_msg *msg; |
| int ret; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) { |
| w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n"); |
| return -1; |
| } |
| |
| if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id, |
| 0, NLM_F_REQUEST, HWSIM_CMD_REGISTER, |
| VERSION_NR) == NULL) { |
| w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__); |
| ret = -1; |
| goto out; |
| } |
| |
| ret = nl_send_auto_complete(sock, msg); |
| if (ret < 0) { |
| w_logf(ctx, LOG_ERR, "%s: nl_send_auto failed\n", __func__); |
| ret = -1; |
| goto out; |
| } |
| ret = 0; |
| |
| out: |
| nlmsg_free(msg); |
| return ret; |
| } |
| |
| static void sock_event_cb(struct usfstl_loop_entry *entry) |
| { |
| struct wmediumd *ctx = entry->data; |
| |
| nl_recvmsgs_default(ctx->sock); |
| } |
| |
| /* |
| * Setup netlink socket and callbacks. |
| */ |
| static int init_netlink(struct wmediumd *ctx) |
| { |
| struct nl_sock *sock; |
| int ret; |
| |
| ctx->cb = nl_cb_alloc(NL_CB_CUSTOM); |
| if (!ctx->cb) { |
| w_logf(ctx, LOG_ERR, "Error allocating netlink callbacks\n"); |
| return -1; |
| } |
| |
| sock = nl_socket_alloc_cb(ctx->cb); |
| if (!sock) { |
| w_logf(ctx, LOG_ERR, "Error allocating netlink socket\n"); |
| return -1; |
| } |
| |
| ctx->sock = sock; |
| |
| ret = genl_connect(sock); |
| if (ret < 0) { |
| w_logf(ctx, LOG_ERR, "Error connecting netlink socket ret=%d\n", ret); |
| return -1; |
| } |
| |
| ctx->family_id = genl_ctrl_resolve(sock, "MAC80211_HWSIM"); |
| if (ctx->family_id < 0) { |
| w_logf(ctx, LOG_ERR, "Family MAC80211_HWSIM not registered\n"); |
| return -1; |
| } |
| |
| nl_cb_set(ctx->cb, NL_CB_MSG_IN, NL_CB_CUSTOM, process_messages_cb, ctx); |
| nl_cb_err(ctx->cb, NL_CB_CUSTOM, nl_err_cb, ctx); |
| |
| return 0; |
| } |
| |
| /* |
| * Print the CLI help |
| */ |
| static void print_help(int exval) |
| { |
| printf("wmediumd v%s - a wireless medium simulator\n", VERSION_STR); |
| printf("wmediumd [-h] [-V] [-l LOG_LVL] [-x FILE] -c FILE \n\n"); |
| |
| printf(" -h print this help and exit\n"); |
| printf(" -V print version and exit\n\n"); |
| |
| printf(" -l LOG_LVL set the logging level\n"); |
| printf(" LOG_LVL: RFC 5424 severity, values 0 - 7\n"); |
| printf(" >= 3: errors are logged\n"); |
| printf(" >= 5: startup msgs are logged\n"); |
| printf(" >= 6: dropped packets are logged (default)\n"); |
| printf(" == 7: all packets will be logged\n"); |
| printf(" -c FILE set input config file\n"); |
| printf(" -x FILE set input PER file\n"); |
| printf(" -t socket set the time control socket\n"); |
| printf(" -u socket expose vhost-user socket, don't use netlink\n"); |
| printf(" -a socket expose wmediumd API socket\n"); |
| printf(" -n force netlink use even with vhost-user\n"); |
| printf(" -p FILE log packets to pcapng file FILE\n"); |
| |
| exit(exval); |
| } |
| |
| static void init_pcapng(struct wmediumd *ctx, const char *filename) |
| { |
| struct { |
| uint32_t type, blocklen, byte_order; |
| uint16_t ver_maj, ver_min; |
| uint64_t seclen; |
| uint32_t blocklen2; |
| } __attribute__((packed)) blockhdr = { |
| .type = 0x0A0D0D0A, |
| .blocklen = sizeof(blockhdr), |
| .byte_order = 0x1A2B3C4D, |
| .ver_maj = 1, |
| .ver_min = 0, |
| .seclen = -1, |
| .blocklen2 = sizeof(blockhdr), |
| }; |
| struct { |
| uint32_t type, blocklen; |
| uint16_t linktype, reserved; |
| uint32_t snaplen; |
| struct { |
| uint16_t code, len; |
| uint8_t val, pad[3]; |
| } opt_if_tsresol; |
| struct { |
| uint16_t code, len; |
| } opt_endofopt; |
| uint32_t blocklen2; |
| } __attribute__((packed)) idb = { |
| .type = 1, |
| .blocklen = sizeof(idb), |
| .linktype = 127, // radiotap |
| .snaplen = -1, |
| .opt_if_tsresol.code = 9, |
| .opt_if_tsresol.len = 1, |
| .opt_if_tsresol.val = 6, // usec |
| .blocklen2 = sizeof(idb), |
| }; |
| |
| if (ctx->pcap_file != NULL) { |
| close_pcapng(ctx); |
| } |
| |
| if (!filename) |
| return; |
| |
| ctx->pcap_file = fopen(filename, "w+"); |
| fwrite(&blockhdr, sizeof(blockhdr), 1, ctx->pcap_file); |
| fwrite(&idb, sizeof(idb), 1, ctx->pcap_file); |
| } |
| |
| #ifndef VIRTIO_F_VERSION_1 |
| #define VIRTIO_F_VERSION_1 32 |
| #endif |
| |
| int main(int argc, char *argv[]) |
| { |
| int opt; |
| struct wmediumd ctx = {}; |
| char *config_file = NULL; |
| char *per_file = NULL; |
| const char *time_socket = NULL, *api_socket = NULL; |
| struct usfstl_sched_ctrl ctrl = {}; |
| struct usfstl_vhost_user_server vusrv = { |
| .ops = &wmediumd_vu_ops, |
| .max_queues = HWSIM_NUM_VQS, |
| .input_queues = 1 << HWSIM_VQ_TX, |
| .features = 1ULL << VIRTIO_F_VERSION_1, |
| .protocol_features = |
| 1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS, |
| .data = &ctx, |
| }; |
| bool use_netlink, force_netlink = false; |
| |
| setvbuf(stdout, NULL, _IOLBF, BUFSIZ); |
| |
| if (argc == 1) { |
| fprintf(stderr, "This program needs arguments....\n\n"); |
| print_help(EXIT_FAILURE); |
| } |
| |
| ctx.log_lvl = 6; |
| unsigned long int parse_log_lvl; |
| char* parse_end_token; |
| |
| while ((opt = getopt(argc, argv, "hVc:l:x:t:u:a:np:")) != -1) { |
| switch (opt) { |
| case 'h': |
| print_help(EXIT_SUCCESS); |
| break; |
| case 'V': |
| printf("wmediumd v%s - a wireless medium simulator " |
| "for mac80211_hwsim\n", VERSION_STR); |
| exit(EXIT_SUCCESS); |
| break; |
| case 'c': |
| config_file = optarg; |
| break; |
| case 'x': |
| printf("Input packet error rate file: %s\n", optarg); |
| per_file = optarg; |
| break; |
| case ':': |
| printf("wmediumd: Error - Option `%c' " |
| "needs a value\n\n", optopt); |
| print_help(EXIT_FAILURE); |
| break; |
| case 'l': |
| parse_log_lvl = strtoul(optarg, &parse_end_token, 10); |
| if ((parse_log_lvl == ULONG_MAX && errno == ERANGE) || |
| optarg == parse_end_token || parse_log_lvl > 7) { |
| printf("wmediumd: Error - Invalid RFC 5424 severity level: " |
| "%s\n\n", optarg); |
| print_help(EXIT_FAILURE); |
| } |
| ctx.log_lvl = parse_log_lvl; |
| break; |
| case 't': |
| time_socket = optarg; |
| break; |
| case 'u': |
| vusrv.socket = optarg; |
| break; |
| case 'a': |
| api_socket = optarg; |
| break; |
| case 'n': |
| force_netlink = true; |
| break; |
| case 'p': |
| init_pcapng(&ctx, optarg); |
| break; |
| case '?': |
| printf("wmediumd: Error - No such option: " |
| "`%c'\n\n", optopt); |
| print_help(EXIT_FAILURE); |
| break; |
| } |
| |
| } |
| |
| if (optind < argc) |
| print_help(EXIT_FAILURE); |
| |
| if (!config_file) { |
| printf("%s: config file must be supplied\n", argv[0]); |
| print_help(EXIT_FAILURE); |
| } |
| |
| w_logf(&ctx, LOG_NOTICE, "Input configuration file: %s\n", config_file); |
| |
| INIT_LIST_HEAD(&ctx.stations); |
| INIT_LIST_HEAD(&ctx.clients); |
| INIT_LIST_HEAD(&ctx.clients_to_free); |
| |
| if (load_config(&ctx, config_file, per_file)) |
| return EXIT_FAILURE; |
| |
| use_netlink = force_netlink || !vusrv.socket; |
| |
| /* init netlink */ |
| if (use_netlink && init_netlink(&ctx) < 0) |
| return EXIT_FAILURE; |
| |
| if (ctx.intf) { |
| ctx.intf_job.start = 10000; // usec |
| ctx.intf_job.name = "interference update"; |
| ctx.intf_job.data = &ctx; |
| ctx.intf_job.callback = wmediumd_intf_update; |
| usfstl_sched_add_job(&scheduler, &ctx.intf_job); |
| } |
| |
| if (vusrv.socket) |
| usfstl_vhost_user_server_start(&vusrv); |
| |
| if (use_netlink) { |
| ctx.nl_client.type = CLIENT_NETLINK; |
| list_add(&ctx.nl_client.list, &ctx.clients); |
| |
| ctx.nl_loop.handler = sock_event_cb; |
| ctx.nl_loop.data = &ctx; |
| ctx.nl_loop.fd = nl_socket_get_fd(ctx.sock); |
| usfstl_loop_register(&ctx.nl_loop); |
| |
| /* register for new frames */ |
| if (send_register_msg(&ctx) == 0) |
| w_logf(&ctx, LOG_NOTICE, "REGISTER SENT!\n"); |
| } |
| |
| if (api_socket) { |
| signal(SIGPIPE, SIG_IGN); |
| usfstl_uds_create(api_socket, wmediumd_api_connected, &ctx); |
| } |
| |
| if (time_socket) { |
| usfstl_sched_ctrl_start(&ctrl, time_socket, |
| 1000 /* nsec per usec */, |
| (uint64_t)-1 /* no ID */, |
| &scheduler); |
| vusrv.scheduler = &scheduler; |
| vusrv.ctrl = &ctrl; |
| ctx.ctrl = &ctrl; |
| } else { |
| usfstl_sched_wallclock_init(&scheduler, 1000); |
| } |
| |
| while (1) { |
| if (time_socket) { |
| usfstl_sched_next(&scheduler); |
| } else { |
| usfstl_sched_wallclock_wait_and_handle(&scheduler); |
| |
| if (usfstl_sched_next_pending(&scheduler, NULL)) |
| usfstl_sched_next(&scheduler); |
| } |
| |
| while (!list_empty(&ctx.clients_to_free)) { |
| struct client *client; |
| |
| client = list_first_entry(&ctx.clients_to_free, |
| struct client, list); |
| |
| list_del(&client->list); |
| free(client); |
| } |
| } |
| |
| free(ctx.sock); |
| free(ctx.cb); |
| free(ctx.intf); |
| free(ctx.per_matrix); |
| |
| return EXIT_SUCCESS; |
| } |