| // SPDX-License-Identifier: GPL-2.0-only |
| /* Kernel module to match the bridge port in and |
| * out device for IP packets coming into contact with a bridge. */ |
| |
| /* (C) 2001-2003 Bart De Schuymer <bdschuym@pandora.be> |
| */ |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/if.h> |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/netfilter_bridge.h> |
| #include <linux/netfilter/x_tables.h> |
| #include <uapi/linux/netfilter/xt_physdev.h> |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Bart De Schuymer <bdschuym@pandora.be>"); |
| MODULE_DESCRIPTION("Xtables: Bridge physical device match"); |
| MODULE_ALIAS("ipt_physdev"); |
| MODULE_ALIAS("ip6t_physdev"); |
| |
| |
| static bool |
| physdev_mt(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| const struct xt_physdev_info *info = par->matchinfo; |
| const struct net_device *physdev; |
| unsigned long ret; |
| const char *indev, *outdev; |
| |
| /* Not a bridged IP packet or no info available yet: |
| * LOCAL_OUT/mangle and LOCAL_OUT/nat don't know if |
| * the destination device will be a bridge. */ |
| if (!nf_bridge_info_exists(skb)) { |
| /* Return MATCH if the invert flags of the used options are on */ |
| if ((info->bitmask & XT_PHYSDEV_OP_BRIDGED) && |
| !(info->invert & XT_PHYSDEV_OP_BRIDGED)) |
| return false; |
| if ((info->bitmask & XT_PHYSDEV_OP_ISIN) && |
| !(info->invert & XT_PHYSDEV_OP_ISIN)) |
| return false; |
| if ((info->bitmask & XT_PHYSDEV_OP_ISOUT) && |
| !(info->invert & XT_PHYSDEV_OP_ISOUT)) |
| return false; |
| if ((info->bitmask & XT_PHYSDEV_OP_IN) && |
| !(info->invert & XT_PHYSDEV_OP_IN)) |
| return false; |
| if ((info->bitmask & XT_PHYSDEV_OP_OUT) && |
| !(info->invert & XT_PHYSDEV_OP_OUT)) |
| return false; |
| return true; |
| } |
| |
| physdev = nf_bridge_get_physoutdev(skb); |
| outdev = physdev ? physdev->name : NULL; |
| |
| /* This only makes sense in the FORWARD and POSTROUTING chains */ |
| if ((info->bitmask & XT_PHYSDEV_OP_BRIDGED) && |
| (!!outdev ^ !(info->invert & XT_PHYSDEV_OP_BRIDGED))) |
| return false; |
| |
| physdev = nf_bridge_get_physindev(skb); |
| indev = physdev ? physdev->name : NULL; |
| |
| if ((info->bitmask & XT_PHYSDEV_OP_ISIN && |
| (!indev ^ !!(info->invert & XT_PHYSDEV_OP_ISIN))) || |
| (info->bitmask & XT_PHYSDEV_OP_ISOUT && |
| (!outdev ^ !!(info->invert & XT_PHYSDEV_OP_ISOUT)))) |
| return false; |
| |
| if (!(info->bitmask & XT_PHYSDEV_OP_IN)) |
| goto match_outdev; |
| |
| if (indev) { |
| ret = ifname_compare_aligned(indev, info->physindev, |
| info->in_mask); |
| |
| if (!ret ^ !(info->invert & XT_PHYSDEV_OP_IN)) |
| return false; |
| } |
| |
| match_outdev: |
| if (!(info->bitmask & XT_PHYSDEV_OP_OUT)) |
| return true; |
| |
| if (!outdev) |
| return false; |
| |
| ret = ifname_compare_aligned(outdev, info->physoutdev, info->out_mask); |
| |
| return (!!ret ^ !(info->invert & XT_PHYSDEV_OP_OUT)); |
| } |
| |
| static int physdev_mt_check(const struct xt_mtchk_param *par) |
| { |
| const struct xt_physdev_info *info = par->matchinfo; |
| static bool brnf_probed __read_mostly; |
| |
| if (!(info->bitmask & XT_PHYSDEV_OP_MASK) || |
| info->bitmask & ~XT_PHYSDEV_OP_MASK) |
| return -EINVAL; |
| if (info->bitmask & (XT_PHYSDEV_OP_OUT | XT_PHYSDEV_OP_ISOUT) && |
| (!(info->bitmask & XT_PHYSDEV_OP_BRIDGED) || |
| info->invert & XT_PHYSDEV_OP_BRIDGED) && |
| par->hook_mask & (1 << NF_INET_LOCAL_OUT)) { |
| pr_info_ratelimited("--physdev-out and --physdev-is-out only supported in the FORWARD and POSTROUTING chains with bridged traffic\n"); |
| return -EINVAL; |
| } |
| |
| if (!brnf_probed) { |
| brnf_probed = true; |
| request_module("br_netfilter"); |
| } |
| |
| return 0; |
| } |
| |
| static struct xt_match physdev_mt_reg __read_mostly = { |
| .name = "physdev", |
| .revision = 0, |
| .family = NFPROTO_UNSPEC, |
| .checkentry = physdev_mt_check, |
| .match = physdev_mt, |
| .matchsize = sizeof(struct xt_physdev_info), |
| .me = THIS_MODULE, |
| }; |
| |
| static int __init physdev_mt_init(void) |
| { |
| return xt_register_match(&physdev_mt_reg); |
| } |
| |
| static void __exit physdev_mt_exit(void) |
| { |
| xt_unregister_match(&physdev_mt_reg); |
| } |
| |
| module_init(physdev_mt_init); |
| module_exit(physdev_mt_exit); |