diff --git a/README.md b/README.md index 39db669..7eb7eab 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,35 @@ agent-click-file-gen.py Click file generator for the agent. Configure and use this script to generate the appropriate Odin agent click file. + +This is an example of how to use the python command: + + $ python agent-click-file-gen.py 4 50 14:CC:20:AC:72:91 192.168.1.129 2819 /sys/kernel/debug/ieee80211/phy0/ath9k/bssid_extra odin-unizar 192.168.1.7 0 11 12 25 0 + +where the parameters have the next meaning: + +`4` number of the channel + +`50` size of the queue + +`14:CC:20:AC:72:91` MAC address of the AP + +`192.168.1.129` IP address of the Controller + +`2819` port used to connect with the Controller + +`/sys/kernel/debug/ieee80211/phy0/ath9k/bssid_extra` directory of the debug file + +`odin-unizar` SSID + +`192.168.1.7` IP address of the AP + +`0` No click debug info + +`11` Debug level 1, with Demo appearance (1) + +`12` 6 Mbps of TX rate + +`25` the Tx power of the AP is 25 dBm (obtained with `$iw dev mon0 info`) + +`0 no hidden mode: the AP will respond to all active scanning requests, even if they have no SSID name diff --git a/agent-click-file-gen.py b/agent-click-file-gen.py index 3bd30a0..38062fd 100644 --- a/agent-click-file-gen.py +++ b/agent-click-file-gen.py @@ -13,10 +13,10 @@ import sys -if (len(sys.argv) != 11): +if (len(sys.argv) != 14): print 'Usage:' print '' - print '%s ' %(sys.argv[0]) + print '%s ' %(sys.argv[0]) print '' print 'AP_CHANNEL: it must be the same where mon0 of the AP is placed. To avoid problems at init time, it MUST be the same channel specified in the /etc/config/wireless file of the AP' print 'QUEUE_SIZE: you can use the size 50' @@ -28,10 +28,16 @@ print 'SSIDAGENT is the name of the SSID of this Odin agent' print 'ODIN_AGENT_IP is the IP address of the AP where this script is running (the IP used for communicating with the controller)' print 'DEBUG_CLICK: "0" no info displayed; "1" only basic info displayed; "2" all the info displayed' - print 'DEBUG_ODIN: "0" no info displayed; "1" only basic info displayed; "2" all the info displayed; "1x" demo info displayed' + print 'DEBUG_ODIN: "00" no info displayed; "01" only basic info displayed; "02" all the info displayed; "11" or "12": demo mode (more separators)' + print 'TX_RATE: it is an integer, and the rate is obtained by its product with 500kbps. e.g. if it is 108, this means 108*500kbps = 54Mbps' + print ' we are not able to send packets at different rates, so a single rate has to be specified' + print 'TX_POWER: (in dBm) as we are not able to modify it, you should add it here manually' + print ' for getting the value, use e.g. $# iw dev mon0 info' + print 'HIDDEN: If HIDDEN is 1, then the AP will only send responses to the active scans targetted to the SSID of Odin' + print ' If HIDDEN is 0, then the AP will also send responses to active scans with an empty SSID' print '' print 'Example:' - print '$ python %s X 50 XX:XX:XX:XX:XX:XX 192.168.1.X 2819 /sys/kernel/debug/ieee80211/phy0/ath9k/bssid_extra odin-unizar 192.168.1.Y L M > agent.click' %(sys.argv[0]) + print '$ python %s X 50 XX:XX:XX:XX:XX:XX 192.168.1.X 2819 /sys/kernel/debug/ieee80211/phy0/ath9k/bssid_extra odin-unizar 192.168.1.Y L MM N P 0 > agent.click' %(sys.argv[0]) print '' print 'and then run the .click file you have generated' print 'click$ ./bin/click agent.click' @@ -49,13 +55,15 @@ AP_UNIQUE_IP = sys.argv[8] # IP address of the wlan0 interface of the router where Click runs (in monitor mode). It seems it does not matter. DEBUG_CLICK = int(sys.argv[9]) DEBUG_ODIN = int(sys.argv[10]) +TX_RATE = int(sys.argv[11]) +TX_POWER = int(sys.argv[12]) +HIDDEN = int(sys.argv[13]) # Set the value of some constants NETWORK_INTERFACE_NAMES = "mon" # beginning of the network interface names in monitor mode. e.g. mon TAP_INTERFACE_NAME = "ap" # name of the TAP device that Click will create in the STA_IP = "192.168.1.11" # IP address of the STA in the LVAP tuple. It only works for a single client without DHCP STA_MAC = "74:F0:6D:20:D4:74" # MAC address of the STA in the LVAP tuple. It only works for a single client without DHCP -RATE = "108" # e.g. if it is 108, this means 108*500kbps = 54Mbps print ''' // This is the scheme: @@ -74,8 +82,8 @@ print ''' // call OdinAgent::configure to create and configure an Odin agent: -odinagent::OdinAgent(HWADDR %s, RT rates, CHANNEL %s, DEFAULT_GW %s, DEBUGFS %s, SSIDAGENT %s, DEBUG_ODIN %s) -''' % (AP_UNIQUE_BSSID, AP_CHANNEL, DEFAULT_GW, DEBUGFS_FILE, SSIDAGENT, DEBUG_ODIN ) +odinagent::OdinAgent(HWADDR %s, RT rates, CHANNEL %s, DEFAULT_GW %s, DEBUGFS %s, SSIDAGENT %s, DEBUG_ODIN %s, TX_RATE %s, TX_POWER %s, HIDDEN %s) +''' % (AP_UNIQUE_BSSID, AP_CHANNEL, DEFAULT_GW, DEBUGFS_FILE, SSIDAGENT, DEBUG_ODIN, TX_RATE, TX_POWER, HIDDEN ) print ''' // send a ping to odinsocket every 2 seconds @@ -103,7 +111,7 @@ print ''' // output 3 of odinagent goes to odinsocket odinagent[3] -> odinsocket -rates :: AvailableRates(DEFAULT 24 36 48 108); // wifi rates in multiples of 500kbps +rates :: AvailableRates(DEFAULT 12 18 24 36 48 72 96 108); // wifi rates in multiples of 500kbps control :: ControlSocket("TCP", 6777); chatter :: ChatterSocket("TCP", 6778); ''' @@ -156,7 +164,7 @@ -> SetTXRate (%s) // e.g. if it is 108, this means 54Mbps=108*500kbps -> RadiotapEncap() -> to_dev :: ToDevice (%s0); -''' % (QUEUE_SIZE, RATE, NETWORK_INTERFACE_NAMES ) +''' % (QUEUE_SIZE, TX_RATE, NETWORK_INTERFACE_NAMES ) print ''' odinagent[2] diff --git a/scripts_start_ap_odin/init_cli.sh b/scripts_start_ap_odin/init_cli.sh new file mode 100644 index 0000000..1f3273a --- /dev/null +++ b/scripts_start_ap_odin/init_cli.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +#Setup variables +# My local IP address, i.e. the one I am using to connect with the Odin Controller +MYIP=192.168.101.9 + +# This is the IP of the Odin Controller +CTLIP=192.168.101.129 + +# This is the TCP port number for the openvswitch database +TCP_PORT_OVS_DBASE=6632 + +# This is the TCP port number for the Click control socket (controlskt) +TCP_PORT_CLICK_CTRL_SOCKET=6777 + +# This is the name of the bridge that we are going to create +SW=br0 + + +# Start Click, using the correct name of the ALIGNED .cli file +./click a_agent.cli & + +# Wait some time +sleep 3 + +# Start the 'ap' interface created by Click +ifconfig ap up + +# Add the 'ap' interface to the openvswitch +ovs-vsctl --db=tcp:$MYIP:$TCP_PORT_OVS_DBASE add-port $SW ap + +# Wait some time +sleep 3 + +# Add an Openflow rule in the Controller, to manage the new flows of the Click control socket +ovs-ofctl add-flow $SW in_port=1,dl_type=0x0800,nw_src=$CTLIP,nw_dst=$MYIP,nw_proto=6,tp_dst=$TCP_PORT_CLICK_CTRL_SOCKET,actions=output:LOCAL \ No newline at end of file diff --git a/scripts_start_ap_odin/script_start_ovs.sh b/scripts_start_ap_odin/init_ovs.sh similarity index 72% rename from scripts_start_ap_odin/script_start_ovs.sh rename to scripts_start_ap_odin/init_ovs.sh index 1181040..b3bc482 100644 --- a/scripts_start_ap_odin/script_start_ovs.sh +++ b/scripts_start_ap_odin/init_ovs.sh @@ -1,27 +1,31 @@ -# This has been taken from https://gist.github.com/marciolm/9f0ab13b877372d08e8f +#!/bin/sh +# This has been adapted from https://gist.github.com/marciolm/9f0ab13b877372d08e8f #Setup variables #My local IP address is required for the ovsdb server. -MYIP=192.168.1.6 +MYIP=192.168.101.9 # This is the OpenFlow controller ID which we're going to load into the OVS -CTLIP=192.168.1.2 +CTLIP=192.168.101.129 # This is our DataPath ID DPID=0000000000000212 -# This is the name of the bridge that we're going to be creating +# This is the name of the bridge that we are going to create SW=br0 # This is the TCP port number for the openvswitch database -TCP_PORT_OVS=6632 +TCP_PORT_OVS_DBASE=6632 + +# This is the TCP port number for the control of OpenFlow +TCP_PORT_OPENFLOW=6633 #What ports are we going to put in the OVS? #DPPORTS="eth0.1 eth0.2 eth0.3 eth0.4 wlan0 wlan0-2 wlan0-3" DPPORTS="eth1.1" #Alias some variables -VSCTL="ovs-vsctl --db=tcp:$MYIP:$TCP_PORT_OVS" +VSCTL="ovs-vsctl --db=tcp:$MYIP:$TCP_PORT_OVS_DBASE" OVSDB=/tmp/ovs-vswitchd.conf.db # Subroutine to wait until a port is ready @@ -46,19 +50,24 @@ rm -f $OVSDB ovsdb-tool create $OVSDB /usr/share/openvswitch/vswitch.ovsschema # Start the OVSDB server and wait until it starts -ovsdb-server $OVSDB --remote=ptcp:$TCP_PORT_OVS:$MYIP & -#wait_port_listen $TCP_PORT_OVS +ovsdb-server $OVSDB --remote=ptcp:$TCP_PORT_OVS_DBASE:$MYIP & +#wait_port_listen $TCP_PORT_OVS_DBASE sleep 5 # Start vSwitchd -ovs-vswitchd tcp:$MYIP:$TCP_PORT_OVS --pidfile=ovs-vswitchd.pid --overwrite-pidfile -- & +ovs-vswitchd tcp:$MYIP:$TCP_PORT_OVS_DBASE --pidfile=ovs-vswitchd.pid --overwrite-pidfile -- & # Create the bridge and pass in some configuration options $VSCTL add-br $SW + +# In OpenWrt 15.05 the bridge is created down +ifconfig $SW up + +#optional: define the version of OpenFlow to be used #$VSCTL set bridge $SW protocols=OpenFlow10 #Configure the switch to have an OpenFlow Controller. This will contact the controller. -$VSCTL set-controller $SW tcp:$CTLIP:6633 +$VSCTL set-controller $SW tcp:$CTLIP:$TCP_PORT_OPENFLOW # Turn off the fail-safe mode $VSCTL set-fail-mode $SW secure diff --git a/scripts_start_ap_odin/script_start_click.sh b/scripts_start_ap_odin/script_start_click.sh deleted file mode 100644 index 927044f..0000000 --- a/scripts_start_ap_odin/script_start_click.sh +++ /dev/null @@ -1,8 +0,0 @@ -./click a_agent.cli & -sleep 3 -ifconfig ap up -ovs-vsctl --db=tcp:192.168.1.5:6632 add-port br0 ap -sleep 3 -ovs-ofctl add-flow br0 in_port=1,dl_type=0x0800,nw_src=192.168.1.129,nw_dst=192.168.1.5,nw_proto=6,tp_dst=6777,actions=output:LOCAL - - diff --git a/scripts_start_ap_odin/start_odin.sh b/scripts_start_ap_odin/start_odin.sh index 461a514..04ed575 100644 --- a/scripts_start_ap_odin/start_odin.sh +++ b/scripts_start_ap_odin/start_odin.sh @@ -20,6 +20,7 @@ route add -net 155.210.156.0 netmask 255.255.255.0 gw 155.210.157.254 eth0 # set the default gateway where masquerading is being performed # NOTE: This may vary according to your setup. Add your default gateway as needed route del default gw 155.210.157.254 +<<<<<<< HEAD route add default gw 192.168.1.129 # if you have put click and start-up scripts in a usb device, mount the USB @@ -31,3 +32,33 @@ cd /mnt/usb/ ./script_start_ovs.sh sleep 2 ./script_start_click.sh +======= +route add default gw 192.168.101.129 + +# this script assumes you have: +# - in the root directory (current): start_odin.sh +# - in the USB: +# - init_ovs.sh +# - init_cli.sh +# - click the compiled Click application +# - a_agent.cli the .cli file to be run by Click. It must be aligned + +# mount the USB +mkdir -p /mnt/usb +mount /dev/sda1 /mnt/usb/ #sda1 may have to be replaced by other device + + +# initiate openvswitch and click with the corresponding scripts: + +# move to /mnt/usb +cd /mnt/usb + +# initiate openvswitch (ovs) +./init_ovs.sh + +# wait some time +sleep 2 + +# initiate click (cli) +./init_cli.sh +>>>>>>> refs/heads/alpha_stats diff --git a/src/odinagent.cc b/src/odinagent.cc index 72e89fe..be07ce2 100644 --- a/src/odinagent.cc +++ b/src/odinagent.cc @@ -30,6 +30,7 @@ #include #include #include +#include CLICK_DECLS @@ -55,7 +56,10 @@ OdinAgent::OdinAgent() _associd(0), _beacon_timer(this), _debugfs_string(""), - _ssid_agent_string("") + _ssid_agent_string(""), + _tx_rate(0), + _tx_power(0), + _hidden(0) { _clean_stats_timer.assign(&cleanup_lvap, (void *) this); _general_timer.assign (&misc_thread, (void *) this); @@ -113,6 +117,8 @@ OdinAgent::configure(Vector &conf, ErrorHandler *errh) _csa_count = _csa_count_default; _count_csa_beacon_default = 4; // Number of beacons before channel switch _count_csa_beacon = _count_csa_beacon_default; + + // read the arguments of the .cli file if (Args(conf, this, errh) .read_mp("HWADDR", _hw_mac_addr) .read_m("RT", ElementCastArg("AvailableRates"), _rtable) @@ -121,6 +127,9 @@ OdinAgent::configure(Vector &conf, ErrorHandler *errh) .read_m("DEBUGFS", _debugfs_string) .read_m("SSIDAGENT", _ssid_agent_string) .read_m("DEBUG_ODIN", _debug_level) + .read_m("TX_RATE", _tx_rate) // as we are not yet able to do per-packet TPC, we use a fixed transmission rate, and we must read it to perform the calculations of the statistics + .read_m("TX_POWER", _tx_power) // as we are not yet able to do per-packet TPC, we use a fixed transmission power, and we must read it to perform the calculations of the statistics + .read_m("HIDDEN", _hidden) .complete() < 0) return -1; @@ -193,14 +202,14 @@ OdinAgent::add_vap (EtherAddress sta_mac, IPAddress sta_ip, EtherAddress sta_bss if (_debug_level % 10 > 0) { //fprintf(stderr, "[Odinagent.cc] add_lvap %s\n", sta_mac.unparse_colon().c_str()); - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n"); fprintf(stderr, "[Odinagent.cc] add_lvap (%s, %s, %s, %s)\n", sta_mac.unparse_colon().c_str() , sta_ip.unparse().c_str() , sta_bssid.unparse().c_str() , vap_ssids[0].c_str()); - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n\n"); } @@ -282,13 +291,13 @@ int OdinAgent::set_vap (EtherAddress sta_mac, IPAddress sta_ip, EtherAddress sta_bssid, Vector vap_ssids) { if (_debug_level % 10 > 0) { - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n"); fprintf(stderr, "[Odinagent.cc] set_lvap (%s, %s, %s, %s)\n", sta_mac.unparse_colon().c_str() , sta_ip.unparse().c_str() , sta_bssid.unparse().c_str() , vap_ssids[0].c_str()); - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n\n"); } @@ -345,12 +354,12 @@ int OdinAgent::remove_vap (EtherAddress sta_mac) { if (_debug_level % 10 > 0) { - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n"); fprintf(stderr, "[Odinagent.cc] remove_lvap (%s)\n", sta_mac.unparse_colon().c_str()); - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n\n"); } @@ -484,8 +493,9 @@ OdinAgent::recv_probe_request (Packet *p) fprintf(stderr, "[Odinagent.cc] SSID frame: %s SSID AP: %s\n", ssid.c_str(), _ssid_agent_string.c_str()); //If we're not aware of this LVAP, then send to the controller. + //If the SSID is hidden, then it will only send responses to the active scans targetted to the _ssid_agent_string if (_sta_mapping_table.find(src) == _sta_mapping_table.end()) { - if ((ssid == "") || (ssid == _ssid_agent_string)) { //if the ssid is blank (broadcast probe) or it is targetted to our SSID, forward it to the controller + if (((ssid == "") && _hidden == 0 ) || (ssid == _ssid_agent_string)) { //if the ssid is blank (broadcast probe) or it is targetted to our SSID, forward it to the controller if (_debug_level % 10 > 1) fprintf(stderr, "[Odinagent.cc] Received probe request: not aware of this LVAP -> probe req sent to the controller\n"); StringAccum sa; @@ -947,7 +957,7 @@ OdinAgent::recv_open_auth_request (Packet *p) { } if (_debug_level % 10 > 0) { - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n"); fprintf(stderr, "[Odinagent.cc] OpenAuth request STA (%s) ----> AP (%s)\n", src.unparse_colon().c_str(), dst.unparse_colon().c_str()); @@ -1208,7 +1218,7 @@ OdinAgent::send_assoc_response (EtherAddress dst, uint16_t status, uint16_t asso if (_debug_level % 10 > 0) { fprintf(stderr, "[Odinagent.cc] Association response STA (%s) <---- AP (%s)\n", dst.unparse_colon().c_str(), src.unparse_colon().c_str()); - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n\n"); } @@ -1288,6 +1298,91 @@ OdinAgent::wifi_encap (Packet *p, EtherAddress bssid) return p_out; } + +/** + * Every time a packet is transmitted, the transmission + * statistics have to be updated + */ +void +OdinAgent::update_tx_stats(Packet *p) +{ + struct click_wifi *w = (struct click_wifi *) p->data(); + EtherAddress dst = EtherAddress(w->i_addr1); // Get the MAC destination address. In this case it is the first MAC address + + //struct click_wifi_extra *ceh = WIFI_EXTRA_ANNO(p); + + StationStats stat; + HashTable::const_iterator it = _tx_stats.find(dst); // find the destination address in the stats table + + // if the station does not have a statistics variable, create it + if (it == _tx_stats.end()) { + stat = StationStats(); + + stat._time_first_packet.assign_now(); // update the value of the first received packet + } + else + stat = it.value(); + + // the rate is directly read from the Click configuration file + //stat._rate = ceh->rate; + stat._rate = _tx_rate; + + // we are not currently modifying per-packet transmission power + //so calculating the average makes no sense + //stat._signal = ceh->rssi + _signal_offset; + stat._signal = _tx_power + 256; // we add 256, as this is the usual way for storing the power + + // we are not reading the noise value, so assign a 0 + //stat._noise = ceh->silence; + stat._noise = 0; + + // read the length of the packet + stat._len_pkt = p->length(); + + stat._packets++; // increase the number of packets + + // Calculate the averaged statistics + stat._avg_rate = stat._avg_rate + ((stat._rate*500 - stat._avg_rate)/stat._packets); // rate in Kbps + + /* as we are not setting different values for each packet, we do not have to calculate this + // Calculate the value of the signal, converting from dBm to mW and back + double signal_mW; + double avg_signal_mW; + signal_mW = pow (10, (stat._signal - 256) / 10); + if (first_packet) // if this is the first packet, the previous average will be 0 + avg_signal_mW = 0; + else + avg_signal_mW = pow (10, stat._avg_signal / 10); + avg_signal_mW = avg_signal_mW + ((signal_mW - avg_signal_mW)/stat._packets); + stat._avg_signal = 10 * log10 (avg_signal_mW); // signal in dBm + //stat._avg_signal = stat._avg_signal + ((stat._signal - 256 - stat._avg_signal)/stat._packets); // signal in dBm + stat._avg_signal = 0; // we are not currently modifying per-packet transmission power so calculating the average makes no sense + */ + stat._avg_signal = _tx_power; // in dBm + + stat._avg_len_pkt = stat._avg_len_pkt + ((stat._len_pkt - stat._avg_len_pkt)/stat._packets); // length in bytes + stat._air_time = stat._air_time + ((double)(8*stat._len_pkt) / (double)(stat._rate*500)); // time used by this packet (in ms) + + // store the timestamp of this packet as the one of the last packet + stat._time_last_packet.assign_now(); + + /* + if (_debug_level % 10 > 1){ + FILE * fp; + fp = fopen ("/root/spring/shared/updated_stats.txt", "w"); + fprintf(fp, "* update_rx_stats: src = %s, rate = %i, noise = %i, signal = %i (%i dBm)\n", src.unparse_colon().c_str(), stat._rate, stat._noise, stat._signal, (stat._signal - 128)*-1); //-(value - 128) + fclose(fp); + } + */ + + // update the statistics table + _tx_stats.set (dst, stat); +} + +/** + * Every time a packet is received, the reception + * statistics have to be updated + */ void OdinAgent::update_rx_stats(Packet *p) { @@ -1297,27 +1392,57 @@ OdinAgent::update_rx_stats(Packet *p) struct click_wifi_extra *ceh = WIFI_EXTRA_ANNO(p); StationStats stat; + bool first_packet = false; // it will be true if the packet is the first one used for the average HashTable::const_iterator it = _rx_stats.find(src); - if (it == _rx_stats.end()) + + // if the station does not have a statistics variable, create it + if (it == _rx_stats.end()) { stat = StationStats(); + + stat._time_first_packet.assign_now(); // update the value of the first received packet + first_packet = true; + } else stat = it.value(); stat._rate = ceh->rate; - stat._noise = ceh->silence; stat._signal = ceh->rssi + _signal_offset; - stat._packets++; - stat._last_received.assign_now(); -/* + stat._noise = ceh->silence; + stat._len_pkt = p->length(); + + stat._packets++; // increase the number of packets + + // Calculate the averaged statistics + stat._avg_rate = stat._avg_rate + ((stat._rate*500 - stat._avg_rate)/stat._packets); // rate in Kbps + + // Calculate the value of the signal, converting from dBm to mW and back + double signal_mW; + double avg_signal_mW; + signal_mW = pow (10, (stat._signal - 256) / 10); + if (first_packet) // if this is the first packet, the previous average will be 0 + avg_signal_mW = 0; + else + avg_signal_mW = pow (10, stat._avg_signal / 10); + avg_signal_mW = avg_signal_mW + ((signal_mW - avg_signal_mW)/stat._packets); + stat._avg_signal = 10 * log10 (avg_signal_mW); // signal in dBm + + stat._avg_len_pkt = stat._avg_len_pkt + ((stat._len_pkt - stat._avg_len_pkt)/stat._packets); // length in bytes + stat._air_time = stat._air_time + ((double)(8*stat._len_pkt) / (double)(stat._rate*500)); // time used by this packet (in ms) + + // store the timestamp of this packet as the one of the last packet + stat._time_last_packet.assign_now(); + + /* if (_debug_level % 10 > 1){ FILE * fp; fp = fopen ("/root/spring/shared/updated_stats.txt", "w"); fprintf(fp, "* update_rx_stats: src = %s, rate = %i, noise = %i, signal = %i (%i dBm)\n", src.unparse_colon().c_str(), stat._rate, stat._noise, stat._signal, (stat._signal - 128)*-1); //-(value - 128) fclose(fp); } -*/ + */ match_against_subscriptions(stat, src); + // update the statistics table _rx_stats.set (src, stat); } @@ -1361,22 +1486,8 @@ OdinAgent::push(int port, Packet *p) struct click_wifi *w = (struct click_wifi *) p->data(); EtherAddress src = EtherAddress(w->i_addr2); - // struct click_wifi_extra *ceh = WIFI_EXTRA_ANNO(p); - - // StationStats stat; - // HashTable::const_iterator it = _rx_stats.find(src); - // if (it == _rx_stats.end()) - // stat = StationStats(); - // else - // stat = it.value(); - - // stat._rate = ceh->rate; - // stat._noise = ceh->silence; - // stat._signal = ceh->rssi + _signal_offset; - // stat._packets++; - // stat._last_received.assign_now(); - - // _rx_stats.set (src, stat); + + // Update Rx statistics update_rx_stats(p); type = w->i_fc[0] & WIFI_FC0_TYPE_MASK; @@ -1457,6 +1568,9 @@ OdinAgent::push(int port, Packet *p) memcpy(w_out->i_addr2, oss._vap_bssid.data(), 6); memcpy(w_out->i_addr3, src.data(), 6); + // Update Tx statistics with this packet + update_tx_stats(p_out); + // send the frame by the output number 2 output(2).push(p_out); return; @@ -1480,7 +1594,7 @@ OdinAgent::push(int port, Packet *p) if (_sta_mapping_table.find (eth) != _sta_mapping_table.end ()) { OdinStationState oss = _sta_mapping_table.get (eth); - // If the client tried to make an ARP request for + // If the client tried to make an ARP request for // its default gateway, and there is a response coming from // upstream, we have to correct the resolved hw-addr with the // VAP-BSSID to which the client corresponds. @@ -1497,7 +1611,12 @@ OdinAgent::push(int port, Packet *p) // memcpy(ea->arp_sha, oss._vap_bssid.data(), 6); // } //} - Packet *p_out = wifi_encap (p, oss._vap_bssid); + + // Add wifi header + Packet *p_out = wifi_encap (p, oss._vap_bssid); + + // Update Tx statistics with this packet + update_tx_stats(p_out); output(2).push(p_out); return; } @@ -1567,8 +1686,8 @@ OdinAgent::match_against_subscriptions(StationStats stats, EtherAddress src) // EtherAddress builds a 00:00:00:00:00:00 MAC address (this is for dealing with '*' subscriptions) // First I check if the address of the arrived packet matches - if (sub.sta_addr != EtherAddress() && sub.sta_addr != src) - continue; + if (sub.sta_addr != EtherAddress() && sub.sta_addr != src) + continue; if (_debug_level % 10 > 1) fprintf(stderr, "[Odinagent.cc] MAC %s in subscription list\n",sub.sta_addr.unparse_colon().c_str()); @@ -1726,34 +1845,77 @@ OdinAgent::read_handler(Element *e, void *user_data) sa << agent->_interval_ms << "\n"; break; } + + // handler for transmission statistics + case handler_txstat: { + + //Timestamp now = Timestamp::now(); + Vector buf; + + // the controller will get the tx statistics of all the STAs associated to this AP + //TODO: we could perhaps add another handler (write) which gets the statistics of a single MAC + for (HashTable::const_iterator iter = agent->_tx_stats.begin(); + iter.live(); iter++) { + + OdinAgent::StationStats n = iter.value(); + //Timestamp age = now - n._time_last_packet; + + sa << iter.key().unparse_colon(); + + sa << " packets:" << n._packets; + sa << " avg_rate:" << n._avg_rate; // rate in Kbps + sa << " avg_signal:" << n._avg_signal; // signal in dBm + sa << " avg_len_pkt:" << n._avg_len_pkt; // length in bytes + sa << " air_time:" << n._air_time; // time in seconds + + sa << " first_received:" << n._time_first_packet; // time in long format + sa << " last_received:" << n._time_last_packet << "\n"; // time in long format + + // sa << " age:" << age << "\n"; + buf.push_back (iter.key()); + } + + for (Vector::const_iterator iter = buf.begin(); iter != buf.end(); iter++) + agent->_tx_stats.erase (*iter); + + break; + } + + // handler for reception statistics case handler_rxstat: { - Timestamp now = Timestamp::now(); + //Timestamp now = Timestamp::now(); + Vector buf; + // the controller will get the rx statistics of all the STAs associated to this AP + //TODO: we could perhaps add another handler (write) which gets the statistics of a single MAC for (HashTable::const_iterator iter = agent->_rx_stats.begin(); - iter.live(); iter++) { + iter.live(); iter++) { OdinAgent::StationStats n = iter.value(); - Timestamp age = now - n._last_received; - // Timestamp avg_signal; - // Timestamp avg_noise; - // if (n._packets) { - // avg_signal = Timestamp::make_msec(1000*n._sum_signal / n._packets); - // avg_noise = Timestamp::make_msec(1000*n._sum_noise / n._packets); - // } - sa << iter.key().unparse_colon(); - sa << " rate:" << n._rate; - sa << " signal:" << n._signal; - sa << " noise:" << n._noise; - // sa << " avg_signal " << avg_signal; - // sa << " avg_noise " << avg_noise; - // sa << " total_signal " << n._sum_signal; - // sa << " total_noise " << n._sum_noise; - sa << " packets:" << n._packets; - sa << " last_received:" << age << "\n"; - } + //Timestamp age = now - n._time_last_packet; - break; + sa << iter.key().unparse_colon(); + + sa << " packets:" << n._packets; + sa << " avg_rate:" << n._avg_rate; // rate in Kbps + sa << " avg_signal:" << n._avg_signal; // signal in dBm + sa << " avg_len_pkt:" << n._avg_len_pkt; // length in bytes + sa << " air_time:" << n._air_time; // time in seconds + + sa << " first_received:" << n._time_first_packet; // time in long format + sa << " last_received:" << n._time_last_packet << "\n"; // time in long format + + // sa << " age:" << age << "\n"; + buf.push_back (iter.key()); + } + + // as I have sent the statistics, I delete the records of all the MACs read + for (Vector::const_iterator iter = buf.begin(); iter != buf.end(); iter++) + agent->_rx_stats.erase (*iter); + + break; } + case handler_subscriptions: { for (Vector::const_iterator iter = agent->_subscription_list.begin(); @@ -2041,7 +2203,7 @@ OdinAgent::write_handler(const String &str, Element *e, void *user_data, ErrorHa stat._signal = value; stat._packets++; - stat._last_received.assign_now(); + stat._time_last_packet.assign_now(); agent->match_against_subscriptions(stat, sta_mac); agent->_rx_stats.set (sta_mac, stat); @@ -2115,6 +2277,7 @@ OdinAgent::add_handlers() add_read_handler("channel", read_handler, handler_channel); add_read_handler("interval", read_handler, handler_interval); add_read_handler("rxstats", read_handler, handler_rxstat); + add_read_handler("txstats", read_handler, handler_txstat); add_read_handler("subscriptions", read_handler, handler_subscriptions); add_read_handler("debug", read_handler, handler_debug); add_read_handler("report_mean", read_handler, handler_report_mean); @@ -2137,8 +2300,8 @@ OdinAgent::add_handlers() void OdinAgent::print_stations_state() { - if (_debug_level % 10 > 0) { - if (_debug_level / 10 == 1) + if (_debug_level % 10 > 0) { // debug is activated + if (_debug_level / 10 == 1) // demo mode. I print more visual information, i.e. rows of "#' fprintf(stderr, "##################################################################\n"); fprintf(stderr,"[Odinagent.cc] ##### Periodic report. Number of stations associated: %i\n", _sta_mapping_table.size()); @@ -2146,7 +2309,8 @@ OdinAgent::print_stations_state() if(_sta_mapping_table.size() != 0) { // Initialize the statistics - HashTable::const_iterator iter = _rx_stats.begin(); + HashTable::const_iterator iter_tx = _tx_stats.begin(); + HashTable::const_iterator iter_rx = _rx_stats.begin(); // For each VAP for (HashTable::iterator it = _sta_mapping_table.begin(); it.live(); it++) { @@ -2159,18 +2323,55 @@ OdinAgent::print_stations_state() //stats //Print info from our stations if available - HashTable::const_iterator iter = _rx_stats.find(it.key()); - if (iter != _rx_stats.end()){ - fprintf(stderr,"[Odinagent.cc] -> rate: %i (%i kbps)\n", iter.value()._rate,iter.value()._rate * 500 ); - fprintf(stderr,"[Odinagent.cc] -> noise: %i\n", (iter.value()._noise)); - fprintf(stderr,"[Odinagent.cc] -> signal: %i (%i dBm)\n", iter.value()._signal, iter.value()._signal - 256 ); // value - 256) - fprintf(stderr,"[Odinagent.cc] -> packets: %i\n", (iter.value()._packets)); - fprintf(stderr,"[Odinagent.cc] -> last heard: %d.%06d \n", (iter.value()._last_received).sec(), (iter.value()._last_received).subsec()); + HashTable::const_iterator iter_tx = _tx_stats.find(it.key()); + if (iter_tx != _tx_stats.end()) { + fprintf(stderr,"[Odinagent.cc] Downlink (transmission)\n"); + fprintf(stderr,"[Odinagent.cc] -> last_packet_rate: %i (%i kbps)\n", iter_tx.value()._rate,iter_tx.value()._rate * 500 ); + fprintf(stderr,"[Odinagent.cc] -> last_packet_noise: %i\n", (iter_tx.value()._noise)); + fprintf(stderr,"[Odinagent.cc] -> last_packet_signal: %i (%i dBm)\n", iter_tx.value()._signal, iter_tx.value()._signal - 256 ); // value - 256) + fprintf(stderr,"[Odinagent.cc] -> last_packet_length: %i bytes\n", iter_tx.value()._len_pkt); + + fprintf(stderr,"[Odinagent.cc] -> total packets: %i\n", iter_tx.value()._packets); + fprintf(stderr,"[Odinagent.cc] -> avg_rate: %f Kbps\n", iter_tx.value()._avg_rate); + fprintf(stderr,"[Odinagent.cc] -> avg_signal: %f dBm\n", iter_tx.value()._avg_signal); + fprintf(stderr,"[Odinagent.cc] -> avg_len_pkt: %f bytes\n", iter_tx.value()._avg_len_pkt); + fprintf(stderr,"[Odinagent.cc] -> air_time: %f ms\n", iter_tx.value()._air_time); + + fprintf(stderr,"[Odinagent.cc] -> first heard: %d.%06d sec\n", (iter_tx.value()._time_first_packet).sec(), (iter_tx.value()._time_first_packet).subsec()); + fprintf(stderr,"[Odinagent.cc] -> last heard: %d.%06d sec\n", (iter_tx.value()._time_last_packet).sec(), (iter_tx.value()._time_last_packet).subsec()); + + // Calculate the time between the two timestamps: the hearing interval in which the statistics have been calculated + Timestamp interval_tx = iter_tx.value()._time_last_packet - iter_tx.value()._time_first_packet; + fprintf(stderr,"[Odinagent.cc] -> interval heard: %d.%06d sec\n", interval_tx.sec(), interval_tx.subsec()); + fprintf(stderr,"[Odinagent.cc]\n"); + } + + HashTable::const_iterator iter_rx = _rx_stats.find(it.key()); + if (iter_rx != _rx_stats.end()) { + fprintf(stderr,"[Odinagent.cc] Uplink (reception)\n"); + fprintf(stderr,"[Odinagent.cc] -> last_packet_rate: %i (%i kbps)\n", iter_rx.value()._rate,iter_rx.value()._rate * 500 ); + fprintf(stderr,"[Odinagent.cc] -> last_packet_noise: %i\n", (iter_rx.value()._noise)); + fprintf(stderr,"[Odinagent.cc] -> last_packet_signal: %i (%i dBm)\n", iter_rx.value()._signal, iter_rx.value()._signal - 256 ); // value - 256) + fprintf(stderr,"[Odinagent.cc] -> last_packet_length: %i bytes\n", iter_rx.value()._len_pkt); + + fprintf(stderr,"[Odinagent.cc] -> total packets: %i\n", iter_rx.value()._packets); + fprintf(stderr,"[Odinagent.cc] -> avg_rate: %f Kbps\n", iter_rx.value()._avg_rate); + fprintf(stderr,"[Odinagent.cc] -> avg_signal: %f dBm\n", iter_rx.value()._avg_signal); + fprintf(stderr,"[Odinagent.cc] -> avg_len_pkt: %f bytes\n", iter_rx.value()._avg_len_pkt); + fprintf(stderr,"[Odinagent.cc] -> air_time: %f ms\n", iter_rx.value()._air_time); + + fprintf(stderr,"[Odinagent.cc] -> first heard: %d.%06d sec\n", (iter_rx.value()._time_first_packet).sec(), (iter_rx.value()._time_first_packet).subsec()); + fprintf(stderr,"[Odinagent.cc] -> last heard: %d.%06d sec\n", (iter_rx.value()._time_last_packet).sec(), (iter_rx.value()._time_last_packet).subsec()); + + // Calculate the time between the two timestamps: the hearing interval in which the statistics have been calculated + Timestamp interval_rx = iter_rx.value()._time_last_packet - iter_rx.value()._time_first_packet; + fprintf(stderr,"[Odinagent.cc] -> interval heard: %d.%06d sec\n", interval_rx.sec(), interval_rx.subsec()); + fprintf(stderr,"[Odinagent.cc]\n"); } } } - if (_debug_level / 10 == 1) + if (_debug_level / 10 == 1) // demo mode. I print more visual information fprintf(stderr, "##################################################################\n\n"); } } @@ -2188,7 +2389,7 @@ cleanup_lvap (Timer *timer, void *data) iter.live(); iter++){ Timestamp now = Timestamp::now(); - Timestamp age = now - iter.value()._last_received; + Timestamp age = now - iter.value()._time_last_packet; if (age.sec() > THRESHOLD_OLD_STATS){ buf.push_back (iter.key()); @@ -2207,12 +2408,12 @@ cleanup_lvap (Timer *timer, void *data) } } - if (agent->_debug_level % 10 > 0) - fprintf(stderr,"\n[Odinagent.cc] Cleaning old info from stations not associated\n"); + if (agent->_debug_level % 10 > 0) + fprintf(stderr,"\n[Odinagent.cc] Cleaning old info from stations not associated\n"); - for (Vector::const_iterator iter = buf.begin(); iter != buf.end(); iter++){ + for (Vector::const_iterator iter = buf.begin(); iter != buf.end(); iter++) { - //If its our station we dont remove, we need the _last_received to see if its inactive or not + //If its our station we dont remove, we need the _time_last_packet to see if its inactive or not if(agent->_sta_mapping_table.find(*iter) != agent->_sta_mapping_table.end()) continue; @@ -2239,4 +2440,4 @@ void misc_thread(Timer *timer, void *data){ CLICK_ENDDECLS EXPORT_ELEMENT(OdinAgent) -ELEMENT_REQUIRES(userlevel) \ No newline at end of file +ELEMENT_REQUIRES(userlevel) diff --git a/src/odinagent.hh b/src/odinagent.hh index ef3944d..97b4f40 100644 --- a/src/odinagent.hh +++ b/src/odinagent.hh @@ -118,6 +118,7 @@ public: handler_num_slots, handler_add_vap, handler_set_vap, + handler_txstat, handler_rxstat, handler_remove_vap, handler_channel, @@ -132,15 +133,22 @@ public: handler_channel_switch_announcement, }; - // Rx-stats about stations + // Tx and Rx-stats about stations class StationStats { public: int _rate; int _noise; int _signal; + int _len_pkt; - int _packets; - Timestamp _last_received; + int _packets; //number of packets + double _avg_signal; //average value of the signal + double _avg_rate; //average rate of the packets + double _avg_len_pkt; //average length of the packets + double _air_time; //airtime consumed by this STA, calculated as 8 * _len_pkt / _rate + + Timestamp _time_first_packet; //timestamp of the first packet included in the statistics + Timestamp _time_last_packet; //timestamp of the last packet included in the statistics StationStats() { memset(this, 0, sizeof(*this)); @@ -159,9 +167,13 @@ public: double _m2; // for estimated variance int _signal_offset; - // Keep track of rx-statistics of stations from which - // we hear frames. Only keeping track of data frames for + // Keep track of tx-statistics of stations from which + // we send frames. Only keeping track of data frames for // now. + HashTable _tx_stats; + + // Keep track of rx-statistics of stations from which + // we hear frames. HashTable _rx_stats; int _interval_ms; // Beacon interval: common between all VAPs for now @@ -180,6 +192,7 @@ public: private: void compute_bssid_mask (); + void update_tx_stats(Packet *p); void update_rx_stats(Packet *p); EtherAddress _hw_mac_addr; class AvailableRates *_rtable; @@ -190,6 +203,9 @@ private: IPAddress _default_gw_addr; String _debugfs_string; String _ssid_agent_string; // stores the SSID of the agent + int _tx_rate; + int _tx_power; + int _hidden; };