blob: 3a7b337135f04d29c9075727bd6f0da85db45aaa [file] [log] [blame]
/*
* (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
* (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* Based on: RPC extension for conntrack.
*
* This port has been sponsored by Vyatta Inc. <http://www.vyatta.com>
*
* Original copyright notice:
*
* (C) 2000 by Marcelo Barbosa Lima <marcelo.lima@dcc.unicamp.br>
* (C) 2001 by Rusty Russell <rusty@rustcorp.com.au>
* (C) 2002,2003 by Ian (Larry) Latter <Ian.Latter@mq.edu.au>
* (C) 2004,2005 by David Stes <stes@pandora.be>
*
* 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 <errno.h>
#include <rpc/rpc_msg.h>
#include <rpc/pmap_prot.h>
#define _GNU_SOURCE
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <libmnl/libmnl.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
#include <libnetfilter_queue/pktbuff.h>
#include <linux/netfilter.h>
/* 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);
}