diff options
author | Pablo Neira Ayuso <pablo@netfilter.org> | 2008-10-14 14:58:31 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-10-14 14:58:31 -0400 |
commit | e6a7d3c04f8fe49099521e6dc9a46b0272381f2f (patch) | |
tree | 4717fcfe05549d39c9281b3b23310b775ad38d16 | |
parent | 129404a1f117c35c6224e020444fc27eb4479817 (diff) |
netfilter: ctnetlink: remove bogus module dependency between ctnetlink and nf_nat
This patch removes the module dependency between ctnetlink and
nf_nat by means of an indirect call that is initialized when
nf_nat is loaded. Now, nf_conntrack_netlink only requires
nf_conntrack and nfnetlink.
This patch puts nfnetlink_parse_nat_setup_hook into the
nf_conntrack_core to avoid dependencies between ctnetlink,
nf_conntrack_ipv4 and nf_conntrack_ipv6.
This patch also introduces the function ctnetlink_change_nat
that is only invoked from the creation path. Actually, the
nat handling cannot be invoked from the update path since
this is not allowed. By introducing this function, we remove
the useless nat handling in the update path and we avoid
deadlock-prone code.
This patch also adds the required EAGAIN logic for nfnetlink.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/netfilter/nfnetlink.h | 3 | ||||
-rw-r--r-- | include/net/netfilter/nf_nat_core.h | 8 | ||||
-rw-r--r-- | net/ipv4/netfilter/nf_nat_core.c | 97 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_core.c | 7 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_netlink.c | 151 | ||||
-rw-r--r-- | net/netfilter/nfnetlink.c | 12 |
6 files changed, 185 insertions, 93 deletions
diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h index 0d8424f76899..7d8e0455ccac 100644 --- a/include/linux/netfilter/nfnetlink.h +++ b/include/linux/netfilter/nfnetlink.h | |||
@@ -78,6 +78,9 @@ extern int nfnetlink_send(struct sk_buff *skb, u32 pid, unsigned group, | |||
78 | int echo); | 78 | int echo); |
79 | extern int nfnetlink_unicast(struct sk_buff *skb, u_int32_t pid, int flags); | 79 | extern int nfnetlink_unicast(struct sk_buff *skb, u_int32_t pid, int flags); |
80 | 80 | ||
81 | extern void nfnl_lock(void); | ||
82 | extern void nfnl_unlock(void); | ||
83 | |||
81 | #define MODULE_ALIAS_NFNL_SUBSYS(subsys) \ | 84 | #define MODULE_ALIAS_NFNL_SUBSYS(subsys) \ |
82 | MODULE_ALIAS("nfnetlink-subsys-" __stringify(subsys)) | 85 | MODULE_ALIAS("nfnetlink-subsys-" __stringify(subsys)) |
83 | 86 | ||
diff --git a/include/net/netfilter/nf_nat_core.h b/include/net/netfilter/nf_nat_core.h index f29eeb9777e0..58684066388c 100644 --- a/include/net/netfilter/nf_nat_core.h +++ b/include/net/netfilter/nf_nat_core.h | |||
@@ -25,4 +25,12 @@ static inline int nf_nat_initialized(struct nf_conn *ct, | |||
25 | else | 25 | else |
26 | return test_bit(IPS_DST_NAT_DONE_BIT, &ct->status); | 26 | return test_bit(IPS_DST_NAT_DONE_BIT, &ct->status); |
27 | } | 27 | } |
28 | |||
29 | struct nlattr; | ||
30 | |||
31 | extern int | ||
32 | (*nfnetlink_parse_nat_setup_hook)(struct nf_conn *ct, | ||
33 | enum nf_nat_manip_type manip, | ||
34 | struct nlattr *attr); | ||
35 | |||
28 | #endif /* _NF_NAT_CORE_H */ | 36 | #endif /* _NF_NAT_CORE_H */ |
diff --git a/net/ipv4/netfilter/nf_nat_core.c b/net/ipv4/netfilter/nf_nat_core.c index 2ac9eaf1a8c9..a65cf692359f 100644 --- a/net/ipv4/netfilter/nf_nat_core.c +++ b/net/ipv4/netfilter/nf_nat_core.c | |||
@@ -584,6 +584,98 @@ static struct nf_ct_ext_type nat_extend __read_mostly = { | |||
584 | .flags = NF_CT_EXT_F_PREALLOC, | 584 | .flags = NF_CT_EXT_F_PREALLOC, |
585 | }; | 585 | }; |
586 | 586 | ||
587 | #if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE) | ||
588 | |||
589 | #include <linux/netfilter/nfnetlink.h> | ||
590 | #include <linux/netfilter/nfnetlink_conntrack.h> | ||
591 | |||
592 | static const struct nla_policy protonat_nla_policy[CTA_PROTONAT_MAX+1] = { | ||
593 | [CTA_PROTONAT_PORT_MIN] = { .type = NLA_U16 }, | ||
594 | [CTA_PROTONAT_PORT_MAX] = { .type = NLA_U16 }, | ||
595 | }; | ||
596 | |||
597 | static int nfnetlink_parse_nat_proto(struct nlattr *attr, | ||
598 | const struct nf_conn *ct, | ||
599 | struct nf_nat_range *range) | ||
600 | { | ||
601 | struct nlattr *tb[CTA_PROTONAT_MAX+1]; | ||
602 | const struct nf_nat_protocol *npt; | ||
603 | int err; | ||
604 | |||
605 | err = nla_parse_nested(tb, CTA_PROTONAT_MAX, attr, protonat_nla_policy); | ||
606 | if (err < 0) | ||
607 | return err; | ||
608 | |||
609 | npt = nf_nat_proto_find_get(nf_ct_protonum(ct)); | ||
610 | if (npt->nlattr_to_range) | ||
611 | err = npt->nlattr_to_range(tb, range); | ||
612 | nf_nat_proto_put(npt); | ||
613 | return err; | ||
614 | } | ||
615 | |||
616 | static const struct nla_policy nat_nla_policy[CTA_NAT_MAX+1] = { | ||
617 | [CTA_NAT_MINIP] = { .type = NLA_U32 }, | ||
618 | [CTA_NAT_MAXIP] = { .type = NLA_U32 }, | ||
619 | }; | ||
620 | |||
621 | static int | ||
622 | nfnetlink_parse_nat(struct nlattr *nat, | ||
623 | const struct nf_conn *ct, struct nf_nat_range *range) | ||
624 | { | ||
625 | struct nlattr *tb[CTA_NAT_MAX+1]; | ||
626 | int err; | ||
627 | |||
628 | memset(range, 0, sizeof(*range)); | ||
629 | |||
630 | err = nla_parse_nested(tb, CTA_NAT_MAX, nat, nat_nla_policy); | ||
631 | if (err < 0) | ||
632 | return err; | ||
633 | |||
634 | if (tb[CTA_NAT_MINIP]) | ||
635 | range->min_ip = nla_get_be32(tb[CTA_NAT_MINIP]); | ||
636 | |||
637 | if (!tb[CTA_NAT_MAXIP]) | ||
638 | range->max_ip = range->min_ip; | ||
639 | else | ||
640 | range->max_ip = nla_get_be32(tb[CTA_NAT_MAXIP]); | ||
641 | |||
642 | if (range->min_ip) | ||
643 | range->flags |= IP_NAT_RANGE_MAP_IPS; | ||
644 | |||
645 | if (!tb[CTA_NAT_PROTO]) | ||
646 | return 0; | ||
647 | |||
648 | err = nfnetlink_parse_nat_proto(tb[CTA_NAT_PROTO], ct, range); | ||
649 | if (err < 0) | ||
650 | return err; | ||
651 | |||
652 | return 0; | ||
653 | } | ||
654 | |||
655 | static int | ||
656 | nfnetlink_parse_nat_setup(struct nf_conn *ct, | ||
657 | enum nf_nat_manip_type manip, | ||
658 | struct nlattr *attr) | ||
659 | { | ||
660 | struct nf_nat_range range; | ||
661 | |||
662 | if (nfnetlink_parse_nat(attr, ct, &range) < 0) | ||
663 | return -EINVAL; | ||
664 | if (nf_nat_initialized(ct, manip)) | ||
665 | return -EEXIST; | ||
666 | |||
667 | return nf_nat_setup_info(ct, &range, manip); | ||
668 | } | ||
669 | #else | ||
670 | static int | ||
671 | nfnetlink_parse_nat_setup(struct nf_conn *ct, | ||
672 | enum nf_nat_manip_type manip, | ||
673 | struct nlattr *attr) | ||
674 | { | ||
675 | return -EOPNOTSUPP; | ||
676 | } | ||
677 | #endif | ||
678 | |||
587 | static int __net_init nf_nat_net_init(struct net *net) | 679 | static int __net_init nf_nat_net_init(struct net *net) |
588 | { | 680 | { |
589 | net->ipv4.nat_bysource = nf_ct_alloc_hashtable(&nf_nat_htable_size, | 681 | net->ipv4.nat_bysource = nf_ct_alloc_hashtable(&nf_nat_htable_size, |
@@ -654,6 +746,9 @@ static int __init nf_nat_init(void) | |||
654 | 746 | ||
655 | BUG_ON(nf_nat_seq_adjust_hook != NULL); | 747 | BUG_ON(nf_nat_seq_adjust_hook != NULL); |
656 | rcu_assign_pointer(nf_nat_seq_adjust_hook, nf_nat_seq_adjust); | 748 | rcu_assign_pointer(nf_nat_seq_adjust_hook, nf_nat_seq_adjust); |
749 | BUG_ON(nfnetlink_parse_nat_setup_hook != NULL); | ||
750 | rcu_assign_pointer(nfnetlink_parse_nat_setup_hook, | ||
751 | nfnetlink_parse_nat_setup); | ||
657 | return 0; | 752 | return 0; |
658 | 753 | ||
659 | cleanup_extend: | 754 | cleanup_extend: |
@@ -667,10 +762,12 @@ static void __exit nf_nat_cleanup(void) | |||
667 | nf_ct_l3proto_put(l3proto); | 762 | nf_ct_l3proto_put(l3proto); |
668 | nf_ct_extend_unregister(&nat_extend); | 763 | nf_ct_extend_unregister(&nat_extend); |
669 | rcu_assign_pointer(nf_nat_seq_adjust_hook, NULL); | 764 | rcu_assign_pointer(nf_nat_seq_adjust_hook, NULL); |
765 | rcu_assign_pointer(nfnetlink_parse_nat_setup_hook, NULL); | ||
670 | synchronize_net(); | 766 | synchronize_net(); |
671 | } | 767 | } |
672 | 768 | ||
673 | MODULE_LICENSE("GPL"); | 769 | MODULE_LICENSE("GPL"); |
770 | MODULE_ALIAS("nf-nat-ipv4"); | ||
674 | 771 | ||
675 | module_init(nf_nat_init); | 772 | module_init(nf_nat_init); |
676 | module_exit(nf_nat_cleanup); | 773 | module_exit(nf_nat_cleanup); |
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 27de3c7b006e..622d7c671cb7 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c | |||
@@ -38,9 +38,16 @@ | |||
38 | #include <net/netfilter/nf_conntrack_core.h> | 38 | #include <net/netfilter/nf_conntrack_core.h> |
39 | #include <net/netfilter/nf_conntrack_extend.h> | 39 | #include <net/netfilter/nf_conntrack_extend.h> |
40 | #include <net/netfilter/nf_conntrack_acct.h> | 40 | #include <net/netfilter/nf_conntrack_acct.h> |
41 | #include <net/netfilter/nf_nat.h> | ||
41 | 42 | ||
42 | #define NF_CONNTRACK_VERSION "0.5.0" | 43 | #define NF_CONNTRACK_VERSION "0.5.0" |
43 | 44 | ||
45 | unsigned int | ||
46 | (*nfnetlink_parse_nat_setup_hook)(struct nf_conn *ct, | ||
47 | enum nf_nat_manip_type manip, | ||
48 | struct nlattr *attr) __read_mostly; | ||
49 | EXPORT_SYMBOL_GPL(nfnetlink_parse_nat_setup_hook); | ||
50 | |||
44 | DEFINE_SPINLOCK(nf_conntrack_lock); | 51 | DEFINE_SPINLOCK(nf_conntrack_lock); |
45 | EXPORT_SYMBOL_GPL(nf_conntrack_lock); | 52 | EXPORT_SYMBOL_GPL(nf_conntrack_lock); |
46 | 53 | ||
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index cadfd15b44f6..08e82d64eb6f 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c | |||
@@ -689,71 +689,6 @@ ctnetlink_parse_tuple(struct nlattr *cda[], struct nf_conntrack_tuple *tuple, | |||
689 | return 0; | 689 | return 0; |
690 | } | 690 | } |
691 | 691 | ||
692 | #ifdef CONFIG_NF_NAT_NEEDED | ||
693 | static const struct nla_policy protonat_nla_policy[CTA_PROTONAT_MAX+1] = { | ||
694 | [CTA_PROTONAT_PORT_MIN] = { .type = NLA_U16 }, | ||
695 | [CTA_PROTONAT_PORT_MAX] = { .type = NLA_U16 }, | ||
696 | }; | ||
697 | |||
698 | static int nfnetlink_parse_nat_proto(struct nlattr *attr, | ||
699 | const struct nf_conn *ct, | ||
700 | struct nf_nat_range *range) | ||
701 | { | ||
702 | struct nlattr *tb[CTA_PROTONAT_MAX+1]; | ||
703 | const struct nf_nat_protocol *npt; | ||
704 | int err; | ||
705 | |||
706 | err = nla_parse_nested(tb, CTA_PROTONAT_MAX, attr, protonat_nla_policy); | ||
707 | if (err < 0) | ||
708 | return err; | ||
709 | |||
710 | npt = nf_nat_proto_find_get(nf_ct_protonum(ct)); | ||
711 | if (npt->nlattr_to_range) | ||
712 | err = npt->nlattr_to_range(tb, range); | ||
713 | nf_nat_proto_put(npt); | ||
714 | return err; | ||
715 | } | ||
716 | |||
717 | static const struct nla_policy nat_nla_policy[CTA_NAT_MAX+1] = { | ||
718 | [CTA_NAT_MINIP] = { .type = NLA_U32 }, | ||
719 | [CTA_NAT_MAXIP] = { .type = NLA_U32 }, | ||
720 | }; | ||
721 | |||
722 | static inline int | ||
723 | nfnetlink_parse_nat(struct nlattr *nat, | ||
724 | const struct nf_conn *ct, struct nf_nat_range *range) | ||
725 | { | ||
726 | struct nlattr *tb[CTA_NAT_MAX+1]; | ||
727 | int err; | ||
728 | |||
729 | memset(range, 0, sizeof(*range)); | ||
730 | |||
731 | err = nla_parse_nested(tb, CTA_NAT_MAX, nat, nat_nla_policy); | ||
732 | if (err < 0) | ||
733 | return err; | ||
734 | |||
735 | if (tb[CTA_NAT_MINIP]) | ||
736 | range->min_ip = nla_get_be32(tb[CTA_NAT_MINIP]); | ||
737 | |||
738 | if (!tb[CTA_NAT_MAXIP]) | ||
739 | range->max_ip = range->min_ip; | ||
740 | else | ||
741 | range->max_ip = nla_get_be32(tb[CTA_NAT_MAXIP]); | ||
742 | |||
743 | if (range->min_ip) | ||
744 | range->flags |= IP_NAT_RANGE_MAP_IPS; | ||
745 | |||
746 | if (!tb[CTA_NAT_PROTO]) | ||
747 | return 0; | ||
748 | |||
749 | err = nfnetlink_parse_nat_proto(tb[CTA_NAT_PROTO], ct, range); | ||
750 | if (err < 0) | ||
751 | return err; | ||
752 | |||
753 | return 0; | ||
754 | } | ||
755 | #endif | ||
756 | |||
757 | static inline int | 692 | static inline int |
758 | ctnetlink_parse_help(struct nlattr *attr, char **helper_name) | 693 | ctnetlink_parse_help(struct nlattr *attr, char **helper_name) |
759 | { | 694 | { |
@@ -879,6 +814,34 @@ out: | |||
879 | } | 814 | } |
880 | 815 | ||
881 | static int | 816 | static int |
817 | ctnetlink_parse_nat_setup(struct nf_conn *ct, | ||
818 | enum nf_nat_manip_type manip, | ||
819 | struct nlattr *attr) | ||
820 | { | ||
821 | typeof(nfnetlink_parse_nat_setup_hook) parse_nat_setup; | ||
822 | |||
823 | parse_nat_setup = rcu_dereference(nfnetlink_parse_nat_setup_hook); | ||
824 | if (!parse_nat_setup) { | ||
825 | #ifdef CONFIG_KMOD | ||
826 | rcu_read_unlock(); | ||
827 | nfnl_unlock(); | ||
828 | if (request_module("nf-nat-ipv4") < 0) { | ||
829 | nfnl_lock(); | ||
830 | rcu_read_lock(); | ||
831 | return -EOPNOTSUPP; | ||
832 | } | ||
833 | nfnl_lock(); | ||
834 | rcu_read_lock(); | ||
835 | if (nfnetlink_parse_nat_setup_hook) | ||
836 | return -EAGAIN; | ||
837 | #endif | ||
838 | return -EOPNOTSUPP; | ||
839 | } | ||
840 | |||
841 | return parse_nat_setup(ct, manip, attr); | ||
842 | } | ||
843 | |||
844 | static int | ||
882 | ctnetlink_change_status(struct nf_conn *ct, struct nlattr *cda[]) | 845 | ctnetlink_change_status(struct nf_conn *ct, struct nlattr *cda[]) |
883 | { | 846 | { |
884 | unsigned long d; | 847 | unsigned long d; |
@@ -897,31 +860,6 @@ ctnetlink_change_status(struct nf_conn *ct, struct nlattr *cda[]) | |||
897 | /* ASSURED bit can only be set */ | 860 | /* ASSURED bit can only be set */ |
898 | return -EBUSY; | 861 | return -EBUSY; |
899 | 862 | ||
900 | if (cda[CTA_NAT_SRC] || cda[CTA_NAT_DST]) { | ||
901 | #ifndef CONFIG_NF_NAT_NEEDED | ||
902 | return -EOPNOTSUPP; | ||
903 | #else | ||
904 | struct nf_nat_range range; | ||
905 | |||
906 | if (cda[CTA_NAT_DST]) { | ||
907 | if (nfnetlink_parse_nat(cda[CTA_NAT_DST], ct, | ||
908 | &range) < 0) | ||
909 | return -EINVAL; | ||
910 | if (nf_nat_initialized(ct, IP_NAT_MANIP_DST)) | ||
911 | return -EEXIST; | ||
912 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_DST); | ||
913 | } | ||
914 | if (cda[CTA_NAT_SRC]) { | ||
915 | if (nfnetlink_parse_nat(cda[CTA_NAT_SRC], ct, | ||
916 | &range) < 0) | ||
917 | return -EINVAL; | ||
918 | if (nf_nat_initialized(ct, IP_NAT_MANIP_SRC)) | ||
919 | return -EEXIST; | ||
920 | nf_nat_setup_info(ct, &range, IP_NAT_MANIP_SRC); | ||
921 | } | ||
922 | #endif | ||
923 | } | ||
924 | |||
925 | /* Be careful here, modifying NAT bits can screw up things, | 863 | /* Be careful here, modifying NAT bits can screw up things, |
926 | * so don't let users modify them directly if they don't pass | 864 | * so don't let users modify them directly if they don't pass |
927 | * nf_nat_range. */ | 865 | * nf_nat_range. */ |
@@ -929,6 +867,31 @@ ctnetlink_change_status(struct nf_conn *ct, struct nlattr *cda[]) | |||
929 | return 0; | 867 | return 0; |
930 | } | 868 | } |
931 | 869 | ||
870 | static int | ||
871 | ctnetlink_change_nat(struct nf_conn *ct, struct nlattr *cda[]) | ||
872 | { | ||
873 | #ifdef CONFIG_NF_NAT_NEEDED | ||
874 | int ret; | ||
875 | |||
876 | if (cda[CTA_NAT_DST]) { | ||
877 | ret = ctnetlink_parse_nat_setup(ct, | ||
878 | IP_NAT_MANIP_DST, | ||
879 | cda[CTA_NAT_DST]); | ||
880 | if (ret < 0) | ||
881 | return ret; | ||
882 | } | ||
883 | if (cda[CTA_NAT_SRC]) { | ||
884 | ret = ctnetlink_parse_nat_setup(ct, | ||
885 | IP_NAT_MANIP_SRC, | ||
886 | cda[CTA_NAT_SRC]); | ||
887 | if (ret < 0) | ||
888 | return ret; | ||
889 | } | ||
890 | return 0; | ||
891 | #else | ||
892 | return -EOPNOTSUPP; | ||
893 | #endif | ||
894 | } | ||
932 | 895 | ||
933 | static inline int | 896 | static inline int |
934 | ctnetlink_change_helper(struct nf_conn *ct, struct nlattr *cda[]) | 897 | ctnetlink_change_helper(struct nf_conn *ct, struct nlattr *cda[]) |
@@ -1157,6 +1120,14 @@ ctnetlink_create_conntrack(struct nlattr *cda[], | |||
1157 | } | 1120 | } |
1158 | } | 1121 | } |
1159 | 1122 | ||
1123 | if (cda[CTA_NAT_SRC] || cda[CTA_NAT_DST]) { | ||
1124 | err = ctnetlink_change_nat(ct, cda); | ||
1125 | if (err < 0) { | ||
1126 | rcu_read_unlock(); | ||
1127 | goto err; | ||
1128 | } | ||
1129 | } | ||
1130 | |||
1160 | if (cda[CTA_PROTOINFO]) { | 1131 | if (cda[CTA_PROTOINFO]) { |
1161 | err = ctnetlink_change_protoinfo(ct, cda); | 1132 | err = ctnetlink_change_protoinfo(ct, cda); |
1162 | if (err < 0) { | 1133 | if (err < 0) { |
diff --git a/net/netfilter/nfnetlink.c b/net/netfilter/nfnetlink.c index b75c9c4a995d..4739f9f961d8 100644 --- a/net/netfilter/nfnetlink.c +++ b/net/netfilter/nfnetlink.c | |||
@@ -44,15 +44,17 @@ static struct sock *nfnl = NULL; | |||
44 | static const struct nfnetlink_subsystem *subsys_table[NFNL_SUBSYS_COUNT]; | 44 | static const struct nfnetlink_subsystem *subsys_table[NFNL_SUBSYS_COUNT]; |
45 | static DEFINE_MUTEX(nfnl_mutex); | 45 | static DEFINE_MUTEX(nfnl_mutex); |
46 | 46 | ||
47 | static inline void nfnl_lock(void) | 47 | void nfnl_lock(void) |
48 | { | 48 | { |
49 | mutex_lock(&nfnl_mutex); | 49 | mutex_lock(&nfnl_mutex); |
50 | } | 50 | } |
51 | EXPORT_SYMBOL_GPL(nfnl_lock); | ||
51 | 52 | ||
52 | static inline void nfnl_unlock(void) | 53 | void nfnl_unlock(void) |
53 | { | 54 | { |
54 | mutex_unlock(&nfnl_mutex); | 55 | mutex_unlock(&nfnl_mutex); |
55 | } | 56 | } |
57 | EXPORT_SYMBOL_GPL(nfnl_unlock); | ||
56 | 58 | ||
57 | int nfnetlink_subsys_register(const struct nfnetlink_subsystem *n) | 59 | int nfnetlink_subsys_register(const struct nfnetlink_subsystem *n) |
58 | { | 60 | { |
@@ -132,6 +134,7 @@ static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
132 | return 0; | 134 | return 0; |
133 | 135 | ||
134 | type = nlh->nlmsg_type; | 136 | type = nlh->nlmsg_type; |
137 | replay: | ||
135 | ss = nfnetlink_get_subsys(type); | 138 | ss = nfnetlink_get_subsys(type); |
136 | if (!ss) { | 139 | if (!ss) { |
137 | #ifdef CONFIG_KMOD | 140 | #ifdef CONFIG_KMOD |
@@ -165,7 +168,10 @@ static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
165 | } else | 168 | } else |
166 | return -EINVAL; | 169 | return -EINVAL; |
167 | 170 | ||
168 | return nc->call(nfnl, skb, nlh, cda); | 171 | err = nc->call(nfnl, skb, nlh, cda); |
172 | if (err == -EAGAIN) | ||
173 | goto replay; | ||
174 | return err; | ||
169 | } | 175 | } |
170 | } | 176 | } |
171 | 177 | ||