diff options
Diffstat (limited to 'src/VBox/HostDrivers/VBoxNetFlt')
52 files changed, 31925 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk b/src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk new file mode 100644 index 00000000..e2704459 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk @@ -0,0 +1,556 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Network Filter Driver (VBoxNetFlt). +# + +# +# 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 <https://www.gnu.org/licenses>. +# +# 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 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +if1of ($(KBUILD_TARGET), darwin win) # this ifeq must go, see @todo around elif + # + # VBoxNetFlt.sys - The mixed case driver. + # + ifdef VBOX_WITH_VBOXDRV + SYSMODS += VBoxNetFlt + endif + VBoxNetFlt_TEMPLATE = VBoxR0Drv + VBoxNetFlt_INST = $(INST_VBOXNETFLT)$(if $(eq $(KBUILD_TARGET),darwin),Contents/MacOS/) + VBoxNetFlt_DEBUG_INST.darwin = $(patsubst %/,%,$(INST_VBOXNETFLT)) + if defined(VBOX_SIGNING_MODE) && "$(KBUILD_TARGET)" == "win" + VBoxNetFlt_INSTTYPE = none + VBoxNetFlt_DEBUG_INSTTYPE = both + endif + VBoxNetFlt_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetFlt_INCS = . + VBoxNetFlt_SOURCES.darwin = \ + darwin/VBoxNetFlt-darwin.cpp + VBoxNetFlt_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetFlt_SOURCES.win = \ + win/drv/VBoxNetFltP-win.cpp \ + win/drv/VBoxNetFltM-win.cpp \ + win/drv/VBoxNetFltRt-win.cpp \ + win/drv/VBoxNetFlt-win.rc + # with WINDDKWLH the WIN9X_COMPAT_SPINLOCK is needed to avoid inline declaration of KeInitializeSpinLock + # otherwise the linker would complain about dumplicate _KeInitializeSpinLock@4 definition + # in ntoskrnl.lib and our object files + VBoxNetFlt_DEFS.win += WIN9X_COMPAT_SPINLOCK=1 + VBoxNetFlt_DEFS.win += VBOXNETFLT_STATIC_CONFIG + VBoxNetFlt_DEFS.win += VBOXNETFLT_NO_PACKET_QUEUE + VBoxNetFlt_DEFS.win += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetFlt_DEFS.win += NDIS50_MINIPORT=1 NDIS50=1 + ifdef VBOX_LOOPBACK_USEFLAGS + VBoxNetFlt_DEFS.win += VBOX_LOOPBACK_USEFLAGS + endif + VBoxNetFlt_SOURCES = VBoxNetFlt.c + #VBoxNetFlt_LDFLAGS.darwin = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded + VBoxNetFlt_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetFlt_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetFlt_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ndis.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/tdi.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetFlt_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + + darwin/VBoxNetFlt-darwin.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + + + # Darwin extras. + if "$(KBUILD_TARGET)" == "darwin" && defined(VBOX_WITH_VBOXDRV) + INSTALLS += VBoxNetFlt.kext + VBoxNetFlt.kext_INST = $(INST_VBOXNETFLT)Contents/ + VBoxNetFlt.kext_SOURCES = $(VBoxNetFlt.kext_0_OUTDIR)/Contents/Info.plist + VBoxNetFlt.kext_CLEAN = $(VBoxNetFlt.kext_0_OUTDIR)/Contents/Info.plist + VBoxNetFlt.kext_BLDDIRS = $(VBoxNetFlt.kext_0_OUTDIR)/Contents/ + + $$(VBoxNetFlt.kext_0_OUTDIR)/Contents/Info.plist: $(PATH_SUB_CURRENT)/darwin/Info.plist $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetFlt,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \ + -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \ + -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \ + -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+g' \ + -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \ + -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \ + -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \ + --output $@ \ + $< + + $(evalcall2 VBOX_TEST_SIGN_KEXT,VBoxNetFlt) + + INSTALLS.darwin += Scripts-darwin + Scripts-darwin_INST = $(INST_DIST) + Scripts-darwin_EXEC_SOURCES = \ + darwin/loadnetflt.sh + endif # darwin && host-drivers + + + ifeq ($(KBUILD_TARGET),win) + # + # Windows extras. + # + INSTALLS.win += VBoxNetFlt-inf + VBoxNetFlt-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetFlt-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.inf \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltM.inf + VBoxNetFlt-inf_CLEAN = $(VBoxNetFlt-inf_SOURCES) + VBoxNetFlt-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetFltCat.dir + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetFlt.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetFlt-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltM.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetFltM.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetFlt-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetFlt-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.sys \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltNobj.dll \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.cat + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.sys: $$(VBoxNetFlt_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltNobj.dll: $$(VBoxNetFltNobj_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.cat: \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.sys \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltNobj.dll \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.inf \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltM.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetFlt-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # signing + + + # + # VBoxNetLwf.sys - The light-weight filter driver for NDIS6. + # + SYSMODS += VBoxNetLwf + VBoxNetLwf_TEMPLATE = VBoxR0Drv + VBoxNetLwf_INST = $(INST_VBOXNETFLT) + if defined(VBOX_SIGNING_MODE) + VBoxNetLwf_INSTTYPE = none + VBoxNetLwf_DEBUG_INSTTYPE = both + endif + VBoxNetLwf_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetLwf_INCS = . + VBoxNetLwf_SOURCES = VBoxNetFlt.c VBoxNetFlt.rc + VBoxNetLwf_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetLwf_SOURCES.win = \ + win/drv/VBoxNetLwf-win.cpp + # with WINDDKWLH the WIN9X_COMPAT_SPINLOCK is needed to avoid inline declaration of KeInitializeSpinLock + # otherwise the linker would complain about dumplicate _KeInitializeSpinLock@4 definition + # in ntoskrnl.lib and our object files + VBoxNetLwf_DEFS.win += WIN9X_COMPAT_SPINLOCK=1 NDISLWF=1 DBG=1 + # VBoxNetLwf_DEFS.win += VBOXNETFLT_STATIC_CONFIG + # VBoxNetLwf_DEFS.win += VBOXNETFLT_NO_PACKET_QUEUE + # VBoxNetLwf_DEFS.win += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetLwf_DEFS.win += NDIS60=1 + # ifdef VBOX_LOOPBACK_USEFLAGS + # VBoxNetLwf_DEFS.win += VBOX_LOOPBACK_USEFLAGS + # endif + # VBoxNetLwf_SOURCES = VBoxNetLwf.c + VBoxNetLwf_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetLwf_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetLwf_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ndis.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/netio.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetLwf_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + # + # VBoxNetLwf installation. + # + INSTALLS.win += VBoxNetLwf-inf + VBoxNetLwf-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetLwf-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.inf + VBoxNetLwf-inf_CLEAN = $(VBoxNetLwf-inf_SOURCES) + VBoxNetLwf-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetLwfCat.dir + + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetLwf.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetLwf-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetLwf-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.sys \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.cat \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.cat=>VBoxNetLwf-PreW10.cat + + # $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwfNobj.dll \ + + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.sys: $$(VBoxNetLwf_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + #$(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwfNobj.dll: $$(VBoxNetLwfNobj_1_TARGET) | $$(dir $$@) + # $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.cat: \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.sys \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetLwf-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # signing + + + + # + # WinNetConfig - static library with host network interface config API (for the installer) + # + LIBRARIES.win += WinNetConfigSharedStatic + WinNetConfigSharedStatic_TEMPLATE = VBoxR3StaticDllNoAsan + WinNetConfigSharedStatic_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) + WinNetConfigSharedStatic_DEFS = _UNICODE UNICODE + WinNetConfigSharedStatic_SOURCES = \ + win/cfg/VBoxNetCfg.cpp \ + ../../Main/glue/string.cpp + + # Version for DLLs: + LIBRARIES.win += WinNetConfigDll + WinNetConfigDll_TEMPLATE = VBoxR3Dll + WinNetConfigDll_EXTENDS = WinNetConfigSharedStatic + + # Version for EXEs: + LIBRARIES.win += WinNetConfigExe + WinNetConfigExe_TEMPLATE = VBoxR3Exe + WinNetConfigExe_EXTENDS = WinNetConfigSharedStatic + + + # + # Template for NetFltInstall and friends. + # + TEMPLATE_VBoxNetFltR3 = Template for NetFltInstall, NetFltUninstall, NetAdpInstall, NetAdpUninstall, ++ + TEMPLATE_VBoxNetFltR3_EXTENDS = VBoxR3Exe + TEMPLATE_VBoxNetFltR3_SDKS = $(TEMPLATE_VBoxR3Exe_SDKS) ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBoxNtDll VBoxWinNewDevLib + TEMPLATE_VBoxNetFltR3_LIBS = $(TEMPLATE_VBoxR3Exe_LIBS) \ + $(WinNetConfigExe_1_TARGET) \ + $(PATH_STAGE_LIB)/VBoxDrvCfgExe$(VBOX_SUFF_LIB) \ + $(LIB_RUNTIME) \ + $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/comsupp.lib \ + $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/WbemUuid.Lib + + # + # NetFltInstall + # + PROGRAMS.win += NetFltInstall + NetFltInstall_TEMPLATE = VBoxNetFltR3 + NetFltInstall_SOURCES = win/tools/VBoxNetFltInstall.cpp + + # + # NetFltUninstall + # + PROGRAMS.win += NetFltUninstall + NetFltUninstall_TEMPLATE = VBoxNetFltR3 + NetFltUninstall_SOURCES = win/tools/VBoxNetFltUninstall.cpp + + # + # NetAdpInstall + # + PROGRAMS.win += NetAdpInstall + NetAdpInstall_TEMPLATE = VBoxNetFltR3 + NetAdpInstall_SOURCES = win/tools/VBoxNetAdpInstall.cpp + + # + # NetAdpUninstall + # + PROGRAMS.win += NetAdpUninstall + NetAdpUninstall_TEMPLATE = VBoxNetFltR3 + NetAdpUninstall_SOURCES = win/tools/VBoxNetAdpUninstall.cpp + + # + # NetAdp6Install + # + PROGRAMS.win += NetAdp6Install + NetAdp6Install_TEMPLATE = VBoxNetFltR3 + NetAdp6Install_SOURCES = win/tools/VBoxNetAdpInstall.cpp + NetAdp6Install_DEFS = NDIS60=1 + + # + # NetAdp6Uninstall + # + PROGRAMS.win += NetAdp6Uninstall + NetAdp6Uninstall_TEMPLATE = VBoxNetFltR3 + NetAdp6Uninstall_SOURCES = win/tools/VBoxNetAdpUninstall.cpp + NetAdp6Uninstall_DEFS = NDIS60=1 + + # + # NetLwfInstall + # + PROGRAMS.win += NetLwfInstall + NetLwfInstall_TEMPLATE = VBoxNetFltR3 + NetLwfInstall_SOURCES = win/tools/VBoxNetLwfInstall.cpp + + # + # NetLwfUninstall + # + PROGRAMS.win += NetLwfUninstall + NetLwfUninstall_TEMPLATE = VBoxNetFltR3 + NetLwfUninstall_SOURCES = win/tools/VBoxNetLwfUninstall.cpp + + # + # VBoxNetFltNobj + # + DLLS.win += VBoxNetFltNobj + VBoxNetFltNobj_TEMPLATE = VBoxR3StaticDll + if defined(VBOX_SIGNING_MODE) + VBoxNetFltNobj_INSTTYPE = none + VBoxNetFltNobj_DEBUG_INSTTYPE = both + endif + VBoxNetFltNobj_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) + VBoxNetFltNobj_DEFS = WIN32 _ATL_STATIC_REGISTRY + VBoxNetFltNobj_INCS = \ + $(VBoxNetFltNobj_0_OUTDIR) + VBoxNetFltNobj_SOURCES = \ + win/nobj/VBoxNetFltNobj.cpp \ + win/nobj/VBoxNetFltNobj.def \ + win/nobj/VBoxNetFltNobj.rc + #VBoxNetFltNobj_INTERMEDIATES = + VBoxNetFltNobj_DEPS = \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_i.c \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_p.c \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.h \ + $(VBoxNetFltNobj_0_OUTDIR)/dlldata.c \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.tlb + VBoxNetFltNobj_CLEAN = $(VBoxNetFltNobj_DEPS) + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_i.c \ + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_p.c \ + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.h \ + + $$(VBoxNetFltNobj_0_OUTDIR)/dlldata.c \ + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.tlb: \ + $(PATH_SUB_CURRENT)/win/nobj/VBoxNetFltNobjT.idl \ + | $$(dir $$@) + $(VBOX_MIDL_REDIRECT) $(VBOX_WIN_MIDL) /nologo \ + /out $(call VBOX_FN_MAKE_WIN_PATH,$(VBoxNetFltNobj_0_OUTDIR)) \ + /cpp_cmd $(VBOX_MIDL_CPP_CMD) \ + $(qforeachfile unq, incdir, $(SDK_$(VBOX_WINDDK)_INCS) $(SDK_$(VBOX_WINPSDK)_INCS),/I $(quote-sh $(incdir))) \ + $(call VBOX_FN_MAKE_WIN_PATH,$<) + $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.h) + $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_i.c) + $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_p.c) + + + # + # VBoxNetAdp.sys - The VirtualBox Adapter miniport driver. + # + SYSMODS.win += VBoxNetAdp + VBoxNetAdp_TEMPLATE = VBoxR0Drv + #VBoxNetAdp_INST = $(INST_VBOXNETADP) + if defined(VBOX_SIGNING_MODE) + VBoxNetAdp_INSTTYPE.win = none + VBoxNetAdp_DEBUG_INSTTYPE.win = both + endif + VBoxNetAdp_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetAdp_INCS := $(PATH_SUB_CURRENT) + VBoxNetAdp_SDKS = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetAdp_SOURCES = \ + VBoxNetFlt.c \ + win/drv/VBoxNetFltM-win.cpp \ + win/drv/VBoxNetFltRt-win.cpp \ + win/drv/VBoxNetFlt-win.rc + VBoxNetAdp_DEFS += VBOXNETFLT_STATIC_CONFIG VBOXNETADP + VBoxNetAdp_DEFS.win += VBOXNETFLT_NO_PACKET_QUEUE + VBoxNetAdp_DEFS += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetAdp_DEFS += NDIS50_MINIPORT=1 NDIS50=1 + VBoxNetAdp_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetAdp_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetAdp_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ndis.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetAdp_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + + + INSTALLS.win += VBoxNetAdp-inf + VBoxNetAdp-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetAdp-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.inf + VBoxNetAdp-inf_CLEAN = $(VBoxNetAdp-inf_SOURCES) + VBoxNetAdp-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetAdpCat.dir + + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetAdp.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetAdp-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetAdp-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.sys \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.cat + + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.sys: $$(VBoxNetAdp_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.cat: \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.sys \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetFlt-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # ifdef VBOX_SIGNING_MODE + + endif #ifeq ($(KBUILD_TARGET), win) + +else if1of ($(KBUILD_TARGET), solaris freebsd) ## @todo merge this with the mixed case stuff. + # + # vboxnetflt(.ko/.o/) - The lower case driver. + # Note! On Solaris the name has to be <= 8 chars long. + # The DEBUG_HASH* stuff is for CONFIG_DYNAMIC_DEBUG-enabled kernels + # + ifdef VBOX_WITH_VBOXDRV + SYSMODS += vboxnetflt + vboxnetflt_TEMPLATE = VBoxR0Drv + vboxnetflt_NAME.solaris = vboxflt + vboxnetflt_DEFS = IN_RT_R0 + vboxnetflt_DEPS.solaris += $(VBOX_SVN_REV_KMK) + vboxnetflt_INCS := \ + $(PATH_SUB_CURRENT) + vboxnetflt_LDFLAGS.solaris += -N drv/vboxdrv -N misc/ctf + vboxnetflt_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + ## @todo vboxflt should resolves all the IPRT bits from vboxdrv. + #vboxnetflt_LIBS += \ + # $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + vboxnetflt_SOURCES.solaris = solaris/VBoxNetFlt-solaris.c + vboxnetflt_SOURCES.freebsd = freebsd/VBoxNetFlt-freebsd.c + vboxnetflt_SOURCES = VBoxNetFlt.c + solaris/VBoxNetFlt-solaris.c_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + + ifdef VBOX_WITH_NETFLT_CROSSBOW + SYSMODS += vboxnetbow + vboxnetbow_TEMPLATE = VBoxR0Drv + vboxnetbow_NAME = vboxbow + vboxnetbow_DEFS = vboxnetflt_DEFS VBOX_WITH_NETFLT_CROSSBOW + vboxnetbow_INCS := $(PATH_SUB_CURRENT) + vboxnetbow_LDFLAGS += -N drv/vboxdrv -N drv/vnic -N misc/mac -N misc/dls + vboxnetbow_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + vboxnetbow_SOURCES.solaris = solaris/VBoxNetFltBow-solaris.c + vboxnetbow_SOURCES = VBoxNetFlt.c + solaris/VBoxNetFltBow-solaris.c_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + endif # VBOX_WITH_NETFLT_CROSSBOW + endif # VBOX_WITH_VBOXDRV +endif # to be removed. + + +ifeq ($(KBUILD_TARGET),linux) + # + # Install source files for compilation on Linux. + # files_vboxnetflt defines VBOX_VBOXNETFLT_SOURCES. + # + include $(PATH_SUB_CURRENT)/linux/files_vboxnetflt + INSTALLS += VBoxNetFlt-src VBoxNetFlt-sh + VBoxNetFlt-src_INST = bin/src/vboxnetflt/ + VBoxNetFlt-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXNETFLT_SOURCES)) \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile + VBoxNetFlt-src_CLEAN = \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile \ + $(PATH_TARGET)/VBoxNetFlt-src-1.dep + + # Scripts needed for building the kernel module. + + includedep $(PATH_TARGET)/VBoxNetFlt-src-1.dep + $$(VBoxNetFlt-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/linux/Makefile \ + $$(if $$(eq $$(VBoxNetFlt/linux/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@ $< + else + $(QUIET)$(CP) -f $< $@ + endif + %$(QUIET2)$(RM) -f -- $(PATH_TARGET)/VBoxNetFlt-src-1.dep + %$(QUIET2)$(APPEND) '$(PATH_TARGET)/VBoxNetFlt-src-1.dep' 'VBoxNetFlt/linux/Makefile_VBOX_HARDENED=$(VBOX_WITH_HARDENING)' + + # + # Build test for the linux host kernel modules. + # + $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,VBoxNetFlt-src,vboxdrv-src,) +endif # linux + +# Gross hack for FreeBSD 7, should figure this out properly +## @todo Properly generate opt_netgraph.h +ifeq ($(KBUILD_TARGET),freebsd) + vboxnetflt_DEPS.freebsd += $(PATH_OUT)/opt_netgraph.h + $(PATH_OUT)/opt_netgraph.h: + echo > $(PATH_OUT)/opt_netgraph.h + + # + # Install source files for compilation on FreeBSD. + # files_vboxnetflt defines VBOX_VBOXNETFLT_SOURCES. + # + include $(PATH_SUB_CURRENT)/freebsd/files_vboxnetflt + INSTALLS += VBoxNetFlt-src + VBoxNetFlt-src_INST = bin/src/vboxnetflt/ + VBoxNetFlt-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXNETFLT_SOURCES)) \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile + VBoxNetFlt-src_CLEAN = \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile + + $$(VBoxNetFlt-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/freebsd/Makefile \ + $$(if $$(eq $$(VBoxNetFlt/freebsd/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + $(QUIET)$(CP) -f $< $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@.tmp $@ + ${QUIET}$(MV) -f $@.tmp $@ + endif + ifndef VBOX_WITH_NETFLT_VIMAGE + $(QUIET)$(SED) -e "s;-DVIMAGE;;g" --output $@.tmp $@ + ${QUIET}$(MV) -f $@.tmp $@ + endif +endif # freebsd + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c new file mode 100644 index 00000000..b80af59e --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c @@ -0,0 +1,1591 @@ +/* $Id: VBoxNetFlt.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), Common 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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +/** @page pg_netflt VBoxNetFlt - Network Interface Filter + * + * This is a kernel module that attaches to a real interface on the host and + * filters and injects packets. + * + * In the big picture we're one of the three trunk interface on the internal + * network, the one named "NIC Filter Driver": @image html Networking_Overview.gif + * + * + * @section sec_netflt_locking Locking and Potential Races + * + * The main challenge here is to make sure the netfilter and internal network + * instances won't be destroyed while someone is calling into them. + * + * The main calls into or out of of the filter driver are: + * - Send. + * - Async send completion (not implemented yet) + * - Release by the internal network. + * - Receive. + * - Disappearance of the host networking interface. + * - Reappearance of the host networking interface. + * + * The latter two calls are can be caused by driver unloading/loading or the + * device being physical unplugged (e.g. a USB network device). Actually, the + * unload scenario must fervently be prevent as it will cause panics because the + * internal network will assume the trunk is around until it releases it. + * @todo Need to figure which host allow unloading and block/fix it. + * + * Currently the netfilter instance lives until the internal network releases + * it. So, it is the internal networks responsibility to make sure there are no + * active calls when it releases the trunk and destroys the network. The + * netfilter assists in this by providing INTNETTRUNKIFPORT::pfnSetState and + * INTNETTRUNKIFPORT::pfnWaitForIdle. The trunk state is used to enable/disable + * promiscuous mode on the hardware NIC (or similar activation) as well + * indicating that disconnect is imminent and no further calls shall be made + * into the internal network. After changing the state to disconnecting and + * prior to invoking INTNETTRUNKIFPORT::pfnDisconnectAndRelease, the internal + * network will use INTNETTRUNKIFPORT::pfnWaitForIdle to wait for any still + * active calls to complete. + * + * The netfilter employs a busy counter and an internal state in addition to the + * public trunk state. All these variables are protected using a spinlock. + * + * + * @section sec_netflt_msc Locking / Sequence Diagrams - OBSOLETE + * + * !OBSOLETE! - THIS WAS THE OLD APPROACH! + * + * This secion contains a few sequence diagrams describing the problematic + * transitions of a host interface filter instance. + * + * The thing that makes it all a bit problematic is that multiple events may + * happen at the same time, and that we have to be very careful to avoid + * deadlocks caused by mixing our locks with the ones in the host kernel. The + * main events are receive, send, async send completion, disappearance of the + * host networking interface and its reappearance. The latter two events are + * can be caused by driver unloading/loading or the device being physical + * unplugged (e.g. a USB network device). + * + * The strategy for dealing with these issues are: + * - Use a simple state machine. + * - Require the user (IntNet) to serialize all its calls to us, + * while at the same time not owning any lock used by any of the + * the callbacks we might call on receive and async send completion. + * - Make sure we're 100% idle before disconnecting, and have a + * disconnected status on both sides to fend off async calls. + * - Protect the host specific interface handle and the state variables + * using a spinlock. + * + * + * @subsection subsec_netflt_msc_dis_rel Disconnect from the network and release - OBSOLETE + * + * @msc + * VM, IntNet, NetFlt, Kernel, Wire; + * + * VM->IntNet [label="pkt0", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * IntNet=>NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * Kernel->Wire [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * + * --- [label="Suspending the trunk interface"]; + * IntNet=>IntNet [label="Lock Network"]; + * + * Wire->Kernel [label="pkt1 - racing us", linecolor="red", textcolor="red"]; + * Kernel=>>NetFlt [label="pkt1 - racing us", linecolor="red", textcolor="red"]; + * NetFlt=>>IntNet [label="pkt1 recv - blocks", linecolor="red", textcolor="red"]; + * + * IntNet=>IntNet [label="Mark Trunk Suspended"]; + * IntNet=>IntNet [label="Unlock Network"]; + * + * IntNet=>NetFlt [label="pfnSetActive(false)"]; + * NetFlt=>NetFlt [label="Mark inactive (atomic)"]; + * IntNet<<NetFlt; + * IntNet=>NetFlt [label="pfnWaitForIdle(forever)"]; + * + * IntNet=>>NetFlt [label="pkt1 to host", linecolor="red", textcolor="red"]; + * NetFlt=>>Kernel [label="pkt1 to host", linecolor="red", textcolor="red"]; + * + * Kernel<-Wire [label="pkt0 on wire", linecolor="green", textcolor="green"]; + * NetFlt<<Kernel [label="pkt0 on wire", linecolor="green", textcolor="green"]; + * IntNet<<=NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet<<=IntNet [label="Lock Net, free SG, Unlock Net", linecolor="green", textcolor="green"]; + * IntNet>>NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * NetFlt<-NetFlt [label="idle", linecolor="green", textcolor="green"]; + * + * IntNet<<NetFlt [label="idle (pfnWaitForIdle)"]; + * + * Wire->Kernel [label="pkt2", linecolor="red", textcolor="red"]; + * Kernel=>>NetFlt [label="pkt2", linecolor="red", textcolor="red"]; + * NetFlt=>>Kernel [label="pkt2 to host", linecolor="red", textcolor="red"]; + * + * VM->IntNet [label="pkt3", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> drop", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * + * --- [label="The trunk interface is idle now, disconnect it"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Unlink Trunk"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pfnDisconnectAndRelease"]; + * NetFlt=>Kernel [label="iflt_detach"]; + * NetFlt<<=Kernel [label="iff_detached"]; + * NetFlt>>Kernel [label="iff_detached"]; + * NetFlt<<Kernel [label="iflt_detach"]; + * NetFlt=>NetFlt [label="Release"]; + * IntNet<<NetFlt [label="pfnDisconnectAndRelease"]; + * + * @endmsc + * + * + * + * @subsection subsec_netflt_msc_hif_rm Host Interface Removal - OBSOLETE + * + * The ifnet_t (pIf) is a tricky customer as any reference to it can potentially + * race the filter detaching. The simple way of solving it on Darwin is to guard + * all access to the pIf member with a spinlock. The other host systems will + * probably have similar race conditions, so the spinlock is a generic thing. + * + * @msc + * VM, IntNet, NetFlt, Kernel; + * + * VM->IntNet [label="pkt0", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * IntNet=>NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="ifnet_reference w/ spinlock", linecolor="green", textcolor="green" ]; + * NetFlt<<Kernel [label="ifnet_reference", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="pkt0 to wire (blocks)", linecolor="green", textcolor="green" ]; + * + * --- [label="The host interface is being disconnected"]; + * Kernel->NetFlt [label="iff_detached"]; + * NetFlt=>Kernel [label="ifnet_release w/ spinlock"]; + * NetFlt<<Kernel [label="ifnet_release"]; + * NetFlt=>NetFlt [label="fDisconnectedFromHost=true"]; + * NetFlt>>Kernel [label="iff_detached"]; + * + * NetFlt<<Kernel [label="dropped", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="Acquire spinlock", linecolor="green", textcolor="green"]; + * NetFlt=>Kernel [label="ifnet_release", linecolor="green", textcolor="green"]; + * NetFlt<<Kernel [label="ifnet_release", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="pIf=NULL", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="Release spinlock", linecolor="green", textcolor="green"]; + * IntNet<=NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet>>NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet<<NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * + * @endmsc + * + * + * + * @subsection subsec_netflt_msc_hif_rd Host Interface Rediscovery - OBSOLETE + * + * The rediscovery is performed when we receive a send request and a certain + * period have elapsed since the last attempt, i.e. we're polling it. We + * synchronize the rediscovery with disconnection from the internal network + * by means of the pfnWaitForIdle call, so no special handling is required. + * + * @msc + * VM2, VM1, IntNet, NetFlt, Kernel, Wire; + * + * --- [label="Rediscovery conditions are not met"]; + * VM1->IntNet [label="pkt0"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Route packet -> wire"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pkt0 to wire"]; + * NetFlt=>NetFlt [label="Read pIf(==NULL) w/ spinlock"]; + * IntNet<<NetFlt [label="pkt0 to wire (dropped)"]; + * + * --- [label="Rediscovery conditions"]; + * VM1->IntNet [label="pkt1"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Route packet -> wire"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pkt1 to wire"]; + * NetFlt=>NetFlt [label="Read pIf(==NULL) w/ spinlock"]; + * NetFlt=>NetFlt [label="fRediscoveryPending=true w/ spinlock"]; + * NetFlt=>Kernel [label="ifnet_find_by_name"]; + * NetFlt<<Kernel [label="ifnet_find_by_name (success)"]; + * + * VM2->IntNet [label="pkt2", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Lock Network", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Unlock Network", linecolor="red", textcolor="red"]; + * IntNet=>NetFlt [label="pkt2 to wire", linecolor="red", textcolor="red"]; + * NetFlt=>NetFlt [label="!pIf || fRediscoveryPending (w/ spinlock)", linecolor="red", textcolor="red"]; + * IntNet<<NetFlt [label="pkt2 to wire (dropped)", linecolor="red", textcolor="red"]; + + * NetFlt=>Kernel [label="iflt_attach"]; + * NetFlt<<Kernel [label="iflt_attach (success)"]; + * NetFlt=>NetFlt [label="Acquire spinlock"]; + * NetFlt=>NetFlt [label="Set pIf and update flags"]; + * NetFlt=>NetFlt [label="Release spinlock"]; + * + * NetFlt=>Kernel [label="pkt1 to wire"]; + * Kernel->Wire [label="pkt1 to wire"]; + * NetFlt<<Kernel [label="pkt1 to wire"]; + * IntNet<<NetFlt [label="pkt1 to wire"]; + * + * + * @endmsc + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV +#include "VBoxNetFltInternal.h" + +#include <VBox/sup.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/spinlock.h> +#include <iprt/uuid.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define IFPORT_2_VBOXNETFLTINS(pIfPort) \ + ( (PVBOXNETFLTINS)((uint8_t *)(pIfPort) - RT_UOFFSETOF(VBOXNETFLTINS, MyPort)) ) + + +AssertCompileMemberSize(VBOXNETFLTINS, enmState, sizeof(uint32_t)); + +/** + * Sets the enmState member atomically. + * + * Used for all updates. + * + * @param pThis The instance. + * @param enmNewState The new value. + */ +DECLINLINE(void) vboxNetFltSetState(PVBOXNETFLTINS pThis, VBOXNETFTLINSSTATE enmNewState) +{ + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmState, enmNewState); +} + + +/** + * Gets the enmState member atomically. + * + * Used for all reads. + * + * @returns The enmState value. + * @param pThis The instance. + */ +DECLINLINE(VBOXNETFTLINSSTATE) vboxNetFltGetState(PVBOXNETFLTINS pThis) +{ + return (VBOXNETFTLINSSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pThis->enmState); +} + + +/** + * Finds a instance by its name, the caller does the locking. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pGlobals The globals. + * @param pszName The name of the instance. + */ +static PVBOXNETFLTINS vboxNetFltFindInstanceLocked(PVBOXNETFLTGLOBALS pGlobals, const char *pszName) +{ + PVBOXNETFLTINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur; pCur = pCur->pNext) + if (!strcmp(pszName, pCur->szName)) + return pCur; + return NULL; +} + + +/** + * Finds a instance by its name, will request the mutex. + * + * No reference to the instance is retained, we're assuming the caller to + * already have one but just for some reason doesn't have the pointer to it. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pGlobals The globals. + * @param pszName The name of the instance. + */ +DECLHIDDEN(PVBOXNETFLTINS) vboxNetFltFindInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName) +{ + PVBOXNETFLTINS pRet; + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, NULL); + + pRet = vboxNetFltFindInstanceLocked(pGlobals, pszName); + + rc = RTSemFastMutexRelease(pGlobals->hFastMtx); + AssertRC(rc); + return pRet; +} + + +/** + * Unlinks an instance from the chain. + * + * @param pGlobals The globals. + * @param pToUnlink The instance to unlink. + */ +static void vboxNetFltUnlinkLocked(PVBOXNETFLTGLOBALS pGlobals, PVBOXNETFLTINS pToUnlink) +{ + if (pGlobals->pInstanceHead == pToUnlink) + pGlobals->pInstanceHead = pToUnlink->pNext; + else + { + PVBOXNETFLTINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur; pCur = pCur->pNext) + if (pCur->pNext == pToUnlink) + { + pCur->pNext = pToUnlink->pNext; + break; + } + Assert(pCur); + } + pToUnlink->pNext = NULL; +} + + +/** + * Performs interface rediscovery if it was disconnected from the host. + * + * @returns true if successfully rediscovered and connected, false if not. + * @param pThis The instance. + */ +static bool vboxNetFltMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + uint64_t Now; + bool fRediscovered; + bool fDoIt; + + /* + * Don't do rediscovery if we're called with preemption disabled. + * + * Note! This may cause trouble if we're always called with preemption + * disabled and vboxNetFltOsMaybeRediscovered actually does some real + * work. For the time being though, only Darwin and FreeBSD depends + * on these call outs and neither supports sending with preemption + * disabled. + */ + if (!RTThreadPreemptIsEnabled(NIL_RTTHREAD)) + return false; + + /* + * Rediscovered already? Time to try again? + */ + Now = RTTimeNanoTS(); + RTSpinlockAcquire(pThis->hSpinlock); + + fRediscovered = !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); + fDoIt = !fRediscovered + && !ASMAtomicUoReadBool(&pThis->fRediscoveryPending) + && Now - ASMAtomicUoReadU64(&pThis->NanoTSLastRediscovery) > UINT64_C(5000000000); /* 5 sec */ + if (fDoIt) + ASMAtomicWriteBool(&pThis->fRediscoveryPending, true); + + RTSpinlockRelease(pThis->hSpinlock); + + /* + * Call the OS specific code to do the job. + * Update the state when the call returns, that is everything except for + * the fDisconnectedFromHost flag which the OS specific code shall set. + */ + if (fDoIt) + { + fRediscovered = vboxNetFltOsMaybeRediscovered(pThis); + + Assert(!fRediscovered || !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)); + + ASMAtomicUoWriteU64(&pThis->NanoTSLastRediscovery, RTTimeNanoTS()); + ASMAtomicWriteBool(&pThis->fRediscoveryPending, false); + + if (fRediscovered) + /** @todo this isn't 100% serialized. */ + vboxNetFltPortOsSetActive(pThis, pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE); + } + + return fRediscovered; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnXmit + */ +static DECLCALLBACK(int) vboxNetFltPortXmit(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc = VINF_SUCCESS; + + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pSG); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, VERR_INVALID_STATE); + + /* + * Do a busy retain and then make sure we're connected to the interface + * before invoking the OS specific code. + */ + if (RT_LIKELY(vboxNetFltTryRetainBusyActive(pThis))) + { + if ( !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost) + || vboxNetFltMaybeRediscovered(pThis)) + rc = vboxNetFltPortOsXmit(pThis, pvIfData, pSG, fDst); + vboxNetFltRelease(pThis, true /* fBusy */); + } + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnWaitForIdle + */ +static DECLCALLBACK(int) vboxNetFltPortWaitForIdle(PINTNETTRUNKIFPORT pIfPort, uint32_t cMillies) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, VERR_INVALID_STATE); + AssertReturn(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING, VERR_INVALID_STATE); + + /* + * Go to sleep on the semaphore after checking the busy count. + */ + vboxNetFltRetain(pThis, false /* fBusy */); + + rc = VINF_SUCCESS; + while (pThis->cBusy && RT_SUCCESS(rc)) + rc = RTSemEventWait(pThis->hEventIdle, cMillies); /** @todo make interruptible? */ + + vboxNetFltRelease(pThis, false /* fBusy */); + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnSetState + */ +static DECLCALLBACK(INTNETTRUNKIFSTATE) vboxNetFltPortSetState(PINTNETTRUNKIFPORT pIfPort, INTNETTRUNKIFSTATE enmState) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + INTNETTRUNKIFSTATE enmOldTrunkState; + + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pThis->pGlobals); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, INTNETTRUNKIFSTATE_INVALID); + AssertReturn(enmState > INTNETTRUNKIFSTATE_INVALID && enmState < INTNETTRUNKIFSTATE_END, + INTNETTRUNKIFSTATE_INVALID); + + /* + * Take the lock and change the state. + */ + RTSpinlockAcquire(pThis->hSpinlock); + enmOldTrunkState = pThis->enmTrunkState; + if (enmOldTrunkState != enmState) + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmTrunkState, enmState); + RTSpinlockRelease(pThis->hSpinlock); + + /* + * If the state change indicates that the trunk has become active or + * inactive, call the OS specific part so they can work the promiscuous + * settings and such. + * Note! The caller makes sure there are no concurrent pfnSetState calls. + */ + if ((enmOldTrunkState == INTNETTRUNKIFSTATE_ACTIVE) != (enmState == INTNETTRUNKIFSTATE_ACTIVE)) + vboxNetFltPortOsSetActive(pThis, (enmState == INTNETTRUNKIFSTATE_ACTIVE)); + + return enmOldTrunkState; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnNotifyMacAddress + */ +static DECLCALLBACK(void) vboxNetFltPortNotifyMacAddress(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PCRTMAC pMac) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + vboxNetFltPortOsNotifyMacAddress(pThis, pvIfData, pMac); + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnConnectInterface + */ +static DECLCALLBACK(int) vboxNetFltPortConnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIf, void **ppvIfData) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + rc = vboxNetFltPortOsConnectInterface(pThis, pvIf, ppvIfData); + vboxNetFltRelease(pThis, false /* fBusy */); + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectInterface + */ +static DECLCALLBACK(void) vboxNetFltPortDisconnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIfData) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + rc = vboxNetFltPortOsDisconnectInterface(pThis, pvIfData); + vboxNetFltRelease(pThis, false /* fBusy */); + AssertRC(rc); /** @todo fix vboxNetFltPortOsDisconnectInterface. */ +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectAndRelease + */ +static DECLCALLBACK(void) vboxNetFltPortDisconnectAndRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + + /* + * Serious paranoia. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected); + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cBusy); + + /* + * Disconnect and release it. + */ + RTSpinlockAcquire(pThis->hSpinlock); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Disconnecting); + RTSpinlockRelease(pThis->hSpinlock); + + vboxNetFltOsDisconnectIt(pThis); + pThis->pSwitchPort = NULL; + +#ifdef VBOXNETFLT_STATIC_CONFIG + RTSpinlockAcquire(pThis->hSpinlock); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Unconnected); + RTSpinlockRelease(pThis->hSpinlock); +#endif + + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * Destroy a device that has been disconnected from the switch. + * + * @returns true if the instance is destroyed, false otherwise. + * @param pThis The instance to be destroyed. This is + * no longer valid when this function returns. + */ +static bool vboxNetFltDestroyInstance(PVBOXNETFLTINS pThis) +{ + PVBOXNETFLTGLOBALS pGlobals = pThis->pGlobals; + uint32_t cRefs = ASMAtomicUoReadU32((uint32_t volatile *)&pThis->cRefs); + int rc; + LogFlow(("vboxNetFltDestroyInstance: pThis=%p (%s)\n", pThis, pThis->szName)); + + /* + * Validate the state. + */ +#ifdef VBOXNETFLT_STATIC_CONFIG + Assert( vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Disconnecting + || vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Unconnected); +#else + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Disconnecting); +#endif + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cRefs); + Assert(!pThis->cBusy); + Assert(!pThis->pSwitchPort); + + /* + * Make sure the state is 'disconnecting' / 'destroying' and let the OS + * specific code do its part of the cleanup outside the mutex. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Disconnecting); + RTSemFastMutexRelease(pGlobals->hFastMtx); + + vboxNetFltOsDeleteInstance(pThis); + + /* + * Unlink the instance and free up its resources. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Destroyed); + vboxNetFltUnlinkLocked(pGlobals, pThis); + RTSemFastMutexRelease(pGlobals->hFastMtx); + + RTSemEventDestroy(pThis->hEventIdle); + pThis->hEventIdle = NIL_RTSEMEVENT; + RTSpinlockDestroy(pThis->hSpinlock); + pThis->hSpinlock = NIL_RTSPINLOCK; + RTMemFree(pThis); + + NOREF(cRefs); + + return true; +} + + +/** + * Releases a reference to the specified instance. + * + * This method will destroy the instance when the count reaches 0. + * It will also take care of decrementing the counter and idle wakeup. + * + * @param pThis The instance. + * @param fBusy Whether the busy counter should be decremented too. + */ +DECLHIDDEN(void) vboxNetFltRelease(PVBOXNETFLTINS pThis, bool fBusy) +{ + uint32_t cRefs; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Work the busy counter. + */ + if (fBusy) + { + cRefs = ASMAtomicDecU32(&pThis->cBusy); + if (!cRefs) + { + int rc = RTSemEventSignal(pThis->hEventIdle); + AssertRC(rc); + } + else + Assert(cRefs < UINT32_MAX / 2); + } + + /* + * The object reference counting. + */ + cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + vboxNetFltDestroyInstance(pThis); + else + Assert(cRefs < UINT32_MAX / 2); +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRelease + */ +static DECLCALLBACK(void) vboxNetFltPortRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * @callback_method_impl{FNINTNETTRUNKIFPORTRELEASEBUSY} + */ +DECL_HIDDEN_CALLBACK(void) vboxNetFltPortReleaseBusy(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRelease(pThis, true /*fBusy*/); +} + + +/** + * Retains a reference to the specified instance and a busy reference too. + * + * @param pThis The instance. + * @param fBusy Whether the busy counter should be incremented as well. + */ +DECLHIDDEN(void) vboxNetFltRetain(PVBOXNETFLTINS pThis, bool fBusy) +{ + uint32_t cRefs; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Retain the object. + */ + cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1 && cRefs < UINT32_MAX / 2); + + /* + * Work the busy counter. + */ + if (fBusy) + { + cRefs = ASMAtomicIncU32(&pThis->cBusy); + Assert(cRefs > 0 && cRefs < UINT32_MAX / 2); + } + + NOREF(cRefs); +} + + +/** + * Tries to retain the device as busy if the trunk is active. + * + * This is used before calling pfnRecv or pfnPreRecv. + * + * @returns true if we succeeded in retaining a busy reference to the active + * device. false if we failed. + * @param pThis The instance. + */ +DECLHIDDEN(bool) vboxNetFltTryRetainBusyActive(PVBOXNETFLTINS pThis) +{ + uint32_t cRefs; + bool fRc; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Do the retaining and checking behind the spinlock. + */ + RTSpinlockAcquire(pThis->hSpinlock); + fRc = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE; + if (fRc) + { + cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + + cRefs = ASMAtomicIncU32(&pThis->cBusy); + AssertMsg(cRefs >= 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + } + RTSpinlockRelease(pThis->hSpinlock); + + return fRc; +} + + +/** + * Tries to retain the device as busy if the trunk is not disconnecting. + * + * This is used before reporting stuff to the internal network. + * + * @returns true if we succeeded in retaining a busy reference to the active + * device. false if we failed. + * @param pThis The instance. + */ +DECLHIDDEN(bool) vboxNetFltTryRetainBusyNotDisconnected(PVBOXNETFLTINS pThis) +{ + uint32_t cRefs; + bool fRc; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Do the retaining and checking behind the spinlock. + */ + RTSpinlockAcquire(pThis->hSpinlock); + fRc = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE + || pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE; + if (fRc) + { + cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + + cRefs = ASMAtomicIncU32(&pThis->cBusy); + AssertMsg(cRefs >= 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + } + RTSpinlockRelease(pThis->hSpinlock); + + return fRc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRetain + */ +static DECLCALLBACK(void) vboxNetFltPortRetain(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRetain(pThis, false /* fBusy */); +} + + +/** + * Connects the instance to the specified switch port. + * + * Called while owning the lock. We're ASSUMING that the internal + * networking code is already owning an recursive mutex, so, there + * will be no deadlocks when vboxNetFltOsConnectIt calls back into + * it for setting preferences. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pSwitchPort The port on the internal network 'switch'. + * @param ppIfPort Where to return our port interface. + */ +static int vboxNetFltConnectIt(PVBOXNETFLTINS pThis, PINTNETTRUNKSWPORT pSwitchPort, PINTNETTRUNKIFPORT *ppIfPort) +{ + int rc; + + /* + * Validate state. + */ + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cBusy); +#ifdef VBOXNETFLT_STATIC_CONFIG + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Unconnected); +#else + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Initializing); +#endif + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE); + + /* + * Do the job. + * Note that we're calling the os stuff while owning the semaphore here. + */ + pThis->pSwitchPort = pSwitchPort; + rc = vboxNetFltOsConnectIt(pThis); + if (RT_SUCCESS(rc)) + { + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Connected); + *ppIfPort = &pThis->MyPort; + } + else + pThis->pSwitchPort = NULL; + + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE); + return rc; +} + + +/** + * Creates a new instance. + * + * The new instance will be in the suspended state in a dynamic config and in + * the inactive in a static one. + * + * Called without owning the lock, but will request is several times. + * + * @returns VBox status code. + * @param pGlobals The globals. + * @param pszName The instance name. + * @param pSwitchPort The port on the switch that we're connected with (dynamic only). + * @param fNoPromisc Do not attempt going into promiscuous mode. + * @param pvContext Context argument for vboxNetFltOsInitInstance. + * @param ppIfPort Where to store the pointer to our port interface (dynamic only). + */ +static int vboxNetFltNewInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PINTNETTRUNKSWPORT pSwitchPort, + bool fNoPromisc, void *pvContext, PINTNETTRUNKIFPORT *ppIfPort) +{ + /* + * Allocate and initialize a new instance before requesting the mutex. + * Note! That in a static config we'll initialize the trunk state to + * disconnecting and flip it in vboxNetFltFactoryCreateAndConnect + * later on. This better reflext the state and it works better with + * assertions in the destruction path. + */ + int rc; + size_t const cchName = strlen(pszName); + PVBOXNETFLTINS pNew = (PVBOXNETFLTINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(VBOXNETFLTINS, szName[cchName + 1])); + if (!pNew) + return VERR_INTNET_FLT_IF_FAILED; + AssertMsg(((uintptr_t)pNew & 7) == 0, ("%p LB %#x\n", pNew, RT_UOFFSETOF_DYN(VBOXNETFLTINS, szName[cchName + 1]))); + pNew->pNext = NULL; + pNew->MyPort.u32Version = INTNETTRUNKIFPORT_VERSION; + pNew->MyPort.pfnRetain = vboxNetFltPortRetain; + pNew->MyPort.pfnRelease = vboxNetFltPortRelease; + pNew->MyPort.pfnDisconnectAndRelease= vboxNetFltPortDisconnectAndRelease; + pNew->MyPort.pfnSetState = vboxNetFltPortSetState; + pNew->MyPort.pfnWaitForIdle = vboxNetFltPortWaitForIdle; + pNew->MyPort.pfnXmit = vboxNetFltPortXmit; + pNew->MyPort.pfnNotifyMacAddress = vboxNetFltPortNotifyMacAddress; + pNew->MyPort.pfnConnectInterface = vboxNetFltPortConnectInterface; + pNew->MyPort.pfnDisconnectInterface = vboxNetFltPortDisconnectInterface; + pNew->MyPort.u32VersionEnd = INTNETTRUNKIFPORT_VERSION; + pNew->pSwitchPort = pSwitchPort; + pNew->pGlobals = pGlobals; + pNew->hSpinlock = NIL_RTSPINLOCK; + pNew->enmState = kVBoxNetFltInsState_Initializing; +#ifdef VBOXNETFLT_STATIC_CONFIG + pNew->enmTrunkState = INTNETTRUNKIFSTATE_DISCONNECTING; +#else + pNew->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; +#endif + pNew->fDisconnectedFromHost = false; + pNew->fRediscoveryPending = false; + pNew->fDisablePromiscuous = fNoPromisc; + pNew->NanoTSLastRediscovery = INT64_MAX; + pNew->cRefs = 1; + pNew->cBusy = 0; + pNew->hEventIdle = NIL_RTSEMEVENT; + RT_BCOPY_UNFORTIFIED(pNew->szName, pszName, cchName + 1); + + rc = RTSpinlockCreate(&pNew->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxNetFltNewInstance"); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pNew->hEventIdle); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltOsPreInitInstance(pNew); + if (RT_SUCCESS(rc)) + { + /* + * Insert the instance into the chain, checking for + * duplicates first of course (race). + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + if (!vboxNetFltFindInstanceLocked(pGlobals, pszName)) + { + pNew->pNext = pGlobals->pInstanceHead; + pGlobals->pInstanceHead = pNew; + RTSemFastMutexRelease(pGlobals->hFastMtx); + + /* + * Call the OS specific initialization code. + */ + rc = vboxNetFltOsInitInstance(pNew, pvContext); + RTSemFastMutexRequest(pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXNETFLT_STATIC_CONFIG + /* + * Static instances are unconnected at birth. + */ + Assert(!pSwitchPort); + pNew->enmState = kVBoxNetFltInsState_Unconnected; + RTSemFastMutexRelease(pGlobals->hFastMtx); + *ppIfPort = &pNew->MyPort; + return rc; + +#else /* !VBOXNETFLT_STATIC_CONFIG */ + /* + * Connect it as well, the OS specific bits has to be done outside + * the lock as they may call back to into intnet. + */ + rc = vboxNetFltConnectIt(pNew, pSwitchPort, ppIfPort); + if (RT_SUCCESS(rc)) + { + RTSemFastMutexRelease(pGlobals->hFastMtx); + Assert(*ppIfPort == &pNew->MyPort); + return rc; + } + + /* Bail out (failed). */ + vboxNetFltOsDeleteInstance(pNew); +#endif /* !VBOXNETFLT_STATIC_CONFIG */ + } + vboxNetFltUnlinkLocked(pGlobals, pNew); + } + else + rc = VERR_INTNET_FLT_IF_BUSY; + RTSemFastMutexRelease(pGlobals->hFastMtx); + } + } + RTSemEventDestroy(pNew->hEventIdle); + } + RTSpinlockDestroy(pNew->hSpinlock); + } + + RTMemFree(pNew); + return rc; +} + + +#ifdef VBOXNETFLT_STATIC_CONFIG +/** + * Searches for the NetFlt instance by its name and creates the new one if not found. + * + * @returns VBox status code. + * @retval VINF_SUCCESS and *ppInstance if a new instance was created. + * @retval VINF_ALREADY_INITIALIZED and *ppInstance if an instance already exists. + * + * @param pGlobal Pointer to the globals. + * @param pszName The instance name. + * @param ppInstance Where to return the instance pointer on success. + * @param pvContext Context which needs to be passed along to vboxNetFltOsInitInstance. + */ +DECLHIDDEN(int) vboxNetFltSearchCreateInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PVBOXNETFLTINS *ppInstance, void *pvContext) +{ + PINTNETTRUNKIFPORT pIfPort; + PVBOXNETFLTINS pCur; + VBOXNETFTLINSSTATE enmState; + int rc; + + *ppInstance = NULL; + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + + /* + * Look for an existing instance in the list. + * + * There might be an existing one in the list if the driver was unbound + * while it was connected to an internal network. We're running into + * a destruction race that is a bit similar to the one in + * vboxNetFltFactoryCreateAndConnect, only the roles are reversed + * and we're not in a position to back down. Instead of backing down + * we'll delay a bit giving the other thread time to complete the + * destructor. + */ + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); + while (pCur) + { + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + if (cRefs > 1) + { + enmState = vboxNetFltGetState(pCur); + switch (enmState) + { + case kVBoxNetFltInsState_Unconnected: + case kVBoxNetFltInsState_Connected: + case kVBoxNetFltInsState_Disconnecting: + if (pCur->fDisconnectedFromHost) + { + /* Wait for it to exit the transitional disconnecting + state. It might otherwise be running the risk of + upsetting the OS specific code... */ + /** @todo This reconnect stuff should be serialized correctly for static + * devices. Shouldn't it? In the dynamic case we're using the INTNET + * outbound thunk lock, but that doesn't quite cut it here, or does + * it? We could either transition to initializing or make a callback + * while owning the mutex here... */ + if (enmState == kVBoxNetFltInsState_Disconnecting) + { + do + { + RTSemFastMutexRelease(pGlobals->hFastMtx); + RTThreadSleep(2); /* (2ms) */ + RTSemFastMutexRequest(pGlobals->hFastMtx); + enmState = vboxNetFltGetState(pCur); + } + while (enmState == kVBoxNetFltInsState_Disconnecting); + AssertMsg(enmState == kVBoxNetFltInsState_Unconnected, ("%d\n", enmState)); + Assert(pCur->fDisconnectedFromHost); + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + *ppInstance = pCur; + return VINF_ALREADY_INITIALIZED; + } + /* fall thru */ + + default: + { + bool fDfH = pCur->fDisconnectedFromHost; + RTSemFastMutexRelease(pGlobals->hFastMtx); + vboxNetFltRelease(pCur, false /* fBusy */); + LogRel(("VBoxNetFlt: Huh? An instance of '%s' already exists! [pCur=%p cRefs=%d fDfH=%RTbool enmState=%d]\n", + pszName, pCur, cRefs - 1, fDfH, enmState)); + *ppInstance = NULL; + return VERR_INTNET_FLT_IF_BUSY; + } + } + } + + /* Zero references, it's being destroyed. Delay a bit so the destructor + can finish its work and try again. (vboxNetFltNewInstance will fail + with duplicate name if we don't.) */ +# ifdef RT_STRICT + Assert(cRefs == 1); + enmState = vboxNetFltGetState(pCur); + AssertMsg( enmState == kVBoxNetFltInsState_Unconnected + || enmState == kVBoxNetFltInsState_Disconnecting + || enmState == kVBoxNetFltInsState_Destroyed, ("%d\n", enmState)); +# endif + ASMAtomicDecU32(&pCur->cRefs); + RTSemFastMutexRelease(pGlobals->hFastMtx); + RTThreadSleep(2); /* (2ms) */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + + /* try again */ + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + + /* + * Try create a new instance. + * (fNoPromisc is overridden in the vboxNetFltFactoryCreateAndConnect path, so pass true here.) + */ + rc = vboxNetFltNewInstance(pGlobals, pszName, NULL, true /* fNoPromisc */, pvContext, &pIfPort); + if (RT_SUCCESS(rc)) + *ppInstance = IFPORT_2_VBOXNETFLTINS(pIfPort); + else + *ppInstance = NULL; + + return rc; +} +#endif /* VBOXNETFLT_STATIC_CONFIG */ + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnCreateAndConnect + */ +static DECLCALLBACK(int) vboxNetFltFactoryCreateAndConnect(PINTNETTRUNKFACTORY pIfFactory, const char *pszName, + PINTNETTRUNKSWPORT pSwitchPort, uint32_t fFlags, + PINTNETTRUNKIFPORT *ppIfPort) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, TrunkFactory)); + PVBOXNETFLTINS pCur; + int rc; + + LogFlow(("vboxNetFltFactoryCreateAndConnect: pszName=%p:{%s} fFlags=%#x\n", pszName, pszName, fFlags)); + Assert(pGlobals->cFactoryRefs > 0); + AssertMsgReturn(!(fFlags & ~(INTNETTRUNKFACTORY_FLAG_NO_PROMISC)), + ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Static: Find instance, check if busy, connect if not. + * Dynamic: Check for duplicate / busy interface instance. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + +//#if defined(VBOXNETADP) && defined(RT_OS_WINDOWS) +// /* temporary hack to pick up the first adapter */ +// pCur = pGlobals->pInstanceHead; /** @todo Don't for get to remove this temporary hack... :-) */ +//#else + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); +//#endif + if (pCur) + { +#ifdef VBOXNETFLT_STATIC_CONFIG + /* Try grab a reference. If the count had already reached zero we're racing the + destructor code and must back down. */ + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + if (cRefs > 1) + { + if (vboxNetFltGetState(pCur) == kVBoxNetFltInsState_Unconnected) + { + pCur->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; /** @todo protect me? */ + pCur->fDisablePromiscuous = !!(fFlags & INTNETTRUNKFACTORY_FLAG_NO_PROMISC); + rc = vboxNetFltConnectIt(pCur, pSwitchPort, ppIfPort); + if (RT_SUCCESS(rc)) + pCur = NULL; /* Don't release it, reference given to the caller. */ + else + pCur->enmTrunkState = INTNETTRUNKIFSTATE_DISCONNECTING; + } + else + rc = VERR_INTNET_FLT_IF_BUSY; + } + else + { + Assert(cRefs == 1); + ASMAtomicDecU32(&pCur->cRefs); + pCur = NULL; /* nothing to release */ + rc = VERR_INTNET_FLT_IF_NOT_FOUND; + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + if (pCur) + vboxNetFltRelease(pCur, false /* fBusy */); +#else + rc = VERR_INTNET_FLT_IF_BUSY; + RTSemFastMutexRelease(pGlobals->hFastMtx); +#endif + LogFlow(("vboxNetFltFactoryCreateAndConnect: returns %Rrc\n", rc)); + return rc; + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + +#ifdef VBOXNETFLT_STATIC_CONFIG + rc = VERR_INTNET_FLT_IF_NOT_FOUND; +#else + /* + * Dynamically create a new instance. + */ + rc = vboxNetFltNewInstance(pGlobals, + pszName, + pSwitchPort, + !!(fFlags & INTNETTRUNKFACTORY_FLAG_NO_PROMISC), + NULL, + ppIfPort); +#endif + LogFlow(("vboxNetFltFactoryCreateAndConnect: returns %Rrc\n", rc)); + return rc; +} + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnRelease + */ +static DECLCALLBACK(void) vboxNetFltFactoryRelease(PINTNETTRUNKFACTORY pIfFactory) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, TrunkFactory)); + + int32_t cRefs = ASMAtomicDecS32(&pGlobals->cFactoryRefs); + Assert(cRefs >= 0); NOREF(cRefs); + LogFlow(("vboxNetFltFactoryRelease: cRefs=%d (new)\n", cRefs)); +} + + +/** + * Implements the SUPDRV component factor interface query method. + * + * @returns Pointer to an interface. NULL if not supported. + * + * @param pSupDrvFactory Pointer to the component factory registration structure. + * @param pSession The session - unused. + * @param pszInterfaceUuid The factory interface id. + */ +static DECLCALLBACK(void *) vboxNetFltQueryFactoryInterface(PCSUPDRVFACTORY pSupDrvFactory, PSUPDRVSESSION pSession, + const char *pszInterfaceUuid) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pSupDrvFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, SupDrvFactory)); + + /* + * Convert the UUID strings and compare them. + */ + RTUUID UuidReq; + int rc = RTUuidFromStr(&UuidReq, pszInterfaceUuid); + if (RT_SUCCESS(rc)) + { + if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_UUID_STR)) + { + ASMAtomicIncS32(&pGlobals->cFactoryRefs); + return &pGlobals->TrunkFactory; + } +#ifdef LOG_ENABLED + /* log legacy queries */ + /* else if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_V1_UUID_STR)) + Log(("VBoxNetFlt: V1 factory query\n")); + */ + else + Log(("VBoxNetFlt: unknown factory interface query (%s)\n", pszInterfaceUuid)); +#endif + } + else + Log(("VBoxNetFlt: rc=%Rrc, uuid=%s\n", rc, pszInterfaceUuid)); + + RT_NOREF1(pSession); + return NULL; +} + + +/** + * Checks whether the VBoxNetFlt wossname can be unloaded. + * + * This will return false if someone is currently using the module. + * + * @returns true if it's relatively safe to unload it, otherwise false. + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(bool) vboxNetFltCanUnload(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + bool fRc = !pGlobals->pInstanceHead + && pGlobals->cFactoryRefs <= 0; + RTSemFastMutexRelease(pGlobals->hFastMtx); + AssertRC(rc); + return fRc; +} + + +/** + * Try to close the IDC connection to SUPDRV if established. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + * + * @sa vboxNetFltTryDeleteIdcAndGlobals() + */ +DECLHIDDEN(int) vboxNetFltTryDeleteIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc; + + Assert(pGlobals->hFastMtx != NIL_RTSEMFASTMUTEX); + + /* + * Check before trying to deregister the factory. + */ + if (!vboxNetFltCanUnload(pGlobals)) + return VERR_WRONG_ORDER; + + if (!pGlobals->fIDCOpen) + rc = VINF_SUCCESS; + else + { + /* + * Disconnect from SUPDRV and check that nobody raced us, + * reconnect if that should happen. + */ + rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + if (!vboxNetFltCanUnload(pGlobals)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + return VERR_WRONG_ORDER; + } + + SUPR0IdcClose(&pGlobals->SupDrvIDC); + pGlobals->fIDCOpen = false; + } + + return rc; +} + + +/** + * Establishes the IDC connection to SUPDRV and registers our component factory. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltInitGlobalsAndIdc(). + */ +DECLHIDDEN(int) vboxNetFltInitIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc; + Assert(!pGlobals->fIDCOpen); + + /* + * Establish a connection to SUPDRV and register our component factory. + */ + rc = SUPR0IdcOpen(&pGlobals->SupDrvIDC, 0 /* iReqVersion = default */, 0 /* iMinVersion = default */, NULL, NULL, NULL); + if (RT_SUCCESS(rc)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + if (RT_SUCCESS(rc)) + { + pGlobals->fIDCOpen = true; + Log(("VBoxNetFlt: pSession=%p\n", SUPR0IdcGetSession(&pGlobals->SupDrvIDC))); + return rc; + } + + /* bail out. */ + LogRel(("VBoxNetFlt: Failed to register component factory, rc=%Rrc\n", rc)); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + } + + return rc; +} + + +/** + * Deletes the globals. + * + * This must be called after the IDC connection has been closed, + * see vboxNetFltTryDeleteIdc(). + * + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltTryDeleteIdcAndGlobals() + */ +DECLHIDDEN(void) vboxNetFltDeleteGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + Assert(!pGlobals->fIDCOpen); + + /* + * Release resources. + */ + RTSemFastMutexDestroy(pGlobals->hFastMtx); + pGlobals->hFastMtx = NIL_RTSEMFASTMUTEX; +} + + +/** + * Initializes the globals. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltInitGlobalsAndIdc(). + */ +DECLHIDDEN(int) vboxNetFltInitGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = RTSemFastMutexCreate(&pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + pGlobals->pInstanceHead = NULL; + + pGlobals->TrunkFactory.pfnRelease = vboxNetFltFactoryRelease; + pGlobals->TrunkFactory.pfnCreateAndConnect = vboxNetFltFactoryCreateAndConnect; +#if defined(RT_OS_WINDOWS) && defined(VBOXNETADP) + memcpy(pGlobals->SupDrvFactory.szName, "VBoxNetAdp", sizeof("VBoxNetAdp")); +#else + memcpy(pGlobals->SupDrvFactory.szName, "VBoxNetFlt", sizeof("VBoxNetFlt")); +#endif + pGlobals->SupDrvFactory.pfnQueryFactoryInterface = vboxNetFltQueryFactoryInterface; + pGlobals->fIDCOpen = false; + + return rc; + } + + return rc; +} + + +/** + * Called by the native part when the OS wants the driver to unload. + * + * @returns VINF_SUCCESS on success, VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(int) vboxNetFltTryDeleteIdcAndGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc = vboxNetFltTryDeleteIdc(pGlobals); + if (RT_SUCCESS(rc)) + vboxNetFltDeleteGlobals(pGlobals); + return rc; +} + + +/** + * Called by the native driver/kext module initialization routine. + * + * It will initialize the common parts of the globals, assuming the caller + * has already taken care of the OS specific bits, and establish the IDC + * connection to SUPDRV. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(int) vboxNetFltInitGlobalsAndIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = vboxNetFltInitGlobals(pGlobals); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltInitIdc(pGlobals); + if (RT_SUCCESS(rc)) + return rc; + + /* bail out. */ + vboxNetFltDeleteGlobals(pGlobals); + } + + return rc; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc new file mode 100644 index 00000000..0205307a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc @@ -0,0 +1,69 @@ +/* $Id: VBoxNetFlt.rc $ */ +/** @file + * VBoxNetFlt - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2014-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_NETWORK +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox NDIS 6.0 Lightweight Filter Driver\0" + VALUE "InternalName", "VBoxNetLwf\0" + VALUE "OriginalFilename", "VBoxNetLwf.sys\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h new file mode 100644 index 00000000..1cff0981 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h @@ -0,0 +1,499 @@ +/* $Id: VBoxNetFltInternal.h $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), Internal Header. + */ + +/* + * 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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_VBoxNetFltInternal_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_VBoxNetFltInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/sup.h> +#include <VBox/intnet.h> +#include <iprt/semaphore.h> +#include <iprt/assert.h> + + +RT_C_DECLS_BEGIN + +/** Pointer to the globals. */ +typedef struct VBOXNETFLTGLOBALS *PVBOXNETFLTGLOBALS; + + +/** + * The state of a filter driver instance. + * + * The state machine differs a bit between the platforms because of + * the way we hook into the stack. On some hosts we can dynamically + * attach when required (on CreateInstance) and on others we will + * have to connect when the network stack is bound up. These modes + * are called static and dynamic config and governed at compile time + * by the VBOXNETFLT_STATIC_CONFIG define. + * + * See sec_netflt_msc for more details on locking and synchronization. + */ +typedef enum VBOXNETFTLINSSTATE +{ + /** The usual invalid state. */ + kVBoxNetFltInsState_Invalid = 0, + /** Initialization. + * We've reserved the interface name but need to attach to the actual + * network interface outside the lock to avoid deadlocks. + * In the dynamic case this happens during a Create(Instance) call. + * In the static case it happens during driver initialization. */ + kVBoxNetFltInsState_Initializing, +#ifdef VBOXNETFLT_STATIC_CONFIG + /** Unconnected, not hooked up to a switch (static only). + * The filter driver instance has been instantiated and hooked up, + * waiting to be connected to an internal network. */ + kVBoxNetFltInsState_Unconnected, +#endif + /** Connected to an internal network. */ + kVBoxNetFltInsState_Connected, + /** Disconnecting from the internal network and possibly the host network interface. + * Partly for reasons of deadlock avoidance again. */ + kVBoxNetFltInsState_Disconnecting, + /** The instance has been disconnected from both the host and the internal network. */ + kVBoxNetFltInsState_Destroyed, + + /** The habitual 32-bit enum hack. */ + kVBoxNetFltInsState_32BitHack = 0x7fffffff +} VBOXNETFTLINSSTATE; + + +/** + * The per-instance data of the VBox filter driver. + * + * This is data associated with a network interface / NIC / wossname which + * the filter driver has been or may be attached to. When possible it is + * attached dynamically, but this may not be possible on all OSes so we have + * to be flexible about things. + * + * A network interface / NIC / wossname can only have one filter driver + * instance attached to it. So, attempts at connecting an internal network + * to an interface that's already in use (connected to another internal network) + * will result in a VERR_SHARING_VIOLATION. + * + * Only one internal network can connect to a filter driver instance. + */ +typedef struct VBOXNETFLTINS +{ + /** Pointer to the next interface in the list. (VBOXNETFLTGLOBAL::pInstanceHead) */ + struct VBOXNETFLTINS *pNext; + /** Our RJ-45 port. + * This is what the internal network plugs into. */ + INTNETTRUNKIFPORT MyPort; + /** The RJ-45 port on the INTNET "switch". + * This is what we're connected to. */ + PINTNETTRUNKSWPORT pSwitchPort; + /** Pointer to the globals. */ + PVBOXNETFLTGLOBALS pGlobals; + + /** The spinlock protecting the state variables and host interface handle. */ + RTSPINLOCK hSpinlock; + /** The current interface state. */ + VBOXNETFTLINSSTATE volatile enmState; + /** The trunk state. */ + INTNETTRUNKIFSTATE volatile enmTrunkState; + bool volatile fActive; + /** Disconnected from the host network interface. */ + bool volatile fDisconnectedFromHost; + /** Rediscovery is pending. + * cBusy will never reach zero during rediscovery, so which + * takes care of serializing rediscovery and disconnecting. */ + bool volatile fRediscoveryPending; + /** Whether we should not attempt to set promiscuous mode at all. */ + bool fDisablePromiscuous; +#if (ARCH_BITS == 32) && defined(__GNUC__) +#if 0 + uint32_t u32Padding; /**< Alignment padding, will assert in ASMAtomicUoWriteU64 otherwise. */ +#endif +#endif + /** The timestamp of the last rediscovery. */ + uint64_t volatile NanoTSLastRediscovery; + /** Reference count. */ + uint32_t volatile cRefs; + /** The busy count. + * This counts the number of current callers and pending packet. */ + uint32_t volatile cBusy; + /** The event that is signaled when we go idle and that pfnWaitForIdle blocks on. */ + RTSEMEVENT hEventIdle; + + /** @todo move MacAddr out of this structure! */ + union + { +#ifdef VBOXNETFLT_OS_SPECFIC + struct + { +# if defined(RT_OS_DARWIN) + /** @name Darwin instance data. + * @{ */ + /** Pointer to the darwin network interface we're attached to. + * This is treated as highly volatile and should only be read and retained + * while owning hSpinlock. Releasing references to this should not be done + * while owning it though as we might end up destroying it in some paths. */ + ifnet_t volatile pIfNet; + /** The interface filter handle. + * Same access rules as with pIfNet. */ + interface_filter_t volatile pIfFilter; + /** Whether we've need to set promiscuous mode when the interface comes up. */ + bool volatile fNeedSetPromiscuous; + /** Whether we've successfully put the interface into to promiscuous mode. + * This is for dealing with the ENETDOWN case. */ + bool volatile fSetPromiscuous; + /** The MAC address of the interface. */ + RTMAC MacAddr; + /** PF_SYSTEM socket to listen for events (XXX: globals?) */ + socket_t pSysSock; + /** @} */ +# elif defined(RT_OS_LINUX) + /** @name Linux instance data + * @{ */ + /** Pointer to the device. */ + struct net_device * volatile pDev; + /** MTU of host's interface. */ + uint32_t cbMtu; + /** Whether we've successfully put the interface into to promiscuous mode. + * This is for dealing with the ENETDOWN case. */ + bool volatile fPromiscuousSet; + /** Whether device exists and physically attached. */ + bool volatile fRegistered; + /** Whether our packet handler is installed. */ + bool volatile fPacketHandler; + /** The MAC address of the interface. */ + RTMAC MacAddr; + struct notifier_block Notifier; /* netdevice */ + struct notifier_block NotifierIPv4; + struct notifier_block NotifierIPv6; + struct packet_type PacketType; +# ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + struct sk_buff_head XmitQueue; + struct work_struct XmitTask; +# endif + /** @} */ +# elif defined(RT_OS_SOLARIS) + /** @name Solaris instance data. + * @{ */ +# ifdef VBOX_WITH_NETFLT_CROSSBOW + /** Whether the underlying interface is a VNIC or not. */ + bool fIsVNIC; + /** Whether the underlying interface is a VNIC template or not. */ + bool fIsVNICTemplate; + /** Handle to list of created VNICs. */ + list_t hVNICs; + /** The MAC address of the host interface. */ + RTMAC MacAddr; + /** Handle of this interface (lower MAC). */ + mac_handle_t hInterface; + /** Handle to link state notifier. */ + mac_notify_handle_t hNotify; +# else + /** Pointer to the bound IPv4 stream. */ + struct vboxnetflt_stream_t * volatile pIp4Stream; + /** Pointer to the bound IPv6 stream. */ + struct vboxnetflt_stream_t * volatile pIp6Stream; + /** Pointer to the bound ARP stream. */ + struct vboxnetflt_stream_t * volatile pArpStream; + /** Pointer to the unbound promiscuous stream. */ + struct vboxnetflt_promisc_stream_t * volatile pPromiscStream; + /** Whether we are attaching to IPv6 stream dynamically now. */ + bool volatile fAttaching; + /** Whether this is a VLAN interface or not. */ + bool volatile fVLAN; + /** Layered device handle to the interface. */ + ldi_handle_t hIface; + /** The MAC address of the interface. */ + RTMAC MacAddr; + /** Mutex protection used for loopback. */ + kmutex_t hMtx; + /** Mutex protection used for dynamic IPv6 attaches. */ + RTSEMFASTMUTEX hPollMtx; +# endif + /** @} */ +# elif defined(RT_OS_FREEBSD) + /** @name FreeBSD instance data. + * @{ */ + /** Interface handle */ + struct ifnet *ifp; + /** Netgraph node handle */ + node_p node; + /** Input hook */ + hook_p input; + /** Output hook */ + hook_p output; + /** Original interface flags */ + unsigned int flags; + /** Input queue */ + struct ifqueue inq; + /** Output queue */ + struct ifqueue outq; + /** Input task */ + struct task tskin; + /** Output task */ + struct task tskout; + /** The MAC address of the interface. */ + RTMAC MacAddr; + /** @} */ +# elif defined(RT_OS_WINDOWS) + /** @name Windows instance data. + * @{ */ + /** Filter driver device context. */ + VBOXNETFLTWIN WinIf; + + volatile uint32_t cModeNetFltRefs; + volatile uint32_t cModePassThruRefs; +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + /** Packet worker thread info */ + PACKET_QUEUE_WORKER PacketQueueWorker; +#endif + /** The MAC address of the interface. Caching MAC for performance reasons. */ + RTMAC MacAddr; + /** mutex used to synchronize WinIf init/deinit */ + RTSEMMUTEX hWinIfMutex; + /** @} */ +# else +# error "PORTME" +# endif + } s; +#endif + /** Padding. */ +#if defined(RT_OS_WINDOWS) +# if defined(VBOX_NETFLT_ONDEMAND_BIND) + uint8_t abPadding[192]; +# elif defined(VBOXNETADP) + uint8_t abPadding[256]; +# else + uint8_t abPadding[1024]; +# endif +#elif defined(RT_OS_LINUX) + uint8_t abPadding[320]; +#elif defined(RT_OS_FREEBSD) + uint8_t abPadding[320]; +#else + uint8_t abPadding[128]; +#endif + } u; + + /** The interface name. */ + char szName[1]; +} VBOXNETFLTINS; +/** Pointer to the instance data of a host network filter driver. */ +typedef struct VBOXNETFLTINS *PVBOXNETFLTINS; + +AssertCompileMemberAlignment(VBOXNETFLTINS, NanoTSLastRediscovery, 8); +#ifdef VBOXNETFLT_OS_SPECFIC +AssertCompile(RT_SIZEOFMEMB(VBOXNETFLTINS, u.s) <= RT_SIZEOFMEMB(VBOXNETFLTINS, u.abPadding)); +#endif + + +/** + * The global data of the VBox filter driver. + * + * This contains the bit required for communicating with support driver, VBoxDrv + * (start out as SupDrv). + */ +typedef struct VBOXNETFLTGLOBALS +{ + /** Mutex protecting the list of instances and state changes. */ + RTSEMFASTMUTEX hFastMtx; + /** Pointer to a list of instance data. */ + PVBOXNETFLTINS pInstanceHead; + + /** The INTNET trunk network interface factory. */ + INTNETTRUNKFACTORY TrunkFactory; + /** The SUPDRV component factory registration. */ + SUPDRVFACTORY SupDrvFactory; + /** The number of current factory references. */ + int32_t volatile cFactoryRefs; + /** Whether the IDC connection is open or not. + * This is only for cleaning up correctly after the separate IDC init on Windows. */ + bool fIDCOpen; + /** The SUPDRV IDC handle (opaque struct). */ + SUPDRVIDCHANDLE SupDrvIDC; +} VBOXNETFLTGLOBALS; + + +DECLHIDDEN(int) vboxNetFltInitGlobalsAndIdc(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltInitGlobals(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltInitIdc(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltTryDeleteIdcAndGlobals(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(void) vboxNetFltDeleteGlobals(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltTryDeleteIdc(PVBOXNETFLTGLOBALS pGlobals); + +DECLHIDDEN(bool) vboxNetFltCanUnload(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(PVBOXNETFLTINS) vboxNetFltFindInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName); + +DECL_HIDDEN_CALLBACK(void) vboxNetFltPortReleaseBusy(PINTNETTRUNKIFPORT pIfPort); +DECLHIDDEN(void) vboxNetFltRetain(PVBOXNETFLTINS pThis, bool fBusy); +DECLHIDDEN(bool) vboxNetFltTryRetainBusyActive(PVBOXNETFLTINS pThis); +DECLHIDDEN(bool) vboxNetFltTryRetainBusyNotDisconnected(PVBOXNETFLTINS pThis); +DECLHIDDEN(void) vboxNetFltRelease(PVBOXNETFLTINS pThis, bool fBusy); + +#ifdef VBOXNETFLT_STATIC_CONFIG +DECLHIDDEN(int) vboxNetFltSearchCreateInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PVBOXNETFLTINS *ppInstance, void * pContext); +#endif + + + +/** @name The OS specific interface. + * @{ */ +/** + * Try rediscover the host interface. + * + * This is called periodically from the transmit path if we're marked as + * disconnected from the host. There is no chance of a race here. + * + * @returns true if the interface was successfully rediscovered and reattach, + * otherwise false. + * @param pThis The new instance. + */ +DECLHIDDEN(bool) vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis); + +/** + * Transmits a frame. + * + * @return IPRT status code. + * @param pThis The new instance. + * @param pvIfData Pointer to the host-private interface data. + * @param pSG The (scatter/)gather list. + * @param fDst The destination mask. At least one bit will be set. + * + * @remarks Owns the out-bound trunk port semaphore. + */ +DECLHIDDEN(int) vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst); + +/** + * This is called when activating or suspending the instance. + * + * Use this method to enable and disable promiscuous mode on + * the interface to prevent unnecessary interrupt load. + * + * It is only called when the state changes. + * + * @param pThis The instance. + * @param fActive Whether to active (@c true) or deactive. + * + * @remarks Owns the lock for the out-bound trunk port. + */ +DECLHIDDEN(void) vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive); + +/** + * This is called when a network interface has obtained a new MAC address. + * + * @param pThis The instance. + * @param pvIfData Pointer to the private interface data. + * @param pMac Pointer to the new MAC address. + */ +DECLHIDDEN(void) vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac); + +/** + * This is called when an interface is connected to the network. + * + * @return IPRT status code. + * @param pThis The instance. + * @param pvIf Pointer to the interface. + * @param ppvIfData Where to store the private interface data. + */ +DECLHIDDEN(int) vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData); + +/** + * This is called when a VM host disconnects from the network. + * + * @param pThis The instance. + * @param pvIfData Pointer to the private interface data. + */ +DECLHIDDEN(int) vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData); + +/** + * This is called to when disconnecting from a network. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks May own the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(int) vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis); + +/** + * This is called to when connecting to a network. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks Owns the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(int) vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis); + +/** + * Counter part to vboxNetFltOsInitInstance(). + * + * @param pThis The new instance. + * + * @remarks May own the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(void) vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis); + +/** + * This is called to attach to the actual host interface + * after linking the instance into the list. + * + * The MAC address as well promiscuousness and GSO capabilities should be + * reported by this function. + * + * @return IPRT status code. + * @param pThis The new instance. + * @param pvContext The user supplied context in the static config only. + * NULL in the dynamic config. + * + * @remarks Owns no locks. + */ +DECLHIDDEN(int) vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext); + +/** + * This is called to perform structure initializations. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks Owns no locks. + */ +DECLHIDDEN(int) vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis); +/** @} */ + + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_VBoxNetFltInternal_h */ + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist new file mode 100644 index 00000000..20338308 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleExecutable</key> <string>VBoxNetFlt</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxNetFlt</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>CFBundleName</key> <string>VBoxNetFlt</string> + <key>CFBundlePackageType</key> <string>KEXT</string> + <key>CFBundleSignature</key> <string>????</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>IOKitPersonalities</key> + <dict> + </dict> + <key>OSBundleLibraries</key> + <dict> + <key>com.apple.kpi.bsd</key> <string>8.8.1</string> + <key>com.apple.kpi.mach</key> <string>8.8.1</string> + <key>com.apple.kpi.libkern</key> <string>8.8.1</string> + <key>com.apple.kpi.unsupported</key> <string>8.8.1</string> + <key>com.apple.kpi.iokit</key> <string>8.8.1</string> + <key>org.virtualbox.kext.VBoxDrv</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + </dict> + <key>OSBundleLibraries_x86_64</key> + <dict> + <key>com.apple.kpi.bsd</key> <string>10.0.0d3</string> + <key>com.apple.kpi.mach</key> <string>10.0.0d3</string> + <key>com.apple.kpi.libkern</key> <string>10.0.0d3</string> + <key>com.apple.kpi.unsupported</key> <string>10.0.0d3</string> + <key>com.apple.kpi.iokit</key> <string>10.0.0d3</string> + <key>org.virtualbox.kext.VBoxDrv</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + </dict> +</dict> +</plist> + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp b/src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp new file mode 100644 index 00000000..3a14a8c5 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp @@ -0,0 +1,1754 @@ +/* $Id: VBoxNetFlt-darwin.cpp $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), Darwin Specific Code. + */ + +/* + * Copyright (C) 2006-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 <https://www.gnu.org/licenses>. + * + * 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 "../../../Runtime/r0drv/darwin/the-darwin-kernel.h" + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/intnetinline.h> +#include <VBox/version.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/process.h> +#include <iprt/alloc.h> +#include <iprt/alloca.h> +#include <iprt/time.h> +#include <iprt/net.h> +#include <iprt/thread.h> + +#include "../../darwin/VBoxNetSend.h" + +#include <mach/kmod.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/ioccom.h> +#include <sys/filio.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/kern_event.h> +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 /* The 10.15 SDK has a slightly butchered API deprecation attempt. */ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmacro-redefined" /* Each header redefines __NKE_API_DEPRECATED. */ +# pragma clang diagnostic ignored "-Wmissing-declarations" /* Misplaced __NKE_API_DEPRECATED; in kpi_mbuf.h. */ +# include <sys/kpi_socket.h> +# include <net/kpi_interface.h> +# include <sys/kpi_mbuf.h> +# include <net/kpi_interfacefilter.h> +# pragma clang diagnostic pop +#else /* < 10.15*/ +# include <sys/kpi_socket.h> +# include <net/kpi_interface.h> +RT_C_DECLS_BEGIN /* Buggy 10.4 headers, fixed in 10.5. */ +# include <sys/kpi_mbuf.h> +# include <net/kpi_interfacefilter.h> +RT_C_DECLS_END +#endif /* < 10.15*/ + + +#include <net/if.h> +#include <net/if_var.h> +RT_C_DECLS_BEGIN +#include <net/bpf.h> +RT_C_DECLS_END +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet6/in6_var.h> + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The maximum number of SG segments. + * Used to prevent stack overflow and similar bad stuff. */ +#define VBOXNETFLT_DARWIN_MAX_SEGS 32 + +#if 0 +/** For testing extremely segmented frames. */ +#define VBOXNETFLT_DARWIN_TEST_SEG_SIZE 14 +#endif + +/* XXX: hidden undef #ifdef __APPLE__ */ +#define VBOX_IN_LOOPBACK(addr) (((addr) & IN_CLASSA_NET) == 0x7f000000) +#define VBOX_IN_LINKLOCAL(addr) (((addr) & IN_CLASSB_NET) == 0xa9fe0000) + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t VBoxNetFltDarwinStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t VBoxNetFltDarwinStop(struct kmod_info *pKModInfo, void *pvData); + +static void vboxNetFltDarwinSysSockUpcall(socket_t pSysSock, void *pvData, int fWait); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The mbuf tag data. + * + * We have to associate the ethernet header with each packet we're sending + * because things like icmp will inherit the tag it self so the tag along + * isn't sufficient to identify our mbufs. For the icmp scenario the ethernet + * header naturally changes before the packet is send pack, so let check it. + */ +typedef struct VBOXNETFLTTAG +{ + /** The ethernet header of the outgoing frame. */ + RTNETETHERHDR EthHdr; +} VBOXNETFLTTAG; +/** Pointer to a VBoxNetFlt mbuf tag. */ +typedef VBOXNETFLTTAG *PVBOXNETFLTTAG; +/** Pointer to a const VBoxNetFlt mbuf tag. */ +typedef VBOXNETFLTTAG const *PCVBOXNETFLTTAG; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Declare the module stuff. + */ +RT_C_DECLS_BEGIN +extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData); +extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData); + +KMOD_EXPLICIT_DECL(VBoxNetFlt, VBOX_VERSION_STRING, _start, _stop) +DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = VBoxNetFltDarwinStart; +DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = VBoxNetFltDarwinStop; +DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; + +/** The unique tag id for this module. + * This is basically a unique string hash that lives on until reboot. + * It is used for tagging mbufs. */ +static mbuf_tag_id_t g_idTag; + +/** The offset of the struct ifnet::if_pcount variable. + * @remarks Initial value is valid for Lion and earlier. We adjust it on attach + * for later releases. */ +static unsigned g_offIfNetPCount = sizeof(void *) * (1 /*if_softc*/ + 1 /*if_name*/ + 2 /*if_link*/ + 2 /*if_addrhead*/ + 1 /*if_check_multi*/) + + sizeof(u_long) /*if_refcnt*/; +/** Macro for accessing ifnet::if_pcount. */ +#define VBOX_GET_PCOUNT(pIfNet) ( *(int *)((uintptr_t)pIfNet + g_offIfNetPCount) ) +/** The size of area of ifnet structure we try to locate if_pcount in. */ +#define VBOXNETFLT_DARWIN_IFNET_SIZE 256 +/** Indicates whether g_offIfNetPCount has been adjusted already (no point in + * doing it more than once). */ +static bool g_fNetPCountFound = false; + + +/** + * Change the promiscuous setting and try spot the changed in @a pIfNet. + * + * @returns Offset of potential p_count field. + * @param pIfNet The interface we're attaching to. + * @param iPromisc Whether to enable (1) or disable (0) promiscuous mode. + * + * @note This implementation relies on if_pcount to be aligned on sizeof(int). + */ +static unsigned vboxNetFltDarwinSetAndDiff(ifnet_t pIfNet, int iPromisc) +{ + int aiSavedState[VBOXNETFLT_DARWIN_IFNET_SIZE / sizeof(int)]; + memcpy(aiSavedState, pIfNet, sizeof(aiSavedState)); + + ifnet_set_promiscuous(pIfNet, iPromisc); + + int const iDiff = iPromisc ? 1 : -1; + + /* + * We assume that ifnet structure will never have less members in front of if_pcount + * than it used to have in Lion. If this turns out to be false assumption we will + * have to start from zero offset. + */ + for (unsigned i = g_offIfNetPCount / sizeof(int); i < RT_ELEMENTS(aiSavedState); i++) + if (((int*)pIfNet)[i] - aiSavedState[i] == iDiff) + return i * sizeof(int); + + return 0; +} + + +/** + * Detect and adjust the offset of ifnet::if_pcount. + * + * @param pIfNet The interface we're attaching to. + */ +static void vboxNetFltDarwinDetectPCountOffset(ifnet_t pIfNet) +{ + if (g_fNetPCountFound) + return; + + /* + * It would be nice to use locking at this point, but it is not available via KPI. + * This is why we try several times. At each attempt we modify if_pcount four times + * to rule out false detections. + */ + unsigned offTry1, offTry2, offTry3, offTry4; + for (int iAttempt = 0; iAttempt < 3; iAttempt++) + { + offTry1 = vboxNetFltDarwinSetAndDiff(pIfNet, 1); + offTry2 = vboxNetFltDarwinSetAndDiff(pIfNet, 1); + offTry3 = vboxNetFltDarwinSetAndDiff(pIfNet, 0); + offTry4 = vboxNetFltDarwinSetAndDiff(pIfNet, 0); + if (offTry1 == offTry2 && offTry2 == offTry3 && offTry3 == offTry4) + { + if (g_offIfNetPCount != offTry1) + { + Log(("VBoxNetFltDarwinDetectPCountOffset: Adjusted if_pcount offset to %x from %x.\n", offTry1, g_offIfNetPCount)); + g_offIfNetPCount = offTry1; + g_fNetPCountFound = true; + } + break; + } + } + + if (g_offIfNetPCount != offTry1) + LogRel(("VBoxNetFlt: Failed to detect promiscuous count, all traffic may reach wire (%x != %x).\n", g_offIfNetPCount, offTry1)); +} + + +/** + * Start the kernel module. + */ +static kern_return_t VBoxNetFltDarwinStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + + /* + * Initialize IPRT and find our module tag id. + * (IPRT is shared with VBoxDrv, it creates the loggers.) + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxNetFltDarwinStart\n")); + errno_t err = mbuf_tag_id_find("org.VirtualBox.kext.VBoxFltDrv", &g_idTag); + if (!err) + { + /* + * 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_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltGlobals); + if (RT_SUCCESS(rc)) + { + LogRel(("VBoxFltDrv: version " VBOX_VERSION_STRING " r%d\n", VBOX_SVN_REV)); + return KMOD_RETURN_SUCCESS; + } + + LogRel(("VBoxFltDrv: failed to initialize device extension (rc=%d)\n", rc)); + } + else + LogRel(("VBoxFltDrv: mbuf_tag_id_find failed, err=%d\n", err)); + RTR0Term(); + } + else + printf("VBoxFltDrv: failed to initialize IPRT (rc=%d)\n", rc); + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + return KMOD_RETURN_FAILURE; +} + + +/** + * Stop the kernel module. + */ +static kern_return_t VBoxNetFltDarwinStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + Log(("VBoxNetFltDarwinStop\n")); + + /* + * Refuse to unload if anyone is currently using the filter driver. + * This is important as I/O kit / xnu will to be able to do usage + * tracking for us! + */ + int rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltGlobals); + if (RT_FAILURE(rc)) + { + Log(("VBoxNetFltDarwinStop - failed, busy.\n")); + return KMOD_RETURN_FAILURE; + } + + /* + * Undo the work done during start (in reverse order). + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + RTR0Term(); + + return KMOD_RETURN_SUCCESS; +} + + +/** + * Reads and retains the host interface handle. + * + * @returns The handle, NULL if detached. + * @param pThis + */ +DECLINLINE(ifnet_t) vboxNetFltDarwinRetainIfNet(PVBOXNETFLTINS pThis) +{ + ifnet_t pIfNet = NULL; + + /* + * Be careful here to avoid problems racing the detached callback. + */ + RTSpinlockAcquire(pThis->hSpinlock); + if (!ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)) + { + pIfNet = ASMAtomicUoReadPtrT(&pThis->u.s.pIfNet, ifnet_t); + if (pIfNet) + ifnet_reference(pIfNet); + } + RTSpinlockRelease(pThis->hSpinlock); + + return pIfNet; +} + + +/** + * Release the host interface handle previously retained + * by vboxNetFltDarwinRetainIfNet. + * + * @param pThis The instance. + * @param pIfNet The vboxNetFltDarwinRetainIfNet return value, NULL is fine. + */ +DECLINLINE(void) vboxNetFltDarwinReleaseIfNet(PVBOXNETFLTINS pThis, ifnet_t pIfNet) +{ + NOREF(pThis); + if (pIfNet) + ifnet_release(pIfNet); +} + + +/** + * Checks whether this is an mbuf created by vboxNetFltDarwinMBufFromSG, + * i.e. a buffer which we're pushing and should be ignored by the filter callbacks. + * + * @returns true / false accordingly. + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pvFrame The frame pointer, optional. + */ +DECLINLINE(bool) vboxNetFltDarwinMBufIsOur(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame) +{ + NOREF(pThis); + + /* + * Lookup the tag set by vboxNetFltDarwinMBufFromSG. + */ + PCVBOXNETFLTTAG pTagData; + size_t cbTagData; + errno_t err = mbuf_tag_find(pMBuf, g_idTag, 0 /* type */, &cbTagData, (void **)&pTagData); + if (err) + return false; + AssertReturn(cbTagData == sizeof(*pTagData), false); + + /* + * Dig out the ethernet header from the mbuf. + */ + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + if (!pEthHdr) + pEthHdr = (PCRTNETETHERHDR)mbuf_pkthdr_header(pMBuf); + if (!pEthHdr) + pEthHdr = (PCRTNETETHERHDR)mbuf_data(pMBuf); + /* ASSUMING that there is enough data to work on! */ + if ( pEthHdr->DstMac.au8[0] != pTagData->EthHdr.DstMac.au8[0] + || pEthHdr->DstMac.au8[1] != pTagData->EthHdr.DstMac.au8[1] + || pEthHdr->DstMac.au8[2] != pTagData->EthHdr.DstMac.au8[2] + || pEthHdr->DstMac.au8[3] != pTagData->EthHdr.DstMac.au8[3] + || pEthHdr->DstMac.au8[4] != pTagData->EthHdr.DstMac.au8[4] + || pEthHdr->DstMac.au8[5] != pTagData->EthHdr.DstMac.au8[5] + || pEthHdr->SrcMac.au8[0] != pTagData->EthHdr.SrcMac.au8[0] + || pEthHdr->SrcMac.au8[1] != pTagData->EthHdr.SrcMac.au8[1] + || pEthHdr->SrcMac.au8[2] != pTagData->EthHdr.SrcMac.au8[2] + || pEthHdr->SrcMac.au8[3] != pTagData->EthHdr.SrcMac.au8[3] + || pEthHdr->SrcMac.au8[4] != pTagData->EthHdr.SrcMac.au8[4] + || pEthHdr->SrcMac.au8[5] != pTagData->EthHdr.SrcMac.au8[5] + || pEthHdr->EtherType != pTagData->EthHdr.EtherType) + { + Log3(("tagged, but the ethernet header has changed\n")); + return false; + } + + return true; +} + + +/** + * Internal worker that create a darwin mbuf for a (scatter/)gather list. + * + * @returns Pointer to the mbuf. + * @param pThis The instance. + * @param pSG The (scatter/)gather list. + */ +static mbuf_t vboxNetFltDarwinMBufFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG) +{ + /// @todo future? mbuf_how_t How = preemption enabled ? MBUF_DONTWAIT : MBUF_WAITOK; + mbuf_how_t How = MBUF_WAITOK; + + /* + * We need some way of getting back to our instance data when + * the mbuf is freed, so use pvUserData for this. + * -- this is not relevant anylonger! -- + */ + Assert(!pSG->pvUserData || pSG->pvUserData == pThis); + Assert(!pSG->pvUserData2); + pSG->pvUserData = pThis; + + /* + * Allocate a packet and copy over the data. + * + * Using mbuf_attachcluster() here would've been nice but there are two + * issues with it: (1) it's 10.5.x only, and (2) the documentation indicates + * that it's not supposed to be used for really external buffers. The 2nd + * point might be argued against considering that the only m_clattach user + * is mallocs memory for the ext mbuf and not doing what's stated in the docs. + * However, it's hard to tell if these m_clattach buffers actually makes it + * to the NICs or not, and even if they did, the NIC would need the physical + * addresses for the pages they contain and might end up copying the data + * to a new mbuf anyway. + * + * So, in the end it's better to just do it the simple way that will work + * 100%, even if it involves some extra work (alloc + copy) we really wished + * to avoid. + * + * Note. We can't make use of the physical addresses on darwin because the + * way the mbuf / cluster stuff works (see mbuf_data_to_physical and + * mcl_to_paddr). + */ + mbuf_t pPkt = NULL; + errno_t err = mbuf_allocpacket(How, pSG->cbTotal, NULL, &pPkt); + if (!err) + { + /* Skip zero sized memory buffers (paranoia). */ + mbuf_t pCur = pPkt; + while (pCur && !mbuf_maxlen(pCur)) + pCur = mbuf_next(pCur); + Assert(pCur); + + /* Set the required packet header attributes. */ + mbuf_pkthdr_setlen(pPkt, pSG->cbTotal); + mbuf_pkthdr_setheader(pPkt, mbuf_data(pCur)); + + /* Special case the single buffer copy. */ + if ( mbuf_next(pCur) + && mbuf_maxlen(pCur) >= pSG->cbTotal) + { + mbuf_setlen(pCur, pSG->cbTotal); + IntNetSgRead(pSG, mbuf_data(pCur)); + } + else + { + /* Multi buffer copying. */ + size_t cbLeft = pSG->cbTotal; + size_t offSrc = 0; + while (cbLeft > 0 && pCur) + { + size_t cb = mbuf_maxlen(pCur); + if (cb > cbLeft) + cb = cbLeft; + mbuf_setlen(pCur, cb); + IntNetSgReadEx(pSG, offSrc, cb, mbuf_data(pCur)); + + /* advance */ + offSrc += cb; + cbLeft -= cb; + pCur = mbuf_next(pCur); + } + Assert(cbLeft == 0); + } + if (!err) + { + /* + * Tag the packet and return successfully. + */ + PVBOXNETFLTTAG pTagData; + err = mbuf_tag_allocate(pPkt, g_idTag, 0 /* type */, sizeof(VBOXNETFLTTAG) /* tag len */, How, (void **)&pTagData); + if (!err) + { + Assert(pSG->aSegs[0].cb >= sizeof(pTagData->EthHdr)); + memcpy(&pTagData->EthHdr, pSG->aSegs[0].pv, sizeof(pTagData->EthHdr)); + return pPkt; + } + + /* bailout: */ + AssertMsg(err == ENOMEM || err == EWOULDBLOCK, ("err=%d\n", err)); + } + + mbuf_freem(pPkt); + } + else + AssertMsg(err == ENOMEM || err == EWOULDBLOCK, ("err=%d\n", err)); + pSG->pvUserData = NULL; + + return NULL; +} + + +/** + * Calculates the number of segments required to represent the mbuf. + * + * @returns Number of segments. + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pvFrame The frame pointer, optional. + */ +DECLINLINE(unsigned) vboxNetFltDarwinMBufCalcSGSegs(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame) +{ + NOREF(pThis); + + /* + * Count the buffers in the chain. + */ + unsigned cSegs = 0; + for (mbuf_t pCur = pMBuf; pCur; pCur = mbuf_next(pCur)) + if (mbuf_len(pCur)) + cSegs++; + else if ( !cSegs + && pvFrame + && (uintptr_t)pvFrame - (uintptr_t)mbuf_datastart(pMBuf) < mbuf_maxlen(pMBuf)) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* + * Add one buffer if the total is less than the ethernet minimum 60 bytes. + * This may allocate a segment too much if the ethernet header is separated, + * but that shouldn't harm us much. + */ + if (mbuf_pkthdr_len(pMBuf) < 60) + cSegs++; +#endif + +#ifdef VBOXNETFLT_DARWIN_TEST_SEG_SIZE + /* maximize the number of segments. */ + cSegs = RT_MAX(VBOXNETFLT_DARWIN_MAX_SEGS - 1, cSegs); +#endif + + return cSegs ? cSegs : 1; +} + + +/** + * Initializes a SG list from an mbuf. + * + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pSG The SG. + * @param pvFrame The frame pointer, optional. + * @param cSegs The number of segments allocated for the SG. + * This should match the number in the mbuf exactly! + * @param fSrc The source of the frame. + */ +DECLINLINE(void) vboxNetFltDarwinMBufToSG(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame, PINTNETSG pSG, unsigned cSegs, + uint32_t fSrc) +{ + RT_NOREF(pThis, fSrc); + + /* + * Walk the chain and convert the buffers to segments. Works INTNETSG::cbTotal. + */ + unsigned iSeg = 0; + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); + for (mbuf_t pCur = pMBuf; pCur; pCur = mbuf_next(pCur)) + { + size_t cbSeg = mbuf_len(pCur); + if (cbSeg) + { + void *pvSeg = mbuf_data(pCur); + + /* deal with pvFrame */ + if (!iSeg && pvFrame && pvFrame != pvSeg) + { + void *pvStart = mbuf_datastart(pMBuf); + uintptr_t offSeg = (uintptr_t)pvSeg - (uintptr_t)pvStart; + uintptr_t offSegEnd = offSeg + cbSeg; + Assert(pvStart && pvSeg && offSeg < mbuf_maxlen(pMBuf) && offSegEnd <= mbuf_maxlen(pMBuf)); NOREF(offSegEnd); + uintptr_t offFrame = (uintptr_t)pvFrame - (uintptr_t)pvStart; + if (RT_LIKELY(offFrame < offSeg)) + { + pvSeg = pvFrame; + cbSeg += offSeg - offFrame; + } + else + AssertMsgFailed(("pvFrame=%p pvStart=%p pvSeg=%p offSeg=%p cbSeg=%#zx offSegEnd=%p offFrame=%p maxlen=%#zx\n", + pvFrame, pvStart, pvSeg, offSeg, cbSeg, offSegEnd, offFrame, mbuf_maxlen(pMBuf))); + pvFrame = NULL; + } + + AssertBreak(iSeg < cSegs); + pSG->cbTotal += cbSeg; + pSG->aSegs[iSeg].cb = cbSeg; + pSG->aSegs[iSeg].pv = pvSeg; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + iSeg++; + } + /* The pvFrame might be in a now empty buffer. */ + else if ( !iSeg + && pvFrame + && (uintptr_t)pvFrame - (uintptr_t)mbuf_datastart(pMBuf) < mbuf_maxlen(pMBuf)) + { + cbSeg = (uintptr_t)mbuf_datastart(pMBuf) + mbuf_maxlen(pMBuf) - (uintptr_t)pvFrame; + pSG->cbTotal += cbSeg; + pSG->aSegs[iSeg].cb = cbSeg; + pSG->aSegs[iSeg].pv = pvFrame; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + iSeg++; + pvFrame = NULL; + } + } + + Assert(iSeg && iSeg <= cSegs); + pSG->cSegsUsed = iSeg; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* + * Add a trailer if the frame is too small. + * + * Since we're getting to the packet before it is framed, it has not + * yet been padded. The current solution is to add a segment pointing + * to a buffer containing all zeros and pray that works for all frames... + */ + if (pSG->cbTotal < 60 && (fSrc == INTNETTRUNKDIR_HOST)) + { + AssertReturnVoid(iSeg < cSegs); + + 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++; + } +#endif + +#ifdef VBOXNETFLT_DARWIN_TEST_SEG_SIZE + /* + * Redistribute the segments. + */ + if (pSG->cSegsUsed < pSG->cSegsAlloc) + { + /* copy the segments to the end. */ + int iSrc = pSG->cSegsUsed; + int iDst = pSG->cSegsAlloc; + while (iSrc > 0) + { + iDst--; + iSrc--; + pSG->aSegs[iDst] = pSG->aSegs[iSrc]; + } + + /* create small segments from the start. */ + pSG->cSegsUsed = pSG->cSegsAlloc; + iSrc = iDst; + iDst = 0; + while ( iDst < iSrc + && iDst < pSG->cSegsAlloc) + { + pSG->aSegs[iDst].Phys = NIL_RTHCPHYS; + pSG->aSegs[iDst].pv = pSG->aSegs[iSrc].pv; + pSG->aSegs[iDst].cb = RT_MIN(pSG->aSegs[iSrc].cb, VBOXNETFLT_DARWIN_TEST_SEG_SIZE); + if (pSG->aSegs[iDst].cb != pSG->aSegs[iSrc].cb) + { + pSG->aSegs[iSrc].cb -= pSG->aSegs[iDst].cb; + pSG->aSegs[iSrc].pv = (uint8_t *)pSG->aSegs[iSrc].pv + pSG->aSegs[iDst].cb; + } + else if (++iSrc >= pSG->cSegsAlloc) + { + pSG->cSegsUsed = iDst + 1; + break; + } + iDst++; + } + } +#endif + + AssertMsg(!pvFrame, ("pvFrame=%p pMBuf=%p iSeg=%d\n", pvFrame, pMBuf, iSeg)); +} + + +/** + * Helper for determining whether the host wants the interface to be + * promiscuous. + */ +static bool vboxNetFltDarwinIsPromiscuous(PVBOXNETFLTINS pThis) +{ + bool fRc = false; + ifnet_t pIfNet = vboxNetFltDarwinRetainIfNet(pThis); + if (pIfNet) + { + /* gather the data */ + uint16_t fIf = ifnet_flags(pIfNet); + unsigned cPromisc = VBOX_GET_PCOUNT(pIfNet); + bool fSetPromiscuous = ASMAtomicUoReadBool(&pThis->u.s.fSetPromiscuous); + vboxNetFltDarwinReleaseIfNet(pThis, pIfNet); + + /* calc the return. */ + fRc = (fIf & IFF_PROMISC) + && cPromisc > fSetPromiscuous; + } + return fRc; +} + + + +/** + * + * @see iff_detached_func in the darwin kpi. + */ +static void vboxNetFltDarwinIffDetached(void *pvThis, ifnet_t pIfNet) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvThis; + uint64_t NanoTS = RTTimeSystemNanoTS(); + LogFlow(("vboxNetFltDarwinIffDetached: pThis=%p NanoTS=%RU64 (%d)\n", + pThis, NanoTS, RT_VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : -1)); + + Assert(!pThis->fDisconnectedFromHost); + Assert(!pThis->fRediscoveryPending); + + /* + * If we've put it into promiscuous mode, undo that now. If we don't + * the if_pcount will go all wrong when it's replugged. + */ + if (ASMAtomicXchgBool(&pThis->u.s.fSetPromiscuous, false)) + ifnet_set_promiscuous(pIfNet, 0); + + /* + * We carefully take the spinlock and increase the interface reference + * behind it in order to avoid problematic races with the detached callback. + */ + RTSpinlockAcquire(pThis->hSpinlock); + + pIfNet = ASMAtomicUoReadPtrT(&pThis->u.s.pIfNet, ifnet_t); + int cPromisc = RT_VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : - 1; + + ASMAtomicUoWriteNullPtr(&pThis->u.s.pIfNet); + ASMAtomicUoWriteNullPtr(&pThis->u.s.pIfFilter); + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + pThis->u.s.fSetPromiscuous = false; + ASMAtomicUoWriteU64(&pThis->NanoTSLastRediscovery, NanoTS); + ASMAtomicUoWriteBool(&pThis->fRediscoveryPending, false); + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); + + RTSpinlockRelease(pThis->hSpinlock); + + if (pIfNet) + ifnet_release(pIfNet); + LogRel(("VBoxNetFlt: was detached from '%s' (%d)\n", pThis->szName, cPromisc)); +} + + +/** + * + * @see iff_ioctl_func in the darwin kpi. + */ +static errno_t vboxNetFltDarwinIffIoCtl(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, u_long uCmd, void *pvArg) +{ + RT_NOREF(pIfNet); + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvThis; + LogFlow(("vboxNetFltDarwinIffIoCtl: pThis=%p uCmd=%lx\n", pThis, uCmd)); + + /* + * Update fOtherPromiscuous. + */ + /** @todo we'll have to find the offset of if_pcount to get this right! */ + //if (uCmd == SIOCSIFFLAGS) + //{ + // + //} + + /* + * We didn't handle it, continue processing. + */ + NOREF(pThis); + NOREF(eProtocol); + NOREF(uCmd); + NOREF(pvArg); + return EOPNOTSUPP; +} + + +/** + * + * @see iff_event_func in the darwin kpi. + */ +static void vboxNetFltDarwinIffEvent(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, const struct kev_msg *pEvMsg) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvThis; + LogFlow(("vboxNetFltDarwinIffEvent: pThis=%p\n", pThis)); + + NOREF(pThis); + NOREF(pIfNet); + NOREF(eProtocol); + NOREF(pEvMsg); + + /* + * Watch out for the interface going online / offline. + */ + if ( RT_VALID_PTR(pThis) + && RT_VALID_PTR(pEvMsg) + && pEvMsg->vendor_code == KEV_VENDOR_APPLE + && pEvMsg->kev_class == KEV_NETWORK_CLASS + && pEvMsg->kev_subclass == KEV_DL_SUBCLASS) + { + if (pThis->u.s.pIfNet == pIfNet) + { + if (pEvMsg->event_code == KEV_DL_LINK_ON) + { + if (ASMAtomicUoReadBool(&pThis->u.s.fNeedSetPromiscuous)) + { + /* failed to bring it online. */ + errno_t err = ifnet_set_promiscuous(pIfNet, 1); + if (!err) + { + ASMAtomicWriteBool(&pThis->u.s.fSetPromiscuous, true); + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + Log(("vboxNetFltDarwinIffEvent: enabled promiscuous mode on %s (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet))); + } + else + Log(("vboxNetFltDarwinIffEvent: ifnet_set_promiscuous failed on %s, err=%d (%d)\n", pThis->szName, err, VBOX_GET_PCOUNT(pIfNet))); + } + else if ( ASMAtomicUoReadBool(&pThis->u.s.fSetPromiscuous) + && !(ifnet_flags(pIfNet) & IFF_PROMISC)) + { + /* Try fix the inconsistency. */ + errno_t err = ifnet_set_flags(pIfNet, IFF_PROMISC, IFF_PROMISC); + if (!err) + err = ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + if (!err && (ifnet_flags(pIfNet) & IFF_PROMISC)) + Log(("vboxNetFltDarwinIffEvent: fixed IFF_PROMISC on %s (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet))); + else + Log(("vboxNetFltDarwinIffEvent: failed to fix IFF_PROMISC on %s, err=%d flags=%#x (%d)\n", + pThis->szName, err, ifnet_flags(pIfNet), VBOX_GET_PCOUNT(pIfNet))); + } + else + Log(("vboxNetFltDarwinIffEvent: online, '%s'. flags=%#x (%d)\n", pThis->szName, ifnet_flags(pIfNet), VBOX_GET_PCOUNT(pIfNet))); + } + else if (pEvMsg->event_code == KEV_DL_LINK_OFF) + Log(("vboxNetFltDarwinIffEvent: %s goes down (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet))); +/** @todo KEV_DL_LINK_ADDRESS_CHANGED -> pfnReportMacAddress */ +/** @todo KEV_DL_SIFFLAGS -> pfnReportPromiscuousMode */ + } + else + Log(("vboxNetFltDarwinIffEvent: pThis->u.s.pIfNet=%p pIfNet=%p (%d)\n", pThis->u.s.pIfNet, pIfNet, RT_VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : -1)); + } + else if (RT_VALID_PTR(pEvMsg)) + Log(("vboxNetFltDarwinIffEvent: vendor_code=%#x kev_class=%#x kev_subclass=%#x event_code=%#x\n", + pEvMsg->vendor_code, pEvMsg->kev_class, pEvMsg->kev_subclass, pEvMsg->event_code)); +} + + +/** + * Internal worker for vboxNetFltDarwinIffInput and vboxNetFltDarwinIffOutput, + * + * @returns 0 or EJUSTRETURN. + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pvFrame The start of the frame, optional. + * @param fSrc Where the packet (allegedly) comes from, one INTNETTRUNKDIR_* value. + * @param eProtocol The protocol. + */ +static errno_t vboxNetFltDarwinIffInputOutputWorker(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame, + uint32_t fSrc, protocol_family_t eProtocol) +{ + /* + * Drop it immediately? + */ + Log2(("vboxNetFltDarwinIffInputOutputWorker: pThis=%p pMBuf=%p pvFrame=%p fSrc=%#x cbPkt=%x\n", + pThis, pMBuf, pvFrame, fSrc, pMBuf ? mbuf_pkthdr_len(pMBuf) : -1)); + if (!pMBuf) + return 0; +#if 0 /* debugging lost icmp packets */ + if (mbuf_pkthdr_len(pMBuf) > 0x300) + { + uint8_t *pb = (uint8_t *)(pvFrame ? pvFrame : mbuf_data(pMBuf)); + Log3(("D=%.6Rhxs S=%.6Rhxs T=%04x IFF\n", pb, pb + 6, RT_BE2H_U16(*(uint16_t *)(pb + 12)))); + } +#endif + if (vboxNetFltDarwinMBufIsOur(pThis, pMBuf, pvFrame)) + return 0; + + /* + * Active? Retain the instance and increment the busy counter. + */ + if (!vboxNetFltTryRetainBusyActive(pThis)) + return 0; + + /* + * Finalize out-bound packets since the stack puts off finalizing + * TCP/IP checksums as long as possible. + * ASSUMES this only applies to outbound IP packets. + */ + if (fSrc == INTNETTRUNKDIR_HOST) + { + Assert(!pvFrame); + mbuf_outbound_finalize(pMBuf, eProtocol, sizeof(RTNETETHERHDR)); + } + + /* + * Create a (scatter/)gather list for the mbuf and feed it to the internal network. + */ + bool fDropIt = false; + unsigned cSegs = vboxNetFltDarwinMBufCalcSGSegs(pThis, pMBuf, pvFrame); + if (cSegs < VBOXNETFLT_DARWIN_MAX_SEGS) + { + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + vboxNetFltDarwinMBufToSG(pThis, pMBuf, pvFrame, pSG, cSegs, fSrc); + + fDropIt = pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + if (fDropIt) + { + /* + * If the interface is in promiscuous mode we should let + * all inbound packets (this one was for a bridged guest) + * reach the driver as it passes them to tap callbacks in + * order for BPF to work properly. + */ + if ( fSrc == INTNETTRUNKDIR_WIRE + && vboxNetFltDarwinIsPromiscuous(pThis)) + { + fDropIt = false; + } + + /* + * A packet from the host to a guest. As we won't pass it + * to the drvier/wire we need to feed it to bpf ourselves. + * + * XXX: TODO: bpf should be done before; use pfnPreRecv? + */ + if (fSrc == INTNETTRUNKDIR_HOST) + { + bpf_tap_out(pThis->u.s.pIfNet, DLT_EN10MB, pMBuf, NULL, 0); + ifnet_stat_increment_out(pThis->u.s.pIfNet, 1, mbuf_len(pMBuf), 0); + } + } + } + + vboxNetFltRelease(pThis, true /* fBusy */); + + if (fDropIt) + { + mbuf_freem(pMBuf); + return EJUSTRETURN; + } + return 0; +} + + +/** + * From the host. + * + * @see iff_output_func in the darwin kpi. + */ +static errno_t vboxNetFltDarwinIffOutput(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, mbuf_t *ppMBuf) +{ + /** @todo there was some note about the ethernet header here or something like that... */ + + NOREF(eProtocol); + NOREF(pIfNet); + return vboxNetFltDarwinIffInputOutputWorker((PVBOXNETFLTINS)pvThis, *ppMBuf, NULL, INTNETTRUNKDIR_HOST, eProtocol); +} + + +/** + * From the wire. + * + * @see iff_input_func in the darwin kpi. + */ +static errno_t vboxNetFltDarwinIffInput(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, mbuf_t *ppMBuf, char **ppchFrame) +{ + RT_NOREF(eProtocol, pIfNet); + return vboxNetFltDarwinIffInputOutputWorker((PVBOXNETFLTINS)pvThis, *ppMBuf, *ppchFrame, INTNETTRUNKDIR_WIRE, eProtocol); +} + + +/** A worker thread for vboxNetFltSendDummy(). */ +static DECLCALLBACK(int) vboxNetFltSendDummyWorker(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + Assert(pvUser); + ifnet_t pIfNet = (ifnet_t)pvUser; + return VBoxNetSendDummy(pIfNet); +} + + +/** + * Prevent GUI icon freeze issue when VirtualBoxVM process terminates. + * + * This function is a workaround for stuck-in-dock issue. The idea here is to + * send a dummy packet to an interface from the context of a kernel thread. + * Therefore, an XNU's receive thread (which is created as a result if we are + * the first who is communicating with the interface) will be associated with + * the kernel thread instead of VirtualBoxVM process. + * + * @param pIfNet Interface to be used to send data. + */ +static void vboxNetFltSendDummy(ifnet_t pIfNet) +{ + RTTHREAD hThread; + int rc = RTThreadCreate(&hThread, vboxNetFltSendDummyWorker, (void *)pIfNet, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "DummyThread"); + if (RT_SUCCESS(rc)) + { + RTThreadWait(hThread, RT_INDEFINITE_WAIT, NULL); + LogFlow(("vboxNetFltSendDummy: a dummy packet has been successfully sent in order to prevent stuck-in-dock issue\n")); + } + else + LogFlow(("vboxNetFltSendDummy: unable to send dummy packet in order to prevent stuck-in-dock issue\n")); +} + + +/** + * Internal worker for vboxNetFltOsInitInstance and vboxNetFltOsMaybeRediscovered. + * + * @returns VBox status code. + * @param pThis The instance. + * @param fRediscovery If set we're doing a rediscovery attempt, so, don't + * flood the release log. + */ +static int vboxNetFltDarwinAttachToInterface(PVBOXNETFLTINS pThis, bool fRediscovery) +{ + LogFlow(("vboxNetFltDarwinAttachToInterface: pThis=%p (%s)\n", pThis, pThis->szName)); + IPRT_DARWIN_SAVE_EFL_AC(); + + /* + * Locate the interface first. + * + * The pIfNet member is updated before iflt_attach is called and used + * to deal with the hypothetical case where someone rips out the + * interface immediately after our iflt_attach call. + */ + ifnet_t pIfNet = NULL; + errno_t err = ifnet_find_by_name(pThis->szName, &pIfNet); + if (err) + { + Assert(err == ENXIO); + if (!fRediscovery) + LogRel(("VBoxFltDrv: failed to find ifnet '%s' (err=%d)\n", pThis->szName, err)); + else + Log(("VBoxFltDrv: failed to find ifnet '%s' (err=%d)\n", pThis->szName, err)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_NOT_FOUND; + } + + AssertCompileMemberAlignment(VBOXNETFLTINS, u.s.pIfNet, ARCH_BITS / 8); + AssertMsg(!((uintptr_t)&pThis->u.s.pIfNet & (ARCH_BITS / 8 - 1)), ("pThis=%p\n", pThis)); + RTSpinlockAcquire(pThis->hSpinlock); + ASMAtomicUoWritePtr(&pThis->u.s.pIfNet, pIfNet); + RTSpinlockRelease(pThis->hSpinlock); + + /* Adjust g_offIfNetPCount as it varies for different versions of xnu. */ + vboxNetFltDarwinDetectPCountOffset(pIfNet); + + /* Prevent stuck-in-dock issue by associating interface receive thread with kernel thread. */ + vboxNetFltSendDummy(pIfNet); + + /* + * Get the mac address while we still have a valid ifnet reference. + */ + err = ifnet_lladdr_copy_bytes(pIfNet, &pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); + if (!err) + { + /* + * Try attach the filter. + */ + struct iff_filter RegRec; + RegRec.iff_cookie = pThis; + RegRec.iff_name = "VBoxNetFlt"; + RegRec.iff_protocol = 0; + RegRec.iff_input = vboxNetFltDarwinIffInput; + RegRec.iff_output = vboxNetFltDarwinIffOutput; + RegRec.iff_event = vboxNetFltDarwinIffEvent; + RegRec.iff_ioctl = vboxNetFltDarwinIffIoCtl; + RegRec.iff_detached = vboxNetFltDarwinIffDetached; + interface_filter_t pIfFilter = NULL; + err = iflt_attach(pIfNet, &RegRec, &pIfFilter); + Assert(err || pIfFilter); + + RTSpinlockAcquire(pThis->hSpinlock); + pIfNet = ASMAtomicUoReadPtrT(&pThis->u.s.pIfNet, ifnet_t); + if (pIfNet && !err) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + ASMAtomicUoWritePtr(&pThis->u.s.pIfFilter, pIfFilter); + pIfNet = NULL; /* don't dereference it */ + } + RTSpinlockRelease(pThis->hSpinlock); + + /* Report capabilities. */ + if ( !pIfNet + && vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); +#if 0 + /* + * XXX: Don't tell SrvIntNetR0 if the interface is + * promiscuous, because there's no code yet to update that + * information and we don't want it stuck, spamming all + * traffic to the host. + */ + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, vboxNetFltDarwinIsPromiscuous(pThis)); +#endif + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + } + + /* Release the interface on failure. */ + if (pIfNet) + ifnet_release(pIfNet); + + int rc = RTErrConvertFromErrno(err); + if (RT_SUCCESS(rc)) + LogRel(("VBoxFltDrv: attached to '%s' / %RTmac\n", pThis->szName, &pThis->u.s.MacAddr)); + else + LogRel(("VBoxFltDrv: failed to attach to ifnet '%s' (err=%d)\n", pThis->szName, err)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + vboxNetFltDarwinAttachToInterface(pThis, true /* fRediscovery */); + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + NOREF(pvIfData); + + int rc = VINF_SUCCESS; + ifnet_t pIfNet = vboxNetFltDarwinRetainIfNet(pThis); + if (pIfNet) + { + /* + * Create a mbuf for the gather list and push it onto the wire. + * BPF tap and stats will be taken care of by the driver. + */ + if (fDst & INTNETTRUNKDIR_WIRE) + { + mbuf_t pMBuf = vboxNetFltDarwinMBufFromSG(pThis, pSG); + if (pMBuf) + { + errno_t err = ifnet_output_raw(pIfNet, PF_LINK, pMBuf); + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + /* + * Create a mbuf for the gather list and push it onto the host stack. + * BPF tap and stats are on us. + */ + if (fDst & INTNETTRUNKDIR_HOST) + { + mbuf_t pMBuf = vboxNetFltDarwinMBufFromSG(pThis, pSG); + if (pMBuf) + { + void *pvEthHdr = mbuf_data(pMBuf); + unsigned const cbEthHdr = 14; + struct ifnet_stat_increment_param stats; + + RT_ZERO(stats); + stats.packets_in = 1; + stats.bytes_in = mbuf_len(pMBuf); /* full ethernet frame */ + + mbuf_pkthdr_setrcvif(pMBuf, pIfNet); + mbuf_pkthdr_setheader(pMBuf, pvEthHdr); /* link-layer header */ + mbuf_adj(pMBuf, cbEthHdr); /* move to payload */ + +#if 0 /* XXX: disabled since we don't request promiscuous from intnet */ + /* + * TODO: Since intnet knows whether it forwarded us + * this packet because it's for us or because we are + * promiscuous, it can perhaps set a flag for us in + * INTNETSG::fFlags so that we don't have to re-check + * it here. + */ + PCRTNETETHERHDR pcEthHdr = (PCRTNETETHERHDR)pvEthHdr; + if ( (pcEthHdr->DstMac.au8[0] & 1) == 0 /* unicast? */ + && memcmp(&pcEthHdr->DstMac, &pThis->u.s.MacAddr, sizeof(RTMAC)) != 0) + { + mbuf_setflags_mask(pMBuf, MBUF_PROMISC, MBUF_PROMISC); + } +#endif + + bpf_tap_in(pIfNet, DLT_EN10MB, pMBuf, pvEthHdr, cbEthHdr); + errno_t err = ifnet_input(pIfNet, pMBuf, &stats); + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + vboxNetFltDarwinReleaseIfNet(pThis, pIfNet); + } + + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; +} + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + ifnet_t pIfNet = vboxNetFltDarwinRetainIfNet(pThis); + if (pIfNet) + { + if (pThis->fDisablePromiscuous) + { + /* + * Promiscuous mode should not be used (wireless), we just need to + * make sure the interface is up. + */ + if (fActive) + { + u_int16_t fIf = ifnet_flags(pIfNet); + if ((fIf & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) + { + ifnet_set_flags(pIfNet, IFF_UP, IFF_UP); + ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + } + } + } + else + { + /* + * This api is a bit weird, the best reference is the code. + * + * Also, we have a bit or race conditions wrt the maintenance of + * host the interface promiscuity for vboxNetFltPortOsIsPromiscuous. + */ + unsigned const cPromiscBefore = VBOX_GET_PCOUNT(pIfNet); + u_int16_t fIf; + if (fActive) + { + Assert(!pThis->u.s.fSetPromiscuous); + errno_t err = ENETDOWN; + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, true); + + /* + * Try bring the interface up and running if it's down. + */ + fIf = ifnet_flags(pIfNet); + if ((fIf & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) + { + err = ifnet_set_flags(pIfNet, IFF_UP, IFF_UP); + errno_t err2 = ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + if (!err) + err = err2; + fIf = ifnet_flags(pIfNet); + } + + /* + * Is it already up? If it isn't, leave it to the link event or + * we'll upset if_pcount (as stated above, ifnet_set_promiscuous is weird). + */ + if ((fIf & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) + { + err = ifnet_set_promiscuous(pIfNet, 1); + pThis->u.s.fSetPromiscuous = err == 0; + if (!err) + { + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + + /* check if it actually worked, this stuff is not always behaving well. */ + if (!(ifnet_flags(pIfNet) & IFF_PROMISC)) + { + err = ifnet_set_flags(pIfNet, IFF_PROMISC, IFF_PROMISC); + if (!err) + err = ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + if (!err) + Log(("vboxNetFlt: fixed IFF_PROMISC on %s (%d->%d)\n", pThis->szName, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + else + Log(("VBoxNetFlt: failed to fix IFF_PROMISC on %s, err=%d (%d->%d)\n", + pThis->szName, err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + } + else + Log(("VBoxNetFlt: ifnet_set_promiscuous -> err=%d grr! (%d->%d)\n", err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + else if (!err) + Log(("VBoxNetFlt: Waiting for the link to come up... (%d->%d)\n", cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + if (err) + LogRel(("VBoxNetFlt: Failed to put '%s' into promiscuous mode, err=%d (%d->%d)\n", pThis->szName, err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + else + { + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + if (pThis->u.s.fSetPromiscuous) + { + errno_t err = ifnet_set_promiscuous(pIfNet, 0); + AssertMsg(!err, ("%d\n", err)); NOREF(err); + } + pThis->u.s.fSetPromiscuous = false; + + fIf = ifnet_flags(pIfNet); + Log(("VBoxNetFlt: fIf=%#x; %d->%d\n", fIf, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + } + + vboxNetFltDarwinReleaseIfNet(pThis, pIfNet); + } + IPRT_DARWIN_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + /* Nothing to do here. */ + RT_NOREF(pThis); + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + /* Nothing to do here. */ + RT_NOREF(pThis); + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + + /* + * Carefully obtain the interface filter reference and detach it. + */ + RTSpinlockAcquire(pThis->hSpinlock); + interface_filter_t pIfFilter = ASMAtomicUoReadPtrT(&pThis->u.s.pIfFilter, interface_filter_t); + if (pIfFilter) + ASMAtomicUoWriteNullPtr(&pThis->u.s.pIfFilter); + RTSpinlockRelease(pThis->hSpinlock); + + if (pIfFilter) + iflt_detach(pIfFilter); + + if (pThis->u.s.pSysSock != NULL) + { + RT_GCC_NO_WARN_DEPRECATED_BEGIN + + sock_close(pThis->u.s.pSysSock); + pThis->u.s.pSysSock = NULL; + + RT_GCC_NO_WARN_DEPRECATED_END + } + + IPRT_DARWIN_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + NOREF(pvContext); + + int rc = vboxNetFltDarwinAttachToInterface(pThis, false /* fRediscovery */); + if (RT_FAILURE(rc)) + return rc; + + if (pThis->pSwitchPort->pfnNotifyHostAddress == NULL) + return rc; + + /* + * XXX: uwe + * + * Learn host's IP addresses and set up notifications for changes. + * To avoid racing, set up notifications first. + * + * XXX: This should probably be global, since the only thing + * specific to ifnet here is its IPv6 link-local address. + */ + IPRT_DARWIN_SAVE_EFL_AC(); + errno_t error; + + /** @todo Figure out how to replace the socket stuff we use to detect + * addresses here as 10.5 deprecates it. */ + RT_GCC_NO_WARN_DEPRECATED_BEGIN + + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + + error = sock_socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT, + vboxNetFltDarwinSysSockUpcall, pThis, + &pThis->u.s.pSysSock); + if (error != 0) + { + LogRel(("sock_socket(SYSPROTO_EVENT): error %d\n", error)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + int nbio = 1; + error = sock_ioctl(pThis->u.s.pSysSock, FIONBIO, &nbio); + if (error != 0) + { + LogRel(("FIONBIO: error %d\n", error)); + sock_close(pThis->u.s.pSysSock); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + if (!sock_isnonblocking(pThis->u.s.pSysSock)) + { + LogRel(("FIONBIO ok, but socket is blocking?!\n")); + sock_close(pThis->u.s.pSysSock); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + struct kev_request req; + req.vendor_code = KEV_VENDOR_APPLE; + req.kev_class = KEV_NETWORK_CLASS; + req.kev_subclass = KEV_ANY_SUBCLASS; /* need both INET and INET6, so have to request all */ + + error = sock_ioctl(pThis->u.s.pSysSock, SIOCSKEVFILT, &req); + if (error != 0) + { + LogRel(("SIOCSKEVFILT: error %d\n", error)); + sock_close(pThis->u.s.pSysSock); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + RT_GCC_NO_WARN_DEPRECATED_END + + ifnet_t pIfNet = pThis->u.s.pIfNet; /* already retained */ + + ifaddr_t *pIfAddrList; + error = ifnet_get_address_list(/* all interfaces*/ NULL, &pIfAddrList); + if (error != 0) + { + LogRel(("ifnet_get_address_list: error %d\n", error)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + for (ifaddr_t *pIfAddr = pIfAddrList; *pIfAddr != NULL; ++pIfAddr) + { + ifaddr_t ifa = *pIfAddr; + sa_family_t family = ifaddr_address_family(ifa); + struct sockaddr_storage ss; + + error = ifaddr_address(ifa, (struct sockaddr *)&ss, sizeof(ss)); + if (error != 0) + { + LogRel(("getting address family %d: error %d\n", family, error)); + continue; + } + + if (family == AF_INET) + { + struct sockaddr_in *sin = (struct sockaddr_in *)&ss; + u_int32_t u32Addr = ntohl(sin->sin_addr.s_addr); + + if (VBOX_IN_LOOPBACK(u32Addr)) + continue; + + if (ifaddr_ifnet(ifa) != pIfNet && VBOX_IN_LINKLOCAL(u32Addr)) + continue; + + Log(("> inet %RTnaipv4\n", sin->sin_addr.s_addr)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv4, &sin->sin_addr); + } + else if (family == AF_INET6) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss; + + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + continue; + + /* link-local from other interfaces are out of scope */ + if (ifaddr_ifnet(ifa) != pIfNet && IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + continue; + + Log(("> inet6 %RTnaipv6\n", &sin6->sin6_addr)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv6, &sin6->sin6_addr); + } + } + + ifnet_free_address_list(pIfAddrList); + + /* + * Now that we've got current addresses, check for events that + * might have happened while we were working. + */ + vboxNetFltDarwinSysSockUpcall(pThis->u.s.pSysSock, pThis, MBUF_DONTWAIT); + + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; +} + + +static void vboxNetFltDarwinSysSockUpcall(socket_t pSysSock, void *pvData, int fWait) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvData; + errno_t error; + + NOREF(fWait); + + if (RT_UNLIKELY(pSysSock != pThis->u.s.pSysSock)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %p != %p?\n", pSysSock, pThis->u.s.pSysSock)); + return; + } + + ifnet_t pIfNet = pThis->u.s.pIfNet; /* XXX: retain? */ + ifnet_family_t if_family = ifnet_family(pIfNet); + u_int32_t if_unit = ifnet_unit(pIfNet); + + for (;;) + { + mbuf_t m; + size_t len = sizeof(struct kern_event_msg) - sizeof(u_int32_t) + sizeof(struct kev_in6_data); + + RT_GCC_NO_WARN_DEPRECATED_BEGIN + error = sock_receivembuf(pSysSock, NULL, &m, 0, &len); + RT_GCC_NO_WARN_DEPRECATED_END + if (error != 0) + { + if (error == EWOULDBLOCK) + { + Log(("vboxNetFltDarwinSysSockUpcall: EWOULDBLOCK - we are done\n")); + error = 0; + } + else + Log(("sock_receivembuf: error %d\n", error)); + break; + } + + if (len < sizeof(struct kern_event_msg) - sizeof(u_int32_t)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %u bytes is too short\n", (unsigned int)len)); + mbuf_freem(m); + return; + } + + struct kern_event_msg *msg = (struct kern_event_msg *)mbuf_data(m); + if (msg->kev_subclass == KEV_INET_SUBCLASS) + { + if (len - (sizeof(struct kern_event_msg) - sizeof(u_int32_t)) < sizeof(struct kev_in_data)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %u bytes is too short for KEV_INET_SUBCLASS\n", (unsigned int)len)); + mbuf_freem(m); + return; + } + + struct kev_in_data *iev = (struct kev_in_data *)msg->event_data; + struct net_event_data *link = &iev->link_data; + PCRTNETADDRU pAddr = (PCRTNETADDRU)&iev->ia_addr; + u_int32_t u32Addr = ntohl(pAddr->IPv4.u); + + if (VBOX_IN_LOOPBACK(u32Addr)) + { + mbuf_freem(m); + continue; + } + + if ( (link->if_family != if_family || link->if_unit != if_unit) + && VBOX_IN_LINKLOCAL(u32Addr)) + { + mbuf_freem(m); + continue; + } + + switch (msg->event_code) + { + case KEV_INET_NEW_ADDR: + Log(("KEV_INET_NEW_ADDR %.*s%d: %RTnaipv4\n", IFNAMSIZ, link->if_name, link->if_unit, pAddr->IPv4.u)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, true /*fAdded*/, kIntNetAddrType_IPv4, pAddr); + break; + + case KEV_INET_ADDR_DELETED: + Log(("KEV_INET_ADDR_DELETED %.*s%d: %RTnaipv4\n", IFNAMSIZ, link->if_name, link->if_unit, pAddr->IPv4.u)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, false /*fAdded*/, kIntNetAddrType_IPv4, pAddr); + break; + + default: + Log(("KEV INET event %u %.*s%d: addr %RTnaipv4\n", + msg->event_code, IFNAMSIZ, link->if_name, link->if_unit, pAddr->IPv4.u)); + break; + } + } + else if (msg->kev_subclass == KEV_INET6_SUBCLASS) + { + if (len - (sizeof(struct kern_event_msg) - sizeof(u_int32_t)) < sizeof(struct kev_in6_data)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %u bytes is too short for KEV_INET6_SUBCLASS\n", + (unsigned int)len)); + mbuf_freem(m); + return; + } + + struct kev_in6_data *iev6 = (struct kev_in6_data *)msg->event_data; + struct net_event_data *link = &iev6->link_data; + PCRTNETADDRU pAddr = (PCRTNETADDRU)&iev6->ia_addr.sin6_addr; + + if (IN6_IS_ADDR_LOOPBACK(&iev6->ia_addr.sin6_addr)) + { + mbuf_freem(m); + continue; + } + + if ( (link->if_family != if_family || link->if_unit != if_unit) + && IN6_IS_ADDR_LINKLOCAL(&iev6->ia_addr.sin6_addr)) + { + mbuf_freem(m); + continue; + } + + switch (msg->event_code) + { + case KEV_INET6_NEW_USER_ADDR: + Log(("KEV_INET6_NEW_USER_ADDR %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + goto kev_inet6_new; + + case KEV_INET6_NEW_LL_ADDR: + Log(("KEV_INET6_NEW_LL_ADDR %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + goto kev_inet6_new; + + case KEV_INET6_NEW_RTADV_ADDR: + Log(("KEV_INET6_NEW_RTADV_ADDR %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + goto kev_inet6_new; + + kev_inet6_new: + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, true /*fAdded*/, kIntNetAddrType_IPv6, pAddr); + break; + + case KEV_INET6_ADDR_DELETED: + Log(("KEV_INET6_ADDR_DELETED %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, false /*fAdded*/, kIntNetAddrType_IPv6, pAddr); + break; + + default: + Log(("KEV INET6 event %u %.*s%d: addr %RTnaipv6\n", + msg->event_code, IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + break; + } + } + else + Log(("vboxNetFltDarwinSysSockUpcall: subclass %u ignored\n", (unsigned)msg->kev_subclass)); + + mbuf_freem(m); + } +} + + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + /* + * Init the darwin specific members. + */ + pThis->u.s.pIfNet = NULL; + pThis->u.s.pIfFilter = NULL; + pThis->u.s.fSetPromiscuous = false; + pThis->u.s.fNeedSetPromiscuous = false; + //pThis->u.s.MacAddr = {0}; + pThis->u.s.pSysSock = NULL; + + return VINF_SUCCESS; +} + + +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; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh b/src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh new file mode 100755 index 00000000..0fede55c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh @@ -0,0 +1,133 @@ +#!/bin/bash +## @file +# For development. +# + +# +# Copyright (C) 2006-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 <https://www.gnu.org/licenses>. +# +# 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 +# + +SCRIPT_NAME="loadnetflt" +XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1` + +DRVNAME="VBoxNetFlt.kext" +BUNDLE="org.virtualbox.kext.VBoxNetFlt" + +DEP_DRVNAME="VBoxDrv.kext" +DEP_BUNDLE="org.virtualbox.kext.VBoxDrv" + + +DIR=`dirname "$0"` +DIR=`cd "$DIR" && pwd` +DEP_DIR="$DIR/$DEP_DRVNAME" +DIR="$DIR/$DRVNAME" +if [ ! -d "$DIR" ]; then + echo "Cannot find $DIR or it's not a directory..." + exit 1; +fi +if [ ! -d "$DEP_DIR" ]; then + echo "Cannot find $DEP_DIR or it's not a directory... (dependency)" + exit 1; +fi +if [ -n "$*" ]; then + OPTS="$*" +else + OPTS="-t" +fi + +trap "sudo chown -R `whoami` $DIR $DEP_DIR; exit 1" INT + +# Try unload any existing instance first. +LOADED=`kextstat -b $BUNDLE -l` +if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: Unloading $BUNDLE..." + sudo kextunload -v 6 -b $BUNDLE + LOADED=`kextstat -b $BUNDLE -l` + if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: failed to unload $BUNDLE, see above..." + exit 1; + fi + echo "${SCRIPT_NAME}.sh: Successfully unloaded $BUNDLE" +fi + +set -e + +# Copy the .kext to the symbols directory and tweak the kextload options. +if test -n "$VBOX_DARWIN_SYMS"; then + echo "${SCRIPT_NAME}.sh: copying the extension the symbol area..." + rm -Rf "$VBOX_DARWIN_SYMS/$DRVNAME" + mkdir -p "$VBOX_DARWIN_SYMS" + cp -R "$DIR" "$VBOX_DARWIN_SYMS/" + OPTS="$OPTS -s $VBOX_DARWIN_SYMS/ " + sync +fi + +# On smbfs, this might succeed just fine but make no actual changes, +# so we might have to temporarily copy the driver to a local directory. +if sudo chown -R root:wheel "$DIR" "$DEP_DIR"; then + OWNER=`/usr/bin/stat -f "%u" "$DIR"` +else + OWNER=1000 +fi +if test "$OWNER" -ne 0; then + TMP_DIR=/tmp/${SCRIPT_NAME}.tmp + echo "${SCRIPT_NAME}.sh: chown didn't work on $DIR, using temp location $TMP_DIR/$DRVNAME" + + # clean up first (no sudo rm) + if test -e "$TMP_DIR"; then + sudo chown -R `whoami` "$TMP_DIR" + rm -Rf "$TMP_DIR" + fi + + # make a copy and switch over DIR + mkdir -p "$TMP_DIR/" + sudo cp -Rp "$DIR" "$TMP_DIR/" + DIR="$TMP_DIR/$DRVNAME" + + # load.sh puts it here. + DEP_DIR="/tmp/loaddrv.tmp/$DEP_DRVNAME" + + # retry + sudo chown -R root:wheel "$DIR" "$DEP_DIR" +fi + +sudo chmod -R o-rwx "$DIR" +sync +if [ "$XNU_VERSION" -ge "10" ]; then + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextutil $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextutil $OPTS -d "$DEP_DIR" "$DIR" +else + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextload $OPTS -d \"$DEP_DIR\" \"$DIR\")" +sudo kextload $OPTS -d "$DEP_DIR" "$DIR" +fi +sync +sudo chown -R `whoami` "$DIR" "$DEP_DIR" +kextstat | grep org.virtualbox.kext + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile new file mode 100644 index 00000000..596f80a0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile @@ -0,0 +1,57 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox FreeBSD Host Driver. +# + +# +# Copyright (C) 2006-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 <https://www.gnu.org/licenses>. +# +# 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 +# + +KMOD = vboxnetflt + +CFLAGS += -DRT_OS_FREEBSD -DIN_RING0 -DIN_RT_R0 -DIN_SUP_R0 -DVBOX -DRT_WITH_VBOX -Iinclude -I. -Ir0drv -w -DVBOX_WITH_HARDENING -DVIMAGE + +.if (${MACHINE_ARCH} == "i386") + CFLAGS += -DRT_ARCH_X86 +.elif (${MACHINE_ARCH} == "amd64") + CFLAGS += -DRT_ARCH_AMD64 +.endif + +SRCS = \ + VBoxNetFlt-freebsd.c \ + VBoxNetFlt.c \ + SUPR0IdcClient-freebsd.c \ + SUPR0IdcClient.c \ + SUPR0IdcClientComponent.c + +SRCS += device_if.h bus_if.h opt_netgraph.h + +.include <bsd.kmod.mk> + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c new file mode 100644 index 00000000..b8b4212e --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c @@ -0,0 +1,817 @@ +/* $Id: VBoxNetFlt-freebsd.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), FreeBSD Specific Code. + */ + +/* + * Copyright (c) 2009 Fredrik Lindberg <fli@shapeshifter.se> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/param.h> +#undef PVM +#include <sys/types.h> +#include <sys/module.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/syscallsubr.h> +#include <sys/queue.h> +#include <sys/taskqueue.h> + +#include <net/if.h> +#include <net/if_var.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/ethernet.h> + +#include <netgraph/ng_message.h> +#include <netgraph/netgraph.h> +#include <netgraph/ng_parse.h> + +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV +#include <VBox/version.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/intnetinline.h> +#include <iprt/initterm.h> +#include <iprt/string.h> +#include <iprt/spinlock.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/uuid.h> +#include <iprt/alloc.h> +#include <iprt/err.h> + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + +static int vboxnetflt_modevent(struct module *, int, void *); +static ng_constructor_t ng_vboxnetflt_constructor; +static ng_rcvmsg_t ng_vboxnetflt_rcvmsg; +static ng_shutdown_t ng_vboxnetflt_shutdown; +static ng_newhook_t ng_vboxnetflt_newhook; +static ng_rcvdata_t ng_vboxnetflt_rcvdata; +static ng_disconnect_t ng_vboxnetflt_disconnect; +static int ng_vboxnetflt_mod_event(module_t mod, int event, void *data); + +/** Netgraph node type */ +#define NG_VBOXNETFLT_NODE_TYPE "vboxnetflt" +/** Netgraph message cookie */ +#define NGM_VBOXNETFLT_COOKIE 0x56424f58 + +/** Input netgraph hook name */ +#define NG_VBOXNETFLT_HOOK_IN "input" +/** Output netgraph hook name */ +#define NG_VBOXNETFLT_HOOK_OUT "output" + +/** mbuf tag identifier */ +#define MTAG_VBOX 0x56424f58 +/** mbuf packet tag */ +#define PACKET_TAG_VBOX 128 + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800500 +# include <sys/jail.h> +# include <net/vnet.h> + +# define VBOXCURVNET_SET(arg) CURVNET_SET_QUIET(arg) +# define VBOXCURVNET_SET_FROM_UCRED() VBOXCURVNET_SET(CRED_TO_VNET(curthread->td_ucred)) +# define VBOXCURVNET_RESTORE() CURVNET_RESTORE() + +#else /* !defined(__FreeBSD_version) || __FreeBSD_version < 800500 */ + +# define VBOXCURVNET_SET(arg) +# define VBOXCURVNET_SET_FROM_UCRED() +# define VBOXCURVNET_RESTORE() + +#endif /* !defined(__FreeBSD_version) || __FreeBSD_version < 800500 */ + +/* + * Netgraph command list, we don't support any + * additional commands. + */ +static const struct ng_cmdlist ng_vboxnetflt_cmdlist[] = +{ + { 0 } +}; + +/* + * Netgraph type definition + */ +static struct ng_type ng_vboxnetflt_typestruct = +{ + .version = NG_ABI_VERSION, + .name = NG_VBOXNETFLT_NODE_TYPE, + .mod_event = vboxnetflt_modevent, + .constructor= ng_vboxnetflt_constructor, + .rcvmsg = ng_vboxnetflt_rcvmsg, + .shutdown = ng_vboxnetflt_shutdown, + .newhook = ng_vboxnetflt_newhook, + .rcvdata = ng_vboxnetflt_rcvdata, + .disconnect = ng_vboxnetflt_disconnect, + .cmdlist = ng_vboxnetflt_cmdlist, +}; +NETGRAPH_INIT(vboxnetflt, &ng_vboxnetflt_typestruct); + +/* + * Use vboxnetflt because the kernel module is named vboxnetflt and vboxnetadp + * depends on this when loading dependencies. + * NETGRAP_INIT will prefix the given name with ng_ so MODULE_DEPEND needs the + * prefixed name. + */ +MODULE_VERSION(vboxnetflt, 1); +MODULE_DEPEND(ng_vboxnetflt, vboxdrv, 1, 1, 1); + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; + +/** + * Module event handler, called from netgraph subsystem. + */ +static int vboxnetflt_modevent(struct module *pMod, int enmEventType, void *pvArg) +{ + int rc; + + Log(("VBoxNetFltFreeBSDModuleEvent\n")); + + switch (enmEventType) + { + case MOD_LOAD: + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + printf("RTR0Init failed %d\n", rc); + return RTErrConvertToErrno(rc); + } + + memset(&g_VBoxNetFltGlobals, 0, sizeof(VBOXNETFLTGLOBALS)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltGlobals); + if (RT_FAILURE(rc)) + { + printf("vboxNetFltInitGlobalsAndIdc failed %d\n", rc); + return RTErrConvertToErrno(rc); + } + /* No MODULE_VERSION in ng_ether so we can't MODULE_DEPEND it */ + kern_kldload(curthread, "ng_ether", NULL); + break; + + case MOD_UNLOAD: + rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltGlobals); + memset(&g_VBoxNetFltGlobals, 0, sizeof(VBOXNETFLTGLOBALS)); + RTR0Term(); + break; + + case MOD_SHUTDOWN: + case MOD_QUIESCE: + default: + return EOPNOTSUPP; + } + + if (RT_SUCCESS(rc)) + return 0; + return RTErrConvertToErrno(rc); +} + +/* + * Convert from mbufs to vbox scatter-gather data structure + */ +static void vboxNetFltFreeBSDMBufToSG(PVBOXNETFLTINS pThis, struct mbuf *m, PINTNETSG pSG, + unsigned int cSegs, unsigned int segOffset) +{ + static uint8_t const s_abZero[128] = {0}; + unsigned int i; + struct mbuf *m0; + + IntNetSgInitTempSegs(pSG, m_length(m, NULL), cSegs, 0 /*cSegsUsed*/); + + for (m0 = m, i = segOffset; m0; m0 = m0->m_next) + { + if (m0->m_len == 0) + continue; + + pSG->aSegs[i].cb = m0->m_len; + pSG->aSegs[i].pv = mtod(m0, uint8_t *); + pSG->aSegs[i].Phys = NIL_RTHCPHYS; + i++; + } + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (pSG->cbTotal < 60) + { + pSG->aSegs[i].Phys = NIL_RTHCPHYS; + pSG->aSegs[i].pv = (void *)&s_abZero[0]; + pSG->aSegs[i].cb = 60 - pSG->cbTotal; + pSG->cbTotal = 60; + i++; + } +#endif + + pSG->cSegsUsed = i; +} + +/* + * Convert to mbufs from vbox scatter-gather data structure + */ +static struct mbuf * vboxNetFltFreeBSDSGMBufFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG) +{ + struct mbuf *m; + int error; + unsigned int i; + + if (pSG->cbTotal == 0) + return (NULL); + + m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR); + if (m == NULL) + return (NULL); + + m->m_pkthdr.len = m->m_len = 0; + m->m_pkthdr.rcvif = NULL; + + for (i = 0; i < pSG->cSegsUsed; i++) + { + error = m_append(m, pSG->aSegs[i].cb, pSG->aSegs[i].pv); + if (error == 0) + { + m_freem(m); + return (NULL); + } + } + return (m); +} + + +static int ng_vboxnetflt_constructor(node_p node) +{ + /* Nothing to do */ + return (EINVAL); +} + +/* + * Setup netgraph hooks + */ +static int ng_vboxnetflt_newhook(node_p node, hook_p hook, const char *name) +{ + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_VBOXNETFLT_HOOK_IN) == 0) + { +#if __FreeBSD_version >= 800000 + NG_HOOK_SET_TO_INBOUND(hook); +#endif + pThis->u.s.input = hook; + } + else if (strcmp(name, NG_VBOXNETFLT_HOOK_OUT) == 0) + { + pThis->u.s.output = hook; + } + else + return (EINVAL); + + NG_HOOK_HI_STACK(hook); + return (0); +} + +/** + * Netgraph message processing for node specific messages. + * We don't accept any special messages so this is not used. + */ +static int ng_vboxnetflt_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + if (msg->header.typecookie != NGM_VBOXNETFLT_COOKIE) + return (EINVAL); + + switch (msg->header.cmd) + { + default: + error = EINVAL; + } + return (error); +} + +/** + * Handle data on netgraph hooks. + * Frames processing is deferred to a taskqueue because this might + * be called with non-sleepable locks held and code paths inside + * the virtual switch might sleep. + */ +static int ng_vboxnetflt_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + struct ifnet *ifp = pThis->u.s.ifp; + struct mbuf *m; + struct m_tag *mtag; + bool fActive; + + VBOXCURVNET_SET(ifp->if_vnet); + fActive = vboxNetFltTryRetainBusyActive(pThis); + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* Locate tag to see if processing should be skipped for this frame */ + mtag = m_tag_locate(m, MTAG_VBOX, PACKET_TAG_VBOX, NULL); + if (mtag != NULL) + { + m_tag_unlink(m, mtag); + m_tag_free(mtag); + } + + /* + * Handle incoming hook. This is connected to the + * input path of the interface, thus handling incoming frames. + */ + if (pThis->u.s.input == hook) + { + if (mtag != NULL || !fActive) + { + ether_demux(ifp, m); + if (fActive) + vboxNetFltRelease(pThis, true /*fBusy*/); + VBOXCURVNET_RESTORE(); + return (0); + } + mtx_lock_spin(&pThis->u.s.inq.ifq_mtx); + _IF_ENQUEUE(&pThis->u.s.inq, m); + mtx_unlock_spin(&pThis->u.s.inq.ifq_mtx); +#if __FreeBSD_version > 1100100 + taskqueue_enqueue(taskqueue_fast, &pThis->u.s.tskin); +#else + taskqueue_enqueue_fast(taskqueue_fast, &pThis->u.s.tskin); +#endif + } + /* + * Handle mbufs on the outgoing hook, frames going to the interface + */ + else if (pThis->u.s.output == hook) + { + if (mtag != NULL || !fActive) + { + int rc = ether_output_frame(ifp, m); + if (fActive) + vboxNetFltRelease(pThis, true /*fBusy*/); + VBOXCURVNET_RESTORE(); + return rc; + } + mtx_lock_spin(&pThis->u.s.outq.ifq_mtx); + _IF_ENQUEUE(&pThis->u.s.outq, m); + mtx_unlock_spin(&pThis->u.s.outq.ifq_mtx); +#if __FreeBSD_version > 1100100 + taskqueue_enqueue(taskqueue_fast, &pThis->u.s.tskout); +#else + taskqueue_enqueue_fast(taskqueue_fast, &pThis->u.s.tskout); +#endif + } + else + { + m_freem(m); + } + + if (fActive) + vboxNetFltRelease(pThis, true /*fBusy*/); + VBOXCURVNET_RESTORE(); + return (0); +} + +static int ng_vboxnetflt_shutdown(node_p node) +{ + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + bool fActive; + + /* Prevent node shutdown if we're active */ + if (pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE) + return (EBUSY); + NG_NODE_UNREF(node); + return (0); +} + +static int ng_vboxnetflt_disconnect(hook_p hook) +{ + return (0); +} + +/** + * Input processing task, handles incoming frames + */ +static void vboxNetFltFreeBSDinput(void *arg, int pending) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)arg; + struct mbuf *m, *m0; + struct ifnet *ifp = pThis->u.s.ifp; + unsigned int cSegs = 0; + bool fDropIt = false, fActive; + PINTNETSG pSG; + + VBOXCURVNET_SET(ifp->if_vnet); + vboxNetFltRetain(pThis, true /* fBusy */); + for (;;) + { + mtx_lock_spin(&pThis->u.s.inq.ifq_mtx); + _IF_DEQUEUE(&pThis->u.s.inq, m); + mtx_unlock_spin(&pThis->u.s.inq.ifq_mtx); + if (m == NULL) + break; + + for (m0 = m; m0 != NULL; m0 = m0->m_next) + if (m0->m_len > 0) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (m_length(m, NULL) < 60) + cSegs++; +#endif + + /* Create a copy and deliver to the virtual switch */ + pSG = RTMemTmpAlloc(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + vboxNetFltFreeBSDMBufToSG(pThis, m, pSG, cSegs, 0); + fDropIt = pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, INTNETTRUNKDIR_WIRE); + RTMemTmpFree(pSG); + if (fDropIt) + m_freem(m); + else + ether_demux(ifp, m); + } + vboxNetFltRelease(pThis, true /* fBusy */); + VBOXCURVNET_RESTORE(); +} + +/** + * Output processing task, handles outgoing frames + */ +static void vboxNetFltFreeBSDoutput(void *arg, int pending) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)arg; + struct mbuf *m, *m0; + struct ifnet *ifp = pThis->u.s.ifp; + unsigned int cSegs = 0; + bool fDropIt = false, fActive; + PINTNETSG pSG; + + VBOXCURVNET_SET(ifp->if_vnet); + vboxNetFltRetain(pThis, true /* fBusy */); + for (;;) + { + mtx_lock_spin(&pThis->u.s.outq.ifq_mtx); + _IF_DEQUEUE(&pThis->u.s.outq, m); + mtx_unlock_spin(&pThis->u.s.outq.ifq_mtx); + if (m == NULL) + break; + + for (m0 = m; m0 != NULL; m0 = m0->m_next) + if (m0->m_len > 0) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (m_length(m, NULL) < 60) + cSegs++; +#endif + /* Create a copy and deliver to the virtual switch */ + pSG = RTMemTmpAlloc(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + vboxNetFltFreeBSDMBufToSG(pThis, m, pSG, cSegs, 0); + fDropIt = pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, INTNETTRUNKDIR_HOST); + RTMemTmpFree(pSG); + + if (fDropIt) + m_freem(m); + else + ether_output_frame(ifp, m); + } + vboxNetFltRelease(pThis, true /* fBusy */); + VBOXCURVNET_RESTORE(); +} + +/** + * Called to deliver a frame to either the host, the wire or both. + */ +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + NOREF(pvIfData); + + void (*input_f)(struct ifnet *, struct mbuf *); + struct ifnet *ifp; + struct mbuf *m; + struct m_tag *mtag; + bool fActive; + int error; + + ifp = ASMAtomicUoReadPtrT(&pThis->u.s.ifp, struct ifnet *); + VBOXCURVNET_SET(ifp->if_vnet); + + if (fDst & INTNETTRUNKDIR_WIRE) + { + m = vboxNetFltFreeBSDSGMBufFromSG(pThis, pSG); + if (m == NULL) + return VERR_NO_MEMORY; + m = m_pullup(m, ETHER_HDR_LEN); + if (m == NULL) + return VERR_NO_MEMORY; + + m->m_flags |= M_PKTHDR; + ether_output_frame(ifp, m); + } + + if (fDst & INTNETTRUNKDIR_HOST) + { + m = vboxNetFltFreeBSDSGMBufFromSG(pThis, pSG); + if (m == NULL) + return VERR_NO_MEMORY; + m = m_pullup(m, ETHER_HDR_LEN); + if (m == NULL) + return VERR_NO_MEMORY; + /* + * Delivering packets to the host will be captured by the + * input hook. Tag the packet with a mbuf tag so that we + * can skip re-delivery of the packet to the guest during + * input hook processing. + */ + mtag = m_tag_alloc(MTAG_VBOX, PACKET_TAG_VBOX, 0, M_NOWAIT); + if (mtag == NULL) + { + m_freem(m); + return VERR_NO_MEMORY; + } + + m_tag_init(m); + m_tag_prepend(m, mtag); + m->m_flags |= M_PKTHDR; + m->m_pkthdr.rcvif = ifp; + ifp->if_input(ifp, m); + } + VBOXCURVNET_RESTORE(); + return VINF_SUCCESS; +} + +static bool vboxNetFltFreeBsdIsPromiscuous(PVBOXNETFLTINS pThis) +{ + /** @todo This isn't taking into account that we put the interface in + * promiscuous mode. */ + return (pThis->u.s.flags & IFF_PROMISC) ? true : false; +} + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + char nam[NG_NODESIZ]; + struct ifnet *ifp; + node_p node; + + VBOXCURVNET_SET_FROM_UCRED(); + NOREF(pvContext); + ifp = ifunit(pThis->szName); + if (ifp == NULL) + return VERR_INTNET_FLT_IF_NOT_FOUND; + + /* Create a new netgraph node for this instance */ + if (ng_make_node_common(&ng_vboxnetflt_typestruct, &node) != 0) + return VERR_INTERNAL_ERROR; + + RTSpinlockAcquire(pThis->hSpinlock); + + ASMAtomicUoWritePtr(&pThis->u.s.ifp, ifp); + pThis->u.s.node = node; + bcopy(IF_LLADDR(ifp), &pThis->u.s.MacAddr, ETHER_ADDR_LEN); + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + + /* Initialize deferred input queue */ + bzero(&pThis->u.s.inq, sizeof(struct ifqueue)); + mtx_init(&pThis->u.s.inq.ifq_mtx, "vboxnetflt inq", NULL, MTX_SPIN); + TASK_INIT(&pThis->u.s.tskin, 0, vboxNetFltFreeBSDinput, pThis); + + /* Initialize deferred output queue */ + bzero(&pThis->u.s.outq, sizeof(struct ifqueue)); + mtx_init(&pThis->u.s.outq.ifq_mtx, "vboxnetflt outq", NULL, MTX_SPIN); + TASK_INIT(&pThis->u.s.tskout, 0, vboxNetFltFreeBSDoutput, pThis); + + RTSpinlockRelease(pThis->hSpinlock); + + NG_NODE_SET_PRIVATE(node, pThis); + + /* Attempt to name it vboxnetflt_<ifname> */ + snprintf(nam, NG_NODESIZ, "vboxnetflt_%s", pThis->szName); + ng_name_node(node, nam); + + /* Report MAC address, promiscuous mode and GSO capabilities. */ + /** @todo keep these reports up to date, either by polling for changes or + * intercept some control flow if possible. */ + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, vboxNetFltFreeBsdIsPromiscuous(pThis)); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + VBOXCURVNET_RESTORE(); + + return VINF_SUCCESS; +} + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + struct ifnet *ifp, *ifp0; + + ifp = ASMAtomicUoReadPtrT(&pThis->u.s.ifp, struct ifnet *); + VBOXCURVNET_SET(ifp->if_vnet); + /* + * Attempt to check if the interface is still there and re-initialize if + * something has changed. + */ + ifp0 = ifunit(pThis->szName); + if (ifp != ifp0) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, true); + ng_rmnode_self(pThis->u.s.node); + pThis->u.s.node = NULL; + } + VBOXCURVNET_RESTORE(); + + if (ifp0 != NULL) + { + vboxNetFltOsDeleteInstance(pThis); + vboxNetFltOsInitInstance(pThis, NULL); + } + + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + + taskqueue_drain(taskqueue_fast, &pThis->u.s.tskin); + taskqueue_drain(taskqueue_fast, &pThis->u.s.tskout); + + mtx_destroy(&pThis->u.s.inq.ifq_mtx); + mtx_destroy(&pThis->u.s.outq.ifq_mtx); + + VBOXCURVNET_SET_FROM_UCRED(); + if (pThis->u.s.node != NULL) + ng_rmnode_self(pThis->u.s.node); + VBOXCURVNET_RESTORE(); + pThis->u.s.node = NULL; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + + pThis->u.s.ifp = NULL; + pThis->u.s.flags = 0; + pThis->u.s.node = NULL; + return VINF_SUCCESS; +} + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + struct ifnet *ifp; + struct ifreq ifreq; + int error; + node_p node; + struct ng_mesg *msg; + struct ngm_connect *con; + struct ngm_rmhook *rm; + char path[NG_PATHSIZ]; + + Log(("%s: fActive:%d\n", __func__, fActive)); + + ifp = ASMAtomicUoReadPtrT(&pThis->u.s.ifp, struct ifnet *); + VBOXCURVNET_SET(ifp->if_vnet); + node = ASMAtomicUoReadPtrT(&pThis->u.s.node, node_p); + + memset(&ifreq, 0, sizeof(struct ifreq)); + /* Activate interface */ + if (fActive) + { + pThis->u.s.flags = ifp->if_flags; + ifpromisc(ifp, 1); + + /* ng_ether nodes are named after the interface name */ + snprintf(path, sizeof(path), "%s:", ifp->if_xname); + + /* + * Send a netgraph connect message to the ng_ether node + * assigned to the bridged interface. Connecting + * the hooks 'lower' (ng_ether) to out 'input'. + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_CONNECT, + sizeof(struct ngm_connect), M_NOWAIT); + if (msg == NULL) + return; + con = (struct ngm_connect *)msg->data; + snprintf(con->path, NG_PATHSIZ, "vboxnetflt_%s:", ifp->if_xname); + strlcpy(con->ourhook, "lower", NG_HOOKSIZ); + strlcpy(con->peerhook, "input", NG_HOOKSIZ); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + + /* + * Do the same for the hooks 'upper' (ng_ether) and our + * 'output' hook. + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_CONNECT, + sizeof(struct ngm_connect), M_NOWAIT); + if (msg == NULL) + return; + con = (struct ngm_connect *)msg->data; + snprintf(con->path, NG_PATHSIZ, "vboxnetflt_%s:", + ifp->if_xname); + strlcpy(con->ourhook, "upper", sizeof(con->ourhook)); + strlcpy(con->peerhook, "output", sizeof(con->peerhook)); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + } + else + { + /* De-activate interface */ + pThis->u.s.flags = 0; + ifpromisc(ifp, 0); + + /* Disconnect msgs are addressed to ourself */ + snprintf(path, sizeof(path), "vboxnetflt_%s:", ifp->if_xname); + + /* + * Send a netgraph message to disconnect our 'input' hook + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_RMHOOK, + sizeof(struct ngm_rmhook), M_NOWAIT); + if (msg == NULL) + return; + rm = (struct ngm_rmhook *)msg->data; + strlcpy(rm->ourhook, "input", NG_HOOKSIZ); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + + /* + * Send a netgraph message to disconnect our 'output' hook + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_RMHOOK, + sizeof(struct ngm_rmhook), M_NOWAIT); + if (msg == NULL) + return; + rm = (struct ngm_rmhook *)msg->data; + strlcpy(rm->ourhook, "output", NG_HOOKSIZ); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + } + VBOXCURVNET_RESTORE(); +} + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + return VINF_SUCCESS; +} + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + return VINF_SUCCESS; +} + +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; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt new file mode 100755 index 00000000..602b72e4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt @@ -0,0 +1,99 @@ +#!/bin/sh +# $Id: files_vboxnetflt $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-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 <https://www.gnu.org/licenses>. +# +# 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 +# + +VBOX_VBOXNETFLT_SOURCES=" \ + ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \ + ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.h \ + ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \ + ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \ + ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \ + ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \ + ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \ + ${PATH_ROOT}/include/iprt/avl.h=>include/iprt/avl.h \ + ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \ + ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \ + ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.h \ + ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \ + ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \ + ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \ + ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \ + ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \ + ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \ + ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \ + ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \ + ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \ + ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \ + ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \ + ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \ + ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \ + ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \ + ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.h \ + ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \ + ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \ + ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \ + ${PATH_ROOT}/include/iprt/thread.h=>include/iprt/thread.h \ + ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \ + ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \ + ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \ + ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \ + ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \ + ${PATH_ROOT}/include/iprt/uuid.h=>include/iprt/uuid.h \ + ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \ + ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \ + ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \ + ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \ + ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \ + ${PATH_ROOT}/include/VBox/intnet.h=>include/VBox/intnet.h \ + ${PATH_ROOT}/include/VBox/intnetinline.h=>include/VBox/intnetinline.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.h \ + ${PATH_ROOT}/include/VBox/sup.h=>include/VBox/sup.h \ + ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \ + ${PATH_ROOT}/include/VBox/version.h=>include/VBox/version.h \ + ${PATH_ROOT}/include/VBox/SUPDrvMangling.h=>include/VBox/SUPDrvMangling.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c=>VBoxNetFlt-freebsd.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c=>VBoxNetFlt.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h=>VBoxNetFltInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClient.c=>SUPR0IdcClient.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c=>SUPR0IdcClientComponent.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h=>SUPR0IdcClientInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/freebsd/SUPR0IdcClient-freebsd.c=>SUPR0IdcClient-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/the-freebsd-kernel.h=>r0drv/freebsd/the-freebsd-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile new file mode 100644 index 00000000..9c5d5b80 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile @@ -0,0 +1,81 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox Linux Host Network Filter Driver. +# + +# +# Copyright (C) 2006-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 <https://www.gnu.org/licenses>. +# +# 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 +# + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile-header.gmk +VBOXNETFLT_DIR := $(VBOX_MODULE_SRC_DIR) + +# Allow building directly from the subdirectory without assuming the toplevel +# makefile has done the copying. Not the default use case, but can be handy. +ifndef KBUILD_EXTRA_SYMBOLS +KBUILD_EXTRA_SYMBOLS=$(abspath $(VBOXNETFLT_DIR)/../vboxdrv/Module.symvers) +endif + +VBOXMOD_NAME = vboxnetflt +VBOXMOD_OBJS = \ + linux/VBoxNetFlt-linux.o \ + VBoxNetFlt.o \ + SUPR0IdcClient.o \ + SUPR0IdcClientComponent.o \ + linux/SUPR0IdcClient-linux.o +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) +VBOXMOD_OBJS += \ + math/gcc/divdi3.o \ + math/gcc/moddi3.o \ + math/gcc/qdivrem.o \ + math/gcc/udivdi3.o \ + math/gcc/udivmoddi4.o \ + math/gcc/divdi3.o \ + math/gcc/umoddi3.o +endif +VBOXMOD_INCL = \ + $(VBOXNETFLT_DIR) \ + $(VBOXNETFLT_DIR)include \ + $(VBOXNETFLT_DIR)r0drv/linux +VBOXMOD_DEFS = \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_SUP_R0 \ + VBOX \ + RT_WITH_VBOX \ + VBOX_WITH_HARDENING \ + VBOX_WITH_64_BITS_GUESTS # <-- must be consistent with Config.kmk! +VBOXMOD_CFLAGS = -include $(VBOXNETFLT_DIR)include/VBox/SUPDrvMangling.h -fno-pie -Wno-declaration-after-statement + +include $(obj)/Makefile-footer.gmk + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c b/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c new file mode 100644 index 00000000..eaf07e8c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c @@ -0,0 +1,2609 @@ +/* $Id: VBoxNetFlt-linux.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), Linux Specific Code. + */ + +/* + * Copyright (C) 2006-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 <https://www.gnu.org/licenses>. + * + * 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 +#define VBOXNETFLT_LINUX_NO_XMIT_QUEUE +#include "the-linux-kernel.h" +#include "version-generated.h" +#include "revision-generated.h" +#include "product-generated.h" +#if RTLNX_VER_MIN(2,6,24) +# include <linux/nsproxy.h> +#endif +#if RTLNX_VER_MIN(6,4,10) || RTLNX_RHEL_RANGE(9,4, 9,99) +# include <net/gso.h> +#endif +#include <linux/netdevice.h> +#if RTLNX_VER_MAX(2,6,29) || RTLNX_VER_MIN(5,11,0) +# include <linux/ethtool.h> +#endif +#include <linux/etherdevice.h> +#include <linux/rtnetlink.h> +#include <linux/miscdevice.h> +#include <linux/inetdevice.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/if_vlan.h> +#if RTLNX_VER_MIN(4,5,0) +# include <uapi/linux/pkt_cls.h> +#endif +#include <net/ipv6.h> +#include <net/if_inet6.h> +#include <net/addrconf.h> + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/intnetinline.h> +#include <VBox/vmm/pdmnetinline.h> +#include <VBox/param.h> +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/net.h> +#include <iprt/log.h> +#include <iprt/mp.h> +#include <iprt/mem.h> +#include <iprt/time.h> + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + +typedef struct VBOXNETFLTNOTIFIER { + struct notifier_block Notifier; + PVBOXNETFLTINS pThis; +} VBOXNETFLTNOTIFIER; +typedef struct VBOXNETFLTNOTIFIER *PVBOXNETFLTNOTIFIER; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_FLT_NB_TO_INST(pNB) RT_FROM_MEMBER(pNB, VBOXNETFLTINS, u.s.Notifier) +#define VBOX_FLT_PT_TO_INST(pPT) RT_FROM_MEMBER(pPT, VBOXNETFLTINS, u.s.PacketType) +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE +# define VBOX_FLT_XT_TO_INST(pXT) RT_FROM_MEMBER(pXT, VBOXNETFLTINS, u.s.XmitTask) +#endif + +#if RTLNX_VER_MIN(3,11,0) +# define VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr) netdev_notifier_info_to_dev(ptr) +#else +# define VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr) ((struct net_device *)ptr) +#endif + +#if RTLNX_VER_MIN(3,5,0) +# define VBOX_SKB_KMAP_FRAG(frag) kmap_atomic(skb_frag_page(frag)) +# define VBOX_SKB_KUNMAP_FRAG(vaddr) kunmap_atomic(vaddr) +#else +# if RTLNX_VER_MIN(3,2,0) +# define VBOX_SKB_KMAP_FRAG(frag) kmap_atomic(skb_frag_page(frag), KM_SKB_DATA_SOFTIRQ) +# define VBOX_SKB_KUNMAP_FRAG(vaddr) kunmap_atomic(vaddr, KM_SKB_DATA_SOFTIRQ) +# else +# define VBOX_SKB_KMAP_FRAG(frag) kmap_atomic(frag->page, KM_SKB_DATA_SOFTIRQ) +# define VBOX_SKB_KUNMAP_FRAG(vaddr) kunmap_atomic(vaddr, KM_SKB_DATA_SOFTIRQ) +# endif +#endif + +#if RTLNX_VER_MIN(2,6,34) +# define VBOX_NETDEV_NAME(dev) netdev_name(dev) +#else +# define VBOX_NETDEV_NAME(dev) ((dev)->reg_state != NETREG_REGISTERED ? "(unregistered net_device)" : (dev)->name) +#endif + +#if RTLNX_VER_MIN(2,6,25) +# define VBOX_IPV4_IS_LOOPBACK(addr) ipv4_is_loopback(addr) +# define VBOX_IPV4_IS_LINKLOCAL_169(addr) ipv4_is_linklocal_169(addr) +#else +# define VBOX_IPV4_IS_LOOPBACK(addr) ((addr & htonl(IN_CLASSA_NET)) == htonl(0x7f000000)) +# define VBOX_IPV4_IS_LINKLOCAL_169(addr) ((addr & htonl(IN_CLASSB_NET)) == htonl(0xa9fe0000)) +#endif + +#if RTLNX_VER_MIN(2,6,22) +# define VBOX_SKB_RESET_NETWORK_HDR(skb) skb_reset_network_header(skb) +# define VBOX_SKB_RESET_MAC_HDR(skb) skb_reset_mac_header(skb) +# define VBOX_SKB_CSUM_OFFSET(skb) skb->csum_offset +#else +# define VBOX_SKB_RESET_NETWORK_HDR(skb) skb->nh.raw = skb->data +# define VBOX_SKB_RESET_MAC_HDR(skb) skb->mac.raw = skb->data +# define VBOX_SKB_CSUM_OFFSET(skb) skb->csum +#endif + +#if RTLNX_VER_MIN(2,6,19) +# define VBOX_SKB_CHECKSUM_HELP(skb) skb_checksum_help(skb) +#else +# define CHECKSUM_PARTIAL CHECKSUM_HW +# if RTLNX_VER_MIN(2,6,10) +# define VBOX_SKB_CHECKSUM_HELP(skb) skb_checksum_help(skb, 0) +# else +# if RTLNX_VER_MIN(2,6,7) +# define VBOX_SKB_CHECKSUM_HELP(skb) skb_checksum_help(&skb, 0) +# else +# define VBOX_SKB_CHECKSUM_HELP(skb) (!skb_checksum_help(skb)) +# endif +/* Versions prior 2.6.10 use stats for both bstats and qstats */ +# define bstats stats +# define qstats stats +# endif +#endif + +#if RTLNX_VER_MIN(3,20,0) || RTLNX_RHEL_RANGE(7,2, 8,0) || RTLNX_RHEL_RANGE(6,8, 7,0) +# define VBOX_HAVE_SKB_VLAN +#endif + +#ifdef VBOX_HAVE_SKB_VLAN +# define vlan_tx_tag_get(skb) skb_vlan_tag_get(skb) +# define vlan_tx_tag_present(skb) skb_vlan_tag_present(skb) +#endif + +#ifndef NET_IP_ALIGN +# define NET_IP_ALIGN 2 +#endif + +#if 1 +/** Create scatter / gather segments for fragments. When not used, we will + * linearize the socket buffer before creating the internal networking SG. */ +# define VBOXNETFLT_SG_SUPPORT 1 +#endif + +#if RTLNX_VER_MIN(2,6,18) + +/** Indicates that the linux kernel may send us GSO frames. */ +# define VBOXNETFLT_WITH_GSO 1 + +/** This enables or disables the transmitting of GSO frame from the internal + * network and to the host. */ +# define VBOXNETFLT_WITH_GSO_XMIT_HOST 1 + +# if 0 /** @todo This is currently disable because it causes performance loss of 5-10%. */ +/** This enables or disables the transmitting of GSO frame from the internal + * network and to the wire. */ +# define VBOXNETFLT_WITH_GSO_XMIT_WIRE 1 +# endif + +/** This enables or disables the forwarding/flooding of GSO frame from the host + * to the internal network. */ +# define VBOXNETFLT_WITH_GSO_RECV 1 + +#endif /* RTLNX_VER_MIN(2,6,18) */ + +#if RTLNX_VER_MIN(2,6,29) +/** This enables or disables handling of GSO frames coming from the wire (GRO). */ +# define VBOXNETFLT_WITH_GRO 1 +#endif + +/* + * GRO support was backported to RHEL 5.4 + */ +#if RTLNX_RHEL_MAJ_PREREQ(5, 4) +# define VBOXNETFLT_WITH_GRO 1 +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int __init VBoxNetFltLinuxInit(void); +static void __exit VBoxNetFltLinuxUnload(void); +static void vboxNetFltLinuxForwardToIntNet(PVBOXNETFLTINS pThis, struct sk_buff *pBuf); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; + +module_init(VBoxNetFltLinuxInit); +module_exit(VBoxNetFltLinuxUnload); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " Network Filter Driver"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV) " (" RT_XSTR(INTNETTRUNKIFPORT_VERSION) ")"); +#endif + + +#if RTLNX_VER_MAX(2,6,12) && defined(LOG_ENABLED) +unsigned dev_get_flags(const struct net_device *dev) +{ + unsigned flags; + + flags = (dev->flags & ~(IFF_PROMISC | + IFF_ALLMULTI | + IFF_RUNNING)) | + (dev->gflags & (IFF_PROMISC | + IFF_ALLMULTI)); + + if (netif_running(dev) && netif_carrier_ok(dev)) + flags |= IFF_RUNNING; + + return flags; +} +#endif /* RTLNX_VER_MAX(2,6,12) */ + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxNetFltLinuxInit(void) +{ + int rc; + /* + * Initialize IPRT. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxNetFltLinuxInit\n")); + + /* + * 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_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltGlobals); + if (RT_SUCCESS(rc)) + { + LogRel(("VBoxNetFlt: Successfully started.\n")); + return 0; + } + + LogRel(("VBoxNetFlt: failed to initialize device extension (rc=%d)\n", rc)); + RTR0Term(); + } + else + LogRel(("VBoxNetFlt: failed to initialize IPRT (rc=%d)\n", rc)); + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + return -RTErrConvertToErrno(rc); +} + + +/** + * Unload the module. + * + * @todo We have to prevent this if we're busy! + */ +static void __exit VBoxNetFltLinuxUnload(void) +{ + int rc; + Log(("VBoxNetFltLinuxUnload\n")); + Assert(vboxNetFltCanUnload(&g_VBoxNetFltGlobals)); + + /* + * Undo the work done during start (in reverse order). + */ + rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltGlobals); + AssertRC(rc); NOREF(rc); + + RTR0Term(); + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + Log(("VBoxNetFltLinuxUnload - done\n")); +} + + +/** + * We filter traffic from the host to the internal network + * before it reaches the NIC driver. + * + * The current code uses a very ugly hack overriding hard_start_xmit + * callback in the device structure, but it has been shown to give us a + * performance boost of 60-100% though. Eventually we have to find some + * less hacky way of getting this job done. + */ +#define VBOXNETFLT_WITH_HOST2WIRE_FILTER + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + +# if RTLNX_VER_MAX(2,6,29) + +typedef struct ethtool_ops OVR_OPSTYPE; +# define OVR_OPS ethtool_ops +# define OVR_XMIT pfnStartXmit + +# else /* RTLNX_VER_MIN(2,6,29) */ + +typedef struct net_device_ops OVR_OPSTYPE; +# define OVR_OPS netdev_ops +# define OVR_XMIT pOrgOps->ndo_start_xmit + +# endif /* RTLNX_VER_MIN(2,6,29) */ + +/** + * The overridden net_device_ops of the device we're attached to. + * + * As there is no net_device_ops structure in pre-2.6.29 kernels we override + * ethtool_ops instead along with hard_start_xmit callback in net_device + * structure. + * + * This is a very dirty hack that was created to explore how much we can improve + * the host to guest transfers by not CC'ing the NIC. It turns out to be + * the only way to filter outgoing packets for devices without TX queue. + */ +typedef struct VBoxNetDeviceOpsOverride +{ + /** Our overridden ops. */ + OVR_OPSTYPE Ops; + /** Magic word. */ + uint32_t u32Magic; + /** Pointer to the original ops. */ + OVR_OPSTYPE const *pOrgOps; +# if RTLNX_VER_MAX(2,6,29) + /** Pointer to the original hard_start_xmit function. */ + int (*pfnStartXmit)(struct sk_buff *pSkb, struct net_device *pDev); +# endif /* RTLNX_VER_MAX(2,6,29) */ + /** Pointer to the net filter instance. */ + PVBOXNETFLTINS pVBoxNetFlt; + /** The number of filtered packages. */ + uint64_t cFiltered; + /** The total number of packets */ + uint64_t cTotal; +} VBOXNETDEVICEOPSOVERRIDE, *PVBOXNETDEVICEOPSOVERRIDE; +/** VBOXNETDEVICEOPSOVERRIDE::u32Magic value. */ +#define VBOXNETDEVICEOPSOVERRIDE_MAGIC UINT32_C(0x00c0ffee) + +/** + * ndo_start_xmit wrapper that drops packets that shouldn't go to the wire + * because they belong on the internal network. + * + * @returns NETDEV_TX_XXX. + * @param pSkb The socket buffer to transmit. + * @param pDev The net device. + */ +static int vboxNetFltLinuxStartXmitFilter(struct sk_buff *pSkb, struct net_device *pDev) +{ + PVBOXNETDEVICEOPSOVERRIDE pOverride = (PVBOXNETDEVICEOPSOVERRIDE)pDev->OVR_OPS; + uint8_t abHdrBuf[sizeof(RTNETETHERHDR) + sizeof(uint32_t) + RTNETIPV4_MIN_LEN]; + PCRTNETETHERHDR pEtherHdr; + PINTNETTRUNKSWPORT pSwitchPort; + uint32_t cbHdrs; + + + /* + * Validate the override structure. + * + * Note! We're racing vboxNetFltLinuxUnhookDev here. If this was supposed + * to be production quality code, we would have to be much more + * careful here and avoid the race. + */ + if ( !RT_VALID_PTR(pOverride) + || pOverride->u32Magic != VBOXNETDEVICEOPSOVERRIDE_MAGIC +# if RTLNX_VER_MIN(2,6,29) + || !RT_VALID_PTR(pOverride->pOrgOps) +# endif + ) + { + printk("vboxNetFltLinuxStartXmitFilter: bad override %p\n", pOverride); + dev_kfree_skb(pSkb); + return NETDEV_TX_OK; + } + pOverride->cTotal++; + + /* + * Do the filtering base on the default OUI of our virtual NICs + * + * Note! In a real solution, we would ask the switch whether the + * destination MAC is 100% to be on the internal network and then + * drop it. + */ + cbHdrs = skb_headlen(pSkb); + cbHdrs = RT_MIN(cbHdrs, sizeof(abHdrBuf)); + pEtherHdr = (PCRTNETETHERHDR)skb_header_pointer(pSkb, 0, cbHdrs, &abHdrBuf[0]); + if ( pEtherHdr + && RT_VALID_PTR(pOverride->pVBoxNetFlt) + && (pSwitchPort = pOverride->pVBoxNetFlt->pSwitchPort) != NULL + && RT_VALID_PTR(pSwitchPort) + && cbHdrs >= 6) + { + INTNETSWDECISION enmDecision; + + /** @todo consider reference counting, etc. */ + enmDecision = pSwitchPort->pfnPreRecv(pSwitchPort, pEtherHdr, cbHdrs, INTNETTRUNKDIR_HOST); + if (enmDecision == INTNETSWDECISION_INTNET) + { + dev_kfree_skb(pSkb); + pOverride->cFiltered++; + return NETDEV_TX_OK; + } + } + + return pOverride->OVR_XMIT(pSkb, pDev); +} + +/** + * Hooks the device ndo_start_xmit operation of the device. + * + * @param pThis The net filter instance. + * @param pDev The net device. + */ +static void vboxNetFltLinuxHookDev(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + PVBOXNETDEVICEOPSOVERRIDE pOverride; + + /* Cancel override if ethtool_ops is missing (host-only case, @bugref{5712}) */ + if (!RT_VALID_PTR(pDev->OVR_OPS)) + return; + pOverride = RTMemAlloc(sizeof(*pOverride)); + if (!pOverride) + return; + pOverride->pOrgOps = pDev->OVR_OPS; + pOverride->Ops = *pDev->OVR_OPS; +# if RTLNX_VER_MAX(2,6,29) + pOverride->pfnStartXmit = pDev->hard_start_xmit; +# else /* RTLNX_VER_MIN(2,6,29) */ + pOverride->Ops.ndo_start_xmit = vboxNetFltLinuxStartXmitFilter; +# endif /* RTLNX_VER_MIN(2,6,29) */ + pOverride->u32Magic = VBOXNETDEVICEOPSOVERRIDE_MAGIC; + pOverride->cTotal = 0; + pOverride->cFiltered = 0; + pOverride->pVBoxNetFlt = pThis; + + RTSpinlockAcquire(pThis->hSpinlock); /* (this isn't necessary, but so what) */ + ASMAtomicWritePtr((void * volatile *)&pDev->OVR_OPS, pOverride); +# if RTLNX_VER_MAX(2,6,29) + ASMAtomicXchgPtr((void * volatile *)&pDev->hard_start_xmit, vboxNetFltLinuxStartXmitFilter); +# endif /* RTLNX_VER_MAX(2,6,29) */ + RTSpinlockRelease(pThis->hSpinlock); +} + +/** + * Undos what vboxNetFltLinuxHookDev did. + * + * @param pThis The net filter instance. + * @param pDev The net device. Can be NULL, in which case + * we'll try retrieve it from @a pThis. + */ +static void vboxNetFltLinuxUnhookDev(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + PVBOXNETDEVICEOPSOVERRIDE pOverride; + + RTSpinlockAcquire(pThis->hSpinlock); + if (!pDev) + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (RT_VALID_PTR(pDev)) + { + pOverride = (PVBOXNETDEVICEOPSOVERRIDE)pDev->OVR_OPS; + if ( RT_VALID_PTR(pOverride) + && pOverride->u32Magic == VBOXNETDEVICEOPSOVERRIDE_MAGIC + && RT_VALID_PTR(pOverride->pOrgOps) + ) + { +# if RTLNX_VER_MAX(2,6,29) + ASMAtomicWritePtr((void * volatile *)&pDev->hard_start_xmit, pOverride->pfnStartXmit); +# endif /* RTLNX_VER_MAX(2,6,29) */ + ASMAtomicWritePtr((void const * volatile *)&pDev->OVR_OPS, pOverride->pOrgOps); + ASMAtomicWriteU32(&pOverride->u32Magic, 0); + } + else + pOverride = NULL; + } + else + pOverride = NULL; + RTSpinlockRelease(pThis->hSpinlock); + + if (pOverride) + { + printk("vboxnetflt: %llu out of %llu packets were not sent (directed to host)\n", pOverride->cFiltered, pOverride->cTotal); + RTMemFree(pOverride); + } +} + +#endif /* VBOXNETFLT_WITH_HOST2WIRE_FILTER */ + + +/** + * Reads and retains the host interface handle. + * + * @returns The handle, NULL if detached. + * @param pThis + */ +DECLINLINE(struct net_device *) vboxNetFltLinuxRetainNetDev(PVBOXNETFLTINS pThis) +{ +#if 0 + struct net_device *pDev = NULL; + + Log(("vboxNetFltLinuxRetainNetDev\n")); + /* + * Be careful here to avoid problems racing the detached callback. + */ + RTSpinlockAcquire(pThis->hSpinlock); + if (!ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)) + { + pDev = (struct net_device *)ASMAtomicUoReadPtr((void * volatile *)&pThis->u.s.pDev); + if (pDev) + { + dev_hold(pDev); + Log(("vboxNetFltLinuxRetainNetDev: Device %p(%s) retained. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + } + } + RTSpinlockRelease(pThis->hSpinlock); + + Log(("vboxNetFltLinuxRetainNetDev - done\n")); + return pDev; +#else + return ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); +#endif +} + + +/** + * Release the host interface handle previously retained + * by vboxNetFltLinuxRetainNetDev. + * + * @param pThis The instance. + * @param pDev The vboxNetFltLinuxRetainNetDev + * return value, NULL is fine. + */ +DECLINLINE(void) vboxNetFltLinuxReleaseNetDev(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ +#if 0 + Log(("vboxNetFltLinuxReleaseNetDev\n")); + NOREF(pThis); + if (pDev) + { + dev_put(pDev); + Log(("vboxNetFltLinuxReleaseNetDev: Device %p(%s) released. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + } + Log(("vboxNetFltLinuxReleaseNetDev - done\n")); +#endif +} + +#define VBOXNETFLT_CB_TAG(skb) (0xA1C90000 | (skb->dev->ifindex & 0xFFFF)) +#define VBOXNETFLT_SKB_TAG(skb) (*(uint32_t*)&((skb)->cb[sizeof((skb)->cb)-sizeof(uint32_t)])) + +/** + * Checks whether this is an mbuf created by vboxNetFltLinuxMBufFromSG, + * i.e. a buffer which we're pushing and should be ignored by the filter callbacks. + * + * @returns true / false accordingly. + * @param pBuf The sk_buff. + */ +DECLINLINE(bool) vboxNetFltLinuxSkBufIsOur(struct sk_buff *pBuf) +{ + return VBOXNETFLT_SKB_TAG(pBuf) == VBOXNETFLT_CB_TAG(pBuf); +} + + +/** + * Checks whether this SG list contains a GSO packet. + * + * @returns true / false accordingly. + * @param pSG The (scatter/)gather list. + */ +DECLINLINE(bool) vboxNetFltLinuxIsGso(PINTNETSG pSG) +{ +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + return !((PDMNETWORKGSOTYPE)pSG->GsoCtx.u8Type == PDMNETWORKGSOTYPE_INVALID); +#else /* !VBOXNETFLT_WITH_GSO_XMIT_WIRE && !VBOXNETFLT_WITH_GSO_XMIT_HOST */ + return false; +#endif /* !VBOXNETFLT_WITH_GSO_XMIT_WIRE && !VBOXNETFLT_WITH_GSO_XMIT_HOST */ +} + + +/** + * Find out the frame size (of a single segment in case of GSO frames). + * + * @returns the frame size. + * @param pSG The (scatter/)gather list. + */ +DECLINLINE(uint32_t) vboxNetFltLinuxFrameSize(PINTNETSG pSG) +{ + uint16_t u16Type = 0; + uint32_t cbVlanTag = 0; + if (pSG->aSegs[0].cb >= sizeof(RTNETETHERHDR)) + u16Type = RT_BE2H_U16(((PCRTNETETHERHDR)pSG->aSegs[0].pv)->EtherType); + else if (pSG->cbTotal >= sizeof(RTNETETHERHDR)) + { + uint32_t off = RT_UOFFSETOF(RTNETETHERHDR, EtherType); + uint32_t i; + for (i = 0; i < pSG->cSegsUsed; ++i) + { + if (off <= pSG->aSegs[i].cb) + { + if (off + sizeof(uint16_t) <= pSG->aSegs[i].cb) + u16Type = RT_BE2H_U16(*(uint16_t *)((uintptr_t)pSG->aSegs[i].pv + off)); + else if (i + 1 < pSG->cSegsUsed) + u16Type = RT_BE2H_U16( ((uint16_t)( ((uint8_t *)pSG->aSegs[i].pv)[off] ) << 8) + + *(uint8_t *)pSG->aSegs[i + 1].pv); /* ASSUMES no empty segments! */ + /* else: frame is too short. */ + break; + } + off -= pSG->aSegs[i].cb; + } + } + if (u16Type == RTNET_ETHERTYPE_VLAN) + cbVlanTag = 4; + return (vboxNetFltLinuxIsGso(pSG) ? (uint32_t)pSG->GsoCtx.cbMaxSeg + pSG->GsoCtx.cbHdrsTotal : pSG->cbTotal) - cbVlanTag; +} + + +/** + * Internal worker that create a linux sk_buff for a + * (scatter/)gather list. + * + * @returns Pointer to the sk_buff. + * @param pThis The instance. + * @param pSG The (scatter/)gather list. + * @param fDstWire Set if the destination is the wire. + */ +static struct sk_buff *vboxNetFltLinuxSkBufFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, bool fDstWire) +{ + struct sk_buff *pPkt; + struct net_device *pDev; +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + unsigned fGsoType = 0; +#endif + + if (pSG->cbTotal == 0) + { + LogRel(("VBoxNetFlt: Dropped empty packet coming from internal network.\n")); + return NULL; + } + Log5(("VBoxNetFlt: Packet to %s of %d bytes (frame=%d).\n", fDstWire?"wire":"host", pSG->cbTotal, vboxNetFltLinuxFrameSize(pSG))); + if (fDstWire && (vboxNetFltLinuxFrameSize(pSG) > ASMAtomicReadU32(&pThis->u.s.cbMtu) + 14)) + { + static bool s_fOnce = true; + if (s_fOnce) + { + s_fOnce = false; + printk("VBoxNetFlt: Dropped over-sized packet (%d bytes) coming from internal network.\n", vboxNetFltLinuxFrameSize(pSG)); + } + return NULL; + } + + /** @todo We should use fragments mapping the SG buffers with large packets. + * 256 bytes seems to be the a threshold used a lot for this. It + * requires some nasty work on the intnet side though... */ + /* + * Allocate a packet and copy over the data. + */ + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + pPkt = dev_alloc_skb(pSG->cbTotal + NET_IP_ALIGN); + if (RT_UNLIKELY(!pPkt)) + { + Log(("vboxNetFltLinuxSkBufFromSG: Failed to allocate sk_buff(%u).\n", pSG->cbTotal)); + pSG->pvUserData = NULL; + return NULL; + } + pPkt->dev = pDev; + pPkt->ip_summed = CHECKSUM_NONE; + + /* Align IP header on 16-byte boundary: 2 + 14 (ethernet hdr size). */ + skb_reserve(pPkt, NET_IP_ALIGN); + + /* Copy the segments. */ + skb_put(pPkt, pSG->cbTotal); + IntNetSgRead(pSG, pPkt->data); + +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + /* + * Setup GSO if used by this packet. + */ + switch ((PDMNETWORKGSOTYPE)pSG->GsoCtx.u8Type) + { + default: + AssertMsgFailed(("%u (%s)\n", pSG->GsoCtx.u8Type, PDMNetGsoTypeName((PDMNETWORKGSOTYPE)pSG->GsoCtx.u8Type) )); + RT_FALL_THRU(); + case PDMNETWORKGSOTYPE_INVALID: + fGsoType = 0; + break; + case PDMNETWORKGSOTYPE_IPV4_TCP: + fGsoType = SKB_GSO_TCPV4; + break; + case PDMNETWORKGSOTYPE_IPV6_TCP: + fGsoType = SKB_GSO_TCPV6; + break; + } + if (fGsoType) + { + struct skb_shared_info *pShInfo = skb_shinfo(pPkt); + + pShInfo->gso_type = fGsoType | SKB_GSO_DODGY; + pShInfo->gso_size = pSG->GsoCtx.cbMaxSeg; + pShInfo->gso_segs = PDMNetGsoCalcSegmentCount(&pSG->GsoCtx, pSG->cbTotal); + + /* + * We need to set checksum fields even if the packet goes to the host + * directly as it may be immediately forwarded by IP layer @bugref{5020}. + */ + Assert(skb_headlen(pPkt) >= pSG->GsoCtx.cbHdrsTotal); + pPkt->ip_summed = CHECKSUM_PARTIAL; +# if RTLNX_VER_MIN(2,6,22) + pPkt->csum_start = skb_headroom(pPkt) + pSG->GsoCtx.offHdr2; + if (fGsoType & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) + pPkt->csum_offset = RT_UOFFSETOF(RTNETTCP, th_sum); + else + pPkt->csum_offset = RT_UOFFSETOF(RTNETUDP, uh_sum); +# else + pPkt->h.raw = pPkt->data + pSG->GsoCtx.offHdr2; + if (fGsoType & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) + pPkt->csum = RT_UOFFSETOF(RTNETTCP, th_sum); + else + pPkt->csum = RT_UOFFSETOF(RTNETUDP, uh_sum); +# endif + if (!fDstWire) + PDMNetGsoPrepForDirectUse(&pSG->GsoCtx, pPkt->data, pSG->cbTotal, PDMNETCSUMTYPE_PSEUDO); + } +#endif /* VBOXNETFLT_WITH_GSO_XMIT_WIRE || VBOXNETFLT_WITH_GSO_XMIT_HOST */ + + /* + * Finish up the socket buffer. + */ + pPkt->protocol = eth_type_trans(pPkt, pDev); + if (fDstWire) + { + VBOX_SKB_RESET_NETWORK_HDR(pPkt); + + /* Restore ethernet header back. */ + skb_push(pPkt, ETH_HLEN); /** @todo VLAN: +4 if VLAN? */ + VBOX_SKB_RESET_MAC_HDR(pPkt); + } + VBOXNETFLT_SKB_TAG(pPkt) = VBOXNETFLT_CB_TAG(pPkt); + + return pPkt; +} + + +/** + * Return the offset where to start checksum computation from. + * + * @returns the offset relative to pBuf->data. + * @param pBuf The socket buffer. + */ +DECLINLINE(unsigned) vboxNetFltLinuxGetChecksumStartOffset(struct sk_buff *pBuf) +{ +#if RTLNX_VER_MIN(2,6,38) + return skb_checksum_start_offset(pBuf); +#elif RTLNX_VER_MIN(2,6,22) + return pBuf->csum_start - skb_headroom(pBuf); +#else + unsigned char *pTransportHdr = pBuf->h.raw; +# if RTLNX_VER_MAX(2,6,19) + /* + * Try to work around the problem with CentOS 4.7 and 5.2 (2.6.9 + * and 2.6.18 kernels), they pass wrong 'h' pointer down. We take IP + * header length from the header itself and reconstruct 'h' pointer + * to TCP (or whatever) header. + */ + if (pBuf->h.raw == pBuf->nh.raw && pBuf->protocol == htons(ETH_P_IP)) + pTransportHdr = pBuf->nh.raw + pBuf->nh.iph->ihl * 4; +# endif + return pTransportHdr - pBuf->data; +#endif +} + + +/** + * Initializes a SG list from an sk_buff. + * + * @param pThis The instance. + * @param pBuf The sk_buff. + * @param pSG The SG. + * @param cbExtra The number of bytes of extra space allocated immediately after the SG. + * @param cSegs The number of segments allocated for the SG. + * This should match the number in the mbuf exactly! + * @param fSrc The source of the frame. + * @param pGsoCtx Pointer to the GSO context if it's a GSO + * internal network frame. NULL if regular frame. + */ +static void vboxNetFltLinuxSkBufToSG(PVBOXNETFLTINS pThis, struct sk_buff *pBuf, PINTNETSG pSG, + unsigned cbExtra, unsigned cSegs, uint32_t fSrc, PCPDMNETWORKGSO pGsoCtx) +{ + int i; + NOREF(pThis); + +#ifndef VBOXNETFLT_SG_SUPPORT + Assert(!skb_shinfo(pBuf)->frag_list); +#else /* VBOXNETFLT_SG_SUPPORT */ + uint8_t *pExtra = (uint8_t *)&pSG->aSegs[cSegs]; + unsigned cbConsumed = 0; + unsigned cbProduced = 0; + +# if RTLNX_VER_MIN(2,6,27) + /* Restore VLAN tag stripped by host hardware */ + if (vlan_tx_tag_present(pBuf)) + { + uint8_t *pMac = pBuf->data; + struct vlan_ethhdr *pVHdr = (struct vlan_ethhdr *)pExtra; + Assert(ETH_ALEN * 2 + VLAN_HLEN <= cbExtra); + memmove(pVHdr, pMac, ETH_ALEN * 2); + /* Consume whole Ethernet header: 2 addresses + EtherType (see @bugref{8599}) */ + cbConsumed += ETH_ALEN * 2 + sizeof(uint16_t); + pVHdr->h_vlan_proto = RT_H2N_U16(ETH_P_8021Q); + pVHdr->h_vlan_TCI = RT_H2N_U16(vlan_tx_tag_get(pBuf)); + pVHdr->h_vlan_encapsulated_proto = *(uint16_t*)(pMac + ETH_ALEN * 2); + cbProduced += VLAN_ETH_HLEN; + } +# endif /* RTLNX_VER_MIN(2,6,27) */ + + if (pBuf->ip_summed == CHECKSUM_PARTIAL && pBuf->pkt_type == PACKET_OUTGOING) + { + unsigned uCsumStartOffset = vboxNetFltLinuxGetChecksumStartOffset(pBuf); + unsigned uCsumStoreOffset = uCsumStartOffset + VBOX_SKB_CSUM_OFFSET(pBuf) - cbConsumed; + Log3(("cbConsumed=%u cbProduced=%u uCsumStartOffset=%u uCsumStoreOffset=%u\n", + cbConsumed, cbProduced, uCsumStartOffset, uCsumStoreOffset)); + Assert(cbProduced + uCsumStoreOffset + sizeof(uint16_t) <= cbExtra); + /* + * We assume that the checksum is stored at the very end of the transport header + * so we will have all headers in a single fragment. If our assumption is wrong + * we may see suboptimal performance. + */ + memmove(pExtra + cbProduced, + pBuf->data + cbConsumed, + uCsumStoreOffset); + unsigned uChecksum = skb_checksum(pBuf, uCsumStartOffset, pBuf->len - uCsumStartOffset, 0); + *(uint16_t*)(pExtra + cbProduced + uCsumStoreOffset) = csum_fold(uChecksum); + cbProduced += uCsumStoreOffset + sizeof(uint16_t); + cbConsumed += uCsumStoreOffset + sizeof(uint16_t); + } +#endif /* VBOXNETFLT_SG_SUPPORT */ + + if (!pGsoCtx) + IntNetSgInitTempSegs(pSG, pBuf->len + cbProduced - cbConsumed, cSegs, 0 /*cSegsUsed*/); + else + IntNetSgInitTempSegsGso(pSG, pBuf->len + cbProduced - cbConsumed, cSegs, 0 /*cSegsUsed*/, pGsoCtx); + + int iSeg = 0; +#ifdef VBOXNETFLT_SG_SUPPORT + if (cbProduced) + { + pSG->aSegs[iSeg].cb = cbProduced; + pSG->aSegs[iSeg].pv = pExtra; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + } + pSG->aSegs[iSeg].cb = skb_headlen(pBuf) - cbConsumed; + pSG->aSegs[iSeg].pv = pBuf->data + cbConsumed; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6((" kmap_atomic:")); +# endif /* LOG_ENABLED */ + for (i = 0; i < skb_shinfo(pBuf)->nr_frags; i++) + { + skb_frag_t *pFrag = &skb_shinfo(pBuf)->frags[i]; +# if RTLNX_VER_MIN(5,4,0) || RTLNX_SUSE_MAJ_PREREQ(15, 2) + pSG->aSegs[iSeg].cb = pFrag->bv_len; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->bv_offset; +# else /* < KERNEL_VERSION(5, 4, 0) */ + pSG->aSegs[iSeg].cb = pFrag->size; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->page_offset; +# endif /* >= KERNEL_VERSION(5, 4, 0) */ + Log6((" %p", pSG->aSegs[iSeg].pv)); + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + } + struct sk_buff *pFragBuf; + for (pFragBuf = skb_shinfo(pBuf)->frag_list; pFragBuf; pFragBuf = pFragBuf->next) + { + pSG->aSegs[iSeg].cb = skb_headlen(pFragBuf); + pSG->aSegs[iSeg].pv = pFragBuf->data; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + for (i = 0; i < skb_shinfo(pFragBuf)->nr_frags; i++) + { + skb_frag_t *pFrag = &skb_shinfo(pFragBuf)->frags[i]; +# if RTLNX_VER_MIN(5,4,0) || RTLNX_SUSE_MAJ_PREREQ(15, 2) + pSG->aSegs[iSeg].cb = pFrag->bv_len; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->bv_offset; +# else /* < KERNEL_VERSION(5, 4, 0) */ + pSG->aSegs[iSeg].cb = pFrag->size; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->page_offset; +# endif /* >= KERNEL_VERSION(5, 4, 0) */ + Log6((" %p", pSG->aSegs[iSeg].pv)); + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + } + } +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6(("\n")); +# endif /* LOG_ENABLED */ +#else + pSG->aSegs[iSeg].cb = pBuf->len; + pSG->aSegs[iSeg].pv = pBuf->data; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; +#endif + + pSG->cSegsUsed = iSeg; + +#if 0 + if (cbProduced) + { + LogRel(("vboxNetFltLinuxSkBufToSG: original packet dump:\n%.*Rhxd\n", pBuf->len-pBuf->data_len, skb_mac_header(pBuf))); + LogRel(("vboxNetFltLinuxSkBufToSG: cbConsumed=%u cbProduced=%u cbExtra=%u\n", cbConsumed, cbProduced, cbExtra)); + uint32_t offset = 0; + for (i = 0; i < pSG->cSegsUsed; ++i) + { + LogRel(("vboxNetFltLinuxSkBufToSG: seg#%d (%d bytes, starting at 0x%x):\n%.*Rhxd\n", + i, pSG->aSegs[i].cb, offset, pSG->aSegs[i].cb, pSG->aSegs[i].pv)); + offset += pSG->aSegs[i].cb; + } + } +#endif + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* + * Add a trailer if the frame is too small. + * + * Since we're getting to the packet before it is framed, it has not + * yet been padded. The current solution is to add a segment pointing + * to a buffer containing all zeros and pray that works for all frames... + */ + if (pSG->cbTotal < 60 && (fSrc & INTNETTRUNKDIR_HOST)) + { + Assert(pBuf->data_len == 0); /* Packets with fragments are never small! */ + static uint8_t const s_abZero[128] = {0}; + + AssertReturnVoid(iSeg < cSegs); + + 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 <= pSG->cSegsAlloc) + } +#endif + + Log6(("vboxNetFltLinuxSkBufToSG: allocated=%d, segments=%d frags=%d next=%p frag_list=%p pkt_type=%x fSrc=%x\n", + pSG->cSegsAlloc, pSG->cSegsUsed, skb_shinfo(pBuf)->nr_frags, pBuf->next, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type, fSrc)); + for (i = 0; i < pSG->cSegsUsed; i++) + Log6(("vboxNetFltLinuxSkBufToSG: #%d: cb=%d pv=%p\n", + i, pSG->aSegs[i].cb, pSG->aSegs[i].pv)); +} + +/** + * Packet handler; not really documented - figure it out yourself. + * + * @returns 0 or EJUSTRETURN - this is probably copy & pastry and thus wrong. + */ +#if RTLNX_VER_MIN(2,6,14) +static int vboxNetFltLinuxPacketHandler(struct sk_buff *pBuf, + struct net_device *pSkbDev, + struct packet_type *pPacketType, + struct net_device *pOrigDev) +#else +static int vboxNetFltLinuxPacketHandler(struct sk_buff *pBuf, + struct net_device *pSkbDev, + struct packet_type *pPacketType) +#endif +{ + PVBOXNETFLTINS pThis; + struct net_device *pDev; + LogFlow(("vboxNetFltLinuxPacketHandler: pBuf=%p pSkbDev=%p pPacketType=%p\n", + pBuf, pSkbDev, pPacketType)); +#if RTLNX_VER_MIN(2,6,18) + Log3(("vboxNetFltLinuxPacketHandler: skb len=%u data_len=%u truesize=%u next=%p nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->gso_size, skb_shinfo(pBuf)->gso_segs, skb_shinfo(pBuf)->gso_type, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +# if RTLNX_VER_MIN(2,6,22) + Log6(("vboxNetFltLinuxPacketHandler: packet dump follows:\n%.*Rhxd\n", pBuf->len-pBuf->data_len, skb_mac_header(pBuf))); +# endif +#else + Log3(("vboxNetFltLinuxPacketHandler: skb len=%u data_len=%u truesize=%u next=%p nr_frags=%u tso_size=%u tso_seqs=%u frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->tso_size, skb_shinfo(pBuf)->tso_segs, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +#endif + /* + * Drop it immediately? + */ + if (!pBuf) + return 0; + + if (pBuf->pkt_type == PACKET_LOOPBACK) + { + /* + * We are not interested in loopbacked packets as they will always have + * another copy going to the wire. + */ + Log2(("vboxNetFltLinuxPacketHandler: dropped loopback packet (cb=%u)\n", pBuf->len)); + dev_kfree_skb(pBuf); /* We must 'consume' all packets we get (@bugref{6539})! */ + return 0; + } + + pThis = VBOX_FLT_PT_TO_INST(pPacketType); + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (pDev != pSkbDev) + { + Log(("vboxNetFltLinuxPacketHandler: Devices do not match, pThis may be wrong! pThis=%p\n", pThis)); + kfree_skb(pBuf); /* This is a failure, so we use kfree_skb instead of dev_kfree_skb. */ + return 0; + } + + Log6(("vboxNetFltLinuxPacketHandler: pBuf->cb dump:\n%.*Rhxd\n", sizeof(pBuf->cb), pBuf->cb)); + if (vboxNetFltLinuxSkBufIsOur(pBuf)) + { + Log2(("vboxNetFltLinuxPacketHandler: got our own sk_buff, drop it.\n")); + dev_kfree_skb(pBuf); + return 0; + } + +#ifndef VBOXNETFLT_SG_SUPPORT + { + /* + * Get rid of fragmented packets, they cause too much trouble. + */ + unsigned int uMacLen = pBuf->mac_len; + struct sk_buff *pCopy = skb_copy(pBuf, GFP_ATOMIC); + dev_kfree_skb(pBuf); + if (!pCopy) + { + LogRel(("VBoxNetFlt: Failed to allocate packet buffer, dropping the packet.\n")); + return 0; + } + pBuf = pCopy; + /* Somehow skb_copy ignores mac_len */ + pBuf->mac_len = uMacLen; +# if RTLNX_VER_MIN(2,6,27) + /* Restore VLAN tag stripped by host hardware */ + if (vlan_tx_tag_present(pBuf) && skb_headroom(pBuf) >= VLAN_ETH_HLEN) + { + uint8_t *pMac = (uint8_t*)skb_mac_header(pBuf); + struct vlan_ethhdr *pVHdr = (struct vlan_ethhdr *)(pMac - VLAN_HLEN); + memmove(pVHdr, pMac, ETH_ALEN * 2); + pVHdr->h_vlan_proto = RT_H2N_U16(ETH_P_8021Q); + pVHdr->h_vlan_TCI = RT_H2N_U16(vlan_tx_tag_get(pBuf)); + pBuf->mac_header -= VLAN_HLEN; + pBuf->mac_len += VLAN_HLEN; + } +# endif /* RTLNX_VER_MIN(2,6,27) */ + +# if RTLNX_VER_MIN(2,6,18) + Log3(("vboxNetFltLinuxPacketHandler: skb copy len=%u data_len=%u truesize=%u next=%p nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->gso_size, skb_shinfo(pBuf)->gso_segs, skb_shinfo(pBuf)->gso_type, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +# if RTLNX_VER_MIN(2,6,22) + Log6(("vboxNetFltLinuxPacketHandler: packet dump follows:\n%.*Rhxd\n", pBuf->len-pBuf->data_len, skb_mac_header(pBuf))); +# endif /* RTLNX_VER_MIN(2,6,22) */ +# else /* RTLNX_VER_MAX(2,6,18) */ + Log3(("vboxNetFltLinuxPacketHandler: skb copy len=%u data_len=%u truesize=%u next=%p nr_frags=%u tso_size=%u tso_seqs=%u frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->tso_size, skb_shinfo(pBuf)->tso_segs, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +# endif /* RTLNX_VER_MAX(2,6,18) */ + } +#endif /* !VBOXNETFLT_SG_SUPPORT */ + +#ifdef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + /* Forward it to the internal network. */ + vboxNetFltLinuxForwardToIntNet(pThis, pBuf); +#else /* !VBOXNETFLT_LINUX_NO_XMIT_QUEUE */ + /* Add the packet to transmit queue and schedule the bottom half. */ + skb_queue_tail(&pThis->u.s.XmitQueue, pBuf); + schedule_work(&pThis->u.s.XmitTask); + Log6(("vboxNetFltLinuxPacketHandler: scheduled work %p for sk_buff %p\n", + &pThis->u.s.XmitTask, pBuf)); +#endif /* !VBOXNETFLT_LINUX_NO_XMIT_QUEUE */ + + /* It does not really matter what we return, it is ignored by the kernel. */ + return 0; +} + +/** + * Calculate the number of INTNETSEG segments the socket buffer will need. + * + * @returns Segment count. + * @param pBuf The socket buffer. + * @param pcbTemp Where to store the number of bytes of the part + * of the socket buffer that will be copied to + * a temporary storage. + */ +DECLINLINE(unsigned) vboxNetFltLinuxCalcSGSegments(struct sk_buff *pBuf, unsigned *pcbTemp) +{ + *pcbTemp = 0; +#ifdef VBOXNETFLT_SG_SUPPORT + unsigned cSegs = 1 + skb_shinfo(pBuf)->nr_frags; + if (pBuf->ip_summed == CHECKSUM_PARTIAL && pBuf->pkt_type == PACKET_OUTGOING) + { + *pcbTemp = vboxNetFltLinuxGetChecksumStartOffset(pBuf) + VBOX_SKB_CSUM_OFFSET(pBuf) + sizeof(uint16_t); + } +# if RTLNX_VER_MIN(2,6,27) + if (vlan_tx_tag_present(pBuf)) + { + if (*pcbTemp) + *pcbTemp += VLAN_HLEN; + else + *pcbTemp = VLAN_ETH_HLEN; + } +# endif /* RTLNX_VER_MIN(2,6,27) */ + if (*pcbTemp) + ++cSegs; + struct sk_buff *pFrag; + for (pFrag = skb_shinfo(pBuf)->frag_list; pFrag; pFrag = pFrag->next) + { + Log6(("vboxNetFltLinuxCalcSGSegments: frag=%p len=%d data_len=%d frags=%d frag_list=%p next=%p\n", + pFrag, pFrag->len, pFrag->data_len, skb_shinfo(pFrag)->nr_frags, skb_shinfo(pFrag)->frag_list, pFrag->next)); + cSegs += 1 + skb_shinfo(pFrag)->nr_frags; + } +#else + unsigned cSegs = 1; +#endif +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* vboxNetFltLinuxSkBufToSG adds a padding segment if it's a runt. */ + if (pBuf->len < 60) + cSegs++; +#endif + return cSegs; +} + + +/** + * Destroy the intnet scatter / gather buffer created by + * vboxNetFltLinuxSkBufToSG. + * + * @param pSG The (scatter/)gather list. + * @param pBuf The original socket buffer that was used to create + * the scatter/gather list. + */ +static void vboxNetFltLinuxDestroySG(PINTNETSG pSG, struct sk_buff *pBuf) +{ +#ifdef VBOXNETFLT_SG_SUPPORT + int i, iSeg = 1; /* Skip non-paged part of SKB */ + /* Check if the extra buffer behind SG structure was used for modified packet header */ + if (pBuf->data != pSG->aSegs[0].pv) + ++iSeg; /* Skip it as well */ +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6(("kunmap_atomic:")); +# endif /* LOG_ENABLED */ + /* iSeg now points to the first mapped fragment if there are any */ + for (i = 0; i < skb_shinfo(pBuf)->nr_frags; i++) + { + Log6((" %p", pSG->aSegs[iSeg].pv)); + VBOX_SKB_KUNMAP_FRAG(pSG->aSegs[iSeg++].pv); + } + struct sk_buff *pFragBuf; + for (pFragBuf = skb_shinfo(pBuf)->frag_list; pFragBuf; pFragBuf = pFragBuf->next) + { + ++iSeg; /* Non-fragment (unmapped) portion of chained SKB */ + for (i = 0; i < skb_shinfo(pFragBuf)->nr_frags; i++) + { + Log6((" %p", pSG->aSegs[iSeg].pv)); + VBOX_SKB_KUNMAP_FRAG(pSG->aSegs[iSeg++].pv); + } + } +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6(("\n")); +# endif /* LOG_ENABLED */ +#endif + NOREF(pSG); +} + +#ifdef LOG_ENABLED +/** + * Logging helper. + */ +static void vboxNetFltDumpPacket(PINTNETSG pSG, bool fEgress, const char *pszWhere, int iIncrement) +{ + int i, offSeg; + uint8_t *pInt, *pExt; + static int iPacketNo = 1; + iPacketNo += iIncrement; + if (fEgress) + { + pExt = pSG->aSegs[0].pv; + pInt = pExt + 6; + } + else + { + pInt = pSG->aSegs[0].pv; + pExt = pInt + 6; + } + Log(("VBoxNetFlt: (int)%02x:%02x:%02x:%02x:%02x:%02x" + " %s (%s)%02x:%02x:%02x:%02x:%02x:%02x (%u bytes) packet #%u\n", + pInt[0], pInt[1], pInt[2], pInt[3], pInt[4], pInt[5], + fEgress ? "-->" : "<--", pszWhere, + pExt[0], pExt[1], pExt[2], pExt[3], pExt[4], pExt[5], + pSG->cbTotal, iPacketNo)); + if (pSG->cSegsUsed == 1) + { + Log4(("%.*Rhxd\n", pSG->aSegs[0].cb, pSG->aSegs[0].pv)); + } + else + { + for (i = 0, offSeg = 0; i < pSG->cSegsUsed; i++) + { + Log4(("-- segment %d at 0x%x (%d bytes)\n --\n%.*Rhxd\n", + i, offSeg, pSG->aSegs[i].cb, pSG->aSegs[i].cb, pSG->aSegs[i].pv)); + offSeg += pSG->aSegs[i].cb; + } + } +} +#else +# define vboxNetFltDumpPacket(a, b, c, d) do {} while (0) +#endif + +#ifdef VBOXNETFLT_WITH_GSO_RECV + +/** + * Worker for vboxNetFltLinuxForwardToIntNet that checks if we can forwards a + * GSO socket buffer without having to segment it. + * + * @returns true on success, false if needs segmenting. + * @param pThis The net filter instance. + * @param pSkb The GSO socket buffer. + * @param fSrc The source. + * @param pGsoCtx Where to return the GSO context on success. + */ +static bool vboxNetFltLinuxCanForwardAsGso(PVBOXNETFLTINS pThis, struct sk_buff *pSkb, uint32_t fSrc, + PPDMNETWORKGSO pGsoCtx) +{ + PDMNETWORKGSOTYPE enmGsoType; + uint16_t uEtherType; + unsigned int cbTransport; + unsigned int offTransport; + unsigned int cbTransportHdr; + unsigned uProtocol; + union + { + RTNETIPV4 IPv4; + RTNETIPV6 IPv6; + RTNETTCP Tcp; + uint8_t ab[40]; + uint16_t au16[40/2]; + uint32_t au32[40/4]; + } Buf; + + /* + * Check the GSO properties of the socket buffer and make sure it fits. + */ + /** @todo Figure out how to handle SKB_GSO_TCP_ECN! */ + if (RT_UNLIKELY( skb_shinfo(pSkb)->gso_type & ~(SKB_GSO_DODGY | SKB_GSO_TCPV6 | SKB_GSO_TCPV4) )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: gso_type=%#x\n", skb_shinfo(pSkb)->gso_type)); + return false; + } + if (RT_UNLIKELY( skb_shinfo(pSkb)->gso_size < 1 + || pSkb->len > VBOX_MAX_GSO_SIZE )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: gso_size=%#x skb_len=%#x (max=%#x)\n", skb_shinfo(pSkb)->gso_size, pSkb->len, VBOX_MAX_GSO_SIZE)); + return false; + } + + /* + * Switch on the ethertype. + */ + uEtherType = pSkb->protocol; + if ( uEtherType == RT_H2N_U16_C(RTNET_ETHERTYPE_VLAN) + && pSkb->mac_len == sizeof(RTNETETHERHDR) + sizeof(uint32_t)) + { + uint16_t const *puEtherType = skb_header_pointer(pSkb, sizeof(RTNETETHERHDR) + sizeof(uint16_t), sizeof(uint16_t), &Buf); + if (puEtherType) + uEtherType = *puEtherType; + } + switch (uEtherType) + { + case RT_H2N_U16_C(RTNET_ETHERTYPE_IPV4): + { + unsigned int cbHdr; + PCRTNETIPV4 pIPv4 = (PCRTNETIPV4)skb_header_pointer(pSkb, pSkb->mac_len, sizeof(Buf.IPv4), &Buf); + if (RT_UNLIKELY(!pIPv4)) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: failed to access IPv4 hdr\n")); + return false; + } + + cbHdr = pIPv4->ip_hl * 4; + cbTransport = RT_N2H_U16(pIPv4->ip_len); + if (RT_UNLIKELY( cbHdr < RTNETIPV4_MIN_LEN + || cbHdr > cbTransport )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: invalid IPv4 lengths: ip_hl=%u ip_len=%u\n", pIPv4->ip_hl, RT_N2H_U16(pIPv4->ip_len))); + return false; + } + cbTransport -= cbHdr; + offTransport = pSkb->mac_len + cbHdr; + uProtocol = pIPv4->ip_p; + if (uProtocol == RTNETIPV4_PROT_TCP) + enmGsoType = PDMNETWORKGSOTYPE_IPV4_TCP; + else if (uProtocol == RTNETIPV4_PROT_UDP) + enmGsoType = PDMNETWORKGSOTYPE_IPV4_UDP; + else /** @todo IPv6: 4to6 tunneling */ + enmGsoType = PDMNETWORKGSOTYPE_INVALID; + break; + } + + case RT_H2N_U16_C(RTNET_ETHERTYPE_IPV6): + { + PCRTNETIPV6 pIPv6 = (PCRTNETIPV6)skb_header_pointer(pSkb, pSkb->mac_len, sizeof(Buf.IPv6), &Buf); + if (RT_UNLIKELY(!pIPv6)) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: failed to access IPv6 hdr\n")); + return false; + } + + cbTransport = RT_N2H_U16(pIPv6->ip6_plen); + offTransport = pSkb->mac_len + sizeof(RTNETIPV6); + uProtocol = pIPv6->ip6_nxt; + /** @todo IPv6: Dig our way out of the other headers. */ + if (uProtocol == RTNETIPV4_PROT_TCP) + enmGsoType = PDMNETWORKGSOTYPE_IPV6_TCP; + else if (uProtocol == RTNETIPV4_PROT_UDP) + enmGsoType = PDMNETWORKGSOTYPE_IPV6_UDP; + else + enmGsoType = PDMNETWORKGSOTYPE_INVALID; + break; + } + + default: + Log5(("vboxNetFltLinuxCanForwardAsGso: uEtherType=%#x\n", RT_H2N_U16(uEtherType))); + return false; + } + + if (enmGsoType == PDMNETWORKGSOTYPE_INVALID) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: Unsupported protocol %d\n", uProtocol)); + return false; + } + + if (RT_UNLIKELY( offTransport + cbTransport <= offTransport + || offTransport + cbTransport > pSkb->len + || cbTransport < (uProtocol == RTNETIPV4_PROT_TCP ? RTNETTCP_MIN_LEN : RTNETUDP_MIN_LEN)) ) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: Bad transport length; off=%#x + cb=%#x => %#x; skb_len=%#x (%s)\n", + offTransport, cbTransport, offTransport + cbTransport, pSkb->len, PDMNetGsoTypeName(enmGsoType) )); + return false; + } + + /* + * Check the TCP/UDP bits. + */ + if (uProtocol == RTNETIPV4_PROT_TCP) + { + PCRTNETTCP pTcp = (PCRTNETTCP)skb_header_pointer(pSkb, offTransport, sizeof(Buf.Tcp), &Buf); + if (RT_UNLIKELY(!pTcp)) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: failed to access TCP hdr\n")); + return false; + } + + cbTransportHdr = pTcp->th_off * 4; + pGsoCtx->cbHdrsSeg = offTransport + cbTransportHdr; + if (RT_UNLIKELY( cbTransportHdr < RTNETTCP_MIN_LEN + || cbTransportHdr > cbTransport + || offTransport + cbTransportHdr >= UINT8_MAX + || offTransport + cbTransportHdr >= pSkb->len )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: No space for TCP header; off=%#x cb=%#x skb_len=%#x\n", offTransport, cbTransportHdr, pSkb->len)); + return false; + } + + } + else + { + Assert(uProtocol == RTNETIPV4_PROT_UDP); + cbTransportHdr = sizeof(RTNETUDP); + pGsoCtx->cbHdrsSeg = offTransport; /* Exclude UDP header */ + if (RT_UNLIKELY( offTransport + cbTransportHdr >= UINT8_MAX + || offTransport + cbTransportHdr >= pSkb->len )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: No space for UDP header; off=%#x skb_len=%#x\n", offTransport, pSkb->len)); + return false; + } + } + + /* + * We're good, init the GSO context. + */ + pGsoCtx->u8Type = enmGsoType; + pGsoCtx->cbHdrsTotal = offTransport + cbTransportHdr; + pGsoCtx->cbMaxSeg = skb_shinfo(pSkb)->gso_size; + pGsoCtx->offHdr1 = pSkb->mac_len; + pGsoCtx->offHdr2 = offTransport; + pGsoCtx->u8Unused = 0; + + return true; +} + +/** + * Forward the socket buffer as a GSO internal network frame. + * + * @returns IPRT status code. + * @param pThis The net filter instance. + * @param pSkb The GSO socket buffer. + * @param fSrc The source. + * @param pGsoCtx Where to return the GSO context on success. + */ +static int vboxNetFltLinuxForwardAsGso(PVBOXNETFLTINS pThis, struct sk_buff *pSkb, uint32_t fSrc, PCPDMNETWORKGSO pGsoCtx) +{ + int rc; + unsigned cbExtra; + unsigned cSegs = vboxNetFltLinuxCalcSGSegments(pSkb, &cbExtra); + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]) + cbExtra); + if (RT_LIKELY(pSG)) + { + vboxNetFltLinuxSkBufToSG(pThis, pSkb, pSG, cbExtra, cSegs, fSrc, pGsoCtx); + + vboxNetFltDumpPacket(pSG, false, (fSrc & INTNETTRUNKDIR_HOST) ? "host" : "wire", 1); + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + + vboxNetFltLinuxDestroySG(pSG, pSkb); + rc = VINF_SUCCESS; + } + else + { + Log(("VBoxNetFlt: Dropping the sk_buff (failure case).\n")); + rc = VERR_NO_MEMORY; + } + return rc; +} + +#endif /* VBOXNETFLT_WITH_GSO_RECV */ + +/** + * Worker for vboxNetFltLinuxForwardToIntNet. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY. + * @param pThis The net filter instance. + * @param pBuf The socket buffer. + * @param fSrc The source. + */ +static int vboxNetFltLinuxForwardSegment(PVBOXNETFLTINS pThis, struct sk_buff *pBuf, uint32_t fSrc) +{ + int rc; + unsigned cbExtra; + unsigned cSegs = vboxNetFltLinuxCalcSGSegments(pBuf, &cbExtra); + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]) + cbExtra); + if (RT_LIKELY(pSG)) + { + vboxNetFltLinuxSkBufToSG(pThis, pBuf, pSG, cbExtra, cSegs, fSrc, NULL /*pGsoCtx*/); + + vboxNetFltDumpPacket(pSG, false, (fSrc & INTNETTRUNKDIR_HOST) ? "host" : "wire", 1); + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + + vboxNetFltLinuxDestroySG(pSG, pBuf); + rc = VINF_SUCCESS; + } + else + { + Log(("VBoxNetFlt: Failed to allocate SG buffer.\n")); + rc = VERR_NO_MEMORY; + } + return rc; +} + + +/** + * I won't disclose what I do, figure it out yourself, including pThis referencing. + * + * @param pThis The net filter instance. + * @param pBuf The socket buffer. + * @param fSrc Where the packet comes from. + */ +static void vboxNetFltLinuxForwardToIntNetInner(PVBOXNETFLTINS pThis, struct sk_buff *pBuf, uint32_t fSrc) +{ +#ifdef VBOXNETFLT_WITH_GSO + if (skb_is_gso(pBuf)) + { + PDMNETWORKGSO GsoCtx; + Log6(("vboxNetFltLinuxForwardToIntNetInner: skb len=%u data_len=%u truesize=%u next=%p" + " nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x ip_summed=%d\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, + skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->gso_size, + skb_shinfo(pBuf)->gso_segs, skb_shinfo(pBuf)->gso_type, + skb_shinfo(pBuf)->frag_list, pBuf->pkt_type, pBuf->ip_summed)); + + if (RT_LIKELY(fSrc & INTNETTRUNKDIR_HOST)) + { + /* + * skb_gso_segment does the following. Do we need to do it as well? + */ +# if RTLNX_VER_MIN(2,6,22) + skb_reset_mac_header(pBuf); + pBuf->mac_len = pBuf->network_header - pBuf->mac_header; +# else + pBuf->mac.raw = pBuf->data; + pBuf->mac_len = pBuf->nh.raw - pBuf->data; +# endif + } + +# ifdef VBOXNETFLT_WITH_GSO_RECV + if ( (skb_shinfo(pBuf)->gso_type & (SKB_GSO_TCPV6 | SKB_GSO_TCPV4)) + && vboxNetFltLinuxCanForwardAsGso(pThis, pBuf, fSrc, &GsoCtx) ) + vboxNetFltLinuxForwardAsGso(pThis, pBuf, fSrc, &GsoCtx); + else +# endif /* VBOXNETFLT_WITH_GSO_RECV */ + { + /* Need to segment the packet */ + struct sk_buff *pNext; + struct sk_buff *pSegment = skb_gso_segment(pBuf, 0 /*supported features*/); + if (IS_ERR(pSegment)) + { + LogRel(("VBoxNetFlt: Failed to segment a packet (%d).\n", PTR_ERR(pSegment))); + return; + } + + for (; pSegment; pSegment = pNext) + { + Log6(("vboxNetFltLinuxForwardToIntNetInner: segment len=%u data_len=%u truesize=%u next=%p" + " nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x\n", + pSegment->len, pSegment->data_len, pSegment->truesize, pSegment->next, + skb_shinfo(pSegment)->nr_frags, skb_shinfo(pSegment)->gso_size, + skb_shinfo(pSegment)->gso_segs, skb_shinfo(pSegment)->gso_type, + skb_shinfo(pSegment)->frag_list, pSegment->pkt_type)); + pNext = pSegment->next; + pSegment->next = 0; + vboxNetFltLinuxForwardSegment(pThis, pSegment, fSrc); + dev_kfree_skb(pSegment); + } + } + } + else +#endif /* VBOXNETFLT_WITH_GSO */ + { + Log6(("vboxNetFltLinuxForwardToIntNetInner: ptk_type=%d ip_summed=%d len=%d" + " data_len=%d headroom=%d hdr_len=%d csum_offset=%d\n", + pBuf->pkt_type, pBuf->ip_summed, pBuf->len, pBuf->data_len, skb_headroom(pBuf), + skb_headlen(pBuf), vboxNetFltLinuxGetChecksumStartOffset(pBuf))); +#ifndef VBOXNETFLT_SG_SUPPORT + if (pBuf->ip_summed == CHECKSUM_PARTIAL && pBuf->pkt_type == PACKET_OUTGOING) + { +# if RTLNX_VER_MIN(2,6,19) + int rc = VBOX_SKB_CHECKSUM_HELP(pBuf); +# else + /* + * Try to work around the problem with CentOS 4.7 and 5.2 (2.6.9 + * and 2.6.18 kernels), they pass wrong 'h' pointer down. We take IP + * header length from the header itself and reconstruct 'h' pointer + * to TCP (or whatever) header. + */ + unsigned char *tmp = pBuf->h.raw; + if (pBuf->h.raw == pBuf->nh.raw && pBuf->protocol == htons(ETH_P_IP)) + pBuf->h.raw = pBuf->nh.raw + pBuf->nh.iph->ihl * 4; + int rc = VBOX_SKB_CHECKSUM_HELP(pBuf); + /* Restore the original (wrong) pointer. */ + pBuf->h.raw = tmp; +# endif + if (rc) + { + LogRel(("VBoxNetFlt: Failed to compute checksum, dropping the packet.\n")); + return; + } + } +#endif /* !VBOXNETFLT_SG_SUPPORT */ + vboxNetFltLinuxForwardSegment(pThis, pBuf, fSrc); + } +} + + +/** + * Temporarily adjust pBuf->data so it always points to the Ethernet header, + * then forward it to the internal network. + * + * @param pThis The net filter instance. + * @param pBuf The socket buffer. This is consumed by this function. + */ +static void vboxNetFltLinuxForwardToIntNet(PVBOXNETFLTINS pThis, struct sk_buff *pBuf) +{ + uint32_t fSrc = pBuf->pkt_type == PACKET_OUTGOING ? INTNETTRUNKDIR_HOST : INTNETTRUNKDIR_WIRE; + + if (RT_UNLIKELY(fSrc & INTNETTRUNKDIR_WIRE)) + { + /* + * The packet came from the wire and the driver has already consumed + * mac header. We need to restore it back. Moreover, after we are + * through with this skb we need to restore its original state! + */ + skb_push(pBuf, pBuf->mac_len); + Log5(("vboxNetFltLinuxForwardToIntNet: mac_len=%d data=%p mac_header=%p network_header=%p\n", + pBuf->mac_len, pBuf->data, skb_mac_header(pBuf), skb_network_header(pBuf))); + } + + vboxNetFltLinuxForwardToIntNetInner(pThis, pBuf, fSrc); + + /* + * Restore the original state of skb as there are other handlers this skb + * will be provided to. + */ + if (RT_UNLIKELY(fSrc & INTNETTRUNKDIR_WIRE)) + skb_pull(pBuf, pBuf->mac_len); + + dev_kfree_skb(pBuf); +} + + +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE +/** + * Work queue handler that forwards the socket buffers queued by + * vboxNetFltLinuxPacketHandler to the internal network. + * + * @param pWork The work queue. + */ +# if RTLNX_VER_MIN(2,6,20) +static void vboxNetFltLinuxXmitTask(struct work_struct *pWork) +# else +static void vboxNetFltLinuxXmitTask(void *pWork) +# endif +{ + PVBOXNETFLTINS pThis = VBOX_FLT_XT_TO_INST(pWork); + struct sk_buff *pBuf; + + Log6(("vboxNetFltLinuxXmitTask: Got work %p.\n", pWork)); + + /* + * Active? Retain the instance and increment the busy counter. + */ + if (vboxNetFltTryRetainBusyActive(pThis)) + { + while ((pBuf = skb_dequeue(&pThis->u.s.XmitQueue)) != NULL) + vboxNetFltLinuxForwardToIntNet(pThis, pBuf); + + vboxNetFltRelease(pThis, true /* fBusy */); + } + else + { + /** @todo Shouldn't we just drop the packets here? There is little point in + * making them accumulate when the VM is paused and it'll only waste + * kernel memory anyway... Hmm. maybe wait a short while (2-5 secs) + * before start draining the packets (goes for the intnet ring buf + * too)? */ + } +} +#endif /* !VBOXNETFLT_LINUX_NO_XMIT_QUEUE */ + +/** + * Reports the GSO capabilities of the hardware NIC. + * + * @param pThis The net filter instance. The caller hold a + * reference to this. + */ +static void vboxNetFltLinuxReportNicGsoCapabilities(PVBOXNETFLTINS pThis) +{ +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + struct net_device *pDev; + unsigned int fFeatures; + + RTSpinlockAcquire(pThis->hSpinlock); + + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (pDev) + fFeatures = pDev->features; + else + fFeatures = 0; + + RTSpinlockRelease(pThis->hSpinlock); + + if (pThis->pSwitchPort) + { + /* Set/update the GSO capabilities of the NIC. */ + uint32_t fGsoCapabilites = 0; + if (fFeatures & NETIF_F_TSO) + fGsoCapabilites |= RT_BIT_32(PDMNETWORKGSOTYPE_IPV4_TCP); + if (fFeatures & NETIF_F_TSO6) + fGsoCapabilites |= RT_BIT_32(PDMNETWORKGSOTYPE_IPV6_TCP); + Log3(("vboxNetFltLinuxReportNicGsoCapabilities: reporting wire %s%s\n", + (fGsoCapabilites & RT_BIT_32(PDMNETWORKGSOTYPE_IPV4_TCP)) ? "tso " : "", + (fGsoCapabilites & RT_BIT_32(PDMNETWORKGSOTYPE_IPV6_TCP)) ? "tso6 " : "")); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, fGsoCapabilites, INTNETTRUNKDIR_WIRE); + } + + vboxNetFltRelease(pThis, true /*fBusy*/); + } +#endif /* VBOXNETFLT_WITH_GSO_XMIT_WIRE || VBOXNETFLT_WITH_GSO_XMIT_HOST */ +} + +/** + * Helper that determines whether the host (ignoreing us) is operating the + * interface in promiscuous mode or not. + */ +static bool vboxNetFltLinuxPromiscuous(PVBOXNETFLTINS pThis) +{ + bool fRc = false; + struct net_device * pDev = vboxNetFltLinuxRetainNetDev(pThis); + if (pDev) + { + fRc = !!(pDev->promiscuity - (ASMAtomicUoReadBool(&pThis->u.s.fPromiscuousSet) & 1)); + LogFlow(("vboxNetFltPortOsIsPromiscuous: returns %d, pDev->promiscuity=%d, fPromiscuousSet=%d\n", + fRc, pDev->promiscuity, pThis->u.s.fPromiscuousSet)); + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + } + return fRc; +} + +/** + * Does this device needs link state change signaled? + * Currently we need it for our own VBoxNetAdp and TAP. + */ +static bool vboxNetFltNeedsLinkState(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + if (pDev->ethtool_ops && pDev->ethtool_ops->get_drvinfo) + { + struct ethtool_drvinfo Info; + + memset(&Info, 0, sizeof(Info)); + Info.cmd = ETHTOOL_GDRVINFO; + pDev->ethtool_ops->get_drvinfo(pDev, &Info); + Log3(("%s: driver=%.*s version=%.*s bus_info=%.*s\n", + __FUNCTION__, + sizeof(Info.driver), Info.driver, + sizeof(Info.version), Info.version, + sizeof(Info.bus_info), Info.bus_info)); + + if (!strncmp(Info.driver, "vboxnet", sizeof(Info.driver))) + return true; + +#if RTLNX_VER_MIN(2,6,36) /* TAP started doing carrier */ + return !strncmp(Info.driver, "tun", 4) + && !strncmp(Info.bus_info, "tap", 4); +#endif + } + + return false; +} + +#if RTLNX_VER_MAX(2,6,18) +DECLINLINE(void) netif_tx_lock_bh(struct net_device *pDev) +{ + spin_lock_bh(&pDev->xmit_lock); +} + +DECLINLINE(void) netif_tx_unlock_bh(struct net_device *pDev) +{ + spin_unlock_bh(&pDev->xmit_lock); +} +#endif + +/** + * Some devices need link state change when filter attaches/detaches + * since the filter is their link in a sense. + */ +static void vboxNetFltSetLinkState(PVBOXNETFLTINS pThis, struct net_device *pDev, bool fLinkUp) +{ + if (vboxNetFltNeedsLinkState(pThis, pDev)) + { + Log3(("%s: bringing device link %s\n", + __FUNCTION__, fLinkUp ? "up" : "down")); + netif_tx_lock_bh(pDev); + if (fLinkUp) + netif_carrier_on(pDev); + else + netif_carrier_off(pDev); + netif_tx_unlock_bh(pDev); + } +} + +/** + * Internal worker for vboxNetFltLinuxNotifierCallback. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pDev The device to attach to. + */ +static int vboxNetFltLinuxAttachToInterface(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + LogFlow(("vboxNetFltLinuxAttachToInterface: pThis=%p (%s)\n", pThis, pThis->szName)); + + /* + * Retain and store the device. + */ + dev_hold(pDev); + + RTSpinlockAcquire(pThis->hSpinlock); + ASMAtomicUoWritePtr(&pThis->u.s.pDev, pDev); + RTSpinlockRelease(pThis->hSpinlock); + + Log(("vboxNetFltLinuxAttachToInterface: Device %p(%s) retained. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + Log(("vboxNetFltLinuxAttachToInterface: Got pDev=%p pThis=%p pThis->u.s.pDev=%p\n", + pDev, pThis, ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *))); + + /* Get the mac address while we still have a valid net_device reference. */ + memcpy(&pThis->u.s.MacAddr, pDev->dev_addr, sizeof(pThis->u.s.MacAddr)); + /* Initialize MTU */ + pThis->u.s.cbMtu = pDev->mtu; + + /* + * Install a packet filter for this device with a protocol wildcard (ETH_P_ALL). + */ + pThis->u.s.PacketType.type = __constant_htons(ETH_P_ALL); + pThis->u.s.PacketType.dev = pDev; + pThis->u.s.PacketType.func = vboxNetFltLinuxPacketHandler; + dev_add_pack(&pThis->u.s.PacketType); + ASMAtomicUoWriteBool(&pThis->u.s.fPacketHandler, true); + Log(("vboxNetFltLinuxAttachToInterface: this=%p: Packet handler installed.\n", pThis)); + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + vboxNetFltLinuxHookDev(pThis, pDev); +#endif + + /* + * Are we the "carrier" for this device (e.g. vboxnet or tap)? + */ + vboxNetFltSetLinkState(pThis, pDev, true); + + /* + * Set indicators that require the spinlock. Be abit paranoid about racing + * the device notification handle. + */ + RTSpinlockAcquire(pThis->hSpinlock); + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (pDev) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + ASMAtomicUoWriteBool(&pThis->u.s.fRegistered, true); + pDev = NULL; /* don't dereference it */ + } + RTSpinlockRelease(pThis->hSpinlock); + + /* + * Report GSO capabilities + */ + Assert(pThis->pSwitchPort); + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + vboxNetFltLinuxReportNicGsoCapabilities(pThis); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, vboxNetFltLinuxPromiscuous(pThis)); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + + LogRel(("VBoxNetFlt: attached to '%s' / %RTmac\n", pThis->szName, &pThis->u.s.MacAddr)); + return VINF_SUCCESS; +} + + +static int vboxNetFltLinuxUnregisterDevice(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + bool fRegistered; + Assert(!pThis->fDisconnectedFromHost); + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + vboxNetFltLinuxUnhookDev(pThis, pDev); +#endif + + if (ASMAtomicCmpXchgBool(&pThis->u.s.fPacketHandler, false, true)) + { + dev_remove_pack(&pThis->u.s.PacketType); + Log(("vboxNetFltLinuxUnregisterDevice: this=%p: packet handler removed.\n", pThis)); + } + + RTSpinlockAcquire(pThis->hSpinlock); + fRegistered = ASMAtomicXchgBool(&pThis->u.s.fRegistered, false); + if (fRegistered) + { + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); + ASMAtomicUoWriteNullPtr(&pThis->u.s.pDev); + } + RTSpinlockRelease(pThis->hSpinlock); + + if (fRegistered) + { +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + skb_queue_purge(&pThis->u.s.XmitQueue); +#endif + Log(("vboxNetFltLinuxUnregisterDevice: this=%p: xmit queue purged.\n", pThis)); + Log(("vboxNetFltLinuxUnregisterDevice: Device %p(%s) released. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + dev_put(pDev); + } + + return NOTIFY_OK; +} + +static int vboxNetFltLinuxDeviceIsUp(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + /* Check if we are not suspended and promiscuous mode has not been set. */ + if ( pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE + && !ASMAtomicUoReadBool(&pThis->u.s.fPromiscuousSet)) + { + /* Note that there is no need for locking as the kernel got hold of the lock already. */ + dev_set_promiscuity(pDev, 1); + ASMAtomicWriteBool(&pThis->u.s.fPromiscuousSet, true); + Log(("vboxNetFltLinuxDeviceIsUp: enabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + else + Log(("vboxNetFltLinuxDeviceIsUp: no need to enable promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + return NOTIFY_OK; +} + +static int vboxNetFltLinuxDeviceGoingDown(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + /* Undo promiscuous mode if we has set it. */ + if (ASMAtomicUoReadBool(&pThis->u.s.fPromiscuousSet)) + { + /* Note that there is no need for locking as the kernel got hold of the lock already. */ + dev_set_promiscuity(pDev, -1); + ASMAtomicWriteBool(&pThis->u.s.fPromiscuousSet, false); + Log(("vboxNetFltLinuxDeviceGoingDown: disabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + else + Log(("vboxNetFltLinuxDeviceGoingDown: no need to disable promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + return NOTIFY_OK; +} + +/** + * Callback for listening to MTU change event. + * + * We need to track changes of host's inteface MTU to discard over-sized frames + * coming from the internal network as they may hang the TX queue of host's + * adapter. + * + * @returns NOTIFY_OK + * @param pThis The netfilter instance. + * @param pDev Pointer to device structure of host's interface. + */ +static int vboxNetFltLinuxDeviceMtuChange(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + ASMAtomicWriteU32(&pThis->u.s.cbMtu, pDev->mtu); + Log(("vboxNetFltLinuxDeviceMtuChange: set MTU for %s to %d\n", pThis->szName, pDev->mtu)); + return NOTIFY_OK; +} + +#ifdef LOG_ENABLED +/** Stringify the NETDEV_XXX constants. */ +static const char *vboxNetFltLinuxGetNetDevEventName(unsigned long ulEventType) +{ + const char *pszEvent = "NETDEV_<unknown>"; + switch (ulEventType) + { + case NETDEV_REGISTER: pszEvent = "NETDEV_REGISTER"; break; + case NETDEV_UNREGISTER: pszEvent = "NETDEV_UNREGISTER"; break; + case NETDEV_UP: pszEvent = "NETDEV_UP"; break; + case NETDEV_DOWN: pszEvent = "NETDEV_DOWN"; break; + case NETDEV_REBOOT: pszEvent = "NETDEV_REBOOT"; break; + case NETDEV_CHANGENAME: pszEvent = "NETDEV_CHANGENAME"; break; + case NETDEV_CHANGE: pszEvent = "NETDEV_CHANGE"; break; + case NETDEV_CHANGEMTU: pszEvent = "NETDEV_CHANGEMTU"; break; + case NETDEV_CHANGEADDR: pszEvent = "NETDEV_CHANGEADDR"; break; + case NETDEV_GOING_DOWN: pszEvent = "NETDEV_GOING_DOWN"; break; +# ifdef NETDEV_FEAT_CHANGE + case NETDEV_FEAT_CHANGE: pszEvent = "NETDEV_FEAT_CHANGE"; break; +# endif + } + return pszEvent; +} +#endif /* LOG_ENABLED */ + +/** + * Callback for listening to netdevice events. + * + * This works the rediscovery, clean up on unregistration, promiscuity on + * up/down, and GSO feature changes from ethtool. + * + * @returns NOTIFY_OK + * @param self Pointer to our notifier registration block. + * @param ulEventType The event. + * @param ptr Event specific, but it is usually the device it + * relates to. + */ +static int vboxNetFltLinuxNotifierCallback(struct notifier_block *self, unsigned long ulEventType, void *ptr) + +{ + PVBOXNETFLTINS pThis = VBOX_FLT_NB_TO_INST(self); + struct net_device *pMyDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + struct net_device *pDev = VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr); + int rc = NOTIFY_OK; + + Log(("VBoxNetFlt: got event %s(0x%lx) on %s, pDev=%p pThis=%p pThis->u.s.pDev=%p\n", + vboxNetFltLinuxGetNetDevEventName(ulEventType), ulEventType, pDev->name, pDev, pThis, pMyDev)); + + if (ulEventType == NETDEV_REGISTER) + { +#if RTLNX_VER_MIN(2,6,24) /* cgroups/namespaces introduced */ +# if RTLNX_VER_MIN(2,6,26) +# define VBOX_DEV_NET(dev) dev_net(dev) +# define VBOX_NET_EQ(n1, n2) net_eq((n1), (n2)) +# else +# define VBOX_DEV_NET(dev) ((dev)->nd_net) +# define VBOX_NET_EQ(n1, n2) ((n1) == (n2)) +# endif + struct net *pMyNet = current->nsproxy->net_ns; + struct net *pDevNet = VBOX_DEV_NET(pDev); + + if (VBOX_NET_EQ(pDevNet, pMyNet)) +#endif /* namespaces */ + { + if (strcmp(pDev->name, pThis->szName) == 0) + { + vboxNetFltLinuxAttachToInterface(pThis, pDev); + } + } + } + else + { + if (pDev == pMyDev) + { + switch (ulEventType) + { + case NETDEV_UNREGISTER: + rc = vboxNetFltLinuxUnregisterDevice(pThis, pDev); + break; + case NETDEV_UP: + rc = vboxNetFltLinuxDeviceIsUp(pThis, pDev); + break; + case NETDEV_GOING_DOWN: + rc = vboxNetFltLinuxDeviceGoingDown(pThis, pDev); + break; + case NETDEV_CHANGEMTU: + rc = vboxNetFltLinuxDeviceMtuChange(pThis, pDev); + break; + case NETDEV_CHANGENAME: + break; +#ifdef NETDEV_FEAT_CHANGE + case NETDEV_FEAT_CHANGE: + vboxNetFltLinuxReportNicGsoCapabilities(pThis); + break; +#endif + } + } + } + + return rc; +} + +/* + * Initial enumeration of netdevs. Called with NETDEV_REGISTER by + * register_netdevice_notifier() under rtnl lock. + */ +static int vboxNetFltLinuxEnumeratorCallback(struct notifier_block *self, unsigned long ulEventType, void *ptr) +{ + PVBOXNETFLTINS pThis = ((PVBOXNETFLTNOTIFIER)self)->pThis; + struct net_device *dev = VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr); + struct in_device *in_dev; + struct inet6_dev *in6_dev; + + if (ulEventType != NETDEV_REGISTER) + return NOTIFY_OK; + + if (RT_UNLIKELY(pThis->pSwitchPort->pfnNotifyHostAddress == NULL)) + return NOTIFY_OK; + + /* + * IPv4 + */ +#if RTLNX_VER_MIN(2,6,14) + in_dev = __in_dev_get_rtnl(dev); +#else + in_dev = __in_dev_get(dev); +#endif + if (in_dev != NULL) + { + struct in_ifaddr *ifa; + + for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { + if (VBOX_IPV4_IS_LOOPBACK(ifa->ifa_address)) + return NOTIFY_OK; + + if ( dev != pThis->u.s.pDev + && VBOX_IPV4_IS_LINKLOCAL_169(ifa->ifa_address)) + continue; + + Log(("%s: %s: IPv4 addr %RTnaipv4 mask %RTnaipv4\n", + __FUNCTION__, VBOX_NETDEV_NAME(dev), + ifa->ifa_address, ifa->ifa_mask)); + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv4, &ifa->ifa_address); + } + } + + /* + * IPv6 + */ + in6_dev = __in6_dev_get(dev); + if (in6_dev != NULL) + { + struct inet6_ifaddr *ifa; + + read_lock_bh(&in6_dev->lock); +#if RTLNX_VER_MIN(2,6,35) + list_for_each_entry(ifa, &in6_dev->addr_list, if_list) +#else + for (ifa = in6_dev->addr_list; ifa != NULL; ifa = ifa->if_next) +#endif + { + if ( dev != pThis->u.s.pDev + && ipv6_addr_type(&ifa->addr) & (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK)) + continue; + + Log(("%s: %s: IPv6 addr %RTnaipv6/%u\n", + __FUNCTION__, VBOX_NETDEV_NAME(dev), + &ifa->addr, (unsigned)ifa->prefix_len)); + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv6, &ifa->addr); + } + read_unlock_bh(&in6_dev->lock); + } + + return NOTIFY_OK; +} + + +static int vboxNetFltLinuxNotifierIPv4Callback(struct notifier_block *self, unsigned long ulEventType, void *ptr) +{ + PVBOXNETFLTINS pThis = RT_FROM_MEMBER(self, VBOXNETFLTINS, u.s.NotifierIPv4); + struct net_device *pDev, *pEventDev; + struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; + bool fMyDev; + int rc = NOTIFY_OK; + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + pEventDev = ifa->ifa_dev->dev; + fMyDev = (pDev == pEventDev); + Log(("VBoxNetFlt: %s: IPv4 event %s(0x%lx) %s: addr %RTnaipv4 mask %RTnaipv4\n", + pDev ? VBOX_NETDEV_NAME(pDev) : "<unknown>", + vboxNetFltLinuxGetNetDevEventName(ulEventType), ulEventType, + pEventDev ? VBOX_NETDEV_NAME(pEventDev) : "<unknown>", + ifa->ifa_address, ifa->ifa_mask)); + + if (pDev != NULL) + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + + if (VBOX_IPV4_IS_LOOPBACK(ifa->ifa_address)) + return NOTIFY_OK; + + if ( !fMyDev + && VBOX_IPV4_IS_LINKLOCAL_169(ifa->ifa_address)) + return NOTIFY_OK; + + if (pThis->pSwitchPort->pfnNotifyHostAddress) + { + bool fAdded; + if (ulEventType == NETDEV_UP) + fAdded = true; + else if (ulEventType == NETDEV_DOWN) + fAdded = false; + else + return NOTIFY_OK; + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, + kIntNetAddrType_IPv4, &ifa->ifa_local); + } + + return rc; +} + + +static int vboxNetFltLinuxNotifierIPv6Callback(struct notifier_block *self, unsigned long ulEventType, void *ptr) +{ + PVBOXNETFLTINS pThis = RT_FROM_MEMBER(self, VBOXNETFLTINS, u.s.NotifierIPv6); + struct net_device *pDev, *pEventDev; + struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr; + bool fMyDev; + int rc = NOTIFY_OK; + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + pEventDev = ifa->idev->dev; + fMyDev = (pDev == pEventDev); + Log(("VBoxNetFlt: %s: IPv6 event %s(0x%lx) %s: %RTnaipv6\n", + pDev ? VBOX_NETDEV_NAME(pDev) : "<unknown>", + vboxNetFltLinuxGetNetDevEventName(ulEventType), ulEventType, + pEventDev ? VBOX_NETDEV_NAME(pEventDev) : "<unknown>", + &ifa->addr)); + + if (pDev != NULL) + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + + if ( !fMyDev + && ipv6_addr_type(&ifa->addr) & (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK)) + return NOTIFY_OK; + + if (pThis->pSwitchPort->pfnNotifyHostAddress) + { + bool fAdded; + if (ulEventType == NETDEV_UP) + fAdded = true; + else if (ulEventType == NETDEV_DOWN) + fAdded = false; + else + return NOTIFY_OK; + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, + kIntNetAddrType_IPv6, &ifa->addr); + } + + return rc; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + struct net_device * pDev; + int err; + int rc = VINF_SUCCESS; + IPRT_LINUX_SAVE_EFL_AC(); + NOREF(pvIfData); + + LogFlow(("vboxNetFltPortOsXmit: pThis=%p (%s)\n", pThis, pThis->szName)); + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + if (pDev) + { + /* + * Create a sk_buff for the gather list and push it onto the wire. + */ + if (fDst & INTNETTRUNKDIR_WIRE) + { + struct sk_buff *pBuf = vboxNetFltLinuxSkBufFromSG(pThis, pSG, true); + if (pBuf) + { + vboxNetFltDumpPacket(pSG, true, "wire", 1); + Log6(("vboxNetFltPortOsXmit: pBuf->cb dump:\n%.*Rhxd\n", sizeof(pBuf->cb), pBuf->cb)); + Log6(("vboxNetFltPortOsXmit: dev_queue_xmit(%p)\n", pBuf)); + err = dev_queue_xmit(pBuf); + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + /* + * Create a sk_buff for the gather list and push it onto the host stack. + */ + if (fDst & INTNETTRUNKDIR_HOST) + { + struct sk_buff *pBuf = vboxNetFltLinuxSkBufFromSG(pThis, pSG, false); + if (pBuf) + { + vboxNetFltDumpPacket(pSG, true, "host", (fDst & INTNETTRUNKDIR_WIRE) ? 0 : 1); + Log6(("vboxNetFltPortOsXmit: pBuf->cb dump:\n%.*Rhxd\n", sizeof(pBuf->cb), pBuf->cb)); + Log6(("vboxNetFltPortOsXmit: netif_rx_ni(%p)\n", pBuf)); +#if RTLNX_VER_MIN(5,18,0) || RTLNX_RHEL_MIN(9,1) + local_bh_disable(); + err = netif_rx(pBuf); + local_bh_enable(); +#else + err = netif_rx_ni(pBuf); +#endif + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + struct net_device *pDev; + IPRT_LINUX_SAVE_EFL_AC(); + + LogFlow(("vboxNetFltPortOsSetActive: pThis=%p (%s), fActive=%RTbool, fDisablePromiscuous=%RTbool\n", + pThis, pThis->szName, fActive, pThis->fDisablePromiscuous)); + + if (pThis->fDisablePromiscuous) + return; + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + if (pDev) + { + /* + * This api is a bit weird, the best reference is the code. + * + * Also, we have a bit or race conditions wrt the maintenance of + * host the interface promiscuity for vboxNetFltPortOsIsPromiscuous. + */ +#ifdef LOG_ENABLED + u_int16_t fIf; + unsigned const cPromiscBefore = pDev->promiscuity; +#endif + if (fActive) + { + Assert(!pThis->u.s.fPromiscuousSet); + + rtnl_lock(); + dev_set_promiscuity(pDev, 1); + rtnl_unlock(); + pThis->u.s.fPromiscuousSet = true; + Log(("vboxNetFltPortOsSetActive: enabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + else + { + if (pThis->u.s.fPromiscuousSet) + { + rtnl_lock(); + dev_set_promiscuity(pDev, -1); + rtnl_unlock(); + Log(("vboxNetFltPortOsSetActive: disabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + pThis->u.s.fPromiscuousSet = false; + +#ifdef LOG_ENABLED + fIf = dev_get_flags(pDev); + Log(("VBoxNetFlt: fIf=%#x; %d->%d\n", fIf, cPromiscBefore, pDev->promiscuity)); +#endif + } + + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + } + IPRT_LINUX_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + /* + * Remove packet handler when we get disconnected from internal switch as + * we don't want the handler to forward packets to disconnected switch. + */ + if (ASMAtomicCmpXchgBool(&pThis->u.s.fPacketHandler, false, true)) + { + IPRT_LINUX_SAVE_EFL_AC(); + dev_remove_pack(&pThis->u.s.PacketType); + Log(("vboxNetFltOsDisconnectIt: this=%p: Packet handler removed.\n", pThis)); + IPRT_LINUX_RESTORE_EFL_AC(); + } + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + /* + * Report the GSO capabilities of the host and device (if connected). + * Note! No need to mark ourselves busy here. + */ + /** @todo duplicate work here now? Attach */ +#if defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + Log3(("vboxNetFltOsConnectIt: reporting host tso tso6\n")); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, + 0 + | RT_BIT_32(PDMNETWORKGSOTYPE_IPV4_TCP) + | RT_BIT_32(PDMNETWORKGSOTYPE_IPV6_TCP) + , INTNETTRUNKDIR_HOST); + +#endif + vboxNetFltLinuxReportNicGsoCapabilities(pThis); + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + struct net_device *pDev; + bool fRegistered; + IPRT_LINUX_SAVE_EFL_AC(); + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + vboxNetFltLinuxUnhookDev(pThis, NULL); +#endif + + /** @todo This code may race vboxNetFltLinuxUnregisterDevice (very very + * unlikely, but none the less). Since it doesn't actually update the + * state (just reads it), it is likely to panic in some interesting + * ways. */ + + RTSpinlockAcquire(pThis->hSpinlock); + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + fRegistered = ASMAtomicXchgBool(&pThis->u.s.fRegistered, false); + RTSpinlockRelease(pThis->hSpinlock); + + if (fRegistered) + { + vboxNetFltSetLinkState(pThis, pDev, false); + +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + skb_queue_purge(&pThis->u.s.XmitQueue); +#endif + Log(("vboxNetFltOsDeleteInstance: this=%p: xmit queue purged.\n", pThis)); + Log(("vboxNetFltOsDeleteInstance: Device %p(%s) released. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + dev_put(pDev); + } + + unregister_inet6addr_notifier(&pThis->u.s.NotifierIPv6); + unregister_inetaddr_notifier(&pThis->u.s.NotifierIPv4); + + Log(("vboxNetFltOsDeleteInstance: this=%p: Notifier removed.\n", pThis)); + unregister_netdevice_notifier(&pThis->u.s.Notifier); + module_put(THIS_MODULE); + + IPRT_LINUX_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + int err; + IPRT_LINUX_SAVE_EFL_AC(); + NOREF(pvContext); + + pThis->u.s.Notifier.notifier_call = vboxNetFltLinuxNotifierCallback; + err = register_netdevice_notifier(&pThis->u.s.Notifier); + if (err) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_FAILED; + } + if (!pThis->u.s.fRegistered) + { + unregister_netdevice_notifier(&pThis->u.s.Notifier); + LogRel(("VBoxNetFlt: failed to find %s.\n", pThis->szName)); + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_NOT_FOUND; + } + + Log(("vboxNetFltOsInitInstance: this=%p: Notifier installed.\n", pThis)); + if ( pThis->fDisconnectedFromHost + || !try_module_get(THIS_MODULE)) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_FAILED; + } + + if (pThis->pSwitchPort->pfnNotifyHostAddress) + { + VBOXNETFLTNOTIFIER Enumerator; + + /* + * register_inetaddr_notifier() and register_inet6addr_notifier() + * do not call the callback for existing devices. Enumerating + * all network devices explicitly is a bit of an ifdef mess, + * so co-opt register_netdevice_notifier() to do that for us. + */ + RT_ZERO(Enumerator); + Enumerator.Notifier.notifier_call = vboxNetFltLinuxEnumeratorCallback; + Enumerator.pThis = pThis; + + err = register_netdevice_notifier(&Enumerator.Notifier); + if (err) + { + LogRel(("%s: failed to enumerate network devices: error %d\n", __FUNCTION__, err)); + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; + } + + unregister_netdevice_notifier(&Enumerator.Notifier); + + pThis->u.s.NotifierIPv4.notifier_call = vboxNetFltLinuxNotifierIPv4Callback; + err = register_inetaddr_notifier(&pThis->u.s.NotifierIPv4); + if (err) + LogRel(("%s: failed to register IPv4 notifier: error %d\n", __FUNCTION__, err)); + + pThis->u.s.NotifierIPv6.notifier_call = vboxNetFltLinuxNotifierIPv6Callback; + err = register_inet6addr_notifier(&pThis->u.s.NotifierIPv6); + if (err) + LogRel(("%s: failed to register IPv6 notifier: error %d\n", __FUNCTION__, err)); + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + /* + * Init the linux specific members. + */ + ASMAtomicUoWriteNullPtr(&pThis->u.s.pDev); + pThis->u.s.fRegistered = false; + pThis->u.s.fPromiscuousSet = false; + pThis->u.s.fPacketHandler = false; + memset(&pThis->u.s.PacketType, 0, sizeof(pThis->u.s.PacketType)); +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + skb_queue_head_init(&pThis->u.s.XmitQueue); +# if RTLNX_VER_MIN(2,6,20) + INIT_WORK(&pThis->u.s.XmitTask, vboxNetFltLinuxXmitTask); +# else + INIT_WORK(&pThis->u.s.XmitTask, vboxNetFltLinuxXmitTask, &pThis->u.s.XmitTask); +# endif +#endif + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + NOREF(pThis); NOREF(pvIfData); NOREF(pMac); +} + + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIf); NOREF(pvIfData); + return VINF_SUCCESS; +} + + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIfData); + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt b/src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt new file mode 100755 index 00000000..f2f8f1f4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt @@ -0,0 +1,113 @@ +#!/bin/sh +# $Id: files_vboxnetflt $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-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 <https://www.gnu.org/licenses>. +# +# 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 +# + +VBOX_VBOXNETFLT_SOURCES=" \ + ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \ + ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.h \ + ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \ + ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \ + ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \ + ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \ + ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \ + ${PATH_ROOT}/include/iprt/avl.h=>include/iprt/avl.h \ + ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \ + ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \ + ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.h \ + ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \ + ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \ + ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \ + ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \ + ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \ + ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \ + ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \ + ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \ + ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \ + ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \ + ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \ + ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \ + ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \ + ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \ + ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \ + ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.h \ + ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \ + ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \ + ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \ + ${PATH_ROOT}/include/iprt/thread.h=>include/iprt/thread.h \ + ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \ + ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \ + ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \ + ${PATH_ROOT}/include/iprt/uint64.h=>include/iprt/uint64.h \ + ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \ + ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \ + ${PATH_ROOT}/include/iprt/uuid.h=>include/iprt/uuid.h \ + ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \ + ${PATH_ROOT}/include/iprt/linux/version.h=>include/iprt/linux/version.h \ + ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \ + ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \ + ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \ + ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \ + ${PATH_ROOT}/include/VBox/intnet.h=>include/VBox/intnet.h \ + ${PATH_ROOT}/include/VBox/intnetinline.h=>include/VBox/intnetinline.h \ + ${PATH_ROOT}/include/VBox/vmm/pdmnetinline.h=>include/VBox/vmm/pdmnetinline.h \ + ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.h \ + ${PATH_ROOT}/include/VBox/sup.h=>include/VBox/sup.h \ + ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \ + ${PATH_ROOT}/include/VBox/SUPDrvMangling.h=>include/VBox/SUPDrvMangling.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c=>linux/VBoxNetFlt-linux.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c=>VBoxNetFlt.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h=>VBoxNetFltInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClient.c=>SUPR0IdcClient.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c=>SUPR0IdcClientComponent.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h=>SUPR0IdcClientInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c=>linux/SUPR0IdcClient-linux.c \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>math/gcc/divdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>math/gcc/moddi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>math/gcc/qdivrem.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>math/gcc/quad.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivdi3.c=>math/gcc/udivdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivmoddi4.c=>math/gcc/udivmoddi4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/umoddi3.c=>math/gcc/umoddi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c new file mode 100644 index 00000000..f455ec3c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c @@ -0,0 +1,4042 @@ +/* $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 <https://www.gnu.org/licenses>. + * + * 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 <VBox/log.h> +#include <VBox/err.h> +#include <VBox/intnetinline.h> +#include <VBox/version.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/alloca.h> +#include <iprt/net.h> +#include <iprt/mem.h> +#include <iprt/thread.h> +#include <iprt/spinlock.h> +#include <iprt/crc.h> +#include <iprt/err.h> +#include <iprt/ctype.h> +#define VBOXNETFLT_SOLARIS_IPV6_POLLING +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING +# include <iprt/timer.h> +# include <iprt/time.h> +#endif + +#include <inet/ip.h> +#include <net/if.h> +#include <sys/socket.h> +#include <sys/kstr.h> +#include <sys/file.h> +#include <sys/sockio.h> +#include <sys/strsubr.h> +#include <sys/pathname.h> +#include <sys/t_kuser.h> + +#include <sys/types.h> +#include <sys/dlpi.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/param.h> +#include <sys/ethernet.h> +#include <sys/stat.h> +#include <sys/stream.h> +#include <sys/stropts.h> +#include <sys/strsun.h> +#include <sys/modctl.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunldi.h> +#include <sys/ctf_api.h> + +// 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; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c new file mode 100644 index 00000000..4a717253 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c @@ -0,0 +1,1702 @@ +/* $Id: VBoxNetFltBow-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 <https://www.gnu.org/licenses>. + * + * 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 <VBox/log.h> +#include <VBox/err.h> +#include <VBox/intnetinline.h> +#include <VBox/version.h> +#include <iprt/initterm.h> +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/rand.h> +#include <iprt/net.h> +#include <iprt/spinlock.h> +#include <iprt/mem.h> + +#include <sys/types.h> +#include <sys/modctl.h> +#include <sys/conf.h> +#include <sys/stat.h> +#include <sys/ddi.h> +#include <sys/gld.h> +#include <sys/sunddi.h> +#include <sys/strsubr.h> +#include <sys/dlpi.h> +#include <sys/dls_mgmt.h> +#include <sys/mac.h> +#include <sys/strsun.h> + +#include <sys/vnic_mgmt.h> +#include <sys/mac_client.h> +#include <sys/mac_provider.h> +#include <sys/dls.h> +#include <sys/dld.h> +#include <sys/cred.h> + + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxbow" +/** The module descriptions as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox NetBow" +/** The dynamically created VNIC name (hardcoded in NetIf-solaris.cpp). + * @todo move this define into a common header. */ +#define VBOXBOW_VNIC_NAME "vboxvnic" +/** The VirtualBox VNIC template name (hardcoded in NetIf-solaris.cpp). + * @todo move this define into a common header. */ +#define VBOXBOW_VNIC_TEMPLATE_NAME "vboxvnic_template" +/** Debugging switch for using symbols in kmdb */ +# define LOCAL static +/** VBOXNETFLTVNIC::u32Magic */ +# define VBOXNETFLTVNIC_MAGIC 0x0ddfaced + +/** 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; + +/* Private: from sys/vlan.h */ +#ifndef VLAN_ID_NONE +# define VLAN_ID_NONE 0 +#endif + +/* Private: from sys/param.h */ +#ifndef MAXLINKNAMESPECIFIER +# define MAXLINKNAMESPECIFIER 96 /* MAXLINKNAMELEN + ZONENAME_MAX */ +#endif + +/* Private: from sys/mac_client_priv.h, mac client function prototypes. */ +extern uint16_t mac_client_vid(mac_client_handle_t hClient); +extern void mac_client_get_resources(mac_client_handle_t hClient, mac_resource_props_t *pResources); +extern int mac_client_set_resources(mac_client_handle_t hClient, mac_resource_props_t *pResources); + + +/********************************************************************************************************************************* +* Kernel Entry Hooks * +*********************************************************************************************************************************/ +LOCAL int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +LOCAL int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +LOCAL int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppvResult); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxNetFltSolarisCbOps = +{ + nulldev, /* c open */ + nulldev, /* c close */ + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + nodev, /* c read */ + nodev, /* c write*/ + nodev, /* c ioctl*/ + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_NEW | D_MP, /* compat. flag */ + CB_REV, /* revision */ + nodev, /* c aread */ + nodev /* c awrite */ +}; + +/** + * dev_ops: for driver device operations + */ +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, + NULL, /* bus ops */ + nodev, /* power */ + ddi_quiesce_not_needed +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxNetFltSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxNetFltSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxNetFltSolarisModLinkage = +{ + MODREV_1, + { + &g_VBoxNetFltSolarisModule, + NULL, + } +}; + +/* + * VBOXNETFLTVNICTEMPLATE: VNIC template information. + */ +typedef struct VBOXNETFLTVNICTEMPLATE +{ + /** The name of link on which the VNIC template is created on. */ + char szLinkName[MAXNAMELEN]; + /** The VLAN Id (can be VLAN_ID_NONE). */ + uint16_t uVLANId; + /** Resources (bandwidth, CPU bindings, flow priority etc.) */ + mac_resource_props_t Resources; +} VBOXNETFLTVNICTEMPLATE; +typedef struct VBOXNETFLTVNICTEMPLATE *PVBOXNETFLTVNICTEMPLATE; + +/** + * VBOXNETFLTVNIC: Per-VNIC instance data. + */ +typedef struct VBOXNETFLTVNIC +{ + /** Magic number (VBOXNETFLTVNIC_MAGIC). */ + uint32_t u32Magic; + /** Whether we created the VNIC or not. */ + bool fCreated; + /** Pointer to the VNIC template if any. */ + PVBOXNETFLTVNICTEMPLATE pVNICTemplate; + /** Pointer to the VirtualBox interface instance. */ + void *pvIf; + /** The MAC handle. */ + mac_handle_t hInterface; + /** The VNIC link ID. */ + datalink_id_t hLinkId; + /** The MAC client handle */ + mac_client_handle_t hClient; + /** The unicast address handle. */ + mac_unicast_handle_t hUnicast; + /** The promiscuous handle. */ + mac_promisc_handle_t hPromisc; + /* The VNIC name. */ + char szName[MAXLINKNAMESPECIFIER]; + /** Handle to the next VNIC in the list. */ + list_node_t hNode; +} VBOXNETFLTVNIC; +typedef struct VBOXNETFLTVNIC *PVBOXNETFLTVNIC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global Device handle we only support one instance. */ +static dev_info_t *g_pVBoxNetFltSolarisDip = NULL; +/** The (common) global data. */ +static VBOXNETFLTGLOBALS g_VBoxNetFltSolarisGlobals; +/** Global next-free VNIC Id (never decrements). */ +static volatile uint64_t g_VBoxNetFltSolarisVNICId; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +LOCAL mblk_t *vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst); +LOCAL unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg); +LOCAL int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc); +LOCAL void vboxNetFltSolarisRecv(void *pvData, mac_resource_handle_t hResource, mblk_t *pMsg, boolean_t fLoopback); +//LOCAL void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg); +LOCAL int vboxNetFltSolarisReportInfo(PVBOXNETFLTINS pThis, mac_handle_t hInterface, bool fIsVNIC); +LOCAL int vboxNetFltSolarisInitVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC); +LOCAL int vboxNetFltSolarisInitVNICTemplate(PVBOXNETFLTINS pThis, PVBOXNETFLTVNICTEMPLATE pVNICTemplate); +LOCAL PVBOXNETFLTVNIC vboxNetFltSolarisAllocVNIC(void); +LOCAL void vboxNetFltSolarisFreeVNIC(PVBOXNETFLTVNIC pVNIC); +LOCAL void vboxNetFltSolarisDestroyVNIC(PVBOXNETFLTVNIC pVNIC); +LOCAL int vboxNetFltSolarisCreateVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC *ppVNIC); +DECLINLINE(int) vboxNetFltSolarisGetLinkId(const char *pszMacName, datalink_id_t *pLinkId); + +/** + * Kernel entry points + */ +int _init(void) +{ + Log((DEVICE_NAME ":_init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxNetFltSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + cmn_err(CE_NOTE, ":failed to disable autounloading!\n"); + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + 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")); + + RTR0Term(); + } + else + cmn_err(CE_NOTE, "failed to initialize IPRT (rc=%d)\n", rc); + + memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + int rc; + Log((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! rc=%d\n", rc)); + return EBUSY; + } + + rc = mod_remove(&g_VBoxNetFltSolarisModLinkage); + if (!rc) + RTR0Term(); + + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + /* _info() can be called before _init() so RTR0Init() might not be called at this point. */ + int rc = mod_info(&g_VBoxNetFltSolarisModLinkage, pModInfo); + 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. + */ +LOCAL int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + Log((DEVICE_NAME ":VBoxNetFltSolarisAttach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_ATTACH: + { + g_pVBoxNetFltSolarisDip = pDip; + return DDI_SUCCESS; + } + + 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. + */ +LOCAL int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + Log((DEVICE_NAME ":VBoxNetFltSolarisDetach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_DETACH: + { + 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; + } +} + + +/** + * 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. + */ +LOCAL int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + Log((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; + break; + } + } + + return DDI_FAILURE; +} + + +/** + * Create a solaris message block from the SG list. + * + * @param pThis The instance. + * @param pSG Pointer to the scatter-gather list. + * @param fDst INTNETTRUNKDIR_XXX. + * + * @returns Solaris message block. + */ +DECLINLINE(mblk_t *) vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG pThis=%p pSG=%p\n", pThis, pSG)); + + mblk_t *pMsg = allocb(pSG->cbTotal, BPRI_HI); + 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; + } + } + return pMsg; +} + + +/** + * Calculate the number of segments required for this message block. + * + * @param pThis The instance + * @param pMsg Pointer to the data message. + * + * @returns Number of segments. + */ +LOCAL 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. + * + * @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. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pThis=%p pMsg=%p pSG=%p cSegs=%d\n", pThis, pMsg, pSG, cSegs)); + + /* + * Convert the message block to segments. Works cbTotal and sets cSegsUsed. + */ + 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; +} + + +#if 0 +/** + * Simple packet dump, used for internal debugging. + * + * @param pMsg Pointer to the message to analyze and dump. + */ +LOCAL 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); + 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 + + +/** + * Helper. + */ +DECLINLINE(bool) vboxNetFltPortSolarisIsHostMac(PVBOXNETFLTINS pThis, PCRTMAC pMac) +{ + 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]; +} + + +/** + * Receive (rx) entry point. + * + * @param pvData Private data. + * @param hResource The resource handle. + * @param pMsg The packet. + * @param fLoopback Whether this is a loopback packet or not. + */ +LOCAL void vboxNetFltSolarisRecv(void *pvData, mac_resource_handle_t hResource, mblk_t *pMsg, boolean_t fLoopback) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisRecv pvData=%p pMsg=%p fLoopback=%d cbData=%d\n", pvData, pMsg, fLoopback, + pMsg ? MBLKL(pMsg) : 0)); + + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvData; + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pMsg); + + /* + * Active? Retain the instance and increment the busy counter. + */ + if (!vboxNetFltTryRetainBusyActive(pThis)) + { + freemsgchain(pMsg); + return; + } + + uint32_t fSrc = INTNETTRUNKDIR_WIRE; + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; + if ( MBLKL(pMsg) >= sizeof(RTNETETHERHDR) + && vboxNetFltPortSolarisIsHostMac(pThis, &pEthHdr->SrcMac)) + fSrc = INTNETTRUNKDIR_HOST; + + /* + * Route all received packets into the internal network. + */ + uint16_t cFailed = 0; + for (mblk_t *pCurMsg = pMsg; pCurMsg != NULL; pCurMsg = pCurMsg->b_next) + { + unsigned cSegs = vboxNetFltSolarisMBlkCalcSGSegs(pThis, pCurMsg); + 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, pSG, fSrc); + else + cFailed++; + } + vboxNetFltRelease(pThis, true /* fBusy */); + + if (RT_UNLIKELY(cFailed)) + LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG failed for %u packets.\n", cFailed)); + + freemsgchain(pMsg); + + NOREF(hResource); +} + + +#if 0 +/** + * MAC layer link notification hook. + * + * @param pvArg Opaque pointer to the instance. + * @param Type Notification Type. + * + * @remarks This hook will be invoked for various changes to the underlying + * interface even when VMs aren't running so don't do any funky stuff + * here. + */ +LOCAL void vboxNetFltSolarisLinkNotify(void *pvArg, mac_notify_type_t Type) +{ + LogRel((DEVICE_NAME ":vboxNetFltSolarisLinkNotify pvArg=%p Type=%d\n", pvArg, Type)); + + PVBOXNETFLTINS pThis = pvArg; + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pThis->u.s.hInterface); + + switch (Type) + { + case MAC_NOTE_LINK: + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisLinkNotify link state change\n")); + link_state_t hLinkState = mac_stat_get(pThis->u.s.hInterface, MAC_STAT_LINK_STATE); + bool fDisconnectedFromHost = hLinkState == LINK_STATE_UP ? false : true; + if (fDisconnectedFromHost != ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, fDisconnectedFromHost); + LogRel((DEVICE_NAME ":vboxNetFltSolarisLinkNotify link state change: new state=%s\n", + fDisconnectedFromHost ? "DOWN" : "UP")); + } + break; + } + + default: + return; + } +} +#endif + + +/** + * Report capabilities and MAC address to IntNet after obtaining the MAC address + * of the underlying interface for a VNIC or the current interface if it's a + * physical/ether-stub interface. + * + * @param pThis The instance. + * @param hInterface The Interface handle. + * @param fIsVNIC Whether this interface handle corresponds to a VNIC + * or not. + * + * @remarks Retains the instance while doing it's job. + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisReportInfo(PVBOXNETFLTINS pThis, mac_handle_t hInterface, bool fIsVNIC) +{ + mac_handle_t hLowerMac = NULL; + if (!fIsVNIC) + hLowerMac = hInterface; + else + { + hLowerMac = mac_get_lower_mac_handle(hInterface); + if (RT_UNLIKELY(!hLowerMac)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisReportInfo failed to get lower MAC handle for '%s'\n", pThis->szName)); + return VERR_INVALID_HANDLE; + } + } + + pThis->u.s.hInterface = hLowerMac; + +#if 0 + /* + * Try setup link notification hooks, this might fail if mac_no_notification() + * doesn't support it. We won't bother using the private function since link notification + * isn't critical for us and ignore failures. + */ + pThis->u.s.hNotify = mac_notify_add(hLowerMac, vboxNetFltSolarisLinkNotify, pThis); + if (!pThis->u.s.hNotify) + LogRel((DEVICE_NAME ":vboxNetFltSolarisReportInfo Warning! Failed to setup link notification hook.\n")); +#endif + + mac_unicast_primary_get(hLowerMac, (uint8_t *)pThis->u.s.MacAddr.au8); + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + Log((DEVICE_NAME ":vboxNetFltSolarisReportInfo phys mac %.6Rhxs\n", &pThis->u.s.MacAddr)); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, false); /** @todo Promisc */ + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisReportInfo failed to retain interface. pThis=%p\n", pThis)); + + return VERR_INTNET_FLT_IF_BUSY; +} + + +/** + * Initialize a VNIC, optionally from a template. + * + * @param pThis The instance. + * @param pVNIC Pointer to the VNIC. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisInitVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC) +{ + /* + * Some paranoia. + */ + AssertReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pVNIC, VERR_INVALID_PARAMETER); + AssertReturn(pVNIC->hInterface, VERR_INVALID_POINTER); + AssertReturn(pVNIC->hLinkId != DATALINK_INVALID_LINKID, VERR_INVALID_HANDLE); + AssertReturn(!pVNIC->hClient, VERR_INVALID_POINTER); + + int rc = mac_client_open(pVNIC->hInterface, &pVNIC->hClient, + NULL, /* name of this client */ + MAC_OPEN_FLAGS_USE_DATALINK_NAME | /* client name same as underlying NIC */ + MAC_OPEN_FLAGS_MULTI_PRIMARY /* allow multiple primary unicasts */ + ); + if (RT_LIKELY(!rc)) + { + if (pVNIC->pVNICTemplate) + rc = mac_client_set_resources(pVNIC->hClient, &pVNIC->pVNICTemplate->Resources); + + if (RT_LIKELY(!rc)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisInitVNIC succesfully initialized VNIC.\n")); + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNIC mac_client_set_resources failed. rc=%d\n", rc)); + rc = VERR_INTNET_FLT_VNIC_INIT_FAILED; + } + + mac_client_close(pVNIC->hClient, 0 /* flags */); + pVNIC->hClient = NULL; + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNIC failed to open mac client for '%s' rc=%d\n", pThis->szName, rc)); + + return VERR_INTNET_FLT_VNIC_OPEN_FAILED; +} + + + +/** + * Get the underlying link name for a VNIC (template). + * + * @return VBox status code. + * @param hVNICMacHandle The handle to the VNIC. + * @param pszLowerLinkName Where to store the lower-mac linkname, must be + * at least MAXLINKNAMELEN in size. + */ +LOCAL int vboxNetFltSolarisGetLowerLinkName(mac_handle_t hVNICMacHandle, char *pszLowerLinkName) +{ + Assert(mac_is_vnic(hVNICMacHandle)); + mac_handle_t hPhysLinkHandle = mac_get_lower_mac_handle(hVNICMacHandle); + if (RT_LIKELY(hPhysLinkHandle)) + { + datalink_id_t PhysLinkId; + const char *pszMacName = mac_name(hPhysLinkHandle); + int rc = vboxNetFltSolarisGetLinkId(pszMacName, &PhysLinkId); + if (RT_SUCCESS(rc)) + { + rc = dls_mgmt_get_linkinfo(PhysLinkId, pszLowerLinkName, NULL /*class*/, NULL /*media*/, NULL /*flags*/); + if (RT_LIKELY(!rc)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLowerLinkName failed to get link info. pszMacName=%s pszLowerLinkName=%s\n", + pszMacName, pszLowerLinkName)); + return VERR_INTNET_FLT_LOWER_LINK_INFO_NOT_FOUND; + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLowerLinkName failed to get link id. pszMacName=%s pszLowerLinkName=%s\n", + pszMacName, pszLowerLinkName)); + return VERR_INTNET_FLT_LOWER_LINK_ID_NOT_FOUND; + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLowerLinkName failed to get lower-mac. pszLowerLinkName=%s\n", pszLowerLinkName)); + return VERR_INTNET_FLT_LOWER_LINK_OPEN_FAILED; +} + + +/** + * Initializes the VNIC template. This involves opening the template VNIC to + * retreive info. like the VLAN Id, underlying MAC address etc. + * + * @param pThis The VM connection instance. + * @param pVNICTemplate Pointer to a VNIC template to initialize. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisInitVNICTemplate(PVBOXNETFLTINS pThis, PVBOXNETFLTVNICTEMPLATE pVNICTemplate) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate pThis=%p pVNICTemplate=%p\n", pThis, pVNICTemplate)); + + AssertReturn(pVNICTemplate, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u.s.fIsVNICTemplate == true, VERR_INVALID_STATE); + + /* + * Get the VNIC template's datalink ID. + */ + datalink_id_t VNICLinkId; + int rc = vboxNetFltSolarisGetLinkId(pThis->szName, &VNICLinkId); + if (RT_SUCCESS(rc)) + { + /* + * Open the VNIC to obtain a MAC handle so as to retreive the VLAN ID. + */ + mac_handle_t hInterface; + rc = mac_open_by_linkid(VNICLinkId, &hInterface); + if (!rc) + { + /* + * Get the underlying linkname. + */ + AssertCompile(sizeof(pVNICTemplate->szLinkName) >= MAXLINKNAMELEN); + rc = vboxNetFltSolarisGetLowerLinkName(hInterface, pVNICTemplate->szLinkName); + if (RT_SUCCESS(rc)) + { + /* + * Now open the VNIC template to retrieve the VLAN Id & resources. + */ + mac_client_handle_t hClient; + rc = mac_client_open(hInterface, &hClient, + NULL, /* name of this client */ + MAC_OPEN_FLAGS_USE_DATALINK_NAME | /* client name same as underlying NIC */ + MAC_OPEN_FLAGS_MULTI_PRIMARY /* allow multiple primary unicasts */ + ); + if (RT_LIKELY(!rc)) + { + pVNICTemplate->uVLANId = mac_client_vid(hClient); + mac_client_get_resources(hClient, &pVNICTemplate->Resources); + mac_client_close(hClient, 0 /* fFlags */); + mac_close(hInterface); + + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate successfully init. VNIC template. szLinkName=%s " + "VLAN Id=%u\n", pVNICTemplate->szLinkName, pVNICTemplate->uVLANId)); + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to open VNIC template. rc=%d\n", rc)); + rc = VERR_INTNET_FLT_IF_FAILED; + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to get lower linkname for VNIC template '%s'.\n", + pThis->szName)); + + mac_close(hInterface); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to open by link ID. rc=%d\n", rc)); + rc = VERR_INTNET_FLT_IF_FAILED; + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to get VNIC template link Id. rc=%d\n", rc)); + + return rc; +} + + +/** + * Allocate a VNIC structure. + * + * @returns An allocated VNIC structure or NULL in case of errors. + */ +LOCAL PVBOXNETFLTVNIC vboxNetFltSolarisAllocVNIC(void) +{ + PVBOXNETFLTVNIC pVNIC = RTMemAllocZ(sizeof(VBOXNETFLTVNIC)); + if (RT_UNLIKELY(!pVNIC)) + return NULL; + + pVNIC->u32Magic = VBOXNETFLTVNIC_MAGIC; + pVNIC->fCreated = false; + pVNIC->pVNICTemplate = NULL; + pVNIC->pvIf = NULL; + pVNIC->hInterface = NULL; + pVNIC->hLinkId = DATALINK_INVALID_LINKID; + pVNIC->hClient = NULL; + pVNIC->hUnicast = NULL; + pVNIC->hPromisc = NULL; + RT_ZERO(pVNIC->szName); + list_link_init(&pVNIC->hNode); + return pVNIC; +} + + +/** + * Frees an allocated VNIC. + * + * @param pVNIC Pointer to the VNIC. + */ +DECLINLINE(void) vboxNetFltSolarisFreeVNIC(PVBOXNETFLTVNIC pVNIC) +{ + RTMemFree(pVNIC); +} + + +/** + * Destroy a created VNIC if it was created by us, or just + * de-initializes the VNIC freeing up resources handles. + * + * @param pVNIC Pointer to the VNIC. + */ +LOCAL void vboxNetFltSolarisDestroyVNIC(PVBOXNETFLTVNIC pVNIC) +{ + AssertPtrReturnVoid(pVNIC); + AssertMsgReturnVoid(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, ("pVNIC=%p u32Magic=%#x\n", pVNIC, pVNIC->u32Magic)); + if (pVNIC) + { + if (pVNIC->hClient) + { +#if 0 + if (pVNIC->hUnicast) + { + mac_unicast_remove(pVNIC->hClient, pVNIC->hUnicast); + pVNIC->hUnicast = NULL; + } +#endif + + if (pVNIC->hPromisc) + { + mac_promisc_remove(pVNIC->hPromisc); + pVNIC->hPromisc = NULL; + } + + mac_rx_clear(pVNIC->hClient); + + mac_client_close(pVNIC->hClient, 0 /* fFlags */); + pVNIC->hClient = NULL; + } + + if (pVNIC->hInterface) + { + mac_close(pVNIC->hInterface); + pVNIC->hInterface = NULL; + } + + if (pVNIC->fCreated) + { + vnic_delete(pVNIC->hLinkId, 0 /* Flags */); + pVNIC->hLinkId = DATALINK_INVALID_LINKID; + pVNIC->fCreated = false; + } + + if (pVNIC->pVNICTemplate) + { + RTMemFree(pVNIC->pVNICTemplate); + pVNIC->pVNICTemplate = NULL; + } + } +} + + +/** + * Create a non-persistent VNIC over the given interface. + * + * @param pThis The VM connection instance. + * @param ppVNIC Where to store the created VNIC. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisCreateVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC *ppVNIC) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC pThis=%p\n", pThis)); + + AssertReturn(pThis, VERR_INVALID_POINTER); + AssertReturn(ppVNIC, VERR_INVALID_POINTER); + + int rc = VERR_INVALID_STATE; + PVBOXNETFLTVNIC pVNIC = vboxNetFltSolarisAllocVNIC(); + if (RT_UNLIKELY(!pVNIC)) + return VERR_NO_MEMORY; + + /* + * Set a random MAC address for now. It will be changed to the VM interface's + * MAC address later, see vboxNetFltPortOsNotifyMacAddress(). + */ + RTMAC GuestMac; + GuestMac.au8[0] = 0x08; + GuestMac.au8[1] = 0x00; + GuestMac.au8[2] = 0x27; + RTRandBytes(&GuestMac.au8[3], 3); + + AssertCompile(sizeof(RTMAC) <= MAXMACADDRLEN); + + const char *pszLinkName = pThis->szName; + uint16_t uVLANId = VLAN_ID_NONE; + vnic_mac_addr_type_t AddrType = VNIC_MAC_ADDR_TYPE_FIXED; + vnic_ioc_diag_t Diag = VNIC_IOC_DIAG_NONE; + int MacSlot = 0; + int MacLen = sizeof(GuestMac); + uint32_t fFlags = 0; + + if (pThis->u.s.fIsVNICTemplate) + { + pVNIC->pVNICTemplate = RTMemAllocZ(sizeof(VBOXNETFLTVNICTEMPLATE)); + if (RT_UNLIKELY(!pVNIC->pVNICTemplate)) + { + vboxNetFltSolarisFreeVNIC(pVNIC); + return VERR_NO_MEMORY; + } + + /* + * Initialize the VNIC template. + */ + rc = vboxNetFltSolarisInitVNICTemplate(pThis, pVNIC->pVNICTemplate); + if (RT_FAILURE(rc)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC failed to initialize VNIC from VNIC template. rc=%Rrc\n", rc)); + vboxNetFltSolarisFreeVNIC(pVNIC); + return rc; + } + + pszLinkName = pVNIC->pVNICTemplate->szLinkName; + uVLANId = pVNIC->pVNICTemplate->uVLANId; +#if 0 + /* + * Required only if we're creating a VLAN interface & not a VNIC with a VLAN Id. + */ + if (uVLANId != VLAN_ID_NONE) + fFlags |= MAC_VLAN; +#endif + Log((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC pThis=%p VLAN Id=%u\n", pThis, uVLANId)); + } + + /* + * Make sure the dynamic VNIC we're creating doesn't already exists, if so pick a new instance. + * This is to avoid conflicts with users manually creating VNICs whose name starts with VBOXBOW_VNIC_NAME. + */ + do + { + AssertCompile(sizeof(pVNIC->szName) > sizeof(VBOXBOW_VNIC_NAME "18446744073709551615" /* UINT64_MAX */)); + RTStrPrintf(pVNIC->szName, sizeof(pVNIC->szName), "%s%RU64", VBOXBOW_VNIC_NAME, g_VBoxNetFltSolarisVNICId); + mac_handle_t hTmpMacHandle; + rc = mac_open_by_linkname(pVNIC->szName, &hTmpMacHandle); + if (rc) + break; + mac_close(hTmpMacHandle); + ASMAtomicIncU64(&g_VBoxNetFltSolarisVNICId); + } while (1); + + /* + * Create the VNIC under 'pszLinkName', which can be the one from the VNIC template or can + * be a physical interface. + */ + rc = vnic_create(pVNIC->szName, pszLinkName, &AddrType, &MacLen, GuestMac.au8, &MacSlot, 0 /* Mac-Prefix Length */, uVLANId, + fFlags, &pVNIC->hLinkId, &Diag, NULL /* Reserved */); + if (!rc) + { + pVNIC->fCreated = true; + ASMAtomicIncU64(&g_VBoxNetFltSolarisVNICId); + + /* + * Now try opening the created VNIC. + */ + rc = mac_open_by_linkid(pVNIC->hLinkId, &pVNIC->hInterface); + if (!rc) + { + /* + * Initialize the VNIC from the physical interface or the VNIC template. + */ + rc = vboxNetFltSolarisInitVNIC(pThis, pVNIC); + if (RT_SUCCESS(rc)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC created VNIC '%s' over '%s' with random mac %.6Rhxs\n", + pVNIC->szName, pszLinkName, &GuestMac)); + *ppVNIC = pVNIC; + return VINF_SUCCESS; + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC vboxNetFltSolarisInitVNIC failed. rc=%d\n", rc)); + mac_close(pVNIC->hInterface); + pVNIC->hInterface = NULL; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC logrel failed to open VNIC '%s' over '%s'. rc=%d\n", pVNIC->szName, + pThis->szName, rc)); + rc = VERR_INTNET_FLT_VNIC_LINK_ID_NOT_FOUND; + } + + vboxNetFltSolarisDestroyVNIC(pVNIC); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC failed to create VNIC '%s' over '%s' rc=%d Diag=%d\n", pVNIC->szName, + pszLinkName, rc, Diag)); + rc = VERR_INTNET_FLT_VNIC_CREATE_FAILED; + } + + vboxNetFltSolarisFreeVNIC(pVNIC); + + return rc; +} + + +/** + * Wrapper for getting the datalink ID given the MAC name. + * + * @param pszMacName The MAC name. + * @param pLinkId Where to store the datalink ID. + * + * @returns VBox status code. + */ +DECLINLINE(int) vboxNetFltSolarisGetLinkId(const char *pszMacName, datalink_id_t *pLinkId) +{ + /* + * dls_mgmt_get_linkid() requires to be in a state to answer upcalls. We should always use this + * first before resorting to other means to retrieve the MAC name. + */ + int rc = dls_mgmt_get_linkid(pszMacName, pLinkId); + if (rc) + rc = dls_devnet_macname2linkid(pszMacName, pLinkId); + + if (RT_LIKELY(!rc)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLinkId failed for '%s'. rc=%d\n", pszMacName, rc)); + return RTErrConvertFromErrno(rc); +} + + +/** + * Set the promiscuous mode RX hook. + * + * @param pThis The VM connection instance. + * @param pVNIC Pointer to the VNIC. + * + * @returns VBox status code. + */ +DECLINLINE(int) vboxNetFltSolarisSetPromisc(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC) +{ + int rc = VINF_SUCCESS; + if (!pVNIC->hPromisc) + { + rc = mac_promisc_add(pVNIC->hClient, MAC_CLIENT_PROMISC_FILTERED, vboxNetFltSolarisRecv, pThis, &pVNIC->hPromisc, + MAC_PROMISC_FLAGS_NO_TX_LOOP | MAC_PROMISC_FLAGS_VLAN_TAG_STRIP | MAC_PROMISC_FLAGS_NO_PHYS); + if (RT_UNLIKELY(rc)) + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetPromisc failed. rc=%d\n", rc)); + rc = RTErrConvertFromErrno(rc); + } + return rc; +} + + +/** + * Clear the promiscuous mode RX hook. + * + * @param pThis The VM connection instance. + * @param pVNIC Pointer to the VNIC. + */ +DECLINLINE(void) vboxNetFltSolarisRemovePromisc(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC) +{ + if (pVNIC->hPromisc) + { + mac_promisc_remove(pVNIC->hPromisc); + pVNIC->hPromisc = NULL; + } +} + + +/* -=-=-=-=-=- Common Hooks -=-=-=-=-=- */ + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + Log((DEVICE_NAME ":vboxNetFltPortOsSetActive pThis=%p fActive=%d\n", pThis, fActive)); + + /* + * Reactivate/quiesce the interface. + */ + PVBOXNETFLTVNIC pVNIC = list_head(&pThis->u.s.hVNICs); + if (fActive) + { + for (; pVNIC != NULL; pVNIC = list_next(&pThis->u.s.hVNICs, pVNIC)) + if (pVNIC->hClient) + { +#if 0 + mac_rx_set(pVNIC->hClient, vboxNetFltSolarisRecv, pThis); +#endif + vboxNetFltSolarisSetPromisc(pThis, pVNIC); + } + } + else + { + for (; pVNIC != NULL; pVNIC = list_next(&pThis->u.s.hVNICs, pVNIC)) + if (pVNIC->hClient) + { +#if 0 + mac_rx_clear(pVNIC->hClient); +#endif + vboxNetFltSolarisRemovePromisc(pThis, pVNIC); + } + } +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + Log((DEVICE_NAME ":vboxNetFltOsDisconnectIt pThis=%p\n", pThis)); + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + Log((DEVICE_NAME ":vboxNetFltOsConnectIt pThis=%p\n", pThis)); + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + Log((DEVICE_NAME ":vboxNetFltOsDeleteInstance pThis=%p\n", pThis)); + + if (pThis->u.s.hNotify) + mac_notify_remove(pThis->u.s.hNotify, B_TRUE /* Wait */); + + /* + * Destroy all managed VNICs. If a VNIC was passed to us, there + * will be only 1 item in the list, otherwise as many interfaces + * that were somehow not destroyed using DisconnectInterface() will be + * present. + */ + PVBOXNETFLTVNIC pVNIC = NULL; + while ((pVNIC = list_remove_head(&pThis->u.s.hVNICs)) != NULL) + { + vboxNetFltSolarisDestroyVNIC(pVNIC); + vboxNetFltSolarisFreeVNIC(pVNIC); + } + + list_destroy(&pThis->u.s.hVNICs); +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p pvContext=%p\n", pThis, pvContext)); + + /* + * Figure out if the interface is a VNIC or a physical/etherstub/whatever NIC, then + * do the actual VNIC creation if necessary in vboxNetFltPortOsConnectInterface(). + */ + mac_handle_t hInterface; + int rc = mac_open_by_linkname(pThis->szName, &hInterface); + if (RT_LIKELY(!rc)) + { + rc = mac_is_vnic(hInterface); + if (!rc) + { + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p physical interface '%s' detected.\n", pThis, pThis->szName)); + pThis->u.s.fIsVNIC = false; + } + else + { + pThis->u.s.fIsVNIC = true; + if (RTStrNCmp(pThis->szName, VBOXBOW_VNIC_TEMPLATE_NAME, sizeof(VBOXBOW_VNIC_TEMPLATE_NAME) - 1) == 0) + { + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p VNIC template '%s' detected.\n", pThis, pThis->szName)); + pThis->u.s.fIsVNICTemplate = true; + } + } + + if ( pThis->u.s.fIsVNIC + && !pThis->u.s.fIsVNICTemplate) + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p VNIC '%s' detected.\n", pThis, pThis->szName)); + + /* + * Report info. (host MAC address, promiscuous, GSO capabilities etc.) to IntNet. + */ + rc = vboxNetFltSolarisReportInfo(pThis, hInterface, pThis->u.s.fIsVNIC); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to report info. rc=%d\n", rc)); + + mac_close(hInterface); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to open link '%s'! rc=%d\n", pThis->szName, rc)); + rc = VERR_INTNET_FLT_IF_FAILED; + } + + return rc; +} + + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + /* + * Init. the solaris specific data. + */ + pThis->u.s.fIsVNIC = false; + pThis->u.s.fIsVNICTemplate = false; + list_create(&pThis->u.s.hVNICs, sizeof(VBOXNETFLTVNIC), offsetof(VBOXNETFLTVNIC, hNode)); + pThis->u.s.hNotify = NULL; + RT_ZERO(pThis->u.s.MacAddr); + return VINF_SUCCESS; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + /* + * @todo Think about this. + */ + return false; +} + + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + /* + * Validate parameters. + */ + PVBOXNETFLTVNIC pVNIC = pvIfData; + AssertPtrReturn(pVNIC, VERR_INVALID_POINTER); + AssertMsgReturn(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, + ("Invalid magic=%#x (expected %#x)\n", pVNIC->u32Magic, VBOXNETFLTVNIC_MAGIC), + VERR_INVALID_MAGIC); + + /* + * Xmit the packet down the appropriate VNIC interface. + */ + int rc = VINF_SUCCESS; + mblk_t *pMsg = vboxNetFltSolarisMBlkFromSG(pThis, pSG, fDst); + if (RT_LIKELY(pMsg)) + { + Log((DEVICE_NAME ":vboxNetFltPortOsXmit pThis=%p cbData=%d\n", pThis, MBLKL(pMsg))); + + mac_tx_cookie_t pXmitCookie = mac_tx(pVNIC->hClient, pMsg, 0 /* Hint */, MAC_DROP_ON_NO_DESC, NULL /* return message */); + if (RT_LIKELY(!pXmitCookie)) + return VINF_SUCCESS; + + pMsg = NULL; + rc = VERR_NET_IO_ERROR; + LogRel((DEVICE_NAME ":vboxNetFltPortOsXmit Xmit failed pVNIC=%p.\n", pVNIC)); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsXmit no memory for allocating Xmit packet.\n")); + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + Log((DEVICE_NAME ":vboxNetFltPortOSNotifyMacAddress pszIf=%s pszVNIC=%s MAC=%.6Rhxs\n", pThis->szName, + ((PVBOXNETFLTVNIC)pvIfData)->szName, pMac)); + + /* + * Validate parameters. + */ + PVBOXNETFLTVNIC pVNIC = pvIfData; + AssertPtrReturnVoid(pVNIC); + AssertMsgReturnVoid(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, + ("Invalid pVNIC=%p magic=%#x (expected %#x)\n", pvIfData, pVNIC->u32Magic, VBOXNETFLTVNIC_MAGIC)); + AssertMsgReturnVoid(pVNIC->hLinkId != DATALINK_INVALID_LINKID, + ("Invalid hLinkId pVNIC=%p magic=%#x\n", pVNIC, pVNIC->u32Magic)); + + /* + * Set the MAC address of the VNIC to the one used by the VM interface. + */ + uchar_t au8GuestMac[MAXMACADDRLEN]; + bcopy(pMac->au8, au8GuestMac, sizeof(RTMAC)); + + vnic_mac_addr_type_t AddrType = VNIC_MAC_ADDR_TYPE_FIXED; + vnic_ioc_diag_t Diag = VNIC_IOC_DIAG_NONE; + int MacSlot = 0; + int MacLen = sizeof(RTMAC); + + int rc = vnic_modify_addr(pVNIC->hLinkId, &AddrType, &MacLen, au8GuestMac, &MacSlot, 0 /* Mac-Prefix Length */, &Diag); + if (RT_LIKELY(!rc)) + { + /* + * Remove existing unicast address, promisc. and the RX hook. + */ +#if 0 + if (pVNIC->hUnicast) + { + mac_rx_clear(pVNIC->hClient); + mac_unicast_remove(pVNIC->hClient, pVNIC->hUnicast); + pVNIC->hUnicast = NULL; + } +#endif + + if (pVNIC->hPromisc) + { + mac_promisc_remove(pVNIC->hPromisc); + pVNIC->hPromisc = NULL; + } + + mac_diag_t MacDiag = MAC_DIAG_NONE; + /* uint16_t uVLANId = pVNIC->pVNICTemplate ? pVNIC->pVNICTemplate->uVLANId : 0; */ +#if 0 + rc = mac_unicast_add(pVNIC->hClient, NULL, MAC_UNICAST_PRIMARY, &pVNIC->hUnicast, 0 /* VLAN Id */, &MacDiag); +#endif + if (RT_LIKELY(!rc)) + { + rc = vboxNetFltSolarisSetPromisc(pThis, pVNIC); +#if 0 + if (RT_SUCCESS(rc)) + { + /* + * Set the RX receive function. + * This shouldn't be necessary as vboxNetFltPortOsSetActive() will be invoked after this, but in the future, + * if the guest NIC changes MAC address this may not be followed by a vboxNetFltPortOsSetActive() call, + * so set it here anyway. + */ + mac_rx_set(pVNIC->hClient, vboxNetFltSolarisRecv, pThis); + Log((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress successfully added unicast address %.6Rhxs\n", pMac)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress failed to set promiscuous mode. rc=%d\n", rc)); + mac_unicast_remove(pVNIC->hClient, pVNIC->hUnicast); + pVNIC->hUnicast = NULL; +#endif + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress failed to add primary unicast address. rc=%d Diag=%d\n", rc, + MacDiag)); + } + } + else + { + /* + * They really ought to use EEXIST, but I'm afraid this error comes from the VNIC device driver directly. + * Sequence: vnic_modify_addr()->mac_unicast_primary_set()->mac_update_macaddr() which uses a function pointer + * to the MAC driver (calls mac_vnic_unicast_set() in our case). Documented here if the error code should change we know + * where to look. + */ + if (rc == ENOTSUP) + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress: failed! a VNIC with mac %.6Rhxs probably already exists.", + pMac, rc)); + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress: This NIC cannot establish connection. szName=%s szVNIC=%s\n", + pThis->szName, pVNIC->szName)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress failed! mac %.6Rhxs rc=%d Diag=%d\n", pMac, rc, Diag)); + } +} + + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + Log((DEVICE_NAME ":vboxNetFltPortOsConnectInterface pThis=%p pvIf=%p\n", pThis, pvIf)); + + int rc = VINF_SUCCESS; + + /* + * If the underlying interface is a physical interface or a VNIC template, we need to create + * a VNIC per guest NIC. + */ + if ( !pThis->u.s.fIsVNIC + || pThis->u.s.fIsVNICTemplate) + { + PVBOXNETFLTVNIC pVNIC = NULL; + rc = vboxNetFltSolarisCreateVNIC(pThis, &pVNIC); + if (RT_SUCCESS(rc)) + { + /* + * VM Interface<->VNIC association so that we can Xmit/Recv on the right ones. + */ + pVNIC->pvIf = pvIf; + *ppvIfData = pVNIC; + + /* + * Add the created VNIC to the list of VNICs we manage. + */ + list_insert_tail(&pThis->u.s.hVNICs, pVNIC); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to create VNIC rc=%d\n", rc)); + } + else + { + /* + * This is a VNIC passed to us, use it directly. + */ + PVBOXNETFLTVNIC pVNIC = vboxNetFltSolarisAllocVNIC(); + if (RT_LIKELY(pVNIC)) + { + pVNIC->fCreated = false; + + rc = mac_open_by_linkname(pThis->szName, &pVNIC->hInterface); + if (!rc) + { + /* + * Obtain the data link ID for this VNIC, it's needed for modifying the MAC address among other things. + */ + rc = vboxNetFltSolarisGetLinkId(pThis->szName, &pVNIC->hLinkId); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the VNIC and add it to the list of managed VNICs. + */ + RTStrPrintf(pVNIC->szName, sizeof(pVNIC->szName), "%s", pThis->szName); + rc = vboxNetFltSolarisInitVNIC(pThis, pVNIC); + if (!rc) + { + pVNIC->pvIf = pvIf; + *ppvIfData = pVNIC; + list_insert_head(&pThis->u.s.hVNICs, pVNIC); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to initialize VNIC. rc=%d\n", rc)); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to get link id for '%s'. rc=%d\n", + pThis->szName, rc)); + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to open VNIC '%s'. rc=%d\n", pThis->szName, rc)); + rc = VERR_OPEN_FAILED; + } + + vboxNetFltSolarisFreeVNIC(pVNIC); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to allocate VNIC private data.\n")); + rc = VERR_NO_MEMORY; + } + } + + return rc; +} + + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + Log((DEVICE_NAME ":vboxNetFltPortOsDisconnectInterface pThis=%p\n", pThis)); + + /* + * It is possible we get called when vboxNetFltPortOsConnectInterface() didn't succeed + * in which case pvIfData will be NULL. See intnetR0NetworkCreateIf() pfnConnectInterface call + * through reference counting in SUPR0ObjRelease() for the "pIf" object. + */ + PVBOXNETFLTVNIC pVNIC = pvIfData; + if (RT_LIKELY(pVNIC)) + { + AssertMsgReturn(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, + ("Invalid magic=%#x (expected %#x)\n", pVNIC->u32Magic, VBOXNETFLTVNIC_MAGIC), VERR_INVALID_POINTER); + + /* + * If the underlying interface is a physical interface or a VNIC template, we need to delete the created VNIC. + */ + if ( !pThis->u.s.fIsVNIC + || pThis->u.s.fIsVNICTemplate) + { + /* + * Remove the VNIC from the list, destroy and free it. + */ + list_remove(&pThis->u.s.hVNICs, pVNIC); + Log((DEVICE_NAME ":vboxNetFltPortOsDisconnectInterface destroying pVNIC=%p\n", pVNIC)); + vboxNetFltSolarisDestroyVNIC(pVNIC); + vboxNetFltSolarisFreeVNIC(pVNIC); + } + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf new file mode 100644 index 00000000..8b4f716d --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf @@ -0,0 +1,43 @@ +# $Id: vboxbow.conf $ +## @file +# Solaris Host VBoxBow (Crossbow) Configuration +# + +# +# 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 <https://www.gnu.org/licenses>. +# +# 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 +# + +# This needs to go into /platform/i86pc/kernel/drv, +# while the 64-bit driver object goes into the amd64 +# subdirectory (32-bit drivers goes into the same +# directory). +# +name="vboxbow" parent="pseudo" instance=0; + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf new file mode 100644 index 00000000..dce8b795 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf @@ -0,0 +1,56 @@ +# $Id: vboxflt.conf $ +## @file +# Solaris Host VBoxFlt Configuration +# + +# +# 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 <https://www.gnu.org/licenses>. +# +# 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 +# + +# This needs to go into /platform/i86pc/kernel/drv, +# while the 64-bit driver object goes into the amd64 +# subdirectory (32-bit drivers goes into the same +# directory). +# +name="vboxflt" parent="pseudo" instance=0; + +# If the interface being used does not have IPv6 plumbed and you want +# vboxflt to automatically attach to it by checking for Ipv6 stream every +# "ipv6-pollinterval" seconds. +# +# By default this is disabled meaning if the interface does not have Ipv6 +# plumbed when the virtual machine starts, guest<->host Ipv6 will not work +# though guest<->remote Ipv6 would work. +# +# Enable if you have a dynamically plumbing/unplumbing Ipv6 interface for +# which you want vboxflt to adjust accordingly, otherwise keep this disabled. +# Enabling this option will have some minor performance penalty. +#ipv6-pollinterval=3; + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp new file mode 100644 index 00000000..a686196f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp @@ -0,0 +1,3770 @@ +/* $Id: VBoxNetCfg.cpp $ */ +/** @file + * VBoxNetCfg.cpp - Network Configuration API. + */ + +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 _WIN32_DCOM + +#include "VBox/VBoxNetCfg-win.h" +#include "VBox/VBoxDrvCfg-win.h" + +#include <devguid.h> +#include <regstr.h> +#include <iprt/win/shlobj.h> +#include <cfgmgr32.h> +#include <iprt/win/objbase.h> + +#include <Wbemidl.h> + +#include <iprt/win/winsock2.h> +#include <iprt/win/ws2tcpip.h> +#include <ws2ipdef.h> +#include <iprt/win/netioapi.h> +#include <iprt/win/iphlpapi.h> + +#include <iprt/asm.h> +#include <iprt/assertcompile.h> +#include <iprt/mem.h> +#include <iprt/list.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <VBox/com/string.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef Assert /** @todo r=bird: where would this be defined? */ +//# ifdef DEBUG +//# define Assert(_expr) assert(_expr) +//# else +//# define Assert(_expr) do{ }while (0) +//# endif +# define Assert _ASSERT +# define AssertMsg(expr, msg) do{}while (0) +#endif + +#define NonStandardLog DoLogging +#define NonStandardLogFlow(x) DoLogging x + +#define SetErrBreak(strAndArgs) \ + if (1) { \ + hrc = E_FAIL; \ + NonStandardLog strAndArgs; \ + bstrError.printfNoThrow strAndArgs; \ + break; \ + } else do {} while (0) + + +#define VBOXNETCFGWIN_NETADP_ID_SZ "sun_VBoxNetAdp" +#define VBOXNETCFGWIN_NETADP_ID_WSZ RT_CONCAT(L,VBOXNETCFGWIN_NETADP_ID_SZ) +#define DRIVERHWID VBOXNETCFGWIN_NETADP_ID_WSZ + +/* We assume the following name matches the device description in vboxnetadp6.inf */ +#define HOSTONLY_ADAPTER_NAME_SZ "VirtualBox Host-Only Ethernet Adapter" +#define HOSTONLY_ADAPTER_NAME_WSZ RT_CONCAT(L,HOSTONLY_ADAPTER_NAME_SZ) + +#define VBOX_CONNECTION_NAME_SZ "VirtualBox Host-Only Network" +#define VBOX_CONNECTION_NAME_WSZ RT_CONCAT(L,VBOX_CONNECTION_NAME_SZ) + +#define VBOXNETCFGWIN_NETLWF_ID L"oracle_VBoxNetLwf" + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static PFNVBOXNETCFGLOGGER volatile g_pfnLogger = NULL; + +/* + * Wrappers for HelpAPI functions + */ +typedef void FNINITIALIZEIPINTERFACEENTRY( _Inout_ PMIB_IPINTERFACE_ROW row); +typedef FNINITIALIZEIPINTERFACEENTRY *PFNINITIALIZEIPINTERFACEENTRY; + +typedef NETIOAPI_API FNGETIPINTERFACEENTRY( _Inout_ PMIB_IPINTERFACE_ROW row); +typedef FNGETIPINTERFACEENTRY *PFNGETIPINTERFACEENTRY; + +typedef NETIOAPI_API FNSETIPINTERFACEENTRY( _Inout_ PMIB_IPINTERFACE_ROW row); +typedef FNSETIPINTERFACEENTRY *PFNSETIPINTERFACEENTRY; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static PFNINITIALIZEIPINTERFACEENTRY g_pfnInitializeIpInterfaceEntry = NULL; +static PFNGETIPINTERFACEENTRY g_pfnGetIpInterfaceEntry = NULL; +static PFNSETIPINTERFACEENTRY g_pfnSetIpInterfaceEntry = NULL; + +static void DoLogging(const char *pszString, ...); + +/* + * Forward declaration for using vboxNetCfgWinSetupMetric() + */ +static HRESULT vboxNetCfgWinSetupMetric(IN NET_LUID *pLuid); +static HRESULT vboxNetCfgWinGetInterfaceLUID(IN HKEY hKey, OUT NET_LUID *pLUID); + + + +static HRESULT vboxNetCfgWinINetCfgLock(IN INetCfg *pNetCfg, + IN LPCWSTR pszwClientDescription, + IN DWORD cmsTimeout, + OUT LPWSTR *ppszwClientDescription) +{ + INetCfgLock *pLock; + HRESULT hr = pNetCfg->QueryInterface(IID_INetCfgLock, (PVOID *)&pLock); + if (FAILED(hr)) + { + NonStandardLogFlow(("QueryInterface failed: %Rhrc\n", hr)); + return hr; + } + + hr = pLock->AcquireWriteLock(cmsTimeout, pszwClientDescription, ppszwClientDescription); + if (hr == S_FALSE) + NonStandardLogFlow(("Write lock busy\n")); + else if (FAILED(hr)) + NonStandardLogFlow(("AcquireWriteLock failed: %Rhrc\n", hr)); + + pLock->Release(); + return hr; +} + +static HRESULT vboxNetCfgWinINetCfgUnlock(IN INetCfg *pNetCfg) +{ + INetCfgLock *pLock; + HRESULT hr = pNetCfg->QueryInterface(IID_INetCfgLock, (PVOID *)&pLock); + if (FAILED(hr)) + { + NonStandardLogFlow(("QueryInterface failed: %Rhrc\n", hr)); + return hr; + } + + hr = pLock->ReleaseWriteLock(); + if (FAILED(hr)) + NonStandardLogFlow(("ReleaseWriteLock failed: %Rhrc\n", hr)); + + pLock->Release(); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinQueryINetCfg(OUT INetCfg **ppNetCfg, + IN BOOL fGetWriteLock, + IN LPCWSTR pszwClientDescription, + IN DWORD cmsTimeout, + OUT LPWSTR *ppszwClientDescription) +{ + INetCfg *pNetCfg = NULL; + HRESULT hr = CoCreateInstance(CLSID_CNetCfg, NULL, CLSCTX_INPROC_SERVER, IID_INetCfg, (PVOID *)&pNetCfg); + if (FAILED(hr)) + { + NonStandardLogFlow(("CoCreateInstance failed: %Rhrc\n", hr)); + return hr; + } + + if (fGetWriteLock) + { + hr = vboxNetCfgWinINetCfgLock(pNetCfg, pszwClientDescription, cmsTimeout, ppszwClientDescription); + if (hr == S_FALSE) + { + NonStandardLogFlow(("Write lock is busy\n", hr)); + hr = NETCFG_E_NO_WRITE_LOCK; + } + } + + if (SUCCEEDED(hr)) + { + hr = pNetCfg->Initialize(NULL); + if (SUCCEEDED(hr)) + { + *ppNetCfg = pNetCfg; + return S_OK; + } + NonStandardLogFlow(("Initialize failed: %Rhrc\n", hr)); + } + + pNetCfg->Release(); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinReleaseINetCfg(IN INetCfg *pNetCfg, IN BOOL fHasWriteLock) +{ + if (!pNetCfg) /* If network config has been released already, just bail out. */ + { + NonStandardLogFlow(("Warning: No network config given but write lock is set to TRUE\n")); + return S_OK; + } + + HRESULT hr = pNetCfg->Uninitialize(); + if (FAILED(hr)) + { + NonStandardLogFlow(("Uninitialize failed: %Rhrc\n", hr)); + /* Try to release the write lock below. */ + } + + if (fHasWriteLock) + { + HRESULT hr2 = vboxNetCfgWinINetCfgUnlock(pNetCfg); + if (FAILED(hr2)) + NonStandardLogFlow(("vboxNetCfgWinINetCfgUnlock failed: %Rhrc\n", hr2)); + if (SUCCEEDED(hr)) + hr = hr2; + } + + pNetCfg->Release(); + return hr; +} + +static HRESULT vboxNetCfgWinGetComponentByGuidEnum(IEnumNetCfgComponent *pEnumNcc, + IN const GUID *pGuid, + OUT INetCfgComponent **ppNcc) +{ + HRESULT hr = pEnumNcc->Reset(); + if (FAILED(hr)) + { + NonStandardLogFlow(("Reset failed: %Rhrc\n", hr)); + return hr; + } + + INetCfgComponent *pNcc = NULL; + while ((hr = pEnumNcc->Next(1, &pNcc, NULL)) == S_OK) + { + ULONG uComponentStatus = 0; + hr = pNcc->GetDeviceStatus(&uComponentStatus); + if (SUCCEEDED(hr)) + { + if (uComponentStatus == 0) + { + GUID NccGuid; + hr = pNcc->GetInstanceGuid(&NccGuid); + + if (SUCCEEDED(hr)) + { + if (NccGuid == *pGuid) + { + /* found the needed device */ + *ppNcc = pNcc; + break; + } + } + else + NonStandardLogFlow(("GetInstanceGuid failed: %Rhrc\n", hr)); + } + } + + pNcc->Release(); + } + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGetComponentByGuid(IN INetCfg *pNc, + IN const GUID *pguidClass, + IN const GUID * pComponentGuid, + OUT INetCfgComponent **ppncc) +{ + IEnumNetCfgComponent *pEnumNcc = NULL; + HRESULT hr = pNc->EnumComponents(pguidClass, &pEnumNcc); + if (SUCCEEDED(hr)) + { + hr = vboxNetCfgWinGetComponentByGuidEnum(pEnumNcc, pComponentGuid, ppncc); + if (hr == S_FALSE) + NonStandardLogFlow(("Component not found\n")); + else if (FAILED(hr)) + NonStandardLogFlow(("vboxNetCfgWinGetComponentByGuidEnum failed: %Rhrc\n", hr)); + pEnumNcc->Release(); + } + else + NonStandardLogFlow(("EnumComponents failed: %Rhrc\n", hr)); + return hr; +} + +static HRESULT vboxNetCfgWinQueryInstaller(IN INetCfg *pNetCfg, IN const GUID *pguidClass, INetCfgClassSetup **ppSetup) +{ + HRESULT hr = pNetCfg->QueryNetCfgClass(pguidClass, IID_INetCfgClassSetup, (void **)ppSetup); + if (FAILED(hr)) + NonStandardLogFlow(("QueryNetCfgClass failed: %Rhrc\n", hr)); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinInstallComponent(IN INetCfg *pNetCfg, IN LPCWSTR pszwComponentId, + IN const GUID *pguidClass, OUT INetCfgComponent **ppComponent) +{ + INetCfgClassSetup *pSetup; + HRESULT hr = vboxNetCfgWinQueryInstaller(pNetCfg, pguidClass, &pSetup); + if (FAILED(hr)) + { + NonStandardLogFlow(("vboxNetCfgWinQueryInstaller failed: %Rhrc\n", hr)); + return hr; + } + + OBO_TOKEN Token; + RT_ZERO(Token); + Token.Type = OBO_USER; + + INetCfgComponent *pTempComponent = NULL; + hr = pSetup->Install(pszwComponentId, &Token, + 0, /* IN DWORD dwSetupFlags */ + 0, /* IN DWORD dwUpgradeFromBuildNo */ + NULL, /* IN LPCWSTR pszwAnswerFile */ + NULL, /* IN LPCWSTR pszwAnswerSections */ + &pTempComponent); + if (SUCCEEDED(hr)) + { + if (pTempComponent != NULL) + { + /* + * Set default metric value of interface to fix multicast issue + * See @bugref{6379} for details. + */ + HKEY hKey = (HKEY)INVALID_HANDLE_VALUE; + HRESULT hrc2 = pTempComponent->OpenParamKey(&hKey); + + /* Set default metric value for host-only interface only */ + if ( SUCCEEDED(hrc2) + && hKey != (HKEY)INVALID_HANDLE_VALUE + /* Original was weird: && wcsnicmp(pszwComponentId, VBOXNETCFGWIN_NETADP_ID_WSZ, 256) == 0) */ + && RTUtf16ICmpAscii(pszwComponentId, VBOXNETCFGWIN_NETADP_ID_SZ) == 0) + { + NET_LUID luid; + hrc2 = vboxNetCfgWinGetInterfaceLUID(hKey, &luid); + + /* Close the key as soon as possible. See @bugref{7973}. */ + RegCloseKey(hKey); + hKey = (HKEY)INVALID_HANDLE_VALUE; + + if (FAILED(hrc2)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("VBoxNetCfgWinInstallComponent Warning! vboxNetCfgWinGetInterfaceLUID failed, default metric for new interface will not be set: %Rhrc\n", hrc2)); + } + else + { + hrc2 = vboxNetCfgWinSetupMetric(&luid); + if (FAILED(hrc2)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("VBoxNetCfgWinInstallComponent Warning! vboxNetCfgWinSetupMetric failed, default metric for new interface will not be set: %Rhrc\n", hrc2)); + } + } + } + if (hKey != (HKEY)INVALID_HANDLE_VALUE) + RegCloseKey(hKey); + if (ppComponent != NULL) + *ppComponent = pTempComponent; + else + pTempComponent->Release(); + } + + /* ignore the apply failure */ + HRESULT hrc3 = pNetCfg->Apply(); + Assert(hrc3 == S_OK); + if (hrc3 != S_OK) + NonStandardLogFlow(("Apply failed: %Rhrc\n", hrc3)); + } + else + NonStandardLogFlow(("Install failed: %Rhrc\n", hr)); + + pSetup->Release(); + return hr; +} + +static HRESULT vboxNetCfgWinInstallInfAndComponent(IN INetCfg *pNetCfg, IN LPCWSTR pszwComponentId, IN const GUID *pguidClass, + IN LPCWSTR const *apwszInfPaths, IN UINT cInfPaths, + OUT INetCfgComponent **ppComponent) +{ + NonStandardLogFlow(("Installing %u INF files ...\n", cInfPaths)); + + HRESULT hr = S_OK; + UINT cFilesProcessed = 0; + for (; cFilesProcessed < cInfPaths; cFilesProcessed++) + { + NonStandardLogFlow(("Installing INF file \"%ls\" ...\n", apwszInfPaths[cFilesProcessed])); + hr = VBoxDrvCfgInfInstall(apwszInfPaths[cFilesProcessed]); + if (FAILED(hr)) + { + NonStandardLogFlow(("VBoxNetCfgWinInfInstall failed: %Rhrc\n", hr)); + break; + } + } + + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinInstallComponent(pNetCfg, pszwComponentId, pguidClass, ppComponent); + if (FAILED(hr)) + NonStandardLogFlow(("VBoxNetCfgWinInstallComponent failed: %Rhrc\n", hr)); + } + + if (FAILED(hr)) + { + NonStandardLogFlow(("Installation failed, rolling back installation set ...\n")); + + do + { + HRESULT hr2 = VBoxDrvCfgInfUninstall(apwszInfPaths[cFilesProcessed], 0); + if (FAILED(hr2)) + NonStandardLogFlow(("VBoxDrvCfgInfUninstall failed: %Rhrc\n", hr2)); + /* Keep going. */ + if (!cFilesProcessed) + break; + } while (cFilesProcessed--); + + NonStandardLogFlow(("Rollback complete\n")); + } + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinUninstallComponent(IN INetCfg *pNetCfg, IN INetCfgComponent *pComponent) +{ + GUID GuidClass; + HRESULT hr = pComponent->GetClassGuid(&GuidClass); + if (FAILED(hr)) + { + NonStandardLogFlow(("GetClassGuid failed: %Rhrc\n", hr)); + return hr; + } + + INetCfgClassSetup *pSetup = NULL; + hr = vboxNetCfgWinQueryInstaller(pNetCfg, &GuidClass, &pSetup); + if (FAILED(hr)) + { + NonStandardLogFlow(("vboxNetCfgWinQueryInstaller failed: %Rhrc\n", hr)); + return hr; + } + + OBO_TOKEN Token; + RT_ZERO(Token); + Token.Type = OBO_USER; + + hr = pSetup->DeInstall(pComponent, &Token, NULL /* OUT LPWSTR *pmszwRefs */); + if (SUCCEEDED(hr)) + { + hr = pNetCfg->Apply(); + if (FAILED(hr)) + NonStandardLogFlow(("Apply failed: %Rhrc\n", hr)); + } + else + NonStandardLogFlow(("DeInstall failed: %Rhrc\n", hr)); + + if (pSetup) + pSetup->Release(); + return hr; +} + +typedef BOOL (*PFN_VBOXNETCFGWIN_NETCFGENUM_CALLBACK_T)(IN INetCfg *pNetCfg, IN INetCfgComponent *pNetCfgComponent, + PVOID pvContext); + +static HRESULT vboxNetCfgWinEnumNetCfgComponents(IN INetCfg *pNetCfg, + IN const GUID *pguidClass, + PFN_VBOXNETCFGWIN_NETCFGENUM_CALLBACK_T pfnCallback, + PVOID pContext) +{ + IEnumNetCfgComponent *pEnumComponent = NULL; + HRESULT hr = pNetCfg->EnumComponents(pguidClass, &pEnumComponent); + if (SUCCEEDED(hr)) + { + INetCfgComponent *pNetCfgComponent; + hr = pEnumComponent->Reset(); + for (;;) + { + hr = pEnumComponent->Next(1, &pNetCfgComponent, NULL); + if (hr == S_OK) + { +// ULONG uComponentStatus; +// hr = pNcc->GetDeviceStatus(&uComponentStatus); +// if (SUCCEEDED(hr)) + BOOL fResult = FALSE; + if (pNetCfgComponent) + { + fResult = pfnCallback(pNetCfg, pNetCfgComponent, pContext); + pNetCfgComponent->Release(); + } + + if (!fResult) + break; + } + else + { + if (hr == S_FALSE) + hr = S_OK; /* no more components */ + else + NonStandardLogFlow(("Next failed: %Rhrc\n", hr)); + break; + } + } + pEnumComponent->Release(); + } + return hr; +} + +/** PFNVBOXNETCFGWINNETENUMCALLBACK */ +static BOOL vboxNetCfgWinRemoveAllNetDevicesOfIdCallback(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDev, PVOID pvContext) +{ + RT_NOREF1(pvContext); + + SP_REMOVEDEVICE_PARAMS rmdParams; + RT_ZERO(rmdParams); + rmdParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + rmdParams.ClassInstallHeader.InstallFunction = DIF_REMOVE; + rmdParams.Scope = DI_REMOVEDEVICE_GLOBAL; + + if (SetupDiSetClassInstallParams(hDevInfo,pDev, + &rmdParams.ClassInstallHeader, sizeof(rmdParams))) + { + if (SetupDiSetSelectedDevice(hDevInfo, pDev)) + { +#ifndef VBOXNETCFG_DELAYEDRENAME + /* Figure out NetCfgInstanceId. */ + HKEY hKey = SetupDiOpenDevRegKey(hDevInfo, + pDev, + DICS_FLAG_GLOBAL, + 0, + DIREG_DRV, + KEY_READ); + if (hKey == INVALID_HANDLE_VALUE) + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiOpenDevRegKey failed with error %u\n", + GetLastError())); + else + { + WCHAR wszCfgGuidString[50] = { L'' }; + DWORD cbSize = sizeof(wszCfgGuidString) - sizeof(WCHAR); /* make sure we get a terminated string back */ + DWORD dwValueType = 0; + LSTATUS lrc = RegQueryValueExW(hKey, L"NetCfgInstanceId", NULL, &dwValueType, (LPBYTE)wszCfgGuidString, &cbSize); + if (lrc == ERROR_SUCCESS) + { + /** @todo r=bird: original didn't check the type here, just assumed it was a + * valid zero terminated string. (zero term handled by -sizeof(WHCAR) above now). */ + if (dwValueType == REG_SZ || dwValueType == REG_EXPAND_SZ || dwValueType == REG_EXPAND_SZ) + { + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Processing device ID \"%ls\"\n", + wszCfgGuidString)); + + /* Figure out device name. */ + WCHAR wszDevName[256 + 1] = {0}; + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, pDev, SPDRP_FRIENDLYNAME, NULL, (PBYTE)wszDevName, + sizeof(wszDevName) - sizeof(WCHAR) /* yes, in bytes */, NULL)) + { + /* + * Rename the connection before removing the device. This will + * hopefully prevent an error when we will be attempting + * to rename a newly created connection (see @bugref{6740}). + */ + WCHAR wszNewName[RT_ELEMENTS(wszDevName) + 128 /* ensure sufficient buffer */]; + HRESULT hr = VBoxNetCfgWinGenHostonlyConnectionName(wszDevName, wszNewName, + RT_ELEMENTS(wszNewName) - 10 /*removed++*/, + NULL); + RTUtf16CatAscii(wszNewName, sizeof(wszNewName), " removed"); + if (SUCCEEDED(hr)) + hr = VBoxNetCfgWinRenameConnection(wszCfgGuidString, wszNewName); + //NonStandardLogFlow(("VBoxNetCfgWinRenameConnection(%S,%S) => 0x%x\n", wszCfgGuidString, TempName, hr_tmp)); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Failed to get friendly name for device \"%ls\"\n", + wszCfgGuidString)); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Friendly name for \"%S\" isn't a string: %d\n", + wszCfgGuidString, dwValueType + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Querying instance ID failed with %u (%#x)\n", + lrc, lrc)); + + RegCloseKey(hKey); + } +#endif /* VBOXNETCFG_DELAYEDRENAME */ + + if (SetupDiCallClassInstaller(DIF_REMOVE, hDevInfo, pDev)) + { + SP_DEVINSTALL_PARAMS_W DevParams = { sizeof(DevParams) }; + if (SetupDiGetDeviceInstallParams(hDevInfo, pDev, &DevParams)) + { + if ( (DevParams.Flags & DI_NEEDRESTART) + || (DevParams.Flags & DI_NEEDREBOOT)) + NonStandardLog(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: A reboot is required\n")); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiGetDeviceInstallParams failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiCallClassInstaller failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiSetSelectedDevice failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiSetClassInstallParams failed with %u\n", + GetLastError())); + + /* Continue enumeration. */ + return TRUE; +} + +typedef struct VBOXNECTFGWINPROPCHANGE +{ + VBOXNECTFGWINPROPCHANGE_TYPE_T enmPcType; + HRESULT hr; +} VBOXNECTFGWINPROPCHANGE, *PVBOXNECTFGWINPROPCHANGE; + +static BOOL vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDev, PVOID pContext) +{ + PVBOXNECTFGWINPROPCHANGE pPc = (PVBOXNECTFGWINPROPCHANGE)pContext; + + SP_PROPCHANGE_PARAMS PcParams; + RT_ZERO(PcParams); + PcParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + PcParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + PcParams.Scope = DICS_FLAG_GLOBAL; + + switch (pPc->enmPcType) + { + case VBOXNECTFGWINPROPCHANGE_TYPE_DISABLE: + PcParams.StateChange = DICS_DISABLE; + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: Change type (DICS_DISABLE): %d\n", pPc->enmPcType)); + break; + case VBOXNECTFGWINPROPCHANGE_TYPE_ENABLE: + PcParams.StateChange = DICS_ENABLE; + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: Change type (DICS_ENABLE): %d\n", pPc->enmPcType)); + break; + default: + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: Unexpected prop change type: %d\n", pPc->enmPcType)); + pPc->hr = E_INVALIDARG; + return FALSE; + } + + if (SetupDiSetClassInstallParamsW(hDevInfo, pDev, &PcParams.ClassInstallHeader, sizeof(PcParams))) + { + if (SetupDiSetSelectedDevice(hDevInfo, pDev)) + { + if (SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, pDev)) + { + SP_DEVINSTALL_PARAMS_W DevParams = { sizeof(DevParams) }; + if (SetupDiGetDeviceInstallParamsW(hDevInfo, pDev, &DevParams)) + { + if ( (DevParams.Flags & DI_NEEDRESTART) + || (DevParams.Flags & DI_NEEDREBOOT)) + NonStandardLog(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: A reboot is required\n")); + } + else + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: SetupDiGetDeviceInstallParams failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: SetupDiCallClassInstaller failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("SetupDiSetSelectedDevice failed with %u\n", GetLastError())); + } + else + NonStandardLogFlow(("SetupDiSetClassInstallParams failed with %u\n", GetLastError())); + + /* Continue enumeration. */ + return TRUE; +} + +typedef BOOL (*PFNVBOXNETCFGWINNETENUMCALLBACK)(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDev, PVOID pContext); + +static HRESULT vboxNetCfgWinEnumNetDevices(LPCWSTR pwszPnPId, PFNVBOXNETCFGWINNETENUMCALLBACK pfnCallback, PVOID pvContext) +{ + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Searching for: %ls\n", pwszPnPId)); + + HRESULT hr; + HDEVINFO hDevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, + NULL, /* IN PCTSTR Enumerator, OPTIONAL */ + NULL, /* IN HWND hwndParent, OPTIONAL */ + DIGCF_PRESENT, /* IN DWORD Flags,*/ + NULL, /* IN HDEVINFO DeviceInfoSet, OPTIONAL */ + NULL, /* IN PCTSTR MachineName, OPTIONAL */ + NULL /* IN PVOID Reserved */); + if (hDevInfo != INVALID_HANDLE_VALUE) + { + size_t const cwcPnPId = RTUtf16Len(pwszPnPId); + DWORD winEr = NO_ERROR; + DWORD dwDevId = 0; + DWORD cbBuffer = 0; + PBYTE pbBuffer = NULL; + for (;;) + { + SP_DEVINFO_DATA Dev; + memset(&Dev, 0, sizeof(SP_DEVINFO_DATA)); + Dev.cbSize = sizeof(SP_DEVINFO_DATA); + + if (!SetupDiEnumDeviceInfo(hDevInfo, dwDevId, &Dev)) + { + winEr = GetLastError(); + if (winEr == ERROR_NO_MORE_ITEMS) + winEr = NO_ERROR; + break; + } + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Enumerating device %u ... \n", dwDevId)); + dwDevId++; + + DWORD cbRequired = 0; + SetLastError(0); + if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &Dev, + SPDRP_HARDWAREID, /* IN DWORD Property */ + NULL, /* OUT PDWORD PropertyRegDataType OPTIONAL */ + pbBuffer, /* OUT PBYTE PropertyBuffer */ + cbBuffer, /* IN DWORD PropertyBufferSize */ + &cbRequired /* OUT PDWORD RequiredSize OPTIONAL */)) + { + winEr = GetLastError(); + if (winEr != ERROR_INSUFFICIENT_BUFFER) + { + if (winEr == ERROR_INVALID_DATA) + { + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetDeviceRegistryPropertyW (1) failed with ERROR_INVALID_DATA - ignoring, skipping to next device\n")); + continue; + } + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetDeviceRegistryPropertyW (1) failed with %u\n", winEr)); + break; + } + winEr = NO_ERROR; + + cbBuffer = RT_ALIGN_32(cbRequired, 64); + void *pvNew = RTMemRealloc(pbBuffer, cbBuffer); + if (pvNew) + pbBuffer = (PBYTE)pvNew; + else + { + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Out of memory allocating %u bytes\n", cbBuffer)); + winEr = ERROR_OUTOFMEMORY; + break; + } + + if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &Dev, + SPDRP_HARDWAREID, /* IN DWORD Property */ + NULL, /* OUT PDWORD PropertyRegDataType, OPTIONAL */ + pbBuffer, /* OUT PBYTE PropertyBuffer */ + cbBuffer, /* IN DWORD PropertyBufferSize */ + &cbRequired /* OUT PDWORD RequiredSize OPTIONAL */)) + { + winEr = GetLastError(); + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetDeviceRegistryPropertyW (2) failed with %u\n", + winEr)); + break; + } + } + + PWSTR pwszCurId = (PWSTR)pbBuffer; + size_t cwcCurId = RTUtf16Len(pwszCurId); + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Device %u: %ls\n", dwDevId, pwszCurId)); + + if (cwcCurId >= cwcPnPId) + { + NonStandardLogFlow(("!RTUtf16NICmp(pwszCurId = (%ls), pwszPnPId = (%ls), cwcPnPId = (%d))\n", pwszCurId, pwszPnPId, cwcPnPId)); + + pwszCurId += cwcCurId - cwcPnPId; + if (!RTUtf16NICmp(pwszCurId, pwszPnPId, cwcPnPId)) + { + if (!pfnCallback(hDevInfo, &Dev, pvContext)) + break; + } + } + } + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Found %u devices total\n", dwDevId)); + + if (pbBuffer) + RTMemFree(pbBuffer); + + hr = HRESULT_FROM_WIN32(winEr); + + SetupDiDestroyDeviceInfoList(hDevInfo); + } + else + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetClassDevsExW failed with %u\n", winEr)); + hr = HRESULT_FROM_WIN32(winEr); + } + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Ended with hr (0x%x)\n", hr)); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRemoveAllNetDevicesOfId(IN LPCWSTR pwszPnPId) +{ + return vboxNetCfgWinEnumNetDevices(pwszPnPId, vboxNetCfgWinRemoveAllNetDevicesOfIdCallback, NULL); +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinPropChangeAllNetDevicesOfId(IN LPCWSTR pwszPnPId, VBOXNECTFGWINPROPCHANGE_TYPE_T enmPcType) +{ + VBOXNECTFGWINPROPCHANGE Pc; + Pc.enmPcType = enmPcType; + Pc.hr = S_OK; + NonStandardLogFlow(("Calling VBoxNetCfgWinEnumNetDevices with pwszPnPId (= %ls) and vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback\n", pwszPnPId)); + + HRESULT hr = vboxNetCfgWinEnumNetDevices(pwszPnPId, vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback, &Pc); + if (!SUCCEEDED(hr)) + { + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices failed 0x%x\n", hr)); + return hr; + } + + if (!SUCCEEDED(Pc.hr)) + { + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback failed 0x%x\n", Pc.hr)); + return Pc.hr; + } + + return S_OK; +} + + + +/********************************************************************************************************************************* +* Logging * +*********************************************************************************************************************************/ + +static void DoLogging(const char *pszString, ...) +{ + PFNVBOXNETCFGLOGGER pfnLogger = g_pfnLogger; + if (pfnLogger) + { + char szBuffer[4096]; + va_list va; + va_start(va, pszString); + RTStrPrintfV(szBuffer, RT_ELEMENTS(szBuffer), pszString, va); + va_end(va); + + pfnLogger(szBuffer); + } +} + +VBOXNETCFGWIN_DECL(void) VBoxNetCfgWinSetLogging(IN PFNVBOXNETCFGLOGGER pfnLogger) +{ + g_pfnLogger = pfnLogger; +} + + + +/********************************************************************************************************************************* +* IP configuration API * +*********************************************************************************************************************************/ +/* network settings config */ +#if 1 /** @todo r=bird: Can't we replace this with VBox/com/ptr.h? */ +/** + * Strong referencing operators. Used as a second argument to ComPtr<>/ComObjPtr<>. + */ +template<class C> +class ComStrongRef +{ +protected: + + static void addref(C *p) { p->AddRef(); } + static void release(C *p) { p->Release(); } +}; + + +/** + * Base template for smart COM pointers. Not intended to be used directly. + */ +template<class C, template<class> class RefOps = ComStrongRef> +class ComPtrBase : protected RefOps<C> +{ +public: + + /* special template to disable AddRef()/Release() */ + template<class I> + class NoAddRefRelease : public I + { + public: + virtual ~NoAddRefRelease() { /* Make VC++ 19.2 happy. */ } + private: +#ifndef VBOX_WITH_XPCOM + STDMETHOD_(ULONG, AddRef)() = 0; + STDMETHOD_(ULONG, Release)() = 0; +#else + NS_IMETHOD_(nsrefcnt) AddRef(void) = 0; + NS_IMETHOD_(nsrefcnt) Release(void) = 0; +#endif + }; + +protected: + + ComPtrBase() : p(NULL) {} + ComPtrBase(const ComPtrBase &that) : p(that.p) { addref(); } + ComPtrBase(C *that_p) : p(that_p) { addref(); } + + ~ComPtrBase() { release(); } + + ComPtrBase &operator=(const ComPtrBase &that) + { + safe_assign(that.p); + return *this; + } + + ComPtrBase &operator=(C *that_p) + { + safe_assign(that_p); + return *this; + } + +public: + + void setNull() + { + release(); + p = NULL; + } + + bool isNull() const + { + return (p == NULL); + } + + bool operator!() const { return isNull(); } + + bool operator<(C* that_p) const { return p < that_p; } + bool operator==(C* that_p) const { return p == that_p; } + + template<class I> + bool equalsTo(I *aThat) const + { + return ComPtrEquals(p, aThat); + } + + template<class OC> + bool equalsTo(const ComPtrBase<OC> &oc) const + { + return equalsTo((OC *) oc); + } + + /** Intended to pass instances as in parameters to interface methods */ + operator C *() const { return p; } + + /** + * Dereferences the instance (redirects the -> operator to the managed + * pointer). + */ + NoAddRefRelease<C> *operator->() const + { + AssertMsg (p, ("Managed pointer must not be null\n")); + return (NoAddRefRelease <C> *) p; + } + + template<class I> + HRESULT queryInterfaceTo(I **pp) const + { + if (pp) + { + if (p) + return p->QueryInterface(COM_IIDOF(I), (void **)pp); + *pp = NULL; + return S_OK; + } + return E_INVALIDARG; + } + + /** Intended to pass instances as out parameters to interface methods */ + C **asOutParam() + { + setNull(); + return &p; + } + +private: + + void addref() + { + if (p) + RefOps<C>::addref(p); + } + + void release() + { + if (p) + RefOps<C>::release(p); + } + + void safe_assign(C *that_p) + { + /* be aware of self-assignment */ + if (that_p) + RefOps<C>::addref(that_p); + release(); + p = that_p; + } + + C *p; +}; + +/** + * Smart COM pointer wrapper that automatically manages refcounting of + * interface pointers. + * + * @param I COM interface class + */ +template<class I, template<class> class RefOps = ComStrongRef> +class ComPtr : public ComPtrBase<I, RefOps> +{ + typedef ComPtrBase<I, RefOps> Base; + +public: + + ComPtr() : Base() {} + ComPtr(const ComPtr &that) : Base(that) {} + ComPtr&operator= (const ComPtr &that) + { + Base::operator=(that); + return *this; + } + + template<class OI> + ComPtr(OI *that_p) : Base () { operator=(that_p); } + + /* specialization for I */ + ComPtr(I *that_p) : Base (that_p) {} + + template <class OC> + ComPtr(const ComPtr<OC, RefOps> &oc) : Base() { operator=((OC *) oc); } + + template<class OI> + ComPtr &operator=(OI *that_p) + { + if (that_p) + that_p->QueryInterface(COM_IIDOF(I), (void **)Base::asOutParam()); + else + Base::setNull(); + return *this; + } + + /* specialization for I */ + ComPtr &operator=(I *that_p) + { + Base::operator=(that_p); + return *this; + } + + template<class OC> + ComPtr &operator=(const ComPtr<OC, RefOps> &oc) + { + return operator=((OC *) oc); + } +}; +#endif + +static HRESULT netIfWinFindAdapterClassById(IWbemServices *pSvc, const GUID *pGuid, IWbemClassObject **pAdapterConfig) +{ + HRESULT hr; + + WCHAR wszGuid[50]; + int cwcGuid = StringFromGUID2(*pGuid, wszGuid, RT_ELEMENTS(wszGuid)); + if (cwcGuid) + { + com::BstrFmt bstrQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE SettingID = \"%ls\"", wszGuid); + IEnumWbemClassObject* pEnumerator = NULL; + hr = pSvc->ExecQuery(com::Bstr("WQL").raw(), bstrQuery.raw(), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, &pEnumerator); + if (SUCCEEDED(hr)) + { + if (pEnumerator) + { + IWbemClassObject *pclsObj = NULL; + ULONG uReturn = 0; + hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); + NonStandardLogFlow(("netIfWinFindAdapterClassById: IEnumWbemClassObject::Next -> hr=0x%x pclsObj=%p uReturn=%u 42=%u\n", + hr, (void *)pclsObj, uReturn, 42)); + if (SUCCEEDED(hr)) + { + if (uReturn && pclsObj != NULL) + { + *pAdapterConfig = pclsObj; + pEnumerator->Release(); + NonStandardLogFlow(("netIfWinFindAdapterClassById: S_OK and %p\n", *pAdapterConfig)); + return S_OK; + } + hr = E_FAIL; + } + pEnumerator->Release(); + } + else + { + NonStandardLogFlow(("ExecQuery returned no enumerator\n")); + hr = E_FAIL; + } + } + else + NonStandardLogFlow(("ExecQuery failed (0x%x)\n", hr)); + } + else + { + DWORD winEr = GetLastError(); + hr = HRESULT_FROM_WIN32(winEr); + if (SUCCEEDED(hr)) + hr = E_FAIL; + NonStandardLogFlow(("StringFromGUID2 failed winEr=%u, hr=0x%x\n", winEr, hr)); + } + + NonStandardLogFlow(("netIfWinFindAdapterClassById: 0x%x and %p\n", hr, *pAdapterConfig)); + return hr; +} + +static HRESULT netIfWinIsHostOnly(IWbemClassObject *pAdapterConfig, BOOL *pfIsHostOnly) +{ + VARIANT vtServiceName; + VariantInit(&vtServiceName); + + HRESULT hr = pAdapterConfig->Get(L"ServiceName", 0 /*lFlags*/, &vtServiceName, NULL /*pvtType*/, NULL /*plFlavor*/); + if (SUCCEEDED(hr)) + { + *pfIsHostOnly = RTUtf16CmpAscii(vtServiceName.bstrVal, "VBoxNetAdp") == 0; + + VariantClear(&vtServiceName); + } + + return hr; +} + +static HRESULT netIfWinGetIpSettings(IWbemClassObject * pAdapterConfig, ULONG *pIpv4, ULONG *pMaskv4) +{ + *pIpv4 = 0; + *pMaskv4 = 0; + + VARIANT vtIp; + VariantInit(&vtIp); + HRESULT hr = pAdapterConfig->Get(L"IPAddress", 0, &vtIp, 0, 0); + if (SUCCEEDED(hr)) + { + if (vtIp.vt == (VT_ARRAY | VT_BSTR)) + { + VARIANT vtMask; + VariantInit(&vtMask); + hr = pAdapterConfig->Get(L"IPSubnet", 0, &vtMask, 0, 0); + if (SUCCEEDED(hr)) + { + if (vtMask.vt == (VT_ARRAY | VT_BSTR)) + { + SAFEARRAY *pIpArray = vtIp.parray; + SAFEARRAY *pMaskArray = vtMask.parray; + if (pIpArray && pMaskArray) + { + BSTR pBstrCurIp; + BSTR pBstrCurMask; + for (LONG i = 0; + SafeArrayGetElement(pIpArray, &i, (PVOID)&pBstrCurIp) == S_OK + && SafeArrayGetElement(pMaskArray, &i, (PVOID)&pBstrCurMask) == S_OK; + i++) + { + com::Utf8Str strIp(pBstrCurIp); + ULONG Ipv4 = inet_addr(strIp.c_str()); + if (Ipv4 != INADDR_NONE) + { + *pIpv4 = Ipv4; + + com::Utf8Str strMask(pBstrCurMask); + *pMaskv4 = inet_addr(strMask.c_str()); + break; + } + } + } + } + VariantClear(&vtMask); + } + } + VariantClear(&vtIp); + } + + return hr; +} + +#if 0 /* unused */ + +static HRESULT netIfWinHasIpSettings(IWbemClassObject * pAdapterConfig, SAFEARRAY * pCheckIp, SAFEARRAY * pCheckMask, bool *pFound) +{ + VARIANT vtIp; + HRESULT hr; + VariantInit(&vtIp); + + *pFound = false; + + hr = pAdapterConfig->Get(L"IPAddress", 0, &vtIp, 0, 0); + if (SUCCEEDED(hr)) + { + VARIANT vtMask; + VariantInit(&vtMask); + hr = pAdapterConfig->Get(L"IPSubnet", 0, &vtMask, 0, 0); + if (SUCCEEDED(hr)) + { + SAFEARRAY * pIpArray = vtIp.parray; + SAFEARRAY * pMaskArray = vtMask.parray; + if (pIpArray && pMaskArray) + { + BSTR pIp, pMask; + for (LONG k = 0; + SafeArrayGetElement(pCheckIp, &k, (PVOID)&pIp) == S_OK + && SafeArrayGetElement(pCheckMask, &k, (PVOID)&pMask) == S_OK; + k++) + { + BSTR pCurIp; + BSTR pCurMask; + for (LONG i = 0; + SafeArrayGetElement(pIpArray, &i, (PVOID)&pCurIp) == S_OK + && SafeArrayGetElement(pMaskArray, &i, (PVOID)&pCurMask) == S_OK; + i++) + { + if (!wcsicmp(pCurIp, pIp)) + { + if (!wcsicmp(pCurMask, pMask)) + *pFound = true; + break; + } + } + } + } + + + VariantClear(&vtMask); + } + + VariantClear(&vtIp); + } + + return hr; +} + +static HRESULT netIfWinWaitIpSettings(IWbemServices *pSvc, const GUID * pGuid, SAFEARRAY * pCheckIp, SAFEARRAY * pCheckMask, ULONG sec2Wait, bool *pFound) +{ + /* on Vista we need to wait for the address to get applied */ + /* wait for the address to appear in the list */ + HRESULT hr = S_OK; + ULONG i; + *pFound = false; + ComPtr<IWbemClassObject> pAdapterConfig; + for (i = 0; + (hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam())) == S_OK + && (hr = netIfWinHasIpSettings(pAdapterConfig, pCheckIp, pCheckMask, pFound)) == S_OK + && !(*pFound) + && i < sec2Wait/6; + i++) + { + Sleep(6000); + } + + return hr; +} + +#endif /* unused */ + +static HRESULT netIfWinCreateIWbemServices(IWbemServices **ppSvc) +{ + IWbemLocator *pLoc = NULL; + HRESULT hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc); + if (SUCCEEDED(hr)) + { + IWbemServices *pSvc = NULL; + hr = pLoc->ConnectServer(com::Bstr(L"ROOT\\CIMV2").raw(), /* [in] const BSTR strNetworkResource */ + NULL, /* [in] const BSTR strUser */ + NULL, /* [in] const BSTR strPassword */ + 0, /* [in] const BSTR strLocale */ + NULL, /* [in] LONG lSecurityFlags */ + 0, /* [in] const BSTR strAuthority */ + 0, /* [in] IWbemContext* pCtx */ + &pSvc /* [out] IWbemServices** ppNamespace */); + if (SUCCEEDED(hr)) + { + hr = CoSetProxyBlanket(pSvc, /* IUnknown * pProxy */ + RPC_C_AUTHN_WINNT, /* DWORD dwAuthnSvc */ + RPC_C_AUTHZ_NONE, /* DWORD dwAuthzSvc */ + NULL, /* WCHAR * pServerPrincName */ + RPC_C_AUTHN_LEVEL_CALL, /* DWORD dwAuthnLevel */ + RPC_C_IMP_LEVEL_IMPERSONATE, /* DWORD dwImpLevel */ + NULL, /* RPC_AUTH_IDENTITY_HANDLE pAuthInfo */ + EOAC_NONE /* DWORD dwCapabilities */ + ); + if (SUCCEEDED(hr)) + { + *ppSvc = pSvc; + /* do not need it any more */ + pLoc->Release(); + return hr; + } + + NonStandardLogFlow(("CoSetProxyBlanket failed: %Rhrc\n", hr)); + pSvc->Release(); + } + else + NonStandardLogFlow(("ConnectServer failed: %Rhrc\n", hr)); + pLoc->Release(); + } + else + NonStandardLogFlow(("CoCreateInstance failed: %Rhrc\n", hr)); + return hr; +} + +static HRESULT netIfWinAdapterConfigPath(IWbemClassObject *pObj, com::Bstr *pRet) +{ + VARIANT index; + VariantInit(&index); + HRESULT hr = pObj->Get(L"Index", 0, &index, 0, 0); + if (SUCCEEDED(hr)) + hr = pRet->printfNoThrow("Win32_NetworkAdapterConfiguration.Index='%u'", index.uintVal); + else + { + pRet->setNull(); + NonStandardLogFlow(("Get failed: %Rhrc\n", hr)); + } + return hr; +} + +static HRESULT netIfExecMethod(IWbemServices * pSvc, IWbemClassObject *pClass, com::Bstr const &rObjPath, + const char *pszMethodName, LPWSTR *papwszArgNames, LPVARIANT *pArgs, UINT cArgs, + IWbemClassObject **ppOutParams) +{ + *ppOutParams = NULL; + com::Bstr bstrMethodName; + HRESULT hr = bstrMethodName.assignEx(pszMethodName); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pInParamsDefinition; + ComPtr<IWbemClassObject> pClassInstance; + if (cArgs) + { + hr = pClass->GetMethod(bstrMethodName.raw(), 0, pInParamsDefinition.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + hr = pInParamsDefinition->SpawnInstance(0, pClassInstance.asOutParam()); + if (SUCCEEDED(hr)) + { + for (UINT i = 0; i < cArgs; i++) + { + hr = pClassInstance->Put(papwszArgNames[i], 0, pArgs[i], 0); + if (FAILED(hr)) + break; + } + } + } + } + + if (SUCCEEDED(hr)) + { + IWbemClassObject *pOutParams = NULL; + hr = pSvc->ExecMethod(rObjPath.raw(), bstrMethodName.raw(), 0, NULL, pClassInstance, &pOutParams, NULL); + if (SUCCEEDED(hr)) + *ppOutParams = pOutParams; + } + } + + return hr; +} + +static HRESULT netIfWinCreateIpArray(SAFEARRAY **ppArray, in_addr const *paIps, UINT cIps) +{ + HRESULT hr = S_OK; + SAFEARRAY *pIpArray = SafeArrayCreateVector(VT_BSTR, 0, cIps); + if (pIpArray) + { + for (UINT i = 0; i < cIps; i++) + { + com::Bstr bstrVal; + hr = bstrVal.printfNoThrow("%RTnaipv4", paIps[i].s_addr); + if (SUCCEEDED(hr)) + { + Assert(bstrVal.equals(inet_ntoa(paIps[i]))); + + BSTR pRawVal; + hr = bstrVal.detachToEx(&pRawVal); + if (SUCCEEDED(hr)) + { + LONG aIndex[1] = { (LONG)i }; + hr = SafeArrayPutElement(pIpArray, aIndex, pRawVal); + if (SUCCEEDED(hr)) + continue; + SysFreeString(pRawVal); + } + } + break; + } + + if (SUCCEEDED(hr)) + *ppArray = pIpArray; + else + SafeArrayDestroy(pIpArray); + } + else + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; +} + +#if 0 /* unused */ +static HRESULT netIfWinCreateIpArrayV4V6(SAFEARRAY **ppArray, BSTR Ip) +{ + HRESULT hr; + SAFEARRAY *pIpArray = SafeArrayCreateVector(VT_BSTR, 0, 1); + if (pIpArray) + { + BSTR val = com::Bstr(Ip, false).copy(); + long aIndex[1]; + aIndex[0] = 0; + hr = SafeArrayPutElement(pIpArray, aIndex, val); + if (FAILED(hr)) + { + SysFreeString(val); + SafeArrayDestroy(pIpArray); + } + + if (SUCCEEDED(hr)) + { + *ppArray = pIpArray; + } + } + else + hr = HRESULT_FROM_WIN32(GetLastError()); + + return hr; +} +#endif + + +static HRESULT netIfWinCreateIpArrayVariantV4(VARIANT *pIpAddresses, in_addr const *paIps, UINT cIps) +{ + VariantInit(pIpAddresses); + pIpAddresses->vt = VT_ARRAY | VT_BSTR; + + SAFEARRAY *pIpArray; + HRESULT hr = netIfWinCreateIpArray(&pIpArray, paIps, cIps); + if (SUCCEEDED(hr)) + pIpAddresses->parray = pIpArray; + return hr; +} + +#if 0 /* unused */ +static HRESULT netIfWinCreateIpArrayVariantV4V6(VARIANT * pIpAddresses, BSTR Ip) +{ + HRESULT hr; + VariantInit(pIpAddresses); + pIpAddresses->vt = VT_ARRAY | VT_BSTR; + SAFEARRAY *pIpArray; + hr = netIfWinCreateIpArrayV4V6(&pIpArray, Ip); + if (SUCCEEDED(hr)) + { + pIpAddresses->parray = pIpArray; + } + return hr; +} +#endif + +static HRESULT netIfWinEnableStatic(IWbemServices *pSvc, const GUID *pGuid, com::Bstr &rObjPath, VARIANT *pIp, VARIANT *pMask) +{ + com::Bstr bstrClassName; + HRESULT hr = bstrClassName.assignEx("Win32_NetworkAdapterConfiguration"); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pClass; + hr = pSvc->GetObject(bstrClassName.raw(), 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + LPWSTR argNames[] = {L"IPAddress", L"SubnetMask"}; + LPVARIANT args[] = { pIp, pMask }; + + ComPtr<IWbemClassObject> pOutParams; + hr = netIfExecMethod(pSvc, pClass, rObjPath.raw(), "EnableStatic", argNames, args, + 2, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + com::Bstr bstrReturnValue; + hr = bstrReturnValue.assignEx("ReturnValue"); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + VariantInit(&varReturnValue); + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + switch (winEr) + { + case 0: + { + hr = S_OK; + //bool bFound; + //HRESULT tmpHr = netIfWinWaitIpSettings(pSvc, pGuid, pIp->parray, pMask->parray, 180, &bFound); + NOREF(pGuid); + break; + } + default: + hr = HRESULT_FROM_WIN32( winEr ); + break; + } + } + } + } + } + } + return hr; +} + + +static HRESULT netIfWinEnableStaticV4(IWbemServices *pSvc, const GUID *pGuid, com::Bstr &rObjPath, + in_addr const *paIps, in_addr const *paMasks, UINT cIpAndMasks) +{ + VARIANT ipAddresses; + HRESULT hr = netIfWinCreateIpArrayVariantV4(&ipAddresses, paIps, cIpAndMasks); + if (SUCCEEDED(hr)) + { + VARIANT ipMasks; + hr = netIfWinCreateIpArrayVariantV4(&ipMasks, paMasks, cIpAndMasks); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStatic(pSvc, pGuid, rObjPath, &ipAddresses, &ipMasks); + VariantClear(&ipMasks); + } + VariantClear(&ipAddresses); + } + return hr; +} + +#if 0 /* unused */ + +static HRESULT netIfWinEnableStaticV4V6(IWbemServices * pSvc, const GUID * pGuid, BSTR ObjPath, BSTR Ip, BSTR Mask) +{ + VARIANT ipAddresses; + HRESULT hr = netIfWinCreateIpArrayVariantV4V6(&ipAddresses, Ip); + if (SUCCEEDED(hr)) + { + VARIANT ipMasks; + hr = netIfWinCreateIpArrayVariantV4V6(&ipMasks, Mask); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStatic(pSvc, pGuid, ObjPath, &ipAddresses, &ipMasks); + VariantClear(&ipMasks); + } + VariantClear(&ipAddresses); + } + return hr; +} + +/* win API allows to set gw metrics as well, we are not setting them */ +static HRESULT netIfWinSetGateways(IWbemServices * pSvc, BSTR ObjPath, VARIANT * pGw) +{ + ComPtr<IWbemClassObject> pClass; + BSTR ClassName = SysAllocString(L"Win32_NetworkAdapterConfiguration"); + HRESULT hr; + if (ClassName) + { + hr = pSvc->GetObject(ClassName, 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + LPWSTR argNames[] = {L"DefaultIPGateway"}; + LPVARIANT args[] = {pGw}; + ComPtr<IWbemClassObject> pOutParams; + + hr = netIfExecMethod(pSvc, pClass, ObjPath, com::Bstr(L"SetGateways"), argNames, args, 1, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + hr = pOutParams->Get(com::Bstr(L"ReturnValue"), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { +// Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + switch (winEr) + { + case 0: + hr = S_OK; + break; + default: + hr = HRESULT_FROM_WIN32( winEr ); + break; + } + } + } + } + SysFreeString(ClassName); + } + else + hr = HRESULT_FROM_WIN32(GetLastError()); + + return hr; +} + +/* win API allows to set gw metrics as well, we are not setting them */ +static HRESULT netIfWinSetGatewaysV4(IWbemServices * pSvc, BSTR ObjPath, in_addr* aGw, UINT cGw) +{ + VARIANT gwais; + HRESULT hr = netIfWinCreateIpArrayVariantV4(&gwais, aGw, cGw); + if (SUCCEEDED(hr)) + { + netIfWinSetGateways(pSvc, ObjPath, &gwais); + VariantClear(&gwais); + } + return hr; +} + +/* win API allows to set gw metrics as well, we are not setting them */ +static HRESULT netIfWinSetGatewaysV4V6(IWbemServices * pSvc, BSTR ObjPath, BSTR Gw) +{ + VARIANT vGw; + HRESULT hr = netIfWinCreateIpArrayVariantV4V6(&vGw, Gw); + if (SUCCEEDED(hr)) + { + netIfWinSetGateways(pSvc, ObjPath, &vGw); + VariantClear(&vGw); + } + return hr; +} + +#endif /* unused */ + +static HRESULT netIfWinEnableDHCP(IWbemServices * pSvc, const com::Bstr &rObjPath) +{ + com::Bstr bstrClassName; + HRESULT hr = bstrClassName.assignEx("Win32_NetworkAdapterConfiguration"); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pClass; + hr = pSvc->GetObject(bstrClassName.raw(), 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pOutParams; + hr = netIfExecMethod(pSvc, pClass, rObjPath, "EnableDHCP", NULL, NULL, 0, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + com::Bstr bstrReturnValue; + hr = bstrReturnValue.assignEx("ReturnValue"); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + VariantInit(&varReturnValue); + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + switch (winEr) + { + case 0: + hr = S_OK; + break; + default: + hr = HRESULT_FROM_WIN32( winEr ); + break; + } + } + } + } + } + } + return hr; +} + +static HRESULT netIfWinDhcpRediscover(IWbemServices * pSvc, const com::Bstr &rObjPath) +{ + com::Bstr bstrClassName; + HRESULT hr = bstrClassName.assignEx("Win32_NetworkAdapterConfiguration"); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pClass; + hr = pSvc->GetObject(bstrClassName.raw(), 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pOutParams; + hr = netIfExecMethod(pSvc, pClass, rObjPath, "ReleaseDHCPLease", NULL, NULL, 0, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + com::Bstr bstrReturnValue; + hr = bstrReturnValue.assignEx("ReturnValue"); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + VariantInit(&varReturnValue); + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + if (winEr == 0) + { + hr = netIfExecMethod(pSvc, pClass, rObjPath, "RenewDHCPLease", NULL, NULL, 0, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + winEr = varReturnValue.uintVal; + if (winEr == 0) + hr = S_OK; + else + hr = HRESULT_FROM_WIN32( winEr ); + } + } + } + else + hr = HRESULT_FROM_WIN32( winEr ); + } + } + } + } + } + + return hr; +} + +static HRESULT vboxNetCfgWinIsDhcpEnabled(IWbemClassObject *pAdapterConfig, BOOL *pfEnabled) +{ + VARIANT vtEnabled; + VariantInit(&vtEnabled); + HRESULT hr = pAdapterConfig->Get(L"DHCPEnabled", 0, &vtEnabled, 0, 0); + if (SUCCEEDED(hr)) + *pfEnabled = vtEnabled.boolVal; + else + *pfEnabled = FALSE; + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGetAdapterSettings(IN const GUID *pGuid, OUT PADAPTER_SETTINGS pSettings) +{ + ComPtr<IWbemServices> pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + hr = vboxNetCfgWinIsDhcpEnabled(pAdapterConfig, &pSettings->bDhcp); + if (SUCCEEDED(hr)) + hr = netIfWinGetIpSettings(pAdapterConfig, &pSettings->ip, &pSettings->mask); + } + } + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinIsDhcpEnabled(const GUID * pGuid, BOOL *pEnabled) +{ + ComPtr<IWbemServices> pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + VARIANT vtEnabled; + hr = pAdapterConfig->Get(L"DHCPEnabled", 0, &vtEnabled, 0, 0); + if (SUCCEEDED(hr)) + *pEnabled = vtEnabled.boolVal; + } + } + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinEnableStaticIpConfig(IN const GUID *pGuid, IN ULONG ip, IN ULONG mask) +{ + NonStandardLogFlow(("VBoxNetCfgWinEnableStaticIpConfig: ip=0x%x mask=0x%x\n", ip, mask)); + ComPtr<IWbemServices> pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BOOL fIsHostOnly; + hr = netIfWinIsHostOnly(pAdapterConfig, &fIsHostOnly); + if (SUCCEEDED(hr)) + { + if (fIsHostOnly) + { + in_addr aIp[1]; + in_addr aMask[1]; + aIp[0].S_un.S_addr = ip; + aMask[0].S_un.S_addr = mask; + + com::Bstr bstrObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &bstrObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStaticV4(pSvc, pGuid, bstrObjPath, aIp, aMask, ip != 0 ? 1 : 0); + if (SUCCEEDED(hr)) + { +#if 0 + in_addr aGw[1]; + aGw[0].S_un.S_addr = gw; + hr = netIfWinSetGatewaysV4(pSvc, bstrObjPath, aGw, 1); + if (SUCCEEDED(hr)) +#endif + { + } + } + } + } + else + { + hr = E_FAIL; + } + } + } + } + + NonStandardLogFlow(("VBoxNetCfgWinEnableStaticIpConfig: returns %Rhrc\n", hr)); + return hr; +} + +#if 0 +static HRESULT netIfEnableStaticIpConfigV6(const GUID *pGuid, IN_BSTR aIPV6Address, IN_BSTR aIPV6Mask, IN_BSTR aIPV6DefaultGateway) +{ + HRESULT hr; + ComPtr<IWbemServices> pSvc; + hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BSTR ObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &ObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStaticV4V6(pSvc, pAdapterConfig, ObjPath, aIPV6Address, aIPV6Mask); + if (SUCCEEDED(hr)) + { + if (aIPV6DefaultGateway) + { + hr = netIfWinSetGatewaysV4V6(pSvc, ObjPath, aIPV6DefaultGateway); + } + if (SUCCEEDED(hr)) + { +// hr = netIfWinUpdateConfig(pIf); + } + } + SysFreeString(ObjPath); + } + } + } + + return SUCCEEDED(hr) ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +static HRESULT netIfEnableStaticIpConfigV6(const GUID *pGuid, IN_BSTR aIPV6Address, ULONG aIPV6MaskPrefixLength) +{ + RTNETADDRIPV6 Mask; + int rc = RTNetPrefixToMaskIPv6(aIPV6MaskPrefixLength, &Mask); + if (RT_SUCCESS(rc)) + { + Bstr maskStr = composeIPv6Address(&Mask); + rc = netIfEnableStaticIpConfigV6(pGuid, aIPV6Address, maskStr, NULL); + } + return rc; +} +#endif + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinEnableDynamicIpConfig(IN const GUID *pGuid) +{ + ComPtr<IWbemServices> pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BOOL fIsHostOnly; + hr = netIfWinIsHostOnly(pAdapterConfig, &fIsHostOnly); + if (SUCCEEDED(hr)) + { + if (fIsHostOnly) + { + com::Bstr bstrObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &bstrObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableDHCP(pSvc, bstrObjPath); + if (SUCCEEDED(hr)) + { + //hr = netIfWinUpdateConfig(pIf); + } + } + } + else + hr = E_FAIL; + } + } + } + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinDhcpRediscover(IN const GUID *pGuid) +{ + ComPtr<IWbemServices> pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr<IWbemClassObject> pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BOOL fIsHostOnly; + hr = netIfWinIsHostOnly(pAdapterConfig, &fIsHostOnly); + if (SUCCEEDED(hr)) + { + if (fIsHostOnly) + { + com::Bstr bstrObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &bstrObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinDhcpRediscover(pSvc, bstrObjPath); + if (SUCCEEDED(hr)) + { + //hr = netIfWinUpdateConfig(pIf); + } + } + } + else + hr = E_FAIL; + } + } + } + + + return hr; +} + +static const char *vboxNetCfgWinAddrToStr(char *pszBuf, size_t cbBuf, LPSOCKADDR pAddr) +{ + switch (pAddr->sa_family) + { + case AF_INET: + RTStrPrintf(pszBuf, cbBuf, "%d.%d.%d.%d", + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b1, + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b2, + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b3, + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b4); + break; + case AF_INET6: + RTStrPrintf(pszBuf, cbBuf, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[0], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[1], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[2], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[3], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[4], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[5], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[6], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[7], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[8], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[9], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[10], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[11], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[12], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[13], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[14], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[15]); + break; + default: + RTStrCopy(pszBuf, cbBuf, "unknown"); + break; + } + return pszBuf; +} + +typedef bool (*PFNVBOXNETCFG_IPSETTINGS_CALLBACK) (ULONG ip, ULONG mask, PVOID pContext); + +static void vboxNetCfgWinEnumIpConfig(PIP_ADAPTER_ADDRESSES pAddresses, PFNVBOXNETCFG_IPSETTINGS_CALLBACK pfnCallback, PVOID pContext) +{ + PIP_ADAPTER_ADDRESSES pAdapter; + for (pAdapter = pAddresses; pAdapter; pAdapter = pAdapter->Next) + { + NonStandardLogFlow(("+- Enumerating adapter '%ls' %s\n", pAdapter->FriendlyName, pAdapter->AdapterName)); + for (PIP_ADAPTER_PREFIX pPrefix = pAdapter->FirstPrefix; pPrefix; pPrefix = pPrefix->Next) + { + char szBuf[80]; + const char *pcszAddress = vboxNetCfgWinAddrToStr(szBuf, sizeof(szBuf), pPrefix->Address.lpSockaddr); + + /* We are concerned with IPv4 only, ignore the rest. */ + if (pPrefix->Address.lpSockaddr->sa_family != AF_INET) + { + NonStandardLogFlow(("| +- %s %d: not IPv4, ignoring\n", pcszAddress, pPrefix->PrefixLength)); + continue; + } + + /* Ignore invalid prefixes as well as host addresses. */ + if (pPrefix->PrefixLength < 1 || pPrefix->PrefixLength > 31) + { + NonStandardLogFlow(("| +- %s %d: host or broadcast, ignoring\n", pcszAddress, pPrefix->PrefixLength)); + continue; + } + + /* Ignore multicast and beyond. */ + ULONG ip = ((struct sockaddr_in *)pPrefix->Address.lpSockaddr)->sin_addr.s_addr; + if ((ip & 0xF0) > 224) + { + NonStandardLogFlow(("| +- %s %d: multicast, ignoring\n", pcszAddress, pPrefix->PrefixLength)); + continue; + } + + ULONG mask = htonl((~(((ULONG)~0) >> pPrefix->PrefixLength))); + bool fContinue = pfnCallback(ip, mask, pContext); + if (!fContinue) + { + NonStandardLogFlow(("| +- %s %d: CONFLICT!\n", pcszAddress, pPrefix->PrefixLength)); + return; + } + + NonStandardLogFlow(("| +- %s %d: no conflict, moving on\n", pcszAddress, pPrefix->PrefixLength)); + } + } +} + +typedef struct _IPPROBE_CONTEXT +{ + ULONG Prefix; + bool fConflict; +}IPPROBE_CONTEXT, *PIPPROBE_CONTEXT; + +#define IPPROBE_INIT(a_pContext, a_addr) \ + do { (a_pContext)->fConflict = false; (a_pContext)->Prefix = (a_addr); } while (0) + +#define IPPROBE_INIT_STR(a_pContext, a_straddr) \ + IPROBE_INIT(a_pContext, inet_addr(_straddr)) + +static bool vboxNetCfgWinIpProbeCallback (ULONG ip, ULONG mask, PVOID pContext) +{ + PIPPROBE_CONTEXT pProbe = (PIPPROBE_CONTEXT)pContext; + + if ((ip & mask) == (pProbe->Prefix & mask)) + { + pProbe->fConflict = true; + return false; + } + + return true; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGenHostOnlyNetworkNetworkIp(OUT PULONG pNetIp, OUT PULONG pNetMask) +{ + HRESULT hr = S_OK; + + *pNetIp = 0; + *pNetMask = 0; + + /* + * MSDN recommends to pre-allocate a 15KB buffer. + */ + ULONG cbBuf = 15 * _1K; + PIP_ADAPTER_ADDRESSES paAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAllocZ(cbBuf); + if (!paAddresses) + return E_OUTOFMEMORY; + DWORD dwRc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, paAddresses, &cbBuf); + if (dwRc == ERROR_BUFFER_OVERFLOW) + { + /* Impressive! More than 10 adapters! Get more memory and try again. */ + RTMemFree(paAddresses); + paAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAllocZ(cbBuf); + if (!paAddresses) + return E_OUTOFMEMORY; + dwRc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, paAddresses, &cbBuf); + } + if (dwRc == NO_ERROR) + { + const ULONG ip192168 = inet_addr("192.168.0.0"); + for (int i = 0; i < 384; i++) + { +#if 0 + ULONG ipProbe = rand()*255 / RAND_MAX; +#else + uint32_t ipProbe = RTRandU32Ex(0, 255); +#endif + ipProbe = ip192168 | (ipProbe << 16); + NonStandardLogFlow(("probing %RTnaipv4\n", ipProbe)); + + IPPROBE_CONTEXT Context; + IPPROBE_INIT(&Context, ipProbe); + vboxNetCfgWinEnumIpConfig(paAddresses, vboxNetCfgWinIpProbeCallback, &Context); + if (!Context.fConflict) + { + NonStandardLogFlow(("found unused net %RTnaipv4\n", ipProbe)); + *pNetIp = ipProbe; + *pNetMask = inet_addr("255.255.255.0"); + break; + } + } + if (*pNetIp == 0) + dwRc = ERROR_DHCP_ADDRESS_CONFLICT; + } + else + NonStandardLogFlow(("GetAdaptersAddresses err (%u)\n", dwRc)); + + RTMemFree(paAddresses); + + if (dwRc != NO_ERROR) + hr = HRESULT_FROM_WIN32(dwRc); + return hr; +} + +/* + * convenience functions to perform netflt/adp manipulations + */ +#define VBOXNETCFGWIN_NETFLT_ID L"sun_VBoxNetFlt" +#define VBOXNETCFGWIN_NETFLT_MP_ID L"sun_VBoxNetFltmp" + +static HRESULT vboxNetCfgWinNetFltUninstall(IN INetCfg *pNc, DWORD InfRmFlags) +{ + INetCfgComponent *pNcc = NULL; + HRESULT hr = pNc->FindComponent(VBOXNETCFGWIN_NETFLT_ID, &pNcc); + if (hr == S_OK) + { + NonStandardLog("NetFlt is installed currently, uninstalling ...\n"); + + hr = VBoxNetCfgWinUninstallComponent(pNc, pNcc); + NonStandardLogFlow(("NetFlt component uninstallation ended with hr (%Rhrc)\n", hr)); + + pNcc->Release(); + } + else if (hr == S_FALSE) + NonStandardLog("NetFlt is not installed currently\n"); + else + NonStandardLogFlow(("FindComponent failed: %Rhrc\n", hr)); + + VBoxDrvCfgInfUninstallAllF(L"NetService", VBOXNETCFGWIN_NETFLT_ID, InfRmFlags); + VBoxDrvCfgInfUninstallAllF(L"Net", VBOXNETCFGWIN_NETFLT_MP_ID, InfRmFlags); + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetFltUninstall(IN INetCfg *pNc) +{ + return vboxNetCfgWinNetFltUninstall(pNc, 0); +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetFltInstall(IN INetCfg *pNc, IN LPCWSTR const *pwszInfFullPath, IN UINT cInfFullPaths) +{ + HRESULT hr = vboxNetCfgWinNetFltUninstall(pNc, SUOI_FORCEDELETE); + if (SUCCEEDED(hr)) + { + NonStandardLog("NetFlt will be installed ...\n"); + hr = vboxNetCfgWinInstallInfAndComponent(pNc, VBOXNETCFGWIN_NETFLT_ID, + &GUID_DEVCLASS_NETSERVICE, + pwszInfFullPath, + cInfFullPaths, + NULL); + } + return hr; +} + +static HRESULT vboxNetCfgWinNetAdpUninstall(IN INetCfg *pNc, LPCWSTR pwszId, DWORD InfRmFlags) +{ + NOREF(pNc); + NonStandardLog("Finding NetAdp driver package and trying to uninstall it ...\n"); + + VBoxDrvCfgInfUninstallAllF(L"Net", pwszId, InfRmFlags); + NonStandardLog("NetAdp is not installed currently\n"); + return S_OK; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetAdpUninstall(IN INetCfg *pNc, IN LPCWSTR pwszId) +{ + return vboxNetCfgWinNetAdpUninstall(pNc, pwszId, SUOI_FORCEDELETE); +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetAdpInstall(IN INetCfg *pNc, + IN LPCWSTR const pwszInfFullPath) +{ + NonStandardLog("NetAdp will be installed ...\n"); + HRESULT hr = vboxNetCfgWinInstallInfAndComponent(pNc, VBOXNETCFGWIN_NETADP_ID_WSZ, + &GUID_DEVCLASS_NET, + &pwszInfFullPath, + 1, + NULL); + return hr; +} + + +static HRESULT vboxNetCfgWinNetLwfUninstall(IN INetCfg *pNc, DWORD InfRmFlags) +{ + INetCfgComponent * pNcc = NULL; + HRESULT hr = pNc->FindComponent(VBOXNETCFGWIN_NETLWF_ID, &pNcc); + if (hr == S_OK) + { + NonStandardLog("NetLwf is installed currently, uninstalling ...\n"); + + hr = VBoxNetCfgWinUninstallComponent(pNc, pNcc); + + pNcc->Release(); + } + else if (hr == S_FALSE) + { + NonStandardLog("NetLwf is not installed currently\n"); + hr = S_OK; + } + else + { + NonStandardLogFlow(("FindComponent failed: %Rhrc\n", hr)); + hr = S_OK; + } + + VBoxDrvCfgInfUninstallAllF(L"NetService", VBOXNETCFGWIN_NETLWF_ID, InfRmFlags); + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetLwfUninstall(IN INetCfg *pNc) +{ + return vboxNetCfgWinNetLwfUninstall(pNc, 0); +} + +static void VBoxNetCfgWinFilterLimitWorkaround(void) +{ + /* + * Need to check if the system has a limit of installed filter drivers. If it + * has, bump the limit to 14, which the maximum value supported by Windows 7. + * Note that we only touch the limit if it is set to the default value (8). + * See @bugref{7899}. + */ + /** @todo r=bird: This code was mixing HRESULT and LSTATUS, checking the return + * codes using SUCCEEDED(lrc) instead of lrc == ERROR_SUCCESS. So, it might + * have misbehaved on bogus registry content, but worked fine on sane values. */ + HKEY hKeyNet = NULL; + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Network", 0, + KEY_QUERY_VALUE | KEY_SET_VALUE, &hKeyNet); + if (lrc == ERROR_SUCCESS) + { + DWORD dwMaxNumFilters = 0; + DWORD cbMaxNumFilters = sizeof(dwMaxNumFilters); + lrc = RegQueryValueExW(hKeyNet, L"MaxNumFilters", NULL, NULL, (LPBYTE)&dwMaxNumFilters, &cbMaxNumFilters); + if (lrc == ERROR_SUCCESS && cbMaxNumFilters == sizeof(dwMaxNumFilters) && dwMaxNumFilters == 8) + { + dwMaxNumFilters = 14; + lrc = RegSetValueExW(hKeyNet, L"MaxNumFilters", 0, REG_DWORD, (LPBYTE)&dwMaxNumFilters, sizeof(dwMaxNumFilters)); + if (lrc == ERROR_SUCCESS) + NonStandardLog("Adjusted the installed filter limit to 14...\n"); + else + NonStandardLog("Failed to set MaxNumFilters, error code %d\n", lrc); + } + RegCloseKey(hKeyNet); + } + else + NonStandardLog("Failed to open network key, error code %d\n", lrc); + +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetLwfInstall(IN INetCfg *pNc, IN LPCWSTR const pwszInfFullPath) +{ + HRESULT hr = vboxNetCfgWinNetLwfUninstall(pNc, SUOI_FORCEDELETE); + if (SUCCEEDED(hr)) + { + VBoxNetCfgWinFilterLimitWorkaround(); + NonStandardLog("NetLwf will be installed ...\n"); + hr = vboxNetCfgWinInstallInfAndComponent(pNc, VBOXNETCFGWIN_NETLWF_ID, + &GUID_DEVCLASS_NETSERVICE, + &pwszInfFullPath, + 1, + NULL); + } + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGenHostonlyConnectionName(IN PCWSTR pwszDevName, OUT WCHAR *pwszBuf, + IN ULONG cwcBuf, OUT PULONG pcwcNeeded) +{ + /* Look for a suffix that we need to preserve. */ + size_t const cwcDevName = RTUtf16Len(pwszDevName); + size_t offSuffix = cwcDevName; + while (offSuffix > 0 && pwszDevName[offSuffix - 1] != '#') + offSuffix--; + size_t const cwcSuffix = pwszDevName[offSuffix] != '#' ? 0 : cwcDevName - offSuffix; + + /* Calculate required buffer size: */ + size_t cwcNeeded = sizeof(VBOX_CONNECTION_NAME_WSZ) / sizeof(wchar_t) /* includes terminator */ + + !!cwcSuffix /*space*/ + cwcSuffix; + if (pcwcNeeded) + *pcwcNeeded = (ULONG)cwcNeeded; + + if (cwcNeeded <= cwcBuf) + { + memcpy(pwszBuf, VBOX_CONNECTION_NAME_WSZ, sizeof(VBOX_CONNECTION_NAME_WSZ)); + if (cwcSuffix > 0) + { + size_t offDst = sizeof(VBOX_CONNECTION_NAME_WSZ) / sizeof(wchar_t) - 1; + pwszBuf[offDst++] = ' '; + memcpy(&pwszBuf[offDst], &pwszDevName[offSuffix], cwcSuffix * sizeof(wchar_t)); + pwszBuf[offDst + cwcSuffix] = '\0'; + } + return S_OK; + } + return E_FAIL; +} + +static BOOL vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority(IN INetCfg *pNc, IN INetCfgComponent *pNcc, PVOID pContext) +{ + GUID * const pGuid = (GUID*)pContext; + RT_NOREF1(pNc); + + /* Get component's binding. */ + INetCfgComponentBindings *pNetCfgBindings = NULL; + HRESULT hr = pNcc->QueryInterface(IID_INetCfgComponentBindings, (PVOID *)&pNetCfgBindings); + if (SUCCEEDED(hr)) + { + /* Get binding path enumerator reference. */ + IEnumNetCfgBindingPath *pEnumNetCfgBindPath = NULL; + hr = pNetCfgBindings->EnumBindingPaths(EBP_BELOW, &pEnumNetCfgBindPath); + if (SUCCEEDED(hr)) + { + bool fFoundIface = false; + hr = pEnumNetCfgBindPath->Reset(); + do + { + INetCfgBindingPath *pNetCfgBindPath = NULL; + hr = pEnumNetCfgBindPath->Next(1, &pNetCfgBindPath, NULL); + if (hr == S_OK) + { + IEnumNetCfgBindingInterface *pEnumNetCfgBindIface; + hr = pNetCfgBindPath->EnumBindingInterfaces(&pEnumNetCfgBindIface); + if (hr == S_OK) + { + pEnumNetCfgBindIface->Reset(); + do + { + INetCfgBindingInterface *pNetCfgBindIfce; + hr = pEnumNetCfgBindIface->Next(1, &pNetCfgBindIfce, NULL); + if (hr == S_OK) + { + INetCfgComponent *pNetCfgCompo; + hr = pNetCfgBindIfce->GetLowerComponent(&pNetCfgCompo); + if (hr == S_OK) + { + ULONG uComponentStatus; + hr = pNetCfgCompo->GetDeviceStatus(&uComponentStatus); + if (hr == S_OK) + { + GUID guid; + hr = pNetCfgCompo->GetInstanceGuid(&guid); + if ( hr == S_OK + && guid == *pGuid) + { + hr = pNetCfgBindings->MoveAfter(pNetCfgBindPath, NULL); + if (FAILED(hr)) + NonStandardLogFlow(("Unable to move interface: %Rhrc\n", hr)); + fFoundIface = true; + + /* + * Enable binding paths for host-only adapters bound to bridged filter + * (see @bugref{8140}). + */ + HRESULT hr2; + LPWSTR pwszHwId = NULL; + if ((hr2 = pNcc->GetId(&pwszHwId)) != S_OK) + NonStandardLogFlow(("Failed to get HW ID: %Rhrc\n", hr2)); + else + { + /** @todo r=bird: Original was: + * _wcsnicmp(pwszHwId, VBOXNETCFGWIN_NETLWF_ID, sizeof(VBOXNETCFGWIN_NETLWF_ID)/2) + * which is the same as _wcsicmp. Not sure if this was accidental, but it's not the + * only one in this code area (VBoxNetFltNobj.cpp had some too IIRC). */ + if (RTUtf16ICmp(pwszHwId, VBOXNETCFGWIN_NETLWF_ID) != 0) + NonStandardLogFlow(("Ignoring component %ls\n", pwszHwId)); + else if ((hr2 = pNetCfgBindPath->IsEnabled()) != S_FALSE) + NonStandardLogFlow(("Already enabled binding path: %Rhrc\n", hr2)); + else if ((hr2 = pNetCfgBindPath->Enable(TRUE)) != S_OK) + NonStandardLogFlow(("Failed to enable binding path: %Rhrc\n", hr2)); + else + NonStandardLogFlow(("Enabled binding path\n")); + CoTaskMemFree(pwszHwId); + } + } + } + pNetCfgCompo->Release(); + } + else + NonStandardLogFlow(("GetLowerComponent failed: %Rhrc\n", hr)); + pNetCfgBindIfce->Release(); + } + else + { + if (hr == S_FALSE) /* No more binding interfaces? */ + hr = S_OK; + else + NonStandardLogFlow(("Next binding interface failed: %Rhrc\n", hr)); + break; + } + } while (!fFoundIface); + pEnumNetCfgBindIface->Release(); + } + else + NonStandardLogFlow(("EnumBindingInterfaces failed: %Rhrc\n", hr)); + pNetCfgBindPath->Release(); + } + else + { + if (hr == S_FALSE) /* No more binding paths? */ + hr = S_OK; + else + NonStandardLogFlow(("Next bind path failed: %Rhrc\n", hr)); + break; + } + } while (!fFoundIface); + pEnumNetCfgBindPath->Release(); + } + else + NonStandardLogFlow(("EnumBindingPaths failed: %Rhrc\n", hr)); + pNetCfgBindings->Release(); + } + else + NonStandardLogFlow(("QueryInterface for IID_INetCfgComponentBindings failed: %Rhrc\n", hr)); + return TRUE; +} + +/** Callback for SetupDiSetDeviceInstallParams used by + * vboxNetCfgWinCreateHostOnlyNetworkInterface */ +static UINT WINAPI vboxNetCfgWinPspFileCallback(PVOID Context, UINT Notification, UINT_PTR Param1, UINT_PTR Param2) +{ + switch (Notification) + { + case SPFILENOTIFY_TARGETNEWER: + case SPFILENOTIFY_TARGETEXISTS: + return TRUE; + } + return SetupDefaultQueueCallbackW(Context, Notification, Param1, Param2); +} + + + + +/* The original source of the VBoxNetAdp adapter creation/destruction code has the following copyright: */ +/* + Copyright 2004 by the Massachusetts Institute of Technology + + All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in + supporting documentation, and that the name of the Massachusetts + Institute of Technology (M.I.T.) not be used in advertising or publicity + pertaining to distribution of the software without specific, written + prior permission. + + M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + SOFTWARE. +*/ + + +/** + * Use the IShellFolder API to rename the connection. + */ +static HRESULT rename_shellfolder(PCWSTR pwszGuid, PCWSTR pwszNewName) +{ + /* Build the display name in the form "::{GUID}". Do this first in case it overflows. */ + WCHAR wszAdapterGuid[MAX_PATH + 2] = {0}; + ssize_t cwc = RTUtf16Printf(wszAdapterGuid, RT_ELEMENTS(wszAdapterGuid), "::%ls", pwszGuid); + if (cwc < 0) + return E_INVALIDARG; + + /* This is the GUID for the network connections folder. It is constant. + * {7007ACC7-3202-11D1-AAD2-00805FC1270E} */ + const GUID MY_CLSID_NetworkConnections = { + 0x7007ACC7, 0x3202, 0x11D1, { + 0xAA, 0xD2, 0x00, 0x80, 0x5F, 0xC1, 0x27, 0x0E + } + }; + + /* Create an instance of the network connections folder. */ + IShellFolder *pShellFolder = NULL; + HRESULT hr = CoCreateInstance(MY_CLSID_NetworkConnections, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder, + reinterpret_cast<LPVOID *>(&pShellFolder)); + if (SUCCEEDED(hr)) + { + /* Parse the display name. */ + LPITEMIDLIST pidl = NULL; + hr = pShellFolder->ParseDisplayName(NULL, NULL, wszAdapterGuid, NULL, &pidl, NULL); + if (SUCCEEDED(hr)) + hr = pShellFolder->SetNameOf(NULL, pidl, pwszNewName, SHGDN_NORMAL, &pidl); + CoTaskMemFree(pidl); + pShellFolder->Release(); + } + return hr; +} + +/** + * Loads a system DLL. + * + * @returns Module handle or NULL + * @param pwszName The DLL name. + */ +static HMODULE loadSystemDll(const wchar_t *pwszName) +{ + WCHAR wszPath[MAX_PATH]; + UINT cwcPath = GetSystemDirectoryW(wszPath, RT_ELEMENTS(wszPath)); + size_t cwcName = RTUtf16Len(pwszName) + 1; + if (cwcPath + 1 + cwcName > RT_ELEMENTS(wszPath)) + return NULL; + + wszPath[cwcPath++] = '\\'; + memcpy(&wszPath[cwcPath], pwszName, cwcName * sizeof(wszPath[0])); + return LoadLibraryW(wszPath); +} + +static bool vboxNetCfgWinDetectStaleConnection(PCWSTR pwszName) +{ + HKEY hKeyAdapters = NULL; + LSTATUS lrc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}", + 0 /*ulOptions*/, KEY_ALL_ACCESS, &hKeyAdapters); + if (lrc != ERROR_SUCCESS) + return false; + + bool fFailureImminent = false; + for (DWORD i = 0; !fFailureImminent; ++i) + { + WCHAR wszAdapterSubKeyName[MAX_PATH]; + DWORD cwcAdapterSubKeyName = MAX_PATH; + lrc = RegEnumKeyEx(hKeyAdapters, i, wszAdapterSubKeyName, &cwcAdapterSubKeyName, NULL, NULL, NULL, NULL); + if (lrc != ERROR_SUCCESS) + break; + + HKEY hKeyAdapter = NULL; + lrc = RegOpenKeyEx(hKeyAdapters, wszAdapterSubKeyName, 0, KEY_ALL_ACCESS, &hKeyAdapter); + if (lrc == ERROR_SUCCESS) + { + HKEY hKeyConnection = NULL; + lrc = RegOpenKeyEx(hKeyAdapter, L"Connection", 0, KEY_ALL_ACCESS, &hKeyConnection); + if (lrc == ERROR_SUCCESS) + { + WCHAR wszCurName[MAX_PATH + 1]; + DWORD cbCurName = sizeof(wszCurName) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + lrc = RegQueryValueEx(hKeyConnection, L"Name", NULL, NULL, (LPBYTE)wszCurName, &cbCurName); + if ( lrc == ERROR_SUCCESS + /** @todo r=bird: The original code didn't do any value type checks, thus allowing all SZ types. */ + && (dwType == REG_SZ || dwType == REG_EXPAND_SZ || dwType == REG_MULTI_SZ)) + { + wszCurName[MAX_PATH] = '\0'; /* returned values doesn't necessarily need to be terminated */ + + if (RTUtf16ICmp(pwszName, pwszName) == 0) + fFailureImminent = true; + } + RegCloseKey(hKeyConnection); + } + RegCloseKey(hKeyAdapter); + } + } + RegCloseKey(hKeyAdapters); + + return fFailureImminent; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRenameConnection(LPWSTR pwszGuid, PCWSTR NewName) +{ + /* + * Before attempting to rename the connection, check if there is a stale + * connection with the same name. We must return ok, so the rest of + * configuration process proceeds normally. + */ + if (vboxNetCfgWinDetectStaleConnection(NewName)) + return S_OK; + + /* First try the IShellFolder interface, which was unimplemented + * for the network connections folder before XP. */ + HRESULT hrc = rename_shellfolder(pwszGuid, NewName); + if (hrc == E_NOTIMPL) + { +/** @todo that code doesn't seem to work! */ + /* The IShellFolder interface is not implemented on this platform. + * Try the (undocumented) HrRenameConnection API in the netshell + * library. */ + CLSID clsid; + hrc = CLSIDFromString((LPOLESTR)pwszGuid, &clsid); + if (FAILED(hrc)) + return E_FAIL; + + HINSTANCE hNetShell = loadSystemDll(L"netshell.dll"); + if (hNetShell == NULL) + return E_FAIL; + + typedef HRESULT (WINAPI *PFNHRRENAMECONNECTION)(const GUID *, PCWSTR); + PFNHRRENAMECONNECTION pfnRenameConnection = (PFNHRRENAMECONNECTION)GetProcAddress(hNetShell, "HrRenameConnection"); + if (pfnRenameConnection != NULL) + hrc = pfnRenameConnection(&clsid, NewName); + else + hrc = E_FAIL; + + FreeLibrary(hNetShell); + } + if (FAILED(hrc)) + return hrc; + return S_OK; +} + + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRemoveHostOnlyNetworkInterface(IN const GUID *pGUID, OUT BSTR *pBstrErrMsg) +{ + HRESULT hrc = S_OK; + com::Bstr bstrError; + + do /* break non-loop */ + { + WCHAR wszPnPInstanceId[512] = {0}; + + /* + * We have to find the device instance ID through a registry search + */ + HKEY hkeyNetwork = NULL; + HKEY hkeyConnection = NULL; + do /* another non-loop for breaking out of */ + { + WCHAR wszGuid[50]; + int cwcGuid = StringFromGUID2(*pGUID, wszGuid, RT_ELEMENTS(wszGuid)); + if (!cwcGuid) + SetErrBreak(("Failed to create a Guid string")); + + WCHAR wszRegLocation[128 + RT_ELEMENTS(wszGuid)]; + RTUtf16Printf(wszRegLocation, RT_ELEMENTS(wszRegLocation), + "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\%ls", wszGuid); + + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszRegLocation, 0, KEY_READ, &hkeyNetwork); + if (lrc != ERROR_SUCCESS || !hkeyNetwork) + SetErrBreak(("Host interface network is not found in registry (%S): lrc=%u [1]", wszRegLocation, lrc)); + + lrc = RegOpenKeyExW(hkeyNetwork, L"Connection", 0, KEY_READ, &hkeyConnection); + if (lrc != ERROR_SUCCESS || !hkeyConnection) + SetErrBreak(("Host interface network is not found in registry (%S): lrc=%u [2]", wszRegLocation, lrc)); + + DWORD cbValue = sizeof(wszPnPInstanceId) - sizeof(WCHAR); + DWORD dwType = ~0U; + lrc = RegQueryValueExW(hkeyConnection, L"PnPInstanceID", NULL, &dwType, (LPBYTE)wszPnPInstanceId, &cbValue); + if (lrc != ERROR_SUCCESS || dwType != REG_SZ) + SetErrBreak(("Host interface network is not found in registry (%S): lrc=%u, dwType=%u [3]", + wszRegLocation, lrc, dwType)); + } while (0); + + if (hkeyConnection) + RegCloseKey(hkeyConnection); + if (hkeyNetwork) + RegCloseKey(hkeyNetwork); + if (FAILED(hrc)) + break; + + /* + * Now we are going to enumerate all network devices and + * wait until we encounter the right device instance ID + */ + HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE; + do /* break-only, not-a-loop */ + { + BOOL ok; + + /* initialize the structure size */ + SP_DEVINFO_DATA DeviceInfoData = { sizeof(DeviceInfoData) }; + + /* copy the net class GUID */ + GUID netGuid; + memcpy(&netGuid, &GUID_DEVCLASS_NET, sizeof(GUID_DEVCLASS_NET)); + + /* return a device info set contains all installed devices of the Net class */ + hDeviceInfo = SetupDiGetClassDevs(&netGuid, NULL, NULL, DIGCF_PRESENT); + if (hDeviceInfo == INVALID_HANDLE_VALUE) + SetErrBreak(("SetupDiGetClassDevs failed (0x%08X)", GetLastError())); + + /* Enumerate the driver info list. */ + bool fFound = false; + for (DWORD index = 0; !fFound; index++) + { + if (!SetupDiEnumDeviceInfo(hDeviceInfo, index, &DeviceInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + /* try to get the hardware ID registry property */ + DWORD cbValue = 0; + if (SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, + &DeviceInfoData, + SPDRP_HARDWAREID, + NULL, + NULL, + 0, + &cbValue)) + continue; /* Something is wrong. This shouldn't have worked with a NULL buffer! */ + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + continue; + + WCHAR *pwszzDeviceHwId = (WCHAR *)RTMemAllocZ(cbValue + sizeof(WCHAR) * 2); + if (!pwszzDeviceHwId) + break; + if (SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, + &DeviceInfoData, + SPDRP_HARDWAREID, + NULL, + (PBYTE)pwszzDeviceHwId, + cbValue, + &cbValue)) + { + /* search the string list. */ + for (WCHAR *pwszCurHwId = pwszzDeviceHwId; + (uintptr_t)pwszCurHwId - (uintptr_t)pwszzDeviceHwId < cbValue && *pwszCurHwId != L'\0'; + pwszCurHwId += RTUtf16Len(pwszCurHwId) + 1) + if (RTUtf16ICmp(DRIVERHWID, pwszCurHwId) == 0) + { + /* get the device instance ID */ + WCHAR wszDevId[MAX_DEVICE_ID_LEN]; + if (CM_Get_Device_IDW(DeviceInfoData.DevInst, wszDevId, MAX_DEVICE_ID_LEN, 0) == CR_SUCCESS) + { + /* compare to what we determined before */ + if (RTUtf16Cmp(wszDevId, wszPnPInstanceId) == 0) + { + fFound = true; + break; + } + } + } + } + RTMemFree(pwszzDeviceHwId); + } + + if (!fFound) + SetErrBreak(("Host Interface Network driver not found (0x%08X)", GetLastError())); + + ok = SetupDiSetSelectedDevice(hDeviceInfo, &DeviceInfoData); + if (!ok) + SetErrBreak(("SetupDiSetSelectedDevice failed (0x%08X)", GetLastError())); + + ok = SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfo, &DeviceInfoData); + if (!ok) + SetErrBreak(("SetupDiCallClassInstaller (DIF_REMOVE) failed (0x%08X)", GetLastError())); + } while (0); + + /* clean up the device info set */ + if (hDeviceInfo != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList (hDeviceInfo); + } while (0); + + if (pBstrErrMsg) + { + *pBstrErrMsg = NULL; + if (bstrError.isNotEmpty()) + bstrError.detachToEx(pBstrErrMsg); + } + return hrc; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinUpdateHostOnlyNetworkInterface(LPCWSTR pcsxwInf, BOOL *pfRebootRequired, LPCWSTR pcsxwId) +{ + return VBoxDrvCfgDrvUpdate(pcsxwId, pcsxwInf, pfRebootRequired); +} + +static const char *vboxNetCfgWinGetStateText(DWORD dwState) +{ + switch (dwState) + { + case SERVICE_STOPPED: return "is not running"; + case SERVICE_STOP_PENDING: return "is stopping"; + case SERVICE_CONTINUE_PENDING: return "continue is pending"; + case SERVICE_PAUSE_PENDING: return "pause is pending"; + case SERVICE_PAUSED: return "is paused"; + case SERVICE_RUNNING: return "is running"; + case SERVICE_START_PENDING: return "is starting"; + } + return "state is invalid"; +} + +static DWORD vboxNetCfgWinGetNetSetupState(SC_HANDLE hService) +{ + SERVICE_STATUS status; + status.dwCurrentState = SERVICE_RUNNING; + if (hService) { + if (QueryServiceStatus(hService, &status)) + NonStandardLogFlow(("NetSetupSvc %s\n", vboxNetCfgWinGetStateText(status.dwCurrentState))); + else + NonStandardLogFlow(("QueryServiceStatus failed (0x%x)\n", GetLastError())); + } + return status.dwCurrentState; +} + +DECLINLINE(bool) vboxNetCfgWinIsNetSetupRunning(SC_HANDLE hService) +{ + return vboxNetCfgWinGetNetSetupState(hService) == SERVICE_RUNNING; +} + +DECLINLINE(bool) vboxNetCfgWinIsNetSetupStopped(SC_HANDLE hService) +{ + return vboxNetCfgWinGetNetSetupState(hService) == SERVICE_STOPPED; +} + +typedef struct +{ + BSTR bstrName; + GUID *pGuid; + HRESULT hr; +} RENAMING_CONTEXT; + +static BOOL vboxNetCfgWinRenameHostOnlyNetworkInterface(IN INetCfg *pNc, IN INetCfgComponent *pNcc, PVOID pContext) +{ + RT_NOREF1(pNc); + RENAMING_CONTEXT *pParams = (RENAMING_CONTEXT *)pContext; + + GUID guid; + pParams->hr = pNcc->GetInstanceGuid(&guid); + if ( pParams->hr == S_OK && guid == *pParams->pGuid) + { + /* Located our component, rename it */ + pParams->hr = pNcc->SetDisplayName(pParams->bstrName); + return FALSE; + } + return TRUE; +} + +/** + * Enumerate all host-only adapters collecting their names into a set, then + * come up with the next available name by taking the first unoccupied index. + */ +static HRESULT vboxNetCfgWinNextAvailableDevName(com::Bstr *pbstrName) +{ + SP_DEVINFO_DATA DeviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + HDEVINFO hDeviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT); + if (hDeviceInfoSet == INVALID_HANDLE_VALUE) + return HRESULT_FROM_WIN32(GetLastError()); + + typedef struct VBOXDEVNAMEENTRY + { + RTLISTNODE ListEntry; + WCHAR wszDevName[64]; + WCHAR wcZeroParanoia; + } VBOXDEVNAMEENTRY; + +#if 0 + /* + * Build a list of names starting with HOSTONLY_ADAPTER_NAME_WSZ belonging to our device. + */ + RTLISTANCHOR Head; /* VBOXDEVNAMEENTRY */ + RTListInit(&Head); + HRESULT hrc = S_OK; +#else + /* + * Build a bitmap of in-use index values of devices starting with HOSTONLY_ADAPTER_NAME_WSZ. + * Reserving 0 for one w/o a suffix, and marking 1 as unusable. + */ + uint64_t bmIndexes[_32K / 64]; /* 4KB - 32767 device should be sufficient. */ + RT_ZERO(bmIndexes); + ASMBitSet(&bmIndexes, 1); +#endif + for (DWORD i = 0; SetupDiEnumDeviceInfo(hDeviceInfoSet, i, &DeviceInfoData); ++i) + { + /* Should be more than enough for both our device id and our device name, we do not care about the rest */ + VBOXDEVNAMEENTRY Entry = { { 0, 0 }, L"", 0 }; /* (initialize it to avoid the wrath of asan) */ + if (!SetupDiGetDeviceRegistryPropertyW(hDeviceInfoSet, &DeviceInfoData, SPDRP_HARDWAREID, + NULL, (PBYTE)Entry.wszDevName, sizeof(Entry.wszDevName), NULL)) + continue; + + /* Ignore everything except our host-only adapters */ + if (RTUtf16ICmp(Entry.wszDevName, DRIVERHWID) == 0) + { + if ( SetupDiGetDeviceRegistryPropertyW(hDeviceInfoSet, &DeviceInfoData, SPDRP_FRIENDLYNAME, + NULL, (PBYTE)Entry.wszDevName, sizeof(Entry.wszDevName), NULL) + || SetupDiGetDeviceRegistryPropertyW(hDeviceInfoSet, &DeviceInfoData, SPDRP_DEVICEDESC, + NULL, (PBYTE)Entry.wszDevName, sizeof(Entry.wszDevName), NULL)) + { + /* We can ignore any host-only adapter with a non-standard name. */ + if (RTUtf16NICmp(Entry.wszDevName, HOSTONLY_ADAPTER_NAME_WSZ, + RT_ELEMENTS(HOSTONLY_ADAPTER_NAME_WSZ) - 1) == 0) + { +#if 0 + VBOXDEVNAMEENTRY *pEntry = (VBOXDEVNAMEENTRY *)RTMemDup(&Entry, sizeof(Entry)); + if (pEntry) + RTListAppend(&Head, &pEntry->ListEntry); + else + { + hrc = E_OUTOFMEMORY; + break; + } +#else + WCHAR const *pwc = &Entry.wszDevName[RT_ELEMENTS(HOSTONLY_ADAPTER_NAME_WSZ) - 1]; + + /* skip leading space */ + WCHAR wc = *pwc; + while (wc == L' ' || wc == L'\t' || wc == L'\n' || wc == L'\r') + wc = *++pwc; + + /* If end of string, use index 0. */ + if (wc == L'\0') + ASMBitSet(bmIndexes, 0); + + /* Hash and digit? */ + else if (wc == L'#') + { + wc = *++pwc; + while (wc == L' ' || wc == L'\t' || wc == L'\n' || wc == L'\r') /* just in case */ + wc = *++pwc; + if (wc >= L'0' && wc <= L'9') + { + /* Convert what we can to a number and mark it as allocated in the bitmap. */ + uint64_t uIndex = wc - L'0'; + while ((wc = *++pwc) >= L'0' && wc <= L'9') + uIndex = uIndex * 10 + wc - L'0'; + if (uIndex < sizeof(bmIndexes) * 8 && uIndex > 0) + ASMBitSet(bmIndexes, (int32_t)uIndex); + } + } +#endif + } + } + } + } +#if 0 + if (SUCCEEDED(hrc)) + { + /* + * First try a name w/o an index, then try index #2 and up. + * + * Note! We have to use ASCII/UTF-8 strings here as Bstr will confuse WCHAR + * with BSTR/OLECHAR strings and use SysAllocString to duplicate it . + */ + char szName[sizeof(HOSTONLY_ADAPTER_NAME_SZ " #4294967296") + 32] = HOSTONLY_ADAPTER_NAME_SZ; + size_t const cchBase = sizeof(HOSTONLY_ADAPTER_NAME_SZ) - 1; + for (DWORD idx = 2;; idx++) + { + bool fFound = false; + VBOXDEVNAMEENTRY *pCur; + RTListForEach(&Head, pCur, VBOXDEVNAMEENTRY, ListEntry) + { + fFound = RTUtf16ICmpAscii(pCur->wszDevName, szName) == 0; + if (fFound) + { + hrc = pbstrName->assignEx(szName); + break; + } + } + if (fFound) + break; + RTStrPrintf(&szName[cchBase], sizeof(szName) - cchBase, " #%u", idx); + } + } + + VBOXDEVNAMEENTRY *pFirst; + while ((pFirst = RTListRemoveFirst(&Head, VBOXDEVNAMEENTRY, ListEntry)) != NULL) + RTMemFree(pFirst); + +#else + /* + * Find an unused index value and format the corresponding name. + */ + HRESULT hrc; + int32_t iBit = ASMBitFirstClear(bmIndexes, sizeof(bmIndexes) * 8); + if (iBit >= 0) + { + if (iBit == 0) + hrc = pbstrName->assignEx(HOSTONLY_ADAPTER_NAME_SZ); /* Not _WSZ! */ + else + hrc = pbstrName->printfNoThrow(HOSTONLY_ADAPTER_NAME_SZ " #%u", iBit); + } + else + { + NonStandardLogFlow(("vboxNetCfgWinNextAvailableDevName: no unused index in the first 32K!\n")); + hrc = E_FAIL; + } +#endif + + if (hDeviceInfoSet) + SetupDiDestroyDeviceInfoList(hDeviceInfoSet); + return hrc; +} + +static HRESULT vboxNetCfgWinCreateHostOnlyNetworkInterface(IN LPCWSTR pwszInfPath, IN bool fIsInfPathFile, + IN BSTR pBstrDesiredName, + OUT GUID *pGuid, OUT BSTR *pBstrName, OUT BSTR *pBstrErrMsg) +{ + com::Bstr bstrError; + + /* Determine the interface name. We make a copy of the input here for + renaming reasons, see futher down. */ + com::Bstr bstrNewInterfaceName; + HRESULT hrc; + if (SysStringLen(pBstrDesiredName) != 0) + hrc = bstrNewInterfaceName.assignEx(pBstrDesiredName); + else + { + hrc = vboxNetCfgWinNextAvailableDevName(&bstrNewInterfaceName); + if (FAILED(hrc)) + NonStandardLogFlow(("vboxNetCfgWinNextAvailableDevName failed with 0x%x\n", hrc)); + } + if (FAILED(hrc)) + return hrc; + + WCHAR wszCfgGuidString[50] = {0}; + WCHAR wszDevName[256 + 1] = {0}; + SP_DEVINFO_DATA DeviceInfoData = { sizeof(DeviceInfoData) }; + HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE; + PVOID pQueueCallbackContext = NULL; + BOOL fRegistered = FALSE; + BOOL destroyList = FALSE; + HKEY hkey = (HKEY)INVALID_HANDLE_VALUE; + LSTATUS lrcRet = ERROR_SUCCESS; /* the */ + + do /* non-loop, for breaking. */ + { + /* copy the net class GUID */ + GUID netGuid; + memcpy(&netGuid, &GUID_DEVCLASS_NET, sizeof(GUID_DEVCLASS_NET)); + + /* Create an empty device info set associated with the net class GUID: */ + hDeviceInfo = SetupDiCreateDeviceInfoList(&netGuid, NULL); + if (hDeviceInfo == INVALID_HANDLE_VALUE) + SetErrBreak(("SetupDiCreateDeviceInfoList failed (%Rwc)", GetLastError())); + + /* Translate the GUID to a class name: */ + WCHAR wszClassName[MAX_PATH]; + if (!SetupDiClassNameFromGuid(&netGuid, wszClassName, MAX_PATH, NULL)) + SetErrBreak(("SetupDiClassNameFromGuid failed (%Rwc)", GetLastError())); + + /* Create a device info element and add the new device instance key to registry: */ + if (!SetupDiCreateDeviceInfo(hDeviceInfo, wszClassName, &netGuid, NULL, NULL, DICD_GENERATE_ID, &DeviceInfoData)) + SetErrBreak(("SetupDiCreateDeviceInfo failed (%Rwc)", GetLastError())); + + /* Select the newly created device info to be the currently selected member: */ + if (!SetupDiSetSelectedDevice(hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiSetSelectedDevice failed (%Rwc)", GetLastError())); + + SP_DEVINSTALL_PARAMS DeviceInstallParams; + if (pwszInfPath) + { + /* get the device install parameters and disable filecopy */ + DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); + if (SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + memset(DeviceInstallParams.DriverPath, 0, sizeof(DeviceInstallParams.DriverPath)); + size_t pathLenght = wcslen(pwszInfPath) + 1/* null terminator */; + if (pathLenght < sizeof(DeviceInstallParams.DriverPath)/sizeof(DeviceInstallParams.DriverPath[0])) + { + memcpy(DeviceInstallParams.DriverPath, pwszInfPath, pathLenght*sizeof(DeviceInstallParams.DriverPath[0])); + + if (fIsInfPathFile) + DeviceInstallParams.Flags |= DI_ENUMSINGLEINF; + + if (!SetupDiSetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + NonStandardLogFlow(("SetupDiSetDeviceInstallParams failed (%Rwc)\n", GetLastError())); + break; + } + } + else + { + NonStandardLogFlow(("SetupDiSetDeviceInstallParams faileed: INF path is too long\n")); + break; + } + } + else + NonStandardLogFlow(("SetupDiGetDeviceInstallParams failed (%Rwc)\n", GetLastError())); + } + + /* build a list of class drivers */ + if (!SetupDiBuildDriverInfoList(hDeviceInfo, &DeviceInfoData, SPDIT_CLASSDRIVER)) + SetErrBreak(("SetupDiBuildDriverInfoList failed (%Rwc)", GetLastError())); + + destroyList = TRUE; + + /* + * Enumerate the driver info list. + */ + /* For our purposes, 2k buffer is more than enough to obtain the + hardware ID of the VBoxNetAdp driver. */ /** @todo r=bird: The buffer isn't 2KB, it's 8KB, but whatever. */ + DWORD detailBuf[2048]; + SP_DRVINFO_DATA DriverInfoData = { sizeof(DriverInfoData) }; + bool fFound = false; + for (DWORD index = 0; !fFound; index++) + { + /* If the function fails with last error set to ERROR_NO_MORE_ITEMS, + then we have reached the end of the list. Otherwise there was + something wrong with this particular driver. */ + if (!SetupDiEnumDriverInfo(hDeviceInfo, &DeviceInfoData, SPDIT_CLASSDRIVER, index, &DriverInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + /* if we successfully find the hardware ID and it turns out to + * be the one for the loopback driver, then we are done. */ + PSP_DRVINFO_DETAIL_DATA_W pDriverInfoDetail = (PSP_DRVINFO_DETAIL_DATA_W)detailBuf; + pDriverInfoDetail->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA); + DWORD cbValue = 0; + if (SetupDiGetDriverInfoDetailW(hDeviceInfo, + &DeviceInfoData, + &DriverInfoData, + pDriverInfoDetail, + sizeof(detailBuf) - sizeof(detailBuf[0]), + &cbValue)) + { + /* Sure that the HardwareID string list is properly zero terminated (paranoia). */ + detailBuf[RT_ELEMENTS(detailBuf) - 1] = 0; AssertCompile(sizeof(detailBuf[0]) == sizeof(WCHAR) * 2); + + /* pDriverInfoDetail->HardwareID is a MULTISZ string. Go through the whole + list and see if there is a match somewhere: */ + for (WCHAR *pwszCurHwId = pDriverInfoDetail->HardwareID; + (uintptr_t)pwszCurHwId - (uintptr_t)pDriverInfoDetail < cbValue && *pwszCurHwId != L'\0'; + pwszCurHwId += RTUtf16Len(pwszCurHwId) + 1) + if (RTUtf16ICmp(DRIVERHWID, pwszCurHwId) == 0) + { + fFound = true; + break; + } + } + } + + if (!fFound) + SetErrBreak(("Could not find Host Interface Networking driver! Please reinstall")); + + /* set the loopback driver to be the currently selected */ + if (!SetupDiSetSelectedDriver(hDeviceInfo, &DeviceInfoData, &DriverInfoData)) + SetErrBreak(("SetupDiSetSelectedDriver failed (%u)", GetLastError())); + + /* register the phantom device to prepare for install */ + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller failed (%u)", GetLastError())); + + /* registered, but remove if errors occur in the following code */ + fRegistered = TRUE; + + /* ask the installer if we can install the device */ + if (!SetupDiCallClassInstaller(DIF_ALLOW_INSTALL, hDeviceInfo, &DeviceInfoData)) + { + if (GetLastError() != ERROR_DI_DO_DEFAULT) + SetErrBreak(("SetupDiCallClassInstaller (DIF_ALLOW_INSTALL) failed (%u)", GetLastError())); + /* that's fine */ + } + + /* get the device install parameters and disable filecopy */ + DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); + if (SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + pQueueCallbackContext = SetupInitDefaultQueueCallback(NULL); + if (pQueueCallbackContext) + { + DeviceInstallParams.InstallMsgHandlerContext = pQueueCallbackContext; + DeviceInstallParams.InstallMsgHandler = (PSP_FILE_CALLBACK)vboxNetCfgWinPspFileCallback; + if (!SetupDiSetDeviceInstallParamsW(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("SetupDiSetDeviceInstallParamsW failed, winEr (%d)\n", winEr)); + Assert(0); + } + } + else + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("SetupInitDefaultQueueCallback failed, winEr (%d)\n", winEr)); + } + } + else + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("SetupDiGetDeviceInstallParams failed, winEr (%d)\n", winEr)); + } + + /* install the files first */ + if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_INSTALLDEVICEFILES) failed (%Rwc)", GetLastError())); + + /* get the device install parameters and disable filecopy */ + DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); + if (SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + DeviceInstallParams.Flags |= DI_NOFILECOPY; + if (!SetupDiSetDeviceInstallParamsW(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + SetErrBreak(("SetupDiSetDeviceInstallParamsW failed (%Rwc)", GetLastError())); + } + /** @todo r=bird: Why isn't SetupDiGetDeviceInstallParams failure fatal here? */ + + /* + * Register any device-specific co-installers for this device, + */ + if (!SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_REGISTER_COINSTALLERS) failed (%Rwc)", GetLastError())); + + /* + * install any installer-specified interfaces. + * and then do the real install + */ + if (!SetupDiCallClassInstaller(DIF_INSTALLINTERFACES, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_INSTALLINTERFACES) failed (%Rwc)", GetLastError())); + + if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICE, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_INSTALLDEVICE) failed (%Rwc)", GetLastError())); + + /* + * Query the instance ID; on Windows 10, the registry key may take a short + * while to appear. Microsoft recommends waiting for up to 5 seconds, but + * we want to be on the safe side, so let's wait for 20 seconds. Waiting + * longer is harmful as network setup service will shut down after a period + * of inactivity. + */ + for (int retries = 0; retries < 2 * 20; ++retries) + { + Sleep(500); /* half second */ + + /* Figure out NetCfgInstanceId */ + hkey = SetupDiOpenDevRegKey(hDeviceInfo, &DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ); + if (hkey == INVALID_HANDLE_VALUE) + break; + + DWORD cbSize = sizeof(wszCfgGuidString); + DWORD dwValueType = 0; + lrcRet = RegQueryValueExW(hkey, L"NetCfgInstanceId", NULL, &dwValueType, (LPBYTE)wszCfgGuidString, &cbSize); + /* As long as the return code is FILE_NOT_FOUND, sleep and retry. */ + if (lrcRet != ERROR_FILE_NOT_FOUND) + break; + + RegCloseKey(hkey); + hkey = (HKEY)INVALID_HANDLE_VALUE; + } + + if (lrcRet == ERROR_FILE_NOT_FOUND) + { + hrc = E_ABORT; + break; + } + + /* + * We need to check 'hkey' after we check 'lrcRet' to distinguish the case + * of failed SetupDiOpenDevRegKey from the case when we timed out. + */ + if (hkey == INVALID_HANDLE_VALUE) + SetErrBreak(("SetupDiOpenDevRegKey failed (%Rwc)", GetLastError())); + + if (lrcRet != ERROR_SUCCESS) + SetErrBreak(("Querying NetCfgInstanceId failed (%Rwc)", lrcRet)); + + NET_LUID luid; + HRESULT hrcSMRes = vboxNetCfgWinGetInterfaceLUID(hkey, &luid); + + /* Close the key as soon as possible. See @bugref{7973}. */ + RegCloseKey(hkey); + hkey = (HKEY)INVALID_HANDLE_VALUE; + + if (FAILED(hrcSMRes)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("vboxNetCfgWinCreateHostOnlyNetworkInterface: Warning! " + "vboxNetCfgWinGetInterfaceLUID failed, default metric for new interface will not be set: %Rhrc\n", + hrcSMRes)); + } + else + { + /* + * Set default metric value of interface to fix multicast issue + * See @bugref{6379} for details. + */ + hrcSMRes = vboxNetCfgWinSetupMetric(&luid); + if (FAILED(hrcSMRes)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("vboxNetCfgWinCreateHostOnlyNetworkInterface: Warning! " + "vboxNetCfgWinSetupMetric failed, default metric for new interface will not be set: %Rhrc\n", + hrcSMRes)); + } + } + + /* + * We need to query the device name after we have succeeded in querying its + * instance ID to avoid similar waiting-and-retrying loop (see @bugref{7973}). + */ + if (!SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, &DeviceInfoData, + SPDRP_FRIENDLYNAME , /* IN DWORD Property,*/ + NULL, /*OUT PDWORD PropertyRegDataType, OPTIONAL*/ + (PBYTE)wszDevName, /*OUT PBYTE PropertyBuffer,*/ + sizeof(wszDevName) - sizeof(WCHAR), /* IN DWORD PropertyBufferSize,*/ + NULL /*OUT PDWORD RequiredSize OPTIONAL*/ )) + { + DWORD dwErr = GetLastError(); + if (dwErr != ERROR_INVALID_DATA) + SetErrBreak(("SetupDiGetDeviceRegistryProperty failed (%Rwc)", dwErr)); + + RT_ZERO(wszDevName); + if (!SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, &DeviceInfoData, + SPDRP_DEVICEDESC, /* IN DWORD Property,*/ + NULL, /*OUT PDWORD PropertyRegDataType, OPTIONAL*/ + (PBYTE)wszDevName, /*OUT PBYTE PropertyBuffer,*/ + sizeof(wszDevName) - sizeof(WCHAR), /* IN DWORD PropertyBufferSize,*/ + NULL /*OUT PDWORD RequiredSize OPTIONAL*/ )) + SetErrBreak(("SetupDiGetDeviceRegistryProperty failed (%Rwc)", GetLastError())); + } + + /* No need to rename the device if the names match. */ + if (RTUtf16Cmp(bstrNewInterfaceName.raw(), wszDevName) == 0) + bstrNewInterfaceName.setNull(); + +#ifdef VBOXNETCFG_DELAYEDRENAME + /* Re-use wszDevName for device instance id retrieval. */ + DWORD cwcReturned = 0; + RT_ZERO(wszDevName); + if (!SetupDiGetDeviceInstanceIdW(hDeviceInfo, &DeviceInfoData, wszDevName, RT_ELEMENTS(wszDevName) - 1, &cwcReturned)) + SetErrBreak(("SetupDiGetDeviceInstanceId failed (%Rwc)", GetLastError())); +#endif /* VBOXNETCFG_DELAYEDRENAME */ + } while (0); + + /* + * Cleanup. + */ + if (hkey != INVALID_HANDLE_VALUE) + RegCloseKey(hkey); + + if (pQueueCallbackContext) + SetupTermDefaultQueueCallback(pQueueCallbackContext); + + if (hDeviceInfo != INVALID_HANDLE_VALUE) + { + /* an error has occurred, but the device is registered, we must remove it */ + if (lrcRet != ERROR_SUCCESS && fRegistered) + SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfo, &DeviceInfoData); + + SetupDiDeleteDeviceInfo(hDeviceInfo, &DeviceInfoData); + + /* destroy the driver info list */ +#if 0 + /* I've remove this, as I was consistently getting crashes in + * SetupDiDestroyDeviceInfoList otherwise during MSI installation + * (W10 build 19044, VBox r153431 + nocrt changes). + * + * Some details from windbg: + * + * (175e8.1596c): Access violation - code c0000005 (first chance) + * First chance exceptions are reported before any exception handling. + * This exception may be expected and handled. + * SETUPAPI!DereferenceClassDriverList+0x4e: + * 00007ffa`83e2a42a 834008ff add dword ptr [rax+8],0FFFFFFFFh ds:00000000`00000008=???????? + * 0:006> k + * # Child-SP RetAddr Call Site + * 00 0000007e`ccd7c070 00007ffa`83e2a287 SETUPAPI!DereferenceClassDriverList+0x4e + * 01 0000007e`ccd7c0a0 00007ffa`56b96bd3 SETUPAPI!SetupDiDestroyDriverInfoList+0x117 + * 02 0000007e`ccd7c0f0 00007ffa`56b982a3 MSIF170!vboxNetCfgWinCreateHostOnlyNetworkInterface+0xb23 [E:\vbox\svn\trunk\src\VBox\HostDrivers\VBoxNetFlt\win\cfg\VBoxNetCfg.cpp @ 3378] + * 03 0000007e`ccd7ef10 00007ffa`56b92cb8 MSIF170!VBoxNetCfgWinCreateHostOnlyNetworkInterface+0x53 [E:\vbox\svn\trunk\src\VBox\HostDrivers\VBoxNetFlt\win\cfg\VBoxNetCfg.cpp @ 3479] + * 04 0000007e`ccd7efc0 00007ffa`610f59d3 MSIF170!_createHostOnlyInterface+0x218 [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1453] + * 05 0000007e`ccd7f260 00007ffa`610d80ac msi!CallCustomDllEntrypoint+0x2b + * 06 0000007e`ccd7f2d0 00007ffa`84567034 msi!CMsiCustomAction::CustomActionThread+0x34c + * 07 0000007e`ccd7f8c0 00007ffa`849a2651 KERNEL32!BaseThreadInitThunk+0x14 + * 08 0000007e`ccd7f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x21 + * 0:006> r + * rax=0000000000000000 rbx=0000025f4f03af90 rcx=00007ffa83f23b40 + * rdx=0000025f4f03af90 rsi=0000025f5108be50 rdi=0000025f4f066f10 + * rip=00007ffa83e2a42a rsp=0000007eccd7c070 rbp=00007ffa83f23000 + * r8=0000007eccd7c0a8 r9=0000007eccd7c1f0 r10=0000000000000000 + * r11=0000007eccd7c020 r12=0000007eccd7c3d0 r13=00007ffa83f23000 + * r14=0000000000000000 r15=0000025f4f03af90 + * iopl=0 nv up ei pl nz na po nc + * cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 + * SETUPAPI!DereferenceClassDriverList+0x4e: + * 00007ffa`83e2a42a 834008ff add dword ptr [rax+8],0FFFFFFFFh ds:00000000`00000008=???????? + * + */ + if (destroyList) + SetupDiDestroyDriverInfoList(hDeviceInfo, &DeviceInfoData, SPDIT_CLASSDRIVER); +#else + RT_NOREF(destroyList); +#endif + + /* clean up the device info set */ + SetupDiDestroyDeviceInfoList(hDeviceInfo); + } + + /* + * Return the network connection GUID on success. + */ + if (SUCCEEDED(hrc)) + { + /** @todo r=bird: Some please explain this mess. It's not just returning a + * GUID here, it's a bit more than that. */ + RENAMING_CONTEXT context; + context.hr = E_FAIL; + + if (pGuid) + { + hrc = CLSIDFromString(wszCfgGuidString, (LPCLSID)pGuid); + if (FAILED(hrc)) + NonStandardLogFlow(("CLSIDFromString failed, hrc (0x%x)\n", hrc)); + } + + INetCfg *pNetCfg = NULL; + LPWSTR pwszApp = NULL; + HRESULT hrc2 = VBoxNetCfgWinQueryINetCfg(&pNetCfg, TRUE, L"VirtualBox Host-Only Creation", + 30 * 1000, /* on Vista we often get 6to4svc.dll holding the lock, wait for 30 sec. */ + /** @todo special handling for 6to4svc.dll ???, i.e. several retrieves */ + &pwszApp); + if (hrc2 == S_OK) + { + if (bstrNewInterfaceName.isNotEmpty()) + { + /* The assigned name does not match the desired one, rename the device */ + context.bstrName = bstrNewInterfaceName.raw(); + context.pGuid = pGuid; + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NET, + vboxNetCfgWinRenameHostOnlyNetworkInterface, &context); + } + if (SUCCEEDED(hrc2)) + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NETSERVICE, + vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority, pGuid); + if (SUCCEEDED(hrc2)) + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NETTRANS, + vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority, pGuid); + if (SUCCEEDED(hrc2)) + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NETCLIENT, + vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority, pGuid); + if (SUCCEEDED(hrc2)) + hrc2 = pNetCfg->Apply(); + else + NonStandardLogFlow(("Enumeration failed, hrc2=%Rhrc\n", hrc2)); + + VBoxNetCfgWinReleaseINetCfg(pNetCfg, TRUE); + } + else if (hrc2 == NETCFG_E_NO_WRITE_LOCK && pwszApp) + { + NonStandardLogFlow(("Application '%ls' is holding the lock, failed\n", pwszApp)); + CoTaskMemFree(pwszApp); + pwszApp = NULL; + } + else + NonStandardLogFlow(("VBoxNetCfgWinQueryINetCfg failed, hrc2=%Rhrc\n", hrc2)); + +#ifndef VBOXNETCFG_DELAYEDRENAME + /* If the device has been successfully renamed, replace the name now. */ + if (SUCCEEDED(hrc2) && SUCCEEDED(context.hr)) + RTUtf16Copy(wszDevName, RT_ELEMENTS(wszDevName), pBstrDesiredName); + + WCHAR wszConnectionName[128]; + hrc2 = VBoxNetCfgWinGenHostonlyConnectionName(wszDevName, wszConnectionName, RT_ELEMENTS(wszConnectionName), NULL); + if (SUCCEEDED(hrc2)) + hrc2 = VBoxNetCfgWinRenameConnection(wszCfgGuidString, wszConnectionName); +#endif + + /* + * Now, return the network connection GUID/name. + */ + if (pBstrName) + { + *pBstrName = SysAllocString((const OLECHAR *)wszDevName); + if (!*pBstrName) + { + NonStandardLogFlow(("SysAllocString failed\n")); + hrc = E_OUTOFMEMORY; + } + } + } + + if (pBstrErrMsg) + { + *pBstrErrMsg = NULL; + if (bstrError.isNotEmpty()) + bstrError.detachToEx(pBstrErrMsg); + } + return hrc; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinCreateHostOnlyNetworkInterface(IN LPCWSTR pwszInfPath, IN bool fIsInfPathFile, + IN BSTR pwszDesiredName, OUT GUID *pGuid, + OUT BSTR *pBstrName, OUT BSTR *pBstrErrMsg) +{ + HRESULT hrc = vboxNetCfgWinCreateHostOnlyNetworkInterface(pwszInfPath, fIsInfPathFile, pwszDesiredName, + pGuid, pBstrName, pBstrErrMsg); + if (hrc == E_ABORT) + { + NonStandardLogFlow(("Timed out while waiting for NetCfgInstanceId, try again immediately...\n")); + + /* + * This is the first time we fail to obtain NetCfgInstanceId, let us + * retry it once. It is needed to handle the situation when network + * setup fails to recognize the arrival of our device node while it + * is busy removing another host-only interface, and it gets stuck + * with no matching network interface created for our device node. + * See @bugref{7973} for details. + */ + hrc = vboxNetCfgWinCreateHostOnlyNetworkInterface(pwszInfPath, fIsInfPathFile, pwszDesiredName, + pGuid, pBstrName, pBstrErrMsg); + if (hrc == E_ABORT) + { + NonStandardLogFlow(("Timed out again while waiting for NetCfgInstanceId, try again after a while...\n")); + + /* + * This is the second time we fail to obtain NetCfgInstanceId, let us + * retry it once more. This time we wait to network setup service + * to go down before retrying. Hopefully it will resolve all error + * conditions. See @bugref{7973} for details. + */ + SC_HANDLE hSCM = OpenSCManagerW(NULL, NULL, GENERIC_READ); + if (hSCM) + { + SC_HANDLE hService = OpenServiceW(hSCM, L"NetSetupSvc", GENERIC_READ); + if (hService) + { + for (int retries = 0; retries < 60 && !vboxNetCfgWinIsNetSetupStopped(hService); ++retries) + Sleep(1000); + CloseServiceHandle(hService); + hrc = vboxNetCfgWinCreateHostOnlyNetworkInterface(pwszInfPath, fIsInfPathFile, pwszDesiredName, + pGuid, pBstrName, pBstrErrMsg); + } + else + NonStandardLogFlow(("OpenService failed (%Rwc)\n", GetLastError())); + CloseServiceHandle(hSCM); + } + else + NonStandardLogFlow(("OpenSCManager failed (%Rwc)", GetLastError())); + + /* Give up and report the error. */ + if (hrc == E_ABORT) + { + if (pBstrErrMsg) + { + com::Bstr bstrError; + bstrError.printfNoThrow("Querying NetCfgInstanceId failed (ERROR_FILE_NOT_FOUND)"); + bstrError.detachToEx(pBstrErrMsg); + } + hrc = E_FAIL; + } + } + } + return hrc; +} + +static HRESULT vboxNetCfgWinGetLoopbackMetric(OUT DWORD *Metric) +{ + Assert(g_pfnInitializeIpInterfaceEntry != NULL); + Assert(g_pfnGetIpInterfaceEntry != NULL); + + MIB_IPINTERFACE_ROW row; + RT_ZERO(row); /* paranoia */ + g_pfnInitializeIpInterfaceEntry(&row); + + row.Family = AF_INET; + row.InterfaceLuid.Info.IfType = IF_TYPE_SOFTWARE_LOOPBACK; + + NETIO_STATUS dwErr = g_pfnGetIpInterfaceEntry(&row); + if (dwErr == NO_ERROR) + { + *Metric = row.Metric; + return S_OK; + } + return HRESULT_FROM_WIN32(dwErr); +} + +static HRESULT vboxNetCfgWinSetInterfaceMetric(IN NET_LUID *pInterfaceLuid, IN DWORD metric) +{ + Assert(g_pfnInitializeIpInterfaceEntry != NULL); + Assert(g_pfnSetIpInterfaceEntry != NULL); + + MIB_IPINTERFACE_ROW newRow; + RT_ZERO(newRow); /* paranoia */ + g_pfnInitializeIpInterfaceEntry(&newRow); + + // identificate the interface to change + newRow.InterfaceLuid = *pInterfaceLuid; + newRow.Family = AF_INET; + + // changed settings + newRow.UseAutomaticMetric = false; + newRow.Metric = metric; + + // change settings + NETIO_STATUS dwErr = g_pfnSetIpInterfaceEntry(&newRow); + if (dwErr == NO_ERROR) + return S_OK; + return HRESULT_FROM_WIN32(dwErr); +} + +static HRESULT vboxNetCfgWinSetupMetric(IN NET_LUID* pLuid) +{ + HRESULT hrc = E_FAIL; + HMODULE hmod = loadSystemDll(L"Iphlpapi.dll"); + if (hmod) + { + g_pfnInitializeIpInterfaceEntry = (PFNINITIALIZEIPINTERFACEENTRY)GetProcAddress(hmod, "InitializeIpInterfaceEntry"); + g_pfnGetIpInterfaceEntry = (PFNGETIPINTERFACEENTRY) GetProcAddress(hmod, "GetIpInterfaceEntry"); + g_pfnSetIpInterfaceEntry = (PFNSETIPINTERFACEENTRY) GetProcAddress(hmod, "SetIpInterfaceEntry"); + Assert(g_pfnInitializeIpInterfaceEntry); + Assert(g_pfnGetIpInterfaceEntry); + Assert(g_pfnSetIpInterfaceEntry); + + if ( g_pfnInitializeIpInterfaceEntry + && g_pfnGetIpInterfaceEntry + && g_pfnSetIpInterfaceEntry) + { + DWORD loopbackMetric = 0; + hrc = vboxNetCfgWinGetLoopbackMetric(&loopbackMetric); + if (SUCCEEDED(hrc)) + hrc = vboxNetCfgWinSetInterfaceMetric(pLuid, loopbackMetric - 1); + } + + g_pfnInitializeIpInterfaceEntry = NULL; + g_pfnSetIpInterfaceEntry = NULL; + g_pfnGetIpInterfaceEntry = NULL; + + FreeLibrary(hmod); + } + return hrc; +} + +static HRESULT vboxNetCfgWinGetInterfaceLUID(IN HKEY hKey, OUT NET_LUID *pLUID) +{ + if (pLUID == NULL) + return E_INVALIDARG; + + DWORD dwLuidIndex = 0; + DWORD cbSize = sizeof(dwLuidIndex); + DWORD dwValueType = REG_DWORD; /** @todo r=bird: This is output only. No checked after the call. So, only for debugging? */ + LSTATUS lrc = RegQueryValueExW(hKey, L"NetLuidIndex", NULL, &dwValueType, (LPBYTE)&dwLuidIndex, &cbSize); + if (lrc == ERROR_SUCCESS) + { + DWORD dwIfType = 0; + cbSize = sizeof(dwIfType); + dwValueType = REG_DWORD; /** @todo r=bird: This is output only. No checked after the call. So, only for debugging? */ + lrc = RegQueryValueExW(hKey, L"*IfType", NULL, &dwValueType, (LPBYTE)&dwIfType, &cbSize); + if (lrc == ERROR_SUCCESS) + { + RT_ZERO(*pLUID); + pLUID->Info.IfType = dwIfType; + pLUID->Info.NetLuidIndex = dwLuidIndex; + return S_OK; + } + } + + RT_ZERO(*pLUID); + return HRESULT_FROM_WIN32(lrc); +} + + +#ifdef VBOXNETCFG_DELAYEDRENAME +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRenameHostOnlyConnection(IN const GUID *pGuid, IN LPCWSTR pwszId, OUT BSTR *pDevName) +{ + if (pDevName) + *pDevName = NULL; + + HRESULT hr = S_OK; /** @todo r=bird: ODD return status for SetupDiCreateDeviceInfoList failures! */ + HDEVINFO hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, NULL); + if (hDevInfo != INVALID_HANDLE_VALUE) + { + SP_DEVINFO_DATA DevInfoData = { sizeof(SP_DEVINFO_DATA) }; + if (SetupDiOpenDeviceInfo(hDevInfo, pwszId, NULL, 0, &DevInfoData)) + { + WCHAR wszDevName[256 + 1] = {0}; + DWORD err = ERROR_SUCCESS; + if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &DevInfoData, SPDRP_FRIENDLYNAME, NULL /*PropertyRegDataType*/, + (PBYTE)wszDevName, sizeof(wszDevName) - sizeof(WCHAR) /*yes, bytes*/, + NULL /*RequiredSize*/)) + { + err = GetLastError(); + if (err == ERROR_INVALID_DATA) + { + RT_ZERO(wszDevName); + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &DevInfoData, SPDRP_DEVICEDESC, NULL /*PropertyRegDataType*/, + (PBYTE)wszDevName, sizeof(wszDevName) - sizeof(WCHAR) /*yes, bytes*/, + NULL /*RequiredSize*/)) + err = ERROR_SUCCESS; + else + err = GetLastError(); + } + } + if (err == ERROR_SUCCESS) + { + WCHAR wszConnectionNewName[128] = {0}; + hr = VBoxNetCfgWinGenHostonlyConnectionName(wszDevName, wszConnectionNewName, + RT_ELEMENTS(wszConnectionNewName), NULL); + if (SUCCEEDED(hr)) + { + WCHAR wszGuid[50]; + int cbWGuid = StringFromGUID2(*pGuid, wszGuid, RT_ELEMENTS(wszGuid)); + if (cbWGuid) + { + hr = VBoxNetCfgWinRenameConnection(wszGuid, wszConnectionNewName); + if (FAILED(hr)) + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: VBoxNetCfgWinRenameConnection failed (0x%x)\n", hr)); + } + else + { + err = GetLastError(); + hr = HRESULT_FROM_WIN32(err); + if (SUCCEEDED(hr)) + hr = E_FAIL; + NonStandardLogFlow(("StringFromGUID2 failed err=%u, hr=0x%x\n", err, hr)); + } + } + else + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: VBoxNetCfgWinGenHostonlyConnectionName failed (0x%x)\n", hr)); + if (SUCCEEDED(hr) && pDevName) + { + *pDevName = SysAllocString((const OLECHAR *)wszDevName); + if (!*pDevName) + { + NonStandardLogFlow(("SysAllocString failed\n")); + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + } + } + else + { + hr = HRESULT_FROM_WIN32(err); + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: SetupDiGetDeviceRegistryPropertyW failed (0x%x)\n", err)); + } + } + else + { + DWORD err = GetLastError(); + hr = HRESULT_FROM_WIN32(err); + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: SetupDiOpenDeviceInfo failed (0x%x)\n", err)); + } + SetupDiDestroyDeviceInfoList(hDevInfo); + } + + return hr; +} +#endif /* VBOXNETCFG_DELAYEDRENAME */ + +#undef SetErrBreak + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf new file mode 100644 index 00000000..9e13a198 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf @@ -0,0 +1,95 @@ +; $Id: VBoxNetAdp.inf $ +;; @file +; VBoxNetAdp.inf - VirtualBox Host-Only Driver inf file +; + +; +; Copyright (C) 2011-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 <https://www.gnu.org/licenses>. +; +; 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 +; + +[Version] +signature = "$Windows NT$" +;cat CatalogFile = VBoxNetAdp.cat +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %ORACLE% +; DriverPackageType=Network +; DriverPackageDisplayName=%VBoxNetAdp_Desc% +;edit-DriverVer=08/13/2008,1.1.0.1 + +[ControlFlags] +;ExcludeFromSelect = sun_VBoxNetAdp + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetAdp.sys=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetAdp.Files.Sys = 12 ; %windir%\System32\drivers + +[Manufacturer] +%ORACLE% = VBoxNetAdp@COMMA-NT-ARCH@ + +[VBoxNetAdp@DOT-NT-ARCH@] +%VBoxNetAdp_Desc% = VBoxNetAdp.ndi, sun_VBoxNetAdp + +[VBoxNetAdp.ndi] +Characteristics = 0x1 ; NCF_VIRTUAL +CopyFiles = VBoxNetAdp.Files.Sys +AddReg = VBoxNetAdp.AddReg + +[VBoxNetAdp.Files.Sys] +VBoxNetAdp.sys,,,2 + +[VBoxNetAdp.ndi.Services] +AddService = VBoxNetAdp,0x2, VBoxNetAdp.AddService + +[VBoxNetAdp.AddService] +DisplayName = %VBoxNetAdp_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetAdp.sys +LoadOrderGroup = NDIS + +[VBoxNetAdp.AddReg] +HKR, , *NdisDeviceType, 0x00010001, 1 ; NDIS_DEVICE_TYPE_ENDPOINT +HKR, Ndi, Service, 0, "VBoxNetAdp" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + +[Strings] +ORACLE = "Oracle Corporation" +VBoxNetAdp_Desc = "VirtualBox Host-Only Ethernet Adapter" +DiskDescription = "VirtualBox Host-Only Ethernet Adapter" + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc new file mode 100644 index 00000000..979fb6dd --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc @@ -0,0 +1,77 @@ +/* $Id: VBoxNetFlt-win.rc $ */ +/** @file + * VBoxNetFlt - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#include <windows.h> +#include <VBox/version.h> + +#ifndef VBOXNETADP +# define DESCRIPTION_STR "VirtualBox Bridged Networking Driver\0" +# define FILENAME_STR "VBoxNetFlt" +#else +# define DESCRIPTION_STR "VirtualBox Host-Only Network Adapter Driver\0" +# define FILENAME_STR "VBoxNetAdp" +#endif + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_NETWORK +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileDescription", DESCRIPTION_STR + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "InternalName", FILENAME_STR "\0" + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "OriginalFilename", FILENAME_STR ".sys\0" + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf new file mode 100644 index 00000000..22425793 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf @@ -0,0 +1,117 @@ +; $Id: VBoxNetFlt.inf $ +;; @file +; VBoxNetFlt.inf - VirtualBox Bridged Networking Driver inf file Protocol edge +; + +; +; Copyright (C) 2011-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 <https://www.gnu.org/licenses>. +; +; 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 +; + +[Version] +Signature = "$Windows NT$" +;cat CatalogFile = VBoxNetFlt.cat +Class = NetService +ClassGUID = {4D36E974-E325-11CE-BFC1-08002BE10318} +Provider = %ORACLE% +;DriverPackageType=Network +;DriverPackageDisplayName=%VBoxNetFlt_Desc% +;edit-DriverVer=08/13/2008,1.1.0.1 + + +[Manufacturer] +%ORACLE% = VBoxNetFlt@COMMA-NT-ARCH@ + +[ControlFlags] + +[VBoxNetFlt@DOT-NT-ARCH@] +%VBoxNetFlt_Desc% = VBoxNetFlt.ndi, sun_VBoxNetFlt + +[VBoxNetFlt.ndi] +AddReg = VBoxNetFlt.ndi.AddReg, VBoxNetFlt.AddReg +Characteristics = 0x4410 ; NCF_FILTER | NCF_NDIS_PROTOCOL +CopyFiles = VBoxNetFlt.Files.DLL, VBoxNetFlt.Files.Sys +CopyInf = VBoxNetFltM.inf + +[VBoxNetFlt.ndi.Remove] +DelFiles = VBoxNetFlt.Files.DLL, VBoxNetFlt.Files.Sys + +[VBoxNetFlt.ndi.Services] +AddService = VBoxNetFlt,, VBoxNetFlt.AddService + +[VBoxNetFlt.AddService] +DisplayName = %VBoxNetFltService_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetFlt.sys +LoadOrderGroup = PNP_TDI +AddReg = VBoxNetFlt.AddService.AddReg + + +[VBoxNetFlt.AddService.AddReg] + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetFlt.sys=1 +VBoxNetFltNobj.dll=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetFlt.Files.DLL = 11 ; %windir%\System32 +VBoxNetFlt.Files.Sys = 12 ; %windir%\System32\drivers + +[VBoxNetFlt.Files.Sys] +VBoxNetFlt.sys,,,2 + +[VBoxNetFlt.Files.DLL] +VBoxNetFltNobj.dll,,,2 + +[VBoxNetFlt.ndi.AddReg] +HKR, Ndi, HelpText, , %VBoxNetFlt_HELP% +HKR, Ndi, ClsID, 0, {f374d1a0-bf08-4bdc-9cb2-c15ddaeef955} +HKR, Ndi, ComponentDll, , VBoxNetFltNobj.dll +HKR, Ndi, FilterClass, , failover +HKR, Ndi, FilterDeviceInfId, , sun_VBoxNetFltmp +HKR, Ndi, Service, , VBoxNetFlt +HKR, Ndi\Interfaces, UpperRange, , noupper +HKR, Ndi\Interfaces, LowerRange, , nolower +HKR, Ndi\Interfaces, FilterMediaTypes, , "ethernet, nolower" + +[VBoxNetFlt.AddReg] +HKR, Parameters, Param1, 0, 4 + +[Strings] +ORACLE = "Oracle Corporation" +DiskDescription = "VirtualBox Bridged Networking Driver" +VBoxNetFlt_Desc = "VirtualBox Bridged Networking Driver" +VBoxNetFlt_HELP = "VirtualBox Bridged Networking Driver" +VBoxNetFltService_Desc = "VirtualBox Bridged Networking Service" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h new file mode 100644 index 00000000..520a7d7c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h @@ -0,0 +1,533 @@ +/* $Id: VBoxNetFltCmn-win.h $ */ +/** @file + * VBoxNetFltCmn-win.h - Bridged Networking Driver, Windows Specific Code. + * Common header with configuration defines and global defs + */ + +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltCmn_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltCmn_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV + +/* debugging flags */ +#ifdef DEBUG +//# define DEBUG_NETFLT_PACKETS +# ifndef DEBUG_misha +# define RT_NO_STRICT +# endif +/* # define DEBUG_NETFLT_LOOPBACK */ +/* receive logic has several branches */ +/* the DEBUG_NETFLT_RECV* macros used to debug the ProtocolReceive callback + * which is typically not used in case the underlying miniport indicates the packets with NdisMIndicateReceivePacket + * the best way to debug the ProtocolReceive (which in turn has several branches) is to enable the DEBUG_NETFLT_RECV + * one by one in the below order, i.e. + * first DEBUG_NETFLT_RECV + * then DEBUG_NETFLT_RECV + DEBUG_NETFLT_RECV_NOPACKET */ +//# define DEBUG_NETFLT_RECV +//# define DEBUG_NETFLT_RECV_NOPACKET +//# define DEBUG_NETFLT_RECV_TRANSFERDATA +/* use ExAllocatePoolWithTag instead of NdisAllocateMemoryWithTag */ +// #define DEBUG_NETFLT_USE_EXALLOC +#endif + +#include <VBox/intnet.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/version.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/process.h> +#include <iprt/alloc.h> +#include <iprt/alloca.h> +#include <iprt/time.h> +#include <iprt/net.h> +#include <iprt/list.h> + +#include <iprt/nt/ntddk.h> +#include <iprt/nt/ndis.h> + +#define VBOXNETFLT_OS_SPECFIC 1 + +/** version + * NOTE: we are NOT using NDIS 5.1 features now */ +#ifdef NDIS51_MINIPORT +# define VBOXNETFLT_VERSION_MP_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_MP_NDIS_MINOR 1 +#else +# define VBOXNETFLT_VERSION_MP_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_MP_NDIS_MINOR 0 +#endif + +#ifndef VBOXNETADP +#ifdef NDIS51 +# define VBOXNETFLT_VERSION_PT_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_PT_NDIS_MINOR 1 /* todo: use 0 here as well ? */ +#else +# define VBOXNETFLT_VERSION_PT_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_PT_NDIS_MINOR 0 +#endif + +# define VBOXNETFLT_NAME_PROTOCOL L"VBoxNetFlt" +/** device to be used to prevent the driver unload & ioctl interface (if necessary in the future) */ +# define VBOXNETFLT_NAME_LINK L"\\DosDevices\\Global\\VBoxNetFlt" +# define VBOXNETFLT_NAME_DEVICE L"\\Device\\VBoxNetFlt" +#else +# define VBOXNETFLT_NAME_LINK L"\\DosDevices\\Global\\VBoxNetAdp" +# define VBOXNETFLT_NAME_DEVICE L"\\Device\\VBoxNetAdp" +#endif + +typedef struct VBOXNETFLTINS *PVBOXNETFLTINS; + +/** configuration */ + +/** Ndis Packet pool settings + * these are applied to both receive and send packet pools */ +/* number of packets for normal used */ +#define VBOXNETFLT_PACKET_POOL_SIZE_NORMAL 0x000000FF +/* number of additional overflow packets */ +#define VBOXNETFLT_PACKET_POOL_SIZE_OVERFLOW 0x0000FF00 + +/** packet queue size used when the driver is working in the "active" mode */ +#define VBOXNETFLT_PACKET_INFO_POOL_SIZE 0x0000FFFF + +#ifndef VBOXNETADP +/** memory tag used for memory allocations + * (VBNF stands for VBox NetFlt) */ +# define VBOXNETFLT_MEM_TAG 'FNBV' +#else +/** memory tag used for memory allocations + * (VBNA stands for VBox NetAdp) */ +# define VBOXNETFLT_MEM_TAG 'ANBV' +#endif + +/** receive and transmit Ndis buffer pool size */ +#define VBOXNETFLT_BUFFER_POOL_SIZE_TX 128 +#define VBOXNETFLT_BUFFER_POOL_SIZE_RX 128 + +#define VBOXNETFLT_PACKET_ETHEADER_SIZE 14 +#define VBOXNETFLT_PACKET_HEADER_MATCH_SIZE 24 +#define VBOXNETFLT_PACKET_QUEUE_SG_SEGS_ALLOC 32 + + +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) +# define VBOXNETFLT_PACKETMATCH_LENGTH (VBOXNETFLT_PACKET_ETHEADER_SIZE + 2) +#endif + +#ifdef VBOXNETADP +#define VBOXNETADP_HEADER_SIZE 14 +#define VBOXNETADP_MAX_DATA_SIZE 1500 +#define VBOXNETADP_MAX_PACKET_SIZE (VBOXNETADP_HEADER_SIZE + VBOXNETADP_MAX_DATA_SIZE) +#define VBOXNETADP_MIN_PACKET_SIZE 60 +/* link speed 100Mbps (measured in 100 bps) */ +#define VBOXNETADP_LINK_SPEED 1000000 +#define VBOXNETADP_MAX_LOOKAHEAD_SIZE VBOXNETADP_MAX_DATA_SIZE +#define VBOXNETADP_VENDOR_ID 0x080027 +#define VBOXNETADP_VENDOR_DRIVER_VERSION 0x00010000 +#define VBOXNETADP_VENDOR_DESC "Sun" +#define VBOXNETADP_MAX_MCAST_LIST 32 +#define VBOXNETADP_ETH_ADDRESS_LENGTH 6 + +//#define VBOXNETADP_REPORT_DISCONNECTED +#endif +/* type defs */ + +/** Flag specifying that the type of enqueued packet + * if set the info contains the PINTNETSG packet + * if clear the packet info contains the PNDIS_PACKET packet + * Typically the packet queue we are maintaining contains PNDIS_PACKETs only, + * however in case the underlying miniport indicates a packet with the NDIS_STATUS_RESOURCES status + * we MUST return the packet back to the miniport immediately + * this is why we are creating the INTNETSG, copying the ndis packet info there and enqueueing it */ +#define VBOXNETFLT_PACKET_SG 0x00000001 + +/** the flag specifying that the packet source + * if set the packet comes from the host (upperlying protocol) + * if clear the packet comes from the wire (underlying miniport) */ +#define VBOXNETFLT_PACKET_SRC_HOST 0x00000002 + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +/** flag specifying the packet was originated by our driver + * i.e. we could use it on our needs and should not return it + * we are enqueueing "our" packets on ProtocolReceive call-back when + * Ndis does not give us a receive packet (the driver below us has called NdisM..IndicateReceive) + * this is supported for Ndis Packet only */ +#define VBOXNETFLT_PACKET_MINE 0x00000004 + +/** flag passed to vboxNetFltWinQuEnqueuePacket specifying that the packet should be copied + * this is supported for Ndis Packet only */ +#define VBOXNETFLT_PACKET_COPY 0x00000008 +#endif + +/** packet queue element containing the packet info */ +typedef struct VBOXNETFLT_PACKET_INFO +{ + /** list entry used for enqueueing the info */ + LIST_ENTRY ListEntry; + /** pointer to the pool containing this packet info */ + struct VBOXNETFLT_PACKET_INFO_POOL *pPool; + /** flags describing the referenced packet. Contains PACKET_xxx flags (i.e. PACKET_SG, PACKET_SRC_HOST) */ + uint32_t fFlags; + /** pointer to the packet this info represents */ + PVOID pPacket; +} VBOXNETFLT_PACKET_INFO, *PVBOXNETFLT_PACKET_INFO; + +/* paranoid check to make sure the elements in the packet info array are properly aligned */ +AssertCompile((sizeof(VBOXNETFLT_PACKET_INFO) & (sizeof(PVOID) - 1)) == 0); + +/** represents the packet queue */ +typedef LIST_ENTRY PVBOXNETFLT_ACKET_QUEUE, *PVBOXNETFLT_PACKET_QUEUE; + +/* + * we are using non-interlocked versions of LIST_ENTRY-related operations macros and synchronize + * access to the queue and its elements by acquiring/releasing a spinlock using Ndis[Acquire,Release]Spinlock + * + * we are NOT using interlocked versions of insert/remove head/tail list functions because we need to iterate though + * the queue elements as well as remove elements from the midle of the queue + * + * * @todo: it seems that we can switch to using interlocked versions of list-entry functions + * since we have removed all functionality (mentioned above, i.e. queue elements iteration, etc.) that might prevent us from doing this + */ +typedef struct VBOXNETFLT_INTERLOCKED_PACKET_QUEUE +{ + /** queue */ + PVBOXNETFLT_ACKET_QUEUE Queue; + /** queue lock */ + NDIS_SPIN_LOCK Lock; +} VBOXNETFLT_INTERLOCKED_PACKET_QUEUE, *PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE; + +typedef struct VBOXNETFLT_SINGLE_LIST +{ + /** queue */ + SINGLE_LIST_ENTRY Head; + /** pointer to the list tail. used to enqueue elements to the tail of the list */ + PSINGLE_LIST_ENTRY pTail; +} VBOXNETFLT_SINGLE_LIST, *PVBOXNETFLT_SINGLE_LIST; + +typedef struct VBOXNETFLT_INTERLOCKED_SINGLE_LIST +{ + /** queue */ + VBOXNETFLT_SINGLE_LIST List; + /** queue lock */ + NDIS_SPIN_LOCK Lock; +} VBOXNETFLT_INTERLOCKED_SINGLE_LIST, *PVBOXNETFLT_INTERLOCKED_SINGLE_LIST; + +/** packet info pool contains free packet info elements to be used for the packet queue + * we are using the pool mechanism to allocate packet queue elements + * the pool mechanism is pretty simple now, we are allocating a bunch of memory + * for maintaining VBOXNETFLT_PACKET_INFO_POOL_SIZE queue elements and just returning null when the pool is exhausted + * This mechanism seems to be enough for now since we are using VBOXNETFLT_PACKET_INFO_POOL_SIZE = 0xffff which is + * the maximum size of packets the ndis packet pool supports */ +typedef struct VBOXNETFLT_PACKET_INFO_POOL +{ + /** free packet info queue */ + VBOXNETFLT_INTERLOCKED_PACKET_QUEUE Queue; + /** memory bugger used by the pool */ + PVOID pBuffer; +} VBOXNETFLT_PACKET_INFO_POOL, *PVBOXNETFLT_PACKET_INFO_POOL; + +typedef enum VBOXNETDEVOPSTATE +{ + kVBoxNetDevOpState_InvalidValue = 0, + kVBoxNetDevOpState_Initializing, + kVBoxNetDevOpState_Initialized, + kVBoxNetDevOpState_Deinitializing, + kVBoxNetDevOpState_Deinitialized, + +} VBOXNETDEVOPSTATE; + +typedef enum VBOXNETFLT_WINIFSTATE +{ + /** The usual invalid state. */ + kVBoxWinIfState_Invalid = 0, + /** Initialization. */ + kVBoxWinIfState_Connecting, + /** Connected fuly functional state */ + kVBoxWinIfState_Connected, + /** Disconnecting */ + kVBoxWinIfState_Disconnecting, + /** Disconnected */ + kVBoxWinIfState_Disconnected, +} VBOXNETFLT_WINIFSTATE; + +/** structure used to maintain the state and reference count of the miniport and protocol */ +typedef struct VBOXNETFLT_WINIF_DEVICE +{ + /** initialize state */ + VBOXNETDEVOPSTATE OpState; + /** ndis power state */ + NDIS_DEVICE_POWER_STATE PowerState; + /** reference count */ + uint32_t cReferences; +} VBOXNETFLT_WINIF_DEVICE, *PVBOXNETFLT_WINIF_DEVICE; + +#define VBOXNDISREQUEST_INPROGRESS 1 +#define VBOXNDISREQUEST_QUEUED 2 + +typedef struct VBOXNETFLTWIN_STATE +{ + union + { + struct + { + UINT fRequestInfo : 2; + UINT fInterfaceClosing : 1; + UINT fStandBy : 1; + UINT fProcessingPacketFilter : 1; + UINT fPPFNetFlt : 1; + UINT fUpperProtSetFilterInitialized : 1; + UINT Reserved : 25; + }; + UINT Value; + }; +} VBOXNETFLTWIN_STATE, *PVBOXNETFLTWIN_STATE; + +DECLINLINE(VBOXNETFLTWIN_STATE) vboxNetFltWinAtomicUoReadWinState(VBOXNETFLTWIN_STATE State) +{ + UINT fValue = ASMAtomicUoReadU32((volatile uint32_t *)&State.Value); + return *((PVBOXNETFLTWIN_STATE)((void*)&fValue)); +} + +/* miniport layer globals */ +typedef struct VBOXNETFLTGLOBALS_MP +{ + /** our miniport handle */ + NDIS_HANDLE hMiniport; + /** ddis wrapper handle */ + NDIS_HANDLE hNdisWrapper; +} VBOXNETFLTGLOBALS_MP, *PVBOXNETFLTGLOBALS_MP; + +#ifndef VBOXNETADP +/* protocol layer globals */ +typedef struct VBOXNETFLTGLOBALS_PT +{ + /** our protocol handle */ + NDIS_HANDLE hProtocol; +} VBOXNETFLTGLOBALS_PT, *PVBOXNETFLTGLOBALS_PT; +#endif /* #ifndef VBOXNETADP */ + +typedef struct VBOXNETFLTGLOBALS_WIN +{ + /** synch event used for device creation synchronization */ + KEVENT SynchEvent; + /** Device reference count */ + int cDeviceRefs; + /** ndis device */ + NDIS_HANDLE hDevice; + /** device object */ + PDEVICE_OBJECT pDevObj; + /* loopback flags */ + /* ndis packet flags to disable packet loopback */ + UINT fPacketDontLoopBack; + /* ndis packet flags specifying whether the packet is looped back */ + UINT fPacketIsLoopedBack; + /* Minport info */ + VBOXNETFLTGLOBALS_MP Mp; +#ifndef VBOXNETADP + /* Protocol info */ + VBOXNETFLTGLOBALS_PT Pt; + /** lock protecting the filter list */ + NDIS_SPIN_LOCK lockFilters; + /** the head of filter list */ + RTLISTANCHOR listFilters; + /** IP address change notifier handle */ + HANDLE hNotifier; +#endif +} VBOXNETFLTGLOBALS_WIN, *PVBOXNETFLTGLOBALS_WIN; + +extern VBOXNETFLTGLOBALS_WIN g_VBoxNetFltGlobalsWin; + +/** represents filter driver device context*/ +typedef struct VBOXNETFLTWIN +{ + /** handle used by miniport edge for ndis calls */ + NDIS_HANDLE hMiniport; + /** miniport edge state */ + VBOXNETFLT_WINIF_DEVICE MpState; + /** ndis packet pool used for receives */ + NDIS_HANDLE hRecvPacketPool; + /** ndis buffer pool used for receives */ + NDIS_HANDLE hRecvBufferPool; + /** driver bind adapter state. */ + VBOXNETFLT_WINIFSTATE enmState; +#ifndef VBOXNETADP + /* misc state flags */ + VBOXNETFLTWIN_STATE StateFlags; + /** handle used by protocol edge for ndis calls */ + NDIS_HANDLE hBinding; + /** protocol edge state */ + VBOXNETFLT_WINIF_DEVICE PtState; + /** ndis packet pool used for receives */ + NDIS_HANDLE hSendPacketPool; + /** ndis buffer pool used for receives */ + NDIS_HANDLE hSendBufferPool; + /** used for maintaining the pending send packets for handling packet loopback */ + VBOXNETFLT_INTERLOCKED_SINGLE_LIST SendPacketQueue; + /** used for serializing calls to the NdisRequest in the vboxNetFltWinSynchNdisRequest */ + RTSEMFASTMUTEX hSynchRequestMutex; + /** event used to synchronize with the Ndis Request completion in the vboxNetFltWinSynchNdisRequest */ + KEVENT hSynchCompletionEvent; + /** status of the Ndis Request initiated by the vboxNetFltWinSynchNdisRequest */ + NDIS_STATUS volatile SynchCompletionStatus; + /** pointer to the Ndis Request being executed by the vboxNetFltWinSynchNdisRequest */ + PNDIS_REQUEST volatile pSynchRequest; + /** open/close adapter status. + * Since ndis adapter open and close requests may complete asynchronously, + * we are using event mechanism to wait for open/close completion + * the status field is being set by the completion call-back */ + NDIS_STATUS OpenCloseStatus; + /** open/close adaptor completion event */ + NDIS_EVENT OpenCloseEvent; + /** medium we are attached to */ + NDIS_MEDIUM enmMedium; + /** + * Passdown request info + */ + /** ndis request we pass down to the miniport below */ + NDIS_REQUEST PassDownRequest; + /** Ndis pass down request bytes read or written original pointer */ + PULONG pcPDRBytesRW; + /** Ndis pass down request bytes needed original pointer */ + PULONG pcPDRBytesNeeded; + /** true if we should indicate the receive complete used by the ProtocolReceive mechanism. + * We need to indicate it only with the ProtocolReceive + NdisMEthIndicateReceive path. + * Note: we're using KeGetCurrentProcessorNumber, which is not entirely correct in case + * we're running on 64bit win7+, which can handle > 64 CPUs, however since KeGetCurrentProcessorNumber + * always returns the number < than the number of CPUs in the first group, we're guaranteed to have CPU index < 64 + * @todo: use KeGetCurrentProcessorNumberEx for Win7+ 64 and dynamically extended array */ + bool abIndicateRxComplete[64]; + /** Pending transfer data packet queue (i.e. packets that were indicated as pending on NdisTransferData call */ + VBOXNETFLT_INTERLOCKED_SINGLE_LIST TransferDataList; + /* mac options initialized on OID_GEN_MAC_OPTIONS */ + ULONG fMacOptions; + /** our miniport devuice name */ + NDIS_STRING MpDeviceName; + /** synchronize with unbind with Miniport initialization */ + NDIS_EVENT MpInitCompleteEvent; + /** media connect status that we indicated */ + NDIS_STATUS MpIndicatedMediaStatus; + /** media connect status pending to indicate */ + NDIS_STATUS MpUnindicatedMediaStatus; + /** packet filter flags set by the upper protocols */ + ULONG fUpperProtocolSetFilter; + /** packet filter flags set by the upper protocols */ + ULONG fSetFilterBuffer; + /** packet filter flags set by us */ + ULONG fOurSetFilter; + /** our own list of filters, needed by notifier */ + RTLISTNODE node; +#else + volatile ULONG cTxSuccess; + volatile ULONG cRxSuccess; + volatile ULONG cTxError; + volatile ULONG cRxError; +#endif +} VBOXNETFLTWIN, *PVBOXNETFLTWIN; + +typedef struct VBOXNETFLT_PACKET_QUEUE_WORKER +{ + /** this event is used to initiate a packet queue worker thread kill */ + KEVENT KillEvent; + /** this event is used to notify a worker thread that the packets are added to the queue */ + KEVENT NotifyEvent; + /** pointer to the packet queue worker thread object */ + PKTHREAD pThread; + /** pointer to the SG used by the packet queue for IntNet receive notifications */ + PINTNETSG pSG; + /** Packet queue */ + VBOXNETFLT_INTERLOCKED_PACKET_QUEUE PacketQueue; + /** Packet info pool, i.e. the pool for the packet queue elements */ + VBOXNETFLT_PACKET_INFO_POOL PacketInfoPool; +} VBOXNETFLT_PACKET_QUEUE_WORKER, *PVBOXNETFLT_PACKET_QUEUE_WORKER; + +/* protocol reserved data held in ndis packet */ +typedef struct VBOXNETFLT_PKTRSVD_PT +{ + /** original packet received from the upperlying protocol + * can be null if the packet was originated by intnet */ + PNDIS_PACKET pOrigPacket; + /** pointer to the buffer to be freed on send completion + * can be null if no buffer is to be freed */ + PVOID pBufToFree; +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + SINGLE_LIST_ENTRY ListEntry; + /* true if the packet is from IntNet */ + bool bFromIntNet; +#endif +} VBOXNETFLT_PKTRSVD_PT, *PVBOXNETFLT_PKTRSVD_PT; + +/** miniport reserved data held in ndis packet */ +typedef struct VBOXNETFLT_PKTRSVD_MP +{ + /** original packet received from the underling miniport + * can be null if the packet was originated by intnet */ + PNDIS_PACKET pOrigPacket; + /** pointer to the buffer to be freed on receive completion + * can be null if no buffer is to be freed */ + PVOID pBufToFree; +} VBOXNETFLT_PKTRSVD_MP, *PVBOXNETFLT_PKTRSVD_MP; + +/** represents the data stored in the protocol reserved field of ndis packet on NdisTransferData processing */ +typedef struct VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT +{ + /** next packet in a list */ + SINGLE_LIST_ENTRY ListEntry; + /* packet buffer start */ + PNDIS_BUFFER pOrigBuffer; +} VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT, *PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT; + +/* VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT should fit into PROTOCOL_RESERVED_SIZE_IN_PACKET because we use protocol reserved part + * of our miniport edge on transfer data processing for honding our own info */ +AssertCompile(sizeof (VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT) <= PROTOCOL_RESERVED_SIZE_IN_PACKET); +/* this should fit in MiniportReserved */ +AssertCompile(sizeof (VBOXNETFLT_PKTRSVD_MP) <= RT_SIZEOFMEMB(NDIS_PACKET, MiniportReserved)); +/* we use RTAsmAtomic*U32 for those, make sure we're correct */ +AssertCompile(sizeof (NDIS_DEVICE_POWER_STATE) == sizeof (uint32_t)); +AssertCompile(sizeof (UINT) == sizeof (uint32_t)); + + +#define NDIS_FLAGS_SKIP_LOOPBACK_W2K 0x400 + +#include "../../VBoxNetFltInternal.h" +#include "VBoxNetFltRt-win.h" +#ifndef VBOXNETADP +# include "VBoxNetFltP-win.h" +#endif +#include "VBoxNetFltM-win.h" + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltCmn_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp new file mode 100644 index 00000000..65bde454 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp @@ -0,0 +1,1570 @@ +/* $Id: VBoxNetFltM-win.cpp $ */ +/** @file + * VBoxNetFltM-win.cpp - Bridged Networking Driver, Windows Specific Code. + * Miniport edge + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ +#include "VBoxNetFltCmn-win.h" + +static const char* vboxNetFltWinMpDumpOid(ULONG oid); + +#ifndef VBOXNETADP +static NDIS_STATUS vboxNetFltWinMpInitialize(OUT PNDIS_STATUS OpenErrorStatus, + OUT PUINT SelectedMediumIndex, + IN PNDIS_MEDIUM MediumArray, + IN UINT MediumArraySize, + IN NDIS_HANDLE MiniportAdapterHandle, + IN NDIS_HANDLE WrapperConfigurationContext) +{ + RT_NOREF1(WrapperConfigurationContext); + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)NdisIMGetDeviceContext(MiniportAdapterHandle); + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + pNetFlt->u.s.WinIf.hMiniport = MiniportAdapterHandle; + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initializing); + /* the MP state should be already set to kVBoxNetDevOpState_Initializing, just a paranoia + * in case NDIS for some reason calls us in some irregular way */ + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initializing); + + NDIS_MEDIUM enmMedium = pNetFlt->u.s.WinIf.enmMedium; + if (enmMedium == NdisMediumWan) + enmMedium = NdisMedium802_3; + + UINT i = 0; + for (; i < MediumArraySize; i++) + { + if (MediumArray[i] == enmMedium) + { + *SelectedMediumIndex = i; + break; + } + } + + do + { + if (i != MediumArraySize) + { + NdisMSetAttributesEx(MiniportAdapterHandle, pNetFlt, 0, + NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT | + NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT| + NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER | + NDIS_ATTRIBUTE_DESERIALIZE | + NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND, + NdisInterfaceInternal /* 0 */); + + pNetFlt->u.s.WinIf.MpIndicatedMediaStatus = NDIS_STATUS_MEDIA_CONNECT; + Assert(vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD3); + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.MpState, NdisDeviceStateD0); + Assert(pNetFlt->u.s.WinIf.MpState.OpState == kVBoxNetDevOpState_Initializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initialized); + + Status = NDIS_STATUS_SUCCESS; + break; + } + else + { + Status = NDIS_STATUS_UNSUPPORTED_MEDIA; + } + + Assert(Status != NDIS_STATUS_SUCCESS); + Assert(vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD3); + Assert(pNetFlt->u.s.WinIf.MpState.OpState == kVBoxNetDevOpState_Initializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } while (0); + + NdisSetEvent(&pNetFlt->u.s.WinIf.MpInitCompleteEvent); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); + + *OpenErrorStatus = Status; + + return Status; +} + +/** + * process the packet send in a "passthru" mode + */ +static NDIS_STATUS vboxNetFltWinSendPassThru(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + , bool bNetFltActive +#endif + ) +{ + PNDIS_PACKET pMyPacket; + NDIS_STATUS Status = vboxNetFltWinPrepareSendPacket(pNetFlt, pPacket, &pMyPacket); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { +#if !defined(VBOX_LOOPBACK_USEFLAGS) /* || defined(DEBUG_NETFLT_PACKETS) */ +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (bNetFltActive) + vboxNetFltWinLbPutSendPacket(pNetFlt, pMyPacket, false /* bFromIntNet */); +# else + /* no need for the loop enqueue & check in a passthru mode , ndis will do everything for us */ +# endif +#endif + NdisSend(&Status, pNetFlt->u.s.WinIf.hBinding, pMyPacket); + if (Status != NDIS_STATUS_PENDING) + { + NdisIMCopySendCompletePerPacketInfo(pPacket, pMyPacket); +#if defined(VBOXNETFLT_NO_PACKET_QUEUE) && !defined(VBOX_LOOPBACK_USEFLAGS) + if (bNetFltActive) + vboxNetFltWinLbRemoveSendPacket(pNetFlt, pMyPacket); +#endif + NdisFreePacket(pMyPacket); + } + } + return Status; +} + +#else /* defined VBOXNETADP */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoDeinitialization(PVBOXNETFLTINS pNetFlt) +{ + uint64_t NanoTS = RTTimeSystemNanoTS(); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); + + RTSpinlockAcquire(pNetFlt->hSpinlock); + ASMAtomicUoWriteBool(&pNetFlt->fDisconnectedFromHost, true); + ASMAtomicUoWriteBool(&pNetFlt->fRediscoveryPending, false); + ASMAtomicUoWriteU64(&pNetFlt->NanoTSLastRediscovery, NanoTS); + + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + + RTSpinlockRelease(pNetFlt->hSpinlock); + + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.MpState); + + /* check packet pool is empty */ + int cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(cPPUsage == 0); + /* for debugging only, ignore the err in release */ + NOREF(cPPUsage); + + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS vboxNetFltWinMpReadApplyConfig(PVBOXNETFLTINS pThis, NDIS_HANDLE hMiniportAdapter, + NDIS_HANDLE hWrapperConfigurationContext) +{ + RT_NOREF1(hMiniportAdapter); + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + NDIS_HANDLE hConfiguration; + PNDIS_CONFIGURATION_PARAMETER pParameterValue; + NDIS_STRING strMAC = NDIS_STRING_CONST("MAC"); + RTMAC mac; + + NdisOpenConfiguration( + &Status, + &hConfiguration, + hWrapperConfigurationContext); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + do + { + int rc; + NDIS_CONFIGURATION_PARAMETER param; + WCHAR MacBuf[13]; + + NdisReadConfiguration(&Status, + &pParameterValue, + hConfiguration, + &strMAC, + NdisParameterString); +// Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + + rc = vboxNetFltWinMACFromNdisString(&mac, &pParameterValue->ParameterData.StringData); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + break; + } + } + + vboxNetFltWinGenerateMACAddress(&mac); + param.ParameterType = NdisParameterString; + param.ParameterData.StringData.Buffer = MacBuf; + param.ParameterData.StringData.MaximumLength = sizeof(MacBuf); + + rc = vboxNetFltWinMAC2NdisString(&mac, ¶m.ParameterData.StringData); + Assert(RT_SUCCESS(rc)); + if (RT_SUCCESS(rc)) + { + NdisWriteConfiguration(&Status, + hConfiguration, + &strMAC, + ¶m); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status != NDIS_STATUS_SUCCESS) + { + /* ignore the failure */ + Status = NDIS_STATUS_SUCCESS; + } + } + } while (0); + + NdisCloseConfiguration(hConfiguration); + } + else + { + vboxNetFltWinGenerateMACAddress(&mac); + } + + pThis->u.s.MacAddr = mac; + + return NDIS_STATUS_SUCCESS; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoInitialization(PVBOXNETFLTINS pNetFlt, NDIS_HANDLE hMiniportAdapter, NDIS_HANDLE hWrapperConfigurationContext) +{ + NDIS_STATUS Status; + pNetFlt->u.s.WinIf.hMiniport = hMiniportAdapter; + + LogFlowFunc(("ENTER: pNetFlt 0x%p\n", pNetFlt)); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initializing); + + vboxNetFltWinMpReadApplyConfig(pNetFlt, hMiniportAdapter, hWrapperConfigurationContext); + + NdisMSetAttributesEx(hMiniportAdapter, pNetFlt, + 0, /* CheckForHangTimeInSeconds */ + NDIS_ATTRIBUTE_DESERIALIZE | + NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND, + NdisInterfaceInternal/* 0 */); + + Assert(vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD3); + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.MpState, NdisDeviceStateD0); + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initialized); + + Status = NDIS_STATUS_SUCCESS; + + LogFlowFunc(("pNetFlt 0x%p, Status 0x%x\n", pNetFlt, Status)); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinMpInitialize(OUT PNDIS_STATUS OpenErrorStatus, + OUT PUINT SelectedMediumIndex, + IN PNDIS_MEDIUM MediumArray, + IN UINT MediumArraySize, + IN NDIS_HANDLE MiniportAdapterHandle, + IN NDIS_HANDLE WrapperConfigurationContext) +{ + + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + UINT i = 0; + + LogFlowFuncEnter(); + + for (; i < MediumArraySize; i++) + { + if (MediumArray[i] == NdisMedium802_3) + { + *SelectedMediumIndex = i; + break; + } + } + + if (i != MediumArraySize) + { + PDEVICE_OBJECT pPdo, pFdo; +#define KEY_PREFIX L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class\\" + UCHAR Buf[512]; + PUCHAR pSuffix; + ULONG cbBuf; + NDIS_STRING RtlStr; + + wcscpy((WCHAR*)Buf, KEY_PREFIX); + pSuffix = Buf + (sizeof(KEY_PREFIX)-2); + + NdisMGetDeviceProperty(MiniportAdapterHandle, + &pPdo, + &pFdo, + NULL, //Next Device Object + NULL, + NULL); + + Status = IoGetDeviceProperty (pPdo, + DevicePropertyDriverKeyName, + sizeof(Buf) - (sizeof(KEY_PREFIX)-2), + pSuffix, + &cbBuf); + if (Status == STATUS_SUCCESS) + { + OBJECT_ATTRIBUTES ObjAttr; + HANDLE hDrvKey; + RtlStr.Buffer=(WCHAR*)Buf; + RtlStr.Length=(USHORT)cbBuf - 2 + sizeof(KEY_PREFIX) - 2; + RtlStr.MaximumLength=sizeof(Buf); + + InitializeObjectAttributes(&ObjAttr, &RtlStr, OBJ_CASE_INSENSITIVE, NULL, NULL); + + Status = ZwOpenKey(&hDrvKey, KEY_READ, &ObjAttr); + if (Status == STATUS_SUCCESS) + { + static UNICODE_STRING NetCfgInstanceIdValue = NDIS_STRING_CONST("NetCfgInstanceId"); +// UCHAR valBuf[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + RTUUID_STR_LENGTH*2 + 10]; +// ULONG cLength = sizeof(valBuf); +#define NAME_PREFIX L"\\DEVICE\\" + PKEY_VALUE_PARTIAL_INFORMATION pInfo = (PKEY_VALUE_PARTIAL_INFORMATION)Buf; + Status = ZwQueryValueKey(hDrvKey, + &NetCfgInstanceIdValue, + KeyValuePartialInformation, + pInfo, + sizeof(Buf), + &cbBuf); + if (Status == STATUS_SUCCESS) + { + if (pInfo->Type == REG_SZ && pInfo->DataLength > 2) + { + WCHAR *pName; + Status = vboxNetFltWinMemAlloc((PVOID*)&pName, pInfo->DataLength + sizeof(NAME_PREFIX)); + if (Status == STATUS_SUCCESS) + { + PVBOXNETFLTINS pNetFlt; + wcscpy(pName, NAME_PREFIX); + wcscpy(pName+(sizeof(NAME_PREFIX)-2)/2, (WCHAR*)pInfo->Data); + RtlStr.Buffer=pName; + RtlStr.Length = (USHORT)pInfo->DataLength - 2 + sizeof(NAME_PREFIX) - 2; + RtlStr.MaximumLength = (USHORT)pInfo->DataLength + sizeof(NAME_PREFIX); + + Status = vboxNetFltWinPtInitBind(&pNetFlt, MiniportAdapterHandle, &RtlStr, WrapperConfigurationContext); + + if (Status == STATUS_SUCCESS) + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initialized); +#if 0 + NdisMIndicateStatus(pNetFlt->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_CONNECT, + (PVOID)NULL, + 0); +#endif + } + else + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + + vboxNetFltWinMemFree(pName); + + } + } + else + { + Status = NDIS_STATUS_FAILURE; + } + } + } + } + } + else + { + Status = NDIS_STATUS_UNSUPPORTED_MEDIA; + } + + /** @todo */ + *OpenErrorStatus = Status; + + LogFlowFunc(("LEAVE: Status (0x%x)\n", Status)); + + return Status; +} +#endif + +static VOID vboxNetFltWinMpSendPackets(IN NDIS_HANDLE hMiniportAdapterContext, + IN PPNDIS_PACKET pPacketArray, + IN UINT cNumberOfPackets) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hMiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + bool bNetFltActive; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + Assert(cNumberOfPackets); + + if (vboxNetFltWinIncReferenceWinIfNetFlt(pNetFlt, cNumberOfPackets, &bNetFltActive)) + { + uint32_t cAdaptRefs = cNumberOfPackets; + uint32_t cNetFltRefs; + uint32_t cPassThruRefs; + if (bNetFltActive) + { + cNetFltRefs = cNumberOfPackets; + cPassThruRefs = 0; + } + else + { + cPassThruRefs = cNumberOfPackets; + cNetFltRefs = 0; + } + + for (UINT i = 0; i < cNumberOfPackets; i++) + { + PNDIS_PACKET pPacket; + + pPacket = pPacketArray[i]; + + if (!cNetFltRefs +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + || !vboxNetFltWinPostIntnet(pNetFlt, pPacket, VBOXNETFLT_PACKET_SRC_HOST) +#else + || (fStatus = vboxNetFltWinQuEnqueuePacket(pNetFlt, pPacket, VBOXNETFLT_PACKET_SRC_HOST)) != NDIS_STATUS_SUCCESS +#endif + ) + { +#ifndef VBOXNETADP + Status = vboxNetFltWinSendPassThru(pNetFlt, pPacket +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + , !!cNetFltRefs +#endif + ); +#else + if (!cNetFltRefs) + { +# ifdef VBOXNETADP_REPORT_DISCONNECTED + Status = NDIS_STATUS_MEDIA_DISCONNECT; + STATISTIC_INCREASE(pNetFlt->u.s.WinIf.cTxError); +# else + Status = NDIS_STATUS_SUCCESS; +# endif + } +#endif + + if (Status != NDIS_STATUS_PENDING) + { + NdisMSendComplete(pNetFlt->u.s.WinIf.hMiniport, pPacket, Status); + } + else + { + cAdaptRefs--; + } + } + else + { +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + NdisMSendComplete(pNetFlt->u.s.WinIf.hMiniport, pPacket, NDIS_STATUS_SUCCESS); +#else + cAdaptRefs--; + cNetFltRefs--; +#endif + } + } + + if (cNetFltRefs) + { + vboxNetFltWinDecReferenceNetFlt(pNetFlt, cNetFltRefs); + } + else if (cPassThruRefs) + { + vboxNetFltWinDecReferenceModePassThru(pNetFlt, cPassThruRefs); + } + if (cAdaptRefs) + { + vboxNetFltWinDecReferenceWinIf(pNetFlt, cAdaptRefs); + } + } + else + { + NDIS_HANDLE h = pNetFlt->u.s.WinIf.hMiniport; + AssertFailed(); + if (h) + { + for (UINT i = 0; i < cNumberOfPackets; i++) + { + PNDIS_PACKET pPacket; + pPacket = pPacketArray[i]; + NdisMSendComplete(h, pPacket, NDIS_STATUS_FAILURE); + } + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +#ifndef VBOXNETADP +static UINT vboxNetFltWinMpRequestStatePrep(PVBOXNETFLTINS pNetFlt, NDIS_STATUS *pStatus) +{ + Assert(!pNetFlt->u.s.WinIf.StateFlags.fRequestInfo); + + if (vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) > kVBoxNetDevOpState_Initialized /* protocol unbind in progress */ + || vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0) + { + *pStatus = NDIS_STATUS_FAILURE; + return 0; + } + + RTSpinlockAcquire(pNetFlt->hSpinlock); + Assert(!pNetFlt->u.s.WinIf.StateFlags.fRequestInfo); + if (vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) > kVBoxNetDevOpState_Initialized /* protocol unbind in progress */ + || vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + *pStatus = NDIS_STATUS_FAILURE; + return 0; + } + + if ((vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState) > NdisDeviceStateD0) + && !pNetFlt->u.s.WinIf.StateFlags.fStandBy) + { + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS | VBOXNDISREQUEST_QUEUED; + RTSpinlockRelease(pNetFlt->hSpinlock); + *pStatus = NDIS_STATUS_PENDING; + return VBOXNDISREQUEST_INPROGRESS | VBOXNDISREQUEST_QUEUED; + } + + if (pNetFlt->u.s.WinIf.StateFlags.fStandBy) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + *pStatus = NDIS_STATUS_FAILURE; + return 0; + } + + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS; + + RTSpinlockRelease(pNetFlt->hSpinlock); + + *pStatus = NDIS_STATUS_SUCCESS; + return VBOXNDISREQUEST_INPROGRESS; +} + +static NDIS_STATUS vboxNetFltWinMpRequestPostQuery(PVBOXNETFLTINS pNetFlt) +{ + if (pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER && VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + bool fNetFltActive; + const bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &fNetFltActive); + + Assert(pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBuffer); + Assert(!pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter); + + if (fNetFltActive) + { + /* netflt is active, simply return the cached value */ + *((PULONG)pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBuffer) = pNetFlt->u.s.WinIf.fUpperProtocolSetFilter; + + /* we've intercepted the query and completed it */ + vboxNetFltWinMpRequestStateComplete(pNetFlt); + + vboxNetFltWinDereferenceNetFlt(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + + return NDIS_STATUS_SUCCESS; + } + else if (fWinIfActive) + { + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 1; + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 0; + /* we're cleaning it in RequestComplete */ + } + } + + NDIS_STATUS Status; + /* issue the request */ + NdisRequest(&Status, pNetFlt->u.s.WinIf.hBinding, &pNetFlt->u.s.WinIf.PassDownRequest); + if (Status != NDIS_STATUS_PENDING) + { + vboxNetFltWinPtRequestComplete(pNetFlt, &pNetFlt->u.s.WinIf.PassDownRequest, Status); + Status = NDIS_STATUS_PENDING; + } + + return Status; +} + +static NDIS_STATUS vboxNetFltWinMpQueryInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesWritten, + OUT PULONG BytesNeeded) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + /* fist check if this is the oid we want to pass down */ + switch (Oid) + { + case OID_PNP_QUERY_POWER: + Status = NDIS_STATUS_SUCCESS; + break; + case OID_TCP_TASK_OFFLOAD: + case OID_GEN_SUPPORTED_GUIDS: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + default: + { + /* the oid is to be passed down, + * check the device state if we can do it + * and update device state accordingly */ + UINT uOp = vboxNetFltWinMpRequestStatePrep(pNetFlt, &Status); + if (uOp) + { + /* save the request info */ + pNetFlt->u.s.WinIf.PassDownRequest.RequestType = NdisRequestQueryInformation; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.Oid = Oid; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBuffer = InformationBuffer; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBufferLength = InformationBufferLength; + pNetFlt->u.s.WinIf.pcPDRBytesNeeded = BytesNeeded; + pNetFlt->u.s.WinIf.pcPDRBytesRW = BytesWritten; + + /* the oid can be processed */ + if (!(uOp & VBOXNDISREQUEST_QUEUED)) + { + Status = vboxNetFltWinMpRequestPostQuery(pNetFlt); + } + } + break; + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} + +#endif /* ifndef VBOXNETADP*/ + +static NDIS_STATUS vboxNetFltWinMpHandlePowerState(PVBOXNETFLTINS pNetFlt, NDIS_DEVICE_POWER_STATE enmState) +{ + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0 + && enmState != NdisDeviceStateD0) + { + /* invalid state transformation */ + AssertFailed(); + return NDIS_STATUS_FAILURE; + } + +#ifndef VBOXNETADP + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD0 + && enmState > NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = TRUE; + } + + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0 + && enmState == NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = FALSE; + } +#endif + + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.MpState, enmState); + +#ifndef VBOXNETADP + if (pNetFlt->u.s.WinIf.StateFlags.fStandBy == FALSE) + { + if (pNetFlt->u.s.WinIf.MpIndicatedMediaStatus != pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus) + { + NdisMIndicateStatus(pNetFlt->u.s.WinIf.hMiniport, pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus, NULL, 0); + NdisMIndicateStatusComplete(pNetFlt->u.s.WinIf.hMiniport); + pNetFlt->u.s.WinIf.MpIndicatedMediaStatus = pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus; + } + } + else + { + pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus = pNetFlt->u.s.WinIf.MpIndicatedMediaStatus; + } +#endif + + return NDIS_STATUS_SUCCESS; +} + +#ifndef VBOXNETADP +static NDIS_STATUS vboxNetFltWinMpRequestPostSet(PVBOXNETFLTINS pNetFlt) +{ + if (pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER && VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + /* need to disable cleaning promiscuous here ?? */ + bool fNetFltActive; + const bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &fNetFltActive); + + Assert(pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer); + Assert(!pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter); + + if (fNetFltActive) + { + Assert(fWinIfActive); + + /* netflt is active, update the cached value */ + /** @todo in case we are are not in promiscuous now, we are issuing a request. + * what should we do in case of a failure? + * i.e. should we update the fUpperProtocolSetFilter in completion routine in this case? etc. */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = *((PULONG)pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer); + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + + if (!(pNetFlt->u.s.WinIf.fOurSetFilter & NDIS_PACKET_TYPE_PROMISCUOUS)) + { + pNetFlt->u.s.WinIf.fSetFilterBuffer = NDIS_PACKET_TYPE_PROMISCUOUS; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer = &pNetFlt->u.s.WinIf.fSetFilterBuffer; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBufferLength = sizeof (pNetFlt->u.s.WinIf.fSetFilterBuffer); + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 1; + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 1; + /* we'll do dereferencing in request complete */ + } + else + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + + /* we've intercepted the query and completed it */ + vboxNetFltWinMpRequestStateComplete(pNetFlt); + return NDIS_STATUS_SUCCESS; + } + } + else if (fWinIfActive) + { + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 1; + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 0; + /* dereference on completion */ + } + } + + NDIS_STATUS Status; + + NdisRequest(&Status, pNetFlt->u.s.WinIf.hBinding, &pNetFlt->u.s.WinIf.PassDownRequest); + if (Status != NDIS_STATUS_PENDING) + { + vboxNetFltWinPtRequestComplete(pNetFlt, &pNetFlt->u.s.WinIf.PassDownRequest, Status); + } + + return Status; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRequestPost(PVBOXNETFLTINS pNetFlt) +{ + switch (pNetFlt->u.s.WinIf.PassDownRequest.RequestType) + { + case NdisRequestQueryInformation: + return vboxNetFltWinMpRequestPostQuery(pNetFlt); + case NdisRequestSetInformation: + return vboxNetFltWinMpRequestPostSet(pNetFlt); + default: + AssertBreakpoint(); + return NDIS_STATUS_FAILURE; + } +} + +static NDIS_STATUS vboxNetFltWinMpSetInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesRead, + OUT PULONG BytesNeeded) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + switch (Oid) + { + case OID_PNP_SET_POWER: + { + if (InformationBufferLength >= sizeof (NDIS_DEVICE_POWER_STATE)) + { + NDIS_DEVICE_POWER_STATE *penmState = (NDIS_DEVICE_POWER_STATE*)InformationBuffer; + Status = vboxNetFltWinMpHandlePowerState(pNetFlt, *penmState); + } + else + { + Status = NDIS_STATUS_INVALID_LENGTH; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + *BytesRead = sizeof (NDIS_DEVICE_POWER_STATE); + *BytesNeeded = 0; + } + else + { + *BytesRead = 0; + *BytesNeeded = sizeof (NDIS_DEVICE_POWER_STATE); + } + break; + } + default: + { + /* the oid is to be passed down, + * check the device state if we can do it + * and update device state accordingly */ + UINT uOp = vboxNetFltWinMpRequestStatePrep(pNetFlt, &Status); + if (uOp) + { + /* save the request info */ + pNetFlt->u.s.WinIf.PassDownRequest.RequestType = NdisRequestSetInformation; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.Oid = Oid; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer = InformationBuffer; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBufferLength = InformationBufferLength; + pNetFlt->u.s.WinIf.pcPDRBytesNeeded = BytesNeeded; + pNetFlt->u.s.WinIf.pcPDRBytesRW = BytesRead; + + /* the oid can be processed */ + if (!(uOp & VBOXNDISREQUEST_QUEUED)) + { + Status = vboxNetFltWinMpRequestPostSet(pNetFlt); + } + } + break; + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} +#else +static NDIS_OID g_vboxNetFltWinMpSupportedOids[] = +{ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_LOOKAHEAD, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_MAC_OPTIONS, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_CURRENT_PACKET_FILTER, + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, + OID_PNP_SET_POWER, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAC_OPTIONS, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, +}; + +static NDIS_STATUS vboxNetFltWinMpQueryInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesWritten, + OUT PULONG BytesNeeded) +{ + /* static data */ + static const NDIS_HARDWARE_STATUS enmHwStatus = NdisHardwareStatusReady; + static const NDIS_MEDIUM enmMedium = NdisMedium802_3; + static NDIS_PNP_CAPABILITIES PnPCaps = {0}; + static BOOLEAN bPnPCapsInited = FALSE; + + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + ULONG64 u64Info = 0; + ULONG u32Info = 0; + USHORT u16Info = 0; + /* default is 4bytes */ + const void* pvInfo = (void*)&u32Info; + ULONG cbInfo = sizeof (u32Info); + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + *BytesWritten = 0; + *BytesNeeded = 0; + + switch (Oid) + { + case OID_GEN_SUPPORTED_LIST: + pvInfo = g_vboxNetFltWinMpSupportedOids; + cbInfo = sizeof (g_vboxNetFltWinMpSupportedOids); + break; + + case OID_GEN_HARDWARE_STATUS: + pvInfo = &enmHwStatus; + cbInfo = sizeof (NDIS_HARDWARE_STATUS); + break; + + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + pvInfo = &enmMedium; + cbInfo = sizeof (NDIS_MEDIUM); + break; + + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_CURRENT_LOOKAHEAD: + u32Info = VBOXNETADP_MAX_LOOKAHEAD_SIZE; + break; + + case OID_GEN_MAXIMUM_FRAME_SIZE: + u32Info = VBOXNETADP_MAX_PACKET_SIZE - VBOXNETADP_HEADER_SIZE; + break; + + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + u32Info = VBOXNETADP_MAX_PACKET_SIZE; + break; + + case OID_GEN_MAC_OPTIONS: + u32Info = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK; + break; + + case OID_GEN_LINK_SPEED: + u32Info = VBOXNETADP_LINK_SPEED; + break; + + case OID_GEN_TRANSMIT_BUFFER_SPACE: + case OID_GEN_RECEIVE_BUFFER_SPACE: + u32Info = VBOXNETADP_MAX_PACKET_SIZE * VBOXNETFLT_PACKET_INFO_POOL_SIZE; + break; + + case OID_GEN_VENDOR_ID: + u32Info = VBOXNETADP_VENDOR_ID; + break; + + case OID_GEN_VENDOR_DESCRIPTION: + pvInfo = VBOXNETADP_VENDOR_DESC; + cbInfo = sizeof (VBOXNETADP_VENDOR_DESC); + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + u32Info = VBOXNETADP_VENDOR_DRIVER_VERSION; + break; + + case OID_GEN_DRIVER_VERSION: + u16Info = (USHORT)((VBOXNETFLT_VERSION_MP_NDIS_MAJOR << 8) + VBOXNETFLT_VERSION_MP_NDIS_MINOR); + pvInfo = (PVOID)&u16Info; + cbInfo = sizeof (USHORT); + break; + + case OID_GEN_MAXIMUM_SEND_PACKETS: + u32Info = VBOXNETFLT_PACKET_INFO_POOL_SIZE; + break; + + case OID_GEN_MEDIA_CONNECT_STATUS: +#ifdef VBOXNETADP_REPORT_DISCONNECTED + { + bool bNetFltActive; + bool bActive = vboxNetFltWinReferenceWinIfNetFltFromAdapt(pNetFlt, bNetFltActive); + if (bActive && bNetFltActive) + { + u32Info = NdisMediaStateConnected; + } + else + { + u32Info = NdisMediaStateDisconnected; + } + + if (bActive) + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + if (bNetFltActive) + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else + { + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + } +#else + u32Info = NdisMediaStateConnected; +#endif + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + u32Info = NDIS_PACKET_TYPE_BROADCAST + | NDIS_PACKET_TYPE_DIRECTED + | NDIS_PACKET_TYPE_ALL_FUNCTIONAL + | NDIS_PACKET_TYPE_ALL_LOCAL + | NDIS_PACKET_TYPE_GROUP + | NDIS_PACKET_TYPE_MULTICAST; + break; + + case OID_PNP_CAPABILITIES: + if (!bPnPCapsInited) + { + PnPCaps.WakeUpCapabilities.MinMagicPacketWakeUp = NdisDeviceStateUnspecified; + PnPCaps.WakeUpCapabilities.MinPatternWakeUp = NdisDeviceStateUnspecified; + bPnPCapsInited = TRUE; + } + cbInfo = sizeof (NDIS_PNP_CAPABILITIES); + pvInfo = &PnPCaps; + + break; + + case OID_PNP_QUERY_POWER: + Status = NDIS_STATUS_SUCCESS; + break; + + case OID_GEN_XMIT_OK: + u64Info = pNetFlt->u.s.WinIf.cTxSuccess; + pvInfo = &u64Info; + if (InformationBufferLength >= sizeof (ULONG64) || InformationBufferLength == 0) + { + cbInfo = sizeof (ULONG64); + } + else + { + cbInfo = sizeof (ULONG); + } + *BytesNeeded = sizeof (ULONG64); + break; + + case OID_GEN_RCV_OK: + u64Info = pNetFlt->u.s.WinIf.cRxSuccess; + pvInfo = &u64Info; + if (InformationBufferLength >= sizeof (ULONG64) || InformationBufferLength == 0) + { + cbInfo = sizeof (ULONG64); + } + else + { + cbInfo = sizeof (ULONG); + } + *BytesNeeded = sizeof (ULONG64); + break; + + case OID_GEN_XMIT_ERROR: + u32Info = pNetFlt->u.s.WinIf.cTxError; + break; + + case OID_GEN_RCV_ERROR: + u32Info = pNetFlt->u.s.WinIf.cRxError; + break; + + case OID_GEN_RCV_NO_BUFFER: + case OID_GEN_RCV_CRC_ERROR: + u32Info = 0; + break; + + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + u32Info = VBOXNETFLT_PACKET_INFO_POOL_SIZE; + break; + + case OID_802_3_PERMANENT_ADDRESS: + pvInfo = &pNetFlt->u.s.MacAddr; + cbInfo = VBOXNETADP_ETH_ADDRESS_LENGTH; + break; + + case OID_802_3_CURRENT_ADDRESS: + pvInfo = &pNetFlt->u.s.MacAddr; + cbInfo = VBOXNETADP_ETH_ADDRESS_LENGTH; + break; + + case OID_802_3_MAXIMUM_LIST_SIZE: + u32Info = VBOXNETADP_MAX_MCAST_LIST; + break; + + case OID_802_3_MAC_OPTIONS: + case OID_802_3_RCV_ERROR_ALIGNMENT: + case OID_802_3_XMIT_ONE_COLLISION: + case OID_802_3_XMIT_MORE_COLLISIONS: + case OID_802_3_XMIT_DEFERRED: + case OID_802_3_XMIT_MAX_COLLISIONS: + case OID_802_3_RCV_OVERRUN: + case OID_802_3_XMIT_UNDERRUN: + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + case OID_802_3_XMIT_TIMES_CRS_LOST: + case OID_802_3_XMIT_LATE_COLLISIONS: + u32Info = 0; + break; + + default: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + if (cbInfo <= InformationBufferLength) + { + *BytesWritten = cbInfo; + if (cbInfo) + NdisMoveMemory(InformationBuffer, pvInfo, cbInfo); + } + else + { + *BytesNeeded = cbInfo; + Status = NDIS_STATUS_INVALID_LENGTH; + } + } + + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinMpSetInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesRead, + OUT PULONG BytesNeeded) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS) MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + *BytesRead = 0; + *BytesNeeded = 0; + + switch (Oid) + { + case OID_802_3_MULTICAST_LIST: + *BytesRead = InformationBufferLength; + if (InformationBufferLength % VBOXNETADP_ETH_ADDRESS_LENGTH) + { + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + if (InformationBufferLength > (VBOXNETADP_MAX_MCAST_LIST * VBOXNETADP_ETH_ADDRESS_LENGTH)) + { + Status = NDIS_STATUS_MULTICAST_FULL; + *BytesNeeded = VBOXNETADP_MAX_MCAST_LIST * VBOXNETADP_ETH_ADDRESS_LENGTH; + break; + } + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + if (InformationBufferLength != sizeof (ULONG)) + { + *BytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + *BytesRead = InformationBufferLength; + + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + if (InformationBufferLength != sizeof (ULONG)){ + *BytesNeeded = sizeof(ULONG); + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + break; + + case OID_PNP_SET_POWER: + if (InformationBufferLength >= sizeof(NDIS_DEVICE_POWER_STATE)) + { + NDIS_DEVICE_POWER_STATE *penmState = (NDIS_DEVICE_POWER_STATE*)InformationBuffer; + Status = vboxNetFltWinMpHandlePowerState(pNetFlt, *penmState); + } + else + { + Status = NDIS_STATUS_INVALID_LENGTH; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + *BytesRead = sizeof (NDIS_DEVICE_POWER_STATE); + *BytesNeeded = 0; + } + else + { + *BytesRead = 0; + *BytesNeeded = sizeof (NDIS_DEVICE_POWER_STATE); + } + break; + + default: + Status = NDIS_STATUS_INVALID_OID; + break; + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} + +#endif + +#define VBOXNETFLTDUMP_STRCASE(_t) \ + case _t: return #_t; +#define VBOXNETFLTDUMP_STRCASE_UNKNOWN() \ + default: /*AssertFailed();*/ return "Unknown"; + +static const char* vboxNetFltWinMpDumpOid(ULONG oid) +{ + switch (oid) + { + VBOXNETFLTDUMP_STRCASE(OID_GEN_SUPPORTED_LIST) + VBOXNETFLTDUMP_STRCASE(OID_GEN_HARDWARE_STATUS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_SUPPORTED) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_IN_USE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_LOOKAHEAD) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_FRAME_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_LINK_SPEED) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSMIT_BUFFER_SPACE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RECEIVE_BUFFER_SPACE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSMIT_BLOCK_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RECEIVE_BLOCK_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VENDOR_ID) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VENDOR_DESCRIPTION) + VBOXNETFLTDUMP_STRCASE(OID_GEN_CURRENT_PACKET_FILTER) + VBOXNETFLTDUMP_STRCASE(OID_GEN_CURRENT_LOOKAHEAD) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DRIVER_VERSION) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_TOTAL_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_PROTOCOL_OPTIONS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAC_OPTIONS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_CONNECT_STATUS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_SEND_PACKETS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VENDOR_DRIVER_VERSION) + VBOXNETFLTDUMP_STRCASE(OID_GEN_SUPPORTED_GUIDS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_NETWORK_LAYER_ADDRESSES) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSPORT_HEADER_OFFSET) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MACHINE_NAME) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RNDIS_CONFIG_PARAMETER) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VLAN_ID) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_CAPABILITIES) + VBOXNETFLTDUMP_STRCASE(OID_GEN_PHYSICAL_MEDIUM) + VBOXNETFLTDUMP_STRCASE(OID_GEN_XMIT_OK) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_OK) + VBOXNETFLTDUMP_STRCASE(OID_GEN_XMIT_ERROR) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_ERROR) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_NO_BUFFER) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_BYTES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_FRAMES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_BYTES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_FRAMES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_BYTES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_FRAMES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_BYTES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_FRAMES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_BYTES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_FRAMES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_BYTES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_FRAMES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_CRC_ERROR) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSMIT_QUEUE_LENGTH) + VBOXNETFLTDUMP_STRCASE(OID_GEN_GET_TIME_CAPS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_GET_NETCARD_TIME) + VBOXNETFLTDUMP_STRCASE(OID_GEN_NETCARD_LOAD) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DEVICE_PROFILE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_INIT_TIME_MS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RESET_COUNTS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_SENSE_COUNTS) + VBOXNETFLTDUMP_STRCASE(OID_PNP_CAPABILITIES) + VBOXNETFLTDUMP_STRCASE(OID_PNP_SET_POWER) + VBOXNETFLTDUMP_STRCASE(OID_PNP_QUERY_POWER) + VBOXNETFLTDUMP_STRCASE(OID_PNP_ADD_WAKE_UP_PATTERN) + VBOXNETFLTDUMP_STRCASE(OID_PNP_REMOVE_WAKE_UP_PATTERN) + VBOXNETFLTDUMP_STRCASE(OID_PNP_ENABLE_WAKE_UP) + VBOXNETFLTDUMP_STRCASE(OID_802_3_PERMANENT_ADDRESS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_CURRENT_ADDRESS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_MULTICAST_LIST) + VBOXNETFLTDUMP_STRCASE(OID_802_3_MAXIMUM_LIST_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_802_3_MAC_OPTIONS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_RCV_ERROR_ALIGNMENT) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_ONE_COLLISION) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_MORE_COLLISIONS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_DEFERRED) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_MAX_COLLISIONS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_RCV_OVERRUN) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_UNDERRUN) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_HEARTBEAT_FAILURE) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_TIMES_CRS_LOST) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_LATE_COLLISIONS) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_OFFLOAD) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_ADD_SA) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_DELETE_SA) + VBOXNETFLTDUMP_STRCASE(OID_TCP_SAN_SUPPORT) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_ADD_UDPESP_SA) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_DELETE_UDPESP_SA) + VBOXNETFLTDUMP_STRCASE_UNKNOWN() + } +} + +DECLHIDDEN(VOID) vboxNetFltWinMpReturnPacket(IN NDIS_HANDLE hMiniportAdapterContext, IN PNDIS_PACKET pPacket) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hMiniportAdapterContext; + PVBOXNETFLT_PKTRSVD_MP pInfo = (PVBOXNETFLT_PKTRSVD_MP)pPacket->MiniportReserved; + PNDIS_PACKET pOrigPacket = pInfo->pOrigPacket; + PVOID pBufToFree = pInfo->pBufToFree; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (pOrigPacket) + { + /* the packet was sent from underlying miniport */ + NdisFreePacket(pPacket); + NdisReturnPackets(&pOrigPacket, 1); + } + else + { + /* the packet was sent from IntNet or it is a packet we allocated on PtReceive for TransferData processing */ + vboxNetFltWinFreeSGNdisPacket(pPacket, !pBufToFree /* bFreeMem */); + } + + if (pBufToFree) + { + vboxNetFltWinMemFree(pBufToFree); + } + + vboxNetFltWinDereferenceWinIf(pNetFlt); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static NDIS_STATUS vboxNetFltWinMpTransferData(OUT PNDIS_PACKET Packet, + OUT PUINT BytesTransferred, + IN NDIS_HANDLE hContext, + IN NDIS_HANDLE MiniportReceiveContext, + IN UINT ByteOffset, + IN UINT BytesToTransfer) +{ +#ifndef VBOXNETADP + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + NDIS_STATUS Status; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if ( vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState) != NdisDeviceStateD0 + || vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) != NdisDeviceStateD0) + { + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, NDIS_STATUS_FAILURE)); + return NDIS_STATUS_FAILURE; + } + + NdisTransferData(&Status, pNetFlt->u.s.WinIf.hBinding, MiniportReceiveContext, + ByteOffset, BytesToTransfer, Packet, BytesTransferred); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); + return Status; + +#else + RT_NOREF6(Packet, BytesTransferred, hContext, MiniportReceiveContext, ByteOffset, BytesToTransfer); + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", hContext)); + /* should never be here */ + AssertFailed(); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", hContext, NDIS_STATUS_FAILURE)); + return NDIS_STATUS_FAILURE; +#endif +} + +static void vboxNetFltWinMpHalt(IN NDIS_HANDLE hContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + NDIS_STATUS Status; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + +#ifndef VBOXNETADP + if (vboxNetFltWinGetWinIfState(pNetFlt) == kVBoxWinIfState_Disconnecting) + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + + vboxNetFltWinPtCloseInterface(pNetFlt, &Status); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + else +#endif + { + /* we're NOT called from protocolUnbinAdapter, perform a full disconnect */ + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); +#ifndef VBOXNETADP + AssertBreakpoint(); +#endif + Status = vboxNetFltWinDetachFromInterface(pNetFlt, false); + Assert(Status == NDIS_STATUS_SUCCESS); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +/** + * register the miniport edge + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRegister(PVBOXNETFLTGLOBALS_MP pGlobalsMp, PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr) +{ + NDIS_MINIPORT_CHARACTERISTICS MpChars; + + NdisMInitializeWrapper(&pGlobalsMp->hNdisWrapper, pDriverObject, pRegistryPathStr, NULL); + + NdisZeroMemory(&MpChars, sizeof (MpChars)); + + MpChars.MajorNdisVersion = VBOXNETFLT_VERSION_MP_NDIS_MAJOR; + MpChars.MinorNdisVersion = VBOXNETFLT_VERSION_MP_NDIS_MINOR; + + MpChars.HaltHandler = vboxNetFltWinMpHalt; + MpChars.InitializeHandler = vboxNetFltWinMpInitialize; + MpChars.QueryInformationHandler = vboxNetFltWinMpQueryInformation; + MpChars.SetInformationHandler = vboxNetFltWinMpSetInformation; + MpChars.TransferDataHandler = vboxNetFltWinMpTransferData; + MpChars.ReturnPacketHandler = vboxNetFltWinMpReturnPacket; + MpChars.SendPacketsHandler = vboxNetFltWinMpSendPackets; + +#ifndef VBOXNETADP + NDIS_STATUS Status = NdisIMRegisterLayeredMiniport(pGlobalsMp->hNdisWrapper, &MpChars, sizeof (MpChars), &pGlobalsMp->hMiniport); +#else + NDIS_STATUS Status = NdisMRegisterMiniport(pGlobalsMp->hNdisWrapper, &MpChars, sizeof (MpChars)); +#endif + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + NdisMRegisterUnloadHandler(pGlobalsMp->hNdisWrapper, vboxNetFltWinUnload); + } + + return Status; +} + +/** + * deregister the miniport edge + */ +DECLHIDDEN(VOID) vboxNetFltWinMpDeregister(PVBOXNETFLTGLOBALS_MP pGlobalsMp) +{ +#ifndef VBOXNETADP + NdisIMDeregisterLayeredMiniport(pGlobalsMp->hMiniport); +#endif + NdisTerminateWrapper(pGlobalsMp->hNdisWrapper, NULL); + + NdisZeroMemory(pGlobalsMp, sizeof (*pGlobalsMp)); +} + +#ifndef VBOXNETADP + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpInitializeDevideInstance(PVBOXNETFLTINS pThis) +{ + NDIS_STATUS Status; + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Initializing); + + Status = NdisIMInitializeDeviceInstanceEx(g_VBoxNetFltGlobalsWin.Mp.hMiniport, + &pThis->u.s.WinIf.MpDeviceName, + pThis); + if (Status == NDIS_STATUS_SUCCESS) + { + if (pThis->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) + { + return NDIS_STATUS_SUCCESS; + } + AssertBreakpoint(); + vboxNetFltWinMpDeInitializeDeviceInstance(pThis, &Status); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + return pThis->u.s.WinIf.OpenCloseStatus; + } + + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + + return Status; +} + +DECLHIDDEN(bool) vboxNetFltWinMpDeInitializeDeviceInstance(PVBOXNETFLTINS pThis, PNDIS_STATUS pStatus) +{ + NDIS_STATUS Status; + + if (vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initializing) + { + Status = NdisIMCancelInitializeDeviceInstance(g_VBoxNetFltGlobalsWin.Mp.hMiniport, &pThis->u.s.WinIf.MpDeviceName); + if (Status == NDIS_STATUS_SUCCESS) + { + /* we've canceled the initialization successfully */ + Assert(pThis->u.s.WinIf.hMiniport == NULL); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + else + NdisWaitEvent(&pThis->u.s.WinIf.MpInitCompleteEvent, 0); + } + else + Status = NDIS_STATUS_SUCCESS; + + Assert( vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized + || vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + if (vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized) + { + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + + Status = NdisIMDeInitializeDeviceInstance(pThis->u.s.WinIf.hMiniport); + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + if (Status != NDIS_STATUS_SUCCESS) + Status = NDIS_STATUS_FAILURE; + + *pStatus = Status; + return true; + } + + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + + *pStatus = Status; + return false; +} + +#endif /* !VBOXNETADP */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h new file mode 100644 index 00000000..7a904aa8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h @@ -0,0 +1,67 @@ +/* $Id: VBoxNetFltM-win.h $ */ +/** @file + * VBoxNetFltM-win.h - Bridged Networking Driver, Windows Specific Code - Miniport edge API + */ + +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltM_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltM_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRegister(PVBOXNETFLTGLOBALS_MP pGlobalsMp, PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr); +DECLHIDDEN(VOID) vboxNetFltWinMpDeregister(PVBOXNETFLTGLOBALS_MP pGlobalsMp); +DECLHIDDEN(VOID) vboxNetFltWinMpReturnPacket(IN NDIS_HANDLE hMiniportAdapterContext, IN PNDIS_PACKET pPacket); + +#ifdef VBOXNETADP +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoInitialization(PVBOXNETFLTINS pThis, NDIS_HANDLE hMiniportAdapter, NDIS_HANDLE hWrapperConfigurationContext); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoDeinitialization(PVBOXNETFLTINS pThis); + +#else + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpInitializeDevideInstance(PVBOXNETFLTINS pThis); +DECLHIDDEN(bool) vboxNetFltWinMpDeInitializeDeviceInstance(PVBOXNETFLTINS pThis, PNDIS_STATUS pStatus); + +DECLINLINE(VOID) vboxNetFltWinMpRequestStateComplete(PVBOXNETFLTINS pNetFlt) +{ + RTSpinlockAcquire(pNetFlt->hSpinlock); + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = 0; + RTSpinlockRelease(pNetFlt->hSpinlock); +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRequestPost(PVBOXNETFLTINS pNetFlt); +#endif + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltM_win_h */ + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf new file mode 100644 index 00000000..fcf30fc8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf @@ -0,0 +1,82 @@ +; $Id: VBoxNetFltM.inf $ +;; @file +; VBoxNetFltM.inf - VirtualBox Bridged Networking Driver inf file Miniport edge +; + +; +; Copyright (C) 2011-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 <https://www.gnu.org/licenses>. +; +; 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 +; + +[Version] +signature = "$Windows NT$" +;cat CatalogFile = VBoxNetFlt.cat +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %ORACLE% +;DriverPackageType=NdisImMiniport +;DriverPackageDisplayName=%VBoxNetFltMP_Desc% +;edit-DriverVer=08/13/2008,1.1.0.1 + +[ControlFlags] +ExcludeFromSelect = sun_VBoxNetFltmp + +[DestinationDirs] +DefaultDestDir=12 +; No files to copy + +[Manufacturer] +%ORACLE% = VBoxNetFltMP@COMMA-NT-ARCH@ + +[VBoxNetFltMP@DOT-NT-ARCH@] +%VBoxNetFltMP_Desc% = VBoxNetFltMP.ndi, sun_VBoxNetFltmp + +[VBoxNetFltMP.ndi] +Characteristics = 0x29 ;NCF_NOT_USER_REMOVABLE | NCF_VIRTUAL | NCF_HIDDEN +CopyFiles = + +[VBoxNetFltMP.ndi.Services] +AddService = VBoxNetFlt,0x2, VBoxNetFltMP.AddService + +[VBoxNetFltMP.AddService] +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetFlt.sys + +[VBoxNetFltMP.AddService.AddReg] + +[Strings] +ORACLE = "Oracle Corporation" +VBoxNetFltMP_Desc = "VirtualBox Bridged Networking Driver Miniport" + +[SourceDisksNames] + +[SourceDisksFiles] + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp new file mode 100644 index 00000000..3e06f3c2 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp @@ -0,0 +1,1595 @@ +/* $Id: VBoxNetFltP-win.cpp $ */ +/** @file + * VBoxNetFltP-win.cpp - Bridged Networking Driver, Windows Specific Code. + * Protocol edge + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ +#include "VBoxNetFltCmn-win.h" + +#ifdef VBOXNETADP +# error "No protocol edge" +#endif + +#define VBOXNETFLT_PT_STATUS_IS_FILTERED(_s) (\ + (_s) == NDIS_STATUS_MEDIA_CONNECT \ + || (_s) == NDIS_STATUS_MEDIA_DISCONNECT \ + ) + +/** + * performs binding to the given adapter + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoBinding(PVBOXNETFLTINS pThis, PNDIS_STRING pOurDeviceName, PNDIS_STRING pBindToDeviceName) +{ + Assert(pThis->u.s.WinIf.PtState.PowerState == NdisDeviceStateD3); + Assert(pThis->u.s.WinIf.PtState.OpState == kVBoxNetDevOpState_Deinitialized); + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Initializing); + + NDIS_STATUS Status = vboxNetFltWinCopyString(&pThis->u.s.WinIf.MpDeviceName, pOurDeviceName); + Assert (Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + vboxNetFltWinSetPowerState(&pThis->u.s.WinIf.PtState, NdisDeviceStateD0); + pThis->u.s.WinIf.OpenCloseStatus = NDIS_STATUS_SUCCESS; + + UINT iMedium; + NDIS_STATUS TmpStatus; + NDIS_MEDIUM aenmNdisMedium[] = + { + /* Ethernet */ + NdisMedium802_3, + /* Wan */ + NdisMediumWan + }; + + NdisResetEvent(&pThis->u.s.WinIf.OpenCloseEvent); + + NdisOpenAdapter(&Status, &TmpStatus, &pThis->u.s.WinIf.hBinding, &iMedium, + aenmNdisMedium, RT_ELEMENTS(aenmNdisMedium), + g_VBoxNetFltGlobalsWin.Pt.hProtocol, + pThis, + pBindToDeviceName, + 0, /* IN UINT OpenOptions, (reserved, should be NULL) */ + NULL /* IN PSTRING AddressingInformation OPTIONAL */ + ); + Assert(Status == NDIS_STATUS_PENDING || Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_PENDING) + { + NdisWaitEvent(&pThis->u.s.WinIf.OpenCloseEvent, 0); + Status = pThis->u.s.WinIf.OpenCloseStatus; + } + + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + Assert(pThis->u.s.WinIf.hBinding); + pThis->u.s.WinIf.enmMedium = aenmNdisMedium[iMedium]; + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Initialized); + + Status = vboxNetFltWinMpInitializeDevideInstance(pThis); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + return NDIS_STATUS_SUCCESS; + } + else + { + LogRelFunc(("vboxNetFltWinMpInitializeDevideInstance failed, Status 0x%x\n", Status)); + } + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinPtCloseInterface(pThis, &TmpStatus); + Assert(TmpStatus == NDIS_STATUS_SUCCESS); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + } + else + { + LogRelFunc(("NdisOpenAdapter failed, Status (0x%x)", Status)); + } + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + pThis->u.s.WinIf.hBinding = NULL; + } + + return Status; +} + +static VOID vboxNetFltWinPtBindAdapter(OUT PNDIS_STATUS pStatus, + IN NDIS_HANDLE hBindContext, + IN PNDIS_STRING pDeviceNameStr, + IN PVOID pvSystemSpecific1, + IN PVOID pvSystemSpecific2) +{ + LogFlowFuncEnter(); + RT_NOREF2(hBindContext, pvSystemSpecific2); + + NDIS_STATUS Status; + NDIS_HANDLE hConfig = NULL; + + NdisOpenProtocolConfiguration(&Status, &hConfig, (PNDIS_STRING)pvSystemSpecific1); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + PNDIS_CONFIGURATION_PARAMETER pParam; + NDIS_STRING UppedBindStr = NDIS_STRING_CONST("UpperBindings"); + NdisReadConfiguration(&Status, &pParam, hConfig, &UppedBindStr, NdisParameterString); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLTINS pNetFlt; + Status = vboxNetFltWinPtInitBind(&pNetFlt, &pParam->ParameterData.StringData, pDeviceNameStr); + Assert(Status == NDIS_STATUS_SUCCESS); + } + + NdisCloseConfiguration(hConfig); + } + + *pStatus = Status; + + LogFlowFunc(("LEAVE: Status 0x%x\n", Status)); +} + +static VOID vboxNetFltWinPtOpenAdapterComplete(IN NDIS_HANDLE hProtocolBindingContext, IN NDIS_STATUS Status, IN NDIS_STATUS OpenErrorStatus) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + RT_NOREF1(OpenErrorStatus); + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Status (0x%x), OpenErrorStatus(0x%x)\n", pNetFlt, Status, OpenErrorStatus)); + Assert(pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS); + Assert(Status == NDIS_STATUS_SUCCESS); + if (pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) + { + pNetFlt->u.s.WinIf.OpenCloseStatus = Status; + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status != NDIS_STATUS_SUCCESS) + LogRelFunc(("Open Complete status is 0x%x", Status)); + } + else + LogRelFunc(("Adapter maintained status is 0x%x", pNetFlt->u.s.WinIf.OpenCloseStatus)); + NdisSetEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x), OpenErrorStatus(0x%x)\n", pNetFlt, Status, OpenErrorStatus)); +} + +static void vboxNetFltWinPtRequestsWaitComplete(PVBOXNETFLTINS pNetFlt) +{ + /* wait for request to complete */ + while (vboxNetFltWinAtomicUoReadWinState(pNetFlt->u.s.WinIf.StateFlags).fRequestInfo == VBOXNDISREQUEST_INPROGRESS) + { + vboxNetFltWinSleep(2); + } + + /* + * If the below miniport is going to low power state, complete the queued request + */ + RTSpinlockAcquire(pNetFlt->hSpinlock); + if (pNetFlt->u.s.WinIf.StateFlags.fRequestInfo & VBOXNDISREQUEST_QUEUED) + { + /* mark the request as InProgress before posting it to RequestComplete */ + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS; + RTSpinlockRelease(pNetFlt->hSpinlock); + vboxNetFltWinPtRequestComplete(pNetFlt, &pNetFlt->u.s.WinIf.PassDownRequest, NDIS_STATUS_FAILURE); + } + else + { + RTSpinlockRelease(pNetFlt->hSpinlock); + } +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoUnbinding(PVBOXNETFLTINS pNetFlt, bool bOnUnbind) +{ + NDIS_STATUS Status; + uint64_t NanoTS = RTTimeSystemNanoTS(); + int cPPUsage; + + LogFlowFunc(("ENTER: pNetFlt 0x%p\n", pNetFlt)); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Initialized); + + RTSpinlockAcquire(pNetFlt->hSpinlock); + + ASMAtomicUoWriteBool(&pNetFlt->fDisconnectedFromHost, true); + ASMAtomicUoWriteBool(&pNetFlt->fRediscoveryPending, false); + ASMAtomicUoWriteU64(&pNetFlt->NanoTSLastRediscovery, NanoTS); + + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitializing); + if (!bOnUnbind) + { + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + } + + RTSpinlockRelease(pNetFlt->hSpinlock); + + vboxNetFltWinPtRequestsWaitComplete(pNetFlt); + + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.MpState); + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.PtState); + + /* check packet pool is empty */ + cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hSendPacketPool); + Assert(cPPUsage == 0); + cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(cPPUsage == 0); + /* for debugging only, ignore the err in release */ + NOREF(cPPUsage); + + if (!bOnUnbind || !vboxNetFltWinMpDeInitializeDeviceInstance(pNetFlt, &Status)) + { + vboxNetFltWinPtCloseInterface(pNetFlt, &Status); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + + if (!bOnUnbind) + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + else + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + } + } + else + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + } + + LogFlowFunc(("LEAVE: pNetFlt 0x%p\n", pNetFlt)); + + return Status; +} + +static VOID vboxNetFltWinPtUnbindAdapter(OUT PNDIS_STATUS pStatus, + IN NDIS_HANDLE hContext, + IN NDIS_HANDLE hUnbindContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + RT_NOREF1(hUnbindContext); + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + *pStatus = vboxNetFltWinDetachFromInterface(pNetFlt, true); + Assert(*pStatus == NDIS_STATUS_SUCCESS); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static VOID vboxNetFltWinPtUnloadProtocol() +{ + LogFlowFuncEnter(); + NDIS_STATUS Status = vboxNetFltWinPtDeregister(&g_VBoxNetFltGlobalsWin.Pt); + Assert(Status == NDIS_STATUS_SUCCESS); NOREF(Status); + LogFlowFunc(("LEAVE: PtDeregister Status (0x%x)\n", Status)); +} + + +static VOID vboxNetFltWinPtCloseAdapterComplete(IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_STATUS Status) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)ProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); + Assert(pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS); + Assert(Status == NDIS_STATUS_SUCCESS); + Assert(pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS); + if (pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) + { + pNetFlt->u.s.WinIf.OpenCloseStatus = Status; + } + NdisSetEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); +} + +static VOID vboxNetFltWinPtResetComplete(IN NDIS_HANDLE hProtocolBindingContext, IN NDIS_STATUS Status) +{ + RT_NOREF2(hProtocolBindingContext, Status); + LogFlowFunc(("ENTER: pNetFlt 0x%p, Status 0x%x\n", hProtocolBindingContext, Status)); + /* + * should never be here + */ + AssertFailed(); + LogFlowFunc(("LEAVE: pNetFlt 0x%p, Status 0x%x\n", hProtocolBindingContext, Status)); +} + +static NDIS_STATUS vboxNetFltWinPtHandleQueryInfoComplete(PVBOXNETFLTINS pNetFlt, NDIS_STATUS Status) +{ + PNDIS_REQUEST pRequest = &pNetFlt->u.s.WinIf.PassDownRequest; + + switch (pRequest->DATA.QUERY_INFORMATION.Oid) + { + case OID_PNP_CAPABILITIES: + { + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof (NDIS_PNP_CAPABILITIES)) + { + PNDIS_PNP_CAPABILITIES pPnPCaps = (PNDIS_PNP_CAPABILITIES)(pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + PNDIS_PM_WAKE_UP_CAPABILITIES pPmWuCaps = &pPnPCaps->WakeUpCapabilities; + pPmWuCaps->MinMagicPacketWakeUp = NdisDeviceStateUnspecified; + pPmWuCaps->MinPatternWakeUp = NdisDeviceStateUnspecified; + pPmWuCaps->MinLinkChangeWakeUp = NdisDeviceStateUnspecified; + *pNetFlt->u.s.WinIf.pcPDRBytesRW = sizeof (NDIS_PNP_CAPABILITIES); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = 0; + Status = NDIS_STATUS_SUCCESS; + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof(NDIS_PNP_CAPABILITIES); + Status = NDIS_STATUS_RESOURCES; + } + } + break; + } + + case OID_GEN_MAC_OPTIONS: + { + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + pNetFlt->u.s.WinIf.fMacOptions = *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer; +#ifndef VBOX_LOOPBACK_USEFLAGS + /* clearing this flag tells ndis we'll handle loopback ourselves + * the ndis layer or nic driver below us would loopback packets as necessary */ + *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer &= ~NDIS_MAC_OPTION_NO_LOOPBACK; +#else + /* we have to catch loopbacks from the underlying driver, so no duplications will occur, + * just indicate NDIS to handle loopbacks for the packets coming from the protocol */ + *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer |= NDIS_MAC_OPTION_NO_LOOPBACK; +#endif + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + break; + } + + case OID_GEN_CURRENT_PACKET_FILTER: + { + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + /* we're here _ONLY_ in the passthru mode */ + Assert(pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter && !pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt); + if (pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter && !pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt) + { + Assert(pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE); + vboxNetFltWinDereferenceModePassThru(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + /* the filter request is issued below only in case netflt is not active, + * simply update the cache here */ + /* cache the filter used by upper protocols */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer; + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + } + break; + } + + default: + Assert(pRequest->DATA.QUERY_INFORMATION.Oid != OID_PNP_QUERY_POWER); + break; + } + + *pNetFlt->u.s.WinIf.pcPDRBytesRW = pRequest->DATA.QUERY_INFORMATION.BytesWritten; + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = pRequest->DATA.QUERY_INFORMATION.BytesNeeded; + + return Status; +} + +static NDIS_STATUS vboxNetFltWinPtHandleSetInfoComplete(PVBOXNETFLTINS pNetFlt, NDIS_STATUS Status) +{ + PNDIS_REQUEST pRequest = &pNetFlt->u.s.WinIf.PassDownRequest; + + switch (pRequest->DATA.SET_INFORMATION.Oid) + { + case OID_GEN_CURRENT_PACKET_FILTER: + { + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + Assert(Status == NDIS_STATUS_SUCCESS); + if (pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter) + { + if (pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt) + { + Assert(pNetFlt->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE); + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 0; + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.SET_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + pNetFlt->u.s.WinIf.fOurSetFilter = *((PULONG)pRequest->DATA.SET_INFORMATION.InformationBuffer); + Assert(pNetFlt->u.s.WinIf.fOurSetFilter == NDIS_PACKET_TYPE_PROMISCUOUS); + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else + { + Assert(pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE); + + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.SET_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + /* the request was issued when the netflt was not active, simply update the cache here */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = *((PULONG)pRequest->DATA.SET_INFORMATION.InformationBuffer); + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 0; + vboxNetFltWinDereferenceWinIf(pNetFlt); + } +#ifdef DEBUG_misha + else + { + AssertFailed(); + } +#endif + } + break; + } + + default: + Assert(pRequest->DATA.SET_INFORMATION.Oid != OID_PNP_SET_POWER); + break; + } + + *pNetFlt->u.s.WinIf.pcPDRBytesRW = pRequest->DATA.SET_INFORMATION.BytesRead; + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = pRequest->DATA.SET_INFORMATION.BytesNeeded; + + return Status; +} + +DECLHIDDEN(VOID) vboxNetFltWinPtRequestComplete(NDIS_HANDLE hContext, PNDIS_REQUEST pNdisRequest, NDIS_STATUS Status) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + PNDIS_REQUEST pSynchRequest = pNetFlt->u.s.WinIf.pSynchRequest; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), pNdisRequest (0x%p), Status (0x%x)\n", pNetFlt, pNdisRequest, Status)); + + if (pSynchRequest == pNdisRequest) + { + /* asynchronous completion of our sync request */ + /*1.set the status */ + pNetFlt->u.s.WinIf.SynchCompletionStatus = Status; + /* 2. set event */ + KeSetEvent(&pNetFlt->u.s.WinIf.hSynchCompletionEvent, 0, FALSE); + /* 3. return; */ + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pNdisRequest (0x%p), Status (0x%x)\n", pNetFlt, pNdisRequest, Status)); + return; + } + + Assert(&pNetFlt->u.s.WinIf.PassDownRequest == pNdisRequest); + Assert(pNetFlt->u.s.WinIf.StateFlags.fRequestInfo == VBOXNDISREQUEST_INPROGRESS); + vboxNetFltWinMpRequestStateComplete(pNetFlt); + + switch (pNdisRequest->RequestType) + { + case NdisRequestQueryInformation: + Status = vboxNetFltWinPtHandleQueryInfoComplete(pNetFlt, Status); + NdisMQueryInformationComplete(pNetFlt->u.s.WinIf.hMiniport, Status); + break; + + case NdisRequestSetInformation: + Status = vboxNetFltWinPtHandleSetInfoComplete(pNetFlt, Status); + NdisMSetInformationComplete(pNetFlt->u.s.WinIf.hMiniport, Status); + break; + + default: + AssertFailed(); + break; + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pNdisRequest (0x%p), Status (0x%x)\n", pNetFlt, pNdisRequest, Status)); +} + +static VOID vboxNetFltWinPtStatus(IN NDIS_HANDLE hProtocolBindingContext, IN NDIS_STATUS GeneralStatus, IN PVOID pvStatusBuffer, IN UINT cbStatusBuffer) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), GeneralStatus (0x%x)\n", pNetFlt, GeneralStatus)); + + if (vboxNetFltWinReferenceWinIf(pNetFlt)) + { + Assert(pNetFlt->u.s.WinIf.hMiniport); + + if (VBOXNETFLT_PT_STATUS_IS_FILTERED(GeneralStatus)) + { + pNetFlt->u.s.WinIf.MpIndicatedMediaStatus = GeneralStatus; + } + NdisMIndicateStatus(pNetFlt->u.s.WinIf.hMiniport, + GeneralStatus, + pvStatusBuffer, + cbStatusBuffer); + + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + else + { + if (pNetFlt->u.s.WinIf.hMiniport != NULL + && VBOXNETFLT_PT_STATUS_IS_FILTERED(GeneralStatus) + ) + { + pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus = GeneralStatus; + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), GeneralStatus (0x%x)\n", pNetFlt, GeneralStatus)); +} + + +static VOID vboxNetFltWinPtStatusComplete(IN NDIS_HANDLE hProtocolBindingContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (vboxNetFltWinReferenceWinIf(pNetFlt)) + { + NdisMIndicateStatusComplete(pNetFlt->u.s.WinIf.hMiniport); + + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static VOID vboxNetFltWinPtSendComplete(IN NDIS_HANDLE hProtocolBindingContext, IN PNDIS_PACKET pPacket, IN NDIS_STATUS Status) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + PVBOXNETFLT_PKTRSVD_PT pSendInfo = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + PNDIS_PACKET pOrigPacket = pSendInfo->pOrigPacket; + PVOID pBufToFree = pSendInfo->pBufToFree; + LogFlowFunc(("ENTER: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x)\n", pNetFlt, pPacket, Status)); + +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + /** @todo for optimization we could check only for netflt-mode packets + * do it for all for now */ + vboxNetFltWinLbRemoveSendPacket(pNetFlt, pPacket); +#endif + + if (pOrigPacket) + { + NdisIMCopySendCompletePerPacketInfo(pOrigPacket, pPacket); + NdisFreePacket(pPacket); + /* the ptk was posted from the upperlying protocol */ + NdisMSendComplete(pNetFlt->u.s.WinIf.hMiniport, pOrigPacket, Status); + } + else + { + /* if the pOrigPacket is zero - the ptk was originated by netFlt send/receive + * need to free packet buffers */ + vboxNetFltWinFreeSGNdisPacket(pPacket, !pBufToFree); + } + + if (pBufToFree) + { + vboxNetFltWinMemFree(pBufToFree); + } + + vboxNetFltWinDereferenceWinIf(pNetFlt); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x)\n", pNetFlt, pPacket, Status)); +} + +/** + * removes searches for the packet in the list and removes it if found + * @return true if the packet was found and removed, false - otherwise + */ +static bool vboxNetFltWinRemovePacketFromList(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PNDIS_PACKET pPacket) +{ + PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT pTDR = (PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT)pPacket->ProtocolReserved; + return vboxNetFltWinInterlockedSearchListEntry(pList, &pTDR->ListEntry, true /* remove*/); +} + +/** + * puts the packet to the tail of the list + */ +static void vboxNetFltWinPutPacketToList(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PNDIS_PACKET pPacket, PNDIS_BUFFER pOrigBuffer) +{ + PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT pTDR = (PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT)pPacket->ProtocolReserved; + pTDR->pOrigBuffer = pOrigBuffer; + vboxNetFltWinInterlockedPutTail(pList, &pTDR->ListEntry); +} + +static bool vboxNetFltWinPtTransferDataCompleteActive(PVBOXNETFLTINS pNetFltIf, PNDIS_PACKET pPacket, NDIS_STATUS Status) +{ + PNDIS_BUFFER pBuffer; + PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT pTDR; + + if (!vboxNetFltWinRemovePacketFromList(&pNetFltIf->u.s.WinIf.TransferDataList, pPacket)) + return false; + + pTDR = (PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT)pPacket->ProtocolReserved; + Assert(pTDR); + Assert(pTDR->pOrigBuffer); + + do + { + NdisUnchainBufferAtFront(pPacket, &pBuffer); + + Assert(pBuffer); + + NdisFreeBuffer(pBuffer); + + pBuffer = pTDR->pOrigBuffer; + + NdisChainBufferAtBack(pPacket, pBuffer); + + /* data transfer was initiated when the netFlt was active + * the netFlt is still retained by us + * 1. check if loopback + * 2. enqueue packet + * 3. release netFlt */ + + if (Status == NDIS_STATUS_SUCCESS) + { + +#ifdef VBOX_LOOPBACK_USEFLAGS + if (vboxNetFltWinIsLoopedBackPacket(pPacket)) + { + /* should not be here */ + AssertFailed(); + } +#else + PNDIS_PACKET pLb = vboxNetFltWinLbSearchLoopBack(pNetFltIf, pPacket, false); + if (pLb) + { +#ifndef DEBUG_NETFLT_RECV_TRANSFERDATA + /* should not be here */ + AssertFailed(); +#endif + if (!vboxNetFltWinLbIsFromIntNet(pLb)) + { + /* the packet is not from int net, need to pass it up to the host */ + NdisMIndicateReceivePacket(pNetFltIf->u.s.WinIf.hMiniport, &pPacket, 1); + /* dereference NetFlt, WinIf will be dereferenced on Packet return */ + vboxNetFltWinDereferenceNetFlt(pNetFltIf); + break; + } + } +#endif + else + { + /* 2. enqueue */ + /* use the same packet info to put the packet in the processing packet queue */ + PVBOXNETFLT_PKTRSVD_MP pRecvInfo = (PVBOXNETFLT_PKTRSVD_MP)pPacket->MiniportReserved; + + VBOXNETFLT_LBVERIFY(pNetFltIf, pPacket); + + pRecvInfo->pOrigPacket = NULL; + pRecvInfo->pBufToFree = NULL; + + NdisGetPacketFlags(pPacket) = 0; +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFltIf, pPacket, 0)) + { + /* drop it */ + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + vboxNetFltWinDereferenceWinIf(pNetFltIf); + } + else + { + NdisMIndicateReceivePacket(pNetFltIf->u.s.WinIf.hMiniport, &pPacket, 1); + } + vboxNetFltWinDereferenceNetFlt(pNetFltIf); + break; +# else + Status = vboxNetFltWinQuEnqueuePacket(pNetFltIf, pPacket, PACKET_MINE); + if (Status == NDIS_STATUS_SUCCESS) + { + break; + } + AssertFailed(); +# endif + } + } + else + { + AssertFailed(); + } + /* we are here because of error either in data transfer or in enqueueing the packet */ + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + vboxNetFltWinDereferenceNetFlt(pNetFltIf); + vboxNetFltWinDereferenceWinIf(pNetFltIf); + } while (0); + + return true; +} + +static VOID vboxNetFltWinPtTransferDataComplete(IN NDIS_HANDLE hProtocolBindingContext, + IN PNDIS_PACKET pPacket, + IN NDIS_STATUS Status, + IN UINT cbTransferred) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + LogFlowFunc(("ENTER: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x), cbTransfered (%d)\n", pNetFlt, pPacket, Status, cbTransferred)); + if (!vboxNetFltWinPtTransferDataCompleteActive(pNetFlt, pPacket, Status)) + { + if (pNetFlt->u.s.WinIf.hMiniport) + { + NdisMTransferDataComplete(pNetFlt->u.s.WinIf.hMiniport, + pPacket, + Status, + cbTransferred); + } + + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + /* else - all processing is done with vboxNetFltWinPtTransferDataCompleteActive already */ + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x), cbTransfered (%d)\n", pNetFlt, pPacket, Status, cbTransferred)); +} + +static INT vboxNetFltWinRecvPacketPassThru(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket) +{ + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + + PNDIS_PACKET pMyPacket; + NDIS_STATUS Status = vboxNetFltWinPrepareRecvPacket(pNetFlt, pPacket, &pMyPacket, true); + /* the Status holds the current packet status it will be checked for NDIS_STATUS_RESOURCES later + * (see below) */ + Assert(pMyPacket); + if (pMyPacket) + { + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + if (Status == NDIS_STATUS_RESOURCES) + { + NdisDprFreePacket(pMyPacket); + return 0; + } + + return 1; + } + + return 0; +} + +/** + * process the packet receive in a "passthru" mode + */ +static NDIS_STATUS vboxNetFltWinRecvPassThru(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket) +{ + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + + NDIS_STATUS Status; + PNDIS_PACKET pMyPacket; + + NdisDprAllocatePacket(&Status, &pMyPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + vboxNetFltWinCopyPacketInfoOnRecv(pMyPacket, pPacket, true /* force NDIS_STATUS_RESOURCES */); + Assert(NDIS_GET_PACKET_STATUS(pMyPacket) == NDIS_STATUS_RESOURCES); + + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + + NdisDprFreePacket(pMyPacket); + } + return Status; +} + +static VOID vboxNetFltWinRecvIndicatePassThru(PVBOXNETFLTINS pNetFlt, NDIS_HANDLE MacReceiveContext, + PVOID pHeaderBuffer, UINT cbHeaderBuffer, PVOID pLookAheadBuffer, UINT cbLookAheadBuffer, UINT cbPacket) +{ + /* Note: we're using KeGetCurrentProcessorNumber, which is not entirely correct in case + * we're running on 64bit win7+, which can handle > 64 CPUs, however since KeGetCurrentProcessorNumber + * always returns the number < than the number of CPUs in the first group, we're guaranteed to have CPU index < 64 + * @todo: use KeGetCurrentProcessorNumberEx for Win7+ 64 and dynamically extended array */ + ULONG Proc = KeGetCurrentProcessorNumber(); + Assert(Proc < RT_ELEMENTS(pNetFlt->u.s.WinIf.abIndicateRxComplete)); + pNetFlt->u.s.WinIf.abIndicateRxComplete[Proc] = TRUE; + switch (pNetFlt->u.s.WinIf.enmMedium) + { + case NdisMedium802_3: + case NdisMediumWan: + NdisMEthIndicateReceive(pNetFlt->u.s.WinIf.hMiniport, + MacReceiveContext, + (PCHAR)pHeaderBuffer, + cbHeaderBuffer, + pLookAheadBuffer, + cbLookAheadBuffer, + cbPacket); + break; + default: + AssertFailed(); + break; + } +} + +/** + * process the ProtocolReceive in an "active" mode + * + * @return NDIS_STATUS_SUCCESS - the packet is processed + * NDIS_STATUS_PENDING - the packet is being processed, we are waiting for the ProtocolTransferDataComplete to be called + * NDIS_STATUS_NOT_ACCEPTED - the packet is not needed - typically this is because this is a loopback packet + * NDIS_STATUS_FAILURE - packet processing failed + */ +static NDIS_STATUS vboxNetFltWinPtReceiveActive(PVBOXNETFLTINS pNetFlt, NDIS_HANDLE MacReceiveContext, PVOID pHeaderBuffer, UINT cbHeaderBuffer, + PVOID pLookaheadBuffer, UINT cbLookaheadBuffer, UINT cbPacket) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + do + { + if (cbHeaderBuffer != VBOXNETFLT_PACKET_ETHEADER_SIZE) + { + Status = NDIS_STATUS_NOT_ACCEPTED; + break; + } + +#ifndef DEBUG_NETFLT_RECV_TRANSFERDATA + if (cbPacket == cbLookaheadBuffer) + { + PINTNETSG pSG; + PUCHAR pRcvData; +#ifndef VBOX_LOOPBACK_USEFLAGS + PNDIS_PACKET pLb; +#endif + + /* allocate SG buffer */ + Status = vboxNetFltWinAllocSG(cbPacket + cbHeaderBuffer, &pSG); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + break; + } + + pRcvData = (PUCHAR)pSG->aSegs[0].pv; + + NdisMoveMappedMemory(pRcvData, pHeaderBuffer, cbHeaderBuffer); + + NdisCopyLookaheadData(pRcvData+cbHeaderBuffer, + pLookaheadBuffer, + cbLookaheadBuffer, + pNetFlt->u.s.WinIf.fMacOptions); +#ifndef VBOX_LOOPBACK_USEFLAGS + pLb = vboxNetFltWinLbSearchLoopBackBySG(pNetFlt, pSG, false); + if (pLb) + { +#ifndef DEBUG_NETFLT_RECV_NOPACKET + /* should not be here */ + AssertFailed(); +#endif + if (!vboxNetFltWinLbIsFromIntNet(pLb)) + { + PNDIS_PACKET pMyPacket; + pMyPacket = vboxNetFltWinNdisPacketFromSG(pNetFlt, /* PVBOXNETFLTINS */ + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + false, /* bool bToWire */ + false); /* bool bCopyMemory */ + if (pMyPacket) + { + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + /* dereference the NetFlt here & indicate SUCCESS, which would mean the caller would not do a dereference + * the WinIf dereference will be done on packet return */ + vboxNetFltWinDereferenceNetFlt(pNetFlt); + Status = NDIS_STATUS_SUCCESS; + } + else + { + vboxNetFltWinMemFree(pSG); + Status = NDIS_STATUS_FAILURE; + } + } + else + { + vboxNetFltWinMemFree(pSG); + Status = NDIS_STATUS_NOT_ACCEPTED; + } + break; + } +#endif + VBOXNETFLT_LBVERIFYSG(pNetFlt, pSG); + + /* enqueue SG */ +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFlt, pSG, VBOXNETFLT_PACKET_SG)) + { + /* drop it */ + vboxNetFltWinMemFree(pSG); + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + else + { + PNDIS_PACKET pMyPacket = vboxNetFltWinNdisPacketFromSG(pNetFlt, /* PVBOXNETFLTINS */ + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + false, /* bool bToWire */ + false); /* bool bCopyMemory */ + Assert(pMyPacket); + if (pMyPacket) + { + NDIS_SET_PACKET_STATUS(pMyPacket, NDIS_STATUS_SUCCESS); + + DBG_CHECK_PACKET_AND_SG(pMyPacket, pSG); + + LogFlow(("non-ndis packet info, packet created (%p)\n", pMyPacket)); + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + } + else + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + Status = NDIS_STATUS_RESOURCES; + } + } + vboxNetFltWinDereferenceNetFlt(pNetFlt); +# else + Status = vboxNetFltWinQuEnqueuePacket(pNetFlt, pSG, PACKET_SG | PACKET_MINE); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + vboxNetFltWinMemFree(pSG); + break; + } +# endif +#endif + } + else + { + PNDIS_PACKET pPacket; + PNDIS_BUFFER pTransferBuffer; + PNDIS_BUFFER pOrigBuffer; + PUCHAR pMemBuf; + UINT cbBuf = cbPacket + cbHeaderBuffer; + UINT cbTransferred; + + /* allocate NDIS Packet buffer */ + NdisAllocatePacket(&Status, &pPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + break; + } + + VBOXNETFLT_OOB_INIT(pPacket); + +#ifdef VBOX_LOOPBACK_USEFLAGS + /* set "don't loopback" flags */ + NdisGetPacketFlags(pPacket) = g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#else + NdisGetPacketFlags(pPacket) = 0; +#endif + + Status = vboxNetFltWinMemAlloc((PVOID*)(&pMemBuf), cbBuf); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + NdisFreePacket(pPacket); + break; + } + NdisAllocateBuffer(&Status, &pTransferBuffer, pNetFlt->u.s.WinIf.hRecvBufferPool, pMemBuf + cbHeaderBuffer, cbPacket); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + NdisFreePacket(pPacket); + vboxNetFltWinMemFree(pMemBuf); + break; + } + + NdisAllocateBuffer(&Status, &pOrigBuffer, pNetFlt->u.s.WinIf.hRecvBufferPool, pMemBuf, cbBuf); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + NdisFreeBuffer(pTransferBuffer); + NdisFreePacket(pPacket); + vboxNetFltWinMemFree(pMemBuf); + break; + } + + NdisChainBufferAtBack(pPacket, pTransferBuffer); + + NdisMoveMappedMemory(pMemBuf, pHeaderBuffer, cbHeaderBuffer); + + vboxNetFltWinPutPacketToList(&pNetFlt->u.s.WinIf.TransferDataList, pPacket, pOrigBuffer); + +#ifdef DEBUG_NETFLT_RECV_TRANSFERDATA + if (cbPacket == cbLookaheadBuffer) + { + NdisCopyLookaheadData(pMemBuf+cbHeaderBuffer, + pLookaheadBuffer, + cbLookaheadBuffer, + pNetFlt->u.s.WinIf.fMacOptions); + } + else +#endif + { + Assert(cbPacket > cbLookaheadBuffer); + + NdisTransferData(&Status, pNetFlt->u.s.WinIf.hBinding, MacReceiveContext, + 0, /* ByteOffset */ + cbPacket, pPacket, &cbTransferred); + } + + if (Status != NDIS_STATUS_PENDING) + { + vboxNetFltWinPtTransferDataComplete(pNetFlt, pPacket, Status, cbTransferred); + } + } + } while (0); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinPtReceive(IN NDIS_HANDLE hProtocolBindingContext, + IN NDIS_HANDLE MacReceiveContext, + IN PVOID pHeaderBuffer, + IN UINT cbHeaderBuffer, + IN PVOID pLookAheadBuffer, + IN UINT cbLookAheadBuffer, + IN UINT cbPacket) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + PNDIS_PACKET pPacket = NULL; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + bool bNetFltActive; + bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &bNetFltActive); + const bool bPassThruActive = !bNetFltActive; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (fWinIfActive) + { + do + { +#ifndef DEBUG_NETFLT_RECV_NOPACKET + pPacket = NdisGetReceivedPacket(pNetFlt->u.s.WinIf.hBinding, MacReceiveContext); + if (pPacket) + { +# ifndef VBOX_LOOPBACK_USEFLAGS + PNDIS_PACKET pLb = NULL; +# else + if (vboxNetFltWinIsLoopedBackPacket(pPacket)) + { + AssertFailed(); + /* nothing else to do here, just return the packet */ + //NdisReturnPackets(&pPacket, 1); + Status = NDIS_STATUS_NOT_ACCEPTED; + break; + } + + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); +# endif + + if (bNetFltActive) + { +# ifndef VBOX_LOOPBACK_USEFLAGS + pLb = vboxNetFltWinLbSearchLoopBack(pNetFlt, pPacket, false); + if (!pLb) +# endif + { + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); + +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFlt, pPacket, 0)) + { + /* drop it */ + break; + } +# else + Status = vboxNetFltWinQuEnqueuePacket(pNetFlt, pPacket, PACKET_COPY); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + //NdisReturnPackets(&pPacket, 1); + fWinIfActive = false; + bNetFltActive = false; + break; + } +# endif + } +# ifndef VBOX_LOOPBACK_USEFLAGS + else if (vboxNetFltWinLbIsFromIntNet(pLb)) + { + /* nothing else to do here, just return the packet */ + //NdisReturnPackets(&pPacket, 1); + Status = NDIS_STATUS_NOT_ACCEPTED; + break; + } + /* we are here because this is a looped back packet set not from intnet + * we will post it to the upper protocol */ +# endif + } + + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { +# ifndef VBOX_LOOPBACK_USEFLAGS + Assert(!pLb || !vboxNetFltWinLbIsFromIntNet(pLb)); +# endif + Status = vboxNetFltWinRecvPassThru(pNetFlt, pPacket); + Assert(Status == STATUS_SUCCESS); + /* we are done with packet processing, and we will + * not receive packet return event for this packet, + * fWinIfActive should be true to ensure we release WinIf*/ + Assert(fWinIfActive); + if (Status == STATUS_SUCCESS) + break; + } + else + { + /* intnet processing failed - fall back to no-packet mode */ + Assert(bNetFltActive); + Assert(fWinIfActive); + } + + } +#endif /* #ifndef DEBUG_NETFLT_RECV_NOPACKET */ + + if (bNetFltActive) + { + Status = vboxNetFltWinPtReceiveActive(pNetFlt, MacReceiveContext, pHeaderBuffer, cbHeaderBuffer, + pLookAheadBuffer, cbLookAheadBuffer, cbPacket); + if (NT_SUCCESS(Status)) + { + if (Status != NDIS_STATUS_NOT_ACCEPTED) + { + fWinIfActive = false; + bNetFltActive = false; + } + else + { +#ifndef VBOX_LOOPBACK_USEFLAGS + /* this is a loopback packet, nothing to do here */ +#else + AssertFailed(); + /* should not be here */ +#endif + } + break; + } + } + + /* we are done with packet processing, and we will + * not receive packet return event for this packet, + * fWinIfActive should be true to ensure we release WinIf*/ + Assert(fWinIfActive); + + vboxNetFltWinRecvIndicatePassThru(pNetFlt, MacReceiveContext, pHeaderBuffer, cbHeaderBuffer, pLookAheadBuffer, cbLookAheadBuffer, cbPacket); + /* the status could contain an error value here in case the IntNet recv failed, + * ensure we return back success status */ + Status = NDIS_STATUS_SUCCESS; + + } while (0); + + if (bNetFltActive) + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else if (bPassThruActive) + { + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + if (fWinIfActive) + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); + + return Status; + +} + +static VOID vboxNetFltWinPtReceiveComplete(NDIS_HANDLE hProtocolBindingContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + bool fNetFltActive; + bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &fNetFltActive); + NDIS_HANDLE hMiniport = pNetFlt->u.s.WinIf.hMiniport; + /* Note: we're using KeGetCurrentProcessorNumber, which is not entirely correct in case + * we're running on 64bit win7+, which can handle > 64 CPUs, however since KeGetCurrentProcessorNumber + * always returns the number < than the number of CPUs in the first group, we're guaranteed to have CPU index < 64 + * @todo: use KeGetCurrentProcessorNumberEx for Win7+ 64 and dynamically extended array */ + ULONG iProc = KeGetCurrentProcessorNumber(); + Assert(iProc < RT_ELEMENTS(pNetFlt->u.s.WinIf.abIndicateRxComplete)); + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (hMiniport != NULL && pNetFlt->u.s.WinIf.abIndicateRxComplete[iProc]) + { + switch (pNetFlt->u.s.WinIf.enmMedium) + { + case NdisMedium802_3: + case NdisMediumWan: + NdisMEthIndicateReceiveComplete(hMiniport); + break; + default: + AssertFailed(); + break; + } + } + + pNetFlt->u.s.WinIf.abIndicateRxComplete[iProc] = FALSE; + + if (fWinIfActive) + { + if (fNetFltActive) + vboxNetFltWinDereferenceNetFlt(pNetFlt); + else + vboxNetFltWinDereferenceModePassThru(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static INT vboxNetFltWinPtReceivePacket(NDIS_HANDLE hProtocolBindingContext, PNDIS_PACKET pPacket) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + INT cRefCount = 0; + bool bNetFltActive; + bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &bNetFltActive); + const bool bPassThruActive = !bNetFltActive; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (fWinIfActive) + { + do + { +#ifdef VBOX_LOOPBACK_USEFLAGS + if (vboxNetFltWinIsLoopedBackPacket(pPacket)) + { + AssertFailed(); + Log(("lb_rp")); + + /* nothing else to do here, just return the packet */ + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + break; + } + + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); +#endif + + if (bNetFltActive) + { +#ifndef VBOX_LOOPBACK_USEFLAGS + PNDIS_PACKET pLb = vboxNetFltWinLbSearchLoopBack(pNetFlt, pPacket, false); + if (!pLb) +#endif + { +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + NDIS_STATUS fStatus; +#endif + bool fResources = NDIS_GET_PACKET_STATUS(pPacket) == NDIS_STATUS_RESOURCES; NOREF(fResources); + + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); +#ifdef DEBUG_misha + /** @todo remove this assert. + * this is a temporary assert for debugging purposes: + * we're probably doing something wrong with the packets if the miniport reports NDIS_STATUS_RESOURCES */ + Assert(!fResources); +#endif + +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFlt, pPacket, 0)) + { + /* drop it */ + cRefCount = 0; + break; + } + +#else + fStatus = vboxNetFltWinQuEnqueuePacket(pNetFlt, pPacket, fResources ? PACKET_COPY : 0); + if (fStatus == NDIS_STATUS_SUCCESS) + { + bNetFltActive = false; + fWinIfActive = false; + if (fResources) + { + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + } + else + cRefCount = 1; + break; + } + else + { + AssertFailed(); + } +#endif + } +#ifndef VBOX_LOOPBACK_USEFLAGS + else if (vboxNetFltWinLbIsFromIntNet(pLb)) + { + /* the packet is from intnet, it has already been set to the host, + * no need for loopng it back to the host again */ + /* nothing else to do here, just return the packet */ + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + break; + } +#endif + } + + cRefCount = vboxNetFltWinRecvPacketPassThru(pNetFlt, pPacket); + if (cRefCount) + { + Assert(cRefCount == 1); + fWinIfActive = false; + } + + } while (FALSE); + + if (bNetFltActive) + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else if (bPassThruActive) + { + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + if (fWinIfActive) + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + } + else + { + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), cRefCount (%d)\n", pNetFlt, cRefCount)); + + return cRefCount; +} + +DECLHIDDEN(bool) vboxNetFltWinPtCloseInterface(PVBOXNETFLTINS pNetFlt, PNDIS_STATUS pStatus) +{ + RTSpinlockAcquire(pNetFlt->hSpinlock); + + if (pNetFlt->u.s.WinIf.StateFlags.fInterfaceClosing) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + AssertFailed(); + return false; + } + if (pNetFlt->u.s.WinIf.hBinding == NULL) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + AssertFailed(); + return false; + } + + pNetFlt->u.s.WinIf.StateFlags.fInterfaceClosing = TRUE; + RTSpinlockRelease(pNetFlt->hSpinlock); + + NdisResetEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent); + NdisCloseAdapter(pStatus, pNetFlt->u.s.WinIf.hBinding); + if (*pStatus == NDIS_STATUS_PENDING) + { + NdisWaitEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent, 0); + *pStatus = pNetFlt->u.s.WinIf.OpenCloseStatus; + } + + Assert (*pStatus == NDIS_STATUS_SUCCESS); + + pNetFlt->u.s.WinIf.hBinding = NULL; + + return true; +} + +static NDIS_STATUS vboxNetFltWinPtPnPSetPower(PVBOXNETFLTINS pNetFlt, NDIS_DEVICE_POWER_STATE enmPowerState) +{ + NDIS_DEVICE_POWER_STATE enmPrevPowerState = vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState); + + RTSpinlockAcquire(pNetFlt->hSpinlock); + + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.PtState, enmPowerState); + + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState) > NdisDeviceStateD0) + { + if (enmPrevPowerState == NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = TRUE; + } + RTSpinlockRelease(pNetFlt->hSpinlock); + vboxNetFltWinPtRequestsWaitComplete(pNetFlt); + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.MpState); + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.PtState); + + /* check packet pool is empty */ + UINT cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hSendPacketPool); + Assert(cPPUsage == 0); + cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(cPPUsage == 0); + /* for debugging only, ignore the err in release */ + NOREF(cPPUsage); + + Assert(!pNetFlt->u.s.WinIf.StateFlags.fRequestInfo); + } + else + { + if (enmPrevPowerState > NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = FALSE; + } + + if (pNetFlt->u.s.WinIf.StateFlags.fRequestInfo & VBOXNDISREQUEST_QUEUED) + { + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS; + RTSpinlockRelease(pNetFlt->hSpinlock); + + vboxNetFltWinMpRequestPost(pNetFlt); + } + else + { + RTSpinlockRelease(pNetFlt->hSpinlock); + } + } + + return NDIS_STATUS_SUCCESS; +} + + +static NDIS_STATUS vboxNetFltWinPtPnPEvent(IN NDIS_HANDLE hProtocolBindingContext, IN PNET_PNP_EVENT pNetPnPEvent) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), NetEvent (%d)\n", pNetFlt, pNetPnPEvent->NetEvent)); + + switch (pNetPnPEvent->NetEvent) + { + case NetEventSetPower: + { + NDIS_DEVICE_POWER_STATE enmPowerState = *((PNDIS_DEVICE_POWER_STATE)pNetPnPEvent->Buffer); + NDIS_STATUS rcNdis = vboxNetFltWinPtPnPSetPower(pNetFlt, enmPowerState); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), NetEvent (%d), rcNdis=%#x\n", pNetFlt, pNetPnPEvent->NetEvent, rcNdis)); + return rcNdis; + } + + case NetEventReconfigure: + { + if (!pNetFlt) + { + NdisReEnumerateProtocolBindings(g_VBoxNetFltGlobalsWin.Pt.hProtocol); + } + } + /** @todo r=bird: Is the fall thru intentional?? */ + default: + LogFlowFunc(("LEAVE: pNetFlt (0x%p), NetEvent (%d)\n", pNetFlt, pNetPnPEvent->NetEvent)); + return NDIS_STATUS_SUCCESS; + } + +} + +#ifdef __cplusplus +# define PTCHARS_40(_p) ((_p).Ndis40Chars) +#else +# define PTCHARS_40(_p) (_p) +#endif + +/** + * register the protocol edge + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtRegister(PVBOXNETFLTGLOBALS_PT pGlobalsPt, PDRIVER_OBJECT pDriverObject, + PUNICODE_STRING pRegistryPathStr) +{ + RT_NOREF2(pDriverObject, pRegistryPathStr); + NDIS_PROTOCOL_CHARACTERISTICS PtChars; + NDIS_STRING NameStr; + + NdisInitUnicodeString(&NameStr, VBOXNETFLT_NAME_PROTOCOL); + + NdisZeroMemory(&PtChars, sizeof (PtChars)); + PTCHARS_40(PtChars).MajorNdisVersion = VBOXNETFLT_VERSION_PT_NDIS_MAJOR; + PTCHARS_40(PtChars).MinorNdisVersion = VBOXNETFLT_VERSION_PT_NDIS_MINOR; + + PTCHARS_40(PtChars).Name = NameStr; + PTCHARS_40(PtChars).OpenAdapterCompleteHandler = vboxNetFltWinPtOpenAdapterComplete; + PTCHARS_40(PtChars).CloseAdapterCompleteHandler = vboxNetFltWinPtCloseAdapterComplete; + PTCHARS_40(PtChars).SendCompleteHandler = vboxNetFltWinPtSendComplete; + PTCHARS_40(PtChars).TransferDataCompleteHandler = vboxNetFltWinPtTransferDataComplete; + PTCHARS_40(PtChars).ResetCompleteHandler = vboxNetFltWinPtResetComplete; + PTCHARS_40(PtChars).RequestCompleteHandler = vboxNetFltWinPtRequestComplete; + PTCHARS_40(PtChars).ReceiveHandler = vboxNetFltWinPtReceive; + PTCHARS_40(PtChars).ReceiveCompleteHandler = vboxNetFltWinPtReceiveComplete; + PTCHARS_40(PtChars).StatusHandler = vboxNetFltWinPtStatus; + PTCHARS_40(PtChars).StatusCompleteHandler = vboxNetFltWinPtStatusComplete; + PTCHARS_40(PtChars).BindAdapterHandler = vboxNetFltWinPtBindAdapter; + PTCHARS_40(PtChars).UnbindAdapterHandler = vboxNetFltWinPtUnbindAdapter; + PTCHARS_40(PtChars).UnloadHandler = vboxNetFltWinPtUnloadProtocol; +#if !defined(DEBUG_NETFLT_RECV) + PTCHARS_40(PtChars).ReceivePacketHandler = vboxNetFltWinPtReceivePacket; +#endif + PTCHARS_40(PtChars).PnPEventHandler = vboxNetFltWinPtPnPEvent; + + NDIS_STATUS Status; + NdisRegisterProtocol(&Status, &pGlobalsPt->hProtocol, &PtChars, sizeof (PtChars)); + Assert(Status == STATUS_SUCCESS); + return Status; +} + +/** + * deregister the protocol edge + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDeregister(PVBOXNETFLTGLOBALS_PT pGlobalsPt) +{ + if (!pGlobalsPt->hProtocol) + return NDIS_STATUS_SUCCESS; + + NDIS_STATUS Status; + + NdisDeregisterProtocol(&Status, pGlobalsPt->hProtocol); + Assert (Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + NdisZeroMemory(pGlobalsPt, sizeof (*pGlobalsPt)); + } + return Status; +} diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h new file mode 100644 index 00000000..11910676 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h @@ -0,0 +1,52 @@ +/* $Id: VBoxNetFltP-win.h $ */ +/** @file + * VBoxNetFltP-win.h - Bridged Networking Driver, Windows Specific Code. + * Protocol edge API + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltP_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltP_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef VBOXNETADP +# error "No protocol edge" +#endif +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtRegister(PVBOXNETFLTGLOBALS_PT pGlobalsPt, PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDeregister(PVBOXNETFLTGLOBALS_PT pGlobalsPt); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoUnbinding(PVBOXNETFLTINS pNetFlt, bool bOnUnbind); +DECLHIDDEN(VOID) vboxNetFltWinPtRequestComplete(NDIS_HANDLE hContext, PNDIS_REQUEST pNdisRequest, NDIS_STATUS Status); +DECLHIDDEN(bool) vboxNetFltWinPtCloseInterface(PVBOXNETFLTINS pNetFlt, PNDIS_STATUS pStatus); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoBinding(PVBOXNETFLTINS pThis, PNDIS_STRING pOurDeviceName, PNDIS_STRING pBindToDeviceName); +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltP_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp new file mode 100644 index 00000000..44c7338c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp @@ -0,0 +1,3650 @@ +/* $Id: VBoxNetFltRt-win.cpp $ */ +/** @file + * VBoxNetFltRt-win.cpp - Bridged Networking Driver, Windows Specific Runtime Code. + */ + +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include "VBoxNetFltCmn-win.h" +#include <VBox/intnetinline.h> +#include <iprt/thread.h> + +#include <iprt/nt/tdikrnl.h> +#include <mstcpip.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** represents the job element of the job queue + * see comments for VBOXNETFLT_JOB_QUEUE */ +typedef struct VBOXNETFLT_JOB +{ + /** link in the job queue */ + LIST_ENTRY ListEntry; + /** job function to be executed */ + PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine; + /** parameter to be passed to the job function */ + PVOID pContext; + /** event that will be fired on job completion */ + KEVENT CompletionEvent; + /** true if the job manager should use the completion even for completion indication, false-otherwise*/ + bool bUseCompletionEvent; +} VBOXNETFLT_JOB, *PVBOXNETFLT_JOB; + +/** + * represents the queue of jobs processed by the worker thread + * + * we use the thread to process tasks which are required to be done at passive level + * our callbacks may be called at APC level by IntNet, there are some tasks that we can not create at APC, + * e.g. thread creation. This is why we schedule such jobs to the worker thread working at passive level + */ +typedef struct VBOXNETFLT_JOB_QUEUE +{ + /* jobs */ + LIST_ENTRY Jobs; + /* we are using ExInterlocked..List functions to access the jobs list */ + KSPIN_LOCK Lock; + /** this event is used to initiate a job worker thread kill */ + KEVENT KillEvent; + /** this event is used to notify a worker thread that jobs are added to the queue */ + KEVENT NotifyEvent; + /** worker thread */ + PKTHREAD pThread; +} VBOXNETFLT_JOB_QUEUE, *PVBOXNETFLT_JOB_QUEUE; + +typedef struct _CREATE_INSTANCE_CONTEXT +{ +#ifndef VBOXNETADP + PNDIS_STRING pOurName; + PNDIS_STRING pBindToName; +#else + NDIS_HANDLE hMiniportAdapter; + NDIS_HANDLE hWrapperConfigurationContext; +#endif + NDIS_STATUS Status; +} CREATE_INSTANCE_CONTEXT, *PCREATE_INSTANCE_CONTEXT; + +/*contexts used for our jobs */ +/* Attach context */ +typedef struct _ATTACH_INFO +{ + PVBOXNETFLTINS pNetFltIf; + PCREATE_INSTANCE_CONTEXT pCreateContext; + bool fRediscovery; + int Status; +} ATTACH_INFO, *PATTACH_INFO; + +/* general worker context */ +typedef struct _WORKER_INFO +{ + PVBOXNETFLTINS pNetFltIf; + int Status; +} WORKER_INFO, *PWORKER_INFO; + +/* idc initialization */ +typedef struct _INIT_IDC_INFO +{ + VBOXNETFLT_JOB Job; + bool bInitialized; + volatile bool bStop; + volatile int rc; + KEVENT hCompletionEvent; +} INIT_IDC_INFO, *PINIT_IDC_INFO; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** global job queue. some operations are required to be done at passive level, e.g. thread creation, adapter bind/unbind initiation, + * while IntNet typically calls us APC_LEVEL, so we just create a system thread in our DriverEntry and enqueue the jobs to that thread */ +static VBOXNETFLT_JOB_QUEUE g_VBoxJobQueue; +volatile static bool g_bVBoxIdcInitialized; +INIT_IDC_INFO g_VBoxInitIdcInfo; + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; +/* win-specific global data */ +VBOXNETFLTGLOBALS_WIN g_VBoxNetFltGlobalsWin = {{{0}}}; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define LIST_ENTRY_2_JOB(pListEntry) \ + ( (PVBOXNETFLT_JOB)((uint8_t *)(pListEntry) - RT_UOFFSETOF(VBOXNETFLT_JOB, ListEntry)) ) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxNetFltWinAttachToInterface(PVBOXNETFLTINS pThis, void * pContext, bool fRediscovery); +static int vboxNetFltWinConnectIt(PVBOXNETFLTINS pThis); +static int vboxNetFltWinFiniIdc(); +static void vboxNetFltWinFiniNetFltBase(); +static int vboxNetFltWinInitNetFltBase(); +static int vboxNetFltWinFiniNetFlt(); +static int vboxNetFltWinStartInitIdcProbing(); +static int vboxNetFltWinStopInitIdcProbing(); + + + +/** makes the current thread to sleep for the given number of miliseconds */ +DECLHIDDEN(void) vboxNetFltWinSleep(ULONG milis) +{ + RTThreadSleep(milis); +} + +/** wait for the given device to be dereferenced */ +DECLHIDDEN(void) vboxNetFltWinWaitDereference(PVBOXNETFLT_WINIF_DEVICE pState) +{ +#ifdef DEBUG + uint64_t StartNanoTS = RTTimeSystemNanoTS(); + uint64_t CurNanoTS; +#endif + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + while (ASMAtomicUoReadU32((volatile uint32_t *)&pState->cReferences)) + { + vboxNetFltWinSleep(2); +#ifdef DEBUG + CurNanoTS = RTTimeSystemNanoTS(); + if (CurNanoTS - StartNanoTS > 20000000) + { + LogRel(("device not idle")); + AssertFailed(); +// break; + } +#endif + } +} + +/** + * mem functions + */ +/* allocates and zeroes the nonpaged memory of a given size */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMemAlloc(PVOID *ppvMemBuf, UINT cbLength) +{ +#ifdef DEBUG_NETFLT_USE_EXALLOC + *ppvMemBuf = ExAllocatePoolWithTag(NonPagedPool, cbLength, VBOXNETFLT_MEM_TAG); + if (*ppvMemBuf) + { + NdisZeroMemory(*ppvMemBuf, cbLength); + return NDIS_STATUS_SUCCESS; + } + return NDIS_STATUS_FAILURE; +#else + NDIS_STATUS fStatus = NdisAllocateMemoryWithTag(ppvMemBuf, cbLength, VBOXNETFLT_MEM_TAG); + if (fStatus == NDIS_STATUS_SUCCESS) + NdisZeroMemory(*ppvMemBuf, cbLength); + return fStatus; +#endif +} + +/* frees memory allocated with vboxNetFltWinMemAlloc */ +DECLHIDDEN(void) vboxNetFltWinMemFree(PVOID pvMemBuf) +{ +#ifdef DEBUG_NETFLT_USE_EXALLOC + ExFreePool(pvMemBuf); +#else + NdisFreeMemory(pvMemBuf, 0, 0); +#endif +} + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + +/* initializes packet info pool and allocates the cSize packet infos for the pool */ +static NDIS_STATUS vboxNetFltWinPpAllocatePacketInfoPool(PVBOXNETFLT_PACKET_INFO_POOL pPool, UINT cSize) +{ + UINT cbBufSize = sizeof(PACKET_INFO)*cSize; + PACKET_INFO * pPacketInfos; + NDIS_STATUS fStatus; + UINT i; + + Assert(cSize > 0); + + INIT_INTERLOCKED_PACKET_QUEUE(&pPool->Queue); + + fStatus = vboxNetFltWinMemAlloc((PVOID*)&pPacketInfos, cbBufSize); + + if (fStatus == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLTPACKET_INFO pInfo; + pPool->pBuffer = pPacketInfos; + + for (i = 0; i < cSize; i++) + { + pInfo = &pPacketInfos[i]; + vboxNetFltWinQuEnqueueTail(&pPool->Queue.Queue, pInfo); + pInfo->pPool = pPool; + } + } + else + { + AssertFailed(); + } + + return fStatus; +} + +/* frees the packet info pool */ +VOID vboxNetFltWinPpFreePacketInfoPool(PVBOXNETFLT_PACKET_INFO_POOL pPool) +{ + vboxNetFltWinMemFree(pPool->pBuffer); + + FINI_INTERLOCKED_PACKET_QUEUE(&pPool->Queue) +} + +#endif + +/** + * copies one string to another. in case the destination string size is not enough to hold the complete source string + * does nothing and returns NDIS_STATUS_RESOURCES . + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinCopyString(PNDIS_STRING pDst, PNDIS_STRING pSrc) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + if (pDst != pSrc) + { + if (pDst->MaximumLength < pSrc->Length) + { + AssertFailed(); + Status = NDIS_STATUS_RESOURCES; + } + else + { + pDst->Length = pSrc->Length; + + if (pDst->Buffer != pSrc->Buffer) + { + NdisMoveMemory(pDst->Buffer, pSrc->Buffer, pSrc->Length); + } + } + } + return Status; +} + +/************************************************************************************ + * PINTNETSG pSG manipulation functions + ************************************************************************************/ + +/* moves the contents of the given NDIS_BUFFER and all other buffers chained to it to the PINTNETSG + * the PINTNETSG is expected to contain one segment whose bugger is large enough to maintain + * the contents of the given NDIS_BUFFER and all other buffers chained to it */ +static NDIS_STATUS vboxNetFltWinNdisBufferMoveToSG0(PNDIS_BUFFER pBuffer, PINTNETSG pSG) +{ + PINTNETSEG paSeg; + uint8_t * ptr; + PVOID pVirtualAddress; + UINT cbCurrentLength; + NDIS_STATUS fStatus = NDIS_STATUS_SUCCESS; + + Assert(pSG->cSegsAlloc == 1); + + paSeg = pSG->aSegs; + ptr = (uint8_t*)paSeg->pv; + paSeg->cb = 0; + paSeg->Phys = NIL_RTHCPHYS; + pSG->cbTotal = 0; + + Assert(paSeg->pv); + + while (pBuffer) + { + NdisQueryBufferSafe(pBuffer, &pVirtualAddress, &cbCurrentLength, NormalPagePriority); + + if (!pVirtualAddress) + { + fStatus = NDIS_STATUS_FAILURE; + break; + } + + pSG->cbTotal += cbCurrentLength; + paSeg->cb += cbCurrentLength; + NdisMoveMemory(ptr, pVirtualAddress, cbCurrentLength); + ptr += cbCurrentLength; + + NdisGetNextBuffer(pBuffer, &pBuffer); + } + + if (fStatus == NDIS_STATUS_SUCCESS) + { + pSG->cSegsUsed = 1; + Assert(pSG->cbTotal == paSeg->cb); + } + return fStatus; +} + +/* converts the PNDIS_BUFFER to PINTNETSG by making the PINTNETSG segments to point to the memory buffers the + * ndis buffer(s) point to (as opposed to vboxNetFltWinNdisBufferMoveToSG0 which copies the memory from ndis buffers(s) to PINTNETSG) */ +static NDIS_STATUS vboxNetFltWinNdisBuffersToSG(PNDIS_BUFFER pBuffer, PINTNETSG pSG) +{ + UINT cSegs = 0; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + PVOID pVirtualAddress; + UINT cbCurrentLength; + + while (pBuffer) + { + NdisQueryBufferSafe(pBuffer, &pVirtualAddress, &cbCurrentLength, NormalPagePriority); + + if (!pVirtualAddress) + { + Status = NDIS_STATUS_FAILURE; + break; + } + + pSG->cbTotal += cbCurrentLength; + pSG->aSegs[cSegs].cb = cbCurrentLength; + pSG->aSegs[cSegs].pv = pVirtualAddress; + pSG->aSegs[cSegs].Phys = NIL_RTHCPHYS; + cSegs++; + + NdisGetNextBuffer(pBuffer, &pBuffer); + } + + AssertFatal(cSegs <= pSG->cSegsAlloc); + + if (Status == NDIS_STATUS_SUCCESS) + { + pSG->cSegsUsed = cSegs; + } + + return Status; +} + +static void vboxNetFltWinDeleteSG(PINTNETSG pSG) +{ + vboxNetFltWinMemFree(pSG); +} + +static PINTNETSG vboxNetFltWinCreateSG(uint32_t cSegs) +{ + PINTNETSG pSG; + NTSTATUS Status = vboxNetFltWinMemAlloc((PVOID*)&pSG, RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + if (Status == STATUS_SUCCESS) + { + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); + return pSG; + } + + return NULL; +} + +/************************************************************************************ + * packet queue functions + ************************************************************************************/ +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +#if !defined(VBOXNETADP) +static NDIS_STATUS vboxNetFltWinQuPostPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PINTNETSG pSG, uint32_t fFlags +# ifdef DEBUG_NETFLT_PACKETS + , PNDIS_PACKET pTmpPacket +# endif + ) +{ + NDIS_STATUS Status; + PNDIS_PACKET pMyPacket; + bool bSrcHost = fFlags & PACKET_SRC_HOST; + + LogFlow(("posting packet back to driver stack..\n")); + + if (!pPacket) + { + /* INTNETSG was in the packet queue, create a new NdisPacket from INTNETSG*/ + pMyPacket = vboxNetFltWinNdisPacketFromSG(pNetFlt, + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + bSrcHost, /* bool bToWire */ + false); /* bool bCopyMemory */ + + Assert(pMyPacket); + + NDIS_SET_PACKET_STATUS(pMyPacket, NDIS_STATUS_SUCCESS); + + DBG_CHECK_PACKET_AND_SG(pMyPacket, pSG); + +#ifdef DEBUG_NETFLT_PACKETS + Assert(pTmpPacket); + + DBG_CHECK_PACKET_AND_SG(pTmpPacket, pSG); + + DBG_CHECK_PACKETS(pTmpPacket, pMyPacket); +#endif + + LogFlow(("non-ndis packet info, packet created (%p)\n", pMyPacket)); + } + else + { + /* NDIS_PACKET was in the packet queue */ + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + + if (!(fFlags & PACKET_MINE)) + { + /* the packet is the one that was passed to us in send/receive callback + * According to the DDK, we can not post it further, + * instead we should allocate our own packet. + * So, allocate our own packet (pMyPacket) and copy the packet info there */ + if (bSrcHost) + { + Status = vboxNetFltWinPrepareSendPacket(pNetFlt, pPacket, &pMyPacket/*, true*/); + LogFlow(("packet from wire, packet created (%p)\n", pMyPacket)); + } + else + { + Status = vboxNetFltWinPrepareRecvPacket(pNetFlt, pPacket, &pMyPacket, false); + LogFlow(("packet from wire, packet created (%p)\n", pMyPacket)); + } + } + else + { + /* the packet enqueued is ours, simply assign pMyPacket and zero pPacket */ + pMyPacket = pPacket; + pPacket = NULL; + } + Assert(pMyPacket); + } + + if (pMyPacket) + { + /* we have successfully initialized our packet, post it to the host or to the wire */ + if (bSrcHost) + { +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + vboxNetFltWinLbPutSendPacket(pNetFlt, pMyPacket, false /* bFromIntNet */); +#endif + NdisSend(&Status, pNetFlt->u.s.hBinding, pMyPacket); + + if (Status != NDIS_STATUS_PENDING) + { +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + /* the status is NOT pending, complete the packet */ + bool bTmp = vboxNetFltWinLbRemoveSendPacket(pNetFlt, pMyPacket); + Assert(bTmp); +#endif + if (pPacket) + { + LogFlow(("status is not pending, completing packet (%p)\n", pPacket)); + + NdisIMCopySendCompletePerPacketInfo (pPacket, pMyPacket); + + NdisFreePacket(pMyPacket); + } + else + { + /* should never be here since the PINTNETSG is stored only when the underlying miniport + * indicates NDIS_STATUS_RESOURCES, we should never have this when processing + * the "from-host" packets */ + AssertFailed(); + LogFlow(("status is not pending, freeing myPacket (%p)\n", pMyPacket)); + vboxNetFltWinFreeSGNdisPacket(pMyPacket, false); + } + } + } + else + { + NdisMIndicateReceivePacket(pNetFlt->u.s.hMiniport, &pMyPacket, 1); + + Status = NDIS_STATUS_PENDING; + /* the packet receive completion is always indicated via MiniportReturnPacket */ + } + } + else + { + /*we failed to create our packet */ + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} +#endif + +static bool vboxNetFltWinQuProcessInfo(PVBOXNETFLTINS pNetFltIf, PPACKET_QUEUE_WORKER pWorker, PVOID pvPacket, const UINT fFlags) +#else +DECLHIDDEN(bool) vboxNetFltWinPostIntnet(PVBOXNETFLTINS pNetFltIf, PVOID pvPacket, const UINT fFlags) +#endif +{ + PNDIS_PACKET pPacket = NULL; + PINTNETSG pSG = NULL; + NDIS_STATUS Status; +#ifndef VBOXNETADP + bool bSrcHost; + bool bDropIt; +# ifndef VBOXNETFLT_NO_PACKET_QUEUE + bool bPending; +# endif +#endif +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + bool bDeleteSG = false; +#endif +#ifdef DEBUG_NETFLT_PACKETS + /* packet used for matching */ + PNDIS_PACKET pTmpPacket = NULL; +#endif + +#ifndef VBOXNETADP + bSrcHost = (fFlags & VBOXNETFLT_PACKET_SRC_HOST) != 0; +#endif + + /* we first need to obtain the INTNETSG to be passed to intnet */ + + /* the queue may contain two "types" of packets: + * the NDIS_PACKET and the INTNETSG. + * I.e. on send/receive we typically enqueue the NDIS_PACKET passed to us by ndis, + * however in case our ProtocolReceive is called or the packet's status is set to NDIS_STSTUS_RESOURCES + * in ProtocolReceivePacket, we must return the packet immediately on ProtocolReceive*** exit + * In this case we allocate the INTNETSG, copy the ndis packet data there and enqueue it. + * In this case the packet info flags has the VBOXNETFLT_PACKET_SG fag set + * + * Besides that the NDIS_PACKET contained in the queue could be either the one passed to us in our send/receive callback + * or the one created by us. The latter is possible in case our ProtocolReceive callback is called and we call NdisTransferData + * in this case we need to allocate the packet the data to be transferred to. + * If the enqueued packet is the one allocated by us the VBOXNETFLT_PACKET_MINE flag is set + * */ + if ((fFlags & VBOXNETFLT_PACKET_SG) == 0) + { + /* we have NDIS_PACKET enqueued, we need to convert it to INTNETSG to be passed to intnet */ + PNDIS_BUFFER pCurrentBuffer = NULL; + UINT cBufferCount; + UINT cbPacketLength; + + pPacket = (PNDIS_PACKET)pvPacket; + + LogFlow(("ndis packet info, packet (%p)\n", pPacket)); + + LogFlow(("preparing pSG")); + NdisQueryPacket(pPacket, NULL, &cBufferCount, &pCurrentBuffer, &cbPacketLength); + Assert(cBufferCount); + +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + pSG = vboxNetFltWinCreateSG(cBufferCount); +#else + /* we can not allocate the INTNETSG on stack since in this case we may get stack overflow + * somewhere outside of our driver (3 pages of system thread stack does not seem to be enough) + * + * since we have a "serialized" packet processing, i.e. all packets are being processed and passed + * to intnet by this thread, we just use one previously allocated INTNETSG which is stored in PVBOXNETFLTINS */ + pSG = pWorker->pSG; + + if (cBufferCount > pSG->cSegsAlloc) + { + pSG = vboxNetFltWinCreateSG(cBufferCount + 2); + if (pSG) + { + vboxNetFltWinDeleteSG(pWorker->pSG); + pWorker->pSG = pSG; + } + else + { + LogRel(("Failed to reallocate the pSG\n")); + } + } +#endif + + if (pSG) + { +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + bDeleteSG = true; +#endif + /* reinitialize */ + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, pSG->cSegsAlloc, 0 /*cSegsUsed*/); + + /* convert the ndis buffers to INTNETSG */ + Status = vboxNetFltWinNdisBuffersToSG(pCurrentBuffer, pSG); + if (Status != NDIS_STATUS_SUCCESS) + { + pSG = NULL; + } + else + { + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + } + } + } + else + { + /* we have the INTNETSG enqueued. (see the above comment explaining why/when this may happen) + * just use the INTNETSG to pass it to intnet */ +#ifndef VBOXNETADP + /* the PINTNETSG is stored only when the underlying miniport + * indicates NDIS_STATUS_RESOURCES, we should never have this when processing + * the "from-host" packedts */ + Assert(!bSrcHost); +#endif + pSG = (PINTNETSG)pvPacket; + + LogFlow(("not ndis packet info, pSG (%p)\n", pSG)); + } + +#ifdef DEBUG_NETFLT_PACKETS + if (!pPacket && !pTmpPacket) + { + /* create tmp packet that woud be used for matching */ + pTmpPacket = vboxNetFltWinNdisPacketFromSG(pNetFltIf, + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + bSrcHost, /* bool bToWire */ + true); /* bool bCopyMemory */ + + NDIS_SET_PACKET_STATUS(pTmpPacket, NDIS_STATUS_SUCCESS); + + DBG_CHECK_PACKET_AND_SG(pTmpPacket, pSG); + + Assert(pTmpPacket); + } +#endif + do + { +#ifndef VBOXNETADP + /* the pSG was successfully initialized, post it to the netFlt*/ + bDropIt = pSG ? pNetFltIf->pSwitchPort->pfnRecv(pNetFltIf->pSwitchPort, NULL /* pvIf */, pSG, + bSrcHost ? INTNETTRUNKDIR_HOST : INTNETTRUNKDIR_WIRE + ) + : false; +#else + if (pSG) + { + pNetFltIf->pSwitchPort->pfnRecv(pNetFltIf->pSwitchPort, NULL /* pvIf */, pSG, INTNETTRUNKDIR_HOST); + STATISTIC_INCREASE(pNetFltIf->u.s.WinIf.cTxSuccess); + } + else + { + STATISTIC_INCREASE(pNetFltIf->u.s.WinIf.cTxError); + } +#endif + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + +# if !defined(VBOXNETADP) + if (!bDropIt) + { + Status = vboxNetFltWinQuPostPacket(pNetFltIf, pPacket, pSG, fFlags +# ifdef DEBUG_NETFLT_PACKETS + , pTmpPacket +# endif + ); + + if (Status == NDIS_STATUS_PENDING) + { + /* we will process packet completion in the completion routine */ + bPending = true; + break; + } + } + else +# endif + { + Status = NDIS_STATUS_SUCCESS; + } + + /* drop it */ + if (pPacket) + { + if (!(fFlags & PACKET_MINE)) + { +# if !defined(VBOXNETADP) + /* complete the packets */ + if (fFlags & PACKET_SRC_HOST) + { +# endif +/* NDIS_SET_PACKET_STATUS(pPacket, Status); */ + NdisMSendComplete(pNetFltIf->u.s.hMiniport, pPacket, Status); +# if !defined(VBOXNETADP) + } + else + { +# endif +# ifndef VBOXNETADP + NdisReturnPackets(&pPacket, 1); +# endif +# if !defined(VBOXNETADP) + } +# endif + } + else + { + Assert(!(fFlags & PACKET_SRC_HOST)); + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + } + } + else + { + Assert(pSG); + vboxNetFltWinMemFree(pSG); + } +# ifndef VBOXNETADP + bPending = false; +# endif + } while (0); + +#ifdef DEBUG_NETFLT_PACKETS + if (pTmpPacket) + { + vboxNetFltWinFreeSGNdisPacket(pTmpPacket, true); + } +#endif + +#ifndef VBOXNETADP + return bPending; +#else + return false; +#endif +#else /* #ifdef VBOXNETFLT_NO_PACKET_QUEUE */ + } while (0); + + if (bDeleteSG) + vboxNetFltWinMemFree(pSG); + +# ifndef VBOXNETADP + return bDropIt; +# else + return true; +# endif +#endif +} +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +/* + * thread start function for the thread which processes the packets enqueued in our send and receive callbacks called by ndis + * + * ndis calls us at DISPATCH_LEVEL, while IntNet is using kernel functions which require Irql<DISPATCH_LEVEL + * this is why we can not immediately post packets to IntNet from our sen/receive callbacks + * instead we put the incoming packets to the queue and maintain the system thread running at passive level + * which processes the queue and posts the packets to IntNet, and further to the host or to the wire. + */ +static VOID vboxNetFltWinQuPacketQueueWorkerThreadProc(PVBOXNETFLTINS pNetFltIf) +{ + bool fResume = true; + NTSTATUS fStatus; + PPACKET_QUEUE_WORKER pWorker = &pNetFltIf->u.s.PacketQueueWorker; + + PVOID apEvents[] = { + (PVOID)&pWorker->KillEvent, + (PVOID)&pWorker->NotifyEvent + }; + + while (fResume) + { + uint32_t cNumProcessed; + uint32_t cNumPostedToHostWire; + + fStatus = KeWaitForMultipleObjects(RT_ELEMENTS(apEvents), apEvents, WaitAny, Executive, KernelMode, FALSE, NULL, NULL); + if (!NT_SUCCESS(fStatus) || fStatus == STATUS_WAIT_0) + { + /* "kill" event was set + * will process queued packets and exit */ + fResume = false; + } + + LogFlow(("processing vboxNetFltWinQuPacketQueueWorkerThreadProc\n")); + + cNumProcessed = 0; + cNumPostedToHostWire = 0; + + do + { + PVBOXNETFLTPACKET_INFO pInfo; + +#ifdef DEBUG_NETFLT_PACKETS + /* packet used for matching */ + PNDIS_PACKET pTmpPacket = NULL; +#endif + + /** @todo FIXME: !!! the better approach for performance would be to dequeue all packets at once + * and then go through all dequeued packets + * the same should be done for enqueue !!! */ + pInfo = vboxNetFltWinQuInterlockedDequeueHead(&pWorker->PacketQueue); + + if (!pInfo) + { + break; + } + + LogFlow(("found info (0x%p)\n", pInfo)); + + if (vboxNetFltWinQuProcessInfo(pNetFltIf, pWorker, pInfo->pPacket, pInfo->fFlags)) + { + cNumPostedToHostWire++; + } + + vboxNetFltWinPpFreePacketInfo(pInfo); + + cNumProcessed++; + } while (TRUE); + + if (cNumProcessed) + { + vboxNetFltWinDecReferenceNetFlt(pNetFltIf, cNumProcessed); + + Assert(cNumProcessed >= cNumPostedToHostWire); + + if (cNumProcessed != cNumPostedToHostWire) + { + vboxNetFltWinDecReferenceWinIf(pNetFltIf, cNumProcessed - cNumPostedToHostWire); + } + } + } + + PsTerminateSystemThread(STATUS_SUCCESS); +} +#endif +/** + * thread start function for the job processing thread + * + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static VOID vboxNetFltWinJobWorkerThreadProc(PVBOXNETFLT_JOB_QUEUE pQueue) +{ + bool fResume = true; + NTSTATUS Status; + + PVOID apEvents[] = { + (PVOID)&pQueue->KillEvent, + (PVOID)&pQueue->NotifyEvent, + }; + + do + { + Status = KeWaitForMultipleObjects(RT_ELEMENTS(apEvents), apEvents, WaitAny, Executive, KernelMode, FALSE, NULL, NULL); + Assert(NT_SUCCESS(Status)); + if (!NT_SUCCESS(Status) || Status == STATUS_WAIT_0) + { + /* will process queued jobs and exit */ + Assert(Status == STATUS_WAIT_0); + fResume = false; + } + + do + { + PLIST_ENTRY pJobEntry = ExInterlockedRemoveHeadList(&pQueue->Jobs, &pQueue->Lock); + PVBOXNETFLT_JOB pJob; + + if (!pJobEntry) + break; + + pJob = LIST_ENTRY_2_JOB(pJobEntry); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + pJob->pfnRoutine(pJob->pContext); + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + if (pJob->bUseCompletionEvent) + { + KeSetEvent(&pJob->CompletionEvent, 1, FALSE); + } + } while (TRUE); + } while (fResume); + + Assert(Status == STATUS_WAIT_0); + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +/** + * enqueues the job to the job queue to be processed by the job worker thread + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static VOID vboxNetFltWinJobEnqueueJob(PVBOXNETFLT_JOB_QUEUE pQueue, PVBOXNETFLT_JOB pJob, bool bEnqueueHead) +{ + if (bEnqueueHead) + { + ExInterlockedInsertHeadList(&pQueue->Jobs, &pJob->ListEntry, &pQueue->Lock); + } + else + { + ExInterlockedInsertTailList(&pQueue->Jobs, &pJob->ListEntry, &pQueue->Lock); + } + + KeSetEvent(&pQueue->NotifyEvent, 1, FALSE); +} + +DECLINLINE(VOID) vboxNetFltWinJobInit(PVBOXNETFLT_JOB pJob, PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext, bool bUseEvent) +{ + pJob->pfnRoutine = pfnRoutine; + pJob->pContext = pContext; + pJob->bUseCompletionEvent = bUseEvent; + if (bUseEvent) + KeInitializeEvent(&pJob->CompletionEvent, NotificationEvent, FALSE); +} + +/** + * enqueues the job to the job queue to be processed by the job worker thread and + * blocks until the job is done + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static VOID vboxNetFltWinJobSynchExec(PVBOXNETFLT_JOB_QUEUE pQueue, PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext) +{ + VBOXNETFLT_JOB Job; + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + vboxNetFltWinJobInit(&Job, pfnRoutine, pContext, true); + + vboxNetFltWinJobEnqueueJob(pQueue, &Job, false); + + KeWaitForSingleObject(&Job.CompletionEvent, Executive, KernelMode, FALSE, NULL); +} + +/** + * enqueues the job to be processed by the job worker thread at passive level and + * blocks until the job is done + */ +DECLHIDDEN(VOID) vboxNetFltWinJobSynchExecAtPassive(PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext) +{ + vboxNetFltWinJobSynchExec(&g_VBoxJobQueue, pfnRoutine, pContext); +} + +/** + * helper function used for system thread creation + */ +static NTSTATUS vboxNetFltWinQuCreateSystemThread(PKTHREAD *ppThread, PKSTART_ROUTINE pfnStartRoutine, PVOID pvStartContext) +{ + OBJECT_ATTRIBUTES ObjectAttributes; + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); + + HANDLE hThread; + NTSTATUS Status = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, &ObjectAttributes, NULL, NULL, (PKSTART_ROUTINE)pfnStartRoutine, pvStartContext); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Status = ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*)ppThread, NULL); + Assert(Status == STATUS_SUCCESS); + ZwClose(hThread); + if (Status == STATUS_SUCCESS) + { + return STATUS_SUCCESS; + } + + /** @todo how would we fail in this case ?*/ + } + return Status; +} + +/** + * initialize the job queue + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static NTSTATUS vboxNetFltWinJobInitQueue(PVBOXNETFLT_JOB_QUEUE pQueue) +{ + NTSTATUS fStatus; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + NdisZeroMemory(pQueue, sizeof(VBOXNETFLT_JOB_QUEUE)); + + KeInitializeEvent(&pQueue->KillEvent, NotificationEvent, FALSE); + + KeInitializeEvent(&pQueue->NotifyEvent, SynchronizationEvent, FALSE); + + InitializeListHead(&pQueue->Jobs); + + fStatus = vboxNetFltWinQuCreateSystemThread(&pQueue->pThread, (PKSTART_ROUTINE)vboxNetFltWinJobWorkerThreadProc, pQueue); + if (fStatus != STATUS_SUCCESS) + { + pQueue->pThread = NULL; + } + else + { + Assert(pQueue->pThread); + } + + return fStatus; +} + +/** + * deinitialize the job queue + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static void vboxNetFltWinJobFiniQueue(PVBOXNETFLT_JOB_QUEUE pQueue) +{ + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + if (pQueue->pThread) + { + KeSetEvent(&pQueue->KillEvent, 0, FALSE); + + KeWaitForSingleObject(pQueue->pThread, Executive, + KernelMode, FALSE, NULL); + } +} + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + +/** + * initializes the packet queue + * */ +DECLHIDDEN(NTSTATUS) vboxNetFltWinQuInitPacketQueue(PVBOXNETFLTINS pInstance) +{ + NTSTATUS Status; + PPACKET_QUEUE_WORKER pWorker = &pInstance->u.s.PacketQueueWorker; + + AssertFatal(!pWorker->pSG); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + KeInitializeEvent(&pWorker->KillEvent, NotificationEvent, FALSE); + + KeInitializeEvent(&pWorker->NotifyEvent, SynchronizationEvent, FALSE); + + INIT_INTERLOCKED_PACKET_QUEUE(&pWorker->PacketQueue); + + do + { + Status = vboxNetFltWinPpAllocatePacketInfoPool(&pWorker->PacketInfoPool, VBOXNETFLT_PACKET_INFO_POOL_SIZE); + + if (Status == NDIS_STATUS_SUCCESS) + { + pWorker->pSG = vboxNetFltWinCreateSG(PACKET_QUEUE_SG_SEGS_ALLOC); + if (!pWorker->pSG) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + Status = vboxNetFltWinQuCreateSystemThread(&pWorker->pThread, (PKSTART_ROUTINE)vboxNetFltWinQuPacketQueueWorkerThreadProc, pInstance); + if (Status != STATUS_SUCCESS) + { + vboxNetFltWinPpFreePacketInfoPool(&pWorker->PacketInfoPool); + vboxNetFltWinMemFree(pWorker->pSG); + pWorker->pSG = NULL; + break; + } + } + + } while (0); + + return Status; +} + +/* + * deletes the packet queue + */ +DECLHIDDEN(void) vboxNetFltWinQuFiniPacketQueue(PVBOXNETFLTINS pInstance) +{ + PINTNETSG pSG; + PPACKET_QUEUE_WORKER pWorker = &pInstance->u.s.PacketQueueWorker; + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + /* using the pPacketQueueSG as an indicator that the packet queue is initialized */ + RTSpinlockAcquire((pInstance)->hSpinlock); + if (pWorker->pSG) + { + pSG = pWorker->pSG; + pWorker->pSG = NULL; + RTSpinlockRelease((pInstance)->hSpinlock); + KeSetEvent(&pWorker->KillEvent, 0, FALSE); + + KeWaitForSingleObject(pWorker->pThread, Executive, + KernelMode, FALSE, NULL); + + vboxNetFltWinPpFreePacketInfoPool(&pWorker->PacketInfoPool); + + vboxNetFltWinDeleteSG(pSG); + + FINI_INTERLOCKED_PACKET_QUEUE(&pWorker->PacketQueue); + } + else + { + RTSpinlockRelease((pInstance)->hSpinlock); + } +} + +#endif + +/* + * creates the INTNETSG containing one segment pointing to the buffer of size cbBufSize + * the INTNETSG created should be cleaned with vboxNetFltWinMemFree + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinAllocSG(UINT cbPacket, PINTNETSG *ppSG) +{ + NDIS_STATUS Status; + PINTNETSG pSG; + + /* allocation: + * 1. SG_PACKET - with one aSegs pointing to + * 2. buffer of cbPacket containing the entire packet */ + AssertCompileSizeAlignment(INTNETSG, sizeof(PVOID)); + Status = vboxNetFltWinMemAlloc((PVOID*)&pSG, cbPacket + sizeof(INTNETSG)); + if (Status == NDIS_STATUS_SUCCESS) + { + IntNetSgInitTemp(pSG, pSG + 1, cbPacket); + LogFlow(("pSG created (%p)\n", pSG)); + *ppSG = pSG; + } + return Status; +} + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +/** + * put the packet info to the queue + */ +DECLINLINE(void) vboxNetFltWinQuEnqueueInfo(PVBOXNETFLTPACKET_QUEUE_WORKER pWorker, PVBOXNETFLTPACKET_INFO pInfo) +{ + vboxNetFltWinQuInterlockedEnqueueTail(&pWorker->PacketQueue, pInfo); + + KeSetEvent(&pWorker->NotifyEvent, IO_NETWORK_INCREMENT, FALSE); +} + +/** + * puts the packet to the queue + * + * @return NDIST_STATUS_SUCCESS iff the packet was enqueued successfully + * and error status otherwise. + * NOTE: that the success status does NOT mean that the packet processing is completed, but only that it was enqueued successfully + * the packet can be returned to the caller protocol/moniport only in case the bReleasePacket was set to true (in this case the copy of the packet was enqueued) + * or if vboxNetFltWinQuEnqueuePacket failed, i.e. the packet was NOT enqueued + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQuEnqueuePacket(PVBOXNETFLTINS pInstance, PVOID pPacket, const UINT fPacketFlags) +{ + PVBOXNETFLT_PACKET_INFO pInfo; + PVBOXNETFLT_PACKET_QUEUE_WORKER pWorker = &pInstance->u.s.PacketQueueWorker; + NDIS_STATUS fStatus = NDIS_STATUS_SUCCESS; + + do + { + if (fPacketFlags & PACKET_COPY) + { + PNDIS_BUFFER pBuffer = NULL; + UINT cBufferCount; + UINT uBytesCopied = 0; + UINT cbPacketLength; + PINTNETSG pSG; + + /* the packet is Ndis packet */ + Assert(!(fPacketFlags & PACKET_SG)); + Assert(!(fPacketFlags & PACKET_MINE)); + + NdisQueryPacket((PNDIS_PACKET)pPacket, + NULL, + &cBufferCount, + &pBuffer, + &cbPacketLength); + + + Assert(cBufferCount); + + fStatus = vboxNetFltWinAllocSG(cbPacketLength, &pSG); + if (fStatus != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + break; + } + + pInfo = vboxNetFltWinPpAllocPacketInfo(&pWorker->PacketInfoPool); + + if (!pInfo) + { + AssertFailed(); + /** @todo what status to set? */ + fStatus = NDIS_STATUS_FAILURE; + vboxNetFltWinMemFree(pSG); + break; + } + + Assert(pInfo->pPool); + + /* the packet we are queueing is SG, add PACKET_SG to flags */ + SET_FLAGS_TO_INFO(pInfo, fPacketFlags | PACKET_SG); + SET_PACKET_TO_INFO(pInfo, pSG); + + fStatus = vboxNetFltWinNdisBufferMoveToSG0(pBuffer, pSG); + if (fStatus != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + vboxNetFltWinPpFreePacketInfo(pInfo); + vboxNetFltWinMemFree(pSG); + break; + } + + DBG_CHECK_PACKET_AND_SG((PNDIS_PACKET)pPacket, pSG); + } + else + { + pInfo = vboxNetFltWinPpAllocPacketInfo(&pWorker->PacketInfoPool); + + if (!pInfo) + { + AssertFailed(); + /** @todo what status to set? */ + fStatus = NDIS_STATUS_FAILURE; + break; + } + + Assert(pInfo->pPool); + + SET_FLAGS_TO_INFO(pInfo, fPacketFlags); + SET_PACKET_TO_INFO(pInfo, pPacket); + } + + vboxNetFltWinQuEnqueueInfo(pWorker, pInfo); + + } while (0); + + return fStatus; +} +#endif + + +/* + * netflt + */ +#ifndef VBOXNETADP +static NDIS_STATUS vboxNetFltWinSynchNdisRequest(PVBOXNETFLTINS pNetFlt, PNDIS_REQUEST pRequest) +{ + int rc; + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + /* 1. serialize */ + rc = RTSemFastMutexRequest(pNetFlt->u.s.WinIf.hSynchRequestMutex); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + NDIS_STATUS fRequestStatus = NDIS_STATUS_SUCCESS; + + /* 2. set pNetFlt->u.s.pSynchRequest */ + Assert(!pNetFlt->u.s.WinIf.pSynchRequest); + pNetFlt->u.s.WinIf.pSynchRequest = pRequest; + + /* 3. call NdisRequest */ + NdisRequest(&fRequestStatus, pNetFlt->u.s.WinIf.hBinding, pRequest); + + if (fRequestStatus == NDIS_STATUS_PENDING) + { + /* 3.1 if pending wait and assign the resulting status */ + KeWaitForSingleObject(&pNetFlt->u.s.WinIf.hSynchCompletionEvent, Executive, + KernelMode, FALSE, NULL); + + fRequestStatus = pNetFlt->u.s.WinIf.SynchCompletionStatus; + } + + /* 4. clear the pNetFlt->u.s.pSynchRequest */ + pNetFlt->u.s.WinIf.pSynchRequest = NULL; + + RTSemFastMutexRelease(pNetFlt->u.s.WinIf.hSynchRequestMutex); AssertRC(rc); + return fRequestStatus; + } + return NDIS_STATUS_FAILURE; +} + + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinGetMacAddress(PVBOXNETFLTINS pNetFlt, PRTMAC pMac) +{ + NDIS_REQUEST request; + NDIS_STATUS status; + request.RequestType = NdisRequestQueryInformation; + request.DATA.QUERY_INFORMATION.InformationBuffer = pMac; + request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(RTMAC); + request.DATA.QUERY_INFORMATION.Oid = OID_802_3_CURRENT_ADDRESS; + status = vboxNetFltWinSynchNdisRequest(pNetFlt, &request); + if (status != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + } + + return status; + +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQueryPhysicalMedium(PVBOXNETFLTINS pNetFlt, NDIS_PHYSICAL_MEDIUM * pMedium) +{ + NDIS_REQUEST Request; + NDIS_STATUS Status; + Request.RequestType = NdisRequestQueryInformation; + Request.DATA.QUERY_INFORMATION.InformationBuffer = pMedium; + Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(NDIS_PHYSICAL_MEDIUM); + Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_PHYSICAL_MEDIUM; + Status = vboxNetFltWinSynchNdisRequest(pNetFlt, &Request); + if (Status != NDIS_STATUS_SUCCESS) + { + if (Status == NDIS_STATUS_NOT_SUPPORTED || Status == NDIS_STATUS_NOT_RECOGNIZED || Status == NDIS_STATUS_INVALID_OID) + { + Status = NDIS_STATUS_NOT_SUPPORTED; + } + else + { + LogRel(("OID_GEN_PHYSICAL_MEDIUM failed: Status (0x%x)", Status)); + AssertFailed(); + } + } + return Status; +} + +DECLHIDDEN(bool) vboxNetFltWinIsPromiscuous(PVBOXNETFLTINS pNetFlt) +{ + /** @todo r=bird: This is too slow and is probably returning the wrong + * information. What we're interested in is whether someone besides us + * has put the interface into promiscuous mode. */ + NDIS_REQUEST request; + NDIS_STATUS status; + ULONG filter; + Assert(VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)); + request.RequestType = NdisRequestQueryInformation; + request.DATA.QUERY_INFORMATION.InformationBuffer = &filter; + request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(filter); + request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + status = vboxNetFltWinSynchNdisRequest(pNetFlt, &request); + if (status != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + return false; + } + return (filter & NDIS_PACKET_TYPE_PROMISCUOUS) != 0; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinSetPromiscuous(PVBOXNETFLTINS pNetFlt, bool bYes) +{ +/** @todo Need to report changes to the switch via: + * pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, fPromisc); + */ + Assert(VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)); + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + NDIS_REQUEST Request; + NDIS_STATUS fStatus; + ULONG fFilter; + ULONG fExpectedFilter; + ULONG fOurFilter; + Request.RequestType = NdisRequestQueryInformation; + Request.DATA.QUERY_INFORMATION.InformationBuffer = &fFilter; + Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(fFilter); + Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + fStatus = vboxNetFltWinSynchNdisRequest(pNetFlt, &Request); + if (fStatus != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + return fStatus; + } + + if (!pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized) + { + /* the cache was not initialized yet, initiate it with the current filter value */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = fFilter; + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + } + + + if (bYes) + { + fExpectedFilter = NDIS_PACKET_TYPE_PROMISCUOUS; + fOurFilter = NDIS_PACKET_TYPE_PROMISCUOUS; + } + else + { + fExpectedFilter = pNetFlt->u.s.WinIf.fUpperProtocolSetFilter; + fOurFilter = 0; + } + + if (fExpectedFilter != fFilter) + { + Request.RequestType = NdisRequestSetInformation; + Request.DATA.SET_INFORMATION.InformationBuffer = &fExpectedFilter; + Request.DATA.SET_INFORMATION.InformationBufferLength = sizeof(fExpectedFilter); + Request.DATA.SET_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + fStatus = vboxNetFltWinSynchNdisRequest(pNetFlt, &Request); + if (fStatus != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + return fStatus; + } + } + pNetFlt->u.s.WinIf.fOurSetFilter = fOurFilter; + return fStatus; + } + return NDIS_STATUS_NOT_SUPPORTED; +} + +#else /* VBOXNETADP */ + +/** + * Generates a new unique MAC address based on our vendor ID + */ +DECLHIDDEN(void) vboxNetFltWinGenerateMACAddress(RTMAC *pMac) +{ + /* temporary use a time info */ + uint64_t NanoTS = RTTimeSystemNanoTS(); + pMac->au8[0] = (uint8_t)((VBOXNETADP_VENDOR_ID >> 16) & 0xff); + pMac->au8[1] = (uint8_t)((VBOXNETADP_VENDOR_ID >> 8) & 0xff); + pMac->au8[2] = (uint8_t)(VBOXNETADP_VENDOR_ID & 0xff); + pMac->au8[3] = (uint8_t)(NanoTS & 0xff0000); + pMac->au16[2] = (uint16_t)(NanoTS & 0xffff); +} + +DECLHIDDEN(int) vboxNetFltWinMAC2NdisString(RTMAC *pMac, PNDIS_STRING pNdisString) +{ + static const char s_achDigits[17] = "0123456789abcdef"; + PWSTR pString; + + /* validate parameters */ + AssertPtrReturn(pMac, VERR_INVALID_PARAMETER); + AssertPtrReturn(pNdisString, VERR_INVALID_PARAMETER); + AssertReturn(pNdisString->MaximumLength >= 13*sizeof(pNdisString->Buffer[0]), VERR_INVALID_PARAMETER); + + pString = pNdisString->Buffer; + + for (int i = 0; i < 6; i++) + { + uint8_t u8 = pMac->au8[i]; + pString[0] = s_achDigits[(u8 >> 4) & 0xf]; + pString[1] = s_achDigits[(u8/*>>0*/)& 0xf]; + pString += 2; + } + + pNdisString->Length = 12*sizeof(pNdisString->Buffer[0]); + + *pString = L'\0'; + + return VINF_SUCCESS; +} + +static int vboxNetFltWinWchar2Byte(WCHAR c, uint8_t *pb) +{ + if (c >= L'A' && c <= L'F') + *pb = (c - L'A') + 10; + else if (c >= L'a' && c <= L'f') + *pb = (c - L'a') + 10; + else if (c >= L'0' && c <= L'9') + *pb = (c - L'0'); + else + return VERR_INVALID_PARAMETER; + return VINF_SUCCESS; +} + +DECLHIDDEN(int) vboxNetFltWinMACFromNdisString(RTMAC *pMac, PNDIS_STRING pNdisString) +{ + + /* validate parameters */ + AssertPtrReturn(pMac, VERR_INVALID_PARAMETER); + AssertPtrReturn(pNdisString, VERR_INVALID_PARAMETER); + AssertReturn(pNdisString->Length >= 12*sizeof(pNdisString->Buffer[0]), VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PWSTR pString = pNdisString->Buffer; + for (int i = 0; i < 6; i++) + { + uint8_t v1, v2; + rc = vboxNetFltWinWchar2Byte(pString[0], &v1); + if (RT_FAILURE(rc)) + break; + + rc = vboxNetFltWinWchar2Byte(pString[1], &v2); + if (RT_FAILURE(rc)) + break; + + pMac->au8[i] = (v1 << 4) | v2; + + pString += 2; + } + + return rc; +} + +#endif /* VBOXNETADP */ + +/** + * creates a NDIS_PACKET from the PINTNETSG + */ +DECLHIDDEN(PNDIS_PACKET) vboxNetFltWinNdisPacketFromSG(PVBOXNETFLTINS pNetFlt, PINTNETSG pSG, PVOID pBufToFree, bool bToWire, bool bCopyMemory) +{ + NDIS_STATUS fStatus; + PNDIS_PACKET pPacket; + + Assert(pSG->aSegs[0].pv); + Assert(pSG->cbTotal >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + +/** @todo Hrmpf, how can we fix this assumption? I fear this'll cause data + * corruption and maybe even BSODs ... */ + AssertReturn(pSG->cSegsUsed == 1 || bCopyMemory, NULL); + +#ifdef VBOXNETADP + NdisAllocatePacket(&fStatus, &pPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); +#else + NdisAllocatePacket(&fStatus, &pPacket, bToWire ? pNetFlt->u.s.WinIf.hSendPacketPool : pNetFlt->u.s.WinIf.hRecvPacketPool); +#endif + if (fStatus == NDIS_STATUS_SUCCESS) + { + PNDIS_BUFFER pBuffer; + PVOID pvMemBuf; + + /** @todo generally we do not always need to zero-initialize the complete OOB data here, reinitialize only when/what we need, + * however we DO need to reset the status for the packets we indicate via NdisMIndicateReceivePacket to avoid packet loss + * in case the status contains NDIS_STATUS_RESOURCES */ + VBOXNETFLT_OOB_INIT(pPacket); + + if (bCopyMemory) + { + fStatus = vboxNetFltWinMemAlloc(&pvMemBuf, pSG->cbTotal); + Assert(fStatus == NDIS_STATUS_SUCCESS); + if (fStatus == NDIS_STATUS_SUCCESS) + IntNetSgRead(pSG, pvMemBuf); + } + else + { + pvMemBuf = pSG->aSegs[0].pv; + } + if (fStatus == NDIS_STATUS_SUCCESS) + { +#ifdef VBOXNETADP + NdisAllocateBuffer(&fStatus, &pBuffer, + pNetFlt->u.s.WinIf.hRecvBufferPool, + pvMemBuf, + pSG->cbTotal); +#else + NdisAllocateBuffer(&fStatus, &pBuffer, + bToWire ? pNetFlt->u.s.WinIf.hSendBufferPool : pNetFlt->u.s.WinIf.hRecvBufferPool, + pvMemBuf, + pSG->cbTotal); +#endif + + if (fStatus == NDIS_STATUS_SUCCESS) + { + NdisChainBufferAtBack(pPacket, pBuffer); + + if (bToWire) + { + PVBOXNETFLT_PKTRSVD_PT pSendInfo = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + pSendInfo->pOrigPacket = NULL; + pSendInfo->pBufToFree = pBufToFree; +#ifdef VBOX_LOOPBACK_USEFLAGS + /* set "don't loopback" flags */ + NdisGetPacketFlags(pPacket) = g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#else + NdisGetPacketFlags(pPacket) = 0; +#endif + } + else + { + PVBOXNETFLT_PKTRSVD_MP pRecvInfo = (PVBOXNETFLT_PKTRSVD_MP)pPacket->MiniportReserved; + pRecvInfo->pOrigPacket = NULL; + pRecvInfo->pBufToFree = pBufToFree; + + /* we must set the header size on receive */ + NDIS_SET_PACKET_HEADER_SIZE(pPacket, VBOXNETFLT_PACKET_ETHEADER_SIZE); + /* NdisAllocatePacket zero-initializes the OOB data, + * but keeps the packet flags, clean them here */ + NdisGetPacketFlags(pPacket) = 0; + } + /** @todo set out of bound data */ + } + else + { + AssertFailed(); + if (bCopyMemory) + { + vboxNetFltWinMemFree(pvMemBuf); + } + NdisFreePacket(pPacket); + pPacket = NULL; + } + } + else + { + AssertFailed(); + NdisFreePacket(pPacket); + pPacket = NULL; + } + } + else + { + pPacket = NULL; + } + + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + + return pPacket; +} + +/* + * frees NDIS_PACKET created with vboxNetFltWinNdisPacketFromSG + */ +DECLHIDDEN(void) vboxNetFltWinFreeSGNdisPacket(PNDIS_PACKET pPacket, bool bFreeMem) +{ + UINT cBufCount; + PNDIS_BUFFER pFirstBuffer; + UINT uTotalPacketLength; + PNDIS_BUFFER pBuffer; + + NdisQueryPacket(pPacket, NULL, &cBufCount, &pFirstBuffer, &uTotalPacketLength); + + Assert(cBufCount == 1); + + do + { + NdisUnchainBufferAtBack(pPacket, &pBuffer); + if (pBuffer != NULL) + { + PVOID pvMemBuf; + UINT cbLength; + + NdisQueryBufferSafe(pBuffer, &pvMemBuf, &cbLength, NormalPagePriority); + NdisFreeBuffer(pBuffer); + if (bFreeMem) + { + vboxNetFltWinMemFree(pvMemBuf); + } + } + else + { + break; + } + } while (true); + + NdisFreePacket(pPacket); +} + +#if !defined(VBOXNETADP) +static void vboxNetFltWinAssociateMiniportProtocol(PVBOXNETFLTGLOBALS_WIN pGlobalsWin) +{ + NdisIMAssociateMiniport(pGlobalsWin->Mp.hMiniport, pGlobalsWin->Pt.hProtocol); +} +#endif + +/* + * NetFlt driver unload function + */ +DECLHIDDEN(VOID) vboxNetFltWinUnload(IN PDRIVER_OBJECT DriverObject) +{ + int rc; + UNREFERENCED_PARAMETER(DriverObject); + + LogFlowFunc(("ENTER: DO (0x%x)\n", DriverObject)); + + rc = vboxNetFltWinFiniIdc(); + if (RT_FAILURE(rc)) + { + /** @todo we can not prevent driver unload here */ + AssertFailed(); + + LogFlowFunc(("vboxNetFltWinFiniIdc - failed, busy.\n")); + } + + vboxNetFltWinJobFiniQueue(&g_VBoxJobQueue); +#ifndef VBOXNETADP + vboxNetFltWinPtDeregister(&g_VBoxNetFltGlobalsWin.Pt); +#endif + + vboxNetFltWinMpDeregister(&g_VBoxNetFltGlobalsWin.Mp); + +#ifndef VBOXNETADP + NdisFreeSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); +#endif /* VBOXNETADP */ + + LogFlow(("LEAVE: DO (0x%x)\n", DriverObject)); + + vboxNetFltWinFiniNetFltBase(); + /* don't use logging or any RT after de-init */ +} + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + /* the idc registration is initiated via IOCTL since our driver + * can be loaded when the VBoxDrv is not in case we are a Ndis IM driver */ + rc = vboxNetFltWinInitNetFltBase(); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Status = vboxNetFltWinJobInitQueue(&g_VBoxJobQueue); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + ULONG MjVersion; + ULONG MnVersion; + + /* note: we do it after we initialize the Job Queue */ + vboxNetFltWinStartInitIdcProbing(); + + NdisZeroMemory(&g_VBoxNetFltGlobalsWin, sizeof (g_VBoxNetFltGlobalsWin)); + KeInitializeEvent(&g_VBoxNetFltGlobalsWin.SynchEvent, SynchronizationEvent, TRUE /* signalled*/); + + PsGetVersion(&MjVersion, &MnVersion, + NULL, /* PULONG BuildNumber OPTIONAL */ + NULL /* PUNICODE_STRING CSDVersion OPTIONAL */ + ); + + g_VBoxNetFltGlobalsWin.fPacketDontLoopBack = NDIS_FLAGS_DONT_LOOPBACK; + + if (MjVersion == 5 && MnVersion == 0) + { + /* this is Win2k, we don't support it actually, but just in case */ + g_VBoxNetFltGlobalsWin.fPacketDontLoopBack |= NDIS_FLAGS_SKIP_LOOPBACK_W2K; + } + + g_VBoxNetFltGlobalsWin.fPacketIsLoopedBack = NDIS_FLAGS_IS_LOOPBACK_PACKET; + +#ifndef VBOXNETADP + RTListInit(&g_VBoxNetFltGlobalsWin.listFilters); + NdisAllocateSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); +#endif + + Status = vboxNetFltWinMpRegister(&g_VBoxNetFltGlobalsWin.Mp, DriverObject, RegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { +#ifndef VBOXNETADP + Status = vboxNetFltWinPtRegister(&g_VBoxNetFltGlobalsWin.Pt, DriverObject, RegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) +#endif + { +#ifndef VBOXNETADP + vboxNetFltWinAssociateMiniportProtocol(&g_VBoxNetFltGlobalsWin); +#endif + return STATUS_SUCCESS; + +//#ifndef VBOXNETADP +// vboxNetFltWinPtDeregister(&g_VBoxNetFltGlobalsWin.Pt); +//#endif + } +#ifndef VBOXNETADP /* unreachable for VBOXNETADP because of the above return */ + vboxNetFltWinMpDeregister(&g_VBoxNetFltGlobalsWin.Mp); +# ifndef VBOXNETADP + NdisFreeSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); +# endif +#endif + } + vboxNetFltWinJobFiniQueue(&g_VBoxJobQueue); + } + vboxNetFltWinFiniNetFlt(); + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} + +#ifndef VBOXNETADP +/** + * creates and initializes the packet to be sent to the underlying miniport given a packet posted to our miniport edge + * according to DDK docs we must create our own packet rather than posting the one passed to us + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket) +{ + NDIS_STATUS Status; + + NdisAllocatePacket(&Status, ppMyPacket, pNetFlt->u.s.WinIf.hSendPacketPool); + + if (Status == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLT_PKTRSVD_PT pSendInfo = (PVBOXNETFLT_PKTRSVD_PT)((*ppMyPacket)->ProtocolReserved); + pSendInfo->pOrigPacket = pPacket; + pSendInfo->pBufToFree = NULL; + /* the rest will be filled on send */ + + vboxNetFltWinCopyPacketInfoOnSend(*ppMyPacket, pPacket); + +#ifdef VBOX_LOOPBACK_USEFLAGS + NdisGetPacketFlags(*ppMyPacket) |= g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#endif + } + else + { + *ppMyPacket = NULL; + } + + return Status; +} + +/** + * creates and initializes the packet to be sent to the upperlying protocol given a packet indicated to our protocol edge + * according to DDK docs we must create our own packet rather than posting the one passed to us + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareRecvPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket, bool bDpr) +{ + NDIS_STATUS Status; + + if (bDpr) + { + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + NdisDprAllocatePacket(&Status, ppMyPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + } + else + { + NdisAllocatePacket(&Status, ppMyPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + } + + if (Status == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLT_PKTRSVD_MP pRecvInfo = (PVBOXNETFLT_PKTRSVD_MP)((*ppMyPacket)->MiniportReserved); + pRecvInfo->pOrigPacket = pPacket; + pRecvInfo->pBufToFree = NULL; + + Status = vboxNetFltWinCopyPacketInfoOnRecv(*ppMyPacket, pPacket, false); + } + else + { + *ppMyPacket = NULL; + } + return Status; +} +#endif +/** + * initializes the VBOXNETFLTINS (our context structure) and binds to the given adapter + */ +#if defined(VBOXNETADP) +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, NDIS_HANDLE hMiniportAdapter, PNDIS_STRING pBindToMiniportName /* actually this is our miniport name*/, NDIS_HANDLE hWrapperConfigurationContext) +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, PNDIS_STRING pOurMiniportName, PNDIS_STRING pBindToMiniportName) +#endif +{ + NDIS_STATUS Status; + do + { + ANSI_STRING AnsiString; + int rc; + PVBOXNETFLTINS pInstance; + USHORT cbAnsiName = pBindToMiniportName->Length;/* the length is is bytes ; *2 ;RtlUnicodeStringToAnsiSize(pBindToMiniportName)*/ + CREATE_INSTANCE_CONTEXT Context; + +# ifndef VBOXNETADP + Context.pOurName = pOurMiniportName; + Context.pBindToName = pBindToMiniportName; +# else + Context.hMiniportAdapter = hMiniportAdapter; + Context.hWrapperConfigurationContext = hWrapperConfigurationContext; +# endif + Context.Status = NDIS_STATUS_SUCCESS; + + AnsiString.Buffer = 0; /* will be allocated by RtlUnicodeStringToAnsiString */ + AnsiString.Length = 0; + AnsiString.MaximumLength = cbAnsiName; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + Status = RtlUnicodeStringToAnsiString(&AnsiString, pBindToMiniportName, true); + + if (Status != STATUS_SUCCESS) + { + break; + } + + rc = vboxNetFltSearchCreateInstance(&g_VBoxNetFltGlobals, AnsiString.Buffer, &pInstance, &Context); + RtlFreeAnsiString(&AnsiString); + if (RT_FAILURE(rc)) + { + AssertFailed(); + Status = Context.Status != NDIS_STATUS_SUCCESS ? Context.Status : NDIS_STATUS_FAILURE; + break; + } + + Assert(pInstance); + + if (rc == VINF_ALREADY_INITIALIZED) + { + /* the case when our adapter was unbound while IntNet was connected to it */ + /* the instance remains valid until IntNet disconnects from it, we simply search and re-use it*/ + rc = vboxNetFltWinAttachToInterface(pInstance, &Context, true); + if (RT_FAILURE(rc)) + { + AssertFailed(); + Status = Context.Status != NDIS_STATUS_SUCCESS ? Context.Status : NDIS_STATUS_FAILURE; + /* release netflt */ + vboxNetFltRelease(pInstance, false); + + break; + } + } + + *ppNetFlt = pInstance; + + } while (FALSE); + + return Status; +} +/* + * deinitializes the VBOXNETFLTWIN + */ +DECLHIDDEN(VOID) vboxNetFltWinPtFiniWinIf(PVBOXNETFLTWIN pWinIf) +{ +#ifndef VBOXNETADP + int rc; +#endif + + LogFlowFunc(("ENTER: pWinIf 0x%p\n", pWinIf)); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); +#ifndef VBOXNETADP + if (pWinIf->MpDeviceName.Buffer) + { + vboxNetFltWinMemFree(pWinIf->MpDeviceName.Buffer); + } + + FINI_INTERLOCKED_SINGLE_LIST(&pWinIf->TransferDataList); +# if defined(DEBUG_NETFLT_LOOPBACK) || !defined(VBOX_LOOPBACK_USEFLAGS) + FINI_INTERLOCKED_SINGLE_LIST(&pWinIf->SendPacketQueue); +# endif + NdisFreeBufferPool(pWinIf->hSendBufferPool); + NdisFreePacketPool(pWinIf->hSendPacketPool); + rc = RTSemFastMutexDestroy(pWinIf->hSynchRequestMutex); AssertRC(rc); +#endif + + /* NOTE: NULL is a valid handle */ + NdisFreeBufferPool(pWinIf->hRecvBufferPool); + NdisFreePacketPool(pWinIf->hRecvPacketPool); + + LogFlowFunc(("LEAVE: pWinIf 0x%p\n", pWinIf)); +} + +#ifndef VBOXNETADP +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf, IN PNDIS_STRING pOurDeviceName) +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf) +#endif +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; +#ifndef VBOXNETADP + int rc; +#endif + + LogFlowFunc(("ENTER: pWinIf 0x%p\n", pWinIf)); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + NdisZeroMemory(pWinIf, sizeof (VBOXNETFLTWIN)); + NdisAllocatePacketPoolEx(&Status, &pWinIf->hRecvPacketPool, + VBOXNETFLT_PACKET_POOL_SIZE_NORMAL, + VBOXNETFLT_PACKET_POOL_SIZE_OVERFLOW, + PROTOCOL_RESERVED_SIZE_IN_PACKET); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + /* NOTE: NULL is a valid handle !!! */ + NdisAllocateBufferPool(&Status, &pWinIf->hRecvBufferPool, VBOXNETFLT_BUFFER_POOL_SIZE_RX); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + pWinIf->MpState.PowerState = NdisDeviceStateD3; + vboxNetFltWinSetOpState(&pWinIf->MpState, kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + pWinIf->PtState.PowerState = NdisDeviceStateD3; + vboxNetFltWinSetOpState(&pWinIf->PtState, kVBoxNetDevOpState_Deinitialized); + + NdisAllocateBufferPool(&Status, + &pWinIf->hSendBufferPool, + VBOXNETFLT_BUFFER_POOL_SIZE_TX); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + INIT_INTERLOCKED_SINGLE_LIST(&pWinIf->TransferDataList); + +# if defined(DEBUG_NETFLT_LOOPBACK) || !defined(VBOX_LOOPBACK_USEFLAGS) + INIT_INTERLOCKED_SINGLE_LIST(&pWinIf->SendPacketQueue); +# endif + NdisInitializeEvent(&pWinIf->OpenCloseEvent); + + KeInitializeEvent(&pWinIf->hSynchCompletionEvent, SynchronizationEvent, FALSE); + + NdisInitializeEvent(&pWinIf->MpInitCompleteEvent); + + NdisAllocatePacketPoolEx(&Status, &pWinIf->hSendPacketPool, + VBOXNETFLT_PACKET_POOL_SIZE_NORMAL, + VBOXNETFLT_PACKET_POOL_SIZE_OVERFLOW, + sizeof (PVBOXNETFLT_PKTRSVD_PT)); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + rc = RTSemFastMutexCreate(&pWinIf->hSynchRequestMutex); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Status = vboxNetFltWinMemAlloc((PVOID*)&pWinIf->MpDeviceName.Buffer, pOurDeviceName->Length); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + pWinIf->MpDeviceName.MaximumLength = pOurDeviceName->Length; + pWinIf->MpDeviceName.Length = 0; + Status = vboxNetFltWinCopyString(&pWinIf->MpDeviceName, pOurDeviceName); +#endif + return NDIS_STATUS_SUCCESS; +#ifndef VBOXNETADP + // unreachable: vboxNetFltWinMemFree(pWinIf->MpDeviceName.Buffer); + } + RTSemFastMutexDestroy(pWinIf->hSynchRequestMutex); + } + else + Status = NDIS_STATUS_FAILURE; + NdisFreePacketPool(pWinIf->hSendPacketPool); + } + NdisFreeBufferPool(pWinIf->hSendBufferPool); + } + NdisFreeBufferPool(pWinIf->hRecvBufferPool); +#endif + } + NdisFreePacketPool(pWinIf->hRecvPacketPool); + } + + LogFlowFunc(("LEAVE: pWinIf 0x%p, Status 0x%x\n", pWinIf, Status)); + + return Status; +} + +/** + * match packets + */ +#define NEXT_LIST_ENTRY(_Entry) ((_Entry)->Flink) +#define PREV_LIST_ENTRY(_Entry) ((_Entry)->Blink) +#define FIRST_LIST_ENTRY NEXT_LIST_ENTRY +#define LAST_LIST_ENTRY PREV_LIST_ENTRY + +#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) + +#ifndef VBOXNETADP + +#ifdef DEBUG_misha + +RTMAC g_vboxNetFltWinVerifyMACBroadcast = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +RTMAC g_vboxNetFltWinVerifyMACGuest = {0x08, 0x00, 0x27, 0x01, 0x02, 0x03}; + +DECLHIDDEN(PRTNETETHERHDR) vboxNetFltWinGetEthHdr(PNDIS_PACKET pPacket) +{ + UINT cBufCount1; + PNDIS_BUFFER pBuffer1; + UINT uTotalPacketLength1; + RTNETETHERHDR* pEth; + UINT cbLength1 = 0; + UINT i = 0; + + NdisQueryPacket(pPacket, NULL, &cBufCount1, &pBuffer1, &uTotalPacketLength1); + + Assert(pBuffer1); + Assert(uTotalPacketLength1 >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + if (uTotalPacketLength1 < VBOXNETFLT_PACKET_ETHEADER_SIZE) + return NULL; + + NdisQueryBufferSafe(pBuffer1, &pEth, &cbLength1, NormalPagePriority); + Assert(cbLength1 >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + if (cbLength1 < VBOXNETFLT_PACKET_ETHEADER_SIZE) + return NULL; + + return pEth; +} + +DECLHIDDEN(PRTNETETHERHDR) vboxNetFltWinGetEthHdrSG(PINTNETSG pSG) +{ + Assert(pSG->cSegsUsed); + Assert(pSG->cSegsAlloc >= pSG->cSegsUsed); + Assert(pSG->aSegs[0].cb >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + + if (!pSG->cSegsUsed) + return NULL; + + if (pSG->aSegs[0].cb < VBOXNETFLT_PACKET_ETHEADER_SIZE) + return NULL; + + return (PRTNETETHERHDR)pSG->aSegs[0].pv; +} + +DECLHIDDEN(bool) vboxNetFltWinCheckMACs(PNDIS_PACKET pPacket, PRTMAC pDst, PRTMAC pSrc) +{ + PRTNETETHERHDR pHdr = vboxNetFltWinGetEthHdr(pPacket); + Assert(pHdr); + + if (!pHdr) + return false; + + if (pDst && memcmp(pDst, &pHdr->DstMac, sizeof(RTMAC))) + return false; + + if (pSrc && memcmp(pSrc, &pHdr->SrcMac, sizeof(RTMAC))) + return false; + + return true; +} + +DECLHIDDEN(bool) vboxNetFltWinCheckMACsSG(PINTNETSG pSG, PRTMAC pDst, PRTMAC pSrc) +{ + PRTNETETHERHDR pHdr = vboxNetFltWinGetEthHdrSG(pSG); + Assert(pHdr); + + if (!pHdr) + return false; + + if (pDst && memcmp(pDst, &pHdr->DstMac, sizeof(RTMAC))) + return false; + + if (pSrc && memcmp(pSrc, &pHdr->SrcMac, sizeof(RTMAC))) + return false; + + return true; +} +#endif + +# if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) +/* + * answers whether the two given packets match based on the packet length and the first cbMatch bytes of the packets + * if cbMatch < 0 matches complete packets. + */ +DECLHIDDEN(bool) vboxNetFltWinMatchPackets(PNDIS_PACKET pPacket1, PNDIS_PACKET pPacket2, const INT cbMatch) +{ + UINT cBufCount1; + PNDIS_BUFFER pBuffer1; + UINT uTotalPacketLength1; + uint8_t *pbMemBuf1 = NULL; + UINT cbLength1 = 0; + + UINT cBufCount2; + PNDIS_BUFFER pBuffer2; + UINT uTotalPacketLength2; + uint8_t *pbMemBuf2 = NULL; + UINT cbLength2 = 0; + bool bMatch = true; + +#ifdef DEBUG_NETFLT_PACKETS + bool bCompleteMatch = false; +#endif + + NdisQueryPacket(pPacket1, NULL, &cBufCount1, &pBuffer1, &uTotalPacketLength1); + NdisQueryPacket(pPacket2, NULL, &cBufCount2, &pBuffer2, &uTotalPacketLength2); + + Assert(pBuffer1); + Assert(pBuffer2); + + if (uTotalPacketLength1 != uTotalPacketLength2) + { + bMatch = false; + } + else + { + UINT ucbLength2Match = 0; + UINT ucbMatch; + if (cbMatch < 0 || (UINT)cbMatch > uTotalPacketLength1) + { + /* NOTE: assuming uTotalPacketLength1 == uTotalPacketLength2*/ + ucbMatch = uTotalPacketLength1; +#ifdef DEBUG_NETFLT_PACKETS + bCompleteMatch = true; +#endif + } + else + { + ucbMatch = (UINT)cbMatch; + } + + for (;;) + { + if (!cbLength1) + { + NdisQueryBufferSafe(pBuffer1, &pbMemBuf1, &cbLength1, NormalPagePriority); + NdisGetNextBuffer(pBuffer1, &pBuffer1); + } + else + { + Assert(pbMemBuf1); + Assert(ucbLength2Match); + pbMemBuf1 += ucbLength2Match; + } + + if (!cbLength2) + { + NdisQueryBufferSafe(pBuffer2, &pbMemBuf2, &cbLength2, NormalPagePriority); + NdisGetNextBuffer(pBuffer2, &pBuffer2); + } + else + { + Assert(pbMemBuf2); + Assert(ucbLength2Match); + pbMemBuf2 += ucbLength2Match; + } + + ucbLength2Match = MIN(ucbMatch, cbLength1); + ucbLength2Match = MIN(ucbLength2Match, cbLength2); + + if (memcmp(pbMemBuf1, pbMemBuf2, ucbLength2Match)) + { + bMatch = false; + break; + } + + ucbMatch -= ucbLength2Match; + if (!ucbMatch) + break; + + cbLength1 -= ucbLength2Match; + cbLength2 -= ucbLength2Match; + } + } + +#ifdef DEBUG_NETFLT_PACKETS + if (bMatch && !bCompleteMatch) + { + /* check that the packets fully match */ + DBG_CHECK_PACKETS(pPacket1, pPacket2); + } +#endif + + return bMatch; +} + +/* + * answers whether the ndis packet and PINTNETSG match based on the packet length and the first cbMatch bytes of the packet and PINTNETSG + * if cbMatch < 0 matches complete packets. + */ +DECLHIDDEN(bool) vboxNetFltWinMatchPacketAndSG(PNDIS_PACKET pPacket, PINTNETSG pSG, const INT cbMatch) +{ + UINT cBufCount1; + PNDIS_BUFFER pBuffer1; + UINT uTotalPacketLength1; + uint8_t *pbMemBuf1 = NULL; + UINT cbLength1 = 0; + UINT uTotalPacketLength2 = pSG->cbTotal; + uint8_t *pbMemBuf2 = NULL; + UINT cbLength2 = 0; + bool bMatch = true; + bool bCompleteMatch = false; + UINT i = 0; + + NdisQueryPacket(pPacket, NULL, &cBufCount1, &pBuffer1, &uTotalPacketLength1); + + Assert(pBuffer1); + Assert(pSG->cSegsUsed); + Assert(pSG->cSegsAlloc >= pSG->cSegsUsed); + + if (uTotalPacketLength1 != uTotalPacketLength2) + { + AssertFailed(); + bMatch = false; + } + else + { + UINT ucbLength2Match = 0; + UINT ucbMatch; + + if (cbMatch < 0 || (UINT)cbMatch > uTotalPacketLength1) + { + /* NOTE: assuming uTotalPacketLength1 == uTotalPacketLength2*/ + ucbMatch = uTotalPacketLength1; + bCompleteMatch = true; + } + else + { + ucbMatch = (UINT)cbMatch; + } + + for (;;) + { + if (!cbLength1) + { + NdisQueryBufferSafe(pBuffer1, &pbMemBuf1, &cbLength1, NormalPagePriority); + NdisGetNextBuffer(pBuffer1, &pBuffer1); + } + else + { + Assert(pbMemBuf1); + Assert(ucbLength2Match); + pbMemBuf1 += ucbLength2Match; + } + + if (!cbLength2) + { + Assert(i < pSG->cSegsUsed); + pbMemBuf2 = (uint8_t*)pSG->aSegs[i].pv; + cbLength2 = pSG->aSegs[i].cb; + i++; + } + else + { + Assert(pbMemBuf2); + Assert(ucbLength2Match); + pbMemBuf2 += ucbLength2Match; + } + + ucbLength2Match = MIN(ucbMatch, cbLength1); + ucbLength2Match = MIN(ucbLength2Match, cbLength2); + + if (memcmp(pbMemBuf1, pbMemBuf2, ucbLength2Match)) + { + bMatch = false; + AssertFailed(); + break; + } + + ucbMatch -= ucbLength2Match; + if (!ucbMatch) + break; + + cbLength1 -= ucbLength2Match; + cbLength2 -= ucbLength2Match; + } + } + + if (bMatch && !bCompleteMatch) + { + /* check that the packets fully match */ + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + } + return bMatch; +} + +# if 0 +/* + * answers whether the two PINTNETSGs match based on the packet length and the first cbMatch bytes of the PINTNETSG + * if cbMatch < 0 matches complete packets. + */ +static bool vboxNetFltWinMatchSGs(PINTNETSG pSG1, PINTNETSG pSG2, const INT cbMatch) +{ + UINT uTotalPacketLength1 = pSG1->cbTotal; + PVOID pbMemBuf1 = NULL; + UINT cbLength1 = 0; + UINT i1 = 0; + UINT uTotalPacketLength2 = pSG2->cbTotal; + PVOID pbMemBuf2 = NULL; + UINT cbLength2 = 0; + + bool bMatch = true; + bool bCompleteMatch = false; + UINT i2 = 0; + + Assert(pSG1->cSegsUsed); + Assert(pSG2->cSegsUsed); + Assert(pSG1->cSegsAlloc >= pSG1->cSegsUsed); + Assert(pSG2->cSegsAlloc >= pSG2->cSegsUsed); + + if (uTotalPacketLength1 != uTotalPacketLength2) + { + AssertFailed(); + bMatch = false; + } + else + { + UINT ucbMatch; + if (cbMatch < 0 || (UINT)cbMatch > uTotalPacketLength1) + { + /* NOTE: assuming uTotalPacketLength1 == uTotalPacketLength2*/ + ucbMatch = uTotalPacketLength1; + bCompleteMatch = true; + } + else + { + ucbMatch = (UINT)cbMatch; + } + + do + { + UINT ucbLength2Match; + if (!cbLength1) + { + Assert(i1 < pSG1->cSegsUsed); + pbMemBuf1 = pSG1->aSegs[i1].pv; + cbLength1 = pSG1->aSegs[i1].cb; + i1++; + } + + if (!cbLength2) + { + Assert(i2 < pSG2->cSegsUsed); + pbMemBuf2 = pSG2->aSegs[i2].pv; + cbLength2 = pSG2->aSegs[i2].cb; + i2++; + } + + ucbLength2Match = MIN(ucbMatch, cbLength1); + ucbLength2Match = MIN(ucbLength2Match, cbLength2); + + if (memcmp(pbMemBuf1, pbMemBuf2, ucbLength2Match)) + { + bMatch = false; + AssertFailed(); + break; + } + ucbMatch -= ucbLength2Match; + cbLength1 -= ucbLength2Match; + cbLength2 -= ucbLength2Match; + } while (ucbMatch); + } + + if (bMatch && !bCompleteMatch) + { + /* check that the packets fully match */ + DBG_CHECK_SGS(pSG1, pSG2); + } + return bMatch; +} +# endif +# endif +#endif + +static void vboxNetFltWinFiniNetFltBase() +{ + do + { + vboxNetFltDeleteGlobals(&g_VBoxNetFltGlobals); + + /* + * Undo the work done during start (in reverse order). + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + + RTR0Term(); + } while (0); +} + +/* + * Defines max timeout for waiting for driver unloading + * (3000 * 100 ms = 5 minutes) + */ +#define MAX_UNLOAD_PROBES 3000 + +static int vboxNetFltWinFiniIdc() +{ + int rc; + int i; + + vboxNetFltWinStopInitIdcProbing(); + + if (g_bVBoxIdcInitialized) + { + for (i = 0; (rc = vboxNetFltTryDeleteIdc(&g_VBoxNetFltGlobals)) == VERR_WRONG_ORDER + && i < MAX_UNLOAD_PROBES; i++) + { + RTThreadSleep(100); + } + if (i == MAX_UNLOAD_PROBES) + { + // seems something hungs in driver + LogFlow(("vboxNetFltWinFiniIdc - Can't delete Idc. pInH=%p cFRefs=%d fIDcOpen=%s", + g_VBoxNetFltGlobals.pInstanceHead, g_VBoxNetFltGlobals.cFactoryRefs, + g_VBoxNetFltGlobals.fIDCOpen ? "true" : "false")); + LogFlow(("vboxNetFltWinFiniIdc g_VBoxNetFltGlobalsWin cDvRefs=%d hDev=%x pDev=%p Mp=%x \n", + g_VBoxNetFltGlobalsWin.cDeviceRefs, g_VBoxNetFltGlobalsWin.hDevice, + g_VBoxNetFltGlobalsWin.pDevObj, g_VBoxNetFltGlobalsWin.Mp.hMiniport)); + Assert(i == MAX_UNLOAD_PROBES); + return VERR_WRONG_ORDER; + } + + if (RT_SUCCESS(rc)) + { + g_bVBoxIdcInitialized = false; + } + } + else + { + rc = VINF_SUCCESS; + } + return rc; + +} + +static int vboxNetFltWinFiniNetFlt() +{ + int rc = vboxNetFltWinFiniIdc(); + if (RT_SUCCESS(rc)) + { + vboxNetFltWinFiniNetFltBase(); + } + return rc; +} + +/** + * base netflt initialization + */ +static int vboxNetFltWinInitNetFltBase() +{ + int rc; + + do + { + Assert(!g_bVBoxIdcInitialized); + + rc = RTR0Init(0); + if (!RT_SUCCESS(rc)) + { + break; + } + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobals(&g_VBoxNetFltGlobals); + if (!RT_SUCCESS(rc)) + { + RTR0Term(); + break; + } + }while (0); + + return rc; +} + +/** + * initialize IDC + */ +static int vboxNetFltWinInitIdc() +{ + int rc; + + do + { + if (g_bVBoxIdcInitialized) + { + rc = VINF_ALREADY_INITIALIZED; + break; + } + + /* + * connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + rc = vboxNetFltInitIdc(&g_VBoxNetFltGlobals); + if (!RT_SUCCESS(rc)) + { + break; + } + + g_bVBoxIdcInitialized = true; + } while (0); + + return rc; +} + +static VOID vboxNetFltWinInitIdcProbingWorker(PVOID pvContext) +{ + PINIT_IDC_INFO pInitIdcInfo = (PINIT_IDC_INFO)pvContext; + int rc = vboxNetFltWinInitIdc(); + if (RT_FAILURE(rc)) + { + bool bInterupted = ASMAtomicUoReadBool(&pInitIdcInfo->bStop); + if (!bInterupted) + { + RTThreadSleep(1000); /* 1 s */ + bInterupted = ASMAtomicUoReadBool(&pInitIdcInfo->bStop); + if (!bInterupted) + { + vboxNetFltWinJobEnqueueJob(&g_VBoxJobQueue, &pInitIdcInfo->Job, false); + return; + } + } + + /* it's interrupted */ + rc = VERR_INTERRUPTED; + } + + ASMAtomicUoWriteS32(&pInitIdcInfo->rc, rc); + KeSetEvent(&pInitIdcInfo->hCompletionEvent, 0, FALSE); +} + +static int vboxNetFltWinStopInitIdcProbing() +{ + if (!g_VBoxInitIdcInfo.bInitialized) + return VERR_INVALID_STATE; + + ASMAtomicUoWriteBool(&g_VBoxInitIdcInfo.bStop, true); + KeWaitForSingleObject(&g_VBoxInitIdcInfo.hCompletionEvent, Executive, KernelMode, FALSE, NULL); + + return g_VBoxInitIdcInfo.rc; +} + +static int vboxNetFltWinStartInitIdcProbing() +{ + Assert(!g_bVBoxIdcInitialized); + KeInitializeEvent(&g_VBoxInitIdcInfo.hCompletionEvent, NotificationEvent, FALSE); + g_VBoxInitIdcInfo.bStop = false; + g_VBoxInitIdcInfo.bInitialized = true; + vboxNetFltWinJobInit(&g_VBoxInitIdcInfo.Job, vboxNetFltWinInitIdcProbingWorker, &g_VBoxInitIdcInfo, false); + vboxNetFltWinJobEnqueueJob(&g_VBoxJobQueue, &g_VBoxInitIdcInfo.Job, false); + return VINF_SUCCESS; +} + +static int vboxNetFltWinInitNetFlt() +{ + int rc; + + do + { + rc = vboxNetFltWinInitNetFltBase(); + if (RT_FAILURE(rc)) + { + AssertFailed(); + break; + } + + /* + * connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + rc = vboxNetFltWinInitIdc(); + if (RT_FAILURE(rc)) + { + AssertFailed(); + vboxNetFltWinFiniNetFltBase(); + break; + } + } while (0); + + return rc; +} + +/* detach*/ +static int vboxNetFltWinDeleteInstance(PVBOXNETFLTINS pThis) +{ + LogFlow(("vboxNetFltWinDeleteInstance: pThis=0x%p \n", pThis)); + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + Assert(pThis); + Assert(pThis->fDisconnectedFromHost); + Assert(!pThis->fRediscoveryPending); + Assert(pThis->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE); +#ifndef VBOXNETADP + Assert(pThis->u.s.WinIf.PtState.OpState == kVBoxNetDevOpState_Deinitialized); + Assert(!pThis->u.s.WinIf.hBinding); +#endif + Assert(pThis->u.s.WinIf.MpState.OpState == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + Assert(!pThis->u.s.PacketQueueWorker.pSG); +#endif + + RTSemMutexDestroy(pThis->u.s.hWinIfMutex); + + vboxNetFltWinDrvDereference(); + + return VINF_SUCCESS; +} + +static NDIS_STATUS vboxNetFltWinDisconnectIt(PVBOXNETFLTINS pInstance) +{ +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + vboxNetFltWinQuFiniPacketQueue(pInstance); +#else + RT_NOREF1(pInstance); +#endif + return NDIS_STATUS_SUCCESS; +} + +/* detach*/ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinDetachFromInterface(PVBOXNETFLTINS pNetFlt, bool bOnUnbind) +{ + NDIS_STATUS Status; + int rc; + LogFlowFunc(("ENTER: pThis=%0xp\n", pNetFlt)); + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + Assert(pNetFlt); + + /* paranoia to ensure the instance is not removed while we're waiting on the mutex + * in case ndis does something unpredictable, e.g. calls our miniport halt independently + * from protocol unbind and concurrently with it*/ + vboxNetFltRetain(pNetFlt, false); + + rc = RTSemMutexRequest(pNetFlt->u.s.hWinIfMutex, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + Assert(vboxNetFltWinGetWinIfState(pNetFlt) == kVBoxWinIfState_Connected); + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Initialized); +#endif + if (vboxNetFltWinGetWinIfState(pNetFlt) == kVBoxWinIfState_Connected) + { + vboxNetFltWinSetWinIfState(pNetFlt, kVBoxWinIfState_Disconnecting); +#ifndef VBOXNETADP + Status = vboxNetFltWinPtDoUnbinding(pNetFlt, bOnUnbind); +#else + Status = vboxNetFltWinMpDoDeinitialization(pNetFlt); +#endif + Assert(Status == NDIS_STATUS_SUCCESS); + + vboxNetFltWinSetWinIfState(pNetFlt, kVBoxWinIfState_Disconnected); + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitialized); +#endif + vboxNetFltWinPtFiniWinIf(&pNetFlt->u.s.WinIf); + + /* we're unbinding, make an unbind-related release */ + vboxNetFltRelease(pNetFlt, false); + } + else + { + AssertBreakpoint(); +#ifndef VBOXNETADP + pNetFlt->u.s.WinIf.OpenCloseStatus = NDIS_STATUS_FAILURE; +#endif + if (!bOnUnbind) + { + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + Status = NDIS_STATUS_FAILURE; + } + RTSemMutexRelease(pNetFlt->u.s.hWinIfMutex); + } + else + { + AssertBreakpoint(); + Status = NDIS_STATUS_FAILURE; + } + + /* release for the retain we made before waining on the mutex */ + vboxNetFltRelease(pNetFlt, false); + + LogFlowFunc(("LEAVE: Status 0x%x\n", Status)); + + return Status; +} + + +/** + * Checks if the host (not us) has put the adapter in promiscuous mode. + * + * @returns true if promiscuous, false if not. + * @param pThis The instance. + */ +static bool vboxNetFltWinIsPromiscuous2(PVBOXNETFLTINS pThis) +{ +#ifndef VBOXNETADP + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pThis)) + { + bool bPromiscuous; + if (!vboxNetFltWinReferenceWinIf(pThis)) + return false; + + bPromiscuous = (pThis->u.s.WinIf.fUpperProtocolSetFilter & NDIS_PACKET_TYPE_PROMISCUOUS) == NDIS_PACKET_TYPE_PROMISCUOUS; + /*vboxNetFltWinIsPromiscuous(pAdapt);*/ + + vboxNetFltWinDereferenceWinIf(pThis); + return bPromiscuous; + } + return false; +#else + RT_NOREF1(pThis); + return true; +#endif +} + + +/** + * Report the MAC address, promiscuous mode setting, GSO capabilities and + * no-preempt destinations to the internal network. + * + * Does nothing if we're not currently connected to an internal network. + * + * @param pThis The instance data. + */ +static void vboxNetFltWinReportStuff(PVBOXNETFLTINS pThis) +{ + /** @todo Keep these up to date, esp. the promiscuous mode bit. */ + if (pThis->pSwitchPort + && vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, + vboxNetFltWinIsPromiscuous2(pThis)); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, + INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + /** @todo We should be able to do pfnXmit at DISPATCH_LEVEL... */ + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } +} + +/** + * Worker for vboxNetFltWinAttachToInterface. + * + * @param pAttachInfo Structure for communicating with + * vboxNetFltWinAttachToInterface. + */ +static void vboxNetFltWinAttachToInterfaceWorker(PATTACH_INFO pAttachInfo) +{ + PVBOXNETFLTINS pThis = pAttachInfo->pNetFltIf; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + /* to ensure we're not removed while we're here */ + vboxNetFltRetain(pThis, false); + + rc = RTSemMutexRequest(pThis->u.s.hWinIfMutex, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + Assert(vboxNetFltWinGetWinIfState(pThis) == kVBoxWinIfState_Disconnected); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitialized); +#endif + if (vboxNetFltWinGetWinIfState(pThis) == kVBoxWinIfState_Disconnected) + { + if (pAttachInfo->fRediscovery) + { + /* rediscovery means adaptor bind is performed while intnet is already using it + * i.e. adaptor was unbound while being used by intnet and now being bound back again */ + Assert( ((VBOXNETFTLINSSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pThis->enmState)) + == kVBoxNetFltInsState_Connected); + } +#ifndef VBOXNETADP + Status = vboxNetFltWinPtInitWinIf(&pThis->u.s.WinIf, pAttachInfo->pCreateContext->pOurName); +#else + Status = vboxNetFltWinPtInitWinIf(&pThis->u.s.WinIf); +#endif + if (Status == NDIS_STATUS_SUCCESS) + { + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Connecting); + +#ifndef VBOXNETADP + Status = vboxNetFltWinPtDoBinding(pThis, pAttachInfo->pCreateContext->pOurName, pAttachInfo->pCreateContext->pBindToName); +#else + Status = vboxNetFltWinMpDoInitialization(pThis, pAttachInfo->pCreateContext->hMiniportAdapter, pAttachInfo->pCreateContext->hWrapperConfigurationContext); +#endif + if (Status == NDIS_STATUS_SUCCESS) + { + if (!pAttachInfo->fRediscovery) + vboxNetFltWinDrvReference(); +#ifndef VBOXNETADP + if (pThis->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) +#endif + { + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Connected); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.PtState) == kVBoxNetDevOpState_Initialized); +#endif + /* 4. mark as connected */ + RTSpinlockAcquire(pThis->hSpinlock); + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + RTSpinlockRelease(pThis->hSpinlock); + + pAttachInfo->Status = VINF_SUCCESS; + pAttachInfo->pCreateContext->Status = NDIS_STATUS_SUCCESS; + + RTSemMutexRelease(pThis->u.s.hWinIfMutex); + + vboxNetFltRelease(pThis, false); + + /* 5. Report MAC address, promiscuousness and GSO capabilities. */ + vboxNetFltWinReportStuff(pThis); + + return; + } +#ifndef VBOXNETADP /* unreachable for VBOXNETADP because of the return above */ + AssertBreakpoint(); + + if (!pAttachInfo->fRediscovery) + { + vboxNetFltWinDrvDereference(); + } +# ifndef VBOXNETADP + vboxNetFltWinPtDoUnbinding(pThis, true); +/*# else - unreachable + vboxNetFltWinMpDoDeinitialization(pThis); */ +# endif +#endif + } + AssertBreakpoint(); + vboxNetFltWinPtFiniWinIf(&pThis->u.s.WinIf); + } + AssertBreakpoint(); + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Disconnected); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitialized); +#endif + } + AssertBreakpoint(); + + pAttachInfo->Status = VERR_GENERAL_FAILURE; + pAttachInfo->pCreateContext->Status = Status; + RTSemMutexRelease(pThis->u.s.hWinIfMutex); + } + else + { + AssertBreakpoint(); + pAttachInfo->Status = rc; + } + + vboxNetFltRelease(pThis, false); + + return; +} + +/** + * Common code for vboxNetFltOsInitInstance and + * vboxNetFltOsMaybeRediscovered. + * + * @returns IPRT status code. + * @param pThis The instance. + * @param fRediscovery True if vboxNetFltOsMaybeRediscovered is calling, + * false if it's vboxNetFltOsInitInstance. + */ +static int vboxNetFltWinAttachToInterface(PVBOXNETFLTINS pThis, void * pContext, bool fRediscovery) +{ + ATTACH_INFO Info; + Info.pNetFltIf = pThis; + Info.fRediscovery = fRediscovery; + Info.pCreateContext = (PCREATE_INSTANCE_CONTEXT)pContext; + + vboxNetFltWinAttachToInterfaceWorker(&Info); + + return Info.Status; +} +static NTSTATUS vboxNetFltWinPtDevDispatch(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) +{ + RT_NOREF1(pDevObj); + PIO_STACK_LOCATION pIrpSl = IoGetCurrentIrpStackLocation(pIrp);; + NTSTATUS Status = STATUS_SUCCESS; + + switch (pIrpSl->MajorFunction) + { + case IRP_MJ_DEVICE_CONTROL: + Status = STATUS_NOT_SUPPORTED; + break; + case IRP_MJ_CREATE: + case IRP_MJ_CLEANUP: + case IRP_MJ_CLOSE: + break; + default: + AssertFailed(); + break; + } + + pIrp->IoStatus.Status = Status; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevCreate(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + NDIS_STRING DevName, LinkName; + PDRIVER_DISPATCH aMajorFunctions[IRP_MJ_MAXIMUM_FUNCTION+1]; + NdisInitUnicodeString(&DevName, VBOXNETFLT_NAME_DEVICE); + NdisInitUnicodeString(&LinkName, VBOXNETFLT_NAME_LINK); + + Assert(!pGlobals->hDevice); + Assert(!pGlobals->pDevObj); + NdisZeroMemory(aMajorFunctions, sizeof (aMajorFunctions)); + aMajorFunctions[IRP_MJ_CREATE] = vboxNetFltWinPtDevDispatch; + aMajorFunctions[IRP_MJ_CLEANUP] = vboxNetFltWinPtDevDispatch; + aMajorFunctions[IRP_MJ_CLOSE] = vboxNetFltWinPtDevDispatch; + aMajorFunctions[IRP_MJ_DEVICE_CONTROL] = vboxNetFltWinPtDevDispatch; + + NDIS_STATUS Status = NdisMRegisterDevice(pGlobals->Mp.hNdisWrapper, + &DevName, &LinkName, + aMajorFunctions, + &pGlobals->pDevObj, + &pGlobals->hDevice); + Assert(Status == NDIS_STATUS_SUCCESS); + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevDestroy(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + Assert(pGlobals->hDevice); + Assert(pGlobals->pDevObj); + NDIS_STATUS Status = NdisMDeregisterDevice(pGlobals->hDevice); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + pGlobals->hDevice = NULL; + pGlobals->pDevObj = NULL; + } + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevCreateReference(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + NDIS_STATUS Status = KeWaitForSingleObject(&pGlobals->SynchEvent, Executive, KernelMode, FALSE, NULL); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Assert(pGlobals->cDeviceRefs >= 0); + if (++pGlobals->cDeviceRefs == 1) + { + Status = vboxNetFltWinDevCreate(pGlobals); + if (Status == NDIS_STATUS_SUCCESS) + { + ObReferenceObject(pGlobals->pDevObj); + } + } + else + { + Status = NDIS_STATUS_SUCCESS; + } + KeSetEvent(&pGlobals->SynchEvent, 0, FALSE); + } + else + { + /* should never happen actually */ + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + } + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevDereference(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + NDIS_STATUS Status = KeWaitForSingleObject(&pGlobals->SynchEvent, Executive, KernelMode, FALSE, NULL); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Assert(pGlobals->cDeviceRefs > 0); + if (!(--pGlobals->cDeviceRefs)) + { + ObDereferenceObject(pGlobals->pDevObj); + Status = vboxNetFltWinDevDestroy(pGlobals); + } + else + { + Status = NDIS_STATUS_SUCCESS; + } + KeSetEvent(&pGlobals->SynchEvent, 0, FALSE); + } + else + { + /* should never happen actually */ + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + } + return Status; +} + +/* reference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvReference() +{ + vboxNetFltWinDevCreateReference(&g_VBoxNetFltGlobalsWin); +} + +/* dereference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvDereference() +{ + vboxNetFltWinDevDereference(&g_VBoxNetFltGlobalsWin); +} + +/* + * + * The OS specific interface definition + * + */ + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + /* AttachToInterface true if disconnected */ + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + RT_NOREF1(pvIfData); + int rc = VINF_SUCCESS; + uint32_t cRefs = 0; +#ifndef VBOXNETADP + if (fDst & INTNETTRUNKDIR_WIRE) + cRefs++; + if (fDst & INTNETTRUNKDIR_HOST) + cRefs++; +#else + if ((fDst & INTNETTRUNKDIR_WIRE) || (fDst & INTNETTRUNKDIR_HOST)) + cRefs = 1; +#endif + + AssertReturn(cRefs, VINF_SUCCESS); + + if (!vboxNetFltWinIncReferenceWinIf(pThis, cRefs)) + { + return VERR_GENERAL_FAILURE; + } +#ifndef VBOXNETADP + if (fDst & INTNETTRUNKDIR_WIRE) + { + PNDIS_PACKET pPacket; + + pPacket = vboxNetFltWinNdisPacketFromSG(pThis, pSG, NULL /*pBufToFree*/, + true /*fToWire*/, true /*fCopyMemory*/); + + if (pPacket) + { + NDIS_STATUS fStatus; + +#ifndef VBOX_LOOPBACK_USEFLAGS + /* force "don't loopback" flags to prevent loopback branch invocation in any case + * to avoid ndis misbehave */ + NdisGetPacketFlags(pPacket) |= g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#else + /* this is done by default in vboxNetFltWinNdisPacketFromSG */ +#endif + +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + vboxNetFltWinLbPutSendPacket(pThis, pPacket, true /* bFromIntNet */); +#endif + NdisSend(&fStatus, pThis->u.s.WinIf.hBinding, pPacket); + if (fStatus != NDIS_STATUS_PENDING) + { +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + /* the status is NOT pending, complete the packet */ + bool fTmp = vboxNetFltWinLbRemoveSendPacket(pThis, pPacket); + Assert(fTmp); NOREF(fTmp); +#endif + if (!NT_SUCCESS(fStatus)) + rc = VERR_GENERAL_FAILURE; /** @todo convert status to VERR_xxx */ + + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + } + else + { + /* pending, dereference on packet complete */ + cRefs--; + } + } + else + { + AssertFailed(); + rc = VERR_NO_MEMORY; + } + } +#endif + +#ifndef VBOXNETADP + if (fDst & INTNETTRUNKDIR_HOST) +#else + if (cRefs) +#endif + { + PNDIS_PACKET pPacket = vboxNetFltWinNdisPacketFromSG(pThis, pSG, NULL /*pBufToFree*/, + false /*fToWire*/, true /*fCopyMemory*/); + if (pPacket) + { + NdisMIndicateReceivePacket(pThis->u.s.WinIf.hMiniport, &pPacket, 1); + cRefs--; +#ifdef VBOXNETADP + STATISTIC_INCREASE(pThis->u.s.WinIf.cRxSuccess); +#endif + } + else + { + AssertFailed(); +#ifdef VBOXNETADP + STATISTIC_INCREASE(pThis->u.s.WinIf.cRxError); +#endif + rc = VERR_NO_MEMORY; + } + } + + Assert(cRefs <= 2); + + if (cRefs) + { + vboxNetFltWinDecReferenceWinIf(pThis, cRefs); + } + + return rc; +} + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ +#ifndef VBOXNETADP + NDIS_STATUS Status; +#endif + /* we first wait for all pending ops to complete + * this might include all packets queued for processing */ + for (;;) + { + if (fActive) + { + if (!pThis->u.s.cModePassThruRefs) + { + break; + } + } + else + { + if (!pThis->u.s.cModeNetFltRefs) + { + break; + } + } + vboxNetFltWinSleep(2); + } + + if (!vboxNetFltWinReferenceWinIf(pThis)) + return; +#ifndef VBOXNETADP + + if (fActive) + { +#ifdef DEBUG_misha + NDIS_PHYSICAL_MEDIUM PhMedium; + bool bPromiscSupported; + + Status = vboxNetFltWinQueryPhysicalMedium(pThis, &PhMedium); + if (Status != NDIS_STATUS_SUCCESS) + { + + LogRel(("vboxNetFltWinQueryPhysicalMedium failed, Status (0x%x), setting medium to NdisPhysicalMediumUnspecified\n", Status)); + Assert(Status == NDIS_STATUS_NOT_SUPPORTED); + if (Status != NDIS_STATUS_NOT_SUPPORTED) + { + LogRel(("vboxNetFltWinQueryPhysicalMedium failed, Status (0x%x), setting medium to NdisPhysicalMediumUnspecified\n", Status)); + } + PhMedium = NdisPhysicalMediumUnspecified; + } + else + { + LogRel(("(SUCCESS) vboxNetFltWinQueryPhysicalMedium SUCCESS\n")); + } + + bPromiscSupported = (!(PhMedium == NdisPhysicalMediumWirelessWan + || PhMedium == NdisPhysicalMediumWirelessLan + || PhMedium == NdisPhysicalMediumNative802_11 + || PhMedium == NdisPhysicalMediumBluetooth + /*|| PhMedium == NdisPhysicalMediumWiMax */ + )); + + Assert(bPromiscSupported == VBOXNETFLT_PROMISCUOUS_SUPPORTED(pThis)); +#endif + } + + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pThis)) + { + Status = vboxNetFltWinSetPromiscuous(pThis, fActive); + if (Status != NDIS_STATUS_SUCCESS) + { + LogRel(("vboxNetFltWinSetPromiscuous failed, Status (0x%x), fActive (%d)\n", Status, fActive)); + AssertFailed(); + } + } +#else +# ifdef VBOXNETADP_REPORT_DISCONNECTED + if (fActive) + { + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_CONNECT, + (PVOID)NULL, + 0); + } + else + { + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_DISCONNECT, + (PVOID)NULL, + 0); + } +#else + if (fActive) + { + /* indicate status change to make the ip settings be re-picked for dhcp */ + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_DISCONNECT, + (PVOID)NULL, + 0); + + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_CONNECT, + (PVOID)NULL, + 0); + } +# endif +#endif + vboxNetFltWinDereferenceWinIf(pThis); + + return; +} + +#ifndef VBOXNETADP + +DECLINLINE(bool) vboxNetFltWinIsAddrLinkLocal4(PCRTNETADDRIPV4 pAddr) +{ + return (pAddr->s.Lo == 0xfea9); /* 169.254 */ +} + +DECLINLINE(bool) vboxNetFltWinIsAddrLinkLocal6(PCRTNETADDRIPV6 pAddr) +{ + return ((pAddr->au8[0] == 0xfe) && ((pAddr->au8[1] & 0xc0) == 0x80)); +} + +void vboxNetFltWinNotifyHostAddress(PTA_ADDRESS pAddress, bool fAdded) +{ + void *pvAddr = NULL; + INTNETADDRTYPE enmAddrType = kIntNetAddrType_Invalid; + + LogFlow(("==>vboxNetFltWinNotifyHostAddress: AddrType=%d %s\n", + pAddress->AddressType, fAdded ? "added" : "deleted")); + if (pAddress->AddressType == TDI_ADDRESS_TYPE_IP) + { + PTDI_ADDRESS_IP pTdiAddrIp = (PTDI_ADDRESS_IP)pAddress->Address; + /* + * Note that we do not get loopback addresses here. If we did we should + * have checked and ignored them too. + */ + if (!vboxNetFltWinIsAddrLinkLocal4((PCRTNETADDRIPV4)(&pTdiAddrIp->in_addr))) + { + pvAddr = &pTdiAddrIp->in_addr; + enmAddrType = kIntNetAddrType_IPv4; + } + else + Log2(("vboxNetFltWinNotifyHostAddress: ignoring link-local address %RTnaipv4\n", + pTdiAddrIp->in_addr)); + } + else if (pAddress->AddressType == TDI_ADDRESS_TYPE_IP6) + { + PTDI_ADDRESS_IP6 pTdiAddrIp6 = (PTDI_ADDRESS_IP6)pAddress->Address; + if (!vboxNetFltWinIsAddrLinkLocal6((PCRTNETADDRIPV6)(pTdiAddrIp6->sin6_addr))) + { + pvAddr = pTdiAddrIp6->sin6_addr; + enmAddrType = kIntNetAddrType_IPv6; + } + else + Log2(("vboxNetFltWinNotifyHostAddress: ignoring link-local address %RTnaipv6\n", + pTdiAddrIp6->sin6_addr)); + } + else + { + Log2(("vboxNetFltWinNotifyHostAddress: ignoring irrelevant address type %d\n", + pAddress->AddressType)); + LogFlow(("<==vboxNetFltWinNotifyHostAddress\n")); + return; + } + if (pvAddr) + { + NdisAcquireSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + /* At this point the list must contain at least one element. */ + PVBOXNETFLTINS pInstance = NULL; + PVBOXNETFLTWIN pFilter; + RTListForEach(&g_VBoxNetFltGlobalsWin.listFilters, pFilter, VBOXNETFLTWIN, node) + { + pInstance = RT_FROM_MEMBER(pFilter, VBOXNETFLTINS, u.s.WinIf); + if (vboxNetFltWinReferenceWinIf(pInstance)) + { + if (pInstance->pSwitchPort && pInstance->pSwitchPort->pfnNotifyHostAddress) + break; + vboxNetFltWinDereferenceWinIf(pInstance); + } + else + Log2(("vboxNetFltWinNotifyHostAddress: failed to retain filter instance %p\n", pInstance)); + pInstance = NULL; + } + NdisReleaseSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + if (pInstance) + { + if (enmAddrType == kIntNetAddrType_IPv4) + Log2(("vboxNetFltWin%sAddressHandler: %RTnaipv4\n", + fAdded ? "Add" : "Del", *(PCRTNETADDRIPV4)pvAddr)); + else + Log2(("vboxNetFltWin%sAddressHandler: %RTnaipv6\n", + fAdded ? "Add" : "Del", pvAddr)); + pInstance->pSwitchPort->pfnNotifyHostAddress(pInstance->pSwitchPort, fAdded, + enmAddrType, pvAddr); + vboxNetFltWinDereferenceWinIf(pInstance); + } + else + Log2(("vboxNetFltWinNotifyHostAddress: no filters require notification\n")); + } + LogFlow(("<==vboxNetFltWinNotifyHostAddress\n")); +} + +void vboxNetFltWinAddAddressHandler(PTA_ADDRESS Address, + PUNICODE_STRING DeviceName, + PTDI_PNP_CONTEXT Context) +{ + RT_NOREF2(DeviceName, Context); + vboxNetFltWinNotifyHostAddress(Address, true); +} + +void vboxNetFltWinDelAddressHandler(PTA_ADDRESS Address, + PUNICODE_STRING DeviceName, + PTDI_PNP_CONTEXT Context) +{ + RT_NOREF2(DeviceName, Context); + vboxNetFltWinNotifyHostAddress(Address, false); +} + +void vboxNetFltWinRegisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltWinRegisterIpAddrNotifier: instance=%p pThis->pSwitchPort=%p pThis->pSwitchPort->pfnNotifyHostAddress=%p\n", + pThis, pThis->pSwitchPort, pThis->pSwitchPort ? pThis->pSwitchPort->pfnNotifyHostAddress : NULL)); + if (pThis->pSwitchPort && pThis->pSwitchPort->pfnNotifyHostAddress) + { + NdisAcquireSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + bool fRegisterHandlers = RTListIsEmpty(&g_VBoxNetFltGlobalsWin.listFilters); + RTListPrepend(&g_VBoxNetFltGlobalsWin.listFilters, &pThis->u.s.WinIf.node); + NdisReleaseSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + + if (fRegisterHandlers) + { + TDI_CLIENT_INTERFACE_INFO Info; + UNICODE_STRING ClientName = RTL_CONSTANT_STRING(L"VBoxNetFlt"); + memset(&Info, 0, sizeof(Info)); + Info.MajorTdiVersion = 2; + Info.MinorTdiVersion = 0; + Info.ClientName = &ClientName; + Info.AddAddressHandlerV2 = vboxNetFltWinAddAddressHandler; + Info.DelAddressHandlerV2 = vboxNetFltWinDelAddressHandler; + Assert(!g_VBoxNetFltGlobalsWin.hNotifier); + NTSTATUS Status = TdiRegisterPnPHandlers(&Info, sizeof(Info), &g_VBoxNetFltGlobalsWin.hNotifier); + Log2(("vboxNetFltWinRegisterIpAddrNotifier: TdiRegisterPnPHandlers returned %d\n", Status)); NOREF(Status); + } + else + Log2(("vboxNetFltWinRegisterIpAddrNotifier: already registed\n")); + } + else + Log2(("vboxNetFltWinRegisterIpAddrNotifier: this instance does not require notifications, ignoring...\n")); + LogFlow(("<==vboxNetFltWinRegisterIpAddrNotifier: notifier=%p\n", g_VBoxNetFltGlobalsWin.hNotifier)); +} + +void vboxNetFltWinUnregisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltWinUnregisterIpAddrNotifier: notifier=%p\n", g_VBoxNetFltGlobalsWin.hNotifier)); + if (pThis->pSwitchPort && pThis->pSwitchPort->pfnNotifyHostAddress) + { + NdisAcquireSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + /* At this point the list must contain at least one element. */ + Assert(!RTListIsEmpty(&g_VBoxNetFltGlobalsWin.listFilters)); + RTListNodeRemove(&pThis->u.s.WinIf.node); + HANDLE hNotifier = NULL; + if (RTListIsEmpty(&g_VBoxNetFltGlobalsWin.listFilters)) + { + /* + * The list has become empty, so we need to deregister handlers. We + * grab hNotifier and reset it while still holding the lock. This + * guaranties that we won't interfere with setting it in + * vboxNetFltWinRegisterIpAddrNotifier(). It is inconceivable that + * vboxNetFltWinUnregisterIpAddrNotifier() will be called for the + * same filter instance while it is still being processed by + * vboxNetFltWinRegisterIpAddrNotifier(). This would require trunk + * destruction in the middle of its creation. It is possible that + * vboxNetFltWinUnregisterIpAddrNotifier() is called for another + * filter instance, but in such case we won't even get here as the + * list won't be empty. + */ + hNotifier = g_VBoxNetFltGlobalsWin.hNotifier; + g_VBoxNetFltGlobalsWin.hNotifier = NULL; + } + NdisReleaseSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + if (hNotifier) + { + NTSTATUS Status = TdiDeregisterPnPHandlers(hNotifier); + Log2(("vboxNetFltWinUnregisterIpAddrNotifier: TdiDeregisterPnPHandlers(%p) returned %d\n", + hNotifier, Status)); NOREF(Status); + } + else + Log2(("vboxNetFltWinUnregisterIpAddrNotifier: filters remain, do not deregister handlers yet\n")); + } + else + Log2(("vboxNetFltWinUnregisterIpAddrNotifier: this instance did not require notifications, ignoring...\n")); + LogFlow(("<==vboxNetFltWinUnregisterIpAddrNotifier\n")); +} +#else /* VBOXNETADP */ +#define vboxNetFltWinRegisterIpAddrNotifier(x) +#define vboxNetFltWinUnregisterIpAddrNotifier(x) +#endif /* VBOXNETADP */ + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + NDIS_STATUS Status = vboxNetFltWinDisconnectIt(pThis); + Log2(("vboxNetFltOsDisconnectIt: pThis=%p pThis->pSwitchPort=%p pThis->pSwitchPort->pfnNotifyHostAddress=%p\n", + pThis, pThis->pSwitchPort, pThis->pSwitchPort ? pThis->pSwitchPort->pfnNotifyHostAddress : NULL)); + vboxNetFltWinUnregisterIpAddrNotifier(pThis); + return Status == NDIS_STATUS_SUCCESS ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +static void vboxNetFltWinConnectItWorker(PVOID pvContext) +{ + PWORKER_INFO pInfo = (PWORKER_INFO)pvContext; +#if !defined(VBOXNETADP) || !defined(VBOXNETFLT_NO_PACKET_QUEUE) + NDIS_STATUS Status; +#endif + PVBOXNETFLTINS pInstance = pInfo->pNetFltIf; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + /* this is not a rediscovery, initialize Mac cache */ + if (vboxNetFltWinReferenceWinIf(pInstance)) + { +#ifndef VBOXNETADP + Status = vboxNetFltWinGetMacAddress(pInstance, &pInstance->u.s.MacAddr); + if (Status == NDIS_STATUS_SUCCESS) +#endif + { +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + pInfo->Status = VINF_SUCCESS; +#else + Status = vboxNetFltWinQuInitPacketQueue(pInstance); + if (Status == NDIS_STATUS_SUCCESS) + { + pInfo->Status = VINF_SUCCESS; + } + else + { + pInfo->Status = VERR_GENERAL_FAILURE; + } +#endif + } +#ifndef VBOXNETADP + else + { + pInfo->Status = VERR_INTNET_FLT_IF_FAILED; + } +#endif + + vboxNetFltWinDereferenceWinIf(pInstance); + } + else + { + pInfo->Status = VERR_INTNET_FLT_IF_NOT_FOUND; + } +} + +static int vboxNetFltWinConnectIt(PVBOXNETFLTINS pThis) +{ + WORKER_INFO Info; + Info.pNetFltIf = pThis; + + vboxNetFltWinJobSynchExecAtPassive(vboxNetFltWinConnectItWorker, &Info); + + if (RT_SUCCESS(Info.Status)) + vboxNetFltWinReportStuff(pThis); + + return Info.Status; +} + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + Log2(("vboxNetFltOsConnectIt: pThis=%p pThis->pSwitchPort=%p pThis->pSwitchPort->pfnNotifyHostAddress=%p\n", + pThis, pThis->pSwitchPort, pThis->pSwitchPort ? pThis->pSwitchPort->pfnNotifyHostAddress : NULL)); + vboxNetFltWinRegisterIpAddrNotifier(pThis); + return vboxNetFltWinConnectIt(pThis); +} + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + vboxNetFltWinDeleteInstance(pThis); +} + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + int rc = RTSemMutexCreate(&pThis->u.s.hWinIfMutex); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltWinAttachToInterface(pThis, pvContext, false /*fRediscovery*/ ); + if (RT_SUCCESS(rc)) + { + return rc; + } + RTSemMutexDestroy(pThis->u.s.hWinIfMutex); + } + return rc; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + pThis->u.s.cModeNetFltRefs = 0; + pThis->u.s.cModePassThruRefs = 0; + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Disconnected); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); +#endif + return VINF_SUCCESS; +} + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + RT_NOREF3(pThis, pvIfData, pMac); +} + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + /* Nothing to do */ + RT_NOREF3(pThis, pvIf, ppvIfData); + return VINF_SUCCESS; +} + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + RT_NOREF2(pThis, pvIfData); + return VINF_SUCCESS; +} diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h new file mode 100644 index 00000000..36235409 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h @@ -0,0 +1,972 @@ +/* $Id: VBoxNetFltRt-win.h $ */ +/** @file + * VBoxNetFltRt-win.h - Bridged Networking Driver, Windows Specific Code. + * NetFlt Runtime API + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltRt_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltRt_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +DECLHIDDEN(VOID) vboxNetFltWinUnload(IN PDRIVER_OBJECT DriverObject); + +#ifndef VBOXNETADP +# if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) +DECLHIDDEN(bool) vboxNetFltWinMatchPackets(PNDIS_PACKET pPacket1, PNDIS_PACKET pPacket2, const INT cbMatch); +DECLHIDDEN(bool) vboxNetFltWinMatchPacketAndSG(PNDIS_PACKET pPacket, PINTNETSG pSG, const INT cbMatch); +# endif +#endif + +/************************* + * packet queue API * + *************************/ + + +#define LIST_ENTRY_2_PACKET_INFO(pListEntry) \ + ( (PVBOXNETFLT_PACKET_INFO)((uint8_t *)(pListEntry) - RT_UOFFSETOF(VBOXNETFLT_PACKET_INFO, ListEntry)) ) + +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + +#define VBOX_SLE_2_PKTRSVD_PT(_pEntry) \ + ( (PVBOXNETFLT_PKTRSVD_PT)((uint8_t *)(_pEntry) - RT_UOFFSETOF(VBOXNETFLT_PKTRSVD_PT, ListEntry)) ) + +#define VBOX_SLE_2_SENDPACKET(_pEntry) \ + ( (PNDIS_PACKET)((uint8_t *)(VBOX_SLE_2_PKTRSVD_PT(_pEntry)) - RT_UOFFSETOF(NDIS_PACKET, ProtocolReserved)) ) + +#endif +/** + * enqueus the packet info to the tail of the queue + */ +DECLINLINE(void) vboxNetFltWinQuEnqueueTail(PVBOXNETFLT_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + InsertTailList(pQueue, &pPacketInfo->ListEntry); +} + +DECLINLINE(void) vboxNetFltWinQuEnqueueHead(PVBOXNETFLT_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + Assert(pPacketInfo->pPool); + InsertHeadList(pQueue, &pPacketInfo->ListEntry); +} + +/** + * enqueus the packet info to the tail of the queue + */ +DECLINLINE(void) vboxNetFltWinQuInterlockedEnqueueTail(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + Assert(pPacketInfo->pPool); + NdisAcquireSpinLock(&pQueue->Lock); + vboxNetFltWinQuEnqueueTail(&pQueue->Queue, pPacketInfo); + NdisReleaseSpinLock(&pQueue->Lock); +} + +DECLINLINE(void) vboxNetFltWinQuInterlockedEnqueueHead(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + NdisAcquireSpinLock(&pQueue->Lock); + vboxNetFltWinQuEnqueueHead(&pQueue->Queue, pPacketInfo); + NdisReleaseSpinLock(&pQueue->Lock); +} + +/** + * dequeus the packet info from the head of the queue + */ +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuDequeueHead(PVBOXNETFLT_PACKET_QUEUE pQueue) +{ + PLIST_ENTRY pListEntry = RemoveHeadList(pQueue); + if (pListEntry != pQueue) + { + PVBOXNETFLT_PACKET_INFO pInfo = LIST_ENTRY_2_PACKET_INFO(pListEntry); + Assert(pInfo->pPool); + return pInfo; + } + return NULL; +} + +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuDequeueTail(PVBOXNETFLT_PACKET_QUEUE pQueue) +{ + PLIST_ENTRY pListEntry = RemoveTailList(pQueue); + if (pListEntry != pQueue) + { + PVBOXNETFLT_PACKET_INFO pInfo = LIST_ENTRY_2_PACKET_INFO(pListEntry); + Assert(pInfo->pPool); + return pInfo; + } + return NULL; +} + +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuInterlockedDequeueHead(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pInterlockedQueue) +{ + PVBOXNETFLT_PACKET_INFO pInfo; + NdisAcquireSpinLock(&pInterlockedQueue->Lock); + pInfo = vboxNetFltWinQuDequeueHead(&pInterlockedQueue->Queue); + NdisReleaseSpinLock(&pInterlockedQueue->Lock); + return pInfo; +} + +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuInterlockedDequeueTail(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pInterlockedQueue) +{ + PVBOXNETFLT_PACKET_INFO pInfo; + NdisAcquireSpinLock(&pInterlockedQueue->Lock); + pInfo = vboxNetFltWinQuDequeueTail(&pInterlockedQueue->Queue); + NdisReleaseSpinLock(&pInterlockedQueue->Lock); + return pInfo; +} + +DECLINLINE(void) vboxNetFltWinQuDequeue(PVBOXNETFLT_PACKET_INFO pInfo) +{ + RemoveEntryList(&pInfo->ListEntry); +} + +DECLINLINE(void) vboxNetFltWinQuInterlockedDequeue(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pInterlockedQueue, PVBOXNETFLT_PACKET_INFO pInfo) +{ + NdisAcquireSpinLock(&pInterlockedQueue->Lock); + vboxNetFltWinQuDequeue(pInfo); + NdisReleaseSpinLock(&pInterlockedQueue->Lock); +} + +/** + * allocates the packet info from the pool + */ +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinPpAllocPacketInfo(PVBOXNETFLT_PACKET_INFO_POOL pPool) +{ + return vboxNetFltWinQuInterlockedDequeueHead(&pPool->Queue); +} + +/** + * returns the packet info to the pool + */ +DECLINLINE(void) vboxNetFltWinPpFreePacketInfo(PVBOXNETFLT_PACKET_INFO pInfo) +{ + PVBOXNETFLT_PACKET_INFO_POOL pPool = pInfo->pPool; + vboxNetFltWinQuInterlockedEnqueueHead(&pPool->Queue, pInfo); +} + +/** initializes the packet queue */ +#define INIT_PACKET_QUEUE(_pQueue) InitializeListHead((_pQueue)) + +/** initializes the packet queue */ +#define INIT_INTERLOCKED_PACKET_QUEUE(_pQueue) \ + { \ + INIT_PACKET_QUEUE(&(_pQueue)->Queue); \ + NdisAllocateSpinLock(&(_pQueue)->Lock); \ + } + +/** delete the packet queue */ +#define FINI_INTERLOCKED_PACKET_QUEUE(_pQueue) NdisFreeSpinLock(&(_pQueue)->Lock) + +/** returns the packet the packet info contains */ +#define GET_PACKET_FROM_INFO(_pPacketInfo) (ASMAtomicUoReadPtr((void * volatile *)&(_pPacketInfo)->pPacket)) + +/** assignes the packet to the packet info */ +#define SET_PACKET_TO_INFO(_pPacketInfo, _pPacket) (ASMAtomicUoWritePtr(&(_pPacketInfo)->pPacket, (_pPacket))) + +/** returns the flags the packet info contains */ +#define GET_FLAGS_FROM_INFO(_pPacketInfo) (ASMAtomicUoReadU32((volatile uint32_t *)&(_pPacketInfo)->fFlags)) + +/** sets flags to the packet info */ +#define SET_FLAGS_TO_INFO(_pPacketInfo, _fFlags) (ASMAtomicUoWriteU32((volatile uint32_t *)&(_pPacketInfo)->fFlags, (_fFlags))) + +#ifdef VBOXNETFLT_NO_PACKET_QUEUE +DECLHIDDEN(bool) vboxNetFltWinPostIntnet(PVBOXNETFLTINS pInstance, PVOID pvPacket, const UINT fFlags); +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQuEnqueuePacket(PVBOXNETFLTINS pInstance, PVOID pPacket, const UINT fPacketFlags); +DECLHIDDEN(void) vboxNetFltWinQuFiniPacketQueue(PVBOXNETFLTINS pInstance); +DECLHIDDEN(NTSTATUS) vboxNetFltWinQuInitPacketQueue(PVBOXNETFLTINS pInstance); +#endif /* #ifndef VBOXNETFLT_NO_PACKET_QUEUE */ + + +#ifndef VBOXNETADP +/** + * searches the list entry in a single-linked list + */ +DECLINLINE(bool) vboxNetFltWinSearchListEntry(PVBOXNETFLT_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry2Search, bool bRemove) +{ + PSINGLE_LIST_ENTRY pHead = &pList->Head; + PSINGLE_LIST_ENTRY pCur; + PSINGLE_LIST_ENTRY pPrev; + for (pCur = pHead->Next, pPrev = pHead; pCur; pPrev = pCur, pCur = pCur->Next) + { + if (pEntry2Search == pCur) + { + if (bRemove) + { + pPrev->Next = pCur->Next; + if (pCur == pList->pTail) + { + pList->pTail = pPrev; + } + } + return true; + } + } + return false; +} + +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinSearchPacket(PVBOXNETFLT_SINGLE_LIST pList, PNDIS_PACKET pPacket2Search, int cbMatch, bool bRemove) +{ + PSINGLE_LIST_ENTRY pHead = &pList->Head; + PSINGLE_LIST_ENTRY pCur; + PSINGLE_LIST_ENTRY pPrev; + PNDIS_PACKET pCurPacket; + for (pCur = pHead->Next, pPrev = pHead; pCur; pPrev = pCur, pCur = pCur->Next) + { + pCurPacket = VBOX_SLE_2_SENDPACKET(pCur); + if (pCurPacket == pPacket2Search || vboxNetFltWinMatchPackets(pPacket2Search, pCurPacket, cbMatch)) + { + if (bRemove) + { + pPrev->Next = pCur->Next; + if (pCur == pList->pTail) + { + pList->pTail = pPrev; + } + } + return pCurPacket; + } + } + return NULL; +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinSearchPacketBySG(PVBOXNETFLT_SINGLE_LIST pList, PINTNETSG pSG, int cbMatch, bool bRemove) +{ + PSINGLE_LIST_ENTRY pHead = &pList->Head; + PSINGLE_LIST_ENTRY pCur; + PSINGLE_LIST_ENTRY pPrev; + PNDIS_PACKET pCurPacket; + for (pCur = pHead->Next, pPrev = pHead; pCur; pPrev = pCur, pCur = pCur->Next) + { + pCurPacket = VBOX_SLE_2_SENDPACKET(pCur); + if (vboxNetFltWinMatchPacketAndSG(pCurPacket, pSG, cbMatch)) + { + if (bRemove) + { + pPrev->Next = pCur->Next; + if (pCur == pList->pTail) + { + pList->pTail = pPrev; + } + } + return pCurPacket; + } + } + return NULL; +} + +#endif /* #if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) */ + +DECLINLINE(bool) vboxNetFltWinSListIsEmpty(PVBOXNETFLT_SINGLE_LIST pList) +{ + return !pList->Head.Next; +} + +DECLINLINE(void) vboxNetFltWinPutTail(PVBOXNETFLT_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + pList->pTail->Next = pEntry; + pList->pTail = pEntry; + pEntry->Next = NULL; +} + +DECLINLINE(void) vboxNetFltWinPutHead(PVBOXNETFLT_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + pEntry->Next = pList->Head.Next; + pList->Head.Next = pEntry; + if (!pEntry->Next) + pList->pTail = pEntry; +} + +DECLINLINE(PSINGLE_LIST_ENTRY) vboxNetFltWinGetHead(PVBOXNETFLT_SINGLE_LIST pList) +{ + PSINGLE_LIST_ENTRY pEntry = pList->Head.Next; + if (pEntry && pEntry == pList->pTail) + { + pList->Head.Next = NULL; + pList->pTail = &pList->Head; + } + return pEntry; +} + +DECLINLINE(bool) vboxNetFltWinInterlockedSearchListEntry(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry2Search, bool bRemove) +{ + bool bFound; + NdisAcquireSpinLock(&pList->Lock); + bFound = vboxNetFltWinSearchListEntry(&pList->List, pEntry2Search, bRemove); + NdisReleaseSpinLock(&pList->Lock); + return bFound; +} + +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinInterlockedSearchPacket(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PNDIS_PACKET pPacket2Search, int cbMatch, bool bRemove) +{ + PNDIS_PACKET pFound; + NdisAcquireSpinLock(&pList->Lock); + pFound = vboxNetFltWinSearchPacket(&pList->List, pPacket2Search, cbMatch, bRemove); + NdisReleaseSpinLock(&pList->Lock); + return pFound; +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinInterlockedSearchPacketBySG(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PINTNETSG pSG, int cbMatch, bool bRemove) +{ + PNDIS_PACKET pFound; + NdisAcquireSpinLock(&pList->Lock); + pFound = vboxNetFltWinSearchPacketBySG(&pList->List, pSG, cbMatch, bRemove); + NdisReleaseSpinLock(&pList->Lock); + return pFound; +} +#endif /* #if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) */ + +DECLINLINE(void) vboxNetFltWinInterlockedPutTail(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + NdisAcquireSpinLock(&pList->Lock); + vboxNetFltWinPutTail(&pList->List, pEntry); + NdisReleaseSpinLock(&pList->Lock); +} + +DECLINLINE(void) vboxNetFltWinInterlockedPutHead(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + NdisAcquireSpinLock(&pList->Lock); + vboxNetFltWinPutHead(&pList->List, pEntry); + NdisReleaseSpinLock(&pList->Lock); +} + +DECLINLINE(PSINGLE_LIST_ENTRY) vboxNetFltWinInterlockedGetHead(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList) +{ + PSINGLE_LIST_ENTRY pEntry; + NdisAcquireSpinLock(&pList->Lock); + pEntry = vboxNetFltWinGetHead(&pList->List); + NdisReleaseSpinLock(&pList->Lock); + return pEntry; +} + +# if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) +DECLINLINE(void) vboxNetFltWinLbPutSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, bool bFromIntNet) +{ + PVBOXNETFLT_PKTRSVD_PT pSrv = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + pSrv->bFromIntNet = bFromIntNet; + vboxNetFltWinInterlockedPutHead(&pNetFlt->u.s.WinIf.SendPacketQueue, &pSrv->ListEntry); +} + +DECLINLINE(bool) vboxNetFltWinLbIsFromIntNet(PNDIS_PACKET pPacket) +{ + PVBOXNETFLT_PKTRSVD_PT pSrv = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + return pSrv->bFromIntNet; +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinLbSearchLoopBack(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, bool bRemove) +{ + return vboxNetFltWinInterlockedSearchPacket(&pNetFlt->u.s.WinIf.SendPacketQueue, pPacket, VBOXNETFLT_PACKETMATCH_LENGTH, bRemove); +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinLbSearchLoopBackBySG(PVBOXNETFLTINS pNetFlt, PINTNETSG pSG, bool bRemove) +{ + return vboxNetFltWinInterlockedSearchPacketBySG(&pNetFlt->u.s.WinIf.SendPacketQueue, pSG, VBOXNETFLT_PACKETMATCH_LENGTH, bRemove); +} + +DECLINLINE(bool) vboxNetFltWinLbRemoveSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket) +{ + PVBOXNETFLT_PKTRSVD_PT pSrv = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + bool bRet = vboxNetFltWinInterlockedSearchListEntry(&pNetFlt->u.s.WinIf.SendPacketQueue, &pSrv->ListEntry, true); +#ifdef DEBUG_misha + Assert(bRet == (pNetFlt->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE)); +#endif + return bRet; +} + +# endif + +#endif + +#ifdef DEBUG_misha +DECLHIDDEN(bool) vboxNetFltWinCheckMACs(PNDIS_PACKET pPacket, PRTMAC pDst, PRTMAC pSrc); +DECLHIDDEN(bool) vboxNetFltWinCheckMACsSG(PINTNETSG pSG, PRTMAC pDst, PRTMAC pSrc); +extern RTMAC g_vboxNetFltWinVerifyMACBroadcast; +extern RTMAC g_vboxNetFltWinVerifyMACGuest; + +# define VBOXNETFLT_LBVERIFY(_pnf, _p) \ + do { \ + Assert(!vboxNetFltWinCheckMACs(_p, NULL, &g_vboxNetFltWinVerifyMACGuest)); \ + Assert(!vboxNetFltWinCheckMACs(_p, NULL, &(_pnf)->u.s.MacAddr)); \ + } while (0) + +# define VBOXNETFLT_LBVERIFYSG(_pnf, _p) \ + do { \ + Assert(!vboxNetFltWinCheckMACsSG(_p, NULL, &g_vboxNetFltWinVerifyMACGuest)); \ + Assert(!vboxNetFltWinCheckMACsSG(_p, NULL, &(_pnf)->u.s.MacAddr)); \ + } while (0) + +#else +# define VBOXNETFLT_LBVERIFY(_pnf, _p) do { } while (0) +# define VBOXNETFLT_LBVERIFYSG(_pnf, _p) do { } while (0) +#endif + +/** initializes the list */ +#define INIT_SINGLE_LIST(_pList) \ + { \ + (_pList)->Head.Next = NULL; \ + (_pList)->pTail = &(_pList)->Head; \ + } + +/** initializes the list */ +#define INIT_INTERLOCKED_SINGLE_LIST(_pList) \ + do { \ + INIT_SINGLE_LIST(&(_pList)->List); \ + NdisAllocateSpinLock(&(_pList)->Lock); \ + } while (0) + +/** delete the packet queue */ +#define FINI_INTERLOCKED_SINGLE_LIST(_pList) \ + do { \ + Assert(vboxNetFltWinSListIsEmpty(&(_pList)->List)); \ + NdisFreeSpinLock(&(_pList)->Lock); \ + } while (0) + + +/************************************************************************** + * PVBOXNETFLTINS , WinIf reference/dereference (i.e. retain/release) API * + **************************************************************************/ + + +DECLHIDDEN(void) vboxNetFltWinWaitDereference(PVBOXNETFLT_WINIF_DEVICE pState); + +DECLINLINE(void) vboxNetFltWinReferenceModeNetFlt(PVBOXNETFLTINS pIns) +{ + ASMAtomicIncU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs); +} + +DECLINLINE(void) vboxNetFltWinReferenceModePassThru(PVBOXNETFLTINS pIns) +{ + ASMAtomicIncU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs); +} + +DECLINLINE(void) vboxNetFltWinIncReferenceModeNetFlt(PVBOXNETFLTINS pIns, uint32_t v) +{ + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs, v); +} + +DECLINLINE(void) vboxNetFltWinIncReferenceModePassThru(PVBOXNETFLTINS pIns, uint32_t v) +{ + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs, v); +} + +DECLINLINE(void) vboxNetFltWinDereferenceModeNetFlt(PVBOXNETFLTINS pIns) +{ + ASMAtomicDecU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs); +} + +DECLINLINE(void) vboxNetFltWinDereferenceModePassThru(PVBOXNETFLTINS pIns) +{ + ASMAtomicDecU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs); +} + +DECLINLINE(void) vboxNetFltWinDecReferenceModeNetFlt(PVBOXNETFLTINS pIns, uint32_t v) +{ + Assert(v); + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs, (uint32_t)(-((int32_t)v))); +} + +DECLINLINE(void) vboxNetFltWinDecReferenceModePassThru(PVBOXNETFLTINS pIns, uint32_t v) +{ + Assert(v); + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs, (uint32_t)(-((int32_t)v))); +} + +DECLINLINE(void) vboxNetFltWinSetPowerState(PVBOXNETFLT_WINIF_DEVICE pState, NDIS_DEVICE_POWER_STATE State) +{ + ASMAtomicUoWriteU32((volatile uint32_t *)&pState->PowerState, State); +} + +DECLINLINE(NDIS_DEVICE_POWER_STATE) vboxNetFltWinGetPowerState(PVBOXNETFLT_WINIF_DEVICE pState) +{ + return (NDIS_DEVICE_POWER_STATE)ASMAtomicUoReadU32((volatile uint32_t *)&pState->PowerState); +} + +DECLINLINE(void) vboxNetFltWinSetOpState(PVBOXNETFLT_WINIF_DEVICE pState, VBOXNETDEVOPSTATE State) +{ + ASMAtomicUoWriteU32((volatile uint32_t *)&pState->OpState, State); +} + +DECLINLINE(VBOXNETDEVOPSTATE) vboxNetFltWinGetOpState(PVBOXNETFLT_WINIF_DEVICE pState) +{ + return (VBOXNETDEVOPSTATE)ASMAtomicUoReadU32((volatile uint32_t *)&pState->OpState); +} + +DECLINLINE(bool) vboxNetFltWinDoReferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState) +{ + if (vboxNetFltWinGetPowerState(pState) == NdisDeviceStateD0 && vboxNetFltWinGetOpState(pState) == kVBoxNetDevOpState_Initialized) + { + /** @todo r=bird: Since this is a volatile member, why don't you declare it as + * such and save yourself all the casting? */ + ASMAtomicIncU32((uint32_t volatile *)&pState->cReferences); + return true; + } + return false; +} + +#ifndef VBOXNETADP +DECLINLINE(bool) vboxNetFltWinDoReferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2) +{ + if (vboxNetFltWinGetPowerState(pState1) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState1) == kVBoxNetDevOpState_Initialized + && vboxNetFltWinGetPowerState(pState2) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState2) == kVBoxNetDevOpState_Initialized) + { + ASMAtomicIncU32((uint32_t volatile *)&pState1->cReferences); + ASMAtomicIncU32((uint32_t volatile *)&pState2->cReferences); + return true; + } + return false; +} +#endif + +DECLINLINE(void) vboxNetFltWinDereferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState) +{ + ASMAtomicDecU32((uint32_t volatile *)&pState->cReferences); + /** @todo r=bird: Add comment explaining why these cannot hit 0 or why + * reference are counted */ +} + +#ifndef VBOXNETADP +DECLINLINE(void) vboxNetFltWinDereferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2) +{ + ASMAtomicDecU32((uint32_t volatile *)&pState1->cReferences); + ASMAtomicDecU32((uint32_t volatile *)&pState2->cReferences); +} +#endif + +DECLINLINE(void) vboxNetFltWinDecReferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState, uint32_t v) +{ + Assert(v); + ASMAtomicAddU32((uint32_t volatile *)&pState->cReferences, (uint32_t)(-((int32_t)v))); +} + +#ifndef VBOXNETADP +DECLINLINE(void) vboxNetFltWinDecReferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2, uint32_t v) +{ + ASMAtomicAddU32((uint32_t volatile *)&pState1->cReferences, (uint32_t)(-((int32_t)v))); + ASMAtomicAddU32((uint32_t volatile *)&pState2->cReferences, (uint32_t)(-((int32_t)v))); +} +#endif + +DECLINLINE(bool) vboxNetFltWinDoIncReferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState, uint32_t v) +{ + Assert(v); + if (vboxNetFltWinGetPowerState(pState) == NdisDeviceStateD0 && vboxNetFltWinGetOpState(pState) == kVBoxNetDevOpState_Initialized) + { + ASMAtomicAddU32((uint32_t volatile *)&pState->cReferences, v); + return true; + } + return false; +} + +#ifndef VBOXNETADP +DECLINLINE(bool) vboxNetFltWinDoIncReferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2, uint32_t v) +{ + if (vboxNetFltWinGetPowerState(pState1) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState1) == kVBoxNetDevOpState_Initialized + && vboxNetFltWinGetPowerState(pState2) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState2) == kVBoxNetDevOpState_Initialized) + { + ASMAtomicAddU32((uint32_t volatile *)&pState1->cReferences, v); + ASMAtomicAddU32((uint32_t volatile *)&pState2->cReferences, v); + return true; + } + return false; +} +#endif + + +DECLINLINE(bool) vboxNetFltWinReferenceWinIfNetFlt(PVBOXNETFLTINS pNetFlt, bool * pbNetFltActive) +{ + RTSpinlockAcquire((pNetFlt)->hSpinlock); +#ifndef VBOXNETADP + if (!vboxNetFltWinDoReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState)) +#else + if (!vboxNetFltWinDoReferenceDevice(&pNetFlt->u.s.WinIf.MpState)) +#endif + { + RTSpinlockRelease((pNetFlt)->hSpinlock); + *pbNetFltActive = false; + return false; + } + + if (pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE) + { + vboxNetFltWinReferenceModePassThru(pNetFlt); + RTSpinlockRelease((pNetFlt)->hSpinlock); + *pbNetFltActive = false; + return true; + } + + vboxNetFltRetain((pNetFlt), true /* fBusy */); + vboxNetFltWinReferenceModeNetFlt(pNetFlt); + RTSpinlockRelease((pNetFlt)->hSpinlock); + + *pbNetFltActive = true; + return true; +} + +DECLINLINE(bool) vboxNetFltWinIncReferenceWinIfNetFlt(PVBOXNETFLTINS pNetFlt, uint32_t v, bool *pbNetFltActive) +{ + uint32_t i; + + Assert(v); + if (!v) + { + *pbNetFltActive = false; + return false; + } + + RTSpinlockAcquire((pNetFlt)->hSpinlock); +#ifndef VBOXNETADP + if (!vboxNetFltWinDoIncReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState, v)) +#else + if (!vboxNetFltWinDoIncReferenceDevice(&pNetFlt->u.s.WinIf.MpState, v)) +#endif + { + RTSpinlockRelease(pNetFlt->hSpinlock); + *pbNetFltActive = false; + return false; + } + + if (pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE) + { + vboxNetFltWinIncReferenceModePassThru(pNetFlt, v); + + RTSpinlockRelease((pNetFlt)->hSpinlock); + *pbNetFltActive = false; + return true; + } + + vboxNetFltRetain(pNetFlt, true /* fBusy */); + + vboxNetFltWinIncReferenceModeNetFlt(pNetFlt, v); + + RTSpinlockRelease(pNetFlt->hSpinlock); + + /* we have marked it as busy, so can do the res references outside the lock */ + for (i = 0; i < v-1; i++) + { + vboxNetFltRetain(pNetFlt, true /* fBusy */); + } + + *pbNetFltActive = true; + + return true; +} + +DECLINLINE(void) vboxNetFltWinDecReferenceNetFlt(PVBOXNETFLTINS pNetFlt, uint32_t n) +{ + uint32_t i; + for (i = 0; i < n; i++) + { + vboxNetFltRelease(pNetFlt, true); + } + + vboxNetFltWinDecReferenceModeNetFlt(pNetFlt, n); +} + +DECLINLINE(void) vboxNetFltWinDereferenceNetFlt(PVBOXNETFLTINS pNetFlt) +{ + vboxNetFltRelease(pNetFlt, true); + + vboxNetFltWinDereferenceModeNetFlt(pNetFlt); +} + +DECLINLINE(void) vboxNetFltWinDecReferenceWinIf(PVBOXNETFLTINS pNetFlt, uint32_t v) +{ +#ifdef VBOXNETADP + vboxNetFltWinDecReferenceDevice(&pNetFlt->u.s.WinIf.MpState, v); +#else + vboxNetFltWinDecReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState, v); +#endif +} + +DECLINLINE(void) vboxNetFltWinDereferenceWinIf(PVBOXNETFLTINS pNetFlt) +{ +#ifdef VBOXNETADP + vboxNetFltWinDereferenceDevice(&pNetFlt->u.s.WinIf.MpState); +#else + vboxNetFltWinDereferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState); +#endif +} + +DECLINLINE(bool) vboxNetFltWinIncReferenceWinIf(PVBOXNETFLTINS pNetFlt, uint32_t v) +{ + Assert(v); + if (!v) + { + return false; + } + + RTSpinlockAcquire(pNetFlt->hSpinlock); +#ifdef VBOXNETADP + if (vboxNetFltWinDoIncReferenceDevice(&pNetFlt->u.s.WinIf.MpState, v)) +#else + if (vboxNetFltWinDoIncReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState, v)) +#endif + { + RTSpinlockRelease(pNetFlt->hSpinlock); + return true; + } + + RTSpinlockRelease(pNetFlt->hSpinlock); + return false; +} + +DECLINLINE(bool) vboxNetFltWinReferenceWinIf(PVBOXNETFLTINS pNetFlt) +{ + RTSpinlockAcquire(pNetFlt->hSpinlock); +#ifdef VBOXNETADP + if (vboxNetFltWinDoReferenceDevice(&pNetFlt->u.s.WinIf.MpState)) +#else + if (vboxNetFltWinDoReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState)) +#endif + { + RTSpinlockRelease(pNetFlt->hSpinlock); + return true; + } + + RTSpinlockRelease(pNetFlt->hSpinlock); + return false; +} + +/*********************************************** + * methods for accessing the network card info * + ***********************************************/ + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinGetMacAddress(PVBOXNETFLTINS pNetFlt, PRTMAC pMac); +DECLHIDDEN(bool) vboxNetFltWinIsPromiscuous(PVBOXNETFLTINS pNetFlt); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinSetPromiscuous(PVBOXNETFLTINS pNetFlt, bool bYes); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQueryPhysicalMedium(PVBOXNETFLTINS pNetFlt, NDIS_PHYSICAL_MEDIUM * pMedium); + +/********************* + * mem alloc API * + *********************/ + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMemAlloc(PVOID* ppMemBuf, UINT cbLength); + +DECLHIDDEN(void) vboxNetFltWinMemFree(PVOID pMemBuf); + +/* convenience method used which allocates and initializes the PINTNETSG containing one + * segment referring the buffer of size cbBufSize + * the allocated PINTNETSG should be freed with the vboxNetFltWinMemFree. + * + * This is used when our ProtocolReceive callback is called and we have to return the indicated NDIS_PACKET + * on a callback exit. This is why we allocate the PINTNETSG and put the packet info there and enqueue it + * for the packet queue */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinAllocSG(UINT cbBufSize, PINTNETSG *ppSG); + +/************************ + * WinIf init/fini API * + ************************/ +#if defined(VBOXNETADP) +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, NDIS_HANDLE hMiniportAdapter, PNDIS_STRING pBindToMiniportName /* actually this is our miniport name*/, NDIS_HANDLE hWrapperConfigurationContext); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf); +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, PNDIS_STRING pOurMiniportName, PNDIS_STRING pBindToMiniportName); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf, PNDIS_STRING pOurDeviceName); +#endif + +DECLHIDDEN(VOID) vboxNetFltWinPtFiniWinIf(PVBOXNETFLTWIN pWinIf); + +/************************************ + * Execute Job at passive level API * + ************************************/ + +typedef VOID (*PFNVBOXNETFLT_JOB_ROUTINE) (PVOID pContext); + +DECLHIDDEN(VOID) vboxNetFltWinJobSynchExecAtPassive(PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext); + +/******************************* + * Ndis Packets processing API * + *******************************/ +DECLHIDDEN(PNDIS_PACKET) vboxNetFltWinNdisPacketFromSG(PVBOXNETFLTINS pNetFlt, PINTNETSG pSG, PVOID pBufToFree, bool bToWire, bool bCopyMemory); + +DECLHIDDEN(void) vboxNetFltWinFreeSGNdisPacket(PNDIS_PACKET pPacket, bool bFreeMem); + +#ifdef DEBUG_NETFLT_PACKETS +#define DBG_CHECK_PACKETS(_p1, _p2) \ + { \ + bool _b = vboxNetFltWinMatchPackets(_p1, _p2, -1); \ + Assert(_b); \ + } + +#define DBG_CHECK_PACKET_AND_SG(_p, _sg) \ + { \ + bool _b = vboxNetFltWinMatchPacketAndSG(_p, _sg, -1); \ + Assert(_b); \ + } + +#define DBG_CHECK_SGS(_sg1, _sg2) \ + { \ + bool _b = vboxNetFltWinMatchSGs(_sg1, _sg2, -1); \ + Assert(_b); \ + } + +#else +#define DBG_CHECK_PACKETS(_p1, _p2) +#define DBG_CHECK_PACKET_AND_SG(_p, _sg) +#define DBG_CHECK_SGS(_sg1, _sg2) +#endif + +/** + * Ndis loops back broadcast packets posted to the wire by IntNet + * This routine is used in the mechanism of preventing this looping + * + * @param pAdapt + * @param pPacket + * @param bOnRecv true is we are receiving the packet from the wire + * false otherwise (i.e. the packet is from the host) + * + * @return true if the packet is a looped back one, false otherwise + */ +#ifdef VBOX_LOOPBACK_USEFLAGS +DECLINLINE(bool) vboxNetFltWinIsLoopedBackPacket(PNDIS_PACKET pPacket) +{ + return (NdisGetPacketFlags(pPacket) & g_fPacketIsLoopedBack) == g_fPacketIsLoopedBack; +} +#endif + +/************************************************************** + * utility methods for ndis packet creation/initialization * + **************************************************************/ + +#define VBOXNETFLT_OOB_INIT(_p) \ + { \ + NdisZeroMemory(NDIS_OOB_DATA_FROM_PACKET(_p), sizeof(NDIS_PACKET_OOB_DATA)); \ + NDIS_SET_PACKET_HEADER_SIZE(_p, VBOXNETFLT_PACKET_ETHEADER_SIZE); \ + } + +#ifndef VBOXNETADP + +DECLINLINE(NDIS_STATUS) vboxNetFltWinCopyPacketInfoOnRecv(PNDIS_PACKET pDstPacket, PNDIS_PACKET pSrcPacket, bool bForceStatusResources) +{ + NDIS_STATUS Status = bForceStatusResources ? NDIS_STATUS_RESOURCES : NDIS_GET_PACKET_STATUS(pSrcPacket); + NDIS_SET_PACKET_STATUS(pDstPacket, Status); + + NDIS_PACKET_FIRST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(pSrcPacket); + NDIS_PACKET_LAST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(pSrcPacket); + + NdisGetPacketFlags(pDstPacket) = NdisGetPacketFlags(pSrcPacket); + + NDIS_SET_ORIGINAL_PACKET(pDstPacket, NDIS_GET_ORIGINAL_PACKET(pSrcPacket)); + NDIS_SET_PACKET_HEADER_SIZE(pDstPacket, NDIS_GET_PACKET_HEADER_SIZE(pSrcPacket)); + + return Status; +} + +DECLINLINE(void) vboxNetFltWinCopyPacketInfoOnSend(PNDIS_PACKET pDstPacket, PNDIS_PACKET pSrcPacket) +{ + NDIS_PACKET_FIRST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(pSrcPacket); + NDIS_PACKET_LAST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(pSrcPacket); + + NdisGetPacketFlags(pDstPacket) = NdisGetPacketFlags(pSrcPacket); + + NdisMoveMemory(NDIS_OOB_DATA_FROM_PACKET(pDstPacket), + NDIS_OOB_DATA_FROM_PACKET(pSrcPacket), + sizeof (NDIS_PACKET_OOB_DATA)); + + NdisIMCopySendPerPacketInfo(pDstPacket, pSrcPacket); + + PVOID pMediaSpecificInfo = NULL; + UINT fMediaSpecificInfoSize = 0; + + NDIS_GET_PACKET_MEDIA_SPECIFIC_INFO(pSrcPacket, &pMediaSpecificInfo, &fMediaSpecificInfoSize); + + if (pMediaSpecificInfo || fMediaSpecificInfoSize) + { + NDIS_SET_PACKET_MEDIA_SPECIFIC_INFO(pDstPacket, pMediaSpecificInfo, fMediaSpecificInfoSize); + } +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareRecvPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket, bool bDpr); +#endif + +DECLHIDDEN(void) vboxNetFltWinSleep(ULONG milis); + +#define MACS_EQUAL(_m1, _m2) \ + ((_m1).au16[0] == (_m2).au16[0] \ + && (_m1).au16[1] == (_m2).au16[1] \ + && (_m1).au16[2] == (_m2).au16[2]) + + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinDetachFromInterface(PVBOXNETFLTINS pNetFlt, bool bOnUnbind); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinCopyString(PNDIS_STRING pDst, PNDIS_STRING pSrc); + + +/** + * Sets the enmState member atomically. + * + * Used for all updates. + * + * @param pThis The instance. + * @param enmNewState The new value. + */ +DECLINLINE(void) vboxNetFltWinSetWinIfState(PVBOXNETFLTINS pNetFlt, VBOXNETFLT_WINIFSTATE enmNewState) +{ + ASMAtomicWriteU32((uint32_t volatile *)&pNetFlt->u.s.WinIf.enmState, enmNewState); +} + +/** + * Gets the enmState member atomically. + * + * Used for all reads. + * + * @returns The enmState value. + * @param pThis The instance. + */ +DECLINLINE(VBOXNETFLT_WINIFSTATE) vboxNetFltWinGetWinIfState(PVBOXNETFLTINS pNetFlt) +{ + return (VBOXNETFLT_WINIFSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pNetFlt->u.s.WinIf.enmState); +} + +/* reference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvReference(); +/* dereference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvDereference(); + + +#ifndef VBOXNETADP +# define VBOXNETFLT_PROMISCUOUS_SUPPORTED(_pNetFlt) (!(_pNetFlt)->fDisablePromiscuous) +#else +# define STATISTIC_INCREASE(_s) ASMAtomicIncU32((uint32_t volatile *)&(_s)); + +DECLHIDDEN(void) vboxNetFltWinGenerateMACAddress(RTMAC *pMac); +DECLHIDDEN(int) vboxNetFltWinMAC2NdisString(RTMAC *pMac, PNDIS_STRING pNdisString); +DECLHIDDEN(int) vboxNetFltWinMACFromNdisString(RTMAC *pMac, PNDIS_STRING pNdisString); + +#endif +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltRt_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp new file mode 100644 index 00000000..6520e909 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp @@ -0,0 +1,2736 @@ +/* $Id: VBoxNetLwf-win.cpp $ */ +/** @file + * VBoxNetLwf-win.cpp - NDIS6 Bridged Networking Driver, Windows-specific code. + */ +/* + * Copyright (C) 2014-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 <https://www.gnu.org/licenses>. + * + * 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 + */ +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV + +/* + * If VBOXNETLWF_SYNC_SEND is defined we won't allocate data buffers, but use + * the original buffers coming from IntNet to build MDLs around them. This + * also means that we need to wait for send operation to complete before + * returning the buffers, which hinders performance way too much. + */ +//#define VBOXNETLWF_SYNC_SEND + +/* + * If VBOXNETLWF_FIXED_SIZE_POOLS is defined we pre-allocate data buffers of + * fixed size in five pools. Each pool uses different size to accomodate packets + * of various sizes. We allocate these buffers once and re-use them when send + * operation is complete. + * If VBOXNETLWF_FIXED_SIZE_POOLS is not defined we allocate data buffers before + * each send operation and free then upon completion. + */ +#define VBOXNETLWF_FIXED_SIZE_POOLS + +/* + * Don't ask me why it is 42. Empirically this is what goes down the stack. + * OTOH, as we know from trustworthy sources, 42 is the answer, so be it. + */ +#define VBOXNETLWF_MAX_FRAME_SIZE(mtu) (mtu + 42) + +#include <VBox/version.h> +#include <VBox/err.h> +#include <iprt/initterm.h> +#include <iprt/net.h> +#include <iprt/list.h> +#include <VBox/intnetinline.h> + +#include <iprt/nt/ntddk.h> +#include <iprt/nt/ndis.h> +#include <iprt/win/netioapi.h> +#include <mstcpip.h> + +#define LogError(x) DbgPrint x + +#if 0 +#undef Log +#define Log(x) DbgPrint x +#undef LogFlow +#define LogFlow(x) DbgPrint x +#endif + +/** We have an entirely different structure than the one defined in VBoxNetFltCmn-win.h */ +typedef struct VBOXNETFLTWIN +{ + /** filter module context handle */ + NDIS_HANDLE hModuleCtx; + /** IP address change notifier handle */ + HANDLE hNotifier; /* Must be here as hModuleCtx may already be NULL when vboxNetFltOsDeleteInstance is called */ +} VBOXNETFLTWIN, *PVBOXNETFLTWIN; +#define VBOXNETFLT_NO_PACKET_QUEUE +#define VBOXNETFLT_OS_SPECFIC 1 +#include "VBoxNetFltInternal.h" + +#include "VBoxNetLwf-win.h" +#include "VBox/VBoxNetCmn-win.h" + +typedef enum { + LwfState_Detached = 0, + LwfState_Attaching, + LwfState_Paused, + LwfState_Restarting, + LwfState_Running, + LwfState_Pausing, + LwfState_32BitHack = 0x7fffffff +} VBOXNETLWFSTATE; + +/* + * Valid state transitions are: + * 1) Disconnected -> Connecting : start the worker thread, attempting to init IDC; + * 2) Connecting -> Disconnected : failed to start IDC init worker thread; + * 3) Connecting -> Connected : IDC init successful, terminate the worker; + * 4) Connecting -> Stopping : IDC init incomplete, but the driver is being unloaded, terminate the worker; + * 5) Connected -> Stopping : IDC init was successful, no worker, the driver is being unloaded; + * + * Driver terminates in Stopping state. + */ +typedef enum { + LwfIdcState_Disconnected = 0, /* Initial state */ + LwfIdcState_Connecting, /* Attemping to init IDC, worker thread running */ + LwfIdcState_Connected, /* Successfully connected to IDC, worker thread terminated */ + LwfIdcState_Stopping /* Terminating the worker thread and disconnecting IDC */ +} VBOXNETLWFIDCSTATE; + +struct _VBOXNETLWF_MODULE; + +typedef struct VBOXNETLWFGLOBALS +{ + /** synch event used for device creation synchronization */ + //KEVENT SynchEvent; + /** Device reference count */ + //int cDeviceRefs; + /** ndis device */ + NDIS_HANDLE hDevice; + /** device object */ + PDEVICE_OBJECT pDevObj; + /** our filter driver handle */ + NDIS_HANDLE hFilterDriver; + /** lock protecting the module list */ + NDIS_SPIN_LOCK Lock; + /** the head of module list */ + RTLISTANCHOR listModules; + /** IDC initialization state */ + volatile uint32_t enmIdcState; + /** IDC init thread handle */ + HANDLE hInitIdcThread; +} VBOXNETLWFGLOBALS, *PVBOXNETLWFGLOBALS; + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; +/* win-specific global data */ +VBOXNETLWFGLOBALS g_VBoxNetLwfGlobals; + +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS +static ULONG g_cbPool[] = { 576+56, 1556, 4096+56, 6192+56, 9056 }; +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ + +typedef struct _VBOXNETLWF_MODULE { + RTLISTNODE node; + + NDIS_HANDLE hFilter; +#ifndef VBOXNETLWF_FIXED_SIZE_POOLS + NDIS_HANDLE hPool; +#else /* VBOXNETLWF_FIXED_SIZE_POOLS */ + NDIS_HANDLE hPool[RT_ELEMENTS(g_cbPool)]; +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ + PVBOXNETLWFGLOBALS pGlobals; + /** Associated instance of NetFlt, one-to-one relationship */ + PVBOXNETFLTINS pNetFlt; /// @todo Consider automic access! + /** Module state as described in http://msdn.microsoft.com/en-us/library/windows/hardware/ff550017(v=vs.85).aspx */ + volatile uint32_t enmState; /* No lock needed yet, atomic should suffice. */ + /** Mutex to prevent pausing while transmitting on behalf of NetFlt */ + NDIS_MUTEX InTransmit; +#ifdef VBOXNETLWF_SYNC_SEND + /** Event signalled when sending to the wire is complete */ + KEVENT EventWire; + /** Event signalled when NDIS returns our receive notification */ + KEVENT EventHost; +#else /* !VBOXNETLWF_SYNC_SEND */ + /** Event signalled when all pending sends (both to wire and host) have completed */ + NDIS_EVENT EventSendComplete; + /** Counter for pending sends (both to wire and host) */ + int32_t cPendingBuffers; + /** Work Item to deliver offloading indications at passive IRQL */ + NDIS_HANDLE hWorkItem; +#endif /* !VBOXNETLWF_SYNC_SEND */ + /** MAC address of underlying adapter */ + RTMAC MacAddr; + /** Size of offload config structure */ + USHORT cbOffloadConfig; + /** Saved offload configuration */ + PNDIS_OFFLOAD pSavedOffloadConfig; + /** Temporary buffer for disabling offload configuration */ + PNDIS_OFFLOAD pDisabledOffloadConfig; + /** the cloned request we have passed down */ + PNDIS_OID_REQUEST pPendingRequest; + /** true if the underlying miniport supplied offloading config */ + bool fOffloadConfigValid; + /** true if the trunk expects data from us */ + bool fActive; + /** true if the host wants the adapter to be in promisc mode */ + bool fHostPromisc; + /** true if the user wants packets being sent or received by VMs to be visible to the host in promisc mode */ + bool fPassVmTrafficToHost; + /** Name of underlying adapter */ + char szMiniportName[1]; +} VBOXNETLWF_MODULE; +typedef VBOXNETLWF_MODULE *PVBOXNETLWF_MODULE; + +/* + * A structure to wrap OID requests in. + */ +typedef struct _VBOXNETLWF_OIDREQ { + NDIS_OID_REQUEST Request; + NDIS_STATUS Status; + NDIS_EVENT Event; +} VBOXNETLWF_OIDREQ; +typedef VBOXNETLWF_OIDREQ *PVBOXNETLWF_OIDREQ; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static FILTER_ATTACH vboxNetLwfWinAttach; +static FILTER_DETACH vboxNetLwfWinDetach; +static FILTER_RESTART vboxNetLwfWinRestart; +static FILTER_PAUSE vboxNetLwfWinPause; +static FILTER_OID_REQUEST vboxNetLwfWinOidRequest; +static FILTER_OID_REQUEST_COMPLETE vboxNetLwfWinOidRequestComplete; +//static FILTER_CANCEL_OID_REQUEST vboxNetLwfWinCancelOidRequest; +static FILTER_STATUS vboxNetLwfWinStatus; +//static FILTER_NET_PNP_EVENT vboxNetLwfWinPnPEvent; +static FILTER_SEND_NET_BUFFER_LISTS vboxNetLwfWinSendNetBufferLists; +static FILTER_SEND_NET_BUFFER_LISTS_COMPLETE vboxNetLwfWinSendNetBufferListsComplete; +static FILTER_RECEIVE_NET_BUFFER_LISTS vboxNetLwfWinReceiveNetBufferLists; +static FILTER_RETURN_NET_BUFFER_LISTS vboxNetLwfWinReturnNetBufferLists; +static KSTART_ROUTINE vboxNetLwfWinInitIdcWorker; + +static VOID vboxNetLwfWinUnloadDriver(IN PDRIVER_OBJECT pDriver); +static int vboxNetLwfWinInitBase(void); +static int vboxNetLwfWinFini(void); + + + +/** + * Logs an error to the system event log. + * + * @param ErrCode Error to report to event log. + * @param ReturnedStatus Error that was reported by the driver to the caller. + * @param uErrId Unique error id representing the location in the driver. + * @param cbDumpData Number of bytes at pDumpData. + * @param pDumpData Pointer to data that will be added to the message (see 'details' tab). + */ +static void vboxNetLwfLogErrorEvent(NTSTATUS uErrCode, NTSTATUS uReturnedStatus, ULONG uErrId) +{ + /* Figure out how many modules are attached and if they are going to fit into the dump data. */ + unsigned cMaxModules = (ERROR_LOG_MAXIMUM_SIZE - FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData)) / sizeof(RTMAC); + unsigned cModules = 0; + PVBOXNETLWF_MODULE pModuleCtx; + NdisAcquireSpinLock(&g_VBoxNetLwfGlobals.Lock); + RTListForEach(&g_VBoxNetLwfGlobals.listModules, pModuleCtx, VBOXNETLWF_MODULE, node) + ++cModules; + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + /* Prevent overflow */ + if (cModules > cMaxModules) + cModules = cMaxModules; + + /* DumpDataSize must be a multiple of sizeof(ULONG). */ + unsigned cbDumpData = (cModules * sizeof(RTMAC) + 3) & ~3; + /* Prevent underflow */ + unsigned cbTotal = RT_MAX(FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData, + sizeof(IO_ERROR_LOG_PACKET)); + + PIO_ERROR_LOG_PACKET pErrEntry; + pErrEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(g_VBoxNetLwfGlobals.pDevObj, + (UCHAR)cbTotal); + if (pErrEntry) + { + PRTMAC pDump = (PRTMAC)pErrEntry->DumpData; + /* + * Initialize the whole structure with zeros in case we are suddenly short + * of data because the list is empty or has become smaller. + */ + memset(pErrEntry, 0, cbTotal); + + NdisAcquireSpinLock(&g_VBoxNetLwfGlobals.Lock); + RTListForEach(&g_VBoxNetLwfGlobals.listModules, pModuleCtx, VBOXNETLWF_MODULE, node) + { + /* The list could have been modified while we were allocating the entry, rely on cModules instead! */ + if (cModules-- == 0) + break; + *pDump++ = pModuleCtx->MacAddr; + } + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + + pErrEntry->DumpDataSize = cbDumpData; + pErrEntry->ErrorCode = uErrCode; + pErrEntry->UniqueErrorValue = uErrId; + pErrEntry->FinalStatus = uReturnedStatus; + IoWriteErrorLogEntry(pErrEntry); + } + else + { + DbgPrint("Failed to allocate error log entry (cb=%u)\n", cbTotal); + } +} + +#ifdef DEBUG + +static const char *vboxNetLwfWinStatusToText(NDIS_STATUS code) +{ + switch (code) + { + case NDIS_STATUS_MEDIA_CONNECT: return "NDIS_STATUS_MEDIA_CONNECT"; + case NDIS_STATUS_MEDIA_DISCONNECT: return "NDIS_STATUS_MEDIA_DISCONNECT"; + case NDIS_STATUS_RESET_START: return "NDIS_STATUS_RESET_START"; + case NDIS_STATUS_RESET_END: return "NDIS_STATUS_RESET_END"; + case NDIS_STATUS_MEDIA_BUSY: return "NDIS_STATUS_MEDIA_BUSY"; + case NDIS_STATUS_MEDIA_SPECIFIC_INDICATION: return "NDIS_STATUS_MEDIA_SPECIFIC_INDICATION"; + case NDIS_STATUS_LINK_SPEED_CHANGE: return "NDIS_STATUS_LINK_SPEED_CHANGE"; + case NDIS_STATUS_LINK_STATE: return "NDIS_STATUS_LINK_STATE"; + case NDIS_STATUS_PORT_STATE: return "NDIS_STATUS_PORT_STATE"; + case NDIS_STATUS_OPER_STATUS: return "NDIS_STATUS_OPER_STATUS"; + case NDIS_STATUS_NETWORK_CHANGE: return "NDIS_STATUS_NETWORK_CHANGE"; + case NDIS_STATUS_PACKET_FILTER: return "NDIS_STATUS_PACKET_FILTER"; + case NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG: return "NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG"; + case NDIS_STATUS_TASK_OFFLOAD_HARDWARE_CAPABILITIES: return "NDIS_STATUS_TASK_OFFLOAD_HARDWARE_CAPABILITIES"; + case NDIS_STATUS_OFFLOAD_ENCASPULATION_CHANGE: return "NDIS_STATUS_OFFLOAD_ENCASPULATION_CHANGE"; + case NDIS_STATUS_TCP_CONNECTION_OFFLOAD_HARDWARE_CAPABILITIES: return "NDIS_STATUS_TCP_CONNECTION_OFFLOAD_HARDWARE_CAPABILITIES"; + } + return "unknown"; +} + +static void vboxNetLwfWinDumpFilterTypes(ULONG uFlags) +{ + if (uFlags & NDIS_PACKET_TYPE_DIRECTED) Log5((" NDIS_PACKET_TYPE_DIRECTED\n")); + if (uFlags & NDIS_PACKET_TYPE_MULTICAST) Log5((" NDIS_PACKET_TYPE_MULTICAST\n")); + if (uFlags & NDIS_PACKET_TYPE_ALL_MULTICAST) Log5((" NDIS_PACKET_TYPE_ALL_MULTICAST\n")); + if (uFlags & NDIS_PACKET_TYPE_BROADCAST) Log5((" NDIS_PACKET_TYPE_BROADCAST\n")); + if (uFlags & NDIS_PACKET_TYPE_PROMISCUOUS) Log5((" NDIS_PACKET_TYPE_PROMISCUOUS\n")); + if (uFlags & NDIS_PACKET_TYPE_ALL_FUNCTIONAL) Log5((" NDIS_PACKET_TYPE_ALL_FUNCTIONAL\n")); + if (uFlags & NDIS_PACKET_TYPE_ALL_LOCAL) Log5((" NDIS_PACKET_TYPE_ALL_LOCAL\n")); + if (uFlags & NDIS_PACKET_TYPE_FUNCTIONAL) Log5((" NDIS_PACKET_TYPE_FUNCTIONAL\n")); + if (uFlags & NDIS_PACKET_TYPE_GROUP) Log5((" NDIS_PACKET_TYPE_GROUP\n")); + if (uFlags & NDIS_PACKET_TYPE_MAC_FRAME) Log5((" NDIS_PACKET_TYPE_MAC_FRAME\n")); + if (uFlags & NDIS_PACKET_TYPE_SMT) Log5((" NDIS_PACKET_TYPE_SMT\n")); + if (uFlags & NDIS_PACKET_TYPE_SOURCE_ROUTING) Log5((" NDIS_PACKET_TYPE_SOURCE_ROUTING\n")); + if (uFlags == 0) Log5((" NONE\n")); +} + +DECLINLINE(void) vboxNetLwfWinDumpEncapsulation(const char *pcszText, ULONG uEncapsulation) +{ + if (uEncapsulation == NDIS_ENCAPSULATION_NOT_SUPPORTED) + Log5(("%s not supported\n", pcszText)); + else + { + Log5(("%s", pcszText)); + if (uEncapsulation & NDIS_ENCAPSULATION_NULL) + Log5((" null")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_802_3) + Log5((" 802.3")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q) + Log5((" 802.3pq")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q_IN_OOB) + Log5((" 802.3pq(oob)")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_LLC_SNAP_ROUTED) + Log5((" LLC")); + Log5(("\n")); + } +} + +DECLINLINE(const char *) vboxNetLwfWinSetOnOffText(ULONG uOnOff) +{ + switch (uOnOff) + { + case NDIS_OFFLOAD_SET_NO_CHANGE: return "no change"; + case NDIS_OFFLOAD_SET_ON: return "on"; + case NDIS_OFFLOAD_SET_OFF: return "off"; + } + return "unknown"; +} + +DECLINLINE(const char *) vboxNetLwfWinOnOffText(ULONG uOnOff) +{ + switch (uOnOff) + { + case NDIS_OFFLOAD_NOT_SUPPORTED: return "off"; + case NDIS_OFFLOAD_SUPPORTED: return "on"; + } + return "unknown"; +} + +DECLINLINE(const char *) vboxNetLwfWinSupportedText(ULONG uSupported) +{ + switch (uSupported) + { + case NDIS_OFFLOAD_NOT_SUPPORTED: return "not supported"; + case NDIS_OFFLOAD_SUPPORTED: return "supported"; + } + return "unknown"; +} + +static void vboxNetLwfWinDumpSetOffloadSettings(PNDIS_OFFLOAD pOffloadConfig) +{ + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv4Transmit.Encapsulation); + Log5((" Checksum.IPv4Transmit.IpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpChecksum))); + Log5((" Checksum.IPv4Transmit.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.UdpChecksum))); + Log5((" Checksum.IPv4Transmit.IpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Receive.Encapsulation =", pOffloadConfig->Checksum.IPv4Receive.Encapsulation); + Log5((" Checksum.IPv4Receive.IpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpChecksum))); + Log5((" Checksum.IPv4Receive.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.UdpChecksum))); + Log5((" Checksum.IPv4Receive.IpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv6Transmit.Encapsulation); + Log5((" Checksum.IPv6Transmit.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv6Transmit.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpChecksum))); + Log5((" Checksum.IPv6Transmit.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Receive.Encapsulation =", pOffloadConfig->Checksum.IPv6Receive.Encapsulation); + Log5((" Checksum.IPv6Receive.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv6Receive.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpChecksum))); + Log5((" Checksum.IPv6Receive.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" LsoV1.IPv4.Encapsulation =", pOffloadConfig->LsoV1.IPv4.Encapsulation); + Log5((" LsoV1.IPv4.TcpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.TcpOptions))); + Log5((" LsoV1.IPv4.IpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.IpOptions))); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv4.Encapsulation =", pOffloadConfig->LsoV2.IPv4.Encapsulation); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv6.Encapsulation =", pOffloadConfig->LsoV2.IPv6.Encapsulation); + Log5((" LsoV2.IPv6.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.IpExtensionHeadersSupported))); + Log5((" LsoV2.IPv6.TcpOptionsSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.TcpOptionsSupported))); +} + +static void vboxNetLwfWinDumpOffloadSettings(PNDIS_OFFLOAD pOffloadConfig) +{ + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv4Transmit.Encapsulation); + Log5((" Checksum.IPv4Transmit.IpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpChecksum))); + Log5((" Checksum.IPv4Transmit.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.UdpChecksum))); + Log5((" Checksum.IPv4Transmit.IpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Receive.Encapsulation =", pOffloadConfig->Checksum.IPv4Receive.Encapsulation); + Log5((" Checksum.IPv4Receive.IpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpChecksum))); + Log5((" Checksum.IPv4Receive.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.UdpChecksum))); + Log5((" Checksum.IPv4Receive.IpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv6Transmit.Encapsulation); + Log5((" Checksum.IPv6Transmit.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv6Transmit.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpChecksum))); + Log5((" Checksum.IPv6Transmit.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Receive.Encapsulation =", pOffloadConfig->Checksum.IPv6Receive.Encapsulation); + Log5((" Checksum.IPv6Receive.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv6Receive.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpChecksum))); + Log5((" Checksum.IPv6Receive.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" LsoV1.IPv4.Encapsulation =", pOffloadConfig->LsoV1.IPv4.Encapsulation); + Log5((" LsoV1.IPv4.TcpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.TcpOptions))); + Log5((" LsoV1.IPv4.IpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.IpOptions))); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv4.Encapsulation =", pOffloadConfig->LsoV2.IPv4.Encapsulation); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv6.Encapsulation =", pOffloadConfig->LsoV2.IPv6.Encapsulation); + Log5((" LsoV2.IPv6.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.IpExtensionHeadersSupported))); + Log5((" LsoV2.IPv6.TcpOptionsSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.TcpOptionsSupported))); +} + +static const char *vboxNetLwfWinStateToText(uint32_t enmState) +{ + switch (enmState) + { + case LwfState_Detached: return "Detached"; + case LwfState_Attaching: return "Attaching"; + case LwfState_Paused: return "Paused"; + case LwfState_Restarting: return "Restarting"; + case LwfState_Running: return "Running"; + case LwfState_Pausing: return "Pausing"; + } + return "invalid"; +} + +static void vboxNetLwfWinDumpPackets(const char *pszMsg, PNET_BUFFER_LIST pBufLists) +{ + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + for (PNET_BUFFER pBuf = NET_BUFFER_LIST_FIRST_NB(pList); pBuf; pBuf = NET_BUFFER_NEXT_NB(pBuf)) + { + Log6(("%s packet: src=%p cb=%d offset=%d", pszMsg, pList->SourceHandle, NET_BUFFER_DATA_LENGTH(pBuf), NET_BUFFER_DATA_OFFSET(pBuf))); + for (PMDL pMdl = NET_BUFFER_FIRST_MDL(pBuf); + pMdl != NULL; + pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + Log6((" MDL: cb=%d", MmGetMdlByteCount(pMdl))); + } + Log6(("\n")); + } + } +} + +DECLINLINE(const char *) vboxNetLwfWinEthTypeStr(uint16_t uType) +{ + switch (uType) + { + case RTNET_ETHERTYPE_IPV4: return "IP"; + case RTNET_ETHERTYPE_IPV6: return "IPv6"; + case RTNET_ETHERTYPE_ARP: return "ARP"; + } + return "unknown"; +} + +#define VBOXNETLWF_PKTDMPSIZE 0x50 + +/** + * Dump a packet to debug log. + * + * @param cpPacket The packet. + * @param cb The size of the packet. + * @param cszText A string denoting direction of packet transfer. + */ +DECLINLINE(void) vboxNetLwfWinDumpPacket(PCINTNETSG pSG, const char *cszText) +{ + uint8_t bPacket[VBOXNETLWF_PKTDMPSIZE]; + + uint32_t cb = pSG->cbTotal < VBOXNETLWF_PKTDMPSIZE ? pSG->cbTotal : VBOXNETLWF_PKTDMPSIZE; + IntNetSgReadEx(pSG, 0, cb, bPacket); + + AssertReturnVoid(cb >= 14); + + uint8_t *pHdr = bPacket; + uint8_t *pEnd = bPacket + cb; + AssertReturnVoid(pEnd - pHdr >= 14); + uint16_t uEthType = RT_N2H_U16(*(uint16_t*)(pHdr+12)); + Log2(("NetLWF: %s (%d bytes), %RTmac => %RTmac, EthType=%s(0x%x)\n", + cszText, pSG->cbTotal, pHdr+6, pHdr, vboxNetLwfWinEthTypeStr(uEthType), uEthType)); + pHdr += sizeof(RTNETETHERHDR); + if (uEthType == RTNET_ETHERTYPE_VLAN) + { + AssertReturnVoid(pEnd - pHdr >= 4); + uEthType = RT_N2H_U16(*(uint16_t*)(pHdr+2)); + Log2((" + VLAN: id=%d EthType=%s(0x%x)\n", RT_N2H_U16(*(uint16_t*)(pHdr)) & 0xFFF, + vboxNetLwfWinEthTypeStr(uEthType), uEthType)); + pHdr += 2 * sizeof(uint16_t); + } + uint8_t uProto = 0xFF; + switch (uEthType) + { + case RTNET_ETHERTYPE_IPV6: + AssertReturnVoid(pEnd - pHdr >= 40); + uProto = pHdr[6]; + Log2((" + IPv6: %RTnaipv6 => %RTnaipv6\n", pHdr+8, pHdr+24)); + pHdr += 40; + break; + case RTNET_ETHERTYPE_IPV4: + AssertReturnVoid(pEnd - pHdr >= 20); + uProto = pHdr[9]; + Log2((" + IP: %RTnaipv4 => %RTnaipv4\n", *(uint32_t*)(pHdr+12), *(uint32_t*)(pHdr+16))); + pHdr += (pHdr[0] & 0xF) * 4; + break; + case RTNET_ETHERTYPE_ARP: + AssertReturnVoid(pEnd - pHdr >= 28); + AssertReturnVoid(RT_N2H_U16(*(uint16_t*)(pHdr+2)) == RTNET_ETHERTYPE_IPV4); + switch (RT_N2H_U16(*(uint16_t*)(pHdr+6))) + { + case 1: /* ARP request */ + Log2((" + ARP-REQ: who-has %RTnaipv4 tell %RTnaipv4\n", + *(uint32_t*)(pHdr+24), *(uint32_t*)(pHdr+14))); + break; + case 2: /* ARP reply */ + Log2((" + ARP-RPL: %RTnaipv4 is-at %RTmac\n", + *(uint32_t*)(pHdr+14), pHdr+8)); + break; + default: + Log2((" + ARP: unknown op %d\n", RT_N2H_U16(*(uint16_t*)(pHdr+6)))); + break; + } + break; + /* There is no default case as uProto is initialized with 0xFF */ + } + while (uProto != 0xFF) + { + switch (uProto) + { + case 0: /* IPv6 Hop-by-Hop option*/ + case 60: /* IPv6 Destination option*/ + case 43: /* IPv6 Routing option */ + case 44: /* IPv6 Fragment option */ + Log2((" + IPv6 option (%d): <not implemented>\n", uProto)); + uProto = pHdr[0]; + pHdr += pHdr[1] * 8 + 8; /* Skip to the next extension/protocol */ + break; + case 51: /* IPv6 IPsec AH */ + Log2((" + IPv6 IPsec AH: <not implemented>\n")); + uProto = pHdr[0]; + pHdr += (pHdr[1] + 2) * 4; /* Skip to the next extension/protocol */ + break; + case 50: /* IPv6 IPsec ESP */ + /* Cannot decode IPsec, fall through */ + Log2((" + IPv6 IPsec ESP: <not implemented>\n")); + uProto = 0xFF; + break; + case 59: /* No Next Header */ + Log2((" + IPv6 No Next Header\n")); + uProto = 0xFF; + break; + case 58: /* IPv6-ICMP */ + switch (pHdr[0]) + { + case 1: Log2((" + IPv6-ICMP: destination unreachable, code %d\n", pHdr[1])); break; + case 128: Log2((" + IPv6-ICMP: echo request\n")); break; + case 129: Log2((" + IPv6-ICMP: echo reply\n")); break; + default: Log2((" + IPv6-ICMP: unknown type %d, code %d\n", pHdr[0], pHdr[1])); break; + } + uProto = 0xFF; + break; + case 1: /* ICMP */ + switch (pHdr[0]) + { + case 0: Log2((" + ICMP: echo reply\n")); break; + case 8: Log2((" + ICMP: echo request\n")); break; + case 3: Log2((" + ICMP: destination unreachable, code %d\n", pHdr[1])); break; + default: Log2((" + ICMP: unknown type %d, code %d\n", pHdr[0], pHdr[1])); break; + } + uProto = 0xFF; + break; + case 6: /* TCP */ + Log2((" + TCP: src=%d dst=%d seq=%x ack=%x\n", + RT_N2H_U16(*(uint16_t*)(pHdr)), RT_N2H_U16(*(uint16_t*)(pHdr+2)), + RT_N2H_U32(*(uint32_t*)(pHdr+4)), RT_N2H_U32(*(uint32_t*)(pHdr+8)))); + uProto = 0xFF; + break; + case 17: /* UDP */ + Log2((" + UDP: src=%d dst=%d\n", + RT_N2H_U16(*(uint16_t*)(pHdr)), RT_N2H_U16(*(uint16_t*)(pHdr+2)))); + uProto = 0xFF; + break; + default: + Log2((" + Unknown: proto=0x%x\n", uProto)); + uProto = 0xFF; + break; + } + } + Log3(("%.*Rhxd\n", cb, bPacket)); +} + +#else /* !DEBUG */ +# define vboxNetLwfWinDumpFilterTypes(uFlags) do { } while (0) +# define vboxNetLwfWinDumpOffloadSettings(p) do { } while (0) +# define vboxNetLwfWinDumpSetOffloadSettings(p) do { } while (0) +# define vboxNetLwfWinDumpPackets(m,l) do { } while (0) +# define vboxNetLwfWinDumpPacket(p,t) do { } while (0) +#endif /* !DEBUG */ + +DECLINLINE(bool) vboxNetLwfWinChangeState(PVBOXNETLWF_MODULE pModuleCtx, uint32_t enmNew, uint32_t enmOld = LwfState_32BitHack) +{ + AssertReturn(pModuleCtx, false); + + bool fSuccess = true; + if (enmOld != LwfState_32BitHack) + { + fSuccess = ASMAtomicCmpXchgU32(&pModuleCtx->enmState, enmNew, enmOld); + if (fSuccess) + Log(("vboxNetLwfWinChangeState: state change %s -> %s\n", + vboxNetLwfWinStateToText(enmOld), + vboxNetLwfWinStateToText(enmNew))); + else + Log(("ERROR! vboxNetLwfWinChangeState: failed state change %s (actual=%s) -> %s\n", + vboxNetLwfWinStateToText(enmOld), + vboxNetLwfWinStateToText(ASMAtomicReadU32(&pModuleCtx->enmState)), + vboxNetLwfWinStateToText(enmNew))); + Assert(fSuccess); + } + else + { + uint32_t enmPrevState = ASMAtomicXchgU32(&pModuleCtx->enmState, enmNew); + Log(("vboxNetLwfWinChangeState: state change %s -> %s\n", + vboxNetLwfWinStateToText(enmPrevState), + vboxNetLwfWinStateToText(enmNew))); + NOREF(enmPrevState); + } + return fSuccess; +} + +DECLINLINE(void) vboxNetLwfWinInitOidRequest(PVBOXNETLWF_OIDREQ pRequest) +{ + NdisZeroMemory(pRequest, sizeof(VBOXNETLWF_OIDREQ)); + + NdisInitializeEvent(&pRequest->Event); + + pRequest->Request.Header.Type = NDIS_OBJECT_TYPE_OID_REQUEST; + pRequest->Request.Header.Revision = NDIS_OID_REQUEST_REVISION_1; + pRequest->Request.Header.Size = NDIS_SIZEOF_OID_REQUEST_REVISION_1; + + pRequest->Request.RequestId = (PVOID)VBOXNETLWF_REQ_ID; +} + +static NDIS_STATUS vboxNetLwfWinSyncOidRequest(PVBOXNETLWF_MODULE pModuleCtx, PVBOXNETLWF_OIDREQ pRequest) +{ + NDIS_STATUS Status = NdisFOidRequest(pModuleCtx->hFilter, &pRequest->Request); + if (Status == NDIS_STATUS_PENDING) + { + NdisWaitEvent(&pRequest->Event, 0); + Status = pRequest->Status; + } + return Status; +} + +DECLINLINE(void) vboxNetLwfWinCopyOidRequestResults(PNDIS_OID_REQUEST pFrom, PNDIS_OID_REQUEST pTo) +{ + switch (pFrom->RequestType) + { + case NdisRequestSetInformation: + pTo->DATA.SET_INFORMATION.BytesRead = pFrom->DATA.SET_INFORMATION.BytesRead; + pTo->DATA.SET_INFORMATION.BytesNeeded = pFrom->DATA.SET_INFORMATION.BytesNeeded; + break; + case NdisRequestMethod: + pTo->DATA.METHOD_INFORMATION.OutputBufferLength = pFrom->DATA.METHOD_INFORMATION.OutputBufferLength; + pTo->DATA.METHOD_INFORMATION.BytesWritten = pFrom->DATA.METHOD_INFORMATION.BytesWritten; + pTo->DATA.METHOD_INFORMATION.BytesRead = pFrom->DATA.METHOD_INFORMATION.BytesRead; + pTo->DATA.METHOD_INFORMATION.BytesNeeded = pFrom->DATA.METHOD_INFORMATION.BytesNeeded; + break; + case NdisRequestQueryInformation: + case NdisRequestQueryStatistics: + default: + pTo->DATA.QUERY_INFORMATION.BytesWritten = pFrom->DATA.QUERY_INFORMATION.BytesWritten; + pTo->DATA.QUERY_INFORMATION.BytesNeeded = pFrom->DATA.QUERY_INFORMATION.BytesNeeded; + } +} + +void inline vboxNetLwfWinOverridePacketFiltersUp(PVBOXNETLWF_MODULE pModuleCtx, ULONG *pFilters) +{ + if (ASMAtomicReadBool(&pModuleCtx->fActive) && !ASMAtomicReadBool(&pModuleCtx->fHostPromisc)) + *pFilters &= ~NDIS_PACKET_TYPE_PROMISCUOUS; +} + +NDIS_STATUS vboxNetLwfWinOidRequest(IN NDIS_HANDLE hModuleCtx, + IN PNDIS_OID_REQUEST pOidRequest) +{ + LogFlow(("==>vboxNetLwfWinOidRequest: module=%p\n", hModuleCtx)); + vboxNetCmnWinDumpOidRequest(__FUNCTION__, pOidRequest); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + PNDIS_OID_REQUEST pClone = NULL; + NDIS_STATUS Status = NdisAllocateCloneOidRequest(pModuleCtx->hFilter, + pOidRequest, + VBOXNETLWF_MEM_TAG, + &pClone); + if (Status == NDIS_STATUS_SUCCESS) + { + /* Save the pointer to the original */ + *((PNDIS_OID_REQUEST*)(pClone->SourceReserved)) = pOidRequest; + + pClone->RequestId = pOidRequest->RequestId; + /* We are not supposed to get another request until we are through with the one we "postponed" */ + PNDIS_OID_REQUEST pPrev = ASMAtomicXchgPtrT(&pModuleCtx->pPendingRequest, pClone, PNDIS_OID_REQUEST); + Assert(pPrev == NULL); + pModuleCtx->pPendingRequest = pClone; + if (pOidRequest->RequestType == NdisRequestSetInformation + && pOidRequest->DATA.SET_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER) + { + ASMAtomicWriteBool(&pModuleCtx->fHostPromisc, !!(*(ULONG*)pOidRequest->DATA.SET_INFORMATION.InformationBuffer & NDIS_PACKET_TYPE_PROMISCUOUS)); + Log(("vboxNetLwfWinOidRequest: host wanted to set packet filter value to:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.SET_INFORMATION.InformationBuffer); + /* Keep adapter in promisc mode as long as we are active. */ + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + *(ULONG*)pClone->DATA.SET_INFORMATION.InformationBuffer |= NDIS_PACKET_TYPE_PROMISCUOUS; + Log5(("vboxNetLwfWinOidRequest: pass the following packet filters to miniport:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.SET_INFORMATION.InformationBuffer); + } + if (pOidRequest->RequestType == NdisRequestSetInformation + && pOidRequest->DATA.SET_INFORMATION.Oid == OID_TCP_OFFLOAD_CURRENT_CONFIG) + { + Log5(("vboxNetLwfWinOidRequest: offloading set to:\n")); + vboxNetLwfWinDumpSetOffloadSettings((PNDIS_OFFLOAD)pOidRequest->DATA.SET_INFORMATION.InformationBuffer); + } + + /* Forward the clone to underlying filters/miniport */ + Status = NdisFOidRequest(pModuleCtx->hFilter, pClone); + if (Status != NDIS_STATUS_PENDING) + { + /* Synchronous completion */ + pPrev = ASMAtomicXchgPtrT(&pModuleCtx->pPendingRequest, NULL, PNDIS_OID_REQUEST); + Assert(pPrev == pClone); + Log5(("vboxNetLwfWinOidRequest: got the following packet filters from miniport:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.QUERY_INFORMATION.InformationBuffer); + /* + * The host does not expect the adapter to be in promisc mode, + * unless it enabled the mode. Let's not disillusion it. + */ + if ( pOidRequest->RequestType == NdisRequestQueryInformation + && pOidRequest->DATA.QUERY_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER) + vboxNetLwfWinOverridePacketFiltersUp(pModuleCtx, (ULONG*)pOidRequest->DATA.QUERY_INFORMATION.InformationBuffer); + Log5(("vboxNetLwfWinOidRequest: reporting to the host the following packet filters:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.QUERY_INFORMATION.InformationBuffer); + vboxNetLwfWinCopyOidRequestResults(pClone, pOidRequest); + NdisFreeCloneOidRequest(pModuleCtx->hFilter, pClone); + } + /* In case of async completion we do the rest in vboxNetLwfWinOidRequestComplete() */ + } + else + { + LogError(("vboxNetLwfWinOidRequest: NdisAllocateCloneOidRequest failed with 0x%x\n", Status)); + } + LogFlow(("<==vboxNetLwfWinOidRequest: Status=0x%x\n", Status)); + return Status; +} + +VOID vboxNetLwfWinOidRequestComplete(IN NDIS_HANDLE hModuleCtx, + IN PNDIS_OID_REQUEST pRequest, + IN NDIS_STATUS Status) +{ + LogFlow(("==>vboxNetLwfWinOidRequestComplete: module=%p req=%p status=0x%x\n", hModuleCtx, pRequest, Status)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + PNDIS_OID_REQUEST pOriginal = *((PNDIS_OID_REQUEST*)(pRequest->SourceReserved)); + if (pOriginal) + { + /* NDIS is supposed to serialize requests */ + PNDIS_OID_REQUEST pPrev = ASMAtomicXchgPtrT(&pModuleCtx->pPendingRequest, NULL, PNDIS_OID_REQUEST); + Assert(pPrev == pRequest); NOREF(pPrev); + + Log5(("vboxNetLwfWinOidRequestComplete: completed rq type=%d oid=%x\n", pRequest->RequestType, pRequest->DATA.QUERY_INFORMATION.Oid)); + vboxNetLwfWinCopyOidRequestResults(pRequest, pOriginal); + if ( pRequest->RequestType == NdisRequestQueryInformation + && pRequest->DATA.QUERY_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER) + { + Log5(("vboxNetLwfWinOidRequestComplete: underlying miniport reports its packet filters:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + vboxNetLwfWinOverridePacketFiltersUp(pModuleCtx, (ULONG*)pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + Log5(("vboxNetLwfWinOidRequestComplete: reporting the following packet filters to upper protocol:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + } + NdisFreeCloneOidRequest(pModuleCtx->hFilter, pRequest); + NdisFOidRequestComplete(pModuleCtx->hFilter, pOriginal, Status); + } + else + { + /* This is not a clone, we originated it */ + Log(("vboxNetLwfWinOidRequestComplete: locally originated request (%p) completed, status=0x%x\n", pRequest, Status)); + PVBOXNETLWF_OIDREQ pRqWrapper = RT_FROM_MEMBER(pRequest, VBOXNETLWF_OIDREQ, Request); + pRqWrapper->Status = Status; + NdisSetEvent(&pRqWrapper->Event); + } + LogFlow(("<==vboxNetLwfWinOidRequestComplete\n")); +} + + +static bool vboxNetLwfWinIsPromiscuous(PVBOXNETLWF_MODULE pModuleCtx) +{ + return ASMAtomicReadBool(&pModuleCtx->fHostPromisc); +} + +#if 0 +static NDIS_STATUS vboxNetLwfWinGetPacketFilter(PVBOXNETLWF_MODULE pModuleCtx) +{ + LogFlow(("==>vboxNetLwfWinGetPacketFilter: module=%p\n", pModuleCtx)); + VBOXNETLWF_OIDREQ Rq; + vboxNetLwfWinInitOidRequest(&Rq); + Rq.Request.RequestType = NdisRequestQueryInformation; + Rq.Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + Rq.Request.DATA.QUERY_INFORMATION.InformationBuffer = &pModuleCtx->uPacketFilter; + Rq.Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(pModuleCtx->uPacketFilter); + NDIS_STATUS Status = vboxNetLwfWinSyncOidRequest(pModuleCtx, &Rq); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinGetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed with 0x%x\n", Status)); + return FALSE; + } + if (Rq.Request.DATA.QUERY_INFORMATION.BytesWritten != sizeof(pModuleCtx->uPacketFilter)) + { + LogError(("vboxNetLwfWinGetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed to write neccessary amount (%d bytes), actually written %d bytes\n", sizeof(pModuleCtx->uPacketFilter), Rq.Request.DATA.QUERY_INFORMATION.BytesWritten)); + } + + Log5(("vboxNetLwfWinGetPacketFilter: OID_GEN_CURRENT_PACKET_FILTER query returned the following filters:\n")); + vboxNetLwfWinDumpFilterTypes(pModuleCtx->uPacketFilter); + + LogFlow(("<==vboxNetLwfWinGetPacketFilter: status=0x%x\n", Status)); + return Status; +} +#endif + +static NDIS_STATUS vboxNetLwfWinSetPacketFilter(PVBOXNETLWF_MODULE pModuleCtx, bool fPromisc) +{ + LogFlow(("==>vboxNetLwfWinSetPacketFilter: module=%p %s\n", pModuleCtx, fPromisc ? "promiscuous" : "normal")); + ULONG uFilter = 0; + VBOXNETLWF_OIDREQ Rq; + vboxNetLwfWinInitOidRequest(&Rq); + Rq.Request.RequestType = NdisRequestQueryInformation; + Rq.Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + Rq.Request.DATA.QUERY_INFORMATION.InformationBuffer = &uFilter; + Rq.Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(uFilter); + NDIS_STATUS Status = vboxNetLwfWinSyncOidRequest(pModuleCtx, &Rq); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinSetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed with 0x%x\n", Status)); + return Status; + } + if (Rq.Request.DATA.QUERY_INFORMATION.BytesWritten != sizeof(uFilter)) + { + LogError(("vboxNetLwfWinSetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed to write neccessary amount (%d bytes), actually written %d bytes\n", sizeof(uFilter), Rq.Request.DATA.QUERY_INFORMATION.BytesWritten)); + return NDIS_STATUS_FAILURE; + } + + Log5(("vboxNetLwfWinSetPacketFilter: OID_GEN_CURRENT_PACKET_FILTER query returned the following filters:\n")); + vboxNetLwfWinDumpFilterTypes(uFilter); + + if (fPromisc) + { + /* If we about to go promiscuous, save the state before we change it. */ + ASMAtomicWriteBool(&pModuleCtx->fHostPromisc, !!(uFilter & NDIS_PACKET_TYPE_PROMISCUOUS)); + uFilter |= NDIS_PACKET_TYPE_PROMISCUOUS; + } + else + { + /* Reset promisc only if it was not enabled before we had changed it. */ + if (!ASMAtomicReadBool(&pModuleCtx->fHostPromisc)) + uFilter &= ~NDIS_PACKET_TYPE_PROMISCUOUS; + } + + Log5(("vboxNetLwfWinSetPacketFilter: OID_GEN_CURRENT_PACKET_FILTER about to set the following filters:\n")); + vboxNetLwfWinDumpFilterTypes(uFilter); + + NdisResetEvent(&Rq.Event); /* need to reset as it has been set by query op */ + Rq.Request.RequestType = NdisRequestSetInformation; + Rq.Request.DATA.SET_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + Rq.Request.DATA.SET_INFORMATION.InformationBuffer = &uFilter; + Rq.Request.DATA.SET_INFORMATION.InformationBufferLength = sizeof(uFilter); + Status = vboxNetLwfWinSyncOidRequest(pModuleCtx, &Rq); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinSetPacketFilter: vboxNetLwfWinSyncOidRequest(set, OID_GEN_CURRENT_PACKET_FILTER, vvv below vvv) failed with 0x%x\n", Status)); + vboxNetLwfWinDumpFilterTypes(uFilter); + } + LogFlow(("<==vboxNetLwfWinSetPacketFilter: status=0x%x\n", Status)); + return Status; +} + + +static NTSTATUS vboxNetLwfWinDevDispatch(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) +{ + RT_NOREF1(pDevObj); + PIO_STACK_LOCATION pIrpSl = IoGetCurrentIrpStackLocation(pIrp);; + NTSTATUS Status = STATUS_SUCCESS; + + switch (pIrpSl->MajorFunction) + { + case IRP_MJ_DEVICE_CONTROL: + Status = STATUS_NOT_SUPPORTED; + break; + case IRP_MJ_CREATE: + case IRP_MJ_CLEANUP: + case IRP_MJ_CLOSE: + break; + default: + AssertFailed(); + break; + } + + pIrp->IoStatus.Status = Status; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return Status; +} + +/** @todo So far we had no use for device, should we even bother to create it? */ +static NDIS_STATUS vboxNetLwfWinDevCreate(PVBOXNETLWFGLOBALS pGlobals) +{ + NDIS_STRING DevName, LinkName; + PDRIVER_DISPATCH aMajorFunctions[IRP_MJ_MAXIMUM_FUNCTION+1]; + NdisInitUnicodeString(&DevName, VBOXNETLWF_NAME_DEVICE); + NdisInitUnicodeString(&LinkName, VBOXNETLWF_NAME_LINK); + + Assert(!pGlobals->hDevice); + Assert(!pGlobals->pDevObj); + NdisZeroMemory(aMajorFunctions, sizeof (aMajorFunctions)); + aMajorFunctions[IRP_MJ_CREATE] = vboxNetLwfWinDevDispatch; + aMajorFunctions[IRP_MJ_CLEANUP] = vboxNetLwfWinDevDispatch; + aMajorFunctions[IRP_MJ_CLOSE] = vboxNetLwfWinDevDispatch; + aMajorFunctions[IRP_MJ_DEVICE_CONTROL] = vboxNetLwfWinDevDispatch; + + NDIS_DEVICE_OBJECT_ATTRIBUTES DeviceAttributes; + NdisZeroMemory(&DeviceAttributes, sizeof(DeviceAttributes)); + DeviceAttributes.Header.Type = NDIS_OBJECT_TYPE_DEVICE_OBJECT_ATTRIBUTES; + DeviceAttributes.Header.Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1; + DeviceAttributes.Header.Size = sizeof(DeviceAttributes); + DeviceAttributes.DeviceName = &DevName; + DeviceAttributes.SymbolicName = &LinkName; + DeviceAttributes.MajorFunctions = aMajorFunctions; + //DeviceAttributes.ExtensionSize = sizeof(FILTER_DEVICE_EXTENSION); + + NDIS_STATUS Status = NdisRegisterDeviceEx(pGlobals->hFilterDriver, + &DeviceAttributes, + &pGlobals->pDevObj, + &pGlobals->hDevice); + Log(("vboxNetLwfWinDevCreate: NdisRegisterDeviceEx returned 0x%x\n", Status)); + Assert(Status == NDIS_STATUS_SUCCESS); +#if 0 + if (Status == NDIS_STATUS_SUCCESS) + { + PFILTER_DEVICE_EXTENSION pExtension; + pExtension = NdisGetDeviceReservedExtension(pGlobals->pDevObj); + pExtension->Signature = VBOXNETLWF_MEM_TAG; + pExtension->Handle = pGlobals->hFilterDriver; + } +#endif + return Status; +} + +static void vboxNetLwfWinDevDestroy(PVBOXNETLWFGLOBALS pGlobals) +{ + Assert(pGlobals->hDevice); + Assert(pGlobals->pDevObj); + NdisDeregisterDeviceEx(pGlobals->hDevice); + pGlobals->hDevice = NULL; + pGlobals->pDevObj = NULL; +} + +static void vboxNetLwfWinDisableOffloading(PNDIS_OFFLOAD pOffloadConfig) +{ + pOffloadConfig->Checksum.IPv4Transmit.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.IpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.TcpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.TcpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.IpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.TcpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.TcpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV1.IPv4.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv4.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv6.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv6.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv6.TcpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; +} + +static void vboxNetLwfWinUpdateSavedOffloadConfig(PVBOXNETLWF_MODULE pModuleCtx, PNDIS_OFFLOAD pOffload) +{ + if (pModuleCtx->cbOffloadConfig < pOffload->Header.Size) + { + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 10); + return; + } + + NdisMoveMemory(pModuleCtx->pSavedOffloadConfig, pOffload, pOffload->Header.Size); + NdisMoveMemory(pModuleCtx->pDisabledOffloadConfig, pOffload, pOffload->Header.Size); + vboxNetLwfWinDisableOffloading(pModuleCtx->pDisabledOffloadConfig); + pModuleCtx->fOffloadConfigValid = true; +} + +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS +static void vboxNetLwfWinFreePools(PVBOXNETLWF_MODULE pModuleCtx, int cPools) +{ + for (int i = 0; i < cPools; ++i) + { + if (pModuleCtx->hPool[i]) + { + NdisFreeNetBufferListPool(pModuleCtx->hPool[i]); + Log4(("vboxNetLwfWinFreePools: freed NBL+NB pool 0x%p\n", pModuleCtx->hPool[i])); + } + } +} +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ + + +static void vboxNetLwfWinFreeModuleResources(PVBOXNETLWF_MODULE pModuleCtx) +{ +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS + vboxNetLwfWinFreePools(pModuleCtx, RT_ELEMENTS(g_cbPool)); +#else /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + if (pModuleCtx->hPool) + { + NdisFreeNetBufferListPool(pModuleCtx->hPool); + Log4(("vboxNetLwfWinFreeModuleResources: freed NBL+NB pool 0x%p\n", pModuleCtx->hPool)); + } +#endif /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + if (pModuleCtx->pDisabledOffloadConfig) + NdisFreeMemory(pModuleCtx->pDisabledOffloadConfig, 0, 0); + if (pModuleCtx->pSavedOffloadConfig) + NdisFreeMemory(pModuleCtx->pSavedOffloadConfig, 0, 0); + if (pModuleCtx->hWorkItem) + NdisFreeIoWorkItem(pModuleCtx->hWorkItem); + NdisFreeMemory(pModuleCtx, 0, 0); +} + + +DECLARE_GLOBAL_CONST_UNICODE_STRING(g_strHostOnlyMiniportName, L"VirtualBox Host-Only"); + +static NDIS_STATUS vboxNetLwfWinAttach(IN NDIS_HANDLE hFilter, IN NDIS_HANDLE hDriverCtx, + IN PNDIS_FILTER_ATTACH_PARAMETERS pParameters) +{ + LogFlow(("==>vboxNetLwfWinAttach: filter=%p\n", hFilter)); + + PVBOXNETLWFGLOBALS pGlobals = (PVBOXNETLWFGLOBALS)hDriverCtx; + if (!pGlobals) + { + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_FAILURE, 1); + return NDIS_STATUS_FAILURE; + } + + /* + * We need a copy of NDIS_STRING structure as we are going to modify length + * of the base miniport instance name since RTL does not support comparing + * first n characters of two strings. We check if miniport names start with + * "Virtual Host-Only" to detect host-only adapters. It is a waste of resources + * to bind our filter to host-only adapters since they now operate independently. + */ + NDIS_STRING strTruncatedInstanceName = *pParameters->BaseMiniportInstanceName; /* Do not copy data, only the structure itself */ + strTruncatedInstanceName.Length = g_strHostOnlyMiniportName.Length; /* Truncate instance name */ + if (RtlEqualUnicodeString(&strTruncatedInstanceName, &g_strHostOnlyMiniportName, TRUE /* Case insensitive */)) + { + DbgPrint("vboxNetLwfWinAttach: won't attach to %wZ\n", pParameters->BaseMiniportInstanceName); + return NDIS_STATUS_FAILURE; + } + + ANSI_STRING strMiniportName; + /* We use the miniport name to associate this filter module with the netflt instance */ + NTSTATUS rc = RtlUnicodeStringToAnsiString(&strMiniportName, + pParameters->BaseMiniportName, + TRUE); + if (rc != STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinAttach: RtlUnicodeStringToAnsiString(%ls) failed with 0x%x\n", + pParameters->BaseMiniportName, rc)); + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_FAILURE, 2); + return NDIS_STATUS_FAILURE; + } + DbgPrint("vboxNetLwfWinAttach: friendly name=%wZ\n", pParameters->BaseMiniportInstanceName); + DbgPrint("vboxNetLwfWinAttach: name=%Z\n", &strMiniportName); + + UINT cbModuleWithNameExtra = sizeof(VBOXNETLWF_MODULE) + strMiniportName.Length; + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)NdisAllocateMemoryWithTagPriority(hFilter, + cbModuleWithNameExtra, + VBOXNETLWF_MEM_TAG, + LowPoolPriority); + if (!pModuleCtx) + { + LogError(("vboxNetLwfWinAttach: Failed to allocate module context for %ls\n", pParameters->BaseMiniportName)); + RtlFreeAnsiString(&strMiniportName); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 3); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetLwfWinAttach: allocated module context 0x%p\n", pModuleCtx)); + + NdisZeroMemory(pModuleCtx, cbModuleWithNameExtra); + NdisMoveMemory(pModuleCtx->szMiniportName, strMiniportName.Buffer, strMiniportName.Length); + RtlFreeAnsiString(&strMiniportName); + + pModuleCtx->hWorkItem = NdisAllocateIoWorkItem(g_VBoxNetLwfGlobals.hFilterDriver); + if (!pModuleCtx->hWorkItem) + { + LogError(("vboxNetLwfWinAttach: Failed to allocate work item for %ls\n", + pParameters->BaseMiniportName)); + NdisFreeMemory(pModuleCtx, 0, 0); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 4); + return NDIS_STATUS_RESOURCES; + } + + Assert(pParameters->MacAddressLength == sizeof(RTMAC)); + NdisMoveMemory(&pModuleCtx->MacAddr, pParameters->CurrentMacAddress, RT_MIN(sizeof(RTMAC), pParameters->MacAddressLength)); + + pModuleCtx->cbOffloadConfig = sizeof(NDIS_OFFLOAD) * 2; /* Best guess to accomodate future expansion. */ + /* Get the exact size, if possible. */ + if (pParameters->DefaultOffloadConfiguration) + pModuleCtx->cbOffloadConfig = pParameters->DefaultOffloadConfiguration->Header.Size; + else + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 8); + + pModuleCtx->pSavedOffloadConfig = + (PNDIS_OFFLOAD)NdisAllocateMemoryWithTagPriority(hFilter, pModuleCtx->cbOffloadConfig, + VBOXNETLWF_MEM_TAG, LowPoolPriority); + pModuleCtx->pDisabledOffloadConfig = + (PNDIS_OFFLOAD)NdisAllocateMemoryWithTagPriority(hFilter, pModuleCtx->cbOffloadConfig, + VBOXNETLWF_MEM_TAG, LowPoolPriority); + if (!pModuleCtx->pSavedOffloadConfig || !pModuleCtx->pDisabledOffloadConfig) + { + LogError(("vboxNetLwfWinAttach: Failed to allocate offload config buffers for %ls\n", + pParameters->BaseMiniportName)); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 9); + return NDIS_STATUS_RESOURCES; + } + + if (pParameters->DefaultOffloadConfiguration) + vboxNetLwfWinUpdateSavedOffloadConfig(pModuleCtx, pParameters->DefaultOffloadConfiguration); + else + { + NdisZeroMemory(pModuleCtx->pDisabledOffloadConfig, pModuleCtx->cbOffloadConfig); + pModuleCtx->pDisabledOffloadConfig->Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + pModuleCtx->pDisabledOffloadConfig->Header.Revision = NDIS_OFFLOAD_REVISION_1; + pModuleCtx->pDisabledOffloadConfig->Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + } + + pModuleCtx->pGlobals = pGlobals; + pModuleCtx->hFilter = hFilter; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Attaching); + /* Initialize transmission mutex and events */ + NDIS_INIT_MUTEX(&pModuleCtx->InTransmit); +#ifdef VBOXNETLWF_SYNC_SEND + KeInitializeEvent(&pModuleCtx->EventWire, SynchronizationEvent, FALSE); + KeInitializeEvent(&pModuleCtx->EventHost, SynchronizationEvent, FALSE); +#else /* !VBOXNETLWF_SYNC_SEND */ + NdisInitializeEvent(&pModuleCtx->EventSendComplete); + pModuleCtx->cPendingBuffers = 0; +#endif /* !VBOXNETLWF_SYNC_SEND */ + +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS + for (int i = 0; i < RT_ELEMENTS(g_cbPool); ++i) + { + /* Allocate buffer pools */ + NET_BUFFER_LIST_POOL_PARAMETERS PoolParams; + NdisZeroMemory(&PoolParams, sizeof(PoolParams)); + PoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + PoolParams.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + PoolParams.Header.Size = sizeof(PoolParams); + PoolParams.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT; + PoolParams.fAllocateNetBuffer = TRUE; + PoolParams.ContextSize = 0; /** @todo Do we need to consider underlying drivers? I think not. */ + PoolParams.PoolTag = VBOXNETLWF_MEM_TAG; + PoolParams.DataSize = g_cbPool[i]; + pModuleCtx->hPool[i] = NdisAllocateNetBufferListPool(hFilter, &PoolParams); + if (!pModuleCtx->hPool[i]) + { + LogError(("vboxNetLwfWinAttach: NdisAllocateNetBufferListPool failed\n")); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 7); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetLwfWinAttach: allocated NBL+NB pool (data size=%u) 0x%p\n", + PoolParams.DataSize, pModuleCtx->hPool[i])); + } +#else /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + /* Allocate buffer pools */ + NET_BUFFER_LIST_POOL_PARAMETERS PoolParams; + NdisZeroMemory(&PoolParams, sizeof(PoolParams)); + PoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + PoolParams.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + PoolParams.Header.Size = sizeof(PoolParams); + PoolParams.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT; + PoolParams.fAllocateNetBuffer = TRUE; + PoolParams.ContextSize = 0; /** @todo Do we need to consider underlying drivers? I think not. */ + PoolParams.PoolTag = VBOXNETLWF_MEM_TAG; + pModuleCtx->hPool = NdisAllocateNetBufferListPool(hFilter, &PoolParams); + if (!pModuleCtx->hPool) + { + LogError(("vboxNetLwfWinAttach: NdisAllocateNetBufferListPool failed\n")); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 7); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetLwfWinAttach: allocated NBL+NB pool 0x%p\n", pModuleCtx->hPool)); +#endif /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + + NDIS_FILTER_ATTRIBUTES Attributes; + NdisZeroMemory(&Attributes, sizeof(Attributes)); + Attributes.Header.Revision = NDIS_FILTER_ATTRIBUTES_REVISION_1; + Attributes.Header.Size = sizeof(Attributes); + Attributes.Header.Type = NDIS_OBJECT_TYPE_FILTER_ATTRIBUTES; + Attributes.Flags = 0; + NDIS_STATUS Status = NdisFSetAttributes(hFilter, pModuleCtx, &Attributes); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinAttach: NdisFSetAttributes failed with 0x%x\n", Status)); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_RESOURCES, 5); + return NDIS_STATUS_RESOURCES; + } + /* Insert into module chain */ + NdisAcquireSpinLock(&pGlobals->Lock); + RTListPrepend(&pGlobals->listModules, &pModuleCtx->node); + NdisReleaseSpinLock(&pGlobals->Lock); + + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Paused); + + /// @todo Somehow the packet filter is 0 at this point: Status = vboxNetLwfWinGetPacketFilter(pModuleCtx); + /// @todo We actually update it later in status handler, perhaps we should not do anything here. + + LogFlow(("<==vboxNetLwfWinAttach: Status = 0x%x\n", Status)); + return Status; +} + +static VOID vboxNetLwfWinDetach(IN NDIS_HANDLE hModuleCtx) +{ + LogFlow(("==>vboxNetLwfWinDetach: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Detached, LwfState_Paused); + + /* Remove from module chain */ + NdisAcquireSpinLock(&pModuleCtx->pGlobals->Lock); + RTListNodeRemove(&pModuleCtx->node); + NdisReleaseSpinLock(&pModuleCtx->pGlobals->Lock); + + PVBOXNETFLTINS pNetFltIns = pModuleCtx->pNetFlt; /// @todo Atomic? + if (pNetFltIns && vboxNetFltTryRetainBusyNotDisconnected(pNetFltIns)) + { + /* + * Set hModuleCtx to null now in order to prevent filter restart, + * OID requests and other stuff associated with NetFlt deactivation. + */ + pNetFltIns->u.s.WinIf.hModuleCtx = NULL; + /* Notify NetFlt that we are going down */ + pNetFltIns->pSwitchPort->pfnDisconnect(pNetFltIns->pSwitchPort, &pNetFltIns->MyPort, vboxNetFltPortReleaseBusy); + /* We do not 'release' netflt instance since it has been done by pfnDisconnect */ + } + pModuleCtx->pNetFlt = NULL; + + /* + * We have to make sure that all NET_BUFFER_LIST structures have been freed by now, but + * it does not require us to do anything here since it has already been taken care of + * by vboxNetLwfWinPause(). + */ + vboxNetLwfWinFreeModuleResources(pModuleCtx); + Log4(("vboxNetLwfWinDetach: freed module context 0x%p\n", pModuleCtx)); + LogFlow(("<==vboxNetLwfWinDetach\n")); +} + + +static NDIS_STATUS vboxNetLwfWinPause(IN NDIS_HANDLE hModuleCtx, IN PNDIS_FILTER_PAUSE_PARAMETERS pParameters) +{ + RT_NOREF1(pParameters); + LogFlow(("==>vboxNetLwfWinPause: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Pausing, LwfState_Running); + /* Wait for pending send/indication operations to complete. */ + NDIS_WAIT_FOR_MUTEX(&pModuleCtx->InTransmit); +#ifndef VBOXNETLWF_SYNC_SEND + NdisWaitEvent(&pModuleCtx->EventSendComplete, 1000 /* ms */); +#endif /* !VBOXNETLWF_SYNC_SEND */ + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Paused, LwfState_Pausing); + NDIS_RELEASE_MUTEX(&pModuleCtx->InTransmit); + LogFlow(("<==vboxNetLwfWinPause\n")); + return NDIS_STATUS_SUCCESS; /* Failure is not an option */ +} + + +static void vboxNetLwfWinIndicateOffload(PVBOXNETLWF_MODULE pModuleCtx, PNDIS_OFFLOAD pOffload) +{ + Log5(("vboxNetLwfWinIndicateOffload: offload config changed to:\n")); + vboxNetLwfWinDumpOffloadSettings(pOffload); + NDIS_STATUS_INDICATION OffloadingIndication; + NdisZeroMemory(&OffloadingIndication, sizeof(OffloadingIndication)); + OffloadingIndication.Header.Type = NDIS_OBJECT_TYPE_STATUS_INDICATION; + OffloadingIndication.Header.Revision = NDIS_STATUS_INDICATION_REVISION_1; + OffloadingIndication.Header.Size = NDIS_SIZEOF_STATUS_INDICATION_REVISION_1; + OffloadingIndication.SourceHandle = pModuleCtx->hFilter; + OffloadingIndication.StatusCode = NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG; + OffloadingIndication.StatusBuffer = pOffload; + OffloadingIndication.StatusBufferSize = pOffload->Header.Size; + NdisFIndicateStatus(pModuleCtx->hFilter, &OffloadingIndication); +} + + +static NDIS_STATUS vboxNetLwfWinRestart(IN NDIS_HANDLE hModuleCtx, IN PNDIS_FILTER_RESTART_PARAMETERS pParameters) +{ + RT_NOREF1(pParameters); + LogFlow(("==>vboxNetLwfWinRestart: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Restarting, LwfState_Paused); + + /* By default the packets that go between VMs and wire are invisible to the host. */ + pModuleCtx->fPassVmTrafficToHost = false; + + NDIS_HANDLE hConfig; + NDIS_CONFIGURATION_OBJECT cfgObj; + cfgObj.Header.Type = NDIS_OBJECT_TYPE_CONFIGURATION_OBJECT; + cfgObj.Header.Revision = NDIS_CONFIGURATION_OBJECT_REVISION_1; + cfgObj.Header.Size = sizeof(NDIS_CONFIGURATION_OBJECT); + cfgObj.NdisHandle = g_VBoxNetLwfGlobals.hFilterDriver; + + NDIS_STATUS Status = NdisOpenConfigurationEx(&cfgObj, &hConfig); + if (Status == NDIS_STATUS_SUCCESS) + { + NDIS_STRING strCfgParam = NDIS_STRING_CONST("PassVmTrafficToHost"); + PNDIS_CONFIGURATION_PARAMETER pParam = NULL; + NdisReadConfiguration(&Status, &pParam, hConfig, &strCfgParam, NdisParameterInteger); + if (Status != NDIS_STATUS_SUCCESS) + { + Log(("vboxNetLwfWinRestart: Failed to read 'PassVmTrafficToHost' from the registry.\n")); + } + else if (pParam->ParameterData.IntegerData != 0) + { + Log(("vboxNetLwfWinRestart: Allowing the host to see VM traffic in promisc mode by user request.\n")); + pModuleCtx->fPassVmTrafficToHost = true; + } + NdisCloseConfiguration(hConfig); + } + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Running, LwfState_Restarting); + LogFlow(("<==vboxNetLwfWinRestart: Status = 0x%x, returning NDIS_STATUS_SUCCESS nontheless.\n", Status)); + return NDIS_STATUS_SUCCESS; +} + + +static void vboxNetLwfWinDestroySG(PINTNETSG pSG) +{ + NdisFreeMemory(pSG, 0, 0); + Log4(("vboxNetLwfWinDestroySG: freed SG 0x%p\n", pSG)); +} + +/** + * Worker for vboxNetLwfWinNBtoSG() that gets the max segment count needed. + * @note vboxNetLwfWinNBtoSG may use fewer depending on cbPacket and offset! + * @note vboxNetAdpWinCalcSegments() is a copy of this code. + */ +DECLINLINE(ULONG) vboxNetLwfWinCalcSegments(PNET_BUFFER pNetBuf) +{ + ULONG cSegs = 0; + for (PMDL pMdl = NET_BUFFER_CURRENT_MDL(pNetBuf); pMdl; pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + /* Skip empty MDLs (see @bugref{9233}) */ + if (MmGetMdlByteCount(pMdl)) + cSegs++; + } + return cSegs; +} + +DECLINLINE(void) vboxNetLwfWinFreeMdlChain(PMDL pMdl) +{ +#ifndef VBOXNETLWF_FIXED_SIZE_POOLS + PMDL pMdlNext; + while (pMdl) + { + pMdlNext = pMdl->Next; +# ifndef VBOXNETLWF_SYNC_SEND + PUCHAR pDataBuf; + ULONG cb = 0; + NdisQueryMdl(pMdl, &pDataBuf, &cb, NormalPagePriority); +# endif /* !VBOXNETLWF_SYNC_SEND */ + NdisFreeMdl(pMdl); + Log4(("vboxNetLwfWinFreeMdlChain: freed MDL 0x%p\n", pMdl)); +# ifndef VBOXNETLWF_SYNC_SEND + NdisFreeMemory(pDataBuf, 0, 0); + Log4(("vboxNetLwfWinFreeMdlChain: freed data buffer 0x%p\n", pDataBuf)); +# endif /* !VBOXNETLWF_SYNC_SEND */ + pMdl = pMdlNext; + } +#else /* VBOXNETLWF_FIXED_SIZE_POOLS */ + RT_NOREF1(pMdl); +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ +} + +/** @todo + * 1) Copy data from SG to MDL (if we decide to complete asynchronously). + * 2) Provide context/backfill space. Nobody does it, should we? + * 3) We always get a single segment from intnet. Simplify? + */ +static PNET_BUFFER_LIST vboxNetLwfWinSGtoNB(PVBOXNETLWF_MODULE pModule, PINTNETSG pSG) +{ + AssertReturn(pSG->cSegsUsed >= 1, NULL); + LogFlow(("==>vboxNetLwfWinSGtoNB: segments=%d hPool=%p cb=%u\n", pSG->cSegsUsed, + pModule->hPool, pSG->cbTotal)); + AssertReturn(pModule->hPool, NULL); + +#ifdef VBOXNETLWF_SYNC_SEND + PINTNETSEG pSeg = pSG->aSegs; + PMDL pMdl = NdisAllocateMdl(pModule->hFilter, pSeg->pv, pSeg->cb); + if (!pMdl) + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an MDL\n")); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + Log4(("vboxNetLwfWinSGtoNB: allocated Mdl 0x%p\n", pMdl)); + PMDL pMdlCurr = pMdl; + for (int i = 1; i < pSG->cSegsUsed; i++) + { + pSeg = &pSG->aSegs[i]; + pMdlCurr->Next = NdisAllocateMdl(pModule->hFilter, pSeg->pv, pSeg->cb); + if (!pMdlCurr->Next) + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an MDL\n")); + /* Tear down all MDL we chained so far */ + vboxNetLwfWinFreeMdlChain(pMdl); + return NULL; + } + pMdlCurr = pMdlCurr->Next; + Log4(("vboxNetLwfWinSGtoNB: allocated Mdl 0x%p\n", pMdlCurr)); + } + PNET_BUFFER_LIST pBufList = NdisAllocateNetBufferAndNetBufferList(pModule->hPool, + 0 /* ContextSize */, + 0 /* ContextBackFill */, + pMdl, + 0 /* DataOffset */, + pSG->cbTotal); + if (pBufList) + { + Log4(("vboxNetLwfWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pModule->hFilter; + /** @todo Do we need to initialize anything else? */ + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an NBL+NB\n")); + vboxNetLwfWinFreeMdlChain(pMdl); + } +#else /* !VBOXNETLWF_SYNC_SEND */ + +# ifdef VBOXNETLWF_FIXED_SIZE_POOLS + int iPool = 0; + ULONG cbFrame = VBOXNETLWF_MAX_FRAME_SIZE(pSG->cbTotal); + /* Let's find the appropriate pool first */ + for (iPool = 0; iPool < RT_ELEMENTS(g_cbPool); ++iPool) + if (cbFrame <= g_cbPool[iPool]) + break; + if (iPool >= RT_ELEMENTS(g_cbPool)) + { + LogError(("vboxNetLwfWinSGtoNB: frame is too big (%u > %u), drop it.\n", cbFrame, g_cbPool[RT_ELEMENTS(g_cbPool)-1])); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + PNET_BUFFER_LIST pBufList = NdisAllocateNetBufferList(pModule->hPool[iPool], + 0 /** @todo ContextSize */, + 0 /** @todo ContextBackFill */); + if (!pBufList) + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate netbuffer (cb=%u) from pool %d\n", cbFrame, iPool)); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + const ULONG cbAlignmentMask = sizeof(USHORT) - 1; /* Microsoft LB/FO provider expects packets to be aligned at word boundary. */ + ULONG cbAlignedFrame = (pSG->cbTotal + cbAlignmentMask) & ~cbAlignmentMask; + Assert(cbAlignedFrame >= pSG->cbTotal); + Assert(cbFrame >= cbAlignedFrame); + NET_BUFFER *pBuffer = NET_BUFFER_LIST_FIRST_NB(pBufList); + NDIS_STATUS Status = NdisRetreatNetBufferDataStart(pBuffer, cbAlignedFrame, 0 /** @todo DataBackfill */, NULL); + if (cbAlignedFrame - pSG->cbTotal > 0) + { + /* Make sure padding zeros do not get to the wire. */ + if (NET_BUFFER_DATA_LENGTH(pBuffer) != cbAlignedFrame) + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 11); + else + NET_BUFFER_DATA_LENGTH(pBuffer) = pSG->cbTotal; + } + if (Status == NDIS_STATUS_SUCCESS) + { + uint8_t *pDst = (uint8_t*)NdisGetDataBuffer(pBuffer, pSG->cbTotal, NULL, 1, 0); + if (pDst) + { + for (int i = 0; i < pSG->cSegsUsed; i++) + { + NdisMoveMemory(pDst, pSG->aSegs[i].pv, pSG->aSegs[i].cb); + pDst += pSG->aSegs[i].cb; + } + Log4(("vboxNetLwfWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pModule->hFilter; + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to obtain the buffer pointer (size=%u)\n", pSG->cbTotal)); + NdisAdvanceNetBufferDataStart(pBuffer, cbAlignedFrame, false, NULL); /** @todo why bother? */ + NdisFreeNetBufferList(pBufList); + pBufList = NULL; + } + } + else + { + LogError(("vboxNetLwfWinSGtoNB: NdisRetreatNetBufferDataStart failed with 0x%x (size=%u)\n", Status, pSG->cbTotal)); + NdisFreeNetBufferList(pBufList); + pBufList = NULL; + } +# else /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + PNET_BUFFER_LIST pBufList = NULL; + ULONG cbMdl = VBOXNETLWF_MAX_FRAME_SIZE(pSG->cbTotal); + ULONG uDataOffset = cbMdl - pSG->cbTotal; + PUCHAR pDataBuf = (PUCHAR)NdisAllocateMemoryWithTagPriority(pModule->hFilter, cbMdl, + VBOXNETLWF_MEM_TAG, NormalPoolPriority); + if (pDataBuf) + { + Log4(("vboxNetLwfWinSGtoNB: allocated data buffer (cb=%u) 0x%p\n", cbMdl, pDataBuf)); + PMDL pMdl = NdisAllocateMdl(pModule->hFilter, pDataBuf, cbMdl); + if (!pMdl) + { + NdisFreeMemory(pDataBuf, 0, 0); + Log4(("vboxNetLwfWinSGtoNB: freed data buffer 0x%p\n", pDataBuf)); + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an MDL (cb=%u)\n", cbMdl)); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + PUCHAR pDst = pDataBuf + uDataOffset; + for (int i = 0; i < pSG->cSegsUsed; i++) + { + NdisMoveMemory(pDst, pSG->aSegs[i].pv, pSG->aSegs[i].cb); + pDst += pSG->aSegs[i].cb; + } + pBufList = NdisAllocateNetBufferAndNetBufferList(pModule->hPool, + 0 /* ContextSize */, + 0 /* ContextBackFill */, + pMdl, + uDataOffset, + pSG->cbTotal); + if (pBufList) + { + Log4(("vboxNetLwfWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pModule->hFilter; + /** @todo Do we need to initialize anything else? */ + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an NBL+NB\n")); + vboxNetLwfWinFreeMdlChain(pMdl); + } + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate data buffer (size=%u)\n", cbMdl)); + } +# endif /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + +#endif /* !VBOXNETLWF_SYNC_SEND */ + LogFlow(("<==vboxNetLwfWinSGtoNB: return %p\n", pBufList)); + return pBufList; +} + +/** + * @note vboxNetAdpWinNBtoSG() is a copy of this code. + */ +static PINTNETSG vboxNetLwfWinNBtoSG(PVBOXNETLWF_MODULE pModule, PNET_BUFFER pNetBuf) +{ + ULONG cbPacket = NET_BUFFER_DATA_LENGTH(pNetBuf); + ULONG cSegs = vboxNetLwfWinCalcSegments(pNetBuf); + /* Allocate and initialize SG */ + PINTNETSG pSG = (PINTNETSG)NdisAllocateMemoryWithTagPriority(pModule->hFilter, + RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]), + VBOXNETLWF_MEM_TAG, + NormalPoolPriority); + AssertReturn(pSG, pSG); + Log4(("vboxNetLwfWinNBtoSG: allocated SG 0x%p\n", pSG)); + IntNetSgInitTempSegs(pSG, cbPacket /*cbTotal*/, cSegs, cSegs /*cSegsUsed*/); + + ULONG uOffset = NET_BUFFER_CURRENT_MDL_OFFSET(pNetBuf); + cSegs = 0; + for (PMDL pMdl = NET_BUFFER_CURRENT_MDL(pNetBuf); + pMdl != NULL && cbPacket > 0; + pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + ULONG cbSrc = MmGetMdlByteCount(pMdl); + if (cbSrc == 0) + continue; /* Skip empty MDLs (see @bugref{9233}) */ + + PUCHAR pSrc = (PUCHAR)MmGetSystemAddressForMdlSafe(pMdl, LowPagePriority); + if (!pSrc) + { + vboxNetLwfWinDestroySG(pSG); + return NULL; + } + + /* Handle the offset in the current (which is the first for us) MDL */ + if (uOffset) + { + if (uOffset < cbSrc) + { + pSrc += uOffset; + cbSrc -= uOffset; + uOffset = 0; + } + else + { + /* This is an invalid MDL chain */ + vboxNetLwfWinDestroySG(pSG); + return NULL; + } + } + + /* Do not read the last MDL beyond packet's end */ + if (cbSrc > cbPacket) + cbSrc = cbPacket; + + Assert(cSegs < pSG->cSegsAlloc); + pSG->aSegs[cSegs].pv = pSrc; + pSG->aSegs[cSegs].cb = cbSrc; + pSG->aSegs[cSegs].Phys = NIL_RTHCPHYS; + cSegs++; + cbPacket -= cbSrc; + } + + Assert(cbPacket == 0); + Assert(cSegs <= pSG->cSegsUsed); + + /* Update actual segment count in case we used fewer than anticipated. */ + pSG->cSegsUsed = (uint16_t)cSegs; + + return pSG; +} + +VOID vboxNetLwfWinStatus(IN NDIS_HANDLE hModuleCtx, IN PNDIS_STATUS_INDICATION pIndication) +{ + LogFlow(("==>vboxNetLwfWinStatus: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + Log(("vboxNetLwfWinStatus: Got status indication: %s\n", vboxNetLwfWinStatusToText(pIndication->StatusCode))); + switch (pIndication->StatusCode) + { + case NDIS_STATUS_PACKET_FILTER: + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pIndication->StatusBuffer); + vboxNetLwfWinOverridePacketFiltersUp(pModuleCtx, (ULONG*)pIndication->StatusBuffer); + Log(("vboxNetLwfWinStatus: Reporting status: %s\n", vboxNetLwfWinStatusToText(pIndication->StatusCode))); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pIndication->StatusBuffer); + break; + case NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG: + Log5(("vboxNetLwfWinStatus: offloading currently set to:\n")); + vboxNetLwfWinDumpOffloadSettings((PNDIS_OFFLOAD)pIndication->StatusBuffer); + vboxNetLwfWinUpdateSavedOffloadConfig(pModuleCtx, (PNDIS_OFFLOAD)pIndication->StatusBuffer); + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + vboxNetLwfWinDisableOffloading((PNDIS_OFFLOAD)pIndication->StatusBuffer); + Log5(("vboxNetLwfWinStatus: reporting offloading up as:\n")); + vboxNetLwfWinDumpOffloadSettings((PNDIS_OFFLOAD)pIndication->StatusBuffer); + break; + } + NdisFIndicateStatus(pModuleCtx->hFilter, pIndication); + LogFlow(("<==vboxNetLwfWinStatus\n")); +} + +static bool vboxNetLwfWinForwardToIntNet(PVBOXNETLWF_MODULE pModuleCtx, PNET_BUFFER_LIST pBufLists, uint32_t fSrc) +{ + /* We must not forward anything to the trunk unless it is ready to receive. */ + if (!ASMAtomicReadBool(&pModuleCtx->fActive)) + { + Log(("vboxNetLwfWinForwardToIntNet: trunk is inactive, won't forward\n")); + return false; + } + /* Some NPF protocols make NDIS to loop back packets at miniport level, we must ignore those. */ + if (NdisTestNblFlag(pBufLists, NDIS_NBL_FLAGS_IS_LOOPBACK_PACKET)) + { + if (pBufLists->SourceHandle == pModuleCtx->hFilter && !pModuleCtx->fPassVmTrafficToHost) + { + /* Drop the packets we've injected. */ + vboxNetLwfWinDumpPackets("vboxNetLwfWinForwardToIntNet: dropping loopback", pBufLists); + return true; + } + vboxNetLwfWinDumpPackets("vboxNetLwfWinForwardToIntNet: passing through loopback", pBufLists); + return false; + } + + AssertReturn(pModuleCtx->pNetFlt, false); + AssertReturn(pModuleCtx->pNetFlt->pSwitchPort, false); + AssertReturn(pModuleCtx->pNetFlt->pSwitchPort->pfnRecv, false); + LogFlow(("==>vboxNetLwfWinForwardToIntNet: module=%p\n", pModuleCtx)); + Assert(pBufLists); /* The chain must contain at least one list */ + Assert(NET_BUFFER_LIST_NEXT_NBL(pBufLists) == NULL); /* The caller is supposed to unlink the list from the chain */ + /* + * Even if NBL contains more than one buffer we are prepared to deal with it. + * When any of buffers should not be dropped we keep the whole list. It is + * better to leak some "unexpected" packets to the wire/host than to loose any. + */ + bool fDropIt = false; + bool fDontDrop = false; + int nLists = 0; + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + int nBuffers = 0; + nLists++; + for (PNET_BUFFER pBuf = NET_BUFFER_LIST_FIRST_NB(pList); pBuf; pBuf = NET_BUFFER_NEXT_NB(pBuf)) + { + nBuffers++; + PINTNETSG pSG = vboxNetLwfWinNBtoSG(pModuleCtx, pBuf); + if (pSG) + { + vboxNetLwfWinDumpPacket(pSG, (fSrc & INTNETTRUNKDIR_WIRE)?"intnet <-- wire":"intnet <-- host"); + /* A bit paranoid, but we do not use any locks, so... */ + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + if (pModuleCtx->pNetFlt->pSwitchPort->pfnRecv(pModuleCtx->pNetFlt->pSwitchPort, NULL, pSG, fSrc)) + fDropIt = true; + else + fDontDrop = true; + vboxNetLwfWinDestroySG(pSG); + } + } + Log(("vboxNetLwfWinForwardToIntNet: list=%d buffers=%d\n", nLists, nBuffers)); + } + Log(("vboxNetLwfWinForwardToIntNet: lists=%d drop=%s don't=%s\n", nLists, fDropIt ? "true":"false", fDontDrop ? "true":"false")); + + /* If the host (and the user) wants to see all packets we must not drop any. */ + if (pModuleCtx->fPassVmTrafficToHost && vboxNetLwfWinIsPromiscuous(pModuleCtx)) + fDropIt = false; + + LogFlow(("<==vboxNetLwfWinForwardToIntNet: return '%s'\n", + fDropIt ? (fDontDrop ? "do not drop (some)" : "drop it") : "do not drop (any)")); + return fDropIt && !fDontDrop; /* Drop the list if ALL its buffers are being dropped! */ +} + +DECLINLINE(bool) vboxNetLwfWinIsRunning(PVBOXNETLWF_MODULE pModule) +{ + Log(("vboxNetLwfWinIsRunning: state=%d\n", ASMAtomicReadU32(&pModule->enmState))); + return ASMAtomicReadU32(&pModule->enmState) == LwfState_Running; +} + +VOID vboxNetLwfWinSendNetBufferLists(IN NDIS_HANDLE hModuleCtx, IN PNET_BUFFER_LIST pBufLists, IN NDIS_PORT_NUMBER nPort, IN ULONG fFlags) +{ + LogFlow(("==>vboxNetLwfWinSendNetBufferLists: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: got", pBufLists); + + if (!ASMAtomicReadBool(&pModule->fActive)) + { + /* + * The trunk is inactive, jusp pass along all packets to the next + * underlying driver. + */ + NdisFSendNetBufferLists(pModule->hFilter, pBufLists, nPort, fFlags); + return; + } + + if (vboxNetLwfWinIsRunning(pModule)) + { + PNET_BUFFER_LIST pNext = NULL; + PNET_BUFFER_LIST pDropHead = NULL; + PNET_BUFFER_LIST pDropTail = NULL; + PNET_BUFFER_LIST pPassHead = NULL; + PNET_BUFFER_LIST pPassTail = NULL; + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = pNext) + { + pNext = NET_BUFFER_LIST_NEXT_NBL(pList); + NET_BUFFER_LIST_NEXT_NBL(pList) = NULL; /* Unlink */ + if (vboxNetLwfWinForwardToIntNet(pModule, pList, INTNETTRUNKDIR_HOST)) + { + NET_BUFFER_LIST_STATUS(pList) = NDIS_STATUS_SUCCESS; + if (pDropHead) + { + NET_BUFFER_LIST_NEXT_NBL(pDropTail) = pList; + pDropTail = pList; + } + else + pDropHead = pDropTail = pList; + } + else + { + if (pPassHead) + { + NET_BUFFER_LIST_NEXT_NBL(pPassTail) = pList; + pPassTail = pList; + } + else + pPassHead = pPassTail = pList; + } + } + Assert((pBufLists == pPassHead) || (pBufLists == pDropHead)); + if (pPassHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: passing down", pPassHead); + NdisFSendNetBufferLists(pModule->hFilter, pBufLists, nPort, fFlags); + } + if (pDropHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: consumed", pDropHead); + NdisFSendNetBufferListsComplete(pModule->hFilter, pDropHead, + fFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL ? NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL : 0); + } + } + else + { + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + NET_BUFFER_LIST_STATUS(pList) = NDIS_STATUS_PAUSED; + } + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: consumed", pBufLists); + NdisFSendNetBufferListsComplete(pModule->hFilter, pBufLists, + fFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL ? NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL : 0); + + } + LogFlow(("<==vboxNetLwfWinSendNetBufferLists\n")); +} + +VOID vboxNetLwfWinSendNetBufferListsComplete(IN NDIS_HANDLE hModuleCtx, IN PNET_BUFFER_LIST pBufLists, IN ULONG fFlags) +{ + LogFlow(("==>vboxNetLwfWinSendNetBufferListsComplete: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + PNET_BUFFER_LIST pList = pBufLists; + PNET_BUFFER_LIST pNextList; + PNET_BUFFER_LIST pPrevList = NULL; + while (pList) + { + pNextList = NET_BUFFER_LIST_NEXT_NBL(pList); + if (pList->SourceHandle == pModule->hFilter) + { + /* We allocated this NET_BUFFER_LIST, let's free it up */ + Assert(NET_BUFFER_LIST_FIRST_NB(pList)); + Assert(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* + * All our NBLs hold a single NB each, no need to iterate over a list. + * There is no need to free an associated NB explicitly either, as it was + * preallocated with NBL structure. + */ + Assert(!NET_BUFFER_NEXT_NB(NET_BUFFER_LIST_FIRST_NB(pList))); + vboxNetLwfWinFreeMdlChain(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* Unlink this list from the chain */ + if (pPrevList) + NET_BUFFER_LIST_NEXT_NBL(pPrevList) = pNextList; + else + pBufLists = pNextList; + Log(("vboxNetLwfWinSendNetBufferListsComplete: our list %p, next=%p, previous=%p, head=%p\n", pList, pNextList, pPrevList, pBufLists)); + NdisFreeNetBufferList(pList); +#ifdef VBOXNETLWF_SYNC_SEND + Log4(("vboxNetLwfWinSendNetBufferListsComplete: freed NBL+NB 0x%p\n", pList)); + KeSetEvent(&pModule->EventWire, 0, FALSE); +#else /* !VBOXNETLWF_SYNC_SEND */ + Log4(("vboxNetLwfWinSendNetBufferListsComplete: freed NBL+NB+MDL+Data 0x%p\n", pList)); + Assert(ASMAtomicReadS32(&pModule->cPendingBuffers) > 0); + if (ASMAtomicDecS32(&pModule->cPendingBuffers) == 0) + NdisSetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + } + else + { + pPrevList = pList; + Log(("vboxNetLwfWinSendNetBufferListsComplete: passing list %p, next=%p, previous=%p, head=%p\n", pList, pNextList, pPrevList, pBufLists)); + } + pList = pNextList; + } + if (pBufLists) + { + /* There are still lists remaining in the chain, pass'em up */ + NdisFSendNetBufferListsComplete(pModule->hFilter, pBufLists, fFlags); + } + LogFlow(("<==vboxNetLwfWinSendNetBufferListsComplete\n")); +} + +VOID vboxNetLwfWinReceiveNetBufferLists(IN NDIS_HANDLE hModuleCtx, + IN PNET_BUFFER_LIST pBufLists, + IN NDIS_PORT_NUMBER nPort, + IN ULONG nBufLists, + IN ULONG fFlags) +{ + /// @todo Do we need loopback handling? + LogFlow(("==>vboxNetLwfWinReceiveNetBufferLists: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: got", pBufLists); + + if (!ASMAtomicReadBool(&pModule->fActive)) + { + /* + * The trunk is inactive, just pass along all packets to the next + * overlying driver. + */ + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pBufLists, nPort, nBufLists, fFlags); + LogFlow(("<==vboxNetLwfWinReceiveNetBufferLists: inactive trunk\n")); + return; + } + + if (vboxNetLwfWinIsRunning(pModule)) + { + if (NDIS_TEST_RECEIVE_CANNOT_PEND(fFlags)) + { + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + PNET_BUFFER_LIST pNext = NET_BUFFER_LIST_NEXT_NBL(pList); + NET_BUFFER_LIST_NEXT_NBL(pList) = NULL; /* Unlink temporarily */ + if (!vboxNetLwfWinForwardToIntNet(pModule, pList, INTNETTRUNKDIR_WIRE)) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: passing up", pList); + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pList, nPort, nBufLists, fFlags); + } + NET_BUFFER_LIST_NEXT_NBL(pList) = pNext; /* Restore the link */ + } + } + else + { + /* We collect dropped NBLs in a separate list in order to "return" them. */ + PNET_BUFFER_LIST pNext = NULL; + PNET_BUFFER_LIST pDropHead = NULL; + PNET_BUFFER_LIST pDropTail = NULL; + PNET_BUFFER_LIST pPassHead = NULL; + PNET_BUFFER_LIST pPassTail = NULL; + ULONG nDrop = 0, nPass = 0; + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = pNext) + { + pNext = NET_BUFFER_LIST_NEXT_NBL(pList); + NET_BUFFER_LIST_NEXT_NBL(pList) = NULL; /* Unlink */ + if (vboxNetLwfWinForwardToIntNet(pModule, pList, INTNETTRUNKDIR_WIRE)) + { + if (nDrop++) + { + NET_BUFFER_LIST_NEXT_NBL(pDropTail) = pList; + pDropTail = pList; + } + else + pDropHead = pDropTail = pList; + } + else + { + if (nPass++) + { + NET_BUFFER_LIST_NEXT_NBL(pPassTail) = pList; + pPassTail = pList; + } + else + pPassHead = pPassTail = pList; + } + } + Assert((pBufLists == pPassHead) || (pBufLists == pDropHead)); + Assert(nDrop + nPass == nBufLists); + if (pPassHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: passing up", pPassHead); + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pPassHead, nPort, nPass, fFlags); + } + if (pDropHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: consumed", pDropHead); + NdisFReturnNetBufferLists(pModule->hFilter, pDropHead, + fFlags & NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL ? NDIS_RETURN_FLAGS_DISPATCH_LEVEL : 0); + } + } + + } + else + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: consumed", pBufLists); + if ((fFlags & NDIS_RECEIVE_FLAGS_RESOURCES) == 0) + NdisFReturnNetBufferLists(pModule->hFilter, pBufLists, + fFlags & NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL ? NDIS_RETURN_FLAGS_DISPATCH_LEVEL : 0); + } + LogFlow(("<==vboxNetLwfWinReceiveNetBufferLists\n")); +} + +VOID vboxNetLwfWinReturnNetBufferLists(IN NDIS_HANDLE hModuleCtx, IN PNET_BUFFER_LIST pBufLists, IN ULONG fFlags) +{ + LogFlow(("==>vboxNetLwfWinReturnNetBufferLists: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + PNET_BUFFER_LIST pList = pBufLists; + PNET_BUFFER_LIST pNextList; + PNET_BUFFER_LIST pPrevList = NULL; + /** @todo Move common part into a separate function to be used by vboxNetLwfWinSendNetBufferListsComplete() as well */ + while (pList) + { + pNextList = NET_BUFFER_LIST_NEXT_NBL(pList); + if (pList->SourceHandle == pModule->hFilter) + { + /* We allocated this NET_BUFFER_LIST, let's free it up */ + Assert(NET_BUFFER_LIST_FIRST_NB(pList)); + Assert(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* + * All our NBLs hold a single NB each, no need to iterate over a list. + * There is no need to free an associated NB explicitly either, as it was + * preallocated with NBL structure. + */ + vboxNetLwfWinFreeMdlChain(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* Unlink this list from the chain */ + if (pPrevList) + NET_BUFFER_LIST_NEXT_NBL(pPrevList) = pNextList; + else + pBufLists = pNextList; + NdisFreeNetBufferList(pList); +#ifdef VBOXNETLWF_SYNC_SEND + Log4(("vboxNetLwfWinReturnNetBufferLists: freed NBL+NB 0x%p\n", pList)); + KeSetEvent(&pModule->EventHost, 0, FALSE); +#else /* !VBOXNETLWF_SYNC_SEND */ + Log4(("vboxNetLwfWinReturnNetBufferLists: freed NBL+NB+MDL+Data 0x%p\n", pList)); + Assert(ASMAtomicReadS32(&pModule->cPendingBuffers) > 0); + if (ASMAtomicDecS32(&pModule->cPendingBuffers) == 0) + NdisSetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + } + else + pPrevList = pList; + pList = pNextList; + } + if (pBufLists) + { + /* There are still lists remaining in the chain, pass'em up */ + NdisFReturnNetBufferLists(pModule->hFilter, pBufLists, fFlags); + } + LogFlow(("<==vboxNetLwfWinReturnNetBufferLists\n")); +} + +/** + * register the filter driver + */ +DECLHIDDEN(NDIS_STATUS) vboxNetLwfWinRegister(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr) +{ + RT_NOREF1(pRegistryPathStr); + NDIS_FILTER_DRIVER_CHARACTERISTICS FChars; + NDIS_STRING FriendlyName; + NDIS_STRING UniqueName; + NDIS_STRING ServiceName; + + NdisInitUnicodeString(&FriendlyName, VBOXNETLWF_NAME_FRIENDLY); + NdisInitUnicodeString(&UniqueName, VBOXNETLWF_NAME_UNIQUE); + NdisInitUnicodeString(&ServiceName, VBOXNETLWF_NAME_SERVICE); + + NdisZeroMemory(&FChars, sizeof (FChars)); + + FChars.Header.Type = NDIS_OBJECT_TYPE_FILTER_DRIVER_CHARACTERISTICS; + FChars.Header.Size = sizeof(NDIS_FILTER_DRIVER_CHARACTERISTICS); + FChars.Header.Revision = NDIS_FILTER_CHARACTERISTICS_REVISION_1; + + FChars.MajorNdisVersion = VBOXNETLWF_VERSION_NDIS_MAJOR; + FChars.MinorNdisVersion = VBOXNETLWF_VERSION_NDIS_MINOR; + + FChars.FriendlyName = FriendlyName; + FChars.UniqueName = UniqueName; + FChars.ServiceName = ServiceName; + + /* Mandatory functions */ + FChars.AttachHandler = vboxNetLwfWinAttach; + FChars.DetachHandler = vboxNetLwfWinDetach; + FChars.RestartHandler = vboxNetLwfWinRestart; + FChars.PauseHandler = vboxNetLwfWinPause; + + /* Optional functions, non changeble at run-time */ + FChars.OidRequestHandler = vboxNetLwfWinOidRequest; + FChars.OidRequestCompleteHandler = vboxNetLwfWinOidRequestComplete; + //FChars.CancelOidRequestHandler = vboxNetLwfWinCancelOidRequest; + FChars.StatusHandler = vboxNetLwfWinStatus; + //FChars.NetPnPEventHandler = vboxNetLwfWinPnPEvent; + + /* Datapath functions */ + FChars.SendNetBufferListsHandler = vboxNetLwfWinSendNetBufferLists; + FChars.SendNetBufferListsCompleteHandler = vboxNetLwfWinSendNetBufferListsComplete; + FChars.ReceiveNetBufferListsHandler = vboxNetLwfWinReceiveNetBufferLists; + FChars.ReturnNetBufferListsHandler = vboxNetLwfWinReturnNetBufferLists; + + pDriverObject->DriverUnload = vboxNetLwfWinUnloadDriver; + + NDIS_STATUS Status; + g_VBoxNetLwfGlobals.hFilterDriver = NULL; + Log(("vboxNetLwfWinRegister: registering filter driver...\n")); + Status = NdisFRegisterFilterDriver(pDriverObject, + (NDIS_HANDLE)&g_VBoxNetLwfGlobals, + &FChars, + &g_VBoxNetLwfGlobals.hFilterDriver); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Log(("vboxNetLwfWinRegister: successfully registered filter driver; registering device...\n")); + Status = vboxNetLwfWinDevCreate(&g_VBoxNetLwfGlobals); + Assert(Status == STATUS_SUCCESS); + Log(("vboxNetLwfWinRegister: vboxNetLwfWinDevCreate() returned 0x%x\n", Status)); + } + else + { + LogError(("vboxNetLwfWinRegister: failed to register filter driver, status=0x%x", Status)); + } + return Status; +} + +static int vboxNetLwfWinStartInitIdcThread() +{ + int rc = VERR_INVALID_STATE; + + if (ASMAtomicCmpXchgU32(&g_VBoxNetLwfGlobals.enmIdcState, LwfIdcState_Connecting, LwfIdcState_Disconnected)) + { + Log(("vboxNetLwfWinStartInitIdcThread: IDC state change Diconnected -> Connecting\n")); + + NTSTATUS Status = PsCreateSystemThread(&g_VBoxNetLwfGlobals.hInitIdcThread, + THREAD_ALL_ACCESS, + NULL, + NULL, + NULL, + vboxNetLwfWinInitIdcWorker, + &g_VBoxNetLwfGlobals); + Log(("vboxNetLwfWinStartInitIdcThread: create IDC initialization thread, status=0x%x\n", Status)); + if (Status != STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinStartInitIdcThread: IDC initialization failed (system thread creation, status=0x%x)\n", Status)); + /* + * We failed to init IDC and there will be no second chance. + */ + Log(("vboxNetLwfWinStartInitIdcThread: IDC state change Connecting -> Diconnected\n")); + ASMAtomicWriteU32(&g_VBoxNetLwfGlobals.enmIdcState, LwfIdcState_Disconnected); + } + rc = RTErrConvertFromNtStatus(Status); + } + return rc; +} + +static void vboxNetLwfWinStopInitIdcThread() +{ +} + + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + /* the idc registration is initiated via IOCTL since our driver + * can be loaded when the VBoxDrv is not in case we are a Ndis IM driver */ + rc = vboxNetLwfWinInitBase(); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + NdisZeroMemory(&g_VBoxNetLwfGlobals, sizeof (g_VBoxNetLwfGlobals)); + RTListInit(&g_VBoxNetLwfGlobals.listModules); + NdisAllocateSpinLock(&g_VBoxNetLwfGlobals.Lock); + /* + * We choose to ignore IDC initialization errors here because if we fail to load + * our filter the upper protocols won't bind to the associated adapter, causing + * network failure at the host. Better to have non-working filter than broken + * networking on the host. + */ + rc = vboxNetLwfWinStartInitIdcThread(); + AssertRC(rc); + + Status = vboxNetLwfWinRegister(pDriverObject, pRegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + Log(("NETLWF: started successfully\n")); + return STATUS_SUCCESS; + } + NdisFreeSpinLock(&g_VBoxNetLwfGlobals.Lock); + vboxNetLwfWinFini(); + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} + + +static VOID vboxNetLwfWinUnloadDriver(IN PDRIVER_OBJECT pDriver) +{ + RT_NOREF1(pDriver); + LogFlow(("==>vboxNetLwfWinUnloadDriver: driver=%p\n", pDriver)); + vboxNetLwfWinDevDestroy(&g_VBoxNetLwfGlobals); + NdisFDeregisterFilterDriver(g_VBoxNetLwfGlobals.hFilterDriver); + NdisFreeSpinLock(&g_VBoxNetLwfGlobals.Lock); + LogFlow(("<==vboxNetLwfWinUnloadDriver\n")); + vboxNetLwfWinFini(); +} + +static const char *vboxNetLwfWinIdcStateToText(uint32_t enmState) +{ + switch (enmState) + { + case LwfIdcState_Disconnected: return "Disconnected"; + case LwfIdcState_Connecting: return "Connecting"; + case LwfIdcState_Connected: return "Connected"; + case LwfIdcState_Stopping: return "Stopping"; + } + return "Unknown"; +} + +static VOID vboxNetLwfWinInitIdcWorker(PVOID pvContext) +{ + int rc; + PVBOXNETLWFGLOBALS pGlobals = (PVBOXNETLWFGLOBALS)pvContext; + + while (ASMAtomicReadU32(&pGlobals->enmIdcState) == LwfIdcState_Connecting) + { + rc = vboxNetFltInitIdc(&g_VBoxNetFltGlobals); + if (RT_SUCCESS(rc)) + { + if (!ASMAtomicCmpXchgU32(&pGlobals->enmIdcState, LwfIdcState_Connected, LwfIdcState_Connecting)) + { + /* The state has been changed (the only valid transition is to "Stopping"), undo init */ + rc = vboxNetFltTryDeleteIdc(&g_VBoxNetFltGlobals); + Log(("vboxNetLwfWinInitIdcWorker: state change (Connecting -> %s) while initializing IDC, deleted IDC, rc=0x%x\n", + vboxNetLwfWinIdcStateToText(ASMAtomicReadU32(&pGlobals->enmIdcState)), rc)); + } + else + { + Log(("vboxNetLwfWinInitIdcWorker: IDC state change Connecting -> Connected\n")); + } + } + else + { + LARGE_INTEGER WaitIn100nsUnits; + WaitIn100nsUnits.QuadPart = -(LONGLONG)10000000; /* 1 sec */ + KeDelayExecutionThread(KernelMode, FALSE /* non-alertable */, &WaitIn100nsUnits); + } + } + PsTerminateSystemThread(STATUS_SUCCESS); +} + +static int vboxNetLwfWinTryFiniIdc() +{ + int rc = VINF_SUCCESS; + NTSTATUS Status; + PKTHREAD pThread = NULL; + uint32_t enmPrevState = ASMAtomicXchgU32(&g_VBoxNetLwfGlobals.enmIdcState, LwfIdcState_Stopping); + + Log(("vboxNetLwfWinTryFiniIdc: IDC state change %s -> Stopping\n", vboxNetLwfWinIdcStateToText(enmPrevState))); + + switch (enmPrevState) + { + case LwfIdcState_Disconnected: + /* Have not even attempted to connect -- nothing to do. */ + break; + case LwfIdcState_Stopping: + /* Impossible, but another thread is alreading doing FiniIdc, bail out */ + LogError(("vboxNetLwfWinTryFiniIdc: called in 'Stopping' state\n")); + rc = VERR_INVALID_STATE; + break; + case LwfIdcState_Connecting: + /* the worker thread is running, let's wait for it to stop */ + Status = ObReferenceObjectByHandle(g_VBoxNetLwfGlobals.hInitIdcThread, + THREAD_ALL_ACCESS, NULL, KernelMode, + (PVOID*)&pThread, NULL); + if (Status == STATUS_SUCCESS) + { + KeWaitForSingleObject(pThread, Executive, KernelMode, FALSE, NULL); + ObDereferenceObject(pThread); + } + else + { + LogError(("vboxNetLwfWinTryFiniIdc: ObReferenceObjectByHandle(%p) failed with 0x%x\n", + g_VBoxNetLwfGlobals.hInitIdcThread, Status)); + } + rc = RTErrConvertFromNtStatus(Status); + break; + case LwfIdcState_Connected: + /* the worker succeeded in IDC init and terminated */ + rc = vboxNetFltTryDeleteIdc(&g_VBoxNetFltGlobals); + Log(("vboxNetLwfWinTryFiniIdc: deleted IDC, rc=0x%x\n", rc)); + break; + } + return rc; +} + +static void vboxNetLwfWinFiniBase() +{ + vboxNetFltDeleteGlobals(&g_VBoxNetFltGlobals); + + /* + * Undo the work done during start (in reverse order). + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + + RTR0Term(); +} + +static int vboxNetLwfWinInitBase() +{ + int rc = RTR0Init(0); + if (!RT_SUCCESS(rc)) + return rc; + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobals(&g_VBoxNetFltGlobals); + if (!RT_SUCCESS(rc)) + RTR0Term(); + + return rc; +} + +static int vboxNetLwfWinFini() +{ + int rc = vboxNetLwfWinTryFiniIdc(); + if (RT_SUCCESS(rc)) + { + vboxNetLwfWinFiniBase(); + } + return rc; +} + + +/* + * + * The OS specific interface definition + * + */ + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltOsMaybeRediscovered: instance=%p\n", pThis)); + LogFlow(("<==vboxNetFltOsMaybeRediscovered: return %RTbool\n", !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost))); + /* AttachToInterface true if disconnected */ + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + RT_NOREF1(pvIfData); + int rc = VINF_SUCCESS; + + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)pThis->u.s.WinIf.hModuleCtx; + LogFlow(("==>vboxNetFltPortOsXmit: instance=%p module=%p\n", pThis, pModule)); + if (!pModule) + { + LogFlow(("<==vboxNetFltPortOsXmit: pModule is null, return %d\n", VERR_INTERNAL_ERROR)); + return VERR_INTERNAL_ERROR; + } + /* Prevent going into "paused" state until all transmissions have been completed. */ + NDIS_WAIT_FOR_MUTEX(&pModule->InTransmit); + /* Ignore all sends if the stack is paused or being paused, etc... */ + if (!vboxNetLwfWinIsRunning(pModule)) + { + NDIS_RELEASE_MUTEX(&pModule->InTransmit); + return VINF_SUCCESS; + } + + vboxNetLwfWinDumpPacket(pSG, !(fDst & INTNETTRUNKDIR_WIRE) ? "intnet --> host" + : !(fDst & INTNETTRUNKDIR_HOST) ? "intnet --> wire" : "intnet --> all"); + + /* + * There are two possible strategies to deal with incoming SGs: + * 1) make a copy of data and complete asynchronously; + * 2) complete synchronously using the original data buffers. + * Before we consider implementing (1) it is quite interesting to see + * how well (2) performs. So we block until our requests are complete. + * Actually there is third possibility -- to use SG retain/release + * callbacks, but those seem not be fully implemented yet. + * Note that ansynchronous completion will require different implementation + * of vboxNetLwfWinPause(), not relying on InTransmit mutex. + */ +#ifdef VBOXNETLWF_SYNC_SEND + PVOID aEvents[2]; /* To wire and to host */ + ULONG nEvents = 0; + LARGE_INTEGER timeout; + timeout.QuadPart = -(LONGLONG)10000000; /* 1 sec */ +#endif /* VBOXNETLWF_SYNC_SEND */ + if (fDst & INTNETTRUNKDIR_WIRE) + { + PNET_BUFFER_LIST pBufList = vboxNetLwfWinSGtoNB(pModule, pSG); + if (pBufList) + { + vboxNetLwfWinDumpPackets("vboxNetFltPortOsXmit: sending down", pBufList); +#ifdef VBOXNETLWF_SYNC_SEND + aEvents[nEvents++] = &pModule->EventWire; +#else /* !VBOXNETLWF_SYNC_SEND */ + if (ASMAtomicIncS32(&pModule->cPendingBuffers) == 1) + NdisResetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + NdisFSendNetBufferLists(pModule->hFilter, pBufList, NDIS_DEFAULT_PORT_NUMBER, 0); /** @todo sendFlags! */ + } + } + if (fDst & INTNETTRUNKDIR_HOST) + { + PNET_BUFFER_LIST pBufList = vboxNetLwfWinSGtoNB(pModule, pSG); + if (pBufList) + { + vboxNetLwfWinDumpPackets("vboxNetFltPortOsXmit: sending up", pBufList); +#ifdef VBOXNETLWF_SYNC_SEND + aEvents[nEvents++] = &pModule->EventHost; +#else /* !VBOXNETLWF_SYNC_SEND */ + if (ASMAtomicIncS32(&pModule->cPendingBuffers) == 1) + NdisResetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pBufList, NDIS_DEFAULT_PORT_NUMBER, 1, 0); + } + } +#ifdef VBOXNETLWF_SYNC_SEND + if (nEvents) + { + NTSTATUS Status = KeWaitForMultipleObjects(nEvents, aEvents, WaitAll, Executive, KernelMode, FALSE, &timeout, NULL); + if (Status != STATUS_SUCCESS) + { + LogError(("vboxNetFltPortOsXmit: KeWaitForMultipleObjects() failed with 0x%x\n", Status)); + if (Status == STATUS_TIMEOUT) + rc = VERR_TIMEOUT; + else + rc = RTErrConvertFromNtStatus(Status); + } + } +#endif /* VBOXNETLWF_SYNC_SEND */ + NDIS_RELEASE_MUTEX(&pModule->InTransmit); + + LogFlow(("<==vboxNetFltPortOsXmit: return %d\n", rc)); + return rc; +} + + +NDIS_IO_WORKITEM_FUNCTION vboxNetLwfWinToggleOffloading; + +VOID vboxNetLwfWinToggleOffloading(PVOID WorkItemContext, NDIS_HANDLE NdisIoWorkItemHandle) +{ + /* WARNING! Call this with IRQL=Passive! */ + RT_NOREF1(NdisIoWorkItemHandle); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)WorkItemContext; + + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + { + /* Disable offloading temporarily by indicating offload config change. */ + /** @todo Be sure to revise this when implementing offloading support! */ + vboxNetLwfWinIndicateOffload(pModuleCtx, pModuleCtx->pDisabledOffloadConfig); + Log(("vboxNetLwfWinToggleOffloading: set offloading off\n")); + } + else + { + /* The filter is inactive -- restore offloading configuration. */ + if (pModuleCtx->fOffloadConfigValid) + { + vboxNetLwfWinIndicateOffload(pModuleCtx, pModuleCtx->pSavedOffloadConfig); + Log(("vboxNetLwfWinToggleOffloading: restored offloading config\n")); + } + else + DbgPrint("VBoxNetLwf: no saved offload config to restore for %s\n", pModuleCtx->szMiniportName); + } +} + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)pThis->u.s.WinIf.hModuleCtx; + LogFlow(("==>vboxNetFltPortOsSetActive: instance=%p module=%p fActive=%RTbool\n", pThis, pModuleCtx, fActive)); + if (!pModuleCtx) + { + LogFlow(("<==vboxNetFltPortOsSetActive: pModuleCtx is null\n")); + return; + } + + NDIS_STATUS Status = STATUS_SUCCESS; + bool fOldActive = ASMAtomicXchgBool(&pModuleCtx->fActive, fActive); + if (fOldActive != fActive) + { + NdisQueueIoWorkItem(pModuleCtx->hWorkItem, vboxNetLwfWinToggleOffloading, pModuleCtx); + Status = vboxNetLwfWinSetPacketFilter(pModuleCtx, fActive); + LogFlow(("<==vboxNetFltPortOsSetActive: vboxNetLwfWinSetPacketFilter() returned 0x%x\n", Status)); + } + else + LogFlow(("<==vboxNetFltPortOsSetActive: no change, remain %sactive\n", fActive ? "":"in")); +} + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + RT_NOREF1(pThis); + LogFlow(("==>vboxNetFltOsDisconnectIt: instance=%p\n", pThis)); + LogFlow(("<==vboxNetFltOsDisconnectIt: return 0\n")); + return VINF_SUCCESS; +} + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + RT_NOREF1(pThis); + LogFlow(("==>vboxNetFltOsConnectIt: instance=%p\n", pThis)); + LogFlow(("<==vboxNetFltOsConnectIt: return 0\n")); + return VINF_SUCCESS; +} + +/* + * Uncommenting the following line produces debug log messages on IP address changes, + * including wired interfaces. No actual calls to a switch port are made. This is for + * debug purposes only! + * #define VBOXNETLWFWIN_DEBUGIPADDRNOTIF 1 + */ +static void __stdcall vboxNetLwfWinIpAddrChangeCallback(IN PVOID pvCtx, + IN PMIB_UNICASTIPADDRESS_ROW pRow, + IN MIB_NOTIFICATION_TYPE enmNotifType) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvCtx; + + /* We are only interested in add or remove notifications. */ + bool fAdded; + if (enmNotifType == MibAddInstance) + fAdded = true; + else if (enmNotifType == MibDeleteInstance) + fAdded = false; + else + return; + + if ( pRow +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + && pThis->pSwitchPort->pfnNotifyHostAddress +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + ) + { + switch (pRow->Address.si_family) + { + case AF_INET: + if ( IN4_IS_ADDR_LINKLOCAL(&pRow->Address.Ipv4.sin_addr) + || pRow->Address.Ipv4.sin_addr.s_addr == IN4ADDR_LOOPBACK) + { + Log(("vboxNetLwfWinIpAddrChangeCallback: ignoring %s address (%RTnaipv4)\n", + pRow->Address.Ipv4.sin_addr.s_addr == IN4ADDR_LOOPBACK ? "loopback" : "link-local", + pRow->Address.Ipv4.sin_addr)); + break; + } + Log(("vboxNetLwfWinIpAddrChangeCallback: %s IPv4 addr=%RTnaipv4 on luid=(%u,%u)\n", + fAdded ? "add" : "remove", pRow->Address.Ipv4.sin_addr, + pRow->InterfaceLuid.Info.IfType, pRow->InterfaceLuid.Info.NetLuidIndex)); +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, kIntNetAddrType_IPv4, + &pRow->Address.Ipv4.sin_addr); +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + break; + case AF_INET6: + if (Ipv6AddressScope(pRow->Address.Ipv6.sin6_addr.u.Byte) <= ScopeLevelLink) + { + Log(("vboxNetLwfWinIpAddrChangeCallback: ignoring link-local address (%RTnaipv6)\n", + &pRow->Address.Ipv6.sin6_addr)); + break; + } + Log(("vboxNetLwfWinIpAddrChangeCallback: %s IPv6 addr=%RTnaipv6 scope=%d luid=(%u,%u)\n", + fAdded ? "add" : "remove", &pRow->Address.Ipv6.sin6_addr, + Ipv6AddressScope(pRow->Address.Ipv6.sin6_addr.u.Byte), + pRow->InterfaceLuid.Info.IfType, pRow->InterfaceLuid.Info.NetLuidIndex)); +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, kIntNetAddrType_IPv6, + &pRow->Address.Ipv6.sin6_addr); +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + break; + } + } + else + Log(("vboxNetLwfWinIpAddrChangeCallback: pRow=%p pfnNotifyHostAddress=%p\n", + pRow, pThis->pSwitchPort->pfnNotifyHostAddress)); +} + +void vboxNetLwfWinRegisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetLwfWinRegisterIpAddrNotifier: instance=%p\n", pThis)); + if ( pThis->pSwitchPort +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + && pThis->pSwitchPort->pfnNotifyHostAddress +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + ) + { + NETIO_STATUS Status; + /* First we need to go over all host IP addresses and add them via pfnNotifyHostAddress. */ + PMIB_UNICASTIPADDRESS_TABLE HostIpAddresses = NULL; + Status = GetUnicastIpAddressTable(AF_UNSPEC, &HostIpAddresses); + if (NETIO_SUCCESS(Status)) + { + for (unsigned i = 0; i < HostIpAddresses->NumEntries; i++) + vboxNetLwfWinIpAddrChangeCallback(pThis, &HostIpAddresses->Table[i], MibAddInstance); + } + else + LogError(("vboxNetLwfWinRegisterIpAddrNotifier: GetUnicastIpAddressTable failed with %x\n", Status)); + /* Now we can register a callback function to keep track of address changes. */ + Status = NotifyUnicastIpAddressChange(AF_UNSPEC, vboxNetLwfWinIpAddrChangeCallback, + pThis, false, &pThis->u.s.WinIf.hNotifier); + if (NETIO_SUCCESS(Status)) + Log(("vboxNetLwfWinRegisterIpAddrNotifier: notifier=%p\n", pThis->u.s.WinIf.hNotifier)); + else + LogError(("vboxNetLwfWinRegisterIpAddrNotifier: NotifyUnicastIpAddressChange failed with %x\n", Status)); + } + else + pThis->u.s.WinIf.hNotifier = NULL; + LogFlow(("<==vboxNetLwfWinRegisterIpAddrNotifier\n")); +} + +void vboxNetLwfWinUnregisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + Log(("vboxNetLwfWinUnregisterIpAddrNotifier: notifier=%p\n", pThis->u.s.WinIf.hNotifier)); + if (pThis->u.s.WinIf.hNotifier) + CancelMibChangeNotify2(pThis->u.s.WinIf.hNotifier); +} + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)pThis->u.s.WinIf.hModuleCtx; + LogFlow(("==>vboxNetFltOsDeleteInstance: instance=%p module=%p\n", pThis, pModuleCtx)); + /* Cancel IP address change notifications */ + vboxNetLwfWinUnregisterIpAddrNotifier(pThis); + /* Technically it is possible that the module has already been gone by now. */ + if (pModuleCtx) + { + Assert(!pModuleCtx->fActive); /* Deactivation ensures bypass mode */ + pModuleCtx->pNetFlt = NULL; + pThis->u.s.WinIf.hModuleCtx = NULL; + } + LogFlow(("<==vboxNetFltOsDeleteInstance\n")); +} + +static void vboxNetLwfWinReportCapabilities(PVBOXNETFLTINS pThis, PVBOXNETLWF_MODULE pModuleCtx) +{ + if (pThis->pSwitchPort + && vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pModuleCtx->MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, + vboxNetLwfWinIsPromiscuous(pModuleCtx)); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, + INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } +} + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + RT_NOREF1(pvContext); + LogFlow(("==>vboxNetFltOsInitInstance: instance=%p context=%p\n", pThis, pvContext)); + AssertReturn(pThis, VERR_INVALID_PARAMETER); + Log(("vboxNetFltOsInitInstance: trunk name=%s\n", pThis->szName)); + NdisAcquireSpinLock(&g_VBoxNetLwfGlobals.Lock); + PVBOXNETLWF_MODULE pModuleCtx; + RTListForEach(&g_VBoxNetLwfGlobals.listModules, pModuleCtx, VBOXNETLWF_MODULE, node) + { + DbgPrint("vboxNetFltOsInitInstance: evaluating module, name=%s\n", pModuleCtx->szMiniportName); + if (!RTStrICmp(pThis->szName, pModuleCtx->szMiniportName)) + { + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + Log(("vboxNetFltOsInitInstance: found matching module, name=%s\n", pThis->szName)); + pThis->u.s.WinIf.hModuleCtx = pModuleCtx; + pModuleCtx->pNetFlt = pThis; + vboxNetLwfWinReportCapabilities(pThis, pModuleCtx); + vboxNetLwfWinRegisterIpAddrNotifier(pThis); + LogFlow(("<==vboxNetFltOsInitInstance: return 0\n")); + return VINF_SUCCESS; + } + } + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + // Internal network code will try to reconnect periodically, we should not spam in event log + //vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 6); + LogFlow(("<==vboxNetFltOsInitInstance: return VERR_INTNET_FLT_IF_NOT_FOUND\n")); + return VERR_INTNET_FLT_IF_NOT_FOUND; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltOsPreInitInstance: instance=%p\n", pThis)); + pThis->u.s.WinIf.hModuleCtx = 0; + pThis->u.s.WinIf.hNotifier = NULL; + LogFlow(("<==vboxNetFltOsPreInitInstance: return 0\n")); + return VINF_SUCCESS; +} + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + RT_NOREF3(pThis, pvIfData, pMac); + LogFlow(("==>vboxNetFltPortOsNotifyMacAddress: instance=%p data=%p mac=%RTmac\n", pThis, pvIfData, pMac)); + LogFlow(("<==vboxNetFltPortOsNotifyMacAddress\n")); +} + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + RT_NOREF3(pThis, pvIf, ppvIfData); + LogFlow(("==>vboxNetFltPortOsConnectInterface: instance=%p if=%p data=%p\n", pThis, pvIf, ppvIfData)); + LogFlow(("<==vboxNetFltPortOsConnectInterface: return 0\n")); + /* Nothing to do */ + return VINF_SUCCESS; +} + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + RT_NOREF2(pThis, pvIfData); + LogFlow(("==>vboxNetFltPortOsDisconnectInterface: instance=%p data=%p\n", pThis, pvIfData)); + LogFlow(("<==vboxNetFltPortOsDisconnectInterface: return 0\n")); + /* Nothing to do */ + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h new file mode 100644 index 00000000..1a0a9e7e --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h @@ -0,0 +1,55 @@ +/* $Id: VBoxNetLwf-win.h $ */ +/** @file + * VBoxNetLwf-win.h - Bridged Networking Driver, Windows-specific code. + */ +/* + * Copyright (C) 2014-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetLwf_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetLwf_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define VBOXNETLWF_VERSION_NDIS_MAJOR 6 +#define VBOXNETLWF_VERSION_NDIS_MINOR 0 + +#define VBOXNETLWF_NAME_FRIENDLY L"VirtualBox NDIS Light-Weight Filter" +#define VBOXNETLWF_NAME_UNIQUE L"{7af6b074-048d-4444-bfce-1ecc8bc5cb76}" +#define VBOXNETLWF_NAME_SERVICE L"VBoxNetLwf" + +#define VBOXNETLWF_NAME_LINK L"\\DosDevices\\Global\\VBoxNetLwf" +#define VBOXNETLWF_NAME_DEVICE L"\\Device\\VBoxNetLwf" + +#define VBOXNETLWF_MEM_TAG 'FLBV' +#define VBOXNETLWF_REQ_ID 'fLBV' + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetLwf_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf new file mode 100644 index 00000000..f5d9cdca --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf @@ -0,0 +1,120 @@ +; $Id: VBoxNetLwf.inf $ +; @file +; VBoxNetLwf.inf - VirtualBox Bridged Networking Driver inf file +; + +; +; Copyright (C) 2014-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 <https://www.gnu.org/licenses>. +; +; 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 +; + +[Version] +Signature = "$Windows NT$" +;cat CatalogFile = VBoxNetLwf.cat +Class = NetService +ClassGUID = {4D36E974-E325-11CE-BFC1-08002BE10318} +Provider = %ORACLE% +;edit-DriverVer=10/23/2014,1.0.1.0 + + +[Manufacturer] +%ORACLE% = VBoxNetLwf@COMMA-NT-ARCH@ + +[ControlFlags] + +[VBoxNetLwf@DOT-NT-ARCH@] +%VBoxNetLwf_Desc% = VBoxNetLwf.ndi, oracle_VBoxNetLwf + +[VBoxNetLwf.ndi] +AddReg = VBoxNetLwf.ndi.AddReg, VBoxNetLwf.AddReg +Characteristics = 0x40000 ; NCF_LW_FILTER +CopyFiles = VBoxNetLwf.Files.Sys +NetCfgInstanceId = "{7af6b074-048d-4444-bfce-1ecc8bc5cb76}" + +[VBoxNetLwf.ndi.Remove.Services] +DelService = VBoxNetLwf,0x200 ; Stop the service before uninstalling + +[VBoxNetLwf.ndi.Services] +AddService = VBoxNetLwf,, VBoxNetLwf.AddService, VBoxNetLwf.AddEventLog + +[VBoxNetLwf.AddService] +DisplayName = %VBoxNetLwfService_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 1 ;SERVICE_SYSTEM_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetLwf.sys +LoadOrderGroup = NDIS +AddReg = VBoxNetLwf.AddService.AddReg + +[VBoxNetLwf.AddService.AddReg] + +[VBoxNetLwf.AddEventLog] +AddReg = VBoxNetLwf.AddEventLog.AddReg + +[VBoxNetLwf.AddEventLog.AddReg] +HKR,,EventMessageFile,0x00020000,"%%SystemRoot%%\System32\IoLogMsg.dll" +HKR,,TypesSupported,0x00010001,7 + + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetLwf.sys=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetLwf.Files.Sys = 12 ; %windir%\System32\drivers + +[VBoxNetLwf.Files.Sys] +VBoxNetLwf.sys,,,2 + + +[VBoxNetLwf.ndi.AddReg] +HKR, Ndi, HelpText, , %VBoxNetLwf_HELP% +;HKR, Ndi, ClsID, 0, {f374d1a0-bf08-4bdc-9cb2-c15ddaeef955} +;HKR, Ndi, ComponentDll, , VBoxNetLwfNobj.dll +HKR, Ndi, FilterClass, , compression +HKR, Ndi, FilterType, 0x10001, 0x2 +HKR, Ndi, FilterRunType,0x10001, 2 ; OPTIONAL, to prevent unbinding of protocol drivers +HKR, Ndi, Service, , VBoxNetLwf +HKR, Ndi, CoServices, 0x10000, VBoxNetLwf +HKR, Ndi\Interfaces, UpperRange, , noupper +HKR, Ndi\Interfaces, LowerRange, , nolower +HKR, Ndi\Interfaces, FilterMediaTypes, , ethernet + +[VBoxNetLwf.AddReg] +;HKR, Parameters, Param1, 0, 4 + +[Strings] +ORACLE = "Oracle Corporation" +DiskDescription = "VirtualBox NDIS6 Bridged Networking Driver" +VBoxNetLwf_Desc = "VirtualBox NDIS6 Bridged Networking Driver" +VBoxNetLwf_HELP = "VirtualBox NDIS6 Bridged Networking Driver" +VBoxNetLwfService_Desc = "VirtualBox NDIS6 Bridged Networking Service" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp new file mode 100644 index 00000000..a5cf004c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp @@ -0,0 +1,747 @@ +/* $Id: VBoxNetFltNobj.cpp $ */ +/** @file + * VBoxNetFltNobj.cpp - Notify Object for Bridged Networking Driver. + * + * Used to filter Bridged Networking Driver bindings + */ + +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include "VBoxNetFltNobj.h" +#include <iprt/win/ntddndis.h> +#include <iprt/win/windows.h> +#include <winreg.h> +#include <Olectl.h> + +#include <VBoxNetFltNobjT_i.c> + +#include <iprt/assert.h> +#include <iprt/utf16.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +//# define VBOXNETFLTNOTIFY_DEBUG_BIND + +#ifdef DEBUG +# define NonStandardAssert(a) Assert(a) +# define NonStandardAssertBreakpoint() AssertFailed() +#else +# define NonStandardAssert(a) do{}while (0) +# define NonStandardAssertBreakpoint() do{}while (0) +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static HMODULE g_hModSelf = (HMODULE)~(uintptr_t)0; + + +VBoxNetFltNobj::VBoxNetFltNobj() + : mpNetCfg(NULL) + , mpNetCfgComponent(NULL) + , mbInstalling(FALSE) +{ +} + +VBoxNetFltNobj::~VBoxNetFltNobj() +{ + cleanup(); +} + +void VBoxNetFltNobj::cleanup() +{ + if (mpNetCfg) + { + mpNetCfg->Release(); + mpNetCfg = NULL; + } + + if (mpNetCfgComponent) + { + mpNetCfgComponent->Release(); + mpNetCfgComponent = NULL; + } +} + +void VBoxNetFltNobj::init(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling) +{ + cleanup(); + + NonStandardAssert(pNetCfg); + NonStandardAssert(pNetCfgComponent); + if (pNetCfg) + { + pNetCfg->AddRef(); + mpNetCfg = pNetCfg; + } + + if (pNetCfgComponent) + { + pNetCfgComponent->AddRef(); + mpNetCfgComponent = pNetCfgComponent; + } + + mbInstalling = bInstalling; +} + +/* INetCfgComponentControl methods */ +STDMETHODIMP VBoxNetFltNobj::Initialize(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling) +{ + init(pNetCfgComponent, pNetCfg, bInstalling); + return S_OK; +} + +STDMETHODIMP VBoxNetFltNobj::ApplyRegistryChanges() +{ + return S_OK; +} + +STDMETHODIMP VBoxNetFltNobj::ApplyPnpChanges(IN INetCfgPnpReconfigCallback *pCallback) +{ + RT_NOREF1(pCallback); + return S_OK; +} + +STDMETHODIMP VBoxNetFltNobj::CancelChanges() +{ + return S_OK; +} + +static HRESULT vboxNetFltWinQueryInstanceKey(IN INetCfgComponent *pComponent, OUT PHKEY phKey) +{ + LPWSTR pwszPnpId; + HRESULT hrc = pComponent->GetPnpDevNodeId(&pwszPnpId); + if (hrc == S_OK) + { + WCHAR wszKeyName[MAX_PATH]; + RTUtf16Copy(wszKeyName, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Enum\\"); + int rc = RTUtf16Cat(wszKeyName, MAX_PATH, pwszPnpId); + if (RT_SUCCESS(rc)) + { + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszKeyName, 0 /*ulOptions*/, KEY_READ, phKey); + if (lrc != ERROR_SUCCESS) + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + } + else + AssertRCStmt(rc, hrc = ERROR_BUFFER_OVERFLOW); + + CoTaskMemFree(pwszPnpId); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinQueryDriverKey(IN HKEY InstanceKey, OUT PHKEY phKey) +{ + HRESULT hrc = S_OK; + + WCHAR wszValue[MAX_PATH]; + DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + LSTATUS lrc = RegQueryValueExW(InstanceKey, L"Driver", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); + if (lrc == ERROR_SUCCESS) + { + if (dwType == REG_SZ) + { + wszValue[RT_ELEMENTS(wszValue) - 1] = '\0'; /* registry strings does not need to be zero terminated. */ + + WCHAR wszKeyName[MAX_PATH]; + RTUtf16Copy(wszKeyName, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Control\\Class\\"); + int rc = RTUtf16Cat(wszKeyName, MAX_PATH, wszValue); + if (RT_SUCCESS(rc)) + { + lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszKeyName, 0 /*ulOptions*/, KEY_READ, phKey); + if (lrc != ERROR_SUCCESS) + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + } + else + AssertRCStmt(rc, hrc = ERROR_BUFFER_OVERFLOW); + } + else + { + hrc = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + NonStandardAssertBreakpoint(); + } + } + else + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + + return hrc; +} + +static HRESULT vboxNetFltWinQueryDriverKey(IN INetCfgComponent *pComponent, OUT PHKEY phKey) +{ + HKEY hKeyInstance = NULL; + HRESULT hrc = vboxNetFltWinQueryInstanceKey(pComponent, &hKeyInstance); + if (hrc == S_OK) + { + hrc = vboxNetFltWinQueryDriverKey(hKeyInstance, phKey); + if (hrc != S_OK) + NonStandardAssertBreakpoint(); + RegCloseKey(hKeyInstance); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinNotifyCheckNetAdp(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + LPWSTR pwszDevId = NULL; + HRESULT hrc = pComponent->GetId(&pwszDevId); + if (hrc == S_OK) + { + /** @todo r=bird: This was _wcsnicmp(pwszDevId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2)) + * which includes the terminator, so it translates to a full compare. Goes way back. */ + if (RTUtf16ICmpAscii(pwszDevId, "sun_VBoxNetAdp") == 0) + *pfShouldBind = false; + else + hrc = S_FALSE; + CoTaskMemFree(pwszDevId); + } + else + NonStandardAssertBreakpoint(); + + return hrc; +} + +static HRESULT vboxNetFltWinNotifyCheckMsLoop(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + LPWSTR pwszDevId = NULL; + HRESULT hrc = pComponent->GetId(&pwszDevId); + if (hrc == S_OK) + { + /** @todo r=bird: This was _wcsnicmp(pwszDevId, L"*msloop", sizeof(L"*msloop")/2) + * which includes the terminator, making it a full compare. Goes way back. */ + if (RTUtf16ICmpAscii(pwszDevId, "*msloop") == 0) + { + /* we need to detect the medium the adapter is presenting + * to do that we could examine in the registry the *msloop params */ + HKEY hKeyDriver; + hrc = vboxNetFltWinQueryDriverKey(pComponent, &hKeyDriver); + if (hrc == S_OK) + { + WCHAR wszValue[64]; /* 2 should be enough actually, paranoid check for extra spaces */ + DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + LSTATUS lrc = RegQueryValueExW(hKeyDriver, L"Medium", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); + if (lrc == ERROR_SUCCESS) + { + if (dwType == REG_SZ) + { + wszValue[RT_ELEMENTS(wszValue) - 1] = '\0'; + + char szUtf8[256]; + char *pszUtf8 = szUtf8; + RTUtf16ToUtf8Ex(wszValue, RTSTR_MAX, &pszUtf8, sizeof(szUtf8), NULL); + pszUtf8 = RTStrStrip(pszUtf8); + + uint64_t uValue = 0; + int rc = RTStrToUInt64Ex(pszUtf8, NULL, 0, &uValue); + if (RT_SUCCESS(rc)) + { + if (uValue == 0) /* 0 is Ethernet */ + *pfShouldBind = true; + else + *pfShouldBind = false; + } + else + { + NonStandardAssertBreakpoint(); + *pfShouldBind = true; + } + } + else + NonStandardAssertBreakpoint(); + } + else + { + /** @todo we should check the default medium in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\<driver_id>\Ndi\Params\Medium, REG_SZ "Default" value */ + NonStandardAssertBreakpoint(); + *pfShouldBind = true; + } + + RegCloseKey(hKeyDriver); + } + else + NonStandardAssertBreakpoint(); + } + else + hrc = S_FALSE; + CoTaskMemFree(pwszDevId); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinNotifyCheckLowerRange(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + HKEY hKeyDriver = NULL; + HRESULT hrc = vboxNetFltWinQueryDriverKey(pComponent, &hKeyDriver); + if (hrc == S_OK) + { + HKEY hKeyInterfaces = NULL; + LSTATUS lrc = RegOpenKeyExW(hKeyDriver, L"Ndi\\Interfaces", 0 /*ulOptions*/, KEY_READ, &hKeyInterfaces); + if (lrc == ERROR_SUCCESS) + { + WCHAR wszValue[MAX_PATH]; + DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + lrc = RegQueryValueExW(hKeyInterfaces, L"LowerRange", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); + if (lrc == ERROR_SUCCESS) + { + if (dwType == REG_SZ) + { + if (RTUtf16FindAscii(wszValue, "ethernet") >= 0 || RTUtf16FindAscii(wszValue, "wan") >= 0) + *pfShouldBind = true; + else + *pfShouldBind = false; + } + } + else + { + /* do not set err status to it */ + *pfShouldBind = false; + NonStandardAssertBreakpoint(); + } + + RegCloseKey(hKeyInterfaces); + } + else + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + + RegCloseKey(hKeyDriver); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + /* filter out only physical adapters */ + DWORD fCharacteristics = 0; + HRESULT hrc = pComponent->GetCharacteristics(&fCharacteristics); + if (hrc != S_OK) + { + NonStandardAssertBreakpoint(); + return hrc; + } + + /* we are not binding to hidden adapters */ + if (fCharacteristics & NCF_HIDDEN) + return S_OK; + + hrc = vboxNetFltWinNotifyCheckMsLoop(pComponent, pfShouldBind); + if ( hrc == S_OK /* this is a loopback adapter, the pfShouldBind already contains the result */ + || hrc != S_FALSE /* error occurred */) + return hrc; + + hrc = vboxNetFltWinNotifyCheckNetAdp(pComponent, pfShouldBind); + if ( hrc == S_OK /* this is a VBoxNetAdp adapter, the pfShouldBind already contains the result */ + || hrc != S_FALSE /* error occurred */) + return hrc; + + //if (!(fCharacteristics & NCF_PHYSICAL)) + //{ + // *pfShouldBind = false; /* we are binding to physical adapters only */ + // return S_OK; + //} + + return vboxNetFltWinNotifyCheckLowerRange(pComponent, pfShouldBind); +} + + +static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgBindingInterface *pIf, OUT bool *pfShouldBind) +{ + INetCfgComponent *pAdapterComponent = NULL; + HRESULT hrc = pIf->GetLowerComponent(&pAdapterComponent); + if (hrc == S_OK) + { + hrc = vboxNetFltWinNotifyShouldBind(pAdapterComponent, pfShouldBind); + + pAdapterComponent->Release(); + } + else + { + NonStandardAssertBreakpoint(); + *pfShouldBind = false; + } + return hrc; +} + +static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgBindingPath *pPath, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + IEnumNetCfgBindingInterface *pIEnumBinding = NULL; + HRESULT hrc = pPath->EnumBindingInterfaces(&pIEnumBinding); + if (hrc == S_OK) + { + hrc = pIEnumBinding->Reset(); + if (hrc == S_OK) + { + for (;;) + { + ULONG uCount = 0; + INetCfgBindingInterface *pIBinding = NULL; + hrc = pIEnumBinding->Next(1, &pIBinding, &uCount); + if (hrc == S_OK) + { + hrc = vboxNetFltWinNotifyShouldBind(pIBinding, pfShouldBind); + pIBinding->Release(); + + if (hrc != S_OK) + break; /* break on failure. */ + if (!*pfShouldBind) + break; + } + else if (hrc == S_FALSE) + { + /* no more elements */ + hrc = S_OK; + break; + } + else + { + NonStandardAssertBreakpoint(); + /* break on falure */ + break; + } + } + } + else + NonStandardAssertBreakpoint(); + + pIEnumBinding->Release(); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static bool vboxNetFltWinNotifyShouldBind(IN INetCfgBindingPath *pPath) +{ +#ifdef VBOXNETFLTNOTIFY_DEBUG_BIND + return VBOXNETFLTNOTIFY_DEBUG_BIND; +#else + bool fShouldBind; + HRESULT hrc = vboxNetFltWinNotifyShouldBind(pPath, &fShouldBind); + if (hrc != S_OK) + fShouldBind = VBOXNETFLTNOTIFY_ONFAIL_BINDDEFAULT; + + return fShouldBind; +#endif +} + + +/* INetCfgComponentNotifyBinding methods */ +STDMETHODIMP VBoxNetFltNobj::NotifyBindingPath(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP) +{ + if (!(dwChangeFlag & NCN_ENABLE) || (dwChangeFlag & NCN_REMOVE) || vboxNetFltWinNotifyShouldBind(pNetCfgBP)) + return S_OK; + return NETCFG_S_DISABLE_QUERY; +} + +STDMETHODIMP VBoxNetFltNobj::QueryBindingPath(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP) +{ + RT_NOREF1(dwChangeFlag); + if (vboxNetFltWinNotifyShouldBind(pNetCfgBP)) + return S_OK; + return NETCFG_S_DISABLE_QUERY; +} + + +static ATL::CComModule _Module; + +BEGIN_OBJECT_MAP(ObjectMap) + OBJECT_ENTRY(CLSID_VBoxNetFltNobj, VBoxNetFltNobj) +END_OBJECT_MAP() + + +extern "C" +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + g_hModSelf = (HMODULE)hInstance; + + _Module.Init(ObjectMap, hInstance); + DisableThreadLibraryCalls(hInstance); + } + else if (dwReason == DLL_PROCESS_DETACH) + { + _Module.Term(); + } + return TRUE; +} + +STDAPI DllCanUnloadNow(void) +{ + return _Module.GetLockCount() == 0 ? S_OK : S_FALSE; +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + return _Module.GetClassObject(rclsid, riid, ppv); +} + + +/* + * ATL::CComModule does not suport server registration/unregistration methods, + * so we need to do it manually. Since this is the only place we do registraton + * manually, we do it the quick-and-dirty way. + */ + +#ifdef RT_EXCEPTIONS_ENABLED +/* Someday we may want to log errors. */ +class AdHocRegError +{ +public: + AdHocRegError(LSTATUS rc) { RT_NOREF1(rc); }; +}; +#endif + +/** + * A simple wrapper on Windows registry functions. + */ +class AdHocRegKey +{ +public: + AdHocRegKey(HKEY hKey) : m_hKey(hKey) {}; + AdHocRegKey(LPCWSTR pcwszName, HKEY hParent = HKEY_CLASSES_ROOT); + ~AdHocRegKey() { RegCloseKey(m_hKey); }; + + AdHocRegKey *create(LPCWSTR pcwszSubkey); + LSTATUS setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue); + HKEY getKey(void) { return m_hKey; }; +private: + HKEY m_hKey; +}; + +AdHocRegKey::AdHocRegKey(LPCWSTR pcwszName, HKEY hParent) : m_hKey(NULL) +{ + LSTATUS rc = RegOpenKeyExW(hParent, pcwszName, 0, KEY_ALL_ACCESS, &m_hKey); + if (rc != ERROR_SUCCESS) +#ifdef RT_EXCEPTIONS_ENABLED + throw AdHocRegError(rc); +#else + m_hKey = NULL; +#endif +} + +AdHocRegKey *AdHocRegKey::create(LPCWSTR pcwszSubkey) +{ + HKEY hSubkey; + LSTATUS rc = RegCreateKeyExW(m_hKey, pcwszSubkey, + 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, + KEY_ALL_ACCESS, NULL /*pSecAttr*/, &hSubkey, NULL /*pdwDisposition*/); + if (rc != ERROR_SUCCESS) +#ifdef RT_EXCEPTIONS_ENABLED + throw AdHocRegError(rc); +#else + return NULL; +#endif + AdHocRegKey *pSubkey = new AdHocRegKey(hSubkey); + if (!pSubkey) + RegCloseKey(hSubkey); + return pSubkey; +} + +LSTATUS AdHocRegKey::setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue) +{ + LSTATUS rc = RegSetValueExW(m_hKey, pcwszName, 0, REG_SZ, (const BYTE *)pcwszValue, + (DWORD)((RTUtf16Len(pcwszValue) + 1) * sizeof(WCHAR))); +#ifdef RT_EXCEPTIONS_ENABLED + if (rc != ERROR_SUCCESS) + throw AdHocRegError(rc); +#endif + return rc; +} + +/** + * Auxiliary class that facilitates automatic destruction of AdHocRegKey objects + * allocated in heap. No reference counting here! + */ +class AdHocRegKeyPtr +{ +public: + AdHocRegKeyPtr(AdHocRegKey *pKey) : m_pKey(pKey) {}; + ~AdHocRegKeyPtr() + { + if (m_pKey) + { + delete m_pKey; + m_pKey = NULL; + } + } + + AdHocRegKey *create(LPCWSTR pcwszSubkey) + { return m_pKey ? m_pKey->create(pcwszSubkey) : NULL; }; + + LSTATUS setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue) + { return m_pKey ? m_pKey->setValue(pcwszName, pcwszValue) : ERROR_INVALID_STATE; }; + +private: + AdHocRegKey *m_pKey; + /* Prevent copying, since we do not support reference counting */ + AdHocRegKeyPtr(const AdHocRegKeyPtr&); + AdHocRegKeyPtr& operator=(const AdHocRegKeyPtr&); +}; + + +STDAPI DllRegisterServer(void) +{ + /* Get the path to the DLL we're running inside. */ + WCHAR wszModule[MAX_PATH + 1]; + UINT cwcModule = GetModuleFileNameW(g_hModSelf, wszModule, MAX_PATH); + if (cwcModule == 0 || cwcModule > MAX_PATH) + return SELFREG_E_CLASS; + wszModule[MAX_PATH] = '\0'; + + /* + * Create registry keys and values. When exceptions are disabled, we depend + * on setValue() to propagate fail key creation failures. + */ +#ifdef RT_EXCEPTIONS_ENABLED + try +#endif + { + AdHocRegKey keyCLSID(L"CLSID"); + AdHocRegKeyPtr pkeyNobjClass(keyCLSID.create(L"{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}")); + LSTATUS lrc = pkeyNobjClass.setValue(NULL, L"VirtualBox Bridged Networking Driver Notify Object v1.1"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_CLASS; + + AdHocRegKeyPtr pkeyNobjSrv(pkeyNobjClass.create(L"InProcServer32")); + lrc = pkeyNobjSrv.setValue(NULL, wszModule); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_CLASS; + lrc = pkeyNobjSrv.setValue(L"ThreadingModel", L"Both"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_CLASS; + } +#ifdef RT_EXCEPTIONS_ENABLED + catch (AdHocRegError) { return SELFREG_E_CLASS; } +#endif + +#ifdef RT_EXCEPTIONS_ENABLED + try +#endif + { + AdHocRegKey keyTypeLib(L"TypeLib"); + AdHocRegKeyPtr pkeyNobjLib(keyTypeLib.create(L"{2A0C94D1-40E1-439C-8FE8-24107CAB0840}\\1.1")); + LSTATUS lrc = pkeyNobjLib.setValue(NULL, L"VirtualBox Bridged Networking Driver Notify Object v1.1 Type Library"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + + AdHocRegKeyPtr pkeyNobjLib0(pkeyNobjLib.create(L"0\\win64")); + lrc = pkeyNobjLib0.setValue(NULL, wszModule); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + AdHocRegKeyPtr pkeyNobjLibFlags(pkeyNobjLib.create(L"FLAGS")); + lrc = pkeyNobjLibFlags.setValue(NULL, L"0"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + + if (GetSystemDirectoryW(wszModule, MAX_PATH) == 0) + return SELFREG_E_TYPELIB; + AdHocRegKeyPtr pkeyNobjLibHelpDir(pkeyNobjLib.create(L"HELPDIR")); + lrc = pkeyNobjLibHelpDir.setValue(NULL, wszModule); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + } +#ifdef RT_EXCEPTIONS_ENABLED + catch (AdHocRegError) { return SELFREG_E_TYPELIB; } +#endif + + return S_OK; +} + + +STDAPI DllUnregisterServer(void) +{ + static struct { HKEY hKeyRoot; wchar_t const *pwszParentKey; wchar_t const *pwszKeyToDelete; HRESULT hrcFail; } + s_aKeys[] = + { + { HKEY_CLASSES_ROOT, L"TypeLib", L"{2A0C94D1-40E1-439C-8FE8-24107CAB0840}", SELFREG_E_TYPELIB }, + { HKEY_CLASSES_ROOT, L"CLSID", L"{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}", SELFREG_E_CLASS }, + }; + + HRESULT hrc = S_OK; + for (size_t i = 0; i < RT_ELEMENTS(s_aKeys); i++) + { + HKEY hKey = NULL; + LSTATUS lrc = RegOpenKeyExW(s_aKeys[i].hKeyRoot, s_aKeys[i].pwszParentKey, 0, KEY_ALL_ACCESS, &hKey); + if (lrc == ERROR_SUCCESS) + { + lrc = RegDeleteTreeW(hKey, s_aKeys[i].pwszKeyToDelete); /* Vista and later */ + RegCloseKey(hKey); + } + + if (lrc != ERROR_SUCCESS && lrc != ERROR_FILE_NOT_FOUND && hrc == S_OK) + hrc = s_aKeys[i].hrcFail; + } + + return S_OK; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def new file mode 100644 index 00000000..abfe3834 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def @@ -0,0 +1,42 @@ +; $Id: VBoxNetFltNobj.def $ +; @file +; VBoxNetFltNobj.def - Notify Object for Bridged Networking Driver. +; Library def file +; + +; +; Copyright (C) 2011-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 <https://www.gnu.org/licenses>. +; +; 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 +; +LIBRARY VBoxNetFltNobj +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h new file mode 100644 index 00000000..87eed992 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h @@ -0,0 +1,96 @@ +/* $Id: VBoxNetFltNobj.h $ */ +/** @file + * VBoxNetFltNobj.h - Notify Object for Bridged Networking Driver. + * Used to filter Bridged Networking Driver bindings + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobj_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobj_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/win/windows.h> + +#include "VBox/com/defs.h" +#include "VBoxNetFltNobjT.h" +#include "VBoxNetFltNobjRc.h" + +#define VBOXNETFLTNOTIFY_ONFAIL_BINDDEFAULT false + +/* + * VirtualBox Bridging driver notify object. + * Needed to make our driver bind to "real" host adapters only + */ +class ATL_NO_VTABLE VBoxNetFltNobj + : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel> + , public ATL::CComCoClass<VBoxNetFltNobj, &CLSID_VBoxNetFltNobj> + , public INetCfgComponentControl + , public INetCfgComponentNotifyBinding +{ +public: + VBoxNetFltNobj(); + virtual ~VBoxNetFltNobj(); + + BEGIN_COM_MAP(VBoxNetFltNobj) + COM_INTERFACE_ENTRY(INetCfgComponentControl) + COM_INTERFACE_ENTRY(INetCfgComponentNotifyBinding) + END_COM_MAP() + + // this is a "just in case" conditional, which is not defined +#ifdef VBOX_FORCE_REGISTER_SERVER + DECLARE_REGISTRY_RESOURCEID(IDR_VBOXNETFLT_NOBJ) +#endif + + /* INetCfgComponentControl methods */ + STDMETHOD(Initialize)(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling); + STDMETHOD(ApplyRegistryChanges)(); + STDMETHOD(ApplyPnpChanges)(IN INetCfgPnpReconfigCallback *pCallback); + STDMETHOD(CancelChanges)(); + + /* INetCfgComponentNotifyBinding methods */ + STDMETHOD(NotifyBindingPath)(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP); + STDMETHOD(QueryBindingPath)(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP); +private: + + void init(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling); + void cleanup(); + + /* these two used to maintain the component info passed to + * INetCfgComponentControl::Initialize */ + INetCfg *mpNetCfg; + INetCfgComponent *mpNetCfgComponent; + BOOL mbInstalling; +}; + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobj_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc new file mode 100644 index 00000000..4a1398c6 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc @@ -0,0 +1,78 @@ +/* $Id: VBoxNetFltNobj.rc $ */ +/** @file + * VBoxNetFltNobj - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#include <windows.h> +#include <VBox/version.h> + +#include "VBoxNetFltNobjRc.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual + BEGIN + VALUE "FileDescription", "VirtualBox Bridged Networking Driver Notify Object v1.1\0" + VALUE "InternalName", "VBoxNetFltNobj\0" + VALUE "OriginalFilename", "VBoxNetFltNobj.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// REGISTRY +// + +IDR_VBOXNETFLT_NOBJ REGISTRY "VBoxNetFltNobj.rgs" + +1 TYPELIB "VBoxNetFltNobjT.tlb" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs new file mode 100644 index 00000000..d6f9bae3 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs @@ -0,0 +1,13 @@ +HKCR +{ + NoRemove CLSID + { + ForceRemove {f374d1a0-bf08-4bdc-9cb2-c15ddaeef955} = s 'VirtualBox Bridged Networking Driver Notify Object v1.1' + { + InProcServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Both' + } + } + } +}
\ No newline at end of file diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h new file mode 100644 index 00000000..187af500 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h @@ -0,0 +1,46 @@ +/* $Id: VBoxNetFltNobjRc.h $ */ +/** @file + * VBoxNetFltNobjRc.h - Notify Object for Bridged Networking Driver. + * Resource definitions + */ +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobjRc_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobjRc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* registry script rc ID */ +#define IDR_VBOXNETFLT_NOBJ 101 + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobjRc_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl new file mode 100644 index 00000000..9bf8b6f9 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl @@ -0,0 +1,55 @@ +/* $Id: VBoxNetFltNobjT.idl $ */ +/** @file + * VBoxNetFltNobjT.idl - Notify Object for Bridged Networking Driver, typelib definition. + */ + +/* + * Copyright (C) 2011-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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#include <netcfgn.idl> + +[ + uuid(2a0c94d1-40e1-439c-8fe8-24107cab0840), + version(1.1), + helpstring("VirtualBox Bridged Networking Driver Notify Object v1.1 Type Library") +] +library VBoxNetFltNobjLib +{ + [ + uuid(f374d1a0-bf08-4bdc-9cb2-c15ddaeef955), + helpstring("VirtualBox Bridged Networking Driver Notify Object Class") + ] + coclass VBoxNetFltNobj + { + [restricted] interface INetCfgComponentControl; + [restricted] interface INetCfgComponentNotifyBinding; + }; +}; diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp new file mode 100644 index 00000000..40a79b1a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp @@ -0,0 +1,341 @@ +/* $Id: VBoxNetAdpInstall.cpp $ */ +/** @file + * NetAdpInstall - VBoxNetAdp installer command line tool. + */ + +/* + * Copyright (C) 2009-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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/VBoxNetCfg-win.h> +#include <VBox/VBoxDrvCfg-win.h> +#include <devguid.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/process.h> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_NETADP_APP_NAME L"NetAdpInstall" + +#define VBOX_NETADP_HWID L"sun_VBoxNetAdp" +#ifdef NDIS60 +# define VBOX_NETADP_INF L"VBoxNetAdp6.inf" +#else +# define VBOX_NETADP_INF L"VBoxNetAdp.inf" +#endif + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + + +/** Wrapper around GetfullPathNameW that will try an alternative INF location. + * + * The default location is the current directory. If not found there, the + * alternative location is the executable directory. If not found there either, + * the first alternative is present to the caller. + */ +static DWORD MyGetfullPathNameW(LPCWSTR pwszName, size_t cchFull, LPWSTR pwszFull) +{ + LPWSTR pwszFilePart; + DWORD dwSize = GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, &pwszFilePart); + if (dwSize <= 0) + return dwSize; + + /* if it doesn't exist, see if the file exists in the same directory as the executable. */ + if (GetFileAttributesW(pwszFull) == INVALID_FILE_ATTRIBUTES) + { + WCHAR wsz[512]; + DWORD cch = GetModuleFileNameW(GetModuleHandle(NULL), &wsz[0], RT_ELEMENTS(wsz)); + if (cch > 0) + { + while (cch > 0 && wsz[cch - 1] != '/' && wsz[cch - 1] != '\\' && wsz[cch - 1] != ':') + cch--; + unsigned i = 0; + while (cch < sizeof(wsz) / sizeof(wsz[0])) + { + wsz[cch] = pwszFilePart[i++]; + if (!wsz[cch]) + { + dwSize = GetFullPathNameW(wsz, (DWORD)cchFull, pwszFull, NULL); + if (dwSize > 0 && GetFileAttributesW(pwszFull) != INVALID_FILE_ATTRIBUTES) + return dwSize; + break; + } + cch++; + } + } + } + + /* fallback */ + return GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, NULL); +} + + +static int VBoxNetAdpInstall(void) +{ + RTMsgInfo("Adding host-only interface..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + WCHAR wszInfFile[MAX_PATH]; + DWORD cwcInfFile = MyGetfullPathNameW(VBOX_NETADP_INF, RT_ELEMENTS(wszInfFile), wszInfFile); + if (cwcInfFile > 0) + { + INetCfg *pnc; + LPWSTR lpszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETADP_APP_NAME, 10000, &lpszLockedBy); + if (hr == S_OK) + { + + hr = VBoxNetCfgWinNetAdpInstall(pnc, wszInfFile); + + if (hr == S_OK) + RTMsgInfo("Installed successfully!"); + else + RTMsgError("failed to install VBoxNetAdp: %Rhrc", hr); + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + } + else + RTMsgError("VBoxNetCfgWinQueryINetCfg failed: %Rhrc", hr); + /* + hr = VBoxDrvCfgInfInstall(MpInf); + if (FAILED(hr)) + printf("VBoxDrvCfgInfInstall failed %#x\n", hr); + + GUID guid; + BSTR name, errMsg; + + hr = VBoxNetCfgWinCreateHostOnlyNetworkInterface (MpInf, true, &guid, &name, &errMsg); + if (SUCCEEDED(hr)) + { + ULONG ip, mask; + hr = VBoxNetCfgWinGenHostOnlyNetworkNetworkIp(&ip, &mask); + if (SUCCEEDED(hr)) + { + // ip returned by VBoxNetCfgWinGenHostOnlyNetworkNetworkIp is a network ip, + // i.e. 192.168.xxx.0, assign 192.168.xxx.1 for the hostonly adapter + ip = ip | (1 << 24); + hr = VBoxNetCfgWinEnableStaticIpConfig(&guid, ip, mask); + if (SUCCEEDED(hr)) + printf("installation successful\n"); + else + printf("VBoxNetCfgWinEnableStaticIpConfig failed: hr=%#lx\n", hr); + } + else + printf("VBoxNetCfgWinGenHostOnlyNetworkNetworkIp failed: hr=%#lx\n", hr); + } + else + printf("VBoxNetCfgWinCreateHostOnlyNetworkInterface failed: hr=%#lx\n", hr); + */ + } + else + { + DWORD dwErr = GetLastError(); + RTMsgError("MyGetfullPathNameW failed: %Rwc", dwErr); + hr = HRESULT_FROM_WIN32(dwErr); + } + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpUninstall(void) +{ + RTMsgInfo("Uninstalling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinRemoveAllNetDevicesOfId(VBOX_NETADP_HWID); + if (SUCCEEDED(hr)) + { + hr = VBoxDrvCfgInfUninstallAllSetupDi(&GUID_DEVCLASS_NET, L"Net", VBOX_NETADP_HWID, 0/* could be SUOI_FORCEDELETE */); + if (SUCCEEDED(hr)) + RTMsgInfo("Uninstallation successful!"); + else + RTMsgWarning("uninstalled successfully, but failed to remove infs (%Rhrc)\n", hr); + } + else + RTMsgError("uninstall failed: %Rhrc", hr); + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpUpdate(void) +{ + RTMsgInfo("Uninstalling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + BOOL fRebootRequired = FALSE; + /* + * Before we can update the driver for existing adapters we need to remove + * all old driver packages from the driver cache. Otherwise we may end up + * with both NDIS5 and NDIS6 versions of VBoxNetAdp in the cache which + * will cause all sorts of trouble. + */ + VBoxDrvCfgInfUninstallAllF(L"Net", VBOX_NETADP_HWID, SUOI_FORCEDELETE); + hr = VBoxNetCfgWinUpdateHostOnlyNetworkInterface(VBOX_NETADP_INF, &fRebootRequired, VBOX_NETADP_HWID); + if (SUCCEEDED(hr)) + { + if (fRebootRequired) + RTMsgWarning("!!REBOOT REQUIRED!!"); + RTMsgInfo("Updated successfully!"); + } + else + RTMsgError("update failed: %Rhrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpDisable(void) +{ + RTMsgInfo("Disabling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinPropChangeAllNetDevicesOfId(VBOX_NETADP_HWID, VBOXNECTFGWINPROPCHANGE_TYPE_DISABLE); + if (SUCCEEDED(hr)) + RTMsgInfo("Disabling successful"); + else + RTMsgError("disable failed: %Rhrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpEnable(void) +{ + RTMsgInfo("Enabling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinPropChangeAllNetDevicesOfId(VBOX_NETADP_HWID, VBOXNECTFGWINPROPCHANGE_TYPE_ENABLE); + if (SUCCEEDED(hr)) + RTMsgInfo("Enabling successful!"); + else + RTMsgError("enabling failed: %hrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static void printUsage(void) +{ + RTPrintf("host-only network adapter configuration tool\n" + " Usage: %s [cmd]\n" + " cmd can be one of the following values:\n" + " i - install a new host-only interface (default command)\n" + " u - uninstall all host-only interfaces\n" + " a - update the host-only driver\n" + " d - disable all host-only interfaces\n" + " e - enable all host-only interfaces\n" + " h - print this message\n", + RTProcShortName()); +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + if (argc < 2) + return VBoxNetAdpInstall(); + if (argc > 2) + { + printUsage(); + return RTEXITCODE_SYNTAX; + } + + if (!strcmp(argv[1], "i")) + return VBoxNetAdpInstall(); + if (!strcmp(argv[1], "u")) + return VBoxNetAdpUninstall(); + if (!strcmp(argv[1], "a")) + return VBoxNetAdpUpdate(); + if (!strcmp(argv[1], "d")) + return VBoxNetAdpDisable(); + if (!strcmp(argv[1], "e")) + return VBoxNetAdpEnable(); + + printUsage(); + return !strcmp(argv[1], "h") ? RTEXITCODE_SUCCESS : RTEXITCODE_SYNTAX; +} diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp new file mode 100644 index 00000000..03508669 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp @@ -0,0 +1,107 @@ +/* $Id: VBoxNetAdpUninstall.cpp $ */ +/** @file + * NetAdpUninstall - VBoxNetAdp uninstaller command line tool + */ + +/* + * Copyright (C) 2009-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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/VBoxNetCfg-win.h> +#include <VBox/VBoxDrvCfg-win.h> + +#include <devguid.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef NDIS60 +# define VBOX_NETADP_HWID L"sun_VBoxNetAdp6" +#else +# define VBOX_NETADP_HWID L"sun_VBoxNetAdp" +#endif + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + +static int VBoxNetAdpUninstall(void) +{ + RTMsgInfo("Uninstalling all Host-Only interfaces ..."); + + int rcExit = RTEXITCODE_FAILURE; + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + hr = VBoxNetCfgWinRemoveAllNetDevicesOfId(VBOX_NETADP_HWID); + if (hr == S_OK) + { + hr = VBoxDrvCfgInfUninstallAllSetupDi(&GUID_DEVCLASS_NET, L"Net", VBOX_NETADP_HWID, 0/* could be SUOI_FORCEDELETE */); + if (hr == S_OK) + RTMsgInfo("Uninstalled successfully!"); + else + RTMsgError("uninstalled successfully, but failed to remove infs (%Rhrc)\n", hr); + rcExit = RTEXITCODE_SUCCESS; + } + else + RTMsgError("uninstall failed: %Rhrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetAdpUninstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp new file mode 100644 index 00000000..e6c4f2de --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp @@ -0,0 +1,201 @@ +/* $Id: VBoxNetFltInstall.cpp $ */ +/** @file + * NetFltInstall - VBoxNetFlt installer command line tool + */ + +/* + * 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/VBoxNetCfg-win.h> +#include <devguid.h> +#include <stdio.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define NETFLT_ID L"sun_VBoxNetFlt" +#define VBOX_NETCFG_APP_NAME L"NetFltInstall" +#define VBOX_NETFLT_PT_INF L".\\VBoxNetFlt.inf" +#define VBOX_NETFLT_MP_INF L".\\VBoxNetFltM.inf" +#define VBOX_NETFLT_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + printf("%s", pszString); +} + +/** Wrapper around GetfullPathNameW that will try an alternative INF location. + * + * The default location is the current directory. If not found there, the + * alternative location is the executable directory. If not found there either, + * the first alternative is present to the caller. + */ +static DWORD MyGetfullPathNameW(LPCWSTR pwszName, size_t cchFull, LPWSTR pwszFull) +{ + LPWSTR pwszFilePart; + DWORD dwSize = GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, &pwszFilePart); + if (dwSize <= 0) + return dwSize; + + /* if it doesn't exist, see if the file exists in the same directory as the executable. */ + if (GetFileAttributesW(pwszFull) == INVALID_FILE_ATTRIBUTES) + { + WCHAR wsz[512]; + DWORD cch = GetModuleFileNameW(GetModuleHandle(NULL), &wsz[0], RT_ELEMENTS(wsz)); + if (cch > 0) + { + while (cch > 0 && wsz[cch - 1] != '/' && wsz[cch - 1] != '\\' && wsz[cch - 1] != ':') + cch--; + unsigned i = 0; + while (cch < RT_ELEMENTS(wsz)) + { + wsz[cch] = pwszFilePart[i++]; + if (!wsz[cch]) + { + dwSize = GetFullPathNameW(wsz, (DWORD)cchFull, pwszFull, NULL); + if (dwSize > 0 && GetFileAttributesW(pwszFull) != INVALID_FILE_ATTRIBUTES) + return dwSize; + break; + } + cch++; + } + } + } + + /* fallback */ + return GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, NULL); +} + +static int VBoxNetFltInstall() +{ + WCHAR wszPtInf[MAX_PATH]; + WCHAR wszMpInf[MAX_PATH]; + INetCfg *pnc; + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + DWORD dwSize; + dwSize = MyGetfullPathNameW(VBOX_NETFLT_PT_INF, RT_ELEMENTS(wszPtInf), wszPtInf); + if (dwSize > 0) + { + /** @todo add size check for (RT_ELEMENTS(wszPtInf) == dwSize (string length in WCHARs) */ + + dwSize = MyGetfullPathNameW(VBOX_NETFLT_MP_INF, RT_ELEMENTS(wszMpInf), wszMpInf); + if (dwSize > 0) + { + /** @todo add size check for (RT_ELEMENTS(wszMpInf) == dwSize (string length in WHCARs) */ + + LPCWSTR apwszInfs[] = { wszPtInf, wszMpInf }; + hr = VBoxNetCfgWinNetFltInstall(pnc, apwszInfs, 2); + if (hr == S_OK) + { + wprintf(L"installed successfully\n"); + rcExit = RTEXITCODE_SUCCESS; + } + else + wprintf(L"error installing VBoxNetFlt (%#lx)\n", hr); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + wprintf(L"error getting full inf path for VBoxNetFltM.inf (%#lx)\n", hr); + } + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + wprintf(L"error getting full inf path for VBoxNetFlt.inf (%#lx)\n", hr); + } + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETFLT_RETRIES && !wcscmp(pwszLockedBy, L"6to4svc.dll")) + { + wprintf(L"6to4svc.dll is holding the lock, retrying %d out of %d\n", i + 1, VBOX_NETFLT_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + wprintf(L"Error: write lock is owned by another application (%s), close the application and retry installing\n", pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + wprintf(L"Error getting the INetCfg interface (%#lx)\n", hr); + break; + } + } + + CoUninitialize(); + } + else + wprintf(L"Error initializing COM (%#lx)\n", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetFltInstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp new file mode 100644 index 00000000..c62d7726 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp @@ -0,0 +1,133 @@ +/* $Id: VBoxNetFltUninstall.cpp $ */ +/** @file + * NetFltUninstall - VBoxNetFlt uninstaller command line tool + */ + +/* + * 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/VBoxNetCfg-win.h> +#include <stdio.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define NETFLT_ID L"sun_VBoxNetFlt" +#define VBOX_NETCFG_APP_NAME L"NetFltUninstall" +#define VBOX_NETFLT_PT_INF L".\\VBoxNetFlt.inf" +#define VBOX_NETFLT_MP_INF L".\\VBoxNetFltM.inf" +#define VBOX_NETFLT_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + printf("%s", pszString); +} + +static int VBoxNetFltUninstall() +{ + INetCfg *pnc; + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + hr = VBoxNetCfgWinNetFltUninstall(pnc); + if (hr != S_OK && hr != S_FALSE) + wprintf(L"error uninstalling VBoxNetFlt (%#lx)\n", hr); + else + { + wprintf(L"uninstalled successfully\n"); + rcExit = RTEXITCODE_SUCCESS; + } + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETFLT_RETRIES && !wcscmp(pwszLockedBy, L"6to4svc.dll")) + { + wprintf(L"6to4svc.dll is holding the lock, retrying %d out of %d\n", i + 1, VBOX_NETFLT_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + wprintf(L"Error: write lock is owned by another application (%s), close the application and retry uninstalling\n", + pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + wprintf(L"Error getting the INetCfg interface (%#lx)\n", hr); + break; + } + } + + CoUninitialize(); + } + else + wprintf(L"Error initializing COM (%#lx)\n", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetFltUninstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp new file mode 100644 index 00000000..1b8cf693 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp @@ -0,0 +1,186 @@ +/* $Id: VBoxNetLwfInstall.cpp $ */ +/** @file + * NetLwfInstall - VBoxNetLwf installer command line tool + */ + +/* + * Copyright (C) 2014-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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/VBoxNetCfg-win.h> +#include <devguid.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_NETCFG_APP_NAME L"NetLwfInstall" +#define VBOX_NETLWF_INF L".\\VBoxNetLwf.inf" +#define VBOX_NETLWF_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + +/** Wrapper around GetfullPathNameW that will try an alternative INF location. + * + * The default location is the current directory. If not found there, the + * alternative location is the executable directory. If not found there either, + * the first alternative is present to the caller. + */ +static DWORD MyGetfullPathNameW(LPCWSTR pwszName, size_t cchFull, LPWSTR pwszFull) +{ + LPWSTR pwszFilePart; + DWORD dwSize = GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, &pwszFilePart); + if (dwSize <= 0) + return dwSize; + + /* if it doesn't exist, see if the file exists in the same directory as the executable. */ + if (GetFileAttributesW(pwszFull) == INVALID_FILE_ATTRIBUTES) + { + WCHAR wsz[512]; + DWORD cch = GetModuleFileNameW(GetModuleHandle(NULL), &wsz[0], RT_ELEMENTS(wsz)); + if (cch > 0) + { + while (cch > 0 && wsz[cch - 1] != '/' && wsz[cch - 1] != '\\' && wsz[cch - 1] != ':') + cch--; + unsigned i = 0; + while (cch < RT_ELEMENTS(wsz)) + { + wsz[cch] = pwszFilePart[i++]; + if (!wsz[cch]) + { + dwSize = GetFullPathNameW(wsz, (DWORD)cchFull, pwszFull, NULL); + if (dwSize > 0 && GetFileAttributesW(pwszFull) != INVALID_FILE_ATTRIBUTES) + return dwSize; + break; + } + cch++; + } + } + } + + /* fallback */ + return GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, NULL); +} + +static int VBoxNetLwfInstall() +{ + WCHAR wszInf[MAX_PATH]; + INetCfg *pnc; + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + DWORD dwSize; + dwSize = MyGetfullPathNameW(VBOX_NETLWF_INF, RT_ELEMENTS(wszInf), wszInf); + if (dwSize > 0) + { + /** @todo add size check for (RT_ELEMENTS(wszInf) == dwSize (string length in WCHARs) */ + hr = VBoxNetCfgWinNetLwfInstall(pnc, wszInf); + if (hr == S_OK) + { + RTMsgInfo("Installed successfully!"); + rcExit = RTEXITCODE_SUCCESS; + } + else + RTMsgError("Failed installing VBoxNetLwf: %Rhrc", hr); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + RTMsgError("Failed getting full inf path for VBoxNetLwf.inf: %Rhrc", hr); + } + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETLWF_RETRIES && RTUtf16ICmpAscii(pwszLockedBy, "6to4svc.dll") == 0) + { + RTMsgInfo("6to4svc.dll is holding the lock - retrying %d out of %d\n", i + 1, VBOX_NETLWF_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + RTMsgError("write lock is owned by another application (%ls), close the application and retry installing", + pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + RTMsgError("Failed getting the INetCfg interface: %Rhrc", hr); + break; + } + } + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetLwfInstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp new file mode 100644 index 00000000..0648a127 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp @@ -0,0 +1,130 @@ +/* $Id: VBoxNetLwfUninstall.cpp $ */ +/** @file + * NetLwfUninstall - VBoxNetLwf uninstaller command line tool + */ + +/* + * Copyright (C) 2014-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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/VBoxNetCfg-win.h> + +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/utf16.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_NETCFG_APP_NAME L"NetLwfUninstall" +#define VBOX_NETLWF_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + +static int VBoxNetLwfUninstall() +{ + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + INetCfg *pnc = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + hr = VBoxNetCfgWinNetLwfUninstall(pnc); + if (hr == S_OK) + { + RTMsgInfo("uninstalled successfully!"); + rcExit = RTEXITCODE_SUCCESS; + } + else + RTMsgError("error uninstalling VBoxNetLwf: %Rhrc"); + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETLWF_RETRIES && RTUtf16ICmpAscii(pwszLockedBy, "6to4svc.dll") == 0) + { + RTMsgInfo("6to4svc.dll is holding the lock - retry %d out of %d ...", i + 1, VBOX_NETLWF_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + RTMsgError("Write lock is owned by another application (%ls), close the application and retry uninstalling", + pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + RTMsgError("Failed getting the INetCfg interface: %Rhrc", hr); + break; + } + } + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetLwfUninstall(); +} + |