1 /* $NetBSD: ipf.c,v 1.6 2012/09/15 17:42:43 plunky Exp $ */ 2 3 /* 4 * Copyright (c) 2004, 2008 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/param.h> 30 #include <sys/types.h> 31 #include <sys/socket.h> 32 #include <sys/ioctl.h> 33 #include <sys/file.h> 34 35 #include <net/if.h> 36 37 #include <netinet/in.h> 38 #include <netinet/in_systm.h> 39 #include <netinet/ip_compat.h> 40 #include <netinet/ipl.h> 41 #include <netinet/ip_fil.h> 42 #include <netinet/ip_nat.h> 43 44 #include <arpa/inet.h> 45 46 #include <err.h> 47 #include <errno.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 #include "filter.h" 54 55 void ipf_init_filter(char *, char *, int); 56 int ipf_add_filter(u_int32_t, u_int8_t, struct sockaddr *, struct sockaddr *, 57 u_int16_t); 58 int ipf_add_nat(u_int32_t, struct sockaddr *, struct sockaddr *, u_int16_t, 59 struct sockaddr *, u_int16_t, u_int16_t); 60 int ipf_add_rdr(u_int32_t, struct sockaddr *, struct sockaddr *, u_int16_t, 61 struct sockaddr *, u_int16_t); 62 int ipf_server_lookup(struct sockaddr *, struct sockaddr *, struct sockaddr *); 63 int ipf_prepare_commit(u_int32_t); 64 int ipf_do_commit(void); 65 int ipf_do_rollback(void); 66 67 const ftp_proxy_ops_t ipf_fprx_ops = { 68 .init_filter = ipf_init_filter, 69 .add_filter = ipf_add_filter, 70 .add_nat = ipf_add_nat, 71 .add_rdr = ipf_add_rdr, 72 .server_lookup = ipf_server_lookup, 73 .prepare_commit = ipf_prepare_commit, 74 .do_commit = ipf_do_commit, 75 .do_rollback = ipf_do_rollback 76 }; 77 78 /* From netinet/in.h, but only _KERNEL_ gets them. */ 79 #define satosin(sa) ((struct sockaddr_in *)(sa)) 80 #define satosin6(sa) ((struct sockaddr_in6 *)(sa)) 81 82 static int natfd; 83 const char *netif; 84 85 struct ftp_proxy_nat { 86 struct ipnat ipn; 87 LIST_ENTRY(ftp_proxy_nat) link; 88 }; 89 90 struct ftp_proxy_entry { 91 u_int32_t id; 92 char proxy_tag[IPFTAG_LEN]; 93 int status; 94 LIST_HEAD(, ftp_proxy_nat) nat_entries; 95 LIST_ENTRY(ftp_proxy_entry) link; 96 }; 97 98 LIST_HEAD(, ftp_proxy_entry) ftp_proxy_entries = 99 LIST_HEAD_INITIALIZER(ftp_proxy_entries); 100 101 static struct ftp_proxy_entry * 102 ftp_proxy_entry_create(u_int32_t id) 103 { 104 struct ftp_proxy_entry *fpe; 105 int rv; 106 107 fpe = malloc(sizeof(*fpe)); 108 if (fpe == NULL) 109 return (NULL); 110 111 fpe->id = id; 112 fpe->status = 0; 113 114 rv = snprintf(fpe->proxy_tag, sizeof(fpe->proxy_tag), "ftp_%d", id); 115 if (rv == -1 || rv >= sizeof(fpe->proxy_tag)) { 116 free(fpe); 117 errno = EINVAL; 118 return (NULL); 119 } 120 LIST_INIT(&fpe->nat_entries); 121 LIST_INSERT_HEAD(&ftp_proxy_entries, fpe, link); 122 123 return (fpe); 124 } 125 126 static void 127 ftp_proxy_entry_remove(struct ftp_proxy_entry *fpe) 128 { 129 struct ftp_proxy_nat *fpn; 130 131 while ((fpn = LIST_FIRST(&fpe->nat_entries)) != NULL) { 132 LIST_REMOVE(fpn, link); 133 free(fpn); 134 } 135 136 LIST_REMOVE(fpe, link); 137 free(fpe); 138 } 139 140 static struct ftp_proxy_entry * 141 ftp_proxy_entry_find(u_int32_t id) 142 { 143 struct ftp_proxy_entry *fpe; 144 145 LIST_FOREACH(fpe, &ftp_proxy_entries, link) { 146 if (fpe->id == id) { 147 return fpe; 148 } 149 } 150 return NULL; 151 } 152 153 static int 154 ftp_proxy_entry_add_nat(struct ftp_proxy_entry *fpe, ipnat_t *ipn) 155 { 156 struct ftp_proxy_nat *fpn; 157 158 fpn = malloc(sizeof(*fpn)); 159 if (fpn == NULL) 160 return (-1); 161 162 memcpy(&fpn->ipn, ipn, sizeof(fpn->ipn)); 163 LIST_INSERT_HEAD(&fpe->nat_entries, fpn, link); 164 165 return (0); 166 } 167 168 static int 169 ipfilter_add_nat(ipnat_t *ipn) 170 { 171 ipfobj_t obj; 172 173 memset(&obj, 0, sizeof(obj)); 174 obj.ipfo_rev = IPFILTER_VERSION; 175 obj.ipfo_size = ipn->in_size; 176 obj.ipfo_type = IPFOBJ_IPNAT; 177 obj.ipfo_ptr = ipn; 178 179 return ioctl(natfd, SIOCADNAT, &obj); 180 } 181 182 static int 183 ipfilter_remove_nat(ipnat_t ipn) 184 { 185 ipfobj_t obj; 186 187 memset(&obj, 0, sizeof(obj)); 188 obj.ipfo_rev = IPFILTER_VERSION; 189 obj.ipfo_size = sizeof(ipn); 190 obj.ipfo_type = IPFOBJ_IPNAT; 191 obj.ipfo_ptr = &ipn; 192 193 return ioctl(natfd, SIOCRMNAT, &obj); 194 } 195 196 int 197 ipf_add_filter(u_int32_t id, u_int8_t dir, struct sockaddr *src, 198 struct sockaddr *dst, u_int16_t d_port) 199 { 200 201 if (!src || !dst || !d_port) { 202 errno = EINVAL; 203 return (-1); 204 } 205 206 /* TODO */ 207 208 return (0); 209 } 210 211 int 212 ipf_add_nat(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, 213 u_int16_t d_port, struct sockaddr *snat, u_int16_t nat_range_low, 214 u_int16_t nat_range_high) 215 { 216 217 /* TODO */ 218 219 return (0); 220 } 221 222 int 223 ipf_add_rdr(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, 224 u_int16_t d_port, struct sockaddr *rdr, u_int16_t rdr_port) 225 { 226 struct ftp_proxy_entry *fpe = ftp_proxy_entry_find(id); 227 ipnat_t *ipn; 228 229 if (fpe == NULL) { 230 errno = ENOENT; 231 return (-1); 232 } 233 234 if (!src || !dst || !d_port || !rdr || !rdr_port || 235 (src->sa_family != rdr->sa_family)) { 236 errno = EINVAL; 237 return (-1); 238 } 239 240 ipn = calloc(1, sizeof(*ipn) + 2 * IF_NAMESIZE + 2); 241 if (ipn == NULL) { 242 errno = ENOMEM; 243 return (-1); 244 } 245 ipn->in_redir = NAT_REDIRECT; 246 ipn->in_v[0] = 4; 247 ipn->in_v[1] = 4; 248 ipn->in_odstaddr = satosin(dst)->sin_addr.s_addr; 249 ipn->in_odstmsk = 0xffffffff; 250 ipn->in_odport = htons(d_port); 251 ipn->in_dtop = htons(d_port); 252 ipn->in_ndstaddr = satosin(rdr)->sin_addr.s_addr; 253 ipn->in_ndstmsk = 0xffffffff; 254 ipn->in_dpnext = htons(rdr_port); 255 ipn->in_flags = IPN_FIXEDDPORT | IPN_TCP; 256 strlcpy(ipn->in_tag.ipt_tag, fpe->proxy_tag, 257 sizeof(ipn->in_tag.ipt_tag)); 258 259 ipn->in_ifnames[0] = 0; 260 (void) strlcpy(ipn->in_names, netif, IF_NAMESIZE); 261 ipn->in_namelen = strlen(ipn->in_names) + 1; 262 ipn->in_ifnames[1] = ipn->in_namelen; 263 (void) strlcpy(ipn->in_names + ipn->in_namelen, netif, IF_NAMESIZE); 264 ipn->in_namelen += strlen(ipn->in_names + ipn->in_ifnames[1]) + 1; 265 ipn->in_size = sizeof(*ipn) + ipn->in_namelen; 266 267 if (ipfilter_add_nat(ipn) == -1) { 268 free(ipn); 269 return (-1); 270 } 271 272 if (ftp_proxy_entry_add_nat(fpe, ipn) == -1) { 273 free(ipn); 274 return (-1); 275 } 276 277 fpe->status = 1; 278 free(ipn); 279 280 return (0); 281 } 282 283 int 284 ipf_do_commit(void) 285 { 286 struct ftp_proxy_entry *fpe, *n; 287 struct ftp_proxy_nat *fpn; 288 289 for (fpe = LIST_FIRST(&ftp_proxy_entries); fpe != NULL; fpe = n) { 290 n = LIST_NEXT(fpe, link); 291 292 /* 293 * If status is nul, then the session is going to be ended. 294 * Remove all nat mappings that were added. 295 */ 296 if (fpe->status == 0) { 297 while ((fpn = LIST_FIRST(&fpe->nat_entries)) != NULL) { 298 if (ipfilter_remove_nat(fpn->ipn) == -1) 299 return (-1); 300 301 LIST_REMOVE(fpn, link); 302 free(fpn); 303 } 304 305 ftp_proxy_entry_remove(fpe); 306 } 307 } 308 309 return (0); 310 } 311 312 int 313 ipf_do_rollback(void) 314 { 315 316 /* TODO ??? */ 317 318 return (0); 319 } 320 321 void 322 ipf_init_filter(char *opt_qname, char *opt_tagname, int opt_verbose) 323 { 324 natfd = open(IPNAT_NAME, O_RDWR); 325 if (natfd == -1) 326 err(EXIT_FAILURE, "cannot open " IPNAT_NAME); 327 } 328 329 int 330 ipf_prepare_commit(u_int32_t id) 331 { 332 struct ftp_proxy_entry *fpe; 333 334 fpe = ftp_proxy_entry_find(id); 335 if (fpe == NULL) { 336 fpe = ftp_proxy_entry_create(id); 337 if (fpe == NULL) 338 return (-1); 339 } 340 fpe->status = 0; 341 342 return (0); 343 } 344 345 int 346 ipf_server_lookup(struct sockaddr *client, struct sockaddr *proxy, 347 struct sockaddr *server) 348 { 349 natlookup_t natlook; 350 ipfobj_t obj; 351 352 /* IPv4-only for now. */ 353 if (client->sa_family != AF_INET) { 354 errno = EPROTONOSUPPORT; 355 return (-1); 356 } 357 358 /* 359 * Build up the ipf object description structure. 360 */ 361 memset((void *)&obj, 0, sizeof(obj)); 362 obj.ipfo_rev = IPFILTER_VERSION; 363 obj.ipfo_size = sizeof(natlook); 364 obj.ipfo_ptr = &natlook; 365 obj.ipfo_type = IPFOBJ_NATLOOKUP; 366 /* 367 * Build up the ipf natlook structure. 368 */ 369 memset((void *)&natlook, 0, sizeof(natlook)); 370 natlook.nl_flags = IPN_TCPUDP; 371 natlook.nl_outip = satosin(client)->sin_addr; 372 natlook.nl_inip = satosin(proxy)->sin_addr; 373 natlook.nl_outport = satosin(client)->sin_port; 374 natlook.nl_inport = satosin(proxy)->sin_port; 375 376 if (ioctl(natfd, SIOCGNATL, &obj) == -1) 377 return (-1); 378 379 /* 380 * Return the real destination address and port number in the sockaddr 381 * passed in. 382 */ 383 memset((void *)server, 0, sizeof(struct sockaddr_in)); 384 satosin(server)->sin_len = sizeof(struct sockaddr_in); 385 satosin(server)->sin_family = AF_INET; 386 satosin(server)->sin_addr = natlook.nl_realip; 387 satosin(server)->sin_port = natlook.nl_realport; 388 389 return (0); 390 } 391