1 /*- 2 * Copyright 2005, Gleb Smirnoff <glebius@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD$ 27 */ 28 29 #include "opt_inet.h" 30 #include "opt_inet6.h" 31 32 #include <sys/param.h> 33 #include <sys/systm.h> 34 #include <sys/kernel.h> 35 #include <sys/lock.h> 36 #include <sys/mbuf.h> 37 #include <sys/malloc.h> 38 #include <sys/ctype.h> 39 #include <sys/errno.h> 40 #include <sys/rwlock.h> 41 #include <sys/socket.h> 42 #include <sys/syslog.h> 43 44 #include <net/if.h> 45 #include <net/if_var.h> 46 47 #include <netinet/in.h> 48 #include <netinet/in_systm.h> 49 #include <netinet/in_var.h> 50 #include <netinet/ip_var.h> 51 #include <netinet/ip_fw.h> 52 #include <netinet/ip.h> 53 #include <netinet/ip6.h> 54 #include <netinet6/ip6_var.h> 55 56 #include <netpfil/ipfw/ip_fw_private.h> 57 58 #include <netgraph/ng_message.h> 59 #include <netgraph/ng_parse.h> 60 #include <netgraph/ng_ipfw.h> 61 #include <netgraph/netgraph.h> 62 63 static int ng_ipfw_mod_event(module_t mod, int event, void *data); 64 static ng_constructor_t ng_ipfw_constructor; 65 static ng_shutdown_t ng_ipfw_shutdown; 66 static ng_newhook_t ng_ipfw_newhook; 67 static ng_connect_t ng_ipfw_connect; 68 static ng_findhook_t ng_ipfw_findhook; 69 static ng_rcvdata_t ng_ipfw_rcvdata; 70 static ng_disconnect_t ng_ipfw_disconnect; 71 72 static hook_p ng_ipfw_findhook1(node_p, u_int16_t ); 73 static int ng_ipfw_input(struct mbuf **, int, struct ip_fw_args *, 74 int); 75 76 /* We have only one node */ 77 static node_p fw_node; 78 79 /* Netgraph node type descriptor */ 80 static struct ng_type ng_ipfw_typestruct = { 81 .version = NG_ABI_VERSION, 82 .name = NG_IPFW_NODE_TYPE, 83 .mod_event = ng_ipfw_mod_event, 84 .constructor = ng_ipfw_constructor, 85 .shutdown = ng_ipfw_shutdown, 86 .newhook = ng_ipfw_newhook, 87 .connect = ng_ipfw_connect, 88 .findhook = ng_ipfw_findhook, 89 .rcvdata = ng_ipfw_rcvdata, 90 .disconnect = ng_ipfw_disconnect, 91 }; 92 NETGRAPH_INIT(ipfw, &ng_ipfw_typestruct); 93 MODULE_DEPEND(ng_ipfw, ipfw, 3, 3, 3); 94 95 /* Information we store for each hook */ 96 struct ng_ipfw_hook_priv { 97 hook_p hook; 98 u_int16_t rulenum; 99 }; 100 typedef struct ng_ipfw_hook_priv *hpriv_p; 101 102 static int 103 ng_ipfw_mod_event(module_t mod, int event, void *data) 104 { 105 int error = 0; 106 107 switch (event) { 108 case MOD_LOAD: 109 110 if (ng_ipfw_input_p != NULL) { 111 error = EEXIST; 112 break; 113 } 114 115 /* Setup node without any private data */ 116 if ((error = ng_make_node_common(&ng_ipfw_typestruct, &fw_node)) 117 != 0) { 118 log(LOG_ERR, "%s: can't create ng_ipfw node", __func__); 119 break; 120 }; 121 122 /* Try to name node */ 123 if (ng_name_node(fw_node, "ipfw") != 0) 124 log(LOG_WARNING, "%s: failed to name node \"ipfw\"", 125 __func__); 126 127 /* Register hook */ 128 ng_ipfw_input_p = ng_ipfw_input; 129 break; 130 131 case MOD_UNLOAD: 132 /* 133 * This won't happen if a node exists. 134 * ng_ipfw_input_p is already cleared. 135 */ 136 break; 137 138 default: 139 error = EOPNOTSUPP; 140 break; 141 } 142 143 return (error); 144 } 145 146 static int 147 ng_ipfw_constructor(node_p node) 148 { 149 return (EINVAL); /* Only one node */ 150 } 151 152 static int 153 ng_ipfw_newhook(node_p node, hook_p hook, const char *name) 154 { 155 hpriv_p hpriv; 156 u_int16_t rulenum; 157 const char *cp; 158 char *endptr; 159 160 /* Protect from leading zero */ 161 if (name[0] == '0' && name[1] != '\0') 162 return (EINVAL); 163 164 /* Check that name contains only digits */ 165 for (cp = name; *cp != '\0'; cp++) 166 if (!isdigit(*cp)) 167 return (EINVAL); 168 169 /* Convert it to integer */ 170 rulenum = (u_int16_t)strtol(name, &endptr, 10); 171 if (*endptr != '\0') 172 return (EINVAL); 173 174 /* Allocate memory for this hook's private data */ 175 hpriv = malloc(sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO); 176 if (hpriv== NULL) 177 return (ENOMEM); 178 179 hpriv->hook = hook; 180 hpriv->rulenum = rulenum; 181 182 NG_HOOK_SET_PRIVATE(hook, hpriv); 183 184 return(0); 185 } 186 187 /* 188 * Set hooks into queueing mode, to avoid recursion between 189 * netgraph layer and ip_{input,output}. 190 */ 191 static int 192 ng_ipfw_connect(hook_p hook) 193 { 194 NG_HOOK_FORCE_QUEUE(hook); 195 return (0); 196 } 197 198 /* Look up hook by name */ 199 static hook_p 200 ng_ipfw_findhook(node_p node, const char *name) 201 { 202 u_int16_t n; /* numeric representation of hook */ 203 char *endptr; 204 205 n = (u_int16_t)strtol(name, &endptr, 10); 206 if (*endptr != '\0') 207 return NULL; 208 return ng_ipfw_findhook1(node, n); 209 } 210 211 /* Look up hook by rule number */ 212 static hook_p 213 ng_ipfw_findhook1(node_p node, u_int16_t rulenum) 214 { 215 hook_p hook; 216 hpriv_p hpriv; 217 218 LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { 219 hpriv = NG_HOOK_PRIVATE(hook); 220 if (NG_HOOK_IS_VALID(hook) && (hpriv->rulenum == rulenum)) 221 return (hook); 222 } 223 224 return (NULL); 225 } 226 227 228 static int 229 ng_ipfw_rcvdata(hook_p hook, item_p item) 230 { 231 struct m_tag *tag; 232 struct ipfw_rule_ref *r; 233 struct mbuf *m; 234 struct ip *ip; 235 236 NGI_GET_M(item, m); 237 NG_FREE_ITEM(item); 238 239 tag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL); 240 if (tag == NULL) { 241 NG_FREE_M(m); 242 return (EINVAL); /* XXX: find smth better */ 243 }; 244 245 if (m->m_len < sizeof(struct ip) && 246 (m = m_pullup(m, sizeof(struct ip))) == NULL) 247 return (ENOBUFS); 248 249 ip = mtod(m, struct ip *); 250 251 r = (struct ipfw_rule_ref *)(tag + 1); 252 if (r->info & IPFW_INFO_IN) { 253 switch (ip->ip_v) { 254 #ifdef INET 255 case IPVERSION: 256 ip_input(m); 257 return (0); 258 #endif 259 #ifdef INET6 260 case IPV6_VERSION >> 4: 261 ip6_input(m); 262 return (0); 263 #endif 264 } 265 } else { 266 switch (ip->ip_v) { 267 #ifdef INET 268 case IPVERSION: 269 return (ip_output(m, NULL, NULL, IP_FORWARDING, 270 NULL, NULL)); 271 #endif 272 #ifdef INET6 273 case IPV6_VERSION >> 4: 274 return (ip6_output(m, NULL, NULL, 0, NULL, 275 NULL, NULL)); 276 #endif 277 } 278 } 279 280 /* unknown IP protocol version */ 281 NG_FREE_M(m); 282 return (EPROTONOSUPPORT); 283 } 284 285 static int 286 ng_ipfw_input(struct mbuf **m0, int dir, struct ip_fw_args *fwa, int tee) 287 { 288 struct mbuf *m; 289 struct ip *ip; 290 hook_p hook; 291 int error = 0; 292 293 /* 294 * Node must be loaded and corresponding hook must be present. 295 */ 296 if (fw_node == NULL || 297 (hook = ng_ipfw_findhook1(fw_node, fwa->rule.info)) == NULL) 298 return (ESRCH); /* no hook associated with this rule */ 299 300 /* 301 * We have two modes: in normal mode we add a tag to packet, which is 302 * important to return packet back to IP stack. In tee mode we make 303 * a copy of a packet and forward it into netgraph without a tag. 304 */ 305 if (tee == 0) { 306 struct m_tag *tag; 307 struct ipfw_rule_ref *r; 308 m = *m0; 309 *m0 = NULL; /* it belongs now to netgraph */ 310 311 tag = m_tag_alloc(MTAG_IPFW_RULE, 0, sizeof(*r), 312 M_NOWAIT|M_ZERO); 313 if (tag == NULL) { 314 m_freem(m); 315 return (ENOMEM); 316 } 317 r = (struct ipfw_rule_ref *)(tag + 1); 318 *r = fwa->rule; 319 r->info &= IPFW_ONEPASS; /* keep this info */ 320 r->info |= dir ? IPFW_INFO_IN : IPFW_INFO_OUT; 321 m_tag_prepend(m, tag); 322 323 } else 324 if ((m = m_dup(*m0, M_NOWAIT)) == NULL) 325 return (ENOMEM); /* which is ignored */ 326 327 if (m->m_len < sizeof(struct ip) && 328 (m = m_pullup(m, sizeof(struct ip))) == NULL) 329 return (EINVAL); 330 331 ip = mtod(m, struct ip *); 332 333 NG_SEND_DATA_ONLY(error, hook, m); 334 335 return (error); 336 } 337 338 static int 339 ng_ipfw_shutdown(node_p node) 340 { 341 342 /* 343 * After our single node has been removed, 344 * the only thing that can be done is 345 * 'kldunload ng_ipfw.ko' 346 */ 347 ng_ipfw_input_p = NULL; 348 NG_NODE_UNREF(node); 349 return (0); 350 } 351 352 static int 353 ng_ipfw_disconnect(hook_p hook) 354 { 355 const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); 356 357 free(hpriv, M_NETGRAPH); 358 NG_HOOK_SET_PRIVATE(hook, NULL); 359 360 return (0); 361 } 362