/* * (C) 2012 by Jozsef Kadlecsik * (C) 2012 by Pablo Neira Ayuso * * Based on: RPC extension for conntrack. * * This port has been sponsored by Vyatta Inc. * * Original copyright notice: * * (C) 2000 by Marcelo Barbosa Lima * (C) 2001 by Rusty Russell * (C) 2002,2003 by Ian (Larry) Latter * (C) 2004,2005 by David Stes * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "conntrackd.h" #include "network.h" /* for before and after */ #include "helper.h" #include "myct.h" #include "log.h" #include #include #include #define _GNU_SOURCE #include #include #include #include #include #include #include #include /* RFC 1050: RPC: Remote Procedure Call Protocol Specification Version 2 */ /* RFC 1014: XDR: External Data Representation Standard */ #define SUPPORTED_RPC_VERSION 2 struct rpc_info { /* XID */ uint32_t xid; /* program */ uint32_t pm_prog; /* program version */ uint32_t pm_vers; /* transport protocol: TCP|UDP */ uint32_t pm_prot; }; /* So, this packet has hit the connection tracking matching code. Mangle it, and change the expectation to match the new version. */ static unsigned int nf_nat_rpc(struct pkt_buff *pkt, int dir, struct nf_expect *exp, uint8_t proto, uint32_t *port_ptr) { const struct nf_conntrack *expected; struct nf_conntrack *nat_tuple; uint16_t initial_port, port; expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); nat_tuple = nfct_new(); if (nat_tuple == NULL) return NF_ACCEPT; initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); /* libnetfilter_conntrack needs this */ nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, proto); nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); /* When you see the packet, we need to NAT it the same as the * this one. */ nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); /* Try to get same port: if not, try to change it. */ for (port = ntohs(initial_port); port != 0; port++) { int ret; nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); ret = cthelper_add_expect(exp); if (ret == 0) break; else if (ret != -EBUSY) { port = 0; break; } } nfct_destroy(nat_tuple); if (port == 0) return NF_DROP; *port_ptr = htonl(port); return NF_ACCEPT; } #define OFFSET(o, n) ((o) += n) #define ROUNDUP(n) ((((n) + 3)/4)*4) static int rpc_call(const uint32_t *data, uint32_t offset, uint32_t datalen, struct rpc_info *rpc_info) { uint32_t p, r; /* RPC CALL message body */ /* call_body { * rpcvers * prog * vers * proc * opaque_auth cred * opaque_auth verf * pmap * } * * opaque_auth { * flavour * opaque[len] <= MAX_AUTH_BYTES * } */ if (datalen < OFFSET(offset, 4*4 + 2*2*4)) { pr_debug("RPC CALL: too short packet: %u < %u\n", datalen, offset); return -1; } /* Check rpcversion */ p = IXDR_GET_INT32(data); if (p != SUPPORTED_RPC_VERSION) { pr_debug("RPC CALL: wrong rpcvers %u != %u\n", p, SUPPORTED_RPC_VERSION); return -1; } /* Skip non-portmap requests */ p = IXDR_GET_INT32(data); if (p != PMAPPROG) { pr_debug("RPC CALL: not portmap %u != %lu\n", p, PMAPPROG); return -1; } /* Check portmap version */ p = IXDR_GET_INT32(data); if (p != PMAPVERS) { pr_debug("RPC CALL: wrong portmap version %u != %lu\n", p, PMAPVERS); return -1; } /* Skip non PMAPPROC_GETPORT requests */ p = IXDR_GET_INT32(data); if (p != PMAPPROC_GETPORT) { pr_debug("RPC CALL: not PMAPPROC_GETPORT %u != %lu\n", p, PMAPPROC_GETPORT); return -1; } /* Check and skip credentials */ r = IXDR_GET_INT32(data); p = IXDR_GET_INT32(data); pr_debug("RPC CALL: cred: %u %u (%u, %u)\n", r, p, datalen, offset); if (p > MAX_AUTH_BYTES) { pr_debug("RPC CALL: invalid sized cred %u > %u\n", p, MAX_AUTH_BYTES); return -1; } r = ROUNDUP(p); if (datalen < OFFSET(offset, r)) { pr_debug("RPC CALL: too short to carry cred: %u < %u, %u\n", datalen, offset, r); return -1; } data += r/4; /* Check and skip verifier */ r = IXDR_GET_INT32(data); p = IXDR_GET_INT32(data); pr_debug("RPC CALL: verf: %u %u (%u, %u)\n", r, p, datalen, offset); if (p > MAX_AUTH_BYTES) { pr_debug("RPC CALL: invalid sized verf %u > %u\n", p, MAX_AUTH_BYTES); return -1; } r = ROUNDUP(p); if (datalen < OFFSET(offset, r)) { pr_debug("RPC CALL: too short to carry verf: %u < %u, %u\n", datalen, offset, r); return -1; } data += r/4; /* pmap { * prog * vers * prot * port * } */ /* Check CALL size */ if (datalen != offset + 4*4) { pr_debug("RPC CALL: invalid size to carry pmap: %u != %u\n", datalen, offset + 4*4); return -1; } rpc_info->pm_prog = IXDR_GET_INT32(data); rpc_info->pm_vers = IXDR_GET_INT32(data); rpc_info->pm_prot = IXDR_GET_INT32(data); /* Check supported protocols */ if (!(rpc_info->pm_prot == IPPROTO_TCP || rpc_info->pm_prot == IPPROTO_UDP)) { pr_debug("RPC CALL: unsupported protocol %u", rpc_info->pm_prot); return -1; } p = IXDR_GET_INT32(data); /* Check port: must be zero */ if (p != 0) { pr_debug("RPC CALL: port is nonzero %u\n", ntohl(p)); return -1; } pr_debug("RPC CALL: processed: xid %u, prog %u, vers %u, prot %u\n", rpc_info->xid, rpc_info->pm_prog, rpc_info->pm_vers, rpc_info->pm_prot); return 0; } static int rpc_reply(uint32_t *data, uint32_t offset, uint32_t datalen, struct rpc_info *rpc_info, uint32_t **port_ptr) { uint16_t port; uint32_t p, r; /* RPC REPLY message body */ /* reply_body { * reply_stat * xdr_union { * accepted_reply * rejected_reply * } * } * accepted_reply { * opaque_auth verf * accept_stat * xdr_union { * port * struct mismatch_info * } * } */ /* Check size: reply status */ if (datalen < OFFSET(offset, 4)) { pr_debug("RPC REPL: too short, missing rp_stat: %u < %u\n", datalen, offset); return -1; } p = IXDR_GET_INT32(data); /* Check accepted request */ if (p != MSG_ACCEPTED) { pr_debug("RPC REPL: not accepted %u != %u\n", p, MSG_ACCEPTED); return -1; } /* Check and skip verifier */ if (datalen < OFFSET(offset, 2*4)) { pr_debug("RPC REPL: too short, missing verf: %u < %u\n", datalen, offset); return -1; } r = IXDR_GET_INT32(data); p = IXDR_GET_INT32(data); pr_debug("RPC REPL: verf: %u %u (%u, %u)\n", r, p, datalen, offset); if (p > MAX_AUTH_BYTES) { pr_debug("RPC REPL: invalid sized verf %u > %u\n", p, MAX_AUTH_BYTES); return -1; } r = ROUNDUP(p); /* verifier + ac_stat + port */ if (datalen != OFFSET(offset, r) + 2*4) { pr_debug("RPC REPL: invalid size to carry verf and " "success: %u != %u\n", datalen, offset + 2*4); return -1; } data += r/4; /* Check success */ p = IXDR_GET_INT32(data); if (p != SUCCESS) { pr_debug("RPC REPL: not success %u != %u\n", p, SUCCESS); return -1; } /* Get port */ *port_ptr = data; port = IXDR_GET_INT32(data); /* -Wunused-but-set-parameter */ if (port == 0) { pr_debug("RPC REPL: port is zero\n"); return -1; } pr_debug("RPC REPL: processed: xid %u, prog %u, vers %u, " "prot %u, port %u\n", rpc_info->xid, rpc_info->pm_prog, rpc_info->pm_vers, rpc_info->pm_prot, port); return 0; } static int rpc_helper_cb(struct pkt_buff *pkt, uint32_t protoff, struct myct *myct, uint32_t ctinfo) { int dir = CTINFO2DIR(ctinfo); unsigned int offset = protoff, datalen; uint32_t *data, *port_ptr = NULL, xid; uint16_t port; uint8_t proto = nfct_get_attr_u8(myct->ct, ATTR_L4PROTO); enum msg_type rm_dir; struct rpc_info *rpc_info = myct->priv_data; union nfct_attr_grp_addr addr, daddr; struct nf_expect *exp = NULL; int ret = NF_ACCEPT; /* Until there's been traffic both ways, don't look into TCP packets. */ if (proto == IPPROTO_TCP && ctinfo != IP_CT_ESTABLISHED && ctinfo != IP_CT_ESTABLISHED_REPLY) { pr_debug("TCP RPC: Conntrackinfo = %u\n", ctinfo); return ret; } if (proto == IPPROTO_TCP) { struct tcphdr *th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); offset += th->doff * 4; } else { offset += sizeof(struct udphdr); } /* Skip broken headers */ if (offset % 4) { pr_debug("RPC: broken header: offset %u%%4 != 0\n", offset); return ret; } /* Take into Record Fragment header */ if (proto == IPPROTO_TCP) offset += 4; datalen = pktb_len(pkt); data = (uint32_t *)(pktb_network_header(pkt) + offset); /* rpc_msg { * xid * direction * xdr_union { * call_body * reply_body * } * } */ /* Check minimal msg size: xid + direction */ if (datalen < OFFSET(offset, 2*4)) { pr_debug("RPC: too short packet: %u < %u\n", datalen, offset); return ret; } xid = IXDR_GET_INT32(data); rm_dir = IXDR_GET_INT32(data); /* Check direction */ if (!((rm_dir == CALL && dir == MYCT_DIR_ORIG) || (rm_dir == REPLY && dir == MYCT_DIR_REPL))) { pr_debug("RPC: rm_dir != dir %u != %u\n", rm_dir, dir); goto out; } if (rm_dir == CALL) { if (rpc_call(data, offset, datalen, rpc_info) < 0) goto out; rpc_info->xid = xid; return ret; } else { /* Check XID */ if (xid != rpc_info->xid) { pr_debug("RPC REPL: XID does not match: %u != %u\n", xid, rpc_info->xid); goto out; } if (rpc_reply(data, offset, datalen, rpc_info, &port_ptr) < 0) goto out; port = IXDR_GET_INT32(port_ptr); port = htons(port); /* We refer to the reverse direction ("!dir") tuples here, * because we're expecting something in the other direction. * Doesn't matter unless NAT is happening. */ cthelper_get_addr_dst(myct->ct, !dir, &daddr); cthelper_get_addr_src(myct->ct, !dir, &addr); exp = nfexp_new(); if (exp == NULL) goto out; if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, rpc_info->pm_prot, NULL, &port, NF_CT_EXPECT_PERMANENT)) { pr_debug("RPC: failed to init expectation\n"); goto out_exp; } /* Now, NAT might want to mangle the packet, and register the * (possibly changed) expectation itself. */ if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { ret = nf_nat_rpc(pkt, dir, exp, rpc_info->pm_prot, port_ptr); goto out_exp; } /* Can't expect this? Best to drop packet now. */ if (cthelper_add_expect(exp) < 0) { pr_debug("RPC: cannot add expectation: %s\n", strerror(errno)); ret = NF_DROP; } } out_exp: nfexp_destroy(exp); out: rpc_info->xid = 0; return ret; } static struct ctd_helper rpc_helper_tcp = { .name = "rpc", .l4proto = IPPROTO_TCP, .cb = rpc_helper_cb, .priv_data_len = sizeof(struct rpc_info), .policy = { { .name = "rpc", .expect_max = 1, .expect_timeout = 300, }, }, }; static struct ctd_helper rpc_helper_udp = { .name = "rpc", .l4proto = IPPROTO_UDP, .cb = rpc_helper_cb, .priv_data_len = sizeof(struct rpc_info), .policy = { { .name = "rpc", .expect_max = 1, .expect_timeout = 300, }, }, }; void __attribute__ ((constructor)) rpc_init(void); void rpc_init(void) { helper_register(&rpc_helper_tcp); helper_register(&rpc_helper_udp); }