Skip to content

Commit

Permalink
bpf,fib: introduce fib_do_redirect function
Browse files Browse the repository at this point in the history
This commit adds a new function, 'fib_do_redirect', to 'lib/fib.h'.

This function decouples the 'bpf_redirect' functionality from the
'bpf_fib_lookup' functionality while keeping the existing 'fib_redirect'
logic the same.

In other words, this function can pickup right after the 'fib_lookup' is
performed in 'fib_redirect' and carry out the same exact operations as
'fib_redirect'.

This will be used in a subsequent commit to decouple the `bpf_fib_lookup`
from the `bpf_redirect`, such that each can be performed independently.

This is an addition-only change and amounts to no functional
changes in the data path.

Signed-off-by: Louis DeLosSantos <louis.delos@isovalent.com>
  • Loading branch information
ldelossa authored and joestringer committed Jun 15, 2023
1 parent 3db8b14 commit 5fff05d
Showing 1 changed file with 128 additions and 8 deletions.
136 changes: 128 additions & 8 deletions bpf/lib/fib.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,134 @@ static __always_inline bool fib_ok(int ret)
return likely(ret == CTX_ACT_TX || ret == CTX_ACT_REDIRECT);
}

/* fib_redirect() is common helper code which performs fib lookup, populates
* the corresponding hardware addresses and pushes the packet to a target
* device for the next hop. Calling fib_redirect_v{4,6} is preferred unless
* due to NAT46x64 struct bpf_fib_lookup_padded needs to be prepared at the
* callsite. oif must be 0 if otherwise not passed in from the BPF CT. The
* needs_l2_check must be true if the packet could transition between L2->L3
* or L3->L2 device.
*/
/* fib_do_redirect will redirect the ctx to a particular output interface.
*
* the redirect can occur with or without a previous call to fib_lookup.
*
* if a previous fib_lookup was performed, this function will attempt to redirect
* to the output interface in the provided 'fib_params', as long as 'fib_ret'
* is set to 'BPF_FIB_LKUP_RET_SUCCESS'
*
* if a previous fib_lookup was performed and the return was 'BPF_FIB_LKUP_NO_NEIGH'
* this function will then attempt to copy the af_family and destination address
* out of 'fib_params' and into 'redir_neigh' struct then perform a
* 'redirect_neigh'.
*
* if no previous fib_lookup was performed, and the desire is to simply use
* 'redirect_neigh' then set 'fib_params' to nil and 'fib_ret' to
* 'BPF_FIB_LKUP_RET_NO_NEIGH'.
* in this case, the 'oif' value will be used for the 'redirect_neigh' call.
*
* in a special case, if a previous fib_lookup was performed, and the return
* was 'BPF_FIB_LKUP_RET_NO_NEIGH', and we are on a kernel version where
* the target interface for the fib lookup is not returned
* (due to ARP failing, see Kernel commit d1c362e1dd68) the provided 'oif'
* will be used as output interface for redirect.
*/
static __always_inline int
fib_do_redirect(struct __ctx_buff *ctx, const bool needs_l2_check,
const struct bpf_fib_lookup_padded *fib_params, __s8 *fib_ret,
int *oif)
{
struct bpf_redir_neigh nh_params;
struct bpf_redir_neigh *nh = NULL;
union macaddr smac = NATIVE_DEV_MAC_BY_IFINDEX(*oif);
union macaddr *dmac = 0;
int ret;

/* sanity check, we only enter this function with these two fib lookup
* return codes.
*/
if (*fib_ret && (*fib_ret != BPF_FIB_LKUP_RET_NO_NEIGH))
return DROP_NO_FIB;

/* determine which oif to use before needs_l2_check determines if layer 2
* header needs to be pushed.
*/
if (fib_params) {
if (*fib_ret == BPF_FIB_LKUP_RET_NO_NEIGH &&
!is_defined(HAVE_FIB_IFINDEX) && *oif) {
/* For kernels without d1c362e1dd68 ("bpf: Always
* return target ifindex in bpf_fib_lookup") we
* fall back to use the caller-provided oif when
* necessary.
* no-op
*/
} else {
*oif = fib_params->l.ifindex;
}
}

/* determine if we need to append layer 2 header */
if (needs_l2_check) {
bool l2_hdr_required = true;

ret = maybe_add_l2_hdr(ctx, *oif, &l2_hdr_required);
if (ret != 0)
return ret;
if (!l2_hdr_required)
goto out_send;
}

/* determine if we are performing redirect or redirect_neigh*/
switch (*fib_ret) {
case BPF_FIB_LKUP_RET_SUCCESS:
if (eth_store_daddr(ctx, fib_params->l.dmac, 0) < 0)
return DROP_WRITE_ERROR;
if (eth_store_saddr(ctx, fib_params->l.smac, 0) < 0)
return DROP_WRITE_ERROR;
break;
case BPF_FIB_LKUP_RET_NO_NEIGH:
/* previous fib lookup was performed, we can fillout both
* a bpf_redir_neigh and a dmac.
*
* the former is used if we have access to redirect_neigh
* the latter is used if we don't and have to use the eBPF
* neighbor map.
*/
if (fib_params) {
nh_params.nh_family = fib_params->l.family;
__bpf_memcpy_builtin(&nh_params.ipv6_nh,
&fib_params->l.ipv6_dst,
sizeof(nh_params.ipv6_nh));
nh = &nh_params;

if (!neigh_resolver_available()) {
/* The neigh_record_ip{4,6} locations are mainly from
* inbound client traffic on the load-balancer where we
* know that replies need to go back to them.
*/
dmac = fib_params->l.family == AF_INET ?
neigh_lookup_ip4(&fib_params->l.ipv4_dst) :
neigh_lookup_ip6((void *)&fib_params->l.ipv6_dst);
}
}

/* If we are able to resolve neighbors on demand, always
* prefer that over the BPF neighbor map since the latter
* might be less accurate in some asymmetric corner cases.
*/
if (neigh_resolver_available()) {
if (nh)
return redirect_neigh(*oif, &nh_params,
sizeof(nh_params), 0);
else
return redirect_neigh(*oif, NULL, 0, 0);
} else {
if (!dmac) {
*fib_ret = BPF_FIB_MAP_NO_NEIGH;
return DROP_NO_FIB;
}
if (eth_store_daddr_aligned(ctx, dmac->addr, 0) < 0)
return DROP_WRITE_ERROR;
if (eth_store_saddr_aligned(ctx, smac.addr, 0) < 0)
return DROP_WRITE_ERROR;
}
};
out_send:
return ctx_redirect(ctx, *oif, 0);
}

static __always_inline int
fib_redirect(struct __ctx_buff *ctx, const bool needs_l2_check,
struct bpf_fib_lookup_padded *fib_params, __s8 *fib_err, int *oif)
Expand Down

0 comments on commit 5fff05d

Please sign in to comment.