Repository: Azure/WALinuxAgent
Pull request #2429 (2021-11-30 22:02:39)

Add DNS TCP Firewall rule - Simplified logic
<!-- DO NOT DELETE THIS TEMPLATE -->

Modified the way to add the New rule to be more readable

The new rule : iptables -t security -I OUTPUT -d 168.63.129.16 -p tcp --destination-port 53 -j ACCEPT -w

Issue # <!-- if any -->
<!--
Please add an informative description that covers that changes made by the pull request. 
This checklist is used to make sure that common issues in a pull request are addressed.
This will expedite the process of getting your pull request merged and avoid extra work on your part to fix issues discovered during the review process.
-->

---

### PR information
- [ ] The title of the PR is clear and informative.
- [ ] There are a small number of commits, each of which has an informative message. This means that previously merged commits do not appear in the history of the PR. For information on cleaning up the commits in your pull request, [see this page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).
- [ ] If applicable, the PR references the bug/issue that it fixes in the description.
- [ ] New Unit tests were added for the changes made

### Quality of Code and Contribution Guidelines
- [ ] I have read the [contribution guidelines](https://github.com/Azure/WALinuxAgent/blob/master/.github/CONTRIBUTING.md).

codecov[bot]: # [Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) Report
> Merging [#2429](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (f52bb31) into [develop](https://codecov.io/gh/Azure/WALinuxAgent/commit/9c7868eb02c4cde48cba5a9528b542775002cd59?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (9c7868e) will **increase** coverage by `0.08%`.
> The diff coverage is `93.58%`.

> :exclamation: Current head f52bb31 differs from pull request most recent head 3994d0b. Consider uploading reports for the commit 3994d0b to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429/graphs/tree.svg?width=650&height=150&src=pr&token=ae90E2KYCw&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)

```diff
@@             Coverage Diff             @@
##           develop    #2429      +/-   ##
===========================================
+ Coverage    71.71%   71.80%   +0.08%     
===========================================
  Files          100      100              
  Lines        14680    14754      +74     
  Branches      2325     2332       +7     
===========================================
+ Hits         10528    10594      +66     
- Misses        3689     3694       +5     
- Partials       463      466       +3     
```


| [Impacted Files](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) | Coverage Δ | |
|---|---|---|
| [azurelinuxagent/ga/update.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2dhL3VwZGF0ZS5weQ==) | `89.81% <90.90%> (+0.04%)` | :arrow_up: |
| [azurelinuxagent/common/utils/networkutil.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2NvbW1vbi91dGlscy9uZXR3b3JrdXRpbC5weQ==) | `95.95% <95.00%> (-0.40%)` | :arrow_down: |
| [azurelinuxagent/common/osutil/default.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2NvbW1vbi9vc3V0aWwvZGVmYXVsdC5weQ==) | `60.54% <100.00%> (+0.42%)` | :arrow_up: |
| [azurelinuxagent/ga/collect\_telemetry\_events.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2dhL2NvbGxlY3RfdGVsZW1ldHJ5X2V2ZW50cy5weQ==) | `89.72% <0.00%> (-1.72%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure). Last update [9c7868e...3994d0b](https://codecov.io/gh/Azure/WALinuxAgent/pull/2429?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).

dhivyaganesan: https://tuxgold.corp.microsoft.com/job/AzLinux/job/DungeonCrawler/job/AgentBVTs.dhivya/166/

This run is yellow because of unable to resolve unable to resolve host error for ubuntu 16. Will update it once I get a all green .
dhivyaganesan: https://tuxgold.corp.microsoft.com/job/AzLinux/job/DungeonCrawler/job/AgentBVTs.dhivya/184/ - Green run for the latest changes
dhivyaganesan: https://tuxgold.corp.microsoft.com/job/AzLinux/job/DungeonCrawler/job/AgentBVTs.dhivya/186/
https://tuxgold.corp.microsoft.com/job/AzLinux/job/DungeonCrawler/job/AgentBVTs.dhivya/185/

DCR run for latest changes

@larohra  Thanks for the detailed review. Please have a look again when you get a chance .

dhivyaganesan: Green run for the above changes https://tuxgold.corp.microsoft.com/job/AzLinux/job/DungeonCrawler/job/AgentBVTs.dhivya/189/

Patch:
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index 820016c1..1c604216 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -71,42 +71,50 @@ def _add_wait(wait, command):
     return command
 
 
-def _get_iptables_version_command():
+def get_iptables_version_command():
     return ["iptables", "--version"]
 
 
-def _get_firewall_accept_command(wait, command, destination, owner_uid):
+def get_accept_tcp_rule(wait, command, destination):
+    return AddFirewallRules.get_accept_tcp_rule(command, destination, wait=wait)
+
+
+def get_firewall_accept_command(wait, command, destination, owner_uid):
     return AddFirewallRules.get_iptables_accept_command(wait, command, destination, owner_uid)
 
 
-def _get_firewall_drop_command(wait, command, destination):
+def get_firewall_drop_command(wait, command, destination):
     return AddFirewallRules.get_iptables_drop_command(wait, command, destination)
 
 
-def _get_firewall_list_command(wait):
+def get_firewall_list_command(wait):
     return _add_wait(wait, ["iptables", "-t", "security", "-L", "-nxv"])
 
 
-def _get_firewall_packets_command(wait):
+def get_firewall_packets_command(wait):
     return _add_wait(wait, ["iptables", "-t", "security", "-L", "OUTPUT", "--zero", "OUTPUT", "-nxv"])
 
 
 # Precisely delete the rules created by the agent.
 # this rule was used <= 2.2.25.  This rule helped to validate our change, and determine impact.
-def _get_firewall_delete_conntrack_accept_command(wait, destination):
+def get_firewall_delete_conntrack_accept_command(wait, destination):
     return _add_wait(wait,
-                     ["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "-m", "conntrack",
+                     ["iptables", "-t", "security", AddFirewallRules.DELETE_COMMAND, "OUTPUT", "-d", destination, "-p", "tcp", "-m", "conntrack",
                       "--ctstate", "INVALID,NEW", "-j", "ACCEPT"])
 
 
-def _get_firewall_delete_owner_accept_command(wait, destination, owner_uid):
-    return _add_wait(wait, ["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "-m", "owner",
+def get_delete_accept_tcp_rule(wait, destination):
+    return AddFirewallRules.get_accept_tcp_rule(AddFirewallRules.DELETE_COMMAND, destination, wait=wait)
+
+
+def get_firewall_delete_owner_accept_command(wait, destination, owner_uid):
+    return _add_wait(wait, ["iptables", "-t", "security", AddFirewallRules.DELETE_COMMAND, "OUTPUT", "-d", destination, "-p", "tcp", "-m", "owner",
                             "--uid-owner", str(owner_uid), "-j", "ACCEPT"])
 
 
-def _get_firewall_delete_conntrack_drop_command(wait, destination):
+def get_firewall_delete_conntrack_drop_command(wait, destination):
     return _add_wait(wait,
-                     ["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "-m", "conntrack",
+                     ["iptables", "-t", "security", AddFirewallRules.DELETE_COMMAND, "OUTPUT", "-d", destination, "-p", "tcp", "-m", "conntrack",
                       "--ctstate", "INVALID,NEW", "-j", "DROP"])
 
 
@@ -162,7 +170,7 @@ class DefaultOSUtil(object):
             wait = self.get_firewall_will_wait()
 
             try:
-                output = shellutil.run_command(_get_firewall_packets_command(wait))
+                output = shellutil.run_command(get_firewall_packets_command(wait))
 
                 pattern = re.compile(PACKET_PATTERN.format(dst_ip))
                 for line in output.split('\n'):
@@ -189,7 +197,7 @@ class DefaultOSUtil(object):
     def get_firewall_will_wait(self):
         # Determine if iptables will serialize access
         try:
-            output = shellutil.run_command(_get_iptables_version_command())
+            output = shellutil.run_command(get_iptables_version_command())
         except Exception as e:
             msg = "Unable to determine version of iptables: {0}".format(ustr(e))
             logger.warn(msg)
@@ -231,10 +239,11 @@ class DefaultOSUtil(object):
 
             # This rule was <= 2.2.25 only, and may still exist on some VMs.  Until 2.2.25
             # has aged out, keep this cleanup in place.
-            self._delete_rule(_get_firewall_delete_conntrack_accept_command(wait, dst_ip))
+            self._delete_rule(get_firewall_delete_conntrack_accept_command(wait, dst_ip))
 
-            self._delete_rule(_get_firewall_delete_owner_accept_command(wait, dst_ip, uid))
-            self._delete_rule(_get_firewall_delete_conntrack_drop_command(wait, dst_ip))
+            self._delete_rule(get_delete_accept_tcp_rule(wait, dst_ip))
+            self._delete_rule(get_firewall_delete_owner_accept_command(wait, dst_ip, uid))
+            self._delete_rule(get_firewall_delete_conntrack_drop_command(wait, dst_ip))
 
             return True
 
@@ -254,7 +263,7 @@ class DefaultOSUtil(object):
 
             # This rule was <= 2.2.25 only, and may still exist on some VMs.  Until 2.2.25
             # has aged out, keep this cleanup in place.
-            self._delete_rule(_get_firewall_delete_conntrack_accept_command(wait, dst_ip))
+            self._delete_rule(get_firewall_delete_conntrack_accept_command(wait, dst_ip))
         except Exception as error:
             logger.info(
                 "Unable to remove legacy firewall rule, won't try removing it again. Error: {0}".format(ustr(error)))
@@ -270,7 +279,7 @@ class DefaultOSUtil(object):
 
             # If the DROP rule exists, make no changes
             try:
-                drop_rule = _get_firewall_drop_command(wait, "-C", dst_ip)
+                drop_rule = get_firewall_drop_command(wait, AddFirewallRules.CHECK_COMMAND, dst_ip)
                 shellutil.run_command(drop_rule)
                 logger.verbose("Firewall appears established")
                 return True
@@ -281,7 +290,7 @@ class DefaultOSUtil(object):
                     logger.warn(msg)
                     raise Exception(msg)
 
-            # Otherwise, append both rules
+            # Otherwise, append all rules
             try:
                 AddFirewallRules.add_iptables_rules(wait, dst_ip, uid)
             except Exception as error:
@@ -291,7 +300,7 @@ class DefaultOSUtil(object):
             logger.info("Successfully added Azure fabric firewall rules")
 
             try:
-                output = shellutil.run_command(_get_firewall_list_command(wait))
+                output = shellutil.run_command(get_firewall_list_command(wait))
                 logger.info("Firewall rules:\n{0}".format(output))
             except Exception as e:
                 logger.warn("Listing firewall rules failed: {0}".format(ustr(e)))
diff --git a/azurelinuxagent/common/utils/networkutil.py b/azurelinuxagent/common/utils/networkutil.py
index a1269ce7..8908d841 100644
--- a/azurelinuxagent/common/utils/networkutil.py
+++ b/azurelinuxagent/common/utils/networkutil.py
@@ -100,14 +100,16 @@ class NetworkInterfaceCard:
 
 
 class FirewallCmdDirectCommands(object):
-    # firewall-cmd --direct --passthrough ipv4 -t security -A OUTPUT -d 1.2.3.5 -p tcp -m owner --uid-owner 999 -j ACCEPT
+    # firewall-cmd --direct --permanent --passthrough ipv4 -t security -A OUTPUT -d 1.2.3.5 -p tcp -m owner --uid-owner 999 -j ACCEPT
     # success
+    # adds the firewalld rule and returns the status
     PassThrough = "--passthrough"
 
     # firewall-cmd --direct --query-passthrough ipv4 -t security -A OUTPUT -d 1.2.3.5 -p tcp -m owner --uid-owner 9999 -j ACCEPT
     # yes
-    # firewall-cmd --direct --query-passthrough ipv4 -t security -A OUTPUT -d 1.2.3.5 -p tcp -m owner --uid-owner 999 -j ACCEPT
+    # firewall-cmd --direct --permanent --query-passthrough ipv4 -t security -A OUTPUT -d 1.2.3.5 -p tcp -m owner --uid-owner 999 -j ACCEPT
     # no
+    # checks if the firewalld rule is present or not
     QueryPassThrough = "--query-passthrough"
 
 
@@ -119,7 +121,19 @@ class AddFirewallRules(object):
     Please make sure to not log anything in any function this class.
     """
 
-    __ACCEPT_COMMAND = "-A"
+    # -A adds the rule to the end of the iptable chain
+    APPEND_COMMAND = "-A"
+
+    # -I inserts the rule at the index specified. If no number specified the rules get added to the top of the chain
+    # iptables -t security -I OUTPUT 1 -d 168.63.129.16 -p tcp --destination-port 53 -j ACCEPT -w and
+    # iptables -t security -I OUTPUT -d 168.63.129.16 -p tcp --destination-port 53 -j ACCEPT -w both adds the rule as the first rule of the chain
+    INSERT_COMMAND = "-I"
+
+    # -D deletes the specific rule in the iptable chain
+    DELETE_COMMAND = "-D"
+
+    # -C checks if a specific rule exists
+    CHECK_COMMAND = "-C"
 
     @staticmethod
     def _add_wait(wait, command):
@@ -131,7 +145,9 @@ class AddFirewallRules(object):
         return command
 
     @staticmethod
-    def __get_iptables_base_command():
+    def __get_iptables_base_command(wait=""):
+        if wait != "":
+            return ["iptables", "-w"]
         return ["iptables"]
 
     @staticmethod
@@ -155,6 +171,17 @@ class AddFirewallRules(object):
         cmd.extend(["conntrack", "--ctstate", "INVALID,NEW", "-j", "DROP"])
         return AddFirewallRules._add_wait(wait, cmd)
 
+    @staticmethod
+    def get_accept_tcp_rule(command, destination, firewalld_command="", wait=""):
+        # This rule allows DNS TCP request to wireserver ip for non root users
+
+        if firewalld_command != "":
+            cmd = AddFirewallRules.__get_firewalld_base_command(firewalld_command)
+        else:
+            cmd = AddFirewallRules.__get_iptables_base_command(wait)
+        cmd = cmd + ['-t', 'security', command, 'OUTPUT', '-d', destination, '-p', 'tcp', '--destination-port', '53', '-j', 'ACCEPT']
+        return cmd
+
     @staticmethod
     def get_iptables_accept_command(wait, command, destination, owner_uid):
         cmd = AddFirewallRules.__get_iptables_base_command()
@@ -171,7 +198,7 @@ class AddFirewallRules(object):
     def get_firewalld_accept_command(command, destination, uid, wait=""):
         cmd = AddFirewallRules.__get_firewalld_base_command(command)
         cmd.extend(
-            AddFirewallRules.__get_common_accept_command_params(wait, AddFirewallRules.__ACCEPT_COMMAND, destination,
+            AddFirewallRules.__get_common_accept_command_params(wait, AddFirewallRules.APPEND_COMMAND, destination,
                                                                 uid))
         return cmd
 
@@ -179,7 +206,7 @@ class AddFirewallRules(object):
     def get_firewalld_drop_command(command, destination, wait=""):
         cmd = AddFirewallRules.__get_firewalld_base_command(command)
         cmd.extend(
-            AddFirewallRules.__get_common_drop_command_params(wait, AddFirewallRules.__ACCEPT_COMMAND, destination))
+            AddFirewallRules.__get_common_drop_command_params(wait, AddFirewallRules.APPEND_COMMAND, destination))
         return cmd
 
     @staticmethod
@@ -200,14 +227,17 @@ class AddFirewallRules(object):
 
     @staticmethod
     def add_iptables_rules(wait, dst_ip, uid):
-
+        # The order in which the below rules are added matters for the ip table rules to work as expected
         AddFirewallRules.__raise_if_empty(dst_ip, "Destination IP")
         AddFirewallRules.__raise_if_empty(uid, "User ID")
 
-        accept_rule = AddFirewallRules.get_iptables_accept_command(wait, AddFirewallRules.__ACCEPT_COMMAND, dst_ip, uid)
+        accept_tcp_rule = AddFirewallRules.get_accept_tcp_rule(AddFirewallRules.APPEND_COMMAND, dst_ip, wait=wait)
+        AddFirewallRules.__execute_cmd(accept_tcp_rule)
+
+        accept_rule = AddFirewallRules.get_iptables_accept_command(wait, AddFirewallRules.APPEND_COMMAND, dst_ip, uid)
         AddFirewallRules.__execute_cmd(accept_rule)
 
-        drop_rule = AddFirewallRules.get_iptables_drop_command(wait, AddFirewallRules.__ACCEPT_COMMAND, dst_ip)
+        drop_rule = AddFirewallRules.get_iptables_drop_command(wait, AddFirewallRules.APPEND_COMMAND, dst_ip)
         AddFirewallRules.__execute_cmd(drop_rule)
 
     @staticmethod
@@ -215,6 +245,11 @@ class AddFirewallRules(object):
         AddFirewallRules.__raise_if_empty(dst_ip, "Destination IP")
         AddFirewallRules.__raise_if_empty(uid, "User ID")
 
+        # Firewalld.service fails if we set `-w` in the iptables command, so not adding it at all for firewalld commands
+
+        accept_tcp_rule = AddFirewallRules.get_accept_tcp_rule(AddFirewallRules.INSERT_COMMAND, dst_ip, firewalld_command=command)
+        AddFirewallRules.__execute_cmd(accept_tcp_rule)
+
         accept_cmd = AddFirewallRules.get_firewalld_accept_command(command, dst_ip, uid)
         AddFirewallRules.__execute_cmd(accept_cmd)
 
@@ -223,7 +258,9 @@ class AddFirewallRules(object):
 
     @staticmethod
     def add_firewalld_rules(dst_ip, uid):
-        # Firwalld.service fails if we set `-w` in the iptables command, so not adding it at all for firewalld commands
+        # Firewalld.service fails if we set `-w` in the iptables command, so not adding it at all for firewalld commands
+        # Firewalld.service with the "--permanent --passthrough" parameter ensures that a firewall rule is set only once even if command is executed multiple times
+
         AddFirewallRules.__execute_firewalld_commands(FirewallCmdDirectCommands.PassThrough, dst_ip, uid)
 
     @staticmethod
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 790ea650..f031b924 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -48,7 +48,10 @@ from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.osutil import get_osutil, systemd
 from azurelinuxagent.common.protocol.util import get_protocol_util
 from azurelinuxagent.common.protocol.hostplugin import HostPluginProtocol
+from azurelinuxagent.common.utils import shellutil
 from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
+from azurelinuxagent.common.utils.networkutil import AddFirewallRules
+from azurelinuxagent.common.utils.shellutil import CommandError
 from azurelinuxagent.common.version import AGENT_NAME, AGENT_VERSION, AGENT_DIR_PATTERN, CURRENT_AGENT,\
     CURRENT_VERSION, DISTRO_NAME, DISTRO_VERSION, is_current_agent_installed, get_lis_version, \
     has_logrotate, PY_VERSION_MAJOR, PY_VERSION_MINOR, PY_VERSION_MICRO
@@ -60,6 +63,8 @@ from azurelinuxagent.ga.exthandlers import HandlerManifest, ExtHandlersHandler,
 from azurelinuxagent.ga.monitor import get_monitor_handler
 
 from azurelinuxagent.ga.send_telemetry_events import get_send_telemetry_events_handler
+from azurelinuxagent.common.osutil.default import get_firewall_drop_command, \
+    get_accept_tcp_rule
 
 AGENT_ERROR_FILE = "error.json"  # File name for agent error record
 AGENT_MANIFEST_FILE = "HandlerManifest.json"
@@ -358,6 +363,7 @@ class UpdateHandler(object):
             self._ensure_cgroups_initialized()
             self._ensure_extension_telemetry_state_configured_properly(protocol)
             self._ensure_firewall_rules_persisted(dst_ip=protocol.get_endpoint())
+            self._add_accept_tcp_firewall_rule_if_not_enabled(dst_ip=protocol.get_endpoint())
 
             # Get all thread handlers
             telemetry_handler = get_send_telemetry_events_handler(self.protocol_util)
@@ -1017,6 +1023,53 @@ class UpdateHandler(object):
             message=msg,
             log_event=False)
 
+    def _add_accept_tcp_firewall_rule_if_not_enabled(self, dst_ip):
+
+        def _execute_run_command(command):
+            # Helper to execute a run command, returns True if no exception
+            # Here we primarily check if an  iptable rule exist. True if it exits , false if not
+            try:
+                shellutil.run_command(command)
+                return True
+            except CommandError as err:
+                # return code 1 is expected while using the check command. Raise if encounter any other return code
+                if err.returncode != 1:
+                    raise
+            return False
+
+        try:
+            wait = self.osutil.get_firewall_will_wait()
+
+            # "-C" checks if the iptable rule is available in the chain. It throws an exception with return code 1 if the ip table rule doesnt exist
+            drop_rule = get_firewall_drop_command(wait, AddFirewallRules.CHECK_COMMAND, dst_ip)
+            if not _execute_run_command(drop_rule):
+                # DROP command doesn't exist indicates then none of the firewall rules are set yet
+                # exiting here as the environment thread will set up all firewall rules
+                logger.info("DROP rule is not available which implies no firewall rules are set yet. Environment thread will set it up.")
+                return
+            else:
+                # DROP rule exists in the ip table chain. Hence checking if the DNS TCP to wireserver rule exists. If not we add it.
+                accept_tcp_rule = get_accept_tcp_rule(wait, AddFirewallRules.CHECK_COMMAND, dst_ip)
+                if not _execute_run_command(accept_tcp_rule):
+                    try:
+                        logger.info(
+                            "Firewall rule to allow DNS TCP request to wireserver for a non root user unavailable. Setting it now.")
+                        accept_tcp_rule = get_accept_tcp_rule(wait, AddFirewallRules.INSERT_COMMAND, dst_ip)
+                        shellutil.run_command(accept_tcp_rule)
+                        logger.info(
+                            "Succesfully added firewall rule to allow non root users to do a DNS TCP request to wireserver")
+                    except CommandError as error:
+                        msg = "Unable to set the non root tcp access firewall rule :" \
+                              "Run command execution for {0} failed with error:{1}.Return Code:{2}"\
+                            .format(error.command, error.stderr, error.returncode)
+                        logger.error(msg)
+                else:
+                    logger.info(
+                        "Not setting the firewall rule to allow DNS TCP request to wireserver for a non root user since it already exists")
+        except Exception as e:
+            msg = "Error while checking ip table rules:{0}".format(ustr(e))
+            logger.error(msg)
+
     def __get_next_upgrade_times(self):
         """
         Get the next upgrade times


Test patch:
diff --git a/tests/common/osutil/test_default.py b/tests/common/osutil/test_default.py
index a736974e..e34e3d4f 100644
--- a/tests/common/osutil/test_default.py
+++ b/tests/common/osutil/test_default.py
@@ -33,6 +33,7 @@ from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.osutil import get_osutil
 from azurelinuxagent.common.utils import fileutil
 from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
+from azurelinuxagent.common.utils.networkutil import AddFirewallRules
 from tests.common.mock_environment import MockEnvironment
 from tests.tools import AgentTestCase, patch, open_patch, load_data, data_dir
 
@@ -654,19 +655,23 @@ Match host 192.168.1.2\n\
             mocked_commands[command_string] = (output.replace("'", "'\"'\"'"), exit_code)
             return command_string
 
-        wait = FlexibleVersion(version) >= osutil._IPTABLES_LOCKING_VERSION
+        wait = "-w" if FlexibleVersion(version) >= osutil._IPTABLES_LOCKING_VERSION else ""
         uid = 42
 
-        version_command = set_command(osutil._get_iptables_version_command(), output=str(version))
-        list_command = set_command(osutil._get_firewall_list_command(wait), output="Mock Output")
-        set_command(osutil._get_firewall_packets_command(wait))
-        set_command(osutil._get_firewall_drop_command(wait, "-C", destination))
-        set_command(osutil._get_firewall_drop_command(wait, "-A", destination))
-        set_command(osutil._get_firewall_accept_command(wait, "-A", destination, uid))
+        version_command = set_command(osutil.get_iptables_version_command(), output=str(version))
+        list_command = set_command(osutil.get_firewall_list_command(wait), output="Mock Output")
+        set_command(osutil.get_firewall_packets_command(wait))
+        set_command(osutil.get_firewall_drop_command(wait, AddFirewallRules.CHECK_COMMAND, destination))
+        set_command(osutil.get_firewall_drop_command(wait, AddFirewallRules.APPEND_COMMAND, destination))
+        set_command(osutil.get_firewall_accept_command(wait, AddFirewallRules.APPEND_COMMAND, destination, uid))
+        set_command(osutil.get_accept_tcp_rule(wait, AddFirewallRules.APPEND_COMMAND, destination))
+        set_command(osutil.get_accept_tcp_rule(wait, AddFirewallRules.INSERT_COMMAND, destination))
+        set_command(osutil.get_accept_tcp_rule(wait, AddFirewallRules.CHECK_COMMAND, destination))
         # the agent assumes the rules have been deleted when these commands return 1
-        set_command(osutil._get_firewall_delete_conntrack_accept_command(wait, destination), exit_code=1)
-        set_command(osutil._get_firewall_delete_owner_accept_command(wait, destination, uid), exit_code=1)
-        set_command(osutil._get_firewall_delete_conntrack_drop_command(wait, destination), exit_code=1)
+        set_command(osutil.get_firewall_delete_conntrack_accept_command(wait, destination), exit_code=1)
+        set_command(osutil.get_delete_accept_tcp_rule(wait, destination), exit_code=1)
+        set_command(osutil.get_firewall_delete_owner_accept_command(wait, destination, uid), exit_code=1)
+        set_command(osutil.get_firewall_delete_conntrack_drop_command(wait, destination), exit_code=1)
 
         command_calls = []
 
@@ -692,191 +697,205 @@ Match host 192.168.1.2\n\
                 yield popen_patcher
 
     def test_get_firewall_dropped_packets_returns_zero_if_firewall_disabled(self):
-        osutil._enable_firewall = False
-        util = osutil.DefaultOSUtil()
+        with patch.object(osutil, '_enable_firewall', False):
+            util = osutil.DefaultOSUtil()
 
-        self.assertEqual(0, util.get_firewall_dropped_packets("not used"))
+            self.assertEqual(0, util.get_firewall_dropped_packets("not used"))
 
     def test_get_firewall_dropped_packets_returns_negative_if_error(self):
-        osutil._enable_firewall = True
-
         with TestOSUtil._mock_iptables() as mock_iptables:
-            mock_iptables.set_command(osutil._get_firewall_packets_command(mock_iptables.wait), exit_code=1)
-
-            self.assertEqual(-1, osutil.DefaultOSUtil().get_firewall_dropped_packets())
+            with patch.object(osutil, '_enable_firewall', True):
+                mock_iptables.set_command(osutil.get_firewall_packets_command(mock_iptables.wait), exit_code=1)
+                self.assertEqual(-1, osutil.DefaultOSUtil().get_firewall_dropped_packets())
 
     def test_get_firewall_dropped_packets_should_ignore_transient_errors(self):
-        osutil._enable_firewall = True
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            mock_iptables.set_command(osutil._get_firewall_packets_command(mock_iptables.wait), exit_code=3, output="can't initialize iptables table `security': iptables who? (do you need to insmod?)")
-
-            self.assertEqual(0, osutil.DefaultOSUtil().get_firewall_dropped_packets())
+            with patch.object(osutil, '_enable_firewall', True):
+                mock_iptables.set_command(osutil.get_firewall_packets_command(mock_iptables.wait), exit_code=3, output="can't initialize iptables table `security': iptables who? (do you need to insmod?)")
+                self.assertEqual(0, osutil.DefaultOSUtil().get_firewall_dropped_packets())
 
     def test_get_firewall_dropped_packets(self):
-        osutil._enable_firewall = True
 
         destination = '168.63.129.16'
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-
-            mock_iptables.set_command(osutil._get_firewall_packets_command(mock_iptables.wait), output='''
-
-Chain OUTPUT (policy ACCEPT 104 packets, 43628 bytes)
-    pkts      bytes target     prot opt in     out     source               destination
-       0        0 ACCEPT     tcp  --  any    any     anywhere             168.63.129.16        owner UID match daemon
-      32     1920 DROP       tcp  --  any    any     anywhere             168.63.129.16
-
-''')
-            self.assertEqual(32, osutil.DefaultOSUtil().get_firewall_dropped_packets(destination))
-
+            with patch.object(osutil, '_enable_firewall', True):
+
+                mock_iptables.set_command(osutil.get_firewall_packets_command(mock_iptables.wait), output='''
+    
+    Chain OUTPUT (policy ACCEPT 104 packets, 43628 bytes)
+        pkts      bytes target     prot opt in     out     source               destination
+           0        0 ACCEPT     tcp  --  any    any     anywhere             168.63.129.16        owner UID match daemon
+          32     1920 DROP       tcp  --  any    any     anywhere             168.63.129.16
+    
+    ''')
+                self.assertEqual(32, osutil.DefaultOSUtil().get_firewall_dropped_packets(destination))
 
     def test_enable_firewall_should_set_up_the_firewall(self):
-        osutil._enable_firewall = True
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            # fail the rule check to force enable of the firewall
-            mock_iptables.set_command(osutil._get_firewall_drop_command(mock_iptables.wait, "-C", mock_iptables.destination), exit_code=1)
+            with patch.object(osutil, '_enable_firewall', True):
+                # fail the rule check to force enable of the firewall
+                mock_iptables.set_command(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=1)
 
-            success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
+                success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
 
-            drop_check_command = TestOSUtil._command_to_string(osutil._get_firewall_drop_command(mock_iptables.wait, "-C", mock_iptables.destination))
-            accept_command = TestOSUtil._command_to_string(osutil._get_firewall_accept_command(mock_iptables.wait, "-A", mock_iptables.destination, mock_iptables.uid))
-            drop_add_command = TestOSUtil._command_to_string(osutil._get_firewall_drop_command(mock_iptables.wait, "-A", mock_iptables.destination))
+                drop_check_command = TestOSUtil._command_to_string(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                accept_tcp_append_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.APPEND_COMMAND, mock_iptables.destination))
+                accept_command = TestOSUtil._command_to_string(osutil.get_firewall_accept_command(mock_iptables.wait, AddFirewallRules.APPEND_COMMAND, mock_iptables.destination, mock_iptables.uid))
+                drop_add_command = TestOSUtil._command_to_string(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.APPEND_COMMAND, mock_iptables.destination))
 
-            self.assertTrue(success, "Enabling the firewall was not successful")
-            self.assertEqual(len(mock_iptables.command_calls), 3, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
-            self.assertEqual(mock_iptables.command_calls[0], drop_check_command, "The first command should check the drop rule")
-            self.assertEqual(mock_iptables.command_calls[1], accept_command, "The second command should add the accept rule")
-            self.assertEqual(mock_iptables.command_calls[2], drop_add_command, "The third command should add the drop rule")
+                self.assertTrue(success, "Enabling the firewall was not successful")
+                # Exactly 4 calls have to be made.
+                # First is the DROP rule check which was mocked to fail, Second, third and forth to Append the three IPTable rules
+                self.assertEqual(len(mock_iptables.command_calls), 4, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
+                self.assertEqual(mock_iptables.command_calls[0], drop_check_command, "The first command should check the drop rule")
+                self.assertEqual(mock_iptables.command_calls[1], accept_tcp_append_rule,
+                                "The second command should add the dns tcp accept rule")
+                self.assertEqual(mock_iptables.command_calls[2], accept_command, "The third command should add the accept rule")
+                self.assertEqual(mock_iptables.command_calls[3], drop_add_command, "The fourth command should add the drop rule")
 
-            self.assertTrue(osutil._enable_firewall, "The firewall should not have been disabled")
+                self.assertTrue(osutil._enable_firewall, "The firewall should not have been disabled")
 
     def test_enable_firewall_should_not_use_wait_when_iptables_does_not_support_it(self):
-        osutil._enable_firewall = True
-
         with TestOSUtil._mock_iptables(version=osutil._IPTABLES_LOCKING_VERSION - 1) as mock_iptables:
-            # fail the rule check to force enable of the firewall
-            mock_iptables.set_command(osutil._get_firewall_drop_command(mock_iptables.wait, "-C", mock_iptables.destination), exit_code=1)
+            with patch.object(osutil, '_enable_firewall', True):
+                # fail the rule check to force enable of the firewall
+                mock_iptables.set_command(
+                    osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND,
+                                                     mock_iptables.destination), exit_code=1)
 
-            success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
+                success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
 
-            self.assertTrue(success, "Enabling the firewall was not successful")
-            self.assertEqual(len(mock_iptables.command_calls), 3, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
-            for command in mock_iptables.command_calls:
-                self.assertNotIn("-w", command, "The -w option should have been used in {0}".format(command))
+                self.assertTrue(success, "Enabling the firewall was not successful")
+                # Exactly 4 calls have to be made.
+                # First is the DROP rule check which was mocked to fail, Second, third and forth
+                # to Append the three IPTable rules
+                self.assertEqual(len(mock_iptables.command_calls), 4,
+                                 "Incorrect number of calls to iptables: [{0}]".format(mock_iptables.command_calls))
+                for command in mock_iptables.command_calls:
+                    self.assertNotIn("-w", command, "The -w option should have been used in {0}".format(command))
 
-            self.assertTrue(osutil._enable_firewall, "The firewall should not have been disabled")
+                self.assertTrue(osutil._enable_firewall, "The firewall should not have been disabled")
 
     def test_enable_firewall_should_not_set_firewall_if_the_drop_rule_exists(self):
-        osutil._enable_firewall = True
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            drop_check_command = mock_iptables.set_command(osutil._get_firewall_drop_command(mock_iptables.wait, "-C", mock_iptables.destination), exit_code=0)
+            with patch.object(osutil, '_enable_firewall', True):
+                drop_check_command = mock_iptables.set_command(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=0)
 
-            success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
+                success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
 
-            self.assertTrue(success, "Enabling the firewall was not successful")
-            self.assertEqual(len(mock_iptables.command_calls), 1, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
-            self.assertEqual(mock_iptables.command_calls[0], drop_check_command, "Unexpected command: {0}".format(mock_iptables.command_calls[0]))
+                self.assertTrue(success, "Enabling the firewall was not successful")
+                self.assertEqual(len(mock_iptables.command_calls), 1, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
+                self.assertEqual(mock_iptables.command_calls[0], drop_check_command, "Unexpected command: {0}".format(mock_iptables.command_calls[0]))
 
-            self.assertTrue(osutil._enable_firewall)
+                self.assertTrue(osutil._enable_firewall)
 
     def test_enable_firewall_should_check_for_invalid_iptables_options(self):
-        osutil._enable_firewall = True
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            # iptables uses the following exit codes
-            #  0 - correct function
-            #  1 - other errors
-            #  2 - errors which appear to be caused by invalid or abused command
-            #      line parameters
-            drop_check_command = mock_iptables.set_command(osutil._get_firewall_drop_command(mock_iptables.wait, "-C", mock_iptables.destination), exit_code=2)
-
-            success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
-
-            delete_conntrack_accept_command = TestOSUtil._command_to_string(osutil._get_firewall_delete_conntrack_accept_command(mock_iptables.wait, mock_iptables.destination))
-            delete_owner_accept_command = TestOSUtil._command_to_string(osutil._get_firewall_delete_owner_accept_command(mock_iptables.wait, mock_iptables.destination, mock_iptables.uid))
-            delete_conntrack_drop_command = TestOSUtil._command_to_string(osutil._get_firewall_delete_conntrack_drop_command(mock_iptables.wait, mock_iptables.destination))
-
-            self.assertFalse(success, "Enable firewall should have failed")
-            self.assertEqual(len(mock_iptables.command_calls), 4, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
-            self.assertEqual(mock_iptables.command_calls[0], drop_check_command, "The first command should check the drop rule: {0}".format(mock_iptables.command_calls[0]))
-            self.assertEqual(mock_iptables.command_calls[1], delete_conntrack_accept_command, "The second command should delete the conntrack accept rule: {0}".format(mock_iptables.command_calls[1]))
-            self.assertEqual(mock_iptables.command_calls[2], delete_owner_accept_command, "The third command should delete the owner accept rule: {0}".format(mock_iptables.command_calls[2]))
-            self.assertEqual(mock_iptables.command_calls[3], delete_conntrack_drop_command, "The fourth command should delete the conntrack accept rule : {0}".format(mock_iptables.command_calls[3]))
-
-            self.assertFalse(osutil._enable_firewall)
+            with patch.object(osutil, '_enable_firewall', True):
+                # iptables uses the following exit codes
+                #  0 - correct function
+                #  1 - other errors
+                #  2 - errors which appear to be caused by invalid or abused command
+                #      line parameters
+                drop_check_command = mock_iptables.set_command(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=2)
+
+                success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
+
+                delete_conntrack_accept_command = TestOSUtil._command_to_string(osutil.get_firewall_delete_conntrack_accept_command(mock_iptables.wait, mock_iptables.destination))
+                delete_accept_tcp_rule = TestOSUtil._command_to_string(osutil.get_delete_accept_tcp_rule(mock_iptables.wait, mock_iptables.destination))
+                delete_owner_accept_command = TestOSUtil._command_to_string(osutil.get_firewall_delete_owner_accept_command(mock_iptables.wait, mock_iptables.destination, mock_iptables.uid))
+                delete_conntrack_drop_command = TestOSUtil._command_to_string(osutil.get_firewall_delete_conntrack_drop_command(mock_iptables.wait, mock_iptables.destination))
+
+                self.assertFalse(success, "Enable firewall should have failed")
+                self.assertEqual(len(mock_iptables.command_calls), 5, "Incorrect number of calls to iptables: [{0}]". format(mock_iptables.command_calls))
+                self.assertEqual(mock_iptables.command_calls[0], drop_check_command, "The first command should check the drop rule: {0}".format(mock_iptables.command_calls[0]))
+                self.assertEqual(mock_iptables.command_calls[1], delete_conntrack_accept_command, "The second command should delete the conntrack accept rule: {0}".format(mock_iptables.command_calls[1]))
+                self.assertEqual(mock_iptables.command_calls[2], delete_accept_tcp_rule,
+                                 "The third command should delete the dns tcp accept rule: {0}".format(
+                                     mock_iptables.command_calls[2]))
+                self.assertEqual(mock_iptables.command_calls[3], delete_owner_accept_command, "The forth command should delete the owner accept rule: {0}".format(mock_iptables.command_calls[3]))
+                self.assertEqual(mock_iptables.command_calls[4], delete_conntrack_drop_command, "The fifth command should delete the conntrack accept rule : {0}".format(mock_iptables.command_calls[4]))
+
+                self.assertFalse(osutil._enable_firewall)
 
     def test_enable_firewall_skips_if_disabled(self):
-        osutil._enable_firewall = False
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
+            with patch.object(osutil, '_enable_firewall', False):
+                success = osutil.DefaultOSUtil().enable_firewall(dst_ip=mock_iptables.destination, uid=mock_iptables.uid)
 
-            self.assertFalse(success, "The firewall should not have been disabled")
-            self.assertEqual(len(mock_iptables.command_calls), 0, "iptables should not have been invoked: [{0}]". format(mock_iptables.command_calls))
+                self.assertFalse(success, "The firewall should not have been disabled")
+                self.assertEqual(len(mock_iptables.command_calls), 0, "iptables should not have been invoked: [{0}]". format(mock_iptables.command_calls))
 
-            self.assertFalse(osutil._enable_firewall)
+                self.assertFalse(osutil._enable_firewall)
 
     def test_remove_firewall(self):
-        osutil._enable_firewall = True
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            delete_commands = {}
-
-            def mock_popen(command, *args, **kwargs):
-                command_string = TestOSUtil._command_to_string(command)
-                if " -D " in command_string:
-                    # The agent invokes the delete commands continuously until they return 1 to indicate the rules has been removed
-                    # The mock returns 0 (success) the first time it is invoked and 1 (rule does not exist) thereafter
-                    if command_string not in delete_commands:
-                        exit_code = 0
-                        delete_commands[command_string] = 1
-                    else:
-                        exit_code = 1
-                        delete_commands[command_string] += 1
-
-                    command = "echo '' && exit {0}".format(exit_code)
-                    kwargs["shell"] = True
-                return mock_popen.original(command, *args, **kwargs)
-            mock_popen.original = subprocess.Popen
-
-            with patch("azurelinuxagent.common.cgroupapi.subprocess.Popen", side_effect=mock_popen):
-                success = osutil.DefaultOSUtil().remove_firewall(mock_iptables.destination, mock_iptables.uid)
-
-                delete_conntrack_accept_command = TestOSUtil._command_to_string(osutil._get_firewall_delete_conntrack_accept_command(mock_iptables.wait, mock_iptables.destination))
-                delete_owner_accept_command = TestOSUtil._command_to_string(osutil._get_firewall_delete_owner_accept_command(mock_iptables.wait, mock_iptables.destination, mock_iptables.uid))
-                delete_conntrack_drop_command = TestOSUtil._command_to_string(osutil._get_firewall_delete_conntrack_drop_command(mock_iptables.wait, mock_iptables.destination))
-
-                self.assertTrue(success, "Removing the firewall should have succeeded")
-                self.assertEqual(len(delete_commands), 3, "Expected 3 delete commands: [{0}]".format(delete_commands))
-                # delete rules < 2.2.26
-                self.assertIn(delete_conntrack_accept_command, delete_commands, "The delete conntrack accept command was not executed")
-                self.assertEqual(delete_commands[delete_conntrack_accept_command], 2, "The delete conntrack accept command should have been executed twice")
-                self.assertIn(delete_owner_accept_command, delete_commands, "The delete owner accept command was not executed")
-                self.assertEqual(delete_commands[delete_owner_accept_command], 2, "The delete owner accept command should have been executed twice")
-                # delete rules >= 2.2.26
-                self.assertIn(delete_conntrack_drop_command, delete_commands, "The delete conntrack drop command was not executed")
-                self.assertEqual(delete_commands[delete_conntrack_drop_command], 2, "The delete conntrack drop command should have been executed twice")
-
-                self.assertTrue(osutil._enable_firewall)
+            with patch.object(osutil, '_enable_firewall', True):
+                delete_commands = {}
+
+                def mock_popen(command, *args, **kwargs):
+                    command_string = TestOSUtil._command_to_string(command)
+                    if AddFirewallRules.DELETE_COMMAND in command_string:
+                        # The agent invokes the delete commands continuously until they return 1 to indicate the rules has been removed
+                        # The mock returns 0 (success) the first time it is invoked and 1 (rule does not exist) thereafter
+                        if command_string not in delete_commands:
+                            exit_code = 0
+                            delete_commands[command_string] = 1
+                        else:
+                            exit_code = 1
+                            delete_commands[command_string] += 1
+
+                        command = "echo '' && exit {0}".format(exit_code)
+                        kwargs["shell"] = True
+                    return mock_popen.original(command, *args, **kwargs)
+                mock_popen.original = subprocess.Popen
+
+                with patch("azurelinuxagent.common.cgroupapi.subprocess.Popen", side_effect=mock_popen):
+                    success = osutil.DefaultOSUtil().remove_firewall(mock_iptables.destination, mock_iptables.uid)
+
+                    delete_conntrack_accept_command = TestOSUtil._command_to_string(osutil.get_firewall_delete_conntrack_accept_command(mock_iptables.wait, mock_iptables.destination))
+                    delete_accept_tcp_rule = TestOSUtil._command_to_string(
+                        osutil.get_delete_accept_tcp_rule(mock_iptables.wait, mock_iptables.destination))
+                    delete_owner_accept_command = TestOSUtil._command_to_string(osutil.get_firewall_delete_owner_accept_command(mock_iptables.wait, mock_iptables.destination, mock_iptables.uid))
+                    delete_conntrack_drop_command = TestOSUtil._command_to_string(osutil.get_firewall_delete_conntrack_drop_command(mock_iptables.wait, mock_iptables.destination))
+
+                    self.assertTrue(success, "Removing the firewall should have succeeded")
+                    self.assertEqual(len(delete_commands), 4, "Expected 4 delete commands: [{0}]".format(delete_commands))
+                    # delete rules < 2.2.26
+                    self.assertIn(delete_accept_tcp_rule, delete_commands, "The delete dns tcp accept command was not executed")
+                    self.assertEqual(delete_commands[delete_accept_tcp_rule], 2, "The delete dns tcp accept command should have been executed twice")
+                    self.assertIn(delete_conntrack_accept_command, delete_commands, "The delete conntrack accept command was not executed")
+                    self.assertEqual(delete_commands[delete_conntrack_accept_command], 2, "The delete conntrack accept command should have been executed twice")
+                    self.assertIn(delete_owner_accept_command, delete_commands, "The delete owner accept command was not executed")
+                    self.assertEqual(delete_commands[delete_owner_accept_command], 2, "The delete owner accept command should have been executed twice")
+                    # delete rules >= 2.2.26
+                    self.assertIn(delete_conntrack_drop_command, delete_commands, "The delete conntrack drop command was not executed")
+                    self.assertEqual(delete_commands[delete_conntrack_drop_command], 2, "The delete conntrack drop command should have been executed twice")
+
+                    self.assertTrue(osutil._enable_firewall)
 
     def test_remove_firewall_should_not_retry_invalid_rule(self):
-        osutil._enable_firewall = True
 
         with TestOSUtil._mock_iptables() as mock_iptables:
-            command = osutil._get_firewall_delete_conntrack_accept_command(mock_iptables.wait, mock_iptables.destination)
-            # Note that the command is actually a valid rule, but we use the mock to report it as invalid (exit code 2)
-            delete_conntrack_accept_command = mock_iptables.set_command(command, exit_code=2)
+            with patch.object(osutil, '_enable_firewall', True):
+                command = osutil.get_firewall_delete_conntrack_accept_command(mock_iptables.wait, mock_iptables.destination)
+                # Note that the command is actually a valid rule, but we use the mock to report it as invalid (exit code 2)
+                delete_conntrack_accept_command = mock_iptables.set_command(command, exit_code=2)
 
-            success = osutil.DefaultOSUtil().remove_firewall(mock_iptables.destination, mock_iptables.uid)
+                success = osutil.DefaultOSUtil().remove_firewall(mock_iptables.destination, mock_iptables.uid)
 
-            self.assertFalse(success, "Removing the firewall should not have succeeded")
-            self.assertEqual(len(mock_iptables.command_calls), 1, "Expected a single call to iptables: [{0}]". format(mock_iptables.command_calls))
-            self.assertEqual(mock_iptables.command_calls[0], delete_conntrack_accept_command, "Expected call to delete conntrack accept command: {0}".format(mock_iptables.command_calls[0]))
+                self.assertFalse(success, "Removing the firewall should not have succeeded")
+                self.assertEqual(len(mock_iptables.command_calls), 1, "Expected a single call to iptables: [{0}]". format(mock_iptables.command_calls))
+                self.assertEqual(mock_iptables.command_calls[0], delete_conntrack_accept_command, "Expected call to delete conntrack accept command: {0}".format(mock_iptables.command_calls[0]))
 
-            self.assertFalse(osutil._enable_firewall)
+                self.assertFalse(osutil._enable_firewall)
 
     def test_get_nic_state(self):
         state = osutil.DefaultOSUtil().get_nic_state()
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 297b97ba..cf64abde 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -16,8 +16,11 @@ import tempfile
 import time
 import unittest
 import zipfile
+
 from datetime import datetime, timedelta
 from threading import currentThread
+from tests.common.osutil.test_default import TestOSUtil
+import azurelinuxagent.common.osutil.default as osutil
 
 _ORIGINAL_POPEN = subprocess.Popen
 
@@ -36,7 +39,7 @@ from azurelinuxagent.common.protocol.util import ProtocolUtil
 from azurelinuxagent.common.protocol.wire import WireProtocol
 from azurelinuxagent.common.utils import fileutil, restutil, textutil
 from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
-from azurelinuxagent.common.utils.networkutil import FirewallCmdDirectCommands
+from azurelinuxagent.common.utils.networkutil import FirewallCmdDirectCommands, AddFirewallRules
 from azurelinuxagent.common.version import AGENT_PKG_GLOB, AGENT_DIR_GLOB, AGENT_NAME, AGENT_DIR_PATTERN, \
     AGENT_VERSION, CURRENT_AGENT, CURRENT_VERSION
 from azurelinuxagent.ga.exthandlers import ExtHandlersHandler, ExtHandlerInstance, HandlerEnvironment, ExtensionStatusValue
@@ -1680,15 +1683,88 @@ class TestUpdate(UpdateTestCase):
         exit_args, _ = update_handler.exit_mock.call_args
         self.assertEqual(exit_args[0], 0, "Exit code should be 0; List of all warnings logged by the agent: {0}".format(
             patch_warn.call_args_list))
-        # Firewall-cmd should only be called 3 times - 1st to check if running, 2nd & 3rd for the QueryPassThrough cmd
-        self.assertEqual(3, len(executed_commands),
-                         "The number of times firewall-cmd should be called is only 3; Executed firewall commands: {0}; All popen calls: {1}".format(
+
+        # Firewall-cmd should only be called 4 times - 1st to check if running, 2nd, 3rd and 4th for the QueryPassThrough cmd
+        self.assertEqual(4, len(executed_commands),
+                         "The number of times firewall-cmd should be called is only 4; Executed firewall commands: {0}; All popen calls: {1}".format(
                              executed_commands, mock_popen.call_args_list))
         self.assertEqual(PersistFirewallRulesHandler._FIREWALLD_RUNNING_CMD, executed_commands.pop(0),
                          "First command should be to check if firewalld is running")
         self.assertTrue([FirewallCmdDirectCommands.QueryPassThrough in cmd for cmd in executed_commands],
                         "The remaining commands should only be for querying the firewall commands")
 
+    def test_it_should_set_dns_tcp_iptable_if_drop_available_accept_unavailable(self):
+
+        with TestOSUtil._mock_iptables() as mock_iptables:
+            with _get_update_handler(test_data=DATA_FILE) as (update_handler, _):
+                with patch.object(osutil, '_enable_firewall', True):
+                    # drop rule is present
+                    mock_iptables.set_command(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=0)
+                    # non root tcp iptable rule is absent
+                    mock_iptables.set_command(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=1)
+                    update_handler.run(debug=True)
+
+                    drop_check_command = TestOSUtil._command_to_string(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                    accept_tcp_check_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                    accept_tcp_insert_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.INSERT_COMMAND, mock_iptables.destination))
+
+                    # Filtering the mock iptable command calls with only the once related to this test.
+                    filtered_mock_iptable_calls = [cmd for cmd in mock_iptables.command_calls if cmd in [drop_check_command, accept_tcp_check_rule, accept_tcp_insert_rule]]
+
+                    self.assertEqual(len(filtered_mock_iptable_calls), 3, "Incorrect number of calls to iptables: [{0}]".format(mock_iptables.command_calls))
+                    self.assertEqual(filtered_mock_iptable_calls[0], drop_check_command,
+                                     "The first command should check the drop rule")
+                    self.assertEqual(filtered_mock_iptable_calls[1], accept_tcp_check_rule,
+                                     "The second command should check the accept rule")
+                    self.assertEqual(filtered_mock_iptable_calls[2], accept_tcp_insert_rule,
+                                     "The third command should add the accept rule")
+
+    def test_it_should_not_set_dns_tcp_iptable_if_drop_unavailable(self):
+
+        with TestOSUtil._mock_iptables() as mock_iptables:
+            with _get_update_handler(test_data=DATA_FILE) as (update_handler, _):
+                with patch.object(osutil, '_enable_firewall', True):
+                    # drop rule is not available
+                    mock_iptables.set_command(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=1)
+
+                    update_handler.run(debug=True)
+
+                    drop_check_command = TestOSUtil._command_to_string(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                    accept_tcp_check_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                    accept_tcp_insert_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.INSERT_COMMAND, mock_iptables.destination))
+
+                    # Filtering the mock iptable command calls with only the once related to this test.
+                    filtered_mock_iptable_calls = [cmd for cmd in mock_iptables.command_calls if cmd in [drop_check_command, accept_tcp_check_rule, accept_tcp_insert_rule]]
+
+                    self.assertEqual(len(filtered_mock_iptable_calls), 1, "Incorrect number of calls to iptables: [{0}]".format(mock_iptables.command_calls))
+                    self.assertEqual(filtered_mock_iptable_calls[0], drop_check_command,
+                                     "The first command should check the drop rule")
+
+    def test_it_should_not_set_dns_tcp_iptable_if_drop_and_accept_available(self):
+
+        with TestOSUtil._mock_iptables() as mock_iptables:
+            with _get_update_handler(test_data=DATA_FILE) as (update_handler, _):
+                with patch.object(osutil, '_enable_firewall', True):
+                    # drop rule is available
+                    mock_iptables.set_command(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=0)
+                    # non root tcp iptable rule is available
+                    mock_iptables.set_command(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination), exit_code=0)
+
+                    update_handler.run(debug=True)
+
+                    drop_check_command = TestOSUtil._command_to_string(osutil.get_firewall_drop_command(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                    accept_tcp_check_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.CHECK_COMMAND, mock_iptables.destination))
+                    accept_tcp_insert_rule = TestOSUtil._command_to_string(osutil.get_accept_tcp_rule(mock_iptables.wait, AddFirewallRules.INSERT_COMMAND, mock_iptables.destination))
+
+                    # Filtering the mock iptable command calls with only the once related to this test.
+                    filtered_mock_iptable_calls = [cmd for cmd in mock_iptables.command_calls if cmd in [drop_check_command, accept_tcp_check_rule, accept_tcp_insert_rule]]
+
+                    self.assertEqual(len(filtered_mock_iptable_calls), 2, "Incorrect number of calls to iptables: [{0}]".format(mock_iptables.command_calls))
+                    self.assertEqual(filtered_mock_iptable_calls[0], drop_check_command,
+                                     "The first command should check the drop rule")
+                    self.assertEqual(filtered_mock_iptable_calls[1], accept_tcp_check_rule,
+                                     "The second command should check the accept rule")
+
     @contextlib.contextmanager
     def _setup_test_for_ext_event_dirs_retention(self):
         try:
diff --git a/tests/utils/test_network_util.py b/tests/utils/test_network_util.py
index 9836d0ee..4c3f5d01 100644
--- a/tests/utils/test_network_util.py
+++ b/tests/utils/test_network_util.py
@@ -84,7 +84,7 @@ class TestAddFirewallRules(AgentTestCase):
 
         self.assertTrue(all(test_dst_ip in cmd for cmd in commands_called), "Dest IP was not set correctly in iptables")
         self.assertTrue(all(test_wait in cmd for cmd in commands_called), "The wait was not set properly")
-        self.assertTrue(all(str(test_uid) in cmd for cmd in commands_called if "ACCEPT" in cmd),
+        self.assertTrue(all(str(test_uid) in cmd for cmd in commands_called if ("ACCEPT" in cmd and "--uid-owner" in cmd)),
                         "The UID is not set for the accept command")
 
     def test_it_should_raise_if_invalid_data(self):