Voip.ms latency measurement, Part 2¶
Contents¶
Indices and tables¶
Background¶
This week, the goal is to ping the voip servers. To learn about ping, open up Konsole and type man ping. You can try to ping the local host as an example, ping -c 4 localhost
However, you won’t be able to ping externally (on a Wescam asset) due to the firewall. So, we will use a web server to do the pinging for us.
The server is at http://ec2-35-168-148-68.compute-1.amazonaws.com/, to see an example return try querying it with a host like python.org
Exercise¶
For this week, the goal is to create a Ping Request class
that will do the following:
It should store the ip or dns address of the server.
It should contain a method to send a ping request:
The method should accept a parameter that is the host name to ping
The method should send a http GET request to the server
- This request should only include the desired ping target, in our case the host name
The method should check that the server response has a status of 200
The method should return the server response.
This will require using the requests
library again. You will likely want to use requests.get()
On completion of this exercise, you should be able to run your stub program and have it do the following:
- Print a list of voip.ms server host names
- For each host name, print the response recieved from the server after sending a request with the host name.
Hints¶
Here is the class definition, as well as signatures for the constructor and function to send the http requests
class PingRequest:
"""
Class to deal with the web server. For now this just store the connection object, it
will make more sense in the final version
"""
def __init__(self, address):
"""
Constructor. Open the connection on ip:port
:param address: The IP or DNS address of the server to connect to
"""
def ping_request(self, host_name):
"""
Send an http GET request on the provided connection to ping the specified host.
:param host_name: Host name or IP address to ping
:return: The response from the ping
"""
For reference, the code running on the web server is below:
#!/usr/bin/env python3
# The code that will go in the cloud
import subprocess
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
import re
import json
from enum import Enum
class Access(Enum):
"""
Enum for clarity of accessing the previous pings
"""
last_time = 0
last_value = 1
class PingServer(BaseHTTPRequestHandler):
"""
Server class, accepts get requests and pings the host name within
"""
pings = dict()
target = re.compile("time=[0-9]*\.?[0-9]*")
# GET request
def do_GET(self):
# Send response status code
self.send_response(200)
# Send headers
self.send_header('Content-type', 'text/html')
self.end_headers()
# Send message to client
address = self.requestline.split()
# /? is added by requests when it makes the "GET" request
message = self.ping_target(address[1].strip('/?'))
# Need to encode to bytes before trying to write out
self.wfile.write(json.dumps(message).encode("utf8"))
return
def ping_target(self, host_name):
"""
Ping an address and return the response. If it has been pinged in the last
second return the stored value. Otherwise make a new ping
:param host_name: Hostname to ping
:return: JSON result or the error, as bytes
"""
try:
# This will wait up to 1 second for the ping response.
now = time.time()
if host_name in self.pings:
# Setting the interval to 1 second for now
if now - self.pings[host_name][Access.last_time.value] < 1:
# Return the existing value
return self.pings[host_name][Access.last_value.value]
# In other cases do the ping again, and save (time, result)
result = subprocess.check_output(["ping", "-c 1", "{}".format(host_name)],
timeout=1)
# Extract the ping time and create a json structure
res = self.target.search(result.decode("utf-8"))
if res is None:
ping_result = {'status': 'failed', 'host': host_name, 'ping': None}
else:
this_ping = float(res.group(0).split("=")[1])
ping_result = {'status': 'success', 'host': host_name, 'ping': this_ping}
now = time.time()
self.pings[host_name] = (now, ping_result)
return ping_result
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err:
# Don't throw, return the error to the caller
return {'status': 'error', 'host': host_name, 'ping': str(err)}
def run(port=80):
"""
Method to start up the server on the specified port. Once this is called you need to
use a keyboard interrupt to stop the server (ctrl+c on most systems)
:param int port: The port to run the server on. Defaults to 80
"""
print('Server starting up...')
print("Port set as {}".format(port))
# Server settings, if changing the port use a large number to avoid issues.
# You will also need to change ec2 security to allow incoming requests on the port
server_address = ("0.0.0.0", port)
httpd = HTTPServer(server_address, PingServer)
print('Server running...')
httpd.serve_forever()
if __name__ == '__main__':
# Run the server as a script
try:
run()
except KeyboardInterrupt:
print("Keyboard interrupt received, shutting down.")
- The server is located at IP: 35.168.148.68 or DNS: ec2-35-168-148-68.compute-1.amazonaws.com. It is on the http port (80), which means you don’t have to worry about including the number.
- You can provided requests with either the IP or DNS address, however using the DNS can be significantly slower to access. (Give it a try!)
- The server response will be a json object, you will need to convert it.