| 1 | /* $NetBSD: ip_rcmd_pxy.c,v 1.5 2013/09/14 12:06:19 martin Exp $ */ |
| 2 | |
| 3 | /* |
| 4 | * Copyright (C) 2012 by Darren Reed. |
| 5 | * |
| 6 | * See the IPFILTER.LICENCE file for details on licencing. |
| 7 | * |
| 8 | * Id: ip_rcmd_pxy.c,v 1.1.1.2 2012/07/22 13:45:33 darrenr Exp |
| 9 | * |
| 10 | * Simple RCMD transparent proxy for in-kernel use. For use with the NAT |
| 11 | * code. |
| 12 | */ |
| 13 | |
| 14 | #include <sys/cdefs.h> |
| 15 | __KERNEL_RCSID(1, "$NetBSD: ip_rcmd_pxy.c,v 1.5 2013/09/14 12:06:19 martin Exp $" ); |
| 16 | |
| 17 | #define IPF_RCMD_PROXY |
| 18 | |
| 19 | typedef struct rcmdinfo { |
| 20 | u_32_t rcmd_port; /* Port number seen */ |
| 21 | u_32_t rcmd_portseq; /* Sequence number where port is first seen */ |
| 22 | ipnat_t *rcmd_rule; /* Template rule for back connection */ |
| 23 | } rcmdinfo_t; |
| 24 | |
| 25 | void ipf_p_rcmd_main_load(void); |
| 26 | void ipf_p_rcmd_main_unload(void); |
| 27 | |
| 28 | int ipf_p_rcmd_init(void); |
| 29 | void ipf_p_rcmd_fini(void); |
| 30 | void ipf_p_rcmd_del(ipf_main_softc_t *, ap_session_t *); |
| 31 | int ipf_p_rcmd_new(void *, fr_info_t *, ap_session_t *, nat_t *); |
| 32 | int ipf_p_rcmd_out(void *, fr_info_t *, ap_session_t *, nat_t *); |
| 33 | int ipf_p_rcmd_in(void *, fr_info_t *, ap_session_t *, nat_t *); |
| 34 | u_short ipf_rcmd_atoi(char *); |
| 35 | int ipf_p_rcmd_portmsg(fr_info_t *, ap_session_t *, nat_t *); |
| 36 | |
| 37 | static frentry_t rcmdfr; |
| 38 | |
| 39 | static int rcmd_proxy_init = 0; |
| 40 | |
| 41 | |
| 42 | /* |
| 43 | * RCMD application proxy initialization. |
| 44 | */ |
| 45 | void |
| 46 | ipf_p_rcmd_main_load(void) |
| 47 | { |
| 48 | bzero((char *)&rcmdfr, sizeof(rcmdfr)); |
| 49 | rcmdfr.fr_ref = 1; |
| 50 | rcmdfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE; |
| 51 | MUTEX_INIT(&rcmdfr.fr_lock, "RCMD proxy rule lock" ); |
| 52 | rcmd_proxy_init = 1; |
| 53 | } |
| 54 | |
| 55 | |
| 56 | void |
| 57 | ipf_p_rcmd_main_unload(void) |
| 58 | { |
| 59 | if (rcmd_proxy_init == 1) { |
| 60 | MUTEX_DESTROY(&rcmdfr.fr_lock); |
| 61 | rcmd_proxy_init = 0; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | |
| 66 | /* |
| 67 | * Setup for a new RCMD proxy. |
| 68 | */ |
| 69 | int |
| 70 | ipf_p_rcmd_new(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
| 71 | { |
| 72 | tcphdr_t *tcp = (tcphdr_t *)fin->fin_dp; |
| 73 | rcmdinfo_t *rc; |
| 74 | ipnat_t *ipn; |
| 75 | |
| 76 | fin = fin; /* LINT */ |
| 77 | |
| 78 | KMALLOC(rc, rcmdinfo_t *); |
| 79 | if (rc == NULL) { |
| 80 | printf("ipf_p_rcmd_new:KMALLOCS(%zu) failed\n" , sizeof(*rc)); |
| 81 | return -1; |
| 82 | } |
| 83 | aps->aps_sport = tcp->th_sport; |
| 84 | aps->aps_dport = tcp->th_dport; |
| 85 | |
| 86 | ipn = ipf_proxy_rule_rev(nat); |
| 87 | if (ipn == NULL) { |
| 88 | KFREE(rc); |
| 89 | return -1; |
| 90 | } |
| 91 | |
| 92 | aps->aps_data = rc; |
| 93 | aps->aps_psiz = sizeof(*rc); |
| 94 | bzero((char *)rc, sizeof(*rc)); |
| 95 | |
| 96 | rc->rcmd_rule = ipn; |
| 97 | |
| 98 | return 0; |
| 99 | } |
| 100 | |
| 101 | |
| 102 | void |
| 103 | ipf_p_rcmd_del(ipf_main_softc_t *softc, ap_session_t *aps) |
| 104 | { |
| 105 | rcmdinfo_t *rci; |
| 106 | |
| 107 | rci = aps->aps_data; |
| 108 | if (rci != NULL) { |
| 109 | rci->rcmd_rule->in_flags |= IPN_DELETE; |
| 110 | ipf_nat_rule_deref(softc, &rci->rcmd_rule); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | |
| 115 | /* |
| 116 | * ipf_rcmd_atoi - implement a simple version of atoi |
| 117 | */ |
| 118 | u_short |
| 119 | ipf_rcmd_atoi(char *ptr) |
| 120 | { |
| 121 | char *s = ptr, c; |
| 122 | u_short i = 0; |
| 123 | |
| 124 | while (((c = *s++) != '\0') && ISDIGIT(c)) { |
| 125 | i *= 10; |
| 126 | i += c - '0'; |
| 127 | } |
| 128 | return i; |
| 129 | } |
| 130 | |
| 131 | |
| 132 | int |
| 133 | ipf_p_rcmd_portmsg(fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
| 134 | { |
| 135 | tcphdr_t *tcp, tcph, *tcp2 = &tcph; |
| 136 | int off, dlen, nflags, direction; |
| 137 | ipf_main_softc_t *softc; |
| 138 | ipf_nat_softc_t *softn; |
| 139 | char portbuf[8], *s; |
| 140 | rcmdinfo_t *rc; |
| 141 | fr_info_t fi; |
| 142 | u_short sp; |
| 143 | nat_t *nat2; |
| 144 | #ifdef USE_INET6 |
| 145 | ip6_t *ip6; |
| 146 | #endif |
| 147 | int tcpsz; |
| 148 | int slen = 0; |
| 149 | ip_t *ip; |
| 150 | mb_t *m; |
| 151 | |
| 152 | tcp = (tcphdr_t *)fin->fin_dp; |
| 153 | |
| 154 | m = fin->fin_m; |
| 155 | ip = fin->fin_ip; |
| 156 | tcpsz = TCP_OFF(tcp) << 2; |
| 157 | #ifdef USE_INET6 |
| 158 | ip6 = (ip6_t *)fin->fin_ip; |
| 159 | #endif |
| 160 | softc = fin->fin_main_soft; |
| 161 | softn = softc->ipf_nat_soft; |
| 162 | off = (char *)tcp - (char *)ip + tcpsz + fin->fin_ipoff; |
| 163 | |
| 164 | dlen = fin->fin_dlen - tcpsz; |
| 165 | if (dlen <= 0) |
| 166 | return 0; |
| 167 | |
| 168 | rc = (rcmdinfo_t *)aps->aps_data; |
| 169 | if ((rc->rcmd_portseq != 0) && |
| 170 | (tcp->th_seq != rc->rcmd_portseq)) |
| 171 | return 0; |
| 172 | |
| 173 | bzero(portbuf, sizeof(portbuf)); |
| 174 | COPYDATA(m, off, MIN(sizeof(portbuf), dlen), portbuf); |
| 175 | |
| 176 | portbuf[sizeof(portbuf) - 1] = '\0'; |
| 177 | s = portbuf; |
| 178 | sp = ipf_rcmd_atoi(s); |
| 179 | if (sp == 0) { |
| 180 | #ifdef IP_RCMD_PROXY_DEBUG |
| 181 | printf("ipf_p_rcmd_portmsg:sp == 0 dlen %d [%s]\n" , |
| 182 | dlen, portbuf); |
| 183 | #endif |
| 184 | return 0; |
| 185 | } |
| 186 | |
| 187 | if (rc->rcmd_port != 0 && sp != rc->rcmd_port) { |
| 188 | #ifdef IP_RCMD_PROXY_DEBUG |
| 189 | printf("ipf_p_rcmd_portmsg:sp(%d) != rcmd_port(%d)\n" , |
| 190 | sp, rc->rcmd_port); |
| 191 | #endif |
| 192 | return 0; |
| 193 | } |
| 194 | |
| 195 | rc->rcmd_port = sp; |
| 196 | rc->rcmd_portseq = tcp->th_seq; |
| 197 | |
| 198 | /* |
| 199 | * Initialise the packet info structure so we can search the NAT |
| 200 | * table to see if there already is soemthing present that matches |
| 201 | * up with what we want to add. |
| 202 | */ |
| 203 | bcopy((char *)fin, (char *)&fi, sizeof(fi)); |
| 204 | fi.fin_flx |= FI_IGNORE; |
| 205 | fi.fin_data[0] = 0; |
| 206 | fi.fin_data[1] = sp; |
| 207 | fi.fin_src6 = nat->nat_ndst6; |
| 208 | fi.fin_dst6 = nat->nat_nsrc6; |
| 209 | |
| 210 | if (nat->nat_v[0] == 6) { |
| 211 | #ifdef USE_INET6 |
| 212 | if (nat->nat_dir == NAT_OUTBOUND) { |
| 213 | nat2 = ipf_nat6_outlookup(&fi, NAT_SEARCH|IPN_TCP, |
| 214 | nat->nat_pr[1], |
| 215 | &nat->nat_osrc6.in6, |
| 216 | &nat->nat_odst6.in6); |
| 217 | } else { |
| 218 | nat2 = ipf_nat6_inlookup(&fi, NAT_SEARCH|IPN_TCP, |
| 219 | nat->nat_pr[0], |
| 220 | &nat->nat_osrc6.in6, |
| 221 | &nat->nat_odst6.in6); |
| 222 | } |
| 223 | #else |
| 224 | nat2 = (void *)-1; |
| 225 | #endif |
| 226 | } else { |
| 227 | if (nat->nat_dir == NAT_OUTBOUND) { |
| 228 | nat2 = ipf_nat_outlookup(&fi, NAT_SEARCH|IPN_TCP, |
| 229 | nat->nat_pr[1], |
| 230 | nat->nat_osrcip, |
| 231 | nat->nat_odstip); |
| 232 | } else { |
| 233 | nat2 = ipf_nat_inlookup(&fi, NAT_SEARCH|IPN_TCP, |
| 234 | nat->nat_pr[0], |
| 235 | nat->nat_osrcip, |
| 236 | nat->nat_odstip); |
| 237 | } |
| 238 | } |
| 239 | if (nat2 != NULL) |
| 240 | return APR_ERR(1); |
| 241 | |
| 242 | /* |
| 243 | * Add skeleton NAT entry for connection which will come |
| 244 | * back the other way. |
| 245 | */ |
| 246 | |
| 247 | if (nat->nat_v[0] == 6) { |
| 248 | #ifdef USE_INET6 |
| 249 | slen = ip6->ip6_plen; |
| 250 | ip6->ip6_plen = htons(sizeof(*tcp)); |
| 251 | #endif |
| 252 | } else { |
| 253 | slen = ip->ip_len; |
| 254 | ip->ip_len = htons(fin->fin_hlen + sizeof(*tcp)); |
| 255 | } |
| 256 | |
| 257 | /* |
| 258 | * Fill out the fake TCP header with a few fields that ipfilter |
| 259 | * considers to be important. |
| 260 | */ |
| 261 | bzero((char *)tcp2, sizeof(*tcp2)); |
| 262 | tcp2->th_win = htons(8192); |
| 263 | TCP_OFF_A(tcp2, 5); |
| 264 | tcp2->th_flags = TH_SYN; |
| 265 | |
| 266 | fi.fin_dp = (char *)tcp2; |
| 267 | fi.fin_fr = &rcmdfr; |
| 268 | fi.fin_dlen = sizeof(*tcp2); |
| 269 | fi.fin_plen = fi.fin_hlen + sizeof(*tcp2); |
| 270 | fi.fin_flx &= FI_LOWTTL|FI_FRAG|FI_TCPUDP|FI_OPTIONS|FI_IGNORE; |
| 271 | |
| 272 | if (nat->nat_dir == NAT_OUTBOUND) { |
| 273 | fi.fin_out = 0; |
| 274 | direction = NAT_INBOUND; |
| 275 | } else { |
| 276 | fi.fin_out = 1; |
| 277 | direction = NAT_OUTBOUND; |
| 278 | } |
| 279 | nflags = SI_W_SPORT|NAT_SLAVE|IPN_TCP; |
| 280 | |
| 281 | MUTEX_ENTER(&softn->ipf_nat_new); |
| 282 | if (fin->fin_v == 4) |
| 283 | nat2 = ipf_nat_add(&fi, rc->rcmd_rule, NULL, nflags, |
| 284 | direction); |
| 285 | #ifdef USE_INET6 |
| 286 | else |
| 287 | nat2 = ipf_nat6_add(&fi, rc->rcmd_rule, NULL, nflags, |
| 288 | direction); |
| 289 | #endif |
| 290 | MUTEX_EXIT(&softn->ipf_nat_new); |
| 291 | |
| 292 | if (nat2 != NULL) { |
| 293 | (void) ipf_nat_proto(&fi, nat2, IPN_TCP); |
| 294 | MUTEX_ENTER(&nat2->nat_lock); |
| 295 | ipf_nat_update(&fi, nat2); |
| 296 | MUTEX_EXIT(&nat2->nat_lock); |
| 297 | fi.fin_ifp = NULL; |
| 298 | if (nat2->nat_dir == NAT_INBOUND) |
| 299 | fi.fin_dst6 = nat->nat_osrc6; |
| 300 | (void) ipf_state_add(softc, &fi, NULL, SI_W_SPORT); |
| 301 | } |
| 302 | if (nat->nat_v[0] == 6) { |
| 303 | #ifdef USE_INET6 |
| 304 | ip6->ip6_plen = slen; |
| 305 | #endif |
| 306 | } else { |
| 307 | ip->ip_len = slen; |
| 308 | } |
| 309 | if (nat2 == NULL) |
| 310 | return APR_ERR(1); |
| 311 | return 0; |
| 312 | } |
| 313 | |
| 314 | |
| 315 | int |
| 316 | ipf_p_rcmd_out(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
| 317 | { |
| 318 | if (nat->nat_dir == NAT_OUTBOUND) |
| 319 | return ipf_p_rcmd_portmsg(fin, aps, nat); |
| 320 | return 0; |
| 321 | } |
| 322 | |
| 323 | |
| 324 | int |
| 325 | ipf_p_rcmd_in(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
| 326 | { |
| 327 | if (nat->nat_dir == NAT_INBOUND) |
| 328 | return ipf_p_rcmd_portmsg(fin, aps, nat); |
| 329 | return 0; |
| 330 | } |
| 331 | |