// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
* Copyright (c) 2014 Intel Corporation
* Author: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
*
* Development of this code funded by Astaro AG (http://www.astaro.com/)
*/
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/random.h>
#include <linux/smp.h>
#include <linux/static_key.h>
#include <net/dst.h>
#include <net/ip.h>
#include <net/sock.h>
#include <net/tcp_states.h> /* for TCP_TIME_WAIT */
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nft_meta.h>
#include <net/netfilter/nf_tables_offload.h>
#include <uapi/linux/netfilter_bridge.h> /* NF_BR_PRE_ROUTING */
#define NFT_META_SECS_PER_MINUTE 60
#define NFT_META_SECS_PER_HOUR 3600
#define NFT_META_SECS_PER_DAY 86400
#define NFT_META_DAYS_PER_WEEK 7
static u8 nft_meta_weekday(void)
{
time64_t secs = ktime_get_real_seconds();
unsigned int dse;
u8 wday;
secs -= NFT_META_SECS_PER_MINUTE * sys_tz.tz_minuteswest;
dse = div_u64(secs, NFT_META_SECS_PER_DAY);
wday = (4 + dse) % NFT_META_DAYS_PER_WEEK;
return wday;
}
static u32 nft_meta_hour(time64_t secs)
{
struct tm tm;
time64_to_tm(secs, 0, &tm);
return tm.tm_hour * NFT_META_SECS_PER_HOUR
+ tm.tm_min * NFT_META_SECS_PER_MINUTE
+ tm.tm_sec;
}
static noinline_for_stack void
nft_meta_get_eval_time(enum nft_meta_keys key,
u32 *dest)
{
switch (key) {
case NFT_META_TIME_NS:
nft_reg_store64(dest, ktime_get_real_ns());
break;
case NFT_META_TIME_DAY:
nft_reg_store8(dest, nft_meta_weekday());
break;
case NFT_META_TIME_HOUR:
*dest = nft_meta_hour(ktime_get_real_seconds());
break;
default:
break;
}
}
static noinline bool
nft_meta_get_eval_pkttype_lo(const struct nft_pktinfo *pkt,
u32 *dest)
{
const struct sk_buff *skb = pkt->skb;
switch (nft_pf(pkt)) {
case NFPROTO_IPV4:
if (ipv4_is_multicast(ip_hdr(skb)->daddr))
nft_reg_store8(dest, PACKET_MULTICAST);
else
nft_reg_store8(dest, PACKET_BROADCAST);
break;
case NFPROTO_IPV6:
nft_reg_store8(dest, PACKET_MULTICAST);
break;
case NFPROTO_NETDEV:
switch (skb->protocol) {
case htons(ETH_P_IP): {
int noff = skb_network_offset(skb);
struct iphdr *iph, _iph;
iph = skb_header_pointer(skb, noff,
sizeof(_iph), &_iph);
if (!iph)
return false;
if (ipv4_is_multicast(iph->daddr))
nft_reg_store8(dest, PACKET_MULTICAST);
else
nft_reg_store8(dest, PACKET_BROADCAST);
break;
}
case htons(ETH_P_IPV6):
nft_reg_store8(dest, PACKET_MULTICAST);
break;
default:
WARN_ON_ONCE(1);
return false;
}
break;
default:
WARN_ON_ONCE(1);
return false;
}
return true;
}
static noinline bool
nft_meta_get_eval_skugid(enum nft_meta_keys key,
u32 *dest,
const struct nft_pktinfo *pkt)
{
struct sock *sk = skb_to_full_sk(pkt->skb);
struct socket *sock;
if (!sk || !sk_fullsock(sk) || !net_eq(nft_net(pkt), sock_net(sk)))
return false;
read_lock_bh(&sk->sk_callback_lock);
sock = sk->sk_socket;
if (!sock || !sock->file) {
read_unlock_bh(&sk->sk_callback_lock);
return false;
}
switch (key) {
case NFT_META_SKUID:
*dest = from_kuid_munged(sock_net(sk)->user_ns,
sock->file->f_cred->fsuid);
break;
case NFT_META_SKGID:
*dest = from_kgid_munged(sock_net(sk)->user_ns,
sock->file->f_cred->fsgid);
break;
default:
break;
}
read_unlock_bh(&sk->sk_callback_lock);
return true;
}
#ifdef CONFIG_CGROUP_NET_CLASSID
static noinline bool
nft_meta_get_eval_cgroup(u32 *dest, const struct nft_pktinfo *pkt)
{
struct sock *sk = skb_to_full_sk(pkt->skb);
if (!sk || !sk_fullsock(sk) || !net_eq(nft_net(pkt), sock_net(sk)))
return false;
*dest = sock_cgroup_classid(&sk->sk_cgrp_data);
return true;
}
#endif
static noinline bool nft_meta_get_eval_kind(enum nft_meta_keys key,
u32 *dest,
const struct nft_pktinfo *pkt)
{
const struct net_device *in = nft_in(pkt), *out = nft_out(pkt);
switch (key) {
case NFT_META_IIFKIND:
if (!in || !in->rtnl_link_ops)
return false;
strncpy((char *)dest, in->rtnl_link_ops->kind, IFNAMSIZ);
break;
case NFT_META_OIFKIND:
if (!out || !out->rtnl_link_ops)
return false;
strncpy((char *)dest, out->rtnl_link_ops->kind, IFNAMSIZ);
break;
default:
return false;
}
return true;
}
static void