diff --git a/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i b/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i
new file mode 100644
index 000000000..7f34de3ba
--- /dev/null
+++ b/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i
@@ -0,0 +1,35 @@
+<!-- include start from firewall/firewall-hashing-parameters.xml.i -->
+<leafNode name="hash">
+  <properties>
+    <help>Define the parameters of the packet header to apply the hashing</help>
+    <completionHelp>
+      <list>source-address destination-address source-port destination-port random</list>
+    </completionHelp>
+    <valueHelp>
+      <format>source-address</format>
+      <description>Use source IP address for hashing</description>
+    </valueHelp>
+    <valueHelp>
+      <format>destination-address</format>
+      <description>Use destination IP address for hashing</description>
+    </valueHelp>
+    <valueHelp>
+      <format>source-port</format>
+      <description>Use source port for hashing</description>
+    </valueHelp>
+    <valueHelp>
+      <format>destination-port</format>
+      <description>Use destination port for hashing</description>
+    </valueHelp>
+    <valueHelp>
+      <format>random</format>
+      <description>Do not use information from ip header. Use random value.</description>
+    </valueHelp>
+    <constraint>
+      <regex>(source-address|destination-address|source-port|destination-port|random)</regex>
+    </constraint>
+    <multi/>
+  </properties>
+  <defaultValue>random</defaultValue>
+</leafNode>
+<!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/include/firewall/nat-balance.xml.i b/interface-definitions/include/firewall/nat-balance.xml.i
new file mode 100644
index 000000000..01793f06b
--- /dev/null
+++ b/interface-definitions/include/firewall/nat-balance.xml.i
@@ -0,0 +1,28 @@
+<!-- include start from firewall/nat-balance.xml.i -->
+<tagNode name="backend">
+  <properties>
+    <help>Translated IP address</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>IPv4 address to match</description>
+    </valueHelp>
+    <constraint>
+      <validator name="ipv4-address"/>
+    </constraint>
+  </properties>
+  <children>
+    <leafNode name="weight">
+      <properties>
+        <help>Set probability for this output value</help>
+        <valueHelp>
+          <format>u32:1-100</format>
+          <description>Set probability for this output value</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--allow-range --range 1-100"/>
+        </constraint>
+      </properties>
+    </leafNode>
+  </children>
+</tagNode>
+<!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index 7b3b8804e..6234e6195 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -1,321 +1,330 @@
 <!-- include start from nat-rule.xml.i -->
 <tagNode name="rule">
   <properties>
     <help>Rule number for NAT</help>
     <valueHelp>
       <format>u32:1-999999</format>
       <description>Number of NAT rule</description>
     </valueHelp>
     <constraint>
       <validator name="numeric" argument="--range 1-999999"/>
     </constraint>
     <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
   </properties>
   <children>
     #include <include/generic-description.xml.i>
     <node name="destination">
       <properties>
         <help>NAT destination parameters</help>
       </properties>
       <children>
         #include <include/nat-address.xml.i>
         #include <include/nat-port.xml.i>
         #include <include/firewall/source-destination-group.xml.i>
       </children>
     </node>
     #include <include/generic-disable-node.xml.i>
     #include <include/nat-exclude.xml.i>
+    <node name="load-balance">
+      <properties>
+        <help>Apply NAT load balance</help>
+      </properties>
+      <children>
+        #include <include/firewall/firewall-hashing-parameters.xml.i>
+        #include <include/firewall/nat-balance.xml.i>
+      </children>
+    </node>
     <leafNode name="log">
       <properties>
         <help>NAT rule logging</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="packet-type">
       <properties>
         <help>Packet type</help>
         <completionHelp>
           <list>broadcast host multicast other</list>
         </completionHelp>
         <valueHelp>
           <format>broadcast</format>
           <description>Match broadcast packet type</description>
         </valueHelp>
         <valueHelp>
           <format>host</format>
           <description>Match host packet type, addressed to local host</description>
         </valueHelp>
         <valueHelp>
           <format>multicast</format>
           <description>Match multicast packet type</description>
         </valueHelp>
         <valueHelp>
           <format>other</format>
           <description>Match packet addressed to another host</description>
         </valueHelp>
         <constraint>
           <regex>(broadcast|host|multicast|other)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="protocol">
       <properties>
         <help>Protocol to NAT</help>
         <completionHelp>
           <list>all ip hopopt icmp igmp ggp ipencap st tcp egp igp pup udp tcp_udp hmp xns-idp rdp iso-tp4 dccp xtp ddp idpr-cmtp ipv6 ipv6-route ipv6-frag idrp rsvp gre esp ah skip ipv6-icmp ipv6-nonxt ipv6-opts rspf vmtp eigrp ospf ax.25 ipip etherip encap 99 pim ipcomp vrrp l2tp isis sctp fc mobility-header udplite mpls-in-ip manet hip shim6 wesp rohc</list>
         </completionHelp>
         <valueHelp>
           <format>all</format>
           <description>All IP protocols</description>
         </valueHelp>
         <valueHelp>
           <format>ip</format>
           <description>Internet Protocol, pseudo protocol number</description>
         </valueHelp>
         <valueHelp>
           <format>hopopt</format>
           <description>IPv6 Hop-by-Hop Option [RFC1883]</description>
         </valueHelp>
         <valueHelp>
           <format>icmp</format>
           <description>internet control message protocol</description>
         </valueHelp>
         <valueHelp>
           <format>igmp</format>
           <description>Internet Group Management</description>
         </valueHelp>
         <valueHelp>
           <format>ggp</format>
           <description>gateway-gateway protocol</description>
         </valueHelp>
         <valueHelp>
           <format>ipencap</format>
           <description>IP encapsulated in IP (officially IP)</description>
         </valueHelp>
         <valueHelp>
           <format>st</format>
           <description>ST datagram mode</description>
         </valueHelp>
         <valueHelp>
           <format>tcp</format>
           <description>transmission control protocol</description>
         </valueHelp>
         <valueHelp>
           <format>egp</format>
           <description>exterior gateway protocol</description>
         </valueHelp>
         <valueHelp>
           <format>igp</format>
           <description>any private interior gateway (Cisco)</description>
         </valueHelp>
         <valueHelp>
           <format>pup</format>
           <description>PARC universal packet protocol</description>
         </valueHelp>
         <valueHelp>
           <format>udp</format>
           <description>user datagram protocol</description>
         </valueHelp>
         <valueHelp>
           <format>tcp_udp</format>
           <description>Both TCP and UDP</description>
         </valueHelp>
         <valueHelp>
           <format>hmp</format>
           <description>host monitoring protocol</description>
         </valueHelp>
         <valueHelp>
           <format>xns-idp</format>
           <description>Xerox NS IDP</description>
         </valueHelp>
         <valueHelp>
           <format>rdp</format>
           <description>"reliable datagram" protocol</description>
         </valueHelp>
         <valueHelp>
           <format>iso-tp4</format>
           <description>ISO Transport Protocol class 4 [RFC905]</description>
         </valueHelp>
         <valueHelp>
           <format>dccp</format>
           <description>Datagram Congestion Control Prot. [RFC4340]</description>
         </valueHelp>
         <valueHelp>
           <format>xtp</format>
           <description>Xpress Transfer Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>ddp</format>
           <description>Datagram Delivery Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>idpr-cmtp</format>
           <description>IDPR Control Message Transport</description>
         </valueHelp>
         <valueHelp>
           <format>Ipv6</format>
           <description>Internet Protocol, version 6</description>
         </valueHelp>
         <valueHelp>
           <format>ipv6-route</format>
           <description>Routing Header for IPv6</description>
         </valueHelp>
         <valueHelp>
           <format>ipv6-frag</format>
           <description>Fragment Header for IPv6</description>
         </valueHelp>
         <valueHelp>
           <format>idrp</format>
           <description>Inter-Domain Routing Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>rsvp</format>
           <description>Reservation Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>gre</format>
           <description>General Routing Encapsulation</description>
         </valueHelp>
         <valueHelp>
           <format>esp</format>
           <description>Encap Security Payload [RFC2406]</description>
         </valueHelp>
         <valueHelp>
           <format>ah</format>
           <description>Authentication Header [RFC2402]</description>
         </valueHelp>
         <valueHelp>
           <format>skip</format>
           <description>SKIP</description>
         </valueHelp>
         <valueHelp>
           <format>ipv6-icmp</format>
           <description>ICMP for IPv6</description>
         </valueHelp>
         <valueHelp>
           <format>ipv6-nonxt</format>
           <description>No Next Header for IPv6</description>
         </valueHelp>
         <valueHelp>
           <format>ipv6-opts</format>
           <description>Destination Options for IPv6</description>
         </valueHelp>
         <valueHelp>
           <format>rspf</format>
           <description>Radio Shortest Path First (officially CPHB)</description>
         </valueHelp>
         <valueHelp>
           <format>vmtp</format>
           <description>Versatile Message Transport</description>
         </valueHelp>
         <valueHelp>
           <format>eigrp</format>
           <description>Enhanced Interior Routing Protocol (Cisco)</description>
         </valueHelp>
         <valueHelp>
           <format>ospf</format>
           <description>Open Shortest Path First IGP</description>
         </valueHelp>
         <valueHelp>
           <format>ax.25</format>
           <description>AX.25 frames</description>
         </valueHelp>
         <valueHelp>
           <format>ipip</format>
           <description>IP-within-IP Encapsulation Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>etherip</format>
           <description>Ethernet-within-IP Encapsulation [RFC3378]</description>
         </valueHelp>
         <valueHelp>
           <format>encap</format>
           <description>Yet Another IP encapsulation [RFC1241]</description>
         </valueHelp>
         <valueHelp>
           <format>99</format>
           <description>Any private encryption scheme</description>
         </valueHelp>
         <valueHelp>
           <format>pim</format>
           <description>Protocol Independent Multicast</description>
         </valueHelp>
         <valueHelp>
           <format>ipcomp</format>
           <description>IP Payload Compression Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>vrrp</format>
           <description>Virtual Router Redundancy Protocol [RFC5798]</description>
         </valueHelp>
         <valueHelp>
           <format>l2tp</format>
           <description>Layer Two Tunneling Protocol [RFC2661]</description>
         </valueHelp>
         <valueHelp>
           <format>isis</format>
           <description>IS-IS over IPv4</description>
         </valueHelp>
         <valueHelp>
           <format>sctp</format>
           <description>Stream Control Transmission Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>fc</format>
           <description>Fibre Channel</description>
         </valueHelp>
         <valueHelp>
           <format>mobility-header</format>
           <description>Mobility Support for IPv6 [RFC3775]</description>
         </valueHelp>
         <valueHelp>
           <format>udplite</format>
           <description>UDP-Lite [RFC3828]</description>
         </valueHelp>
         <valueHelp>
           <format>mpls-in-ip</format>
           <description>MPLS-in-IP [RFC4023]</description>
         </valueHelp>
         <valueHelp>
           <format>manet</format>
           <description>MANET Protocols [RFC5498]</description>
         </valueHelp>
         <valueHelp>
           <format>hip</format>
           <description>Host Identity Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>shim6</format>
           <description>Shim6 Protocol</description>
         </valueHelp>
         <valueHelp>
           <format>wesp</format>
           <description>Wrapped Encapsulating Security Payload</description>
         </valueHelp>
         <valueHelp>
           <format>rohc</format>
           <description>Robust Header Compression</description>
         </valueHelp>
         <valueHelp>
           <format>u32:0-255</format>
           <description>IP protocol number</description>
         </valueHelp>
         <constraint>
           <validator name="ip-protocol"/>
         </constraint>
       </properties>
       <defaultValue>all</defaultValue>
     </leafNode>
     <node name="source">
       <properties>
         <help>NAT source parameters</help>
       </properties>
       <children>
         #include <include/nat-address.xml.i>
         #include <include/nat-port.xml.i>
         #include <include/firewall/source-destination-group.xml.i>
       </children>
     </node>
   </children>
 </tagNode>
 <!-- include end -->
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 603fedb9b..418efe649 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -1,248 +1,281 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2022 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 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/>.
 
 from vyos.template import is_ip_network
 from vyos.utils.dict import dict_search_args
 from vyos.template import bracketize_ipv6
 
 
 def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
     output = []
     ip_prefix = 'ip6' if ipv6 else 'ip'
     log_prefix = ('DST' if nat_type == 'destination' else 'SRC') + f'-NAT-{rule_id}'
     log_suffix = ''
 
     if ipv6:
         log_prefix = log_prefix.replace("NAT-", "NAT66-")
 
     ignore_type_addr = False
     translation_str = ''
 
     if 'inbound_interface' in rule_conf:
         ifname = rule_conf['inbound_interface']
         if ifname != 'any':
             output.append(f'iifname "{ifname}"')
 
     if 'outbound_interface' in rule_conf:
         ifname = rule_conf['outbound_interface']
         if ifname != 'any':
             output.append(f'oifname "{ifname}"')
 
     if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
         protocol = rule_conf['protocol']
         if protocol == 'tcp_udp':
             protocol = '{ tcp, udp }'
         output.append(f'meta l4proto {protocol}')
 
     if 'packet_type' in rule_conf:
         output.append(f'pkttype ' + rule_conf['packet_type'])
 
     if 'exclude' in rule_conf:
         translation_str = 'return'
         log_suffix = '-EXCL'
     elif 'translation' in rule_conf:
         addr = dict_search_args(rule_conf, 'translation', 'address')
         port = dict_search_args(rule_conf, 'translation', 'port')
         redirect_port = dict_search_args(rule_conf, 'translation', 'redirect', 'port')
         if redirect_port:
             translation_output = [f'redirect to {redirect_port}']
         else:
             translation_prefix = nat_type[:1]
             translation_output = [f'{translation_prefix}nat']
 
             if addr and is_ip_network(addr):
                 if not ipv6:
                     map_addr =  dict_search_args(rule_conf, nat_type, 'address')
                     translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
                     ignore_type_addr = True
                 else:
                     translation_output.append(f'prefix to {addr}')
             elif addr == 'masquerade':
                 if port:
                     addr = f'{addr} to '
                 translation_output = [addr]
                 log_suffix = '-MASQ'
             else:
                 translation_output.append('to')
                 if addr:
                     addr = bracketize_ipv6(addr)
                     translation_output.append(addr)
 
         options = []
         addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping')
         port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping')
         if addr_mapping == 'persistent':
             options.append('persistent')
         if port_mapping and port_mapping != 'none':
             options.append(port_mapping)
 
         translation_str = " ".join(translation_output) + (f':{port}' if port else '')
 
         if options:
             translation_str += f' {",".join(options)}'
 
+        if 'backend' in rule_conf['load_balance']:
+            hash_input_items = []
+            current_prob = 0
+            nat_map = []
+
+            for trans_addr, addr in rule_conf['load_balance']['backend'].items():
+                item_prob = int(addr['weight'])
+                upper_limit = current_prob + item_prob - 1
+                hash_val = str(current_prob) + '-' + str(upper_limit)
+                element = hash_val + " : " + trans_addr
+                nat_map.append(element)
+                current_prob = current_prob + item_prob
+
+            elements = ' , '.join(nat_map)
+
+            if 'hash' in rule_conf['load_balance'] and 'random' in rule_conf['load_balance']['hash']:
+                translation_str += ' numgen random mod 100 map ' + '{ ' + f'{elements}' + ' }'
+            else:
+                for input_param in rule_conf['load_balance']['hash']:
+                    if input_param == 'source-address':
+                        param = 'ip saddr'
+                    elif input_param == 'destination-address':
+                        param = 'ip daddr'
+                    elif input_param == 'source-port':
+                        prot = rule_conf['protocol']
+                        param = f'{prot} sport'
+                    elif input_param == 'destination-port':
+                        prot = rule_conf['protocol']
+                        param = f'{prot} dport'
+                    hash_input_items.append(param)
+                hash_input = ' . '.join(hash_input_items)
+                translation_str += f' jhash ' + f'{hash_input}' + ' mod 100 map ' + '{ ' + f'{elements}' + ' }'
+
     for target in ['source', 'destination']:
         if target not in rule_conf:
             continue
 
         side_conf = rule_conf[target]
         prefix = target[:1]
 
         addr = dict_search_args(side_conf, 'address')
         if addr and not (ignore_type_addr and target == nat_type):
             operator = ''
             if addr[:1] == '!':
                 operator = '!='
                 addr = addr[1:]
             output.append(f'{ip_prefix} {prefix}addr {operator} {addr}')
 
         addr_prefix = dict_search_args(side_conf, 'prefix')
         if addr_prefix and ipv6:
             operator = ''
             if addr_prefix[:1] == '!':
                 operator = '!='
                 addr_prefix = addr[1:]
             output.append(f'ip6 {prefix}addr {operator} {addr_prefix}')
 
         port = dict_search_args(side_conf, 'port')
         if port:
             protocol = rule_conf['protocol']
             if protocol == 'tcp_udp':
                 protocol = 'th'
             operator = ''
             if port[:1] == '!':
                 operator = '!='
                 port = port[1:]
             output.append(f'{protocol} {prefix}port {operator} {{ {port} }}')
 
         if 'group' in side_conf:
             group = side_conf['group']
             if 'address_group' in group and not (ignore_type_addr and target == nat_type):
                 group_name = group['address_group']
                 operator = ''
                 if group_name[0] == '!':
                     operator = '!='
                     group_name = group_name[1:]
                 output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}')
             # Generate firewall group domain-group
             elif 'domain_group' in group and not (ignore_type_addr and target == nat_type):
                 group_name = group['domain_group']
                 operator = ''
                 if group_name[0] == '!':
                     operator = '!='
                     group_name = group_name[1:]
                 output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}')
             elif 'network_group' in group and not (ignore_type_addr and target == nat_type):
                 group_name = group['network_group']
                 operator = ''
                 if group_name[0] == '!':
                     operator = '!='
                     group_name = group_name[1:]
                 output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}')
             if 'mac_group' in group:
                 group_name = group['mac_group']
                 operator = ''
                 if group_name[0] == '!':
                     operator = '!='
                     group_name = group_name[1:]
                 output.append(f'ether {prefix}addr {operator} @M_{group_name}')
             if 'port_group' in group:
                 proto = rule_conf['protocol']
                 group_name = group['port_group']
 
                 if proto == 'tcp_udp':
                     proto = 'th'
 
                 operator = ''
                 if group_name[0] == '!':
                     operator = '!='
                     group_name = group_name[1:]
 
                 output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
 
     output.append('counter')
 
     if 'log' in rule_conf:
         output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
 
     if translation_str:
         output.append(translation_str)
 
     output.append(f'comment "{log_prefix}"')
 
     return " ".join(output)
 
 def parse_nat_static_rule(rule_conf, rule_id, nat_type):
     output = []
     log_prefix = ('STATIC-DST' if nat_type == 'destination' else 'STATIC-SRC') + f'-NAT-{rule_id}'
     log_suffix = ''
 
     ignore_type_addr = False
     translation_str = ''
 
     if 'inbound_interface' in rule_conf:
         ifname = rule_conf['inbound_interface']
         ifprefix = 'i' if nat_type == 'destination' else 'o'
         if ifname != 'any':
             output.append(f'{ifprefix}ifname "{ifname}"')
 
     if 'exclude' in rule_conf:
         translation_str = 'return'
         log_suffix = '-EXCL'
     elif 'translation' in rule_conf:
         translation_prefix = nat_type[:1]
         translation_output = [f'{translation_prefix}nat']
         addr = dict_search_args(rule_conf, 'translation', 'address')
         map_addr =  dict_search_args(rule_conf, 'destination', 'address')
 
         if nat_type == 'source':
             addr, map_addr = map_addr, addr # Swap
 
         if addr and is_ip_network(addr):
             translation_output.append(f'ip prefix to ip {translation_prefix}addr map {{ {map_addr} : {addr} }}')
             ignore_type_addr = True
         elif addr:
             translation_output.append(f'to {addr}')
 
         options = []
         addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping')
         port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping')
         if addr_mapping == 'persistent':
             options.append('persistent')
         if port_mapping and port_mapping != 'none':
             options.append(port_mapping)
 
         if options:
             translation_output.append(",".join(options))
 
         translation_str = " ".join(translation_output)
 
     prefix = nat_type[:1]
     addr = dict_search_args(rule_conf, 'translation' if nat_type == 'source' else nat_type, 'address')
     if addr and not ignore_type_addr:
         output.append(f'ip {prefix}addr {addr}')
 
     output.append('counter')
 
     if translation_str:
         output.append(translation_str)
 
     if 'log' in rule_conf:
         output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
 
     output.append(f'comment "{log_prefix}"')
 
     return " ".join(output)
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 28d566eba..e6eaedeff 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -1,256 +1,292 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020-2022 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 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/>.
 
 import jmespath
 import json
 import os
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 from vyos.configsession import ConfigSessionError
 from vyos.utils.process import cmd
 from vyos.utils.dict import dict_search
 
 base_path = ['nat']
 src_path = base_path + ['source']
 dst_path = base_path + ['destination']
 static_path = base_path + ['static']
 
 nftables_nat_config = '/run/nftables_nat.conf'
 nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
 
 class TestNAT(VyOSUnitTestSHIM.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestNAT, cls).setUpClass()
 
         # ensure we can also run this test on a live system - so lets clean
         # out the current configuration :)
         cls.cli_delete(cls, base_path)
 
     def tearDown(self):
         self.cli_delete(base_path)
         self.cli_commit()
         self.assertFalse(os.path.exists(nftables_nat_config))
         self.assertFalse(os.path.exists(nftables_static_nat_conf))
 
     def verify_nftables(self, nftables_search, table, inverse=False, args=''):
         nftables_output = cmd(f'sudo nft {args} list table {table}')
 
         for search in nftables_search:
             matched = False
             for line in nftables_output.split("\n"):
                 if all(item in line for item in search):
                     matched = True
                     break
             self.assertTrue(not matched if inverse else matched, msg=search)
 
     def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
         # Resolver no longer blocks commit, need to wait for daemon to populate set
         count = 0
         while count < max_wait:
             code = run(f'sudo nft get element {table} {set_name} {{ {element} }}')
             if code == 0:
                 return True
             count += 1
             sleep(1)
         return False
 
     def test_snat(self):
         rules = ['100', '110', '120', '130', '200', '210', '220', '230']
         outbound_iface_100 = 'eth0'
         outbound_iface_200 = 'eth1'
 
         nftables_search = ['jump VYOS_PRE_SNAT_HOOK']
 
         for rule in rules:
             network = f'192.168.{rule}.0/24'
             # depending of rule order we check either for source address for NAT
             # or configured destination address for NAT
             if int(rule) < 200:
                 self.cli_set(src_path + ['rule', rule, 'source', 'address', network])
                 self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_100])
                 self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
                 nftables_search.append([f'saddr {network}', f'oifname "{outbound_iface_100}"', 'masquerade'])
             else:
                 self.cli_set(src_path + ['rule', rule, 'destination', 'address', network])
                 self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_200])
                 self.cli_set(src_path + ['rule', rule, 'exclude'])
                 nftables_search.append([f'daddr {network}', f'oifname "{outbound_iface_200}"', 'return'])
 
         self.cli_commit()
 
         self.verify_nftables(nftables_search, 'ip vyos_nat')
 
     def test_snat_groups(self):
         address_group = 'smoketest_addr'
         address_group_member = '192.0.2.1'
         rule = '100'
         outbound_iface = 'eth0'
 
         self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
 
         self.cli_set(src_path + ['rule', rule, 'source', 'group', 'address-group', address_group])
         self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface])
         self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
 
         self.cli_commit()
 
         nftables_search = [
             [f'set A_{address_group}'],
             [f'elements = {{ {address_group_member} }}'],
             [f'ip saddr @A_{address_group}', f'oifname "{outbound_iface}"', 'masquerade']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_nat')
 
         self.cli_delete(['firewall'])
 
     def test_dnat(self):
         rules = ['100', '110', '120', '130', '200', '210', '220', '230']
         inbound_iface_100 = 'eth0'
         inbound_iface_200 = 'eth1'
         inbound_proto_100 = 'udp'
         inbound_proto_200 = 'tcp'
 
         nftables_search = ['jump VYOS_PRE_DNAT_HOOK']
 
         for rule in rules:
             port = f'10{rule}'
             self.cli_set(dst_path + ['rule', rule, 'source', 'port', port])
             self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1'])
             self.cli_set(dst_path + ['rule', rule, 'translation', 'port', port])
             rule_search = [f'dnat to 192.0.2.1:{port}']
             if int(rule) < 200:
                 self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_100])
                 self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_100])
                 rule_search.append(f'{inbound_proto_100} sport {port}')
                 rule_search.append(f'iifname "{inbound_iface_100}"')
             else:
                 self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_200])
                 self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_200])
                 rule_search.append(f'iifname "{inbound_iface_200}"')
 
             nftables_search.append(rule_search)
 
         self.cli_commit()
 
         self.verify_nftables(nftables_search, 'ip vyos_nat')
 
     def test_snat_required_translation_address(self):
         # T2813: Ensure translation address is specified
         rule = '5'
         self.cli_set(src_path + ['rule', rule, 'source', 'address', '192.0.2.0/24'])
 
         # check validate() - outbound-interface must be defined
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'eth0'])
 
         # check validate() - translation address not specified
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
         self.cli_commit()
 
     def test_dnat_negated_addresses(self):
         # T3186: negated addresses are not accepted by nftables
         rule = '1000'
         self.cli_set(dst_path + ['rule', rule, 'destination', 'address', '!192.0.2.1'])
         self.cli_set(dst_path + ['rule', rule, 'destination', 'port', '53'])
         self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'eth0'])
         self.cli_set(dst_path + ['rule', rule, 'protocol', 'tcp_udp'])
         self.cli_set(dst_path + ['rule', rule, 'source', 'address', '!192.0.2.1'])
         self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1'])
         self.cli_set(dst_path + ['rule', rule, 'translation', 'port', '53'])
         self.cli_commit()
 
     def test_nat_no_rules(self):
         # T3206: deleting all rules but keep the direction 'destination' or
         # 'source' resulteds in KeyError: 'rule'.
         #
         # Test that both 'nat destination' and 'nat source' nodes can exist
         # without any rule
         self.cli_set(src_path)
         self.cli_set(dst_path)
         self.cli_set(static_path)
         self.cli_commit()
 
     def test_dnat_without_translation_address(self):
         self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
         self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443'])
         self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp'])
         self.cli_set(dst_path + ['rule', '1', 'packet-type', 'host'])
         self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443'])
 
         self.cli_commit()
 
         nftables_search = [
             ['iifname "eth1"', 'tcp dport 443', 'pkttype host', 'dnat to :443']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_nat')
 
     def test_static_nat(self):
         dst_addr_1 = '10.0.1.1'
         translate_addr_1 = '192.168.1.1'
         dst_addr_2 = '203.0.113.0/24'
         translate_addr_2 = '192.0.2.0/24'
         ifname = 'eth0'
 
         self.cli_set(static_path + ['rule', '10', 'destination', 'address', dst_addr_1])
         self.cli_set(static_path + ['rule', '10', 'inbound-interface', ifname])
         self.cli_set(static_path + ['rule', '10', 'translation', 'address', translate_addr_1])
 
         self.cli_set(static_path + ['rule', '20', 'destination', 'address', dst_addr_2])
         self.cli_set(static_path + ['rule', '20', 'inbound-interface', ifname])
         self.cli_set(static_path + ['rule', '20', 'translation', 'address', translate_addr_2])
 
         self.cli_commit()
 
         nftables_search = [
             [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'dnat to {translate_addr_1}'],
             [f'oifname "{ifname}"', f'ip saddr {translate_addr_1}', f'snat to {dst_addr_1}'],
             [f'iifname "{ifname}"', f'dnat ip prefix to ip daddr map {{ {dst_addr_2} : {translate_addr_2} }}'],
             [f'oifname "{ifname}"', f'snat ip prefix to ip saddr map {{ {translate_addr_2} : {dst_addr_2} }}']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_static_nat')
 
     def test_dnat_redirect(self):
         dst_addr_1 = '10.0.1.1'
         dest_port = '5122'
         protocol = 'tcp'
         redirected_port = '22'
         ifname = 'eth0'
 
         self.cli_set(dst_path + ['rule', '10', 'destination', 'address', dst_addr_1])
         self.cli_set(dst_path + ['rule', '10', 'destination', 'port', dest_port])
         self.cli_set(dst_path + ['rule', '10', 'protocol', protocol])
         self.cli_set(dst_path + ['rule', '10', 'inbound-interface', ifname])
         self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port])
 
         self.cli_commit()
 
         nftables_search = [
             [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_nat')
 
+    def test_nat_balance(self):
+        ifname = 'eth0'
+        member_1 = '198.51.100.1'
+        weight_1 = '10'
+        member_2 = '198.51.100.2'
+        weight_2 = '90'
+        member_3 = '192.0.2.1'
+        weight_3 = '35'
+        member_4 = '192.0.2.2'
+        weight_4 = '65'
+        dst_port = '443'
+
+        self.cli_set(dst_path + ['rule', '1', 'inbound-interface', ifname])
+        self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp'])
+        self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dst_port])
+        self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-address'])
+        self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-port'])
+        self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-address'])
+        self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-port'])
+        self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_1, 'weight', weight_1])
+        self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_2, 'weight', weight_2])
+
+        self.cli_set(src_path + ['rule', '1', 'outbound-interface', ifname])
+        self.cli_set(src_path + ['rule', '1', 'load-balance', 'hash', 'random'])
+        self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_3, 'weight', weight_3])
+        self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_4, 'weight', weight_4])
+
+        self.cli_commit()
+
+        nftables_search = [
+            [f'iifname "{ifname}"', f'tcp dport {dst_port}', f'dnat to jhash ip saddr . tcp sport . ip daddr . tcp dport mod 100 map', f'0-9 : {member_1}, 10-99 : {member_2}'],
+            [f'oifname "{ifname}"', f'snat to numgen random mod 100 map', f'0-34 : {member_3}, 35-99 : {member_4}']
+        ]
+
+        self.verify_nftables(nftables_search, 'ip vyos_nat')
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index b27470b6e..8e3a11ff4 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -1,282 +1,293 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 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/>.
 
 import jmespath
 import json
 import os
 
 from distutils.version import LooseVersion
 from platform import release as kernel_version
 from sys import exit
 from netifaces import interfaces
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.configdict import dict_merge
 from vyos.template import render
 from vyos.template import is_ip_network
 from vyos.utils.kernel import check_kmod
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_search_args
 from vyos.utils.process import cmd
 from vyos.utils.process import run
 from vyos.utils.network import is_addr_assigned
 from vyos.xml import defaults
 from vyos import ConfigError
 
 from vyos import airbag
 airbag.enable()
 
 if LooseVersion(kernel_version()) > LooseVersion('5.1'):
     k_mod = ['nft_nat', 'nft_chain_nat']
 else:
     k_mod = ['nft_nat', 'nft_chain_nat_ipv4']
 
 nftables_nat_config = '/run/nftables_nat.conf'
 nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
 
 valid_groups = [
     'address_group',
     'domain_group',
     'network_group',
     'port_group'
 ]
 
 def get_handler(json, chain, target):
     """ Get nftable rule handler number of given chain/target combination.
     Handler is required when adding NAT/Conntrack helper targets """
     for x in json:
         if x['chain'] != chain:
             continue
         if x['target'] != target:
             continue
         return x['handle']
 
     return None
 
 
 def verify_rule(config, err_msg, groups_dict):
     """ Common verify steps used for both source and destination NAT """
 
     if (dict_search('translation.port', config) != None or
         dict_search('translation.redirect.port', config) != None or
         dict_search('destination.port', config) != None or
         dict_search('source.port', config)):
 
         if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
             raise ConfigError(f'{err_msg}\n' \
                               'ports can only be specified when protocol is '\
                               'either tcp, udp or tcp_udp!')
 
         if is_ip_network(dict_search('translation.address', config)):
             raise ConfigError(f'{err_msg}\n' \
                              'Cannot use ports with an IPv4 network as translation address as it\n' \
                              'statically maps a whole network of addresses onto another\n' \
                              'network of addresses')
 
     for side in ['destination', 'source']:
         if side in config:
             side_conf = config[side]
 
             if len({'address', 'fqdn'} & set(side_conf)) > 1:
                 raise ConfigError('Only one of address, fqdn or geoip can be specified')
 
             if 'group' in side_conf:
                 if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
                     raise ConfigError('Only one address-group, network-group or domain-group can be specified')
 
                 for group in valid_groups:
                     if group in side_conf['group']:
                         group_name = side_conf['group'][group]
                         error_group = group.replace("_", "-")
 
                         if group in ['address_group', 'network_group', 'domain_group']:
                             types = [t for t in ['address', 'fqdn'] if t in side_conf]
                             if types:
                                 raise ConfigError(f'{error_group} and {types[0]} cannot both be defined')
 
                         if group_name and group_name[0] == '!':
                             group_name = group_name[1:]
 
                         group_obj = dict_search_args(groups_dict, group, group_name)
 
                         if group_obj is None:
                             raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
 
                         if not group_obj:
                             Warning(f'{error_group} "{group_name}" has no members!')
 
             if dict_search_args(side_conf, 'group', 'port_group'):
                 if 'protocol' not in config:
                     raise ConfigError('Protocol must be defined if specifying a port-group')
 
                 if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
                     raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group')
 
+    if 'load_balance' in config:
+        for item in ['source-port', 'destination-port']:
+            if item in config['load_balance']['hash'] and config['protocol'] not in ['tcp', 'udp']:
+                raise ConfigError('Protocol must be tcp or udp when specifying hash ports')
+        count = 0
+        if 'backend' in config['load_balance']:
+            for member in config['load_balance']['backend']:
+                weight = config['load_balance']['backend'][member]['weight']
+                count = count +  int(weight)
+            if count != 100:
+                Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour')
+
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     base = ['nat']
     nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
 
     # T2665: we must add the tagNode defaults individually until this is
     # moved to the base class
     for direction in ['source', 'destination', 'static']:
         if direction in nat:
             default_values = defaults(base + [direction, 'rule'])
             for rule in dict_search(f'{direction}.rule', nat) or []:
                 nat[direction]['rule'][rule] = dict_merge(default_values,
                     nat[direction]['rule'][rule])
 
     # read in current nftable (once) for further processing
     tmp = cmd('nft -j list table raw')
     nftable_json = json.loads(tmp)
 
     # condense the full JSON table into a list with only relevand informations
     pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
     condensed_json = jmespath.search(pattern, nftable_json)
 
     if not conf.exists(base):
         if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'):
             nat['helper_functions'] = 'remove'
 
             # Retrieve current table handler positions
             nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
             nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
             nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
             nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
         nat['deleted'] = ''
         return nat
 
     nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
                                     no_tag_node_value_mangle=True)
 
     # check if NAT connection tracking helpers need to be set up - this has to
     # be done only once
     if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
         nat['helper_functions'] = 'add'
 
         # Retrieve current table handler positions
         nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
         nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
         nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
         nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
 
     return nat
 
 def verify(nat):
     if not nat or 'deleted' in nat:
         # no need to verify the CLI as NAT is going to be deactivated
         return None
 
     if 'helper_functions' in nat:
         if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']):
             raise Exception('could not determine nftable ruleset handlers')
 
     if dict_search('source.rule', nat):
         for rule, config in dict_search('source.rule', nat).items():
             err_msg = f'Source NAT configuration error in rule {rule}:'
             if 'outbound_interface' not in config:
                 raise ConfigError(f'{err_msg} outbound-interface not specified')
 
             if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
                 Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
 
             if not dict_search('translation.address', config) and not dict_search('translation.port', config):
-                if 'exclude' not in config:
+                if 'exclude' not in config and 'backend' not in config['load_balance']:
                     raise ConfigError(f'{err_msg} translation requires address and/or port')
 
             addr = dict_search('translation.address', config)
             if addr != None and addr != 'masquerade' and not is_ip_network(addr):
                 for ip in addr.split('-'):
                     if not is_addr_assigned(ip):
                         Warning(f'IP address {ip} does not exist on the system!')
 
             # common rule verification
             verify_rule(config, err_msg, nat['firewall_group'])
 
-
     if dict_search('destination.rule', nat):
         for rule, config in dict_search('destination.rule', nat).items():
             err_msg = f'Destination NAT configuration error in rule {rule}:'
 
             if 'inbound_interface' not in config:
                 raise ConfigError(f'{err_msg}\n' \
                                   'inbound-interface not specified')
             elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
                 Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
 
             if not dict_search('translation.address', config) and not dict_search('translation.port', config) and not dict_search('translation.redirect.port', config):
-                if 'exclude' not in config:
+                if 'exclude' not in config and 'backend' not in config['load_balance']:
                     raise ConfigError(f'{err_msg} translation requires address and/or port')
 
             # common rule verification
             verify_rule(config, err_msg, nat['firewall_group'])
 
     if dict_search('static.rule', nat):
         for rule, config in dict_search('static.rule', nat).items():
             err_msg = f'Static NAT configuration error in rule {rule}:'
 
             if 'inbound_interface' not in config:
                 raise ConfigError(f'{err_msg}\n' \
                                   'inbound-interface not specified')
 
             # common rule verification
             verify_rule(config, err_msg, nat['firewall_group'])
 
     return None
 
 def generate(nat):
     if not os.path.exists(nftables_nat_config):
         nat['first_install'] = True
 
     render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
     render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat)
 
     # dry-run newly generated configuration
     tmp = run(f'nft -c -f {nftables_nat_config}')
     if tmp > 0:
         raise ConfigError('Configuration file errors encountered!')
 
     tmp = run(f'nft -c -f {nftables_static_nat_conf}')
     if tmp > 0:
         raise ConfigError('Configuration file errors encountered!')
 
     return None
 
 def apply(nat):
     cmd(f'nft -f {nftables_nat_config}')
     cmd(f'nft -f {nftables_static_nat_conf}')
 
     if not nat or 'deleted' in nat:
         os.unlink(nftables_nat_config)
         os.unlink(nftables_static_nat_conf)
 
     return None
 
 if __name__ == '__main__':
     try:
         check_kmod(k_mod)
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)