1 // SPDX-License-Identifier: GPL-2.0-only 2 /* Support ct functions for openvswitch and used by OVS and TC conntrack. */ 3 4 #include <net/netfilter/nf_conntrack_helper.h> 5 #include <net/netfilter/nf_conntrack_seqadj.h> 6 #include <net/netfilter/ipv6/nf_defrag_ipv6.h> 7 #include <net/ipv6_frag.h> 8 #include <net/ip.h> 9 #include <linux/netfilter_ipv6.h> 10 11 /* 'skb' should already be pulled to nh_ofs. */ 12 int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, 13 enum ip_conntrack_info ctinfo, u16 proto) 14 { 15 const struct nf_conntrack_helper *helper; 16 const struct nf_conn_help *help; 17 unsigned int protoff; 18 int err; 19 20 if (ctinfo == IP_CT_RELATED_REPLY) 21 return NF_ACCEPT; 22 23 help = nfct_help(ct); 24 if (!help) 25 return NF_ACCEPT; 26 27 helper = rcu_dereference(help->helper); 28 if (!helper) 29 return NF_ACCEPT; 30 31 if (helper->tuple.src.l3num != NFPROTO_UNSPEC && 32 helper->tuple.src.l3num != proto) 33 return NF_ACCEPT; 34 35 switch (proto) { 36 case NFPROTO_IPV4: 37 protoff = ip_hdrlen(skb); 38 proto = ip_hdr(skb)->protocol; 39 break; 40 case NFPROTO_IPV6: { 41 u8 nexthdr = ipv6_hdr(skb)->nexthdr; 42 __be16 frag_off; 43 int ofs; 44 45 ofs = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, 46 &frag_off); 47 if (ofs < 0 || (frag_off & htons(~0x7)) != 0) { 48 pr_debug("proto header not found\n"); 49 return NF_ACCEPT; 50 } 51 protoff = ofs; 52 proto = nexthdr; 53 break; 54 } 55 default: 56 WARN_ONCE(1, "helper invoked on non-IP family!"); 57 return NF_DROP; 58 } 59 60 if (helper->tuple.dst.protonum != proto) 61 return NF_ACCEPT; 62 63 err = helper->help(skb, protoff, ct, ctinfo); 64 if (err != NF_ACCEPT) 65 return err; 66 67 /* Adjust seqs after helper. This is needed due to some helpers (e.g., 68 * FTP with NAT) adusting the TCP payload size when mangling IP 69 * addresses and/or port numbers in the text-based control connection. 70 */ 71 if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && 72 !nf_ct_seq_adjust(skb, ct, ctinfo, protoff)) 73 return NF_DROP; 74 return NF_ACCEPT; 75 } 76 EXPORT_SYMBOL_GPL(nf_ct_helper); 77 78 int nf_ct_add_helper(struct nf_conn *ct, const char *name, u8 family, 79 u8 proto, bool nat, struct nf_conntrack_helper **hp) 80 { 81 struct nf_conntrack_helper *helper; 82 struct nf_conn_help *help; 83 int ret = 0; 84 85 helper = nf_conntrack_helper_try_module_get(name, family, proto); 86 if (!helper) 87 return -EINVAL; 88 89 help = nf_ct_helper_ext_add(ct, GFP_KERNEL); 90 if (!help) { 91 nf_conntrack_helper_put(helper); 92 return -ENOMEM; 93 } 94 #if IS_ENABLED(CONFIG_NF_NAT) 95 if (nat) { 96 ret = nf_nat_helper_try_module_get(name, family, proto); 97 if (ret) { 98 nf_conntrack_helper_put(helper); 99 return ret; 100 } 101 } 102 #endif 103 rcu_assign_pointer(help->helper, helper); 104 *hp = helper; 105 return ret; 106 } 107 EXPORT_SYMBOL_GPL(nf_ct_add_helper); 108 109 /* Trim the skb to the length specified by the IP/IPv6 header, 110 * removing any trailing lower-layer padding. This prepares the skb 111 * for higher-layer processing that assumes skb->len excludes padding 112 * (such as nf_ip_checksum). The caller needs to pull the skb to the 113 * network header, and ensure ip_hdr/ipv6_hdr points to valid data. 114 */ 115 int nf_ct_skb_network_trim(struct sk_buff *skb, int family) 116 { 117 unsigned int len; 118 119 switch (family) { 120 case NFPROTO_IPV4: 121 len = skb_ip_totlen(skb); 122 break; 123 case NFPROTO_IPV6: 124 len = ntohs(ipv6_hdr(skb)->payload_len); 125 if (ipv6_hdr(skb)->nexthdr == NEXTHDR_HOP) { 126 int err = nf_ip6_check_hbh_len(skb, &len); 127 128 if (err) 129 return err; 130 } 131 len += sizeof(struct ipv6hdr); 132 break; 133 default: 134 len = skb->len; 135 } 136 137 return pskb_trim_rcsum(skb, len); 138 } 139 EXPORT_SYMBOL_GPL(nf_ct_skb_network_trim); 140 141 /* Returns 0 on success, -EINPROGRESS if 'skb' is stolen, or other nonzero 142 * value if 'skb' is freed. 143 */ 144 int nf_ct_handle_fragments(struct net *net, struct sk_buff *skb, 145 u16 zone, u8 family, u8 *proto, u16 *mru) 146 { 147 int err; 148 149 if (family == NFPROTO_IPV4) { 150 enum ip_defrag_users user = IP_DEFRAG_CONNTRACK_IN + zone; 151 152 memset(IPCB(skb), 0, sizeof(struct inet_skb_parm)); 153 local_bh_disable(); 154 err = ip_defrag(net, skb, user); 155 local_bh_enable(); 156 if (err) 157 return err; 158 159 *mru = IPCB(skb)->frag_max_size; 160 #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) 161 } else if (family == NFPROTO_IPV6) { 162 enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone; 163 164 memset(IP6CB(skb), 0, sizeof(struct inet6_skb_parm)); 165 err = nf_ct_frag6_gather(net, skb, user); 166 if (err) { 167 if (err != -EINPROGRESS) 168 kfree_skb(skb); 169 return err; 170 } 171 172 *proto = ipv6_hdr(skb)->nexthdr; 173 *mru = IP6CB(skb)->frag_max_size; 174 #endif 175 } else { 176 kfree_skb(skb); 177 return -EPFNOSUPPORT; 178 } 179 180 skb_clear_hash(skb); 181 skb->ignore_df = 1; 182 183 return 0; 184 } 185 EXPORT_SYMBOL_GPL(nf_ct_handle_fragments); 186