vmware-host-modules/vmnet-only/driver.c

1663 lines
41 KiB
C

/*********************************************************
* Copyright (C) 1998,2021 VMware, Inc. All rights reserved.
*
* 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 version 2 and no 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.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*********************************************************/
#include "driver-config.h"
#define EXPORT_SYMTAB
#include <linux/kernel.h>
#include "compat_module.h"
#include <linux/version.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/smp.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/mm.h>
#include "compat_skbuff.h"
#include <linux/if_ether.h>
#include <linux/sockios.h>
#include "compat_sock.h"
#include <linux/kdev_t.h>
#include <linux/mutex.h>
#define __KERNEL_SYSCALLS__
#include <asm/io.h>
#include <linux/proc_fs.h>
#include <linux/file.h>
#include "vnetInt.h"
#include "vnetFilter.h"
#include "vmnetInt.h"
/*
* Structure for cycle detection of host interfaces. This
* struct is only used by VNetCycleDetectIf().
*/
typedef struct VNetInterface {
char name[VNET_NAME_LEN];
int myGeneration;
struct VNetInterface *next;
} VNetInterface;
static VNetInterface *vnetInterfaces = NULL;
/* this will let all multicast packets go through. */
const uint8 allMultiFilter[VNET_LADRF_LEN] = { 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff };
/* broadcast MAC */
const uint8 broadcast[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
/*
* All jack->peer accesses are guarded by this lock.
*
* This lock is acquired for read from interrupt context:
* use write_lock_irqsave() to gain write access.
*
* If you are acquiring this lock for write, and you do
* not have vnetStructureMutex already acquired,
* it is most certainly a bug.
*/
static DEFINE_RWLOCK(vnetPeerLock);
/*
* All concurrent changes to the network structure are
* guarded by this mutex.
*
* For change to peer field you must own both
* vnetStructureMutex and vnetPeerLock for write.
*/
DEFINE_MUTEX(vnetStructureMutex);
/*
* The following method ensures that only one thread uses iotcl code
* in the absence of BKL on newer kernels.
*/
static DEFINE_MUTEX(vnetIoctlMutex);
/*
* List of known ports. Use vnetStructureMutex for locking.
*/
static VNetPort *vnetAllPorts = NULL;
struct proto vmnet_proto = {
.name = "VMNET",
.owner = THIS_MODULE,
.obj_size = sizeof(struct sock),
};
/*
* Device driver interface.
*/
int VNetRegister(int value);
static int VNetFileOpOpen(struct inode *inode, struct file *filp);
static int VNetFileOpClose(struct inode *inode, struct file *filp);
static unsigned int VNetFileOpPoll(struct file *filp, poll_table *wait);
static ssize_t VNetFileOpRead(struct file *filp, char *buf, size_t count,
loff_t *ppos);
static ssize_t VNetFileOpWrite(struct file *filp, const char *buf, size_t count,
loff_t *ppos);
static long VNetFileOpUnlockedIoctl(struct file * filp,
unsigned int iocmd, unsigned long ioarg);
static struct file_operations vnetFileOps;
/*
* Utility functions
*/
static void VNetFreeInterfaceList(void);
static int VNetSwitchToDifferentPeer(VNetJack *jack, VNetJack *newPeer,
Bool connectNewToPeer,
struct file *filp, VNetPort *jackPort,
VNetPort *newPeerPort);
static void VNetKrefRelease(struct kref *kref);
uint vnet_max_qlen = VNET_MAX_QLEN;
module_param(vnet_max_qlen, uint, 0);
MODULE_PARM_DESC(vnet_max_qlen, "Maximum queue length of the vmnet, default is"
" 1024, maximum is 1024");
/*
*----------------------------------------------------------------------
*
* VNetRegister --
*
* (debugging support) Should be the first function of this file
*
* Results:
*
* Registers the module.
* /sbin/ksyms -a | grep VNetRegister will return the base
* address of that function as loaded in the kernel.
*
* Since this is the first function of the kernel module,
* every other symbol can be computing by adding the base
* to the output of nm.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
VNetRegister(int value) // IN: unused
{
LOG(0, (KERN_WARNING "/dev/vmnet: VNetRegister called\n"));
return 0;
}
#ifdef VMW_HAVE_SK_ALLOC_WITH_PROTO
/*
*----------------------------------------------------------------------
*
* VNetProtoRegister --
* VNetProtoUnregister --
*
* Register or unregister the struct proto that we use for sk_alloc.
*
* Results:
* int.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
#define VNetProtoRegister() proto_register(&vmnet_proto, 0)
#define VNetProtoUnregister() \
do { \
proto_unregister(&vmnet_proto); \
} while (0)
#else
#define VNetProtoRegister() 0
#define VNetProtoUnregister()
#endif
/*
*----------------------------------------------------------------------
*
* VNetAddPortToList --
*
* Add port to list of known ports.
* Caller must own vnetStructureMutex.
*
* Results:
*
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static INLINE void
VNetAddPortToList(VNetPort *port) // IN: port to add to list
{
port->next = vnetAllPorts;
vnetAllPorts = port;
}
/*
*----------------------------------------------------------------------
*
* VNetRemovePortFromList --
*
* Remove port from list of known ports.
* Caller must own vnetStructureMutex.
*
* Results:
*
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static INLINE void
VNetRemovePortFromList(const VNetPort *port) // IN: port to remove from list
{
VNetPort **p;
for (p = &vnetAllPorts; *p; p = &(*p)->next) {
if (*p == port) {
*p = port->next;
break;
}
}
}
/*
*----------------------------------------------------------------------
*
* vmnet_init_module --
*
* linux module entry point. Called by /sbin/insmod command.
* Initializes module and Registers this driver for a
* vnet major #. The 64-bit version of this driver also
* registers handlers for 32-bit applications.
*
* Results:
* errno (0 on success).
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
vmnet_init_module(void)
{
int retval;
/*
* First initialize everything, and as a last step register
* vmnet device: immediately after registration anybody can
* ask driver for anything.
*/
retval = VNetProc_Init();
if (retval) {
LOG(0, (KERN_NOTICE "/dev/vmnet: could not register proc fs\n"));
return -ENOENT;
}
retval = VNetProtoRegister();
if (retval) {
goto err_proto;
}
/*
* Initialize the file_operations structure. Because this code is always
* compiled as a module, this is fine to do it here and not in a static
* initializer.
*/
memset(&vnetFileOps, 0, sizeof vnetFileOps);
vnetFileOps.owner = THIS_MODULE;
vnetFileOps.read = VNetFileOpRead;
vnetFileOps.write = VNetFileOpWrite;
vnetFileOps.poll = VNetFileOpPoll;
vnetFileOps.unlocked_ioctl = VNetFileOpUnlockedIoctl;
vnetFileOps.compat_ioctl = VNetFileOpUnlockedIoctl;
vnetFileOps.open = VNetFileOpOpen;
vnetFileOps.release = VNetFileOpClose;
retval = register_chrdev(VNET_MAJOR_NUMBER, "vmnet", &vnetFileOps);
if (retval) {
LOG(0, (KERN_NOTICE "/dev/vmnet: could not register major device %d\n",
VNET_MAJOR_NUMBER));
goto err_chrdev;
}
if (vnet_max_qlen < (VNET_MAX_QLEN >> 1) || vnet_max_qlen > 1024) {
LOG(0, (KERN_NOTICE "vmnet: Invalid vnet_max_qlen specified, "
"vnet_max_qlen is to be set to default value 1024.\n"));
vnet_max_qlen = VNET_MAX_QLEN;
}
return 0;
err_chrdev:
VNetProtoUnregister();
err_proto:
VNetProc_Cleanup();
return retval;
}
/*
*----------------------------------------------------------------------
*
* vmnet_cleanup_module --
*
* Called by /sbin/rmmod. Unregisters this driver for a
* vnet major #, and deinitializes the modules. The 64-bit
* version of this driver also unregisters the handlers
* for 32-bit applications.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static void
vmnet_cleanup_module(void)
{
unregister_chrdev(VNET_MAJOR_NUMBER, "vmnet");
VNetProtoUnregister();
VNetProc_Cleanup();
}
/*
*----------------------------------------------------------------------
*
* VNetFileOpOpen --
*
* The virtual network's open file operation. Connects to (and
* potentially allocates) a hub, then opens a connection to
* this virtual network (i.e., plugs a cable into the virtual
* hub).
*
* Results:
* Errno.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
VNetFileOpOpen(struct inode *inode, // IN: used to get hub number
struct file *filp) // IN: filp
{
VNetPort *port;
VNetJack *hubJack;
int hubNum;
int retval;
LOG(1, (KERN_DEBUG "/dev/vmnet: open called by PID %d (%s)\n",
current->pid, current->comm));
/*
* Sanity check the hub number.
*/
hubNum = MINOR(inode->i_rdev);
if (hubNum < 0 || hubNum >= VNET_NUM_VNETS) {
return -ENODEV;
}
/*
* Allocate port
*/
retval = VNetUserIf_Create(&port);
if (retval) {
return -retval;
}
port->hubNum = hubNum;
/*
* Allocate and connect to hub.
*/
hubJack = VNetHub_AllocVnet(hubNum);
if (!hubJack) {
kref_put(&port->jack.kref, VNetKrefRelease);
return -EBUSY;
}
mutex_lock(&vnetStructureMutex);
retval = VNetConnect(&port->jack, hubJack);
if (retval) {
mutex_unlock(&vnetStructureMutex);
kref_put(&port->jack.kref, VNetKrefRelease);
kref_put(&hubJack->kref, VNetKrefRelease);
return retval;
}
VNetAddPortToList(port);
mutex_unlock(&vnetStructureMutex);
/*
* Store away jack in file pointer private field for later use.
*/
filp->private_data = port;
LOG(1, (KERN_DEBUG "/dev/vmnet: port on hub %d successfully opened\n", hubNum));
return 0;
}
/*
*----------------------------------------------------------------------
*
* VNetFileOpClose --
*
* The virtual network's close file operation. Disconnects
* from the virtual hub (i.e., unplugs the cable).
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
VNetFileOpClose(struct inode *inode, // IN: (unused)
struct file *filp) // IN: filp
{
VNetPort *port = (VNetPort*)filp->private_data;
VNetJack *peer;
if (!port) {
LOG(1, (KERN_DEBUG "/dev/vmnet: bad file pointer on close\n"));
return -EBADF;
}
mutex_lock(&vnetStructureMutex);
peer = VNetDisconnect(&port->jack);
VNetRemovePortFromList(port);
mutex_unlock(&vnetStructureMutex);
kref_put(&port->jack.kref, VNetKrefRelease);
kref_put(&peer->kref, VNetKrefRelease);
return 0;
}
/*
*----------------------------------------------------------------------
*
* VNetFileOpRead --
*
* The virtual network's read file operation.
*
* Results:
* On success the len of the packet received,
* else if no packet waiting and nonblocking 0,
* else -errno.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
ssize_t
VNetFileOpRead(struct file *filp, // IN:
char *buf, // OUT:
size_t count, // IN:
loff_t *ppos) // IN: (unused)
{
VNetPort *port = (VNetPort*)filp->private_data;
if (!port) {
LOG(1, (KERN_DEBUG "/dev/vmnet: bad file pointer on read\n"));
return -EBADF;
}
if (!port->fileOpRead) {
return -EPERM;
}
return port->fileOpRead(port, filp, buf, count);
}
/*
*----------------------------------------------------------------------
*
* VNetFileOpWrite --
*
* The virtual network's write file operation.
*
* Results:
* On success the count of bytes written else errno.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
ssize_t
VNetFileOpWrite(struct file *filp, // IN:
const char *buf, // IN:
size_t count, // IN:
loff_t *ppos) // IN: (unused)
{
VNetPort *port = (VNetPort*)filp->private_data;
if (!port) {
LOG(1, (KERN_DEBUG "/dev/vmnet: bad file pointer on write\n"));
return -EBADF;
}
if (!port->fileOpWrite) {
return -EPERM;
}
return port->fileOpWrite(port, filp, buf, count);
}
/*
*----------------------------------------------------------------------
*
* VNetFileOpPoll --
*
* The virtual network's file select operation.
*
* Results:
* Return 1 if success, else sleep and return 0.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static unsigned int
VNetFileOpPoll(struct file *filp, // IN:
poll_table *wait) // IN:
{
VNetPort *port = (VNetPort*)filp->private_data;
if (!port) {
LOG(1, (KERN_DEBUG "/dev/vmnet: bad file pointer on poll\n"));
return -EBADF;
}
if (!port->fileOpPoll) {
return -EPERM;
}
return port->fileOpPoll(port, filp, wait);
}
/*
*----------------------------------------------------------------------
*
* VNetFileOpUnlockedIoctl --
*
* The virtual network's ioctl file operation. This is used for
* setup of the connection.
*
* Currently supported commands are in device-private ioctl space:
*
* SIOCGETAPIVERSION2 - version check - ioarg IN: uint32 version
* SIOCSPORTFLAGS - set flags - ioarg IN: 4 bytes
* SIOCSLADRF (0x89F2) - set logical address filter (for
* filtering multicast packets) - ioarg IN: 8 bytes
* SIOCGBRSTATUS - get bridging status - ioarg OUT: 4 bytes
* SIOCSPEER3 - set bridge peer interface - ioarg IN: 36 bytes
* SIOCSBIND - bind to a particular vnet/PVN - ioarg IN: VNet_Bind
* SIOCSFILTERRULES - set host filter rules - ioarg IN: VNet_Filter
* SIOCSUSERLISTENER - set user listener - ioarg IN: VNet_SetUserListener
* SIOCNETIF - set peer - ioarg IN: 8-char name
* SIOCGETMACADDR - get ethernet address - ioarg OUT: 6 bytes
* SIOCSETMACADDR - set ethernet address - ioarg IN: 6 bytes
* SIOCSMCASTFILTER - set multicast filter - ioarg IN: VNetMcastFilter
*
* Supported flags are (taken from if.h):
*
* IFF_UP - ready to receive packets - OFF by default
* IFF_BROADCAST - receive broadcast packets - OFF by default
* IFF_DEBUG - turn on debugging - OFF by default
* IFF_PROMISC - promiscuous mode - OFF by default
* IFF_MULTICAST - receive multicast packets - OFF by default
* IFF_ALLMULTI - receive all multicast packets
* (like IFF_PROMISC but with multicast) - OFF by default
*
* Results:
* On success 0 else errno.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
long
VNetFileOpUnlockedIoctl(struct file *filp, // IN:
unsigned int iocmd, // IN:
unsigned long ioarg) // IN:
{
VNetPort *port = (VNetPort*)filp->private_data;
VNetJack *hubJack;
VNetPort *new;
char name[32];
long retval;
VNet_SetMacAddrIOCTL macAddr;
VNet_Bind newNetwork;
VNet_BridgeParams bridgeParams;
if (!port) {
LOG(1, (KERN_DEBUG "/dev/vmnet: bad file pointer on ioctl\n"));
return -EBADF;
}
// sprintf(vnetHub[hubNum]->devName, "vmnet%d", hubNum);
switch (iocmd) {
case SIOCSPEER3:
retval = copy_from_user(&bridgeParams, (void *)ioarg,
sizeof bridgeParams);
if (retval) {
return -EFAULT;
}
NULL_TERMINATE_STRING(bridgeParams.name);
if (!capable(CAP_NET_RAW)) {
return -EACCES;
}
mutex_lock(&vnetIoctlMutex);
retval = VNetBridge_Create(bridgeParams.name, bridgeParams.flags,
port->jack.peer, &new);
if (!retval) {
retval = VNetSwitchToDifferentPeer(&port->jack, &new->jack, TRUE,
filp, port, new);
}
mutex_unlock(&vnetIoctlMutex);
break;
case SIOCSUSERLISTENER:
{
VNet_SetUserListener param;
/* copy parameters */
if (copy_from_user(&param, (void *)ioarg, sizeof param)) {
return -EFAULT;
}
/* check version */
if (param.version != VNET_EVENT_VERSION) {
return -EINVAL;
}
mutex_lock(&vnetIoctlMutex);
/* create user listener */
retval = VNetUserListener_Create(param.classMask, port->jack.peer, &new);
if (retval == 0) {
/* replace current port with user listener */
retval = VNetSwitchToDifferentPeer(&port->jack, &new->jack, TRUE,
filp, port, new);
}
mutex_unlock(&vnetIoctlMutex);
}
break;
case SIOCNETIF:
if (copy_from_user(name, (void *)ioarg, 8)) {
return -EFAULT;
}
name[8] = '\0'; /* allow 8-char unterminated string */
mutex_lock(&vnetIoctlMutex);
retval = VNetNetIf_Create(name, &new, port->hubNum);
if (retval == 0) {
retval = VNetSwitchToDifferentPeer(&port->jack, &new->jack,
TRUE, filp, port, new);
}
mutex_unlock(&vnetIoctlMutex);
break;
case SIOCSBIND:
if (copy_from_user(&newNetwork, (void *)ioarg, sizeof newNetwork)) {
return -EFAULT;
}
if (newNetwork.version != VNET_BIND_VERSION) {
LOG(1, (KERN_NOTICE "/dev/vmnet: bad bind version: %u %u\n",
newNetwork.version, VNET_BIND_VERSION));
return -EINVAL;
}
mutex_lock(&vnetIoctlMutex);
switch (newNetwork.bindType) {
case VNET_BIND_TO_VNET:
if (newNetwork.number < 0 || newNetwork.number >= VNET_NUM_VNETS) {
LOG(1, (KERN_NOTICE "/dev/vmnet: invalid bind to vnet %d\n",
newNetwork.number));
retval = -EINVAL;
} else {
hubJack = VNetHub_AllocVnet(newNetwork.number);
retval = 0;
}
break;
case VNET_BIND_TO_PVN:
{
uint8 id[VNET_PVN_ID_LEN] = {0};
if (memcmp(id, newNetwork.id,
min(sizeof id, sizeof newNetwork.id)) == 0) {
LOG(0, (KERN_NOTICE "/dev/vmnet: invalid bind to pvn\n"));
retval = -EINVAL;
} else {
memcpy(id, newNetwork.id,
min(sizeof id, sizeof newNetwork.id));
hubJack = VNetHub_AllocPvn(id);
retval = 0;
}
}
break;
default:
LOG(1, (KERN_NOTICE "/dev/vmnet: bad bind type: %u\n",
newNetwork.bindType));
retval = -EINVAL;
}
if (retval == 0) {
retval = VNetSwitchToDifferentPeer(&port->jack, hubJack,
FALSE, NULL, NULL, NULL);
}
mutex_unlock(&vnetIoctlMutex);
break;
case SIOCSFILTERRULES:
LOG(0, (KERN_NOTICE "/dev/vmnet: kernel doesn't support netfilter\n"));
return -EINVAL;
case SIOCGBRSTATUS:
{
uint32 flags;
read_lock(&vnetPeerLock);
flags = VNetIsBridged(&port->jack);
read_unlock(&vnetPeerLock);
retval = put_user(flags, (uint32 *)ioarg) ? -EFAULT : 0;
}
break;
case SIOCGETMACADDR:
{
uint8 paddr[ETH_ALEN];
mutex_lock(&vnetIoctlMutex);
memcpy(paddr, port->paddr, sizeof paddr);
mutex_unlock(&vnetIoctlMutex);
retval = copy_to_user((void *)ioarg, paddr, ETH_ALEN) ? -EFAULT : 0;
}
break;
case SIOCSLADRF:
{
uint8 ladrf[VNET_LADRF_LEN];
if (copy_from_user(ladrf, (void *)ioarg, sizeof ladrf)) {
return -EFAULT;
}
mutex_lock(&vnetIoctlMutex);
memcpy(port->ladrf, ladrf, sizeof port->ladrf);
port->exactFilterLen = 0;
mutex_unlock(&vnetIoctlMutex);
retval = 0;
}
break;
case SIOCSPORTFLAGS:
{
uint32 flags;
if (get_user(flags, (uint32 *)ioarg)) {
return -EFAULT;
}
/* Sanitize */
flags &= IFF_UP | IFF_BROADCAST | IFF_DEBUG | IFF_PROMISC |
IFF_MULTICAST | IFF_ALLMULTI;
flags |= IFF_RUNNING;
mutex_lock(&vnetIoctlMutex);
port->flags = flags;
if (port->fileOpIoctl) {
/*
* Userif ports have some postprocessing when the IFF_UP flags is
* changed.
*/
port->fileOpIoctl(port, filp, iocmd, ioarg);
}
mutex_unlock(&vnetIoctlMutex);
retval = 0;
}
break;
case SIOCSETMACADDR:
if (copy_from_user(&macAddr, (void *)ioarg, sizeof macAddr)) {
return -EFAULT;
}
switch (macAddr.version) {
case 1:
if (macAddr.flags & VNET_SETMACADDRF_UNIQUE) {
if (VMX86_IS_VIRT_ADAPTER_MAC(macAddr.addr)) {
return -EBUSY;
}
mutex_lock(&vnetIoctlMutex);
retval = VNetSetMACUnique(port, macAddr.addr);
mutex_unlock(&vnetIoctlMutex);
} else {
mutex_lock(&vnetIoctlMutex);
memcpy(port->paddr, macAddr.addr, ETH_ALEN);
mutex_unlock(&vnetIoctlMutex);
retval = 0;
}
break;
default:
retval = -EINVAL;
break;
}
break;
case SIOCSMCASTFILTER:
{
VNetMcastFilter vnetMcastFilter;
if (copy_from_user(&vnetMcastFilter, (void *)ioarg,
sizeof vnetMcastFilter)) {
return -EFAULT;
}
if (vnetMcastFilter.exactFilterLen > VNET_MAX_EXACT_FILTER_LEN) {
return -EFAULT;
}
mutex_lock(&vnetIoctlMutex);
memcpy(port->ladrf, vnetMcastFilter.ladrf, sizeof port->ladrf);
memcpy(port->exactFilter, vnetMcastFilter.exactFilter,
vnetMcastFilter.exactFilterLen * ETHER_ADDR_LEN);
port->exactFilterLen = vnetMcastFilter.exactFilterLen;
mutex_unlock(&vnetIoctlMutex);
retval = 0;
break;
}
case SIOCGETAPIVERSION2:
{
uint32 verFromUser;
if (get_user(verFromUser, (uint32 *)ioarg)) {
return -EFAULT;
}
/* Should we require verFromUser == VNET_API_VERSION? */
}
retval = put_user(VNET_API_VERSION, (uint32 *)ioarg) ? -EFAULT : 0;
break;
default:
if (!port->fileOpIoctl) {
return -ENOIOCTLCMD;
}
mutex_lock(&vnetIoctlMutex);
retval = port->fileOpIoctl(port, filp, iocmd, ioarg);
mutex_unlock(&vnetIoctlMutex);
break;
}
return retval;
}
/*
*----------------------------------------------------------------------
*
* VNetSwitchToDifferentPeer --
*
* This function is used to disconnect from a one peer and
* connect to another peer. If the connect to the new peer
* fails (e.g., if the connect would create a cycle), then
* the function will reconnect back to the original peer.
* The function will deallocate the old or the new peer,
* whichever is the one that has been disconnected.
*
* optional behavior:
*
* For the case where the function was successful in switching
* to the new peer, the caller can optionally provide a 'filp'
* (private_data is set to the new port), and the caller
* can also provide one each of a port to be added to
* and/or removed from the port list. If the caller provides
* ports to add/remove from the list, then
* connectNewToPeerOfJack should be set to TRUE (otherwise
* inconsistencies in the port list are likely to occur).
*
* Results:
* errno (0 on success).
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
VNetSwitchToDifferentPeer(VNetJack *jack, // IN: jack whose peer is to be changed
VNetJack *newPeer, // IN: the new peer to try to switch to
Bool connectNewToPeerOfJack, // IN: connect new to peer of 'jack' or to 'jack' itself?
struct file *filp, // IN: (optional) set filp to 'newpeerPort' on success
VNetPort *jackPort, // IN: (optional) port to remove from list on success
VNetPort *newPeerPort) // IN: (optional) port to add to list on success
{
VNetJack *oldPeer;
int retval;
if (newPeer == NULL) {
LOG(0, (KERN_NOTICE "/dev/vmnet: failed to alloc new peer\n"));
return -EINVAL;
}
/*
* OK this is tricky. Try and connect the new peer while saving
* enough information so that we can reconnect back to the
* old peer if a cycle is detected.
*/
mutex_lock(&vnetStructureMutex);
/* Disconnect from the old peer */
oldPeer = VNetDisconnect(jack);
/* Try to connect to the new peer */
if (connectNewToPeerOfJack) {
retval = VNetConnect(oldPeer, newPeer);
} else {
retval = VNetConnect(jack, newPeer);
}
if (retval) {
/* Connect failed, so reconnect back to old peer */
int retval2 = VNetConnect(jack, oldPeer);
mutex_unlock(&vnetStructureMutex);
/* Free the new peer */
kref_put(&newPeer->kref, VNetKrefRelease);
if (retval2) {
// assert xxx redo this
LOG(1, (KERN_NOTICE "/dev/vmnet: cycle on connect failure\n"));
return -EBADF;
}
return retval;
}
if (filp != NULL) {
newPeerPort->hubNum = jackPort->hubNum;
VNetAddPortToList(newPeerPort);
filp->private_data = newPeerPort;
VNetRemovePortFromList(jackPort);
}
mutex_unlock(&vnetStructureMutex);
/* Connected to new peer, so dealloc the old peer */
if (connectNewToPeerOfJack) {
kref_put(&jack->kref, VNetKrefRelease);
} else {
kref_put(&oldPeer->kref, VNetKrefRelease);
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* VNetMulticastFilter --
*
* Utility function that filters multicast packets according
* to a 64-bit logical address filter (like the one on the
* lance chipset). AllMultiFilter lets all packets through.
*
* We generate a hash value from the destination MAC address
* and see if it's in our filter. Broadcast packets have
* already OK'd by PacketMatch, so we don't have to worry
* about that.
*
* (This is in the green AMD "Ethernet Controllers" book,
* page 1-53.)
*
* Results:
* TRUE if packet is in filter, FALSE if not.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */
static INLINE_SINGLE_CALLER Bool
VNetMulticastFilter(const uint8 *destAddr, // IN: multicast MAC
const uint8 *exactFilter, // IN: exact mc filter
const uint32 exactFilterLen, // IN: length of exact mc filter
const uint8 *ladrf) // IN: logical addr filter
{
uint16 hashcode;
int32 crc;
int32 poly = CRC_POLYNOMIAL_BE;
int j;
int bit;
int byte;
/* first check exact multicast filter */
if (exactFilterLen) {
uint32 i;
for (i = 0; i < exactFilterLen; i++) {
if (MAC_EQ(destAddr, exactFilter + i * ETHER_ADDR_LEN)){
return TRUE;
}
}
/*
* Do not need to further compute and check ladrf if no match
* in exact multicast filter, since only one of them is
* used at a time.
*/
return FALSE;
}
crc = 0xffffffff; /* init CRC for each address */
for (byte = 0; byte < ETH_ALEN; byte++) { /* for each address byte */
/* process each address bit */
for (bit = *destAddr++, j = 0;
j < VNET_LADRF_LEN;
j++, bit >>= 1) {
crc = (crc << 1) ^ ((((crc<0?1:0) ^ bit) & 0x01) ? poly : 0);
}
}
hashcode = (crc & 1); /* hashcode is 6 LSb of CRC ... */
for (j = 0; j < 5; j++) { /* ... in reverse order. */
hashcode = (hashcode << 1) | ((crc>>=1) & 1);
}
byte = hashcode >> 3; /* bit[3-5] -> byte in filter */
bit = 1 << (hashcode & 0x07); /* bit[0-2] -> bit in byte */
if (ladrf[byte] & bit) {
return TRUE;
} else {
return FALSE;
}
}
/*
*----------------------------------------------------------------------
*
* VNetPacketMatch --
*
* Determines whether the packet should be given to the interface.
*
* Results:
* TRUE if the pasket is OK for this interface, FALSE otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
Bool
VNetPacketMatch(const uint8 *destAddr, // IN: destination MAC
const uint8 *ifAddr, // IN: MAC of interface
const uint8 *exactFilter, // IN: exact mc filter
const uint32 exactFilterLen, // IN: size of exact mc filter
const uint8 *ladrf, // IN: multicast filter
uint32 flags) // IN: filter flags
{
/*
* Return TRUE if promiscuous requested, or unicast destined
* for interface, or broadcast (and broadcast requested), or
* if multicast (and all multicast, or this specific
* multicast MAC, was requested).
*/
return ((flags & IFF_PROMISC) || MAC_EQ(destAddr, ifAddr) ||
((flags & IFF_BROADCAST) && MAC_EQ(destAddr, broadcast)) ||
((destAddr[0] & 0x1) && (flags & IFF_ALLMULTI ||
(flags & IFF_MULTICAST &&
VNetMulticastFilter(destAddr, exactFilter, exactFilterLen, ladrf)))));
}
/*
*----------------------------------------------------------------------
*
* VNet_MakeMACAddress --
*
* Generate a unique MAC address and assign it to the given port.
* The address will be in the range:
*
* VMX86_STATIC_OUI:e0:00:00 - VMX86_STATIC_OUI:ff:ff:ff
*
* Results:
* errno.
*
* Side effects:
* The address is changed.
*
*----------------------------------------------------------------------
*/
int
VNet_MakeMACAddress(VNetPort *port) // IN: port
{
uint8 paddr[ETH_ALEN] = {0};
int conflict;
int maxTries = 1000;
do {
VMX86_GENERATE_RANDOM_MAC(paddr);
conflict = VNetSetMACUnique(port, paddr);
/*
* We don't have to check for conflicts with the virtual
* host adapters since they are in the range
* c0:00:00-c0:00:FF.
*/
} while (maxTries-- > 0 && conflict);
return conflict;
}
/*
*----------------------------------------------------------------------
*
* VNetConnect --
*
* Connect 2 jacks.
* vnetStructureMutex must be held.
*
* Results:
* errno.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
VNetConnect(VNetJack *jack1, // IN: jack
VNetJack *jack2) // IN: jack
{
static int vnetGeneration = 0;
Bool foundCycle;
unsigned long flags;
vnetGeneration++;
foundCycle = VNetCycleDetect(jack1, vnetGeneration);
if (foundCycle) {
VNetFreeInterfaceList();
return -EDEADLK;
}
foundCycle = VNetCycleDetect(jack2, vnetGeneration);
if (foundCycle) {
VNetFreeInterfaceList();
return -EDEADLK;
}
VNetFreeInterfaceList();
/*
* Synchronize with peer readers
*/
write_lock_irqsave(&vnetPeerLock, flags);
jack1->peer = jack2;
jack2->peer = jack1;
jack1->state = TRUE;
jack2->state = TRUE;
kref_init(&jack1->kref);
kref_init(&jack2->kref);
write_unlock_irqrestore(&vnetPeerLock, flags);
if (jack2->numPorts) {
VNetPortsChanged(jack1);
}
if (jack1->numPorts) {
VNetPortsChanged(jack2);
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* VNetDisconnect --
*
* Disconnect 2 jacks.
* vnetStructureMutex must be held.
*
* Results:
* Return the peer jack (returns NULL on error, or if no peer)
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
VNetJack *
VNetDisconnect(VNetJack *jack) // IN: jack
{
VNetJack *peer;
unsigned long flags;
write_lock_irqsave(&vnetPeerLock, flags);
peer = jack->peer;
if (!peer) {
write_unlock_irqrestore(&vnetPeerLock, flags);
return NULL;
}
jack->state = FALSE;
peer->state = FALSE;
write_unlock_irqrestore(&vnetPeerLock, flags);
if (peer->numPorts) {
VNetPortsChanged(jack);
}
if (jack->numPorts) {
VNetPortsChanged(peer);
}
return peer;
}
/*
*----------------------------------------------------------------------
*
* VNetCycleDetectIf --
*
* Perform the cycle detect alogorithm for this generation on a
* specific interface. This could be a bridged interface, host
* interface or both.
* vnetStructureMutex must be held.
*
* Results:
* TRUE if a cycle was detected, FALSE otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
Bool
VNetCycleDetectIf(const char *name, // IN:
int generation) // IN:
{
VNetInterface *p;
for (p = vnetInterfaces; p != NULL; p = p->next) {
if (!strcmp(name, p->name)) {
if (p->myGeneration == generation) {
return TRUE;
} else {
p->myGeneration = generation;
return FALSE;
}
}
}
p = kmalloc(sizeof *p, GFP_USER);
if (!p) {
// assert
return TRUE;
}
memcpy(p->name, name, sizeof p->name);
NULL_TERMINATE_STRING(p->name);
p->myGeneration = generation;
p->next = vnetInterfaces;
vnetInterfaces = p;
return FALSE;
}
/*
*----------------------------------------------------------------------
*
* VNetFreeInterfaceList --
*
* Free's the linked list that may have been constructed
* during a recent run on the cycle detect alogorithm.
* vnetStructureMutex must be held.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static void
VNetFreeInterfaceList(void)
{
while (vnetInterfaces != NULL) {
VNetInterface *next = vnetInterfaces->next;
kfree(vnetInterfaces);
vnetInterfaces = next;
}
}
/*
*----------------------------------------------------------------------
*
* VNetKrefRelease --
*
* Free the VNetJack if no reference.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static void
VNetKrefRelease(struct kref *kref)
{
struct VNetJack *jack = container_of(kref, struct VNetJack, kref);
jack->state = FALSE;
jack->peer = NULL;
VNetFree(jack);
}
/*
*----------------------------------------------------------------------
*
* VNetSend --
*
* Send a packet through this jack. Note, the packet goes to the
* jacks peer.
*
* Results:
* None.
*
* Side effects:
* The skb is no longer owned by us.
*
*----------------------------------------------------------------------
*/
void
VNetSend(VNetJack *jack, // IN: jack
struct sk_buff *skb) // IN: packet
{
VNetJack *peer;
read_lock(&vnetPeerLock);
if (jack && jack->peer && jack->peer->rcv) {
peer = jack->peer;
kref_get(&(peer->kref));
read_unlock(&vnetPeerLock);
peer->rcv(peer, skb);
kref_put(&(peer->kref), VNetKrefRelease);
} else {
read_unlock(&vnetPeerLock);
dev_kfree_skb(skb);
}
}
/*
*----------------------------------------------------------------------
*
* VNetSetMACUnique --
*
* Verify that MAC address is not used by other ports.
* Function grabs mutex, so caller shouldn't hold any
* locks.
*
* Results:
*
* 0 if address is unique. Port's paddr is updated.
* -EBUSY if address is already in use. Port's paddr is unchanged.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
VNetSetMACUnique(VNetPort *port, // IN:
const uint8 mac[ETH_ALEN]) // IN:
{
VNetPort *p;
mutex_lock(&vnetStructureMutex);
for (p = vnetAllPorts; p != NULL; p = p->next) {
if (p != port && MAC_EQ(p->paddr, mac)) {
mutex_unlock(&vnetStructureMutex);
return -EBUSY;
}
}
memcpy(port->paddr, mac, ETH_ALEN);
mutex_unlock(&vnetStructureMutex);
return 0;
}
/*
*----------------------------------------------------------------------
*
* VNetPrintJack --
*
* Print info about the jack to a buffer.
*
* Results:
* Length of the write.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
VNetPrintJack(const VNetJack *jack, // IN: jack
char *buf) // OUT: info about jack
{
int len = 0;
read_lock(&vnetPeerLock);
if (!jack->peer) {
len += sprintf(buf+len, "connected not ");
} else {
len += sprintf(buf+len, "connected %s ", jack->peer->name);
}
read_unlock(&vnetPeerLock);
return len;
}
/*
*----------------------------------------------------------------------
*
* VNetPrintPort --
*
* Print info about the port to a buffer.
*
* Results:
* Length of the write.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
VNetPrintPort(const VNetPort *port, // IN: port
char *buf) // OUT: info about port
{
int len = 0;
len += VNetPrintJack(&port->jack, buf+len);
len += sprintf(buf+len, "mac %02x:%02x:%02x:%02x:%02x:%02x ",
port->paddr[0], port->paddr[1], port->paddr[2],
port->paddr[3], port->paddr[4], port->paddr[5]);
len += sprintf(buf+len, "ladrf %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x ",
port->ladrf[0], port->ladrf[1], port->ladrf[2],
port->ladrf[3], port->ladrf[4], port->ladrf[5],
port->ladrf[6], port->ladrf[7]);
len += sprintf(buf+len, "flags IFF_RUNNING");
if (port->flags & IFF_UP) {
len += sprintf(buf+len, ",IFF_UP");
}
if (port->flags & IFF_BROADCAST) {
len += sprintf(buf+len, ",IFF_BROADCAST");
}
if (port->flags & IFF_DEBUG) {
len += sprintf(buf+len, ",IFF_DEBUG");
}
if (port->flags & IFF_PROMISC) {
len += sprintf(buf+len, ",IFF_PROMISC");
}
if (port->flags & IFF_MULTICAST) {
len += sprintf(buf+len, ",IFF_MULTICAST");
}
if (port->flags & IFF_ALLMULTI) {
len += sprintf(buf+len, ",IFF_ALLMULTI");
}
len += sprintf(buf+len, " ");
return len;
}
/*
*----------------------------------------------------------------------
*
* VNetSnprintf --
*
* Wrapper to account for lack of snprintf() in older kernels,
* where 'old' appears to be older than 2.4.8.
*
* Results:
* Refer to docs for snprintf() and / or sprintf(). This
* version unconditionally adds NULL termination to the end
* of the string.
*
* Side effects:
* Might overrun buffer on older kernels.
*
*----------------------------------------------------------------------
*/
int
VNetSnprintf(char *str, // OUT: resulting string
size_t size, // IN: length of 'result' in bytes
const char *format, // IN: format string
...) // IN: (optional)
{
int length;
va_list args;
va_start(args, format);
length = vsnprintf(str, size, format, args);
va_end(args);
str[size - 1] = '\0';
return length;
}
MODULE_AUTHOR("VMware, Inc.");
MODULE_DESCRIPTION("VMware Virtual Networking Driver.");
MODULE_LICENSE("GPL v2");
/*
* Starting with SLE10sp2, Novell requires that IHVs sign a support agreement
* with them and mark their kernel modules as externally supported via a
* change to the module header. If this isn't done, the module will not load
* by default (i.e., neither mkinitrd nor modprobe will accept it).
*/
MODULE_INFO(supported, "external");
module_init(vmnet_init_module);
module_exit(vmnet_cleanup_module);