diff mbox series

[net-next,v8,15/15] p4tc: Add P4 extern interface

Message ID 20231116145948.203001-16-jhs@mojatatu.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series Introducing P4TC | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit fail Errors and warnings before: 1131 this patch: 1131
netdev/cc_maintainers warning 5 maintainers not CCed: john.fastabend@gmail.com pctammela@mojatatu.com hawk@kernel.org ast@kernel.org victor@mojatatu.com
netdev/build_clang fail Errors and warnings before: 1160 this patch: 1160
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 1158 this patch: 1158
netdev/checkpatch warning CHECK: multiple assignments should be avoided WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns WARNING: line length of 90 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns WARNING: line length of 93 exceeds 80 columns WARNING: line length of 97 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Jamal Hadi Salim Nov. 16, 2023, 2:59 p.m. UTC
P4 externs are an abstraction in the language to call for extending
language functionality. For example, the function that sends a packet to a
specific port (send_to_port) in P4 PNA is an extern.

Externs can be seen as classes, which have constructors and methods.
Take, for example, the Register extern definition:

extern Register<T> {
    Register(@tc_numel bit<32> size);
    @tc_md_read T read(@tc_key bit<32> index);
    @tc_md_write void write(@tc_key bit<32> index, @tc_data T value);
}

struct tc_ControlPath_Register<T> {
    @tc_key bit<32> index;
    @tc_data T value;
}

Which can then be instantiated within a P4 program as:
Register<bit<32>>(128) reg1;
Register<bit<16>>(1024) reg2;

Will be abstracted into the template by the P4C compiler for "reg1" as
follows:

tc p4template create extern/root/register extid 10 numinstances 2
tc p4template create extern_inst/aP4Proggie/register/reg1 instid 1 \
control_path tc_key index type bit32 tc_data value type bit32 \
numelemens 128 default_value 22

=========================EXTERN RUNTIME COMMANDS=========================

Once we seal the pipeline, the register values will be assigned to the
default value specified on the template as "default_value". After sealing,
we can update the runtime instance element. For example to update
reg1[2] with the value 33, we will do the following:

tc p4ctrl update aP4proggie/extern/register/reg1 tc_key index 2 \
tc_data value 33

We can also get its value:

tc p4ctrl get aP4proggie/extern/register/reg1 tc_key index 2

Which will yield the following output:

total exts 0
        extern order 1:
          tc_key index id 1 type bit32 value: 1
          tc_data value id 2 type bit32 value: 33

We can also dump all of the elements in this register:

tc p4ctrl get aP4proggie/extern/register/reg1

Note that the only valid runtime operations are get and update.

=========================EXTERN P4 Runtime =========================

The generated ebpf code invokes the externs in the P4TC domain
using the md_read or md_write kfuncs, for example:
if the P4 program had this invocation:

tmp1 = reg1.read(index1);

Then equivalent generated ebpf code is as follows:

param.pipe_id = aP4Proggie_ID;
param.ext_id = EXTERN_REGISTER;
param.inst_id = EXTERN_REGISTER_INSTANCE_ID1;
param.index = index1;
param.param_id = EXTERN_REGISTER_PARAM_ID;
bpf_p4tc_extern_md_read(skb, &res, &param);
tmp1 = (u32 *)res.params;

Co-developed-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Co-developed-by: Pedro Tammela <pctammela@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
 include/net/p4tc.h                |  161 +++
 include/net/p4tc_ext_api.h        |  199 +++
 include/uapi/linux/p4tc.h         |   61 +
 include/uapi/linux/p4tc_ext.h     |   36 +
 net/sched/p4tc/Makefile           |    2 +-
 net/sched/p4tc/p4tc_bpf.c         |   79 +-
 net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
 net/sched/p4tc/p4tc_pipeline.c    |   34 +-
 net/sched/p4tc/p4tc_runtime_api.c |   10 +-
 net/sched/p4tc/p4tc_table.c       |   57 +-
 net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
 net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
 net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
 13 files changed, 5083 insertions(+), 10 deletions(-)
 create mode 100644 include/net/p4tc_ext_api.h
 create mode 100644 include/uapi/linux/p4tc_ext.h
 create mode 100644 net/sched/p4tc/p4tc_ext.c
 create mode 100644 net/sched/p4tc/p4tc_tmpl_ext.c

Comments

Jiri Pirko Nov. 16, 2023, 4:42 p.m. UTC | #1
Thu, Nov 16, 2023 at 03:59:48PM CET, jhs@mojatatu.com wrote:

[...]

> include/net/p4tc.h                |  161 +++
> include/net/p4tc_ext_api.h        |  199 +++
> include/uapi/linux/p4tc.h         |   61 +
> include/uapi/linux/p4tc_ext.h     |   36 +
> net/sched/p4tc/Makefile           |    2 +-
> net/sched/p4tc/p4tc_bpf.c         |   79 +-
> net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
> net/sched/p4tc/p4tc_pipeline.c    |   34 +-
> net/sched/p4tc/p4tc_runtime_api.c |   10 +-
> net/sched/p4tc/p4tc_table.c       |   57 +-
> net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
> net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
> net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
> 13 files changed, 5083 insertions(+), 10 deletions(-)

This is for this patch. Now for the whole patchset you have:
 30 files changed, 16676 insertions(+), 39 deletions(-)

I understand that you want to fit into 15 patches with all the work.
But sorry, patches like this are unreviewable. My suggestion is to split
the patchset into multiple ones including smaller patches and allow
people to digest this. I don't believe that anyone can seriously stand
to review a patch with more than 200 lines changes.

[...]
Jamal Hadi Salim Nov. 17, 2023, 12:14 p.m. UTC | #2
On Thu, Nov 16, 2023 at 11:42 AM Jiri Pirko <jiri@resnulli.us> wrote:
>
> Thu, Nov 16, 2023 at 03:59:48PM CET, jhs@mojatatu.com wrote:
>
> [...]
>
> > include/net/p4tc.h                |  161 +++
> > include/net/p4tc_ext_api.h        |  199 +++
> > include/uapi/linux/p4tc.h         |   61 +
> > include/uapi/linux/p4tc_ext.h     |   36 +
> > net/sched/p4tc/Makefile           |    2 +-
> > net/sched/p4tc/p4tc_bpf.c         |   79 +-
> > net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
> > net/sched/p4tc/p4tc_pipeline.c    |   34 +-
> > net/sched/p4tc/p4tc_runtime_api.c |   10 +-
> > net/sched/p4tc/p4tc_table.c       |   57 +-
> > net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
> > net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
> > net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
> > 13 files changed, 5083 insertions(+), 10 deletions(-)
>
> This is for this patch. Now for the whole patchset you have:
>  30 files changed, 16676 insertions(+), 39 deletions(-)
>
> I understand that you want to fit into 15 patches with all the work.
> But sorry, patches like this are unreviewable. My suggestion is to split
> the patchset into multiple ones including smaller patches and allow
> people to digest this. I don't believe that anyone can seriously stand
> to review a patch with more than 200 lines changes.

This specific patch is not difficult to split into two. I can do that
and send out minus the first 8 trivial patches - but not familiar with
how to do "here's part 1 of the patches" and "here's patchset two".
There's dependency between them so not clear how patchwork and
reviewers would deal with it. Thoughts?

Note: The code machinery is really repeatable; for example if you look
at the tables control you will see very similar patterns to actions
etc. i.e spending time to review one will make it easy for the rest.

cheers,
jamal

> [...]
Jiri Pirko Nov. 20, 2023, 8:22 a.m. UTC | #3
Fri, Nov 17, 2023 at 01:14:43PM CET, jhs@mojatatu.com wrote:
>On Thu, Nov 16, 2023 at 11:42 AM Jiri Pirko <jiri@resnulli.us> wrote:
>>
>> Thu, Nov 16, 2023 at 03:59:48PM CET, jhs@mojatatu.com wrote:
>>
>> [...]
>>
>> > include/net/p4tc.h                |  161 +++
>> > include/net/p4tc_ext_api.h        |  199 +++
>> > include/uapi/linux/p4tc.h         |   61 +
>> > include/uapi/linux/p4tc_ext.h     |   36 +
>> > net/sched/p4tc/Makefile           |    2 +-
>> > net/sched/p4tc/p4tc_bpf.c         |   79 +-
>> > net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
>> > net/sched/p4tc/p4tc_pipeline.c    |   34 +-
>> > net/sched/p4tc/p4tc_runtime_api.c |   10 +-
>> > net/sched/p4tc/p4tc_table.c       |   57 +-
>> > net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
>> > net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
>> > net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
>> > 13 files changed, 5083 insertions(+), 10 deletions(-)
>>
>> This is for this patch. Now for the whole patchset you have:
>>  30 files changed, 16676 insertions(+), 39 deletions(-)
>>
>> I understand that you want to fit into 15 patches with all the work.
>> But sorry, patches like this are unreviewable. My suggestion is to split
>> the patchset into multiple ones including smaller patches and allow
>> people to digest this. I don't believe that anyone can seriously stand
>> to review a patch with more than 200 lines changes.
>
>This specific patch is not difficult to split into two. I can do that
>and send out minus the first 8 trivial patches - but not familiar with
>how to do "here's part 1 of the patches" and "here's patchset two".

Split into multiple patchsets and send one by one. No need to have all
in at once.


>There's dependency between them so not clear how patchwork and

What dependency. It should compile. Introduce some basic functionality
first and extend it incrementally with other patchsets. The usual way.


>reviewers would deal with it. Thoughts?
>
>Note: The code machinery is really repeatable; for example if you look
>at the tables control you will see very similar patterns to actions
>etc. i.e spending time to review one will make it easy for the rest.
>
>cheers,
>jamal
>
>> [...]
Jamal Hadi Salim Nov. 20, 2023, 2:02 p.m. UTC | #4
On Mon, Nov 20, 2023 at 3:22 AM Jiri Pirko <jiri@resnulli.us> wrote:
>
> Fri, Nov 17, 2023 at 01:14:43PM CET, jhs@mojatatu.com wrote:
> >On Thu, Nov 16, 2023 at 11:42 AM Jiri Pirko <jiri@resnulli.us> wrote:
> >>
> >> Thu, Nov 16, 2023 at 03:59:48PM CET, jhs@mojatatu.com wrote:
> >>
> >> [...]
> >>
> >> > include/net/p4tc.h                |  161 +++
> >> > include/net/p4tc_ext_api.h        |  199 +++
> >> > include/uapi/linux/p4tc.h         |   61 +
> >> > include/uapi/linux/p4tc_ext.h     |   36 +
> >> > net/sched/p4tc/Makefile           |    2 +-
> >> > net/sched/p4tc/p4tc_bpf.c         |   79 +-
> >> > net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
> >> > net/sched/p4tc/p4tc_pipeline.c    |   34 +-
> >> > net/sched/p4tc/p4tc_runtime_api.c |   10 +-
> >> > net/sched/p4tc/p4tc_table.c       |   57 +-
> >> > net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
> >> > net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
> >> > net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
> >> > 13 files changed, 5083 insertions(+), 10 deletions(-)
> >>
> >> This is for this patch. Now for the whole patchset you have:
> >>  30 files changed, 16676 insertions(+), 39 deletions(-)
> >>
> >> I understand that you want to fit into 15 patches with all the work.
> >> But sorry, patches like this are unreviewable. My suggestion is to split
> >> the patchset into multiple ones including smaller patches and allow
> >> people to digest this. I don't believe that anyone can seriously stand
> >> to review a patch with more than 200 lines changes.
> >
> >This specific patch is not difficult to split into two. I can do that
> >and send out minus the first 8 trivial patches - but not familiar with
> >how to do "here's part 1 of the patches" and "here's patchset two".
>
> Split into multiple patchsets and send one by one. No need to have all
> in at once.
>
>
> >There's dependency between them so not clear how patchwork and
>
> What dependency. It should compile. Introduce some basic functionality
> first and extend it incrementally with other patchsets. The usual way.
>

Sorry, still not following:
Lets say i split the current patchset 1 with patch 1-8 (which are
trivial and have been reviewed) then make the rest into patchset 2
with a new set 1-8. I dont see how patchset 2 compiles unless it has
access to code from patchset 1. Unless patchset 1 is merged i dont see
how this works with patchwork or reviewers. Am i missing something?

cheers,
jamal

>
> >reviewers would deal with it. Thoughts?
> >
> >Note: The code machinery is really repeatable; for example if you look
> >at the tables control you will see very similar patterns to actions
> >etc. i.e spending time to review one will make it easy for the rest.
> >
> >cheers,
> >jamal
> >
> >> [...]
Jiri Pirko Nov. 20, 2023, 4:27 p.m. UTC | #5
Mon, Nov 20, 2023 at 03:02:49PM CET, jhs@mojatatu.com wrote:
>On Mon, Nov 20, 2023 at 3:22 AM Jiri Pirko <jiri@resnulli.us> wrote:
>>
>> Fri, Nov 17, 2023 at 01:14:43PM CET, jhs@mojatatu.com wrote:
>> >On Thu, Nov 16, 2023 at 11:42 AM Jiri Pirko <jiri@resnulli.us> wrote:
>> >>
>> >> Thu, Nov 16, 2023 at 03:59:48PM CET, jhs@mojatatu.com wrote:
>> >>
>> >> [...]
>> >>
>> >> > include/net/p4tc.h                |  161 +++
>> >> > include/net/p4tc_ext_api.h        |  199 +++
>> >> > include/uapi/linux/p4tc.h         |   61 +
>> >> > include/uapi/linux/p4tc_ext.h     |   36 +
>> >> > net/sched/p4tc/Makefile           |    2 +-
>> >> > net/sched/p4tc/p4tc_bpf.c         |   79 +-
>> >> > net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
>> >> > net/sched/p4tc/p4tc_pipeline.c    |   34 +-
>> >> > net/sched/p4tc/p4tc_runtime_api.c |   10 +-
>> >> > net/sched/p4tc/p4tc_table.c       |   57 +-
>> >> > net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
>> >> > net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
>> >> > net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
>> >> > 13 files changed, 5083 insertions(+), 10 deletions(-)
>> >>
>> >> This is for this patch. Now for the whole patchset you have:
>> >>  30 files changed, 16676 insertions(+), 39 deletions(-)
>> >>
>> >> I understand that you want to fit into 15 patches with all the work.
>> >> But sorry, patches like this are unreviewable. My suggestion is to split
>> >> the patchset into multiple ones including smaller patches and allow
>> >> people to digest this. I don't believe that anyone can seriously stand
>> >> to review a patch with more than 200 lines changes.
>> >
>> >This specific patch is not difficult to split into two. I can do that
>> >and send out minus the first 8 trivial patches - but not familiar with
>> >how to do "here's part 1 of the patches" and "here's patchset two".
>>
>> Split into multiple patchsets and send one by one. No need to have all
>> in at once.
>>
>>
>> >There's dependency between them so not clear how patchwork and
>>
>> What dependency. It should compile. Introduce some basic functionality
>> first and extend it incrementally with other patchsets. The usual way.
>>
>
>Sorry, still not following:
>Lets say i split the current patchset 1 with patch 1-8 (which are
>trivial and have been reviewed) then make the rest into patchset 2
>with a new set 1-8. I dont see how patchset 2 compiles unless it has
>access to code from patchset 1. Unless patchset 1 is merged i dont see
>how this works with patchwork or reviewers. Am i missing something?

Why it would not work. Describe your motivation and plans and submit
part of the work, the rest later on. No problem.


>
>cheers,
>jamal
>
>>
>> >reviewers would deal with it. Thoughts?
>> >
>> >Note: The code machinery is really repeatable; for example if you look
>> >at the tables control you will see very similar patterns to actions
>> >etc. i.e spending time to review one will make it easy for the rest.
>> >
>> >cheers,
>> >jamal
>> >
>> >> [...]
Jamal Hadi Salim Nov. 20, 2023, 7 p.m. UTC | #6
On Mon, Nov 20, 2023 at 11:27 AM Jiri Pirko <jiri@resnulli.us> wrote:
>
> Mon, Nov 20, 2023 at 03:02:49PM CET, jhs@mojatatu.com wrote:
> >On Mon, Nov 20, 2023 at 3:22 AM Jiri Pirko <jiri@resnulli.us> wrote:
> >>
> >> Fri, Nov 17, 2023 at 01:14:43PM CET, jhs@mojatatu.com wrote:
> >> >On Thu, Nov 16, 2023 at 11:42 AM Jiri Pirko <jiri@resnulli.us> wrote:
> >> >>
> >> >> Thu, Nov 16, 2023 at 03:59:48PM CET, jhs@mojatatu.com wrote:
> >> >>
> >> >> [...]
> >> >>
> >> >> > include/net/p4tc.h                |  161 +++
> >> >> > include/net/p4tc_ext_api.h        |  199 +++
> >> >> > include/uapi/linux/p4tc.h         |   61 +
> >> >> > include/uapi/linux/p4tc_ext.h     |   36 +
> >> >> > net/sched/p4tc/Makefile           |    2 +-
> >> >> > net/sched/p4tc/p4tc_bpf.c         |   79 +-
> >> >> > net/sched/p4tc/p4tc_ext.c         | 2204 ++++++++++++++++++++++++++++
> >> >> > net/sched/p4tc/p4tc_pipeline.c    |   34 +-
> >> >> > net/sched/p4tc/p4tc_runtime_api.c |   10 +-
> >> >> > net/sched/p4tc/p4tc_table.c       |   57 +-
> >> >> > net/sched/p4tc/p4tc_tbl_entry.c   |   25 +-
> >> >> > net/sched/p4tc/p4tc_tmpl_api.c    |    4 +
> >> >> > net/sched/p4tc/p4tc_tmpl_ext.c    | 2221 +++++++++++++++++++++++++++++
> >> >> > 13 files changed, 5083 insertions(+), 10 deletions(-)
> >> >>
> >> >> This is for this patch. Now for the whole patchset you have:
> >> >>  30 files changed, 16676 insertions(+), 39 deletions(-)
> >> >>
> >> >> I understand that you want to fit into 15 patches with all the work.
> >> >> But sorry, patches like this are unreviewable. My suggestion is to split
> >> >> the patchset into multiple ones including smaller patches and allow
> >> >> people to digest this. I don't believe that anyone can seriously stand
> >> >> to review a patch with more than 200 lines changes.
> >> >
> >> >This specific patch is not difficult to split into two. I can do that
> >> >and send out minus the first 8 trivial patches - but not familiar with
> >> >how to do "here's part 1 of the patches" and "here's patchset two".
> >>
> >> Split into multiple patchsets and send one by one. No need to have all
> >> in at once.
> >>
> >>
> >> >There's dependency between them so not clear how patchwork and
> >>
> >> What dependency. It should compile. Introduce some basic functionality
> >> first and extend it incrementally with other patchsets. The usual way.
> >>
> >
> >Sorry, still not following:
> >Lets say i split the current patchset 1 with patch 1-8 (which are
> >trivial and have been reviewed) then make the rest into patchset 2
> >with a new set 1-8. I dont see how patchset 2 compiles unless it has
> >access to code from patchset 1. Unless patchset 1 is merged i dont see
> >how this works with patchwork or reviewers. Am i missing something?
>
> Why it would not work. Describe your motivation and plans and submit
> part of the work, the rest later on. No problem.

Sorry, still not following ;->
So push the trivial patches 1-8 for merge - then push rest in small increments?

cheers,
jamal
>
> >
> >cheers,
> >jamal
> >
> >>
> >> >reviewers would deal with it. Thoughts?
> >> >
> >> >Note: The code machinery is really repeatable; for example if you look
> >> >at the tables control you will see very similar patterns to actions
> >> >etc. i.e spending time to review one will make it easy for the rest.
> >> >
> >> >cheers,
> >> >jamal
> >> >
> >> >> [...]
diff mbox series

Patch

diff --git a/include/net/p4tc.h b/include/net/p4tc.h
index e25eaa4ac..b6b825c64 100644
--- a/include/net/p4tc.h
+++ b/include/net/p4tc.h
@@ -11,6 +11,7 @@ 
 #include <linux/rhashtable-types.h>
 #include <net/tc_act/p4tc.h>
 #include <net/p4tc_types.h>
+#include <linux/bpf.h>
 
 #define P4TC_DEFAULT_NUM_TABLES P4TC_MINTABLES_COUNT
 #define P4TC_DEFAULT_MAX_RULES 1
@@ -21,6 +22,10 @@ 
 #define P4TC_DEFAULT_TMASKS 8
 #define P4TC_MAX_T_AGING 864000000
 #define P4TC_DEFAULT_T_AGING 30000
+#define P4TC_DEFAULT_NUM_EXT_INSTS 1
+#define P4TC_MAX_NUM_EXT_INSTS 1024
+#define P4TC_DEFAULT_NUM_EXT_INST_ELEMS 1
+#define P4TC_MAX_NUM_EXT_INST_ELEMS 1024
 
 #define P4TC_MAX_PERMISSION (GENMASK(P4TC_PERM_MAX_BIT, 0))
 
@@ -30,6 +35,8 @@ 
 #define P4TC_TBLID_IDX 1
 #define P4TC_AID_IDX 1
 #define P4TC_PARSEID_IDX 1
+#define P4TC_TMPL_EXT_IDX 1
+#define P4TC_TMPL_EXT_INST_IDX 2
 
 struct p4tc_dump_ctx {
 	u32 ids[P4TC_PATH_MAX];
@@ -79,9 +86,14 @@  struct p4tc_pipeline {
 	struct p4tc_template_common common;
 	struct idr                  p_act_idr;
 	struct idr                  p_tbl_idr;
+	/* IDR where the externs are stored globally in the root pipeline */
+	struct idr                  p_ext_idr;
+	/* IDR where the per user pipeline data related to externs is stored */
+	struct idr                  user_ext_idr;
 	struct rcu_head             rcu;
 	struct net                  *net;
 	u32                         num_created_acts;
+	u32                         num_created_ext_elems;
 	/* Accounts for how many entities are referencing this pipeline.
 	 * As for now only P4 filters can refer to pipelines.
 	 */
@@ -207,6 +219,27 @@  static inline int p4tc_action_destroy(struct tc_action **acts)
 
 #define P4TC_MAX_PARAM_DATA_SIZE 124
 
+#define P4TC_EXT_FLAGS_UNSPEC 0x0
+#define P4TC_EXT_FLAGS_CONTROL_READ 0x1
+#define P4TC_EXT_FLAGS_CONTROL_WRITE 0x2
+
+struct p4tc_ext_bpf_params {
+	u32 pipe_id;
+	u32 ext_id;
+	u32 inst_id;
+	u32 index;
+	u32 param_id;
+	u32 flags;
+	u8  in_params[128]; /* extern specific params if any */
+};
+
+struct p4tc_ext_bpf_res {
+	u32 ext_id;
+	u32 index;
+	u32 verdict;
+	u8 out_params[128]; /* specific values if any */
+};
+
 struct p4tc_table_defact {
 	struct tc_action **default_acts;
 	/* Will have two 7 bits blocks containing CRUDXPS (Create, read, update,
@@ -237,6 +270,7 @@  struct p4tc_table {
 	struct p4tc_table_perm __rcu        *tbl_permissions;
 	struct p4tc_table_entry_mask __rcu  **tbl_masks_array;
 	unsigned long __rcu                 *tbl_free_masks_bitmap;
+	struct p4tc_extern_inst             *tbl_counter;
 	u64                                 tbl_aging;
 	/* Locks the available masks IDR which will be used when adding and
 	 * deleting table entries.
@@ -352,6 +386,7 @@  struct p4tc_table_entry_key {
 struct p4tc_table_entry_value {
 	u32                              prio;
 	int                              num_acts;
+	struct p4tc_extern_common        *counter;
 	struct tc_action                 **acts;
 	/* Accounts for how many entities are referencing, eg: Data path,
 	 * one or more control path and timer.
@@ -576,8 +611,134 @@  static inline bool p4tc_runtime_msg_is_update(struct nlmsghdr *n)
 	return n->nlmsg_type == RTM_P4TC_UPDATE;
 }
 
+struct p4tc_user_pipeline_extern {
+	struct idr		e_inst_idr;
+	struct p4tc_tmpl_extern	*tmpl_ext;
+	void (*free)(struct p4tc_user_pipeline_extern *pipe_ext,
+		     struct idr *tmpl_exts_idr);
+	u32			ext_id;
+	/* Accounts for how many instances refer to this extern. */
+	refcount_t		ext_ref;
+	/* Counts how many instances this extern has */
+	atomic_t                curr_insts_num;
+	u32                     PAD0;
+	char			ext_name[EXTERNNAMSIZ];
+};
+
+struct p4tc_tmpl_extern {
+	struct p4tc_template_common  common;
+	struct idr                   params_idr;
+	const struct p4tc_extern_ops *ops;
+	u32                          ext_id;
+	u32                          num_params;
+	u32                          max_num_insts;
+	/* Accounts for how many pipelines refer to this extern. */
+	refcount_t                   tmpl_ref;
+	char                         mod_name[MODULE_NAME_LEN];
+	bool                         has_exec_method;
+};
+
+struct p4tc_extern_inst {
+	struct p4tc_template_common      common;
+	struct p4tc_extern_params        *params;
+	const struct p4tc_extern_ops     *ops;
+	struct idr                       control_elems_idr;
+	struct list_head                 unused_elems;
+	/* Locks the available externs list.
+	 * Which will be used by table entries that reference externs (refer to
+	 * direct counters and meters in P4).
+	 * Note that table entries can be created, update or deleted by both
+	 * control and data path. So this list may be modified from both
+	 * contexts.
+	 */
+	spinlock_t                       available_list_lock;
+	/* Accounts for how many elements refer to this extern. */
+	refcount_t                       inst_ref;
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	char                             *ext_name;
+	/* Counts how many elements this extern has */
+	atomic_t                         curr_num_elems;
+	u32                              max_num_elems;
+	u32                              ext_id;
+	u32                              ext_inst_id;
+	u32                              num_control_params;
+	struct p4tc_extern_params        *constr_params;
+	u32				 flags;
+	bool                             tbl_bindable;
+	bool				 is_scalar;
+};
+
+int p4tc_pipeline_create_extern_net(struct p4tc_tmpl_extern *tmpl_ext);
+int p4tc_pipeline_del_extern_net(struct p4tc_tmpl_extern *tmpl_ext);
+struct p4tc_extern_inst *
+p4tc_ext_inst_find_bynames(struct net *net, struct p4tc_pipeline *pipeline,
+			   const char *extname, const char *instname,
+			   struct netlink_ext_ack *extack);
+struct p4tc_user_pipeline_extern *
+p4tc_pipe_ext_find_bynames(struct net *net, struct p4tc_pipeline *pipeline,
+			   const char *extname, struct netlink_ext_ack *extack);
+struct p4tc_extern_inst *
+p4tc_ext_inst_table_bind(struct p4tc_pipeline *pipeline,
+			 struct p4tc_user_pipeline_extern **pipe_ext,
+			 const char *ext_inst_path,
+			 struct netlink_ext_ack *extack);
+void
+p4tc_ext_inst_table_unbind(struct p4tc_table *table,
+			   struct p4tc_user_pipeline_extern *pipe_ext,
+			   struct p4tc_extern_inst *inst);
+struct p4tc_extern_inst *
+p4tc_ext_inst_get_byids(struct net *net, struct p4tc_pipeline **pipeline,
+			struct p4tc_ext_bpf_params *params);
+struct p4tc_extern_inst *
+p4tc_ext_find_byids(struct p4tc_pipeline *pipeline,
+		    const u32 ext_id, const u32 inst_id);
+struct p4tc_extern_inst *
+p4tc_ext_inst_alloc(const struct p4tc_extern_ops *ops, const u32 max_num_elems,
+		    bool tbl_bindable, char *ext_name);
+
+int __bpf_p4tc_extern_md_write(struct net *net,
+			       struct p4tc_ext_bpf_params *params);
+int __bpf_p4tc_extern_md_read(struct net *net,
+			      struct p4tc_ext_bpf_res *res,
+			      struct p4tc_ext_bpf_params *params);
+struct p4tc_extern_params *
+p4tc_ext_params_copy(struct p4tc_extern_params *params_orig);
+
+extern const struct p4tc_template_ops p4tc_tmpl_ext_ops;
+extern const struct p4tc_template_ops p4tc_ext_inst_ops;
+
+struct p4tc_extern_param {
+	struct p4tc_extern_param_ops    *ops;
+	struct p4tc_extern_param_ops    *mod_ops;
+	void				*value;
+	struct p4tc_type		*type;
+	struct p4tc_type_mask_shift	*mask_shift;
+	u32				id;
+	u32				index;
+	u32                             bitsz;
+	u32				flags;
+	char				name[EXTPARAMNAMSIZ];
+	struct rcu_head			rcu;
+};
+
+struct p4tc_extern_param_ops {
+	int (*init_value)(struct net *net,
+			  struct p4tc_extern_param *nparam, void *value,
+			  struct netlink_ext_ack *extack);
+	/* Only for bit<X> parameter types */
+	void (*default_value)(struct p4tc_extern_param *nparam);
+	int (*dump_value)(struct sk_buff *skb, struct p4tc_extern_param_ops *op,
+			  struct p4tc_extern_param *param);
+	void (*free)(struct p4tc_extern_param *param);
+	u32 len;
+	u32 alloc_len;
+};
+
 #define to_pipeline(t) ((struct p4tc_pipeline *)t)
 #define to_act(t) ((struct p4tc_act *)t)
 #define to_table(t) ((struct p4tc_table *)t)
 
+#define to_extern(t) ((struct p4tc_tmpl_extern *)t)
+#define to_extern_inst(t) ((struct p4tc_extern_inst *)t)
+
 #endif
diff --git a/include/net/p4tc_ext_api.h b/include/net/p4tc_ext_api.h
new file mode 100644
index 000000000..fcc16d252
--- /dev/null
+++ b/include/net/p4tc_ext_api.h
@@ -0,0 +1,199 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __NET_P4TC_EXT_API_H
+#define __NET_P4TC_EXT_API_H
+
+/*
+ * Public extern P4TC_EXT API
+ */
+
+#include <uapi/linux/p4tc_ext.h>
+#include <linux/refcount.h>
+#include <net/flow_offload.h>
+#include <net/sch_generic.h>
+#include <net/pkt_sched.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/p4tc.h>
+
+struct p4tc_extern_ops;
+
+struct p4tc_extern_params {
+	struct idr params_idr;
+	rwlock_t params_lock;
+	u32 num_params;
+	u32 PAD0;
+};
+
+struct p4tc_extern_common {
+	struct list_head             node;
+	struct p4tc_extern_params    *params;
+	const struct p4tc_extern_ops *ops;
+	struct p4tc_extern_inst      *inst;
+	u32                          p4tc_ext_flags;
+	u32                          p4tc_ext_key;
+	refcount_t                   p4tc_ext_refcnt;
+	u32                          PAD0;
+};
+
+struct p4tc_extern {
+	struct p4tc_extern_common       common;
+	struct idr			*elems_idr;
+	struct rcu_head			rcu;
+	size_t				attrs_size;
+	/* Extern element lock */
+	spinlock_t			p4tc_ext_lock;
+};
+
+/* Reserve 16 bits for user-space. See P4TC_EXT_FLAGS_NO_PERCPU_STATS. */
+#define P4TC_EXT_FLAGS_USER_BITS 16
+#define P4TC_EXT_FLAGS_USER_MASK 0xffff
+
+struct p4tc_extern_ops {
+	struct list_head head;
+	size_t size;
+	size_t elem_size;
+	struct module *owner;
+	struct p4tc_tmpl_extern *tmpl_ext;
+	int (*exec)(struct p4tc_extern_common *common, void *priv);
+	int (*construct)(struct p4tc_extern_inst **common,
+			 struct p4tc_extern_params *params,
+			 struct p4tc_extern_params *constr_params,
+			 u32 max_num_elems, bool tbl_bindable,
+			 struct netlink_ext_ack *extack);
+	void (*deconstruct)(struct p4tc_extern_inst *common);
+	int (*dump)(struct sk_buff *skb,
+		    struct p4tc_extern_inst *common,
+		    struct netlink_callback *cb);
+	int (*rctrl)(int cmd, struct p4tc_extern_inst *inst,
+		     struct p4tc_extern_common **e,
+		     struct p4tc_extern_params *params,
+		     void *key_u32, struct netlink_ext_ack *extack);
+	u32 id; /* identifier should match kind */
+	u32 PAD0;
+	char kind[P4TC_EXT_NAMSIZ];
+};
+
+#define P4TC_EXT_P_CREATED 1
+#define P4TC_EXT_P_DELETED 1
+
+int p4tc_register_extern(struct p4tc_extern_ops *ext);
+int p4tc_unregister_extern(struct p4tc_extern_ops *ext);
+
+int p4tc_ctl_extern_dump(struct sk_buff *skb, struct netlink_callback *cb,
+			 struct nlattr **tb, const char *pname);
+void p4tc_ext_purge(struct idr *idr);
+void p4tc_ext_inst_purge(struct p4tc_extern_inst *inst);
+
+int p4tc_ctl_extern(struct sk_buff *skb, struct nlmsghdr *n, int cmd,
+		    struct netlink_ext_ack *extack);
+struct p4tc_extern_param *
+p4tc_ext_param_find_byanyattr(struct idr *params_idr,
+			      struct nlattr *name_attr,
+			      const u32 param_id,
+			      struct netlink_ext_ack *extack);
+struct p4tc_extern_param *
+p4tc_ext_param_find_byid(struct idr *params_idr, const u32 param_id);
+
+int p4tc_ext_param_value_init(struct net *net,
+			      struct p4tc_extern_param *param,
+			      struct nlattr **tb, u32 typeid,
+			      bool value_required,
+			      struct netlink_ext_ack *extack);
+void p4tc_ext_param_value_free_tmpl(struct p4tc_extern_param *param);
+int p4tc_ext_param_value_dump_tmpl(struct sk_buff *skb,
+				   struct p4tc_extern_param *param);
+int p4tc_extern_insts_init_elems(struct idr *user_ext_idr);
+int p4tc_extern_inst_init_elems(struct p4tc_extern_inst *inst, u32 num_elems);
+
+int p4tc_unregister_extern(struct p4tc_extern_ops *ext);
+
+struct p4tc_extern_common *p4tc_ext_elem_get(struct p4tc_extern_inst *inst);
+void p4tc_ext_elem_put_list(struct p4tc_extern_inst *inst,
+			    struct p4tc_extern_common *e);
+
+int p4tc_ext_elem_dump_1(struct sk_buff *skb, struct p4tc_extern_common *e);
+void p4tc_ext_params_free(struct p4tc_extern_params *params, bool free_vals);
+
+static inline struct p4tc_extern_param *
+p4tc_extern_params_find_byid(struct p4tc_extern_params *params, u32 param_id)
+{
+	return idr_find(&params->params_idr, param_id);
+}
+
+int p4tc_ext_init_defval_params(struct p4tc_extern_inst *inst,
+				struct p4tc_extern_common *common,
+				struct idr *control_params_idr,
+				struct netlink_ext_ack *extack);
+struct p4tc_extern_params *p4tc_extern_params_init(void);
+
+static inline bool p4tc_ext_inst_has_dump(const struct p4tc_extern_inst *inst)
+{
+	const struct p4tc_extern_ops *ops = inst->ops;
+
+	return ops && ops->dump;
+}
+
+static inline bool p4tc_ext_has_rctrl(const struct p4tc_extern_ops *ops)
+{
+	return ops && ops->rctrl;
+}
+
+static inline bool p4tc_ext_has_exec(const struct p4tc_extern_ops *ops)
+{
+	return ops && ops->exec;
+}
+
+static inline bool p4tc_ext_has_construct(const struct p4tc_extern_ops *ops)
+{
+	return ops && ops->construct;
+}
+
+static inline bool
+p4tc_ext_inst_has_construct(const struct p4tc_extern_inst *inst)
+{
+	const struct p4tc_extern_ops *ops = inst->ops;
+
+	return p4tc_ext_has_construct(ops);
+}
+
+static inline bool
+p4tc_ext_inst_has_rctrl(const struct p4tc_extern_inst *inst)
+{
+	const struct p4tc_extern_ops *ops = inst->ops;
+
+	return p4tc_ext_has_rctrl(ops);
+}
+
+static inline bool
+p4tc_ext_inst_has_exec(const struct p4tc_extern_inst *inst)
+{
+	const struct p4tc_extern_ops *ops = inst->ops;
+
+	return p4tc_ext_has_exec(ops);
+}
+
+struct p4tc_extern *
+p4tc_ext_elem_find(struct p4tc_extern_inst *inst,
+		   struct p4tc_ext_bpf_params *params);
+
+struct p4tc_extern_common *
+p4tc_ext_common_elem_get(struct sk_buff *skb, struct p4tc_pipeline **pipeline,
+			 struct p4tc_ext_bpf_params *params);
+struct p4tc_extern_common *
+p4tc_xdp_ext_common_elem_get(struct xdp_buff *ctx,
+			     struct p4tc_pipeline **pipeline,
+			     struct p4tc_ext_bpf_params *params);
+void p4tc_ext_common_elem_put(struct p4tc_pipeline *pipeline,
+			      struct p4tc_extern_common *common);
+
+static inline void p4tc_ext_inst_inc_num_elems(struct p4tc_extern_inst *inst)
+{
+	atomic_inc(&inst->curr_num_elems);
+}
+
+static inline void p4tc_ext_inst_dec_num_elems(struct p4tc_extern_inst *inst)
+{
+	atomic_dec(&inst->curr_num_elems);
+}
+
+#endif
diff --git a/include/uapi/linux/p4tc.h b/include/uapi/linux/p4tc.h
index a2a39303f..3f19a1e5e 100644
--- a/include/uapi/linux/p4tc.h
+++ b/include/uapi/linux/p4tc.h
@@ -18,6 +18,8 @@  struct p4tcmsg {
 #define P4TC_MSGBATCH_SIZE 16
 
 #define P4TC_ACT_MAX_NUM_PARAMS P4TC_MSGBATCH_SIZE
+#define EXTPARAMNAMSIZ 256
+#define P4TC_MAX_EXTERN_METHODS 32
 
 #define P4TC_MAX_KEYSZ 512
 #define P4TC_DEFAULT_NUM_PREALLOC 16
@@ -27,6 +29,8 @@  struct p4tcmsg {
 #define ACTTMPLNAMSIZ TEMPLATENAMSZ
 #define ACTPARAMNAMSIZ TEMPLATENAMSZ
 #define TABLENAMSIZ TEMPLATENAMSZ
+#define EXTERNNAMSIZ TEMPLATENAMSZ
+#define EXTERNINSTNAMSIZ TEMPLATENAMSZ
 
 #define P4TC_TABLE_FLAGS_KEYSZ (1 << 0)
 #define P4TC_TABLE_FLAGS_MAX_ENTRIES (1 << 1)
@@ -119,6 +123,8 @@  enum {
 	P4TC_ROOT_UNSPEC,
 	P4TC_ROOT, /* nested messages */
 	P4TC_ROOT_PNAME, /* string */
+	P4TC_ROOT_COUNT,
+	P4TC_ROOT_FLAGS,
 	__P4TC_ROOT_MAX,
 };
 
@@ -130,6 +136,8 @@  enum {
 	P4TC_OBJ_PIPELINE,
 	P4TC_OBJ_ACT,
 	P4TC_OBJ_TABLE,
+	P4TC_OBJ_EXT,
+	P4TC_OBJ_EXT_INST,
 	__P4TC_OBJ_MAX,
 };
 
@@ -139,6 +147,7 @@  enum {
 enum {
 	P4TC_OBJ_RUNTIME_UNSPEC,
 	P4TC_OBJ_RUNTIME_TABLE,
+	P4TC_OBJ_RUNTIME_EXTERN,
 	__P4TC_OBJ_RUNTIME_MAX,
 };
 
@@ -233,6 +242,7 @@  enum {
 	P4TC_TABLE_DEFAULT_MISS, /* nested default miss action attributes */
 	P4TC_TABLE_CONST_ENTRY, /* nested const table entry*/
 	P4TC_TABLE_ACTS_LIST, /* nested table actions list */
+	P4TC_TABLE_COUNTER, /* string */
 	__P4TC_TABLE_MAX
 };
 
@@ -325,6 +335,7 @@  enum {
 	P4TC_ENTRY_TBL_ATTRS, /* nested table attributes */
 	P4TC_ENTRY_DYNAMIC, /* u8 tells if table entry is dynamic */
 	P4TC_ENTRY_AGING, /* u64 table entry aging */
+	P4TC_ENTRY_COUNTER, /* nested extern associated with entry counter */
 	P4TC_ENTRY_PAD,
 	__P4TC_ENTRY_MAX
 };
@@ -339,6 +350,56 @@  enum {
 	P4TC_ENTITY_MAX
 };
 
+/* P4 Extern attributes */
+enum {
+	P4TC_TMPL_EXT_UNSPEC,
+	P4TC_TMPL_EXT_NAME, /* string */
+	P4TC_TMPL_EXT_NUM_INSTS, /* u16 */
+	P4TC_TMPL_EXT_HAS_EXEC_METHOD, /* u8 */
+	__P4TC_TMPL_EXT_MAX
+};
+
+#define P4TC_TMPL_EXT_MAX (__P4TC_TMPL_EXT_MAX - 1)
+
+enum {
+	P4TC_TMPL_EXT_INST_UNSPEC,
+	P4TC_TMPL_EXT_INST_EXT_NAME, /* string */
+	P4TC_TMPL_EXT_INST_NAME, /* string */
+	P4TC_TMPL_EXT_INST_NUM_ELEMS, /* u32 */
+	P4TC_TMPL_EXT_INST_CONTROL_PARAMS, /* nested control params */
+	P4TC_TMPL_EXT_INST_TABLE_BINDABLE, /* bool */
+	P4TC_TMPL_EXT_INST_CONSTR_PARAMS, /* nested constructor params */
+	__P4TC_TMPL_EXT_INST_MAX
+};
+
+#define P4TC_TMPL_EXT_INST_MAX (__P4TC_TMPL_EXT_INST_MAX - 1)
+
+/* Extern params attributes */
+enum {
+	P4TC_EXT_PARAMS_VALUE_UNSPEC,
+	P4TC_EXT_PARAMS_VALUE_RAW, /* binary */
+	__P4TC_EXT_PARAMS_VALUE_MAX
+};
+
+#define P4TC_EXT_VALUE_PARAMS_MAX (__P4TC_EXT_PARAMS_VALUE_MAX - 1)
+
+#define P4TC_EXT_PARAMS_FLAG_ISKEY 0x1
+#define P4TC_EXT_PARAMS_FLAG_IS_DATASCALAR 0x2
+
+/* Extern params attributes */
+enum {
+	P4TC_EXT_PARAMS_UNSPEC,
+	P4TC_EXT_PARAMS_NAME, /* string */
+	P4TC_EXT_PARAMS_ID, /* u32 */
+	P4TC_EXT_PARAMS_VALUE, /* bytes */
+	P4TC_EXT_PARAMS_TYPE, /* u32 */
+	P4TC_EXT_PARAMS_BITSZ, /* u16 */
+	P4TC_EXT_PARAMS_FLAGS, /* u8 */
+	__P4TC_EXT_PARAMS_MAX
+};
+
+#define P4TC_EXT_PARAMS_MAX (__P4TC_EXT_PARAMS_MAX - 1)
+
 #define P4TC_RTA(r) \
 	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct p4tcmsg))))
 
diff --git a/include/uapi/linux/p4tc_ext.h b/include/uapi/linux/p4tc_ext.h
new file mode 100644
index 000000000..7d4ecb5b1
--- /dev/null
+++ b/include/uapi/linux/p4tc_ext.h
@@ -0,0 +1,36 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_P4TC_EXT_H
+#define __LINUX_P4TC_EXT_H
+
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+#define P4TC_EXT_NAMSIZ 64
+
+/* Extern attributes */
+enum {
+	P4TC_EXT_UNSPEC,
+	P4TC_EXT_INST_NAME,
+	P4TC_EXT_KIND,
+	P4TC_EXT_PARAMS,
+	P4TC_EXT_KEY,
+	P4TC_EXT_PAD,
+	P4TC_EXT_FLAGS,
+	__P4TC_EXT_MAX
+};
+
+#define P4TC_EXT_ID_DYN 0x01
+#define P4TC_EXT_ID_MAX 1023
+
+/* See other P4TC_EXT_FLAGS_ * flags in include/net/act_api.h. */
+#define P4TC_EXT_FLAGS_NO_PERCPU_STATS (1 << 0) /* Don't use percpu allocator
+						 * for externs stats.
+						 */
+#define P4TC_EXT_FLAGS_SKIP_HW	(1 << 1) /* don't offload action to HW */
+#define P4TC_EXT_FLAGS_SKIP_SW	(1 << 2) /* don't use action in SW */
+
+#define P4TC_EXT_FLAG_LARGE_DUMP_ON	(1 << 0)
+
+#define P4TC_EXT_MAX __P4TC_EXT_MAX
+
+#endif
diff --git a/net/sched/p4tc/Makefile b/net/sched/p4tc/Makefile
index 726902f10..0e1ca964f 100644
--- a/net/sched/p4tc/Makefile
+++ b/net/sched/p4tc/Makefile
@@ -4,5 +4,5 @@  CFLAGS_trace.o := -I$(src)
 
 obj-y := p4tc_types.o p4tc_tmpl_api.o p4tc_pipeline.o \
 	p4tc_action.o p4tc_table.o p4tc_tbl_entry.o \
-	p4tc_runtime_api.o trace.o
+	p4tc_runtime_api.o trace.o p4tc_ext.o p4tc_tmpl_ext.o
 obj-$(CONFIG_DEBUG_INFO_BTF) += p4tc_bpf.o
diff --git a/net/sched/p4tc/p4tc_bpf.c b/net/sched/p4tc/p4tc_bpf.c
index fe84c1504..71f397961 100644
--- a/net/sched/p4tc/p4tc_bpf.c
+++ b/net/sched/p4tc/p4tc_bpf.c
@@ -13,6 +13,7 @@ 
 #include <linux/btf_ids.h>
 #include <linux/net_namespace.h>
 #include <net/p4tc.h>
+#include <net/p4tc_ext_api.h>
 #include <linux/netdevice.h>
 #include <net/sock.h>
 #include <net/xdp.h>
@@ -22,6 +23,8 @@  BTF_ID(struct, p4tc_table_entry_act_bpf)
 BTF_ID(struct, p4tc_table_entry_act_bpf_params)
 BTF_ID(struct, p4tc_table_entry_act_bpf)
 BTF_ID(struct, p4tc_table_entry_create_bpf_params)
+BTF_ID(struct, p4tc_ext_bpf_params)
+BTF_ID(struct, p4tc_ext_bpf_res)
 
 static struct p4tc_table_entry_act_bpf dummy_act_bpf = {};
 
@@ -294,6 +297,50 @@  xdp_p4tc_entry_delete(struct xdp_md *xdp_ctx,
 	return __bpf_p4tc_entry_delete(net, params, key, key__sz);
 }
 
+__bpf_kfunc int bpf_p4tc_extern_md_read(struct __sk_buff *skb_ctx,
+					struct p4tc_ext_bpf_res *res,
+					struct p4tc_ext_bpf_params *params)
+{
+	struct sk_buff *skb = (struct sk_buff *)skb_ctx;
+	struct net *net;
+
+	net = skb->dev ? dev_net(skb->dev) : sock_net(skb->sk);
+
+	return __bpf_p4tc_extern_md_read(net, res, params);
+}
+
+__bpf_kfunc int bpf_p4tc_extern_md_write(struct __sk_buff *skb_ctx,
+					 struct p4tc_ext_bpf_params *params)
+{
+	struct sk_buff *skb = (struct sk_buff *)skb_ctx;
+	struct net *net;
+
+	net = skb->dev ? dev_net(skb->dev) : sock_net(skb->sk);
+
+	return __bpf_p4tc_extern_md_write(net, params);
+}
+
+__bpf_kfunc int xdp_p4tc_extern_md_read(struct xdp_md *xdp_ctx,
+					struct p4tc_ext_bpf_res *res,
+					struct p4tc_ext_bpf_params *params)
+{
+	struct xdp_buff *ctx = (struct xdp_buff *)xdp_ctx;
+	struct net *net;
+
+	net = dev_net(ctx->rxq->dev);
+
+	return __bpf_p4tc_extern_md_read(net, res, params);
+}
+
+__bpf_kfunc int xdp_p4tc_extern_md_write(struct xdp_md *xdp_ctx,
+					 struct p4tc_ext_bpf_params *params)
+{
+	struct xdp_buff *ctx = (struct xdp_buff *)xdp_ctx;
+	struct net *net = dev_net(ctx->rxq->dev);
+
+	return __bpf_p4tc_extern_md_write(net, params);
+}
+
 __diag_pop();
 
 BTF_SET8_START(p4tc_kfunc_check_tbl_set_skb)
@@ -322,6 +369,26 @@  static const struct btf_kfunc_id_set p4tc_kfunc_tbl_set_xdp = {
 	.set = &p4tc_kfunc_check_tbl_set_xdp,
 };
 
+BTF_SET8_START(p4tc_kfunc_check_ext_set_skb)
+BTF_ID_FLAGS(func, bpf_p4tc_extern_md_write);
+BTF_ID_FLAGS(func, bpf_p4tc_extern_md_read);
+BTF_SET8_END(p4tc_kfunc_check_ext_set_skb)
+
+static const struct btf_kfunc_id_set p4tc_kfunc_ext_set_skb = {
+	.owner = THIS_MODULE,
+	.set = &p4tc_kfunc_check_ext_set_skb,
+};
+
+BTF_SET8_START(p4tc_kfunc_check_ext_set_xdp)
+BTF_ID_FLAGS(func, xdp_p4tc_extern_md_write);
+BTF_ID_FLAGS(func, xdp_p4tc_extern_md_read);
+BTF_SET8_END(p4tc_kfunc_check_ext_set_xdp)
+
+static const struct btf_kfunc_id_set p4tc_kfunc_ext_set_xdp = {
+	.owner = THIS_MODULE,
+	.set = &p4tc_kfunc_check_ext_set_xdp,
+};
+
 int register_p4tc_tbl_bpf(void)
 {
 	int ret;
@@ -332,6 +399,16 @@  int register_p4tc_tbl_bpf(void)
 		return ret;
 
 	/* There is no unregister_btf_kfunc_id_set function */
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP,
+					&p4tc_kfunc_tbl_set_xdp);
+	if (ret < 0)
+		return ret;
+
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_ACT,
+					&p4tc_kfunc_ext_set_skb);
+	if (ret < 0)
+		return ret;
+
 	return register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP,
-					 &p4tc_kfunc_tbl_set_xdp);
+					 &p4tc_kfunc_ext_set_xdp);
 }
diff --git a/net/sched/p4tc/p4tc_ext.c b/net/sched/p4tc/p4tc_ext.c
new file mode 100644
index 000000000..604634c8e
--- /dev/null
+++ b/net/sched/p4tc/p4tc_ext.c
@@ -0,0 +1,2204 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * net/sched/p4tc_ext.c	P4 TC EXTERN API
+ *
+ * Copyright (c) 2022-2023, Mojatatu Networks
+ * Copyright (c) 2022-2023, Intel Corporation.
+ * Authors:     Jamal Hadi Salim <jhs@mojatatu.com>
+ *              Victor Nogueira <victor@mojatatu.com>
+ *              Pedro Tammela <pctammela@mojatatu.com>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
+#include <net/sch_generic.h>
+#include <net/pkt_cls.h>
+#include <net/p4tc.h>
+#include <net/p4tc_types.h>
+#include <net/p4tc_ext_api.h>
+#include <net/netlink.h>
+#include <net/flow_offload.h>
+#include <net/tc_wrapper.h>
+#include <uapi/linux/p4tc.h>
+#include <net/xdp.h>
+
+static bool p4tc_ext_param_ops_is_init(struct p4tc_extern_param_ops *ops)
+{
+	struct p4tc_extern_param_ops uninit_ops = {NULL};
+
+	return memcmp(ops, &uninit_ops, sizeof(*ops));
+}
+
+static void p4tc_ext_put_param(struct p4tc_extern_param *param, bool free_val)
+{
+	struct p4tc_extern_param_ops *val_ops;
+
+	if (p4tc_ext_param_ops_is_init(param->ops))
+		val_ops = param->ops;
+	else
+		val_ops = param->mod_ops;
+
+	if (free_val) {
+		if (val_ops && val_ops->free)
+			val_ops->free(param);
+		else
+			kfree(param->value);
+	}
+
+	if (param->mask_shift)
+		p4t_release(param->mask_shift);
+	kfree(param);
+}
+
+static void p4tc_ext_put_many_params(struct idr *params_idr,
+				     struct p4tc_extern_param *params[],
+				     int params_count)
+{
+	int i;
+
+	for (i = 0; i < params_count; i++)
+		p4tc_ext_put_param(params[i], true);
+}
+
+static void p4tc_ext_insert_param(struct idr *params_idr,
+				  struct p4tc_extern_param *param)
+{
+	struct p4tc_extern_param *param_old;
+
+	param_old = idr_replace(params_idr, param, param->id);
+	if (param_old != ERR_PTR(-EBUSY))
+		p4tc_ext_put_param(param_old, true);
+}
+
+static void p4tc_ext_insert_many_params(struct idr *params_idr,
+					struct p4tc_extern_param *params[],
+					int params_count)
+{
+	int i;
+
+	for (i = 0; i < params_count; i++)
+		p4tc_ext_insert_param(params_idr, params[i]);
+}
+
+static void __p4tc_ext_params_free(struct p4tc_extern_params *params,
+				   bool free_vals)
+{
+	struct p4tc_extern_param *parm;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(&params->params_idr, parm, tmp, id) {
+		idr_remove(&params->params_idr, id);
+		p4tc_ext_put_param(parm, free_vals);
+	}
+}
+
+void p4tc_ext_params_free(struct p4tc_extern_params *params, bool free_vals)
+{
+	__p4tc_ext_params_free(params, free_vals);
+	idr_destroy(&params->params_idr);
+	kfree(params);
+}
+EXPORT_SYMBOL_GPL(p4tc_ext_params_free);
+
+static void free_p4tc_ext(struct p4tc_extern *p)
+{
+	if (p->common.params)
+		p4tc_ext_params_free(p->common.params, true);
+
+	kfree(p);
+}
+
+static void free_p4tc_ext_rcu(struct rcu_head *rcu)
+{
+	struct p4tc_extern *p;
+
+	p = container_of(rcu, struct p4tc_extern, rcu);
+
+	free_p4tc_ext(p);
+}
+
+static void p4tc_extern_cleanup(struct p4tc_extern *p)
+{
+	free_p4tc_ext_rcu(&p->rcu);
+}
+
+static int __p4tc_extern_put(struct p4tc_extern *p)
+{
+	if (refcount_dec_and_test(&p->common.p4tc_ext_refcnt)) {
+		idr_remove(p->elems_idr, p->common.p4tc_ext_key);
+
+		p4tc_extern_cleanup(p);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int __p4tc_ext_idr_release(struct p4tc_extern *p)
+{
+	int ret = 0;
+
+	if (p) {
+		if (__p4tc_extern_put(p))
+			ret = P4TC_EXT_P_DELETED;
+	}
+
+	return ret;
+}
+
+static int p4tc_ext_idr_release(struct p4tc_extern *e)
+{
+	return __p4tc_ext_idr_release(e);
+}
+
+static int p4tc_ext_idr_release_dec_num_elems(struct p4tc_extern *e)
+{
+	struct p4tc_extern_inst *inst = e->common.inst;
+	int ret;
+
+	ret = __p4tc_ext_idr_release(e);
+	if (ret == P4TC_EXT_P_DELETED)
+		p4tc_ext_inst_dec_num_elems(inst);
+
+	return ret;
+}
+
+static size_t p4tc_extern_shared_attrs_size(void)
+{
+	return  nla_total_size(0) /* extern number nested */
+		+ nla_total_size(EXTERNNAMSIZ)  /* P4TC_EXT_KIND */
+		+ nla_total_size(EXTERNINSTNAMSIZ) /* P4TC_EXT_INST_NAME */
+		+ nla_total_size(sizeof(struct nla_bitfield32)); /* P4TC_EXT_FLAGS */
+}
+
+static size_t p4tc_extern_full_attrs_size(size_t sz)
+{
+	return NLMSG_HDRLEN                     /* struct nlmsghdr */
+		+ sizeof(struct p4tcmsg)
+		+ nla_total_size(0)             /* P4TC_ROOT nested */
+		+ sz;
+}
+
+static int
+generic_dump_ext_param_value(struct sk_buff *skb, struct p4tc_type *type,
+			     struct p4tc_extern_param *param)
+{
+	const u32 bytesz = BITS_TO_BYTES(type->container_bitsz);
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct nlattr *nla_value;
+
+	nla_value = nla_nest_start(skb, P4TC_EXT_PARAMS_VALUE);
+	if (nla_put(skb, P4TC_EXT_PARAMS_VALUE_RAW, bytesz,
+		    param->value))
+		goto out_nlmsg_trim;
+	nla_nest_end(skb, nla_value);
+
+	return 0;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static const struct nla_policy p4tc_extern_params_value_policy[P4TC_EXT_VALUE_PARAMS_MAX + 1] = {
+	[P4TC_EXT_PARAMS_VALUE_RAW] = { .type = NLA_BINARY },
+};
+
+static int dev_init_param_value(struct net *net,
+				struct p4tc_extern_param *nparam,
+				void *value,
+				struct netlink_ext_ack *extack)
+{
+	u32 ifindex, *ifindex_ptr;
+
+	if (value) {
+		ifindex = *((u32 *)value);
+	} else {
+		ifindex = 1;
+		goto param_alloc;
+	}
+
+	rcu_read_lock();
+	if (!dev_get_by_index_rcu(net, ifindex)) {
+		NL_SET_ERR_MSG(extack, "Invalid ifindex");
+		rcu_read_unlock();
+		return -EINVAL;
+	}
+	rcu_read_unlock();
+
+param_alloc:
+	nparam->value = kzalloc(sizeof(ifindex), GFP_KERNEL);
+	if (!nparam->value)
+		return -ENOMEM;
+
+	ifindex_ptr = nparam->value;
+	*ifindex_ptr = ifindex;
+
+	return 0;
+}
+
+static int dev_dump_param_value(struct sk_buff *skb,
+				struct p4tc_extern_param_ops *op,
+				struct p4tc_extern_param *param)
+{
+	struct nlattr *nest;
+	u32 *ifindex;
+	int ret;
+
+	nest = nla_nest_start(skb, P4TC_EXT_PARAMS_VALUE);
+	ifindex = (u32 *)param->value;
+
+	if (nla_put_u32(skb, P4TC_EXT_PARAMS_VALUE_RAW, *ifindex)) {
+		ret = -EINVAL;
+		goto out_nla_cancel;
+	}
+	nla_nest_end(skb, nest);
+
+	return 0;
+
+out_nla_cancel:
+	nla_nest_cancel(skb, nest);
+	return ret;
+}
+
+static void dev_free_param_value(struct p4tc_extern_param *param)
+{
+	kfree(param->value);
+}
+
+static const struct p4tc_extern_param_ops ext_param_ops[P4T_MAX + 1] = {
+	[P4T_DEV] = {
+		.init_value = dev_init_param_value,
+		.dump_value = dev_dump_param_value,
+		.free = dev_free_param_value,
+	},
+};
+
+static int p4tc_extern_elem_dump_param_noval(struct sk_buff *skb,
+					     struct p4tc_extern_param *parm)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+
+	if (nla_put_string(skb, P4TC_EXT_PARAMS_NAME,
+			   parm->name))
+		goto nla_put_failure;
+
+	if (nla_put_u32(skb, P4TC_EXT_PARAMS_ID, parm->id))
+		goto nla_put_failure;
+
+	if (nla_put_u32(skb, P4TC_EXT_PARAMS_TYPE, parm->type->typeid))
+		goto nla_put_failure;
+
+	return skb->len;
+
+nla_put_failure:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static int
+p4tc_extern_elem_dump_params(struct sk_buff *skb, struct p4tc_extern_common *e)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct p4tc_extern_param *parm;
+	struct nlattr *nest_parms;
+	int id;
+
+	nest_parms = nla_nest_start(skb, P4TC_EXT_PARAMS);
+	if (e->params) {
+		int i = 1;
+
+		idr_for_each_entry(&e->params->params_idr, parm, id) {
+			struct p4tc_extern_param_ops *val_ops;
+			struct nlattr *nest_count;
+
+			nest_count = nla_nest_start(skb, i);
+			if (!nest_count)
+				goto nla_put_failure;
+
+			if (p4tc_extern_elem_dump_param_noval(skb, parm) < 0)
+				goto nla_put_failure;
+
+			if (p4tc_ext_param_ops_is_init(parm->ops))
+				val_ops = parm->ops;
+			else
+				val_ops = parm->mod_ops;
+
+			read_lock_bh(&e->params->params_lock);
+			if (val_ops && val_ops->dump_value) {
+				if (val_ops->dump_value(skb, parm->ops, parm) < 0) {
+					read_unlock_bh(&e->params->params_lock);
+					goto nla_put_failure;
+				}
+			} else {
+				if (generic_dump_ext_param_value(skb, parm->type, parm)) {
+					read_unlock_bh(&e->params->params_lock);
+					goto nla_put_failure;
+				}
+			}
+			read_unlock_bh(&e->params->params_lock);
+
+			if (nla_put_u32(skb, P4TC_EXT_PARAMS_FLAGS,
+					parm->flags))
+				goto nla_put_failure;
+
+			nla_nest_end(skb, nest_count);
+			i++;
+		}
+	}
+	nla_nest_end(skb, nest_parms);
+
+	return skb->len;
+
+nla_put_failure:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+int
+p4tc_ext_elem_dump_1(struct sk_buff *skb, struct p4tc_extern_common *e)
+{
+	const char *instname = e->inst->common.name;
+	unsigned char *b = nlmsg_get_pos(skb);
+	const char *kind = e->inst->ext_name;
+	u32 flags = e->p4tc_ext_flags;
+	u32 key = e->p4tc_ext_key;
+	int err;
+
+	if (nla_put_string(skb, P4TC_EXT_KIND, kind))
+		goto nla_put_failure;
+
+	if (nla_put_string(skb, P4TC_EXT_INST_NAME, instname))
+		goto nla_put_failure;
+
+	if (nla_put_u32(skb, P4TC_EXT_KEY, key))
+		goto nla_put_failure;
+
+	if (flags && nla_put_bitfield32(skb, P4TC_EXT_FLAGS,
+					flags, flags))
+		goto nla_put_failure;
+
+	err = p4tc_extern_elem_dump_params(skb, e);
+	if (err < 0)
+		goto nla_put_failure;
+
+	return skb->len;
+
+nla_put_failure:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+EXPORT_SYMBOL(p4tc_ext_elem_dump_1);
+
+static int p4tc_ext_dump_walker(struct p4tc_extern_inst *inst,
+				struct sk_buff *skb,
+				struct netlink_callback *cb)
+{
+	struct idr *idr = &inst->control_elems_idr;
+	int err = 0, s_i = 0, n_i = 0;
+	u32 ext_flags = cb->args[2];
+	struct p4tc_extern *p;
+	unsigned long id = 1;
+	struct nlattr *nest;
+	unsigned long tmp;
+	int key = -1;
+
+	if (p4tc_ext_inst_has_dump(inst)) {
+		n_i = inst->ops->dump(skb, inst, cb);
+		if (n_i < 0)
+			return n_i;
+	} else {
+		s_i = cb->args[0];
+
+		idr_for_each_entry_ul(idr, p, tmp, id) {
+			key++;
+			if (key < s_i)
+				continue;
+			if (IS_ERR(p))
+				continue;
+
+			nest = nla_nest_start(skb, n_i);
+			if (!nest) {
+				key--;
+				goto nla_put_failure;
+			}
+
+			err = p4tc_ext_elem_dump_1(skb, &p->common);
+			if (err < 0) {
+				key--;
+				nlmsg_trim(skb, nest);
+				goto done;
+			}
+			nla_nest_end(skb, nest);
+			n_i++;
+			if (!(ext_flags & P4TC_EXT_FLAG_LARGE_DUMP_ON) &&
+			    n_i >= P4TC_MSGBATCH_SIZE)
+				goto done;
+		}
+	}
+done:
+	if (key >= 0)
+		cb->args[0] = key + 1;
+
+	if (n_i) {
+		if (ext_flags & P4TC_EXT_FLAG_LARGE_DUMP_ON)
+			cb->args[1] = n_i;
+	}
+	return n_i;
+
+nla_put_failure:
+	nla_nest_cancel(skb, nest);
+	goto done;
+}
+
+static void __p4tc_ext_idr_purge(struct p4tc_extern *p)
+{
+	atomic_dec(&p->common.inst->curr_num_elems);
+	p4tc_extern_cleanup(p);
+}
+
+static void p4tc_ext_idr_purge(struct p4tc_extern *p)
+{
+	idr_remove(p->elems_idr, p->common.p4tc_ext_key);
+	__p4tc_ext_idr_purge(p);
+}
+
+/* Called when pipeline is being purged */
+void p4tc_ext_purge(struct idr *idr)
+{
+	struct p4tc_extern *p;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(idr, p, tmp, id) {
+		if (IS_ERR(p))
+			continue;
+		p4tc_ext_idr_purge(p);
+	}
+}
+
+static int p4tc_ext_idr_search(struct p4tc_extern_inst *inst,
+			       struct p4tc_extern **e, u32 key)
+{
+	struct idr *elems_idr = &inst->control_elems_idr;
+	struct p4tc_extern *p;
+
+	p = idr_find(elems_idr, key);
+	if (IS_ERR(p))
+		p = NULL;
+
+	if (p) {
+		*e = p;
+		return true;
+	}
+	return false;
+}
+
+static int __p4tc_ext_idr_search(struct p4tc_extern_inst *inst,
+				 struct p4tc_extern **e, u32 key)
+{
+	if (p4tc_ext_idr_search(inst, e, key)) {
+		refcount_inc(&((*e)->common.p4tc_ext_refcnt));
+		return true;
+	}
+
+	return false;
+}
+
+static int p4tc_ext_copy(struct p4tc_extern_inst *inst,
+			 u32 key, struct p4tc_extern **e,
+			 struct p4tc_extern *e_orig,
+			 const struct p4tc_extern_ops *ops,
+			 u32 flags)
+{
+	const u32 size = (ops && ops->elem_size) ? ops->elem_size : sizeof(**e);
+	struct p4tc_extern *p = kzalloc(size, GFP_KERNEL);
+
+	if (unlikely(!p))
+		return -ENOMEM;
+
+	spin_lock_init(&p->p4tc_ext_lock);
+	p->common.p4tc_ext_key = key;
+	p->common.p4tc_ext_flags = flags;
+	refcount_set(&p->common.p4tc_ext_refcnt,
+		     refcount_read(&e_orig->common.p4tc_ext_refcnt));
+
+	p->elems_idr = e_orig->elems_idr;
+	p->common.inst = inst;
+	p->common.ops = ops;
+	*e = p;
+	return 0;
+}
+
+static int p4tc_ext_idr_create(struct p4tc_extern_inst *inst,
+			       u32 key, struct p4tc_extern **e,
+			       const struct p4tc_extern_ops *ops,
+			       u32 flags)
+{
+	struct p4tc_extern *p = kzalloc(sizeof(*p), GFP_KERNEL);
+	u32 max_num_elems = inst->max_num_elems;
+
+	if (unlikely(!p))
+		return -ENOMEM;
+
+	if (atomic_read(&inst->curr_num_elems) == max_num_elems) {
+		kfree(p);
+		return -E2BIG;
+	}
+
+	p4tc_ext_inst_inc_num_elems(inst);
+
+	refcount_set(&p->common.p4tc_ext_refcnt, 1);
+
+	spin_lock_init(&p->p4tc_ext_lock);
+	p->common.p4tc_ext_key = key;
+	p->common.p4tc_ext_flags = flags;
+
+	p->elems_idr = &inst->control_elems_idr;
+	p->common.inst = inst;
+	p->common.ops = ops;
+	*e = p;
+	return 0;
+}
+
+/* Check if extern with specified key exists. If extern is found, increments
+ * its reference, and return 1. Otherwise return -ENOENT.
+ */
+static int p4tc_ext_idr_check_alloc(struct p4tc_extern_inst *inst,
+				    u32 key, struct p4tc_extern **e,
+				    struct netlink_ext_ack *extack)
+{
+	struct idr *elems_idr = &inst->control_elems_idr;
+	struct p4tc_extern *p;
+
+	p = idr_find(elems_idr, key);
+	if (p) {
+		refcount_inc(&p->common.p4tc_ext_refcnt);
+		*e = p;
+		return 1;
+	}
+
+	NL_SET_ERR_MSG_FMT(extack, "Unable to find element with key %u",
+			   key);
+	return -ENOENT;
+}
+
+struct p4tc_extern *
+p4tc_ext_elem_find(struct p4tc_extern_inst *inst,
+		   struct p4tc_ext_bpf_params *params)
+{
+	struct p4tc_extern *e;
+
+	e = idr_find(&inst->control_elems_idr, params->index);
+	if (!e)
+		return ERR_PTR(-ENOENT);
+
+	return e;
+}
+EXPORT_SYMBOL(p4tc_ext_elem_find);
+
+#define p4tc_ext_common_elem_find(common, params) \
+	((struct p4tc_extern_common *)p4tc_ext_elem_find(common, params))
+
+static struct p4tc_extern_common *
+__p4tc_ext_common_elem_get(struct net *net, struct p4tc_pipeline **pipeline,
+			   struct p4tc_ext_bpf_params *params)
+{
+	struct p4tc_extern_common *ext_common;
+	struct p4tc_extern_inst *inst;
+	int err;
+
+	inst = p4tc_ext_inst_get_byids(net, pipeline, params);
+	if (IS_ERR(inst)) {
+		err = PTR_ERR(inst);
+		goto put_pipe;
+	}
+
+	ext_common = p4tc_ext_common_elem_find(inst, params);
+	if (IS_ERR(ext_common)) {
+		err = PTR_ERR(ext_common);
+		goto put_pipe;
+	}
+
+	if (!refcount_inc_not_zero(&ext_common->p4tc_ext_refcnt)) {
+		err = -EBUSY;
+		goto put_pipe;
+	}
+
+	return ext_common;
+
+put_pipe:
+	p4tc_pipeline_put(*pipeline);
+	return ERR_PTR(err);
+}
+
+/* This function should be paired with p4tc_ext_common_elem_put */
+struct p4tc_extern_common *
+p4tc_ext_common_elem_get(struct sk_buff *skb, struct p4tc_pipeline **pipeline,
+			 struct p4tc_ext_bpf_params *params)
+{
+	struct net *net;
+
+	net = skb->dev ? dev_net(skb->dev) : sock_net(skb->sk);
+
+	return __p4tc_ext_common_elem_get(net, pipeline, params);
+}
+EXPORT_SYMBOL(p4tc_ext_common_elem_get);
+
+/* This function should be paired with p4tc_ext_common_elem_put */
+struct p4tc_extern_common *
+p4tc_xdp_ext_common_elem_get(struct xdp_buff *ctx,
+			     struct p4tc_pipeline **pipeline,
+			     struct p4tc_ext_bpf_params *params)
+{
+	struct net *net;
+
+	net = dev_net(ctx->rxq->dev);
+
+	return __p4tc_ext_common_elem_get(net, pipeline, params);
+}
+EXPORT_SYMBOL(p4tc_xdp_ext_common_elem_get);
+
+void p4tc_ext_common_elem_put(struct p4tc_pipeline *pipeline,
+			      struct p4tc_extern_common *common)
+{
+	refcount_dec(&common->p4tc_ext_refcnt);
+	p4tc_pipeline_put(pipeline);
+}
+EXPORT_SYMBOL(p4tc_ext_common_elem_put);
+
+static bool p4tc_ext_param_is_writable(struct p4tc_extern_param *param)
+{
+	return param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY;
+}
+
+int __bpf_p4tc_extern_md_write(struct net *net,
+			       struct p4tc_ext_bpf_params *params)
+{
+	struct p4tc_extern_param *param;
+	struct p4tc_pipeline *pipeline;
+	struct p4tc_extern_common *e;
+	struct p4tc_type *type;
+	u8 *params_data;
+	int err = 0;
+
+	if (!params)
+		return -EINVAL;
+
+	params_data = params->in_params;
+
+	e = __p4tc_ext_common_elem_get(net, &pipeline, params);
+	if (IS_ERR(e))
+		return PTR_ERR(e);
+
+	param = idr_find(&e->params->params_idr, params->param_id);
+	if (unlikely(!param)) {
+		err = -EINVAL;
+		goto put_pipe;
+	}
+
+	if (!p4tc_ext_param_is_writable(param)) {
+		err = -EINVAL;
+		goto put_pipe;
+	}
+
+	type = param->type;
+	if (unlikely(!type->ops->host_read)) {
+		err = -EINVAL;
+		goto put_pipe;
+	}
+
+	if (unlikely(!type->ops->host_write)) {
+		err = -EINVAL;
+		goto put_pipe;
+	}
+
+	write_lock_bh(&e->params->params_lock);
+	p4t_copy(param->mask_shift, type, param->value,
+		 param->mask_shift, type, params_data);
+	write_unlock_bh(&e->params->params_lock);
+
+put_pipe:
+	p4tc_ext_common_elem_put(pipeline, e);
+
+	return err;
+}
+
+int __bpf_p4tc_extern_md_read(struct net *net,
+			      struct p4tc_ext_bpf_res *res,
+			      struct p4tc_ext_bpf_params *params)
+{
+	const struct p4tc_type_ops *ops;
+	struct p4tc_extern_param *param;
+	struct p4tc_pipeline *pipeline;
+	struct p4tc_extern_common *e;
+	int err = 0;
+
+	if (!params || !res)
+		return -EINVAL;
+
+	e = __p4tc_ext_common_elem_get(net, &pipeline, params);
+	if (IS_ERR(e))
+		return PTR_ERR(e);
+
+	param = idr_find(&e->params->params_idr, params->param_id);
+	if (unlikely(!param)) {
+		err = -ENOENT;
+		goto refcount_dec;
+	}
+
+	ops = param->type->ops;
+	if (unlikely(!ops->host_read)) {
+		err = -ENOENT;
+		goto refcount_dec;
+	}
+
+	read_lock_bh(&e->params->params_lock);
+	ops->host_read(param->type, param->mask_shift, param->value,
+		       res->out_params);
+	read_unlock_bh(&e->params->params_lock);
+
+refcount_dec:
+	p4tc_ext_common_elem_put(pipeline, e);
+
+	return err;
+}
+
+static int p4tc_extern_destroy(struct p4tc_extern *externs[])
+{
+	struct p4tc_extern *e;
+	int ret = 0, i;
+
+	for (i = 0; i < P4TC_MSGBATCH_SIZE && externs[i]; i++) {
+		e = externs[i];
+		externs[i] = NULL;
+		free_p4tc_ext_rcu(&e->rcu);
+	}
+	return ret;
+}
+
+static int p4tc_extern_put(struct p4tc_extern *p)
+{
+	return __p4tc_extern_put(p);
+}
+
+/* Put all externs in this array, skip those NULL's. */
+static void p4tc_extern_put_many(struct p4tc_extern *externs[])
+{
+	int i;
+
+	for (i = 0; i < P4TC_MSGBATCH_SIZE; i++) {
+		struct p4tc_extern *e = externs[i];
+
+		if (!e)
+			continue;
+		p4tc_extern_put(e);
+	}
+}
+
+static int p4tc_extern_elem_dump(struct sk_buff *skb,
+				 struct p4tc_extern *externs[],
+				 int ref)
+{
+	struct p4tc_extern *e;
+	int err = -EINVAL, i;
+	struct nlattr *nest;
+
+	for (i = 0; i < P4TC_MSGBATCH_SIZE && externs[i]; i++) {
+		e = externs[i];
+		nest = nla_nest_start_noflag(skb, i + 1);
+		if (!nest)
+			goto nla_put_failure;
+		err = p4tc_ext_elem_dump_1(skb, &e->common);
+		if (err < 0)
+			goto errout;
+		nla_nest_end(skb, nest);
+	}
+
+	return 0;
+
+nla_put_failure:
+	err = -EINVAL;
+errout:
+	nla_nest_cancel(skb, nest);
+	return err;
+}
+
+static void generic_free_param_value(struct p4tc_extern_param *param)
+{
+	kfree(param->value);
+}
+
+static void *generic_parse_param_value(struct p4tc_extern_param *nparam,
+				       struct p4tc_type *type,
+				       struct nlattr *nla, bool value_required,
+				       struct netlink_ext_ack *extack)
+{
+	const u32 alloc_len = BITS_TO_BYTES(type->container_bitsz);
+	struct nlattr *tb_value[P4TC_EXT_VALUE_PARAMS_MAX + 1];
+	void *value;
+	int err;
+
+	if (!nla) {
+		if (value_required) {
+			NL_SET_ERR_MSG(extack, "Must specify param value");
+			return ERR_PTR(-EINVAL);
+		} else {
+			return NULL;
+		}
+	}
+
+	err = nla_parse_nested(tb_value, P4TC_EXT_VALUE_PARAMS_MAX,
+			       nla, p4tc_extern_params_value_policy,
+			       extack);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	value = nla_data(tb_value[P4TC_EXT_PARAMS_VALUE_RAW]);
+	if (type->ops->validate_p4t) {
+		err = type->ops->validate_p4t(type, value, 0, type->bitsz - 1,
+					      extack);
+		if (err < 0)
+			return ERR_PTR(err);
+	}
+
+	if (nla_len(tb_value[P4TC_EXT_PARAMS_VALUE_RAW]) != alloc_len)
+		return ERR_PTR(-EINVAL);
+
+	return value;
+}
+
+static int generic_init_param_value(struct net *net,
+				    struct p4tc_extern_param *nparam,
+				    struct nlattr **tb,
+				    u32 byte_sz, bool value_required,
+				    struct netlink_ext_ack *extack)
+{
+	const u32 alloc_len = BITS_TO_BYTES(nparam->type->container_bitsz);
+	struct p4tc_extern_param_ops *ops;
+	void *value;
+
+	if (p4tc_ext_param_ops_is_init(nparam->ops))
+		ops = nparam->ops;
+	else
+		ops = nparam->mod_ops;
+
+	value = generic_parse_param_value(nparam, nparam->type,
+					  tb[P4TC_EXT_PARAMS_VALUE],
+					  value_required, extack);
+	if (IS_ERR_OR_NULL(value))
+		return PTR_ERR(value);
+
+	if (ops && ops->init_value)
+		return ops->init_value(net, nparam, value, extack);
+
+	nparam->value = kzalloc(alloc_len, GFP_KERNEL);
+	if (!nparam->value)
+		return -ENOMEM;
+
+	memcpy(nparam->value, value, byte_sz);
+
+	return 0;
+}
+
+static const struct nla_policy p4tc_extern_policy[P4TC_EXT_MAX + 1] = {
+	[P4TC_EXT_INST_NAME] = {
+		.type = NLA_STRING,
+		.len = EXTERNINSTNAMSIZ
+	},
+	[P4TC_EXT_KIND]		= { .type = NLA_STRING },
+	[P4TC_EXT_PARAMS]	= { .type = NLA_NESTED },
+	[P4TC_EXT_KEY]		= { .type = NLA_NESTED },
+	[P4TC_EXT_FLAGS]	= { .type = NLA_BITFIELD32 },
+};
+
+static const struct nla_policy p4tc_extern_params_policy[P4TC_EXT_PARAMS_MAX + 1] = {
+	[P4TC_EXT_PARAMS_NAME] = { .type = NLA_STRING, .len = EXTPARAMNAMSIZ },
+	[P4TC_EXT_PARAMS_ID] = { .type = NLA_U32 },
+	[P4TC_EXT_PARAMS_VALUE] = { .type = NLA_NESTED },
+	[P4TC_EXT_PARAMS_TYPE] = { .type = NLA_U32 },
+	[P4TC_EXT_PARAMS_BITSZ] = { .type = NLA_U16 },
+	[P4TC_EXT_PARAMS_FLAGS] = { .type = NLA_U8 },
+};
+
+int p4tc_ext_param_value_init(struct net *net,
+			      struct p4tc_extern_param *param,
+			      struct nlattr **tb, u32 typeid,
+			      bool value_required,
+			      struct netlink_ext_ack *extack)
+{
+	u32 byte_sz = BITS_TO_BYTES(param->bitsz);
+
+	if (!param->ops) {
+		struct p4tc_extern_param_ops *ops;
+
+		ops = (struct p4tc_extern_param_ops *)&ext_param_ops[typeid];
+		param->ops = ops;
+	}
+
+	return generic_init_param_value(net, param, tb, byte_sz, value_required,
+					extack);
+}
+
+void p4tc_ext_param_value_free_tmpl(struct p4tc_extern_param *param)
+{
+	if (param->ops->free)
+		return param->ops->free(param);
+
+	return generic_free_param_value(param);
+}
+
+int p4tc_ext_param_value_dump_tmpl(struct sk_buff *skb,
+				   struct p4tc_extern_param *param)
+{
+	if (param->ops && param->ops->dump_value)
+		return param->ops->dump_value(skb, param->ops, param);
+
+	return generic_dump_ext_param_value(skb, param->type, param);
+}
+
+static struct p4tc_extern_param *
+p4tc_ext_create_param(struct net *net, struct p4tc_extern_params *params,
+		      struct idr *control_params_idr,
+		      struct nlattr **tb, size_t *attrs_size,
+		      bool init_param, struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_param *param, *nparam;
+	u32 param_id = 0;
+	int err = 0;
+
+	if (tb[P4TC_EXT_PARAMS_ID])
+		param_id = nla_get_u32(tb[P4TC_EXT_PARAMS_ID]);
+	*attrs_size += nla_total_size(sizeof(u32));
+
+	param = p4tc_ext_param_find_byanyattr(control_params_idr,
+					      tb[P4TC_EXT_PARAMS_NAME],
+					      param_id, extack);
+	if (IS_ERR(param))
+		return param;
+
+	if (tb[P4TC_EXT_PARAMS_TYPE]) {
+		u32 typeid = nla_get_u32(tb[P4TC_EXT_PARAMS_TYPE]);
+
+		if (param->type->typeid != typeid) {
+			NL_SET_ERR_MSG(extack,
+				       "Param type differs from template");
+			return ERR_PTR(-EINVAL);
+		}
+	} else {
+		NL_SET_ERR_MSG(extack, "Must specify param type");
+		return ERR_PTR(-EINVAL);
+	}
+	*attrs_size += nla_total_size(sizeof(u32));
+
+	nparam = kzalloc(sizeof(*nparam), GFP_KERNEL);
+	if (!nparam)
+		return ERR_PTR(-ENOMEM);
+
+	strscpy(nparam->name, param->name, EXTPARAMNAMSIZ);
+	nparam->type = param->type;
+	nparam->bitsz = param->bitsz;
+
+	if (init_param) {
+		err = p4tc_ext_param_value_init(net, nparam, tb,
+						param->type->typeid, true,
+						extack);
+	} else {
+		void *value;
+
+		value = generic_parse_param_value(nparam, nparam->type,
+						  tb[P4TC_EXT_PARAMS_VALUE],
+						  true, extack);
+		if (IS_ERR(value))
+			err = PTR_ERR(value);
+		else
+			nparam->value = value;
+	}
+
+	if (err < 0)
+		goto free;
+
+	*attrs_size += nla_total_size(BITS_TO_BYTES(param->type->container_bitsz));
+	nparam->id = param->id;
+
+	err = idr_alloc_u32(&params->params_idr, ERR_PTR(-EBUSY), &nparam->id,
+			    nparam->id, GFP_KERNEL);
+	if (err < 0)
+		goto free_val;
+
+	return nparam;
+
+free_val:
+	if (param->ops && param->ops->free)
+		param->ops->free(nparam);
+	else
+		generic_free_param_value(nparam);
+
+free:
+	kfree(nparam);
+
+	return ERR_PTR(err);
+}
+
+static struct p4tc_extern_param *
+p4tc_ext_init_param(struct net *net, struct idr *control_params_idr,
+		    struct p4tc_extern_params *params, struct nlattr *nla,
+		    size_t *attrs_size, bool init_value,
+		    struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_EXT_PARAMS_MAX + 1];
+	int err;
+
+	err = nla_parse_nested(tb, P4TC_EXT_PARAMS_MAX, nla,
+			       p4tc_extern_params_policy, extack);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	return p4tc_ext_create_param(net, params, control_params_idr, tb,
+				     attrs_size, init_value, extack);
+}
+
+static int p4tc_ext_get_key_param_value(struct nlattr *nla,
+					u32 *key, struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_EXT_VALUE_PARAMS_MAX + 1];
+	u32 *value;
+	int err;
+
+	if (!nla) {
+		NL_SET_ERR_MSG(extack, "Must specify key param value");
+		return -EINVAL;
+	}
+
+	err = nla_parse_nested(tb, P4TC_EXT_VALUE_PARAMS_MAX,
+			       nla, p4tc_extern_params_value_policy, extack);
+	if (err < 0)
+		return err;
+
+	if (!tb[P4TC_EXT_PARAMS_VALUE_RAW]) {
+		NL_SET_ERR_MSG(extack, "Must specify raw value attr");
+		return -EINVAL;
+	}
+
+	if (nla_len(tb[P4TC_EXT_PARAMS_VALUE_RAW]) > sizeof(*key)) {
+		NL_SET_ERR_MSG(extack,
+			       "Param value is bigger than 32 bits");
+		return -EINVAL;
+	}
+
+	value = nla_data(tb[P4TC_EXT_PARAMS_VALUE_RAW]);
+
+	*key = *value;
+
+	return 0;
+}
+
+static int p4tc_ext_get_nonscalar_key_param(struct idr *params_idr,
+					    struct nlattr *nla, u32 *key,
+					    struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_EXT_PARAMS_MAX + 1];
+	struct p4tc_extern_param *index_param;
+	char *param_name;
+	int err;
+
+	err = nla_parse_nested(tb, P4TC_EXT_PARAMS_MAX, nla,
+			       p4tc_extern_params_policy, extack);
+	if (err < 0)
+		return err;
+
+	if (!tb[P4TC_EXT_PARAMS_NAME]) {
+		NL_SET_ERR_MSG(extack, "Must specify key param name");
+		return -EINVAL;
+	}
+	param_name = nla_data(tb[P4TC_EXT_PARAMS_NAME]);
+
+	index_param = p4tc_ext_param_find_byanyattr(params_idr,
+						    tb[P4TC_EXT_PARAMS_NAME],
+						    0, extack);
+	if (IS_ERR(index_param)) {
+		NL_SET_ERR_MSG(extack, "Key param name not found");
+		return -EINVAL;
+	}
+
+	if (!(index_param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY)) {
+		NL_SET_ERR_MSG_FMT(extack, "%s is not the key param name",
+				   param_name);
+		return -EINVAL;
+	}
+
+	err = p4tc_ext_get_key_param_value(tb[P4TC_EXT_PARAMS_VALUE], key,
+					   extack);
+	if (err < 0)
+		return err;
+
+	return index_param->id;
+}
+
+static int p4tc_ext_get_key_param_scalar(struct p4tc_extern_inst *inst,
+					 struct nlattr *nla, u32 *key,
+					 struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_EXT_PARAMS_MAX + 1];
+	int err;
+
+	err = nla_parse_nested(tb, P4TC_EXT_PARAMS_MAX, nla,
+			       p4tc_extern_params_policy, extack);
+	if (err < 0)
+		return err;
+
+	return p4tc_ext_get_key_param_value(tb[P4TC_EXT_PARAMS_VALUE], key,
+					    extack);
+}
+
+struct p4tc_extern_params *p4tc_extern_params_init(void)
+{
+	struct p4tc_extern_params *params;
+
+	params = kzalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return NULL;
+
+	idr_init(&params->params_idr);
+	rwlock_init(&params->params_lock);
+
+	return params;
+}
+
+static int __p4tc_ext_init_params(struct net *net,
+				  struct idr *control_params_idr,
+				  struct p4tc_extern_params **params,
+				  struct nlattr *nla, size_t *attrs_size,
+				  bool init_values,
+				  struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_param *params_backup[P4TC_MSGBATCH_SIZE] = { NULL };
+	struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+	int err;
+	int i;
+
+	if (!*params) {
+		*params = p4tc_extern_params_init();
+		if (!*params)
+			return -ENOMEM;
+	}
+
+	err = nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL, extack);
+	if (err < 0) {
+		kfree(*params);
+		*params = NULL;
+		return err;
+	}
+
+	for (i = 1; i < P4TC_MSGBATCH_SIZE + 1 && tb[i]; i++) {
+		struct p4tc_extern_param *param;
+
+		param = p4tc_ext_init_param(net, control_params_idr, *params,
+					    tb[i], attrs_size, init_values,
+					    extack);
+		if (IS_ERR(param)) {
+			err = PTR_ERR(param);
+			goto params_del;
+		}
+		params_backup[i - 1] = param;
+		*attrs_size = nla_total_size(0);  /* params array element nested */
+	}
+
+	p4tc_ext_insert_many_params(&((*params)->params_idr), params_backup,
+				    i - 1);
+	return 0;
+
+params_del:
+	p4tc_ext_put_many_params(&((*params)->params_idr), params_backup,
+				 i - 1);
+	kfree(*params);
+	*params = NULL;
+	return err;
+}
+
+#define p4tc_ext_init_params(net, control_params_idr, params, nla, atrrs_size, extack) \
+	(__p4tc_ext_init_params(net, control_params_idr, params, \
+				nla, &(attrs_size), true, extack))
+
+#define p4tc_ext_parse_params(net, control_params_idr, params, nla, attrs_size, extack) \
+	(__p4tc_ext_init_params(net, control_params_idr, params, \
+				nla, &(attrs_size), false, extack))
+
+void p4tc_ext_elem_put_list(struct p4tc_extern_inst *inst,
+			    struct p4tc_extern_common *e)
+{
+	struct p4tc_extern_param *param;
+	unsigned long param_id, tmp;
+
+	idr_for_each_entry_ul(&e->params->params_idr, param, tmp, param_id) {
+		const struct p4tc_type *type = param->type;
+		const u32 type_bytesz = BITS_TO_BYTES(type->container_bitsz);
+
+		if (param->mod_ops)
+			param->mod_ops->default_value(param);
+		else
+			memset(param->value, 0, type_bytesz);
+	}
+
+	spin_lock(&inst->available_list_lock);
+	list_add_tail(&e->node, &inst->unused_elems);
+	refcount_dec(&e->p4tc_ext_refcnt);
+	spin_unlock(&inst->available_list_lock);
+}
+
+struct p4tc_extern_common *p4tc_ext_elem_get(struct p4tc_extern_inst *inst)
+{
+	struct p4tc_extern_common *e;
+
+	spin_lock(&inst->available_list_lock);
+	e = list_first_entry_or_null(&inst->unused_elems,
+				     struct p4tc_extern_common, node);
+	if (e) {
+		refcount_inc(&e->p4tc_ext_refcnt);
+		list_del_init(&e->node);
+	}
+
+	spin_unlock(&inst->available_list_lock);
+
+	return e;
+}
+
+static void p4tc_ext_idr_insert_many(struct p4tc_extern *externs[])
+{
+	int i;
+
+	for (i = 0; i < P4TC_MSGBATCH_SIZE; i++) {
+		struct p4tc_extern *e = externs[i];
+		struct p4tc_extern_inst *inst;
+		struct p4tc_extern *old_e;
+
+		if (!e)
+			continue;
+
+		inst = e->common.inst;
+		spin_lock(&inst->available_list_lock);
+		old_e = idr_replace(e->elems_idr, e, e->common.p4tc_ext_key);
+		if (inst->tbl_bindable)
+			list_del(&old_e->common.node);
+		call_rcu(&old_e->rcu, free_p4tc_ext_rcu);
+		if (inst->tbl_bindable)
+			list_add(&e->common.node, &inst->unused_elems);
+		spin_unlock(&inst->available_list_lock);
+	}
+}
+
+static const char *
+p4tc_ext_get_kind(struct nlattr *nla, struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_EXT_MAX + 1];
+	struct nlattr *kind;
+	int err;
+
+	err = nla_parse_nested(tb, P4TC_EXT_MAX, nla,
+			       p4tc_extern_policy, extack);
+	if (err < 0)
+		return ERR_PTR(err);
+	err = -EINVAL;
+	kind = tb[P4TC_EXT_KIND];
+	if (!kind) {
+		NL_SET_ERR_MSG(extack, "TC extern name must be specified");
+		return ERR_PTR(err);
+	}
+
+	return nla_data(kind);
+}
+
+static struct p4tc_extern *
+p4tc_ext_init(struct net *net, struct nlattr *nla,
+	      struct p4tc_extern_inst *inst,
+	      u32 key, u32 flags,
+	      struct netlink_ext_ack *extack)
+{
+	struct idr *control_params_idr = &inst->params->params_idr;
+	const struct p4tc_extern_ops *e_o = inst->ops;
+	struct p4tc_extern_params *params = NULL;
+	struct p4tc_extern *e_orig = NULL;
+	size_t attrs_size = 0;
+	struct p4tc_extern *e;
+	int err = 0;
+
+	if (!nla) {
+		NL_SET_ERR_MSG(extack, "Must specify extern params");
+		err =  -EINVAL;
+		goto out;
+	}
+
+	if (p4tc_ext_has_rctrl(e_o)) {
+		err = p4tc_ext_parse_params(net, control_params_idr, &params,
+					    nla, attrs_size, extack);
+		if (err < 0)
+			goto out;
+
+		err = e_o->rctrl(RTM_P4TC_UPDATE, inst,
+				 (struct p4tc_extern_common **)&e, params, &key,
+				 extack);
+		p4tc_ext_params_free(params, false);
+		if (err < 0)
+			goto out;
+
+		return e;
+	}
+
+	err = p4tc_ext_idr_check_alloc(inst, key, &e_orig, extack);
+	if (err < 0)
+		goto out;
+
+	err = p4tc_ext_copy(inst, key, &e, e_orig, e_o, flags);
+	if (err < 0)
+		goto out;
+
+	err = p4tc_ext_init_params(net, control_params_idr, &params,
+				   nla, &attrs_size, extack);
+	if (err < 0)
+		goto release_idr;
+	attrs_size += nla_total_size(0) + p4tc_extern_shared_attrs_size();
+	e->attrs_size = attrs_size;
+
+	e->common.params = params;
+
+	return e;
+
+release_idr:
+	p4tc_ext_idr_release(e);
+
+out:
+	return ERR_PTR(err);
+}
+
+static struct p4tc_extern_param *find_key_param(struct idr *params_idr)
+{
+	struct p4tc_extern_param *param;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(params_idr, param, tmp, id) {
+		if (param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY)
+			return param;
+	}
+
+	return NULL;
+}
+
+static struct p4tc_extern_param *
+p4tc_ext_init_defval_param(struct p4tc_extern_param *param,
+			   struct netlink_ext_ack *extack)
+{
+	const u32 bytesz = BITS_TO_BYTES(param->type->container_bitsz);
+	struct p4tc_extern_param_ops *val_ops;
+	struct p4tc_extern_param *nparam;
+	int err;
+
+	if (p4tc_ext_param_ops_is_init(param->ops))
+		val_ops = param->ops;
+	else
+		val_ops = param->mod_ops;
+
+	nparam = kzalloc(sizeof(*nparam), GFP_KERNEL);
+	if (!nparam) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	strscpy(nparam->name, param->name, EXTPARAMNAMSIZ);
+	nparam->type = param->type;
+	nparam->id = param->id;
+
+	if (val_ops) {
+		if (param->mod_ops && !val_ops->default_value) {
+			NL_SET_ERR_MSG_FMT(extack,
+					   "Param %s should have default_value op",
+					   param->name);
+			err = -EINVAL;
+			goto free_param;
+		}
+		err = val_ops->init_value(NULL, nparam, param->value,
+					  extack);
+		if (err < 0)
+			goto free_param;
+	} else {
+		nparam->value = kzalloc(bytesz, GFP_KERNEL);
+		if (!nparam->value) {
+			err = -ENOMEM;
+			goto free_param;
+		}
+
+		if (param->value)
+			memcpy(nparam->value, param->value, bytesz);
+	}
+	nparam->ops = param->ops;
+	nparam->mod_ops = param->mod_ops;
+
+	return nparam;
+
+free_param:
+	kfree(nparam);
+out:
+	return ERR_PTR(err);
+}
+
+struct p4tc_extern_params *
+p4tc_ext_params_copy(struct p4tc_extern_params *params_orig)
+{
+	struct p4tc_extern_param *nparam = NULL;
+	struct p4tc_extern_params *params_copy;
+	const struct p4tc_extern_param *param;
+	unsigned long tmp, id;
+	int err;
+
+	params_copy = p4tc_extern_params_init();
+	if (!params_copy) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	idr_for_each_entry_ul(&params_orig->params_idr, param, tmp, id) {
+		struct p4tc_type *param_type = param->type;
+		u32 alloc_len = BITS_TO_BYTES(param_type->container_bitsz);
+		struct p4tc_type_mask_shift *mask_shift = NULL;
+
+		nparam = kzalloc(sizeof(*nparam), GFP_KERNEL);
+		if (!nparam) {
+			err = -ENOMEM;
+			goto free_params;
+		}
+		nparam->ops = param->ops;
+		nparam->mod_ops = param->mod_ops;
+		nparam->type = param->type;
+
+		if (param->value) {
+			nparam->value = kzalloc(alloc_len, GFP_KERNEL);
+			if (!nparam->value) {
+				err = -ENOMEM;
+				goto free_param;
+			}
+			memcpy(nparam->value, param->value, alloc_len);
+		}
+
+		if (param_type->ops && param_type->ops->create_bitops) {
+			const u32 bitsz = param->bitsz;
+
+			mask_shift = param_type->ops->create_bitops(bitsz, 0,
+								    bitsz - 1,
+								    NULL);
+			if (IS_ERR(mask_shift)) {
+				err = PTR_ERR(mask_shift);
+				goto free_param_value;
+			}
+			nparam->mask_shift = mask_shift;
+		}
+
+		nparam->id = param->id;
+		err = idr_alloc_u32(&params_copy->params_idr, nparam,
+				    &nparam->id, nparam->id, GFP_KERNEL);
+		if (err < 0)
+			goto free_mask_shift;
+
+		nparam->index = param->index;
+		nparam->bitsz = param->bitsz;
+		nparam->flags = param->flags;
+		strscpy(nparam->name, param->name, EXTPARAMNAMSIZ);
+		params_copy->num_params++;
+	}
+
+	return params_copy;
+
+free_mask_shift:
+	if (nparam->mask_shift)
+		p4t_release(nparam->mask_shift);
+free_param_value:
+	kfree(nparam->value);
+free_param:
+	kfree(nparam);
+free_params:
+	p4tc_ext_params_free(params_copy, true);
+err_out:
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p4tc_ext_params_copy);
+
+int p4tc_ext_init_defval_params(struct p4tc_extern_inst *inst,
+				struct p4tc_extern_common *common,
+				struct idr *control_params_idr,
+				struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_params *params = NULL;
+	struct p4tc_extern_param *param;
+	unsigned long tmp, id;
+	int err;
+
+	params = p4tc_extern_params_init();
+	if (!params)
+		return -ENOMEM;
+
+	idr_for_each_entry_ul(control_params_idr, param, tmp, id) {
+		struct p4tc_extern_param *nparam;
+
+		if (param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY)
+			/* Skip key param */
+			continue;
+
+		nparam = p4tc_ext_init_defval_param(param, extack);
+		if (IS_ERR(nparam)) {
+			err = PTR_ERR(nparam);
+			goto free_params;
+		}
+
+		err = idr_alloc_u32(&params->params_idr, nparam, &nparam->id,
+				    nparam->id, GFP_KERNEL);
+		if (err < 0) {
+			kfree(nparam);
+			goto free_params;
+		}
+		params->num_params++;
+	}
+
+	common->params = params;
+	common->inst = inst;
+	common->ops = inst->ops;
+	refcount_set(&common->p4tc_ext_refcnt, 1);
+	if (inst->tbl_bindable)
+		list_add(&common->node, &inst->unused_elems);
+
+	return 0;
+
+free_params:
+	p4tc_ext_params_free(params, true);
+	return err;
+}
+EXPORT_SYMBOL_GPL(p4tc_ext_init_defval_params);
+
+static int p4tc_ext_init_defval(struct p4tc_extern **e,
+				struct p4tc_extern_inst *inst,
+				u32 key, struct netlink_ext_ack *extack)
+{
+	const struct p4tc_extern_ops *e_o = inst->ops;
+	int err;
+
+	if (!inst->is_scalar) {
+		struct p4tc_extern_param *key_param;
+
+		key_param = find_key_param(&inst->params->params_idr);
+		if (!key_param) {
+			NL_SET_ERR_MSG(extack, "Unable to find key param");
+			return -ENOENT;
+		}
+	}
+
+	err = p4tc_ext_idr_create(inst, key, e, e_o, 0);
+	if (err < 0)
+		return err;
+
+	/* We already store it in the IDR, because we arrive here with the
+	 * rtnl_lock, so this code is never accessed concurrently.
+	 */
+	err = idr_alloc_u32(&inst->control_elems_idr, *e, &key,
+			    key, GFP_KERNEL);
+	if (err < 0) {
+		__p4tc_ext_idr_purge(*e);
+		return err;
+	}
+
+	err = p4tc_ext_init_defval_params(inst, &((*e)->common),
+					  &inst->params->params_idr, extack);
+	if (err < 0)
+		goto release_idr;
+
+	return 0;
+
+release_idr:
+	p4tc_ext_idr_release_dec_num_elems(*e);
+
+	return err;
+}
+
+static void p4tc_extern_inst_destroy_elems(struct idr *insts_idr)
+{
+	struct p4tc_extern_inst *inst;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(insts_idr, inst, tmp, id) {
+		unsigned long tmp2, elem_id;
+		struct p4tc_extern *e;
+
+		idr_for_each_entry_ul(&inst->control_elems_idr, e,
+				      tmp2, elem_id) {
+			p4tc_ext_idr_purge(e);
+		}
+	}
+}
+
+static void p4tc_user_pipe_ext_destroy_elems(struct idr *user_ext_idr)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(user_ext_idr, pipe_ext, tmp, id) {
+		if (p4tc_ext_has_construct(pipe_ext->tmpl_ext->ops))
+			continue;
+
+		p4tc_extern_inst_destroy_elems(&pipe_ext->e_inst_idr);
+	}
+}
+
+int p4tc_extern_inst_init_elems(struct p4tc_extern_inst *inst, u32 num_elems)
+{
+	int err = 0;
+	int i;
+
+	for (i = 0; i < num_elems; i++) {
+		struct p4tc_extern *e = NULL;
+
+		err = p4tc_ext_init_defval(&e, inst, i + 1, NULL);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(p4tc_extern_inst_init_elems);
+
+static int
+__p4tc_extern_insts_init_elems(struct idr *insts_idr)
+{
+	struct p4tc_extern_inst *inst;
+	unsigned long tmp, id;
+	int err = 0;
+
+	idr_for_each_entry_ul(insts_idr, inst, tmp, id) {
+		u32 max_num_elems = inst->max_num_elems;
+
+		err = p4tc_extern_inst_init_elems(inst, max_num_elems);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Called before sealing the pipeline */
+int p4tc_extern_insts_init_elems(struct idr *user_ext_idr)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	unsigned long tmp, id;
+	int err;
+
+	idr_for_each_entry_ul(user_ext_idr, pipe_ext, tmp, id) {
+		/* We assume the module construct will create the initial elems
+		 * by itself.
+		 * We only initialise when sealing if we don't have construct.
+		 */
+		if (p4tc_ext_has_construct(pipe_ext->tmpl_ext->ops))
+			continue;
+
+		err = __p4tc_extern_insts_init_elems(&pipe_ext->e_inst_idr);
+		if (err < 0)
+			goto destroy_ext_inst_elems;
+	}
+
+	return 0;
+
+destroy_ext_inst_elems:
+	p4tc_user_pipe_ext_destroy_elems(user_ext_idr);
+	return err;
+}
+
+static struct p4tc_extern *
+p4tc_extern_init_1(struct p4tc_pipeline *pipeline,
+		   struct p4tc_extern_inst *inst,
+		   struct nlattr *nla, u32 key, u32 flags,
+		   struct netlink_ext_ack *extack)
+{
+	return p4tc_ext_init(pipeline->net, nla, inst, key,
+			     flags, extack);
+}
+
+static int tce_get_fill(struct sk_buff *skb, struct p4tc_extern *externs[],
+			u32 portid, u32 seq, u16 flags, u32 pipeid, int cmd,
+			int ref, struct netlink_ext_ack *extack)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct nlmsghdr *nlh;
+	struct nlattr *nest;
+	struct p4tcmsg *t;
+
+	nlh = nlmsg_put(skb, portid, seq, cmd, sizeof(*t), flags);
+	if (!nlh)
+		goto out_nlmsg_trim;
+	t = nlmsg_data(nlh);
+	t->pipeid = pipeid;
+	t->obj = P4TC_OBJ_RUNTIME_EXTERN;
+
+	nest = nla_nest_start(skb, P4TC_ROOT);
+	if (p4tc_extern_elem_dump(skb, externs, ref) < 0)
+		goto out_nlmsg_trim;
+
+	nla_nest_end(skb, nest);
+
+	nlh->nlmsg_len = (unsigned char *)nlmsg_get_pos(skb) - b;
+
+	return skb->len;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static int
+p4tc_extern_get_respond(struct net *net, u32 portid, struct nlmsghdr *n,
+			struct p4tc_extern *externs[], u32 pipeid,
+			size_t attr_size, struct netlink_ext_ack *extack)
+{
+	struct sk_buff *skb;
+
+	skb = alloc_skb(attr_size <= NLMSG_GOODSIZE ? NLMSG_GOODSIZE : attr_size,
+			GFP_KERNEL);
+	if (!skb)
+		return -ENOBUFS;
+	if (tce_get_fill(skb, externs, portid, n->nlmsg_seq, 0, pipeid,
+			 RTM_P4TC_GET, 1, NULL) <= 0) {
+		NL_SET_ERR_MSG(extack,
+			       "Failed to fill netlink attributes while adding TC extern");
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	return rtnl_unicast(skb, net, portid);
+}
+
+static struct p4tc_extern *
+p4tc_extern_get_1(struct p4tc_extern_inst *inst,
+		  struct nlattr *nla, const char *kind, struct nlmsghdr *n,
+		  u32 key, u32 portid, struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern *e;
+	int err;
+
+	if (p4tc_ext_inst_has_rctrl(inst)) {
+		err = inst->ops->rctrl(n->nlmsg_type, inst,
+				       (struct p4tc_extern_common **)&e,
+				       NULL, &key, extack);
+		if (err < 0)
+			return ERR_PTR(err);
+
+		return e;
+	}
+
+	if (__p4tc_ext_idr_search(inst, &e, key) == 0) {
+		err = -ENOENT;
+		NL_SET_ERR_MSG(extack, "TC extern with specified key not found");
+		goto err_out;
+	}
+
+	return e;
+
+err_out:
+	return ERR_PTR(err);
+}
+
+static int
+p4tc_extern_add_notify(struct net *net, struct nlmsghdr *n,
+		       struct p4tc_extern *externs[], u32 portid, u32 pipeid,
+		       size_t attr_size, struct netlink_ext_ack *extack)
+{
+	struct sk_buff *skb;
+
+	skb = alloc_skb(attr_size <= NLMSG_GOODSIZE ? NLMSG_GOODSIZE : attr_size,
+			GFP_KERNEL);
+	if (!skb)
+		return -ENOBUFS;
+
+	if (tce_get_fill(skb, externs, portid, n->nlmsg_seq, n->nlmsg_flags,
+			 pipeid, n->nlmsg_type, 0, extack) <= 0) {
+		NL_SET_ERR_MSG(extack,
+			       "Failed to fill netlink attributes while adding TC extern");
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+
+	return rtnetlink_send(skb, net, portid, RTNLGRP_TC,
+			      n->nlmsg_flags & NLM_F_ECHO);
+}
+
+static int p4tc_ext_get_key_param(struct p4tc_extern_inst *inst,
+				  struct nlattr *nla,
+				  struct idr *params_idr, u32 *key,
+				  struct netlink_ext_ack *extack)
+{
+	int err = 0;
+
+	if (inst->is_scalar) {
+		if (nla) {
+			err = p4tc_ext_get_key_param_scalar(inst, nla, key,
+							    extack);
+			if (err < 0)
+				return err;
+
+			if (*key != 1) {
+				NL_SET_ERR_MSG(extack,
+					       "Key of scalar must be 1");
+				return -EINVAL;
+			}
+		} else {
+			*key = 1;
+		}
+	} else {
+		if (nla) {
+			err = p4tc_ext_get_nonscalar_key_param(params_idr, nla,
+							       key, extack);
+			if (err < 0)
+				return -EINVAL;
+		}
+
+		if (!key) {
+			NL_SET_ERR_MSG(extack, "Must specify extern key");
+			return -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+static struct p4tc_extern *
+__p4tc_ctl_extern_1(struct p4tc_pipeline *pipeline,
+		    struct nlattr *nla, struct nlmsghdr *n,
+		    u32 portid, u32 flags, bool rctrl_allowed,
+		    struct netlink_ext_ack *extack)
+{
+	const char *kind = p4tc_ext_get_kind(nla, extack);
+	struct nlattr *tb[P4TC_EXT_MAX + 1];
+	struct p4tc_extern_inst *inst;
+	struct nlattr *params_attr;
+	struct p4tc_extern *e;
+	char *instname;
+	u32 key;
+	int err;
+
+	err = nla_parse_nested(tb, P4TC_EXT_MAX, nla,
+			       p4tc_extern_policy, extack);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	if (IS_ERR(kind))
+		return (struct p4tc_extern *)kind;
+
+	if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_EXT_INST_NAME)) {
+		NL_SET_ERR_MSG(extack,
+			       "TC extern inst name must be specified");
+		return ERR_PTR(-EINVAL);
+	}
+	instname = nla_data(tb[P4TC_EXT_INST_NAME]);
+
+	err = -EINVAL;
+	inst = p4tc_ext_inst_find_bynames(pipeline->net, pipeline, kind,
+					  instname, extack);
+	if (IS_ERR(inst))
+		return (struct p4tc_extern *)inst;
+
+	if (!rctrl_allowed && p4tc_ext_has_rctrl(inst->ops)) {
+		NL_SET_ERR_MSG(extack,
+			       "Runtime message may only have one extern with rctrl op");
+		return ERR_PTR(-EINVAL);
+	}
+
+	err = p4tc_ext_get_key_param(inst, tb[P4TC_EXT_KEY],
+				     &inst->params->params_idr, &key,
+				     extack);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	params_attr = tb[P4TC_EXT_PARAMS];
+
+	switch (n->nlmsg_type) {
+	case RTM_P4TC_CREATE:
+		NL_SET_ERR_MSG(extack,
+			       "Create command is not supported");
+		return ERR_PTR(-EOPNOTSUPP);
+	case RTM_P4TC_UPDATE: {
+		struct nla_bitfield32 userflags = { 0, 0 };
+
+		if (tb[P4TC_EXT_FLAGS])
+			userflags = nla_get_bitfield32(tb[P4TC_EXT_FLAGS]);
+
+		flags = userflags.value | flags;
+		e = p4tc_extern_init_1(pipeline, inst, params_attr, key,
+				       flags, extack);
+		break;
+	}
+	case RTM_P4TC_DEL:
+		NL_SET_ERR_MSG(extack,
+			       "Delete command is not supported");
+		return ERR_PTR(-EOPNOTSUPP);
+	case RTM_P4TC_GET: {
+		e = p4tc_extern_get_1(inst, params_attr, kind, n, key, portid,
+				      extack);
+		break;
+	}
+	default:
+		NL_SET_ERR_MSG_FMT(extack, "Unknown extern command #%u",
+				   n->nlmsg_type);
+		return ERR_PTR(-EOPNOTSUPP);
+	}
+
+	return e;
+}
+
+static int __p4tc_ctl_extern(struct p4tc_pipeline *pipeline,
+			     struct nlattr *nla, struct nlmsghdr *n,
+			     u32 portid, u32 flags,
+			     struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern *externs[P4TC_MSGBATCH_SIZE] = {};
+	struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+	bool processed_rctrl_extern = false;
+	struct p4tc_extern *ext;
+	size_t attr_size = 0;
+	bool has_one_element;
+	int i, ret;
+
+	ret = nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL,
+			       extack);
+	if (ret < 0)
+		return ret;
+
+	/* We only allow 1 batched element in case the extern has an rctrl
+	 * callback.
+	 */
+	has_one_element = !tb[2];
+	ext = __p4tc_ctl_extern_1(pipeline, tb[1], n, portid,
+				  flags, has_one_element, extack);
+	if (IS_ERR(ext))
+		return PTR_ERR(ext);
+
+	externs[0] = ext;
+	if (p4tc_ext_has_rctrl(ext->common.ops)) {
+		processed_rctrl_extern = true;
+		goto notify;
+	} else {
+		attr_size += ext->attrs_size;
+	}
+
+	for (i = 2; i <= P4TC_MSGBATCH_SIZE && tb[i]; i++) {
+		ext = __p4tc_ctl_extern_1(pipeline, tb[i], n, portid,
+					  flags, false, extack);
+		if (IS_ERR(ext)) {
+			ret = PTR_ERR(ext);
+			goto err;
+		}
+
+		attr_size += ext->attrs_size;
+		/* Only add to externs array, extern modules that don't
+		 * implement rctrl callback.
+		 */
+		externs[i - 1] = ext;
+	}
+
+notify:
+	attr_size = p4tc_extern_full_attrs_size(attr_size);
+
+	if (n->nlmsg_type == RTM_P4TC_UPDATE) {
+		int listeners = rtnl_has_listeners(pipeline->net, RTNLGRP_TC);
+		int echo = n->nlmsg_flags & NLM_F_ECHO;
+
+		if (!processed_rctrl_extern)
+			p4tc_ext_idr_insert_many(externs);
+
+		if (echo || listeners)
+			p4tc_extern_add_notify(pipeline->net, n, externs,
+					       portid, pipeline->common.p_id,
+					       attr_size, extack);
+	} else if (n->nlmsg_type == RTM_P4TC_GET) {
+		p4tc_extern_get_respond(pipeline->net, portid, n, externs,
+					pipeline->common.p_id, attr_size,
+					extack);
+	}
+
+	return 0;
+
+err:
+	if (n->nlmsg_type == RTM_P4TC_UPDATE)
+		p4tc_extern_destroy(externs);
+	else if (n->nlmsg_type == RTM_P4TC_GET)
+		p4tc_extern_put_many(externs);
+
+	return ret;
+}
+
+static int parse_dump_ext_attrs(struct nlattr *nla,
+				struct nlattr **tb2)
+{
+	struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+
+	if (nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL,
+			     NULL) < 0)
+		return -EINVAL;
+
+	if (!tb[1])
+		return -EINVAL;
+	if (nla_parse_nested(tb2, P4TC_EXT_MAX, tb[1],
+			     p4tc_extern_policy, NULL) < 0)
+		return -EINVAL;
+
+	if (!tb2[P4TC_EXT_KIND])
+		return -EINVAL;
+
+	if (!tb2[P4TC_EXT_INST_NAME])
+		return -EINVAL;
+
+	return 0;
+}
+
+int p4tc_ctl_extern_dump(struct sk_buff *skb, struct netlink_callback *cb,
+			 struct nlattr **tb, const char *pname)
+{
+	struct netlink_ext_ack *extack = cb->extack;
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct nlattr *tb2[P4TC_EXT_MAX + 1];
+	struct net *net = sock_net(skb->sk);
+	struct nlattr *count_attr = NULL;
+	struct p4tc_pipeline *pipeline;
+	struct p4tc_extern_inst *inst;
+	char *kind_str, *instname;
+	struct nla_bitfield32 bf;
+	struct nlmsghdr *nlh;
+	struct nlattr *nest;
+	u32 ext_count = 0;
+	struct p4tcmsg *t;
+	int ret = 0;
+
+	pipeline = p4tc_pipeline_find_byany(net, pname, 0, extack);
+	if (IS_ERR(pipeline))
+		return PTR_ERR(pipeline);
+
+	if (!pipeline_sealed(pipeline)) {
+		NL_SET_ERR_MSG(extack,
+			       "Pipeline must be sealed for extern runtime ops");
+		return -EINVAL;
+	}
+
+	ret = parse_dump_ext_attrs(tb[P4TC_ROOT], tb2);
+	if (ret < 0)
+		return ret;
+
+	kind_str = nla_data(tb2[P4TC_EXT_KIND]);
+	if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_EXT_KIND)) {
+		NL_SET_ERR_MSG(extack,
+			       "TC extern kind name must be specified");
+		return -EINVAL;
+	}
+
+	instname = nla_data(tb2[P4TC_EXT_INST_NAME]);
+	if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_EXT_INST_NAME)) {
+		NL_SET_ERR_MSG(extack,
+			       "TC extern inst name must be specified");
+		return -EINVAL;
+	}
+
+	inst = p4tc_ext_inst_find_bynames(pipeline->net, pipeline, kind_str,
+					  instname, extack);
+	if (IS_ERR(inst))
+		return PTR_ERR(inst);
+
+	cb->args[2] = 0;
+	if (tb[P4TC_ROOT_FLAGS]) {
+		bf = nla_get_bitfield32(tb[P4TC_ROOT_FLAGS]);
+		cb->args[2] = bf.value;
+	}
+
+	nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
+			cb->nlh->nlmsg_type, sizeof(*t), 0);
+	if (!nlh)
+		goto err_out;
+
+	t = nlmsg_data(nlh);
+	t->pipeid = pipeline->common.p_id;
+	t->obj = P4TC_OBJ_RUNTIME_EXTERN;
+	count_attr = nla_reserve(skb, P4TC_ROOT_COUNT, sizeof(u32));
+	if (!count_attr)
+		goto err_out;
+
+	nest = nla_nest_start_noflag(skb, P4TC_ROOT);
+	if (!nest)
+		goto err_out;
+
+	ret = p4tc_ext_dump_walker(inst, skb, cb);
+	if (ret < 0)
+		goto err_out;
+
+	if (ret > 0) {
+		nla_nest_end(skb, nest);
+		ret = skb->len;
+		ext_count = cb->args[1];
+		memcpy(nla_data(count_attr), &ext_count, sizeof(u32));
+		cb->args[1] = 0;
+	} else {
+		nlmsg_trim(skb, b);
+	}
+
+	nlh->nlmsg_len = (unsigned char *)nlmsg_get_pos(skb) - b;
+	if (NETLINK_CB(cb->skb).portid && ret)
+		nlh->nlmsg_flags |= NLM_F_MULTI;
+	return skb->len;
+
+err_out:
+	nlmsg_trim(skb, b);
+	return skb->len;
+}
+
+int p4tc_ctl_extern(struct sk_buff *skb, struct nlmsghdr *n, int cmd,
+		    struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_ROOT_MAX + 1];
+	struct net *net = sock_net(skb->sk);
+	u32 portid = NETLINK_CB(skb).portid;
+	struct p4tc_pipeline *pipeline;
+	struct nlattr *root;
+	char *pname = NULL;
+	u32 flags = 0;
+	int ret = 0;
+
+	if (cmd != RTM_P4TC_GET && !netlink_capable(skb, CAP_NET_ADMIN)) {
+		NL_SET_ERR_MSG(extack, "Need CAP_NET_ADMIN to do CRU ops");
+		return -EPERM;
+	}
+
+	ret = nlmsg_parse(n, sizeof(struct p4tcmsg), tb, P4TC_ROOT_MAX,
+			  p4tc_root_policy, extack);
+	if (ret < 0)
+		return ret;
+
+	if (tb[P4TC_ROOT_PNAME])
+		pname = nla_data(tb[P4TC_ROOT_PNAME]);
+
+	if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_ROOT)) {
+		NL_SET_ERR_MSG(extack, "Netlink P4TC extern attributes missing");
+		return -EINVAL;
+	}
+
+	root = tb[P4TC_ROOT];
+
+	pipeline = p4tc_pipeline_find_byany(net, pname, 0, extack);
+	if (IS_ERR(pipeline))
+		return PTR_ERR(pipeline);
+
+	if (!pipeline_sealed(pipeline)) {
+		NL_SET_ERR_MSG(extack,
+			       "Pipeline must be sealed for extern runtime ops");
+		return -EPERM;
+	}
+
+	return __p4tc_ctl_extern(pipeline, root, n, portid, flags, extack);
+}
diff --git a/net/sched/p4tc/p4tc_pipeline.c b/net/sched/p4tc/p4tc_pipeline.c
index 6c00747ac..c61115f78 100644
--- a/net/sched/p4tc/p4tc_pipeline.c
+++ b/net/sched/p4tc/p4tc_pipeline.c
@@ -27,6 +27,7 @@ 
 #include <net/netlink.h>
 #include <net/flow_offload.h>
 #include <net/p4tc_types.h>
+#include <net/p4tc_ext_api.h>
 
 static unsigned int pipeline_net_id;
 static struct p4tc_pipeline *root_pipeline;
@@ -99,6 +100,7 @@  static void __net_exit pipeline_exit_net(struct net *net)
 		__p4tc_pipeline_put(pipeline, &pipeline->common, NULL);
 	}
 	idr_destroy(&pipe_net->pipeline_idr);
+
 	rtnl_unlock();
 }
 
@@ -119,6 +121,7 @@  static void p4tc_pipeline_destroy(struct p4tc_pipeline *pipeline)
 {
 	idr_destroy(&pipeline->p_act_idr);
 	idr_destroy(&pipeline->p_tbl_idr);
+	idr_destroy(&pipeline->user_ext_idr);
 
 	kfree(pipeline);
 }
@@ -141,7 +144,8 @@  static void p4tc_pipeline_teardown(struct p4tc_pipeline *pipeline,
 	struct net *net = pipeline->net;
 	struct p4tc_pipeline_net *pipe_net = net_generic(net, pipeline_net_id);
 	struct net *pipeline_net = maybe_get_net(net);
-	unsigned long iter_act_id, tmp;
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	unsigned long iter_act_id, ext_id, tmp;
 	struct p4tc_table *table;
 	struct p4tc_act *act;
 	unsigned long tbl_id;
@@ -152,6 +156,20 @@  static void p4tc_pipeline_teardown(struct p4tc_pipeline *pipeline,
 	idr_for_each_entry_ul(&pipeline->p_act_idr, act, tmp, iter_act_id)
 		act->common.ops->put(pipeline, &act->common, extack);
 
+	idr_for_each_entry_ul(&pipeline->user_ext_idr, pipe_ext, tmp, ext_id) {
+		unsigned long tmp_in, inst_id;
+		struct p4tc_extern_inst *inst;
+
+		idr_for_each_entry_ul(&pipe_ext->e_inst_idr, inst, tmp_in,
+				      inst_id) {
+			struct p4tc_template_common *common = &inst->common;
+
+			common->ops->put(pipeline, common, extack);
+		}
+
+		pipe_ext->free(pipe_ext, &pipeline->user_ext_idr);
+	}
+
 	/* If we are on netns cleanup we can't touch the pipeline_idr.
 	 * On pre_exit we will destroy the idr but never call into teardown
 	 * if filters are active which makes pipeline pointers dangle until
@@ -210,9 +228,18 @@  static int pipeline_try_set_state_ready(struct p4tc_pipeline *pipeline,
 	if (ret < 0)
 		return ret;
 
+	ret = p4tc_extern_insts_init_elems(&pipeline->user_ext_idr);
+	if (ret < 0)
+		goto unset_table_state_ready;
+
 	pipeline->p_state = P4TC_STATE_READY;
 
 	return true;
+
+unset_table_state_ready:
+	p4tc_table_put_mask_array(pipeline);
+
+	return ret;
 }
 
 struct p4tc_pipeline *p4tc_pipeline_find_byid(struct net *net, const u32 pipeid)
@@ -308,6 +335,9 @@  static struct p4tc_pipeline *p4tc_pipeline_create(struct net *net,
 
 	idr_init(&pipeline->p_tbl_idr);
 	pipeline->curr_tables = 0;
+	idr_init(&pipeline->p_tbl_idr);
+
+	idr_init(&pipeline->user_ext_idr);
 
 	pipeline->num_created_acts = 0;
 
@@ -645,6 +675,8 @@  static void __p4tc_pipeline_init(void)
 
 	strscpy(root_pipeline->common.name, "kernel", PIPELINENAMSIZ);
 
+	idr_init(&root_pipeline->p_ext_idr);
+
 	root_pipeline->common.ops =
 		(struct p4tc_template_ops *)&p4tc_pipeline_ops;
 
diff --git a/net/sched/p4tc/p4tc_runtime_api.c b/net/sched/p4tc/p4tc_runtime_api.c
index bcb280909..f085d1b2a 100644
--- a/net/sched/p4tc/p4tc_runtime_api.c
+++ b/net/sched/p4tc/p4tc_runtime_api.c
@@ -27,16 +27,17 @@ 
 #include <net/p4tc.h>
 #include <net/netlink.h>
 #include <net/flow_offload.h>
+#include <net/p4tc_ext_api.h>
 
 static int tc_ctl_p4_root(struct sk_buff *skb, struct nlmsghdr *n, int cmd,
 			  struct netlink_ext_ack *extack)
 {
 	struct p4tcmsg *t = (struct p4tcmsg *)nlmsg_data(n);
+	int ret;
 
 	switch (t->obj) {
 	case P4TC_OBJ_RUNTIME_TABLE: {
 		struct net *net = sock_net(skb->sk);
-		int ret;
 
 		net = maybe_get_net(net);
 		if (!net) {
@@ -50,6 +51,11 @@  static int tc_ctl_p4_root(struct sk_buff *skb, struct nlmsghdr *n, int cmd,
 
 		return ret;
 	}
+	case P4TC_OBJ_RUNTIME_EXTERN:
+		rtnl_lock();
+		ret = p4tc_ctl_extern(skb, n, cmd, extack);
+		rtnl_unlock();
+		return ret;
 	default:
 		NL_SET_ERR_MSG(extack, "Unknown P4 runtime object type");
 		return -EOPNOTSUPP;
@@ -120,6 +126,8 @@  static int tc_ctl_p4_dump(struct sk_buff *skb, struct netlink_callback *cb)
 	case P4TC_OBJ_RUNTIME_TABLE:
 		return p4tc_tbl_entry_dumpit(sock_net(skb->sk), skb, cb,
 					     tb[P4TC_ROOT], p_name);
+	case P4TC_OBJ_RUNTIME_EXTERN:
+		return p4tc_ctl_extern_dump(skb, cb, tb, p_name);
 	default:
 		NL_SET_ERR_MSG_FMT(cb->extack,
 				   "Unknown p4 runtime object type %u\n",
diff --git a/net/sched/p4tc/p4tc_table.c b/net/sched/p4tc/p4tc_table.c
index 7d79b01e5..534b972b9 100644
--- a/net/sched/p4tc/p4tc_table.c
+++ b/net/sched/p4tc/p4tc_table.c
@@ -114,6 +114,10 @@  static const struct nla_policy p4tc_table_policy[P4TC_TABLE_MAX + 1] = {
 	[P4TC_TABLE_DEFAULT_MISS] = { .type = NLA_NESTED },
 	[P4TC_TABLE_ACTS_LIST] = { .type = NLA_NESTED },
 	[P4TC_TABLE_CONST_ENTRY] = { .type = NLA_NESTED },
+	[P4TC_TABLE_COUNTER] = {
+		.type = NLA_STRING,
+		.len = EXTERNINSTNAMSIZ * 2 + 1
+	},
 };
 
 static int _p4tc_table_fill_nlmsg(struct sk_buff *skb, struct p4tc_table *table)
@@ -146,6 +150,12 @@  static int _p4tc_table_fill_nlmsg(struct sk_buff *skb, struct p4tc_table *table)
 	parm.tbl_aging = table->tbl_aging;
 	parm.tbl_num_entries = atomic_read(&table->tbl_nelems);
 
+	if (table->tbl_counter) {
+		if (nla_put_string(skb, P4TC_TABLE_COUNTER,
+				   table->tbl_counter->common.name) < 0)
+			goto out_nlmsg_trim;
+	}
+
 	tbl_perm = rcu_dereference_rtnl(table->tbl_permissions);
 	parm.tbl_permissions = tbl_perm->permissions;
 
@@ -914,7 +924,9 @@  static struct p4tc_table *p4tc_table_create(struct net *net, struct nlattr **tb,
 {
 	struct rhashtable_params table_hlt_params = entry_hlt_params;
 	struct p4tc_table_default_act_params def_params = {0};
+	struct p4tc_user_pipeline_extern *pipe_ext = NULL;
 	struct p4tc_table_perm *tbl_init_perms = NULL;
+	struct p4tc_extern_inst *inst = NULL;
 	struct p4tc_table_parm *parm;
 	struct p4tc_table *table;
 	char *tblname;
@@ -1077,13 +1089,25 @@  static struct p4tc_table *p4tc_table_create(struct net *net, struct nlattr **tb,
 
 	refcount_set(&table->tbl_ctrl_ref, 1);
 
+	if (tb[P4TC_TABLE_COUNTER]) {
+		const char *ext_inst_path = nla_data(tb[P4TC_TABLE_COUNTER]);
+
+		inst = p4tc_ext_inst_table_bind(pipeline, &pipe_ext,
+						ext_inst_path, extack);
+		if (IS_ERR(inst)) {
+			ret = PTR_ERR(inst);
+			goto free_permissions;
+		}
+		table->tbl_counter = inst;
+	}
+
 	if (tbl_id) {
 		table->tbl_id = tbl_id;
 		ret = idr_alloc_u32(&pipeline->p_tbl_idr, table, &table->tbl_id,
 				    table->tbl_id, GFP_KERNEL);
 		if (ret < 0) {
 			NL_SET_ERR_MSG(extack, "Unable to allocate table id");
-			goto free_permissions;
+			goto put_inst;
 		}
 	} else {
 		table->tbl_id = 1;
@@ -1091,7 +1115,7 @@  static struct p4tc_table *p4tc_table_create(struct net *net, struct nlattr **tb,
 				    UINT_MAX, GFP_KERNEL);
 		if (ret < 0) {
 			NL_SET_ERR_MSG(extack, "Unable to allocate table id");
-			goto free_permissions;
+			goto put_inst;
 		}
 	}
 
@@ -1169,11 +1193,15 @@  static struct p4tc_table *p4tc_table_create(struct net *net, struct nlattr **tb,
 idr_rm:
 	idr_remove(&pipeline->p_tbl_idr, table->tbl_id);
 
+	p4tc_table_acts_list_destroy(&table->tbl_acts_list);
+
+put_inst:
+	if (inst)
+		p4tc_ext_inst_table_unbind(table, pipe_ext, inst);
+
 free_permissions:
 	kfree(tbl_init_perms);
 
-	p4tc_table_acts_list_destroy(&table->tbl_acts_list);
-
 free:
 	kfree(table);
 
@@ -1189,7 +1217,9 @@  static struct p4tc_table *p4tc_table_update(struct net *net, struct nlattr **tb,
 {
 	u32 tbl_max_masks = 0, tbl_max_entries = 0, tbl_keysz = 0;
 	struct p4tc_table_default_act_params def_params = {0};
+	struct p4tc_user_pipeline_extern *pipe_ext = NULL;
 	struct list_head *tbl_acts_list = NULL;
+	struct p4tc_extern_inst *inst = NULL;
 	struct p4tc_table_perm *perm = NULL;
 	struct p4tc_table_parm *parm = NULL;
 	struct p4tc_table *table;
@@ -1315,6 +1345,17 @@  static struct p4tc_table *p4tc_table_update(struct net *net, struct nlattr **tb,
 		}
 	}
 
+	if (tb[P4TC_TABLE_COUNTER]) {
+		const char *ext_inst_path = nla_data(tb[P4TC_TABLE_COUNTER]);
+
+		inst = p4tc_ext_inst_table_bind(pipeline, &pipe_ext,
+						ext_inst_path, extack);
+		if (IS_ERR(inst)) {
+			ret = PTR_ERR(inst);
+			goto free_perm;
+		}
+	}
+
 	if (tb[P4TC_TABLE_CONST_ENTRY]) {
 		struct p4tc_table_entry *entry;
 
@@ -1324,7 +1365,7 @@  static struct p4tc_table *p4tc_table_update(struct net *net, struct nlattr **tb,
 						  pipeline, table, extack);
 		if (IS_ERR(entry)) {
 			ret = PTR_ERR(entry);
-			goto free_perm;
+			goto put_inst;
 		}
 
 		table->tbl_const_entry = entry;
@@ -1342,6 +1383,8 @@  static struct p4tc_table *p4tc_table_update(struct net *net, struct nlattr **tb,
 	table->tbl_type = tbl_type;
 	if (tbl_aging)
 		table->tbl_aging = tbl_aging;
+	if (inst)
+		table->tbl_counter = inst;
 
 	if (tbl_acts_list)
 		p4tc_table_acts_list_replace(&table->tbl_acts_list,
@@ -1349,6 +1392,10 @@  static struct p4tc_table *p4tc_table_update(struct net *net, struct nlattr **tb,
 
 	return table;
 
+put_inst:
+	if (inst)
+		p4tc_ext_inst_table_unbind(table, pipe_ext, inst);
+
 free_perm:
 	kfree(perm);
 
diff --git a/net/sched/p4tc/p4tc_tbl_entry.c b/net/sched/p4tc/p4tc_tbl_entry.c
index c6953199d..f90b6c66f 100644
--- a/net/sched/p4tc/p4tc_tbl_entry.c
+++ b/net/sched/p4tc/p4tc_tbl_entry.c
@@ -27,6 +27,7 @@ 
 #include <net/p4tc.h>
 #include <net/netlink.h>
 #include <net/flow_offload.h>
+#include <net/p4tc_ext_api.h>
 
 #define SIZEOF_MASKID (sizeof(((struct p4tc_table_entry_key *)0)->maskid))
 
@@ -324,6 +325,7 @@  int p4tc_tbl_entry_fill(struct sk_buff *skb, struct p4tc_table *table,
 	struct p4tc_table_entry_tm dtm, *tm;
 	struct nlattr *nest, *nest_acts;
 	u32 ids[P4TC_ENTRY_MAX_IDS];
+	struct nlattr *nest_counter;
 	int ret = -ENOMEM;
 
 	ids[P4TC_TBLID_IDX - 1] = tbl_id;
@@ -385,6 +387,11 @@  int p4tc_tbl_entry_fill(struct sk_buff *skb, struct p4tc_table *table,
 				      P4TC_ENTRY_PAD))
 			goto out_nlmsg_trim;
 	}
+	if (value->counter) {
+		nest_counter = nla_nest_start(skb, P4TC_ENTRY_COUNTER);
+		p4tc_ext_elem_dump_1(skb, value->counter);
+		nla_nest_end(skb, nest_counter);
+	}
 
 	nla_nest_end(skb, nest);
 
@@ -1568,11 +1575,20 @@  __must_hold(RCU)
 		goto free_work;
 	}
 
+	if (table->tbl_counter) {
+		value->counter = p4tc_ext_elem_get(table->tbl_counter);
+		if (!value->counter) {
+			atomic_dec(&table->tbl_nelems);
+			ret = -ENOENT;
+			goto free_work;
+		}
+	}
+
 	if (rhltable_insert(&table->tbl_entries, &entry->ht_node,
 			    entry_hlt_params) < 0) {
 		atomic_dec(&table->tbl_nelems);
 		ret = -EBUSY;
-		goto free_work;
+		goto put_ext;
 	}
 
 	if (value->is_dyn) {
@@ -1592,6 +1608,10 @@  __must_hold(RCU)
 
 	return 0;
 
+put_ext:
+	if (table->tbl_counter && value->counter)
+		p4tc_ext_elem_put_list(table->tbl_counter, value->counter);
+
 free_work:
 	kfree(entry_work);
 
@@ -1820,6 +1840,9 @@  __must_hold(RCU)
 			      HRTIMER_MODE_REL);
 	}
 
+	if (value_old->counter)
+		value->counter = value_old->counter;
+
 	INIT_WORK(&entry_work->work, p4tc_table_entry_del_work);
 
 	if (rhltable_insert(&table->tbl_entries, &entry->ht_node,
diff --git a/net/sched/p4tc/p4tc_tmpl_api.c b/net/sched/p4tc/p4tc_tmpl_api.c
index a3b3b1430..55a738d1b 100644
--- a/net/sched/p4tc/p4tc_tmpl_api.c
+++ b/net/sched/p4tc/p4tc_tmpl_api.c
@@ -44,6 +44,8 @@  static bool obj_is_valid(u32 obj)
 	case P4TC_OBJ_PIPELINE:
 	case P4TC_OBJ_ACT:
 	case P4TC_OBJ_TABLE:
+	case P4TC_OBJ_EXT:
+	case P4TC_OBJ_EXT_INST:
 		return true;
 	default:
 		return false;
@@ -54,6 +56,8 @@  static const struct p4tc_template_ops *p4tc_ops[P4TC_OBJ_MAX + 1] = {
 	[P4TC_OBJ_PIPELINE] = &p4tc_pipeline_ops,
 	[P4TC_OBJ_ACT] = &p4tc_act_ops,
 	[P4TC_OBJ_TABLE] = &p4tc_table_ops,
+	[P4TC_OBJ_EXT] = &p4tc_tmpl_ext_ops,
+	[P4TC_OBJ_EXT_INST] = &p4tc_ext_inst_ops,
 };
 
 int p4tc_tmpl_generic_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
diff --git a/net/sched/p4tc/p4tc_tmpl_ext.c b/net/sched/p4tc/p4tc_tmpl_ext.c
new file mode 100644
index 000000000..ec3efbc68
--- /dev/null
+++ b/net/sched/p4tc/p4tc_tmpl_ext.c
@@ -0,0 +1,2221 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * net/sched/p4tc_tmpl_extern.c	P4 TC EXTERN TEMPLATE
+ *
+ * Copyright (c) 2022-2023, Mojatatu Networks
+ * Copyright (c) 2022-2023, Intel Corporation.
+ * Authors:     Jamal Hadi Salim <jhs@mojatatu.com>
+ *              Victor Nogueira <victor@mojatatu.com>
+ *              Pedro Tammela <pctammela@mojatatu.com>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <net/net_namespace.h>
+#include <net/pkt_cls.h>
+#include <net/p4tc.h>
+#include <net/netlink.h>
+#include <net/p4tc_types.h>
+#include <net/sock.h>
+#include <net/p4tc_ext_api.h>
+
+static LIST_HEAD(ext_base);
+static DEFINE_RWLOCK(ext_mod_lock);
+
+static const struct nla_policy tc_extern_inst_policy[P4TC_TMPL_EXT_INST_MAX + 1] = {
+	[P4TC_TMPL_EXT_INST_EXT_NAME] = {
+		.type = NLA_STRING,
+		.len =  EXTERNNAMSIZ
+	},
+	[P4TC_TMPL_EXT_INST_NAME] = {
+		.type = NLA_STRING,
+		.len =  EXTERNINSTNAMSIZ
+	},
+	[P4TC_TMPL_EXT_INST_NUM_ELEMS] = NLA_POLICY_RANGE(NLA_U32, 1,
+							  P4TC_MAX_NUM_EXT_INST_ELEMS),
+	[P4TC_TMPL_EXT_INST_CONTROL_PARAMS] = { .type = NLA_NESTED },
+	[P4TC_TMPL_EXT_INST_TABLE_BINDABLE] = { . type = NLA_U8 },
+	[P4TC_TMPL_EXT_INST_CONSTR_PARAMS] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy tc_extern_policy[P4TC_TMPL_EXT_MAX + 1] = {
+	[P4TC_TMPL_EXT_NAME] = { .type = NLA_STRING, .len =  EXTERNNAMSIZ },
+	[P4TC_TMPL_EXT_NUM_INSTS] = NLA_POLICY_RANGE(NLA_U16, 1,
+						     P4TC_MAX_NUM_EXT_INSTS),
+	[P4TC_TMPL_EXT_HAS_EXEC_METHOD] = NLA_POLICY_RANGE(NLA_U8, 1, 1),
+};
+
+static const struct nla_policy p4tc_extern_params_policy[P4TC_EXT_PARAMS_MAX + 1] = {
+	[P4TC_EXT_PARAMS_NAME] = { .type = NLA_STRING, .len = EXTPARAMNAMSIZ },
+	[P4TC_EXT_PARAMS_ID] = { .type = NLA_U32 },
+	[P4TC_EXT_PARAMS_VALUE] = { .type = NLA_NESTED },
+	[P4TC_EXT_PARAMS_TYPE] = { .type = NLA_U32 },
+	[P4TC_EXT_PARAMS_BITSZ] = { .type = NLA_U16 },
+	[P4TC_EXT_PARAMS_FLAGS] = { .type = NLA_U8 },
+};
+
+static void p4tc_extern_ops_put(const struct p4tc_extern_ops *ops)
+{
+	if (ops)
+		module_put(ops->owner);
+}
+
+static bool
+p4tc_extern_mod_callbacks_check(const struct p4tc_extern_ops *ext)
+{
+	if ((ext->construct || ext->deconstruct) && !(ext->rctrl || ext->dump))
+		return (ext->construct && ext->deconstruct);
+
+	if (ext->rctrl || ext->dump)
+		return (ext->construct && ext->deconstruct && ext->rctrl &&
+			ext->dump);
+
+	return true;
+}
+
+static struct p4tc_extern_ops *p4tc_extern_lookup_n(char *kind)
+{
+	struct p4tc_extern_ops *a = NULL;
+
+	read_lock(&ext_mod_lock);
+	list_for_each_entry(a, &ext_base, head) {
+		if (strcmp(kind, a->kind) == 0) {
+			read_unlock(&ext_mod_lock);
+			return a;
+		}
+	}
+	read_unlock(&ext_mod_lock);
+
+	return NULL;
+}
+
+static int
+p4tc_extern_mod_name(char *mod_name, char *kind)
+{
+	int nbytes;
+
+	nbytes = snprintf(mod_name, EXTERNNAMSIZ, "ext_%s", kind);
+	/* Extern name was too long */
+	if (nbytes == EXTERNNAMSIZ)
+		return -E2BIG;
+
+	return 0;
+}
+
+static struct p4tc_extern_ops *p4tc_extern_ops_load(char *kind)
+{
+	struct p4tc_extern_ops *ops = NULL;
+	char mod_name[EXTERNNAMSIZ] = {0};
+	int err;
+
+	if (!kind)
+		return NULL;
+
+	err = p4tc_extern_mod_name(mod_name, kind);
+	if (err < 0)
+		return NULL;
+
+	ops = p4tc_extern_lookup_n(mod_name);
+	if (ops && try_module_get(ops->owner))
+		return ops;
+
+	if (!ops) {
+		rtnl_unlock();
+		request_module(mod_name);
+		rtnl_lock();
+
+		ops = p4tc_extern_lookup_n(mod_name);
+		if (ops) {
+			if (try_module_get(ops->owner))
+				return ops;
+
+			return NULL;
+		}
+	}
+
+	return ops;
+}
+
+static void p4tc_extern_put_param(struct p4tc_extern_param *param)
+{
+	if (param->mask_shift)
+		p4t_release(param->mask_shift);
+	if (param->value)
+		p4tc_ext_param_value_free_tmpl(param);
+	kfree(param);
+}
+
+static void p4tc_extern_put_param_idr(struct idr *params_idr,
+				      struct p4tc_extern_param *param)
+{
+	idr_remove(params_idr, param->id);
+	p4tc_extern_put_param(param);
+}
+
+static void
+p4tc_user_pipeline_ext_put_ref(struct p4tc_user_pipeline_extern *pipe_ext)
+{
+	refcount_dec(&pipe_ext->ext_ref);
+}
+
+static void
+p4tc_user_pipeline_ext_free(struct p4tc_user_pipeline_extern *pipe_ext,
+			    struct idr *tmpl_exts_idr)
+{
+	idr_remove(tmpl_exts_idr, pipe_ext->ext_id);
+	idr_destroy(&pipe_ext->e_inst_idr);
+	refcount_dec(&pipe_ext->tmpl_ext->tmpl_ref);
+	kfree(pipe_ext);
+}
+
+static void
+p4tc_user_pipeline_ext_put(struct p4tc_pipeline *pipeline,
+			   struct p4tc_user_pipeline_extern *pipe_ext,
+			   bool release, struct idr *tmpl_exts_idr)
+{
+	if (refcount_dec_and_test(&pipe_ext->ext_ref) && release)
+		p4tc_user_pipeline_ext_free(pipe_ext, tmpl_exts_idr);
+}
+
+static struct p4tc_user_pipeline_extern *
+p4tc_user_pipeline_ext_find_byid(struct p4tc_pipeline *pipeline,
+				 const u32 ext_id)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+
+	pipe_ext = idr_find(&pipeline->user_ext_idr, ext_id);
+
+	return pipe_ext;
+}
+
+static struct p4tc_user_pipeline_extern *
+p4tc_user_pipeline_ext_get(struct p4tc_pipeline *pipeline, const u32 ext_id)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+
+	pipe_ext = p4tc_user_pipeline_ext_find_byid(pipeline, ext_id);
+	if (!pipe_ext)
+		return ERR_PTR(-ENOENT);
+
+	refcount_inc(&pipe_ext->ext_ref);
+
+	return pipe_ext;
+}
+
+void p4tc_ext_inst_purge(struct p4tc_extern_inst *inst)
+{
+	p4tc_ext_purge(&inst->control_elems_idr);
+}
+EXPORT_SYMBOL_GPL(p4tc_ext_inst_purge);
+
+static void ___p4tc_ext_inst_put(struct p4tc_extern_inst *inst, bool put_params)
+{
+	if (p4tc_ext_inst_has_construct(inst)) {
+		inst->ops->deconstruct(inst);
+	} else {
+		if (inst->params && put_params)
+			p4tc_ext_params_free(inst->params, true);
+
+		p4tc_ext_inst_purge(inst);
+		kfree(inst);
+	}
+}
+
+static int __p4tc_ext_inst_put(struct p4tc_pipeline *pipeline,
+			       struct p4tc_extern_inst *inst, bool teardown,
+			       bool release, struct netlink_ext_ack *extack)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext = inst->pipe_ext;
+	const u32 inst_id = inst->ext_inst_id;
+
+	if (!teardown && !refcount_dec_if_one(&inst->inst_ref)) {
+		NL_SET_ERR_MSG(extack,
+			       "Can't delete referenced extern instance template");
+		return -EBUSY;
+	}
+
+	___p4tc_ext_inst_put(inst, true);
+
+	idr_remove(&pipe_ext->e_inst_idr, inst_id);
+
+	p4tc_user_pipeline_ext_put(pipeline, pipe_ext, release,
+				   &pipeline->user_ext_idr);
+
+	return 0;
+}
+
+static int _p4tc_tmpl_ext_put(struct p4tc_pipeline *pipeline,
+			      struct p4tc_tmpl_extern *ext, bool teardown,
+			      struct netlink_ext_ack *extack)
+{
+	if (!teardown && !refcount_dec_if_one(&ext->tmpl_ref)) {
+		NL_SET_ERR_MSG(extack,
+			       "Can't delete referenced extern template");
+		return -EBUSY;
+	}
+
+	idr_remove(&pipeline->p_ext_idr, ext->ext_id);
+	p4tc_extern_ops_put(ext->ops);
+
+	kfree(ext);
+
+	return 0;
+}
+
+static int p4tc_tmpl_ext_put(struct p4tc_pipeline *pipeline,
+			     struct p4tc_template_common *tmpl,
+			     struct netlink_ext_ack *extack)
+{
+	struct p4tc_tmpl_extern *ext;
+
+	ext = to_extern(tmpl);
+
+	return _p4tc_tmpl_ext_put(pipeline, ext, true, extack);
+}
+
+struct p4tc_extern_inst *
+p4tc_ext_inst_alloc(const struct p4tc_extern_ops *ops, const u32 max_num_elems,
+		    bool tbl_bindable, char *ext_name)
+{
+	struct p4tc_extern_inst *inst;
+	const u32 inst_size = (ops && ops->size) ? ops->size : sizeof(*inst);
+
+	inst = kzalloc(inst_size, GFP_KERNEL);
+	if (!inst)
+		return ERR_PTR(-ENOMEM);
+
+	inst->ops = ops;
+	inst->max_num_elems = max_num_elems;
+	refcount_set(&inst->inst_ref, 1);
+	INIT_LIST_HEAD(&inst->unused_elems);
+	spin_lock_init(&inst->available_list_lock);
+	atomic_set(&inst->curr_num_elems, 0);
+	idr_init(&inst->control_elems_idr);
+	inst->ext_name = ext_name;
+	inst->tbl_bindable = tbl_bindable;
+
+	inst->common.ops = (typeof(inst->common.ops))&p4tc_ext_inst_ops;
+
+	return inst;
+}
+EXPORT_SYMBOL(p4tc_ext_inst_alloc);
+
+static int p4tc_ext_inst_put(struct p4tc_pipeline *pipeline,
+			     struct p4tc_template_common *tmpl,
+			     struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_inst *inst;
+
+	inst = to_extern_inst(tmpl);
+
+	return __p4tc_ext_inst_put(pipeline, inst, true, false, extack);
+}
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_find_byname(struct p4tc_user_pipeline_extern *pipe_ext,
+			  const char *instname)
+{
+	struct p4tc_extern_inst *ext_inst;
+	unsigned long tmp, inst_id;
+
+	idr_for_each_entry_ul(&pipe_ext->e_inst_idr, ext_inst, tmp, inst_id) {
+		if (strncmp(ext_inst->common.name, instname,
+			    EXTERNINSTNAMSIZ) == 0)
+			return ext_inst;
+	}
+
+	return NULL;
+}
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_find_byid(struct p4tc_user_pipeline_extern *pipe_ext,
+			const u32 inst_id)
+{
+	struct p4tc_extern_inst *ext_inst;
+
+	ext_inst = idr_find(&pipe_ext->e_inst_idr, inst_id);
+
+	return ext_inst;
+}
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_find_byany(struct p4tc_user_pipeline_extern *pipe_ext,
+			 const char *instname, u32 instid,
+			 struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_inst *inst;
+	int err;
+
+	if (instid) {
+		inst = p4tc_ext_inst_find_byid(pipe_ext, instid);
+		if (!inst) {
+			NL_SET_ERR_MSG(extack, "Unable to find instance by id");
+			err = -EINVAL;
+			goto out;
+		}
+	} else {
+		if (instname) {
+			inst = p4tc_ext_inst_find_byname(pipe_ext, instname);
+			if (!inst) {
+				NL_SET_ERR_MSG_FMT(extack,
+						   "Instance name not found %s\n",
+						   instname);
+				err = -EINVAL;
+				goto out;
+			}
+		} else {
+			NL_SET_ERR_MSG(extack,
+				       "Must specify instance name or id");
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	return inst;
+
+out:
+	return ERR_PTR(err);
+}
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_get(struct p4tc_user_pipeline_extern *pipe_ext,
+		  const char *instname, const u32 ext_inst_id,
+		  struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_inst *ext_inst;
+
+	ext_inst = p4tc_ext_inst_find_byany(pipe_ext, instname, ext_inst_id,
+					    extack);
+	if (IS_ERR(ext_inst))
+		return ext_inst;
+
+	/* Extern instance template was deleted in parallel */
+	if (!refcount_inc_not_zero(&ext_inst->inst_ref))
+		return ERR_PTR(-EBUSY);
+
+	return ext_inst;
+}
+
+static void p4tc_ext_inst_put_ref(struct p4tc_extern_inst *inst)
+{
+	refcount_dec(&inst->inst_ref);
+}
+
+static struct p4tc_tmpl_extern *
+p4tc_tmpl_ext_find_name(struct p4tc_pipeline *pipeline, const char *extern_name)
+{
+	struct p4tc_tmpl_extern *ext;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(&pipeline->p_ext_idr, ext, tmp, id)
+		if (ext->common.name[0] &&
+		    strncmp(ext->common.name, extern_name,
+			    EXTERNNAMSIZ) == 0)
+			return ext;
+
+	return NULL;
+}
+
+static struct p4tc_tmpl_extern *
+p4tc_tmpl_ext_find_byid(struct p4tc_pipeline *pipeline, const u32 ext_id)
+{
+	return idr_find(&pipeline->p_ext_idr, ext_id);
+}
+
+static struct p4tc_tmpl_extern *
+p4tc_tmpl_ext_find_byany(struct p4tc_pipeline *pipeline,
+			 const char *extern_name, u32 ext_id,
+			 struct netlink_ext_ack *extack)
+{
+	struct p4tc_tmpl_extern *ext;
+	int err;
+
+	if (ext_id) {
+		ext = p4tc_tmpl_ext_find_byid(pipeline, ext_id);
+		if (!ext) {
+			NL_SET_ERR_MSG(extack, "Unable to find ext by id");
+			err = -EINVAL;
+			goto out;
+		}
+	} else {
+		if (extern_name) {
+			ext = p4tc_tmpl_ext_find_name(pipeline, extern_name);
+			if (!ext) {
+				NL_SET_ERR_MSG(extack,
+					       "Extern name not found");
+				err = -EINVAL;
+				goto out;
+			}
+		} else {
+			NL_SET_ERR_MSG(extack,
+				       "Must specify ext name or id");
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	return ext;
+
+out:
+	return ERR_PTR(err);
+}
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_find_byanyattr(struct p4tc_user_pipeline_extern *pipe_ext,
+			     struct nlattr *name_attr, u32 instid,
+			     struct netlink_ext_ack *extack)
+{
+	char *instname = NULL;
+
+	if (name_attr)
+		instname = nla_data(name_attr);
+
+	return p4tc_ext_inst_find_byany(pipe_ext, instname, instid,
+					extack);
+}
+
+static struct p4tc_extern_param *
+p4tc_ext_param_find_byname(struct idr *params_idr, const char *param_name)
+{
+	struct p4tc_extern_param *param;
+	unsigned long tmp, id;
+
+	idr_for_each_entry_ul(params_idr, param, tmp, id) {
+		if (param == ERR_PTR(-EBUSY))
+			continue;
+		if (strncmp(param->name, param_name, EXTPARAMNAMSIZ) == 0)
+			return param;
+	}
+
+	return NULL;
+}
+
+struct p4tc_extern_param *
+p4tc_ext_param_find_byid(struct idr *params_idr, const u32 param_id)
+{
+	return idr_find(params_idr, param_id);
+}
+EXPORT_SYMBOL(p4tc_ext_param_find_byid);
+
+static struct p4tc_extern_param *
+p4tc_ext_param_find_byany(struct idr *params_idr, const char *param_name,
+			  const u32 param_id, struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_param *param;
+	int err;
+
+	if (param_id) {
+		param = p4tc_ext_param_find_byid(params_idr, param_id);
+		if (!param) {
+			NL_SET_ERR_MSG(extack, "Unable to find param by id");
+			err = -EINVAL;
+			goto out;
+		}
+	} else {
+		if (param_name) {
+			param = p4tc_ext_param_find_byname(params_idr,
+							   param_name);
+			if (!param) {
+				NL_SET_ERR_MSG(extack, "Param name not found");
+				err = -EINVAL;
+				goto out;
+			}
+		} else {
+			NL_SET_ERR_MSG(extack, "Must specify param name or id");
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	return param;
+
+out:
+	return ERR_PTR(err);
+}
+
+struct p4tc_extern_param *
+p4tc_ext_param_find_byanyattr(struct idr *params_idr,
+			      struct nlattr *name_attr,
+			      const u32 param_id,
+			      struct netlink_ext_ack *extack)
+{
+	char *param_name = NULL;
+
+	if (name_attr)
+		param_name = nla_data(name_attr);
+
+	return p4tc_ext_param_find_byany(params_idr, param_name, param_id,
+					 extack);
+}
+
+static struct p4tc_extern_param *
+p4tc_extern_create_param(struct idr *params_idr, struct nlattr **tb,
+			 u32 param_id, struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_param *param;
+	u8 *flags = NULL;
+	char *name;
+	int ret;
+
+	if (tb[P4TC_EXT_PARAMS_NAME]) {
+		name = nla_data(tb[P4TC_EXT_PARAMS_NAME]);
+	} else {
+		NL_SET_ERR_MSG(extack, "Must specify param name");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	param = kzalloc(sizeof(*param), GFP_KERNEL);
+	if (!param) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if ((param_id && p4tc_ext_param_find_byid(params_idr, param_id)) ||
+	    p4tc_ext_param_find_byname(params_idr, name)) {
+		NL_SET_ERR_MSG_FMT(extack, "Param already exists %s", name);
+		ret = -EEXIST;
+		goto free;
+	}
+
+	if ((tb[P4TC_EXT_PARAMS_TYPE] && !tb[P4TC_EXT_PARAMS_BITSZ]) ||
+	    (!tb[P4TC_EXT_PARAMS_TYPE] && tb[P4TC_EXT_PARAMS_BITSZ])) {
+		NL_SET_ERR_MSG(extack, "Must specify type with bit size");
+		ret = -EINVAL;
+		goto free;
+	}
+
+	if (tb[P4TC_EXT_PARAMS_TYPE]) {
+		struct p4tc_type_mask_shift *mask_shift = NULL;
+		struct p4tc_type *type;
+		u32 typeid;
+		u16 bitsz;
+
+		typeid = nla_get_u32(tb[P4TC_EXT_PARAMS_TYPE]);
+		bitsz = nla_get_u16(tb[P4TC_EXT_PARAMS_BITSZ]);
+
+		type = p4type_find_byid(typeid);
+		if (!type) {
+			NL_SET_ERR_MSG(extack, "Param type is invalid");
+			ret = -EINVAL;
+			goto free;
+		}
+		param->type = type;
+		if (bitsz > param->type->bitsz) {
+			NL_SET_ERR_MSG(extack, "Bit size is bigger than type");
+			ret = -EINVAL;
+			goto free;
+		}
+		if (type->ops->create_bitops) {
+			mask_shift = type->ops->create_bitops(bitsz, 0,
+							      bitsz - 1,
+							      extack);
+			if (IS_ERR(mask_shift)) {
+				ret = PTR_ERR(mask_shift);
+				goto free;
+			}
+		}
+		param->mask_shift = mask_shift;
+		param->bitsz = bitsz;
+	} else {
+		NL_SET_ERR_MSG(extack, "Must specify param type");
+		ret = -EINVAL;
+		goto free;
+	}
+
+	if (tb[P4TC_EXT_PARAMS_FLAGS]) {
+		flags = nla_data(tb[P4TC_EXT_PARAMS_FLAGS]);
+		param->flags = *flags;
+	}
+
+	if (flags && *flags & P4TC_EXT_PARAMS_FLAG_ISKEY) {
+		switch (param->type->typeid) {
+		case P4T_U8:
+		case P4T_U16:
+		case P4T_U32:
+			break;
+		default: {
+			NL_SET_ERR_MSG(extack,
+				       "Key must be an unsigned integer");
+			ret = -EINVAL;
+			goto free_mask_shift;
+		}
+		}
+	}
+
+	if (param_id) {
+		ret = idr_alloc_u32(params_idr, param, &param_id,
+				    param_id, GFP_KERNEL);
+		if (ret < 0) {
+			NL_SET_ERR_MSG(extack, "Unable to allocate param id");
+			goto free_mask_shift;
+		}
+		param->id = param_id;
+	} else {
+		param->id = 1;
+
+		ret = idr_alloc_u32(params_idr, param, &param->id,
+				    UINT_MAX, GFP_KERNEL);
+		if (ret < 0) {
+			NL_SET_ERR_MSG(extack, "Unable to allocate param id");
+			goto free_mask_shift;
+		}
+	}
+
+	strscpy(param->name, name, EXTPARAMNAMSIZ);
+
+	return param;
+
+free_mask_shift:
+	if (param->mask_shift)
+		p4t_release(param->mask_shift);
+
+free:
+	kfree(param);
+
+out:
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_extern_param *
+p4tc_extern_create_param_value(struct net *net, struct idr *params_idr,
+			       struct nlattr **tb, u32 param_id,
+			       struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_param *param;
+	int err;
+
+	param = p4tc_extern_create_param(params_idr, tb, param_id, extack);
+	if (IS_ERR(param))
+		return param;
+
+	err = p4tc_ext_param_value_init(net, param, tb, param->type->typeid,
+					false, extack);
+	if (err < 0) {
+		p4tc_extern_put_param_idr(params_idr, param);
+		return ERR_PTR(err);
+	}
+
+	return param;
+}
+
+static struct p4tc_extern_param *
+p4tc_extern_init_param_value(struct net *net, struct idr *params_idr,
+			     struct nlattr *nla,
+			     struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_EXT_PARAMS_MAX + 1];
+	u32 param_id = 0;
+	int ret;
+
+	ret = nla_parse_nested(tb, P4TC_EXT_PARAMS_MAX, nla,
+			       p4tc_extern_params_policy, extack);
+	if (ret < 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (tb[P4TC_EXT_PARAMS_ID])
+		param_id = nla_get_u32(tb[P4TC_EXT_PARAMS_ID]);
+
+	return p4tc_extern_create_param_value(net, params_idr, tb,
+					      param_id, extack);
+
+out:
+	return ERR_PTR(ret);
+}
+
+static bool
+p4tc_extern_params_check_flags(struct p4tc_extern_param *param,
+			       struct netlink_ext_ack *extack)
+{
+	if (param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY &&
+	    param->flags & P4TC_EXT_PARAMS_FLAG_IS_DATASCALAR) {
+		NL_SET_ERR_MSG(extack,
+			       "Can't set key and data scalar flags at the same time");
+		return false;
+	}
+
+	return true;
+}
+
+static struct p4tc_extern_params *
+p4tc_extern_init_params_value(struct net *net,
+			      struct p4tc_extern_params *params,
+			      struct nlattr **tb,
+			      bool *is_scalar, bool tbl_bindable,
+			      struct netlink_ext_ack *extack)
+{
+	bool has_scalar_param = false;
+	bool has_key_param = false;
+	int ret;
+	int i;
+
+	for (i = 1; i < P4TC_MSGBATCH_SIZE + 1 && tb[i]; i++) {
+		struct p4tc_extern_param *param;
+
+		param = p4tc_extern_init_param_value(net, &params->params_idr,
+						     tb[i], extack);
+		if (IS_ERR(param)) {
+			ret = PTR_ERR(param);
+			goto params_del;
+		}
+
+		if (!p4tc_extern_params_check_flags(param, extack)) {
+			ret = -EINVAL;
+			goto params_del;
+		}
+
+		if (has_key_param) {
+			if (param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY) {
+				NL_SET_ERR_MSG(extack,
+					       "There can't be 2 key params");
+				ret = -EINVAL;
+				goto params_del;
+			}
+		} else {
+			has_key_param = param->flags & P4TC_EXT_PARAMS_FLAG_ISKEY;
+		}
+
+		if (has_scalar_param) {
+			if (!param->flags ||
+			    (param->flags & P4TC_EXT_PARAMS_FLAG_IS_DATASCALAR)) {
+				NL_SET_ERR_MSG(extack,
+					       "All data parameters must be scalars");
+				ret = -EINVAL;
+				goto params_del;
+			}
+		} else {
+			has_scalar_param = param->flags & P4TC_EXT_PARAMS_FLAG_IS_DATASCALAR;
+		}
+		if (tbl_bindable) {
+			if (!p4tc_is_type_unsigned(param->type->typeid)) {
+				NL_SET_ERR_MSG_FMT(extack,
+						   "Extern with %s parameter is unbindable",
+						   param->type->name);
+				ret = -EINVAL;
+				goto params_del;
+			}
+		}
+		params->num_params++;
+	}
+	*is_scalar = has_scalar_param;
+
+	return params;
+
+params_del:
+	p4tc_ext_params_free(params, true);
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_extern_params *
+p4tc_extern_create_params_value(struct net *net, struct nlattr *nla,
+				bool *is_scalar, bool tbl_bindable,
+				struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+	struct p4tc_extern_params *params;
+	int ret;
+
+	params = p4tc_extern_params_init();
+	if (!params) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	if (nla) {
+		ret = nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL,
+				       extack);
+		if (ret < 0) {
+			ret = -EINVAL;
+			goto params_del;
+		}
+	} else {
+		return params;
+	}
+
+	return p4tc_extern_init_params_value(net, params, tb, is_scalar,
+					     tbl_bindable, extack);
+
+params_del:
+	p4tc_ext_params_free(params, true);
+err_out:
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_extern_params *
+p4tc_extern_update_params_value(struct net *net, struct nlattr *nla,
+				bool *is_scalar, bool tbl_bindable,
+				struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+	struct p4tc_extern_params *params;
+	int ret;
+
+	if (nla) {
+		params = p4tc_extern_params_init();
+		if (!params) {
+			ret = -ENOMEM;
+			goto err_out;
+		}
+
+		ret = nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL,
+				       extack);
+		if (ret < 0) {
+			ret = -EINVAL;
+			goto params_del;
+		}
+	} else {
+		return NULL;
+	}
+
+	return p4tc_extern_init_params_value(net, params, tb, is_scalar,
+					     tbl_bindable, extack);
+
+params_del:
+	p4tc_ext_params_free(params, true);
+err_out:
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_tmpl_extern *
+p4tc_tmpl_ext_find_byanyattr(struct p4tc_pipeline *pipeline,
+			     struct nlattr *name_attr, u32 ext_id,
+			     struct netlink_ext_ack *extack)
+{
+	char *extern_name = NULL;
+
+	if (name_attr)
+		extern_name = nla_data(name_attr);
+
+	return p4tc_tmpl_ext_find_byany(pipeline, extern_name, ext_id,
+				       extack);
+}
+
+int p4tc_register_extern(struct p4tc_extern_ops *ext)
+{
+	if (p4tc_extern_lookup_n(ext->kind))
+		return -EEXIST;
+
+	if (!p4tc_extern_mod_callbacks_check(ext))
+		return -EINVAL;
+
+	write_lock(&ext_mod_lock);
+	list_add_tail(&ext->head, &ext_base);
+	write_unlock(&ext_mod_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(p4tc_register_extern);
+
+int p4tc_unregister_extern(struct p4tc_extern_ops *ext)
+{
+	struct p4tc_extern_ops *a;
+	int err = -ENOENT;
+
+	write_lock(&ext_mod_lock);
+	list_for_each_entry(a, &ext_base, head) {
+		if (a == ext) {
+			list_del(&ext->head);
+			err = 0;
+			break;
+		}
+	}
+	write_unlock(&ext_mod_lock);
+	return err;
+}
+EXPORT_SYMBOL(p4tc_unregister_extern);
+
+static struct p4tc_user_pipeline_extern *
+p4tc_user_pipeline_ext_find_byname(struct p4tc_pipeline *pipeline,
+				   const char *extname)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	unsigned long tmp, ext_id;
+
+	idr_for_each_entry_ul(&pipeline->user_ext_idr, pipe_ext, tmp, ext_id) {
+		if (strncmp(pipe_ext->ext_name, extname, EXTERNNAMSIZ) == 0)
+			return pipe_ext;
+	}
+
+	return NULL;
+}
+
+static struct p4tc_user_pipeline_extern *
+p4tc_user_pipeline_ext_find_byany(struct p4tc_pipeline *pipeline,
+				  const char *extname, u32 ext_id,
+				  struct netlink_ext_ack *extack)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	int err;
+
+	if (ext_id) {
+		pipe_ext = p4tc_user_pipeline_ext_find_byid(pipeline, ext_id);
+		if (!pipe_ext) {
+			NL_SET_ERR_MSG(extack, "Unable to find extern");
+			err = -EINVAL;
+			goto out;
+		}
+	} else {
+		if (extname) {
+			pipe_ext = p4tc_user_pipeline_ext_find_byname(pipeline,
+								      extname);
+			if (!pipe_ext) {
+				NL_SET_ERR_MSG(extack,
+					       "Extern name not found");
+				err = -EINVAL;
+				goto out;
+			}
+		} else {
+			NL_SET_ERR_MSG(extack,
+				       "Must specify extern name or id");
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	return pipe_ext;
+
+out:
+	return ERR_PTR(err);
+}
+
+static struct p4tc_user_pipeline_extern *
+p4tc_user_pipeline_ext_find_byanyattr(struct p4tc_pipeline *pipeline,
+				      struct nlattr *name_attr, u32 ext_id,
+				      struct netlink_ext_ack *extack)
+{
+	char *extname = NULL;
+
+	if (name_attr)
+		extname = nla_data(name_attr);
+
+	return p4tc_user_pipeline_ext_find_byany(pipeline, extname, ext_id,
+						 extack);
+}
+
+static bool
+p4tc_user_pipeline_insts_exceeded(struct p4tc_user_pipeline_extern *pipe_ext)
+{
+	const u32 max_num_insts = pipe_ext->tmpl_ext->max_num_insts;
+
+	return atomic_read(&pipe_ext->curr_insts_num) == max_num_insts;
+}
+
+static struct p4tc_user_pipeline_extern *
+p4tc_user_pipeline_ext_find_or_create(struct p4tc_pipeline *pipeline,
+				      struct p4tc_tmpl_extern *tmpl_ext,
+				      struct netlink_ext_ack *extack)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	int err;
+
+	pipe_ext = p4tc_user_pipeline_ext_get(pipeline, tmpl_ext->ext_id);
+	if (pipe_ext != ERR_PTR(-ENOENT)) {
+		if (p4tc_user_pipeline_insts_exceeded(pipe_ext)) {
+			NL_SET_ERR_MSG(extack,
+				       "Maximum number of instances exceeded");
+			p4tc_user_pipeline_ext_put_ref(pipe_ext);
+			return ERR_PTR(-ENOSPC);
+		}
+
+		return pipe_ext;
+	}
+
+	pipe_ext = kzalloc(sizeof(*pipe_ext), GFP_KERNEL);
+	if (!pipe_ext)
+		return ERR_PTR(-ENOMEM);
+
+	pipe_ext->ext_id = tmpl_ext->ext_id;
+	err = idr_alloc_u32(&pipeline->user_ext_idr, pipe_ext,
+			    &pipe_ext->ext_id, pipe_ext->ext_id, GFP_KERNEL);
+	if (err < 0)
+		goto free_pipe_ext;
+
+	strscpy(pipe_ext->ext_name, tmpl_ext->common.name, EXTERNNAMSIZ);
+	idr_init(&pipe_ext->e_inst_idr);
+	refcount_set(&pipe_ext->ext_ref, 2);
+	atomic_set(&pipe_ext->curr_insts_num, 0);
+	refcount_inc(&tmpl_ext->tmpl_ref);
+	pipe_ext->tmpl_ext = tmpl_ext;
+	pipe_ext->free = p4tc_user_pipeline_ext_free;
+
+	return pipe_ext;
+
+free_pipe_ext:
+	kfree(pipe_ext);
+	return ERR_PTR(err);
+}
+
+struct p4tc_user_pipeline_extern *
+p4tc_pipe_ext_find_bynames(struct net *net, struct p4tc_pipeline *pipeline,
+			   const char *extname, struct netlink_ext_ack *extack)
+{
+	return p4tc_user_pipeline_ext_find_byany(pipeline, extname, 0,
+						 extack);
+}
+
+struct p4tc_extern_inst *
+p4tc_ext_inst_find_bynames(struct net *net, struct p4tc_pipeline *pipeline,
+			   const char *extname, const char *instname,
+			   struct netlink_ext_ack *extack)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	struct p4tc_extern_inst *inst;
+
+	pipe_ext = p4tc_pipe_ext_find_bynames(net, pipeline, extname, extack);
+	if (IS_ERR(pipe_ext))
+		return (void *)pipe_ext;
+
+	inst = p4tc_ext_inst_find_byany(pipe_ext, instname, 0, extack);
+	if (IS_ERR(inst))
+		return inst;
+
+	return inst;
+}
+
+static void
+__p4tc_ext_inst_table_unbind(struct p4tc_user_pipeline_extern *pipe_ext,
+			     struct p4tc_extern_inst *inst)
+{
+	p4tc_user_pipeline_ext_put_ref(pipe_ext);
+	p4tc_ext_inst_put_ref(inst);
+}
+
+void
+p4tc_ext_inst_table_unbind(struct p4tc_table *table,
+			   struct p4tc_user_pipeline_extern *pipe_ext,
+			   struct p4tc_extern_inst *inst)
+{
+	table->tbl_counter = NULL;
+	__p4tc_ext_inst_table_unbind(pipe_ext, inst);
+}
+
+struct p4tc_extern_inst *
+p4tc_ext_find_byids(struct p4tc_pipeline *pipeline,
+		    const u32 ext_id, const u32 inst_id)
+{
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	struct p4tc_extern_inst *inst;
+	int err;
+
+	pipe_ext = p4tc_user_pipeline_ext_find_byid(pipeline, ext_id);
+	if (!pipe_ext) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	inst = p4tc_ext_inst_find_byid(pipe_ext, inst_id);
+	if (!inst) {
+		err = -EBUSY;
+		goto out;
+	}
+
+	return inst;
+
+out:
+	return ERR_PTR(err);
+}
+
+#define SEPARATOR "/"
+
+struct p4tc_extern_inst *
+p4tc_ext_inst_table_bind(struct p4tc_pipeline *pipeline,
+			 struct p4tc_user_pipeline_extern **pipe_ext,
+			 const char *ext_inst_path,
+			 struct netlink_ext_ack *extack)
+{
+	char *instname_clone, *extname, *instname;
+	struct p4tc_extern_inst *inst;
+	int err;
+
+	instname_clone = instname = kstrdup(ext_inst_path, GFP_KERNEL);
+	if (!instname)
+		return ERR_PTR(-ENOMEM);
+
+	extname = strsep(&instname, SEPARATOR);
+
+	*pipe_ext = p4tc_pipe_ext_find_bynames(pipeline->net, pipeline, extname,
+					       extack);
+	if (IS_ERR(*pipe_ext)) {
+		err = PTR_ERR(*pipe_ext);
+		goto free_inst_path;
+	}
+
+	inst = p4tc_ext_inst_get(*pipe_ext, instname, 0, extack);
+	if (IS_ERR(inst)) {
+		err = PTR_ERR(inst);
+		goto free_inst_path;
+	}
+
+	if (!inst->tbl_bindable) {
+		__p4tc_ext_inst_table_unbind(*pipe_ext, inst);
+		NL_SET_ERR_MSG_FMT(extack,
+				   "Extern instance %s can't be bound to a table",
+				   inst->common.name);
+		err = -EPERM;
+		goto put_inst;
+	}
+
+	kfree(instname_clone);
+
+	return inst;
+
+put_inst:
+	p4tc_ext_inst_put_ref(inst);
+
+free_inst_path:
+	kfree(instname_clone);
+	return ERR_PTR(err);
+}
+
+struct p4tc_extern_inst *
+p4tc_ext_inst_get_byids(struct net *net, struct p4tc_pipeline **pipeline,
+			struct p4tc_ext_bpf_params *params)
+{
+	struct p4tc_extern_inst *inst;
+	int err;
+
+	*pipeline = p4tc_pipeline_find_get(net, NULL, params->pipe_id, NULL);
+	if (IS_ERR(*pipeline))
+		return (struct p4tc_extern_inst *)*pipeline;
+
+	inst = p4tc_ext_find_byids(*pipeline, params->ext_id, params->inst_id);
+	if (IS_ERR(inst)) {
+		err = PTR_ERR(inst);
+		goto put_pipeline;
+	}
+
+	return inst;
+
+put_pipeline:
+	p4tc_pipeline_put(*pipeline);
+
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL(p4tc_ext_inst_get_byids);
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_update(struct net *net, struct nlmsghdr *n,
+		     struct nlattr *nla, struct p4tc_pipeline *pipeline,
+		     u32 *ids, struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_params *new_params, *new_constr_params;
+	struct p4tc_extern_params *params, *constr_params;
+	struct nlattr *tb[P4TC_TMPL_EXT_INST_MAX + 1];
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	struct p4tc_extern_inst *new_inst = NULL;
+	struct p4tc_pipeline *root_pipeline;
+	struct p4tc_extern_inst *old_inst;
+	bool has_scalar_params = false;
+	struct p4tc_tmpl_extern *ext;
+	u32 ext_id = 0, inst_id = 0;
+	bool tbl_bindable = false;
+	char *inst_name = NULL;
+	u32 max_num_elems = 0;
+	int ret;
+
+	ret = nla_parse_nested(tb, P4TC_TMPL_EXT_INST_MAX, nla,
+			       tc_extern_inst_policy, extack);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	ext_id = ids[P4TC_TMPL_EXT_IDX];
+
+	root_pipeline = p4tc_pipeline_find_byid(net, P4TC_KERNEL_PIPEID);
+
+	ext = p4tc_tmpl_ext_find_byanyattr(root_pipeline,
+					   tb[P4TC_TMPL_EXT_INST_EXT_NAME],
+					   ext_id, extack);
+	if (IS_ERR(ext))
+		return (struct p4tc_extern_inst *)ext;
+
+	inst_id = ids[P4TC_TMPL_EXT_INST_IDX];
+
+	if (tb[P4TC_TMPL_EXT_INST_NAME])
+		inst_name = nla_data(tb[P4TC_TMPL_EXT_INST_NAME]);
+
+	pipe_ext = p4tc_user_pipeline_ext_find_byid(pipeline, ext->ext_id);
+	if (!pipe_ext) {
+		NL_SET_ERR_MSG(extack, "Unable to find pipeline extern by id");
+		return ERR_PTR(-ENOENT);
+	}
+
+	old_inst = p4tc_ext_inst_find_byanyattr(pipe_ext, tb[P4TC_TMPL_EXT_INST_NAME],
+						inst_id, extack);
+	if (IS_ERR(old_inst)) {
+		NL_SET_ERR_MSG(extack, "Unable to find extern instance by id");
+		return ERR_PTR(-ENOENT);
+	}
+
+	if (tb[P4TC_TMPL_EXT_INST_NUM_ELEMS])
+		max_num_elems = nla_get_u32(tb[P4TC_TMPL_EXT_INST_NUM_ELEMS]);
+
+	if (tb[P4TC_TMPL_EXT_INST_TABLE_BINDABLE])
+		tbl_bindable = true;
+	else
+		tbl_bindable = old_inst->tbl_bindable;
+
+	if (tbl_bindable && !p4tc_ext_has_exec(ext->ops)) {
+		NL_SET_ERR_MSG(extack,
+			       "Instance may only be table bindable if module has exec");
+		return ERR_PTR(-EINVAL);
+	}
+
+	new_params = p4tc_extern_update_params_value(net,
+						     tb[P4TC_TMPL_EXT_INST_CONTROL_PARAMS],
+						     &has_scalar_params, tbl_bindable,
+						     extack);
+	if (IS_ERR(new_params))
+		return (struct p4tc_extern_inst *)new_params;
+
+	params = new_params ?: old_inst->params;
+	max_num_elems = max_num_elems ?: old_inst->max_num_elems;
+
+	if (p4tc_ext_inst_has_construct(old_inst)) {
+		struct nlattr *nla_constr_params = tb[P4TC_TMPL_EXT_INST_CONSTR_PARAMS];
+
+		new_constr_params = p4tc_extern_update_params_value(net,
+								    nla_constr_params,
+								    &has_scalar_params,
+								    tbl_bindable,
+								    extack);
+		if (IS_ERR(new_constr_params)) {
+			if (new_params)
+				p4tc_ext_params_free(new_params, true);
+
+			return (struct p4tc_extern_inst *)new_constr_params;
+		}
+		constr_params = new_constr_params ?: old_inst->constr_params;
+
+		ret = old_inst->ops->construct(&new_inst, params, constr_params,
+					       max_num_elems, tbl_bindable,
+					       extack);
+		if (new_params)
+			p4tc_ext_params_free(new_params, true);
+		if (new_constr_params)
+			p4tc_ext_params_free(new_constr_params, true);
+		if (ret < 0)
+			return ERR_PTR(ret);
+	} else {
+		if (tb[P4TC_TMPL_EXT_INST_CONSTR_PARAMS]) {
+			NL_SET_ERR_MSG(extack,
+				       "Need construct mod op to pass constructor params");
+			ret = -EINVAL;
+			goto free_control_params;
+		}
+
+		new_inst = p4tc_ext_inst_alloc(ext->ops, max_num_elems,
+					       tbl_bindable,
+					       pipe_ext->ext_name);
+		if (IS_ERR(new_inst)) {
+			ret = PTR_ERR(new_inst);
+			goto free_control_params;
+		}
+		new_inst->params = params;
+	}
+
+	new_inst->ext_inst_id = old_inst->ext_inst_id;
+	new_inst->is_scalar = has_scalar_params;
+	new_inst->ext_id = ext->ext_id;
+	new_inst->pipe_ext = pipe_ext;
+
+	strscpy(new_inst->common.name, inst_name, EXTERNINSTNAMSIZ);
+
+	idr_replace(&pipe_ext->e_inst_idr, new_inst, old_inst->ext_inst_id);
+
+	___p4tc_ext_inst_put(old_inst, !!new_params);
+
+	return new_inst;
+
+free_control_params:
+	if (new_params)
+		p4tc_ext_params_free(new_params, true);
+
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_extern_inst *
+p4tc_ext_inst_create(struct net *net, struct nlmsghdr *n,
+		     struct nlattr *nla, struct p4tc_pipeline *pipeline,
+		     u32 *ids, struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_params *constr_params = NULL, *params;
+	struct nlattr *tb[P4TC_TMPL_EXT_INST_MAX + 1];
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	struct p4tc_pipeline *root_pipeline;
+	bool has_scalar_params = false;
+	struct p4tc_extern_inst *inst;
+	struct p4tc_tmpl_extern *ext;
+	u32 ext_id = 0, inst_id = 0;
+	bool tbl_bindable = false;
+	char *inst_name = NULL;
+	u32 max_num_elems;
+	int ret;
+
+	ret = nla_parse_nested(tb, P4TC_TMPL_EXT_INST_MAX, nla,
+			       tc_extern_inst_policy, extack);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	ext_id = ids[P4TC_TMPL_EXT_IDX];
+
+	root_pipeline = p4tc_pipeline_find_byid(net, P4TC_KERNEL_PIPEID);
+
+	ext = p4tc_tmpl_ext_find_byanyattr(root_pipeline,
+					   tb[P4TC_TMPL_EXT_INST_EXT_NAME],
+					   ext_id, extack);
+	if (IS_ERR(ext))
+		return (struct p4tc_extern_inst *)ext;
+
+	if (tb[P4TC_TMPL_EXT_INST_NAME]) {
+		inst_name = nla_data(tb[P4TC_TMPL_EXT_INST_NAME]);
+	} else {
+		NL_SET_ERR_MSG(extack,
+			       "Must specify extern name");
+		return ERR_PTR(-EINVAL);
+	}
+
+	inst_id = ids[P4TC_TMPL_EXT_INST_IDX];
+	if (!inst_id) {
+		NL_SET_ERR_MSG(extack, "Must specify extern instance id");
+		return ERR_PTR(-EINVAL);
+	}
+
+	pipe_ext = p4tc_user_pipeline_ext_find_or_create(pipeline, ext, extack);
+	if (IS_ERR(pipe_ext))
+		return (struct p4tc_extern_inst *)pipe_ext;
+
+	if (p4tc_ext_inst_find_byid(pipe_ext, inst_id) ||
+	    p4tc_ext_inst_find_byname(pipe_ext, inst_name)) {
+		NL_SET_ERR_MSG(extack,
+			       "Extern instance with same name or ID already exists");
+		ret = -EEXIST;
+		goto put_pipe_ext;
+	}
+
+	if (tb[P4TC_TMPL_EXT_INST_NUM_ELEMS])
+		max_num_elems = nla_get_u32(tb[P4TC_TMPL_EXT_INST_NUM_ELEMS]);
+	else
+		max_num_elems = P4TC_DEFAULT_NUM_EXT_INST_ELEMS;
+
+	if (tb[P4TC_TMPL_EXT_INST_TABLE_BINDABLE])
+		tbl_bindable = true;
+
+	if (tbl_bindable && !p4tc_ext_has_exec(ext->ops)) {
+		NL_SET_ERR_MSG(extack,
+			       "Instance may only be table bindable if module has exec");
+		return ERR_PTR(-EINVAL);
+	}
+
+	params = p4tc_extern_create_params_value(net,
+						 tb[P4TC_TMPL_EXT_INST_CONTROL_PARAMS],
+						 &has_scalar_params,
+						 tbl_bindable, extack);
+	if (IS_ERR(params))
+		return (struct p4tc_extern_inst *)params;
+
+	if (p4tc_ext_has_construct(ext->ops)) {
+		struct nlattr *nla_constr_params = tb[P4TC_TMPL_EXT_INST_CONSTR_PARAMS];
+
+		constr_params = p4tc_extern_create_params_value(net,
+								nla_constr_params,
+								&has_scalar_params,
+								tbl_bindable, extack);
+		if (IS_ERR(constr_params)) {
+			ret = PTR_ERR(constr_params);
+			goto free_control_params;
+		}
+
+		ret = ext->ops->construct(&inst, params, constr_params,
+					  max_num_elems, tbl_bindable, extack);
+		p4tc_ext_params_free(params, true);
+		p4tc_ext_params_free(constr_params, true);
+		if (ret < 0)
+			goto put_pipe_ext;
+	} else {
+		if (tb[P4TC_TMPL_EXT_INST_CONSTR_PARAMS]) {
+			NL_SET_ERR_MSG(extack,
+				       "Need construct mod op to pass constructor params");
+			ret = -EINVAL;
+			goto free_control_params;
+		}
+
+		inst = p4tc_ext_inst_alloc(ext->ops, max_num_elems,
+					   tbl_bindable, pipe_ext->ext_name);
+		if (IS_ERR(inst)) {
+			ret = -ENOMEM;
+			goto free_control_params;
+		}
+
+		inst->params = params;
+	}
+
+	inst->ext_id = ext->ext_id;
+	inst->ext_inst_id = inst_id;
+	inst->pipe_ext = pipe_ext;
+	inst->ext_id = ext->ext_id;
+	inst->is_scalar = has_scalar_params;
+
+	strscpy(inst->common.name, inst_name, EXTERNINSTNAMSIZ);
+
+	ret = idr_alloc_u32(&pipe_ext->e_inst_idr, inst, &inst_id,
+			    inst_id, GFP_KERNEL);
+	if (ret < 0) {
+		NL_SET_ERR_MSG(extack,
+			       "Unable to allocate ID for extern instance");
+		goto free_extern;
+	}
+
+	atomic_inc(&pipe_ext->curr_insts_num);
+
+	return inst;
+
+free_extern:
+	if (p4tc_ext_inst_has_construct(inst))
+		inst->ops->deconstruct(inst);
+	else
+		kfree(inst);
+
+free_control_params:
+	if (!p4tc_ext_has_construct(ext->ops) && params)
+		p4tc_ext_params_free(params, true);
+
+put_pipe_ext:
+	p4tc_user_pipeline_ext_put_ref(pipe_ext);
+
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_template_common *
+p4tc_ext_inst_cu(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
+		 struct p4tc_path_nlattrs *nl_path_attrs,
+		 struct netlink_ext_ack *extack)
+{
+	u32 *ids = nl_path_attrs->ids;
+	u32 pipeid = ids[P4TC_PID_IDX];
+	struct p4tc_pipeline *pipeline;
+	struct p4tc_extern_inst *inst;
+
+	pipeline = p4tc_pipeline_find_byany_unsealed(net, nl_path_attrs->pname,
+						     pipeid, extack);
+	if (IS_ERR(pipeline))
+		return (void *)pipeline;
+
+	switch (n->nlmsg_type) {
+	case RTM_CREATEP4TEMPLATE:
+		inst = p4tc_ext_inst_create(net, n, nla, pipeline, ids,
+					    extack);
+		break;
+	case RTM_UPDATEP4TEMPLATE:
+		inst = p4tc_ext_inst_update(net, n, nla, pipeline, ids,
+					    extack);
+		break;
+	default:
+		/* Should never happen */
+		NL_SET_ERR_MSG(extack,
+			       "Only create and update are supported for extern inst");
+		return ERR_PTR(-EOPNOTSUPP);
+	}
+
+	if (IS_ERR(inst))
+		goto out;
+
+	if (!nl_path_attrs->pname_passed)
+		strscpy(nl_path_attrs->pname, pipeline->common.name,
+			PIPELINENAMSIZ);
+
+	if (!ids[P4TC_PID_IDX])
+		ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+	if (!ids[P4TC_TMPL_EXT_IDX])
+		ids[P4TC_TMPL_EXT_IDX] = inst->ext_id;
+
+	if (!ids[P4TC_TMPL_EXT_INST_IDX])
+		ids[P4TC_TMPL_EXT_INST_IDX] = inst->ext_inst_id;
+
+out:
+	return (struct p4tc_template_common *)inst;
+}
+
+static struct p4tc_tmpl_extern *
+p4tc_tmpl_ext_create(struct nlmsghdr *n, struct nlattr *nla,
+		     struct p4tc_pipeline *pipeline, u32 *ids,
+		     struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_TMPL_EXT_MAX + 1];
+	struct p4tc_tmpl_extern *ext;
+	char *extern_name = NULL;
+	u32 ext_id = 0;
+	int ret;
+
+	ret = nla_parse_nested(tb, P4TC_TMPL_EXT_MAX, nla, tc_extern_policy,
+			       extack);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	ext_id = ids[P4TC_TMPL_EXT_IDX];
+	if (!ext_id) {
+		NL_SET_ERR_MSG(extack, "Must specify extern id");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (tb[P4TC_TMPL_EXT_NAME]) {
+		extern_name = nla_data(tb[P4TC_TMPL_EXT_NAME]);
+	} else {
+		NL_SET_ERR_MSG(extack,
+			       "Must specify extern name");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (p4tc_tmpl_ext_find_byid(pipeline, ext_id) ||
+	    p4tc_tmpl_ext_find_name(pipeline, extern_name)) {
+		NL_SET_ERR_MSG(extack,
+			       "Extern with same id or name was already inserted");
+		return ERR_PTR(-EEXIST);
+	}
+
+	ext = kzalloc(sizeof(*ext), GFP_KERNEL);
+	if (!ext) {
+		NL_SET_ERR_MSG(extack, "Failed to allocate ext");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	if (tb[P4TC_TMPL_EXT_NUM_INSTS]) {
+		u16 *num_insts = nla_data(tb[P4TC_TMPL_EXT_NUM_INSTS]);
+
+		ext->max_num_insts = *num_insts;
+	} else {
+		ext->max_num_insts = P4TC_DEFAULT_NUM_EXT_INSTS;
+	}
+
+	if (tb[P4TC_TMPL_EXT_HAS_EXEC_METHOD])
+		ext->has_exec_method = nla_get_u8(tb[P4TC_TMPL_EXT_HAS_EXEC_METHOD]);
+
+	/* Extern module is not mandatory */
+	if (ext->has_exec_method) {
+		struct p4tc_extern_ops *ops;
+
+		ops = p4tc_extern_ops_load(extern_name);
+		if (!ops) {
+			ret = -ENOENT;
+			goto free_extern;
+		}
+
+		ext->ops = ops;
+	}
+
+	ret = idr_alloc_u32(&pipeline->p_ext_idr, ext, &ext_id, ext_id,
+			    GFP_KERNEL);
+	if (ret < 0) {
+		NL_SET_ERR_MSG(extack, "Unable to allocate ID for extern");
+		goto free_extern;
+	}
+
+	ext->ext_id = ext_id;
+
+	strscpy(ext->common.name, extern_name, EXTERNNAMSIZ);
+
+	refcount_set(&ext->tmpl_ref, 1);
+
+	ext->common.p_id = pipeline->common.p_id;
+	ext->common.ops = (struct p4tc_template_ops *)&p4tc_tmpl_ext_ops;
+
+	return ext;
+
+free_extern:
+	p4tc_extern_ops_put(ext->ops);
+	kfree(ext);
+
+	return ERR_PTR(ret);
+}
+
+static struct p4tc_template_common *
+p4tc_tmpl_ext_cu(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
+		 struct p4tc_path_nlattrs *nl_path_attrs,
+		 struct netlink_ext_ack *extack)
+{
+	struct p4tc_pipeline *pipeline;
+	u32 *ids = nl_path_attrs->ids;
+	struct p4tc_tmpl_extern *ext;
+
+	if (p4tc_tmpl_msg_is_update(n)) {
+		NL_SET_ERR_MSG(extack, "Extern update not supported");
+		return ERR_PTR(-EOPNOTSUPP);
+	}
+
+	pipeline = p4tc_pipeline_find_byid(net, P4TC_KERNEL_PIPEID);
+
+	ext = p4tc_tmpl_ext_create(n, nla, pipeline, ids, extack);
+	if (IS_ERR(ext))
+		goto out;
+
+out:
+	return (struct p4tc_template_common *)ext;
+}
+
+static int ext_inst_param_fill_nlmsg(struct sk_buff *skb,
+				     struct p4tc_extern_param *param)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+
+	if (nla_put_string(skb, P4TC_EXT_PARAMS_NAME, param->name))
+		goto out_nlmsg_trim;
+
+	if (nla_put_u32(skb, P4TC_EXT_PARAMS_ID, param->id))
+		goto out_nlmsg_trim;
+
+	if (nla_put_u32(skb, P4TC_EXT_PARAMS_TYPE, param->type->typeid))
+		goto out_nlmsg_trim;
+
+	if (param->value && p4tc_ext_param_value_dump_tmpl(skb, param))
+		goto out_nlmsg_trim;
+
+	return skb->len;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static int ext_inst_params_fill_nlmsg(struct sk_buff *skb,
+				      struct p4tc_extern_params *params)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct p4tc_extern_param *param;
+	struct nlattr *nest_count;
+	unsigned long id, tmp;
+	int i = 1;
+
+	if (!params)
+		return skb->len;
+
+	idr_for_each_entry_ul(&params->params_idr, param, tmp, id) {
+		nest_count = nla_nest_start(skb, i);
+		if (!nest_count)
+			goto out_nlmsg_trim;
+
+		if (ext_inst_param_fill_nlmsg(skb, param) < 0)
+			goto out_nlmsg_trim;
+
+		nla_nest_end(skb, nest_count);
+		i++;
+	}
+
+	return skb->len;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static int
+__p4tc_ext_inst_fill_nlmsg(struct sk_buff *skb, struct p4tc_extern_inst *inst,
+			   struct netlink_ext_ack *extack)
+{
+	struct nlattr *nest, *parms, *constr_parms;
+	const char *ext_name = inst->ext_name;
+	unsigned char *b = nlmsg_get_pos(skb);
+	/* Parser instance id + header field id */
+	u32 ids[2];
+
+	ids[0] = inst->ext_id;
+	ids[1] = inst->ext_inst_id;
+
+	if (nla_put(skb, P4TC_PATH, sizeof(ids), &ids))
+		goto out_nlmsg_trim;
+
+	nest = nla_nest_start(skb, P4TC_PARAMS);
+	if (!nest)
+		goto out_nlmsg_trim;
+
+	if (ext_name[0]) {
+		if (nla_put_string(skb, P4TC_TMPL_EXT_INST_EXT_NAME,
+				   ext_name))
+			goto out_nlmsg_trim;
+	}
+
+	if (inst->common.name[0]) {
+		if (nla_put_string(skb, P4TC_TMPL_EXT_INST_NAME,
+				   inst->common.name))
+			goto out_nlmsg_trim;
+	}
+
+	if (nla_put_u32(skb, P4TC_TMPL_EXT_INST_NUM_ELEMS,
+			inst->max_num_elems))
+		goto out_nlmsg_trim;
+
+	parms = nla_nest_start(skb, P4TC_TMPL_EXT_INST_CONTROL_PARAMS);
+	if (!parms)
+		goto out_nlmsg_trim;
+
+	if (ext_inst_params_fill_nlmsg(skb, inst->params) < 0)
+		goto out_nlmsg_trim;
+
+	nla_nest_end(skb, parms);
+
+	constr_parms = nla_nest_start(skb, P4TC_TMPL_EXT_INST_CONSTR_PARAMS);
+	if (!constr_parms)
+		goto out_nlmsg_trim;
+
+	if (ext_inst_params_fill_nlmsg(skb, inst->constr_params) < 0)
+		goto out_nlmsg_trim;
+
+	nla_nest_end(skb, constr_parms);
+
+	nla_nest_end(skb, nest);
+
+	return skb->len;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static int __p4tc_tmpl_ext_fill_nlmsg(struct sk_buff *skb,
+				      struct p4tc_tmpl_extern *ext)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct nlattr *nest;
+	/* Parser instance id + header field id */
+	u32 id;
+
+	id = ext->ext_id;
+
+	if (nla_put(skb, P4TC_PATH, sizeof(id), &id))
+		goto out_nlmsg_trim;
+
+	nest = nla_nest_start(skb, P4TC_PARAMS);
+	if (!nest)
+		goto out_nlmsg_trim;
+
+	if (ext->common.name[0]) {
+		if (nla_put_string(skb, P4TC_TMPL_EXT_NAME, ext->common.name))
+			goto out_nlmsg_trim;
+	}
+
+	if (nla_put_u16(skb, P4TC_TMPL_EXT_NUM_INSTS, ext->max_num_insts))
+		goto out_nlmsg_trim;
+
+	nla_nest_end(skb, nest);
+
+	return skb->len;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -1;
+}
+
+static int p4tc_ext_inst_fill_nlmsg(struct net *net, struct sk_buff *skb,
+				    struct p4tc_template_common *template,
+				    struct netlink_ext_ack *extack)
+{
+	struct p4tc_extern_inst *inst = to_extern_inst(template);
+
+	if (__p4tc_ext_inst_fill_nlmsg(skb, inst, extack) < 0) {
+		NL_SET_ERR_MSG(extack,
+			       "Failed to fill notification attributes for extern instance");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int p4tc_tmpl_ext_fill_nlmsg(struct net *net, struct sk_buff *skb,
+				    struct p4tc_template_common *template,
+				    struct netlink_ext_ack *extack)
+{
+	struct p4tc_tmpl_extern *ext = to_extern(template);
+
+	if (__p4tc_tmpl_ext_fill_nlmsg(skb, ext) <= 0) {
+		NL_SET_ERR_MSG(extack,
+			       "Failed to fill notification attributes for extern");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int p4tc_tmpl_ext_flush(struct sk_buff *skb,
+			       struct p4tc_pipeline *pipeline,
+			       struct netlink_ext_ack *extack)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct p4tc_tmpl_extern *ext;
+	unsigned long tmp, ext_id;
+	int ret = 0;
+	u32 path[1];
+	int i = 0;
+
+	path[0] = 0;
+
+	if (idr_is_empty(&pipeline->p_ext_idr)) {
+		NL_SET_ERR_MSG(extack, "There are no externs to flush");
+		goto out_nlmsg_trim;
+	}
+
+	if (nla_put(skb, P4TC_PATH, sizeof(path), path))
+		goto out_nlmsg_trim;
+
+	idr_for_each_entry_ul(&pipeline->p_ext_idr, ext, tmp, ext_id) {
+		if (_p4tc_tmpl_ext_put(pipeline, ext, false, extack) < 0) {
+			ret = -EBUSY;
+			continue;
+		}
+		i++;
+	}
+
+	nla_put_u32(skb, P4TC_COUNT, i);
+
+	if (ret < 0) {
+		if (i == 0) {
+			NL_SET_ERR_MSG(extack,
+				       "Unable to flush any externs");
+			goto out_nlmsg_trim;
+		} else {
+			NL_SET_ERR_MSG_FMT(extack,
+					   "Flush only %u externs", i);
+		}
+	}
+
+	return i;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return 0;
+}
+
+static int p4tc_ext_inst_flush(struct sk_buff *skb,
+			       struct p4tc_pipeline *pipeline,
+			       struct p4tc_user_pipeline_extern *pipe_ext,
+			       struct netlink_ext_ack *extack)
+{
+	unsigned char *b = nlmsg_get_pos(skb);
+	struct p4tc_extern_inst *inst;
+	unsigned long tmp, inst_id;
+	int ret = 0;
+	u32 path[2];
+	int i = 0;
+
+	if (idr_is_empty(&pipe_ext->e_inst_idr)) {
+		NL_SET_ERR_MSG(extack, "There are no externs to flush");
+		goto out_nlmsg_trim;
+	}
+
+	path[0] = pipe_ext->ext_id;
+	path[1] = 0;
+
+	if (nla_put(skb, P4TC_PATH, sizeof(path), path))
+		goto out_nlmsg_trim;
+
+	idr_for_each_entry_ul(&pipe_ext->e_inst_idr, inst, tmp, inst_id) {
+		if (__p4tc_ext_inst_put(pipeline, inst, false, false,
+					extack) < 0) {
+			ret = -EBUSY;
+			continue;
+		}
+		i++;
+	}
+
+	/* We don't release pipe_ext in the loop to avoid use-after-free whilst
+	 * iterating through e_inst_idr. We free it here only if flush
+	 * succeeded, that is, all instances were deleted and thus ext_ref == 1
+	 */
+	if (refcount_read(&pipe_ext->ext_ref) == 1)
+		p4tc_user_pipeline_ext_free(pipe_ext, &pipeline->user_ext_idr);
+
+	nla_put_u32(skb, P4TC_COUNT, i);
+
+	if (ret < 0) {
+		if (i == 0) {
+			NL_SET_ERR_MSG(extack,
+				       "Unable to flush any externs instance");
+			goto out_nlmsg_trim;
+		} else {
+			NL_SET_ERR_MSG_FMT(extack,
+					   "Flushed only %u extern instances", i);
+		}
+	}
+
+	return i;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return 0;
+}
+
+static int p4tc_ext_inst_gd(struct net *net, struct sk_buff *skb,
+			    struct nlmsghdr *n, struct nlattr *nla,
+			    struct p4tc_path_nlattrs *nl_path_attrs,
+			    struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_TMPL_EXT_INST_MAX + 1] = {NULL};
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	u32 *ids = nl_path_attrs->ids;
+	u32 inst_id = ids[P4TC_TMPL_EXT_INST_IDX];
+	unsigned char *b = nlmsg_get_pos(skb);
+	u32 ext_id = ids[P4TC_TMPL_EXT_IDX];
+	u32 pipe_id = ids[P4TC_PID_IDX];
+	struct p4tc_pipeline *pipeline;
+	struct p4tc_extern_inst *inst;
+	int ret;
+
+	if (n->nlmsg_type == RTM_GETP4TEMPLATE)
+		pipeline = p4tc_pipeline_find_byany(net, nl_path_attrs->pname,
+						    pipe_id, extack);
+	else
+		pipeline = p4tc_pipeline_find_byany_unsealed(net,
+							     nl_path_attrs->pname,
+							     pipe_id, extack);
+	if (IS_ERR(pipeline))
+		return PTR_ERR(pipeline);
+
+	if (nla) {
+		ret = nla_parse_nested(tb, P4TC_TMPL_EXT_MAX, nla,
+				       tc_extern_inst_policy, extack);
+		if (ret < 0)
+			return ret;
+	}
+
+	pipe_ext = p4tc_user_pipeline_ext_find_byanyattr(pipeline,
+							 tb[P4TC_TMPL_EXT_INST_EXT_NAME],
+							 ext_id, extack);
+	if (IS_ERR(pipe_ext))
+		return PTR_ERR(pipe_ext);
+
+	if (!nl_path_attrs->pname_passed)
+		strscpy(nl_path_attrs->pname, pipeline->common.name,
+			PIPELINENAMSIZ);
+
+	if (!ids[P4TC_PID_IDX])
+		ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+	if (!ids[P4TC_TMPL_EXT_IDX])
+		ids[P4TC_TMPL_EXT_IDX] = pipe_ext->ext_id;
+
+	if (n->nlmsg_type == RTM_DELP4TEMPLATE && n->nlmsg_flags & NLM_F_ROOT)
+		return p4tc_ext_inst_flush(skb, pipeline, pipe_ext, extack);
+
+	inst = p4tc_ext_inst_find_byanyattr(pipe_ext,
+					    tb[P4TC_TMPL_EXT_INST_NAME],
+					    inst_id, extack);
+	if (IS_ERR(inst))
+		return PTR_ERR(inst);
+
+	ret = __p4tc_ext_inst_fill_nlmsg(skb, inst, extack);
+	if (ret < 0)
+		return -ENOMEM;
+
+	if (!ids[P4TC_TMPL_EXT_INST_IDX])
+		ids[P4TC_TMPL_EXT_INST_IDX] = inst->ext_inst_id;
+
+	if (n->nlmsg_type == RTM_DELP4TEMPLATE) {
+		ret = __p4tc_ext_inst_put(pipeline, inst, false, true, extack);
+		if (ret < 0)
+			goto out_nlmsg_trim;
+	}
+
+	return 0;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return ret;
+}
+
+static int p4tc_tmpl_ext_gd(struct net *net, struct sk_buff *skb,
+			    struct nlmsghdr *n, struct nlattr *nla,
+			    struct p4tc_path_nlattrs *nl_path_attrs,
+			    struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_TMPL_EXT_MAX + 1] = {NULL};
+	unsigned char *b = nlmsg_get_pos(skb);
+	u32 *ids = nl_path_attrs->ids;
+	u32 ext_id = ids[P4TC_TMPL_EXT_IDX];
+	struct p4tc_pipeline *pipeline;
+	struct p4tc_tmpl_extern *ext;
+	int ret;
+
+	pipeline = p4tc_pipeline_find_byid(net, P4TC_KERNEL_PIPEID);
+
+	if (nla) {
+		ret = nla_parse_nested(tb, P4TC_TMPL_EXT_MAX, nla,
+				       tc_extern_policy, extack);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (!nl_path_attrs->pname_passed)
+		strscpy(nl_path_attrs->pname, pipeline->common.name,
+			PIPELINENAMSIZ);
+
+	if (!ids[P4TC_PID_IDX])
+		ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+	if (n->nlmsg_type == RTM_DELP4TEMPLATE && n->nlmsg_flags & NLM_F_ROOT)
+		return p4tc_tmpl_ext_flush(skb, pipeline, extack);
+
+	ext = p4tc_tmpl_ext_find_byanyattr(pipeline, tb[P4TC_TMPL_EXT_NAME],
+					   ext_id, extack);
+	if (IS_ERR(ext))
+		return PTR_ERR(ext);
+
+	ret = __p4tc_tmpl_ext_fill_nlmsg(skb, ext);
+	if (ret < 0)
+		return -ENOMEM;
+
+	if (!ids[P4TC_TMPL_EXT_IDX])
+		ids[P4TC_TMPL_EXT_IDX] = ext->ext_id;
+
+	if (n->nlmsg_type == RTM_DELP4TEMPLATE) {
+		ret = _p4tc_tmpl_ext_put(pipeline, ext, false, extack);
+		if (ret < 0)
+			goto out_nlmsg_trim;
+	}
+
+	return 0;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return ret;
+}
+
+static int p4tc_tmpl_ext_dump_1(struct sk_buff *skb,
+				struct p4tc_template_common *common)
+{
+	struct nlattr *param = nla_nest_start(skb, P4TC_PARAMS);
+	struct p4tc_tmpl_extern *ext = to_extern(common);
+	unsigned char *b = nlmsg_get_pos(skb);
+	u32 path[2];
+
+	if (!param)
+		goto out_nlmsg_trim;
+
+	if (ext->common.name[0] &&
+	    nla_put_string(skb, P4TC_TMPL_EXT_NAME, ext->common.name))
+		goto out_nlmsg_trim;
+
+	nla_nest_end(skb, param);
+
+	path[0] = ext->ext_id;
+	if (nla_put(skb, P4TC_PATH, sizeof(path), path))
+		goto out_nlmsg_trim;
+
+	return 0;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -ENOMEM;
+}
+
+static int p4tc_tmpl_ext_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
+			      struct nlattr *nla, char **p_name, u32 *ids,
+			      struct netlink_ext_ack *extack)
+{
+	struct net *net = sock_net(skb->sk);
+	struct p4tc_pipeline *pipeline;
+
+	pipeline = p4tc_pipeline_find_byid(net, P4TC_KERNEL_PIPEID);
+
+	if (!ids[P4TC_PID_IDX])
+		ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+	if (!(*p_name))
+		*p_name = pipeline->common.name;
+
+	return p4tc_tmpl_generic_dump(skb, ctx, &pipeline->p_ext_idr,
+				      P4TC_TMPL_EXT_IDX, extack);
+}
+
+static int p4tc_ext_inst_dump_1(struct sk_buff *skb,
+				struct p4tc_template_common *common)
+{
+	struct nlattr *param = nla_nest_start(skb, P4TC_PARAMS);
+	struct p4tc_extern_inst *inst = to_extern_inst(common);
+	unsigned char *b = nlmsg_get_pos(skb);
+	u32 path[2];
+
+	if (!param)
+		goto out_nlmsg_trim;
+
+	if (inst->common.name[0] &&
+	    nla_put_string(skb, P4TC_TMPL_EXT_NAME, inst->common.name))
+		goto out_nlmsg_trim;
+
+	nla_nest_end(skb, param);
+
+	path[0] = inst->ext_id;
+	path[1] = inst->ext_inst_id;
+	if (nla_put(skb, P4TC_PATH, sizeof(path), path))
+		goto out_nlmsg_trim;
+
+	return 0;
+
+out_nlmsg_trim:
+	nlmsg_trim(skb, b);
+	return -ENOMEM;
+}
+
+static int p4tc_ext_inst_dump(struct sk_buff *skb,
+			      struct p4tc_dump_ctx *ctx,
+			      struct nlattr *nla, char **p_name,
+			      u32 *ids, struct netlink_ext_ack *extack)
+{
+	struct nlattr *tb[P4TC_TMPL_EXT_INST_MAX + 1] = {NULL};
+	struct p4tc_user_pipeline_extern *pipe_ext;
+	u32 ext_id = ids[P4TC_TMPL_EXT_IDX];
+	struct net *net = sock_net(skb->sk);
+	struct p4tc_pipeline *pipeline;
+	u32 pipeid = ids[P4TC_PID_IDX];
+	int ret;
+
+	pipeline = p4tc_pipeline_find_byany(net, *p_name,
+					    pipeid, extack);
+	if (IS_ERR(pipeline))
+		return PTR_ERR(pipeline);
+
+	if (!ids[P4TC_PID_IDX])
+		ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+	if (!(*p_name))
+		*p_name = pipeline->common.name;
+
+	if (nla) {
+		ret = nla_parse_nested(tb, P4TC_TMPL_EXT_INST_MAX, nla,
+				       tc_extern_inst_policy, extack);
+		if (ret < 0)
+			return ret;
+	}
+
+	pipe_ext = p4tc_user_pipeline_ext_find_byanyattr(pipeline,
+							 tb[P4TC_TMPL_EXT_INST_EXT_NAME],
+							 ext_id, extack);
+	if (IS_ERR(pipe_ext))
+		return PTR_ERR(pipe_ext);
+
+	return p4tc_tmpl_generic_dump(skb, ctx, &pipe_ext->e_inst_idr,
+				      P4TC_TMPL_EXT_INST_IDX, extack);
+}
+
+const struct p4tc_template_ops p4tc_ext_inst_ops = {
+	.cu = p4tc_ext_inst_cu,
+	.fill_nlmsg = p4tc_ext_inst_fill_nlmsg,
+	.gd = p4tc_ext_inst_gd,
+	.put = p4tc_ext_inst_put,
+	.dump = p4tc_ext_inst_dump,
+	.dump_1 = p4tc_ext_inst_dump_1,
+};
+
+const struct p4tc_template_ops p4tc_tmpl_ext_ops = {
+	.cu = p4tc_tmpl_ext_cu,
+	.fill_nlmsg = p4tc_tmpl_ext_fill_nlmsg,
+	.gd = p4tc_tmpl_ext_gd,
+	.put = p4tc_tmpl_ext_put,
+	.dump = p4tc_tmpl_ext_dump,
+	.dump_1 = p4tc_tmpl_ext_dump_1,
+};