The LANforge Scripting Cookbook provides a series of detailed examples of how to craft testing scripts for unattended/automated operation. Each example intends to give the reader a runnable test script and a better understanding of how to use the LANforge Scripting.
You do not need to operate scripts directly from the LANforge server, and this allows you to code scripts in your preferred text editing environment. Likewise, you do not need to run a copy of LANforge Server on your desktop. Scripts will create a plain-text connection to the LANforger server you specify.
Your desktop (or other computer) running CLI scripts needs to have a reliable (wired) connection to the management port of your LANforge server. If you are engaging in long running tests, you might consider running the scripts from the LANforge manager itself if your desktop machine needs to be powered off.
You can run CLI scripts from any Windows desktop as long as you have Perl installed. You can use ActiveState Perl or Perl from the Cygwin project. We also highly suggest installing PuTTY ssh client to access your LANforge server.
Please contact us at support@candelatech.com if you have any questions.
Before attempting the examples below, ensure that you have successfully followed these software installation guides:
It is also recommended that you back up your current running LANforge Server database so that you may safely return to your current operating state.
For instance:su - root cd /home/lanforge tar -cvzf my_db_backup.tar.gz DB
Traffic emulation may be run unattended and automated using Perl scripts provided with the LANforge Server. These scripts can be run from within the LANforge server or outside the LANforge Server (on a Windows or other desktop). The output of the scripts should be redirected into a text file for you to process the results. |
On most versions of windows, the LANforge Server installs scripts in
C:\Program Files (x86)\LANforge-Server\scripts
In the home directory for user LANforge:
/home/lanforge/scripts
$ ./lf_portmod.pl \ --manager 192.168.100.1 \ --card 3 \ --show_port
$ cd /home/lanforge/scripts $ ./lf_portmod.pl --help #...help appears... $ ./lf_portmod.pl --manager 192.168.100.1 --card 1 --show_port # ... displays port info
$ cd /home/lanforge/scripts $ ./lf_portmod.pl --manager 192.168.100.1 --card 3 --show_port # ... displays port info
wget "http://guest:guest@www.candelatech.com/private/downloads/r5.3.2/LANforgeServer-5.3.2_Linux-F21-x64.tar.gz"
$ cd ~/Documents/scripts
$ tar xf LANforgeServer*tar.gz
$ cp -r LANforgeServer-5.3.2/scripts/. ~/Documents/scripts/
To use your scripts, in your terminal, change directories to ~/Documents/scripts and they will operate similar to the above examples.
$ cd ~/Documents/scripts $ ./lf_portmod.pl --manager 192.168.100.1 --card 3 --show_port
C:> cd C:\Users\bob\Documents\scripts C:> perl .\lf_portmod.pl --manager 192.168.100.1 --card 3 --show_port --port_name eth1 --quiet 1
C:> perl .\lf_portmod.pl --manager 192.168.100.1 --card 3 --show_port --port_name eth1 --quiet 1 > results.txt C:> notepad.exe results.txt
You do not need to connect to your LANforge manager using ssh or VNC to manage connections or operate tests. In this cookbook, you will see an example of using the lf_firemod.pl script which can give you port information. This example will require a Windows desktop with Java, ActiveState Perl, the LANforge Server and the LANforge GUI installed. You will not need to start the LANforge server on your Windows computer, so no licenses will be required for operating on the Windows computer. This cookbook assumes connectivity between the Windows computer and a running LANforge manager computer. |
|
For more information see LANforge Server Installation
For more information see LANforge GUI Installation
Port statistics can be programatically monitored using the script lf_portmod.pl. This script can also reset ports, alter WiFi station settings, and pass arbitrary LANforge CLI commands directly to the LANforge manager. |
Ports of all kinds can be viewed with the lf_portmod.pl perl script. You can also do some limited manipulation of ports as well.
C:\> perl .\lf_portmod.pl --quiet 1 --manager jedtest --card 1 --port_name eth1 --show_port
perl .\lf_portmod.pl --manager jedtest --card 1 --quiet 1 --port_name eth1 --show_port "RxDrop,Rxp,IP"Produces: Consider that is a lot of text to type. If we want, we can reformat that command.
perl .\lf_portmod.pl --manager jedtest ^ --card 1 --quiet 1 --port_name eth1 ^ --show_port "RxDrop,Rxp,IP"Produces the same output:
lf_portmod.pl --load day_238This matches the same database name seen in the Status tab database dropdown.
lf_portmod.pl --manager 192.168.1.101 --card 1 --port_name eth2 --set_ifstate down
lf_portmod.pl --manager 192.168.1.101 --card 1 --port_name eth2 --cmd reset
lf_portmod.pl --manager 192.168.1.101 --cli_cmd "scan 1 1 sta0"
Creating connections in the LANforgeGUI implies creating endpoints. These endpoint entities are created with predictable names and are usually created in pairs. Understanding these naming conventions and how they are created is fundamental to your proficiency with creating connections with LANforge CLI scripts. |
Most examples in our cookbooks assume a dual-ended connection, also known as a cross-connect or abbreviated as CX.
Let's follow the creation of a Connection:
Using a terminal on the LANforge machine, we look at the /home/lanforge/DB/DFLT/endps.db file and inspect the commands issued that create that connection:
That's a lot of commands. We will point out what is particularly necessary when using our Perl scripts.
The connection we created above is named tutorial-cx. Two endpoints also have names, tutorial-cx-A and tutorial-cx-B. The A-side of a connection is always managed. A B-side endpoint may be unmanaged. When you write CLI scripts that create connections, name your endpoints using a similar convention.
We can use the lf_firemod.pl script to create endpoints and a cross connect in this order:
$ ./lf_firemod.pl --action create_endp --endp_name tutorial2-cx-A \ --speed 256000 --endp_type lf_tcp --port_name sta301 $ ./lf_firemod.pl --action create_endp --endp_name tutorial2-cx-B \ --speed 256000 --endp_type lf_tcp --port_name eth1 $ ./lf_firemod.pl --action create_cx --cx_name tutorial2-cx \ --cx_endps tutorial2-cx-A,tutorial2-cx-B
We can see the results of those script commands in our Layer-3 and L3 Endps tabs:
When a connection is first created, it is STOPPED. When you start it, it becomes RUNNING. When you set a connection to STOPPED, both endpoints immediately stop sending and recieving. That can have a consequence of leaving unacknowledged packets in flight. The safest way is to QUIECE the connection, which first stops the endpoints transmitting, waits a short time, and then stops the endpoints from recieving.
Normally, if you see one endpoint, it should only be a multicast endpoint. A single endpoint can be seen in these situations:
A single endpoint is not an illegal entity, but lonely endpoints can add confusion. If you find endpoints that do not match any existing connections, we suggest deleting them.
A Cross-Connect can be one-sided, that is, have one unmanaged endpoint. The A side endpoint is a LANforge managed port transmitting to another device that's not a LANforge machine. Some connection types create this style of endpoint pairs, like File-endpoints and Layer 4-7 connections.
Multicast endpoints are created differently both in the GUI and in the CLI scripting environment. This tutorial does not focus on multicast, but see the section Creating Endpoints section of Creating Connections with FIREMod Script and the chapter on WiFi Multicast Download.
Traffic emulation can be run unattended and using automated tools without use of the LANforgeGUI using Perl scripts provided with the LANforge Server. These scripts can be run from within the LANforge server or outside the LANforge Server (on a Windows desktop). The output of the scripts needs to be redirected into a text file for you to process the results. |
The lf_firemod.pl script has a lot of options because endpoints have a lot of features. Basic actions:
Creating a basic cross connect requires two endpoints, and each endpoint requires a port (network interface). Script options often begin by stating the manager, resource and action:
C:\> perl .\lf_firemod.pl --mgr 192.168.100.1 --resource 2 --action create_endp ...more options...
We use these parameters when creating and endpoint:
lf_firemod.pl --action create_endp \ --mgr 192.168.45.34 --mgr_port 4002 \ --endp_name web_1 --speed 154000 \ --endp_type tcp --port_name eth1 \ --quiet no
lf_firemod.pl --action create_endp \ --mgr 192.168.45.34 --mgr_port 4002 \ --endp_name mcast_xmit_1 --speed 154000 \ --endp_type mc_udp --mcast_addr 224.9.9.8 --mcast_port 9998 \ --rcv_mcast NO --port_name eth1 \ --min_pkt_sz 1072 --max_pkt_sz 1472 \ --use_csums NO --ttl 32 \ --quiet no --report_timer 1000
lf_firemod.pl --action create_endp \ --mgr 192.168.45.34 --mgr_port 4002 \ --endp_name web_1 --speed 154000 \ --endp_type tcp --port_name eth1 \ --quiet no --test_manager web_tm
By default, using the show_endp action shows all endpoints. It might be useful to place output like this right into a file or to immediate use grep to find the rows you want.
$ ./lf_firemod.pl --action show_endp --mgr cholla-f19 RSLT: 0 Cmd: 'nc_show_endp' FileEndp [e2#0-nfs-100] (NOT_RUNNING, WRITING, WRITE_RATE_BURSTY, CHECK_MOUNT, AUTO_MOUNT, UN_MOUNT, O_TRUNC) Shelf: 1, Card: 1 Port: 10 Endpoint: 1 Type: FILE_NFS Pattern: INCREASING MinWriteRate: 1544000bps MaxWriteRate: 0bps MinRead/WriteSz: 4096B MaxRead/WriteSz: 32768B MinReadRate: 1544000bps MaxReadRate: 1544000bps QuiesceAfterFiles: -1 NumFiles: 2 MinFileSize: 26214400B MaxFileSize: 26214400B Directory: AUTO Prefix: AUTO Volume: Server-Mount: 10.41.0.1:/tank/tmp Mount-Dir: AUTO Mount-Options: RptTimer: 1000ms RunningFor: 0s StopIn: 0s Quiesce: 3 LastRpt: 0.000 secs ago RealWriteRate: 0bps RealReadRate: 0bps RetryTimer: 1000ms InFailedIO: 0ms Buffers Read: Total: 0 Time: 0s Cur: 0 0/s Bytes Read: Total: 0 Time: 0s Cur: 0 0/s Files Read: Total: 0 Time: 0s Cur: 0 0/s Bytes Written: Total: 0 Time: 0s Cur: 0 0/s Buffers Written: Total: 0 Time: 0s Cur: 0 0/s Files Written: Total: 0 Time: 0s Cur: 0 0/s Read CRC Failed: Total: 0 Time: 0s Cur: 0 0/s FileEndp [e2#0-nfs-101] (NOT_RUNNING, WRITING, WRITE_RATE_BURSTY, CHECK_MOUNT, AUTO_MOUNT, UN_MOUNT, O_TRUNC) Shelf: 1, Card: 1 Port: 12 Endpoint: 2 Type: FILE_NFS Pattern: INCREASING MinWriteRate: 1544000bps MaxWriteRate: 0bps MinRead/WriteSz: 4096B MaxRead/WriteSz: 32768B MinReadRate: 1544000bps MaxReadRate: 1544000bps QuiesceAfterFiles: -1 NumFiles: 2 MinFileSize: 26214400B MaxFileSize: 26214400B Directory: AUTO Prefix: AUTO Volume: Server-Mount: 10.41.0.1:/tank/tmp Mount-Dir: AUTO Mount-Options: RptTimer: 1000ms RunningFor: 0s StopIn: 0s Quiesce: 3 LastRpt: 0.000 secs ago RealWriteRate: 0bps RealReadRate: 0bps RetryTimer: 1000ms InFailedIO: 0ms Buffers Read: Total: 0 Time: 0s Cur: 0 0/s Bytes Read: Total: 0 Time: 0s Cur: 0 0/s Files Read: Total: 0 Time: 0s Cur: 0 0/s Bytes Written: Total: 0 Time: 0s Cur: 0 0/s Buffers Written: Total: 0 Time: 0s Cur: 0 0/s Files Written: Total: 0 Time: 0s Cur: 0 0/s Read CRC Failed: Total: 0 Time: 0s Cur: 0 0/s
You can redirect all output into a file:
$ ./lf_firemod.pl --action show_endp --mgr cholla-f19 > /var/tmp/endp-stats.txt
It is possible to print out one-word attributes, such as MaxWriteRate tx_bps or rx_bps:
./lf_firemod.pl --mgr 127.0.0.1 --quiet 1 --action show_endp --endp_name cx_0-B --endp_vals tx_bps,rx_bps Rx Bytes: 99938104 Tx Bytes: 99993112
This is for changing the attributes of an endpoint, such as endpoint TX rate.
$ ./lf_firemod.pl --mgr cholla-f19 --action set_endp --endp_name cx_0-A --speed 2000000
This is pretty useful for getting transmit rate on ports during a connection while not having to use the lf_portmod script. If you do not specify --port_name, all ports will be listed.
$ ./lf_firemod.pl --action show_port --mgr cholla-f19 --port_name eth2#0 Shelf: 1, Card: 1, Port: 10 Type: MacVLAN Alias: Win32-Name: Win32-Desc: Parent/Peer: eth2 Rpt-Timer: 8000 CPU-Mask: 0 Current: UP LINK-UP TSO UFO GSO GRO PROBE_ERROR Supported: UP SEND_TO_SELF Partner: UP Advertising: 10bt-HD 10bt-FD 100bt-HD 100bt-FD 1000-FD TSO-ENABLED UFO-ENABLED GSO-ENABLED GRO-ENABLED IP: 10.41.0.10 MASK: 255.255.255.0 GW: 0.0.0.0 VID: 0 ResetState: COMPLETE DNS Servers: IPv6-Global: DELETED IPv6-Link: fe80::a00:27ff:fe09:183d/64 IPv6-Gateway: DELETED MAC: 08:00:27:09:18:3d DEV: eth2#0 MTU: 1500 TX Queue Len: 0 LastDHCP: 0ms Driver: macvlan Tx-Rate: 1000000Kbps Bus-Speed: 0/0 Bus-Width: 0/0 Bridge-Port-Cost: Ignore Prio: Ignore Aging: 0 DHCP-Client-ID: NONE DHCP-Vendor-ID: NONE pps_tx: 0 pps_rx: 0 bps_tx: 0 bps_rx: 0 Rxp: 5652 Txp: 21 Rxb: 1932984 Txb: 1826 RxERR: 0 TxERR: 0 RxDrop: 0 TxDrop: 0 Multi: 5652 Coll: 0 RxLenERR: 0 RxOverFlow: 0 RxCRC: 0 RxFrame: 0 RxFifo: 0 RxMissed: 0 TxAbort: 0 TxCarrier: 0 TxFifo: 0 TxHeartBeat: 0 TxWindow: 0 RxBytesLL: 2068632 TxBytesLL: 2330
This is the same as --show_port without the port_name option.
In case you wanted to pass a CLI command directly in. Below is an example of setting the TOS flag for an endpoint:
C:\> perl .\lf_firemod.pl --mgr 192.168.100.1 --action do_cmd \ --cmd "set_endp_tos cx_01-A LOWDELAY 10"
See the LANforge CLI User Guide for more info.
Remember to remove the cross connect before removing the endpoint.
$ ./lf_firemod.pl --action delete_endp --mgr cholla-f19 --endp_name cx-0-A
First you want to create two endpoints. You will add those endpoints to your cross connect. This example below shows all three steps:
$ ./lf_firemod.pl --action create_endp --mgr cholla-f19 --port_name eth2#0 \ --endp_name cx_0-A --speed 1000000 --endp_type tcp --min_pkt_sz 1462 --report_timer 1000 $ ./lf_firemod.pl --action create_endp --mgr cholla-f19 --port_name eth2#1 \ --endp_name cx_0-B --speed 1000000 --endp_type tcp --min_pkt_sz 1462 --report_timer 1000 $ ./lf_firemod.pl --action create_cx --mgr cholla-f19 --cx_name cx_0 \ --cx_endps cx_0-A,cx_0-B --report_timer 1000
Below we see the endpoints created: and the CX details screen:
This shows the cross connects and their enpoints:
$ ./lf_firemod.pl --action list_cx --mgr cholla-f19 CX cx_0, endpoint cx_0-A, endpoint cx_0-B
The output of this command begins with the basic stats for the CX and includes the statistics of each endpoint.
$ ./lf_firemod.pl --action show_endp --mgr cholla-f19 --endp_name cx_0-A
LANFORGE_TCP CX: cx_0 id: 12 type: LANFORGE_TCP DesiredState: UN_INITIALIZED RealState: STOPPED tx_endpoint: cx_0-A rx_endpoint: cx_0-B report_timer: 1.000000s TestMgr: default_tm Endpoint [cx_0-A] (NOT_RUNNING, FIXED_PLD_SIZE, RATE_BURSTY, IP_PORT_AUTO) Shelf: 1, Card: 1 Port: 10 Endpoint: 11 Type: LANFORGE_TCP Pattern: INCREASING MinTxRate: 1000000bps MaxTxRate: 1000000bps MinPktSize: 1462B MaxPktSize: 1462B DestMAC: 08:00:27:69:1a:3d DestIpAddr: 10.41.0.11 DestIpPort: 0 Quiesce: 3 SrcMAC: 08:00:27:09:18:3d SrcIp: 0.0.0.0 IpPort: 0-0 IpTOS: DONT-SET Priority: 0 Role: CONNECT RptTimer: 1000ms RunningFor: 0s StopIn: 0s Avg-Jitter: 0ms Latency: 0 -:0:- 0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) Pkt-Gaps: 0 -:0:- 0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) Last-Rpt: 0.000 secs ago RealTxRate: 0bps RealRxRate: 0bps TTL: 0 Conn-Timeout: 10000 FileName: SendBadCrc: 0 RcvBuf: 0 SndBuf: 0 CWND: 0 SND-MSS: 0 RxDrop%-SEQ: 0.0000 RxDrop%-CX: 0.0000 Conn-Timer: -1-0ms Conn-Pause: 0-0ms PktsToSend: 0 Multi-Conn: 0 Active-Connections: 0 Files-Played: 0 RunningInGroup: NONE Script-steps-completed: 0 Steps-Failed: 0 First-Rx: -1ms Mcast-Source: 0.0.0.0:0 Rx Pkts: Total: 0 Time: 60s Cur: 0 0/s Rx Pkts (On Wire): Total: 0 Time: 60s Cur: 0 0/s Rx Bytes: Total: 0 Time: 60s Cur: 0 0/s Rx Bytes (On Wire): Total: 0 Time: 60s Cur: 0 0/s Rx OOO Pkts: Total: 0 Time: 60s Cur: 0 0/s RX Wrong Dev: Total: 0 Time: 60s Cur: 0 0/s RX CRC Failed: Total: 0 Time: 60s Cur: 0 0/s RX Bit Errors: Total: 0 Time: 3s Cur: 0 0/s Rx Dropped Pkts: Total: 0 Time: 3s Cur: 0 0/s Cx Detected: 0 Rx Duplicate Pkts: Total: 0 Time: 60s Cur: 0 0/s Tx Pkts: Total: 0 Time: 60s Cur: 0 0/s Tx Pkts (On Wire): Total: 0 Time: 60s Cur: 0 0/s Tx Bytes: Total: 0 Time: 60s Cur: 0 0/s Tx Bytes (On Wire): Total: 0 Time: 3s Cur: 0 0/s Tx Failed Pkts: Total: 0 Time: 60s Cur: 0 0/s Tx Failed Bytes: Total: 0 Time: 60s Cur: 0 0/s Conn Established: Total: 0 Time: 30s Cur: 0 0/s TCP Retransmits: Total: 0 Time: 3s Cur: 0 0/s Conn Timeouts: Total: 0 Time: 30s Cur: 0 0/s Endpoint [cx_0-B] (NOT_RUNNING, FIXED_PLD_SIZE, RATE_BURSTY, IP_PORT_AUTO) Shelf: 1, Card: 1 Port: 12 Endpoint: 12 Type: LANFORGE_TCP Pattern: INCREASING MinTxRate: 1000000bps MaxTxRate: 1000000bps MinPktSize: 1462B MaxPktSize: 1462B DestMAC: 08:00:27:09:18:3d DestIpAddr: 10.41.0.10 DestIpPort: 0 Quiesce: 3 SrcMAC: 08:00:27:69:1a:3d SrcIp: 0.0.0.0 IpPort: 0-0 IpTOS: DONT-SET Priority: 0 Role: ACCEPT RptTimer: 1000ms RunningFor: 0s StopIn: 0s Avg-Jitter: 0ms Latency: 0 -:0:- 0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) Pkt-Gaps: 0 -:0:- 0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) Last-Rpt: 0.000 secs ago RealTxRate: 0bps RealRxRate: 0bps TTL: 0 Conn-Timeout: 10000 FileName: SendBadCrc: 0 RcvBuf: 0 SndBuf: 0 CWND: 0 SND-MSS: 0 RxDrop%-SEQ: 0.0000 RxDrop%-CX: 0.0000 Conn-Timer: -1-0ms Conn-Pause: 0-0ms PktsToSend: 0 Multi-Conn: 0 Active-Connections: 0 Files-Played: 0 RunningInGroup: NONE Script-steps-completed: 0 Steps-Failed: 0 First-Rx: -1ms Mcast-Source: 0.0.0.0:0 Rx Pkts: Total: 0 Time: 60s Cur: 0 0/s Rx Pkts (On Wire): Total: 0 Time: 60s Cur: 0 0/s Rx Bytes: Total: 0 Time: 60s Cur: 0 0/s Rx Bytes (On Wire): Total: 0 Time: 60s Cur: 0 0/s Rx OOO Pkts: Total: 0 Time: 60s Cur: 0 0/s RX Wrong Dev: Total: 0 Time: 60s Cur: 0 0/s RX CRC Failed: Total: 0 Time: 60s Cur: 0 0/s RX Bit Errors: Total: 0 Time: 3s Cur: 0 0/s Rx Dropped Pkts: Total: 0 Time: 3s Cur: 0 0/s Cx Detected: 0 Rx Duplicate Pkts: Total: 0 Time: 60s Cur: 0 0/s Tx Pkts: Total: 0 Time: 60s Cur: 0 0/s Tx Pkts (On Wire): Total: 0 Time: 60s Cur: 0 0/s Tx Bytes: Total: 0 Time: 60s Cur: 0 0/s Tx Bytes (On Wire): Total: 0 Time: 3s Cur: 0 0/s Tx Failed Pkts: Total: 0 Time: 60s Cur: 0 0/s Tx Failed Bytes: Total: 0 Time: 60s Cur: 0 0/s Conn Established: Total: 0 Time: 30s Cur: 0 0/s TCP Retransmits: Total: 0 Time: 3s Cur: 0 0/s Conn Timeouts: Total: 0 Time: 30s Cur: 0 0/s
Remember to delete a cross connect before you delete its endpoints.
$ ./lf_firemod.pl --action delete_cx --mgr cholla-f19 --cx_name cx_0
By default, cross connects are created in the default_tm test manager. To control them, you want to specify default_tm in your set_cx_state CLI command.
./lf_firemod.pl --mgr 127.0.0.1 --quiet 0 --action do_cmd --cmd "set_cx_state default_tm cx_0 RUNNING"
The format of the command is specified in the CLI User Guide: set_cx_state. Possible CX states include:
$ ./lf_firemod.pl --mgr cholla-f19 --action stop_endp --endp_name cx_0-A
There are different options for creating multicast endpoints.
$ ./lf_firemod.pl --action create_endp --endp_name mcast_xmit_1 \ --endp_type mc_udp --speed 154000 \ --mcast_addr 224.9.9.8 --mcast_port 9998 \ --rcv_mcast NO --port_name eth1 \ --min_pkt_sz 1072 --max_pkt_sz 1472 \ --use_csums NO --ttl 32
This is not a default script option, so we use the do_cmd action:
C:\> perl .\lf_firemod.pl --mgr 192.168.100.1 --action do_cmd "set_sec_ip 1 1 eth1 10.26.0.20-250/24"
See the LANforge CLI User Guide for more info.
This is the output of lf_firemod.pl --help:
./lf_firemod.pl --action { create_endp | show_endp | set_endp | show_port | list_ports | do_cmd | start_endp | stop_endp | delete_endp | create_cx | list_cx | show_cx | delete_cx } ] [--endp_vals {key,key,key,key}] # show_endp output can be narrowed with key-value arguments # Examples: # --action show_endp --endp_vals MinTxRate,DestMAC,Avg-Jitter # Not available: Latency,Pkt-Gaps, or rows below steps-failed. # Special Keys: # --endp_vals tx_bps (Tx Bytes) # --endp_vals rx_bps (Rx Bytes) [--mgr {host-name | IP}] [--mgr_port {ip port}] [--cmd {lf-cli-command text}] [--endp_name {name}] [--port_name {name}] [--resource {number}] [--speed {speed in bps}] [--tos { DONT-SET | LOWDELAY | THROUGHPUT | RELIABILITY | LOWCOST },{priority}] [--max_speed {speed in bps}] [--quiet { yes | no }] [--endp_type { lf_udp | lf_udp6 | lf_tcp | lf_tcp6 | mc_udp | mc_udp6 }] [--mcast_addr {multicast address, for example: 224.4.5.6}] [--mcast_port {multicast port number}] [--min_pkt_sz {minimum payload size in bytes}] [--max_pkt_sz {maximum payload size in bytes}] [--rcv_mcast { yes (receiver) | no (transmitter) }] [--use_csums { yes | no, should we checksum the payload }] [--ttl {time-to-live}] [--report_timer {miliseconds}] [--cx_name {connection name}] [--cx_endps {endp1},{endp2}] [--test_mgr {default_tm|all|other-tm-name}] Example: ./lf_firemod.pl --action set_endp --endp_name udp1-A --speed 154000 ./lf_firemod.pl --action create_endp --endp_name mcast_xmit_1 --speed 154000 \ --endp_type mc_udp --mcast_addr 224.9.9.8 --mcast_port 9998 \ --rcv_mcast NO --port_name eth1 \ --min_pkt_sz 1072 --max_pkt_sz 1472 \ --use_csums NO --ttl 32 \ --quiet no --report_timer 1000 ./lf_firemod.pl --action create_endp --endp_name bc1 --speed 256000 \ --endp_type lf_tcp --tos THROUGHPUT,100 --port_name rd0#1 ./lf_firemod.pl --action list_cx --test_mgr all --cx_name all ./lf_firemod.pl --action create_cx --cx_name L301 \ --cx_endps ep_rd0a,ep_rd1a --report_timer 1000
Layer-3 endpoints can manipulate their own transmission parameters using a variety of internal scripts, known as Endpoint Scripts. Using the lf_endp_script.pl CLI script, you can operate those internal endpoints behaviours. |
This cookbook talks about Endpoint Scripts and CLI scripts at the same time. In this chapter, if the term script is used, assume Endpoint Script. Additionally, the terms operating and running can also be confusing. To keep the activities distinct, a LANforge user will operate a CLI script from a terminal. The LANforge server will run the Endpoint Script. A CLI script is a user-space perl script that issues CLI commands to a LANforge server. A CLI command is an instruction obeyed by the LANforge server.
There are a number of subsystems running while we operate an automated Endpoint Script, so let's review them:
We recommend starting your first script off by the LANforge GUI to save an endpoint with an Endpoint Script. Next, inspect the LANforge database on the server for the script parameters. Take those parameters and adapt them to the operator's CLI script.
set_script tutorial-cx-A bunny-script 37120 ScriptHunt '5000 5000 100000,20000,100000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 1472 0,100,300,500,550,600,650,700,750,850,900,950 NONE' ALL 20
C:\> .\lf_endp_script.pl --mgr jedtest --resource 1 ^ --action set_script --script_type Hunt --script_name bunny-script ^ --endp_name tutorial-CX-A -loops 1 --flags 37120 ^ --private "5000 5000 100000,20000,100000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 1472 0,100,300,500,550,600,650,700,750,850,900,950 NONE"
In a Linux terminal, we can use double
$ ./lf_endp_script.pl --mgr jedtest --resource 1 \ --action set_script --script_type Hunt --script_name bunny-script \ --endp_name tutorial-CX-A -loops 1 --flags 37120 \ --private '5000 5000 100000,20000,100000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 1472 0,100,300,500,550,600,650,700,750,850,900,950 NONE'
lf_endp_script --mgr jedtest --resource 1 --action start_cx --cx_name tutorial-CX
lf_endp_script --mgr jedtest --resource 1 --action quiesce_cx --cx_name tutorial-CX
We could also use action stop_cx to immediately stop the connection.
lf_endp_script.pl --action show_report --endp_name tutorial-CX-A --quiet no
Or to save it to a text file:
lf_endp_script.pl --action show_report --endp_name tutorial-CX-A --quiet no > /home/lanforge/Documents/report.txt
lf_endp_script.pl --action remove_script --endp_name tutorial-CX-A
We have covered creating endpoints in earlier cookbooks. The perl script lf_endp_script.pl was created to modify endpoints and operate their Endpoint Scripts. That script is using the set_script CLI command (documented here). A call to it looks like:
set_script tutorial-cx-A bunny-script 37120 ScriptHunt '...' ALL 20
That vague '...' section is the private parameter which is a parameter list each script type requires. The private parameter combines a series of constraints (sub-parameters). For the ScriptHunt, we might use:
run_duration pause_duration constraints payload_sizes_a payload_sizes_b attenuations attenuator 5000 | | | | | | 5000 | | | | | 100000,20000,100000,20,56000,30000,1,100000 | | | | | 60,128,256,512,1024,1280,1460,1472,1514 | | | | 1472 | | | 0,100,300,500,550,600,650,700,750,850,900,950 | v 1.1.14 drops,jitter_us,latency_us,max_steps,start_rate,accuracy,is_bps,max_tx_slowdown
Accuracy is also Threshold, max_tx_slowdown is also Underrun. The result is a very long line that has to be surrounded the the CLI level by one pair of single quotes:
'5000 5000 100000,20000,100000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 1472 0,100,300,500,550,600,650,700,750,850,900,950 NONE'
Automated wireless traffic is possible using the lf_associate_ap.pl script. This script can be run within the LANforge server or outside the LANforge Server (on a windows desktop). The output of the script should be redirected to a text file if you want to review the resuts. Use this file in conjunction with the lf_firemod.pl script to create traffic. Requires a LANforge CT520 (or better) system and an access point. |
The lf_assocatiate_ap.pl script has many options, but here are the basic actions:
perl .\lf_associate_ap.pl --resource 1 --resource 1 --mgr jedtest ^ --action step1 --radio wiphy0 --ssid jedtest ^ --first_sta sta100 --num_stations 1 --duration 20 ^ --first_ip DHCP --upstream eth1 --security wpa2 --passphrase jedtest1
./lf_associate_ap.pl --resource 1 --mgr localhost \ --action step1 --radio wiphy0 --ssid jedtest \ --first_sta sta100 --num_stations 1 --duration 20 \ --first_ip DHCP --upstream eth1 --security wpa2 --passphrase jedtest1
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 20 \
--first_ip DHCP --upstream eth1 --security wpa2 --passphrase jedtest1
./lf_associate_ap.pl --resource 1 --mgr localhost \ --action step1 --radio wiphy0 --ssid jedtest \ --first_sta sta100 --num_stations 10 --duration 120 \ --first_ip DHCP --upstream eth1 --security wpa2 --passphrase jedtest1 \ --cxtype tcp --bps-min 30Mpbs \ --bps-max 450Mbps
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 120 \
--first_ip DHCP --upstream eth1 --security wpa2 --passphrase jedtest1 \
--cxtype tcp --bps-min 30Mpbs --bps-max 450Mbps &> report.txt
$ ./lf_associate_ap.pl --resource 1 --mgr localhost \ --action step1 --radio wiphy0 --ssid jedtest \ --first_sta sta100 --num_stations 10 --duration 120 \ --first_ip DHCP --upstream eth1 --security wpa2 --passphrase jedtest1 \ --cxtype udp --bps-min 450Mpbs \ --bps-max SAME &> report.txt $ more report.txt
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 120 \
--first_ip DHCP --upstream eth1 --security open
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 120 \
--first_ip DHCP --upstream eth1 --security open \
--wifi_mode abg
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 120 \
--first_ip DHCP --upstream eth1 --security open \
--db_preload day_236
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 120 \
--first_ip DHCP --upstream eth1 --security open \
--db_preload day_236 --db_save station_results
./lf_associate_ap.pl --resource 1 --mgr localhost \
--action step1 --radio wiphy0 --ssid jedtest \
--first_sta sta100 --num_stations 10 --duration 120 \
--first_ip DHCP --upstream eth1 --security open \
--db_preload day_236 --db_save station_results --db_postload EMPTY
We can have a series of stations associate and unassociate over and over. This can be quite a bit of exercise for an AP. Below is a command that tests five clients connecting.
./lf_associate_ap.pl --mgr jedtest --action step2 \ --ssid jedtest --first_sta sta100 --first_ip DHCP \ --num_stations 10 --security wpa2 --passphrase jedtest1This will create set of ten stations bring them up and then take them down.
These might have been update since publication, please check --help output for your version of the script.
./lf_associate_ap.pl [--mgr {host-name | IP}] [--mgr_port {ip port}] # use if on non-default management port [--resource {resource}] # use if multiple lanforge systems; defaults to 1 [--quiet { yes | no }] # debug output; -q ## AP selection [--radio {name}] # e.g. wiphy2 [--ssid {ssid}] # e.g. jedtest [--security {open|wep|wpa|wpa2}] # station authentication type [--passphrase {...}] # implies wpa2 if --security not set [--wifi_mode {a|abg|abgn|abgnAC|an|anAC|b|bg|bgn|g}] ## station configuration [--num_stations {10}] [--first_sta {sta100}] [--first_ip {DHCP |ip address}] [--netmask {255.255.0.0}] ## connection configuration [--cxtype {tcp/tcp6/udp/udp6}] # use a tcp/udp connection, default tcp [--upstream {name|eth1}] # could be AP or could be port on LANforge # connected to WAN side of AP [--bps-min {10000000}] # minimum tx bps [--bps-max {SAME|bps-value}] # maximum tx bps, use SAME or omit for SAME [--duration {30}] # connection duration, seconds, default 60 [--poll-time {5}] # nap time between connection displays [--action {step1,step2}] # step1: creates [num_stations] stations and L3 connections # step2: does bringup test [--traffic_type {separate|concurrent}] # for step1: separate does download then upload # concurrent does upload and download at same time [--db_preload {scenario name}] # load this database before creating stations # option intended as a cleanup step [--db_save {name}] # save the state of this test scenario after running the # connections, before --db_postload [--db_postload {scenario name}] # load this database after running connections, # option intended as a cleanup step
Programatically creating LANforge virtual stations requires using the add_sta command. If you already have a station and need to change the SSID, you still use the add_sta command. |
The general sequence of commands is:
cur_flags=0x1 interest_flags=0x800002
cur_flags=0x0 interest_flags=0x800002
We can create a station using this script command:
./lf_associate_ap.pl --action step2 --mgr jedtest \ --resource 1 --radio wiphy0 \ --ssid jedtest --first_sta sta100 \ --num_stations 1 --first_ip=DHCP \ --wifi_mode abgn --security wpa2 \ --passphrase jedtest1 --quiet=0
The format of the add_sta command is listed in the CLI User's Guide. When we watch the debug output of the lf_associate_ap script, we see this add_sta command executed:
'add_sta' '1' '1' 'wiphy0' 'sta100' '1024' 'jedtest' 'NA' 'jedtest1' 'AUTO' 'NA' '00:E3:F7:91:4A:1A' '5' 'NA' 'NA' 'NA' 'NA' 'NA' '1024' 'NA' 'NA' 'NA' 'NA'
Looking at an example in the lf_associate_ap.pl script we see it being formatted here:
my $sta1_cmd = fmt_vsta_cmd($::resource, $::sta_wiphy, $sta_name, "$flags", "$::ssid", "$::passphrase", $mac_addr, "$flagsmask", $wifi_m); doCmd($sta1_cmd);
We format the parameters:
return fmt_cmd("add_sta", 1, $resource, $sta_wiphy, $sta_name, "$flags", "$ssid", "NA", "$key", $ap, $cfg_file, $mac, $mode, $rate, $amsdu, $ampdu_factor, $ampdu_density, $sta_br_id, "$flags_mask" );
Creating a series of scripts using the lf_associate_ap.pl script is not adequate for negotiating a captive-portal environment, that script does not set the POST_IFUP parameter for the station. However, stations can be modified to gain that field. |
Creating a station that negotiates a Captive Portal environment requires the POST_IFUP field to name a script. (Usually a portal-bot.pl script.) We can assign that port parameter with the set_wifi_extra2 command. At the time of this writing, there are no perl scripts using this CLI command, but I will show an example here:
set_wifi_extra2, 1, # resource number sta100, # port name 0, # flush-to-kernel NA, # ignore probe NA, # ignore auth NA, # ignore assoc NA, # ignore_reassoc NA, # corrupt_gtk_rekey_mic NA, # radius_ip NA, # radius_port NA, # freq_24 NA, # freq_5 # post_ifup_script './portal-bot.pl --bot bp.pm --user "username" --pass "secret" --start_url "http://www.google.com/" --ap_url "http://localhost/" --login_form "login.php" --login_action "login.php" --logout_form "logout.php"', NA # ocsp
The above command would never actually be formatted in in the way it appears above. It would all appear on one line without commentds.
In a perl script, the command could be formatted like:
my $cmd = fmt_cmd("set_wifi_extra2", 1, "sta100", # port name 0, # flush-to-kernel "NA", # ignore probe "NA", # ignore auth "NA", # ignore assoc "NA", # ignore_reassoc "NA", # corrupt_gtk_rekey_mic "NA", # radius_ip "NA", # radius_port "NA", # freq_24 "NA", # freq_5 qq(./portal-bot.pl --bot bp.pm ) # post_ifup_script .qq(--user "username" --pass "secret" ) .qq(--start_url "http://www.google.com/" ) .qq(--ap_url "http://localhost/" ) .qq(--login_form "login.php" ) .qq(--login_action "login.php" ) .qq(--logout_form "logout.php"), "NA" # ocsp );
Playing back a series of WiFi attenuation levels using the attenuator_series.pl and a CSV file of attenuations make it possible to emulate the motion of a station (or stations) moving among a series APs. Or it could emulate interference in a crowd of moving people. Requires a LANforge CT703 (or better) and a LANforge CT520 (or better) system, and an access point. |
Our LANforge manager (resource 1.1) has an attenuator serial number 3 (resource 1.1.3) connected to the Device Under Test. The attenuator will be 1.1.1.3. There will be station sta100 on LANforge resource 1 and AP vap0 on LANforge resource 2. Cables connect the radios to the the attenator. The radios are configured in 1x1 mode. The corresponding channel on the attenuator is 1.1.3.0
[See LANforge Entity IDs for more on numbering.]
Let's script it with a simple data file: /home/lanforge/atten_test1.csv
channels,1.1.3.0 delay,5000 attenuate,250 attenuate,320 attenuate,450 attenuate,520 attenuate,820
We run the script in our terminal:
$ cd /home/lanforge/scripts $ ./attenuate_series.pl -f ../atten_test1.csv
Watching a Layer-3 connection in the Dynamic Display, we will see a dip, rise and dip at 10 second intervals.
Next we cable up the second channel (1.1.3.1). We can update the csv test file, by adding a new column for the channel.
channels,1.1.3.0,1.1.3.1 delay,5000 attenuate,250,250 attenuate,320,250 attenuate,450,250 attenuate,520,520 attenuate,820,820
We can run the same command and watch the dynamic reports window to see a similar graph.
The first radio on each LANforge is connected in 2x2 mode to both attenuators. This example is drawn to illustrate how you design the connection of your channels independently of their radios. Obviously, you don't need two attenuators for this scenario. However, if you had a CT523 with three radios and want to perform 2x2 testing with three client radios, it is possible to do so with only two CT703 attenuators.
We change the data file to specify the first channel on attenuator 14 (1.1.14.0): /home/lanforge/atten_test3.csv
channels,1.1.3.0,1.1.14.0 delay,5000 attenuate,250,250 attenuate,320,320 attenuate,450,450 attenuate,520,520 attenuate,820,820
We can run the script once in our terminal:
$ cd /home/lanforge/scripts $ ./attenuate_series.pl -f ../atten_test3.csv
Watching the port signal in the dynamic display we will see a rise and dip at 10 second intervals.
There is no different in attenuator control whether you have one radio in 3x3 or three radios in 1x1 to control. If you are testing multiple radios, you will be monitoring their RX Signal in the dynamic report.
Editing the test data file with a basic spreadsheet program than can save to CSV format is possible. You will want to save with comma format, without double-quoting the cells. These directives are converted to lower-case, so you can type them in UPPER-CASE or Mixed-Case if necessary.
The format of the CSV file allows you to specify many options that might also be specified on the command line.
attenuate,250 attenuate,@+50Results in the channel at 30.0dB. Spreadsheets often omit signed values when saving, so @+ will force a text type cell.
attenuate,300 attenuate,@-50Results in the channel at 25.0dB. Spreadsheets often omit signed values when saving, so @- will force a text type cell.
attenuate,500,400 attenuate,=B1+50,=C1-50, # results in 550, 350 attenuate,=B2+5,=C2-5, # fails: B2 and C2 were formulas.This feature is unlikely to be as useful as it sounds, because pasting a column of forumae will be pretty useless, since a spreadsheet processes them recursively. Also, most spreadsheets saved to CSV typically don't save formulae by default, you probably will get the computed values in your CSV file.
The attenuate_series.pl script uses these arguments. They support long and short argument switch names:
-m --mgr LANforge manager host, like localhost or 192.168.101.1 -f --file CSV file with attenuation data -d --delay Override of DELAY variable, milliseconds between applying rows -l --loop Repeat indefinitely -c --channel Override of channels variable, eg: 1.2.3.1,2.3.4.3 -i --min --minimum Set minimum attenuation value (not lower than zero) -x --max --maximum Set maximum attenuation value (not higher than 955) -n --dry --dryrun --dry_run Do not apply attenuation, just parse file, ignore nap times
This CSV shows a working example that gives warnings.
Here's the Attenuators tab used for the examples:
Here are options used for the open dialog in LibreOffice Calc:
Here are the options used for the save dialog in LibreOffice Calc:
Every port, radio, virtual port, endpoint and connection in LANforge has an ID known as an EID. These are an internal notation that expresses the hierarchy of the physical and virtual objects managed by LANforge realm. |
Entity IDs (EIDs) are a dotted-decimal phrase. It expresses the Shelf, Resource Number, Port or Connection number, and if it is an endpoint, it gains a fourth decimal. An example:
1.2.8.4 : EID 1 : shelf 2 : resource 8 : port 4 : endpoint
For now, assume the shelf number will always be 1. The Resource number will refer to the LANforge machine ID as reported on the Status tab. The Port ID is only unique within a LANforge machine. The Port ID also refers to hardware in a machine: radios get a third decimal. The fourth decimal refers to either endpoints or connections.
While some items with port numbers, notably radios and ports, do not generate traffic. Endpoints generate traffic, and typically endpoints are transmitting to an opposite endpoint. The exception to this are multicast endpoints.
From the dotted-decimal perspective:
The formatting of the decimals might or might not be zero-padded. The picture below should convey how a connection (Layer 3) relates to two endpoints, and two ports:
The exception is connections. Connections are numbered outside of this hierarchy.
Usually not, for these reasons:
In scripts, it is legal to reference port numbers, but not advised to store them between sessions. If you reference an EID, it should be from within your present LANforge session. If your resources tend to disappear off the network and return (you had a machine reboot) those EIDs are not guaranteed to return.
If you look into the saved scenarios (in /home/lanforge/DB/DFLT) you will notice that ports, endpoints, and connections are refered to by name. Event though in the CLI Users's guide, where it states port number, use names in your scripts:
CMD | SHELF | | RESOURCE | | | PORT | | | | set_port 1 1 eth1 10.26.1.2 255.255.255.0 10.26.1.1 ....
For more information see Step 2: LANforge Manager
For more information see Ports (Interfaces)
For more information see Layer-3 Cross-Connects (FIRE)
For more information see Layer 4-7
For more information see Resources (Data Generator Machines)
For more information see Tab Display Preferences
For more information see Netsmith: Virtual Network Configurator
For more information see Station Creation : Step 1
For more information see Scripting a Station in the GUI (Step 3)
For more information see Creating a MAC-VLAN in the GUI(Step 3)
For more information see Scripting a MAC-VLAN in the GUI
For more information see Creating a Bridge in Netsmith (Step 2)
For more information see Scripting the GUI to create a Bridge (Step 6)
For more information see Scripting the GUI to create a VAP (Step 7)
For more information see Scripting the GUI to create a Monitor
For more information see How to Create and Modify Cross-Connects & Cross-Connect Information
For more information see Interpreting the Layer-3 Endps tab: Layer-3 Cross Connect Endpoints & Batch-Creating Cross-Connects
For more information see Scripting a Layer-3 Cross Connect (Step 8)
For more information see Creating and Modifying Layer 4-7 Endpoints, L4 Endpoint Information, Batch-Create Layer 4-7 Endpoints
For more information see Layer 4-7 Endpoint Display
For more information see Setting up a Simple HTTP Get/Download in the GUI
For more information see Scripting the GUI to create Layer 4-7 traffic (Step 9)
Updated 2019-11-21:New features in 5.4.1.
Updated 2018-07-24:New features in 5.3.8. The LANforge GUI (as of release 5.3.6) can be configured to start an embedded web server that can provide data about ports and layer-3 connections. This service can be queried with with any browser or AJAX connection. We're going to increasingly refer to it as the LANforge client. This feature provides these benefits:
Present and potential drawbacks of the JSON feature:
|
The LANforge GUI is started using a script (lfclient.bash or lfclient.bat). From a terminal, we call that script with the -httpd switch. By default the GUI will listen on port 8080:
$ cd /home/lanforge $ ./lfclient.bash -httpd
You can specify the port to listen on:
$ ./lfclient.bash -httpd 3210
You can run the client headless with the -daemon switch as well:
$ ./lfclient.bash -httpd -daemon
There is a setting in the 5.3.8 Control→Preferences menu for setting a minimized mode and the HTTP port number as well.
From the terminal we can query the port to find a basic message from the GUI:
$ curl -sq http://localhost:8080/
This first page (/) will give you a JSON list of the resource URLs available. Most URLs will provide JSON as their default content type. Notably, /help defaults to HTML.
By default, most URLs will treat a default Accept: */* header as text/html. Compare the two techniques below:
$ curl -sqv -H 'Accept: application/json' http://localhost:8080/resource/1/1 {"handler":"candela.lanforge.HttpResource$JsonResponse","uri":"resource","candela.lanforge.HttpResource":{"duration":"27"},"resources":[{"1.1":{"_links":"/resource/1/1","entity id":"NA","hostname":"idtest.candelatech.com"}},{"1.2":{"_links":"/resource/1/2","entity id":"NA","hostname":"hedtest"}},{"1.3":{"_links":"/resource/1/3","entity id":"NA","hostname":"ct524-debbie"}},{"1.4":{"_links":"/resource/1/4","entity id":"NA","hostname":"jed-apu2-a"}},{"1.5":{"_links":"/resource/1/5","entity id":"NA","hostname":"jed-apu2-b"}},{"1.6":{"_links":"/resource/1/6","entity id":"NA","hostname":"ct524-emily"}},{"1.7":{"_links":"/resource/1/7","entity id":"NA","hostname":"ct524-freya"}},{"1.8":{"_links":"/resource/1/8","entity id":"NA","hostname":"ct524-genia"}}]}
Clearly, the JSON output is difficult to read. We cover formatting output below.
Most of the queries to the client will return JSON by default. The notable exception is the /help URL. To get HTML output in the terminal, you have to specify Accept: text/html to curl:
$ curl -sqv -H 'Accept: text/html' http://localhost:8080/port/1/1/1 <!DOCTYPE html> <html> <head><title>/port</title> </head> <body> <table border='1'><thead><tr><th>EID</th><th>AP</th><th>Activity</th><th>Channel</th><th>Device</th><th>Down</th><th>IP</th><th>Parent Dev</th><th>Phantom</th><th>Port</th><th>SSID</th></tr></thead> <tbody> <tr><td>1.1.1</td><td></td><td>0.0</td><td></td><td>eth1</td><td>false</td><td>0.0.0.0</td><td></td><td>false</td><td>1.1.01</td><td></td></tr> </table><hr /> </body> </html>
JSON formatted text is pretty difficult to read, there are a few different utilities that can help you look at it: jq, json_pp, json_reformat, tidy, xmllint, yajl and jsonlint.
On Fedora, install:
$ sudo dnf install -y jq perl-JSON-PP tidy libxml2 yajl
On Ubuntu, install:
$ sudo apt install -y jq libjson-pp-perl perltidy xmllint libxml2-utils yajl-tools
Now we can perform a query:
$ curl -sq /port/1/1/1 { "candela.lanforge.HttpPort" : { "duration" : "1" }, "handler" : "candela.lanforge.HttpPort$JsonResponse", "interface" : { "stuff":... }, "uri" : "port/:shelf_id/:resource_id/:port_id" }
Notice that the URI object list paths with colon-tagged positions in them, e.g.: /cli-form/:cmd. These are interpreted as URL parameters and not query string parameters, they cannot be moved into the query string.
To save you typing, you might want to add this function to your .bash_aliases file:
function Json() { curl -sqv -H 'Accept: application/json' "http://localhost:8080${@}" \ | json_reformat | less }
Then you can make your calls this way:
$ Json /port/1/1/1
We can view a URL in a browser as well:
You can both view and stream event data. Querying events and alerts are both quite similar:
$ Json /events { "handler" : "candela.lanforge.HttpEvents$FixedJsonResponder", "events" : [ { "2249259" : { "event" : "Connect", "_links" : "/events/2249259", "entity id" : "NA" } }, ....
A busy LANforge system will generate hundreds of thousands of events. Only the last few thousand can be recalled.
You can inspect a singular event:
$ Json /events/2249259 { "handler": "candela.lanforge.HttpEvents$FixedJsonResponder", "uri": "events/:event_id", "candela.lanforge.HttpEvents": { "duration": "0" }, "event": { "eid": "1.3.21", "entity id": "NA", "event": "Connect", "event description": "sta3106 (phy #1): connected to 00:0e:8e:d5:fa:e6", "id": "2249259", "name": "sta3106", "priority": " Info", "time-stamp": "2018-07-24 14:39:33.776", "type": "Port" } }
We can view /alerts similarly.
$ Json /alerts/92 { "handler" : "candela.lanforge.HttpEvents$FixedJsonResponder", "uri" : "alerts/:event_id", "alert" : { "name" : "wlan0", "time-stamp" : "2018-07-02 16:23:30.880", "entity id" : "NA", "id" : "92", "eid" : "1.1.5", "event description" : "Port wlan0 has no WiFi SSID Configured.", "event" : "WiFi-Config", "priority" : " Warning", "type" : "Port" }, "candela.lanforge.HttpEvents" : { "duration" : "1" } }
Continually polling the /events URL is not as effective as streaming a websocket providing the same data. We need a web socket client. Websockets are built into modern browsers and there are python and perl utilities for the job as well. An easy to use python client is wsdump.
There is a useful python utility called wsdump (or wsdump.py). Try to install the python-websocket package to get it. There are many similar matches, but there is not one dedicated package that provides it. On Fedora:
root@fedora$ dnf whatprovides `which wsdump` root@fedora$ dnf install -y python3-websocket-client
root@ubuntu$ ls -l /usr/bin/wsdump /usr/bin/wsdump → /etc/alternatives/wsdump root@ubuntu$ ls -l /etc/alternatives/wsdump /etc/alternatives/wsdump → /usr/bin/python2-wsdump root@ubuntu$ dpkg-query -S /usr/bin/python2-wsdump python-websocket: /usr/bin/python2-wsdump root@ubuntu$ sudo apt install python-websocket
You might need to install pip, and that might be in the python3-pip package. Then you can install via:
$ sudo apt install python-pip # or sudo dnf install python-pip $ sudo pip install --upgrade pip $ pip search websocket $ sudo pip install websocket-client
Here's an example of wsdump below. Don't forget you are now using h the ws:// schema and not the http:// schema!
$ /usr/bin/wsdump ws://localhost:8081/
It might take a few second to start showing results if your system is not very active. You should be able to prompt output by executing this message in the Messages tab: gossip hi ben!
You can also use a web page to follow events because websockets are built into modern browsers. This is a screenshot of the
$ Json /shelf/1 { "handler": "candela.lanforge.HttpResource$JsonResponse", "uri": "shelf/:shelf_id", "candela.lanforge.HttpResource": { "duration": "0" }, "resources": [ { "1.1": { "_links": "/resource/1/1", "hostname": "idtest.candelatech.com" } }, { "1.2": { "_links": "/resource/1/2", "hostname": "hedtest" } } ] }
The /resource URL provides a digest of ports available at the requested resource.
$ Json /resource/1/1 { "handler" : "candela.lanforge.HttpResource$JsonResponse", "resource" : { "free swap" : 526332, "free mem" : 4634228, "load" : 0.4, "bps-rx-3s" : 7850, "sw version" : " 5.3.8 64bit", "entity id" : "NA", "tx bytes" : 40533976395, "phantom" : false, "eid" : "1.1", "hostname" : "idtest.candelatech.com", "hw version" : "Linux/x86-64", "mem" : 8057280, "cpu" : "Intel(R) Core(TM) i7-3555LE CPU (2137Mhz)(x4)", "max staged" : 50, "ctrl-ip" : "192.168.100.41", "ports" : "0 1 2 3 4 5 6 7 8 9 10 11 12 ", "gps" : "0.0N 0.0E 0m", "max if-up" : 15, "bps-tx-3s" : 1753832, "cli-port" : "4003", "sta up" : 12, "shelf" : "1", "rx bytes" : 606510139, "ctrl-port" : "4004", "swap" : 526332 }, "uri" : "resource/:shelf_id/:resource_id", "candela.lanforge.HttpResource" : { "duration" : "1" } }
$ Json /port/1/5/list { "handler" : "candela.lanforge.HttpPort$JsonResponse", "uri" : "port/:shelf_id/:resource_id/:port_id", "interfaces" : [ { "1.5.b5000" : { "entity id" : "NA", "_links" : "/port/1/5/7", "alias" : "b5000" } }, { "1.5.eth0" : { "alias" : "eth0", "_links" : "/port/1/5/0", "entity id" : "NA" } }, ... ], "candela.lanforge.HttpPort" : { "duration" : "2" } }
We can query multiple ports at a time by their number or their name by placing a comma between the specifiers. Additionally, we can query for just the fields we desire. All field names are lower-case: ?fields=tx+crr,rx+fifo.
$ Json '/port/1/5/wiphy0,wiphy1?fields=device,phantom,tx+bytes,mode' { "interfaces" : [ { "1.5.wiphy0" : { "tx bytes" : 401236186, "mode" : "802.11abgn", "device" : "wiphy0", "phantom" : false } }, { "1.5.wiphy1" : { "phantom" : false, "device" : "wiphy1", "mode" : "802.11abgn", "tx bytes" : 403975812 } } ], "candela.lanforge.HttpPort" : { "duration" : "1" }, "uri" : "port/:shelf_id/:resource_id/:port_id", "handler" : "candela.lanforge.HttpPort$JsonResponse" }
The /cx URL allows us to query Layer-3 connection information.
$ Json /cx { "uri" : "cx", "handler" : "candela.lanforge.GenericJsonResponder", "connections" : [ "41.1" : { "entity id" : "NA", "name" : "udp:r3r2:3000", "_links" : "/cx/41" }, "50.1" : { "name" : "udp:r3r2:3009", "entity id" : "NA", "_links" : "/cx/50" } ] }
And individual connections:
$ Json /cx/udp:r3r2:3000$ Json 'cx/udp:r3r2:3000' { "uri" : "cx/:cx_id", "41.1" : { "drop pkts b" : 0, "type" : "LF/UDP", "rx drop % a" : 0, "rpt timer" : "1000", "pkt rx a" : 0, "avg rtt" : 0, "rx drop % b" : 0, "name" : "udp:r3r2:3000", "endpoints (a ↔ b)" : "udp:r3r2:3000-A <=> udp:r3r2:3000-B", "drop pkts a" : 0, "entity id" : "NA", "bps rx a" : 0, "eid" : "1.41", "state" : "Stopped", "pkt rx b" : 0, "bps rx b" : 0 }, "handler" : "candela.lanforge.GenericJsonResponder" }
Endpoints may be listed and inspected:
$ Json /endp/ { "uri" : "endp", "handler" : "candela.lanforge.HttpEndp$JsonResponse", "candela.lanforge.HttpEndp" : { "duration" : "4" }, "endpoint" : [ { "1.2.8.55.2" : { "_links" : "/endp/55", "entity id" : "NA", "name" : "sta3000-ep-B" } }, { "1.2.8.57.1" : { "_links" : "/endp/57", "name" : "udp:r3r2:3000-B", "entity id" : "NA" } }, ... ] }
$ Json /endp/sta3000-ep-B { "candela.lanforge.HttpEndp" : { "duration" : "1" }, "uri" : "endp/:endp_id", "endpoint" : { "rx rate ll" : 0, "pdu/s tx" : 0, "bursty" : false, "rx rate" : 0, "tx pkts ll" : 0, "rx bytes" : 0, "run" : false, "tcp rtx" : 0, "min pdu" : 1460, "pps rx ll" : 0, "ooo pkts" : 0, "cx to" : 0, "tx rate ll" : 0, "source addr" : "10.41.0.2 0", "name" : "sta3000-ep-B", "rx ber" : 0, "min rate" : 56000, "rx dup %" : 0, "max rate" : 56000, "tx rate (1 min)" : 0, "a/b" : "B", "destination addr" : "0.0.0.0 0", "dropped" : 0, "script" : "None", "jitter" : 0, "elapsed" : 0, "rx rate (1 min)" : 0, "tx rate" : 0, "1st rx" : -1, "tx bytes" : 0, "crc fail" : 0, "rx wrong dev" : 0, "tx pdus" : 0, "pattern" : "INCREASING", "rx drop %" : 0, "entity id" : "NA", "cwnd" : 0, "delay" : 0, "mng" : true, "cx estab" : 0, "rx pdus" : 0, "tcp mss" : "0/0", "dup pkts" : 0, "rcv buf" : "0/0", "pdu/s rx" : 0, "rx pkts ll" : 0, "max pdu" : 1460, "cx active" : 0, "eid" : "1.2.8.55", "rx ooo %" : 0, "replays" : 0, "cx estab/s" : 0, "pps tx ll" : 0, "tx rate (last)" : 0, "rx rate (last)" : 0, "send buf" : "0/0" }, "handler" : "candela.lanforge.HttpEndp$JsonResponse" }
It is possible to create ports and connections by using the CLI commands. Your LANforge test scenarios (located in the /home/lanforge/DB/ directory) contain all the CLI commands that create your ports and connections. You can submit those commands over HTTP in two ways:
curl -X POST -H 'Content-type: application/json' \ -d '{"message":"hello world"}' http://localhost:8080/cli-json/gossipThen check your LANforge GUI messages.
curl -X POST -d 'message=hello+world' http://localhost:8080/cli/gossipThen check your LANforge GUI messages.
curl -X POST -d 'cmd=gossip hello' http://localhost:8080/cli/
Except for /cli-json, these methods accept application/x-www-form-urlencoded content type submissions. This is default for the NanoHttp library and default for curl.
These CLI commands do not return data, only a result code. All data that the Perl scripts would collect from command line queries is sent directly to the GUI. Some CLI commands send data over the websocket, like the diag command.
Commands are often complex and include a number of bitwise flags to set the state and features of ports. There is presently no tag-substitution for port flags, but there is a help utility that can help you compute them.
http://localhost:8080/help/
Select a command to see the field helper screen:
http://localhost:8080/help/set_port
Type values into the field inputs and the CLI command will be refreshed:
Click the Parse Command button and the values in the command box will be displayed in the curl command and the field inputs. (Notice this form is doing a GET request.)
You may find a list of flag fields that are organized by field names. The text area below the selection list is the sum of the selected fields. Copy the flag values into the input field above to incorporate it into your command.
Please refer to the scripts lf_associate_ap.pl and lf_vue_mod.sh for examples of how to produce lists of CLI commands involved in creating stations. Please refer to:
These will provide ways of collecting the CLI commands in log files for you to place into the command /help/ page.
$ cd scripts $ ./lf_vue_mod.sh --mgr localhost --resource 3 --create_sta --name sta3101 \ --radio wiphy1 --ssid idtest-1000-open --passphrase '[BLANK]' \ --log_cli /tmp/clilog.txt $ cat /tmp/clilog.txt set_wifi_radio 1 3 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NA add_sta 1 3 wiphy1 sta3101 1024 idtest-1000-open NA [BLANK] AUTO NA 00:0e:8e:c1:df:45 8 NA NA NA NA NA 1024 set_port 1 3 sta3101 0.0.0.0 255.255.0.0 0.0.0.0 NA 2147483648 00:0e:8e:c1:df:45 NA NA NA 8405038 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE
http://localhost:8080/help/set_wifi_radio?cli=1 3 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NAProduces:
$ echo 'shelf=1&resource=3&radio=wiphy1&channel=-1&flags=0x1' > /tmp/curl_data $ curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' \ http://localhost:8080/cli-form/set_wifi_radio
http://localhost:8080/help/add_sta?cli=1 3 wiphy1 sta3101 1024 idtest-1000-open NA [BLANK] AUTO NA 00:0e:8e:c1:df:45 8 NA NA NA NA NA 1024Produces:
$ echo 'shelf=1&resource=3&radio=wiphy1&sta_name=sta3101&flags=1024&ssid=idtest-1000-open&key=[BLANK]&ap=AUTO&mac=00:0e:8e:c1:df:45&mode=8&flags_mask=1024' > /tmp/curl_data $ curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' \ http://localhost:8080/cli-form/add_sta
http://localhost:8080/help/set_port?cli=1 3 sta3101 0.0.0.0 255.255.0.0 0.0.0.0 NA 2147483648 00:0e:8e:c1:df:45 NA NA NA 8405038 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONEProduces:
$ echo 'shelf=1&resource=3&port=sta3101&ip_addr=0.0.0.0&netmask=255.255.0.0&gateway=0.0.0.0¤t_flags=2147483648&mac=00:0e:8e:c1:df:45&interest=8405038&report_timer=1000¤t_flags_msk=NONE' > /tmp/curl_data $ curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' \ http://localhost:8080/cli-form/set_port
Using the /cli-json/add_endp and /cli-json/add_cx URLs, it is possible to create Layer-3 connections. Create the Layer-3 endpoints first, of course.
Construct your command using the /help/add_endp page. For an example, use these parameters:
Click Parse Command and copy the resulting curl command into a text editor:
And for the B endpoint, choose a station:
Click Parse Command and copy the resulting curl command into a text editor:
We'll save this file as a shell script: ~/create-endp.sh We can then run it from our terminal like so: bash -x create-endp.sh
We should see the endpoints we've created in the LANforge GUI Endps tab:
With the creation of two endpoints, we can proceed with creating a Layer 3 cross-connect. This is much simpler, it really only takes the names of the two endpoints we created above. We'll choose default_tm for the test manager.
Click the Parse Command button and copy the resulting curl command into your editor with the shell script. Run the script again. It doesn't hurt to re-create the endpoints.
Cross connects have three good state: STOPPED, RUNNING, and QUIESCE. The command to change them is set_cx_state. You will have no trouble creating the command:
Click Parse Command and then you can paste the resulting command into your editor.
You can make JSON submissions and you can also submit Base64 encoded values in both form an and JSON submission URLs.
Field names that end in -64 are interpreted as base64 encoded values. From a linux terminal, you can convert text to base64 encoded value using the base64 command:
$ echo "RUNNING" | base 64 UlVOTklORwo=
Below is a CLI command example. You typically would not care to spend the effort doing this unless the data you need to express is difficult to URL encode.
$ echo 'test_mgr-64=YW55Cg==&cx_name-64=dWRwMTAwMAo=&cx_state-64=UlVOTklORwo=' > /tmp/curl_data $ curl -A 'Accept: application/json' -X POST -d @/tmp/curl_data http://host/cli-form?set_cx_state
Instead of posting to /cli-form, you can post to /cli-json and your submission will be parsed as a json object. The parameter names stay the same. The base64 name extensions are also available! You need to specify that your Content-type in the POST is application/json.
$ echo '{"test_mgr":"default_tm","cx_name":"udp1000","cx_state":"RUNNING"}' > /tmp/curl_data $ curl -sq -H 'Content-type: application-json' -H 'Accept: application/json' \ -X POST -d@/tmp/curl_data http://localhost:8080/cli-json/set_cx_state
(This should be fixed as of 2018/08/14) When the LANforge cliet is in GUI mode, the columns of data that are returned match the GUI table columns displayed. You can use the Right-click→Add/Remove Table Columns menu item to change this. We do not recommend doing this for querying JSON data though, because the table columns definitions will not match up to the data the webserver expects to return.
$ curl -sq http://localhost:8080/port | json_pp { "error_list" : [ "names_to_col_ids map is not going to work:\n[tx abort][25 > 4]\n[cx time (us)][48 > 4]\n[bytes rx ll][33 > 4]\n[bps tx][14 > 4]\n[channel][39 > 4]\n[no cx (us)][47 > 4]\n[rx frame][22 > 4]\n[login-fail][58 > 4]\n[tx hb][28 > 4]\n[mode][40 > 4]\n[anqp time (us)][49 > 4]\n[rx pkts][8 > 4]\n[bytes tx ll][31 > 4]\n[key/phrase][56 > 4]\n[signal][42 > 4]\n[connections][44 > 4]\n[ipv6 gateway][68 > 4]\n[time-stamp][69 > 4]\n[entity id][70 > 4]\n[bps rx ll][32 > 4]\n[rx errors][16 > 4]\n[tx errors][17 > 4]\n[pps tx][13 > 4]\n[rx over][20 > 4]\n[ap][38 > 4]\n[mtu][63 > 4]\n[qlen][62 > 4]\n[beacon][54 > 4]\n[rx drop][18 > 4]\n[tx wind][29 > 4]\n[reset][34 > 4]\n[device][61 > 4]\n[status][37 > 4]\n[activity][41 > 4]\n[cx ago][46 > 4]\n[crypt][51 > 4]\n[rx crc][21 > 4]\n[ipv6 address][67 > 4]\n[logout-ok][59 > 4]\n[parent dev][6 > 4]\n[ssid][55 > 4]\n[tx-rate][35 > 4]\n[mac][66 > 4]\n[login-ok][57 > 4]\n[4way time (us)][50 > 4]\n[logout-fail][60 > 4]\n[rx miss][24 > 4]\n[rx fifo][23 > 4]\n[noise][43 > 4]\n[alias][5 > 4]\n[tx pkts][12 > 4]\n[tx crr][26 > 4]\n[rx length][19 > 4]\n[dhcp (ms)][45 > 4]\n[retry][52 > 4]\n[misc][53 > 4]\n[mask][64 > 4]\n[tx bytes][11 > 4]\n[tx fifo][27 > 4]\n[collisions][15 > 4]\n[pps rx][9 > 4]\n[gateway ip][65 > 4]\n[bps tx ll][30 > 4]\n[rx bytes][7 > 4]\n[bps rx][10 > 4]\n[rx-rate][36 > 4]" ], "status" : "INTERNAL_ERROR" }
The terminal you started the LANforge client on will also give a similar error:
1532480073953: names_to_col_ids size:71 java.lang.IllegalArgumentException: names_to_col_ids map is not going to work: 1532480073953: lfj_table columns:10
The LANforge perl scripts have always been able to print out the
CLI commands used to communicate with the LANforger manager. These
examples show recent modifications that allow you to collect the CLI
commands more easily using the --log_cli switch. (Not all
scripts have this feature yet).
The --log_cli switch prints CLI commands to your console. You
can collect those commands in a file using the switch with an argument
of the file name:
$ ./lf_vue_mod.sh --log_cli /tmp/clilog.txt It is possible to repeat these commands using the lf_firemod.pl script: $ ./lf_firemod.pl --mgr localhost --action do_cmd \ --cmd "set_port 1 2 sta200 NA NA NA NA 0 NA NA NA NA 8388610" Requires a LANforge CT520 (or better) system and an access point. |
This was done in a two-machine LANforge cluster, the manager named jedtest and the second resource named kedtest. The CLI output of these CLI commands has been discarded as well as any show_port commands.
The show_port commands are useful for inspecting the results of previous commands. Often there is useful wait before issuing the show_port command to allow processing time on the manager. Please inspect the scripts in the /home/lanforge/scripts directory for how and when they tend to sleep.
These commands are also found in the /home/lanforge/DB/DFLT directory files. You cannot run those DB files directly, because they are executed in certain order. However, you can grep for connection- and station-names in those files to find results of GUI commands.
This station is created with DHCP enabled. That is controlled via flags that are descibed in the add_sta command.
set_wifi_radio 1 2 wiphy0 NA -1 NA NA NA NA NA NA NA NA 0x1 NA add_sta 1 2 wiphy0 sta100 0 jedtest NA [BLANK] AUTO NA 00:0e:8e:8d:8d:e9 8 NA NA NA NA NA 0 set_port 1 2 sta100 0.0.0.0 255.255.0.0 0.0.0.0 NA 2147483648 00:0e:8e:8d:8d:e9 NA NA NA 8405038 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE
This station is created with DHCP enabled. That is controlled via flags that are descibed in the add_sta command.
$ ./lf_vue_mod.sh --mgr jedtest --resource 2 --create_sta --name sta200 \ --radio wiphy1 --security wpa2 --ssid jedtest --passphrase jedtest1 \ --log_cli /tmp/clilog.txt
./lf_associate_ap.pl --mgr jedtest --resource 2 \ --action add --radio wiphy1 --security wpa2 --ssid jedtest \ --passphrase jedtest1 --first_sta sta200 --first_ip DHCP --num_stations 1
set_wifi_radio 1 2 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NA add_sta 1 2 wiphy1 sta200 1024 jedtest NA jedtest1 AUTO NA 00:0e:8e:6f:01:62 8 NA NA NA NA NA 1024 set_port 1 2 sta200 0.0.0.0 255.255.0.0 0.0.0.0 NA 2147483648 00:0e:8e:6f:01:62 NA NA NA 8405038 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE
Here is an example of creating a virtual station with a static address: 10.26.2.14/255.255.254.0
set_wifi_radio 1 2 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NA show_port 1 2 wiphy1 add_sta 1 2 wiphy1 sta203 1024 jedtest NA jedtest1 AUTO NA 00:0e:8e:63:50:62 8 NA NA NA NA NA 1024 set_port 1 2 sta100 10.26.2.4 255.255.254.0 0.0.0.0 NA 0 00:0e:8e:63:50:62 NA NA NA 8388654 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE
For the station to gain a DHCP IP address, you have to admin-up the station.
The lf_associate_ap script contains logic that parses a MAC address pattern and produces new MAC addresses. This is not a feature of the LANforge Manager. Your CLI calls to the LANforge manager will not parse the mask.
The pattern nomenclature of the LANforge GUI can also be used when specifying a MAC address for stations:
set_wifi_radio 1 2 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NA show_port 1 2 wiphy1 add_sta 1 2 wiphy1 sta205 1024 jedtest NA jedtest1 AUTO NA 4e:0e:8e:43:f1:01 8 NA NA NA NA NA 1024 set_port 1 2 sta205 10.26.2.4 255.255.254.0 0.0.0.0 NA 0 4e:0e:8e:43:f1:01 NA NA NA 8388654 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE
set_port 1 2 sta200 NA NA NA NA 1 NA NA NA NA 8388610
set_port 1 2 sta200 NA NA NA NA 0 NA NA NA NA 8388610
rm_vlan 1 2 sta100
LANforge can create Layer-3 and Layer 4-7 connections using the lf_vue_mod.sh script. When connections are created, they exist in a stopped state. Connections can then have their state changed to RUNNING to start traffic.
Layer-3 and Layer 4-7 connections both subject to the states STOPPED, RUNNING, and QUIECSE.
./lf_vue_mod.sh --mgr jedtest --start_cx --name tcp200 --log_cli /tmp/clilog.txt --quiet 1
./lf_vue_mod.sh --mgr jedtest --stop_cx --name tcp200 --log_cli /tmp/clilog.txt --quiet 1
./lf_firemod.pl --mgr jedtest --action do_cmd --cmd "set_cx_state default_tm tcp200 RUNNING"
./lf_firemod.pl --mgr jedtest --action do_cmd --cmd "set_cx_state default_tm tcp200 STOPPED"
set_cx_state default_tm tcp200 RUNNING
set_cx_state default_tm tcp200 STOPPED
./lf_firemod.pl --mgr jedtest --resource 2 --action create_endp --endp_name tcp200-A --speed 1000000 --endp_type lf_tcp --port_name sta200 ./lf_firemod.pl --mgr jedtest --resource 2 --action create_endp --endp_name tcp200-B --speed 1000000 --endp_type lf_tcp --port_name eth1 ./lf_firemod.pl --mgr jedtest --resource 2 --action create_cx --cx_name tcp200 --cx_endps tcp200-A,tcp200-B
add_endp tcp200-A 1 2 sta200 lf_tcp -1 NA 1000000 1000000 NA -1 -1 increasing NO NA 0 0 set_endp_report_timer tcp200-A 5000 add_endp tcp200-B 1 2 eth1 lf_tcp -1 NA 1000000 1000000 NA -1 -1 increasing NO NA 0 0 set_endp_report_timer tcp200-B 5000 add_cx tcp200 default_tm tcp200-A tcp200-B set_cx_report_timer default_tm tcp200 5000 NA
./lf_firemod.pl --mgr jedtest --resource 2 --log_cli /tmp/clilog.txt --quiet 1 --action create_endp --endp_name udp200-A --speed 2000000 --endp_type lf_udp --port_name sta200 ./lf_firemod.pl --mgr jedtest --resource 2 --log_cli /tmp/clilog.txt --quiet 1 --action create_endp --endp_name udp200-B --speed 2000000 --endp_type lf_udp --port_name eth1 ./lf_firemod.pl --mgr jedtest --resource 2 --log_cli /tmp/clilog.txt --quiet 1 --action create_cx --cx_name udp200 --cx_endps udp200-A,udp200-B
add_endp udp200-A 1 2 sta200 lf_udp -1 NA 2000000 2000000 NA -1 -1 increasing NO NA 0 0 set_endp_report_timer udp200-A 5000 add_endp udp200-B 1 2 eth1 lf_udp -1 NA 2000000 2000000 NA -1 -1 increasing NO NA 0 0 set_endp_report_timer udp200-B 5000 add_cx udp200 default_tm udp200-A udp200-B set_cx_report_timer default_tm udp200 5000 NA
Layer 4-7 connections are created with a one-sided technique, the curl command always operates on the A-side and the B-side is unmanaged. The endpoint and connection naming does not follow the Layer-3 convention.
add_l4_endp yh200 1 2 sta200 l4_generic 0 10000 2400 'dl http://www.yahoo.com/ /dev/null' NA NA ca-bundle.crt NA 0 0 60 0 512 0.0.0.0 set_endp_tos yh200 DONT-SET 0 set_endp_flag yh200 L4Enable404 0 set_endp_report_timer yh200 5000 set_endp_flag yh200 ClearPortOnStart 0 set_endp_quiesce yh200 3 add_cx CX_yh200 default_tm yh200
Similar to the Layer3 perl script, lf_firemod.pl, the
lf_generic_ping.pl script has been enhanced to use curl or other
commands with parameter expansions. The lf_curl.sh script is a
helper script that wraps curl commands and reports success or failure.
Introduced in LANforge 5.3.8. |
Generic endpoints are effectively one-legged connections. You can ping an IP or use curl to download web content. Both of these types of connections can be pointed back at the LANforge itself. We can operate these connections from redirect interfaces. The same techniques can apply to WiFi stations, of course.
/home/lanforge$ . lanforge.profile
/home/lanforge$ ping -I 10.41.1.141 10.41.0.1
/home/lanforge$ curl -sq --interface 10.41.1.141 http://10.41.0.1/
In the /home/lanforge/scripts directory, the lf_generic_ping.pl script creates a wrapped ping command by default. There is another script, lfping, that reports ping results to LANforge. Here is an example of creating 1 ping endpoint:
/home/lanforge/scripts$ ./lf_generic_ping.pl --mgr localhost --resource 1 \ --dest 10.41.0.1 --interface r1b#0 --name pingtest
Create a ping endpoint for every MAC VLAN parented by r1b:
$ ./lf_generic_ping.pl --mgr localhost --resource 1 \ --dest 10.41.0.1 --parent r1b
Create a ping endpoint for every virtual station parented by wiphy0:
$ ./lf_generic_ping.pl --mgr localhost --resource 1 \ --dest 10.41.0.1 --radio wiphy0
Create a ping endpoint for every MAC VLAN beginning with r1b#1 (r1b#1, r1b#10):
$ ./lf_generic_ping.pl --mgr localhost --resource 1 \ --dest 10.41.0.1 --match 'r1b#1'
To use other commands with the script, you can create a --cmd parameter. You can use curl directly if desired, but curl's output is not formatted well for LANforge to understand. By default, commands do not understand what port or IP they should be interacting as. We need to provide special parameters to help.
The lf_generic_ping.pl script will look for these tokens in the --cmd parameter:
The scripts/lf_curl.sh script is a wrapper for curl that detects success or failure, and an operate the request in a loop. Expandable parameters are expanded by lf_generic_ping.pl, not lf_curl.sh.
You can use lf_curl.sh from the command line to test it out:
$ ./lf_curl.sh -i 10.41.1.141 -p r1b#1 -o /tmp/output -d http://example.com/
Executes:
curl -sqLk --interface 10.0.0.1 -o /tmp/output_r1b#1 http://example.com/
So it is best used from lf_generic_ping.pl to construct commands referencing this script:
./lf_generic_ping.pl --mgr localhost --resource 1 \ --name curl_ex_ --match 'r1b#' --dest http://10.41.0.1/ \ --cmd 'lf_curl.sh -o /tmp/curl_%p.out -i %i -d %d -p %p'
In this test scenario, a script is used to bring up the WiFi Capacity test with a pre-configured configuration. The capacity test is then started and a report is generated. All of this is automated, and other tests such as Dataplane are also supported. This feature requires LANforge version 5.4.1 or higher. |
|
There are many python modules which control LANforge. These modules allow the user to automate many tasks. This cookbook will introduce the initial steps to start LANforge automation, the libraries that need to be imported to run Candela's python scripts, and how to create objects in python. There are two options to run lanforge_scripts. If you are a programmer, you can clone the git repository locally by using Candela supports Fedora 27+ and Python 3.7+. Older python versions are not supported. The advantage to cloning lanforge_scripts from git is that it is the latest code available, while the pip repository will lag slightly behind the git repo. The advantage to using pip is that there might be occasional errors in the git repository, which will be ironed out by the time we push it to the pip repository. The pip repository is always close to the master branch of the git repo, lagging no more than a couple weeks. Pick the one which best suits your needs. Please run Python as the LANforge user on your system. If you run python as root, you might break your LANforge system. The LANforge Linux OS requires some certain python packages and versions to operate and configure networking correctly. Candelatech does not recommend updating python packages as root. Your package manager will automatically update these dependencies when you run updates as well, which can overwrite your changes. The safe way to run python scripts is as a non-root user. If you have python dependencies installed locally by using Users are responsible for making certain the version of Python running on their system is supported by the Python Foundation. An up to date list can be found at Python Foundation support. Candela Technologies does not support versions of Python which have been deprecated by the Python Foundation. Customers with a support contract can contact support@candelatech.com to get support upgrading their LANforge systems. |
|
pip3 install --user -r requirements.txt --upgrade
python3 your_script_here.py --your_flag
python3
import lanforge_scripts
./create_station.py --radio wiphy0 --ssid lanforge --passwd password --security wpa2
sudo iw dev wlan0 scan | grep SSID
into your command line
--ap
to the end of the command line argument you are running followed by the router's MAC Address.
./create_bridge.py --lf_mgr localhost --bridge_name br0 --target_device eth1 --no_cleanup
./create_vap.py --radio wiphy0 --security wpa2 --ssid lanforge --passwd password
./create_l3.py --radio wiphy0 --num_stations 0
./create_l4.py --radio wiphy0 --ssid lanforge --passwd password --security wpa2
./test_ipv4_variable_time.py --radio wiphy0 --security wpa2 --ssid lanforge --password password --output_format csv
On your LANforge Server, the scripts directory is located at /home/lanforge/scripts. Under that directory, the py-json directory contains Python scripts.
Script | Purpose |
---|---|
__init__.py | Defines the py-json module |
show_ports.py | Simplest example of querying all ports. This is typical of querying any of the APIs you browse from / or /help |
create_sta.py | Script creates a virtual station |
LANforge/__init__.py | Defines the py-json/LANforge module |
LANforge/LFRequest.py | Use the LFRequest module to help make GET and POST requests to the LANforge Client |
LANforge/LFUtils.py | Use the LFUtils module to help process JSON results |
First, start the LANforge Client (LANforge GUI) and connect it to your LANforge Server. If you want to start the client in headless mode, open a terminal, and from the LANforgeGUI_5.4.1 directory, start the script with the -daemon argument:
$ ./lfclient -daemon -s localhost
In the /home/lanforge/scripts/py-json directory:
$ python3 ./show_ports.py { '1.1.eth0': { '_links': '/port/1/1/0', 'alias': 'eth0', 'phantom': False, 'port': '1.1.00'}} { '1.1.eth1': { '_links': '/port/1/1/1', 'alias': 'eth1', 'phantom': False, 'port': '1.1.01'}} { '1.1.sta00500': { '_links': '/port/1/1/9', 'alias': 'sta00500', 'phantom': False, 'port': '1.1.09'}} { '1.1.sta00501': { '_links': '/port/1/1/10', 'alias': 'sta00501', 'phantom': False, 'port': '1.1.10'}} { '1.1.sta00502': { '_links': '/port/1/1/11', 'alias': 'sta00502', 'phantom': False, 'port': '1.1.11'}}
This script is a way of pretty-printing the results of GET http://localhost:8080/port/1/1/list
lf_r = LFRequest.LFRequest("http://localhost:8080/port/1/1/list") json_response = lf_r.getAsJson() j_printer = pprint.PrettyPrinter(indent=2) for record in json_response['interfaces']: j_printer.pprint(record)
Other variations of this you can try are:
[lanforge@ct524-debbie py-json]$ python3 ./create_sta.py Example 1: will create stations sta0200,sta0201,sta0202 Ex 1: Checking for station : http://localhost:8080/port/1/1/sta0200 ... Ex 1: Next we create stations... Ex 1: Next we create station sta0200... Ex 1: station up sta0200... Example 2: using port list to find stations Ex 2: checking for station : sta0220 Ex 2: create station sta0220 Ex 2: set port sta0220 Example 3: bring ports up and down Ex 3: setting ports up... Ex 3: setting ports down... ...ports are down Example 4: Modify stations to mode /a using add_sta to set sta0200 mode Example 5: change station encryption from wpa2 to wpa3... using add_sta to set sta0200 wpa3 Example 7: alter TX power on wiphy0...
lf_r = LFRequest.LFRequest(base_url+"/cli-form/add_sta")
Flags are a decimal equivalent of a hexadecimal bitfield you can submit as either 0x(hex) or (dec) a helper page is available at http://localhost:8080/help/add_sta
You can watch console output of the LANforge GUI client when you get errors to this command, and you can also watch the websocket output for a response to this command at ws://localhost:8081. Use $ wsdump ws://localhost:8081/ to follow those messages.
Modes are listed at http://localhost/LANforgeDocs-5.4.1/lfcli_ug.html or at https://www.candelatech.com/lfcli_ug.html
The MAC address field is a pattern for creation: entirely random mac addresses do not take advantage of address mask matchin in Ath10k hardware, so we developed this pattern to randomize a section of octets:
If you get errors like "X is invalid hex character", this indicates a previous rm_vlan call has not removed your station yet: you cannot rewrite mac addresses with this call, just create new stations.
The staNewDownStaRequest() creates a station in the Admin-Down state. This is a good way to efficiently create batches of stations because it defers all the PHY layer activity which takes significant time when you do it in a loop.
lf_r.addPostData( LFUtils.staNewDownStaRequest(sta_name, resource_id=resource_id, radio=radio, ssid=ssid, passphrase=passphrase)) lf_r.formPost() sleep(0.05)
Sleeping for 50ms is not sufficient to interact with the station, but is a functional minimum to allow the LANforge to start processing the command; this is a good value to use in a loop that creates stations. Follow with:
LFUtils.waitUntilPortsAppear(resource_id, desired_stations)
The LANforge API separates STA creation and Ethernet port settings. We need to revisit the stations we create and amend flags to add things like DHCP or ip+gateway, admin-{up,down} for sta_name in desired_stations:
lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_port") data = LFUtils.portDhcpUpRequest(resource_id, sta_name) lf_r.addPostData(data) lf_r.jsonPost() sleep(0.05)
for sta_name in desired_stations: lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_port") lf_r.addPostData(LFUtils.portDownRequest(resource_id, sta_name)) lf_r.jsonPost() LFUtils.waitUntilPortsAdminDown(resource_id, desired_stations)
There is not a set_sta command. Many LANforge CLI commands do a default modify if the entity already exists. This is how we can modify attributes of existing stations. For the mode values, see http://www.candelatech.com/lfcli_ug.php#add_sta
for sta_name in desired_stations: lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_sta") lf_r.addPostData({ "shelf":1, "resource": resource_id, "radio": radio, "sta_name": sta_name, "mode": 1, # 802.11a }) lf_r.jsonPost() sleep(0.5)
Flags for add_sta and set_port are actually 64-bit values. When the values in the command below are read by the /help/add_sta page, Javascript cannot deal with integers greater than 32-bits long.
lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_sta") lf_r.addPostData({ "shelf":1, "resource": resource_id, "radio": radio, "sta_name": sta_name, "mode": 0, # mode AUTO # sets use-wpa3 "flags": 1099511627776, # sets interest in use-wpa3, wpa2_enable (becomes zero) "flags_mask": 1099511628800 }) print("using add_sta to set %s wpa3"%sta_name) lf_r.jsonPost()
Virtual stations do not have individual tx power states. You can set the radio transmit power. See http://www.candelatech.com/lfcli_ug.php#set_wifi_radio. The txpower is set through iwconfig, so see man 8 iwconfig. Power is in dBm, auto or off.
Not all flags in a JSON request are actually LANforge CLI parameters. The suppress_preexec_method parameter is a meta-flag tells the LANforge client to not check that the port exists before issuing the command. You would use this to expedite a script, because a check-port command is synchronous, not intended to be used in a loop.
lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_wifi_radio") lf_r.addPostData({ "shelf":1, "resource":resource_id, "radio":radio, "mode":NA, "txpower": "auto", "suppress_preexec_method": "true" }) lf_r.jsonPost()
Use the wsdump utility on the LANforge to see the output of system errors and WiFi Events:
$ wsdump ws://localhost:8081/
The output will be mostly similar to what you see in the WiFi-Messages tab in the GUI:
< {"wifi-event":"1.1: IFNAME=sta0200 <3>CTRL-EVENT-SCAN-STARTED","timestamp":"2019-11-21T16:00:50.095Z"} < {"wifi-event":"1.1: IFNAME=sta0200 <3>CTRL-EVENT-NETWORK-NOT-FOUND","timestamp":"2019-11-21T16:00:50.095Z"} < {"wifi-event":"1.1: sta0200 (phy #1): scan started","timestamp":"2019-11-21T16:00:50.095Z"} < {"wifi-event":"1.1: sta0200 (phy #1): scan finished: 5745, \"\"","timestamp":"2019-11-21T16:00:50.095Z"} < {"wifi-event":"1.1: IFNAME=sta0220 <3>CTRL-EVENT-SCAN-STARTED","timestamp":"2019-11-21T16:00:50.095Z"} < {"wifi-event":"1.1: IFNAME=sta0220 <3>CTRL-EVENT-NETWORK-NOT-FOUND","timestamp":"2019-11-21T16:00:50.095Z"}
The message CTRL-EVENT-NETWORK-NOT-FOUND indicates that the SSID we are attempting to connect to is unavailable.
It won't be uncommon to find errors similar to this:
Url: http://localhost:8080/cli-form/set_port Error: <class 'urllib.error.HTTPError'> Request URL: 'http://localhost:8080/cli-form/set_port' Request Content-type: 'application/x-www-form-urlencoded' Request Accept: 'application/json' Request Data: (b'shelf=1&resource=1&port=sta0200¤t_flags=2147483649&interest=75513858&r' b'eport_timer=8000')
The HTTPError exception is just some kind of 500 error and is often timing related. Perl scripts are subject to similar timing issues. When LANforge is busy creating and destroying stations, it is modifying the network stack during each modification...and this takes time.
You can decode this set_port request data by pasting the individual values into the /help/set_port page provided by your LANforge client: http://localhost:8080/help/set_port.
You can copy the py-json directory to your laptop or workstation. You may also use git and clone the LANforge-scripts repository: https://github.com/greearb/lanforge-scripts/.
Create a new LFRequest object to help create a request:
lf_r = LFRequest.LFRequest(base_url+"/port/1/1/wiphy0") wiphy0_json = lf_r.getAsJson()
Your REST requests are discussed in the Querying LANforge GUI for JSON Data chapter.
[ { "1.1.eth0": { "alias":"eth0" } }, { ... } ]
Naturally, this is more difficult to digest. This method returns a more intuitive structure:
{ "eth0" : { "1.1.eth0": { "alias":"eth0" }, }, "eth1": {}, ... }
We will start by creating a WAN Link between ports eth2 and eth5 on our ct522. Two Ethernet ports will be involved in this example.
This script is located in /home/lanforge/scripts/py-json or on the lanforge-scripts github page. You can copy this script to a new name and edit it to fit your enviroment. Remember, these JSON scripts will be querying a LANforge Client (GUI or headless). The URL you will see being queried is going to be http://localhost:8080/ for these exaples, assuming you are running the LANforge client on the same machine you are running your script.
This script performs the basic tasks you might use to manage WANlink connections:
Notice that when we get a listing response, we are looking for items in the response that are dictionarys with a _links key/value pair. There are other key/values used for diagnostics, such as uri, handler, warnings and errors.
base_url = "http://localhost:8080" json_post = "" json_response = "" num_wanlinks = -1 # see if there are old wanlinks to remove lf_r = LFRequest.LFRequest(base_url+"/wl/list") try: json_response = lf_r.getAsJson() # For debugging, this makes json response more legible: LFUtils.debug_printer.pprint(json_response) for key,value in json_response.items(): if (isinstance(value, dict) and "_links" in value): num_wanlinks = 1 except urllib.error.HTTPError as error: num_wanlinks = 0;
{ "wl_eg1" : { "_links" : "/wl/42.6", "name" : "wl_eg1", "entity id" : "Cross-Connect cx_id: 42, type: 6, idx: 0" }, "uri" : "wl/:wl_id", "handler" : "candela.lanforge.GenericJsonResponder" }
If there are no wanlinks, you will only see a warnings block telling you there are connections found that don't apply as WANlinks:
{ "warnings" : [ "HttpWl::selectColumnsFromRow: eid not in table: Cross-Connect cx_id: 17, type: 1, idx: -1", "HttpWl::myEvaluateGet: EidCx type 1 (LANforge / UDP) unavailable in WL table: 17.1", "HttpWl::selectColumnsFromRow: eid not in table: Cross-Connect cx_id: 18, type: 1, idx: -1", "HttpWl::myEvaluateGet: EidCx type 1 (LANforge / UDP) unavailable in WL table: 18.1" ], "handler" : "candela.lanforge.GenericJsonResponder", "uri" : "wl/:wl_id" }
If we found WANlinks, we can remove them by posting the data to the corresponding CLI command URIs: /cli-json/rm_cx and /cli-json/rm_endp.
if (num_wanlinks > 0): lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_cx") lf_r.addPostData({ 'test_mgr': 'all', # could be 'default-tm', too 'cx_name': 'wl_eg1' }) lf_r.jsonPost() sleep(0.05)
The parameters for each command can be found via the help page: http://localhost:8080/help/rm_cx.
Notice that slight pause between commands: 50ms is a good idea between deletion commands.
lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_endp") lf_r.addPostData({ 'endp_name': 'wl_eg1-A' }) lf_r.jsonPost() sleep(0.05) lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_endp") lf_r.addPostData({ 'endp_name': 'wl_eg1-B' }) lf_r.jsonPost() sleep(0.05)
Create the two endpoints first. Each side of a WANlink has its own transmission rate, buffer size and corruption parameters. Each WANlink requires an ethernet port. Side A will be 128,000bps with 75ms latency:
lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_wl_endp") lf_r.addPostData({ 'alias': 'wl_eg1-A', 'shelf': 1, 'resource': '1', 'port': 'eth3', 'latency': '75', 'max_rate': '128000', 'description': 'cookbook-example' }) lf_r.jsonPost() sleep(0.05)
Side B will be 256,000bps with 95ms latency:
lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_wl_endp") lf_r.addPostData({ 'alias': 'wl_eg1-B', 'shelf': 1, 'resource': '1', 'port': 'eth5', 'latency': '95', 'max_rate': '256000', 'description': 'cookbook-example' }) lf_r.jsonPost() sleep(0.05)
Creating the WANlink is simple, we will add it to the default test manager default-tm:
lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_cx") lf_r.addPostData({ 'alias': 'wl_eg1', 'test_mgr': 'default_tm', 'tx_endp': 'wl_eg1-A', 'rx_endp': 'wl_eg1-B', }) lf_r.jsonPost() sleep(0.05)
The LANforge server is very asynchronous. Before immediately changing the state on a connection or endpoint, test to see that it exists.
seen = 0 while (seen < 1): sleep(1) lf_r = LFRequest.LFRequest(base_url+"/wl/wl_eg1?fields=name,state,_links")
Note how we can request fields by name, in this case name, state, and _links.
try: json_response = lf_r.getAsJson() if (json_response is None): continue
If there is no response, or we get a 400 error, the wanlink has probably not finished creating. Our response will be None.
In the reponse below, we're testing for dict entries that have the key _links in them. If the name value matches, our WANlink has been created:
for key,value in json_response.items(): if (isinstance(value, dict)): if ("_links" in value): if (value["name"] == "wl_eg1"): seen = 1
It might be helpful to use these else clauses when getting started:
#else: # print(" name was not wl_eg1") #else: # print("value lacks _links") #else: # print("value not a dict") except urllib.error.HTTPError as error: print("Error code "+error.code) continue
Starting and stopping connections is done by changing the state:
lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_cx_state") lf_r.addPostData({ 'test_mgr': 'all', 'cx_name': 'wl_eg1', 'cx_state': 'RUNNING' }) lf_r.jsonPost()
The connection might take a second to start. You can poll it similar to to how we polled it above:
running = 0 while (running < 1): sleep(1) lf_r = LFRequest.LFRequest(base_url+"/wl/wl_eg1?fields=name,state,_links") try: json_response = lf_r.getAsJson() if (json_response is None): continue for key,value in json_response.items(): if (isinstance(value, dict)): if ("_links" in value): if (value["name"] == "wl_eg1"): if (value["state"].startswith("Run")): running = 1 except urllib.error.HTTPError as error: print("Error code "+error.code) continue
The frequency fields below are in occurrance per million. Speeds are set in bits per second (bps). Latencies are in milliseconds.
lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_wanlink_info") lf_r.addPostData({ 'name': 'wl_eg1-A', 'speed': 265333, # bps 'latency': 30, # 30 ms 'reorder_freq': 3200, # 3200/1000000 'drop_freq': 2000, # 2000/1000000 'dup_freq': 1325, # 1325/1000000 'jitter_freq': 25125, # 25125/1000000 }) lf_r.jsonPost()
Stopping a WANlink is again, changing its state to either STOPPED or QUIESCE. The QUIESCE state stops transmission on both endpoints but does not close the connection so that in-flight packets can arrive. Choose QUIESCE if you want to make your accounting of packets the most accurate.
lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_cx_state") lf_r.addPostData({ 'test_mgr': 'all', 'cx_name': 'wl_eg1', 'cx_state': 'STOPPED' }) lf_r.jsonPost()
There might be a milliseconds to seconds of delay depending on how your connection is stopped. You might have to wait for slightly longer than QUIESCE-TIME before the connections are closed when using a QUIESCE stop. Polling the state of the connection is relatively simple:
running = 1 while (running > 0): sleep(1) lf_r = LFRequest.LFRequest(base_url+"/wl/wl_eg1?fields=name,state,_links") # LFUtils.debug_printer.pprint(json_response)
You might want to watch that debug output at first.
try: json_response = lf_r.getAsJson() if (json_response is None): continue for key,value in json_response.items(): if (isinstance(value, dict)): if ("_links" in value): if (value["name"] == "wl_eg1"): if (value["state"].startswith("Stop")): LFUtils.debug_printer.pprint(json_response) running = 0 except urllib.error.HTTPError as error: print("Error code "+error.code) continue
{ 'handler': 'candela.lanforge.GenericJsonResponder', 'uri': 'wl/:wl_id', 'wl_eg1': { '_links': '/wl/wl_eg1', 'name': 'wl_eg1', 'state': 'Run'}} { 'handler': 'candela.lanforge.GenericJsonResponder', 'uri': 'wl/:wl_id', 'wl_eg1': { '_links': '/wl/wl_eg1', 'name': 'wl_eg1', 'state': 'Stopped'}}
Each of the endpoints will show the amount of packets transmitted:
lf_r = LFRequest.LFRequest(base_url+"/wl_ep/wl_eg1-A") json_response = lf_r.getAsJson() LFUtils.debug_printer.pprint(json_response)
The key/value pairs are grouped for this example, but attribute order is not normally ordered.
{ "endpoint" : { "name" : "wl_eg1-A", "buffer" : 19936, # bytes "corrupt 1" : 0, # these corruptions are per-wanpath "corrupt 2" : 0, "corrupt 3" : 0, "corrupt 4" : 0, "corrupt 5" : 0, "corrupt 6" : 0, "delay" : 30000, "dropped" : 0, "dropfreq %" : 0.200000002980232, "dup pkts" : 0, "dupfreq %" : 0.132499992847443, "eid" : "1.1.3.82", "elapsed" : 7, "extrabuf" : 17408, "failed-late" : 0, "jitfreq %" : 2.51250004768372, "maxjitter" : 0, "maxlate" : 606, "max rate" : 265333, "ooo pkts" : 0, "qdisc" : "FIFO", "reordfrq %" : 0.319999992847443, "run" : false, "rx bytes" : 0, "rx pkts" : 0, "script" : "None", # applicable if 2544 script has be applied "serdelay" : 45648.296875, "tx bytes" : 0, "tx drop %" : 0, "tx pkts" : 0, "tx rate" : 0, "tx-failed" : 0, # below is a to-string debug value "wps" : "TblJButton, eid: Shelf: 1 Resource: 1 Port: 3 Endpoint: 82 Type: WanLink", }, "uri" : "wl_ep/:wl_ep_id", "candela.lanforge.HttpWlEndp" : { "duration" : "0" }, "handler" : "candela.lanforge.HttpWlEndp$JsonResponse" }
The CT840a chamber with rotating platform contains an embedded controller that is operated by the modbus protocol. For manual control of the turntable, the LANforge GUI may be used. For automation, you will want to use the lf_chamber.pl script.The lf_chamber.pl script allows you to monitor the door sensor, table angle, light state and fan state. It also provides control over lights, fans, and table position. This script requires a LANforge server version 5.4.1 or higher to communicate with the chamber. A CT521a or a virtual machine instance is adequate for the task. These instructions apply to the CT840a chamber. | |
# ./lf_chamber.pl
Usage:
./lf_chamber.pl --angle 45 --speed 3 --targ 192.168.100.122
./lf_chamber.pl --adjust 5 --targ 192.168.100.122
./lf_chamber.pl --fan 1 --targ 192.168.100.122
./lf_chamber.pl --lights 1 --targ 192.168.100.122
./lf_chamber.pl --status 1 [ --id foo --mgt_pipe /foo/bar ] --targ 192.168.100.122
Current-Angle: 3598 Door-Open: 0 Table-Moving: 0 Lights: 0 Fan: 0 Jog-Speed: 3 Return-Speed: 3 Absolute-Speed: 3 Jog Angle: 449
Current-Angle: mbpoll -a 1 -r 4139 -t 4 10.0.0.9 -1 Door-Open: mbpoll -a 1 -r 2094 -t 1 10.0.0.9 -1 0 Table-Moving: mbpoll -a 1 -r 3046 -t 1 10.0.0.9 -1 0 Lights: mbpoll -a 1 -r 1283 -t 1 10.0.0.9 -1 0 Fan: mbpoll -a 1 -r 1284 -t 1 10.0.0.9 -1 0 Jog-Speed: mbpoll -a 1 -r 4507 -t 4 10.0.0.9 -1 3 Return-Speed: mbpoll -a 1 -r 4509 -t 4 10.0.0.9 -1 3 Absolute-Speed: mbpoll -a 1 -r 4511 -t 4 10.0.0.9 -1 3 Jog Angle: mbpoll -a 1 -r 4513 -t 4 10.0.0.9 -1
Current-Angle: /home/lanforge/local/bin/mbpoll: Connection failed: Operation now in progress.
COMM-FAIL
Current-Angle: 450 Door-Open: 0 Table-Moving: 0 Lights: 0 Fan: 0 Jog-Speed: 3 Return-Speed: 3 Absolute-Speed: 3 Jog Angle: 450
Adjust 5
Current-Angle: 500 Door-Open: 0 Table-Moving: 0 Lights: 0 Fan: 0 Jog-Speed: 3 Return-Speed: 3 Absolute-Speed: 3 Jog Angle: 500
Toggle fan /home/lanforge/local/bin/mbpoll -a 1 -r 2074 -t 0 10.0.0.9 -1 0 > /dev/null /home/lanforge/local/bin/mbpoll -a 1 -r 2074 -t 0 10.0.0.9 -1 1 > /dev/null
Current-Angle: 3598 Door-Open: 0 Table-Moving: 0 Lights: 0 Fan: 1 Jog-Speed: 3 Return-Speed: 3 Absolute-Speed: 3 Jog Angle: 449
Toggle fan /home/lanforge/local/bin/mbpoll -a 1 -r 2074 -t 0 10.0.0.9 -1 0 > /dev/null /home/lanforge/local/bin/mbpoll -a 1 -r 2074 -t 0 10.0.0.9 -1 1 > /dev/null
Current-Angle: 3598 Door-Open: 0 Table-Moving: 0 Lights: 0 Fan: 0 Jog-Speed: 3 Return-Speed: 3 Absolute-Speed: 3 Jog Angle: 449
For more information see USB Cable Types
Using the l3_video_em.pl and the l3_vid_group.pl, we assemble two test groups of connections, a group of Generic connections, and a group of Layer3 connections, that emulate the bursty buffer filling pattern of traffic that video streaming tends to resemble. Requires LANforge 5.4.2. | |
For more information see Creating Stations
For more information see LANforge Entity IDs
./l3_vid_group.pl --action create --endp_type tcp --first_sta sta000
--num_cx 16 --test_grp sixteen --upstream 1.1.eth1
/home/lanforge/scripts/l3_video_em.pl --mgr localhost --mgr_port 4001You can paste this command into a shell prompt on your LANforge and use it. We discuss the options in the following section.
--cx_name _sixteen-0000 --max_tx 1000000000 --buf_size 3145728 --stream yt-sdr-360p30
--tx_style bufferfill --quiet yes
./l3_vid_group.pl --test_grp sixteen --action start
./l3_vid_group.pl --test_grp sixteen --action stop
Using the realm.py library we will write a script that will allow us to automate the creation of stations and Layer-3 cross connects. We will also be able to start and stop traffic over the cross connects using the script. We will be referencing the script, test_ipv4_variable_time.py, as an example throughout this cookbook. Requires LANforge 5.4.2. | |
import sys
. Then useif 'py-json' not in sys.path: sys.path.append(os.path.join(os.path.abspath('..'), 'py-json'))
from LANforge.lfcli_base import LFCliBase
station_list = LFUtils.port_name_series(prefix_="sta",
start_id_=0,
end_id_=4,
padding_number_=10000)
ip_var_test = IPV4VariableTime(lfjson_host, lfjson_port,
number_template="00",
sta_list=station_list,
name_prefix="var_time",
ssid="testNet",
password="testPass",
resource=1,
security="wpa2",
test_duration="5m",
side_a_min_rate=256,
side_b_min_rate=256)
def main():
lfjson_host = "localhost"
lfjson_port = 8080
station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=4, padding_number_=10000)
ip_var_test = IPV4VariableTime(lfjson_host, lfjson_port, number_template="00", sta_list=station_list,
name_prefix="var_time",
ssid="testNet",
password="testPass",
resource=1,
security="wpa2", test_duration="5m",
side_a_min_rate=256, side_b_min_rate=256),
ip_var_test.cleanup(station_list)
ip_var_test.build()
if not ip_var_test.passes():
print(ip_var_test.get_fail_message())
exit(1)
ip_var_test.start(False, False)
ip_var_test.stop()
if not ip_var_test.passes():
print(ip_var_test.get_fail_message())
exit(1)
time.sleep(30)
ip_var_test.cleanup(station_list)
if ip_var_test.passes():
print("Full test passed, all connections increased rx bytes")
get_result_list()
get_failed_result_list()
get_fail_message()
get_all_message()
station_profile.use_security()
station_profile.set_number_template()
to name our stations
self.station_profile.set_command_flag("add_sta","create_admin_down",1)
self.station_profile.set_command_param("set_port","report_timer",1500)
self.station_profile.set_command_flag("set_port","rpt_timer", 1)
for station in range(len(self.sta_list)):
temp_sta_list.append(str(self.resource)+"."+self.sta_list[station])
self.station_profile.create(resource=1, radio="wiphy0", sta_names_=self.sta_list, debug=False)
self.cx_profile.create(endp_type="lf_udp", side_a=temp_sta_list, side_b="1.eth1", sleep_time=.5)
self._pass("PASS: Station build finished")
self.station_profile.use_security(security_type, ssid, passwd)
is the preferred method to use when setting the security type, ssid, and password variablesself.station_profile.use_security("wpa2", "testNet", "testPass")
self.station_profile.number_template_="00"
self.station_profile.mode=0
self.cx_profile.name_prefix="test_"
self.cx_profile.side_a_min_bps=56000
self.cx_profile.side_b_min_bps=56000
key_mgmt="WPA-EAP"We can then use these variables to call the set_wifi_extra() method
eap="TTLS"
identity="testuser"
passwd="testpasswd"
realm="localhost.localdomain"
domain="localhost.localdomain"
hessid="00:00:00:00:00:01"
station_profile.set_wifi_extra(key_mgmt, eap, identity, passwd, realm, domain, hessid)
_debug_on=True
in StationProfile.create()
- 381 - sta0000- - - - - - - - - - - - - - - - - -
{'flags': 132096,
'flags_mask': 68719608832,
'key': 'testPass',
'mac': 'xx:xx:xx:xx:*:xx',
'mode': 0,'
radio': 'wiphy0',
'resource': 1,
'shelf': 1,
'ssid': 'testNet,
'sta_name': 'sta0000'}/pre>
{'current_flags': 2147483649,
'interest': 8437762,
'port': 'sta0000',
'report_timer': 1500,
'resource': 1,
'shelf': 1}
<LANforge.LFRequest.LFRequest object at 0x7f13dbc56850>
-~381 - - - - - - - - - - - - - - - - - - - /pre>
Using the realm.py library we will write a script that will allow us to automate the creation of stations and Layer 4 cross connects. We will also be able to start and stop traffic over the cross connects using the script. Station and Cross Connect creation is covered in the Realm Scripting Cookbook. Requires LANforge 5.4.2. | |
requests_per_ten = 600
will set our URL request rate to 1 per second. There is no limit to what can be used as the rate but common rates are: l4_cx_profile.url = "dl http://10.40.0.1 /dev/null"
class IPV4L4(LFCliBase):
def __init__(self, host, port, ssid, security, password, url, requests_per_ten, station_list,
target_requests_per_ten=600, number_template="00000", resource=1, num_tests=1,
_debug_on=False,
_exit_on_error=False,
_exit_on_fail=False):
super().__init__(host, port, _debug=_debug_on, _halt_on_error=_exit_on_error, _exit_on_fail=_exit_on_fail)
self.host = host
self.port = port
self.ssid = ssid
self.security = security
self.password = password
self.url = url
self.requests_per_ten = requests_per_ten
self.number_template = number_template
self.sta_list = station_list
self.resource = resource
self.num_tests = num_tests
self.target_requests_per_ten = target_requests_per_ten
self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port)
self.cx_profile = self.local_realm.new_l4_cx_profile()
self.cx_profile.url = self.url
self.cx_profile.requests_per_ten = self.requests_per_ten
# Station Profile init
def build(self):
# Build stations
self.station_profile.use_security(self.security, self.ssid, self.password)
print("Creating stations")
self.station_profile.create(resource=1, radio="wiphy0", sta_names_=self.sta_list, debug=self.debug)
temp_sta_list = []
for station in range(len(self.sta_list)):
temp_sta_list.append(str(self.resource) + "." + self.sta_list[station])
self.l4_profile.create(ports=temp_sta_list, sleep_time=.5, debug_=self.debug, suppress_related_commands_=None)
def start(self, print_pass=False, print_fail=False):
temp_stas = self.sta_list.copy()
temp_stas.append("eth1")
cur_time = datetime.datetime.now()
interval_time = cur_time + datetime.timedelta(minutes=1)
passes = 0
expected_passes = 0
self.station_profile.admin_up(1)
self.local_realm.wait_for_ip(self.resource, temp_stas)
self.l4_profile.start_cx()
print("Starting test")
for test in range(self.num_tests):
expected_passes += 1
while cur_time < interval_time:
time.sleep(1)
cur_time = datetime.datetime.now()
if self.l4_profile.check_errors(self.debug):
if self.__check_request_rate():
passes += 1
else:
self._fail("FAIL: Request rate did not exceed 90% target rate", print_fail)
break
else:
self._fail("FAIL: Errors found getting to %s " % self.url, print_fail)
break
interval_time = cur_time + datetime.timedelta(minutes=1)
if passes == expected_passes:
self._pass("PASS: All tests passes", print_pass)
Using the realm.py library we will write a script that will allow us to automate the creation of stations and generic cross connects. We will also be able to start and stop traffic over the cross connects using the script. Station and Cross Connect creation is covered in the Realm Scripting Cookbook. Requires LANforge 5.4.2. | |
self.cx_profile.type = "lfping"
self.cx_profile.dest = "127.0.0.1"
self.cx_profile.interval = 1
class GenTest(LFCliBase):
def __init__(self, host, port, ssid, security, password, sta_list, name_prefix, upstream,
number_template="00000", test_duration="5m", type="lfping", dest="127.0.0.1",
interval=1, radio="wiphy0",
_debug_on=False,
_exit_on_error=False,
_exit_on_fail=False):
super().__init__(host, port, _debug=_debug_on, _halt_on_error=_exit_on_error, _exit_on_fail=_exit_on_fail)
self.host = host
self.port = port
self.ssid = ssid
self.radio = radio
self.upstream = upstream
self.sta_list = sta_list
self.security = security
self.password = password
self.number_template = number_template
self.name_prefix = name_prefix
self.test_duration = test_duration
self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port)
self.cx_profile = self.local_realm.new_generic_cx_profile()
self.cx_profile.type = type
self.cx_profile.dest = dest
self.cx_profile.interval = interval
# Station Profile init
def build(self):
self.station_profile.use_security(self.security, self.ssid, self.password)
self.station_profile.set_number_template(self.number_template)
print("Creating stations")
self.station_profile.set_command_flag("add_sta", "create_admin_down", 1)
self.station_profile.set_command_param("set_port", "report_timer", 1500)
self.station_profile.set_command_flag("set_port", "rpt_timer", 1)
self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug)
self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5)
self._pass("PASS: Station build finished")
def start(self, print_pass=False, print_fail=False):
self.station_profile.admin_up()
temp_stas = self.sta_list.copy()
temp_stas.append(self.upstream)
if self.local_realm.wait_for_ip(temp_stas):
self._pass("All stations got IPs", print_pass)
else:
self._fail("Stations failed to get IPs", print_fail)
exit(1)
cur_time = datetime.datetime.now()
passes = 0
expected_passes = 0
self.cx_profile.start_cx()
time.sleep(15)
end_time = self.local_realm.parse_time("30s") + cur_time
print("Starting Test...")
while cur_time < end_time:
cur_time = datetime.datetime.now()
gen_results = self.json_get("generic/list?fields=name,last+results", debug_=self.debug)
if gen_results['endpoints'] is not None:
for name in gen_results['endpoints']:
for k, v in name.items():
if v['name'] in self.cx_profile.created_endp and not v['name'].endswith('1'):
expected_passes += 1
if v['last results'] != "" and "Unreachable" not in v['last results']:
passes += 1
else:
self._fail("%s Failed to ping %s " % (v['name'], self.cx_profile.dest), print_fail)
break
time.sleep(1)
if passes == expected_passes:
self._pass("PASS: All tests passed", print_pass)
64 bytes from 10.40.0.1: icmp_seq=1 time=4.55 ms *** drop: 0 (0, 0.000) rx: 1 fail: 0 bytes: 64Results can also be seen in the generic tab in the LANforge Manager: Double-clicking on an endpoint will allow you to see more specific results as well as the command used by the endpoint. Using the sync button will allow you to see updated results.
Using the realm.py library we will write a script that will allow us to automate the creation of VAPs. Requires LANforge 5.4.2 | |
vap_profile.vap_name = "TestNet"
vap_profile.use_security(security_type, ssid, passwd)
is the preferred method to use when setting the security type, ssid, and password variables. Available security types are wpa, wpa2, wpa3, wep, and open.vap_profile.use_security(type="wpa2", ssid="testNet", passwd="testPass")
vap_profile.mode = 1
vap_profile.create(resource=1, radio="wiphy0", channel=36, up_=True)
key_mgmt="WPA-EAP"We can then use these variables to call the set_wifi_extra() method
eap="TTLS"
identity="testuser"
passwd="testpasswd"
realm="localhost.localdomain"
domain="localhost.localdomain"
hessid="00:00:00:00:00:01"
vap_profile.set_wifi_extra(key_mgmt, eap, identity, passwd, realm, domain, hessid)
This cookbook will demonstrate how we can use json to load DB scenarios and control test groups using python. We will be referencing the script scenario.py. Requires LANforge 5.4.2. | |
Some scripts in the LANforge library have a monitor function built in. We are going to be using the test_ipv4_variable_time script for this demonstration. This is useful for running a test and then analyzing the results afterwards. |
|
git clone https://github.com/greearb/lanforge-scripts
into your terminal.
./test_ipv4_variable_time.py --radio wiphy0 --security wpa2 --ssid lanforge --password password --output_format csv
Some scripts in the LANforge library have a monitor function built in. We are going to be using the test_ipv4_variable_time script for this demonstration. This is useful for running a test and then analyzing the results afterwards. |
|
git clone https://github.com/greearb/lanforge-scripts
into your terminal.
./test_ipv4_variable_time.py --radio wiphy0 --security wpa2 --ssid lanforge --password password --output_format excel
This cookbook will demonstrate the proper method for documentation in Candelatech created test scripts using PEP 257 guidelines.
Any docstrings occurring after the attribute docstring will be referred to as “additional docstrings“. Docstrings in Python are defined as a string literal that is the first statement in a module, function, class or method definition. Such string literals are referred to as “attribute docstrings” and will become the __doc__ attribute of the module, function, class, or method in which they are used.
PEP 257 establishes a standard for docstring usage. In order to keep consistency, triple double quotes should be used for all docstrings. Single-line docstrings should be contained entirely on one line. In the example given, a docstring for a function should briefly describe its purpose and specify the return type.
Example taken from PEP 257 page:def function(a, b): """Do X and return a list."""
Multi-line docstrings should consist of a brief one line summary, followed by a blank line, and finally followed by a more elaborate description. The summary line may either be inline with the opening quotes or on the next line and the whole docstring should be on the same line of indentation as the opening and closing quotations. Closing quotes should exist on their own line, if part of a multi-line docstring, to prevent confusion.
Example taken from PEP 257 page:def complex(real=0.0, imag=0.0): """Form a complex number. Keyword arguments: real -- the real part (default 0.0) imag -- the imaginary part (default 0.0) """ if imag == 0.0 and real == 0.0: return complex_zero ...
Candelatech scripts will follow PEP 257 specifications for module, function, class or method definitions. To keep things standardized the following example will cover the preferred format for module level docstrings.
#!/usr/bin/env python3 """Module overview/one line description More detailed summary of module, elaborate on when to use module/ test coverage of full script (Pass/ Fail conditions, columns/ information tested) External scenario requirements: Cookbook: http://www.candelatech.com/cookbook.php?vol=cli&book=_______ Copyright 2021 Candela Technologies Inc License: Free to distribute and modify. LANforge systems must be licensed. """
We will learn how to use a script to create a station and scan for available APs. We will then look at the /scanresults/ URI and the info we can get from a scan through JSON. Please refer to sta_scan_test.py as an example script. | |
--sta_name nameOfStationSpecifies the name of the station to be created, if this option is used, the name will default to sta0000.
--ssid nameOfNetworkSpecifies the name of the network to connect to.
--security {WEP, WPA, WPA2, WPA3, Open}Specifies the security type of the network to connect to.
./sta_scan_test.py --sta_name sta0000 --ssid fake_ssid --security open --radio wiphy0
BSS Signal SSIDThis script produces limited output, for more detail we can look at the webpage hosted by LANforge.
08:36:c9:e3:d4:da -32.0 Logan-Test-Net
10:56:11:0c:04:02 -80.0 :)
22:56:11:0c:04:02 -79.0 xfinitywifi
32:56:11:0c:04:02 -80.0 NA
{"handler":"candela.lanforge.HttpStationScan$FixedJsonResponder","uri":"
scanresults/:shelf_id/:resource_id/:port_id","candela.lanforge.HttpStationScan":
{"duration":"1"},"scan-results":[{"1.1.4.08:36:c9:e3:d4:da":{"age":"2238","auth":"WPA2",
"beacon":"200","bss":"08:36:c9:e3:d4:da","channel":"44","entity id":"1.1.4",
"frequency":"5220","info":"3x3 MCS 0-9 AC","signal":"-32.0","ssid":"Logan-Test-Net"}}]}
data = {
"shelf": 1,
"resource": 1,
"port": self.sta_list
}
self.json_post("/cli-json/scan_wifi", data)
time.sleep(15)
scan_results = self.json_get("scanresults/1/1/%s" % ','.join(self.sta_list))
print("{0:<23}".format("BSS"), "{0:<7}".format("Signal"), "{0:<5}".format("SSID"))
for result in scan_results['scan-results']:
for name, info in result.items():
print("%s\t%s\t%s" % (info['bss'], info['signal'], info['ssid']))
def start(self):
self.station_profile.admin_up()
print(self.sta_list)
print("Sleeping 15s while waiting for scan")
data = {
"shelf": 1,
"resource": 1,
"port": self.sta_list
}
self.json_post("/cli-json/scan_wifi", data)
time.sleep(15)
scan_results = self.json_get("scanresults/1/1/%s" % ','.join(self.sta_list))
print("{0:<23}".format("BSS"), "{0:<7}".format("Signal"), "{0:<5}".format("SSID"))
for result in scan_results['scan-results']:
for name, info in result.items():
print("%s\t%s\t%s" % (info['bss'], info['signal'], info['ssid']))
BSS Signal SSID
00:0e:8e:52:4e:82 -33.0 test-net
08:36:c9:e3:d4:db -31.0 Logan-Test-Net
08:36:c9:e3:d4:dc -27.0 Logan-Test-Net
We will learn how to use a script to probe a port for more information.We will also look at the ouput from the GUI, JSON response, and the script itself. Use the port_probe.py script as a reference. | |
--port_eid portEIDSpecifies the eid of the port to be probed, if this option is used, the name will default to 1.1.eth0.
./sta_probe_test.py --port_eid 1.1.wlan1This example will probe the existing wlan1 port
The LANforge CICD framework provides an ability to execute a suite of tests and report results. |
|
test_rig.json { "test_rig":{ "Notes":[ "This JSON file describes LANforge system and test run configuration" ] }, "test_rig_parameters":{ "TEST_BED": "CT-TEST-001", "TEST_RIG": "CT-TEST-001", "DATABASE_SQLITE": "./tools/qa_sqlite3.db", "LF_MGR_IP": "192.168.100.116", "LF_MGR_PORT": "8080", "LF_MGR_USER": "lanforge", "LF_MGR_PASS": "lanforge", "UPSTREAM_PORT":"1.1.eth2", "TEST_TIMEOUT": 600, "EMAIL_LIST_PRODUCTION": "support@candelatech.com", "EMAIL_LIST_TEST": "support@candelatech.com", "EMAIL_TITLE_TXT": "Lanforge QA Testing", "EMAIL_TXT": "Lanforge QA Testing" } }
ct_AX88U_dut.json { "ct_AX88U_dut":{ "Notes":[ "The device undertest configuration is contained in this file" ] }, "test_dut":{ "DUT_SET_NAME": "DUT_NAME ASUSRT-AX88U", "USE_DUT_NAME": "ASUSRT-AX88U", "wireless_network_dict":{ "ssid_idx=0":{"ssid_idx":"0","SSID_USED":"asus11ax-2","SSID_PW_USED":"hello123","BSSID":"3c:7c:3f:55:4d:60","SECURITY_USED":"wpa2"}, "ssid_idx=1":{"ssid_idx":"1","SSID_USED":"asus11ax-5","SSID_PW_USED":"hello123","BSSID":"3c:7c:3f:55:4d:64","SECURITY_USED":"wpa2"} } } }
ct_tests.json { "ct_tests_001":{ "Notes":[ "This JSON file describes tests to be run by LANforge system" ] }, "test_suites":{ "suite_wc":{ "create_chamberview_dut_wc":{ "enabled":"TRUE", "load_db":"skip", "command":"create_chamberview_dut.py", "args":"", "args_list":[ " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", " --ssid 'ssid_idx=0 ssid=SSID_USED security=SECURITY_USED password=SSID_PW_USED bssid=BSSID'", " --ssid 'ssid_idx=1 ssid=SSID_USED security=SECURITY_USED password=SSID_PW_USED bssid=BSSID'", " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" ] }, "create_chamberview_wc":{ "enabled":"TRUE", "load_db":"skip", "command":"create_chamberview.py", "args":"", "args_list":[ " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", " --create_scenario scenario_wpa2_wc", " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" ] }, "wifi_capacity":{ "enabled":"TRUE", "timeout":"600", "iterations":"1", "load_db":"skip", "command":"lf_wifi_capacity_test.py", "args":"", "args_list":[ " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user LF_MGR_USER --lf_password LF_MGR_PASS --instance_name scenario_wpa2_wc", " --upstream UPSTREAM_PORT --batch_size 1,10,19 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'wpa2_wc'", " --test_rig TEST_RIG", " --set DUT_SET_NAME" ] }, "lf_qa":{ "enabled":"TRUE", "timeout":"600", "load_db":"skip", "command":"./tools/lf_qa.py", "args":"", "args_list":[ " --path REPORT_PATH --store --png --database DATABASE_SQLITE" ] } } } }
./lf_check.py --json_rig ct_test_rig.json \ --json_dut ct_AX88U_dut.json \ --json_test ct_tests.json \ --suite "suite_wc" \ --path '/home/lanforge/html-reports/ct_results_directory'
./lf_check.py --json_rig ct_test_rig.json \ --json_dut ct_AX88U_dut.json \ --json_test ct_tests.json \ --suite "suite_wc" \ --path '/home/lanforge/html-reports/ct_results_directory'
./lf_qa.py --path /home/lanforge/html-reports/ct_results_directory/(results dir of lf_check.py)\
--store \
--png \
--database ./tools/qa_aqlite3.db
"enabled":"TRUE"Allows for individual test enable and disable of the test.
"load_db":"CUSTOM_DATABASE"Allows for loading a LANforge database prior to the test run.
"timeout":"300"Allows for test to have individual timeout other then default.
"iterations":"2"Allows for test to run multiple iterations.
Each LANforge system has Python scripts preinstalled at /home/lanforge/scripts to configure the LANforge and run Traffic Emulation. Goal is to execute sta_connect2.py, one of the pre-installed python scripts located at /home/lanforge/scripts/py-script/sta_connect2.py The Script sta_connect2.py will create a station, create TCP and UDP traffic, run traffic for a short amount of time, and verify whether traffic was sent and received. It also verifies the station connected to the requested BSSID if bssid is specified as an argument. The script will clean up the station and connections at the end of the test. An html and pdf or the results will be generated and placed in /home/lanforge/html-reports directory The script will clean up the station and connections at the end of the test. |
|
Start the LANforgeGUI if GUI not running:
To start the LANforgeGUI navigate to : /home/lanforge/LANforgeGUI_5.4.5
Execute : ./lfclient.bash
Select 'Connect' to connect to: localhost:4002 Local Machine Address
Where Do I Find Scripts?
Preinstalled Python Scripts Location on LANforge: /home/lanforge/scripts/py-scripts
Example script sta_connect2.py location: /home/lanforge/scripts/py-scripts/sta_connect2.py
Initial Information to gather as input to sta_connect2.py script:
Note: An example of a Device Under Test is an Access Point
The DUT information is used in report generation. The DUT information may be optional.
Example Command for sta_connect2.py:
./sta_connect2.py --mgr localhost --upstream_port 1.1.eth2
--radio 1.1.wiphy1 --dut_ssid axe11000_5g --dut_passwd lf_axe11000_5g --dut_security wpa2
--dut_model_num GT-AXE11000 --dut_hw_version 1.0 --dut_sw_version V3.0.0.4.386.0000
--dut_serial_num M32A
Results for sta_connect2.py located in /home/lanforge/html-reports:
Results for sta_connect2.py located in /home/lanforge/html-reports:
Script produces both html and pdf results
Sample sta_connect2.py Script HTML Output: example of html output
Sample sta_connect2.py Script pdf Output: example of pdf output
Other script options may be shown by typing ./sta_connect2.py --help
Each LANforge system has Python scripts installed at /home/lanforge/scripts. You can find it at /home/lanforge/scripts/py-script/test_l3.py The script test_l3.py will:
The traffic prioritization is configurable:
The upload and download statistsics are recorded at the end of each polling interval. The script will verify whether traffic is sent and received. The script cleans up the station and connections at the end of the test. An HTML and PDF report of the results will be generated and placed in the /home/lanforge/html-reports directory. |
|
./test_l3.py --lfmgr 192.168.0.103 \
--upstream_port 1.1.eth2 \
--endp_type 'lf_udp,lf_tcp' \
--tos 'BK,VI' \
--side_a_min_bps 256000 \
--side_b_min_bps 102400000 \
--radio 'radio==wiphy1 stations==1 ssid==asus_5g ssid_pw==lf_asus_5g security==wpa2' \
--test_duration 30s \
--polling_interval 5s \
--test_rig CT_LAB_104 \
--test_tag Layer_3_Example \
--dut_model_num RT-AX88U \
--dut_hw_version A1.1
--dut_sw_version 3.0.0.4.384 \
--dut_serial_num M1IAHP000003
Each LANforge system has Python scripts installed at /home/lanforge/scripts. You can find test_l4.py at /home/lanforge/scripts/py-script/test_l4.py The script test_l4.py will:
The test type attribute is configurable:
The monitored Layer 4-7 attribute statistics are recorded at the end of each polling interval. Test_l4.py will monitor the urls/s, bytes-rd, or bytes-wr attribute of the layer 4-7 endpoints. These attributes can be tested over FTP using a --ftp flag. If the monitored value does not continually increase, this test will not pass. The script cleans up the stations and connections at the end of the test. An HTML and PDF report of the results will be generated and placed in the /home/lanforge/html-reports directory. |
|
./test_l4.py --lfmgr localhost \
--upstream_port 1.1.eth1 \
--radio 1.1.wiphy0 \
--num_stations 2 \
--ssid AP_SSID \
--security wpa2 \
--passwd password \
--test_duration 1m \
--url "dl http://upstream_port_ip /dev/null" \
--test_type bytes-rd \
--requests_per_ten 600 \
--test_rig CT_LAB_L4 \
--test_tag Layer_4_Example \
--dut_model_num RT-AX88U \
--dut_hw_version A1.1 \
--dut_sw_version 3.0.0.4.384 \
--dut_serial_num M1IAHP000003
./test_l4.py --lfmgr localhost \
--upstream_port 1.1.eth1 \
--radio 1.1.wiphy0 \
--num_stations 2 \
--ssid AP_SSID \
--security wpa2 \
--passwd password \
--test_duration 1m \
--url "dl http://upstream_port_ip /dev/null" \
--test_type urls \
--requests_per_ten 600 \
--test_rig CT_LAB_L4 \
--test_tag Layer_4_Example \
--dut_model_num RT-AX88U \
--dut_hw_version A1.1 \
--dut_sw_version 3.0.0.4.384 \
--dut_serial_num M1IAHP000003
Each LANforge system has Python scripts installed at /home/lanforge/scripts. You can find test_l4.py at /home/lanforge/scripts/py-script/test_l4.py The script test_l4.py will:
The test type attribute is configurable:
The monitored Layer 4-7 attribute statistics are recorded at the end of each polling interval. Test_l4.py will monitor the urls/s, bytes-rd, or bytes-wr attribute of the layer 4-7 endpoints. These attributes can be tested over FTP using a --ftp flag. If the monitored value does not continually increase, this test will not pass. The script cleans up the stations and connections at the end of the test. An HTML and PDF report of the results will be generated and placed in the /home/lanforge/html-reports directory. |
|
./test_l4.py --lfmgr localhost \
--upstream_port 1.1.eth1 \
--radio 1.1.wiphy0 \
--num_stations 2 \
--ssid AP_SSID \
--security wpa2 \
--passwd password \
--test_duration 1m \
--ftp \
--url "dl ftp://upstream_port_ip /dev/null" \
--test_type bytes-rd \
--requests_per_ten 600 \
--test_rig CT_LAB_L4 \
--test_tag Layer_4_Example \
--dut_model_num RT-AX88U \
--dut_hw_version A1.1 \
--dut_sw_version 3.0.0.4.384 \
--dut_serial_num M1IAHP000003
./test_l4.py --lfmgr localhost \
--upstream_port 1.1.eth1 \
--radio 1.1.wiphy0 \
--num_stations 2 \
--ssid AP_SSID \
--security wpa2 \
--passwd password \
--test_duration 1m \
--ftp \
--url "dl ftp://upstream_port_ip /dev/null" \
--test_type urls \
--requests_per_ten 600 \
--test_rig CT_LAB_L4 \
--test_tag Layer_4_Example \
--dut_model_num RT-AX88U \
--dut_hw_version A1.1 \
--dut_sw_version 3.0.0.4.384 \
--dut_serial_num M1IAHP000003
This cookbook describes how to edit a bash script, cv_dataplane_script.sh, that executes the 'Create Chamber DUT', 'Create Chamberview' and the 'Dataplane' test python scripts (create_chamberview_dut.py, create_chamberview.py, lf_dataplane_test.py). These 3 python scripts are broken up into sections within this one bash script, that have their own arguments passed into each python script. The python scripts will run in consecutive order within the bash script and the LANforge GUI will reflect when each python script runs. Requires LANforge 5.4.2. | |
It is possible to configure a Nginx proxy in a manner to allow remote REST clients access to multiple isolated LANforge systems. This leverages the proxy_pass feature in Nginx. There are multiple ways to configure proxy access.
For the example below, we will assume these values:
public proxy hostname is bizproxy, 10.39.0.44
bizproxy is running Nginx
Isolated LAN with LF machines: 192.168.92.0/24
Example LANforge machines:
192.168.92.10 ct523-jedway1
192.168.92.11 ct522-jedway3
the LANforge machines need to have GUIs configured to start automatically
The HTTP library that the LANforge GUI incorporates is very simple. It is not configured to parse Host: headers. There is no need to rewrite the Host header when proxying to port 8080.
Proxying to Apache on LANforge (mgt_ip, port 80) is different. If you want to proxy requests to a LF Apache instance on port 80, you should incorporate Host header rewriting. (No examples below, sorry.)
Three ways of making proxy requests include:
Port Rewriting. Works best with our python libraries.
Hostname Rewriting, more difficult, but still works with python libraries.
URL (path-name) Rewriting: this does NOT work well with our python libraries.
This manner of proxying just translates different server listening ports to the target machines. It is another easy transformation, but it opens up quite a number of high-numbered ports on bizproxy.
Nginx config:
server {
listen 1910;
server_name _;
root /usr/share/nginx/html;
location / {
rewrite /(.*) /$1 break;
proxy_pass http://192.168.92.10:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
server {
listen 1911;
server_name _;
root /usr/share/nginx/html;
location / {
rewrite /(.*) /$1 break;
proxy_pass http://192.168.92.11:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Use curl to test access:
curl -sqv -H 'Accept: application/html' http://bizproxy:1910/port/1/1/list
Example script usage:
./scenario.py --mgr bizproxy --mgr_port 1910 \
--load BLANK --action overwrite
It is possible to rewrite hostnames and host headers to isolated LF systems. This is complicated rewrite because the DNS names need to be present at the developer’s workstation. (It is unlikely that the the headers in the HTTP request can be manipulated to add the Host header.) Ideally, the non-isolated LAN DNS can be configured to return the return the IP of bizproxy.corp.me when hostnames like ct523-jedway1.bizproxy.corp.me are requested.
On the developer workstation, this is possible with extra effort on the user side by manipulating the /etc/hosts
file on a workstation:
# etc/hosts
10.39.0.44 ct523-jedway1.bizproxy.corp.me ct523-jedway1
Nginx config:
server {
listen 80;
server_name ct523-jedway1;
root /usr/share/nginx/html;
location / {
rewrite /(.*) /$1 break;
proxy_pass http://192.168.92.10:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Check the URL access using curl:
# check by IP:
$ curl -sqv \
-H 'Host: ct523-jedway1' \
-H 'Accept: application/json' \
http://10.39.0.44/port/1/1/list
# check by hostname
$ curl -sqv \
-H 'Accept: application/json' \
http://ct523-jedway1.bizproxy.corp.me/port/1/1/list
Example script usage:
./scenario.py --mgr ct523-jedway1 --mgr_port 80 \
--load BLANK --action overwrite
The bizproxy logs should be located in /var/log/nginx
. In LF 5.4.6, the GUI can send messages to syslog. Messages from the GUI would look like:
1685573102952: ip[192.168.92.1] sess[] GET url[/port/1/1/list]
URL Rewriting is mentioned here so the reader can understand what not to configure.
Below is an example permitting REST access to LF hosts by way of a URL prefix. For example, the URL http://bizproxy/92.11/port/1/1/list becomes the URL http://192.168.92.11:8080/port/1/1/list . This is not the best kind of proxy rewriting, but it is the easiest. Using a URL prefix is less ideal because it inherently conflicts with the LANforge python libraries provided.
Nginx config:
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location /92.10 {
rewrite /92.10/(.*) /$1 break;
proxy_pass http://192.168.92.10:8080;
proxy_redirect off;
proxy_set_header Host biz_lflab5_9210;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
location /92.11 {
rewrite /92.11/(.*) /$1 break;
proxy_pass http://192.168.92.11:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Use curl to query the REST endpoint:
$ curl -sqv -H 'Accept: application/json' http://bizproxy/92.10/port/1/1/list
This is not compatible with the py-scripts library.