diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD
index 151522ab2..d7a229929 100644
--- a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD
+++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD
@@ -1 +1 @@
-dc99863d1f2b
+fd3e605a7232
diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/fd3e605a7232_create_hpp_db.py b/gbpservice/neutron/db/migration/alembic_migrations/versions/fd3e605a7232_create_hpp_db.py
new file mode 100644
index 000000000..26186a65a
--- /dev/null
+++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/fd3e605a7232_create_hpp_db.py
@@ -0,0 +1,51 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""HPP Table
+
+Revision ID: fd3e605a7232
+Revises: dc99863d1f2b
+Create Date: 2023-09-19 02:08:54.252877
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'fd3e605a7232'
+down_revision = 'dc99863d1f2b'
+
+from alembic import op
+from alembic import util
+import sqlalchemy as sa
+
+
+def upgrade():
+
+    bind = op.get_bind()
+    op.create_table(
+        'apic_aim_hpp',
+        sa.Column('hpp_normalized', sa.Boolean, nullable=False),
+        sa.PrimaryKeyConstraint('hpp_normalized'))
+
+    try:
+        from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
+             data_migrations)
+
+        session = sa.orm.Session(bind=bind, autocommit=True)
+        with session.begin(subtransactions=True):
+            data_migrations.do_hpp_insertion(session)
+    except Exception as e:
+        util.warn("Caught exception while migrating data in %s: %s" %
+            ('apic_aim_hpp', e))
+
+
+def downgrade():
+    pass
diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py
index 4a4f30230..bca7446a8 100644
--- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py
+++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/data_migrations.py
@@ -70,6 +70,11 @@ AddressScopeMapping = sa.Table(
         sa.Column('vrf_owned', sa.Boolean, nullable=False))
 
 
+HPPDB = sa.Table(
+        'apic_aim_hpp', sa.MetaData(),
+        sa.Column('hpp_normalized', sa.Boolean, nullable=False))
+
+
 # The following definition has been taken from commit:
 # f8b41855acbbb7e59a0bab439445c198fc6aa146
 # and is frozen for the data migration script that was included
@@ -496,8 +501,7 @@ def do_ha_ip_network_id_insertion(session):
     haip_port_id = HAIPAddressToPortAssociation.c.port_id
 
     with db_api.CONTEXT_WRITER.using(session):
-        haip_ip = HAIPAddressToPortAssociation.c.ha_ip_address
-        haip_port_id = HAIPAddressToPortAssociation.c.port_id
+
         port_and_haip_dbs = (session.query(models_v2.Port,
                              HAIPAddressToPortAssociation).join(
                              HAIPAddressToPortAssociation,
@@ -512,3 +516,93 @@ def do_ha_ip_network_id_insertion(session):
 
     alembic_util.msg(
         "Finished network id insertion for HA IP table.")
+
+
+def do_hpp_insertion(session):
+    alembic_util.msg(
+        "Starting hpp normalized value insertion for HPP table.")
+
+    with session.begin(subtransactions=True):
+        session.execute(HPPDB.insert().values(hpp_normalized=False))
+
+    alembic_util.msg(
+        "Finished hpp normalized value insertion for HPP table.")
+
+
+def normalize_hpp_ips(session):
+    aim = aim_manager.AimManager()
+    aim_ctx = aim_context.AimContext(session)
+    mapper = apic_mapper.APICNameMapper()
+
+    sg_dbs = (session.query(sg_models.SecurityGroup).
+              options(lazyload('*')).all())
+    for sg_db in sg_dbs:
+        tenant_aname = mapper.project(session,
+            sg_db['tenant_id'])
+
+        # Create remote group container for each security group
+        try:
+            sg_remote_group = aim_resource.SecurityGroupRemoteIpContainer(
+                tenant_name=tenant_aname,
+                security_group_name=sg_db['id'],
+                name=sg_db['id'])
+            aim.create(aim_ctx, sg_remote_group)
+        except Exception as e:
+            alembic_util.warning("%s" % e)
+
+        sg_ports = (session.query(models_v2.Port).
+                    join(sg_models.SecurityGroupPortBinding,
+                         sg_models.SecurityGroupPortBinding.port_id ==
+                         models_v2.Port.id).
+                    filter(sg_models.SecurityGroupPortBinding.
+                           security_group_id ==
+                           sg_db['id']).
+                    options(lazyload('*')).all())
+
+        for sg_port in sg_ports:
+            for fixed_ip in sg_port['fixed_ips']:
+                # Create SG remote ip resource for each fixed ip
+                sg_remote_group_ip = aim_resource.SecurityGroupRemoteIp(
+                    tenant_name=tenant_aname,
+                    security_group_name=sg_db['id'],
+                    addr=fixed_ip['ip_address'])
+                aim.create(aim_ctx, sg_remote_group_ip)
+
+
+def normalize_hpps(session):
+    aim = aim_manager.AimManager()
+    aim_ctx = aim_context.AimContext(session)
+    mapper = apic_mapper.APICNameMapper()
+
+    sg_rule_dbs = (session.query(sg_models.SecurityGroupRule).
+                   options(lazyload('*')).all())
+    for sg_rule_db in sg_rule_dbs:
+        sg = (session.query(sg_models.SecurityGroup).
+            filter(sg_models.SecurityGroup.id ==
+                   sg_rule_db['security_group_id']).
+            options(lazyload('*')).first())
+        tenant_id = sg['tenant_id']
+        tenant_aname = mapper.project(session, tenant_id)
+        if sg_rule_db.get('remote_group_id'):
+            sg_rule_aim = aim_resource.SecurityGroupRule(
+                tenant_name=tenant_aname,
+                security_group_name=sg_rule_db['security_group_id'],
+                security_group_subject_name='default',
+                name=sg_rule_db['id'])
+            sg_rule_aim = aim.get(aim_ctx, sg_rule_aim)
+
+            if sg_rule_aim.remote_ips:
+                # Get the remote group container's dn
+                rg = (session.query(sg_models.SecurityGroup).
+                    filter(sg_models.SecurityGroup.id ==
+                           sg_rule_db['remote_group_id']).
+                    options(lazyload('*')).first())
+                rg_tenant_id = rg['tenant_id']
+                rg_tenant_aname = mapper.project(session, rg_tenant_id)
+                rg_cont = aim_resource.SecurityGroupRemoteIpContainer(
+                    tenant_name=rg_tenant_aname,
+                    security_group_name=sg_rule_db['remote_group_id'],
+                    name=sg_rule_db['remote_group_id'])
+                # Update SG rule tDn with remote group container dn
+                aim.update(aim_ctx, sg_rule_aim,
+                    remote_ips=[], tDn=rg_cont.dn)
diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py
index a2a827b87..4f62e18f1 100644
--- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py
+++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py
@@ -186,6 +186,13 @@ class RouterExtensionContractDb(model_base.BASEV2):
     provides = sa.Column(sa.Boolean, primary_key=True)
 
 
+class HPPDb(model_base.BASEV2):
+    __tablename__ = 'apic_aim_hpp'
+
+    hpp_normalized = sa.Column(sa.Boolean, default=False,
+                               primary_key=True)
+
+
 class ExtensionDbMixin(object):
 
     def _set_if_not_none(self, res_dict, res_attr, db_attr):
@@ -706,3 +713,16 @@ class ExtensionDbMixin(object):
                     'contract_name',
                     res_dict[cisco_apic_l3.EXTERNAL_CONSUMED_CONTRACTS],
                     router_id=router_id, provides=False)
+
+    def get_hpp_normalized(self, session):
+        with session.begin(subtransactions=True):
+            query = BAKERY(lambda s: s.query(HPPDb))
+            db_obj = query(session).first()
+            return db_obj['hpp_normalized']
+
+    def set_hpp_normalized(self, session, hpp_normalized):
+        with session.begin(subtransactions=True):
+            query = BAKERY(lambda s: s.query(HPPDb))
+            db_obj = query(session).first()
+            db_obj['hpp_normalized'] = hpp_normalized
+            session.add(db_obj)
diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py
index ef77dff47..00d43e5c3 100644
--- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py
+++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py
@@ -87,6 +87,7 @@ from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
 from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper
 from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import cache
 from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import config  # noqa
+from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import data_migrations
 from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import db
 from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import exceptions
 from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db
@@ -2837,18 +2838,77 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
         # and try binding hierarchically if the network-type is OpFlex.
         self._bind_physical_node(context)
 
-    def _update_sg_rule_with_remote_group_set(self, context, port):
-        security_groups = port['security_groups']
-        original_port = context.original
-        if original_port:
-            removed_sgs = (set(original_port['security_groups']) -
-                           set(security_groups))
-            added_sgs = (set(security_groups) -
-                         set(original_port['security_groups']))
-            self._really_update_sg_rule_with_remote_group_set(
-                                context, port, removed_sgs, is_delete=True)
-            self._really_update_sg_rule_with_remote_group_set(
-                                context, port, added_sgs, is_delete=False)
+    def _really_update_sg_remote_groups(
+                    self, context, port, security_groups, is_delete):
+        if not security_groups:
+            return
+        session = context._plugin_context.session
+        aim_ctx = aim_context.AimContext(session)
+
+        fixed_ips = [x['ip_address'] for x in port['fixed_ips']]
+        for sg in security_groups:
+            tenant_id = self._get_sg_tenant_id(session, sg)
+            tenant_aname = self.name_mapper.project(session, tenant_id)
+            for fixed_ip in fixed_ips:
+                # Create or delete SG remote ip resource for fixed ip
+                sg_rg_ip_aim = aim_resource.SecurityGroupRemoteIp(
+                    tenant_name=tenant_aname,
+                    security_group_name=sg,
+                    addr=fixed_ip)
+                if is_delete:
+                    if self.aim.get(aim_ctx, sg_rg_ip_aim):
+                        self.aim.delete(aim_ctx, sg_rg_ip_aim)
+                else:
+                    if not self.aim.get(aim_ctx, sg_rg_ip_aim):
+                        self.aim.create(aim_ctx, sg_rg_ip_aim)
+
+            query = BAKERY(lambda s: s.query(
+                models_v2.Port))
+            query += lambda q: q.join(
+                sg_models.SecurityGroupPortBinding,
+                sg_models.SecurityGroupPortBinding.port_id ==
+                models_v2.Port.id)
+            query += lambda q: q.filter(
+                sg_models.SecurityGroupPortBinding.security_group_id ==
+                sa.bindparam('sg_id'))
+            sg_ports = query(session).params(
+                sg_id=sg).all()
+
+            all_fixed_ips = []
+            for sg_port in sg_ports:
+                ip_addr = [x['ip_address'] for x in sg_port['fixed_ips']]
+                all_fixed_ips.extend(ip_addr)
+            old_fixed_ips = list(set(all_fixed_ips) - set(fixed_ips))
+
+            if (fixed_ips and not old_fixed_ips):
+                query = BAKERY(lambda s: s.query(
+                    sg_models.SecurityGroupRule))
+                query += lambda q: q.filter(
+                    sg_models.SecurityGroupRule.remote_group_id ==
+                    sa.bindparam('security_group'))
+                sg_rules = query(session).params(
+                    security_group=sg).all()
+
+                sg_rule_to_tenant = {}
+                for sg_rule in sg_rules:
+                    sg_id = sg_rule['security_group_id']
+                    sg_rule_tenant_id = sg_rule_to_tenant.setdefault(sg_id,
+                        self._get_sg_rule_tenant_id(session, sg_rule))
+                    sg_rule_tenant_aname = self.name_mapper.project(
+                        session, sg_rule_tenant_id)
+                    sg_rule_aim = aim_resource.SecurityGroupRule(
+                        tenant_name=sg_rule_tenant_aname,
+                        security_group_name=sg_id,
+                        security_group_subject_name='default',
+                        name=sg_rule['id'])
+                    if is_delete:
+                        self.aim.update(aim_ctx, sg_rule_aim, tDn='')
+                    else:
+                        rg_cont = aim_resource.SecurityGroupRemoteIpContainer(
+                            tenant_name=tenant_aname,
+                            security_group_name=sg_rule['remote_group_id'],
+                            name=sg_rule['remote_group_id'])
+                        self.aim.update(aim_ctx, sg_rule_aim, tDn=rg_cont.dn)
 
     def _really_update_sg_rule_with_remote_group_set(
                     self, context, port, security_groups, is_delete):
@@ -2869,11 +2929,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
         sg_to_tenant = {}
         for sg_rule in sg_rules:
             sg_id = sg_rule['security_group_id']
-            if sg_id in sg_to_tenant:
-                tenant_id = sg_to_tenant[sg_id]
-            else:
-                tenant_id = self._get_sg_rule_tenant_id(session, sg_rule)
-                sg_to_tenant[sg_id] = tenant_id
+            tenant_id = sg_to_tenant.setdefault(sg_id,
+                self._get_sg_rule_tenant_id(session, sg_rule))
             tenant_aname = self.name_mapper.project(session, tenant_id)
             sg_rule_aim = aim_resource.SecurityGroupRule(
                 tenant_name=tenant_aname,
@@ -3140,11 +3197,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 
     def create_port_precommit(self, context):
         port = context.current
+        session = context._plugin_context.session
         self._check_active_active_aap(context, port)
         if port.get(cisco_apic.ERSPAN_CONFIG):
             self._check_valid_erspan_config(port)
-        self._really_update_sg_rule_with_remote_group_set(
-            context, port, port['security_groups'], is_delete=False)
+        if self.get_hpp_normalized(session):
+            self._really_update_sg_remote_groups(
+                context, port, port['security_groups'], is_delete=False)
+        else:
+            self._really_update_sg_rule_with_remote_group_set(
+                context, port, port['security_groups'], is_delete=False)
         self._insert_provisioning_block(context)
 
         # Handle router gateway port creation.
@@ -3366,7 +3428,41 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                                                erspan_deletions)
                 self._create_erspan_aim_config(context, cep_dn, port)
 
-        self._update_sg_rule_with_remote_group_set(context, port)
+        security_groups = port['security_groups']
+        # Check if any security groups are changed for the port
+        removed_sgs = (set(orig['security_groups']) -
+                       set(security_groups))
+        added_sgs = (set(security_groups) -
+                     set(orig['security_groups']))
+
+        # Check if any fixed ips are changed for the port
+        orig_ips = [x['ip_address'] for x in orig['fixed_ips']]
+
+        ips = [x['ip_address'] for x in port['fixed_ips']]
+        removed_ips = (set(orig_ips) - set(ips))
+        added_ips = (set(ips) - set(orig_ips))
+
+        if self.get_hpp_normalized(session):
+            self._really_update_sg_remote_groups(
+                                context, port, removed_sgs, is_delete=True)
+            self._really_update_sg_remote_groups(
+                                context, port, added_sgs, is_delete=False)
+            if removed_ips or added_ips:
+                self._really_update_sg_remote_groups(
+                    context, orig, security_groups, is_delete=True)
+                self._really_update_sg_remote_groups(
+                    context, port, security_groups, is_delete=False)
+        else:
+            self._really_update_sg_rule_with_remote_group_set(
+                                context, port, removed_sgs, is_delete=True)
+            self._really_update_sg_rule_with_remote_group_set(
+                                context, port, added_sgs, is_delete=False)
+            if removed_ips or added_ips:
+                self._really_update_sg_rule_with_remote_group_set(
+                    context, orig, security_groups, is_delete=True)
+                self._really_update_sg_rule_with_remote_group_set(
+                    context, port, security_groups, is_delete=False)
+
         self._check_allowed_address_pairs(context, port)
         self._insert_provisioning_block(context)
         registry.publish(aim_cst.GBP_PORT, events.PRECOMMIT_UPDATE,
@@ -3423,6 +3519,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 
     def delete_port_precommit(self, context):
         port = context.current
+        session = context._plugin_context.session
         if self._is_port_bound(port):
             if self._use_static_path(context.bottom_bound_segment):
                 self._update_static_path(context, remove=True)
@@ -3433,8 +3530,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                       context.bottom_bound_segment[api.NETWORK_TYPE])):
                 self.disassociate_domain(context)
             self._delete_erspan_aim_config(context, port)
-        self._really_update_sg_rule_with_remote_group_set(
-            context, port, port['security_groups'], is_delete=True)
+        if self.get_hpp_normalized(session):
+            self._really_update_sg_remote_groups(
+                context, port, port['security_groups'], is_delete=True)
+        else:
+            self._really_update_sg_rule_with_remote_group_set(
+                context, port, port['security_groups'], is_delete=True)
 
         # Set status of floating ip DOWN.
         self._update_floatingip_status(
@@ -3546,6 +3647,14 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
             security_group_name=sg['id'], name='default')
         self.aim.create(aim_ctx, sg_subject)
 
+        if self.get_hpp_normalized(session):
+            # Create default remote group container
+            sg_remote_group = aim_resource.SecurityGroupRemoteIpContainer(
+                tenant_name=tenant_aname,
+                security_group_name=sg['id'],
+                remote_group_id=sg['id'])
+            self.aim.create(aim_ctx, sg_remote_group)
+
         # Create those implicit rules
         for sg_rule in sg.get('security_group_rules', []):
             sg_rule_aim = aim_resource.SecurityGroupRule(
@@ -3604,6 +3713,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
 
         return tenant_id
 
+    def _get_sg_tenant_id(self, session, sg_id):
+        query = BAKERY(lambda s: s.query(
+            sg_models.SecurityGroup.tenant_id))
+        query += lambda q: q.filter(
+            sg_models.SecurityGroup.id == sa.bindparam('sg_id'))
+        tenant_id = query(session).params(
+            sg_id=sg_id).first()[0]
+
+        return tenant_id
+
     def create_security_group_rule_precommit(self, context):
         session = context._plugin_context.session
         aim_ctx = aim_context.AimContext(session)
@@ -3612,6 +3731,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
         tenant_aname = self.name_mapper.project(session, tenant_id)
         if sg_rule.get('remote_group_id'):
             remote_ips = []
+            remote_group_id = sg_rule['remote_group_id']
+            dn = ''
 
             query = BAKERY(lambda s: s.query(
                 models_v2.Port))
@@ -3625,20 +3746,35 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
             sg_ports = query(session).params(
                 sg_id=sg_rule['remote_group_id']).all()
 
-            ip_version = 0
-            if sg_rule['ethertype'] == 'IPv4':
-                ip_version = 4
-            elif sg_rule['ethertype'] == 'IPv6':
-                ip_version = 6
-
-            for sg_port in sg_ports:
-                for fixed_ip in sg_port['fixed_ips']:
-                    if ip_version == netaddr.IPAddress(
-                        fixed_ip['ip_address']).version:
-                        remote_ips.append(fixed_ip['ip_address'])
-
-            remote_group_id = sg_rule['remote_group_id']
+            if self.get_hpp_normalized(session):
+                has_fixed_ips = False
+                for sg_port in sg_ports:
+                    if sg_port['fixed_ips']:
+                        has_fixed_ips = True
+                        break
+                if has_fixed_ips:
+                    # Get the remote group container's dn
+                    rg_tenant_id = self._get_sg_tenant_id(session,
+                        sg_rule['remote_group_id'])
+                    rg_tenant_aname = self.name_mapper.project(session,
+                        rg_tenant_id)
+                    rg_cont = aim_resource.SecurityGroupRemoteIpContainer(
+                        tenant_name=rg_tenant_aname,
+                        security_group_name=sg_rule['remote_group_id'],
+                        name=sg_rule['remote_group_id'])
+                    dn = rg_cont.dn
+            else:
+                ip_version = 0
+                if sg_rule['ethertype'] == 'IPv4':
+                    ip_version = 4
+                elif sg_rule['ethertype'] == 'IPv6':
+                    ip_version = 6
 
+                for sg_port in sg_ports:
+                    for fixed_ip in sg_port['fixed_ips']:
+                        if ip_version == netaddr.IPAddress(
+                            fixed_ip['ip_address']).version:
+                            remote_ips.append(fixed_ip['ip_address'])
         elif sg_rule.get('remote_address_group_id'):
             remote_ips = []
 
@@ -3663,11 +3799,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                     remote_ips.append(addr['address'])
 
             remote_group_id = ''
+            dn = ''
 
         else:
             remote_ips = ([sg_rule['remote_ip_prefix']]
                           if sg_rule['remote_ip_prefix'] else '')
             remote_group_id = ''
+            dn = ''
 
         sg_rule_aim = aim_resource.SecurityGroupRule(
             tenant_name=tenant_aname,
@@ -3692,6 +3830,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                        if sg_rule['port_range_min'] else 'unspecified'),
             to_port=(sg_rule['port_range_max']
                      if sg_rule['port_range_max'] else 'unspecified'),
+            tDn=dn,
             remote_group_id=remote_group_id)
         self.aim.create(aim_ctx, sg_rule_aim)
 
@@ -6729,6 +6868,25 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                                  metadata={
                                      'network_id': mapping.network_id}))
 
+    def normalize_hpp(self):
+        session = db_api.get_writer_session()
+        aim_ctx = aim_context.AimContext(db_session=session)
+        aim = aim_manager.AimManager()
+
+        remoteip_normalization = aim.get(aim_ctx,
+            aim_infra.ACISupportedMo(name="remoteipcont"))
+        if remoteip_normalization.supports:
+            if not self.get_hpp_normalized(session):
+                ctx = n_context.get_admin_context()
+                with db_api.CONTEXT_WRITER.using(ctx):
+                    aim_ctx = aim_context.AimContext(session)
+                    data_migrations.normalize_hpp_ips(session)
+                    data_migrations.normalize_hpps(session)
+                    self.set_hpp_normalized(session, True)
+            return True
+        else:
+            return False
+
     def validate_aim_mapping(self, mgr):
         mgr.register_aim_resource_class(aim_infra.HostDomainMappingV2)
         mgr.register_aim_resource_class(aim_resource.ApplicationProfile)
@@ -6747,6 +6905,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
         mgr.register_aim_resource_class(aim_resource.L3Outside)
         mgr.register_aim_resource_class(aim_resource.PhysicalDomain)
         mgr.register_aim_resource_class(aim_resource.SecurityGroup)
+        mgr.register_aim_resource_class(
+            aim_resource.SecurityGroupRemoteIpContainer)
+        mgr.register_aim_resource_class(aim_resource.SecurityGroupRemoteIp)
         mgr.register_aim_resource_class(aim_resource.SecurityGroupRule)
         mgr.register_aim_resource_class(aim_resource.SecurityGroupSubject)
         mgr.register_aim_resource_class(aim_resource.Subnet)
@@ -7506,6 +7667,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
     def _validate_security_groups(self, mgr):
         if not mgr._should_validate_neutron_resource("security_group"):
             return
+        session = db_api.get_writer_session()
         sg_ips = defaultdict(set)
 
         query = BAKERY(lambda s: s.query(
@@ -7549,17 +7711,45 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                     tenant_name=tenant_name, security_group_name=sg_db.id,
                     name='default')
                 mgr.expect_aim_resource(sg_subject)
+
+                if self.get_hpp_normalized(mgr.actual_session):
+                    sg_rg = aim_resource.SecurityGroupRemoteIpContainer(
+                        tenant_name=tenant_name,
+                        security_group_name=sg_db.id,
+                        name=sg_db.id)
+                    mgr.expect_aim_resource(sg_rg)
+                    remote_ips = [
+                        ip for ip in sg_ips[sg_db.id]]
+                    for remote_ip in remote_ips:
+                        sg_rg_ip = aim_resource.SecurityGroupRemoteIp(
+                            tenant_name=tenant_name,
+                            security_group_name=sg_db.id,
+                            addr=remote_ip)
+                        mgr.expect_aim_resource(sg_rg_ip)
+
                 for rule_db in sg_db.rules:
                     remote_ips = []
                     remote_group_id = ''
+                    dn = ''
                     if rule_db.remote_group_id:
                         remote_group_id = rule_db.remote_group_id
-                        ip_version = (4 if rule_db.ethertype == 'IPv4' else
-                                      6 if rule_db.ethertype == 'IPv6' else
-                                      0)
-                        remote_ips = [
-                            ip for ip in sg_ips[rule_db.remote_group_id]
-                            if netaddr.IPAddress(ip).version == ip_version]
+                        if self.get_hpp_normalized(mgr.actual_session):
+                            rg_tenant_id = self._get_sg_tenant_id(
+                                session, rule_db.remote_group_id)
+                            rg_tenant_aname = self.name_mapper.project(
+                                session, rg_tenant_id)
+                            rg = aim_resource.SecurityGroupRemoteIpContainer(
+                                tenant_name=rg_tenant_aname,
+                                security_group_name=rule_db.remote_group_id,
+                                name=rule_db.remote_group_id)
+                            dn = rg.dn
+                        else:
+                            ip_version = (4 if rule_db.ethertype == 'IPv4' else
+                                          6 if rule_db.ethertype == 'IPv6' else
+                                          0)
+                            remote_ips = [
+                                ip for ip in sg_ips[rule_db.remote_group_id]
+                                if netaddr.IPAddress(ip).version == ip_version]
                     elif rule_db.remote_ip_prefix:
                         remote_ips = [rule_db.remote_ip_prefix]
                     sg_rule = aim_resource.SecurityGroupRule(
@@ -7577,6 +7767,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
                         to_port=(rule_db.port_range_max
                                  if rule_db.port_range_max
                                  else 'unspecified'),
+                        tDn=dn,
                         remote_group_id=remote_group_id)
                     mgr.expect_aim_resource(sg_rule)
 
diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py
index b77e29ec6..1cb99478b 100644
--- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py
+++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py
@@ -242,6 +242,7 @@ class ApicAimTestMixin(object):
         self.aim_cfg_manager = aim_cfg.ConfigManager(
             aim_context.AimContext(db_session=session), '')
         self.aim_cfg_manager.replace_all(aim_cfg.CONF)
+        data_migrations.do_hpp_insertion(session)
 
     def set_override(self, item, value, group=None, host=''):
         # Override DB config as well
@@ -544,6 +545,27 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
             self.assertIsNotNone(sg_rule)
             return sg_rule
 
+    def _check_sg_remote_group_container(self, remote_group_name,
+                                       sg_name, tenant_name):
+        session = db_api.get_reader_session()
+        aim_ctx = aim_context.AimContext(session)
+        sg_remote_group = aim_resource.SecurityGroupRemoteIpContainer(
+            tenant_name=tenant_name, security_group_name=sg_name,
+            name=remote_group_name)
+        sg_remote_group = self.aim_mgr.get(aim_ctx, sg_remote_group)
+        self.assertIsNotNone(sg_remote_group)
+        return sg_remote_group
+
+    def _get_sg_remote_group_ip(self, remote_ip,
+                                sg_name, tenant_name):
+        session = db_api.get_reader_session()
+        aim_ctx = aim_context.AimContext(session)
+        sg_remote_group_ip = aim_resource.SecurityGroupRemoteIp(
+            tenant_name=tenant_name, security_group_name=sg_name,
+            addr=remote_ip)
+        sg_remote_group_ip = self.aim_mgr.get(aim_ctx, sg_remote_group_ip)
+        return sg_remote_group_ip
+
     def _get_contract(self, contract_name, tenant_name):
         ctx = n_context.get_admin_context()
         # TODO(pulkit): replace with AIM reader context once API supports it.
@@ -11914,7 +11936,9 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
                                  set(self._doms(epg1.physical_domains,
                                                 with_type=False)))
 
-    def test_update_sg_rule_with_remote_group_set(self):
+    def _test_sg_update_remote_groups(self):
+        session = db_api.get_reader_session()
+        extn = extn_db.ExtensionDbMixin()
         # Create network.
         net_resp = self._make_network(self.fmt, 'net1', True)
         net = net_resp['network']
@@ -11931,16 +11955,29 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
         default_sg_id = port['security_groups'][0]
         default_sg = self._show('security-groups',
                                 default_sg_id)['security_group']
+        tenant_aname = self.name_mapper.project(None, default_sg['tenant_id'])
+
         for sg_rule in default_sg['security_group_rules']:
             if sg_rule['remote_group_id'] and sg_rule['ethertype'] == 'IPv4':
                 break
-        tenant_aname = self.name_mapper.project(None, default_sg['tenant_id'])
         aim_sg_rule = self._get_sg_rule(
             sg_rule['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
         self.assertEqual(
             aim_sg_rule.remote_group_id, sg_rule['remote_group_id'])
 
+        if extn.get_hpp_normalized(session):
+            rg_cont = self._check_sg_remote_group_container(default_sg_id,
+                default_sg_id, tenant_aname)
+            remote_ips = ['10.0.1.100']
+            for remote_ip in remote_ips:
+                aim_sg_remote_group_ip = self._get_sg_remote_group_ip(
+                    remote_ip, default_sg_id, tenant_aname)
+                self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip)
+            self.assertEqual(aim_sg_rule.tDn, rg_cont.dn)
+            self.assertEqual(aim_sg_rule.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+
         # add another rule with remote_group_id set
         rule1 = self._build_security_group_rule(
             default_sg_id, 'ingress', n_constants.PROTO_NAME_TCP, '22', '23',
@@ -11950,9 +11987,15 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
             self.fmt, rules)['security_group_rules'][0]
         aim_sg_rule = self._get_sg_rule(
             sg_rule1['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
         self.assertEqual(
             aim_sg_rule.remote_group_id, sg_rule1['remote_group_id'])
+        if extn.get_hpp_normalized(session):
+            dn = 'uni/tn-' + tenant_aname + '/pol-' + sg_rule1[
+                'remote_group_id'] + '/remoteipcont'
+            self.assertEqual(aim_sg_rule.tDn, dn)
+            self.assertEqual(aim_sg_rule.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
 
         rule2 = self._build_security_group_rule(
             default_sg_id, 'ingress', n_constants.PROTO_NAME_ICMP, '33', '2',
@@ -11962,9 +12005,17 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
             self.fmt, rules)['security_group_rules'][0]
         aim_sg_rule = self._get_sg_rule(
             sg_rule2['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+        self.assertEqual(
+            aim_sg_rule.remote_group_id, sg_rule2['remote_group_id'])
         self.assertEqual(aim_sg_rule.icmp_code, '2')
         self.assertEqual(aim_sg_rule.icmp_type, '33')
+        if extn.get_hpp_normalized(session):
+            dn = 'uni/tn-' + tenant_aname + '/pol-' + sg_rule2[
+                'remote_group_id'] + '/remoteipcont'
+            self.assertEqual(aim_sg_rule.tDn, dn)
+            self.assertEqual(aim_sg_rule.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
 
         rule3 = self._build_security_group_rule(
             default_sg_id, 'ingress', n_constants.PROTO_NAME_ICMP, None, None,
@@ -11974,37 +12025,58 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
             self.fmt, rules)['security_group_rules'][0]
         aim_sg_rule = self._get_sg_rule(
             sg_rule3['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+        self.assertEqual(
+            aim_sg_rule.remote_group_id, sg_rule3['remote_group_id'])
         self.assertEqual(aim_sg_rule.icmp_code, 'unspecified')
         self.assertEqual(aim_sg_rule.icmp_type, 'unspecified')
+        if extn.get_hpp_normalized(session):
+            dn = 'uni/tn-' + tenant_aname + '/pol-' + sg_rule3[
+                'remote_group_id'] + '/remoteipcont'
+            self.assertEqual(aim_sg_rule.tDn, dn)
+            self.assertEqual(aim_sg_rule.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
 
         # delete SG from port
         data = {'port': {'security_groups': []}}
         port = self._update('ports', port['id'], data)['port']
         aim_sg_rule = self._get_sg_rule(
             sg_rule['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, [])
-        aim_sg_rule = self._get_sg_rule(
+        aim_sg_rule1 = self._get_sg_rule(
             sg_rule1['id'], 'default', default_sg_id, tenant_aname)
+        if extn.get_hpp_normalized(session):
+            self._check_sg_remote_group_container(
+                default_sg_id, default_sg_id, tenant_aname)
+            remote_ip = ''
+            aim_sg_remote_group_ip = self._get_sg_remote_group_ip(
+                sg_rule['id'], default_sg_id, tenant_aname)
+            self.assertIsNone(aim_sg_remote_group_ip)
+            self.assertEqual(aim_sg_rule.tDn, '')
+            self.assertEqual(aim_sg_rule1.tDn, '')
         self.assertEqual(aim_sg_rule.remote_ips, [])
+        self.assertEqual(aim_sg_rule1.remote_ips, [])
 
         # add SG to port
         data = {'port': {'security_groups': [default_sg_id]}}
         port = self._update('ports', port['id'], data)['port']
         aim_sg_rule = self._get_sg_rule(
              sg_rule['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
-        aim_sg_rule = self._get_sg_rule(
+        aim_sg_rule1 = self._get_sg_rule(
             sg_rule1['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
-
-        self._delete('ports', port['id'])
-        aim_sg_rule = self._get_sg_rule(
-            sg_rule['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, [])
-        aim_sg_rule = self._get_sg_rule(
-            sg_rule1['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, [])
+        if extn.get_hpp_normalized(session):
+            rg_cont = self._check_sg_remote_group_container(
+                default_sg_id, default_sg_id, tenant_aname)
+            remote_ips = ['10.0.1.100']
+            for remote_ip in remote_ips:
+                aim_sg_remote_group_ip = self._get_sg_remote_group_ip(
+                    remote_ip, default_sg_id, tenant_aname)
+                self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip)
+            self.assertEqual(aim_sg_rule.tDn, rg_cont.dn)
+            self.assertEqual(aim_sg_rule1.tDn, rg_cont.dn)
+            self.assertEqual(aim_sg_rule1.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+            self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100'])
 
     def test_sg_rule_with_remote_address_group(self):
         net_resp = self._make_network(self.fmt, 'net1', True)
@@ -12041,7 +12113,9 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
         # Delete Address group
         self._delete('address-groups', ag['address_group']['id'])
 
-    def test_create_sg_rule_with_remote_group_set_different_tenant(self):
+    def _test_create_sg_rule_with_remote_group_set_different_tenant(self):
+        session = db_api.get_reader_session()
+        extn = extn_db.ExtensionDbMixin()
         # Create network.
         net_resp = self._make_network(
             self.fmt, 'net1', True, tenant_id='tenant_1')
@@ -12065,7 +12139,19 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
         tenant_aname = self.name_mapper.project(None, default_sg['tenant_id'])
         aim_sg_rule = self._get_sg_rule(
             sg_rule['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+        if extn.get_hpp_normalized(session):
+            rg_cont = self._check_sg_remote_group_container(
+                sg_rule['remote_group_id'],
+                sg_rule['remote_group_id'], tenant_aname)
+            remote_ips = ['10.0.1.100']
+            for remote_ip in remote_ips:
+                aim_sg_remote_group_ip = self._get_sg_remote_group_ip(
+                    remote_ip, sg_rule['remote_group_id'], tenant_aname)
+                self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip)
+            self.assertEqual(aim_sg_rule.tDn, rg_cont.dn)
+            self.assertEqual(aim_sg_rule.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
         self.assertEqual(
             aim_sg_rule.remote_group_id, sg_rule['remote_group_id'])
 
@@ -12078,7 +12164,19 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
             self.fmt, rules, tenant_id='tenant_2')['security_group_rules'][0]
         aim_sg_rule1 = self._get_sg_rule(
             sg_rule1['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100'])
+        if extn.get_hpp_normalized(session):
+            rg_cont = self._check_sg_remote_group_container(
+                sg_rule1['remote_group_id'],
+                sg_rule1['remote_group_id'], tenant_aname)
+            remote_ips = ['10.0.1.100']
+            for remote_ip in remote_ips:
+                aim_sg_remote_group_ip = self._get_sg_remote_group_ip(
+                    remote_ip, sg_rule1['remote_group_id'], tenant_aname)
+                self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip)
+            self.assertEqual(aim_sg_rule1.tDn, rg_cont.dn)
+            self.assertEqual(aim_sg_rule1.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100'])
         self.assertEqual(
             aim_sg_rule1.remote_group_id, sg_rule1['remote_group_id'])
 
@@ -12088,9 +12186,102 @@ class TestPortOnPhysicalNode(TestPortVlanNetwork):
                                tenant_id='tenant_1')['port']
         aim_sg_rule1 = self._get_sg_rule(
             sg_rule1['id'], 'default', default_sg_id, tenant_aname)
-        self.assertEqual(aim_sg_rule1.remote_ips, ['10.0.1.100', '10.0.1.200'])
+        if extn.get_hpp_normalized(session):
+            rg_cont = self._check_sg_remote_group_container(
+                sg_rule['remote_group_id'],
+                sg_rule['remote_group_id'], tenant_aname)
+            remote_ips = ['10.0.1.100', '10.0.1.200']
+            for remote_ip in remote_ips:
+                aim_sg_remote_group_ip = self._get_sg_remote_group_ip(
+                    remote_ip, sg_rule['remote_group_id'], tenant_aname)
+                self.assertEqual(aim_sg_remote_group_ip.addr, remote_ip)
+            self.assertEqual(aim_sg_rule1.tDn, rg_cont.dn)
+            self.assertEqual(aim_sg_rule1.remote_ips, [])
+        else:
+            self.assertEqual(aim_sg_rule1.remote_ips,
+                ['10.0.1.100', '10.0.1.200'])
         self.assertEqual(
-            aim_sg_rule1.remote_group_id, sg_rule['remote_group_id'])
+            aim_sg_rule1.remote_group_id, sg_rule1['remote_group_id'])
+
+    def test_sg_update_remote_groups_normalized(self):
+        session = db_api.get_reader_session()
+        extn = extn_db.ExtensionDbMixin()
+        extn.set_hpp_normalized(session, True)
+        self._test_sg_update_remote_groups()
+        self._test_create_sg_rule_with_remote_group_set_different_tenant()
+
+    def test_sg_update_remote_groups_not_normalized(self):
+        self._test_sg_update_remote_groups()
+        self._test_create_sg_rule_with_remote_group_set_different_tenant()
+
+    def test_normalize_hpp(self):
+        session = db_api.get_reader_session()
+        extn = extn_db.ExtensionDbMixin()
+        aim_ctx = aim_context.AimContext(session)
+        # Create network.
+        net_resp = self._make_network(self.fmt, 'net1', True)
+        net = net_resp['network']
+
+        # Create subnet
+        subnet = self._make_subnet(self.fmt, net_resp, '10.0.1.1',
+                                   '10.0.1.0/24')['subnet']
+        subnet_id = subnet['id']
+
+        # Create port on subnet
+        fixed_ips = [{'subnet_id': subnet_id, 'ip_address': '10.0.1.100'}]
+        port = self._make_port(self.fmt, net['id'],
+                               fixed_ips=fixed_ips)['port']
+        default_sg_id = port['security_groups'][0]
+        default_sg = self._show('security-groups',
+                                default_sg_id)['security_group']
+        for sg_rule in default_sg['security_group_rules']:
+            if sg_rule['remote_group_id'] and sg_rule['ethertype'] == 'IPv4':
+                break
+        tenant_aname = self.name_mapper.project(None, default_sg['tenant_id'])
+        aim_sg_rule = self._get_sg_rule(
+            sg_rule['id'], 'default', default_sg_id, tenant_aname)
+        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+        self.assertEqual(
+            aim_sg_rule.remote_group_id, sg_rule['remote_group_id'])
+
+        # add rule with remote_group_id set
+        self.assertFalse(extn.get_hpp_normalized(session))
+        rule1 = self._build_security_group_rule(
+            default_sg_id, 'ingress', n_constants.PROTO_NAME_TCP, '22', '23',
+            remote_group_id=default_sg_id, ethertype=n_constants.IPv4)
+        rules = {'security_group_rules': [rule1['security_group_rule']]}
+        sg_rule1 = self._make_security_group_rule(
+            self.fmt, rules)['security_group_rules'][0]
+        aim_sg_rule = self._get_sg_rule(
+            sg_rule1['id'], 'default', default_sg_id, tenant_aname)
+        self.assertEqual(aim_sg_rule.remote_ips, ['10.0.1.100'])
+        self.assertEqual(
+            aim_sg_rule.remote_group_id, sg_rule1['remote_group_id'])
+
+        # normalize the remote groups
+        self.remoteip_normalization = aim_infra.ACISupportedMo(
+            name="remoteipcont", supports=False)
+        self.aim_mgr.create(aim_ctx, self.remoteip_normalization)
+        self.mech = md.ApicMechanismDriver()
+        self.result = self.mech.normalize_hpp()
+        self.assertFalse(self.result)
+
+        self.aim_mgr.update(aim_ctx, self.remoteip_normalization,
+                        supports=True)
+        self.result = self.mech.normalize_hpp()
+        self.assertTrue(self.result)
+        self.assertTrue(extn.get_hpp_normalized(session))
+        rg_cont = self._check_sg_remote_group_container(
+            default_sg_id, default_sg_id, tenant_aname)
+        remote_ips = ['10.0.1.100']
+        for remote_ip in remote_ips:
+            aim_sg_remote_ip = self._get_sg_remote_group_ip(
+                remote_ip, default_sg_id, tenant_aname)
+            self.assertEqual(aim_sg_remote_ip.addr, remote_ip)
+        aim_sg_rule = self._get_sg_rule(
+            sg_rule1['id'], 'default', default_sg_id, tenant_aname)
+        self.assertEqual(aim_sg_rule.remote_ips, [])
+        self.assertEqual(aim_sg_rule.tDn, rg_cont.dn)
 
     def test_mixed_ports_on_network_with_specific_domains(self):
         with db_api.CONTEXT_READER.using(self.db_session):
diff --git a/gbpservice/tools/hpp_normalize/__init__.py b/gbpservice/tools/hpp_normalize/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/gbpservice/tools/hpp_normalize/cli.py b/gbpservice/tools/hpp_normalize/cli.py
new file mode 100644
index 000000000..849ed8fd2
--- /dev/null
+++ b/gbpservice/tools/hpp_normalize/cli.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2018 Cisco Systems Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import sys
+
+from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import (
+    mechanism_driver as md)
+from oslo_config import cfg
+from neutron.common import config
+from neutron import manager
+
+
+def main():
+    config.init(sys.argv[1:])
+
+    # Enable logging but prevent output to stderr.
+    cfg.CONF.use_stderr = False
+    config.setup_logging()
+
+    if not cfg.CONF.config_file:
+        sys.exit(_("ERROR: Unable to find configuration file via the default"
+                   " search paths (~/.neutron/, ~/, /etc/neutron/, /etc/) and"
+                   " the '--config-file' option!"))
+
+    manager.init()
+
+    mech = md.ApicMechanismDriver()
+    result = mech.normalize_hpp()
+
+    if not result:
+        sys.exit(_("ERROR: APIC version doesn't support"
+                   " normalization of remote ips"))
+    return 0
diff --git a/setup.cfg b/setup.cfg
index 7c3435912..31ef3b819 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -39,6 +39,7 @@ scripts =
 console_scripts=
     gbp-db-manage = gbpservice.neutron.db.migration.cli:main
     gbp-validate = gbpservice.tools.validate.cli:main
+    hpp-normalize = gbpservice.tools.hpp_normalize.cli:main
 neutron.core_plugins =
     ml2plus = gbpservice.neutron.plugins.ml2plus.plugin:Ml2PlusPlugin
 neutron.service_plugins =