blob: 05009492c50fa74cc313c2b9f73a624ae1bc448a [file] [log] [blame]
/*
* wmediumd, wireless medium simulator for mac80211_hwsim kernel module
* Copyright (c) 2011 cozybit Inc.
* Copyright (C) 2020 Intel Corporation
*
* Author: Javier Lopez <jlopex@cozybit.com>
* Javier Cardona <javier@cozybit.com>
*
* 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 <sys/timerfd.h>
#include <libconfig.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
#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",
&param->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",
&param->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",
&param->nFLOORS) != CONFIG_TRUE) {
w_flogf(ctx, LOG_ERR, stderr,
"nFLOORS not found\n");
return -EINVAL;
}
if (config_setting_lookup_int(model, "LF",
&param->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);
}
return 0;
}
void calc_path_loss(struct wmediumd *ctx) {
recalc_path_loss(ctx);
}