diff mbox series

[net-next,v5,17/25] ovpn: implement keepalive mechanism

Message ID 20240627130843.21042-18-antonio@openvpn.net (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series Introducing OpenVPN Data Channel Offload | expand

Checks

Context Check Description
netdev/series_format fail Series longer than 15 patches
netdev/tree_selection success Clearly marked for net-next, async
netdev/ynl success Generated files up to date; no warnings/errors; GEN HAS DIFF 2 files changed, 2612 insertions(+);
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 success Errors and warnings before: 842 this patch: 842
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 1 maintainers not CCed: openvpn-devel@lists.sourceforge.net
netdev/build_clang success Errors and warnings before: 849 this patch: 849
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 success Errors and warnings before: 846 this patch: 846
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 270 lines checked
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
netdev/contest success net-next-2024-06-28--06-00 (tests: 666)

Commit Message

Antonio Quartulli June 27, 2024, 1:08 p.m. UTC
OpenVPN supports configuring a periodic keepalive "ping"
message to allow the remote endpoint detect link failures.

This change implements the ping sending and timer expiring logic.

Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
 drivers/net/ovpn/io.c   | 82 +++++++++++++++++++++++++++++++++++++++++
 drivers/net/ovpn/io.h   |  1 +
 drivers/net/ovpn/peer.c | 72 ++++++++++++++++++++++++++++++++++++
 drivers/net/ovpn/peer.h | 45 ++++++++++++++++++++++
 4 files changed, 200 insertions(+)

Comments

Sabrina Dubroca July 15, 2024, 2:44 p.m. UTC | #1
2024-06-27, 15:08:35 +0200, Antonio Quartulli wrote:
> +static const unsigned char ovpn_keepalive_message[] = {
> +	0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
> +	0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
> +};
> +
> +/**
> + * ovpn_is_keepalive - check if skb contains a keepalive message
> + * @skb: packet to check
> + *
> + * Assumes that the first byte of skb->data is defined.
> + *
> + * Return: true if skb contains a keepalive or false otherwise
> + */
> +static bool ovpn_is_keepalive(struct sk_buff *skb)
> +{
> +	if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)

You could use ovpn_keepalive_message[0], and then you wouldn't need
this extra constant.

> +		return false;
> +
> +	if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
> +		return false;
> +
> +	return !memcmp(skb->data, ovpn_keepalive_message,
> +		       sizeof(ovpn_keepalive_message));

Is a packet that contains some extra bytes after the exact keepalive
considered a valid keepalive, or does it need to be the correct
length?

> +}
> +
>  /* Called after decrypt to write the IP packet to the device.
>   * This method is expected to manage/free the skb.
>   */
> @@ -91,6 +116,9 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>  		goto drop;
>  	}
>  
> +	/* note event of authenticated packet received for keepalive */
> +	ovpn_peer_keepalive_recv_reset(peer);
> +
>  	/* point to encapsulated IP packet */
>  	__skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
>  
> @@ -107,6 +135,12 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>  			goto drop;
>  		}
>  
> +		if (ovpn_is_keepalive(skb)) {
> +			netdev_dbg(peer->ovpn->dev,
> +				   "ping received from peer %u\n", peer->id);

That should probably be _ratelimited, but it seems we don't have
_ratelimited variants for the netdev_* helpers.



> +/**
> + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
> + * @peer: peer to send the message to
> + * @data: message content
> + * @len: message length
> + *
> + * Assumes that caller holds a reference to peer
> + */
> +static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
> +			      const unsigned int len)
> +{
> +	struct ovpn_struct *ovpn;
> +	struct sk_buff *skb;
> +
> +	ovpn = peer->ovpn;
> +	if (unlikely(!ovpn))
> +		return;
> +
> +	skb = alloc_skb(256 + len, GFP_ATOMIC);

Where is that 256 coming from?

> +	if (unlikely(!skb))
> +		return;

Failure to send a keepalive should probably have a counter, to help
users troubleshoot why their connection dropped.
(can be done later unless someone insists)


> +	skb_reserve(skb, 128);

And that 128?

> +	skb->priority = TC_PRIO_BESTEFFORT;
> +	memcpy(__skb_put(skb, len), data, len);

nit: that's __skb_put_data

> +	/* increase reference counter when passing peer to sending queue */
> +	if (!ovpn_peer_hold(peer)) {
> +		netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
> +			   __func__);
> +		kfree_skb(skb);
> +		return;
> +	}
> +
> +	ovpn_send(ovpn, skb, peer);
> +}
> +
> +/**
> + * ovpn_keepalive_xmit - send keepalive message to peer
> + * @peer: the peer to send the message to
> + */
> +void ovpn_keepalive_xmit(struct ovpn_peer *peer)
> +{
> +	ovpn_xmit_special(peer, ovpn_keepalive_message,
> +			  sizeof(ovpn_keepalive_message));
> +}

I don't see other users of ovpn_xmit_special in this series, if you
don't have more planned in the future you could drop the extra function.


> +/**
> + * ovpn_peer_expire - timer task for incoming keepialive timeout

typo: s/keepialive/keepalive/



> +/**
> + * ovpn_peer_keepalive_set - configure keepalive values for peer
> + * @peer: the peer to configure
> + * @interval: outgoing keepalive interval
> + * @timeout: incoming keepalive timeout
> + */
> +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
> +{
> +	u32 delta;
> +
> +	netdev_dbg(peer->ovpn->dev,
> +		   "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
> +		   __func__, peer->id, interval, timeout);
> +
> +	peer->keepalive_interval = interval;
> +	if (interval > 0) {
> +		delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
> +		mod_timer(&peer->keepalive_xmit, jiffies + delta);

Maybe something to consider in the future: this could be resetting a
timer that was just about to go off to a somewhat distant time in the
future. Not sure the peer will be happy about that (and not consider
it a timeout).

> +	} else {
> +		timer_delete(&peer->keepalive_xmit);
> +	}
> +
> +	peer->keepalive_timeout = timeout;
> +	if (timeout) {

pedantic nit: inconsistent style with the "interval > 0" test just
above

> +		delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
> +		mod_timer(&peer->keepalive_recv, jiffies + delta);
> +	} else {
> +		timer_delete(&peer->keepalive_recv);
> +	}
> +}
> +

[...]
> +/**
> + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
> + * @peer: peer for which the timeout should be reset
> + *
> + * To be invoked upon reception of an authenticated packet from peer in order
> + * to report valid activity and thus reset the keepalive timeout
> + */
> +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
> +{
> +	u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
> +
> +	if (unlikely(!delta))
> +		return;
> +
> +	mod_timer(&peer->keepalive_recv, jiffies + delta);

This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
each packet. I wonder how well the timer subsystem deals with one
timer getting updated possibly thousands of time per second.
Antonio Quartulli July 17, 2024, 3:30 p.m. UTC | #2
Hi,

On 15/07/2024 16:44, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:35 +0200, Antonio Quartulli wrote:
>> +static const unsigned char ovpn_keepalive_message[] = {
>> +	0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
>> +	0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
>> +};
>> +
>> +/**
>> + * ovpn_is_keepalive - check if skb contains a keepalive message
>> + * @skb: packet to check
>> + *
>> + * Assumes that the first byte of skb->data is defined.
>> + *
>> + * Return: true if skb contains a keepalive or false otherwise
>> + */
>> +static bool ovpn_is_keepalive(struct sk_buff *skb)
>> +{
>> +	if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)
> 
> You could use ovpn_keepalive_message[0], and then you wouldn't need
> this extra constant.

Indeed, shame on me, will do as suggested

> 
>> +		return false;
>> +
>> +	if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
>> +		return false;
>> +
>> +	return !memcmp(skb->data, ovpn_keepalive_message,
>> +		       sizeof(ovpn_keepalive_message));
> 
> Is a packet that contains some extra bytes after the exact keepalive
> considered a valid keepalive, or does it need to be the correct
> length?

I checked the userspace code and it assumes the length of the received 
keepalive message to be the same as the ovpn_keepalive_message array.

So no extra byte expected, otherwise the message is not considered a 
keepalive anymore.

This means I must add an extra check before the memcmp to make sure 
there is no extra data.

Good catch, thanks!

> 
>> +}
>> +
>>   /* Called after decrypt to write the IP packet to the device.
>>    * This method is expected to manage/free the skb.
>>    */
>> @@ -91,6 +116,9 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>>   		goto drop;
>>   	}
>>   
>> +	/* note event of authenticated packet received for keepalive */
>> +	ovpn_peer_keepalive_recv_reset(peer);
>> +
>>   	/* point to encapsulated IP packet */
>>   	__skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
>>   
>> @@ -107,6 +135,12 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>>   			goto drop;
>>   		}
>>   
>> +		if (ovpn_is_keepalive(skb)) {
>> +			netdev_dbg(peer->ovpn->dev,
>> +				   "ping received from peer %u\n", peer->id);
> 
> That should probably be _ratelimited, but it seems we don't have
> _ratelimited variants for the netdev_* helpers.

Right.
I have used the net_*_ratelimited() variants when needed.
Too bad we don't have those.

> 
> 
> 
>> +/**
>> + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
>> + * @peer: peer to send the message to
>> + * @data: message content
>> + * @len: message length
>> + *
>> + * Assumes that caller holds a reference to peer
>> + */
>> +static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
>> +			      const unsigned int len)
>> +{
>> +	struct ovpn_struct *ovpn;
>> +	struct sk_buff *skb;
>> +
>> +	ovpn = peer->ovpn;
>> +	if (unlikely(!ovpn))
>> +		return;
>> +
>> +	skb = alloc_skb(256 + len, GFP_ATOMIC);
> 
> Where is that 256 coming from?

"Reasonable number" which should be enough[tm] to hold the entire packet.

> 
>> +	if (unlikely(!skb))
>> +		return;
> 
> Failure to send a keepalive should probably have a counter, to help
> users troubleshoot why their connection dropped.
> (can be done later unless someone insists)

This will be part of a more sophisticated error counting that I will 
introduce later on.

> 
> 
>> +	skb_reserve(skb, 128);
> 
> And that 128?

same "logic" as 256.

> 
>> +	skb->priority = TC_PRIO_BESTEFFORT;
>> +	memcpy(__skb_put(skb, len), data, len);
> 
> nit: that's __skb_put_data

oh cool, thanks!

> 
>> +	/* increase reference counter when passing peer to sending queue */
>> +	if (!ovpn_peer_hold(peer)) {
>> +		netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
>> +			   __func__);
>> +		kfree_skb(skb);
>> +		return;
>> +	}
>> +
>> +	ovpn_send(ovpn, skb, peer);
>> +}
>> +
>> +/**
>> + * ovpn_keepalive_xmit - send keepalive message to peer
>> + * @peer: the peer to send the message to
>> + */
>> +void ovpn_keepalive_xmit(struct ovpn_peer *peer)
>> +{
>> +	ovpn_xmit_special(peer, ovpn_keepalive_message,
>> +			  sizeof(ovpn_keepalive_message));
>> +}
> 
> I don't see other users of ovpn_xmit_special in this series, if you
> don't have more planned in the future you could drop the extra function.

initially there were plans, but I have always fought back any idea about 
adding more unnecessary logic to the kernel side. So for now there is 
nothing planned.
I'll remove the extra wrapper.

> 
> 
>> +/**
>> + * ovpn_peer_expire - timer task for incoming keepialive timeout
> 
> typo: s/keepialive/keepalive/

Thanks

> 
> 
> 
>> +/**
>> + * ovpn_peer_keepalive_set - configure keepalive values for peer
>> + * @peer: the peer to configure
>> + * @interval: outgoing keepalive interval
>> + * @timeout: incoming keepalive timeout
>> + */
>> +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
>> +{
>> +	u32 delta;
>> +
>> +	netdev_dbg(peer->ovpn->dev,
>> +		   "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
>> +		   __func__, peer->id, interval, timeout);
>> +
>> +	peer->keepalive_interval = interval;
>> +	if (interval > 0) {
>> +		delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
>> +		mod_timer(&peer->keepalive_xmit, jiffies + delta);
> 
> Maybe something to consider in the future: this could be resetting a
> timer that was just about to go off to a somewhat distant time in the
> future. Not sure the peer will be happy about that (and not consider
> it a timeout).

Normally this timer is only set upon connection, or maybe upon some 
future parameter exchange. In both cases we can assume the connection is 
alive, so this case should not scare us.

But thanks for pointing it out

> 
>> +	} else {
>> +		timer_delete(&peer->keepalive_xmit);
>> +	}
>> +
>> +	peer->keepalive_timeout = timeout;
>> +	if (timeout) {
> 
> pedantic nit: inconsistent style with the "interval > 0" test just
> above

ACK, will make them uniform.

> 
>> +		delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
>> +		mod_timer(&peer->keepalive_recv, jiffies + delta);
>> +	} else {
>> +		timer_delete(&peer->keepalive_recv);
>> +	}
>> +}
>> +
> 
> [...]
>> +/**
>> + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
>> + * @peer: peer for which the timeout should be reset
>> + *
>> + * To be invoked upon reception of an authenticated packet from peer in order
>> + * to report valid activity and thus reset the keepalive timeout
>> + */
>> +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
>> +{
>> +	u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
>> +
>> +	if (unlikely(!delta))
>> +		return;
>> +
>> +	mod_timer(&peer->keepalive_recv, jiffies + delta);
> 
> This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
> each packet. I wonder how well the timer subsystem deals with one
> timer getting updated possibly thousands of time per second.
> 

May it even introduce some performance penalty?

Maybe we should get rid of the timer object and introduce a periodic 
(1s) worker which checks some last_recv timestamp on every known peer?
What do you think?


Thanks!
Eyal Birger July 17, 2024, 4:19 p.m. UTC | #3
Hi,

On Wed, Jul 17, 2024 at 8:29 AM Antonio Quartulli <antonio@openvpn.net> wrote:
>
> On 15/07/2024 16:44, Sabrina Dubroca wrote:

> > This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
> > each packet. I wonder how well the timer subsystem deals with one
> > timer getting updated possibly thousands of time per second.
> >
>
> May it even introduce some performance penalty?
>
> Maybe we should get rid of the timer object and introduce a periodic
> (1s) worker which checks some last_recv timestamp on every known peer?
> What do you think?

FWIW In NATT keepalive for IPsec the first RFC was using timers and
the workqueue
approach was suggested [1], and later implemented [2].
The code is currently in net-next.

Eyal.

[1] https://linux-ipsec.org/pipermail/devel/2023/000283.html
[2] https://patchwork.kernel.org/project/netdevbpf/patch/20240528032914.2551267-1-eyal.birger@gmail.com/
Sabrina Dubroca July 17, 2024, 8:40 p.m. UTC | #4
2024-07-17, 17:30:17 +0200, Antonio Quartulli wrote:
> Hi,
> 
> On 15/07/2024 16:44, Sabrina Dubroca wrote:
> > 2024-06-27, 15:08:35 +0200, Antonio Quartulli wrote:
> > > +/**
> > > + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
> > > + * @peer: peer to send the message to
> > > + * @data: message content
> > > + * @len: message length
> > > + *
> > > + * Assumes that caller holds a reference to peer
> > > + */
> > > +static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
> > > +			      const unsigned int len)
> > > +{
> > > +	struct ovpn_struct *ovpn;
> > > +	struct sk_buff *skb;
> > > +
> > > +	ovpn = peer->ovpn;
> > > +	if (unlikely(!ovpn))
> > > +		return;
> > > +
> > > +	skb = alloc_skb(256 + len, GFP_ATOMIC);
> > 
> > Where is that 256 coming from?
> 
> "Reasonable number" which should be enough[tm] to hold the entire packet.

Ok, let's go with that for now, unless someone else wants you to
change it.

> > > +	if (unlikely(!skb))
> > > +		return;
> > 
> > Failure to send a keepalive should probably have a counter, to help
> > users troubleshoot why their connection dropped.
> > (can be done later unless someone insists)
> 
> This will be part of a more sophisticated error counting that I will
> introduce later on.

Cool, thanks.


> > > +/**
> > > + * ovpn_peer_keepalive_set - configure keepalive values for peer
> > > + * @peer: the peer to configure
> > > + * @interval: outgoing keepalive interval
> > > + * @timeout: incoming keepalive timeout
> > > + */
> > > +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
> > > +{
> > > +	u32 delta;
> > > +
> > > +	netdev_dbg(peer->ovpn->dev,
> > > +		   "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
> > > +		   __func__, peer->id, interval, timeout);
> > > +
> > > +	peer->keepalive_interval = interval;
> > > +	if (interval > 0) {
> > > +		delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
> > > +		mod_timer(&peer->keepalive_xmit, jiffies + delta);
> > 
> > Maybe something to consider in the future: this could be resetting a
> > timer that was just about to go off to a somewhat distant time in the
> > future. Not sure the peer will be happy about that (and not consider
> > it a timeout).
> 
> Normally this timer is only set upon connection, or maybe upon some future
> parameter exchange. In both cases we can assume the connection is alive, so
> this case should not scare us.
> 
> But thanks for pointing it out

Ok, I was thinking about updates while the connection is fully
established. If it's only done during setup, it shouldn't be a
problem.


> > > +/**
> > > + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
> > > + * @peer: peer for which the timeout should be reset
> > > + *
> > > + * To be invoked upon reception of an authenticated packet from peer in order
> > > + * to report valid activity and thus reset the keepalive timeout
> > > + */
> > > +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
> > > +{
> > > +	u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
> > > +
> > > +	if (unlikely(!delta))
> > > +		return;
> > > +
> > > +	mod_timer(&peer->keepalive_recv, jiffies + delta);
> > 
> > This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
> > each packet. I wonder how well the timer subsystem deals with one
> > timer getting updated possibly thousands of time per second.
> > 
> 
> May it even introduce some performance penalty?

That's what I was worried about, yes.

I asked Paolo, he suggested checking that we're actually doing any
change to the timer:

   if (new_timeout_time != old_timeout_time)
       mod_timer(...)

This would reduce the update frequency to one per jiffy, which should
be acceptable.

> Maybe we should get rid of the timer object and introduce a periodic (1s)
> worker which checks some last_recv timestamp on every known peer?
> What do you think?

That should work, or the workqueue like Eyal is saying.
Andrew Lunn July 18, 2024, 2:01 a.m. UTC | #5
> > > +		if (ovpn_is_keepalive(skb)) {
> > > +			netdev_dbg(peer->ovpn->dev,
> > > +				   "ping received from peer %u\n", peer->id);
> > 
> > That should probably be _ratelimited, but it seems we don't have
> > _ratelimited variants for the netdev_* helpers.
> 
> Right.
> I have used the net_*_ratelimited() variants when needed.
> Too bad we don't have those.

If you think netdev_dbg_ratelimited() would be useful, i don't see why
you cannot add it.

I just did an search and found something interesting in the history:

https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u

Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
abuse DaveM was worried about.

    Andrew
Antonio Quartulli July 18, 2024, 7:46 a.m. UTC | #6
On 18/07/2024 04:01, Andrew Lunn wrote:
>>>> +		if (ovpn_is_keepalive(skb)) {
>>>> +			netdev_dbg(peer->ovpn->dev,
>>>> +				   "ping received from peer %u\n", peer->id);
>>>
>>> That should probably be _ratelimited, but it seems we don't have
>>> _ratelimited variants for the netdev_* helpers.
>>
>> Right.
>> I have used the net_*_ratelimited() variants when needed.
>> Too bad we don't have those.
> 
> If you think netdev_dbg_ratelimited() would be useful, i don't see why
> you cannot add it.
> 
> I just did an search and found something interesting in the history:
> 
> https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
> 
> Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
> abuse DaveM was worried about.

I see what Dave says however...

...along the packet processing routine there are several messages (some 
are err or warn or info) which require ratelimiting.
Otherwise you end up with a gazilion log entries in case of a long 
lasting issue.

Right now I am using net_dbg/warn/err/info_ratelimited(), therefore not 
having a netdev counterpart is not really helping with Dave's argument.

I can try to take on this mission after this patchset is in and see what 
Dave/Jakub think about it.


Regards,
Antonio Quartulli July 18, 2024, 8:20 a.m. UTC | #7
Hi Eyal,

thanks a lot for chiming in.

On 17/07/2024 18:19, Eyal Birger wrote:
> Hi,
> 
> On Wed, Jul 17, 2024 at 8:29 AM Antonio Quartulli <antonio@openvpn.net> wrote:
>>
>> On 15/07/2024 16:44, Sabrina Dubroca wrote:
> 
>>> This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
>>> each packet. I wonder how well the timer subsystem deals with one
>>> timer getting updated possibly thousands of time per second.
>>>
>>
>> May it even introduce some performance penalty?
>>
>> Maybe we should get rid of the timer object and introduce a periodic
>> (1s) worker which checks some last_recv timestamp on every known peer?
>> What do you think?
> 
> FWIW In NATT keepalive for IPsec the first RFC was using timers and
> the workqueue
> approach was suggested [1], and later implemented [2].
> The code is currently in net-next.
> 
> Eyal.
> 
> [1] https://linux-ipsec.org/pipermail/devel/2023/000283.html
> [2] https://patchwork.kernel.org/project/netdevbpf/patch/20240528032914.2551267-1-eyal.birger@gmail.com/

Thanks for these pointers.

Basically this is pretty much what I had in mind, but rather than 
running the worker every second, the next run is scheduled based on the 
closest expiration time.

I like it!

Since this worker has no other housekeeping to do, I will go with this 
approach as well.


Thanks!
Regards,
Antonio Quartulli July 18, 2024, 8:22 a.m. UTC | #8
On 17/07/2024 22:40, Sabrina Dubroca wrote:
>>>> +/**
>>>> + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
>>>> + * @peer: peer for which the timeout should be reset
>>>> + *
>>>> + * To be invoked upon reception of an authenticated packet from peer in order
>>>> + * to report valid activity and thus reset the keepalive timeout
>>>> + */
>>>> +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
>>>> +{
>>>> +	u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
>>>> +
>>>> +	if (unlikely(!delta))
>>>> +		return;
>>>> +
>>>> +	mod_timer(&peer->keepalive_recv, jiffies + delta);
>>>
>>> This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
>>> each packet. I wonder how well the timer subsystem deals with one
>>> timer getting updated possibly thousands of time per second.
>>>
>>
>> May it even introduce some performance penalty?
> 
> That's what I was worried about, yes.
> 
> I asked Paolo, he suggested checking that we're actually doing any
> change to the timer:
> 
>     if (new_timeout_time != old_timeout_time)
>         mod_timer(...)
> 
> This would reduce the update frequency to one per jiffy, which should
> be acceptable.
> 
>> Maybe we should get rid of the timer object and introduce a periodic (1s)
>> worker which checks some last_recv timestamp on every known peer?
>> What do you think?
> 
> That should work, or the workqueue like Eyal is saying.

I will go with Eyal's approach.
This way we also eliminate any timer related operation from the fast path.


Cheers!
Andrew Lunn July 19, 2024, 3:31 a.m. UTC | #9
On Thu, Jul 18, 2024 at 09:46:00AM +0200, Antonio Quartulli wrote:
> On 18/07/2024 04:01, Andrew Lunn wrote:
> > > > > +		if (ovpn_is_keepalive(skb)) {
> > > > > +			netdev_dbg(peer->ovpn->dev,
> > > > > +				   "ping received from peer %u\n", peer->id);
> > > > 
> > > > That should probably be _ratelimited, but it seems we don't have
> > > > _ratelimited variants for the netdev_* helpers.
> > > 
> > > Right.
> > > I have used the net_*_ratelimited() variants when needed.
> > > Too bad we don't have those.
> > 
> > If you think netdev_dbg_ratelimited() would be useful, i don't see why
> > you cannot add it.
> > 
> > I just did an search and found something interesting in the history:
> > 
> > https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
> > 
> > Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
> > abuse DaveM was worried about.
> 
> I see what Dave says however...
> 
> ...along the packet processing routine there are several messages (some are
> err or warn or info) which require ratelimiting.
> Otherwise you end up with a gazilion log entries in case of a long lasting
> issue.
> 
> Right now I am using net_dbg/warn/err/info_ratelimited(), therefore not
> having a netdev counterpart is not really helping with Dave's argument.

Yes, i think Dave' argument is weak because these alternatives
exist. Maybe they did not at the time? 

I suspect he was using it as a way to force fixing the real issue. A
driver should not be issues lots of err or info messages. Protocol
errors are part of normal behaviour, just increment a counter and keep
going. Peer devices disappearing is normal behaviour, count it and
keep going. _err is generally reserved for something fatal happened,
and there is no recovery, other than unload the kernel module and
reload it.

	Andrew
Antonio Quartulli July 19, 2024, 8:59 a.m. UTC | #10
On 19/07/2024 05:31, Andrew Lunn wrote:
> On Thu, Jul 18, 2024 at 09:46:00AM +0200, Antonio Quartulli wrote:
>> On 18/07/2024 04:01, Andrew Lunn wrote:
>>>>>> +		if (ovpn_is_keepalive(skb)) {
>>>>>> +			netdev_dbg(peer->ovpn->dev,
>>>>>> +				   "ping received from peer %u\n", peer->id);
>>>>>
>>>>> That should probably be _ratelimited, but it seems we don't have
>>>>> _ratelimited variants for the netdev_* helpers.
>>>>
>>>> Right.
>>>> I have used the net_*_ratelimited() variants when needed.
>>>> Too bad we don't have those.
>>>
>>> If you think netdev_dbg_ratelimited() would be useful, i don't see why
>>> you cannot add it.
>>>
>>> I just did an search and found something interesting in the history:
>>>
>>> https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
>>>
>>> Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
>>> abuse DaveM was worried about.
>>
>> I see what Dave says however...
>>
>> ...along the packet processing routine there are several messages (some are
>> err or warn or info) which require ratelimiting.
>> Otherwise you end up with a gazilion log entries in case of a long lasting
>> issue.
>>
>> Right now I am using net_dbg/warn/err/info_ratelimited(), therefore not
>> having a netdev counterpart is not really helping with Dave's argument.
> 
> Yes, i think Dave' argument is weak because these alternatives
> exist. Maybe they did not at the time?

They did and it's exactly what Hangbin was introducing:

https://lore.kernel.org/all/20190801090347.8258-1-liuhangbin@gmail.com/

before being suggested by Joe to implement the new macros:

https://lore.kernel.org/all/209f7fe62e2a79cd8c02b104b8e3babdd16bff30.camel@perches.com/

> 
> I suspect he was using it as a way to force fixing the real issue. A
> driver should not be issues lots of err or info messages. Protocol
> errors are part of normal behaviour, just increment a counter and keep
> going. Peer devices disappearing is normal behaviour, count it and
> keep going. _err is generally reserved for something fatal happened,
> and there is no recovery, other than unload the kernel module and
> reload it.

Yeah, it looks like it and David pushed them to rethink the error 
handling rather than making the error less painful.

But I can still netdev_dbg_ratelimited useful for debugging/development.
Anyway, I will add this to my todo and see if this specific variant can 
be accepted.

Regards,
diff mbox series

Patch

diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 764b3df996bc..dedc4d4a1c72 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -25,6 +25,31 @@ 
 #include "udp.h"
 #include "skb.h"
 
+static const unsigned char ovpn_keepalive_message[] = {
+	0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
+	0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
+};
+
+/**
+ * ovpn_is_keepalive - check if skb contains a keepalive message
+ * @skb: packet to check
+ *
+ * Assumes that the first byte of skb->data is defined.
+ *
+ * Return: true if skb contains a keepalive or false otherwise
+ */
+static bool ovpn_is_keepalive(struct sk_buff *skb)
+{
+	if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)
+		return false;
+
+	if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
+		return false;
+
+	return !memcmp(skb->data, ovpn_keepalive_message,
+		       sizeof(ovpn_keepalive_message));
+}
+
 /* Called after decrypt to write the IP packet to the device.
  * This method is expected to manage/free the skb.
  */
@@ -91,6 +116,9 @@  void ovpn_decrypt_post(struct sk_buff *skb, int ret)
 		goto drop;
 	}
 
+	/* note event of authenticated packet received for keepalive */
+	ovpn_peer_keepalive_recv_reset(peer);
+
 	/* point to encapsulated IP packet */
 	__skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
 
@@ -107,6 +135,12 @@  void ovpn_decrypt_post(struct sk_buff *skb, int ret)
 			goto drop;
 		}
 
+		if (ovpn_is_keepalive(skb)) {
+			netdev_dbg(peer->ovpn->dev,
+				   "ping received from peer %u\n", peer->id);
+			goto drop;
+		}
+
 		net_info_ratelimited("%s: unsupported protocol received from peer %u\n",
 				     peer->ovpn->dev->name, peer->id);
 		goto drop;
@@ -203,6 +237,7 @@  void ovpn_encrypt_post(struct sk_buff *skb, int ret)
 		/* no transport configured yet */
 		goto err;
 	}
+	ovpn_peer_keepalive_xmit_reset(peer);
 	/* skb passed down the stack - don't free it */
 	skb = NULL;
 err:
@@ -345,3 +380,50 @@  netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
 	kfree_skb_list(skb);
 	return NET_XMIT_DROP;
 }
+
+/**
+ * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
+ * @peer: peer to send the message to
+ * @data: message content
+ * @len: message length
+ *
+ * Assumes that caller holds a reference to peer
+ */
+static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
+			      const unsigned int len)
+{
+	struct ovpn_struct *ovpn;
+	struct sk_buff *skb;
+
+	ovpn = peer->ovpn;
+	if (unlikely(!ovpn))
+		return;
+
+	skb = alloc_skb(256 + len, GFP_ATOMIC);
+	if (unlikely(!skb))
+		return;
+
+	skb_reserve(skb, 128);
+	skb->priority = TC_PRIO_BESTEFFORT;
+	memcpy(__skb_put(skb, len), data, len);
+
+	/* increase reference counter when passing peer to sending queue */
+	if (!ovpn_peer_hold(peer)) {
+		netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
+			   __func__);
+		kfree_skb(skb);
+		return;
+	}
+
+	ovpn_send(ovpn, skb, peer);
+}
+
+/**
+ * ovpn_keepalive_xmit - send keepalive message to peer
+ * @peer: the peer to send the message to
+ */
+void ovpn_keepalive_xmit(struct ovpn_peer *peer)
+{
+	ovpn_xmit_special(peer, ovpn_keepalive_message,
+			  sizeof(ovpn_keepalive_message));
+}
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
index ad741552d742..63fcd576ddaf 100644
--- a/drivers/net/ovpn/io.h
+++ b/drivers/net/ovpn/io.h
@@ -13,6 +13,7 @@ 
 netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
 
 void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb);
+void ovpn_keepalive_xmit(struct ovpn_peer *peer);
 
 void ovpn_encrypt_post(struct sk_buff *skb, int ret);
 void ovpn_decrypt_post(struct sk_buff *skb, int ret);
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 8c23268db916..289fe3f84ed4 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -22,6 +22,63 @@ 
 #include "peer.h"
 #include "socket.h"
 
+/**
+ * ovpn_peer_ping - timer task for sending periodic keepalive
+ * @t: timer object that triggered the task
+ */
+static void ovpn_peer_ping(struct timer_list *t)
+{
+	struct ovpn_peer *peer = from_timer(peer, t, keepalive_xmit);
+
+	netdev_dbg(peer->ovpn->dev, "%s: sending ping to peer %u\n", __func__,
+		   peer->id);
+	ovpn_keepalive_xmit(peer);
+}
+
+/**
+ * ovpn_peer_expire - timer task for incoming keepialive timeout
+ * @t: the timer that triggered the task
+ */
+static void ovpn_peer_expire(struct timer_list *t)
+{
+	struct ovpn_peer *peer = from_timer(peer, t, keepalive_recv);
+
+	netdev_dbg(peer->ovpn->dev, "%s: peer %u expired\n", __func__,
+		   peer->id);
+	ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_EXPIRED);
+}
+
+/**
+ * ovpn_peer_keepalive_set - configure keepalive values for peer
+ * @peer: the peer to configure
+ * @interval: outgoing keepalive interval
+ * @timeout: incoming keepalive timeout
+ */
+void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
+{
+	u32 delta;
+
+	netdev_dbg(peer->ovpn->dev,
+		   "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
+		   __func__, peer->id, interval, timeout);
+
+	peer->keepalive_interval = interval;
+	if (interval > 0) {
+		delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
+		mod_timer(&peer->keepalive_xmit, jiffies + delta);
+	} else {
+		timer_delete(&peer->keepalive_xmit);
+	}
+
+	peer->keepalive_timeout = timeout;
+	if (timeout) {
+		delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
+		mod_timer(&peer->keepalive_recv, jiffies + delta);
+	} else {
+		timer_delete(&peer->keepalive_recv);
+	}
+}
+
 /**
  * ovpn_peer_new - allocate and initialize a new peer object
  * @ovpn: the openvpn instance inside which the peer should be created
@@ -63,9 +120,22 @@  struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
 
 	netdev_hold(ovpn->dev, NULL, GFP_KERNEL);
 
+	timer_setup(&peer->keepalive_xmit, ovpn_peer_ping, 0);
+	timer_setup(&peer->keepalive_recv, ovpn_peer_expire, 0);
+
 	return peer;
 }
 
+/**
+ * ovpn_peer_timer_delete_all - killall keepalive timers
+ * @peer: peer for which timers should be killed
+ */
+static void ovpn_peer_timer_delete_all(struct ovpn_peer *peer)
+{
+	timer_shutdown_sync(&peer->keepalive_xmit);
+	timer_shutdown_sync(&peer->keepalive_recv);
+}
+
 /**
  * ovpn_peer_release - release peer private members
  * @peer: the peer to release
@@ -77,6 +147,8 @@  static void ovpn_peer_release(struct ovpn_peer *peer)
 
 	ovpn_crypto_state_release(&peer->crypto);
 	ovpn_bind_reset(peer, NULL);
+	ovpn_peer_timer_delete_all(peer);
+
 	dst_cache_destroy(&peer->dst_cache);
 }
 
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 6e92e09a3504..fd7e9d57b38a 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -50,6 +50,10 @@ 
  * @crypto: the crypto configuration (ciphers, keys, etc..)
  * @dst_cache: cache for dst_entry used to send to peer
  * @bind: remote peer binding
+ * @keepalive_xmit: timer used to send the next keepalive
+ * @keepalive_interval: seconds after which a new keepalive should be sent
+ * @keepalive_recv: timer used to check for received keepalives
+ * @keepalive_timeout: seconds after which an inactive peer is considered dead
  * @halt: true if ovpn_peer_mark_delete was called
  * @vpn_stats: per-peer in-VPN TX/RX stays
  * @link_stats: per-peer link/transport TX/RX stats
@@ -98,6 +102,10 @@  struct ovpn_peer {
 	struct ovpn_crypto_state crypto;
 	struct dst_cache dst_cache;
 	struct ovpn_bind __rcu *bind;
+	struct timer_list keepalive_xmit;
+	unsigned long keepalive_interval;
+	struct timer_list keepalive_recv;
+	unsigned long keepalive_timeout;
 	bool halt;
 	struct ovpn_peer_stats vpn_stats;
 	struct ovpn_peer_stats link_stats;
@@ -144,4 +152,41 @@  struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
 bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
 			    struct ovpn_peer *peer);
 
+/**
+ * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
+ * @peer: peer for which the timeout should be reset
+ *
+ * To be invoked upon reception of an authenticated packet from peer in order
+ * to report valid activity and thus reset the keepalive timeout
+ */
+static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
+{
+	u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
+
+	if (unlikely(!delta))
+		return;
+
+	mod_timer(&peer->keepalive_recv, jiffies + delta);
+}
+
+/**
+ * ovpn_peer_keepalive_xmit_reset - reset keepalive sending timer
+ * @peer: peer for which the timer should be reset
+ *
+ * To be invoked upon sending of an authenticated packet to peer in order
+ * to report valid outgoing activity and thus reset the keepalive sending
+ * timer
+ */
+static inline void ovpn_peer_keepalive_xmit_reset(struct ovpn_peer *peer)
+{
+	u32 delta = msecs_to_jiffies(peer->keepalive_interval * MSEC_PER_SEC);
+
+	if (unlikely(!delta))
+		return;
+
+	mod_timer(&peer->keepalive_xmit, jiffies + delta);
+}
+
+void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
+
 #endif /* _NET_OVPN_OVPNPEER_H_ */