
# mPlane Protocol Reference Implementation
# scamper probe component code
#
# (c) 2013-2014 mPlane Consortium (http://www.ict-mplane.eu)
#               Author: Korian Edeline <korian.edeline@ulg.ac.be>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""
Implements Tracebox for integration into 
the mPlane reference implementation.

integrated tools:
    -  dealias                         NO OUTPUT
    -  neighbourdisc                   NO OUTPUT
    -  ping                            OK
    -  trace                           OK
    -  tracelb                         OK
    -  sting                           MAC OS ONLY
    -  tbit                            MAC OS ONLY
    -  tracebox                        OK

"""

import re
import ipaddress
import threading
import subprocess
import collections
from time import sleep
from datetime import datetime, timedelta
from ipaddress import ip_address
from socket import socket
import ssl
import mplane.model
import mplane.scheduler
import mplane.httpsrv
import tornado.web
import tornado.ioloop
import argparse
import json
from urllib3 import HTTPSConnectionPool
from urllib3 import HTTPConnectionPool


DEFAULT_IP4_NET = "192.168.1.0/24"
DEFAULT_SUPERVISOR_IP4 = '127.0.0.1'
DEFAULT_SUPERVISOR_PORT = 8888
REGISTRATION_PATH = "register/capability"
SPECIFICATION_PATH = "show/specification"
RESULT_PATH = "register/result"

DUMMY_DN = "Dummy.Distinguished.Name"
LOOP4 = "127.0.0.1"
LOOP6 = "::1"
_scampercmd = ["scamper", "-c"]
_scamper_dip = "-i"

###############################################################################
#################################  ARGUMENTS  #################################
###############################################################################

_traceboxcmd = _scampercmd + ["tracebox"]
_traceboxopt_v6 = "-6"

_traceboxopt_udp = "-u"
_traceboxopt_dport = "-d"
_traceboxopt_probe = "-p"
_traceboxopt_ipl = "-t" # icmp payload length

_pingcmd = _scampercmd + ["ping"]
_pingopt_period   = "-i" # -
_pingopt_payload  = "-B"
_pingopt_count    = "-c" # -
_pingopt_icmp_sum = "-C"
_pingopt_dport    = "-d"
_pingopt_sport    = "-F" # default
_pingopt_ttl      = "-m" # default 64
_pingopt_rcount   = "-o" # default all == 0
_pingopt_pattern  = "-p" # default 0
_pingopt_method   = "-P" # default icmp-echo 
_pingopt_RR       = "-R"
_pingopt_size     = "-s" # default v4:84 v6:56
_pingopt_source   = "-S" # -
_pingopt_tos      = "-z" # default 0x0

_tracecmd = _scampercmd + ["trace"]

_traceopt_confidence = "-c"
_traceopt_dport      = "-d"
_traceopt_firsthop   = "-f"
_traceopt_gaplimit   = "-g"
_traceopt_gapaction  = "-G"
_traceopt_maxttl     = "-m"
_traceopt_M          = "-M"
_traceopt_loops      = "-l"
_traceopt_loopaction = "-L"
_traceopt_payload    = "-p"
_traceopt_method     = "-P"
_traceopt_attempts   = "-q"
_traceopt_Q          = "-Q"
_traceopt_sport      = "-s"
_traceopt_srcaddr    = "-S"
_traceopt_tos        = "-t"
_traceopt_T          = "-T"
_traceopt_userid     = "-U"
_traceopt_wait       = "-w"
_traceopt_waitprobe  = "-W"
_traceopt_gssentry   = "-z"
_traceopt_lssname    = "-Z"

_dealiascmd = _scampercmd + ["dealias"]

_dealiasopt_dport       = "-d"
_dealiasopt_fudge       = "-f"
_dealiasopt_method      = "-m"
_dealiasopt_replyc      = "-o"
_dealiasopt_option      = "-O"
_dealiasopt_probedef    = "-p"
_dealiasopt_attempts    = "-q"
_dealiasopt_waitround   = "-r"
_dealiasopt_sport       = "-s"
_dealiasopt_t           = "-t"
_dealiasopt_userid      = "-U"
_dealiasopt_waittimeout = "-w"
_dealiasopt_waitprobe   = "-W"
_dealiasopt_exclude     = "-x"

_neighbourdisccmd = _scampercmd + ["neighbourdisc"]

_neighbourdiscopt_F          = "-F"
_neighbourdiscopt_Q          = "-Q"
_neighbourdiscopt_interface  = "-i"
_neighbourdiscopt_replyc     = "-o"
_neighbourdiscopt_attempts   = "-q"
_neighbourdiscopt_wait       = "-w"

_tracelbcmd = _scampercmd + ["tracelb"]

_tracelbopt_confidence  = "-c"
_tracelbopt_dport       = "-d"
_tracelbopt_firsthop    = "-f"
_tracelbopt_gaplimit    = "-g"
_tracelbopt_method      = "-P"
_tracelbopt_attempts    = "-q"
_tracelbopt_maxprobec   = "-Q"
_tracelbopt_sport       = "-s"
_tracelbopt_tos         = "-t"
_tracelbopt_userid      = "-U"
_tracelbopt_waittimeout = "-w"
_tracelbopt_waitprobe   = "-W"

_stingcmd = _scampercmd + ["sting"]

_stingopt_count        = "-c"
_stingopt_dport        = "-d"
_stingopt_distribution = "-f"
_stingopt_request      = "-h"
_stingopt_hole         = "-H"
_stingopt_inter        = "-i"
_stingopt_mean         = "-m"
_stingopt_sport        = "-s"

_tbitcmd = _scampercmd + ["tbit"]

_tbitopt_type      = "-t"
_tbitopt_app       = "-p"
_tbitopt_dport     = "-d"
_tbitopt_sport     = "-s"
_tbitopt_mss       = "-m"
_tbitopt_mtu       = "-M"
_tbitopt_option    = "-O"
_tbitopt_ptbsrc    = "-P"
_tbitopt_srcaddr   = "-s"
_tbitopt_url       = "-u"


###############################################################################
#################################  PARSING  ###################################
###############################################################################

TraceboxValue = collections.namedtuple("TraceboxValue", ["addr", "modifs", "payload_len"])

def _detail_ipl(ipl):
    """
    return a detailed string
    """
    ipls = {"(full)": "Full packet", "(8B)" : "Layer 3 Header + First 8 L3 payload bytes", "(L3)" : "Layer 3 header", "(0)" : "Empty"}
    return ipls[ipl] if ipl in ipls else ipl

_pingline_re = re.compile("seq=(\d+)\s+\S+=(\d+)\s+time=([\d\.]+)\s+ms")

PingValue = collections.namedtuple("PingValue", ["time", "seq", "ttl", "usec"])

def _parse_ping_line(line):
    m = _pingline_re.search(line)
    if m is None:
        print(line)
        return None
    mg = m.groups()
    return PingValue(datetime.utcnow(), int(mg[0]), int(mg[1]), int(float(mg[2]) * 1000))

TraceValue = collections.namedtuple("TraceValue", ["addr", "rtt"])

def _parse_trace(tb_output):
    """
    returns list of tuple (intermediate.addr, intermediate.modifs)
    """
    tuples=[]

    for line in tb_output[2:]:
        pline=line.split()
        if len(pline)==4:
            tuples.append(TraceValue(pline[1], pline[2]))
        else:
            tuples.append(TraceValue("*", "NaN"))
    return tuples

###############################################################################
#################################  PROCESS  ###################################
###############################################################################


def _tracebox_process(sipaddr, dipaddr, v, udp=None, dport=None, probe=None, get_icmp_payload_len=None):
    tracebox_argv = list(_traceboxcmd)
    if v is 6:
        tracebox_argv[-1] += " "+_traceboxopt_v6
    if udp is not None:
        tracebox_argv[-1] += " "+_traceboxopt_udp
    if get_icmp_payload_len is not None:
        tracebox_argv[-1] += " "+_traceboxopt_ipl
    if dport is not None:
        tracebox_argv[-1] += " "+_traceboxopt_dport+" "+str(dport)
    if probe is not None:
        tracebox_argv[-1] += " "+_traceboxopt_probe+" "+str(probe)

    tracebox_argv += [_scamper_dip, str(dipaddr)]

    print("running " + " ".join(tracebox_argv))
    return subprocess.Popen(tracebox_argv, stdout=subprocess.PIPE)

def _tracebox4_process(sipaddr, dipaddr, v=4, udp=None, dport=None, probe=None, get_icmp_payload_len=None):
    return _tracebox_process(sipaddr, dipaddr, v, udp, dport, probe, get_icmp_payload_len)

def _tracebox6_process(sipaddr, dipaddr, v=6, udp=None, dport=None, probe=None, get_icmp_payload_len=None):
    return _tracebox_process(sipaddr, dipaddr, v, udp, dport, probe, get_icmp_payload_len)

def _ping_process(sipaddr, dipaddr, period, count, payload, chksum, dport, sport, ttl, pattern, method, rr, size, tos, rcount):
    ping_argv = list(_pingcmd)
    if period is not None:
        ping_argv[-1] += " "+_pingopt_period+" "+str(period)
    if count is not None:
        ping_argv[-1] += " "+_pingopt_count+" "+str(count)
    if payload is not None and payload is not "":
        ping_argv[-1] += " "+_pingopt_payload+" "+str(payload)
    if chksum is not None and chksum is not "":
        ping_argv[-1] += " "+_pingopt_icmp_sum+" "+str(chksum)
    if dport is not None and dport != -1:
        ping_argv[-1] += " "+_pingopt_dport+" "+str(dport)
    if sport is not None and sport != -1:
        ping_argv[-1] += " "+_pingopt_sport+" "+str(sport)
    if ttl is not None and ttl != -1:
        ping_argv[-1] += " "+_pingopt_ttl+" "+str(ttl)
    if rcount is not None and rcount != -1:
        ping_argv[-1] += " "+_pingopt_rcount+" "+str(rcount)
    if pattern is not None and pattern is not "":
        ping_argv[-1] += " "+_pingopt_pattern+" "+str(pattern)
    if method is not None and method is not "":
        ping_argv[-1] += " "+_pingopt_method+" "+str(method)
    if rr:
        ping_argv[-1] += " "+_pingopt_RR
    if size is not None:
        ping_argv[-1] += " "+_pingopt_size+" "+str(size)
    if tos is not None:
        ping_argv[-1] += " "+_pingopt_tos+" "+str(tos)

    ping_argv[-1] += " "+_pingopt_source+" "+str(sipaddr)

    ping_argv += [_scamper_dip, str(dipaddr)]

    print("running " + " ".join(ping_argv))

    return subprocess.Popen(ping_argv, stdout=subprocess.PIPE)

def _ping4_process(sipaddr, dipaddr, period=None, count=None):
    return _ping_process(sipaddr, dipaddr, period, count)

def _ping6_process(sipaddr, dipaddr, period=None, count=None):
    return _ping_process(sipaddr, dipaddr, period, count)

def pings_min_delay(pings):
    return min(map(lambda x: x.usec, pings))

def pings_mean_delay(pings):
    return int(sum(map(lambda x: x.usec, pings)) / len(pings))

def pings_median_delay(pings):
    return sorted(map(lambda x: x.usec, pings))[int(len(pings) / 2)]

def pings_max_delay(pings):
    return max(map(lambda x: x.usec, pings))

def pings_start_time(pings):
    return pings[0].time

def pings_end_time(pings):
    return pings[-1].time

def _trace_process(sipaddr, dipaddr, confidence, dport, firsthop, gaplimit, gapaction, maxttl, M, loops, loopaction, payload, method, attempts, Q, sport, srcaddr, tos, T, userid, wait, waitprobe, lssname):
    trace_argv = list(_tracecmd)

    if confidence is not None and confidence is not -1:
        trace_argv[-1] += " "+_traceopt_confidence+" "+str(confidence)
    if dport is not None and dport is not -1:
        trace_argv[-1] += " "+_traceopt_dport+" "+str(dport)
    if firsthop is not None and firsthop is not -1:
        trace_argv[-1] += " "+_traceopt_firsthop+" "+str(firsthop)
    if gaplimit is not None and gaplimit is not -1:
        trace_argv[-1] += " "+_traceopt_gaplimit+" "+str(gaplimit)
    if gapaction is not None and gapaction is not -1:
        trace_argv[-1] += " "+_traceopt_gapaction+" "+str(gapaction)
    if maxttl is not None and maxttl is not -1:
        trace_argv[-1] += " "+_traceopt_maxttl+" "+str(maxttl)
    if M:
        trace_argv[-1] += " "+_traceopt_M
    if loops is not None and loops is not -1:
        trace_argv[-1] += " "+_traceopt_loops+" "+str(loops)
    if loopaction is not None and loopaction is not -1:
        trace_argv[-1] += " "+_traceopt_loopaction+" "+str(loopaction)
    if payload is not None and payload is not "":
        trace_argv[-1] += " "+_traceopt_payload+" "+str(payload)
    if method is not None and method is not "":
        trace_argv[-1] += " "+_traceopt_method+" "+str(method)
    if attempts is not None and attempts is not -1:
        trace_argv[-1] += " "+_traceopt_attempts+" "+str(attempts)
    if Q:
        trace_argv[-1] += " "+_traceopt_Q
    if sport is not None and sport is not -1:
        trace_argv[-1] += " "+_traceopt_sport+" "+str(sport)
    if srcaddr:
        trace_argv[-1] += " "+_traceopt_srcaddr+" "+str(sipaddr)
    if tos is not None and tos is not -1:
        trace_argv[-1] += " "+_traceopt_tos+" "+str(tos)
    if T:
        trace_argv[-1] += " "+_traceopt_T
    if userid is not None and userid is not -1:
        trace_argv[-1] += " "+_traceopt_userid+" "+str(userid)
    if wait is not None and wait is not -1:
        trace_argv[-1] += " "+_traceopt_wait+" "+str(wait)
    if waitprobe is not None and waitprobe is not -1:
        trace_argv[-1] += " "+_traceopt_waitprobe+" "+str(waitprobe)
    #if gssentry is not None and gssentry is not "":
    #    trace_argv[-1] += " "+_traceopt_gssentry+" "+str(gssentry)
    if lssname is not None and lssname is not "":
        trace_argv[-1] += " "+_traceopt_lssname+" "+str(lssname)

    trace_argv += [_scamper_dip, str(dipaddr)]

    print("running " + " ".join(trace_argv))
    return subprocess.Popen(trace_argv, stdout=subprocess.PIPE)


def _tracelb_process(sipaddr, dipaddr, confidence, dport, firsthop, gaplimit,  method, attempts, maxprobec, sport, tos, userid, waittimeout, waitprobe):
    tracelb_argv = list(_tracelbcmd)

    if confidence is not None and confidence is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_confidence+" "+str(confidence)
    if dport is not None and dport is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_dport+" "+str(dport)
    if firsthop is not None and firsthop is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_firsthop+" "+str(firsthop)
    if gaplimit is not None and gaplimit is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_gaplimit+" "+str(gaplimit)
    if method is not None and method is not "":
        tracelb_argv[-1] += " "+_tracelbopt_method+" "+str(method)
    if attempts is not None and attempts is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_attempts+" "+str(attempts)
    if maxprobec is not None and maxprobec is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_maxprobec+" "+str(maxprobec)
    if sport is not None and sport is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_sport+" "+str(sport)
    if tos is not None and tos is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_tos+" "+str(tos)
    if userid is not None and userid is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_userid+" "+str(userid)
    if waittimeout is not None and waittimeout is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_waittimeout+" "+str(waittimeout)
    if waitprobe is not None and waitprobe is not -1:
        tracelb_argv[-1] += " "+_tracelbopt_waitprobe+" "+str(waitprobe)

    tracelb_argv += [_scamper_dip, str(dipaddr)]

    print("running " + " ".join(tracelb_argv))
    return subprocess.Popen(tracelb_argv, stdout=subprocess.PIPE)


###############################################################################
###############################  CAPABILITIES  ################################
###############################################################################

def tracebox4_standard_capability(ipaddr):
    cap = mplane.model.Capability(label="tracebox-standard-ip4", when = "now ... future")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")
    cap.add_result_column("tracebox.hop.ip4")
    cap.add_result_column("tracebox.hop.modifications")
    return cap

def tracebox4_specific_capability(ipaddr):
    #!!! do not set udp=1 with probe=IP/TCP/ 
    cap = mplane.model.Capability(label="tracebox-specific-ip4", when = "now ... future")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")
    cap.add_parameter("tracebox.udp")
    cap.add_parameter("tracebox.dport")
    cap.add_parameter("tracebox.probe")
    cap.add_result_column("tracebox.hop.ip4")
    cap.add_result_column("tracebox.hop.modifications")
    return cap

def tracebox4_specific_quotesize_capability(ipaddr):
    #!!! do not set udp=1 with probe=IP/TCP/ 
    cap = mplane.model.Capability(label="tracebox-specific-quotesize-ip4", when = "now ... future")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")
    cap.add_parameter("tracebox.udp")
    cap.add_parameter("tracebox.dport")
    cap.add_parameter("tracebox.probe")
    cap.add_result_column("tracebox.hop.ip4")
    cap.add_result_column("tracebox.hop.modifications")
    cap.add_result_column("tracebox.hop.icmp.payload.len")
    return cap


def tracebox6_standard_capability(ipaddr):
    cap = mplane.model.Capability(label="tracebox-standard-ip6", when = "now ... future")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6")
    cap.add_result_column("tracebox.hop.ip6")
    cap.add_result_column("tracebox.hop.modifications")
    return cap

def tracebox6_specific_capability(ipaddr):
    #!!! do not set udp=1 with probe=IP/TCP/ (opposite is ok)
    cap = mplane.model.Capability(label="tracebox-specific-ip6", when = "now ... future")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6")
    cap.add_parameter("tracebox.udp")
    cap.add_parameter("tracebox.dport")
    cap.add_parameter("tracebox.probe")
    cap.add_result_column("tracebox.hop.ip6")
    cap.add_result_column("tracebox.hop.modifications")
    return cap

def tracebox6_specific_quotesize_capability(ipaddr):
    #!!! do not set udp=1 with probe=IP/TCP/ (opposite is ok)
    cap = mplane.model.Capability(label="tracebox-specific-quotesize-ip6", when = "now ... future")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6")
    cap.add_parameter("tracebox.udp")
    cap.add_parameter("tracebox.dport")
    cap.add_parameter("tracebox.probe")
    cap.add_result_column("tracebox.hop.ip6")
    cap.add_result_column("tracebox.hop.modifications")
    cap.add_result_column("tracebox.hop.icmp.payload.len")
    return cap

def ping4_aggregate_capability(ipaddr):
    cap = mplane.model.Capability(label="ping-average-ip4", when = "now ... future / 1s")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")

    cap.add_parameter("ping.payload","")
    cap.add_parameter("ping.icmp.checksum","")
    cap.add_parameter("ping.dport",-1)
    cap.add_parameter("ping.sport",-1)
    cap.add_parameter("ping.ttl",64)
    cap.add_parameter("ping.rcount",0)
    cap.add_parameter("ping.pattern","")
    cap.add_parameter("ping.method","icmp-echo")
    cap.add_parameter("ping.rr",0)
    cap.add_parameter("ping.size",84)
    cap.add_parameter("ping.tos",0)

    #default values
    cap.add_result_column("delay.twoway.icmp.us.min")
    cap.add_result_column("delay.twoway.icmp.us.mean")
    cap.add_result_column("delay.twoway.icmp.us.max")
    cap.add_result_column("delay.twoway.icmp.count")
    return cap

def ping4_singleton_capability(ipaddr):
    cap = mplane.model.Capability(label="ping-detail-ip4", when = "now ... future / 1s")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")
    cap.add_parameter("ping.payload","")
    cap.add_parameter("ping.icmp.checksum","")
    cap.add_parameter("ping.dport",-1)
    cap.add_parameter("ping.sport",-1)
    cap.add_parameter("ping.ttl",64)
    cap.add_parameter("ping.pattern","")
    cap.add_parameter("ping.method","icmp-echo")
    cap.add_parameter("ping.rr",0)
    cap.add_parameter("ping.size",84)
    cap.add_parameter("ping.tos",0)

    cap.add_result_column("time")
    cap.add_result_column("delay.twoway.icmp.us")
    return cap

def ping6_aggregate_capability(ipaddr):
    cap = mplane.model.Capability(label="ping-average-ip6", when = "now ... future / 1s")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6") 
    cap.add_parameter("ping.payload","")
    cap.add_parameter("ping.icmp.checksum","")
    cap.add_parameter("ping.dport",-1)
    cap.add_parameter("ping.sport",-1)
    cap.add_parameter("ping.ttl",64)
    cap.add_parameter("ping.rcount",0)
    cap.add_parameter("ping.pattern","")
    cap.add_parameter("ping.method","icmp-echo")
    cap.add_parameter("ping.rr",0)
    cap.add_parameter("ping.size",56)
    cap.add_parameter("ping.tos",0)
    cap.add_result_column("delay.twoway.icmp.us.min")
    cap.add_result_column("delay.twoway.icmp.us.mean")
    cap.add_result_column("delay.twoway.icmp.us.max")
    cap.add_result_column("delay.twoway.icmp.count")
    return cap

def ping6_singleton_capability(ipaddr):
    cap = mplane.model.Capability(label="ping-detail-ip6", when = "now ... future / 1s")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6")
    cap.add_parameter("ping.payload","")
    cap.add_parameter("ping.icmp.checksum","")
    cap.add_parameter("ping.dport",-1)
    cap.add_parameter("ping.sport",-1)
    cap.add_parameter("ping.ttl",64)
    cap.add_parameter("ping.pattern","")
    cap.add_parameter("ping.method","icmp-echo")
    cap.add_parameter("ping.rr",0)
    cap.add_parameter("ping.size",56)
    cap.add_parameter("ping.tos",0)
    cap.add_result_column("time")
    cap.add_result_column("delay.twoway.icmp.us")
    return cap

def trace4_standard_capability(ipaddr):
    cap = mplane.model.Capability(label="trace-standard-ip4", when = "now ... future")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")

    cap.add_parameter("trace.confidence",-1)
    cap.add_parameter("trace.dport",-1)
    cap.add_parameter("trace.firsthop",1)
    cap.add_parameter("trace.gaplimit",5)
    cap.add_parameter("trace.gapaction",1)
    cap.add_parameter("trace.maxttl",255)
    cap.add_parameter("trace.M",0)
    cap.add_parameter("trace.loops",1)
    cap.add_parameter("trace.loopaction",-1)
    cap.add_parameter("trace.payload","")
    cap.add_parameter("trace.method","UDP-paris")
    cap.add_parameter("trace.attempts",2)
    cap.add_parameter("trace.Q",0)
    cap.add_parameter("trace.sport",-1)
    cap.add_parameter("trace.srcaddr",1)
    cap.add_parameter("trace.tos",0)
    cap.add_parameter("trace.T",0)
    cap.add_parameter("trace.userid",-1)
    cap.add_parameter("trace.wait",5)
    cap.add_parameter("trace.waitprobe",-1)
    #cap.add_parameter("trace.gssentry","*")
    cap.add_parameter("trace.lssname","")

    cap.add_result_column("trace.hop.ip4")
    cap.add_result_column("rtt.ms")
    cap.add_result_column("rtt.us")
    return cap

def trace6_standard_capability(ipaddr):
    cap = mplane.model.Capability(label="trace-standard-ip6", when = "now ... future")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6")

    cap.add_parameter("trace.confidence",-1)
    cap.add_parameter("trace.dport",-1)
    cap.add_parameter("trace.firsthop",1)
    cap.add_parameter("trace.gaplimit",5)
    cap.add_parameter("trace.gapaction",1)
    cap.add_parameter("trace.maxttl",255)
    cap.add_parameter("trace.M",0)
    cap.add_parameter("trace.loops",1)
    cap.add_parameter("trace.loopaction",-1)
    cap.add_parameter("trace.payload","")
    cap.add_parameter("trace.method","UDP-paris")
    cap.add_parameter("trace.attempts",2)
    cap.add_parameter("trace.Q",0)
    cap.add_parameter("trace.sport",-1)
    cap.add_parameter("trace.srcaddr",1)
    cap.add_parameter("trace.tos",0)
    cap.add_parameter("trace.T",0)
    cap.add_parameter("trace.userid",-1)
    cap.add_parameter("trace.wait",5)
    cap.add_parameter("trace.waitprobe",-1)
    #cap.add_parameter("trace.gssentry","*")
    cap.add_parameter("trace.lssname","")

    cap.add_result_column("trace.hop.ip6")
    cap.add_result_column("rtt.ms")
    cap.add_result_column("rtt.us")
    return cap

def tracelb4_standard_capability(ipaddr):
    cap = mplane.model.Capability(label="tracelb-standard-ip4", when = "now ... future")
    cap.add_parameter("source.ip4",ipaddr)
    cap.add_parameter("destination.ip4")

    cap.add_parameter("tracelb.confidence",95)
    cap.add_parameter("tracelb.dport",33435)
    cap.add_parameter("tracelb.firsthop",-1)
    cap.add_parameter("tracelb.gaplimit",3)
    cap.add_parameter("tracelb.method","udp-dport")
    cap.add_parameter("tracelb.attempts",2)
    cap.add_parameter("tracelb.maxprobec",3000)
    cap.add_parameter("tracelb.sport",-1)
    cap.add_parameter("tracelb.tos",0)
    cap.add_parameter("tracelb.userid",-1)
    cap.add_parameter("tracelb.waittimeout",5)
    cap.add_parameter("tracelb.waitprobe",25)

    cap.add_result_column("tracelb.result")
    return cap

def tracelb6_standard_capability(ipaddr):
    cap = mplane.model.Capability(label="tracelb-standard-ip6", when = "now ... future")
    cap.add_parameter("source.ip6",ipaddr)
    cap.add_parameter("destination.ip6")

    cap.add_parameter("tracelb.confidence",95)
    cap.add_parameter("tracelb.dport",33435)
    cap.add_parameter("tracelb.firsthop",-1)
    cap.add_parameter("tracelb.gaplimit",3)
    cap.add_parameter("tracelb.method","udp-dport")
    cap.add_parameter("tracelb.attempts",2)
    cap.add_parameter("tracelb.maxprobec",3000)
    cap.add_parameter("tracelb.sport",-1)
    cap.add_parameter("tracelb.tos",0)
    cap.add_parameter("tracelb.userid",-1)
    cap.add_parameter("tracelb.waittimeout",5)
    cap.add_parameter("tracelb.waitprobe",25)

    cap.add_result_column("tracelb.result")
    return cap

###############################################################################
#################################  SERVICE  ###################################
###############################################################################

class ScamperService(mplane.scheduler.Service):
    
    #default tracebox parameter values
    _default_udp=0
    _default_dport=80
    def _default_probe(self,udp):
        return "IP/"+("UDP" if udp else "TCP")

    #label keywords
    _quote_size="quotesize"

    __available_services=["dealias","neighbourdisc","ping","tracebox","tracelb","trace","sniff"]

    def __init__(self, cap):
        # verify the capability is acceptable
        if not ((cap.has_parameter("source.ip4") or 
                 cap.has_parameter("source.ip6")) and
                (cap.has_parameter("destination.ip4") or 
                 cap.has_parameter("destination.ip6"))):
            raise ValueError("capability not acceptable")
                
        super(ScamperService, self).__init__(cap)
        self._get_ipl = 1 if self._quote_size in self.capability().get_label() else None

        #init service
        for serv in self.__available_services:
            if serv in self.capability().get_label():
                self.__service=serv
                print(serv)
                break
        self.__input_funcs={"dealias":self.__input_dealias,
                            "neighbourdisc":self.__input_neighbourdisc,
                            "ping":self.__input_ping,
                            "trace":self.__input_trace,
                            "tracelb":self.__input_tracelb,
                            "sniff":self.__input_sniff,
                            "tracebox":self.__input_tracebox}
        self.__parsing_funcs={"dealias":self.__input_dealias,
                              "neighbourdisc":self.__input_neighbourdisc,
                              "ping":self.__parse_ping,
                              "trace":_parse_trace,
                              "tracelb":self.__parse_tracelb,
                              "sniff":self.__input_sniff,
                              "tracebox":self.__parse_tracebox}
        self.__output_funcs={"dealias":self.__input_dealias,
                             "neighbourdisc":self.__input_neighbourdisc,
                             "ping":self.__output_ping,
                             "trace":self.__output_trace,
                             "tracelb":self.__output_tracelb,
                             "sniff":self.__input_sniff,
                             "tracebox":self.__output_tracebox}
        self.__capabilities_funcs=[tracebox4_standard_capability,
                                   tracebox4_specific_capability,
                                   tracebox4_specific_quotesize_capability,
                                   ping4_aggregate_capability,
                                   ping4_singleton_capability,
                                   trace4_standard_capability,
                                   tracelb4_standard_capability,
                                   ping6_aggregate_capability,
                                   ping6_singleton_capability,
                                   tracebox6_standard_capability,
                                   tracebox6_specific_capability,
                                   tracebox6_specific_quotesize_capability,
                                   trace6_standard_capability,
                                   tracelb6_standard_capability]

    def capabilities(self):
        """
        returns a list of mplane.model.Capability objects
        representing the capabilities of this component
        """
        return [f() for f in self.__capabilities_funcs]

    ########################### INPUT #########################
    def __input_dealias(self):
        return

    def __input_neighbourdisc(self):
        return

    def __input_ping(self,spec):
        period = int(spec.when().period().total_seconds())
        duration = int(spec.when().duration().total_seconds())
        if duration is not None and duration > 0:
            count = int(duration / period)
        else:
            count = None
        payload = spec.get_parameter_value("ping.payload")
        chksum  = spec.get_parameter_value("ping.icmp.checksum")
        dport   = spec.get_parameter_value("ping.dport")
        sport   = spec.get_parameter_value("ping.sport")
        ttl     = spec.get_parameter_value("ping.ttl")
        pattern = spec.get_parameter_value("ping.pattern")
        method  = spec.get_parameter_value("ping.method")
        rr      = spec.get_parameter_value("ping.rr")  
        size    = spec.get_parameter_value("ping.size")
        tos     = spec.get_parameter_value("ping.tos")
        if spec.has_parameter("ping.rcount"):
            rcount  = spec.get_parameter_value("ping.rcount")
        else:
            rcount  = -1

        if spec.has_parameter("destination.ip4"):
            sipaddr = spec.get_parameter_value("source.ip4")
            dipaddr = spec.get_parameter_value("destination.ip4")
            ping_process = _ping_process(sipaddr, dipaddr, period, count, payload, chksum, dport, sport, ttl, pattern, method, rr, size, tos, rcount)
        elif spec.has_parameter("destination.ip6"):
            sipaddr = spec.get_parameter_value("source.ip6")
            dipaddr = spec.get_parameter_value("destination.ip6")
            ping_process = _ping_process(sipaddr, dipaddr, period, count, payload, chksum, dport, sport, ttl, pattern, method, rr, size, tos, rcount)
        else:
            raise ValueError("Missing destination")

        return ping_process

    def __input_trace(self,spec):
        # retreive parameters. if no value, sets tracebox default value        
        confidence = spec.get_parameter_value("trace.confidence")
        dport      = spec.get_parameter_value("trace.dport")
        firsthop   = spec.get_parameter_value("trace.firsthop")
        gaplimit   = spec.get_parameter_value("trace.gaplimit")
        gapaction  = spec.get_parameter_value("trace.gapaction")
        maxttl     = spec.get_parameter_value("trace.maxttl")
        M          = spec.get_parameter_value("trace.M")
        loops      = spec.get_parameter_value("trace.loops")
        loopaction = spec.get_parameter_value("trace.loopaction")
        payload    = spec.get_parameter_value("trace.payload")
        method     = spec.get_parameter_value("trace.method")
        attempts   = spec.get_parameter_value("trace.attempts")
        Q          = spec.get_parameter_value("trace.Q")
        sport      = spec.get_parameter_value("trace.sport")
        srcaddr    = spec.get_parameter_value("trace.srcaddr")
        tos        = spec.get_parameter_value("trace.tos")
        T          = spec.get_parameter_value("trace.T")
        userid     = spec.get_parameter_value("trace.userid")
        wait       = spec.get_parameter_value("trace.wait")
        waitprobe  = spec.get_parameter_value("trace.waitprobe")
        #gssentry   = spec.get_parameter_value("trace.gssentry")
        lssname    = spec.get_parameter_value("trace.lssname")     

        #launch probe
        if spec.has_parameter("destination.ip4"):
            sipaddr = spec.get_parameter_value("source.ip4")
            dipaddr = spec.get_parameter_value("destination.ip4")
            trace_process = _trace_process(sipaddr, dipaddr, confidence, dport, firsthop, gaplimit, gapaction, maxttl, M, loops, loopaction, payload, method, attempts, Q, sport, srcaddr, tos, T, userid, wait, waitprobe, lssname)
        elif spec.has_parameter("destination.ip6"):
            sipaddr = spec.get_parameter_value("source.ip6")
            dipaddr = spec.get_parameter_value("destination.ip6")
            trace_process = _trace_process(sipaddr, dipaddr,confidence, dport, firsthop, gaplimit, gapaction, maxttl, M, loops, loopaction, payload, method, attempts, Q, sport, srcaddr, tos, T, userid, wait, waitprobe, lssname)
        else:
            raise ValueError("Missing destination")

        return trace_process

    def __input_tracelb(self,spec):
        confidence  = spec.get_parameter_value("tracelb.confidence")
        dport       = spec.get_parameter_value("tracelb.dport")
        firsthop    = spec.get_parameter_value("tracelb.firsthop")
        gaplimit    = spec.get_parameter_value("tracelb.gaplimit")
        method      = spec.get_parameter_value("tracelb.method")
        attempts    = spec.get_parameter_value("tracelb.attempts")
        maxprobec   = spec.get_parameter_value("tracelb.maxprobec")
        sport       = spec.get_parameter_value("tracelb.sport")
        tos         = spec.get_parameter_value("tracelb.tos")
        userid      = spec.get_parameter_value("tracelb.userid")
        waittimeout = spec.get_parameter_value("tracelb.waittimeout")
        waitprobe   = spec.get_parameter_value("tracelb.waitprobe")

        #launch probe
        if spec.has_parameter("destination.ip4"):
            sipaddr = spec.get_parameter_value("source.ip4")
            dipaddr = spec.get_parameter_value("destination.ip4")
            tracelb_process = _tracelb_process(sipaddr, dipaddr, confidence, dport, firsthop, gaplimit,  method, attempts, maxprobec, sport, tos, userid, waittimeout, waitprobe)
        elif spec.has_parameter("destination.ip6"):
            sipaddr = spec.get_parameter_value("source.ip6")
            dipaddr = spec.get_parameter_value("destination.ip6")
            tracelb_process = _tracelb_process(sipaddr, dipaddr,confidence, dport, firsthop, gaplimit,  method, attempts, maxprobec, sport, tos, userid, waittimeout, waitprobe)
        else:
            raise ValueError("Missing destination")

        return tracelb_process


    def __input_sniff(self):
        return

    def __input_tracebox(self,spec):
        # retreive parameters. if no value, sets tracebox default value        
        if spec.has_parameter("tracebox.udp"):
            udp=spec.get_parameter_value("tracebox.udp")
            if udp is None:
                spec.set_parameter_value("tracebox.udp",self._default_udp)

            dport=spec.get_parameter_value("tracebox.dport")
            if dport is None:
                spec.set_parameter_value("tracebox.dport",self._default_dport)

            probe=spec.get_parameter_value("tracebox.probe")
            if probe is None:
                spec.set_parameter_value("tracebox.probe",self._default_probe(udp))

        else:
            udp=None
            dport=None
            probe=None             
        
        #launch probe
        if spec.has_parameter("destination.ip4"):
            sipaddr = spec.get_parameter_value("source.ip4")
            dipaddr = spec.get_parameter_value("destination.ip4")
            tracebox_process = _tracebox4_process(sipaddr, dipaddr, udp=udp, dport=dport, probe=probe, get_icmp_payload_len=self._get_ipl)
        elif spec.has_parameter("destination.ip6"):
            sipaddr = spec.get_parameter_value("source.ip6")
            dipaddr = spec.get_parameter_value("destination.ip6")
            tracebox_process = _tracebox6_process(sipaddr, dipaddr, udp=udp, dport=dport, probe=probe, get_icmp_payload_len=self._get_ipl)
        else:
            raise ValueError("Missing destination")
    
        return tracebox_process

    ########################### PARSING #########################
    def __parse_tracebox(self,tb_output):
        """
        returns list of tuple (intermediate.addr, intermediate.modifs)
        """
        tuples=[]
        if (len(tb_output)<3):
            return []

        min_words = 3 if self._get_ipl else 2

        for line in tb_output[2:]:
            pline=line.split()
            if pline[1] == "*":
                tuples.append(TraceboxValue(pline[1], "", ""))
            elif len(pline)<=min_words:
                tuples.append(TraceboxValue(pline[1], "", _detail_ipl(pline[2]) if self._get_ipl else ""))
            else:
                tuples.append(TraceboxValue(pline[1]," ".join(pline[min_words:]), _detail_ipl(pline[2]) if self._get_ipl else ""))
        return tuples

    def __parse_ping(self,ping_process):
        pings = []
        for line in ping_process:
            oneping = _parse_ping_line(line)
            if oneping is not None:
                print("ping "+repr(oneping))
                pings.append(oneping)
        return pings

    def __parse_tracelb(self,tracelb_process):
        tuples = []
        for line in tracelb_process[1:]:
            tuples.append(line.rstrip('\n'))
        return tuples

    ########################### OUTPUT #########################

    def __output_tracebox(self,res,parsed_output):

        for i, onehop in enumerate(parsed_output):
            print("tracebox "+repr(onehop))
            if res.has_parameter("destination.ip4"):
                res.set_result_value("tracebox.hop.ip4", onehop.addr,i)
            else:
                res.set_result_value("tracebox.hop.ip6", onehop.addr,i)
            res.set_result_value("tracebox.hop.modifications", onehop.modifs,i)
            if self._get_ipl:
                res.set_result_value("tracebox.hop.icmp.payload.len", onehop.payload_len, i)
        return res

    def __output_trace(self,res,parsed_output):

        for i, onehop in enumerate(parsed_output):
            print("trace "+repr(onehop))
            if res.has_parameter("destination.ip4"):
                res.set_result_value("trace.hop.ip4", onehop.addr,i)
            else:
                res.set_result_value("trace.hop.ip6", onehop.addr,i)

            
            if onehop.rtt is not "NaN":
                rtt_ms = int(float(onehop.rtt))
                rtt_us = float(onehop.rtt) * 1000
                res.set_result_value("rtt.ms", rtt_ms,i)
                res.set_result_value("rtt.us", rtt_us,i)
        return res

    def __output_ping(self,res,pings):
        if res.has_result_column("delay.twoway.icmp.us"):
            # raw numbers
            for i, oneping in enumerate(pings):
                res.set_result_value("delay.twoway.icmp.us", oneping.usec, i)
            if res.has_result_column("time"):
                for i, oneping in enumerate(pings):
                    res.set_result_value("time", oneping.time, i)
        else:
            # aggregates. single row.
            if res.has_result_column("delay.twoway.icmp.us.min"):
                res.set_result_value("delay.twoway.icmp.us.min", pings_min_delay(pings))
            if res.has_result_column("delay.twoway.icmp.us.mean"):
                res.set_result_value("delay.twoway.icmp.us.mean", pings_mean_delay(pings))
            if res.has_result_column("delay.twoway.icmp.us.median"):
                res.set_result_value("delay.twoway.icmp.us.median", pings_median_delay(pings))
            if res.has_result_column("delay.twoway.icmp.us.max"):
                res.set_result_value("delay.twoway.icmp.us.max", pings_max_delay(pings))
            if res.has_result_column("delay.twoway.icmp.us.count"):
                res.set_result_value("delay.twoway.icmp.us.count", len(pings))
    
        return res

    def __output_tracelb(self,res,lines):

        for i, line in enumerate(lines):
            print(line)
            res.set_result_value("tracelb.result",line, i)

        return res

    ########################### RUN #########################
    
    def run(self, spec, check_interrupt):

        #save probe start time    
        start_time=datetime.utcnow()

        #process
        process=self.__input_funcs[self.__service](spec)

        #save probe end time 
        end_time=datetime.utcnow()

        # read and parse output from tracebox
        output = []
        for line in process.stdout:
            output.append(line.decode("utf-8"))     
 
        #parse output
        parsed_output = self.__parsing_funcs[self.__service](output)

        # shut down and reap
        try:
            process.kill()
        except OSError:
            pass
        process.wait()
        
        # derive a result from the specification
        res = mplane.model.Result(specification=spec)

        # put actual start and end time into result
        res.set_when(mplane.model.When(a = start_time, b = end_time))

        # output
        res = self.__output_funcs[self.__service](res,parsed_output)

        return res

"""
def manually_test_tracebox():
    
    #standard tcp probe
    svc = ScamperTraceboxService(tracebox4_standard_capability(LOOP4))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip4", LOOP4)
    spec.set_parameter_value("destination.ip4", "23.212.108.142")
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    #standard udp probe
    svc = ScamperTraceboxService(tracebox4_specific_capability(LOOP4))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip4", LOOP4)
    spec.set_parameter_value("destination.ip4", "23.212.108.142")
    spec.set_parameter_value("tracebox.udp",1)
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    #changing dport
    svc = ScamperTraceboxService(tracebox4_specific_capability(LOOP4))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip4", LOOP4)
    spec.set_parameter_value("destination.ip4", "23.212.108.142")
    spec.set_parameter_value("tracebox.dport",53)
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    #defining multiple tcp options
    svc = ScamperTraceboxService(tracebox4_specific_capability(LOOP4))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip4", LOOP4)
    spec.set_parameter_value("destination.ip4", "23.212.108.142")
    spec.set_parameter_value("tracebox.probe","IP/TCP/MSS/SACK/MPJOIN")
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    
    #testing icmp payload len retreival
    svc = ScamperTraceboxService(tracebox4_specific_quotesize_capability(LOOP4))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip4", LOOP4)
    spec.set_parameter_value("destination.ip4", "23.212.108.142")
    spec.set_parameter_value("tracebox.probe","IP/TCP/MSS/SACK/MPJOIN")
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    #same for IPv6
    svc = ScamperTraceboxService(tracebox6_standard_capability(LOOP6))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip6", LOOP6)
    spec.set_parameter_value("destination.ip6", "2a00:1450:400c:c06::8a")
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    svc = ScamperTraceboxService(tracebox6_specific_capability(LOOP6))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip6", LOOP6)
    spec.set_parameter_value("destination.ip6", "2a00:1450:400c:c06::8a")
    spec.set_parameter_value("tracebox.udp",1)
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    svc = ScamperTraceboxService(tracebox6_specific_capability(LOOP6))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip6", LOOP6)
    spec.set_parameter_value("destination.ip6", "2a00:1450:400c:c06::8a")
    spec.set_parameter_value("tracebox.dport",53)
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    svc = ScamperTraceboxService(tracebox6_specific_capability(LOOP6))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip6", LOOP6)
    spec.set_parameter_value("destination.ip6", "2a00:1450:400c:c06::8a")
    spec.set_parameter_value("tracebox.probe","IP/TCP/MSS/SACK/MPJOIN")
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))

    
    #testing icmp payload len retreival
    svc = ScamperTraceboxService(tracebox4_specific_quotesize_capability(LOOP4))
    spec = mplane.model.Specification(capability=svc.capability())
    spec.set_parameter_value("source.ip4", LOOP4)
    spec.set_parameter_value("destination.ip4", "23.212.108.142")
    spec.set_parameter_value("tracebox.probe","IP/TCP/MSS/SACK/MPJOIN")
    spec.set_when("now ... future")

    res = svc.run(spec, lambda: False)
    print(repr(res))
    print(mplane.model.unparse_yaml(res))
"""

def parse_args():
    global args
    parser = argparse.ArgumentParser(description="Run an mPlane scamper probe server")
    parser.add_argument('--ip4addr', '-4', metavar="source-v4-address",
                        default="127.0.0.1",dest="IP4",help="Launch Tracebox from the given IPv4 address",)
    parser.add_argument('--ip6addr', '-6', metavar="source-v6-address",
                        default="::1",dest="IP6",help="Launch Tracebox from the given IPv6 address")
    parser.add_argument('--disable-ssl', action='store_true', default=False, dest='DISABLE_SSL',
                        help='Disable secure communication')
    parser.add_argument('-c', '--certfile', metavar="path", dest='CERTFILE', default = None,
                        help="Location of the configuration file for certificates")
    parser.add_argument('-d', '--supervisor-ip4', metavar='supervisor-ip4', default=DEFAULT_SUPERVISOR_IP4, dest='SUPERVISOR_IP4',
                        help='Supervisor IP address')
    parser.add_argument('-p', '--supervisor-port', metavar='supervisor-port', default=DEFAULT_SUPERVISOR_PORT, dest='SUPERVISOR_PORT',
                        help='Supervisor port number')
    args = parser.parse_args()

    # check format of Supervisor IP address
    ip4_pattern = re.compile("^\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}$")
    if not ip4_pattern.match(args.SUPERVISOR_IP4):
        print('\nERROR: invalid Supervisor IP format \n')
        parser.print_help()
        sys.exit(1)

    # check format of Supervisor port number
    args.SUPERVISOR_PORT = int(args.SUPERVISOR_PORT)
    if (args.SUPERVISOR_PORT <= 0 or args.SUPERVISOR_PORT > 65536):
        print('\nERROR: invalid port number \n')
        parser.print_help()
        sys.exit(1)

    # check if the file containing the paths for the 
    # certificates has been inserted in the command line
    # (only if security is enabled)
    if args.DISABLE_SSL == False and not args.CERTFILE:
        print('\nERROR: missing -C|--certfile\n')
        parser.print_help()
        sys.exit(1)

class HttpProbe():
    """
    This class manages interactions with the supervisor:
    registration, specification retrievement, and return of results
    
    """
    
    def __init__(self, immediate_ms = 5000):
        parse_args()
        self.dn = None
        
        # check if security is enabled, if so read certificate files
        self.security = not args.DISABLE_SSL
        if self.security:
            mplane.utils.check_file(args.CERTFILE)
            self.cert = mplane.utils.normalize_path(mplane.utils.read_setting(args.CERTFILE, "cert"))
            self.key = mplane.utils.normalize_path(mplane.utils.read_setting(args.CERTFILE, "key"))
            self.ca = mplane.utils.normalize_path(mplane.utils.read_setting(args.CERTFILE, "ca-chain"))
            mplane.utils.check_file(self.cert)
            mplane.utils.check_file(self.key)
            mplane.utils.check_file(self.ca)
            self.pool = HTTPSConnectionPool(args.SUPERVISOR_IP4, args.SUPERVISOR_PORT, key_file=self.key, cert_file=self.cert, ca_certs=self.ca)
        else: 
            self.pool = HTTPConnectionPool(args.SUPERVISOR_IP4, args.SUPERVISOR_PORT)
            self.cert = None
        
        # get server DN, for Access Control purposes
        self.dn = self.get_dn()
        
        # generate a Service for each capability
        self.immediate_ms = immediate_ms
        self.scheduler = mplane.scheduler.Scheduler(self.security, self.cert)
        
        if args.IP4 is not None:
            self.scheduler.add_service(ScamperService(tracebox4_standard_capability(args.IP4)))
            self.scheduler.add_service(ScamperService(tracebox4_specific_capability(args.IP4)))
            self.scheduler.add_service(ScamperService(tracebox4_specific_quotesize_capability(args.IP4)))
            self.scheduler.add_service(ScamperService(ping4_aggregate_capability(args.IP4)))
            self.scheduler.add_service(ScamperService(ping4_singleton_capability(args.IP4)))
            self.scheduler.add_service(ScamperService(trace4_standard_capability(args.IP4)))
            self.scheduler.add_service(ScamperService(tracelb4_standard_capability(args.IP4)))

        if args.IP6 is not None:
            self.scheduler.add_service(ScamperService(ping6_aggregate_capability(args.IP6)))
            self.scheduler.add_service(ScamperService(ping6_singleton_capability(args.IP6)))
            self.scheduler.add_service(ScamperService(tracebox6_standard_capability(args.IP6)))
            self.scheduler.add_service(ScamperService(tracebox6_specific_capability(args.IP6)))
            self.scheduler.add_service(ScamperService(tracebox6_specific_quotesize_capability(args.IP6)))    
            self.scheduler.add_service(ScamperService(trace6_standard_capability(args.IP6)))
            self.scheduler.add_service(ScamperService(tracelb6_standard_capability(args.IP6)))
        
    def get_dn(self):
        """
        Extracts the DN from the server. 
        If SSL is disabled, returns a dummy DN
        
        """
        if self.security == True:
            
            # extract DN from server certificate.
            # Unfortunately, there seems to be no way to do this using urllib3,
            # thus ssl library is being used
            s = socket()
            c = ssl.wrap_socket(s,cert_reqs=ssl.CERT_REQUIRED, keyfile=self.key, certfile=self.cert, ca_certs=self.ca)
            c.connect((args.SUPERVISOR_IP4, args.SUPERVISOR_PORT))
            cert = c.getpeercert()
            
            dn = ""
            for elem in cert.get('subject'):
                if dn == "":
                    dn = dn + str(elem[0][1])
                else: 
                    dn = dn + "." + str(elem[0][1])
        else:
            dn = DUMMY_DN
        return dn
     
    def register_to_supervisor(self):
        """
        Sends a list of capabilities to the Supervisor, in order to register them
        
        """
        url = "/" + REGISTRATION_PATH
        
        # generate the capability list
        caps_list = ""
        no_caps_exposed = True
        for key in self.scheduler.capability_keys():
            cap = self.scheduler.capability_for_key(key)
            if (self.scheduler.ac.check_azn(cap._label, self.dn)):
                caps_list = caps_list + mplane.model.unparse_json(cap) + ","
                no_caps_exposed = False
        caps_list = "[" + caps_list[:-1].replace("\n","") + "]"
        connected = False
        
        if no_caps_exposed is True:
           print("\nNo Capabilities are being exposed to the Supervisor, check permission files. Exiting")
           exit(0)
        
        # send the list to the supervisor, if reachable
        while not connected:
            try:
                res = self.pool.urlopen('POST', url, 
                    body=caps_list.encode("utf-8"), 
                    headers={"content-type": "application/x-mplane+json"})
                connected = True
                
            except:
                print("Supervisor unreachable. Retrying connection in 5 seconds")
                sleep(5)
                
        # handle response message
        if res.status == 200:
            body = json.loads(res.data.decode("utf-8"))
            print("\nCapability registration outcome:")
            for key in body:
                if body[key]['registered'] == "ok":
                    print(key + ": Ok")
                else:
                    print(key + ": Failed (" + body[key]['reason'] + ")")
            print("")
        else:
            print("Error registering capabilities, Supervisor said: " + str(res.status) + " - " + res.data.decode("utf-8"))
            exit(1)
    
    def check_for_specs(self):
        """
        Poll the supervisor for specifications
        
        """
        url = "/" + SPECIFICATION_PATH
        
        # send a request for specifications
        res = self.pool.request('GET', url)
        if res.status == 200:
            
            # specs retrieved: split them if there is more than one
            specs = mplane.utils.split_stmt_list(res.data.decode("utf-8"))
            for spec in specs:
                
                # hand spec to scheduler
                reply = self.scheduler.receive_message(self.dn, spec)
                
                # return error if spec is not authorized
                if isinstance(reply, mplane.model.Exception):
                    result_url = "/" + RESULT_PATH
                    # send result to the Supervisor
                    res = self.pool.urlopen('POST', result_url, 
                            body=mplane.model.unparse_json(reply).encode("utf-8"), 
                            headers={"content-type": "application/x-mplane+json"})
                    return
                
                # enqueue job
                job = self.scheduler.job_for_message(reply)
                
                # launch a thread to monitor the status of the running measurement
                t = threading.Thread(target=self.return_results, args=[job])
                t.start()
                
        # not registered on supervisor, need to re-register
        elif res.status == 428:
            print("\nRe-registering capabilities on Supervisor")
            self.register_to_supervisor()
        pass
    
    def return_results(self, job):
        """
        Monitors a job, and as soon as it is complete sends it to the Supervisor
        
        """
        url = "/" + RESULT_PATH
        reply = job.get_reply()
        
        # check if job is completed
        while job.finished() is not True:
            if job.failed():
                reply = job.get_reply()
                break
            sleep(1)
        if isinstance (reply, mplane.model.Receipt):
            reply = job.get_reply()
        
        # send result to the Supervisor
        res = self.pool.urlopen('POST', url, 
                body=mplane.model.unparse_json(reply).encode("utf-8"), 
                headers={"content-type": "application/x-mplane+json"})
                
        # handle response
        if res.status == 200:
            print("Result for " + reply.get_label() + " successfully returned!")
        else:
            print("Error returning Result for " + reply.get_label())
            print("Supervisor said: " + str(res.status) + " - " + res.data.decode("utf-8"))
        pass


# For right now, start a Tornado-based tracebox server
if __name__ == "__main__":
    mplane.model.initialize_registry()
    probe = HttpProbe()
    
    # register this probe to the Supervisor
    probe.register_to_supervisor()
    
    # periodically polls the Supervisor for Specifications
    print("Checking for Specifications...")
    while(True):
        probe.check_for_specs()
        sleep(5)
    
