-
Notifications
You must be signed in to change notification settings - Fork 7
Lab1 Hints
You can setup a switch that has some errors by creating an "unreliable_switch" in your playground networking.
pnetworking add bad_sw1 unreliable_switch local 8888
pnetworking enable bad_sw1
pnetworking disable eth0
pnetworking config eth0 connect bad_sw1
pnetworking enable eth0
pnetworking query bad_sw1 get-error-rate
Please note, there is an error in the pnetworking code right now where it won't show you the unreliable switch's address and port in the pnetworking status output. However, you will see that your vnic reports being connected to the correct location. So you should be able to verify that you set it up correctly.
Also note the new utility "query". This allows you to query certain devices (and configure them) while they're up and running. The "config" utility, by contrast, only allows changes while the device is inactive. The query here returns the current error rate of the unreliable switch. By default, it should report:
Response: Errors per Bytes = (1, 102400)
You can also send the query "verbs" to interrogate a device to find out what real-time queries and configurations it supports.
pnetworking query bad_sw1 verbs
Response: verbs, all-log-levels, get-log-level, set-log-level, get-error-rate, set-error-rate, get-delay-rate, set-delay-rate
You can set the error rate using the "set-error-rate" query verb like so:
pnetworking query bad_sw1 set-error-rate 2 102400
This will set the error rate to 2 bytes in 102400.
I have uploaded a throughput testing file to the class repository under src/test/ThroughputTester.py. What it does is connect to your local VNIC (you must have your networking enabled), creates a client and server, and transmits increasingly larger sequences of random bytes. The throughput tester does NOT change the error rate of your switch. If you want to test it without errors, either use a reliable switch or set your unreliable switch's error rate to 0.
The throughput testing utility is designed to operate on two different stacks, although you can, and probably will, just use one (your own) for initial testing purposes. But keep this in mind or the command line parameters won't make sense.
The utility takes three important arguments.
- The first argument is either "client" or "server". This parameter indicates which side of the communication is under test. If you're using the same stack on both sides, this parameter doesn't matter.
- The second argument is "--testing-stack=". This parameter specifies the testing stack
- The third argument is "--reference-stack=". From the perspective of the tester, this stack is supposed to be correct
In actual practice, if you're just testing your own protocol, you pass the same protocol to both "--testing-stack" and "--reference-stack" and "client" or "server" doesn't matter. Suppose you call your stack "ripp"
python ThroughputTester.py server --testing-stack=ripp --reference-stack=ripp
On the other hand, if you wanted to test interoperability with a friend, you could have them send you their module (just dot pyc files in order to prevent code sharing), and you could test each other. Name one stack ripp_me, and ripp_friend and then test in both directions
python ThroughputTester.py client --testing-stack=ripp_friend --reference-stack=ripp_me
python ThroughputTester.py server --testing-stack=ripp_friend --reference-stack=ripp_me
Testing both ways is important because your protocol should do the SYN in one set of tests and your colleague's protocol should do the SYN in others.
Please note, you can register the same connector under multiple names. So, after installing your friend's module under .playground/connectors, you could edit their init.py file and add a new line with an additional setConnector() call. This allows you to have modules that don't have collisions in the namespace.
The ThroughputTester has a debug line that has been commented out. If you're getting a lot of failures, you can turn this on. It will report if packets are having trouble being deserialized, which typically indicates that your ripp layer is passing up the wrong data in data_received.
You'll find that, as soon as you turn on errors on the switch, your protocol starts failing. Here are a few very important hints that can help
FIRST, do NOT use an infinite send window size. Instead, use a backlog. Pick some send window size (I use 16), and as soon as you have more than 16 packets in the window, store all remaining bytes in a variable like "backlog". As your window gets cleared out, take more data out of the backlog.
Why? Because, if you are SLAMMING your peer with thousands of packets, if one gets dropped, it still has to process all the thousands incoming packets. Even if you are set to resend the missed packet on timeout, that packet is being received at the end of a very long queue. It can cause some significant latency and delays that can appear to be a lack of progress.
If you decide a backlog is too complicated, or you really want an infinite send window, you MUST not send all of your packets in a tight loop. Suppose your MTU is 1500 and your're sending 100MB. That's a LOT of packets. Do not send them all in a while loop. Send a packet and then do a callback
SECOND, If you find yourself creating multiple timers for different functions, merge them into a single "maintenance" function that is called regularly. This is easier to debug and manage
THIRD, If you're allowing bad data to go up to the application layer, you may not see an error unless certain debug statements are on. During packet deserialization, if something is wrong, it just silently drops the bad bytes and starts over. So, if you ripp layer calls higher_protocol().data_received(pkt.data) and the data is bad, you may not see any visible errors. This is when you would turn on the debug statement in the tester and watch for deserialization errors.