From: Cong Wang <hidden> Date: 2021-08-21 01:02:50
From: Cong Wang <redacted>
This *incomplete* patch introduces a programmable Qdisc with
eBPF. The goal is to make Qdisc as programmable as possible,
that is, to replace as many existing Qdisc's as we can. ;)
The design was discussed during last LPC:
https://linuxplumbersconf.org/event/7/contributions/679/attachments/520/1188/sch_bpf.pdf
Here is a summary of design decisions I made:
1. Avoid eBPF struct_ops, as it would be really hard to program
a Qdisc with this approach.
2. Avoid exposing skb's to user-space, which means we can't introduce
a map to store skb's. Instead, store them in kernel without exposure
to user-space.
So I choose to use priority queues to store skb's inside a
flow and to store flows inside a Qdisc, and let eBPF programs
decide the *relative* position of the skb within the flow and the
*relative* order of the flows too, upon each enqueue and dequeue.
Each flow is also exposed to user as a TC class, like many other
classful Qdisc's.
Although the biggest limitation is obviously that users can
not traverse the packets or flows inside the Qdisc, I think
at least they could store those global information of interest
inside their own map and map can be shared between enqueue and
dequeue. For example, users could use skb pointer as key and
rank as a value to find out the absolute order.
One of the challeges is how to interact with existing TC infra,
for instance, if users install TC filters on this Qdisc, should
we respect this by ignoring or rejecting eBPF enqueue program
attached or vice versa? Should we allow users to replace each
priority queue of a class with a regular Qdisc?
Any high-level feedbacks are welcome. Please do not review any
coding details until RFC tag is removed.
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: Cong Wang <redacted>
---
include/linux/bpf_types.h | 2 +
include/linux/priority_queue.h | 90 +++++++
include/linux/skbuff.h | 2 +
include/uapi/linux/bpf.h | 20 ++
include/uapi/linux/pkt_sched.h | 15 ++
net/sched/Kconfig | 15 ++
net/sched/Makefile | 1 +
net/sched/sch_bpf.c | 590 +++++++++++++++++++++++++++++++++++++++++
8 files changed, 735 insertions(+)
@@ -735,6 +736,7 @@ struct sk_buff {};};structrb_noderbnode;/* used in netem, ip4 defrag, and tcp stack */+structpq_nodepqnode;/* used in ebpf qdisc */structlist_headlist;};
@@ -949,6 +949,7 @@ enum bpf_prog_type {BPF_PROG_TYPE_LSM,BPF_PROG_TYPE_SK_LOOKUP,BPF_PROG_TYPE_SYSCALL,/* a program that can execute syscalls */+BPF_PROG_TYPE_SCHED_QDISC,};enumbpf_attach_type{
From: Martin KaFai Lau <hidden> Date: 2021-08-24 23:47:45
On Fri, Aug 20, 2021 at 06:02:40PM -0700, Cong Wang wrote:
From: Cong Wang <redacted>
This *incomplete* patch introduces a programmable Qdisc with
eBPF. The goal is to make Qdisc as programmable as possible,
that is, to replace as many existing Qdisc's as we can. ;)
The design was discussed during last LPC:
https://linuxplumbersconf.org/event/7/contributions/679/attachments/520/1188/sch_bpf.pdf
Here is a summary of design decisions I made:
1. Avoid eBPF struct_ops, as it would be really hard to program
a Qdisc with this approach.
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
2. Avoid exposing skb's to user-space, which means we can't introduce
a map to store skb's. Instead, store them in kernel without exposure
to user-space.
So I choose to use priority queues to store skb's inside a
flow and to store flows inside a Qdisc, and let eBPF programs
decide the *relative* position of the skb within the flow and the
*relative* order of the flows too, upon each enqueue and dequeue.
Each flow is also exposed to user as a TC class, like many other
classful Qdisc's.
Although the biggest limitation is obviously that users can
not traverse the packets or flows inside the Qdisc, I think
at least they could store those global information of interest
inside their own map and map can be shared between enqueue and
dequeue. For example, users could use skb pointer as key and
rank as a value to find out the absolute order.
One of the challeges is how to interact with existing TC infra,
for instance, if users install TC filters on this Qdisc, should
we respect this by ignoring or rejecting eBPF enqueue program
attached or vice versa? Should we allow users to replace each
priority queue of a class with a regular Qdisc?
Any high-level feedbacks are welcome. Please do not review any
coding details until RFC tag is removed.
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: Cong Wang <redacted>
From: Cong Wang <hidden> Date: 2021-09-01 04:39:15
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
Thanks.
From: John Fastabend <john.fastabend@gmail.com> Date: 2021-09-01 05:45:52
Cong Wang wrote:
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
Thanks.
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
I think one trick might be how to trigger the dequeue event on
transition from stopped to running net_device or other events like
this, but that could be solved with another program attached to
those events to kick the dequeue logic.
.John
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
Thanks.
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
Yup, that's my approach as well.
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
FWIW there's some gnarly details here (for instance, we need to make
sure the BPF program doesn't leak packet references after they are
dequeued from the map). My idea is to use a scheme similar to what we do
for XDP_REDIRECT, where a helper sets some hidden variables and doesn't
actually remove the packet from the queue until the BPF program exits
(so the kernel can make sure things are accounted correctly).
I think one trick might be how to trigger the dequeue event on
transition from stopped to running net_device or other events like
this, but that could be solved with another program attached to
those events to kick the dequeue logic.
This is actually easy in the qdisc case, I think: there's already a
qdisc_dequeue() operation, which just needs to execute a BPF program
that picks which packet to dequeue (by pulling it off a queue map). For
XDP we do need a new hook, on driver TX completion or something like
that. Details TBD. Also, we need a way to BPF to kick an idle interface
and make it start transmitting; that way we can implement a traffic
shaper (that delays packets) by using BPF timers :)
-Toke
From: Martin KaFai Lau <hidden> Date: 2021-09-01 17:45:52
On Wed, Sep 01, 2021 at 12:42:03PM +0200, Toke Høiland-Jørgensen wrote:
John Fastabend [off-list ref] writes:
quoted
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
_if_ it is using as a qdisc object/interface,
the patch "looks" easier because it obscures some of the ops/interface
from the bpf user. The user will eventually ask for more flexibility
and then an on-par interface as the kernel's qdisc. If there are some
common 'ops', the common bpf code can be shared as a library in userspace
or there is also kfunc call to call into the kernel implementation.
For existing kernel qdisc author, it will be easier to use the same
interface also.
quoted
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Agree. Regardless how the interface may look like,
I even think being able to queue/dequeue an skb into different bpf maps
should be the first thing to do here. Looking forward to your patches.
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
Interesting idea. If it does not need to use the qdisc object/interface
and be able to do the qdisc hierarchy setup in a programmable way, it may
be nice. It will be useful for the future patches to come with some
bpf prog examples to do that.
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
quoted
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
Yup, that's my approach as well.
quoted
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
FWIW there's some gnarly details here (for instance, we need to make
sure the BPF program doesn't leak packet references after they are
dequeued from the map). My idea is to use a scheme similar to what we do
for XDP_REDIRECT, where a helper sets some hidden variables and doesn't
actually remove the packet from the queue until the BPF program exits
(so the kernel can make sure things are accounted correctly).
The verifier is tracking the sk's references. Can it be reused to
track the skb's reference?
quoted
I think one trick might be how to trigger the dequeue event on
transition from stopped to running net_device or other events like
this, but that could be solved with another program attached to
those events to kick the dequeue logic.
This is actually easy in the qdisc case, I think: there's already a
qdisc_dequeue() operation, which just needs to execute a BPF program
that picks which packet to dequeue (by pulling it off a queue map). For
XDP we do need a new hook, on driver TX completion or something like
that. Details TBD. Also, we need a way to BPF to kick an idle interface
and make it start transmitting; that way we can implement a traffic
shaper (that delays packets) by using BPF timers :)
-Toke
On Wed, Sep 1, 2021 at 10:46 AM Martin KaFai Lau [off-list ref] wrote:
quoted
quoted
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Agree. Regardless how the interface may look like,
I even think being able to queue/dequeue an skb into different bpf maps
should be the first thing to do here. Looking forward to your patches.
quoted
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
Interesting idea. If it does not need to use the qdisc object/interface
and be able to do the qdisc hierarchy setup in a programmable way, it may
be nice. It will be useful for the future patches to come with some
bpf prog examples to do that.
Wow. When core developers think along the same lines and
build/refine the idea together it's simply awesome.
On Wed, Sep 01, 2021 at 12:42:03PM +0200, Toke Høiland-Jørgensen wrote:
quoted
John Fastabend [off-list ref] writes:
quoted
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
_if_ it is using as a qdisc object/interface,
the patch "looks" easier because it obscures some of the ops/interface
from the bpf user. The user will eventually ask for more flexibility
and then an on-par interface as the kernel's qdisc. If there are some
common 'ops', the common bpf code can be shared as a library in userspace
or there is also kfunc call to call into the kernel implementation.
For existing kernel qdisc author, it will be easier to use the same
interface also.
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
My hope is that we can (longer term) develop some higher-level tools to
express queueing policies that can then generate the BPF code needed to
implement them. Or as a start just some libraries to make this easier,
which I think is also what you're hinting at here? :)
quoted
quoted
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Agree. Regardless how the interface may look like,
I even think being able to queue/dequeue an skb into different bpf maps
should be the first thing to do here. Looking forward to your patches.
Thanks! Guess I should go work on them, then :D
quoted
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
Interesting idea. If it does not need to use the qdisc object/interface
and be able to do the qdisc hierarchy setup in a programmable way, it may
be nice. It will be useful for the future patches to come with some
bpf prog examples to do that.
Absolutely; we plan to include example algorithm implementations as well!
quoted
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
quoted
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
Yup, that's my approach as well.
quoted
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
FWIW there's some gnarly details here (for instance, we need to make
sure the BPF program doesn't leak packet references after they are
dequeued from the map). My idea is to use a scheme similar to what we do
for XDP_REDIRECT, where a helper sets some hidden variables and doesn't
actually remove the packet from the queue until the BPF program exits
(so the kernel can make sure things are accounted correctly).
The verifier is tracking the sk's references. Can it be reused to
track the skb's reference?
I was vaguely aware that it does this, but have not looked at the
details. Would be great if this was possible; will see how far I get
with it, and iterate from there (with your help, hopefully :))
-Toke
From: John Fastabend <john.fastabend@gmail.com> Date: 2021-09-02 20:41:00
Toke Høiland-Jørgensen wrote:
Martin KaFai Lau [off-list ref] writes:
quoted
On Wed, Sep 01, 2021 at 12:42:03PM +0200, Toke Høiland-Jørgensen wrote:
quoted
John Fastabend [off-list ref] writes:
quoted
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
_if_ it is using as a qdisc object/interface,
the patch "looks" easier because it obscures some of the ops/interface
from the bpf user. The user will eventually ask for more flexibility
and then an on-par interface as the kernel's qdisc. If there are some
common 'ops', the common bpf code can be shared as a library in userspace
or there is also kfunc call to call into the kernel implementation.
For existing kernel qdisc author, it will be easier to use the same
interface also.
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
If its a map value then you get all the goodness with normal map
inspection.
My hope is that we can (longer term) develop some higher-level tools to
express queueing policies that can then generate the BPF code needed to
implement them. Or as a start just some libraries to make this easier,
which I think is also what you're hinting at here? :)
The P4 working group has thought about QOS and queuing from P4 side if
you want to think in terms of a DSL. Might be interesting and have
some benefits if you want to drop into hardware offload side. For example
compile to XDP for fast CPU architectures, Altera/Xilinx backend for FPGA or
switch silicon for others. This was always the dream on my side maybe
we've finally got close to actualizing it, 10 years later ;)
quoted
quoted
quoted
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Agree. Regardless how the interface may look like,
I even think being able to queue/dequeue an skb into different bpf maps
should be the first thing to do here. Looking forward to your patches.
Thanks! Guess I should go work on them, then :D
Happy to review any RFCs.
quoted
quoted
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
Interesting idea. If it does not need to use the qdisc object/interface
and be able to do the qdisc hierarchy setup in a programmable way, it may
be nice. It will be useful for the future patches to come with some
bpf prog examples to do that.
Absolutely; we plan to include example algorithm implementations as well!
A weighted round robin queue setup might be a useful example and easy
to implement/understand, but slightly more interesting than a pfifo. Also
would force understanding multiple cpus and timer issues.
quoted
quoted
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
quoted
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
Yup, that's my approach as well.
Having something reported back to userspace as the value might be helpful
for debugging/tracing. Maybe the skb->hash? Then you could set this and
then track a skb through the stack even when its in a bpf skb queue.
quoted
quoted
quoted
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
FWIW there's some gnarly details here (for instance, we need to make
sure the BPF program doesn't leak packet references after they are
dequeued from the map). My idea is to use a scheme similar to what we do
for XDP_REDIRECT, where a helper sets some hidden variables and doesn't
actually remove the packet from the queue until the BPF program exits
(so the kernel can make sure things are accounted correctly).
The verifier is tracking the sk's references. Can it be reused to
track the skb's reference?
I was vaguely aware that it does this, but have not looked at the
details. Would be great if this was possible; will see how far I get
with it, and iterate from there (with your help, hopefully :))
Also might need to drop any socket references from the networking side
so an enqueued sock can't hold a socket open.
On Wed, Sep 01, 2021 at 12:42:03PM +0200, Toke Høiland-Jørgensen wrote:
quoted
John Fastabend [off-list ref] writes:
quoted
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
_if_ it is using as a qdisc object/interface,
the patch "looks" easier because it obscures some of the ops/interface
from the bpf user. The user will eventually ask for more flexibility
and then an on-par interface as the kernel's qdisc. If there are some
common 'ops', the common bpf code can be shared as a library in userspace
or there is also kfunc call to call into the kernel implementation.
For existing kernel qdisc author, it will be easier to use the same
interface also.
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
If its a map value then you get all the goodness with normal map
inspection.
Yup, exactly, so why bother with struct_ops to implement all the other
qdisc ops (apart from enqueue/dequeue)?
quoted
My hope is that we can (longer term) develop some higher-level tools to
express queueing policies that can then generate the BPF code needed to
implement them. Or as a start just some libraries to make this easier,
which I think is also what you're hinting at here? :)
The P4 working group has thought about QOS and queuing from P4 side if
you want to think in terms of a DSL. Might be interesting and have
some benefits if you want to drop into hardware offload side. For example
compile to XDP for fast CPU architectures, Altera/Xilinx backend for FPGA or
switch silicon for others. This was always the dream on my side maybe
we've finally got close to actualizing it, 10 years later ;)
Yup, would love to see this! Let's just hope it doesn't take another
decade ;)
quoted
quoted
quoted
quoted
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Agree. Regardless how the interface may look like,
I even think being able to queue/dequeue an skb into different bpf maps
should be the first thing to do here. Looking forward to your patches.
Thanks! Guess I should go work on them, then :D
Happy to review any RFCs.
quoted
quoted
quoted
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
Interesting idea. If it does not need to use the qdisc object/interface
and be able to do the qdisc hierarchy setup in a programmable way, it may
be nice. It will be useful for the future patches to come with some
bpf prog examples to do that.
Absolutely; we plan to include example algorithm implementations as well!
A weighted round robin queue setup might be a useful example and easy
to implement/understand, but slightly more interesting than a pfifo. Also
would force understanding multiple cpus and timer issues.
Yup, some sort of RR queueing is definitely on the list!
quoted
quoted
quoted
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
quoted
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
Yup, that's my approach as well.
Having something reported back to userspace as the value might be helpful
for debugging/tracing. Maybe the skb->hash? Then you could set this and
then track a skb through the stack even when its in a bpf skb queue.
Yeah. I've just been using the pointer value for my initial testing.
That's not a good solution, of course, but having a visible identifier
would be neat. skb->hash makes sense for the qdisc layer, but not for
XDP...
quoted
quoted
quoted
quoted
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
FWIW there's some gnarly details here (for instance, we need to make
sure the BPF program doesn't leak packet references after they are
dequeued from the map). My idea is to use a scheme similar to what we do
for XDP_REDIRECT, where a helper sets some hidden variables and doesn't
actually remove the packet from the queue until the BPF program exits
(so the kernel can make sure things are accounted correctly).
The verifier is tracking the sk's references. Can it be reused to
track the skb's reference?
I was vaguely aware that it does this, but have not looked at the
details. Would be great if this was possible; will see how far I get
with it, and iterate from there (with your help, hopefully :))
Also might need to drop any socket references from the networking side
so an enqueued sock can't hold a socket open.
From: Martin KaFai Lau <hidden> Date: 2021-09-02 23:35:29
On Fri, Sep 03, 2021 at 12:27:52AM +0200, Toke Høiland-Jørgensen wrote:
quoted
quoted
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
Instead of bpftool can only introspect bpf qdisc and the existing tc
can only introspect kernel qdisc, it will be nice to have bpf
qdisc work as other qdisc and showing details together with others
in tc. e.g. a bpf qdisc export its data/stats with its btf-id
to tc and have tc print it out in a generic way?
On Fri, Sep 03, 2021 at 12:27:52AM +0200, Toke Høiland-Jørgensen wrote:
quoted
quoted
quoted
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
Instead of bpftool can only introspect bpf qdisc and the existing tc
can only introspect kernel qdisc, it will be nice to have bpf
qdisc work as other qdisc and showing details together with others
in tc. e.g. a bpf qdisc export its data/stats with its btf-id
to tc and have tc print it out in a generic way?
I'm not opposed to the idea, certainly. I just wonder if people who go
to the trouble of writing a custom qdisc in BPF will feel it's worth it
to do the extra work to make this available via a second API. We could
certainly encourage it, and some things are easy (drop and pkt counters,
etc), but other things (like class stats) will depend on the semantics
of the qdisc being implemented, so will require extra work from the BPF
qdisc developer...
-Toke
From: Jamal Hadi Salim <jhs@mojatatu.com> Date: 2021-09-03 15:33:35
On 2021-09-03 10:44 a.m., Toke Høiland-Jørgensen wrote:
Martin KaFai Lau [off-list ref] writes:
quoted
On Fri, Sep 03, 2021 at 12:27:52AM +0200, Toke Høiland-Jørgensen wrote:
quoted
quoted
quoted
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
Instead of bpftool can only introspect bpf qdisc and the existing tc
can only introspect kernel qdisc, it will be nice to have bpf
qdisc work as other qdisc and showing details together with others
in tc. e.g. a bpf qdisc export its data/stats with its btf-id
to tc and have tc print it out in a generic way?
I'm not opposed to the idea, certainly. I just wonder if people who go
to the trouble of writing a custom qdisc in BPF will feel it's worth it
to do the extra work to make this available via a second API. We could
certainly encourage it, and some things are easy (drop and pkt counters,
etc), but other things (like class stats) will depend on the semantics
of the qdisc being implemented, so will require extra work from the BPF
qdisc developer...
The idea of using btf to overcome the domain difference is _very_
appealing but sounds like a lot of work? Havent delved enough
into btf - but wondering if the same could be stated for filters
and actions...Note:
Aside from current existing tooling being well understood,
challenges you will be faced with is reinventing all the
infrastructure that tc qdiscs have taken care of over the years,
example:
the proper integrations with softirqs and multiprocessor protections,
irqs, timers etc which take care of smooth triggering of
enqueue/dequeue, taking care of defering things when the target
device/hw is busy, hierarchies, etc, etc;
not saying it is the most perfect or performant but it is one of
those 'day 3' deployments i.e a lot of corner cases taken care of.
I noticed you mentioned some of those things in one of your emails.
For this reason - Cong's approach looks appealing because it
reuses said infra. Main thing that needs to have extensibility is
the de/enqueue ops as ebpf progs. Allowing enq/deq to be ebpf specific
sounds like will allow one scheme that works for both tc and XDP
(with enq/deq taking care of the buffer contextual differences).
I admit XDP is a little harder than plain tc....
cheers,
jamal
From: Cong Wang <hidden> Date: 2021-09-04 01:06:08
On Tue, Aug 31, 2021 at 10:45 PM John Fastabend
[off-list ref] wrote:
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
Thanks.
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
Because it is pointless to expose them to user-space in this context.
For example, what is the point of dropping a packet from user-space
in the context of Qdisc?
And I don't think there is a way for user-space to read those skb's inside
a map, which makes it more pointless.
You mention skb should not be exposed to userspace? Why? Whats the
reason for this? Anyways we can make kernel only maps if we want or
scrub the data before passing it to userspace. We do this already in
some cases.
I am not aware of any kernel-only map. For starters, we can't create
a map from kernel-space.
IMO it seems cleaner and more general to allow sk_buffs
to be stored in maps and pulled back out later for enqueue/dequeue.
Which exact map are you referring to? The queue map? It would only
provide FIFO. We want to give users as much freedom to order the
skbs as we can, I doubt hashmap could offer such freedom.
I think one trick might be how to trigger the dequeue event on
transition from stopped to running net_device or other events like
this, but that could be solved with another program attached to
those events to kick the dequeue logic.
I think we can still use current enqueue/dequeue eBPF program
except we need to transfer skb ownership and storage to map.
Thanks.
From: Cong Wang <hidden> Date: 2021-09-04 01:09:59
On Wed, Sep 1, 2021 at 10:45 AM Martin KaFai Lau [off-list ref] wrote:
_if_ it is using as a qdisc object/interface,
the patch "looks" easier because it obscures some of the ops/interface
from the bpf user. The user will eventually ask for more flexibility
and then an on-par interface as the kernel's qdisc. If there are some
common 'ops', the common bpf code can be shared as a library in userspace
or there is also kfunc call to call into the kernel implementation.
For existing kernel qdisc author, it will be easier to use the same
interface also.
Thanks for showing the advantages of a kernel module. And no, we
are not writing kernel modules in eBPF.
And kfunc call really sucks, it does not even guarantee a stable ABI, it
is a serious mistake you made for eBPF.
Thanks.
From: Cong Wang <hidden> Date: 2021-09-04 01:30:57
On Wed, Sep 1, 2021 at 3:42 AM Toke Høiland-Jørgensen [off-list ref] wrote:
John Fastabend [off-list ref] writes:
quoted
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
Thanks.
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
I do see a lot of reasons, for starters, struct skb_buff is very different
from struct xdp_buff, any specialized map can not be reused. I guess you
are using a generic one, how do you handle the refcnt at least for skb?
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Please give an example without a queue. ;) Queue is too simple, show us
something more useful please. How do you plan to re-implement EDT with
just queues?
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
I have no idea why you call this a benefit, because my goal is to replace
Qdisc's, not to replace any other things. You know there are plenty of Qdisc's
which are not implemented in Linux kernel.
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
XDP has no skb but xdp_buff, no? And again, why only queues?
Thanks.
On Wed, Sep 1, 2021 at 3:42 AM Toke Høiland-Jørgensen [off-list ref] wrote:
quoted
John Fastabend [off-list ref] writes:
quoted
Cong Wang wrote:
quoted
On Tue, Aug 24, 2021 at 4:47 PM Martin KaFai Lau [off-list ref] wrote:
quoted
Please explain more on this. What is currently missing
to make qdisc in struct_ops possible?
I think you misunderstand this point. The reason why I avoid it is
_not_ anything is missing, quite oppositely, it is because it requires
a lot of work to implement a Qdisc with struct_ops approach, literally
all those struct Qdisc_ops (not to mention struct Qdisc_class_ops).
WIth current approach, programmers only need to implement two
eBPF programs (enqueue and dequeue).
Thanks.
Another idea. Rather than work with qdisc objects which creates all
these issues with how to work with existing interfaces, filters, etc.
Why not create an sk_buff map? Then this can be used from the existing
egress/ingress hooks independent of the actual qdisc being used.
I agree. In fact, I'm working on doing just this for XDP, and I see no
reason why the map type couldn't be reused for skbs as well. Doing it
this way has a couple of benefits:
I do see a lot of reasons, for starters, struct skb_buff is very different
from struct xdp_buff, any specialized map can not be reused. I guess you
are using a generic one, how do you handle the refcnt at least for skb?
Well, you can't keep XDP frames and skbs in the same map instance, but
you can create a map type that can be instantiated to hold either type
and otherwise keep the same semantics. The map can just inc/dec the
refcnt as skbs are added/removed from it.
quoted
- It leaves more flexibility to BPF: want a simple FIFO queue? just
implement that with a single queue map. Or do you want to build a full
hierarchical queueing structure? Just instantiate as many queue maps
as you need to achieve this. Etc.
Please give an example without a queue. ;) Queue is too simple, show us
something more useful please. How do you plan to re-implement EDT with
just queues?
I'm using 'queue' as a shorthand for any queueing/scheduling algorithm
implementable by a qdisc. We need to cover them all, obviously, not just
FIFO queues (in fact I think we should actively be discouraging those,
but that's a different story :) )
For EDT it would be something like:
- On enqueue, stick frames into the map with a rank corresponding to
their transmission time (the map implements the PIFO queue, just like
your patch).
- (re-)arm a BPF timer to fire at the time of the next transmission
event, and have that timer trigger interface TX.
The first bit is straight-forward, and that last bit needs a new helper
or something like it. For qdiscs I guess we could just expose
qdisc_watchdog()? For XDP we'd need something new...
quoted
- The behaviour is defined entirely by BPF program behaviour, and does
not require setting up a qdisc hierarchy in addition to writing BPF
code.
I have no idea why you call this a benefit, because my goal is to
replace Qdisc's, not to replace any other things. You know there are
plenty of Qdisc's which are not implemented in Linux kernel.
It's a benefit because it means you can keep everything together. I.e.,
you don't need to *both* write BPF code implementing your qdisc, *and* a
setup script to build the qdisc hierarchy. That simplifies deployment.
I suppose we could support inserting BPF qdiscs into a qdisc hierarchy
as well if needed. I don't personally see much use for that, but if
there's a use case, sure, why not?
quoted
- It should be possible to structure the hooks in a way that allows
reusing queueing algorithm implementations between the qdisc and XDP
layers.
XDP has no skb but xdp_buff, no? And again, why only queues?
From: Martin KaFai Lau <hidden> Date: 2021-09-10 06:55:47
On Fri, Sep 03, 2021 at 04:44:04PM +0200, Toke Høiland-Jørgensen wrote:
Martin KaFai Lau [off-list ref] writes:
quoted
On Fri, Sep 03, 2021 at 12:27:52AM +0200, Toke Høiland-Jørgensen wrote:
quoted
quoted
quoted
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
Instead of bpftool can only introspect bpf qdisc and the existing tc
can only introspect kernel qdisc, it will be nice to have bpf
qdisc work as other qdisc and showing details together with others
in tc. e.g. a bpf qdisc export its data/stats with its btf-id
to tc and have tc print it out in a generic way?
I'm not opposed to the idea, certainly. I just wonder if people who go
to the trouble of writing a custom qdisc in BPF will feel it's worth it
to do the extra work to make this available via a second API. We could
certainly encourage it, and some things are easy (drop and pkt counters,
etc), but other things (like class stats) will depend on the semantics
of the qdisc being implemented, so will require extra work from the BPF
qdisc developer...
Right, different qdisc has different stats, I think it is currently
stored in qdisc_priv()? When a qdisc is created, a separate priv is
created together.
Yes, the bpf qdisc prog can store its stats to a bpf map, but then when the
same prog attached to different qdiscs, it has to create different stats maps?
Also, instead of ->enqueue() itself is a bpf prog,
having an ->enqueue() preparing a bpf ctx (zeroing, assigning...etc) and
then make another call to a bpf prog will all add some costs.
That said, I still think it needs a bpf skb map that can queue/dequeue
skb first. Then it will become possible to prototype different interface
ideas.
On Fri, Sep 03, 2021 at 04:44:04PM +0200, Toke Høiland-Jørgensen wrote:
quoted
Martin KaFai Lau [off-list ref] writes:
quoted
On Fri, Sep 03, 2021 at 12:27:52AM +0200, Toke Høiland-Jørgensen wrote:
quoted
quoted
quoted
The question is if it's useful to provide the full struct_ops for
qdiscs? Having it would allow a BPF program to implement that interface
towards userspace (things like statistics, classes etc), but the
question is if anyone is going to bother with that given the wealth of
BPF-specific introspection tools already available?
Instead of bpftool can only introspect bpf qdisc and the existing tc
can only introspect kernel qdisc, it will be nice to have bpf
qdisc work as other qdisc and showing details together with others
in tc. e.g. a bpf qdisc export its data/stats with its btf-id
to tc and have tc print it out in a generic way?
I'm not opposed to the idea, certainly. I just wonder if people who go
to the trouble of writing a custom qdisc in BPF will feel it's worth it
to do the extra work to make this available via a second API. We could
certainly encourage it, and some things are easy (drop and pkt counters,
etc), but other things (like class stats) will depend on the semantics
of the qdisc being implemented, so will require extra work from the BPF
qdisc developer...
Right, different qdisc has different stats, I think it is currently
stored in qdisc_priv()? When a qdisc is created, a separate priv is
created together.
Yes, the bpf qdisc prog can store its stats to a bpf map, but then
when the same prog attached to different qdiscs, it has to create
different stats maps?
Hmm, yeah, I guess it would. But if it's storing the packets in a map it
would need to have separate instances of those as well. I was kinda
assuming that a separate instance of the BPF program would be loaded
into the kernel for each qdisc instance, with its own instance of all
maps etc.
Also, instead of ->enqueue() itself is a bpf prog, having an
->enqueue() preparing a bpf ctx (zeroing, assigning...etc) and then
make another call to a bpf prog will all add some costs.
Hmm, yeah, I guess, but I kinda doubt we can avoid having *some* kind of
setup to get the right semantics for the BPF program, which might as
well be in the qdisc enqueue() func. But let's see, happy to be proved
wrong on this :)
That said, I still think it needs a bpf skb map that can queue/dequeue
skb first. Then it will become possible to prototype different interface
ideas.
From: Martin KaFai Lau <hidden> Date: 2021-09-17 04:19:13
On Fri, Sep 03, 2021 at 06:09:46PM -0700, Cong Wang wrote:
On Wed, Sep 1, 2021 at 10:45 AM Martin KaFai Lau [off-list ref] wrote:
quoted
_if_ it is using as a qdisc object/interface,
the patch "looks" easier because it obscures some of the ops/interface
from the bpf user. The user will eventually ask for more flexibility
and then an on-par interface as the kernel's qdisc. If there are some
common 'ops', the common bpf code can be shared as a library in userspace
or there is also kfunc call to call into the kernel implementation.
For existing kernel qdisc author, it will be easier to use the same
interface also.
Thanks for showing the advantages of a kernel module. And no, we
are not writing kernel modules in eBPF.
The line is very blurry between a bpf_prog and kernel module,
especially with the advancement of bpf, btf, and CO-RE.
Both bpf_prog.o (struct_ops or not) and some_native_kern_mod.ko are attaching
to some kernel hooks to be called. If writing bpf and attaching it to a hook
does not work for you, bpf does not fit your case.
And kfunc call really sucks, it does not even guarantee a stable ABI, it
is a serious mistake you made for eBPF.
Not ture. It depends on what is allowed to be called by bpf.
Needless to say I cannot agree with the "sucks" description.
This kind of dismissive discussion is worse than unproductive
and not the best way to use the mailing list time.