/* $Id: VBoxNetFlt-solaris.c $ */ /** @file * VBoxNetFlt - Network Filter Driver (Host), Solaris Specific Code. */ /* * Copyright (C) 2008-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * 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, in version 3 of the * License. * * 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, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_NET_FLT_DRV #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VBOXNETFLT_SOLARIS_IPV6_POLLING #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Workaround for very strange define in sys/user.h // #define u (curproc->p_user) /* user is now part of proc structure */ #ifdef u #undef u #endif #define VBOXNETFLT_OS_SPECFIC 1 #include "../VBoxNetFltInternal.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The module name. */ #define DEVICE_NAME "vboxflt" /** The module descriptions as seen in 'modinfo'. */ #define DEVICE_DESC_DRV "VirtualBox NetDrv" #define DEVICE_DESC_MOD "VirtualBox NetMod" #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING /** Driver properties */ # define VBOXNETFLT_IP6POLLINTERVAL "ipv6-pollinterval" #endif /** Maximum loopback packet queue size per interface */ #define VBOXNETFLT_LOOPBACK_SIZE 32 /** VLAN tag masking, should probably be in IPRT? */ #define VLAN_ID(vlan) (((vlan) >> 0) & 0x0fffu) #define VLAN_CFI(vlan) (((vlan) >> 12) & 0x0001u) #define VLAN_PRI(vlan) (((vlan) >> 13) & 0x0007u) #define VLAN_TAG(pri,cfi,vid) (((pri) << 13) | ((cfi) << 12) | ((vid) << 0)) typedef struct VLANHEADER { uint16_t Type; uint16_t Data; } VLANHEADER; typedef struct VLANHEADER *PVLANHEADER; /********************************************************************************************************************************* * Global Functions * *********************************************************************************************************************************/ /** * Stream Driver hooks. */ static int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppvResult); static int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); static int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); static int VBoxNetFltSolarisQuiesceNotNeeded(dev_info_t *pDip); /** * Stream Module hooks. */ static int VBoxNetFltSolarisModOpen(queue_t *pQueue, dev_t *pDev, int fFile, int fStream, cred_t *pCred); static int VBoxNetFltSolarisModClose(queue_t *pQueue, int fFile, cred_t *pCred); static int VBoxNetFltSolarisModReadPut(queue_t *pQueue, mblk_t *pMsg); static int VBoxNetFltSolarisModWritePut(queue_t *pQueue, mblk_t *pMsg); /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Streams: module info. */ static struct module_info g_VBoxNetFltSolarisModInfo = { 0xbad, /* module id */ DEVICE_NAME, 0, /* min. packet size */ INFPSZ, /* max. packet size */ 0, /* hi-water mark */ 0 /* lo-water mark */ }; /** * Streams: read queue hooks. */ static struct qinit g_VBoxNetFltSolarisReadQ = { VBoxNetFltSolarisModReadPut, NULL, /* service */ VBoxNetFltSolarisModOpen, VBoxNetFltSolarisModClose, NULL, /* admin (reserved) */ &g_VBoxNetFltSolarisModInfo, NULL /* module stats */ }; /** * Streams: write queue hooks. */ static struct qinit g_VBoxNetFltSolarisWriteQ = { VBoxNetFltSolarisModWritePut, NULL, /* service */ NULL, /* open */ NULL, /* close */ NULL, /* admin (reserved) */ &g_VBoxNetFltSolarisModInfo, NULL /* module stats */ }; /** * Streams: IO stream tab. */ static struct streamtab g_VBoxNetFltSolarisStreamTab = { &g_VBoxNetFltSolarisReadQ, &g_VBoxNetFltSolarisWriteQ, NULL, /* muxread init */ NULL /* muxwrite init */ }; /** * cb_ops: driver char/block entry points */ static struct cb_ops g_VBoxNetFltSolarisCbOps = { nulldev, /* cb open */ nulldev, /* cb close */ nodev, /* b strategy */ nodev, /* b dump */ nodev, /* b print */ nodev, /* cb read */ nodev, /* cb write */ nodev, /* cb ioctl */ nodev, /* c devmap */ nodev, /* c mmap */ nodev, /* c segmap */ nochpoll, /* c poll */ ddi_prop_op, /* property ops */ &g_VBoxNetFltSolarisStreamTab, D_NEW | D_MP | D_MTQPAIR | D_MTOUTPERIM | D_MTOCEXCL, /* compat. flag */ CB_REV /* revision */ }; /** * dev_ops: driver entry/exit and other ops. */ static struct dev_ops g_VBoxNetFltSolarisDevOps = { DEVO_REV, /* driver build revision */ 0, /* ref count */ VBoxNetFltSolarisGetInfo, nulldev, /* identify */ nulldev, /* probe */ VBoxNetFltSolarisAttach, VBoxNetFltSolarisDetach, nodev, /* reset */ &g_VBoxNetFltSolarisCbOps, (struct bus_ops *)0, nodev, /* power */ VBoxNetFltSolarisQuiesceNotNeeded }; /** * modldrv: export driver specifics to kernel */ static struct modldrv g_VBoxNetFltSolarisDriver = { &mod_driverops, /* extern from kernel */ DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), &g_VBoxNetFltSolarisDevOps }; /** * fmodsw: streams module ops */ static struct fmodsw g_VBoxNetFltSolarisModOps = { DEVICE_NAME, &g_VBoxNetFltSolarisStreamTab, D_NEW | D_MP | D_MTQPAIR | D_MTOUTPERIM | D_MTOCEXCL }; /** * modlstrmod: streams module specifics to kernel */ static struct modlstrmod g_VBoxNetFltSolarisModule = { &mod_strmodops, /* extern from kernel */ DEVICE_DESC_MOD " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), &g_VBoxNetFltSolarisModOps }; /** * modlinkage: export install/remove/info to the kernel */ static struct modlinkage g_VBoxNetFltSolarisModLinkage = { MODREV_1, /* loadable module system revision */ { &g_VBoxNetFltSolarisDriver, /* streams driver framework */ &g_VBoxNetFltSolarisModule, /* streams module framework */ NULL /* terminate array of linkage structures */ } }; struct vboxnetflt_state_t; /** * vboxnetflt_dladdr_t: DL SAP address format */ typedef struct vboxnetflt_dladdr_t { ether_addr_t Mac; uint16_t SAP; } vboxnetflt_dladdr_t; #define VBOXNETFLT_DLADDRL sizeof(vboxnetflt_dladdr_t) /** * which stream is this? */ typedef enum VBOXNETFLTSTREAMTYPE { kUndefined = 0, kIp4Stream = 0x1b, kIp6Stream = 0xcc, kArpStream = 0xab, kPromiscStream = 0xdf } VBOXNETFLTSTREAMTYPE; /** * loopback packet identifier */ typedef struct VBOXNETFLTPACKETID { struct VBOXNETFLTPACKETID *pNext; uint16_t cbPacket; uint16_t Checksum; RTMAC SrcMac; RTMAC DstMac; } VBOXNETFLTPACKETID; typedef struct VBOXNETFLTPACKETID *PVBOXNETFLTPACKETID; /** * vboxnetflt_stream_t: per-stream data (multiple streams per interface) */ typedef struct vboxnetflt_stream_t { int DevMinor; /* minor device no. (for clone) */ queue_t *pReadQueue; /* read side queue */ struct vboxnetflt_stream_t *pNext; /* next stream in list */ PVBOXNETFLTINS volatile pThis; /* the backend instance */ VBOXNETFLTSTREAMTYPE Type; /* the type of the stream */ } vboxnetflt_stream_t; /** * vboxnetflt_promisc_stream_t: per-interface dedicated stream data */ typedef struct vboxnetflt_promisc_stream_t { vboxnetflt_stream_t Stream; /* dedicated/promiscuous stream */ bool fPromisc; /* cached promiscuous value */ bool fRawMode; /* whether raw mode request was successful */ uint32_t ModeReqId; /* track MIOCTLs for swallowing our fake request acknowledgements */ #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING PRTTIMER pIp6Timer; /* ipv6 stream poll timer for dynamic ipv6 stream attachment */ #endif size_t cLoopback; /* loopback queue size list */ timeout_id_t volatile TimeoutId; /* timeout id of promisc. req */ PVBOXNETFLTPACKETID pHead; /* loopback packet identifier head */ PVBOXNETFLTPACKETID pTail; /* loopback packet identifier tail */ } vboxnetflt_promisc_stream_t; typedef struct vboxnetflt_promisc_params_t { PVBOXNETFLTINS pThis; /* the backend instance */ bool fPromiscOn; /* whether promiscuous req. on or off */ } vboxnetflt_promisc_params_t; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static int vboxNetFltSolarisSetRawMode(vboxnetflt_promisc_stream_t *pPromiscStream); /* static int vboxNetFltSolarisSetFastMode(queue_t *pQueue); */ static int vboxNetFltSolarisPhysAddrReq(queue_t *pQueue); static void vboxNetFltSolarisCachePhysAddr(PVBOXNETFLTINS pThis, mblk_t *pPhysAddrAckMsg); static int vboxNetFltSolarisBindReq(queue_t *pQueue, int SAP); static int vboxNetFltSolarisNotifyReq(queue_t *pQueue); /* static int vboxNetFltSolarisUnitDataToRaw(PVBOXNETFLTINS pThis, mblk_t *pMsg, mblk_t **ppRawMsg); */ static int vboxNetFltSolarisRawToUnitData(mblk_t *pMsg, mblk_t **ppDlpiMsg); static inline void vboxNetFltSolarisInitPacketId(PVBOXNETFLTPACKETID pTag, mblk_t *pMsg); static int vboxNetFltSolarisQueueLoopback(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg); static bool vboxNetFltSolarisIsOurMBlk(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg); static mblk_t *vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst); static unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg); static int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc); static int vboxNetFltSolarisRecv(PVBOXNETFLTINS pThis, vboxnetflt_stream_t *pStream, queue_t *pQueue, mblk_t *pMsg); /* static mblk_t *vboxNetFltSolarisFixChecksums(mblk_t *pMsg); */ /* static void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg); */ /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Global device info handle. */ static dev_info_t *g_pVBoxNetFltSolarisDip = NULL; /** The (common) global data. */ static VBOXNETFLTGLOBALS g_VBoxNetFltSolarisGlobals; /** The list of all opened streams. */ vboxnetflt_stream_t *g_VBoxNetFltSolarisStreams = NULL; /** Global mutex protecting open/close. */ static RTSEMFASTMUTEX g_VBoxNetFltSolarisMtx = NIL_RTSEMFASTMUTEX; /** Global credentials using during open/close. */ static cred_t *g_pVBoxNetFltSolarisCred = NULL; /** * g_VBoxNetFltInstance is the current PVBOXNETFLTINS to be associated with the stream being created * in ModOpen. This is just shared global data between the dynamic attach and the ModOpen procedure. */ PVBOXNETFLTINS volatile g_VBoxNetFltSolarisInstance = NULL; /** Goes along with the instance to determine type of stream being opened/created. */ VBOXNETFLTSTREAMTYPE volatile g_VBoxNetFltSolarisStreamType = kUndefined; #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING /** Global IPv6 polling interval */ static int g_VBoxNetFltSolarisPollInterval = -1; #endif static int s_off_vnode = -1; #define VNODE_FOR_FILE_T(filetpointer) (*(struct vnode **)((char *)(filetpointer) + s_off_vnode)) static int vboxNetFltSolarisCtfGetMemberOffset(ctf_file_t *pCtfFile, const char *pszStruct, const char *pszMember, int *pOffset) { AssertReturn(pCtfFile, VERR_INVALID_PARAMETER); AssertReturn(pszStruct, VERR_INVALID_PARAMETER); AssertReturn(pszMember, VERR_INVALID_PARAMETER); AssertReturn(pOffset, VERR_INVALID_PARAMETER); ctf_id_t TypeId = ctf_lookup_by_name(pCtfFile, pszStruct); if (TypeId != CTF_ERR) { ctf_membinfo_t MemberInfo; bzero(&MemberInfo, sizeof(MemberInfo)); if (ctf_member_info(pCtfFile, TypeId, pszMember, &MemberInfo) != CTF_ERR) { *pOffset = (MemberInfo.ctm_offset >> 3); LogRel((DEVICE_NAME ":%s::%s at %d\n", pszStruct, pszMember, *pOffset)); return VINF_SUCCESS; } else LogRel((DEVICE_NAME ":ctf_member_info failed for struct %s member %s\n", pszStruct, pszMember)); } else LogRel((DEVICE_NAME ":ctf_lookup_by_name failed for struct %s\n", pszStruct)); return VERR_NOT_FOUND; } static int vboxNetFltSolarisProbeCtf(void) { /* * CTF probing for fluid f_vnode member in file_t. */ int rc = VERR_INTERNAL_ERROR; modctl_t *pModCtl = mod_hold_by_name("genunix"); if (pModCtl) { int err; mutex_enter(&mod_lock); ctf_file_t *pCtfFile = ctf_modopen(pModCtl->mod_mp, &err); mutex_exit(&mod_lock); if (pCtfFile) { rc = vboxNetFltSolarisCtfGetMemberOffset(pCtfFile, "file_t", "f_vnode", &s_off_vnode); ctf_close(pCtfFile); } else LogRel((DEVICE_NAME ":ctf_modopen failed. err=%d\n", err)); mod_release_mod(pModCtl); } else LogRel((DEVICE_NAME ":mod_hold_by_name failed.\n")); return rc; } /** * Kernel entry points */ int _init(void) { LogFunc((DEVICE_NAME ":_init\n")); /* * Prevent module autounloading. */ modctl_t *pModCtl = mod_getctl(&g_VBoxNetFltSolarisModLinkage); if (pModCtl) pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; else LogRel((DEVICE_NAME ":failed to disable autounloading!\n")); /* * Initialize IPRT. */ int rc = RTR0Init(0); if (RT_SUCCESS(rc)) { rc = vboxNetFltSolarisProbeCtf(); if (RT_SUCCESS(rc)) { /* * Initialize Solaris specific globals here. */ g_VBoxNetFltSolarisStreams = NULL; g_VBoxNetFltSolarisInstance = NULL; g_pVBoxNetFltSolarisCred = crdup(kcred); if (RT_LIKELY(g_pVBoxNetFltSolarisCred)) { rc = RTSemFastMutexCreate(&g_VBoxNetFltSolarisMtx); if (RT_SUCCESS(rc)) { /* * Initialize the globals and connect to the support driver. * * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) * for establishing the connect to the support driver. */ memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltSolarisGlobals); if (RT_SUCCESS(rc)) { rc = mod_install(&g_VBoxNetFltSolarisModLinkage); if (!rc) return rc; LogRel((DEVICE_NAME ":mod_install failed. rc=%d\n", rc)); vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltSolarisGlobals); } else LogRel((DEVICE_NAME ":failed to initialize globals.\n")); RTSemFastMutexDestroy(g_VBoxNetFltSolarisMtx); g_VBoxNetFltSolarisMtx = NIL_RTSEMFASTMUTEX; } } else { LogRel((DEVICE_NAME ":failed to allocate credentials.\n")); rc = VERR_NO_MEMORY; } } else LogRel((DEVICE_NAME ":vboxNetFltSolarisProbeCtf failed. rc=%d\n", rc)); RTR0Term(); } else LogRel((DEVICE_NAME ":failed to initialize IPRT (rc=%d)\n", rc)); memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); return RTErrConvertToErrno(rc); } int _fini(void) { int rc; LogFunc((DEVICE_NAME ":_fini\n")); /* * Undo the work done during start (in reverse order). */ rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltSolarisGlobals); if (RT_FAILURE(rc)) { LogRel((DEVICE_NAME ":_fini - busy!\n")); return EBUSY; } rc = mod_remove(&g_VBoxNetFltSolarisModLinkage); if (!rc) { if (g_pVBoxNetFltSolarisCred) { crfree(g_pVBoxNetFltSolarisCred); g_pVBoxNetFltSolarisCred = NULL; } if (g_VBoxNetFltSolarisMtx != NIL_RTSEMFASTMUTEX) { RTSemFastMutexDestroy(g_VBoxNetFltSolarisMtx); g_VBoxNetFltSolarisMtx = NIL_RTSEMFASTMUTEX; } RTR0Term(); } return rc; } int _info(struct modinfo *pModInfo) { LogFunc((DEVICE_NAME ":_info\n")); int rc = mod_info(&g_VBoxNetFltSolarisModLinkage, pModInfo); Log((DEVICE_NAME ":_info returns %d\n", rc)); return rc; } /** * Attach entry point, to attach a device to the system or resume it. * * @param pDip The module structure instance. * @param enmCmd Operation type (attach/resume). * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) { LogFunc((DEVICE_NAME ":VBoxNetFltSolarisAttach pDip=%p enmCmd=%d\n", pDip, enmCmd)); switch (enmCmd) { case DDI_ATTACH: { int rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, CLONE_DEV); if (rc == DDI_SUCCESS) { g_pVBoxNetFltSolarisDip = pDip; #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING /* * Get the user prop. for polling interval. */ int Interval = ddi_getprop(DDI_DEV_T_ANY, pDip, DDI_PROP_DONTPASS, VBOXNETFLT_IP6POLLINTERVAL, -1 /* default */); if (Interval == -1) Log((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: no poll interval property specified. Skipping Ipv6 polling.\n")); else if (Interval < 1 || Interval > 120) { LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Invalid polling interval %d. Expected between 1 and 120 secs.\n", Interval)); Interval = -1; } g_VBoxNetFltSolarisPollInterval = Interval; #endif ddi_report_dev(pDip); return DDI_SUCCESS; } else LogRel((DEVICE_NAME ":VBoxNetFltSolarisAttach failed to create minor node. rc%d\n", rc)); return DDI_FAILURE; } case DDI_RESUME: { /* Nothing to do here... */ return DDI_SUCCESS; } /* case DDI_PM_RESUME: */ default: return DDI_FAILURE; } } /** * Detach entry point, to detach a device to the system or suspend it. * * @param pDip The module structure instance. * @param enmCmd Operation type (detach/suspend). * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) { LogFunc((DEVICE_NAME ":VBoxNetFltSolarisDetach pDip=%p enmCmd=%d\n", pDip, enmCmd)); switch (enmCmd) { case DDI_DETACH: { ddi_remove_minor_node(pDip, NULL); return DDI_SUCCESS; } case DDI_RESUME: { /* Nothing to do here... */ return DDI_SUCCESS; } /* case DDI_PM_SUSPEND: */ /* case DDI_HOT_PLUG_DETACH: */ default: return DDI_FAILURE; } } /** * Quiesce not-needed entry point, as Solaris 10 doesn't have any * ddi_quiesce_not_needed() function. * * @param pDip The module structure instance. * * @return corresponding solaris error code. */ static int VBoxNetFltSolarisQuiesceNotNeeded(dev_info_t *pDip) { return DDI_SUCCESS; } /** * Info entry point, called by solaris kernel for obtaining driver info. * * @param pDip The module structure instance (do not use). * @param enmCmd Information request type. * @param pvArg Type specific argument. * @param ppvResult Where to store the requested info. * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) { LogFunc((DEVICE_NAME ":VBoxNetFltSolarisGetInfo pDip=%p enmCmd=%d pArg=%p instance=%d\n", pDip, enmCmd, getminor((dev_t)pvArg))); switch (enmCmd) { case DDI_INFO_DEVT2DEVINFO: { *ppvResult = g_pVBoxNetFltSolarisDip; return *ppvResult ? DDI_SUCCESS : DDI_FAILURE; } case DDI_INFO_DEVT2INSTANCE: { /* There can only be a single-instance of this driver and thus its instance number is 0. */ *ppvResult = (void *)0; return DDI_SUCCESS; } } return DDI_FAILURE; } /** * Stream module open entry point, initializes the queue and allows streams processing. * * @param pQueue Pointer to the read queue (cannot be NULL). * @param pDev Pointer to the dev_t associated with the driver at the end of the stream. * @param fOpenMode Open mode (always 0 for streams driver, thus ignored). * @param fStreamMode Stream open mode. * @param pCred Pointer to user credentials. * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisModOpen(queue_t *pQueue, dev_t *pDev, int fOpenMode, int fStreamMode, cred_t *pCred) { Assert(pQueue); LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModOpen pQueue=%p pDev=%p fOpenMode=%d fStreamMode=%d\n", pQueue, pDev, fOpenMode, fStreamMode)); /* * Already open? */ if (pQueue->q_ptr) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen invalid open.\n")); return ENOENT; } /* * Check that the request was initiated by our code. * * This ASSUMES that crdup() will return a copy with a unique address and * not do any kind of clever pooling. This check will when combined with * g_VBoxNetFltSolarisMtx prevent races and that the instance gets * associated with the wrong streams. */ if (pCred != g_pVBoxNetFltSolarisCred) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen invalid credentials.\n")); return EACCES; } /* * Check for the VirtualBox instance. */ PVBOXNETFLTINS pThis = g_VBoxNetFltSolarisInstance; if (!pThis) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed to get VirtualBox instance.\n")); return ENOENT; } /* * Check VirtualBox stream type. */ if ( g_VBoxNetFltSolarisStreamType != kPromiscStream && g_VBoxNetFltSolarisStreamType != kArpStream && g_VBoxNetFltSolarisStreamType != kIp6Stream && g_VBoxNetFltSolarisStreamType != kIp4Stream) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed due to undefined VirtualBox open mode. Type=%d\n", g_VBoxNetFltSolarisStreamType)); return ENOENT; } /* * Get minor number. For clone opens provide a new dev_t. */ minor_t DevMinor = 0; vboxnetflt_stream_t *pStream = NULL; vboxnetflt_stream_t **ppPrevStream = &g_VBoxNetFltSolarisStreams; if (fStreamMode == CLONEOPEN) { for (; (pStream = *ppPrevStream) != NULL; ppPrevStream = &pStream->pNext) { if (DevMinor < pStream->DevMinor) break; DevMinor++; } *pDev = makedevice(getmajor(*pDev), DevMinor); } else DevMinor = getminor(*pDev); if (g_VBoxNetFltSolarisStreamType == kPromiscStream) { vboxnetflt_promisc_stream_t *pPromiscStream = RTMemAlloc(sizeof(vboxnetflt_promisc_stream_t)); if (RT_UNLIKELY(!pPromiscStream)) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed to allocate promiscuous stream data.\n")); return ENOMEM; } pPromiscStream->fPromisc = false; pPromiscStream->fRawMode = false; pPromiscStream->ModeReqId = 0; pPromiscStream->pHead = NULL; pPromiscStream->pTail = NULL; pPromiscStream->cLoopback = 0; pPromiscStream->TimeoutId = 0; #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING pPromiscStream->pIp6Timer = NULL; #endif pStream = (vboxnetflt_stream_t *)pPromiscStream; } else { /* * Allocate & initialize per-stream data. Hook it into the (read and write) queue's module specific data. */ pStream = RTMemAlloc(sizeof(vboxnetflt_stream_t)); if (RT_UNLIKELY(!pStream)) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed to allocate stream data.\n")); return ENOMEM; } } pStream->DevMinor = DevMinor; pStream->pReadQueue = pQueue; /* * Pick up the current global VBOXNETFLTINS instance as * the one that we will associate this stream with. */ ASMAtomicUoWritePtr(&pStream->pThis, pThis); pStream->Type = g_VBoxNetFltSolarisStreamType; switch (pStream->Type) { case kIp4Stream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pIp4Stream, pStream); break; case kIp6Stream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pIp6Stream, pStream); break; case kArpStream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pArpStream, pStream); break; case kPromiscStream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pPromiscStream, pStream); break; default: /* Heh. */ { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen huh!? Invalid stream type %d\n", pStream->Type)); RTMemFree(pStream); return EINVAL; } } pQueue->q_ptr = pStream; WR(pQueue)->q_ptr = pStream; /* * Link it to the list of streams. */ pStream->pNext = *ppPrevStream; *ppPrevStream = pStream; /* * Increment IntNet reference count for this stream. */ vboxNetFltRetain(pThis, false /* fBusy */); qprocson(pQueue); /* * Don't hold the spinlocks across putnext calls as it could * (and does mostly) re-enter the put procedure on the same thread. */ if (pStream->Type == kPromiscStream) { vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; /* * Bind to SAP 0 (DL_ETHER). * Note: We don't support DL_TPR (token passing ring) SAP as that is unnecessary asynchronous * work to get DL_INFO_REQ acknowledgements and determine SAP based on the Mac Type etc. * Besides TPR doesn't really exist anymore practically as far as I know. */ int rc = vboxNetFltSolarisBindReq(pStream->pReadQueue, 0 /* SAP */); if (RT_LIKELY(RT_SUCCESS(rc))) { /* * Request the physical address (we cache the acknowledgement). */ rc = vboxNetFltSolarisPhysAddrReq(pStream->pReadQueue); if (RT_LIKELY(RT_SUCCESS(rc))) { /* * Ask for DLPI link notifications, don't bother check for errors here. */ vboxNetFltSolarisNotifyReq(pStream->pReadQueue); /* * Enable raw mode. */ rc = vboxNetFltSolarisSetRawMode(pPromiscStream); if (RT_FAILURE(rc)) LogRel((DEVICE_NAME ":vboxNetFltSolarisSetRawMode failed rc=%Rrc.\n", rc)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisSetRawMode failed rc=%Rrc.\n", rc)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisBindReq failed rc=%Rrc.\n", rc)); } NOREF(fOpenMode); Log((DEVICE_NAME ":VBoxNetFltSolarisModOpen returns 0, DevMinor=%d pQueue=%p\n", DevMinor, pStream->pReadQueue)); return 0; } /** * Stream module close entry point, undoes the work done on open and closes the stream. * * @param pQueue Pointer to the read queue (cannot be NULL). * @param fOpenMode Open mode (always 0 for streams driver, thus ignored). * @param pCred Pointer to user credentials. * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisModClose(queue_t *pQueue, int fOpenMode, cred_t *pCred) { Assert(pQueue); LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModClose pQueue=%p fOpenMode=%d\n", pQueue, fOpenMode)); vboxnetflt_stream_t *pStream = NULL; vboxnetflt_stream_t **ppPrevStream = NULL; /* * Get instance data. */ pStream = (vboxnetflt_stream_t *)pQueue->q_ptr; if (RT_UNLIKELY(!pStream)) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModClose failed to get stream.\n")); return ENXIO; } if (pStream->Type == kPromiscStream) { /* * If there are any timeout scheduled, we need to make sure they are cancelled. */ vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; timeout_id_t TimeoutId = ASMAtomicReadPtr(&pPromiscStream->TimeoutId); if (TimeoutId) { quntimeout(WR(pPromiscStream->Stream.pReadQueue), TimeoutId); ASMAtomicWritePtr(&pPromiscStream->TimeoutId, NULL); } flushq(pQueue, FLUSHALL); flushq(WR(pQueue), FLUSHALL); } qprocsoff(pQueue); if (pStream->Type == kPromiscStream) { vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; mutex_enter(&pStream->pThis->u.s.hMtx); /* * Free-up loopback buffers. */ PVBOXNETFLTPACKETID pCur = pPromiscStream->pHead; while (pCur) { PVBOXNETFLTPACKETID pNext = pCur->pNext; RTMemFree(pCur); pCur = pNext; } pPromiscStream->pHead = NULL; pPromiscStream->pTail = NULL; pPromiscStream->cLoopback = 0; #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING /* * Sheer paranoia. */ if (pPromiscStream->pIp6Timer != NULL) { RTTimerStop(pPromiscStream->pIp6Timer); RTTimerDestroy(pPromiscStream->pIp6Timer); ASMAtomicUoWriteNullPtr(&pPromiscStream->pIp6Timer); } #endif mutex_exit(&pStream->pThis->u.s.hMtx); } /* * Unlink it from the list of streams. */ for (ppPrevStream = &g_VBoxNetFltSolarisStreams; (pStream = *ppPrevStream) != NULL; ppPrevStream = &pStream->pNext) if (pStream == (vboxnetflt_stream_t *)pQueue->q_ptr) break; *ppPrevStream = pStream->pNext; /* * Delete the stream. */ switch (pStream->Type) { case kIp4Stream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pIp4Stream); break; case kIp6Stream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pIp6Stream); break; case kArpStream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pArpStream); break; case kPromiscStream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pPromiscStream); break; default: /* Heh. */ { AssertRelease(pStream->Type); break; } } /* * Decrement IntNet reference count for this stream. */ vboxNetFltRelease(pStream->pThis, false /* fBusy */); RTMemFree(pStream); pQueue->q_ptr = NULL; WR(pQueue)->q_ptr = NULL; NOREF(fOpenMode); NOREF(pCred); return 0; } /** * Read side put procedure for processing messages in the read queue. * All streams, bound and unbound share this read procedure. * * @param pQueue Pointer to the read queue. * @param pMsg Pointer to the message. * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisModReadPut(queue_t *pQueue, mblk_t *pMsg) { if (!pMsg) return 0; LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModReadPut pQueue=%p pMsg=%p\n", pQueue, pMsg)); bool fSendUpstream = true; vboxnetflt_stream_t *pStream = pQueue->q_ptr; PVBOXNETFLTINS pThis = NULL; /* * In the unlikely case where VirtualBox crashed and this filter * is somehow still in the host stream we must try not to panic the host. */ if ( pStream && pStream->Type == kPromiscStream) { fSendUpstream = false; pThis = ASMAtomicUoReadPtrT(&pStream->pThis, PVBOXNETFLTINS); if (RT_LIKELY(pThis)) { /* * Retain the instance if we're filtering regardless of we are active or not * The reason being even when we are inactive we reference the instance (e.g * the promiscuous OFF acknowledgement case). */ RTSpinlockAcquire(pThis->hSpinlock); const bool fActive = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE; vboxNetFltRetain(pThis, true /* fBusy */); RTSpinlockRelease(pThis->hSpinlock); vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; switch (DB_TYPE(pMsg)) { case M_DATA: { Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut M_DATA\n")); if ( fActive && pPromiscStream->fRawMode) { vboxNetFltSolarisRecv(pThis, pStream, pQueue, pMsg); } break; } case M_PROTO: case M_PCPROTO: { union DL_primitives *pPrim = (union DL_primitives *)pMsg->b_rptr; t_uscalar_t Prim = pPrim->dl_primitive; Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_PCPROTO %d\n", Prim)); switch (Prim) { case DL_NOTIFY_IND: { if (MBLKL(pMsg) < DL_NOTIFY_IND_SIZE) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: Invalid notification size; expected>=%d" " got=%d\n", DL_NOTIFY_IND_SIZE, MBLKL(pMsg))); break; } dl_notify_ind_t *pNotifyInd = (dl_notify_ind_t *)pMsg->b_rptr; switch (pNotifyInd->dl_notification) { case DL_NOTE_PHYS_ADDR: { if (pNotifyInd->dl_data != DL_CURR_PHYS_ADDR) break; size_t cOffset = pNotifyInd->dl_addr_offset; size_t cbAddr = pNotifyInd->dl_addr_length; if (!cOffset || !cbAddr) { LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_PHYS_ADDR." "Invalid offset/addr.\n")); fSendUpstream = false; break; } bcopy(pMsg->b_rptr + cOffset, &pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_PHYS_ADDR. New Mac=%.*Rhxs\n", sizeof(pThis->u.s.MacAddr), &pThis->u.s.MacAddr)); break; } case DL_NOTE_LINK_UP: { if (ASMAtomicXchgBool(&pThis->fDisconnectedFromHost, false)) Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_LINK_UP.\n")); break; } case DL_NOTE_LINK_DOWN: { if (!ASMAtomicXchgBool(&pThis->fDisconnectedFromHost, true)) Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_LINK_DOWN.\n")); break; } } break; } case DL_BIND_ACK: { /* * Swallow our bind request acknowledgement. */ Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_BIND_ACK. Bound to requested SAP!\n")); break; } case DL_PHYS_ADDR_ACK: { /* * Swallow our physical address request acknowledgement. */ vboxNetFltSolarisCachePhysAddr(pThis, pMsg); break; } case DL_OK_ACK: { /* * Swallow our fake promiscuous request acknowledgement. */ dl_ok_ack_t *pOkAck = (dl_ok_ack_t *)pMsg->b_rptr; if (pOkAck->dl_correct_primitive == DL_PROMISCON_REQ) { Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_PCPROTO: DL_OK_ACK: fPromisc is ON.\n")); pPromiscStream->fPromisc = true; } else if (pOkAck->dl_correct_primitive == DL_PROMISCOFF_REQ) { Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_PCPROTO: DL_OK_ACK: fPromisc is OFF.\n")); pPromiscStream->fPromisc = false; } break; } } break; } case M_IOCACK: { /* * Swallow our fake raw/fast path mode request acknowledgement. */ struct iocblk *pIOC = (struct iocblk *)pMsg->b_rptr; if (pIOC->ioc_id == pPromiscStream->ModeReqId) { pPromiscStream->fRawMode = true; Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: Mode acknowledgement. RawMode is %s\n", pPromiscStream->fRawMode ? "ON" : "OFF")); } break; } case M_IOCNAK: { /* * Swallow our fake raw/fast path mode request not acknowledged. */ struct iocblk *pIOC = (struct iocblk *)pMsg->b_rptr; if (pIOC->ioc_id == pPromiscStream->ModeReqId) { pPromiscStream->fRawMode = false; LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: WARNING! Mode not acknowledged. RawMode is %s\n", pPromiscStream->fRawMode ? "ON" : "OFF")); } break; } case M_FLUSH: { /* * We must support flushing queues. */ Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_FLUSH\n")); if (*pMsg->b_rptr & FLUSHR) flushq(pQueue, FLUSHALL); break; } } vboxNetFltRelease(pThis, true /* fBusy */); } else LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: Could not find VirtualBox instance!!\n")); } if (fSendUpstream) { /* * Don't queue up things here, can cause bad things to happen when the system * is under heavy loads and we need to jam across high priority messages which * if it's not done properly will end up in an infinite loop. */ putnext(pQueue, pMsg); } else { /* * We need to free up the message if we don't pass it through. */ freemsg(pMsg); } return 0; } /** * Write side put procedure for processing messages in the write queue. * All streams, bound and unbound share this write procedure. * * @param pQueue Pointer to the write queue. * @param pMsg Pointer to the message. * * @returns corresponding solaris error code. */ static int VBoxNetFltSolarisModWritePut(queue_t *pQueue, mblk_t *pMsg) { LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModWritePut pQueue=%p pMsg=%p\n", pQueue, pMsg)); putnext(pQueue, pMsg); return 0; } /** * Put the stream in raw mode. * * @returns VBox status code. * @param pPromiscStream Pointer to the read queue. */ static int vboxNetFltSolarisSetRawMode(vboxnetflt_promisc_stream_t *pPromiscStream) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisSetRawMode pPromiscStream=%p\n", pPromiscStream)); mblk_t *pRawMsg = NULL; pRawMsg = mkiocb(DLIOCRAW); if (RT_UNLIKELY(!pRawMsg)) return VERR_NO_MEMORY; queue_t *pQueue = pPromiscStream->Stream.pReadQueue; if (!pQueue) return VERR_INVALID_POINTER; struct iocblk *pIOC = (struct iocblk *)pRawMsg->b_rptr; pPromiscStream->ModeReqId = pIOC->ioc_id; pIOC->ioc_count = 0; qreply(pQueue, pRawMsg); return VINF_SUCCESS; } #if 0 /** * Put the stream back in fast path mode. * * @returns VBox status code. * @param pQueue Pointer to the read queue. */ static int vboxNetFltSolarisSetFastMode(queue_t *pQueue) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisSetFastMode pQueue=%p\n", pQueue)); mblk_t *pFastMsg = mkiocb(DL_IOC_HDR_INFO); if (RT_UNLIKELY(!pFastMsg)) return VERR_NO_MEMORY; vboxnetflt_stream_t *pStream = pQueue->q_ptr; struct iocblk *pIOC = (struct iocblk *)pFastMsg->b_rptr; pStream->ModeReqId = pIOC->ioc_id; size_t cbReq = sizeof(dl_unitdata_req_t) + sizeof(vboxnetflt_dladdr_t); mblk_t *pDataReqMsg = allocb(cbReq, BPRI_MED); if (RT_UNLIKELY(!pDataReqMsg)) return VERR_NO_MEMORY; DB_TYPE(pDataReqMsg) = M_PROTO; dl_unitdata_req_t *pDataReq = (dl_unitdata_req_t *)pDataReqMsg->b_rptr; pDataReq->dl_primitive = DL_UNITDATA_REQ; pDataReq->dl_dest_addr_length = sizeof(vboxnetflt_dladdr_t); pDataReq->dl_dest_addr_offset = sizeof(dl_unitdata_req_t); pDataReq->dl_priority.dl_min = 0; pDataReq->dl_priority.dl_max = 0; bzero(pDataReqMsg->b_rptr + sizeof(dl_unitdata_req_t), sizeof(vboxnetflt_dladdr_t)); pDataReqMsg->b_wptr = pDataReqMsg->b_rptr + cbReq; /* * Link the data format request message into the header ioctl message. */ pFastMsg->b_cont = pDataReqMsg; pIOC->ioc_count = msgdsize(pDataReqMsg); qreply(pQueue, pFastMsg); return VINF_SUCCESS; } #endif /** * Callback function for qwriter to send promiscuous request messages * downstream. * * @param pQueue Pointer to the write queue. * @param fPromisc Whether to send promiscuous ON or OFF requests. * * @returns VBox status code. */ static int vboxNetFltSolarisPromiscReq(queue_t *pQueue, bool fPromisc) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisPromiscReq pQueue=%p fPromisc=%d\n", pQueue, fPromisc)); t_uscalar_t Cmd; size_t cbReq = 0; if (fPromisc) { Cmd = DL_PROMISCON_REQ; cbReq = DL_PROMISCON_REQ_SIZE; } else { Cmd = DL_PROMISCOFF_REQ; cbReq = DL_PROMISCOFF_REQ_SIZE; } mblk_t *pPromiscPhysMsg = mexchange(NULL, NULL, cbReq, M_PROTO, Cmd); if (RT_UNLIKELY(!pPromiscPhysMsg)) return VERR_NO_MEMORY; mblk_t *pPromiscSapMsg = mexchange(NULL, NULL, cbReq, M_PROTO, Cmd); if (RT_UNLIKELY(!pPromiscSapMsg)) { freemsg(pPromiscPhysMsg); return VERR_NO_MEMORY; } if (fPromisc) { ((dl_promiscon_req_t *)pPromiscPhysMsg->b_rptr)->dl_level = DL_PROMISC_PHYS; ((dl_promiscon_req_t *)pPromiscSapMsg->b_rptr)->dl_level = DL_PROMISC_SAP; } else { ((dl_promiscoff_req_t *)pPromiscPhysMsg->b_rptr)->dl_level = DL_PROMISC_PHYS; ((dl_promiscoff_req_t *)pPromiscSapMsg->b_rptr)->dl_level = DL_PROMISC_SAP; } putnext(pQueue, pPromiscPhysMsg); putnext(pQueue, pPromiscSapMsg); return VINF_SUCCESS; } /** * Callback wrapper for qwriter() to safely send promiscuous requests. This is * called at the outer perimeter with exclusive lock held. * * @param pQueue Pointer to the write queue. * @param pMsg A one byte message indicates a Promisc ON, otherwise * a promiscuous OFF request. See * vboxNetFltSolarisPromiscReqWrap(). */ static void vboxNetFltSolarisPromiscReqWrapExcl(queue_t *pQueue, mblk_t *pMsg) { /* * Paranoia. */ AssertReturnVoid(pQueue); if (RT_UNLIKELY(!pMsg)) LogRel((DEVICE_NAME ":VBoxNetFltSolarisPromiscReqWrapExcl pQueue=%p missing message!\n", pQueue)); bool fPromisc = (MBLKL(pMsg) == 1); freemsg(pMsg); pMsg = NULL; int rc = vboxNetFltSolarisPromiscReq(pQueue, fPromisc); if (RT_FAILURE(rc)) LogRel((DEVICE_NAME ":VBoxNetFltSolarisPromiscReqWrapExcl vboxNetFltSolarisPromiscReq failed. rc=%d\n", rc)); } /** * Callback wrapper for qtimeout() to safely send promiscuous requests. This is * called at the inner perimeter with shared lock. * * @param pvData Pointer to vboxnetflt_promisc_params_t. See * vboxNetFltPortOsSetActive(). */ static void vboxNetFltSolarisPromiscReqWrap(void *pvData) { vboxnetflt_promisc_params_t *pParams = pvData; if (RT_LIKELY(pParams)) { PVBOXNETFLTINS pThis = pParams->pThis; vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); if ( pPromiscStream && pPromiscStream->Stream.pReadQueue) { /* * Use size of message to indicate to qwriter callback whether it must send * promiscuous On or Off messages. This is ugly but easier and more efficient than * scheduling two separate qwriter callbacks with prepared messages to putnext. */ size_t cbMsg = pParams->fPromiscOn ? 1 : 2; mblk_t *pMsg = allocb(cbMsg, BPRI_HI); if (RT_UNLIKELY(!pMsg)) { LogRel((DEVICE_NAME ":Failed to alloc message of %u bytes\n", cbMsg)); return; } /* * Move the data pointer so we can use MBLKL, as MBLKSIZE gets the db_lim which is * always aligned. */ pMsg->b_wptr += cbMsg; /* * Upgrade inner perimeter lock to exclusive outer perimeter lock and * then call putnext while we are at the outer perimeter. */ qwriter(WR(pPromiscStream->Stream.pReadQueue), pMsg, vboxNetFltSolarisPromiscReqWrapExcl, PERIM_OUTER); ASMAtomicWritePtr(&pPromiscStream->TimeoutId, NULL); } RTMemFree(pParams); } } /** * Send a fake physical address request downstream. * * @returns VBox status code. * @param pQueue Pointer to the read queue. */ static int vboxNetFltSolarisPhysAddrReq(queue_t *pQueue) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisPhysAddrReq pQueue=%p\n", pQueue)); t_uscalar_t Cmd = DL_PHYS_ADDR_REQ; size_t cbReq = DL_PHYS_ADDR_REQ_SIZE; mblk_t *pPhysAddrMsg = mexchange(NULL, NULL, cbReq, M_PROTO, Cmd); if (RT_UNLIKELY(!pPhysAddrMsg)) return VERR_NO_MEMORY; dl_phys_addr_req_t *pPhysAddrReq = (dl_phys_addr_req_t *)pPhysAddrMsg->b_rptr; pPhysAddrReq->dl_addr_type = DL_CURR_PHYS_ADDR; qreply(pQueue, pPhysAddrMsg); return VINF_SUCCESS; } /** * Cache the MAC address into the VirtualBox instance given a physical * address acknowledgement message. * * @param pThis The instance. * @param pMsg Pointer to the physical address acknowledgement message. */ static void vboxNetFltSolarisCachePhysAddr(PVBOXNETFLTINS pThis, mblk_t *pMsg) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisCachePhysAddr pThis=%p pMsg=%p\n", pThis, pMsg)); AssertCompile(sizeof(RTMAC) == ETHERADDRL); dl_phys_addr_ack_t *pPhysAddrAck = (dl_phys_addr_ack_t *)pMsg->b_rptr; if (pPhysAddrAck->dl_addr_length == sizeof(pThis->u.s.MacAddr)) { bcopy(pMsg->b_rptr + pPhysAddrAck->dl_addr_offset, &pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); Log((DEVICE_NAME ":vboxNetFltSolarisCachePhysAddr: DL_PHYS_ADDR_ACK: Mac=%.*Rhxs\n", sizeof(pThis->u.s.MacAddr), &pThis->u.s.MacAddr)); if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) { Assert(pThis->pSwitchPort); if (pThis->pSwitchPort) pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); vboxNetFltRelease(pThis, true /*fBusy*/); } } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisCachePhysAddr: Invalid address size. expected=%d got=%d\n", ETHERADDRL, pPhysAddrAck->dl_addr_length)); } } /** * Prepare DLPI bind request to a SAP. * * @returns VBox status code. * @param pQueue Pointer to the read queue. * @param SAP The SAP to bind the stream to. */ static int vboxNetFltSolarisBindReq(queue_t *pQueue, int SAP) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisBindReq SAP=%d\n", SAP)); mblk_t *pBindMsg = mexchange(NULL, NULL, DL_BIND_REQ_SIZE, M_PROTO, DL_BIND_REQ); if (RT_UNLIKELY(!pBindMsg)) return VERR_NO_MEMORY; dl_bind_req_t *pBindReq = (dl_bind_req_t *)pBindMsg->b_rptr; pBindReq->dl_sap = SAP; pBindReq->dl_max_conind = 0; pBindReq->dl_conn_mgmt = 0; pBindReq->dl_xidtest_flg = 0; pBindReq->dl_service_mode = DL_CLDLS; qreply(pQueue, pBindMsg); return VINF_SUCCESS; } /** * Prepare DLPI notifications request. * * @returns VBox status code. * @param pQueue Pointer to the read queue. */ static int vboxNetFltSolarisNotifyReq(queue_t *pQueue) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisNotifyReq\n")); mblk_t *pNotifyMsg = mexchange(NULL, NULL, DL_NOTIFY_REQ_SIZE, M_PROTO, DL_NOTIFY_REQ); if (RT_UNLIKELY(!pNotifyMsg)) return VERR_NO_MEMORY; dl_notify_req_t *pNotifyReq = (dl_notify_req_t *)pNotifyMsg->b_rptr; pNotifyReq->dl_notifications = DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN | DL_NOTE_PHYS_ADDR; qreply(pQueue, pNotifyMsg); return VINF_SUCCESS; } /** * Opens the required device and returns the vnode_t associated with it. * We require this for the funny attach/detach routine. * * @returns VBox status code. * @param pszDev The device path. * @param ppVNode Where to store the vnode_t pointer associated with the opened device. * @param ppVNodeHeld Where to store the vnode_t required during closing of the device. * @param ppUser Open handle required while closing the device. */ static int vboxNetFltSolarisOpenDev(char *pszDev, vnode_t **ppVNode, vnode_t **ppVNodeHeld, TIUSER **ppUser) { int rc; vnode_t *pVNodeHeld = NULL; rc = lookupname(pszDev, UIO_SYSSPACE, FOLLOW, NULLVPP, &pVNodeHeld); if ( !rc && pVNodeHeld) { TIUSER *pUser; rc = t_kopen((file_t *)NULL, pVNodeHeld->v_rdev, FREAD | FWRITE, &pUser, kcred); if (!rc) { if ( pUser && pUser->fp && VNODE_FOR_FILE_T(pUser->fp)) { *ppVNode = VNODE_FOR_FILE_T(pUser->fp); *ppVNodeHeld = pVNodeHeld; *ppUser = pUser; return VINF_SUCCESS; } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenDev failed. pUser=%p fp=%p f_vnode=%p\n", pUser, pUser ? pUser->fp : NULL, pUser && pUser->fp ? VNODE_FOR_FILE_T(pUser->fp) : NULL)); } if (pUser) t_kclose(pUser, 0); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenDev t_kopen failed. rc=%d\n", rc)); VN_RELE(pVNodeHeld); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenDev lookupname failed. rc=%d pVNodeHeld=%p\n", rc, pVNodeHeld)); return VERR_PATH_NOT_FOUND; } /** * Close the device opened using vboxNetFltSolarisOpenDev. * * @param pVNodeHeld Pointer to the held vnode of the device. * @param pUser Pointer to the file handle. */ static void vboxNetFltSolarisCloseDev(vnode_t *pVNodeHeld, TIUSER *pUser) { t_kclose(pUser, 0); VN_RELE(pVNodeHeld); } /** * Set the DLPI style-2 PPA via an attach request, Synchronous. * Waits for request acknowledgement and verifies the result. * * @returns VBox status code. * @param hDevice Layered device handle. * @param PPA Physical Point of Attachment (PPA) number. */ static int vboxNetFltSolarisAttachReq(ldi_handle_t hDevice, int PPA) { int rc; mblk_t *pAttachMsg = mexchange(NULL, NULL, DL_ATTACH_REQ_SIZE, M_PROTO, DL_ATTACH_REQ); if (RT_UNLIKELY(!pAttachMsg)) return VERR_NO_MEMORY; dl_attach_req_t *pAttachReq = (dl_attach_req_t *)pAttachMsg->b_rptr; pAttachReq->dl_ppa = PPA; rc = ldi_putmsg(hDevice, pAttachMsg); if (!rc) { rc = ldi_getmsg(hDevice, &pAttachMsg, NULL); if (!rc) { /* * Verify if the attach succeeded. */ size_t cbMsg = MBLKL(pAttachMsg); if (cbMsg >= sizeof(t_uscalar_t)) { union DL_primitives *pPrim = (union DL_primitives *)pAttachMsg->b_rptr; t_uscalar_t AckPrim = pPrim->dl_primitive; if ( AckPrim == DL_OK_ACK /* Success! */ && cbMsg == DL_OK_ACK_SIZE) { rc = VINF_SUCCESS; } else if ( AckPrim == DL_ERROR_ACK /* Error Ack. */ && cbMsg == DL_ERROR_ACK_SIZE) { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg succeeded, but unsupported op.\n")); rc = VERR_NOT_SUPPORTED; } else /* Garbled reply */ { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg succeeded, but invalid op." " expected %d recvd %d\n", DL_OK_ACK, AckPrim)); rc = VERR_INVALID_FUNCTION; } } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg succeeded, but invalid size %d expected %d\n", cbMsg, DL_OK_ACK_SIZE)); rc = VERR_INVALID_FUNCTION; } } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg failed. rc=%d\n", rc)); rc = VERR_INVALID_FUNCTION; } } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_putmsg failed. rc=%d\n", rc)); rc = VERR_UNRESOLVED_ERROR; } freemsg(pAttachMsg); return rc; } /** * Get the logical interface flags from the stream. * * @returns VBox status code. * @param hDevice Layered device handle. * @param pInterface Pointer to the interface. */ static int vboxNetFltSolarisGetIfFlags(ldi_handle_t hDevice, struct lifreq *pInterface) { struct strioctl IOCReq; int rc; int ret; IOCReq.ic_cmd = SIOCGLIFFLAGS; IOCReq.ic_timout = 40; IOCReq.ic_len = sizeof(struct lifreq); IOCReq.ic_dp = (caddr_t)pInterface; rc = ldi_ioctl(hDevice, I_STR, (intptr_t)&IOCReq, FKIOCTL, kcred, &ret); if (!rc) return VINF_SUCCESS; return RTErrConvertFromErrno(rc); } /** * Sets the multiplexor ID from the interface. * * @returns VBox status code. * @param pVNode Pointer to the device vnode. * @param pInterface Pointer to the interface. */ static int vboxNetFltSolarisSetMuxId(vnode_t *pVNode, struct lifreq *pInterface) { struct strioctl IOCReq; int rc; int ret; IOCReq.ic_cmd = SIOCSLIFMUXID; IOCReq.ic_timout = 40; IOCReq.ic_len = sizeof(struct lifreq); IOCReq.ic_dp = (caddr_t)pInterface; rc = strioctl(pVNode, I_STR, (intptr_t)&IOCReq, 0, K_TO_K, kcred, &ret); if (!rc) return VINF_SUCCESS; return RTErrConvertFromErrno(rc); } /** * Get the multiplexor file descriptor of the lower stream. * * @returns VBox status code. * @param pVNode Pointer to the device vnode. * @param MuxId The multiplexor ID. * @param pFd Where to store the lower stream file descriptor. */ static int vboxNetFltSolarisMuxIdToFd(vnode_t *pVNode, int MuxId, int *pFd) { int ret; *pFd = -1; /* silence compiler warnings from -Wmaybe-uninitialized */ int rc = strioctl(pVNode, _I_MUXID2FD, (intptr_t)MuxId, 0, K_TO_K, kcred, &ret); if (!rc) { *pFd = ret; return VINF_SUCCESS; } return RTErrConvertFromErrno(rc); } /** * Relinks the lower and the upper IPv4 stream. * * @returns VBox status code. * @param pVNode Pointer to the device vnode. * @param pInterface Pointer to the interface. * @param IpMuxFd The IP multiplexor ID. * @param ArpMuxFd The ARP multiplexor ID. */ static int vboxNetFltSolarisRelinkIp4(vnode_t *pVNode, struct lifreq *pInterface, int IpMuxFd, int ArpMuxFd) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisRelinkIp4: pVNode=%p pInterface=%p IpMuxFd=%d ArpMuxFd=%d\n", pVNode, pInterface, IpMuxFd, ArpMuxFd)); int NewIpMuxId; int NewArpMuxId; int rc = strioctl(pVNode, I_PLINK, (intptr_t)IpMuxFd, 0, K_TO_K, kcred, &NewIpMuxId); int rc2 = strioctl(pVNode, I_PLINK, (intptr_t)ArpMuxFd, 0, K_TO_K, kcred, &NewArpMuxId); if ( !rc && !rc2) { pInterface->lifr_ip_muxid = NewIpMuxId; pInterface->lifr_arp_muxid = NewArpMuxId; rc = vboxNetFltSolarisSetMuxId(pVNode, pInterface); if (RT_SUCCESS(rc)) return VINF_SUCCESS; LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp4: failed to set new Mux Id.\n")); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp4: failed to link.\n")); return VERR_GENERAL_FAILURE; } /** * Relinks the lower and the upper IPv6 stream. * * @returns VBox status code. * @param pVNode Pointer to the device vnode. * @param pInterface Pointer to the interface. * @param Ip6MuxFd The IPv6 multiplexor ID. */ static int vboxNetFltSolarisRelinkIp6(vnode_t *pVNode, struct lifreq *pInterface, int Ip6MuxFd) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisRelinkIp6: pVNode=%p pInterface=%p Ip6MuxFd=%d\n", pVNode, pInterface, Ip6MuxFd)); int NewIp6MuxId; int rc = strioctl(pVNode, I_PLINK, (intptr_t)Ip6MuxFd, 0, K_TO_K, kcred, &NewIp6MuxId); if (!rc) { pInterface->lifr_ip_muxid = NewIp6MuxId; rc = vboxNetFltSolarisSetMuxId(pVNode, pInterface); if (RT_SUCCESS(rc)) return VINF_SUCCESS; LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp6: failed to set new Mux Id.\n")); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp6: failed to link.\n")); return VERR_GENERAL_FAILURE; } /** * Dynamically find the position on the host stack where to attach/detach ourselves. * * @returns VBox status code. * @param fAttach Is this an attach or detach. * @param pVNode Pointer to the lower stream vnode. * @param pModPos Where to store the module position. */ static int vboxNetFltSolarisDetermineModPos(bool fAttach, vnode_t *pVNode, int *pModPos) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: fAttach=%d pVNode=%p pModPos=%p\n", fAttach, pVNode, pModPos)); int cMod; int rc = strioctl(pVNode, I_LIST, (intptr_t)NULL, 0, K_TO_K, kcred, &cMod); if (!rc) { if (cMod < 1) { LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: too few modules on host interface. cMod=%d\n")); return VERR_OUT_OF_RANGE; } /* * While attaching we make sure we are at the bottom most of the stack, excepting * the host driver. */ Log((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: cMod=%d\n", cMod)); if (fAttach) { *pModPos = cMod - 1; return VINF_SUCCESS; } /* * Detaching is a bit more complicated; since user could have altered the stack positions * we take the safe approach by finding our position. */ struct str_list StrList; StrList.sl_nmods = cMod; StrList.sl_modlist = RTMemAllocZ(cMod * sizeof(struct str_list)); if (RT_UNLIKELY(!StrList.sl_modlist)) { Log((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to alloc memory for StrList.\n")); return VERR_NO_MEMORY; } /* * Get the list of all modules on the stack. */ int ret; rc = strioctl(pVNode, I_LIST, (intptr_t)&StrList, 0, K_TO_K, kcred, &ret); if (!rc) { /* * Find our filter. */ for (int i = 0; i < StrList.sl_nmods; i++) { if (!strcmp(DEVICE_NAME, StrList.sl_modlist[i].l_name)) { Log((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: Success! Found %s at %d.\n", DEVICE_NAME, i)); *pModPos = i; RTMemFree(StrList.sl_modlist); return VINF_SUCCESS; } } LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to find %s in the host stack.\n", DEVICE_NAME)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to get module information. rc=%d\n")); RTMemFree(StrList.sl_modlist); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to get list of modules on host interface. rc=%d\n", rc)); return VERR_GENERAL_FAILURE; } /** * Opens up the DLPI style 2 link that requires explicit PPA attach * phase. * * @returns VBox status code. * @param pThis The instance. * @param pDevId Where to store the opened LDI device id. */ static int vboxNetFltSolarisOpenStyle2(PVBOXNETFLTINS pThis, ldi_ident_t *pDevId) { /* * Strip out PPA from the device name, eg: "ce3". */ char *pszDev = RTStrDup(pThis->szName); if (!pszDev) return VERR_NO_MEMORY; char *pszEnd = strchr(pszDev, '\0'); while (--pszEnd > pszDev) if (!RT_C_IS_DIGIT(*pszEnd)) break; pszEnd++; int rc = VERR_GENERAL_FAILURE; long PPA = -1; if ( pszEnd && ddi_strtol(pszEnd, NULL, 10, &PPA) == 0) { *pszEnd = '\0'; char szDev[128]; RTStrPrintf(szDev, sizeof(szDev), "/dev/%s", pszDev); /* * Try open the device as DPLI style 2. */ rc = ldi_open_by_name(szDev, FREAD | FWRITE, kcred, &pThis->u.s.hIface, *pDevId); if (!rc) { /* * Attach the PPA explictly. */ rc = vboxNetFltSolarisAttachReq(pThis->u.s.hIface, (int)PPA); if (RT_SUCCESS(rc)) { RTStrFree(pszDev); return rc; } ldi_close(pThis->u.s.hIface, FREAD | FWRITE, kcred); pThis->u.s.hIface = NULL; LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStyle2 dl_attach failed. rc=%d szDev=%s PPA=%d rc=%d\n", rc, szDev, PPA)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStyle2 Failed to open. rc=%d szDev=%s PPA=%d\n", rc, szDev, PPA)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStyle2 Failed to construct PPA. pszDev=%s pszEnd=%s.\n", pszDev, pszEnd)); RTStrFree(pszDev); return VERR_INTNET_FLT_IF_FAILED; } /** * Opens up dedicated stream on top of the interface. * As a side-effect, the stream gets opened during * the I_PUSH phase. * * @param pThis The instance. */ static int vboxNetFltSolarisOpenStream(PVBOXNETFLTINS pThis) { ldi_ident_t DevId; DevId = ldi_ident_from_anon(); int ret; /* * Figure out if this is a VLAN interface or not based on the interface name. * Only works for the VLAN PPA-hack based names. See @bugref{4854} for details. */ char *pszEnd = strchr(pThis->szName, '\0'); while (--pszEnd > pThis->szName) if (!RT_C_IS_DIGIT(*pszEnd)) break; pszEnd++; uint32_t PPA = RTStrToUInt32(pszEnd); if (PPA > 1000) { pThis->u.s.fVLAN = true; LogRel((DEVICE_NAME ": %s detected as VLAN interface with VID=%u.\n", pThis->szName, PPA / 1000U)); } /* * Try style-1 open first. */ char szDev[128]; RTStrPrintf(szDev, sizeof(szDev), "/dev/net/%s", pThis->szName); int rc = ldi_open_by_name(szDev, FREAD | FWRITE, kcred, &pThis->u.s.hIface, DevId); if ( rc && rc == ENODEV) /* ENODEV is returned when resolvepath fails, not ENOENT */ { /* * Fallback to non-ClearView style-1 open. */ RTStrPrintf(szDev, sizeof(szDev), "/dev/%s", pThis->szName); rc = ldi_open_by_name(szDev, FREAD | FWRITE, kcred, &pThis->u.s.hIface, DevId); } if (rc) { /* * Try DLPI style 2. */ rc = vboxNetFltSolarisOpenStyle2(pThis, &DevId); if (RT_FAILURE(rc)) LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream vboxNetFltSolarisOpenStyle2 failed. rc=%d\n", rc)); else rc = 0; } ldi_ident_release(DevId); if (rc) { LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream Failed to open '%s' rc=%d pszName='%s'\n", szDev, rc, pThis->szName)); return VERR_INTNET_FLT_IF_FAILED; } rc = ldi_ioctl(pThis->u.s.hIface, I_FIND, (intptr_t)DEVICE_NAME, FKIOCTL, kcred, &ret); if (!rc) { if (!ret) { if (RT_LIKELY(g_pVBoxNetFltSolarisCred)) /* Paranoia */ { rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); AssertRCReturn(rc, rc); g_VBoxNetFltSolarisInstance = pThis; g_VBoxNetFltSolarisStreamType = kPromiscStream; rc = ldi_ioctl(pThis->u.s.hIface, I_PUSH, (intptr_t)DEVICE_NAME, FKIOCTL, g_pVBoxNetFltSolarisCred, &ret); g_VBoxNetFltSolarisInstance = NULL; g_VBoxNetFltSolarisStreamType = kUndefined; RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream huh!? Missing credentials.\n")); rc = VERR_INVALID_POINTER; } if (!rc) return VINF_SUCCESS; LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream Failed to push filter onto host interface '%s'\n", pThis->szName)); } else return VINF_SUCCESS; } else LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream Failed to search for filter in interface '%s'.\n", pThis->szName)); ldi_close(pThis->u.s.hIface, FREAD | FWRITE, kcred); pThis->u.s.hIface = NULL; return VERR_INTNET_FLT_IF_FAILED; } /** * Closes the interface, thereby closing the dedicated stream. * * @param pThis The instance. */ static void vboxNetFltSolarisCloseStream(PVBOXNETFLTINS pThis) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisCloseStream pThis=%p\n")); if (pThis->u.s.hIface) { ldi_close(pThis->u.s.hIface, FREAD | FWRITE, kcred); pThis->u.s.hIface = NULL; } } /** * Dynamically attach under IPv4 and ARP streams on the host stack. * * @returns VBox status code. * @param pThis The instance. * @param fAttach Is this an attach or detach. */ static int vboxNetFltSolarisAttachIp4(PVBOXNETFLTINS pThis, bool fAttach) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisAttachIp4 pThis=%p fAttach=%d\n", pThis, fAttach)); /* * Statutory Warning: Hackish code ahead. */ char *pszModName = DEVICE_NAME; struct lifreq Ip4Interface; bzero(&Ip4Interface, sizeof(Ip4Interface)); Ip4Interface.lifr_addr.ss_family = AF_INET; strncpy(Ip4Interface.lifr_name, pThis->szName, sizeof(Ip4Interface.lifr_name)); struct strmodconf StrMod; StrMod.mod_name = pszModName; StrMod.pos = -1; /* this is filled in later. */ struct strmodconf ArpStrMod; bcopy(&StrMod, &ArpStrMod, sizeof(StrMod)); int rc; int rc2; int ret; ldi_ident_t DeviceIdent = ldi_ident_from_anon(); ldi_handle_t Ip4DevHandle; ldi_handle_t ArpDevHandle; /* * Open the IP and ARP streams as layered devices. */ rc = ldi_open_by_name(IP_DEV_NAME, FREAD | FWRITE, kcred, &Ip4DevHandle, DeviceIdent); if (rc) { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to open the IP stream on '%s'.\n", pThis->szName)); ldi_ident_release(DeviceIdent); return VERR_INTNET_FLT_IF_FAILED; } rc = ldi_open_by_name("/dev/arp", FREAD | FWRITE, kcred, &ArpDevHandle, DeviceIdent); if (rc) { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to open the ARP stream on '%s'.\n", pThis->szName)); ldi_ident_release(DeviceIdent); ldi_close(Ip4DevHandle, FREAD | FWRITE, kcred); return VERR_INTNET_FLT_IF_FAILED; } ldi_ident_release(DeviceIdent); /* * Obtain the interface flags from IPv4. */ rc = vboxNetFltSolarisGetIfFlags(Ip4DevHandle, &Ip4Interface); if (RT_SUCCESS(rc)) { /* * Open the UDP stream. We sort of cheat here and obtain the vnode so that we can perform * things that are not possible from the layered interface. */ vnode_t *pUdp4VNode = NULL; vnode_t *pUdp4VNodeHeld = NULL; TIUSER *pUdp4User = NULL; rc = vboxNetFltSolarisOpenDev(UDP_DEV_NAME, &pUdp4VNode, &pUdp4VNodeHeld, &pUdp4User); if (RT_SUCCESS(rc)) { /* * Get the multiplexor IDs. */ rc = ldi_ioctl(Ip4DevHandle, SIOCGLIFMUXID, (intptr_t)&Ip4Interface, FKIOCTL, kcred, &ret); if (!rc) { /* * Get the multiplex file descriptor to the lower streams. Generally this is lost * once a module is I_PLINK, we need to reobtain it for inserting/removing ourselves from the stack. */ int Ip4MuxFd; int ArpMuxFd; rc = vboxNetFltSolarisMuxIdToFd(pUdp4VNode, Ip4Interface.lifr_ip_muxid, &Ip4MuxFd); rc2 = vboxNetFltSolarisMuxIdToFd(pUdp4VNode, Ip4Interface.lifr_arp_muxid, &ArpMuxFd); if ( RT_SUCCESS(rc) && RT_SUCCESS(rc2)) { /* * We need to I_PUNLINK on these multiplexor IDs before we can start * operating on the lower stream as insertions are direct operations on the lower stream. */ rc = strioctl(pUdp4VNode, I_PUNLINK, (intptr_t)Ip4Interface.lifr_ip_muxid, 0, K_TO_K, kcred, &ret); rc2 = strioctl(pUdp4VNode, I_PUNLINK, (intptr_t)Ip4Interface.lifr_arp_muxid, 0, K_TO_K, kcred, &ret); if ( !rc && !rc2) { /* * Obtain the vnode from the useless userland file descriptor. */ file_t *pIpFile = getf(Ip4MuxFd); file_t *pArpFile = getf(ArpMuxFd); if ( pIpFile && pArpFile && VNODE_FOR_FILE_T(pArpFile) && VNODE_FOR_FILE_T(pIpFile)) { vnode_t *pIp4VNode = VNODE_FOR_FILE_T(pIpFile); vnode_t *pArpVNode = VNODE_FOR_FILE_T(pArpFile); /* * Find the position on the host stack for attaching/detaching ourselves. */ rc = vboxNetFltSolarisDetermineModPos(fAttach, pIp4VNode, &StrMod.pos); rc2 = vboxNetFltSolarisDetermineModPos(fAttach, pArpVNode, &ArpStrMod.pos); if ( RT_SUCCESS(rc) && RT_SUCCESS(rc2)) { /* * Inject/Eject from the host IP stack. */ /* * Set global data which will be grabbed by ModOpen. * There is a known (though very unlikely) race here because * of the inability to pass user data while inserting. */ rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); AssertRCReturn(rc, rc); if (fAttach) { g_VBoxNetFltSolarisInstance = pThis; g_VBoxNetFltSolarisStreamType = kIp4Stream; } rc = strioctl(pIp4VNode, fAttach ? _I_INSERT : _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, g_pVBoxNetFltSolarisCred, &ret); if (fAttach) { g_VBoxNetFltSolarisInstance = NULL; g_VBoxNetFltSolarisStreamType = kUndefined; } RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); if (!rc) { /* * Inject/Eject from the host ARP stack. */ rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); AssertRCReturn(rc, rc); if (fAttach) { g_VBoxNetFltSolarisInstance = pThis; g_VBoxNetFltSolarisStreamType = kArpStream; } rc = strioctl(pArpVNode, fAttach ? _I_INSERT : _I_REMOVE, (intptr_t)&ArpStrMod, 0, K_TO_K, g_pVBoxNetFltSolarisCred, &ret); if (fAttach) { g_VBoxNetFltSolarisInstance = NULL; g_VBoxNetFltSolarisStreamType = kUndefined; } RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); if (!rc) { /* * Our job's not yet over; we need to relink the upper and lower streams * otherwise we've pretty much screwed up the host interface. */ rc = vboxNetFltSolarisRelinkIp4(pUdp4VNode, &Ip4Interface, Ip4MuxFd, ArpMuxFd); if (RT_SUCCESS(rc)) { /* * Close the devices ONLY during the return from function case; otherwise * we end up close twice which is an instant kernel panic. */ vboxNetFltSolarisCloseDev(pUdp4VNodeHeld, pUdp4User); ldi_close(ArpDevHandle, FREAD | FWRITE, kcred); ldi_close(Ip4DevHandle, FREAD | FWRITE, kcred); releasef(Ip4MuxFd); releasef(ArpMuxFd); Log((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: Success! %s %s@(IPv4:%d Arp:%d) " "%s interface %s\n", fAttach ? "Injected" : "Ejected", StrMod.mod_name, StrMod.pos, ArpStrMod.pos, fAttach ? "to" : "from", pThis->szName)); return VINF_SUCCESS; } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: Relinking failed. Mode=%s rc=%d.\n", fAttach ? "inject" : "eject", rc)); } /* * Try failing gracefully during attach. */ if (fAttach) strioctl(pArpVNode, _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, kcred, &ret); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to %s the ARP stack. rc=%d\n", fAttach ? "inject into" : "eject from", rc)); } if (fAttach) strioctl(pIp4VNode, _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, kcred, &ret); vboxNetFltSolarisRelinkIp4(pUdp4VNode, &Ip4Interface, Ip4MuxFd, ArpMuxFd); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to %s the IP stack. rc=%d\n", fAttach ? "inject into" : "eject from", rc)); } } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to find position. rc=%d rc2=%d\n", rc, rc2)); } releasef(Ip4MuxFd); releasef(ArpMuxFd); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to get vnode from MuxFd.\n")); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to unlink upper stream rc=%d rc2=%d.\n", rc, rc2)); } } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to get MuxFd from MuxId. rc=%d rc2=%d\n", rc, rc2)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to get Mux Ids. rc=%d\n", rc)); vboxNetFltSolarisCloseDev(pUdp4VNodeHeld, pUdp4User); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to open UDP. rc=%d\n", rc)); rc = VERR_INTNET_FLT_IF_FAILED; } else { /* * This would happen for interfaces that are not plumbed. */ LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: Warning: seems '%s' is unplumbed.\n", pThis->szName)); rc = VINF_SUCCESS; } ldi_close(ArpDevHandle, FREAD | FWRITE, kcred); ldi_close(Ip4DevHandle, FREAD | FWRITE, kcred); return rc; } /** * Dynamically attach under IPv6 on the host stack. * * @returns VBox status code. * @param pThis The instance. * @param fAttach Is this an attach or detach. */ static int vboxNetFltSolarisAttachIp6(PVBOXNETFLTINS pThis, bool fAttach) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisAttachIp6 pThis=%p fAttach=%d\n", pThis, fAttach)); /* * Statutory Warning: Hackish code ahead. */ char *pszModName = DEVICE_NAME; struct lifreq Ip6Interface; bzero(&Ip6Interface, sizeof(Ip6Interface)); Ip6Interface.lifr_addr.ss_family = AF_INET6; strncpy(Ip6Interface.lifr_name, pThis->szName, sizeof(Ip6Interface.lifr_name)); struct strmodconf StrMod; StrMod.mod_name = pszModName; StrMod.pos = -1; /* this is filled in later. */ int rc; int ret; ldi_ident_t DeviceIdent = ldi_ident_from_anon(); ldi_handle_t Ip6DevHandle; /* * Open the IPv6 stream as a layered devices. */ rc = ldi_open_by_name(IP6_DEV_NAME, FREAD | FWRITE, kcred, &Ip6DevHandle, DeviceIdent); ldi_ident_release(DeviceIdent); if (rc) { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to open the IPv6 stream on '%s'.\n", pThis->szName)); return VERR_INTNET_FLT_IF_FAILED; } /* * Obtain the interface flags from IPv6. */ rc = vboxNetFltSolarisGetIfFlags(Ip6DevHandle, &Ip6Interface); if (RT_SUCCESS(rc)) { /* * Open the UDP stream. We sort of cheat here and obtain the vnode so that we can perform * things that are not possible from the layered interface. */ vnode_t *pUdp6VNode = NULL; vnode_t *pUdp6VNodeHeld = NULL; TIUSER *pUdp6User = NULL; rc = vboxNetFltSolarisOpenDev(UDP6_DEV_NAME, &pUdp6VNode, &pUdp6VNodeHeld, &pUdp6User); if (RT_SUCCESS(rc)) { /* * Get the multiplexor IDs. */ rc = ldi_ioctl(Ip6DevHandle, SIOCGLIFMUXID, (intptr_t)&Ip6Interface, FKIOCTL, kcred, &ret); if (!rc) { /* * Get the multiplex file descriptor to the lower streams. Generally this is lost * once a module is I_PLINK, we need to reobtain it for inserting/removing ourselves from the stack. */ int Ip6MuxFd; rc = vboxNetFltSolarisMuxIdToFd(pUdp6VNode, Ip6Interface.lifr_ip_muxid, &Ip6MuxFd); if (RT_SUCCESS(rc)) { /* * We need to I_PUNLINK on these multiplexor IDs before we can start * operating on the lower stream as insertions are direct operations on the lower stream. */ rc = strioctl(pUdp6VNode, I_PUNLINK, (intptr_t)Ip6Interface.lifr_ip_muxid, 0, K_TO_K, kcred, &ret); if (!rc) { /* * Obtain the vnode from the useless userland file descriptor. */ file_t *pIpFile = getf(Ip6MuxFd); if ( pIpFile && VNODE_FOR_FILE_T(pIpFile)) { vnode_t *pIp6VNode = VNODE_FOR_FILE_T(pIpFile); /* * Find the position on the host stack for attaching/detaching ourselves. */ rc = vboxNetFltSolarisDetermineModPos(fAttach, pIp6VNode, &StrMod.pos); if (RT_SUCCESS(rc)) { /* * Set global data which will be grabbed by ModOpen. * There is a known (though very unlikely) race here because * of the inability to pass user data while inserting. */ rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); AssertRCReturn(rc, rc); if (fAttach) { g_VBoxNetFltSolarisInstance = pThis; g_VBoxNetFltSolarisStreamType = kIp6Stream; } /* * Inject/Eject from the host IPv6 stack. */ rc = strioctl(pIp6VNode, fAttach ? _I_INSERT : _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, g_pVBoxNetFltSolarisCred, &ret); if (fAttach) { g_VBoxNetFltSolarisInstance = NULL; g_VBoxNetFltSolarisStreamType = kUndefined; } RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); if (!rc) { /* * Our job's not yet over; we need to relink the upper and lower streams * otherwise we've pretty much screwed up the host interface. */ rc = vboxNetFltSolarisRelinkIp6(pUdp6VNode, &Ip6Interface, Ip6MuxFd); if (RT_SUCCESS(rc)) { /* * Close the devices ONLY during the return from function case; otherwise * we end up close twice which is an instant kernel panic. */ vboxNetFltSolarisCloseDev(pUdp6VNodeHeld, pUdp6User); ldi_close(Ip6DevHandle, FREAD | FWRITE, kcred); releasef(Ip6MuxFd); Log((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: Success! %s %s@(IPv6:%d) " "%s interface %s\n", fAttach ? "Injected" : "Ejected", StrMod.mod_name, StrMod.pos, fAttach ? "to" : "from", pThis->szName)); return VINF_SUCCESS; } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: Relinking failed. Mode=%s rc=%d.\n", fAttach ? "inject" : "eject", rc)); } if (fAttach) strioctl(pIp6VNode, _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, kcred, &ret); vboxNetFltSolarisRelinkIp6(pUdp6VNode, &Ip6Interface, Ip6MuxFd); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to %s the IP stack. rc=%d\n", fAttach ? "inject into" : "eject from", rc)); } } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to find position. rc=%d\n", rc)); releasef(Ip6MuxFd); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get vnode from MuxFd.\n")); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to unlink upper stream rc=%d.\n", rc)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get MuxFd from MuxId. rc=%d\n", rc)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get Mux Ids. rc=%d\n", rc)); vboxNetFltSolarisCloseDev(pUdp6VNodeHeld, pUdp6User); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to open UDP. rc=%d\n", rc)); rc = VERR_INTNET_FLT_IF_FAILED; } else { Log((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get IPv6 flags.\n", pThis->szName)); rc = VERR_INTNET_FLT_IF_NOT_FOUND; } ldi_close(Ip6DevHandle, FREAD | FWRITE, kcred); return rc; } #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING /** * Ipv6 dynamic attachment timer callback to attach to the Ipv6 stream if needed. * * @param pTimer Pointer to the timer. * @param pvData Opaque pointer to the instance. * @param iTick Timer tick (unused). */ static void vboxNetFltSolarispIp6Timer(PRTTIMER pTimer, void *pvData, uint64_t iTick) { LogFunc((DEVICE_NAME ":vboxNetFltSolarispIp6Timer pTimer=%p pvData=%p\n", pTimer, pvData)); PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvData; if ( RT_LIKELY(pThis) && RT_LIKELY(pTimer)) { vboxnetflt_stream_t *pIp6Stream = ASMAtomicUoReadPtrT(&pThis->u.s.pIp6Stream, vboxnetflt_stream_t *); bool fIp6Attaching = ASMAtomicUoReadBool(&pThis->u.s.fAttaching); if ( !pIp6Stream && !fIp6Attaching) { int rc = RTSemFastMutexRequest(pThis->u.s.hPollMtx); if (RT_SUCCESS(rc)) { ASMAtomicUoWriteBool(&pThis->u.s.fAttaching, true); vboxNetFltSolarisAttachIp6(pThis, true /* fAttach */); ASMAtomicUoWriteBool(&pThis->u.s.fAttaching, false); RTSemFastMutexRelease(pThis->u.s.hPollMtx); } else LogRel((DEVICE_NAME ":vboxNetFltSolarispIp6Timer failed to obtain mutex. rc=%Rrc\n", rc)); } } NOREF(iTick); } /** * Setups up a kernel timer based on the driver property for attaching to IPv6 stream * whenever the stream gets plumbed for the interface. * * @returns VBox status code. * @param pThis The instance. */ static int vboxNetFltSolarisSetupIp6Polling(PVBOXNETFLTINS pThis) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling pThis=%p\n", pThis)); int rc = VERR_GENERAL_FAILURE; vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); if (RT_LIKELY(pPromiscStream)) { if (RT_LIKELY(pPromiscStream->pIp6Timer == NULL)) { /* * Validate IPv6 polling interval. */ int Interval = g_VBoxNetFltSolarisPollInterval; if (Interval < 1 || Interval > 120) { LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Invalid polling interval %d. Expected between" " 1 and 120 secs.\n", Interval)); return VERR_INVALID_PARAMETER; } /* * Setup kernel poll timer. */ rc = RTTimerCreateEx(&pPromiscStream->pIp6Timer, Interval * (uint64_t)1000000000, RTTIMER_FLAGS_CPU_ANY, vboxNetFltSolarispIp6Timer, (void *)pThis); if (RT_SUCCESS(rc)) { rc = RTTimerStart(pPromiscStream->pIp6Timer, 10 * (uint64_t)1000000000 /* 10 seconds to blastoff */); Log((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Ipv6 %d second timer begins firing in 10 seconds.\n", Interval)); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Failed to create timer. rc=%d\n", rc)); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Polling already started.\n")); rc = VINF_SUCCESS; } } return rc; } #endif /** * Wrapper for detaching ourselves from the interface. * * @returns VBox status code. * @param pThis The instance. * @remarks Owns the globals mutex, so re-requesting it anytime during this phase * would panic the system (e.g. in vboxNetFltSolarisFindInstance). */ static int vboxNetFltSolarisDetachFromInterface(PVBOXNETFLTINS pThis) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisDetachFromInterface pThis=%p\n", pThis)); ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); vboxNetFltSolarisCloseStream(pThis); int rc = VINF_SUCCESS; if (pThis->u.s.pIp4Stream) rc = vboxNetFltSolarisAttachIp4(pThis, false /* fAttach */); if (pThis->u.s.pIp6Stream) rc = vboxNetFltSolarisAttachIp6(pThis, false /* fAttach */); #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); if ( pPromiscStream && pPromiscStream->pIp6Timer == NULL) { RTTimerStop(pPromiscStream->pIp6Timer); RTTimerDestroy(pPromiscStream->pIp6Timer); ASMAtomicUoWriteNullPtr(&pPromiscStream->pIp6Timer); } #endif return rc; } /** * Wrapper for attaching ourselves to the interface. * * @returns VBox status code. * @param pThis The instance. */ static int vboxNetFltSolarisAttachToInterface(PVBOXNETFLTINS pThis) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface pThis=%p\n", pThis)); /* * Since this is asynchronous streams injection, let the attach succeed before we can start * processing the stream. */ ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); int rc = vboxNetFltSolarisOpenStream(pThis); if (RT_SUCCESS(rc)) { rc = vboxNetFltSolarisAttachIp4(pThis, true /* fAttach */); if (RT_SUCCESS(rc)) { /* * Ipv6 attaching is optional and can fail. We don't bother to bring down the whole * attach process just if Ipv6 interface is unavailable. */ int rc2 = vboxNetFltSolarisAttachIp6(pThis, true /* fAttach */); #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING /* * If Ip6 interface is not plumbed and an Ip6 polling interval is specified, we need * to begin polling to attach on the Ip6 interface whenever it comes up. */ if ( rc2 == VERR_INTNET_FLT_IF_NOT_FOUND && g_VBoxNetFltSolarisPollInterval != -1) { int rc3 = vboxNetFltSolarisSetupIp6Polling(pThis); if (RT_FAILURE(rc3)) { /* * If we failed to setup Ip6 polling, warn in the release log and continue. */ LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface IPv6 polling inactive. rc=%Rrc\n", rc3)); } } #endif /* * Report promiscuousness and capabilities. */ if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) { Assert(pThis->pSwitchPort); /** @todo There is no easy way of obtaining the global host side promiscuous * counter. Currently we just return false. */ pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, false); pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); vboxNetFltRelease(pThis, true /*fBusy*/); } /* * Ipv4 is successful, and maybe Ipv6, we're ready for transfers. */ ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, false); return VINF_SUCCESS; } vboxNetFltSolarisCloseStream(pThis); } else LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface vboxNetFltSolarisOpenStream failed rc=%Rrc\n", rc)); return rc; } /** * Create a solaris message block from the SG list. * * @returns Solaris message block. * @param pThis The instance. * @param pSG Pointer to the scatter-gather list. * @param fDst The destination mask, INTNETTRUNKDIR_XXX. Ignored. */ static mblk_t *vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG pThis=%p pSG=%p\n", pThis, pSG)); mblk_t *pMsg = allocb(pSG->cbTotal, BPRI_MED); if (RT_UNLIKELY(!pMsg)) { LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG failed to alloc %d bytes for mblk_t.\n", pSG->cbTotal)); return NULL; } /* * Single buffer copy. Maybe later explore the * need/possibility for using a mblk_t chain rather. */ for (unsigned i = 0; i < pSG->cSegsUsed; i++) { if (pSG->aSegs[i].pv) { bcopy(pSG->aSegs[i].pv, pMsg->b_wptr, pSG->aSegs[i].cb); pMsg->b_wptr += pSG->aSegs[i].cb; } } DB_TYPE(pMsg) = M_DATA; return pMsg; } /** * Calculate the number of segments required for this message block. * * @returns Number of segments. * @param pThis The instance * @param pMsg Pointer to the data message. */ static unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg) { unsigned cSegs = 0; for (mblk_t *pCur = pMsg; pCur; pCur = pCur->b_cont) if (MBLKL(pCur)) cSegs++; #ifdef PADD_RUNT_FRAMES_FROM_HOST if (msgdsize(pMsg) < 60) cSegs++; #endif NOREF(pThis); return RT_MAX(cSegs, 1); } /** * Initializes an SG list from the given message block. * * @returns VBox status code. * @param pThis The instance. * @param pMsg Pointer to the data message. The caller must ensure it's not a control message block. * @param pSG Pointer to the SG. * @param cSegs Number of segments in the SG. * This should match the number in the message block exactly! * @param fSrc The source of the message. */ static int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pThis=%p pMsg=%p pSG=%p cSegs=%d\n", pThis, pMsg, pSG, cSegs)); /* * Convert the message block to segments. Work INTNETSG::cbTotal. */ IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); mblk_t *pCur = pMsg; unsigned iSeg = 0; while (pCur) { size_t cbSeg = MBLKL(pCur); if (cbSeg) { void *pvSeg = pCur->b_rptr; pSG->aSegs[iSeg].pv = pvSeg; pSG->aSegs[iSeg].cb = cbSeg; pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; pSG->cbTotal += cbSeg; iSeg++; } pCur = pCur->b_cont; } pSG->cSegsUsed = iSeg; #ifdef PADD_RUNT_FRAMES_FROM_HOST if (pSG->cbTotal < 60 && (fSrc & INTNETTRUNKDIR_HOST)) { Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pulling up to length.\n")); static uint8_t const s_abZero[128] = {0}; pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; pSG->aSegs[iSeg].pv = (void *)&s_abZero[0]; pSG->aSegs[iSeg].cb = 60 - pSG->cbTotal; pSG->cbTotal = 60; pSG->cSegsUsed++; Assert(iSeg + 1 < cSegs); } #endif Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG iSeg=%d pSG->cbTotal=%d msgdsize=%d\n", iSeg, pSG->cbTotal, msgdsize(pMsg))); return VINF_SUCCESS; } /** * Converts raw mode M_DATA messages to M_PROTO DL_UNITDATA_IND format. * * @returns VBox status code. * @param pMsg Pointer to the raw message. * @param ppDlpiMsg Where to store the M_PROTO message. * * @remarks The original raw message would be no longer valid and will be * linked as part of the new DLPI message. Callers must take care * not to use the raw message if this routine is successful. */ static int vboxNetFltSolarisRawToUnitData(mblk_t *pMsg, mblk_t **ppDlpiMsg) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisRawToUnitData pMsg=%p\n", pMsg)); if (DB_TYPE(pMsg) != M_DATA) return VERR_NO_MEMORY; size_t cbMsg = sizeof(dl_unitdata_ind_t) + 2 * sizeof(vboxnetflt_dladdr_t); mblk_t *pDlpiMsg = allocb(cbMsg, BPRI_MED); if (RT_UNLIKELY(!pDlpiMsg)) return VERR_NO_MEMORY; DB_TYPE(pDlpiMsg) = M_PROTO; dl_unitdata_ind_t *pDlpiData = (dl_unitdata_ind_t *)pDlpiMsg->b_rptr; pDlpiData->dl_primitive = DL_UNITDATA_IND; pDlpiData->dl_dest_addr_length = VBOXNETFLT_DLADDRL; pDlpiData->dl_dest_addr_offset = sizeof(dl_unitdata_ind_t); pDlpiData->dl_src_addr_length = VBOXNETFLT_DLADDRL; pDlpiData->dl_src_addr_offset = VBOXNETFLT_DLADDRL + sizeof(dl_unitdata_ind_t); PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; vboxnetflt_dladdr_t *pDlAddr = (vboxnetflt_dladdr_t *)(pDlpiMsg->b_rptr + pDlpiData->dl_dest_addr_offset); pDlAddr->SAP = RT_BE2H_U16(pEthHdr->EtherType); bcopy(&pEthHdr->DstMac, &pDlAddr->Mac, sizeof(RTMAC)); pDlAddr = (vboxnetflt_dladdr_t *)(pDlpiMsg->b_rptr + pDlpiData->dl_src_addr_offset); pDlAddr->SAP = RT_BE2H_U16(pEthHdr->EtherType); bcopy(&pEthHdr->SrcMac, &pDlAddr->Mac, sizeof(RTMAC)); pDlpiMsg->b_wptr = pDlpiMsg->b_rptr + cbMsg; /* Make the message point to the protocol header */ pMsg->b_rptr += sizeof(RTNETETHERHDR); pDlpiMsg->b_cont = pMsg; *ppDlpiMsg = pDlpiMsg; return VINF_SUCCESS; } #if 0 /** * Converts DLPI M_PROTO messages to the raw mode M_DATA format. * * @returns VBox status code. * @param pMsg Pointer to the M_PROTO message. * @param ppRawMsg Where to store the converted message. * * @remarks If successful, the original pMsg is no longer valid, it will be deleted. * Callers must take care not to continue to use pMsg after a successful * call to this conversion routine. */ static int vboxNetFltSolarisUnitDataToRaw(PVBOXNETFLTINS pThis, mblk_t *pMsg, mblk_t **ppRawMsg) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisUnitDataToRaw pMsg=%p\n", pMsg)); if ( !pMsg->b_cont || DB_TYPE(pMsg) != M_PROTO) { LogRel((DEVICE_NAME ":vboxNetFltSolarisUnitDataToRaw invalid input message.\n")); return VERR_NET_PROTOCOL_ERROR; } /* * Upstream consumers send/receive packets in the fast path mode. * We of course need to convert them into raw ethernet frames. */ RTNETETHERHDR EthHdr; union DL_primitives *pPrim = (union DL_primitives *)pMsg->b_rptr; switch (pPrim->dl_primitive) { case DL_UNITDATA_IND: { /* * Receive side. */ dl_unitdata_ind_t *pDlpiMsg = (dl_unitdata_ind_t *)pMsg->b_rptr; bcopy(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset, &EthHdr.DstMac, sizeof(EthHdr.DstMac)); bcopy(pMsg->b_rptr + pDlpiMsg->dl_src_addr_offset, &EthHdr.SrcMac, sizeof(EthHdr.SrcMac)); vboxnetflt_dladdr_t *pDLSapAddr = (vboxnetflt_dladdr_t *)(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset); EthHdr.EtherType = RT_H2BE_U16(pDLSapAddr->SAP); break; } case DL_UNITDATA_REQ: { /* * Send side. */ dl_unitdata_req_t *pDlpiMsg = (dl_unitdata_req_t *)pMsg->b_rptr; bcopy(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset, &EthHdr.DstMac, sizeof(EthHdr.DstMac)); bcopy(&pThis->u.s.MacAddr, &EthHdr.SrcMac, sizeof(EthHdr.SrcMac)); vboxnetflt_dladdr_t *pDLSapAddr = (vboxnetflt_dladdr_t *)(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset); EthHdr.EtherType = RT_H2BE_U16(pDLSapAddr->SAP); break; } default: { LogRel((DEVICE_NAME ":vboxNetFltSolarisUnitDataToRaw Unknown M_PROTO. This shouldn't be happening!!")); return VERR_NET_PROTOCOL_ERROR; } } /* * Let us just link it as a mblk_t chain rather than re-copy the entire message. * The vboxNetFltSolarisMBlkToSG function will handle chained mblk_t's. */ size_t cbLen = sizeof(EthHdr); mblk_t *pEtherMsg = allocb(cbLen, BPRI_MED); if (RT_UNLIKELY(!pEtherMsg)) return VERR_NO_MEMORY; DB_TYPE(pEtherMsg) = M_DATA; bcopy(&EthHdr, pEtherMsg->b_wptr, sizeof(EthHdr)); pEtherMsg->b_wptr += cbLen; pEtherMsg->b_cont = pMsg->b_cont; /* * Change the chained blocks to type M_DATA. */ for (mblk_t *pTmp = pEtherMsg->b_cont; pTmp; pTmp = pTmp->b_cont) DB_TYPE(pTmp) = M_DATA; pMsg->b_cont = NULL; freemsg(pMsg); *ppRawMsg = pEtherMsg; return VINF_SUCCESS; } #endif /** * Initializes a packet identifier. * * @param pTag Pointer to the packed identifier. * @param pMsg Pointer to the message to be identified. * * @remarks Warning!!! This function assumes 'pMsg' is an unchained message. */ static inline void vboxNetFltSolarisInitPacketId(PVBOXNETFLTPACKETID pTag, mblk_t *pMsg) { PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; size_t cbMsg = MBLKL(pMsg); pTag->cbPacket = cbMsg; pTag->Checksum = RTCrc32(pMsg->b_rptr, cbMsg); bcopy(&pEthHdr->SrcMac, &pTag->SrcMac, sizeof(RTMAC)); bcopy(&pEthHdr->DstMac, &pTag->DstMac, sizeof(RTMAC)); } /** * Queues a packet for loopback elimination. * * @returns VBox status code. * @param pThis The instance. * @param pPromiscStream Pointer to the promiscuous stream. * @param pMsg Pointer to the message. */ static int vboxNetFltSolarisQueueLoopback(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg) { Assert(pThis); Assert(pMsg); Assert(DB_TYPE(pMsg) == M_DATA); Assert(pPromiscStream); LogFunc((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback pThis=%p pPromiscStream=%p pMsg=%p\n", pThis, pPromiscStream, pMsg)); if (RT_UNLIKELY(pMsg->b_cont)) { /* * We don't currently make chained messages in on Xmit * so this only needs to be supported when we do that. */ return VERR_NOT_SUPPORTED; } size_t cbMsg = MBLKL(pMsg); if (RT_UNLIKELY(cbMsg < sizeof(RTNETETHERHDR))) return VERR_NET_MSG_SIZE; int rc = VINF_SUCCESS; mutex_enter(&pThis->u.s.hMtx); PVBOXNETFLTPACKETID pCur = NULL; if (pPromiscStream->cLoopback < VBOXNETFLT_LOOPBACK_SIZE || ( pPromiscStream->pHead && pPromiscStream->pHead->cbPacket == 0)) { do { if (!pPromiscStream->pHead) { pCur = RTMemAlloc(sizeof(VBOXNETFLTPACKETID)); if (RT_UNLIKELY(!pCur)) { rc = VERR_NO_MEMORY; break; } vboxNetFltSolarisInitPacketId(pCur, pMsg); pCur->pNext = NULL; pPromiscStream->pHead = pCur; pPromiscStream->pTail = pCur; pPromiscStream->cLoopback++; Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback initialized head. checksum=%u.\n", pPromiscStream->pHead->Checksum)); break; } else if ( pPromiscStream->pHead && pPromiscStream->pHead->cbPacket == 0) { pCur = pPromiscStream->pHead; vboxNetFltSolarisInitPacketId(pCur, pMsg); Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback re-used head checksum=%u cLoopback=%d.\n", pCur->Checksum, pPromiscStream->cLoopback)); break; } else { pCur = RTMemAlloc(sizeof(VBOXNETFLTPACKETID)); if (RT_UNLIKELY(!pCur)) { rc = VERR_NO_MEMORY; break; } vboxNetFltSolarisInitPacketId(pCur, pMsg); pCur->pNext = pPromiscStream->pHead; pPromiscStream->pHead = pCur; pPromiscStream->cLoopback++; Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback added head checksum=%u cLoopback=%d.\n", pCur->Checksum, pPromiscStream->cLoopback)); break; } } while (0); } else { /* * Maximum loopback queue size reached. Re-use tail as head. */ Assert(pPromiscStream->pHead); Assert(pPromiscStream->pTail); /* * Find tail's previous item. */ PVBOXNETFLTPACKETID pPrev = NULL; pCur = pPromiscStream->pHead; /** @todo consider if this is worth switching to a double linked list... */ while (pCur != pPromiscStream->pTail) { pPrev = pCur; pCur = pCur->pNext; } pPromiscStream->pTail = pPrev; pPromiscStream->pTail->pNext = NULL; pCur->pNext = pPromiscStream->pHead; pPromiscStream->pHead = pCur; vboxNetFltSolarisInitPacketId(pCur, pMsg); Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback recycled tail!! checksum=%u cLoopback=%d\n", pCur->Checksum, pPromiscStream->cLoopback)); } mutex_exit(&pThis->u.s.hMtx); return rc; } /** * Checks if the packet is enqueued for loopback as our own packet. * * @returns If it's our packet, returns true after dequeuing it, otherwise false. * @param pThis The instance. * @param pPromiscStream Pointer to the promiscuous stream. * @param pMsg Pointer to the message. */ static bool vboxNetFltSolarisIsOurMBlk(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg) { Assert(pThis); Assert(pPromiscStream); Assert(pMsg); Assert(DB_TYPE(pMsg) == M_DATA); LogFunc((DEVICE_NAME ":vboxNetFltSolarisIsOurMBlk pThis=%p pMsg=%p\n", pThis, pMsg)); if (pMsg->b_cont) { /** Handle this when Xmit makes chained messages */ return false; } size_t cbMsg = MBLKL(pMsg); if (cbMsg < sizeof(RTNETETHERHDR)) return false; mutex_enter(&pThis->u.s.hMtx); PVBOXNETFLTPACKETID pPrev = NULL; PVBOXNETFLTPACKETID pCur = pPromiscStream->pHead; bool fIsOurPacket = false; while (pCur) { PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; if ( pCur->cbPacket != cbMsg || pCur->SrcMac.au8[0] != pEthHdr->SrcMac.au8[0] || pCur->SrcMac.au8[1] != pEthHdr->SrcMac.au8[1] || pCur->SrcMac.au8[2] != pEthHdr->SrcMac.au8[2] || pCur->SrcMac.au8[3] != pEthHdr->SrcMac.au8[3] || pCur->SrcMac.au8[4] != pEthHdr->SrcMac.au8[4] || pCur->SrcMac.au8[5] != pEthHdr->SrcMac.au8[5] || pCur->DstMac.au8[0] != pEthHdr->DstMac.au8[0] || pCur->DstMac.au8[1] != pEthHdr->DstMac.au8[1] || pCur->DstMac.au8[2] != pEthHdr->DstMac.au8[2] || pCur->DstMac.au8[3] != pEthHdr->DstMac.au8[3] || pCur->DstMac.au8[4] != pEthHdr->DstMac.au8[4] || pCur->DstMac.au8[5] != pEthHdr->DstMac.au8[5]) { pPrev = pCur; pCur = pCur->pNext; continue; } uint16_t Checksum = RTCrc32(pMsg->b_rptr, cbMsg); if (pCur->Checksum != Checksum) { pPrev = pCur; pCur = pCur->pNext; continue; } /* * Yes, it really is our own packet, mark it as handled * and move it as a "free slot" to the head and return success. */ pCur->cbPacket = 0; if (pPrev) { if (!pCur->pNext) pPromiscStream->pTail = pPrev; pPrev->pNext = pCur->pNext; pCur->pNext = pPromiscStream->pHead; pPromiscStream->pHead = pCur; } fIsOurPacket = true; Log((DEVICE_NAME ":vboxNetFltSolarisIsOurMBlk found packet %p Checksum=%u cLoopback=%d\n", pMsg, Checksum, pPromiscStream->cLoopback)); break; } Log((DEVICE_NAME ":vboxNetFltSolarisIsOurMBlk returns %d.\n", fIsOurPacket)); mutex_exit(&pThis->u.s.hMtx); return fIsOurPacket; } /** * Helper. */ DECLINLINE(bool) vboxNetFltPortSolarisIsHostMac(PVBOXNETFLTINS pThis, PCRTMAC pMac) { /* * MAC address change acknowledgements are intercepted on the read side * hence theoretically we are always update to date with any changes. */ return pThis->u.s.MacAddr.au16[0] == pMac->au16[0] && pThis->u.s.MacAddr.au16[1] == pMac->au16[1] && pThis->u.s.MacAddr.au16[2] == pMac->au16[2]; } /** * Worker for routing messages from the wire or from the host. * * @returns VBox status code. * @param pThis The instance. * @param pStream Pointer to the stream. * @param pQueue Pointer to the read queue. * @param pMsg Pointer to the message. */ static int vboxNetFltSolarisRecv(PVBOXNETFLTINS pThis, vboxnetflt_stream_t *pStream, queue_t *pQueue, mblk_t *pMsg) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisRecv pThis=%p pMsg=%p\n", pThis, pMsg)); AssertCompile(sizeof(struct ether_header) == sizeof(RTNETETHERHDR)); Assert(pStream->Type == kPromiscStream); vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); if (RT_UNLIKELY(!pPromiscStream)) { LogRel((DEVICE_NAME ":Promiscuous stream missing!! Failing to receive packet.\n")); return VERR_INVALID_POINTER; } /* * Paranoia... */ if (RT_UNLIKELY(MBLKL(pMsg) < sizeof(RTNETETHERHDR))) { size_t cbMsg = msgdsize(pMsg); if (cbMsg < sizeof(RTNETETHERHDR)) { LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv %s: packet too small. Dropping packet.\n", pThis->szName)); return VINF_SUCCESS; } mblk_t *pFullMsg = msgpullup(pMsg, -1 /* all data blocks */); if (pFullMsg) { freemsg(pMsg); pMsg = pFullMsg; } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv msgpullup failed.\n")); return VERR_NO_MEMORY; } } /* * Don't loopback packets we transmit to the wire. */ if (vboxNetFltSolarisIsOurMBlk(pThis, pPromiscStream, pMsg)) { Log((DEVICE_NAME ":Avoiding packet loopback.\n")); return VINF_SUCCESS; } /* * Figure out the source of the packet based on the source Mac address. */ uint32_t fSrc = INTNETTRUNKDIR_WIRE; PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; if (vboxNetFltPortSolarisIsHostMac(pThis, &pEthHdr->SrcMac)) fSrc = INTNETTRUNKDIR_HOST; /* * Afaik; we no longer need to worry about incorrect checksums because we now use * a dedicated stream and don't intercept packets under IP/ARP which might be doing * checksum offloading. */ #if 0 if (fSrc & INTNETTRUNKDIR_HOST) { mblk_t *pCorrectedMsg = vboxNetFltSolarisFixChecksums(pMsg); if (pCorrectedMsg) pMsg = pCorrectedMsg; } vboxNetFltSolarisAnalyzeMBlk(pMsg); #endif /* * Solaris raw mode streams for priority-tagged VLAN does not strip the VLAN tag. * It zero's the VLAN-Id but keeps the tag intact as part of the Ethernet header. * We need to manually strip these tags out or the guests might get confused. */ bool fCopied = false; bool fTagged = false; if ( pThis->u.s.fVLAN && pPromiscStream->fRawMode) { if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_VLAN)) { if (msgdsize(pMsg) > sizeof(RTNETETHERHDR) + sizeof(VLANHEADER)) { if (pMsg->b_cont) { mblk_t *pFullMsg = msgpullup(pMsg, -1 /* all data blocks */); if (pFullMsg) { /* Original pMsg will be freed by the caller */ pMsg = pFullMsg; fCopied = true; } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv msgpullup failed.\n")); return VERR_NO_MEMORY; } } PVLANHEADER pVlanHdr = (PVLANHEADER)(pMsg->b_rptr + sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType)); Log((DEVICE_NAME ":Recv VLAN Pcp=%u Cfi=%u Id=%u\n", VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_CFI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)))); if ( VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)) > 0 && VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)) == 0) { /* * Create new Ethernet header with stripped VLAN tag. */ size_t cbEthPrefix = sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType); mblk_t *pStrippedMsg = allocb(cbEthPrefix, BPRI_MED); if (RT_LIKELY(pStrippedMsg)) { fTagged = true; /* * Copy ethernet header excluding the ethertype. */ bcopy(pMsg->b_rptr, pStrippedMsg->b_wptr, cbEthPrefix); pStrippedMsg->b_wptr += cbEthPrefix; /* * Link the rest of the message (ethertype + data, skipping VLAN header). */ pMsg->b_rptr += cbEthPrefix + sizeof(VLANHEADER); pStrippedMsg->b_cont = pMsg; pMsg = pStrippedMsg; Log((DEVICE_NAME ":Stripped VLAN tag.\n")); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv insufficient memory for creating VLAN stripped packet" " cbMsg=%u.\n", cbEthPrefix)); if (fCopied) freemsg(pMsg); return VERR_NO_MEMORY; } } } } } /* * Route all received packets into the internal network. */ unsigned cSegs = vboxNetFltSolarisMBlkCalcSGSegs(pThis, pMsg); PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); int rc = vboxNetFltSolarisMBlkToSG(pThis, pMsg, pSG, cSegs, fSrc); if (RT_SUCCESS(rc)) pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); else LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG failed. rc=%d\n", rc)); /* * If we've allocated the prefix before the VLAN tag in a new message, free that. */ if (fTagged) { mblk_t *pTagMsg = pMsg->b_cont; pMsg->b_cont = NULL; /* b_cont could be the message from the caller or a copy we made (fCopied) */ freemsg(pMsg); pMsg = pTagMsg; } /* * If we made an extra copy for VLAN stripping, we need to free that ourselves. */ if (fCopied) freemsg(pMsg); return VINF_SUCCESS; } #if 0 /** * Finalize the message to be fed into the internal network. * Verifies and tries to fix checksums for TCP, UDP and IP. * * @returns Corrected message or NULL if no change was required. * @param pMsg Pointer to the message block. * This must not be DLPI linked messages, must be M_DATA. * * @remarks If this function returns a checksum adjusted message, the * passed in input message has been freed and should not be * referenced anymore by the caller. */ static mblk_t *vboxNetFltSolarisFixChecksums(mblk_t *pMsg) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisFixChecksums pMsg=%p\n")); Assert(DB_TYPE(pMsg) == M_DATA); if (MBLKL(pMsg) < sizeof(RTNETETHERHDR)) { LogRel((DEVICE_NAME ":vboxNetFltSolarisFixChecksums Packet shorter than ethernet header size!\n")); return NULL; } PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV4)) { /* * Check if we have a complete packet or being fed a chain. */ size_t cbIpPacket = 0; mblk_t *pFullMsg = NULL; if (pMsg->b_cont) { Log((DEVICE_NAME ":Chained mblk_t.\n")); /* * Handle chain by making a packet copy to verify if the IP checksum is correct. * Contributions to calculating IP checksums from a chained message block with * odd/non-pulled up sizes are welcome. */ size_t cbFullMsg = msgdsize(pMsg); mblk_t *pFullMsg = allocb(cbFullMsg, BPRI_MED); Log((DEVICE_NAME ":msgdsize returns %d\n", cbFullMsg)); if (RT_UNLIKELY(!pFullMsg)) { LogRel((DEVICE_NAME ":vboxNetFltSolarisFixChecksums failed to alloc new message of %d bytes.\n", cbFullMsg)); return NULL; } for (mblk_t *pTmp = pMsg; pTmp; pTmp = pTmp->b_cont) { if (DB_TYPE(pTmp) == M_DATA) { bcopy(pTmp->b_rptr, pFullMsg->b_wptr, MBLKL(pTmp)); pFullMsg->b_wptr += MBLKL(pTmp); } } DB_TYPE(pFullMsg) = M_DATA; pEthHdr = (PRTNETETHERHDR)pFullMsg->b_rptr; cbIpPacket = MBLKL(pFullMsg) - sizeof(RTNETETHERHDR); } else cbIpPacket = MBLKL(pMsg) - sizeof(RTNETETHERHDR); /* * Check if the IP checksum is valid. */ uint8_t *pbProtocol = (uint8_t *)(pEthHdr + 1); PRTNETIPV4 pIpHdr = (PRTNETIPV4)pbProtocol; size_t cbPayload = cbIpPacket - (pIpHdr->ip_hl << 2); bool fChecksumAdjusted = false; if (RTNetIPv4IsHdrValid(pIpHdr, cbPayload, cbPayload)) { pbProtocol += (pIpHdr->ip_hl << 2); /* * Fix up TCP/UDP and IP checksums if they're incomplete/invalid. */ if (pIpHdr->ip_p == RTNETIPV4_PROT_TCP) { PRTNETTCP pTcpHdr = (PRTNETTCP)pbProtocol; uint16_t TcpChecksum = RTNetIPv4TCPChecksum(pIpHdr, pTcpHdr, NULL); if (pTcpHdr->th_sum != TcpChecksum) { pTcpHdr->th_sum = TcpChecksum; fChecksumAdjusted = true; Log((DEVICE_NAME ":fixed TCP checksum.\n")); } } else if (pIpHdr->ip_p == RTNETIPV4_PROT_UDP) { PRTNETUDP pUdpHdr = (PRTNETUDP)pbProtocol; uint16_t UdpChecksum = RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pUdpHdr + 1); if (pUdpHdr->uh_sum != UdpChecksum) { pUdpHdr->uh_sum = UdpChecksum; fChecksumAdjusted = true; Log((DEVICE_NAME ":Fixed UDP checksum.")); } } } if (fChecksumAdjusted) { /* * If we made a copy and the checksum is corrected on the copy, * free the original, return the checksum fixed copy. */ if (pFullMsg) { freemsg(pMsg); return pFullMsg; } return pMsg; } /* * If we made a copy and the checksum is NOT corrected, free the copy, * and return NULL. */ if (pFullMsg) freemsg(pFullMsg); return NULL; } return NULL; } /** * Simple packet dump, used for internal debugging. * * @param pMsg Pointer to the message to analyze and dump. */ static void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg) { LogFunc((DEVICE_NAME ":vboxNetFltSolarisAnalyzeMBlk pMsg=%p\n", pMsg)); PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; uint8_t *pb = pMsg->b_rptr; if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV4)) { PRTNETIPV4 pIpHdr = (PRTNETIPV4)(pEthHdr + 1); size_t cbLen = MBLKL(pMsg) - sizeof(*pEthHdr); if (!pMsg->b_cont) { if (pIpHdr->ip_p == RTNETIPV4_PROT_ICMP) LogRel((DEVICE_NAME ":ICMP D=%.6Rhxs S=%.6Rhxs T=%04x\n", pb, pb + 6, RT_BE2H_U16(*(uint16_t *)(pb + 12)))); else if (pIpHdr->ip_p == RTNETIPV4_PROT_TCP) LogRel((DEVICE_NAME ":TCP D=%.6Rhxs S=%.6Rhxs\n", pb, pb + 6)); else if (pIpHdr->ip_p == RTNETIPV4_PROT_UDP) { PCRTNETUDP pUdpHdr = (PCRTNETUDP)((uint32_t *)pIpHdr + pIpHdr->ip_hl); if ( RT_BE2H_U16(pUdpHdr->uh_sport) == 67 && RT_BE2H_U16(pUdpHdr->uh_dport) == 68) { LogRel((DEVICE_NAME ":UDP bootp ack D=%.6Rhxs S=%.6Rhxs UDP_CheckSum=%04x Computex=%04x\n", pb, pb + 6, RT_BE2H_U16(pUdpHdr->uh_sum), RT_BE2H_U16(RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pUdpHdr + 1)))); } } } else { Log((DEVICE_NAME ":Chained IP packet. Skipping validity check.\n")); } } else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_VLAN)) { PVLANHEADER pVlanHdr = (PVLANHEADER)(pMsg->b_rptr + sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType)); LogRel((DEVICE_NAME ":VLAN Pcp=%u Cfi=%u Id=%u\n", VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_CFI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)))); LogRel((DEVICE_NAME "%.*Rhxd\n", sizeof(VLANHEADER), pVlanHdr)); } else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_ARP)) { PRTNETARPHDR pArpHdr = (PRTNETARPHDR)(pEthHdr + 1); LogRel((DEVICE_NAME ":ARP Op=%d\n", pArpHdr->ar_oper)); } else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV6)) { LogRel((DEVICE_NAME ":IPv6 D=%.6Rhxs S=%.6Rhxs\n", pb, pb + 6)); } else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_1) || pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_2) || pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_3)) { LogRel((DEVICE_NAME ":IPX packet.\n")); } else { LogRel((DEVICE_NAME ":Unknown EtherType=%x D=%.6Rhxs S=%.6Rhxs\n", RT_H2BE_U16(pEthHdr->EtherType), &pEthHdr->DstMac, &pEthHdr->SrcMac)); /* Log((DEVICE_NAME ":%.*Rhxd\n", MBLKL(pMsg), pMsg->b_rptr)); */ } } #endif /* -=-=-=-=-=- Common Hooks -=-=-=-=-=- */ void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) { LogFunc((DEVICE_NAME ":vboxNetFltPortOsSetActive pThis=%p fActive=%d\n", pThis, fActive)); /* * Enable/disable promiscuous mode. */ vboxnetflt_promisc_params_t *pData = RTMemAllocZ(sizeof(vboxnetflt_promisc_params_t)); if (RT_LIKELY(pData)) { /* * See @bugref{5262} as to why we need to do all this qtimeout/qwriter tricks. */ vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); if ( pPromiscStream && pPromiscStream->Stream.pReadQueue) { pData->pThis = pThis; pData->fPromiscOn = fActive; if (ASMAtomicReadPtr(&pPromiscStream->TimeoutId)) quntimeout(WR(pPromiscStream->Stream.pReadQueue), pPromiscStream->TimeoutId); timeout_id_t TimeoutId = qtimeout(WR(pPromiscStream->Stream.pReadQueue), vboxNetFltSolarisPromiscReqWrap, pData, 1 /* ticks */); ASMAtomicWritePtr(&pPromiscStream->TimeoutId, TimeoutId); return; /* pData will be freed by vboxNetFltSolarisPromiscReqWrap() */ } else LogRel((DEVICE_NAME ":vboxNetFltPortOsSetActive pThis=%p fActive=%d missing stream!\n", pThis, fActive)); RTMemFree(pData); } else LogRel((DEVICE_NAME ":vboxNetFltPortOsSetActive out of memory!\n")); } int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) { LogFunc((DEVICE_NAME ":vboxNetFltOsDisconnectIt pThis=%p\n", pThis)); vboxNetFltSolarisDetachFromInterface(pThis); return VINF_SUCCESS; } int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) { /* Nothing to do here. */ return VINF_SUCCESS; } void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) { LogFunc((DEVICE_NAME ":vboxNetFltOsDeleteInstance pThis=%p\n", pThis)); mutex_destroy(&pThis->u.s.hMtx); #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING if (pThis->u.s.hPollMtx != NIL_RTSEMFASTMUTEX) { RTSemFastMutexDestroy(pThis->u.s.hPollMtx); pThis->u.s.hPollMtx = NIL_RTSEMFASTMUTEX; } #endif } int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) { LogFunc((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p\n")); /* * Mutex used for loopback lockouts. */ int rc = VINF_SUCCESS; mutex_init(&pThis->u.s.hMtx, NULL /* name */, MUTEX_DRIVER, NULL /* cookie */); #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING rc = RTSemFastMutexCreate(&pThis->u.s.hPollMtx); if (RT_SUCCESS(rc)) { #endif rc = vboxNetFltSolarisAttachToInterface(pThis); if (RT_SUCCESS(rc)) return rc; LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface failed. rc=%Rrc\n", rc)); #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING RTSemFastMutexDestroy(pThis->u.s.hPollMtx); pThis->u.s.hPollMtx = NIL_RTSEMFASTMUTEX; } else LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to create poll mutex. rc=%Rrc\n", rc)); #endif mutex_destroy(&pThis->u.s.hMtx); NOREF(pvContext); return rc; } int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) { /* * Init. the solaris specific data. */ pThis->u.s.hIface = NULL; pThis->u.s.pIp4Stream = NULL; pThis->u.s.pIp6Stream = NULL; pThis->u.s.pArpStream = NULL; pThis->u.s.pPromiscStream = NULL; pThis->u.s.fAttaching = false; pThis->u.s.fVLAN = false; #ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING pThis->u.s.hPollMtx = NIL_RTSEMFASTMUTEX; #endif bzero(&pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); return VINF_SUCCESS; } bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) { /* * We don't support interface rediscovery on Solaris hosts because the * filter is very tightly bound to the stream. */ return false; } void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) { NOREF(pThis); NOREF(pvIfData); NOREF(pMac); } int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) { /* Nothing to do */ NOREF(pThis); NOREF(pvIf); NOREF(ppvIfData); return VINF_SUCCESS; } int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) { /* Nothing to do */ NOREF(pThis); NOREF(pvIfData); return VINF_SUCCESS; } int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) { NOREF(pvIfData); LogFunc((DEVICE_NAME ":vboxNetFltPortOsXmit pThis=%p pSG=%p fDst=%d\n", pThis, pSG, fDst)); int rc = VINF_SUCCESS; if (fDst & INTNETTRUNKDIR_WIRE) { vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); if (RT_LIKELY(pPromiscStream)) { mblk_t *pMsg = vboxNetFltSolarisMBlkFromSG(pThis, pSG, fDst); if (RT_LIKELY(pMsg)) { Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_WIRE\n")); vboxNetFltSolarisQueueLoopback(pThis, pPromiscStream, pMsg); putnext(WR(pPromiscStream->Stream.pReadQueue), pMsg); } else { LogRel((DEVICE_NAME ":vboxNetFltPortOsXmit vboxNetFltSolarisMBlkFromSG failed.\n")); return VERR_NO_MEMORY; } } } if (fDst & INTNETTRUNKDIR_HOST) { /* * For unplumbed interfaces we would not be bound to IP or ARP. * We either bind to both or neither; so atomic reading one should be sufficient. */ vboxnetflt_stream_t *pIp4Stream = ASMAtomicUoReadPtrT(&pThis->u.s.pIp4Stream, vboxnetflt_stream_t *); if (!pIp4Stream) return rc; /* * Create a message block and send it up the host stack (upstream). */ mblk_t *pMsg = vboxNetFltSolarisMBlkFromSG(pThis, pSG, fDst); if (RT_LIKELY(pMsg)) { PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; /* * Send message up ARP stream. */ if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_ARP)) { Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_HOST ARP\n")); vboxnetflt_stream_t *pArpStream = ASMAtomicUoReadPtrT(&pThis->u.s.pArpStream, vboxnetflt_stream_t *); if (pArpStream) { /* * Construct a DL_UNITDATA_IND style message for ARP as it doesn't understand fast path. */ mblk_t *pDlpiMsg; rc = vboxNetFltSolarisRawToUnitData(pMsg, &pDlpiMsg); if (RT_SUCCESS(rc)) { pMsg = pDlpiMsg; queue_t *pArpReadQueue = pArpStream->pReadQueue; putnext(pArpReadQueue, pMsg); } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisRawToUnitData failed!\n")); freemsg(pMsg); rc = VERR_NO_MEMORY; } } else freemsg(pMsg); /* Should really never happen... */ } else { vboxnetflt_stream_t *pIp6Stream = ASMAtomicUoReadPtrT(&pThis->u.s.pIp6Stream, vboxnetflt_stream_t *); if ( pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV6) && pIp6Stream) { /* * Send messages up IPv6 stream. */ Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_HOST IPv6\n")); pMsg->b_rptr += sizeof(RTNETETHERHDR); queue_t *pIp6ReadQueue = pIp6Stream->pReadQueue; putnext(pIp6ReadQueue, pMsg); } else { /* * Send messages up IPv4 stream. */ Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_HOST IPv4\n")); pMsg->b_rptr += sizeof(RTNETETHERHDR); queue_t *pIp4ReadQueue = pIp4Stream->pReadQueue; putnext(pIp4ReadQueue, pMsg); } } } else { LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG failed.\n")); rc = VERR_NO_MEMORY; } } return rc; }