/* * wmediumd, wireless medium simulator for mac80211_hwsim kernel module * Copyright (c) 2011 cozybit Inc. * Copyright (C) 2020 Intel Corporation * * Author: Javier Lopez * Javier Cardona * * 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 #include #include #include #include #include #include "wmediumd.h" #include "config.h" #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif static void string_to_mac_address(const char *str, u8 *addr) { int a[ETH_ALEN]; sscanf(str, "%x:%x:%x:%x:%x:%x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]); addr[0] = (u8) a[0]; addr[1] = (u8) a[1]; addr[2] = (u8) a[2]; addr[3] = (u8) a[3]; addr[4] = (u8) a[4]; addr[5] = (u8) a[5]; } static int get_link_snr_default(struct wmediumd *ctx, struct station *sender, struct station *receiver) { return SNR_DEFAULT; } static int get_link_snr_from_snr_matrix(struct wmediumd *ctx, struct station *sender, struct station *receiver) { return ctx->snr_matrix[sender->index * ctx->num_stas + receiver->index]; } static double _get_error_prob_from_snr(struct wmediumd *ctx, double snr, unsigned int rate_idx, u32 freq, int frame_len, struct station *src, struct station *dst) { return get_error_prob_from_snr(snr, rate_idx, freq, frame_len); } static double get_error_prob_from_matrix(struct wmediumd *ctx, double snr, unsigned int rate_idx, u32 freq, int frame_len, struct station *src, struct station *dst) { if (dst == NULL) // dst is multicast. returned value will not be used. return 0.0; return ctx->error_prob_matrix[ctx->num_stas * src->index + dst->index]; } int use_fixed_random_value(struct wmediumd *ctx) { return ctx->error_prob_matrix != NULL; } #define FREQ_1CH (2.412e9) // [Hz] #define SPEED_LIGHT (2.99792458e8) // [meter/sec] /* * Calculate path loss based on a free-space path loss * * This function returns path loss [dBm]. */ static int calc_path_loss_free_space(void *model_param, struct station *dst, struct station *src) { double PL, d; d = sqrt((src->x - dst->x) * (src->x - dst->x) + (src->y - dst->y) * (src->y - dst->y)); /* * Calculate PL0 with Free-space path loss in decibels * * 20 * log10 * (4 * M_PI * d * f / c) * d: distance [meter] * f: frequency [Hz] * c: speed of light in a vacuum [meter/second] * * https://en.wikipedia.org/wiki/Free-space_path_loss */ PL = 20.0 * log10(4.0 * M_PI * d * FREQ_1CH / SPEED_LIGHT); return MAX(PL, 0); } /* * Calculate path loss based on a log distance model * * This function returns path loss [dBm]. */ static int calc_path_loss_log_distance(void *model_param, struct station *dst, struct station *src) { struct log_distance_model_param *param; double PL, PL0, d; param = model_param; d = sqrt((src->x - dst->x) * (src->x - dst->x) + (src->y - dst->y) * (src->y - dst->y)); /* * Calculate PL0 with Free-space path loss in decibels * * 20 * log10 * (4 * M_PI * d * f / c) * d: distance [meter] * f: frequency [Hz] * c: speed of light in a vacuum [meter/second] * * https://en.wikipedia.org/wiki/Free-space_path_loss */ PL0 = 20.0 * log10(4.0 * M_PI * 1.0 * FREQ_1CH / SPEED_LIGHT); /* * Calculate signal strength with Log-distance path loss model * https://en.wikipedia.org/wiki/Log-distance_path_loss_model */ PL = PL0 + 10.0 * param->path_loss_exponent * log10(d) + param->Xg; return MAX(PL, 0); } /* * Calculate path loss based on a itu model * * This function returns path loss [dBm]. */ static int calc_path_loss_itu(void *model_param, struct station *dst, struct station *src) { struct itu_model_param *param; double PL, d; int N=28; param = model_param; d = sqrt((src->x - dst->x) * (src->x - dst->x) + (src->y - dst->y) * (src->y - dst->y)); if (d>16) N=38; /* * Calculate signal strength with ITU path loss model * Power Loss Coefficient Based on the Paper * Site-Specific Validation of ITU Indoor Path Loss Model at 2.4 GHz * from Theofilos Chrysikos, Giannis Georgopoulos and Stavros Kotsopoulos * LF: floor penetration loss factor * nFLOORS: number of floors */ PL = 20.0 * log10(FREQ_1CH) + N * log10(d) + param->LF * param->nFLOORS - 28; return MAX(PL, 0); } static void recalc_path_loss(struct wmediumd *ctx) { int start, end, tx_power, path_loss, signal; for (start = 0; start < ctx->num_stas; start++) { for (end = 0; end < ctx->num_stas; end++) { if (start == end) { continue; } tx_power = ctx->sta_array[start]->tx_power; path_loss = ctx->calc_path_loss(ctx->path_loss_param, ctx->sta_array[end], ctx->sta_array[start]); // Test breakage exists in WifiStatsTests when signal is not negative value. signal = MIN(tx_power - path_loss, -1); ctx->snr_matrix[ctx->num_stas * start + end] = signal - NOISE_LEVEL; } } } static void move_stations_to_direction(struct usfstl_job *job) { struct wmediumd *ctx = job->data; struct station *station; list_for_each_entry(station, &ctx->stations, list) { station->x += station->dir_x; station->y += station->dir_y; } recalc_path_loss(ctx); job->start += MOVE_INTERVAL * 1000000; usfstl_sched_add_job(&scheduler, job); } static int parse_path_loss(struct wmediumd *ctx, config_t *cf) { struct station *station; const config_setting_t *positions, *position; const config_setting_t *directions, *direction; const config_setting_t *tx_powers, *model; const char *path_loss_model_name; positions = config_lookup(cf, "model.positions"); if (!positions) { w_flogf(ctx, LOG_ERR, stderr, "No positions found in model\n"); return -EINVAL; } if (config_setting_length(positions) != ctx->num_stas) { w_flogf(ctx, LOG_ERR, stderr, "Specify %d positions\n", ctx->num_stas); return -EINVAL; } directions = config_lookup(cf, "model.directions"); if (directions) { if (config_setting_length(directions) != ctx->num_stas) { w_flogf(ctx, LOG_ERR, stderr, "Specify %d directions\n", ctx->num_stas); return -EINVAL; } ctx->move_job.start = MOVE_INTERVAL * 1000000; ctx->move_job.name = "move"; ctx->move_job.data = ctx; ctx->move_job.callback = move_stations_to_direction; usfstl_sched_add_job(&scheduler, &ctx->move_job); } tx_powers = config_lookup(cf, "model.tx_powers"); if (!tx_powers) { w_flogf(ctx, LOG_ERR, stderr, "No tx_powers found in model\n"); return -EINVAL; } if (config_setting_length(tx_powers) != ctx->num_stas) { w_flogf(ctx, LOG_ERR, stderr, "Specify %d tx_powers\n", ctx->num_stas); return -EINVAL; } model = config_lookup(cf, "model"); if (config_setting_lookup_string(model, "model_name", &path_loss_model_name) != CONFIG_TRUE) { w_flogf(ctx, LOG_ERR, stderr, "Specify model_name\n"); return -EINVAL; } if (strncmp(path_loss_model_name, "log_distance", sizeof("log_distance")) == 0) { struct log_distance_model_param *param; ctx->calc_path_loss = calc_path_loss_log_distance; param = malloc(sizeof(*param)); if (!param) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory(path_loss_param)\n"); return -EINVAL; } if (config_setting_lookup_float(model, "path_loss_exp", ¶m->path_loss_exponent) != CONFIG_TRUE) { w_flogf(ctx, LOG_ERR, stderr, "path_loss_exponent not found\n"); return -EINVAL; } if (config_setting_lookup_float(model, "xg", ¶m->Xg) != CONFIG_TRUE) { w_flogf(ctx, LOG_ERR, stderr, "xg not found\n"); return -EINVAL; } ctx->path_loss_param = param; } else if (strncmp(path_loss_model_name, "free_space", sizeof("free_space")) == 0) { struct log_distance_model_param *param; ctx->calc_path_loss = calc_path_loss_free_space; param = malloc(sizeof(*param)); if (!param) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory(path_loss_param)\n"); return -EINVAL; } } else if (strncmp(path_loss_model_name, "itu", sizeof("itu")) == 0) { struct itu_model_param *param; ctx->calc_path_loss = calc_path_loss_itu; param = malloc(sizeof(*param)); if (!param) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory(path_loss_param)\n"); return -EINVAL; } if (config_setting_lookup_int(model, "nFLOORS", ¶m->nFLOORS) != CONFIG_TRUE) { w_flogf(ctx, LOG_ERR, stderr, "nFLOORS not found\n"); return -EINVAL; } if (config_setting_lookup_int(model, "LF", ¶m->LF) != CONFIG_TRUE) { w_flogf(ctx, LOG_ERR, stderr, "LF not found\n"); return -EINVAL; } ctx->path_loss_param = param; } else { w_flogf(ctx, LOG_ERR, stderr, "No path loss model found\n"); return -EINVAL; } list_for_each_entry(station, &ctx->stations, list) { position = config_setting_get_elem(positions, station->index); if (config_setting_length(position) != 2) { w_flogf(ctx, LOG_ERR, stderr, "Invalid position: expected (double,double)\n"); return -EINVAL; } station->x = config_setting_get_float_elem(position, 0); station->y = config_setting_get_float_elem(position, 1); if (directions) { direction = config_setting_get_elem(directions, station->index); if (config_setting_length(direction) != 2) { w_flogf(ctx, LOG_ERR, stderr, "Invalid direction: expected (double,double)\n"); return -EINVAL; } station->dir_x = config_setting_get_float_elem( direction, 0); station->dir_y = config_setting_get_float_elem( direction, 1); } station->tx_power = config_setting_get_float_elem( tx_powers, station->index); } recalc_path_loss(ctx); return 0; } static double pseudo_normal_distribution(void) { int i; double normal = -6.0; for (i = 0; i < 12; i++) normal += drand48(); return normal; } static int _get_fading_signal(struct wmediumd *ctx) { return ctx->fading_coefficient * pseudo_normal_distribution(); } static int get_no_fading_signal(struct wmediumd *ctx) { return 0; } /* Existing link is from from -> to; copy to other dir */ static void mirror_link(struct wmediumd *ctx, int from, int to) { ctx->snr_matrix[ctx->num_stas * to + from] = ctx->snr_matrix[ctx->num_stas * from + to]; if (ctx->error_prob_matrix) { ctx->error_prob_matrix[ctx->num_stas * to + from] = ctx->error_prob_matrix[ctx->num_stas * from + to]; } } int validate_config(const char* file) { struct wmediumd ctx = {}; INIT_LIST_HEAD(&ctx.stations); INIT_LIST_HEAD(&ctx.clients); INIT_LIST_HEAD(&ctx.clients_to_free); int load_result = load_config(&ctx, file, NULL); clear_config(&ctx); if (load_result < 0) return 0; return 1; } /* * Loads a config file into memory */ int load_config(struct wmediumd *ctx, const char *file, const char *per_file) { config_t cfg, *cf; const config_setting_t *ids, *links, *model_type; const config_setting_t *error_probs = NULL, *error_prob; const config_setting_t *enable_interference; const config_setting_t *fading_coefficient, *default_prob; int count_ids, i, j; int start, end, snr; struct station *station; const char *model_type_str; float default_prob_value = 0.0; bool *link_map = NULL; ctx->config_path = strdup(file); /*initialize the config file*/ cf = &cfg; config_init(cf); /*read the file*/ if (!config_read_file(cf, file)) { w_logf(ctx, LOG_ERR, "Error loading file %s at line:%d, reason: %s\n", file, config_error_line(cf), config_error_text(cf)); config_destroy(cf); return -EIO; } ids = config_lookup(cf, "ifaces.ids"); if (!ids) { w_logf(ctx, LOG_ERR, "ids not found in config file\n"); return -EIO; } count_ids = config_setting_length(ids); w_logf(ctx, LOG_NOTICE, "#_if = %d\n", count_ids); /* Fill the mac_addr */ ctx->sta_array = calloc(count_ids, sizeof(struct station *)); if (!ctx->sta_array) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory(sta_array)!\n"); return -ENOMEM; } for (i = 0; i < count_ids; i++) { u8 addr[ETH_ALEN]; const char *str = config_setting_get_string_elem(ids, i); string_to_mac_address(str, addr); station = calloc(1, sizeof(*station)); if (!station) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory!\n"); return -ENOMEM; } station->index = i; memcpy(station->addr, addr, ETH_ALEN); memcpy(station->hwaddr, addr, ETH_ALEN); station->tx_power = SNR_DEFAULT; station_init_queues(station); list_add_tail(&station->list, &ctx->stations); ctx->sta_array[i] = station; w_logf(ctx, LOG_NOTICE, "Added station %d: " MAC_FMT "\n", i, MAC_ARGS(addr)); } ctx->num_stas = count_ids; enable_interference = config_lookup(cf, "ifaces.enable_interference"); if (enable_interference && config_setting_get_bool(enable_interference)) { ctx->intf = calloc(ctx->num_stas * ctx->num_stas, sizeof(struct intf_info)); if (!ctx->intf) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory(intf)\n"); return -ENOMEM; } for (i = 0; i < ctx->num_stas; i++) for (j = 0; j < ctx->num_stas; j++) ctx->intf[i * ctx->num_stas + j].signal = -200; } else { ctx->intf = NULL; } fading_coefficient = config_lookup(cf, "model.fading_coefficient"); if (fading_coefficient && config_setting_get_int(fading_coefficient) > 0) { ctx->get_fading_signal = _get_fading_signal; ctx->fading_coefficient = config_setting_get_int(fading_coefficient); } else { ctx->get_fading_signal = get_no_fading_signal; ctx->fading_coefficient = 0; } /* create link quality matrix */ ctx->snr_matrix = calloc(sizeof(int), count_ids * count_ids); if (!ctx->snr_matrix) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory!\n"); return -ENOMEM; } /* set default snrs */ for (i = 0; i < count_ids * count_ids; i++) ctx->snr_matrix[i] = SNR_DEFAULT; links = config_lookup(cf, "ifaces.links"); if (!links) { model_type = config_lookup(cf, "model.type"); if (model_type) { model_type_str = config_setting_get_string(model_type); if (memcmp("snr", model_type_str, strlen("snr")) == 0) { links = config_lookup(cf, "model.links"); } else if (memcmp("prob", model_type_str, strlen("prob")) == 0) { error_probs = config_lookup(cf, "model.links"); } else if (memcmp("path_loss", model_type_str, strlen("path_loss")) == 0) { /* calculate signal from positions */ if (parse_path_loss(ctx, cf)) goto fail; } } } if (per_file && error_probs) { w_flogf(ctx, LOG_ERR, stderr, "per_file and error_probs could not be used at the same time\n"); goto fail; } ctx->get_link_snr = get_link_snr_from_snr_matrix; ctx->get_error_prob = _get_error_prob_from_snr; ctx->per_matrix = NULL; ctx->per_matrix_row_num = 0; if (per_file && read_per_file(ctx, per_file)) goto fail; ctx->error_prob_matrix = NULL; if (error_probs) { ctx->error_prob_matrix = calloc(sizeof(double), count_ids * count_ids); if (!ctx->error_prob_matrix) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory(error_prob_matrix)\n"); goto fail; } ctx->get_link_snr = get_link_snr_default; ctx->get_error_prob = get_error_prob_from_matrix; default_prob = config_lookup(cf, "model.default_prob"); if (default_prob) { default_prob_value = config_setting_get_float( default_prob); if (default_prob_value < 0.0 || default_prob_value > 1.0) { w_flogf(ctx, LOG_ERR, stderr, "model.default_prob should be in [0.0, 1.0]\n"); goto fail; } } } link_map = calloc(sizeof(bool), count_ids * count_ids); if (!link_map) { w_flogf(ctx, LOG_ERR, stderr, "Out of memory\n"); goto fail; } /* read snr values */ for (i = 0; links && i < config_setting_length(links); i++) { config_setting_t *link; link = config_setting_get_elem(links, i); if (config_setting_length(link) != 3) { w_flogf(ctx, LOG_ERR, stderr, "Invalid link: expected (int,int,int)\n"); goto fail; } start = config_setting_get_int_elem(link, 0); end = config_setting_get_int_elem(link, 1); snr = config_setting_get_int_elem(link, 2); if (start < 0 || start >= ctx->num_stas || end < 0 || end >= ctx->num_stas) { w_flogf(ctx, LOG_ERR, stderr, "Invalid link [%d,%d,%d]: index out of range\n", start, end, snr); goto fail; } ctx->snr_matrix[ctx->num_stas * start + end] = snr; link_map[ctx->num_stas * start + end] = true; } /* initialize with default_prob */ for (start = 0; error_probs && start < ctx->num_stas; start++) for (end = start + 1; end < ctx->num_stas; end++) { ctx->error_prob_matrix[ctx->num_stas * start + end] = ctx->error_prob_matrix[ctx->num_stas * end + start] = default_prob_value; } /* read error probabilities */ for (i = 0; error_probs && i < config_setting_length(error_probs); i++) { float error_prob_value; error_prob = config_setting_get_elem(error_probs, i); if (config_setting_length(error_prob) != 3) { w_flogf(ctx, LOG_ERR, stderr, "Invalid error probability: expected (int,int,float)\n"); goto fail; } start = config_setting_get_int_elem(error_prob, 0); end = config_setting_get_int_elem(error_prob, 1); error_prob_value = config_setting_get_float_elem(error_prob, 2); if (start < 0 || start >= ctx->num_stas || end < 0 || end >= ctx->num_stas || error_prob_value < 0.0 || error_prob_value > 1.0) { w_flogf(ctx, LOG_ERR, stderr, "Invalid error probability [%d,%d,%f]\n", start, end, error_prob_value); goto fail; } ctx->error_prob_matrix[ctx->num_stas * start + end] = error_prob_value; link_map[ctx->num_stas * start + end] = true; } /* * If any links are specified in only one direction, mirror them, * making them symmetric. If specified in both directions they * can be asymmetric. */ for (i = 0; i < ctx->num_stas; i++) { for (j = 0; j < ctx->num_stas; j++) { if (i == j) continue; if (link_map[ctx->num_stas * i + j] && !link_map[ctx->num_stas * j + i]) { mirror_link(ctx, i, j); } } } free(link_map); config_destroy(cf); return 0; fail: free(ctx->snr_matrix); free(ctx->error_prob_matrix); config_destroy(cf); return -EINVAL; } int clear_config(struct wmediumd *ctx) { free(ctx->sta_array); free(ctx->intf); free(ctx->snr_matrix); free(ctx->error_prob_matrix); free(ctx->config_path); ctx->sta_array = NULL; ctx->intf = NULL; ctx->snr_matrix = NULL; ctx->error_prob_matrix = NULL; ctx->config_path = NULL; while (!list_empty(&ctx->stations)) { struct station *station; station = list_first_entry(&ctx->stations, struct station, list); list_del(&station->list); free(station->lci); free(station->civicloc); free(station); } return 0; } void calc_path_loss(struct wmediumd *ctx) { recalc_path_loss(ctx); }