Files
openflow/datapath/nx_act_snat.c
T
Justin Pettit 2f58a5d003 Fix crash when SNAT support is built and traffic is in-band.
In order to make netfilter happy, we have to create a fake routing table
entry.  Unfortunately, the kernel really doesn't like this when it actually
needs to make use of it.  This code now removes the link between the packet
and the fake routing entry when the packet is being sent to a local port.
2008-10-21 17:16:46 -07:00

552 lines
13 KiB
C

#ifdef SUPPORT_SNAT
/*
* Distributed under the terms of the GNU GPL version 2.
* Copyright (c) 2008 Nicira Networks
*/
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/in.h>
#include <net/ip.h>
#include <linux/icmp.h>
#include <linux/if_ether.h>
#include <net/arp.h>
#include <net/route.h>
#include "forward.h"
#include "dp_act.h"
#include "nx_act_snat.h"
/* We need these fake structures to make netfilter happy --
* lots of places assume that skb->dst != NULL, which isn't
* all that unreasonable.
*
* Currently, we fill in the PMTU entry because netfilter
* refragmentation needs it, and the rt_flags entry because
* ipt_REJECT needs it. Future netfilter modules might
* require us to fill additional fields. */
static struct net_device __fake_net_device = {
.hard_header_len = ETH_HLEN
};
static struct rtable __fake_rtable = {
.u = {
.dst = {
.__refcnt = ATOMIC_INIT(1),
.dev = &__fake_net_device,
.path = &__fake_rtable.u.dst,
.metrics = {[RTAX_MTU - 1] = 1500},
.flags = DST_NOXFRM,
}
},
.rt_flags = 0,
};
/* Define ARP for IP since the Linux headers don't do it cleanly. */
struct ip_arphdr {
uint16_t ar_hrd;
uint16_t ar_pro;
uint8_t ar_hln;
uint8_t ar_pln;
uint16_t ar_op;
uint8_t ar_sha[ETH_ALEN];
uint32_t ar_sip;
uint8_t ar_tha[ETH_ALEN];
uint32_t ar_tip;
} __attribute__((packed));
OFP_ASSERT(sizeof(struct ip_arphdr) == 28);
/* Push the Ethernet header back on and tranmit the packet. */
static int
dp_xmit_skb_push(struct sk_buff *skb)
{
skb_push(skb, ETH_HLEN);
return dp_xmit_skb(skb);
}
/* Perform maintainence related to a SNAT'd interface. Currently, this only
* checks whether MAC->IP bindings have expired.
*
* Called with the RCU read lock */
void
snat_maint(struct net_bridge_port *p)
{
struct snat_conf *sc;
struct snat_mapping *m, *n;
unsigned long flags;
unsigned long timeout;
spin_lock_irqsave(&p->lock, flags);
sc = p->snat;
if (!sc)
goto done;
timeout = sc->mac_timeout * HZ;
list_for_each_entry_safe (m, n, &sc->mappings, node) {
if (time_after(jiffies, m->used + timeout)) {
list_del(&m->node);
kfree(m);
}
}
done:
spin_unlock_irqrestore(&p->lock, flags);
}
/* When the packet is bound for a local interface, strip off the fake
* routing table.
*/
void snat_local_in(struct sk_buff *skb)
{
if (skb->dst == (struct dst_entry *)&__fake_rtable) {
dst_release(skb->dst);
skb->dst = NULL;
}
}
/* Check whether destination IP's address is in the IP->MAC mappings.
* If it is, then overwrite the destination MAC with the value from the
* cache.
*
* Returns -1 if there is a problem, otherwise 0. */
static int
dnat_mac(struct net_bridge_port *p, struct sk_buff *skb)
{
struct snat_conf *sc;
struct iphdr *iph = ip_hdr(skb);
struct ethhdr *eh = eth_hdr(skb);
struct snat_mapping *m;
unsigned long flags;
spin_lock_irqsave(&p->lock, flags);
sc = p->snat;
if (!sc) {
spin_unlock_irqrestore(&p->lock, flags);
return -EINVAL;
}
if (skb->protocol != htons(ETH_P_IP)) {
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
list_for_each_entry (m, &sc->mappings, node) {
if (m->ip_addr == iph->daddr){
/* Found it! */
if (!make_writable(&skb)) {
if (net_ratelimit())
printk("make_writable failed\n");
spin_unlock_irqrestore(&p->lock, flags);
return -EINVAL;
}
m->used = jiffies;
memcpy(eh->h_dest, m->hw_addr, ETH_ALEN);
break;
}
}
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
static int
snat_pre_route_finish(struct sk_buff *skb)
{
struct net_bridge_port *p = skb->dev->br_port;
skb->dst = (struct dst_entry *)&__fake_rtable;
dst_hold(skb->dst);
/* If SNAT is configured for this input device, check the IP->MAC
* mappings to see if we should update the destination MAC. */
if (p->snat)
dnat_mac(skb->dev->br_port, skb);
return 0;
}
/* Checks whether 'skb' is an ARP request for an SNAT'd interface. If
* so, it will generate a response.
*
* Returns 0 if the packet was not handled. Otherwise, -1 is returned
* and the caller is responsible for freeing 'skb'. */
static int
handle_arp_snat(struct sk_buff *skb)
{
struct net_bridge_port *p = skb->dev->br_port;
struct ip_arphdr *ah = (struct ip_arphdr *)arp_hdr(skb);
uint32_t ip_addr;
unsigned long flags;
struct snat_conf *sc;
if ((ah->ar_op != htons(ARPOP_REQUEST))
|| ah->ar_hln != ETH_ALEN
|| ah->ar_pro != htons(ETH_P_IP)
|| ah->ar_pln != 4)
return 0;
ip_addr = ntohl(ah->ar_tip);
spin_lock_irqsave(&p->lock, flags);
sc = p->snat;
/* We're only interested in addresses we rewrite. */
if (!sc || (sc && ((ip_addr < sc->ip_addr_start)
|| (ip_addr > sc->ip_addr_end)))) {
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
spin_unlock_irqrestore(&p->lock, flags);
arp_send(ARPOP_REPLY, ETH_P_ARP, ah->ar_sip, skb->dev, ah->ar_tip,
ah->ar_sha, p->dp->netdev->dev_addr, ah->ar_sha);
return -1;
}
/* Checks whether 'skb' is a ping request for an SNAT'd interface. If
* so, it will generate a response.
*
* Returns 0 if the packet was not handled. Otherwise, -1 is returned
* and the caller is responsible for freeing 'skb'. */
static int
handle_icmp_snat(struct sk_buff *skb)
{
struct net_bridge_port *p = skb->dev->br_port;
struct snat_conf *sc;
struct ethhdr *eh;
struct iphdr *iph = ip_hdr(skb);
uint32_t ip_addr;
struct icmphdr *icmph;
unsigned int datalen;
uint8_t tmp_eth[ETH_ALEN];
uint32_t tmp_ip;
struct sk_buff *nskb;
unsigned long flags;
ip_addr = ntohl(iph->daddr);
spin_lock_irqsave(&p->lock, flags);
sc = p->snat;
/* We're only interested in addresses we rewrite. */
if (!sc || (sc && ((ip_addr < sc->ip_addr_start)
|| (ip_addr > sc->ip_addr_end)))) {
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
spin_unlock_irqrestore(&p->lock, flags);
icmph = (struct icmphdr *) ((u_int32_t *)iph + iph->ihl);
datalen = skb->len - iph->ihl * 4;
/* Drop fragments and packets not long enough to hold the ICMP
* header. */
if (((ntohs(iph->frag_off) & IP_OFFSET) != 0) || datalen < 4)
return 0;
/* We only respond to echo requests to our address. Continue
* processing replies and other ICMP messages since they may be
* intended for NAT'd hosts. */
if (icmph->type != ICMP_ECHO)
return 0;
/* Send an echo reply in response */
nskb = skb_copy(skb, GFP_ATOMIC);
if (!nskb) {
if (net_ratelimit())
printk("skb copy failed for icmp reply\n");
return -1;
}
eh = eth_hdr(nskb);
iph = ip_hdr(nskb);
icmph = (struct icmphdr *) ((u_int32_t *)iph + iph->ihl);
tmp_ip = iph->daddr;
iph->daddr = iph->saddr;
iph->saddr = tmp_ip;
memcpy(tmp_eth, eh->h_dest, ETH_ALEN);
memcpy(eh->h_dest, eh->h_source, ETH_ALEN);
memcpy(eh->h_source, tmp_eth, ETH_ALEN);
icmph->type = ICMP_ECHOREPLY;
dp_xmit_skb_push(nskb);
return -1;
}
/* Check if any SNAT maintenance needs to be done on 'skb' before it's
* checked against the datapath's tables. This includes DNAT
* modification based on prior SNAT action and responding to ARP and
* echo requests for the SNAT interface.
*
* Returns 0 if 'skb' should continue to be processed by the caller.
* Returns -1 if the packet was handled, and the caller should free
* 'skb'.
*/
int
snat_pre_route(struct sk_buff *skb)
{
struct iphdr *iph;
int len;
if (skb->protocol == htons(ETH_P_ARP))
return handle_arp_snat(skb);
else if (skb->protocol != htons(ETH_P_IP))
return 0;
iph = ip_hdr(skb);
if (iph->ihl < 5 || iph->version != 4)
goto ipv4_error;
if (!pskb_may_pull(skb, iph->ihl*4))
goto ipv4_error;
/* Check if we need to echo reply for this address */
if ((iph->protocol == IPPROTO_ICMP) && (handle_icmp_snat(skb)))
return -1;
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto ipv4_error;
len = ntohs(iph->tot_len);
if ((skb->len < len) || len < (iph->ihl*4))
goto ipv4_error;
if (pskb_trim_rcsum(skb, len))
goto ipv4_error;
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, skb->dev, NULL,
snat_pre_route_finish);
ipv4_error:
return -1;
}
static int
snat_skb_finish(struct sk_buff *skb)
{
NF_HOOK(PF_INET, NF_INET_POST_ROUTING, skb, NULL, skb->dev,
dp_xmit_skb_push);
return 0;
}
/* Update the MAC->IP mappings for the private side of the SNAT'd
* interface. */
static void
update_mapping(struct net_bridge_port *p, struct sk_buff *skb)
{
unsigned long flags;
struct snat_conf *sc;
struct iphdr *iph = ip_hdr(skb);
struct ethhdr *eh = eth_hdr(skb);
struct snat_mapping *m;
spin_lock_irqsave(&p->lock, flags);
sc = p->snat;
if (!sc)
goto done;
list_for_each_entry (m, &sc->mappings, node) {
if (m->ip_addr == iph->saddr){
if (memcmp(m->hw_addr, eh->h_source, ETH_ALEN)) {
memcpy(m->hw_addr, eh->h_source, ETH_ALEN);
}
m->used = jiffies;
goto done;
}
}
m = kmalloc(sizeof *m, GFP_ATOMIC);
m->ip_addr = iph->saddr;
memcpy(m->hw_addr, eh->h_source, ETH_ALEN);
m->used = jiffies;
list_add(&m->node, &sc->mappings);
done:
spin_unlock_irqrestore(&p->lock, flags);
}
/* Perform SNAT modification on 'skb' and send out 'out_port'. If the
* port was not configured for SNAT, it will be sent through the interface
* unmodified. 'skb' is not consumed, so caller will need to free it.
*/
void
snat_skb(struct datapath *dp, struct sk_buff *skb, int out_port)
{
struct net_bridge_port *p = dp->ports[out_port];
struct sk_buff *nskb;
if (!p)
return;
nskb = skb_copy(skb, GFP_ATOMIC);
if (!nskb)
return;
nskb->dev = p->dev;
/* We only SNAT IP, so just send it on its way if not */
if (skb->protocol != htons(ETH_P_IP)) {
dp_xmit_skb(nskb);
return;
}
/* Set the source MAC to the OF interface */
memcpy(eth_hdr(nskb)->h_source, dp->netdev->dev_addr, ETH_ALEN);
update_mapping(p, skb);
/* Take the Ethernet header back off for netfilter hooks. */
skb_pull(nskb, ETH_HLEN);
NF_HOOK(PF_INET, NF_INET_FORWARD, nskb, skb->dev, nskb->dev,
snat_skb_finish);
}
/* Remove SNAT configuration on port 'p'.
*
* NB: The caller must hold the port's spinlock. */
int
snat_free_conf(struct net_bridge_port *p)
{
struct snat_conf *sc = p->snat;
if (!sc)
return -EINVAL;
/* Free existing mapping entries */
while (!list_empty(&sc->mappings)) {
struct snat_mapping *m = list_entry(sc->mappings.next,
struct snat_mapping, node);
list_del(&m->node);
kfree(m);
}
kfree(p->snat);
p->snat = NULL;
return 0;
}
/* Remove SNAT configuration from an interface. */
static int
snat_del_port(struct datapath *dp, uint16_t port)
{
unsigned long flags;
struct net_bridge_port *p = dp->ports[port];
if (!p) {
if (net_ratelimit())
printk("Attempt to remove snat on non-existent port: %d\n", port);
return -EINVAL;
}
spin_lock_irqsave(&p->lock, flags);
if (snat_free_conf(p)) {
/* SNAT not configured on this port */
spin_unlock_irqrestore(&p->lock, flags);
if (net_ratelimit())
printk("Attempt to remove snat on non-snat port: %d\n", port);
return -EINVAL;
}
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
/* Add SNAT configuration to an interface. */
static int
snat_add_port(struct datapath *dp, uint16_t port,
uint32_t ip_addr_start, uint32_t ip_addr_end,
uint16_t mac_timeout)
{
unsigned long flags;
struct net_bridge_port *p = dp->ports[port];
struct snat_conf *sc;
if (mac_timeout == 0)
mac_timeout = MAC_TIMEOUT_DEFAULT;
if (!p) {
if (net_ratelimit())
printk("Attempt to add snat on non-existent port: %d\n", port);
return -EINVAL;
}
/* If SNAT is already configured on the port, check whether the same
* IP addresses are used. If so, just update the mac timeout
* configuration. Otherwise, drop all SNAT configuration and
* reconfigure it. */
spin_lock_irqsave(&p->lock, flags);
if (p->snat) {
if ((p->snat->ip_addr_start == ip_addr_start)
&& (p->snat->ip_addr_end = ip_addr_end)) {
p->snat->mac_timeout = mac_timeout;
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
/* Free the existing configuration and mappings. */
snat_free_conf(p);
}
sc = kzalloc(sizeof *sc, GFP_ATOMIC);
if (!sc) {
spin_unlock_irqrestore(&p->lock, flags);
return -ENOMEM;
}
sc->ip_addr_start = ip_addr_start;
sc->ip_addr_end = ip_addr_end;
sc->mac_timeout = mac_timeout;
INIT_LIST_HEAD(&sc->mappings);
p->snat = sc;
spin_unlock_irqrestore(&p->lock, flags);
return 0;
}
/* Handle a SNAT configuration message.
*
* Returns 0 if no problems are found. Otherwise, a negative errno. */
int
snat_mod_config(struct datapath *dp, const struct nx_act_config *nac)
{
int n_entries = (ntohs(nac->header.header.length) - sizeof *nac)
/ sizeof (struct nx_snat_config);
int ret = 0;
int i;
for (i=0; i<n_entries; i++) {
const struct nx_snat_config *sc = &nac->snat[i];
uint16_t port = ntohs(sc->port);
int r = 0;
if (sc->command == NXSC_ADD)
r = snat_add_port(dp, port,
ntohl(sc->ip_addr_start), ntohl(sc->ip_addr_end),
ntohs(sc->mac_timeout));
else
r = snat_del_port(dp, port);
if (r)
ret = r;
}
return ret;
}
#endif