Skip to content
This repository was archived by the owner on Nov 9, 2023. It is now read-only.

Commit 0a67c7b

Browse files
committed
Clean up iptable rules after happy topology tearing down
Fixed: - Skip retries (3) when nmcli disabled. (state = NoneType) - Clean iptables after tearing down happy topology. (happy-state-delete) - Handle duplicate iptables rules insertion. - Remove all the existing iptables rules after tearing down topology. - Raise meaningful error message when insertion failed.
1 parent 5d998f4 commit 0a67c7b

File tree

1 file changed

+100
-35
lines changed

1 file changed

+100
-35
lines changed

happy/HappyInternet.py

Lines changed: 100 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import os
2828
import sys
29+
import json
2930
import time
3031

3132
from happy.ReturnMsg import ReturnMsg
@@ -92,6 +93,8 @@ def __init__(self, opts=options):
9293
self.isp_pool = None
9394
self.init_happy_isp(isp_id=(self.isp_id + '_'))
9495

96+
self.iptable_rules = list()
97+
9598
def __pre_check(self):
9699
if isinstance(self.seed, int) and not (0 < self.seed < 253):
97100
emsg = "seed %s is not in range[1, 252]" % self.seed
@@ -228,6 +231,7 @@ def __connect_internet_to_isp(self):
228231
cmd = "ip link set " + self.internet_host_end + " netns %s" % self.bridge
229232
cmd = self.runAsRoot(cmd)
230233
ret = self.CallAtHost(cmd)
234+
231235
cmd = "ip netns exec %s brctl addif %s " % (self.bridge, self.bridge) + self.internet_host_end
232236
cmd = self.runAsRoot(cmd)
233237
ret = self.CallAtHost(cmd)
@@ -251,17 +255,28 @@ def __nmconf(self):
251255
# configure nmcli
252256
tries = 3
253257
state = self.getHostNMInterfaceStatus(self.internet_node_end)
254-
while state != "connecting":
258+
259+
if state is None:
260+
return
261+
elif state == "unmanaged":
262+
cmd = "nmcli dev set {} managed yes".format(self.internet_node_end)
263+
cmd = self.runAsRoot(cmd)
264+
ret = self.CallAtHost(cmd)
265+
state = self.getHostNMInterfaceStatus(self.internet_node_end)
266+
267+
while tries > 0:
268+
if state == "connecting":
269+
break
255270
time.sleep(1)
256271
state = self.getHostNMInterfaceStatus(self.internet_node_end)
257272
tries -= 1
258-
if tries <= 0:
259-
emsg = "Failed to setup host interface %s with nmcli. Internet may not working." % \
260-
(self.internet_node_end)
261-
self.logger.warning("[localhost] HappyInternet: %s" % (emsg))
262-
return
273+
else:
274+
emsg = "Failed to setup host interface {} with nmcli. Internet may not working.".format(
275+
self.internet_node_end)
276+
self.logger.warning("[localhost] HappyInternet: {}".format(emsg))
277+
return
263278

264-
cmd = "nmcli dev disconnect iface " + self.internet_node_end
279+
cmd = "nmcli dev disconnect " + self.internet_node_end
265280
cmd = self.runAsRoot(cmd)
266281
ret = self.CallAtHost(cmd)
267282

@@ -371,10 +386,7 @@ def __nat_host(self):
371386
self.exit()
372387

373388
# configure nat on host
374-
if self.add:
375-
status = 1
376-
else:
377-
status = 0
389+
status = 1 if self.add else 0
378390

379391
cmd = "sysctl -n -w net.ipv6.conf.all.forwarding=%d" % (status)
380392
cmd = self.runAsRoot(cmd)
@@ -388,35 +400,87 @@ def __nat_host(self):
388400
return
389401

390402
# Post routing on host
403+
iptable_cmd_list = [
404+
"POSTROUTING -o {} -j MASQUERADE".format(self.iface),
405+
"FORWARD -i {} -o {} -m state --state RELATED,ESTABLISHED -j ACCEPT".format(self.iface,
406+
self.internet_node_end),
407+
"FORWARD -i {} -o {} -j ACCEPT".format(self.internet_node_end, self.iface)
408+
]
409+
for rule in iptable_cmd_list:
410+
table = "-t filter"
411+
if any(keyword in rule for keyword in ('POSTROUTING', 'PREROUTING')):
412+
table = "-t nat"
413+
# Checking if rule exists
414+
cmd = "iptables {} -C {}".format(table, rule)
415+
cmd = self.runAsRoot(cmd)
416+
ret = self.CallAtHost(cmd)
417+
if not ret:
418+
self.logger.info("iptables rule exists..do nothing..")
419+
self.iptable_rules.append(rule)
420+
continue
421+
# Add iptable rule
422+
cmd = "iptables {} -A {}".format(table, rule)
423+
cmd = self.runAsRoot(cmd)
424+
ret = self.CallAtHost(cmd)
425+
if ret:
426+
_, err = self.CallAtHostForOutput(cmd)
427+
raise Exception("Unable to add iptable rule: \nErr: {}".format(err))
391428

392-
cmd = "iptables -t nat -A POSTROUTING -o " + self.iface + " -j MASQUERADE"
393-
cmd = self.runAsRoot(cmd)
394-
ret = self.CallAtHost(cmd)
395-
396-
cmd = "iptables -A FORWARD -i " + self.iface + " -o " + self.internet_node_end + \
397-
" -m state --state RELATED,ESTABLISHED -j ACCEPT"
398-
cmd = self.runAsRoot(cmd)
399-
ret = self.CallAtHost(cmd)
400-
401-
cmd = "iptables -A FORWARD -i " + self.internet_node_end + " -o " + self.iface + " -j ACCEPT"
402-
cmd = self.runAsRoot(cmd)
403-
ret = self.CallAtHost(cmd)
429+
self.iptable_rules.append(rule)
404430

405431
def __nat_isp_node(self):
406432
# configure nat on node
407433
# Post routing on node
434+
iptable_cmd_list = [
435+
"POSTROUTING -o {} -j MASQUERADE".format(self.isp_node_end),
436+
"FORWARD -o {} -m state --state RELATED,ESTABLISHED -j ACCEPT".format(self.isp_node_end),
437+
"FORWARD -i {} -j ACCEPT".format(self.isp_node_end)
438+
]
439+
for rule in iptable_cmd_list:
440+
table = "-t filter"
441+
if any(keyward in rule for keyward in ('POSTROUTING', 'PREROUTING')):
442+
table = "-t nat"
443+
cmd = "iptables {} -A {}".format(table, rule)
444+
cmd = self.runAsRoot(cmd)
445+
ret = self.CallAtNode(self.node_id, cmd)
446+
447+
def __save_iptable_commands(self):
448+
"""
449+
API to save successfully executed iptable rules for later restore back to
450+
origin iptable settings.
451+
"""
452+
if not len(self.iptable_rules):
453+
self.logger.warn("iptable: Nothing to be save, "
454+
"Please check and see if that is correct.")
455+
return
456+
isp_state_dict = json.load(open(self.isp_state_file, 'r'))
457+
isp_state_dict.update({"isp_state_fw": self.iptable_rules})
458+
with open(self.isp_state_file, 'w') as fp:
459+
json.dump(isp_state_dict, fp)
460+
461+
def __flush_iptable_commands(self):
462+
"""API to remove iptable rules that is create by happy"""
463+
if not os.path.isfile(self.isp_state_file):
464+
self.logger.warn("Unable to run iptable rules flush, "
465+
"firewall config file is missing..do nothing")
466+
return
408467

409-
cmd = "iptables -t nat -A POSTROUTING -o " + self.isp_node_end + " -j MASQUERADE"
410-
cmd = self.runAsRoot(cmd)
411-
ret = self.CallAtNode(self.node_id, cmd)
412-
413-
cmd = "iptables -A FORWARD -o " + self.isp_node_end + " -m state --state RELATED,ESTABLISHED -j ACCEPT"
414-
cmd = self.runAsRoot(cmd)
415-
ret = self.CallAtNode(self.node_id, cmd)
416-
417-
cmd = "iptables -A FORWARD -i " + self.isp_node_end + " -j ACCEPT"
418-
cmd = self.runAsRoot(cmd)
419-
ret = self.CallAtNode(self.node_id, cmd)
468+
with open(self.isp_state_file, 'r') as fp:
469+
fw_dict = json.load(fp).get("isp_state_fw", None)
470+
if not fw_dict:
471+
self.logger.warn("No added firewall rules need to be flush. "
472+
"Do nothing...")
473+
return
474+
for rule in fw_dict:
475+
table = "-t filter"
476+
if any(keyword in rule for keyword in ('POSTROUTING', 'PREROUTING')):
477+
table = "-t nat"
478+
cmd = "iptables {} -C {}".format(table, rule)
479+
cmd = self.runAsRoot(cmd)
480+
while not self.CallAtHost(cmd):
481+
cmd1 = "iptables {} -D {}".format(table, rule)
482+
cmd1 = self.runAsRoot(cmd1)
483+
ret = self.CallAtHost(cmd1)
420484

421485
def __delete_isp(self):
422486
# delete isp network namespace
@@ -451,7 +515,7 @@ def run(self):
451515
with self.getStateLockManager(lock_id="rt"):
452516
self.__route()
453517
self.__nat_isp_node()
454-
518+
self.__save_iptable_commands()
455519
with self.getStateLockManager():
456520
self.__internet_state()
457521
self.writeState()
@@ -474,5 +538,6 @@ def run(self):
474538
self.removeGlobalIsp()
475539

476540
self.writeIspState()
541+
self.__flush_iptable_commands()
477542

478543
return ReturnMsg(0)

0 commit comments

Comments
 (0)