18eb2bee6SAlexander V. Chernikov#!/usr/local/bin/python3 28eb2bee6SAlexander V. Chernikovimport json 38eb2bee6SAlexander V. Chernikovimport os 4*b23dbabbSKristof Provostimport subprocess 58eb2bee6SAlexander V. Chernikov 68eb2bee6SAlexander V. Chernikov 78eb2bee6SAlexander V. Chernikovclass ToolsHelper(object): 88eb2bee6SAlexander V. Chernikov NETSTAT_PATH = "/usr/bin/netstat" 9cfc9cf9bSAlexander V. Chernikov IFCONFIG_PATH = "/sbin/ifconfig" 108eb2bee6SAlexander V. Chernikov 118eb2bee6SAlexander V. Chernikov @classmethod 128eb2bee6SAlexander V. Chernikov def get_output(cls, cmd: str, verbose=False) -> str: 138eb2bee6SAlexander V. Chernikov if verbose: 148eb2bee6SAlexander V. Chernikov print("run: '{}'".format(cmd)) 158eb2bee6SAlexander V. Chernikov return os.popen(cmd).read() 168eb2bee6SAlexander V. Chernikov 178eb2bee6SAlexander V. Chernikov @classmethod 18*b23dbabbSKristof Provost def pf_rules(cls, rules, verbose=True): 19*b23dbabbSKristof Provost pf_conf = "" 20*b23dbabbSKristof Provost for r in rules: 21*b23dbabbSKristof Provost pf_conf = pf_conf + r + "\n" 22*b23dbabbSKristof Provost 23*b23dbabbSKristof Provost if verbose: 24*b23dbabbSKristof Provost print("Set rules:") 25*b23dbabbSKristof Provost print(pf_conf) 26*b23dbabbSKristof Provost 27*b23dbabbSKristof Provost ps = subprocess.Popen("/sbin/pfctl -g -f -", shell=True, 28*b23dbabbSKristof Provost stdin=subprocess.PIPE) 29*b23dbabbSKristof Provost ps.communicate(bytes(pf_conf, 'utf-8')) 30*b23dbabbSKristof Provost ret = ps.wait() 31*b23dbabbSKristof Provost if ret != 0: 32*b23dbabbSKristof Provost raise Exception("Failed to set pf rules %d" % ret) 33*b23dbabbSKristof Provost 34*b23dbabbSKristof Provost if verbose: 35*b23dbabbSKristof Provost cls.print_output("/sbin/pfctl -sr") 36*b23dbabbSKristof Provost 37*b23dbabbSKristof Provost @classmethod 38cfc9cf9bSAlexander V. Chernikov def print_output(cls, cmd: str, verbose=True): 39cfc9cf9bSAlexander V. Chernikov if verbose: 40cfc9cf9bSAlexander V. Chernikov print("======= {} =====".format(cmd)) 41cfc9cf9bSAlexander V. Chernikov print(cls.get_output(cmd)) 42cfc9cf9bSAlexander V. Chernikov if verbose: 43cfc9cf9bSAlexander V. Chernikov print() 44cfc9cf9bSAlexander V. Chernikov 45cfc9cf9bSAlexander V. Chernikov @classmethod 46cfc9cf9bSAlexander V. Chernikov def print_net_debug(cls): 47cfc9cf9bSAlexander V. Chernikov cls.print_output("ifconfig") 48cfc9cf9bSAlexander V. Chernikov cls.print_output("netstat -rnW") 49cfc9cf9bSAlexander V. Chernikov 50cfc9cf9bSAlexander V. Chernikov @classmethod 51cfc9cf9bSAlexander V. Chernikov def set_sysctl(cls, oid, val): 52cfc9cf9bSAlexander V. Chernikov cls.get_output("sysctl {}={}".format(oid, val)) 53cfc9cf9bSAlexander V. Chernikov 54cfc9cf9bSAlexander V. Chernikov @classmethod 558eb2bee6SAlexander V. Chernikov def get_routes(cls, family: str, fibnum: int = 0): 568eb2bee6SAlexander V. Chernikov family_key = {"inet": "-4", "inet6": "-6"}.get(family) 578eb2bee6SAlexander V. Chernikov out = cls.get_output( 587064c94aSAlexander V. Chernikov "{} {} -rnW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) 598eb2bee6SAlexander V. Chernikov ) 608eb2bee6SAlexander V. Chernikov js = json.loads(out) 618eb2bee6SAlexander V. Chernikov js = js["statistics"]["route-information"]["route-table"]["rt-family"] 628eb2bee6SAlexander V. Chernikov if js: 638eb2bee6SAlexander V. Chernikov return js[0]["rt-entry"] 648eb2bee6SAlexander V. Chernikov else: 658eb2bee6SAlexander V. Chernikov return [] 66cfc9cf9bSAlexander V. Chernikov 67cfc9cf9bSAlexander V. Chernikov @classmethod 687064c94aSAlexander V. Chernikov def get_nhops(cls, family: str, fibnum: int = 0): 697064c94aSAlexander V. Chernikov family_key = {"inet": "-4", "inet6": "-6"}.get(family) 707064c94aSAlexander V. Chernikov out = cls.get_output( 717064c94aSAlexander V. Chernikov "{} {} -onW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) 727064c94aSAlexander V. Chernikov ) 737064c94aSAlexander V. Chernikov js = json.loads(out) 747064c94aSAlexander V. Chernikov js = js["statistics"]["route-nhop-information"]["nhop-table"]["rt-family"] 757064c94aSAlexander V. Chernikov if js: 767064c94aSAlexander V. Chernikov return js[0]["nh-entry"] 777064c94aSAlexander V. Chernikov else: 787064c94aSAlexander V. Chernikov return [] 797064c94aSAlexander V. Chernikov 807064c94aSAlexander V. Chernikov @classmethod 81cfc9cf9bSAlexander V. Chernikov def get_linklocals(cls): 82cfc9cf9bSAlexander V. Chernikov ret = {} 83cfc9cf9bSAlexander V. Chernikov ifname = None 84cfc9cf9bSAlexander V. Chernikov ips = [] 85cfc9cf9bSAlexander V. Chernikov for line in cls.get_output(cls.IFCONFIG_PATH).splitlines(): 86cfc9cf9bSAlexander V. Chernikov if line[0].isalnum(): 87cfc9cf9bSAlexander V. Chernikov if ifname: 88cfc9cf9bSAlexander V. Chernikov ret[ifname] = ips 89cfc9cf9bSAlexander V. Chernikov ips = [] 90cfc9cf9bSAlexander V. Chernikov ifname = line.split(":")[0] 91cfc9cf9bSAlexander V. Chernikov else: 92cfc9cf9bSAlexander V. Chernikov words = line.split() 93cfc9cf9bSAlexander V. Chernikov if words[0] == "inet6" and words[1].startswith("fe80"): 94cfc9cf9bSAlexander V. Chernikov # inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2 95cfc9cf9bSAlexander V. Chernikov ip = words[1].split("%")[0] 96cfc9cf9bSAlexander V. Chernikov scopeid = int(words[words.index("scopeid") + 1], 16) 97cfc9cf9bSAlexander V. Chernikov ips.append((ip, scopeid)) 98cfc9cf9bSAlexander V. Chernikov if ifname: 99cfc9cf9bSAlexander V. Chernikov ret[ifname] = ips 100cfc9cf9bSAlexander V. Chernikov return ret 101