mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 10:40:03 +01:00
1237 lines
33 KiB
C
1237 lines
33 KiB
C
/*
|
|
* Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free software Foundation, Inc.,
|
|
* 59 Temple Place, suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/string.h>
|
|
#include <linux/version.h>
|
|
|
|
#include <linux/net.h>
|
|
#include <linux/in.h>
|
|
#include <linux/inet.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/udp.h>
|
|
|
|
#include <net/ip.h>
|
|
#include <net/protocol.h>
|
|
#include <net/route.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/spinlock.h>
|
|
#include <asm/semaphore.h>
|
|
|
|
#include <tunnel.h>
|
|
#include <vnet.h>
|
|
#include <vif.h>
|
|
#include <varp.h>
|
|
#include <if_varp.h>
|
|
|
|
#include "allocate.h"
|
|
#include "hash_table.h"
|
|
#include "sys_net.h"
|
|
#include "sys_string.h"
|
|
|
|
#define MODULE_NAME "VARP"
|
|
//#define DEBUG 1
|
|
#undef DEBUG
|
|
#include "debug.h"
|
|
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
|
|
// The 'ethernet' field in the skb->mac union went away.
|
|
#define MAC_ETH(_skb) ((struct ethhdr *)(_skb)->mac.raw)
|
|
#else
|
|
#define MAC_ETH(_skb) ((_skb)->mac.ethernet)
|
|
#endif
|
|
|
|
/** @file VARP: Virtual ARP.
|
|
*
|
|
* Handles virtual ARP requests for vnet/vmac.
|
|
*/
|
|
|
|
/*
|
|
|
|
Varp uses UDP on port 1798.
|
|
|
|
on domain up: ?
|
|
send varp.announce { id, vmac, vnet, coa } for each vif
|
|
that haven't announced before, or has changed.
|
|
install vif entries in local table.
|
|
|
|
on varp.announce{ id, vmac, vnet, coa }:
|
|
update VARP entry for vmac x vnet if have one, reset ttl.
|
|
|
|
on varp.request { id, vmac, vnet }:
|
|
if have a vif for the requested vmac/vnet,
|
|
reply with varp.announce{ id, vmac, vnet, coa }
|
|
|
|
on timer:
|
|
traverse VARP table, flush old entries.
|
|
|
|
on probe timer:
|
|
probe again if not out of tries.
|
|
if out of tries invalidate entry.
|
|
|
|
*/
|
|
|
|
/** Time-to-live of varp entries (in jiffies).*/
|
|
#define VARP_ENTRY_TTL (60*HZ)
|
|
|
|
/** Maximum number of varp probes to make. */
|
|
#define VARP_PROBE_MAX 5
|
|
|
|
/** Interval between varp probes (in jiffies). */
|
|
#define VARP_PROBE_INTERVAL (3*HZ)
|
|
|
|
/** Maximum number of queued skbs for a varp entry. */
|
|
#define VARP_QUEUE_MAX 16
|
|
|
|
/** Number of buckets in the varp table (must be prime). */
|
|
#define VARP_TABLE_BUCKETS 3001
|
|
|
|
/** Varp entry states. */
|
|
enum {
|
|
VARP_STATE_INCOMPLETE = 1,
|
|
VARP_STATE_REACHABLE = 2,
|
|
VARP_STATE_FAILED = 3
|
|
};
|
|
|
|
/** Varp entry flags. */
|
|
enum {
|
|
VARP_FLAG_PROBING = 1,
|
|
VARP_FLAG_PERMANENT = 2,
|
|
};
|
|
|
|
/** Key for varp entries. */
|
|
typedef struct VarpKey {
|
|
/** Vnet id (host order). */
|
|
u32 vnet;
|
|
/** Virtual MAC address. */
|
|
Vmac vmac;
|
|
} VarpKey;
|
|
|
|
/** An entry in the varp cache. */
|
|
typedef struct VarpEntry {
|
|
/** Key for the entry. */
|
|
VarpKey key;
|
|
/** Care-of address for the key. */
|
|
u32 addr;
|
|
/** Last-updated timestamp. */
|
|
unsigned long timestamp;
|
|
/** State. */
|
|
short state;
|
|
/** Flags. */
|
|
short flags;
|
|
/** Reference count. */
|
|
atomic_t refcount;
|
|
/** Lock. */
|
|
rwlock_t lock;
|
|
/** How many probes have been made. */
|
|
atomic_t probes;
|
|
/** Probe timer. */
|
|
struct timer_list timer;
|
|
void (*error)(struct VarpEntry *ventry, struct sk_buff *skb);
|
|
/** Outbound skb queue. */
|
|
struct sk_buff_head queue;
|
|
/** Maximum size of the queue. */
|
|
int queue_max;
|
|
|
|
int locks;
|
|
} VarpEntry;
|
|
|
|
/** The varp cache. Varp entries indexed by VarpKey. */
|
|
typedef struct VarpTable {
|
|
|
|
HashTable *table;
|
|
|
|
/** Sweep timer. */
|
|
struct timer_list timer;
|
|
|
|
/** Lock. Need to use a semaphore instead of a spinlock because
|
|
* some operations under the varp table lock can schedule - and
|
|
* you mustn't hold a spinlock when scheduling.
|
|
*/
|
|
struct semaphore lock;
|
|
|
|
} VarpTable;
|
|
|
|
/** The varp cache. */
|
|
static VarpTable *varp_table = NULL;
|
|
|
|
/** Module parameter for the multicast address. */
|
|
static char *varp_mcaddr = NULL;
|
|
|
|
/** Multicast address (network order). */
|
|
u32 varp_mcast_addr = 0;
|
|
|
|
/** Unicast address (network order). */
|
|
u32 varp_ucast_addr = 0;
|
|
|
|
/** UDP port (network order). */
|
|
u16 varp_port = 0;
|
|
|
|
/** Network device to use. */
|
|
char *varp_device = DEVICE;
|
|
|
|
#define VarpTable_read_lock(z, flags) do{ (flags) = 0; down(&(z)->lock); } while(0)
|
|
#define VarpTable_read_unlock(z, flags) do{ (flags) = 0; up(&(z)->lock); } while(0)
|
|
#define VarpTable_write_lock(z, flags) do{ (flags) = 0; down(&(z)->lock); } while(0)
|
|
#define VarpTable_write_unlock(z, flags) do{ (flags) = 0; up(&(z)->lock); } while(0)
|
|
|
|
#define VarpEntry_lock(ventry, flags) write_lock_irqsave(&(ventry)->lock, (flags))
|
|
#define VarpEntry_unlock(ventry, flags) write_unlock_irqrestore(&(ventry)->lock, (flags))
|
|
|
|
void VarpTable_sweep(VarpTable *z, int all);
|
|
void VarpTable_print(VarpTable *z);
|
|
|
|
/** Print the varp cache (if debug on).
|
|
*/
|
|
void varp_dprint(void){
|
|
#ifdef DEBUG
|
|
VarpTable_print(varp_table);
|
|
#endif
|
|
}
|
|
|
|
/** Print varp info and the varp cache.
|
|
*/
|
|
void varp_print(void){
|
|
printk(KERN_INFO "=== VARP ===============================================================\n");
|
|
printk(KERN_INFO "varp_device %s\n", varp_device);
|
|
printk(KERN_INFO "varp_mcast_addr " IPFMT "\n", NIPQUAD(varp_mcast_addr));
|
|
printk(KERN_INFO "varp_ucast_addr " IPFMT "\n", NIPQUAD(varp_ucast_addr));
|
|
printk(KERN_INFO "varp_port %d\n", ntohs(varp_port));
|
|
VarpTable_print(varp_table);
|
|
printk(KERN_INFO "========================================================================\n");
|
|
}
|
|
|
|
/** Lookup a network device by name.
|
|
*
|
|
* @param name device name
|
|
* @param dev return parameter for the device
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int vnet_get_device(const char *name, struct net_device **dev){
|
|
int err = 0;
|
|
*dev = dev_get_by_name(name);
|
|
if(!*dev){
|
|
err = -ENETDOWN;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/** Get the source address from a device.
|
|
*
|
|
* @param dev device
|
|
* @param addr return parameter for address
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int vnet_get_device_address(struct net_device *dev, u32 *addr){
|
|
int err = 0;
|
|
struct in_device *in_dev;
|
|
|
|
//printk("%s>\n", __FUNCTION__);
|
|
in_dev = in_dev_get(dev);
|
|
if(!in_dev){
|
|
err = -EIO;
|
|
goto exit;
|
|
}
|
|
*addr = in_dev->ifa_list->ifa_address;
|
|
in_dev_put(in_dev);
|
|
exit:
|
|
//printk("%s< err=%d\n", __FUNCTION__, err);
|
|
return err;
|
|
}
|
|
|
|
#ifndef LL_RESERVED_SPACE
|
|
#define HH_DATA_MOD 16
|
|
#define LL_RESERVED_SPACE(dev) \
|
|
((dev->hard_header_len & ~(HH_DATA_MOD - 1)) + HH_DATA_MOD)
|
|
#endif
|
|
|
|
/** Send a varp protocol message.
|
|
*
|
|
* @param opcode varp opcode (host order)
|
|
* @param dev device (may be null)
|
|
* @param skb skb being replied to (may be null)
|
|
* @param vnet vnet id (in host order)
|
|
* @param vmac vmac (in network order)
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int varp_send(u16 opcode, struct net_device *dev, struct sk_buff *skbin,
|
|
u32 vnet, Vmac *vmac){
|
|
int err = 0;
|
|
int link_n = 0;
|
|
int ip_n = sizeof(struct iphdr);
|
|
int udp_n = sizeof(struct udphdr);
|
|
int varp_n = sizeof(VarpHdr);
|
|
struct sk_buff *skbout = NULL;
|
|
struct in_device *in_dev = NULL;
|
|
VarpHdr *varph = NULL;
|
|
u8 macbuf[6] = {};
|
|
u8 *smac, *dmac;
|
|
u32 saddr, daddr;
|
|
u16 sport, dport;
|
|
|
|
dmac = macbuf;
|
|
dprintf("> opcode=%d vnet=%d vmac=" MACFMT "\n",
|
|
opcode, ntohl(vnet), MAC6TUPLE(vmac->mac));
|
|
if(!dev){
|
|
//todo: should use routing for daddr to get device.
|
|
err = vnet_get_device(varp_device, &dev);
|
|
if(err) goto exit;
|
|
}
|
|
link_n = LL_RESERVED_SPACE(dev);
|
|
in_dev = in_dev_get(dev);
|
|
if(!in_dev) goto exit;
|
|
|
|
smac = dev->dev_addr;
|
|
saddr = in_dev->ifa_list->ifa_address;
|
|
|
|
if(skbin){
|
|
dmac = MAC_ETH(skbin)->h_source;
|
|
sport = skbin->h.uh->dest;
|
|
daddr = skbin->nh.iph->saddr;
|
|
//dport = skbin->h.uh->source;
|
|
dport = varp_port;
|
|
} else {
|
|
if(!in_dev) goto exit;
|
|
if(MULTICAST(varp_mcast_addr)){
|
|
daddr = varp_mcast_addr;
|
|
ip_eth_mc_map(daddr, dmac);
|
|
} else {
|
|
daddr = in_dev->ifa_list->ifa_broadcast;
|
|
dmac = dev->broadcast;
|
|
}
|
|
sport = varp_port;
|
|
dport = varp_port;
|
|
}
|
|
in_dev_put(in_dev);
|
|
|
|
dprintf("> smac=" MACFMT " dmac=" MACFMT "\n", MAC6TUPLE(smac), MAC6TUPLE(dmac));
|
|
dprintf("> saddr=" IPFMT " daddr=" IPFMT "\n", NIPQUAD(saddr), NIPQUAD(daddr));
|
|
dprintf("> sport=%u dport=%u\n", ntohs(sport), ntohs(dport));
|
|
|
|
skbout = alloc_skb(link_n + ip_n + udp_n + varp_n, GFP_ATOMIC);
|
|
if (!skbout){
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
skbout->dev = dev;
|
|
skb_reserve(skbout, link_n);
|
|
skbout->protocol = htons(ETH_P_IP);
|
|
|
|
// Device header. Pushes device header on front of skb.
|
|
if (dev->hard_header){
|
|
err = dev->hard_header(skbout, dev, ETH_P_IP, dmac, smac, skbout->len);
|
|
if(err < 0) goto exit;
|
|
skbout->mac.raw = skbout->data;
|
|
}
|
|
|
|
// IP header.
|
|
skbout->nh.raw = skb_put(skbout, ip_n);
|
|
skbout->nh.iph->version = 4;
|
|
skbout->nh.iph->ihl = ip_n / 4;
|
|
skbout->nh.iph->tos = 0;
|
|
skbout->nh.iph->tot_len = htons(ip_n + udp_n + varp_n);
|
|
skbout->nh.iph->id = 0;
|
|
skbout->nh.iph->frag_off = 0;
|
|
skbout->nh.iph->ttl = 64;
|
|
skbout->nh.iph->protocol = IPPROTO_UDP;
|
|
skbout->nh.iph->saddr = saddr;
|
|
skbout->nh.iph->daddr = daddr;
|
|
skbout->nh.iph->check = 0;
|
|
|
|
// UDP header.
|
|
skbout->h.raw = skb_put(skbout, udp_n);
|
|
skbout->h.uh->source = sport;
|
|
skbout->h.uh->dest = dport;
|
|
skbout->h.uh->len = htons(udp_n + varp_n);
|
|
skbout->h.uh->check = 0;
|
|
|
|
// Varp header.
|
|
varph = (void*)skb_put(skbout, varp_n);
|
|
*varph = (VarpHdr){};
|
|
varph->id = htons(VARP_ID);
|
|
varph->opcode = htons(opcode);
|
|
varph->vnet = htonl(vnet);
|
|
varph->vmac = *vmac;
|
|
varph->addr = saddr;
|
|
|
|
err = skb_xmit(skbout);
|
|
|
|
exit:
|
|
if(err && skbout) kfree_skb(skbout);
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Send a varp request for the vnet and destination mac of a packet.
|
|
*
|
|
* @param skb packet
|
|
* @param vnet vnet (in host order)
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int varp_solicit(struct sk_buff *skb, int vnet){
|
|
int err = 0;
|
|
dprintf("> skb=%p\n", skb);
|
|
varp_dprint();
|
|
err = varp_send(VARP_OP_REQUEST, NULL, NULL,
|
|
vnet, (Vmac*)MAC_ETH(skb)->h_dest);
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/* Test some flags.
|
|
*
|
|
* @param z varp entry
|
|
* @param flags to test
|
|
* @return nonzero if flags set
|
|
*/
|
|
int VarpEntry_get_flags(VarpEntry *z, int flags){
|
|
return z->flags & flags;
|
|
}
|
|
|
|
/** Set some flags.
|
|
*
|
|
* @param z varp entry
|
|
* @param flags to set
|
|
* @param set set flags on if nonzero, off if zero
|
|
* @return new flags value
|
|
*/
|
|
int VarpEntry_set_flags(VarpEntry *z, int flags, int set){
|
|
if(set){
|
|
z->flags |= flags;
|
|
} else {
|
|
z->flags &= ~flags;
|
|
}
|
|
return z->flags;
|
|
}
|
|
|
|
/** Print a varp entry.
|
|
*
|
|
* @param ventry varp entry
|
|
*/
|
|
void VarpEntry_print(VarpEntry *ventry){
|
|
if(ventry){
|
|
char *c, *d;
|
|
switch(ventry->state){
|
|
case VARP_STATE_INCOMPLETE: c = "INC"; break;
|
|
case VARP_STATE_REACHABLE: c = "RCH"; break;
|
|
case VARP_STATE_FAILED: c = "FLD"; break;
|
|
default: c = "UNK"; break;
|
|
}
|
|
d = (VarpEntry_get_flags(ventry, VARP_FLAG_PROBING) ? "P" : " ");
|
|
|
|
printk(KERN_INFO "VENTRY(%p ref=%1d %s %s vnet=%d vmac=" MACFMT " addr=" IPFMT " q=%d t=%lu)\n",
|
|
ventry,
|
|
atomic_read(&ventry->refcount),
|
|
c, d,
|
|
ventry->key.vnet,
|
|
MAC6TUPLE(ventry->key.vmac.mac),
|
|
NIPQUAD(ventry->addr),
|
|
skb_queue_len(&ventry->queue),
|
|
ventry->timestamp);
|
|
} else {
|
|
printk("VENTRY: Null!\n");
|
|
}
|
|
}
|
|
|
|
/** Free a varp entry.
|
|
*
|
|
* @param z varp entry
|
|
*/
|
|
void VarpEntry_free(VarpEntry *z){
|
|
if(!z) return;
|
|
deallocate(z);
|
|
}
|
|
|
|
/** Increment reference count.
|
|
*
|
|
* @param z varp entry (may be null)
|
|
*/
|
|
void VarpEntry_incref(VarpEntry *z){
|
|
if(!z) return;
|
|
atomic_inc(&z->refcount);
|
|
//dprintf("> "); VarpEntry_print(z);
|
|
}
|
|
|
|
/** Decrement reference count, freeing if zero.
|
|
*
|
|
* @param z varp entry (may be null)
|
|
*/
|
|
void VarpEntry_decref(VarpEntry *z){
|
|
if(!z) return;
|
|
//dprintf("> "); VarpEntry_print(z);
|
|
if(atomic_dec_and_test(&z->refcount)){
|
|
//dprintf("> freeing %p...\n", z);
|
|
VarpEntry_free(z);
|
|
}
|
|
}
|
|
|
|
/** Call the error handler.
|
|
*
|
|
* @param ventry varp entry
|
|
*/
|
|
void VarpEntry_error(VarpEntry *ventry){
|
|
struct sk_buff *skb;
|
|
skb = skb_peek(&ventry->queue);
|
|
if(!skb) return;
|
|
if(ventry->error) ventry->error(ventry, skb);
|
|
skb_queue_purge(&ventry->queue);
|
|
}
|
|
|
|
/** Schedule the varp entry timer.
|
|
* Must increment the reference count before doing
|
|
* this the first time, so the ventry won' be freed
|
|
* before the timer goes off.
|
|
*
|
|
* @param ventry varp entry
|
|
*/
|
|
void VarpEntry_schedule(VarpEntry *ventry){
|
|
unsigned long now = jiffies;
|
|
ventry->timer.expires = now + VARP_PROBE_INTERVAL;
|
|
add_timer(&ventry->timer);
|
|
}
|
|
|
|
/** Function called when a varp entry timer goes off.
|
|
* If the entry is still incomplete, carries on probing.
|
|
* Otherwise stops probing.
|
|
*
|
|
* @param arg ventry
|
|
*/
|
|
static void varp_timer_fn(unsigned long arg){
|
|
unsigned long flags;
|
|
VarpEntry *ventry = (VarpEntry *)arg;
|
|
struct sk_buff *skb = NULL;
|
|
int locked = 0, probing = 0;
|
|
|
|
dprintf(">\n"); //VarpEntry_print(ventry);
|
|
VarpEntry_lock(ventry, flags);
|
|
locked = 1;
|
|
if(ventry->state == VARP_STATE_REACHABLE){
|
|
// Do nothing.
|
|
} else {
|
|
// Probe if haven't run out of tries, otherwise fail.
|
|
if(atomic_read(&ventry->probes) < VARP_PROBE_MAX){
|
|
probing = 1;
|
|
VarpEntry_schedule(ventry);
|
|
skb = skb_peek(&ventry->queue);
|
|
if(skb){
|
|
dprintf("> skbs in queue - solicit\n");
|
|
atomic_inc(&ventry->probes);
|
|
VarpEntry_unlock(ventry, flags);
|
|
locked = 0;
|
|
varp_solicit(skb, ventry->key.vnet);
|
|
} else {
|
|
dprintf("> empty queue.\n");
|
|
}
|
|
} else {
|
|
dprintf("> Out of probes: FAILED\n");
|
|
VarpEntry_error(ventry);
|
|
ventry->state = VARP_STATE_FAILED;
|
|
}
|
|
}
|
|
VarpEntry_set_flags(ventry, VARP_FLAG_PROBING, probing);
|
|
if(locked) VarpEntry_unlock(ventry, flags);
|
|
if(!probing) VarpEntry_decref(ventry);
|
|
dprintf("<\n");
|
|
}
|
|
|
|
/** Default error function for varp entries.
|
|
*
|
|
* @param ventry varp entry
|
|
* @param skb packet dropped because of error
|
|
*/
|
|
static void varp_error_fn(VarpEntry *ventry, struct sk_buff *skb){
|
|
}
|
|
|
|
/** Create a varp entry. Initializes the internal state.
|
|
*
|
|
* @param vnet vnet id
|
|
* @param vmac virtual MAC address (copied)
|
|
* @return ventry or null
|
|
*/
|
|
VarpEntry * VarpEntry_new(u32 vnet, Vmac *vmac){
|
|
VarpEntry *z = ALLOCATE(VarpEntry);
|
|
if(z){
|
|
unsigned long now = jiffies;
|
|
|
|
atomic_set(&z->refcount, 1);
|
|
z->lock = RW_LOCK_UNLOCKED;
|
|
z->state = VARP_STATE_INCOMPLETE;
|
|
z->queue_max = VARP_QUEUE_MAX;
|
|
skb_queue_head_init(&z->queue);
|
|
init_timer(&z->timer);
|
|
z->timer.data = (unsigned long)z;
|
|
z->timer.function = varp_timer_fn;
|
|
z->timestamp = now;
|
|
z->error = varp_error_fn;
|
|
|
|
z->key.vnet = vnet;
|
|
z->key.vmac = *vmac;
|
|
}
|
|
return z;
|
|
}
|
|
|
|
/** Hash function for keys in the varp cache.
|
|
* Hashes the vnet id and mac.
|
|
*
|
|
* @param k key (VarpKey)
|
|
* @return hashcode
|
|
*/
|
|
Hashcode varp_key_hash_fn(void *k){
|
|
VarpKey *key = k;
|
|
Hashcode h;
|
|
h = hash_2ul(key->vnet,
|
|
(key->vmac.mac[0] << 24) |
|
|
(key->vmac.mac[1] << 16) |
|
|
(key->vmac.mac[2] << 8) |
|
|
(key->vmac.mac[3] ));
|
|
h = hash_hul(h,
|
|
(key->vmac.mac[4] << 8) |
|
|
(key->vmac.mac[5] ));
|
|
return h;
|
|
}
|
|
|
|
/** Test equality for keys in the varp cache.
|
|
* Compares vnet and mac.
|
|
*
|
|
* @param k1 key to compare (VarpKey)
|
|
* @param k2 key to compare (VarpKey)
|
|
* @return 1 if equal, 0 otherwise
|
|
*/
|
|
int varp_key_equal_fn(void *k1, void *k2){
|
|
VarpKey *key1 = k1;
|
|
VarpKey *key2 = k2;
|
|
return (key1->vnet == key2->vnet)
|
|
&& (memcmp(key1->vmac.mac, key2->vmac.mac, ETH_ALEN) == 0);
|
|
}
|
|
|
|
/** Free an entry in the varp cache.
|
|
*
|
|
* @param table containing table
|
|
* @param entry entry to free
|
|
*/
|
|
static void varp_entry_free_fn(HashTable *table, HTEntry *entry){
|
|
VarpEntry *ventry;
|
|
if(!entry) return;
|
|
ventry = entry->value;
|
|
if(ventry) VarpEntry_decref(ventry);
|
|
HTEntry_free(entry);
|
|
}
|
|
|
|
/** Free the whole varp cache.
|
|
* Dangerous.
|
|
*
|
|
* @param z varp cache
|
|
*/
|
|
void VarpTable_free(VarpTable *z){
|
|
unsigned long flags;
|
|
if(!z) return;
|
|
VarpTable_write_lock(z, flags);
|
|
del_timer(&z->timer);
|
|
z->timer.data = 0;
|
|
if(z->table) HashTable_free(z->table);
|
|
VarpTable_write_unlock(z, flags);
|
|
deallocate(z);
|
|
}
|
|
|
|
/** Schedule the varp table timer.
|
|
*
|
|
* @param z varp table
|
|
*/
|
|
void VarpTable_schedule(VarpTable *z){
|
|
unsigned long now = jiffies;
|
|
z->timer.expires = now + VARP_ENTRY_TTL;
|
|
add_timer(&z->timer);
|
|
}
|
|
|
|
/** Function called when the varp table timer goes off.
|
|
* Sweeps old varp cache entries and reschedules itself.
|
|
*
|
|
* @param arg varp table
|
|
*/
|
|
static void varp_table_timer_fn(unsigned long arg){
|
|
VarpTable *z = (VarpTable *)arg;
|
|
//dprintf("> z=%p\n", z);
|
|
if(z){
|
|
VarpTable_sweep(z, 0);
|
|
VarpTable_schedule(z);
|
|
}
|
|
//dprintf("<\n");
|
|
}
|
|
|
|
/** Print a varp table.
|
|
*
|
|
* @param z table
|
|
*/
|
|
void VarpTable_print(VarpTable *z){
|
|
HashTable_for_decl(entry);
|
|
VarpEntry *ventry;
|
|
unsigned long flags, vflags;
|
|
|
|
//dprintf(">\n");
|
|
VarpTable_read_lock(z, flags);
|
|
HashTable_for_each(entry, varp_table->table){
|
|
ventry = entry->value;
|
|
VarpEntry_lock(ventry, vflags);
|
|
VarpEntry_print(ventry);
|
|
VarpEntry_unlock(ventry, vflags);
|
|
}
|
|
VarpTable_read_unlock(z, flags);
|
|
//dprintf("<\n");
|
|
}
|
|
|
|
/** Create a varp table.
|
|
*
|
|
* @return new table or null
|
|
*/
|
|
VarpTable * VarpTable_new(void){
|
|
int err = -ENOMEM;
|
|
VarpTable *z = NULL;
|
|
|
|
z = ALLOCATE(VarpTable);
|
|
if(!z) goto exit;
|
|
z->table = HashTable_new(VARP_TABLE_BUCKETS);
|
|
if(!z->table) goto exit;
|
|
z->table->key_equal_fn = varp_key_equal_fn;
|
|
z->table->key_hash_fn = varp_key_hash_fn;
|
|
z->table->entry_free_fn = varp_entry_free_fn;
|
|
init_MUTEX(&z->lock);
|
|
init_timer(&z->timer);
|
|
z->timer.data = (unsigned long)z;
|
|
z->timer.function = varp_table_timer_fn;
|
|
VarpTable_schedule(z);
|
|
err = 0;
|
|
exit:
|
|
if(err){
|
|
VarpTable_free(z);
|
|
z = NULL;
|
|
}
|
|
return z;
|
|
}
|
|
|
|
/** Add a new entry to the varp table.
|
|
*
|
|
* @param z table
|
|
* @param vnet vnet id
|
|
* @param vmac virtual MAC address (copied)
|
|
* @return new entry or null
|
|
*/
|
|
VarpEntry * VarpTable_add(VarpTable *z, u32 vnet, Vmac *vmac){
|
|
int err = -ENOMEM;
|
|
VarpEntry *ventry;
|
|
HTEntry *entry;
|
|
unsigned long flags;
|
|
|
|
ventry = VarpEntry_new(vnet, vmac);
|
|
if(!ventry) goto exit;
|
|
//dprintf("> "); VarpEntry_print(ventry);
|
|
VarpTable_write_lock(z, flags);
|
|
entry = HashTable_add(z->table, ventry, ventry);
|
|
VarpTable_write_unlock(z, flags);
|
|
if(!entry) goto exit;
|
|
VarpEntry_incref(ventry);
|
|
err = 0;
|
|
exit:
|
|
if(err){
|
|
VarpEntry_free(ventry);
|
|
ventry = NULL;
|
|
}
|
|
return ventry;
|
|
}
|
|
|
|
/** Remove an entry from the varp table.
|
|
*
|
|
* @param z table
|
|
* @param ventry entry to remove
|
|
* @return removed count
|
|
*/
|
|
int VarpTable_remove(VarpTable *z, VarpEntry *ventry){
|
|
return HashTable_remove(z->table, ventry);
|
|
}
|
|
|
|
/** Lookup an entry in the varp table.
|
|
*
|
|
* @param z table
|
|
* @param vnet vnet id
|
|
* @param vmac virtual MAC addres
|
|
* @return entry found or null
|
|
*/
|
|
VarpEntry * VarpTable_lookup(VarpTable *z, u32 vnet, Vmac *vmac){
|
|
unsigned long flags;
|
|
VarpKey key = { .vnet = vnet, .vmac = *vmac };
|
|
VarpEntry *ventry;
|
|
VarpTable_read_lock(z, flags);
|
|
ventry = HashTable_get(z->table, &key);
|
|
VarpTable_read_unlock(z, flags);
|
|
if(ventry) VarpEntry_incref(ventry);
|
|
return ventry;
|
|
}
|
|
|
|
/** Handle output for a reachable ventry.
|
|
* Send the skb using the tunnel to the care-of address.
|
|
*
|
|
* @param ventry varp entry
|
|
* @param skb skb to send
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int VarpEntry_send(VarpEntry *ventry, struct sk_buff *skb){
|
|
int err = 0;
|
|
unsigned long flags = 0;
|
|
u32 addr;
|
|
|
|
dprintf("> skb=%p\n", skb);
|
|
addr = ventry->addr;
|
|
VarpEntry_unlock(ventry, flags);
|
|
err = vnet_tunnel_send(ventry->key.vnet, addr, skb);
|
|
VarpEntry_lock(ventry, flags);
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Handle output for a non-reachable ventry. Send messages to complete it.
|
|
* If the entry is still incomplete, queue the skb, otherwise
|
|
* send it. If the queue is full, dequeue and free an old skb to
|
|
* make room for the new one.
|
|
*
|
|
* @param ventry varp entry
|
|
* @param skb skb to send
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int VarpEntry_resolve(VarpEntry *ventry, struct sk_buff *skb){
|
|
int err = 0;
|
|
unsigned long flags = 0;
|
|
|
|
dprintf("> skb=%p\n", skb); //VarpEntry_print(ventry);
|
|
ventry->state = VARP_STATE_INCOMPLETE;
|
|
atomic_set(&ventry->probes, 1);
|
|
if(!VarpEntry_get_flags(ventry, VARP_FLAG_PROBING)){
|
|
VarpEntry_set_flags(ventry, VARP_FLAG_PROBING, 1);
|
|
VarpEntry_incref(ventry);
|
|
VarpEntry_schedule(ventry);
|
|
}
|
|
VarpEntry_unlock(ventry, flags);
|
|
varp_solicit(skb, ventry->key.vnet);
|
|
VarpEntry_lock(ventry, flags);
|
|
|
|
if(ventry->state == VARP_STATE_INCOMPLETE){
|
|
if(skb_queue_len(&ventry->queue) >= ventry->queue_max){
|
|
struct sk_buff *oldskb;
|
|
oldskb = ventry->queue.next;
|
|
__skb_unlink(oldskb, &ventry->queue);
|
|
dprintf("> purging skb=%p\n", oldskb);
|
|
kfree_skb(oldskb);
|
|
}
|
|
__skb_queue_tail(&ventry->queue, skb);
|
|
} else {
|
|
err = VarpEntry_send(ventry, skb);
|
|
}
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Handle output for a ventry. Resolves the ventry
|
|
* if necessary.
|
|
*
|
|
* @param ventry varp entry
|
|
* @param skb skb to send
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int VarpEntry_output(VarpEntry *ventry, struct sk_buff *skb){
|
|
int err = 0;
|
|
|
|
switch(ventry->state){
|
|
case VARP_STATE_REACHABLE:
|
|
err = VarpEntry_send(ventry, skb);
|
|
break;
|
|
default:
|
|
err = VarpEntry_resolve(ventry, skb);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/** Process the output queue for a ventry. Sends the queued skbs if
|
|
* the ventry is reachable, otherwise drops them.
|
|
*
|
|
* @param ventry varp entry
|
|
*/
|
|
void VarpEntry_process_queue(VarpEntry *ventry){
|
|
struct sk_buff *skb;
|
|
for( ; ; ){
|
|
if(ventry->state != VARP_STATE_REACHABLE) break;
|
|
skb = __skb_dequeue(&ventry->queue);
|
|
if(!skb) break;
|
|
VarpEntry_output(ventry, skb);
|
|
}
|
|
skb_queue_purge(&ventry->queue);
|
|
}
|
|
|
|
/** Update a ventry. Sets the address and state to those given
|
|
* and sets the timestamp to 'now'.
|
|
*
|
|
* @param ventry varp entry
|
|
* @param addr care-of address
|
|
* @param state state
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int VarpEntry_update(VarpEntry *ventry, u32 addr, int state){
|
|
int err = 0;
|
|
unsigned long now = jiffies;
|
|
unsigned long flags;
|
|
|
|
dprintf("> addr=" IPFMT " state=%d\n", NIPQUAD(addr), state);
|
|
//VarpEntry_print(ventry);
|
|
VarpEntry_lock(ventry, flags);
|
|
if(VarpEntry_get_flags(ventry, VARP_FLAG_PERMANENT)) goto exit;
|
|
ventry->addr = addr;
|
|
ventry->timestamp = now;
|
|
ventry->state = state;
|
|
VarpEntry_process_queue(ventry);
|
|
exit:
|
|
//dprintf("> "); VarpEntry_print(ventry);
|
|
VarpEntry_unlock(ventry, flags);
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
int VarpTable_update(VarpTable *z, int vnet, Vmac *vmac, u32 addr,
|
|
int state, int force){
|
|
int err = 0;
|
|
VarpEntry *ventry;
|
|
|
|
dprintf("> vnet=%d mac=" MACFMT " addr=" IPFMT " state=%d force=%d\n",
|
|
vnet, MAC6TUPLE(vmac->mac), NIPQUAD(addr), state, force);
|
|
ventry = VarpTable_lookup(z, vnet, vmac);
|
|
if(force && !ventry){
|
|
dprintf("> No entry, adding\n");
|
|
ventry = VarpTable_add(z, vnet, vmac);
|
|
}
|
|
if(ventry){
|
|
dprintf("> Updating\n");
|
|
err = VarpEntry_update(ventry, addr, state);
|
|
VarpEntry_decref(ventry);
|
|
} else {
|
|
dprintf("> No entry found\n");
|
|
err = -ENOENT;
|
|
}
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Update the ventry corresponding to the given varp header.
|
|
*
|
|
* @param z table
|
|
* @param varph varp header
|
|
* @param state state
|
|
* @return 0 on success, -ENOENT if no entry found
|
|
*/
|
|
int VarpTable_update_entry(VarpTable *z, VarpHdr *varph, int state){
|
|
return VarpTable_update(z, ntohl(varph->vnet), &varph->vmac, varph->addr, state, 0);
|
|
}
|
|
|
|
int varp_update(int vnet, unsigned char *vmac, u32 addr){
|
|
if(!varp_table){
|
|
return -ENOSYS;
|
|
}
|
|
return VarpTable_update(varp_table, vnet, (Vmac*)vmac, addr,
|
|
VARP_STATE_REACHABLE, 1);
|
|
}
|
|
|
|
/** Put old varp entries into the incomplete state.
|
|
* Permanent entries are not changed.
|
|
* If 'all' is non-zero, all non-permanent entries
|
|
* are put into the incomplete state, regardless of age.
|
|
*
|
|
* @param z table
|
|
* @param all reset all entries if non-zero
|
|
*/
|
|
void VarpTable_sweep(VarpTable *z, int all){
|
|
HashTable_for_decl(entry);
|
|
VarpEntry *ventry;
|
|
unsigned long now = jiffies;
|
|
unsigned long old = now - VARP_ENTRY_TTL;
|
|
unsigned long flags, vflags;
|
|
|
|
//dprintf(">\n");
|
|
VarpTable_read_lock(z, flags);
|
|
HashTable_for_each(entry, varp_table->table){
|
|
ventry = entry->value;
|
|
VarpEntry_lock(ventry, vflags);
|
|
if(!VarpEntry_get_flags(ventry, VARP_FLAG_PERMANENT) &&
|
|
(all || (ventry->timestamp < old))){
|
|
VarpEntry_process_queue(ventry);
|
|
ventry->state = VARP_STATE_INCOMPLETE;
|
|
}
|
|
VarpEntry_unlock(ventry, vflags);
|
|
}
|
|
VarpTable_read_unlock(z, flags);
|
|
//dprintf("<\n");
|
|
}
|
|
|
|
/** Handle a varp request. Look for a vif with the requested
|
|
* vnet and vmac. If find one, reply with the vnet, vmac and our
|
|
* address. Otherwise do nothing.
|
|
*
|
|
* @param skb incoming message
|
|
* @param varph varp message
|
|
* @return 0 if ok, -ENOENT if no matching vif, or error code
|
|
*/
|
|
int varp_handle_request(struct sk_buff *skb, VarpHdr *varph){
|
|
int err = -ENOENT;
|
|
u32 vnet;
|
|
Vmac *vmac;
|
|
Vif *vif = NULL;
|
|
|
|
dprintf(">\n");
|
|
vnet = ntohl(varph->vnet);
|
|
vmac = &varph->vmac;
|
|
dprintf("> vnet=%d vmac=" MACFMT "\n", vnet, MAC6TUPLE(vmac->mac));
|
|
if(vif_lookup(vnet, vmac, &vif)) goto exit;
|
|
varp_send(VARP_OP_ANNOUNCE, skb->dev, skb, vnet, vmac);
|
|
vif_decref(vif);
|
|
exit:
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Announce the vnet and vmac of a vif (gratuitous varp).
|
|
*
|
|
* @param dev device to send on (may be null)
|
|
* @param vif vif
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int varp_announce_vif(struct net_device *dev, Vif *vif){
|
|
int err = 0;
|
|
dprintf(">\n");
|
|
if(!varp_table){
|
|
err = -ENOSYS;
|
|
goto exit;
|
|
}
|
|
err = varp_send(VARP_OP_ANNOUNCE, dev, NULL, vif->vnet, &vif->vmac);
|
|
exit:
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Handle a varp announce message.
|
|
* Update the matching ventry if we have one.
|
|
*
|
|
* @param skb incoming message
|
|
* @param varp message
|
|
* @return 0 if OK, -ENOENT if no matching entry
|
|
*/
|
|
int varp_handle_announce(struct sk_buff *skb, VarpHdr *varph){
|
|
int err = 0;
|
|
|
|
dprintf(">\n");
|
|
err = VarpTable_update_entry(varp_table, varph, VARP_STATE_REACHABLE);
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Handle an incoming varp message.
|
|
*
|
|
* @param skb incoming message
|
|
* @return 0 if OK, error code otherwise
|
|
*/
|
|
int varp_handle_message(struct sk_buff *skb){
|
|
// Assume h. nh set, skb->data point after udp hdr (at varphdr).
|
|
int err = -EINVAL, mine = 0;
|
|
VarpHdr *varph = (void*)(skb->h.uh + 1);
|
|
|
|
dprintf(">\n");
|
|
if(!varp_table){
|
|
err = -ENOSYS;
|
|
goto exit;
|
|
}
|
|
if(MULTICAST(skb->nh.iph->daddr) &&
|
|
(skb->nh.iph->daddr != varp_mcast_addr)){
|
|
// Ignore multicast packets not addressed to us.
|
|
err = 0;
|
|
dprintf("> daddr=" IPFMT " mcaddr=" IPFMT "\n",
|
|
NIPQUAD(skb->nh.iph->daddr), NIPQUAD(varp_mcast_addr));
|
|
goto exit;
|
|
}
|
|
if(skb->len < sizeof(*varph)){
|
|
wprintf("> Varp msg too short: %d < %d\n", skb->len, sizeof(*varph));
|
|
goto exit;
|
|
}
|
|
mine = 1;
|
|
if(varph->id != htons(VARP_ID)){
|
|
// It's not varp at all - ignore it.
|
|
wprintf("> Unknown id: %d \n", ntohs(varph->id));
|
|
goto exit;
|
|
}
|
|
if(1){
|
|
dprintf("> saddr=" IPFMT " daddr=" IPFMT "\n",
|
|
NIPQUAD(skb->nh.iph->saddr), NIPQUAD(skb->nh.iph->daddr));
|
|
dprintf("> sport=%u dport=%u\n", ntohs(skb->h.uh->source), ntohs(skb->h.uh->dest));
|
|
dprintf("> opcode=%d vnet=%u vmac=" MACFMT " addr=" IPFMT "\n",
|
|
ntohs(varph->opcode),
|
|
ntohl(varph->vnet),
|
|
MAC6TUPLE(varph->vmac.mac),
|
|
NIPQUAD(varph->addr));
|
|
varp_dprint();
|
|
}
|
|
switch(ntohs(varph->opcode)){
|
|
case VARP_OP_REQUEST:
|
|
err = varp_handle_request(skb, varph);
|
|
break;
|
|
case VARP_OP_ANNOUNCE:
|
|
err = varp_handle_announce(skb, varph);
|
|
break;
|
|
default:
|
|
wprintf("> Unknown opcode: %d \n", ntohs(varph->opcode));
|
|
break;
|
|
}
|
|
exit:
|
|
if(mine) err = 1;
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Send an outgoing packet on the appropriate vnet tunnel.
|
|
*
|
|
* @param skb outgoing message
|
|
* @param vnet vnet (host order)
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int varp_output(struct sk_buff *skb, u32 vnet){
|
|
int err = 0;
|
|
unsigned char *mac = NULL;
|
|
Vmac *vmac = NULL;
|
|
VarpEntry *ventry = NULL;
|
|
|
|
dprintf("> skb=%p vnet=%u\n", skb, vnet);
|
|
if(!varp_table){
|
|
err = -ENOSYS;
|
|
goto exit;
|
|
}
|
|
dprintf("> skb.mac=%p\n", skb->mac.raw);
|
|
if(!skb->mac.raw){
|
|
wprintf("> No ethhdr in skb!\n");
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
mac = MAC_ETH(skb)->h_dest;
|
|
vmac = (Vmac*)mac;
|
|
if(mac_is_multicast(mac)){
|
|
err = vnet_tunnel_send(vnet, varp_mcast_addr, skb);
|
|
} else {
|
|
ventry = VarpTable_lookup(varp_table, vnet, vmac);
|
|
if(!ventry){
|
|
ventry = VarpTable_add(varp_table, vnet, vmac);
|
|
}
|
|
if(ventry){
|
|
unsigned long flags;
|
|
VarpEntry_lock(ventry, flags);
|
|
err = VarpEntry_output(ventry, skb);
|
|
VarpEntry_unlock(ventry, flags);
|
|
VarpEntry_decref(ventry);
|
|
} else {
|
|
err = -ENOMEM;
|
|
}
|
|
}
|
|
exit:
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Set the varp multicast address (after initialization).
|
|
*
|
|
* @param addr address (network order)
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int varp_set_mcast_addr(uint32_t addr){
|
|
int err = 0;
|
|
varp_close();
|
|
varp_mcast_addr = addr;
|
|
err = varp_open(varp_mcast_addr, varp_ucast_addr, varp_port);
|
|
return err;
|
|
}
|
|
|
|
/** Initialize the varp multicast address from a module parameter.
|
|
*
|
|
* @param s address in IPv4 notation
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
static void varp_init_mcast_addr(char *s){
|
|
unsigned long v = 0;
|
|
|
|
dprintf("> %s\n", s);
|
|
if(s && (get_inet_addr(s, &v) >= 0)){
|
|
varp_mcast_addr = (u32)v;
|
|
} else {
|
|
varp_mcast_addr = htonl(VARP_MCAST_ADDR);
|
|
}
|
|
}
|
|
|
|
/** Initialize the varp cache.
|
|
*
|
|
* @return 0 on success, error code otherwise
|
|
*/
|
|
int varp_init(void){
|
|
int err = 0;
|
|
struct net_device *dev = NULL;
|
|
|
|
dprintf(">\n");
|
|
varp_table = VarpTable_new();
|
|
if(!varp_table){
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
varp_init_mcast_addr(varp_mcaddr);
|
|
err = vnet_get_device(varp_device, &dev);
|
|
dprintf("> vnet_get_device(%s)=%d\n", varp_device, err);
|
|
if(err) goto exit;
|
|
err = vnet_get_device_address(dev, &varp_ucast_addr);
|
|
dprintf("> vnet_get_device_address()=%d\n", err);
|
|
if(err) goto exit;
|
|
varp_port = htons(VARP_PORT);
|
|
|
|
err = varp_open(varp_mcast_addr, varp_ucast_addr, varp_port);
|
|
dprintf("> varp_open()=%d\n", err);
|
|
exit:
|
|
if(dev) dev_put(dev);
|
|
dprintf("< err=%d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/** Close the varp cache.
|
|
*/
|
|
void varp_exit(void){
|
|
dprintf(">\n");
|
|
varp_close();
|
|
if(varp_table){
|
|
VarpTable *z = varp_table;
|
|
varp_table = NULL;
|
|
VarpTable_free(z);
|
|
}
|
|
dprintf("<\n");
|
|
}
|
|
|
|
MODULE_PARM(varp_mcaddr, "s");
|
|
MODULE_PARM_DESC(varp_mcaddr, "VARP multicast address");
|
|
|
|
MODULE_PARM(varp_device, "s");
|
|
MODULE_PARM_DESC(varp_device, "VARP network device");
|