/*
 * NAT module for NetBIOS datagram service
 * Matt Chapman <matthewc@cse.unsw.edu.au>
 */

#include <linux/module.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/kernel.h>
#include <net/tcp.h>
#include <net/udp.h>

#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv4/ip_nat_helper.h>

MODULE_DESCRIPTION("NAT module for NetBIOS datagram service");
MODULE_AUTHOR("Matt Chapman <matthewc@cse.unsw.edu.au>");

/* UDP port for the datagram service */
#define NETBIOS_PORT 138

/* fixed part of a NetBIOS header */
struct netbios_hdr
{
	u_int8_t  nodetype;
	u_int8_t  flags;
	u_int16_t dgramid;
	u_int32_t srcip;
	u_int16_t srcport;
	u_int16_t length;
	u_int16_t offset;
};

/* do translation on a NetBIOS packet */
static unsigned int netbios_nat(struct ip_conntrack *ct,
				struct ip_nat_info *info,
				enum ip_conntrack_info ctinfo,
				unsigned int hooknum,
				struct sk_buff **pskb)
{
	struct iphdr *iph = (*pskb)->nh.iph;
	struct udphdr *udph = (void *)iph + iph->ihl * 4;
	unsigned int udplen = (*pskb)->len - iph->ihl * 4;
	unsigned int datalen = udplen - sizeof(struct udphdr);
	struct netbios_hdr *nbh;
	int dir;

	/* deny short packets */
	if (datalen < sizeof(struct netbios_hdr))
		return NF_DROP;

	/* only mangle things once: during POST_ROUTING in original direction,
	   and during PRE_ROUTING in reply direction */
	dir = CTINFO2DIR(ctinfo);
	if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
	      || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY)))
		return NF_ACCEPT;

	/* change addresses inside NetBIOS header */
	nbh = (struct netbios_hdr *)((char *)udph + sizeof(struct udphdr));
	nbh->srcip   = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
	nbh->srcport = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.udp.port;

	/* fix checksums */
	(*pskb)->csum = csum_partial((char *)nbh, datalen, 0);
	udph->check = 0;
	udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr, udplen,
					IPPROTO_UDP, csum_partial((char *)udph,
					sizeof(struct udphdr), (*pskb)->csum));
	ip_send_check(iph);

	/* done */
	return NF_ACCEPT;
}

static struct ip_nat_helper netbios_helper = { { NULL, NULL },
					{ { 0, { __constant_htons(NETBIOS_PORT) } },
					  { 0, { 0 }, IPPROTO_UDP } },
					       { { 0, { 0xFFFF } },
						 { 0, { 0 }, 0xFFFF } },
					       netbios_nat, "netbios" };

static int __init init(void)
{
	return ip_nat_helper_register(&netbios_helper);
}

static void __exit fini(void)
{
	ip_nat_helper_unregister(&netbios_helper);
}

module_init(init);
module_exit(fini);
