├── .gitignore ├── COPYING.txt ├── NOTICE.txt ├── src ├── vmw_conn_notify.conf ├── Makefile ├── include │ └── vmw_conn.h ├── vmw_conn_notify_watchdog.sh ├── vmw_conn_notifyd ├── vmw_conn_main.c └── vmw_conn_netfilter.c ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/guest-introspection-nsx/HEAD/COPYING.txt -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Guest Introspection for VMware NSX 2 | 3 | Copyright (c) 2018 VMware, Inc. All Rights Reserved. 4 | 5 | This product is licensed to you under the GNU GENERAL PUBLIC LICENSE Version 2 license (the "License"). You may not use this product except in compliance with the GPL 2.0 License. 6 | 7 | This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. 8 | 9 | -------------------------------------------------------------------------------- /src/vmw_conn_notify.conf: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 VMware, Inc. All rights reserved. 2 | # 3 | # This program is free software; you can redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published 5 | # by the Free Software Foundation; version 2. 6 | # 7 | # This program is distributed in the hope that it will be useful, but 8 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 9 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 10 | # License for more details. 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software Foundation, Inc., 14 | # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 15 | # 16 | 17 | # 18 | # SPDX-License-Identifier: GPL-2.0-only 19 | # 20 | 21 | # 22 | # Configuration required for vmw_conn_notify 23 | # 24 | [VMW_CONN_NOTIFY_CONFIG] 25 | DEBUG_LEVEL=4 26 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2018 VMware, Inc. All rights reserved. 3 | # 4 | # This program is free software; you can redistribute it and/or modify it 5 | # under the terms of the GNU General Public License as published 6 | # by the Free Software Foundation; version 2. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 10 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 11 | # License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public License 14 | # along with this program; if not, write to the Free Software Foundation, Inc., 15 | # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 16 | # 17 | 18 | # 19 | # SPDX-License-Identifier: GPL-2.0-only 20 | # 21 | 22 | MKFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) 23 | SRCDIR := $(dir $(abspath $(MKFILE))) 24 | SRCROOT := $(SRCDIR:%/=%) 25 | 26 | default: vmw_conn_oss 27 | 28 | all: vmw_conn_oss install 29 | 30 | ifndef LIB_NETFILTER_INCLUDE 31 | LIB_NETFILTER_INCLUDE := -I/usr/src/debug/libnetfilter_conntrack-1.0.6/include 32 | endif 33 | 34 | CC := gcc 35 | 36 | GLIB_INCLUDES := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include 37 | 38 | INCLUDES := \ 39 | -I./include/ \ 40 | -I/usr/include \ 41 | -I/usr/lib/x86_64-linux-gnu/glib-2.0/include \ 42 | $(GLIB_INCLUDES) \ 43 | $(LIB_NETFILTER_INCLUDE) 44 | 45 | CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -g $(INCLUDES) 46 | 47 | LDFLAGS := \ 48 | -L/usr/lib64/ \ 49 | -L/usr/lib/x86_64-linux-gnu/lib \ 50 | -lpthread \ 51 | -lglib-2.0 \ 52 | -lmnl \ 53 | -lnfnetlink \ 54 | -lnetfilter_queue \ 55 | -lnetfilter_conntrack 56 | 57 | VMW_OPEN_SOURCE_BIN := $(SRCROOT)/vmw_conn_notify 58 | 59 | VMW_INSTALL_CONFDIR := /etc/vmw_conn_notify 60 | VMW_INSTALL_INITDIR := /etc/init.d 61 | 62 | ############################### 63 | # vmw_conn_notify open source 64 | ############################### 65 | VMW_OPEN_SOURCE_SRCS := \ 66 | $(SRCROOT)/vmw_conn_main.c \ 67 | $(SRCROOT)/vmw_conn_netfilter.c 68 | 69 | VMW_OPEN_SOURCE_OBJS := \ 70 | $(SRCROOT)/vmw_conn_main.o \ 71 | $(SRCROOT)/vmw_conn_netfilter.o 72 | 73 | $(VMW_OPEN_SOURCE_OBJS): %.o: %.c 74 | $(CC) $(CFLAGS) -c -o $@ $< 75 | 76 | vmw_conn_oss: $(VMW_OPEN_SOURCE_OBJS) 77 | $(CC) -o $(VMW_OPEN_SOURCE_BIN) $(VMW_OPEN_SOURCE_OBJS) $(LDFLAGS) 78 | 79 | install: 80 | mkdir -p $(VMW_INSTALL_CONFDIR) 81 | cp $(SRCROOT)/vmw_conn_notify.conf $(VMW_INSTALL_CONFDIR) 82 | cp $(SRCROOT)/vmw_conn_notifyd $(VMW_INSTALL_INITDIR) 83 | chmod 755 $(VMW_INSTALL_INITDIR)/vmw_conn_notifyd 84 | cp $(VMW_OPEN_SOURCE_BIN) /usr/sbin/ 85 | chmod 755 /usr/sbin/vmw_conn_notify 86 | 87 | clean: 88 | rm -f $(VMW_OPEN_SOURCE_OBJS) 89 | rm -f $(VMW_OPEN_SOURCE_BIN) 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Contributing to guest-introspection-nsx 4 | 5 | The guest-introspection-nsx project team welcomes contributions from the community. If you wish to contribute code and you have not 6 | signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any 7 | questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). 8 | 9 | ## Logging Bugs 10 | 11 | Anyone can log a bug using the GitHub 'New Issue' button. Please use a short title and give as much information as you can about what the problem is, relevant software versions, and how to reproduce it. If you know the fix or a workaround include that too. 12 | 13 | ## Contribution Flow 14 | 15 | This is a rough outline of what a contributor's workflow looks like: 16 | 17 | - Create a topic branch from where you want to base your work 18 | - Make commits of logical units 19 | - Make sure your commit messages are in the proper format (see below) 20 | - Push your changes to a topic branch in your fork of the repository 21 | - Submit a pull request 22 | 23 | Example: 24 | 25 | ``` shell 26 | git remote add upstream https://github.com/vmware/guest-introspection-nsx.git 27 | git checkout -b my-new-feature master 28 | git commit -a 29 | git push origin my-new-feature 30 | ``` 31 | 32 | ### Staying In Sync With Upstream 33 | 34 | When your branch gets out of sync with the vmware/master branch, use the following to update: 35 | 36 | ``` shell 37 | git checkout my-new-feature 38 | git fetch -a 39 | git pull --rebase upstream master 40 | git push --force-with-lease origin my-new-feature 41 | ``` 42 | 43 | ### Updating pull requests 44 | 45 | If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into 46 | existing commits. 47 | 48 | If your pull request contains a single commit or your changes are related to the most recent commit, you can simply 49 | amend the commit. 50 | 51 | ``` shell 52 | git add . 53 | git commit --amend 54 | git push --force-with-lease origin my-new-feature 55 | ``` 56 | 57 | If you need to squash changes into an earlier commit, you can use: 58 | 59 | ``` shell 60 | git add . 61 | git commit --fixup 62 | git rebase -i --autosquash master 63 | git push --force-with-lease origin my-new-feature 64 | ``` 65 | 66 | Be sure to add a comment to the PR indicating your new changes are ready to review, as GitHub does not generate a 67 | notification when you git push. 68 | 69 | ### Code Style 70 | 71 | Make sure your code look like code that is already there. 72 | 73 | ### Formatting Commit Messages 74 | 75 | We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). 76 | 77 | Be sure to include any related GitHub issue references in the commit message. See 78 | [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues 79 | and commits. 80 | 81 | ## Reporting Bugs and Creating Issues 82 | 83 | When opening a new issue, try to roughly follow the commit message format conventions above. 84 | 85 | ## Final Words 86 | 87 | Thanks for helping us make the project better! 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # General 4 | 5 | ## What is Guest Introspection for VMware NSX? 6 | Guest Introspection for VMware NSX is a user space daemon installed inside Linux guest virtual machine for a proprietary NSX Guest Introspection product, for the purpose of providing network connection control and monitoring capability. This daemon uses capability provided by netfilter libraries (libnetfilter_queue and libnetfilter_conntrack) and netfilter kernel subsystem to offer network connection control and monitoring functionality. 7 | 8 | Guest Introspection for VMware NSX provides following features: 9 | 10 | * Packet filtering (IPv4 and IPv6 over TCP/UDP) and controlling 11 | * Network connection monitoring (TCP and UDP) 12 | 13 | ### Dependencies 14 | Guest Introspection for VMware NSX requires following libraries: 15 | * libnetfilter_queue 16 | * libnetfilter_conntrack 17 | * netfilter kernel modules 18 | * libglib-2.0 19 | * libmnl 20 | 21 | 22 | ### Availabiltiy 23 | Guest Introspection for VMware NSX is available on the following Linux distributions and versions: 24 | * Ubuntu 14.04 LTS 64-bit 25 | * Ubuntu 16.04 LTS 64-bit 26 | * RHEL 7 64-bit 27 | * SLES 12 SP2 64-bit 28 | * CentOS 7 64 bit 29 | 30 | ### Build & Run 31 | 32 | The following steps will work on most recent Linux distributions (Please install libnetfilter_queue, libnetfilter_conntrack and libglib-2.0 into appropriate path before building. For path, please refer makefile) : 33 | 34 | * make 35 | * sudo make install 36 | 37 | Use the following step to start daemon 38 | * /etc/init.d/vmw_conn_notifyd start 39 | 40 | To change syslog logging level of this daemon, please update DEBUG_LEVEL in /etc/vmw_conn_notify/vmw_conn_notify.conf. The accepted value of the logging level is from 0 to 7. 41 | 42 | # Contributing 43 | 44 | The guest-introspection-nsx project team welcomes contributions from the community. If you wish to contribute code and you have not 45 | signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any 46 | questions about the contributor license agreement (CLA) process, please refer to our [FAQ](https://cla.vmware.com/faq). 47 | 48 | ## How can I get involved today? 49 | 50 | You can get involved today in several different ways: 51 | 52 | * Start using guest-introspection-nsx today and give us feedback. 53 | 54 | * Suggest feature enhancements. 55 | 56 | * Identify and submit bugs under issues section: https://github.com/vmware/guest-introspection-nsx/issues 57 | 58 | 59 | ## Will external developers be allowed to become committers to the project? 60 | 61 | Yes. Initially, VMware engineers will be the only committers. As we roll out our development infrastructure, we will be looking to add external committers to the project as well. 62 | 63 | ## How can I submit code changes like bug fixes, patches, new features to the project? 64 | 65 | Initially, you can submit bug fixes, patches and new features to the project development mailing list as attachments to e-mails or bug reports. To contribute source code, you will need to fill out a contribution agreement form as part of the submission process. We will have more details on this process shortly. 66 | 67 | 68 | # License 69 | The code is being released under GPL v2 license. 70 | -------------------------------------------------------------------------------- /src/include/vmw_conn.h: -------------------------------------------------------------------------------- 1 | #ifndef VMW_CONN_H 2 | #define VMW_CONN_H 3 | /* 4 | * 5 | * Copyright (C) 2018 VMware, Inc. All rights reserved. 6 | * 7 | * This program is free software; you can redistribute it and/or modify it under 8 | * the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; version 2. 10 | 11 | * This program is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 | * details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, Inc., 18 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 19 | * 20 | */ 21 | 22 | /* 23 | * SPDX-License-Identifier: GPL-2.0-only 24 | */ 25 | 26 | /* 27 | * vmw_conn.h contains data structures/definitions for interaction with clients 28 | * (consumer of network events). 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #ifdef _x86_64 44 | #include 45 | #endif 46 | 47 | #include 48 | #include 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #include 58 | #include 59 | 60 | #define ATOMIC_OR(var, value) __sync_or_and_fetch((var), (value)) 61 | 62 | #define LOG_MSG(level, str, fmt, ...) \ 63 | syslog(level, "%s: %s: " fmt, str, __FUNCTION__, ##__VA_ARGS__) 64 | 65 | #define INFO(fmt, ...) LOG_MSG(LOG_INFO, "INFO", fmt, ##__VA_ARGS__) 66 | #define WARN(fmt, ...) LOG_MSG(LOG_WARNING, "WARN", fmt, ##__VA_ARGS__) 67 | #define ERROR(fmt, ...) LOG_MSG(LOG_ERR, "ERROR", fmt, ##__VA_ARGS__) 68 | #define DEBUG(fmt, ...) LOG_MSG(LOG_DEBUG, "DEBUG", fmt, ##__VA_ARGS__) 69 | #define NOTICE(fmt, ...) LOG_MSG(LOG_NOTICE, "NOTICE", fmt, ##__VA_ARGS__) 70 | 71 | #define VERSION_MAJOR 1 72 | #define VERSION_MINOR 1 73 | #define VERSION_BUILD 0 74 | #define VERSION_REVISION 0 75 | 76 | #define PROG_NAME "vmw_conn_notify" 77 | 78 | /* Maximum number of supported client */ 79 | #define MAX_CLIENTS 2 80 | 81 | /* 82 | * Client will register with vmw_conn_notify for protocols they are 83 | * interested in. These macros indicate how the bits in the 'protocol' 84 | * field of vmw_client_info are interpreted. 85 | */ 86 | #define TCP_OUT_PRE_CONN_SUPPORT 1<<0 87 | #define TCP_IN_PRE_CONN_SUPPORT 1<<1 88 | #define TCP_EST_CONN_SUPPORT 1<<2 89 | #define TCP_CLOSE_CONN_SUPPORT 1<<3 90 | #define UDP_SUPPORT 1<<4 91 | 92 | /* Network event Type */ 93 | enum vmw_conn_event_type { 94 | OUTBOUND_PRECONNECT = 1, /* Outgoing connection initiation*/ 95 | POSTCONNECT, /* Established connection */ 96 | DISCONNECT, /* Disconnected connetion */ 97 | INBOUND_PRECONNECT, /* Incoming connection inititation */ 98 | MAX_EVENT, 99 | }; 100 | 101 | /* DNS payload data */ 102 | struct vmw_dns_payload { 103 | uint16_t len; /* Length of DNS payload */ 104 | char *payload; /* DNS payload */ 105 | }; 106 | 107 | /* Network connection identification related data */ 108 | struct vmw_conn_identity_data { 109 | struct sockaddr_storage src; /* Source ip */ 110 | struct sockaddr_storage dst; /* Destination ip */ 111 | enum vmw_conn_event_type event_type; /* Network connection type */ 112 | uint32_t event_id; /* Event id */ 113 | uint8_t protocol; /* L3 protocol */ 114 | struct vmw_dns_payload dns_payload[1]; /* DNS payload */ 115 | }; 116 | 117 | struct vmw_client_scope { 118 | int client_sockfd; 119 | pthread_mutex_t client_sock_lock; /* Lock to sync nfq send/recv packets */ 120 | int client_version; /* Client version */ 121 | GHashTable *queued_pkthash; /* Hash table to store packets queued for 122 | verdict */ 123 | uint8_t pkthash_cleanup_wait; /* Client hashtable cleanup in progress*/ 124 | uint32_t client_proto_info; /* Protocol info for which client is 125 | interested */ 126 | }; 127 | 128 | /* Client fd in cleanup is not considered a free fd */ 129 | #define IS_CLIENT_FD_FREE(ctx) \ 130 | ((ctx.client_sockfd < 0) && (!ctx.pkthash_cleanup_wait)) 131 | 132 | /* Packet info maintained in the global hash table */ 133 | typedef struct _vmw_global_packet_info { 134 | uint32_t event_id; /* Id information per packet */ 135 | uint32_t ref_count; /* Number of client referring to packet */ 136 | uint32_t mark; /* Mark to be set on the packet */ 137 | pthread_mutex_t lock; /* lock protecting this structure */ 138 | } global_packet_info; 139 | 140 | /* Packet info from client */ 141 | typedef struct _vmw_verdict { 142 | uint32_t packetId; /* packet tracking id for client */ 143 | int verdict; /* verdict received from client */ 144 | } vmw_verdict; 145 | 146 | /* version and mark info per client */ 147 | typedef struct _vmw_client_info { 148 | int version; /* Client version */ 149 | uint32_t protocol; /* Protocol events for which client is 150 | interested */ 151 | } vmw_client_info; 152 | #endif 153 | -------------------------------------------------------------------------------- /src/vmw_conn_notify_watchdog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2018 VMware, Inc. All rights reserved. 4 | # 5 | # This program is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; version 2. 8 | # 9 | # This program is distributed in the hope that it will be useful, but 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 12 | # License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, Inc., 16 | # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 17 | # 18 | 19 | # 20 | # SPDX-License-Identifier: GPL-2.0-only 21 | # 22 | # 23 | # This is the connection notifier watchdog script that monitors vmw_conn_notify 24 | 25 | # Sleep for 60 seconds by default 26 | IMMORTAL=60 27 | 28 | 29 | VMW_CONN_NOTIFY_NAME=vmw_conn_notify 30 | VMW_CONN_NOTIFY_BIN=/usr/sbin/$VMW_CONN_NOTIFY_NAME 31 | VMW_CONN_NOTIFY_CONFIG=/etc/vmw_conn_notify/vmw_conn_notify.cfg 32 | LOCK_FILE=/var/lock/subsys/vmw_conn_notifyd 33 | VMW_CONN_NOTIFY_WATCHDOG_NAME=vmw_conn_notifyd_watchdog 34 | VMW_CONN_NOTIFY_WATCHDOG=/usr/sbin/$VMW_CONN_NOTIFY_WATCHDOG_NAME 35 | 36 | # Logging related variables 37 | LOGPREFIX=$VMW_CONN_NOTIFY_WATCHDOG_NAME 38 | PID_LOGPREFIX=$$ 39 | VERBOSE=1 40 | SILENT=0 41 | LOG_LEVEL=$VERBOSE 42 | GM_SUCCESS=0 43 | GM_FAIL=1 44 | 45 | MAX_RETRIES_VMW_CONN_NOTIFY=8 46 | 47 | 48 | # Logging related variables 49 | GM_SUCCESS=0 50 | GM_FAIL=1 51 | 52 | # Time taken for vmw_conn_notify to stop its all services 53 | VMW_CONN_NOTIFY_STOP_TIME=4 54 | 55 | # Check for missing binaries 56 | if [ ! -f $VMW_CONN_NOTIFY_BIN ] ; then 57 | vmw_logger "$VMW_CONN_NOTIFY_BIN not installed" $SILENT $ERROR 58 | if [ "$1" = "stop" ] ; then 59 | exit 0 60 | else 61 | exit 5 62 | fi 63 | fi 64 | 65 | ##################################################################### 66 | # Definition: 67 | # Function to log messages 68 | # 69 | # Arguments: 70 | # Argument 1: String to print 71 | # Argument 2: LOG_LEVEL(SILENT(0) / VERBOSE(1)) 72 | # Argument 3: Log Status (success|info, warning, failure|err) 73 | ##################################################################### 74 | vmw_logger() { 75 | local VERBOSE=1 76 | local SILENT=0 77 | local STR=$1 78 | local LOG_LEVEL=${2:-$VERBOSE} 79 | local LOG_STATUS=${3:-"info"} 80 | 81 | if [ "${LOG_LEVEL}" -eq $VERBOSE ]; then 82 | echo "${STR}" 83 | fi 84 | 85 | case "$LOG_STATUS" in 86 | "err"|"failure") # failure 87 | LOG_STATUS="err" 88 | ;; 89 | "warn") # warning 90 | LOG_STATUS="warning" 91 | ;; 92 | "info"|"success") # success 93 | LOG_STATUS="info" 94 | ;; 95 | *) 96 | LOG_STATUS="info" 97 | ;; 98 | esac 99 | 100 | logger -p daemon.$LOG_STATUS -t "$VMW_CONN_NOTIFY_WATCHDOG_NAME[$$]" "$STR" 101 | } 102 | 103 | # 104 | # Enable this when we have a config file 105 | # Check for missing config file 106 | [ -e $VMW_CONN_NOTIFY_CONFIG ] && . $VMW_CONN_NOTIFY_CONFIG 107 | 108 | status() { 109 | local retval="$GM_SUCCESS" 110 | 111 | # 112 | # Check if there's already running instance of vmw_conn_notify, using lock_file 113 | # and using pidof command.. so at any time we will run only one instance 114 | # 115 | if [ -f $LOCK_FILE -a -n "`pidof $VMW_CONN_NOTIFY_BIN`" ] ; then 116 | retval="$GM_SUCCESS" 117 | else 118 | retval="$GM_FAIL" 119 | fi 120 | 121 | return "$retval" 122 | } 123 | 124 | 125 | start() { 126 | local retval="$GM_SUCCESS" 127 | local IMMORTAL=${IMMORTAL:-60} 128 | local VERBOSE=1 129 | local SILENT=0 130 | local LOG_LEVEL=$SILENT 131 | local iter_vmw_conn_notify=0 132 | 133 | while : 134 | do 135 | # Sleep so we don't continuously spawn the process(s) 136 | sleep $IMMORTAL 137 | # Status returns the combined status and can pinpoint which service is stopped 138 | status 139 | retval=$? 140 | if [ $retval -eq $GM_SUCCESS ] ; then 141 | : 142 | else 143 | vmw_logger "vmw_conn_notify not running" $SILENT $ERROR 144 | vmw_logger "Attempting fix" $SILENT $INFO 145 | mkdir -p /var/lock/subsys 146 | touch $LOCK_FILE 147 | iter_vmw_conn_notify=$((iter_vmw_conn_notify+1)) 148 | $VMW_CONN_NOTIFY_BIN & 149 | fi 150 | 151 | # We reach here which means we have exhausted the number of retries 152 | # Check one more time if our last start was successful, if not then exit 153 | if [ $iter_vmw_conn_notify -ge $MAX_RETRIES_VMW_CONN_NOTIFY ]; then 154 | status 155 | retval=$? 156 | if [ $retval -ne $GM_SUCCESS ]; then 157 | vmw_logger "Max retries exceeded! Closing $VMW_CONN_NOTIFY_NAME service" $SILENT $ERROR 158 | stop 159 | exit $GM_FAIL 160 | else 161 | # Our last start was successful, reset counters again 162 | iter_vmw_conn_notify=0 163 | fi 164 | fi 165 | done 166 | 167 | } 168 | 169 | if [ "`id -u`" -ne 0 ] ; then 170 | vmw_logger "User has insufficient privilege." $SILENT $ERROR 171 | exit 4 172 | fi 173 | 174 | # Stop the vmw_conn_notify watchdog 175 | stop() { 176 | local retval=$GM_SUCCESS 177 | local VERBOSE=1 178 | local SILENT=0 179 | local LOG_LEVEL=$SILENT 180 | 181 | vmw_logger "Stopping $VMW_CONN_NOTIFY_NAME service" $SILENT $INFO 182 | 183 | kill -SIGTERM `pidof $VMW_CONN_NOTIFY_BIN` 184 | retval=$? 185 | if [ $retval -eq $GM_SUCCESS ] ; then 186 | ### Now, delete the lock file ### 187 | rm -f $LOCK_FILE 188 | sleep $VMW_CONN_NOTIFY_STOP_TIME 189 | else 190 | vmw_logger "$VMW_CONN_NOTIFY_NAME service stop error" $SILENT $ERROR 191 | fi 192 | } 193 | 194 | start 195 | # We already handle TERM signals, should never come here... 196 | exit 1 197 | -------------------------------------------------------------------------------- /src/vmw_conn_notifyd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2018 VMware, Inc. All rights reserved. 4 | # 5 | # This program is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; version 2. 8 | # 9 | # This program is distributed in the hope that it will be useful, but 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 12 | # License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, Inc., 16 | # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 17 | # 18 | 19 | # 20 | # SPDX-License-Identifier: GPL-2.0-only 21 | # 22 | 23 | # 24 | # Configuration required for vmw_conn_notify 25 | # 26 | 27 | ### BEGIN INIT INFO 28 | # Provides: vmw_conn_notifyd 29 | # Required-Start: $network $remote_fs $syslog $time 30 | # Required-Stop: $network $remote_fs $syslog $time 31 | # Default-Start: 2 3 5 32 | # Default-Stop: 0 1 6 33 | # Short-Description: VMware Network Connection Introspection Service 34 | # Description: Manages the service need to run Network Connecion Introspection 35 | ### END INIT INFO 36 | shopt -s expand_aliases > /dev/null 2>&1 37 | export _SYSTEMCTL_SKIP_REDIRECT=1 38 | 39 | VMW_CONN_NOTIFY_NAME=vmw_conn_notify 40 | VMW_CONN_NOTIFY_BIN=/usr/sbin/$VMW_CONN_NOTIFY_NAME 41 | VMW_CONN_NOTIFY_CONFIG=/etc/vmw_conn_notify/vmw_conn_notify.cfg 42 | LOCK_FILE=/var/lock/subsys/vmw_conn_notifyd 43 | VMW_CONN_NOTIFY_STOP_TIME=2 44 | VMW_CONN_NOTIFY_WATCHDOG_NAME=vmw_conn_notifyd_watchdog 45 | VMW_CONN_NOTIFY_WATCHDOG=/usr/sbin/$VMW_CONN_NOTIFY_WATCHDOG_NAME 46 | 47 | # GM_STATUS related variables 48 | GM_SUCCESS=0 49 | GM_FAIL=1 50 | 51 | # logger variables 52 | VERBOSE=1 53 | SILENT=0 54 | LOG_LEVEL=$VERBOSE 55 | SUCCESS="info" 56 | ERROR="err" 57 | WARNING="warning" 58 | INFO="info" 59 | 60 | NFQUEUE_BYPASS="NFQUEUE --queue-num 0 --queue-bypass" 61 | TCP_PACKET_FILTER_OP="-p tcp --tcp-flags FIN,SYN,RST,ACK,PSH SYN" 62 | VNET_PACKET_STAMP="-m mark ! --mark 0x1/0x1" 63 | VNET_CHAIN=vnetchain 64 | 65 | if [ -x '/sbin/iptables' ]; then 66 | IPTABLES="/sbin/iptables" 67 | elif [ -x '/usr/sbin/iptables' ]; then 68 | IPTABLES="/usr/sbin/iptables" 69 | else 70 | IPTABLES="iptables" 71 | fi 72 | 73 | if [ -x '/sbin/ip6tables' ]; then 74 | IP6TABLES="/sbin/ip6tables" 75 | elif [ -x '/usr/sbin/ip6tables' ]; then 76 | IP6TABLES="/usr/sbin/ip6tables" 77 | else 78 | IP6TABLES="ip6tables" 79 | fi 80 | 81 | MODPROBE="/sbin/modprobe" 82 | 83 | ##################################################################### 84 | # Definition: 85 | # Function to log Success/Fail 86 | # 87 | # Arguments: 88 | # Argument 1: Return code 89 | # Argument 2: LOG_LEVEL 90 | ##################################################################### 91 | eval_cmd() { 92 | local rc=$1 93 | local VERBOSE=1 94 | local SILENT=0 95 | local LOG_LEVEL=${2:-$VERBOSE} 96 | 97 | if [ "${LOG_LEVEL}" -eq $VERBOSE ]; then 98 | if [ "$rc" -eq "$GM_SUCCESS" ]; then 99 | echo '[ OK ]' 100 | else 101 | echo '[ FAILED ]' 102 | fi 103 | fi 104 | 105 | return "$rc" 106 | } 107 | 108 | ##################################################################### 109 | # Definition: 110 | # Function to log messages 111 | # 112 | # Arguments: 113 | # Argument 1: String to print 114 | # Argument 2: LOG_LEVEL(SILENT(0) / VERBOSE(1)) (optional) 115 | # Argument 3: Log Status (success|info, warning, failure|err) 116 | ##################################################################### 117 | vmw_logger() { 118 | local VERBOSE=1 119 | local SILENT=0 120 | local STR=$1 121 | local LOG_LEVEL=${2:-$VERBOSE} 122 | local LOG_STATUS=${3:-"info"} 123 | 124 | if [ "${LOG_LEVEL}" -eq $VERBOSE ]; then 125 | echo "${STR}" 126 | fi 127 | 128 | case "$LOG_STATUS" in 129 | "err"|"failure") # failure 130 | LOG_STATUS="err" 131 | ;; 132 | "warn") # warning 133 | LOG_STATUS="warning" 134 | ;; 135 | "info"|"success") # success 136 | LOG_STATUS="info" 137 | ;; 138 | *) 139 | LOG_STATUS="info" 140 | ;; 141 | esac 142 | 143 | logger -p daemon.$LOG_STATUS -t "vmw_conn_notifyd[$$]" "$STR" 144 | 145 | } 146 | 147 | # Check for missing binaries 148 | if [ ! -f $VMW_CONN_NOTIFY_BIN ] ; then 149 | vmw_logger "$VMW_CONN_NOTIFY_BIN not installed" $VERBOSE $ERROR 150 | if [ "$1" = "stop" ] ; then 151 | exit 0 152 | else 153 | exit 5 154 | fi 155 | fi 156 | 157 | # 158 | # Enable this when we have a config file 159 | # Check for missing config file 160 | [ -e $VMW_CONN_NOTIFY_CONFIG ] && . $VMW_CONN_NOTIFY_CONFIG 161 | 162 | # Determine OS type based on the functions library path 163 | OS="" 164 | if [ -f /etc/init.d/functions ] ; then 165 | OS="Redhat" 166 | #. /etc/init.d/functions 167 | 168 | MODPROBE_PARAM="" 169 | elif [ -f /lib/lsb/init-functions ] && [ -f /etc/rc.status ] ; then 170 | OS="Suse" 171 | 172 | MODPROBE_PARAM="--allow-unsupported-modules" 173 | elif [ -f /lib/lsb/init-functions ] ; then 174 | OS="Ubuntu" 175 | 176 | MODPROBE_PARAM="" 177 | else 178 | OS="Unsupported OS, exiting." 179 | vmw_logger $OS $SILENT $ERROR 180 | exit 1 181 | fi 182 | 183 | # Execute the given command and bail-out on failure 184 | exec_or_die() { 185 | status="0" 186 | cmd="$@" 187 | out=$($cmd 2>&1) || status="$?" 188 | if [ "$status" -ne 0 ]; then 189 | # Remove extra spaces if any because of indentation 190 | cmd=$(echo $cmd | /bin/sed 's/[[:space:]]/ /g') 191 | vmw_logger "Failure in executing \"$cmd\", error code: $status, \"$out\"" $SILENT $ERROR 192 | exit 4 193 | fi 194 | } 195 | 196 | # Execute the given command and log warning message on failure 197 | exec_or_warn() { 198 | status="0" 199 | cmd="$@" 200 | out=$($cmd 2>&1) || status="$?" 201 | if [ "$status" -ne 0 ]; then 202 | # Remove extra spaces if any because of indentation 203 | cmd=$(echo $cmd | /bin/sed 's/[[:space:]]/ /g') 204 | vmw_logger "\"$cmd\" returns with code: $status, message: \"$out\"" $SILENT $ERROR 205 | return 4 206 | fi 207 | } 208 | 209 | # Load netfilter kernel modules 210 | load_netfilter_modules() { 211 | exec_or_die ${MODPROBE} iptable_filter 212 | 213 | exec_or_die ${MODPROBE} xt_NFQUEUE 214 | exec_or_die ${MODPROBE} nf_conntrack_ipv4 215 | 216 | if test -f /proc/net/if_inet6 217 | then 218 | exec_or_die ${MODPROBE} ip6table_filter 219 | exec_or_die ${MODPROBE} nf_conntrack_ipv6 220 | fi 221 | } 222 | 223 | add_vnetchain_filter_rules() { 224 | exec_or_die "${IPTABLES} -N ${VNET_CHAIN}" 225 | 226 | exec_or_die "${IPTABLES} -I INPUT ${VNET_PACKET_STAMP} \ 227 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 228 | exec_or_die "${IPTABLES} -I OUTPUT ${VNET_PACKET_STAMP} \ 229 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 230 | exec_or_die "${IPTABLES} -I ${VNET_CHAIN} -j ${NFQUEUE_BYPASS}" 231 | 232 | # 233 | # Add ipv6 rules only when ipv6 is supported on the system 234 | # 235 | if test -f /proc/net/if_inet6 236 | then 237 | exec_or_die "${IP6TABLES} -N ${VNET_CHAIN}" 238 | exec_or_die "${IP6TABLES} -I INPUT ${VNET_PACKET_STAMP} \ 239 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 240 | exec_or_die "${IP6TABLES} -I OUTPUT ${VNET_PACKET_STAMP} \ 241 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 242 | exec_or_die "${IP6TABLES} -I ${VNET_CHAIN} -j ${NFQUEUE_BYPASS}" 243 | fi 244 | } 245 | 246 | remove_vnetchain_filter_rules() { 247 | exec_or_warn "${IPTABLES} -D INPUT ${VNET_PACKET_STAMP} \ 248 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 249 | exec_or_warn "${IPTABLES} -D OUTPUT ${VNET_PACKET_STAMP} \ 250 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 251 | exec_or_warn "${IPTABLES} -D ${VNET_CHAIN} -j ${NFQUEUE_BYPASS}" 252 | 253 | 254 | exec_or_warn "${IPTABLES} -X ${VNET_CHAIN}" 255 | 256 | if test -f /proc/net/if_inet6 257 | then 258 | exec_or_warn "${IP6TABLES} -D INPUT ${VNET_PACKET_STAMP} \ 259 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 260 | exec_or_warn "${IP6TABLES} -D OUTPUT ${VNET_PACKET_STAMP} \ 261 | ${TCP_PACKET_FILTER_OP} -j ${VNET_CHAIN}" 262 | exec_or_warn "${IP6TABLES} -D ${VNET_CHAIN} -j ${NFQUEUE_BYPASS}" 263 | exec_or_warn "${IP6TABLES} -X ${VNET_CHAIN}" 264 | fi 265 | } 266 | 267 | if [ "`id -u`" -ne 0 ] ; then 268 | vmw_logger "User has insufficient privilege." $VERBOSE $ERROR 269 | exit 4 270 | fi 271 | 272 | ##################################################################### 273 | # Starts the watchdog service 274 | ##################################################################### 275 | start_watchdog() { 276 | local retval="$GM_SUCCESS" 277 | 278 | # setsid creates a new session id for watchdog and 279 | # keeps it running even if shell session is closed 280 | # Watchdog is redundant when there is systemd service 281 | # But necessary when there is upstart or SysVinit 282 | setsid $VMW_CONN_NOTIFY_WATCHDOG >/dev/null 2>&1 < /dev/null & 283 | retval=$? 284 | return "$retval" 285 | } 286 | 287 | status_vmw_conn_notify() { 288 | 289 | # Check if there's already running instance of vmw_conn_notify, using lock_file 290 | # and using pidof command.. so at any time we will run only one instance 291 | # 292 | # This assumes that we use full pathname to start (/usr/sbin/vmw_conn_notify) 293 | local retval_vmw_conn_notify="$GM_SUCCESS" 294 | 295 | if [ -f $LOCK_FILE -a -n "`pidof $VMW_CONN_NOTIFY_BIN`" ] ; then 296 | retval_vmw_conn_notify="$GM_SUCCESS" 297 | else 298 | retval_vmw_conn_notify="$GM_FAIL" 299 | fi 300 | 301 | return "$retval_vmw_conn_notify" 302 | } 303 | 304 | ##################################################################### 305 | # Queries the watchdog service 306 | ##################################################################### 307 | status_watchdog() { 308 | local retval="$GM_SUCCESS" 309 | local pids=$(pgrep -f $VMW_CONN_NOTIFY_WATCHDOG_NAME) 310 | 311 | if [ -n "$pids" ]; then 312 | retval="$GM_SUCCESS" 313 | else 314 | retval="$GM_FAIL" 315 | fi 316 | return "$retval" 317 | } 318 | 319 | ##################################################################### 320 | # Arguments: 321 | # Argument 1: LOG_LEVEL(SILENT(0) / VERBOSE(1)) 322 | # Argument 2: NO_WATCHDOG (optional) 323 | ##################################################################### 324 | start () { 325 | local retval_watchdog="$GM_SUCCESS" 326 | local retval_vmw_conn_notify="$GM_SUCCESS" 327 | local retval="$GM_SUCCESS" 328 | local VERBOSE=1 329 | local SILENT=0 330 | local LOG_LEVEL=${1:-$VERBOSE} 331 | 332 | status_vmw_conn_notify 333 | retval_vmw_conn_notify=$? 334 | if [ "$retval_vmw_conn_notify" -ne "$GM_SUCCESS" ]; then 335 | vmw_logger "Starting $VMW_CONN_NOTIFY_NAME service" $SILENT $INFO 336 | $VMW_CONN_NOTIFY_BIN 337 | retval_vmw_conn_notify=$? 338 | fi 339 | 340 | if [ "$retval_vmw_conn_notify" -ne "$GM_SUCCESS" ]; then 341 | retval_vmw_conn_notify="$GM_FAIL" 342 | vmw_logger "Unable to start $VMW_CONN_NOTIFY_NAME binary" $SILENT $ERROR 343 | else 344 | retval_vmw_conn_notify="$GM_SUCCESS" 345 | vmw_logger "$VMW_CONN_NOTIFY_NAME binary started successfully" $SILENT $SUCCESS 346 | ### Create the lock file ### 347 | mkdir -p /var/lock/subsys 348 | touch $LOCK_FILE 349 | load_netfilter_modules 350 | add_vnetchain_filter_rules 351 | fi 352 | 353 | status_watchdog 354 | retval_watchdog=$? 355 | if [ "$retval_watchdog" -ne "$GM_SUCCESS" ]; then 356 | vmw_logger "Starting watchdog for $VMW_CONN_NOTIFY_NAME" $SILENT $INFO 357 | start_watchdog 358 | status_watchdog 359 | retval_watchdog=$? 360 | fi 361 | 362 | if [ "$retval_watchdog" -ne "$GM_SUCCESS" ]; then 363 | sleep 1 364 | status_watchdog 365 | retval_watchdog=$? 366 | fi 367 | 368 | # Display results for watchdog 369 | if [ "$retval_watchdog" -ne "$GM_SUCCESS" ]; then 370 | retval_watchdog="$GM_FAIL" 371 | vmw_logger "Unable to start $VMW_CONN_NOTIFY_WATCHDOG_NAME" $SILENT $ERROR 372 | else 373 | retval_watchdog="$GM_SUCCESS" 374 | vmw_logger "$VMW_CONN_NOTIFY_WATCHDOG_NAME started successfully" $SILENT $SUCCESS 375 | fi 376 | 377 | retval=$((retval_vmw_conn_notify || retval_watchdog)) 378 | if [ "$retval" -eq "$GM_SUCCESS" ]; then 379 | vmw_logger "$VMW_CONN_NOTIFY_NAME service is running" $LOG_LEVEL $SUCCESS 380 | else 381 | vmw_logger "$VMW_CONN_NOTIFY_NAME service is stopped" $LOG_LEVEL $ERROR 382 | remove_vnetchain_filter_rules 383 | fi 384 | eval_cmd "$retval" "$LOG_LEVEL" 385 | 386 | return "$retval" 387 | } 388 | 389 | 390 | 391 | 392 | ##################################################################### 393 | # Arguments: 394 | # Argument 1: LOG_LEVEL(SILENT(0) / VERBOSE(1)) 395 | # Argument 2: NO_WATCHDOG (optional) 396 | ##################################################################### 397 | stop() { 398 | local retval_watchdog="$GM_SUCCESS" 399 | local retval_vmw_conn_notify="$GM_SUCCESS" 400 | local retval="$GM_SUCCESS" 401 | local VERBOSE=1 402 | local SILENT=0 403 | local LOG_LEVEL=${1:-$VERBOSE} 404 | 405 | vmw_logger "Stopping $VMW_CONN_NOTIFY_NAME service" $SILENT $INFO 406 | 407 | status_watchdog 408 | retval_watchdog=$? 409 | if [ "$retval_watchdog" -eq "$GM_SUCCESS" ]; then 410 | vmw_logger "Stopping $VMW_CONN_NOTIFY_WATCHDOG_NAME for vmw_conn_notify" $SILENT $SUCCESS 411 | kill -SIGKILL `pgrep -f $VMW_CONN_NOTIFY_WATCHDOG_NAME` >/dev/null 2>&1 412 | status_watchdog 413 | retval_watchdog=$? 414 | fi 415 | if [ "$retval_watchdog" -ne "$GM_SUCCESS" ]; then 416 | retval_watchdog="$GM_SUCCESS" 417 | vmw_logger "$VMW_CONN_NOTIFY_WATCHDOG_NAME stopped successfully" $SILENT $SUCCESS 418 | else 419 | retval_watchdog="$GM_FAIL" 420 | vmw_logger "Failed to stop $VMW_CONN_NOTIFY_WATCHDOG_NAME" $SILENT $ERROR 421 | fi 422 | 423 | status_vmw_conn_notify 424 | retval_vmw_conn_notify=$? 425 | if [ "$retval_vmw_conn_notify" -eq "$GM_SUCCESS" ]; then 426 | vmw_logger "Stopping $VMW_CONN_NOTIFY_NAME binary" $SILENT $INFO 427 | kill -SIGTERM `pidof $VMW_CONN_NOTIFY_NAME` >/dev/null 2>&1 428 | sleep 1 429 | status_vmw_conn_notify 430 | retval_vmw_conn_notify=$? 431 | fi 432 | 433 | if [ "$retval_vmw_conn_notify" -ne "$GM_SUCCESS" ]; then 434 | retval_vmw_conn_notify="$GM_SUCCESS" 435 | rm -f $LOCK_FILE 436 | vmw_logger "$VMW_CONN_NOTIFY_NAME binary stopped successfully" $SILENT $SUCCESS 437 | else 438 | retval_vmw_conn_notify="$GM_FAIL" 439 | vmw_logger "Failed to stop $VMW_CONN_NOTIFY_NAME binary" $SILENT $ERROR 440 | fi 441 | 442 | retval=$((retval_vmw_conn_notify || retval_watchdog)) 443 | if [ "$retval" -eq "$GM_SUCCESS" ]; then 444 | vmw_logger "$VMW_CONN_NOTIFY_NAME service is stopped" $LOG_LEVEL $SUCCESS 445 | else 446 | vmw_logger "$VMW_CONN_NOTIFY_NAME service is running" $LOG_LEVEL $ERROR 447 | fi 448 | remove_vnetchain_filter_rules 449 | 450 | eval_cmd "$retval" "$LOG_LEVEL" 451 | 452 | return "$retval" 453 | } 454 | 455 | 456 | ##################################################################### 457 | # Arguments: 458 | # Argument 1: LOG_LEVEL(SILENT(0) / VERBOSE(1)) 459 | # Argument 2: NO_WATCHDOG (optional) 460 | ##################################################################### 461 | status() { 462 | local retval_vmw_conn_notify="$GM_SUCCESS" 463 | local retval_watchdog="$GM_SUCCESS" 464 | local retval="$GM_SUCCESS" 465 | local VERBOSE=1 466 | local SILENT=0 467 | local LOG_LEVEL=${1:-$VERBOSE} 468 | local NO_WATCHDOG=${2:-0} 469 | 470 | status_vmw_conn_notify 471 | retval_vmw_conn_notify=$? 472 | if [ "$retval_vmw_conn_notify" -ne "$GM_SUCCESS" ]; then 473 | retval_vmw_conn_notify="$GM_FAIL" 474 | vmw_logger "$VMW_CONN_NOTIFY_NAME binary is stopped" $SILENT $INFO 475 | else 476 | retval_vmw_conn_notify="$GM_SUCCESS" 477 | vmw_logger "$VMW_CONN_NOTIFY_NAME binary is running" $SILENT $INFO 478 | fi 479 | 480 | if [ "${NO_WATCHDOG}" -eq 0 ]; then 481 | status_watchdog 482 | retval_watchdog=$? 483 | if [ "$retval_watchdog" -ne "$GM_SUCCESS" ]; then 484 | retval_watchdog="$GM_FAIL" 485 | vmw_logger "$VMW_CONN_NOTIFY_WATCHDOG_NAME is stopped" $SILENT $INFO 486 | else 487 | retval_watchdog="$GM_SUCCESS" 488 | vmw_logger "$VMW_CONN_NOTIFY_WATCHDOG_NAME is running" $SILENT $INFO 489 | fi 490 | fi 491 | 492 | retval=$((retval_vmw_conn_notify || retval_watchdog)) 493 | if [ "$retval" -eq "$GM_SUCCESS" ]; then 494 | vmw_logger "$VMW_CONN_NOTIFY_NAME service is running" $LOG_LEVEL $INFO 495 | else 496 | vmw_logger "$VMW_CONN_NOTIFY_NAME service is stopped" $LOG_LEVEL $INFO 497 | fi 498 | 499 | return "$retval" 500 | } 501 | 502 | ### main logic ### 503 | case "$1" in 504 | # Argument : LOG_LEVEL(SILENT(0) / VERBOSE(1)) 505 | start) 506 | start $2 507 | ;; 508 | # Argument : LOG_LEVEL(SILENT(0) / VERBOSE(1)) 509 | stop) 510 | stop $2 511 | ;; 512 | # Argument : LOG_LEVEL(SILENT(0) / VERBOSE(1)) 513 | status) 514 | status $2 515 | ;; 516 | # Argument : LOG_LEVEL(SILENT(0) / VERBOSE(1)) 517 | restart|force-reload) 518 | stop $2 519 | # Sleep for few seconds for the MUX connection to get refreshed 520 | start $2 521 | ;; 522 | *) 523 | vmw_logger "Usage: $0 {start|stop|restart|status|force-reload}" $VERBOSE $ERROR 524 | exit 1 525 | esac 526 | exit 0 527 | -------------------------------------------------------------------------------- /src/vmw_conn_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 VMware, Inc. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify it under 5 | * the terms of the GNU General Public License as published by the 6 | * Free Software Foundation; version 2. 7 | 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 11 | * details. 12 | * 13 | * You should have received a copy of the GNU Lesser General Public License 14 | * along with this program; if not, write to the Free Software Foundation, Inc., 15 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 16 | * 17 | */ 18 | 19 | /* 20 | * SPDX-License-Identifier: GPL-2.0-only 21 | */ 22 | 23 | /* 24 | * Linux userland component for enabling network connection event notification. 25 | * 26 | * vmw_conn_main.c (connection notifier) can be run as a daemon or a server 27 | * process with the name vmw_conn_notify. It listens on a unix domain socket. 28 | * It can be gracefully terminated using SIGINT or SIGTERM signal. 29 | * 30 | * It supports connections from MAX_CLIENT number of client through unix domain 31 | * socket. At a time only one instance of this process/daemon can run. 32 | * 33 | * It is a multithreaded program. The main thread waits for connection from the 34 | * clients and other threads defined in vmw_conn_netfilter.c interacts with the 35 | * registerd clients and netfilter libraries. 36 | */ 37 | 38 | #include "vmw_conn.h" 39 | 40 | /* Unix domain socket path */ 41 | #define SOCK_PATH "/var/run/.vmw_conn_notify_socket" 42 | 43 | /* PID file to don't allow running of duplicate instance */ 44 | #define VMW_PID_FILE "/var/run/vmw_conn_notify.pid" 45 | 46 | #define PROC_PATH_SIZE 256 47 | 48 | #define VMW_CONFIG_FILE "/etc/vmw_conn_notify/vmw_conn_notify.conf" 49 | #define VMW_CONFIG_GROUP_NAME "VMW_CONN_NOTIFY_CONFIG" 50 | 51 | extern volatile int g_vmw_init_done; 52 | extern void *vmw_init(void *); 53 | extern void cleanup_client_hash_entry(gpointer data); 54 | extern void cleanup_global_hash_entry(gpointer data); 55 | /* Global client context array */ 56 | struct vmw_client_scope g_client_ctx[MAX_CLIENTS]; 57 | 58 | /* Global variable to indicate process termintation/shutdown event */ 59 | volatile int g_need_to_quit = 0; 60 | 61 | /* Global hash table to store packets queued for verdict */ 62 | GHashTable *global_queued_pkthash; 63 | 64 | /* 65 | * Array of FDs to break select() in case of shutdown event or new client 66 | * connection event. 67 | */ 68 | static int process_exit_fds[2]; 69 | 70 | /* Command line get_opt flags */ 71 | #define GETOPT_OPTIONS "vpl:" 72 | int process_flag = 0; 73 | int version_flag = 0; 74 | int log_flag = 0; 75 | 76 | /* 77 | * Do dummy write on pipe write fd to break select() blocking in case of 78 | * process termination event or new connection event 79 | */ 80 | void 81 | vmw_notify_exit() 82 | { 83 | int dummy_value = 0; 84 | 85 | ATOMIC_OR(&g_need_to_quit, 1); 86 | write(process_exit_fds[1], &dummy_value, sizeof(dummy_value)); 87 | 88 | return; 89 | } 90 | 91 | /* 92 | * Process SIGINT/SIFTERM signal to notify all running threads blocked on 93 | * select() to inititate shutdown. 94 | */ 95 | static void 96 | vmw_handle_process_exit_signal(int sig_num) 97 | { 98 | 99 | NOTICE("%s recieved signal %d", PROG_NAME, sig_num); 100 | if (SIGINT == sig_num || SIGTERM == sig_num) { 101 | NOTICE("%s is being shutdown", PROG_NAME); 102 | vmw_notify_exit(); 103 | } else { 104 | ERROR("Invalid signal is received %d", sig_num); 105 | } 106 | return; 107 | } 108 | 109 | /* 110 | * This function opens and loads the GKeyFile object for a given file. 111 | * Caller has to free the GKeyFile object in case of success. 112 | */ 113 | GKeyFile * 114 | vmw_get_loaded_gkey_file(char *fileName) 115 | { 116 | GKeyFile *configFile = NULL; 117 | 118 | configFile = g_key_file_new(); 119 | if (NULL == configFile) { 120 | ERROR("Failed to create new GKeyFile object"); 121 | goto exit; 122 | } 123 | 124 | if (!g_key_file_load_from_file(configFile, fileName, 0, NULL)) { 125 | ERROR("Failed to load config file"); 126 | g_key_file_free(configFile); 127 | configFile = NULL; 128 | goto exit; 129 | } 130 | 131 | exit: 132 | return configFile; 133 | } 134 | 135 | /* 136 | * This function opens and loads the vmw_conn_notify config file, reads 137 | * the config params and sets the log level accordingly. 138 | */ 139 | static void 140 | vmw_handle_config_change_signal() { 141 | int value = 0; 142 | GError *error = NULL; 143 | GKeyFile *configFile = NULL; 144 | 145 | if (ATOMIC_OR(&g_need_to_quit, 0)) { 146 | goto exit; 147 | } 148 | 149 | configFile = vmw_get_loaded_gkey_file(VMW_CONFIG_FILE); 150 | if (NULL == configFile) { 151 | ERROR("Failed to load the config file."); 152 | goto exit; 153 | } 154 | 155 | /* Getting the debug level */ 156 | value = g_key_file_get_integer(configFile, 157 | VMW_CONFIG_GROUP_NAME, 158 | "DEBUG_LEVEL", 159 | &error); 160 | 161 | if (!error) { 162 | if ((LOG_EMERG <= value) && (LOG_DEBUG >= value)) { 163 | setlogmask(LOG_UPTO(value)); 164 | NOTICE("Successfully setting the Log level to %d", value); 165 | } else { 166 | WARN("Wrong debug level provided in " 167 | "/etc/vmw_conn_notify/vmw_conn_notify.conf %d", 168 | value); 169 | } 170 | } else { 171 | WARN("Failed to read /etc/vmw_conn_notify/vmw_conn_notify.conf error: %s", 172 | error->message); 173 | } 174 | 175 | exit: 176 | if (error) { 177 | g_error_free(error); 178 | } 179 | if (configFile) { 180 | g_key_file_free(configFile); 181 | } 182 | return; 183 | } 184 | 185 | /* Install signal handler for SIGINT and SIGTERM signals */ 186 | int 187 | vmw_set_sighandler() 188 | { 189 | struct sigaction sig_action; 190 | int ret = 0; 191 | 192 | /* Set the handler for SIGTERM to do graceful exit of the threads */ 193 | memset(&sig_action, 0, sizeof(sig_action)); 194 | sig_action.sa_handler = vmw_handle_process_exit_signal; 195 | ret = sigaction(SIGTERM, &sig_action, NULL); 196 | if (0 != ret) { 197 | ERROR("Failed to set the SIGTERM signal handler, error: %s", 198 | strerror(errno)); 199 | goto exit; 200 | } 201 | 202 | /* Set the handler for SIGINT to do graceful exit of thread threads. */ 203 | memset(&sig_action, 0, sizeof(sig_action)); 204 | sig_action.sa_handler = vmw_handle_process_exit_signal; 205 | ret = sigaction(SIGINT, &sig_action, NULL); 206 | if (0 != ret) { 207 | ERROR("Failed to set the SIGINT signal handler, error: %s", 208 | strerror(errno)); 209 | goto exit; 210 | } 211 | 212 | /* 213 | * Set the handler for SIGHUP to change the log level as 214 | * per the config file. 215 | */ 216 | memset(&sig_action, 0, sizeof(sig_action)); 217 | sig_action.sa_handler = vmw_handle_config_change_signal; 218 | ret = sigaction(SIGHUP, &sig_action, NULL); 219 | if (0 != ret) { 220 | ERROR("Failed to set the SIGHUP signal handler, error: %s", 221 | strerror(errno)); 222 | goto exit; 223 | } 224 | 225 | /* Dummy pipe to notify process exit blocked on select */ 226 | ret = pipe(process_exit_fds); 227 | if (0 != ret) { 228 | ERROR("Could not create dummy eventfd, error: %s", strerror(errno)); 229 | goto exit; 230 | } 231 | 232 | exit: 233 | return ret; 234 | } 235 | 236 | /* Record pid in a file to detect duplicate process */ 237 | static int 238 | vmw_record_pid() 239 | { 240 | FILE *fpid = NULL; 241 | int status = 0; 242 | 243 | if ((fpid = fopen(VMW_PID_FILE, "w"))) { 244 | fprintf(fpid, "%d", getpid()); 245 | fclose(fpid); 246 | } else { 247 | status = -1; 248 | ERROR("Failed to create pid file %s: %s", VMW_PID_FILE, strerror(errno)); 249 | } 250 | return status; 251 | } 252 | 253 | /* 254 | * Check whether a process with given pid is running or not; return true 255 | * if a process is running otherwise return false 256 | */ 257 | static bool 258 | vmw_check_process_running(pid_t pid) 259 | { 260 | bool status = false; 261 | FILE *fproc = NULL; 262 | char proc_file[PROC_PATH_SIZE] = {0}; 263 | 264 | sprintf(proc_file, "/proc/%d/stat", pid); 265 | if ((fproc = fopen(proc_file, "r"))) { 266 | fprintf(stdout, "Found %s already running with pid %d, exiting\n", 267 | PROG_NAME, pid); 268 | ERROR("Found %s already running with pid %d, exiting\n", 269 | PROG_NAME, pid); 270 | fclose(fproc); 271 | status = true; 272 | } 273 | 274 | return status; 275 | } 276 | 277 | /* Check if a process with given pid is already running */ 278 | static bool 279 | vmw_check_duplicate_process() 280 | { 281 | FILE *fpid = NULL; 282 | pid_t pid; 283 | int ret; 284 | bool status = false; 285 | 286 | /* Check if VMW_PID_FILE exists */ 287 | if ((fpid = fopen(VMW_PID_FILE, "r+"))) { 288 | 289 | /* Read the pid from the pid file */ 290 | ret = fscanf(fpid, "%d", &pid); 291 | if (1 == ret) { 292 | /* 293 | * We found a pid, now check if there is an instnace of this process 294 | * already running with this pid 295 | */ 296 | status = vmw_check_process_running(pid); 297 | } 298 | fclose(fpid); 299 | } 300 | 301 | return status; 302 | } 303 | 304 | /* 305 | * In addition to given fds, call select() to monitor event on read fd of pipe. 306 | * This is to break blocking on select() in the event of process termincation by 307 | * just dummy write on the the write fd of the pipe. 308 | */ 309 | int 310 | vmw_wait_for_event(int maxfd, fd_set *readfds, uint8_t new_client_connreq) 311 | { 312 | int ret; 313 | int dummy_value = 1; 314 | 315 | if (ATOMIC_OR(&g_need_to_quit, 0)) { 316 | ret = 0; 317 | goto exit; 318 | } 319 | 320 | /* 321 | * Use read fd of pipe to get process termination/new client connection 322 | * event 323 | */ 324 | FD_SET(process_exit_fds[0], readfds); 325 | if (process_exit_fds[0] > maxfd) { 326 | maxfd = process_exit_fds[0]; 327 | } 328 | 329 | ret = select(maxfd + 1, readfds, NULL, NULL, NULL); 330 | if (ret < 0) { 331 | ERROR("select() failed with error %s", strerror(errno)); 332 | goto exit; 333 | } 334 | if (FD_ISSET(process_exit_fds[0], readfds)) { 335 | if (new_client_connreq && !ATOMIC_OR(&g_need_to_quit, 0)) { 336 | /* Consume new client connection event */ 337 | ret = read(process_exit_fds[0], (void *)&dummy_value, 338 | sizeof(dummy_value)); 339 | } 340 | ret = 0; 341 | goto exit; 342 | } 343 | 344 | exit: 345 | return ret; 346 | } 347 | 348 | /* Print command line help */ 349 | static void 350 | usage(void) 351 | { 352 | fprintf(stderr, "usage: %s \n", PROG_NAME); 353 | fprintf(stderr, " -v: Display %s version and exit\n", PROG_NAME); 354 | fprintf(stderr, " -p: Run as process \n"); 355 | fprintf(stderr, " -l : Log level in 1..7 range\n"); 356 | 357 | exit(1); 358 | } 359 | /* Parse command line options */ 360 | static void 361 | get_opt(int argc, char **argv) 362 | { 363 | int opt; 364 | int option_index; 365 | 366 | static struct option options[] = { 367 | {"version", no_argument, 0, 'v'}, 368 | {"process", no_argument, 0, 'p'}, 369 | {0, 0, 0, 0} 370 | }; 371 | 372 | while (1) { 373 | opt = getopt_long(argc, argv, GETOPT_OPTIONS, options, &option_index); 374 | if (-1 == opt) { 375 | break; 376 | } 377 | switch (opt) { 378 | case 'v': 379 | version_flag = 1; 380 | break; 381 | case 'p': 382 | process_flag = 1; 383 | break; 384 | case 'l': 385 | log_flag = atoi(optarg); 386 | if (log_flag > LOG_DEBUG) { 387 | fprintf(stderr, "Invalid log level %d", log_flag); 388 | ERROR("Invalid log level %d", log_flag); 389 | usage(); 390 | } 391 | break; 392 | default: 393 | usage(); 394 | } 395 | } 396 | } 397 | 398 | /* 399 | * Process command line options. Set appropriate log level based on command line 400 | * input; also record pid after starting this program in daemon or process mode 401 | * based on command line input. 402 | */ 403 | static int 404 | vmw_process_option(int argc, char **argv) 405 | { 406 | int ret = 0; 407 | 408 | get_opt(argc, argv); 409 | 410 | if (version_flag) { 411 | fprintf(stdout, "%s version :\t%u.%u.%u.%u\n", PROG_NAME, 412 | VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION); 413 | goto exit; 414 | } 415 | 416 | /* Use PID during logging */ 417 | openlog(PROG_NAME, LOG_CONS | LOG_PID, LOG_USER); 418 | 419 | if (true == vmw_check_duplicate_process()) { 420 | ERROR("%s is already running\n", PROG_NAME); 421 | ret = 1; 422 | closelog(); 423 | goto exit; 424 | } 425 | 426 | if (!process_flag) { 427 | daemon(0, 0); 428 | } 429 | 430 | if (log_flag > 0) { 431 | /* Set log prority for this process/daemon for syslog logging*/ 432 | setlogmask(LOG_UPTO(log_flag)); 433 | } else { 434 | setlogmask(LOG_UPTO(LOG_INFO)); 435 | } 436 | 437 | if (!process_flag) { 438 | INFO("%s has started as daemon\n", PROG_NAME); 439 | } else { 440 | INFO("%s has started\n", PROG_NAME); 441 | } 442 | 443 | /* Record PID into pid file */ 444 | ret = vmw_record_pid(); 445 | 446 | exit: 447 | return ret; 448 | } 449 | 450 | 451 | /* 452 | * Pack the version into 32 bit integer keeping one byte for each field. 453 | * Client may simply perform the integer comparison 454 | * for example 0x1002 >= 0x1001 455 | * Returns the 32 bit version. 456 | */ 457 | static uint32_t 458 | vmw_pack_version() 459 | { 460 | char ver[64]; 461 | uint32_t version = (VERSION_MAJOR << 24 | VERSION_MINOR << 16 | \ 462 | VERSION_BUILD << 8 | VERSION_REVISION); 463 | snprintf(ver, 64, "%u.%u.%u.%u", VERSION_MAJOR, VERSION_MINOR, 464 | VERSION_BUILD, VERSION_REVISION); 465 | INFO("%s protocol version %s 0x%x", PROG_NAME, ver, version); 466 | return version; 467 | } 468 | 469 | /* 470 | * First do the handshake with the connected client. 471 | * Exchange version and get the protocol from client. 472 | */ 473 | static int 474 | vmw_handshake_version(int new_socket, struct vmw_client_scope *client_ctx) 475 | { 476 | int ret = 0; 477 | uint32_t version = 0; 478 | vmw_client_info client_info = { 0 }; 479 | 480 | /* get the client version and protocol infromation */ 481 | ret = recv(new_socket, (void *)&client_info, sizeof(vmw_client_info), 0); 482 | if (ret <= 0) { 483 | ERROR("Failed to complete connection with client socket %d, " 484 | "error %s", new_socket, strerror(errno)); 485 | close(new_socket); 486 | new_socket = -1; 487 | /* The return value will be 0 when the peer 488 | * has performed an orderly shutdown. 489 | */ 490 | ret = -1; 491 | goto exit; 492 | } 493 | 494 | /* get packed version */ 495 | version = vmw_pack_version(); 496 | 497 | /* send the current protocol/GI-NSX version to the client 498 | * The version can be used to identify client verdict but currently 499 | * it is not used. 500 | */ 501 | send(new_socket, &version, sizeof(version), 0); 502 | 503 | pthread_mutex_lock(&client_ctx->client_sock_lock); 504 | client_ctx->client_version = client_info.version; 505 | client_ctx->client_sockfd = new_socket; 506 | client_ctx->client_proto_info = client_info.protocol; 507 | pthread_mutex_unlock(&client_ctx->client_sock_lock); 508 | 509 | /* everything is good! */ 510 | INFO("Adding client to the client connection list socket %d " 511 | "version %x protocol %x", 512 | new_socket, client_info.version, client_info.protocol); 513 | ret = 0; 514 | 515 | exit: 516 | return ret; 517 | } 518 | 519 | int 520 | main(int argc, char **argv) { 521 | struct sockaddr_un local, remote; 522 | fd_set readfds, master; 523 | pthread_t init_thread; 524 | pthread_t *init_thread_ptr = NULL; 525 | socklen_t len; 526 | int i, maxfd; 527 | int dummy_value = 0, ret = 0; 528 | int sock = -1, new_socket = -1; 529 | uint8_t new_client_connreq = 0; 530 | 531 | /* Process command line options */ 532 | if (vmw_process_option(argc, argv)) { 533 | ret = 1; 534 | goto exit; 535 | } 536 | 537 | if (version_flag) { 538 | ret = 0; 539 | goto exit; 540 | } 541 | 542 | /* Setting the log level */ 543 | vmw_handle_config_change_signal(); 544 | 545 | /* 546 | * Setup signal handler to catch SIGINT and SIGTERM for provided graceful 547 | * shutdown 548 | */ 549 | if (vmw_set_sighandler()) { 550 | ret = 1; 551 | goto exit; 552 | } 553 | 554 | /* Create unix domain socket */ 555 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 556 | ERROR("Socket creation failed with error %s", strerror(errno)); 557 | ret = sock; 558 | goto exit; 559 | } 560 | 561 | local.sun_family = AF_UNIX; 562 | strcpy(local.sun_path, SOCK_PATH); 563 | unlink(local.sun_path); 564 | len = strlen(local.sun_path) + sizeof(local.sun_family); 565 | 566 | ret = bind(sock, (struct sockaddr *)&local, len); 567 | if (-1 == ret) { 568 | ERROR("bind() failed with error %s", strerror(errno)); 569 | goto exit; 570 | } 571 | 572 | ret = listen(sock, MAX_CLIENTS); 573 | if (-1 == ret) { 574 | ERROR("bind() failed with error %s", strerror(errno)); 575 | goto exit; 576 | } 577 | 578 | /* Clear the file descriptor set that to be monitored by select */ 579 | FD_ZERO(&readfds); 580 | 581 | /* Add sock fd to FD set to be monitored by select for new connection */ 582 | FD_SET(sock, &readfds); 583 | master = readfds; 584 | maxfd = sock; 585 | 586 | global_queued_pkthash = g_hash_table_new_full(g_direct_hash, 587 | g_direct_equal, 588 | NULL, 589 | cleanup_global_hash_entry); 590 | for (i = 0; i < MAX_CLIENTS; i++) { 591 | /* 592 | * Create per client hash table for keeping record of packets which are 593 | * delivered to the client and verdict yet to be receivied for them. 594 | */ 595 | g_client_ctx[i].client_sockfd = -1; 596 | g_client_ctx[i].queued_pkthash = g_hash_table_new(g_direct_hash, 597 | g_direct_equal); 598 | pthread_mutex_init(&g_client_ctx[i].client_sock_lock, NULL); 599 | } 600 | 601 | while (1) { 602 | 603 | /* Is process/daemon being shutdown? */ 604 | if (ATOMIC_OR(&g_need_to_quit, 0)) { 605 | break; 606 | } 607 | 608 | /* 609 | * Copy the master set back to readfds set so that sock fd can be 610 | * monitored again for any new connection 611 | */ 612 | readfds = master; 613 | ret = vmw_wait_for_event(maxfd, &readfds, new_client_connreq); 614 | if (-1 == ret) { 615 | if (EINTR == errno) { 616 | continue; 617 | } 618 | ERROR("Failed to accept new connection, select failure error %s", 619 | strerror(errno)); 620 | break; 621 | } else if (0 == ret) { 622 | /* Looks like signal is received for graceful shutdown */ 623 | continue; 624 | } 625 | 626 | len = sizeof(remote); 627 | new_socket = accept(sock, (struct sockaddr *)&remote, &len); 628 | if (-1 == new_socket) { 629 | ERROR("bind() failed with error %s", strerror(errno)); 630 | ret = new_socket; 631 | ret = -1; 632 | goto cleanup; 633 | } 634 | 635 | INFO("Connection from client socket %d is recevied", new_socket); 636 | for (i = 0; i < MAX_CLIENTS; i++) { 637 | pthread_mutex_lock(&g_client_ctx[i].client_sock_lock); 638 | if (IS_CLIENT_FD_FREE(g_client_ctx[i])) { 639 | pthread_mutex_unlock(&g_client_ctx[i].client_sock_lock); 640 | 641 | /* perform version handshake with client 642 | * this function closes the given fd in erroneous case 643 | */ 644 | ret = vmw_handshake_version(new_socket, &g_client_ctx[i]); 645 | if (ret < 0) { 646 | ERROR("Failed to complete connection with client socket %d", 647 | new_socket); 648 | break; 649 | } 650 | 651 | /* 652 | * Send new client connection notification to vmw_client_msg_recv 653 | * thread for updating client_cnt array 654 | */ 655 | write(process_exit_fds[1], &dummy_value, sizeof(dummy_value)); 656 | break; 657 | } else { 658 | pthread_mutex_unlock(&g_client_ctx[i].client_sock_lock); 659 | } 660 | } 661 | 662 | if (i == MAX_CLIENTS) { 663 | ERROR("Closing the connection with new client socket %d as maximum " 664 | "number of client %d is already registered", 665 | new_socket, MAX_CLIENTS); 666 | close(new_socket); 667 | new_socket = -1; 668 | } 669 | 670 | if (!ATOMIC_OR(&g_vmw_init_done, 0)) { 671 | pthread_create(&init_thread, NULL, vmw_init, NULL); 672 | init_thread_ptr = &init_thread; 673 | } 674 | } 675 | 676 | if (init_thread_ptr) { 677 | pthread_join(init_thread, NULL); 678 | } 679 | 680 | cleanup: 681 | for (i = 0; i < MAX_CLIENTS; i++) { 682 | if (g_client_ctx[i].client_sockfd > 0) { 683 | close(g_client_ctx[i].client_sockfd); 684 | } 685 | pthread_mutex_destroy(&g_client_ctx[i].client_sock_lock); 686 | g_hash_table_remove_all(g_client_ctx[i].queued_pkthash); 687 | g_hash_table_destroy(g_client_ctx[i].queued_pkthash); 688 | } 689 | g_hash_table_remove_all(global_queued_pkthash); 690 | g_hash_table_destroy(global_queued_pkthash); 691 | 692 | closelog(); 693 | unlink(VMW_PID_FILE); 694 | 695 | exit: 696 | if (sock > 0) { 697 | close(sock); 698 | } 699 | return ret; 700 | } 701 | -------------------------------------------------------------------------------- /src/vmw_conn_netfilter.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright (C) 2018 VMware, Inc. All rights reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or modify it under 6 | * the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; version 2. 8 | 9 | * This program is distributed in the hope that it will be useful, but WITHOUT 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 12 | * details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with this program; if not, write to the Free Software Foundation, Inc., 16 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 17 | * 18 | */ 19 | 20 | /* 21 | * SPDX-License-Identifier: GPL-2.0-only 22 | */ 23 | 24 | /* 25 | * This file (a part of vmw_conn_notify) contains logic to interact with 26 | * netfilter libraries and the connected clients. 27 | * 28 | * It forks two threads : 29 | * 1. vmw_netfilter_event_handler : It uses libnetfilter_queue and 30 | * libnetfilter_conntrack to receive network connection events from 31 | * netfilter kernel modules. 32 | * 33 | * 2. vmw_client_msg_recv : It receives client's response and redirects back 34 | * to appropriate netfilter library (which requires verdict). 35 | * 36 | * Please note that events are deliverd to the connected client through unix 37 | * domain socket in netfilter library callbacks. 38 | */ 39 | 40 | #include "vmw_conn.h" 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifndef DNS_PORT 49 | #define DNS_PORT (53) 50 | #endif 51 | 52 | /* Maximum queue length to handle connection burst */ 53 | #define VNET_NFQLENGTH 65536 54 | 55 | /* Packet size to be retrieved from netfilter queue */ 56 | #define VNET_BUFSIZE 1024 57 | 58 | /* Mark on packets to be put by our daemon after processing. */ 59 | #define VMW_NFQ_PKT_MARK 0x1 60 | 61 | /* Loop back or local address and mask */ 62 | #define VMW_LOOPBACK_ADDRESS "127.0.0.1" 63 | #define VMW_LOOPBACK_MASK 0xffffffff 64 | 65 | 66 | /* 67 | * A wrapper structure to keep netfilter queue handle and FD for getting 68 | * packets from queue library/kernel module. 69 | */ 70 | struct _vmw_net_queue { 71 | struct nfq_handle *handle; /* Netfilter queue connection handle */ 72 | struct nfq_q_handle *qhandle; /* Netfilter queue handle */ 73 | int qfd; /* Netfilter queue fd */ 74 | }; 75 | 76 | /* 77 | * A wrapper structure to keep netfilter conntrack handle and FD for getting 78 | * connection states from conntrack library/kernel module. 79 | */ 80 | struct _vmw_net_conntrack { 81 | struct nfct_handle *cthandle; /* Netfilter conntrack handle */ 82 | int ctfd; /* Netfilter conntrack FD */ 83 | }; 84 | 85 | /* 86 | * Global data structure that contains netfilter related structures, listen 87 | * start/stop hash cache and thread pool structure for delivering events. 88 | */ 89 | struct vmw_net_session { 90 | struct _vmw_net_queue queue_ctx; /* NFQ handle & FD */ 91 | struct _vmw_net_conntrack conntrack_ctx; /* Conntack FD and handle */ 92 | pthread_mutex_t queue_lock; /* Lock to sync nfq send/recv */ 93 | }; 94 | 95 | /* Global structure to maintain netfilter Queue and conntrack handle */ 96 | struct vmw_net_session *vmw_net_sess_handle; 97 | 98 | /* 99 | * Global lock to protect concurrent updates and lookups of the global 100 | * hashtable 101 | */ 102 | pthread_mutex_t global_pkthash_lock = PTHREAD_MUTEX_INITIALIZER; 103 | 104 | /* 105 | * Lock used to send the payload and message header to client over UDS 106 | */ 107 | pthread_mutex_t clientUDSLock; 108 | 109 | /* 110 | * Global variable indicates completion of all necessary 111 | * initialization 112 | */ 113 | volatile int g_vmw_init_done = 0; 114 | 115 | extern struct vmw_client_scope g_client_ctx[MAX_CLIENTS]; 116 | extern GHashTable *global_queued_pkthash; 117 | extern volatile int g_need_to_quit; 118 | 119 | extern int 120 | vmw_wait_for_event(int, fd_set *, uint8_t); 121 | extern void 122 | vmw_notify_exit(); 123 | 124 | /* 125 | * Check if the client registered protocol and packet protocol matches 126 | */ 127 | static inline bool 128 | vmw_match_protocols(struct vmw_client_scope client, 129 | struct vmw_conn_identity_data *conn) 130 | { 131 | bool match = FALSE; 132 | switch (conn->protocol) { 133 | case IPPROTO_TCP: 134 | { 135 | if (OUTBOUND_PRECONNECT == conn->event_type) { 136 | match = client.client_proto_info & TCP_OUT_PRE_CONN_SUPPORT ? TRUE : FALSE; 137 | } else if (POSTCONNECT == conn->event_type) { 138 | match = client.client_proto_info & TCP_EST_CONN_SUPPORT ? TRUE : FALSE; 139 | } else if (DISCONNECT == conn->event_type) { 140 | match = client.client_proto_info & TCP_CLOSE_CONN_SUPPORT ? TRUE : FALSE; 141 | } else if (INBOUND_PRECONNECT == conn->event_type) { 142 | match = client.client_proto_info & TCP_IN_PRE_CONN_SUPPORT ? TRUE : FALSE; 143 | } 144 | break; 145 | } 146 | case IPPROTO_UDP: 147 | { 148 | match = client.client_proto_info & UDP_SUPPORT ? TRUE : FALSE; 149 | break; 150 | } 151 | default: 152 | { 153 | ERROR("Protocol %d not handled packet id %d event type %x", 154 | conn->protocol, conn->event_id, conn->event_type); 155 | } 156 | } 157 | return match; 158 | } 159 | 160 | 161 | /* 162 | * Destructor for the value entry of global hash table 163 | */ 164 | void 165 | cleanup_global_hash_entry(gpointer data) 166 | { 167 | global_packet_info *packet = NULL; 168 | if (data) { 169 | packet = (global_packet_info *)data; 170 | pthread_mutex_destroy(&packet->lock); 171 | DEBUG("Cleaning packet data with eventid %d from global hash table", 172 | packet->event_id); 173 | free(data); 174 | data = NULL; 175 | } 176 | } 177 | 178 | /* 179 | * Preserve the existing mark of packet and append it with 180 | * vmw_notifier mark 181 | */ 182 | uint32_t 183 | update_packet_mark(struct nfq_data *nfad) 184 | { 185 | return nfq_get_nfmark(nfad) | VMW_NFQ_PKT_MARK; 186 | } 187 | 188 | /* Log ipv4 address */ 189 | void 190 | vmw_log_ipv4_address(struct sockaddr_in *addr) 191 | { 192 | char dst[INET_ADDRSTRLEN] = {0}; 193 | 194 | if (!addr) { 195 | goto exit; 196 | } 197 | 198 | inet_ntop(AF_INET, 199 | (const void *)&addr->sin_addr, 200 | dst, 201 | INET_ADDRSTRLEN); 202 | 203 | DEBUG("addr: %s, port: %u\n", dst, addr->sin_port); 204 | 205 | exit: 206 | return; 207 | } 208 | 209 | /* Log ipv6 address */ 210 | void 211 | vmw_log_ipv6_address(struct sockaddr_in6 *addr) 212 | { 213 | char dst[INET6_ADDRSTRLEN] = {0}; 214 | 215 | if (!addr) { 216 | goto exit; 217 | } 218 | 219 | inet_ntop(AF_INET6, 220 | (const void *)&addr->sin6_addr, 221 | dst, 222 | INET6_ADDRSTRLEN); 223 | DEBUG("addr: %s, port: %u\n", dst, addr->sin6_port); 224 | 225 | exit: 226 | return; 227 | } 228 | 229 | /* 230 | * Deduce inbound/outbound event type based on network hook embedded in packet 231 | * header 232 | */ 233 | enum vmw_conn_event_type 234 | get_event_type(struct nfq_data *nfa) 235 | { 236 | struct nfqnl_msg_packet_hdr *ph; 237 | uint32_t index; 238 | enum nf_inet_hooks hook; 239 | enum vmw_conn_event_type ret = MAX_EVENT; 240 | 241 | if (!nfa) { 242 | goto exit; 243 | } 244 | 245 | /* Get the nfq message packet header */ 246 | ph = nfq_get_msg_packet_hdr(nfa); 247 | if (NULL == ph) { 248 | goto exit; 249 | } 250 | hook = ph->hook; 251 | 252 | /* 253 | * The index of the device the queued packet was received via. 254 | * If the returned index is 0, the packet was locally generated or the input 255 | * interface is not known (i.e. POSTROUTING) 256 | */ 257 | index = nfq_get_indev(nfa); 258 | if (index && (NF_INET_LOCAL_IN == hook)) { 259 | ret = INBOUND_PRECONNECT; 260 | goto exit; 261 | } 262 | 263 | /* 264 | * The index of the device the queued packet will be sent out. If the 265 | * returned index is 0, the packet is destined for localhost or the output 266 | * interface is not yet known (ie. PREROUTING?). 267 | */ 268 | index = nfq_get_outdev(nfa); 269 | if (index && (NF_INET_LOCAL_OUT == hook)) { 270 | ret = OUTBOUND_PRECONNECT; 271 | goto exit; 272 | } 273 | 274 | DEBUG("Packet received for invalid hook %d", (int)hook); 275 | 276 | exit: 277 | return ret; 278 | } 279 | 280 | void 281 | vmw_process_global_packet(uint32_t event_id, 282 | int verdict, 283 | struct vmw_net_session *sess) 284 | { 285 | global_packet_info *global_packet = NULL; 286 | gpointer gkey = NULL; 287 | gpointer gvalue = NULL; 288 | uint32_t mark; 289 | 290 | pthread_mutex_lock(&global_pkthash_lock); 291 | g_hash_table_lookup_extended(global_queued_pkthash, 292 | GUINT_TO_POINTER(event_id), 293 | &gkey, 294 | &gvalue); 295 | pthread_mutex_unlock(&global_pkthash_lock); 296 | 297 | if (gvalue == NULL) { 298 | return; 299 | } 300 | global_packet = (global_packet_info*) gvalue; 301 | /* 302 | * To prevent races with conn_data_send, take the packet 303 | * lock. This ensures the first recv for a packet is not 304 | * processed till all clients have been sent the packet. 305 | */ 306 | pthread_mutex_lock(&global_packet->lock); 307 | 308 | /* 309 | * Verdict is sent either when the packet reference count is one 310 | * or when verdict of any client is NF_DROP 311 | */ 312 | if ((1 == global_packet->ref_count) || 313 | (verdict == NF_DROP)) { 314 | mark = global_packet->mark; 315 | pthread_mutex_unlock(&global_packet->lock); 316 | 317 | pthread_mutex_lock(&global_pkthash_lock); 318 | g_hash_table_remove(global_queued_pkthash, 319 | GUINT_TO_POINTER(event_id)); 320 | pthread_mutex_unlock(&global_pkthash_lock); 321 | 322 | pthread_mutex_lock(&sess->queue_lock); 323 | nfq_set_verdict2(sess->queue_ctx.qhandle, 324 | event_id, 325 | verdict, 326 | mark, 327 | 0, 328 | NULL); 329 | pthread_mutex_unlock(&sess->queue_lock); 330 | } else { 331 | /* 332 | * Don't send verdict as this packet is being processed by other 333 | * client, just reduce reference count in the global packet hash 334 | * table 335 | */ 336 | global_packet->ref_count--; 337 | pthread_mutex_unlock(&global_packet->lock); 338 | } 339 | return; 340 | } 341 | 342 | /* 343 | * Release packet queued in netfilter NFQUEUE. This is called during client 344 | * disconnect. 345 | * Per client queued_pkthash contains packets that are delivered to the client 346 | * but response for them has not been come yet from the client so during 347 | * client disconnect, verdict for these packets are send to netfilter 348 | * library. On getting the verdict, netfilter library clears in-kernel data 349 | * structure for the packets. 350 | */ 351 | void 352 | vmw_queued_pkthash_cleanup(struct vmw_net_session *sess, 353 | struct vmw_client_scope *client_ptr) 354 | { 355 | GHashTableIter iter; 356 | gpointer key = NULL; 357 | gpointer value = NULL; 358 | uint32_t event_id; 359 | 360 | g_hash_table_iter_init(&iter, client_ptr->queued_pkthash); 361 | while (g_hash_table_iter_next(&iter, &key, &value)) { 362 | event_id = GPOINTER_TO_UINT(key); 363 | if (!value) { 364 | continue; 365 | } 366 | vmw_process_global_packet(event_id, NF_REPEAT, sess); 367 | 368 | g_hash_table_iter_remove(&iter); 369 | } 370 | 371 | return; 372 | } 373 | 374 | /* A client has disconnected, cleanup all related data structures */ 375 | void 376 | vmw_client_cleanup(struct vmw_net_session *sess, int idx) 377 | { 378 | int sd = 0; 379 | 380 | pthread_mutex_lock(&g_client_ctx[idx].client_sock_lock); 381 | sd = g_client_ctx[idx].client_sockfd; 382 | close(g_client_ctx[idx].client_sockfd); 383 | /* 384 | * Invalidate fd so send() adds no more packets to the 385 | * client hashtable 386 | */ 387 | g_client_ctx[idx].client_sockfd = -1; 388 | g_client_ctx[idx].pkthash_cleanup_wait = 1; 389 | pthread_mutex_unlock(&g_client_ctx[idx].client_sock_lock); 390 | 391 | /* 392 | * Cleanup the client hashtable and references to this client 393 | * in the global hashtable. 394 | */ 395 | vmw_queued_pkthash_cleanup(sess, &g_client_ctx[idx]); 396 | 397 | /* Cleanup completed, this index can be reused now */ 398 | pthread_mutex_lock(&g_client_ctx[idx].client_sock_lock); 399 | g_client_ctx[idx].pkthash_cleanup_wait = 0; 400 | pthread_mutex_unlock(&g_client_ctx[idx].client_sock_lock); 401 | 402 | WARN("Client %d disconnected", sd); 403 | return; 404 | } 405 | 406 | /* Receive message from client and forward verdict back to netfilter NFQUEUE */ 407 | void * 408 | vmw_client_msg_recv(void *arg) 409 | { 410 | struct vmw_net_session *sess = (struct vmw_net_session *)arg; 411 | vmw_verdict client_verdict = { 0 }; 412 | fd_set client_fds; 413 | int sd, max_sd, i, activity, ret; 414 | 415 | while (1) { 416 | if (ATOMIC_OR(&g_need_to_quit, 0)) { 417 | break; 418 | } 419 | /* 420 | * Only this thread frees client related 421 | * data structures so no need to hold a client_sock_lock 422 | */ 423 | max_sd = 0; 424 | FD_ZERO(&client_fds); 425 | for (i = 0 ; i < MAX_CLIENTS; i++) { 426 | sd = g_client_ctx[i].client_sockfd; 427 | if (sd > 0) { 428 | FD_SET(sd, &client_fds); 429 | if (sd > max_sd) { 430 | max_sd = sd; 431 | } 432 | } 433 | } 434 | /* 435 | * vmw_wait_for_event() returns when 436 | * 1. graceful shutdown is initiated; 437 | * 2. client response is received for the packet delivered earlier; 438 | * 3. new client connection is formed or an established client connection 439 | * is disconnected. 440 | */ 441 | activity = vmw_wait_for_event(max_sd, &client_fds, 1); 442 | if (activity <= 0) { 443 | continue; 444 | } 445 | for (i = 0; i < MAX_CLIENTS; i++) { 446 | sd = g_client_ctx[i].client_sockfd; 447 | if (sd < 0) { 448 | continue; 449 | } 450 | /* Data received or socket has been closed */ 451 | if (FD_ISSET(sd , &client_fds)) { 452 | ret = recv(sd, (void *)&client_verdict, sizeof(client_verdict), 0); 453 | if (!ret) { 454 | /* 455 | * Client got disconneted, so release packets queued in netfilter 456 | * NFQUEUE. 457 | */ 458 | vmw_client_cleanup(sess, i); 459 | continue; 460 | } 461 | if (!client_verdict.packetId) { 462 | continue; 463 | } 464 | if (client_verdict.verdict != NF_DROP) { 465 | client_verdict.verdict = NF_REPEAT; 466 | } 467 | vmw_process_global_packet(client_verdict.packetId, 468 | client_verdict.verdict, 469 | sess); 470 | 471 | pthread_mutex_lock(&g_client_ctx[i].client_sock_lock); 472 | g_hash_table_remove(g_client_ctx[i].queued_pkthash, 473 | GUINT_TO_POINTER(client_verdict.packetId)); 474 | pthread_mutex_unlock(&g_client_ctx[i].client_sock_lock); 475 | 476 | } 477 | } 478 | } 479 | 480 | return NULL; 481 | } 482 | 483 | /* Deliver network event to the connected clients */ 484 | int 485 | vmw_conn_data_send(struct vmw_conn_identity_data *conn_data, 486 | uint32_t packet_mark) 487 | { 488 | global_packet_info *global_packet = NULL; 489 | char *buf = NULL; 490 | int i, sd; 491 | int ret = 0, refcnt = 0; 492 | 493 | /* 494 | * The event id of each contrack event is zero. The conntrack event 495 | * is just a notification; there is no response required for the 496 | * conntrack event. 497 | */ 498 | if (conn_data->event_id) { 499 | /* 500 | * Init and add a global packet for this conn_data 501 | * to the hashtable 502 | */ 503 | global_packet = (global_packet_info *) 504 | malloc(sizeof(global_packet_info)); 505 | if (NULL == global_packet) { 506 | ret = -1; 507 | ERROR("Failed to allocate global packet info"); 508 | goto exit; 509 | } 510 | global_packet->event_id = conn_data->event_id; 511 | global_packet->ref_count = 0; 512 | global_packet->mark = packet_mark; 513 | pthread_mutex_init(&global_packet->lock, NULL); 514 | pthread_mutex_lock(&global_pkthash_lock); 515 | g_hash_table_replace(global_queued_pkthash, 516 | GUINT_TO_POINTER(conn_data->event_id), 517 | (gpointer)global_packet); 518 | pthread_mutex_unlock(&global_pkthash_lock); 519 | /* 520 | * Need to hold the packet lock till we finish 521 | * sending this packets to all clients. We don't want 522 | * process the recv from a client till we have finished sending 523 | * it to all clients as the race will make the refcounts 524 | * go wrong. 525 | */ 526 | pthread_mutex_lock(&global_packet->lock); 527 | } 528 | 529 | for (i = 0; i < MAX_CLIENTS; i++) { 530 | pthread_mutex_lock(&g_client_ctx[i].client_sock_lock); 531 | if (g_client_ctx[i].client_sockfd < 0) { 532 | pthread_mutex_unlock(&g_client_ctx[i].client_sock_lock); 533 | continue; 534 | } 535 | if (FALSE == vmw_match_protocols(g_client_ctx[i], conn_data)) { 536 | DEBUG("Protocol mismatch: client protocol %x packet prtocol %u " 537 | "packet id %d event type %x", g_client_ctx[i].client_proto_info, 538 | conn_data->protocol, conn_data->event_id, conn_data->event_type); 539 | pthread_mutex_unlock(&g_client_ctx[i].client_sock_lock); 540 | continue; 541 | 542 | } 543 | sd = g_client_ctx[i].client_sockfd; 544 | 545 | if (conn_data->event_id) { 546 | g_hash_table_replace(g_client_ctx[i].queued_pkthash, 547 | GUINT_TO_POINTER(conn_data->event_id), 548 | NULL); 549 | } 550 | pthread_mutex_unlock(&g_client_ctx[i].client_sock_lock); 551 | 552 | if (conn_data->dns_payload->payload != NULL) { 553 | buf = (char *) malloc(sizeof(*conn_data) + 554 | conn_data->dns_payload->len); 555 | if (NULL == buf) { 556 | ERROR("Failed to allocate memory for DNS payload buf"); 557 | ret = -1; 558 | goto exit; 559 | } 560 | memset(buf, 0, sizeof(*conn_data) + conn_data->dns_payload->len); 561 | memcpy(buf, conn_data,sizeof(*conn_data)); 562 | memcpy(buf + sizeof(*conn_data), conn_data->dns_payload->payload, 563 | conn_data->dns_payload->len); 564 | pthread_mutex_lock(&clientUDSLock); 565 | ret = send(sd, buf, sizeof(*conn_data) + conn_data->dns_payload->len, 566 | 0); 567 | pthread_mutex_unlock(&clientUDSLock); 568 | free(buf); 569 | } else { 570 | pthread_mutex_lock(&clientUDSLock); 571 | ret = send(sd, conn_data, sizeof(*conn_data), 0); 572 | pthread_mutex_unlock(&clientUDSLock); 573 | } 574 | 575 | if (ret <= 0) { 576 | ERROR("Could not send event %u to client %d (error %s)", 577 | conn_data->event_id, sd, strerror(errno)); 578 | /* Cleanup will be done by recv */ 579 | continue; 580 | } 581 | 582 | /* 583 | * Send message successfully to client, update the global 584 | * packet, only if this is not a conntrack event. 585 | */ 586 | if (conn_data->event_id) { 587 | global_packet->ref_count++; 588 | } 589 | } 590 | 591 | exit: 592 | if (global_packet) { 593 | refcnt = global_packet->ref_count; 594 | pthread_mutex_unlock(&global_packet->lock); 595 | /* 596 | * No clients were sent this packet, remove it from the hash 597 | */ 598 | if (refcnt == 0) { 599 | pthread_mutex_lock(&global_pkthash_lock); 600 | g_hash_table_remove(global_queued_pkthash, 601 | GUINT_TO_POINTER(conn_data->event_id)); 602 | pthread_mutex_unlock(&global_pkthash_lock); 603 | } 604 | } 605 | return ret; 606 | } 607 | 608 | /* Log connection related data and sends the data to client */ 609 | int 610 | vmw_client_notify(struct vmw_conn_identity_data *conn_data, 611 | struct vmw_net_session *sess, 612 | uint32_t packet_mark) 613 | { 614 | int ret = -1; 615 | 616 | if (!conn_data || !sess) { 617 | goto exit; 618 | } 619 | 620 | if ((AF_INET == conn_data->src.ss_family) && 621 | (AF_INET == conn_data->dst.ss_family)) { 622 | vmw_log_ipv4_address((struct sockaddr_in *)&conn_data->src); 623 | vmw_log_ipv4_address((struct sockaddr_in *)&conn_data->dst); 624 | } else if ((AF_INET6 == conn_data->src.ss_family) && 625 | (AF_INET6 == conn_data->dst.ss_family)) { 626 | vmw_log_ipv6_address((struct sockaddr_in6 *)&conn_data->src); 627 | vmw_log_ipv6_address( (struct sockaddr_in6 *)&conn_data->dst); 628 | } else { 629 | goto exit; 630 | } 631 | 632 | ret = vmw_conn_data_send(conn_data, packet_mark); 633 | 634 | exit: 635 | return ret; 636 | } 637 | 638 | /* 639 | * Receive the network event notification from conntrack and NFQUEUE and 640 | * process it. 641 | */ 642 | void * 643 | vmw_netfilter_event_handler(void *arg) 644 | { 645 | char buf[VNET_BUFSIZE] __attribute__ ((aligned)); 646 | struct vmw_net_session *sess = (struct vmw_net_session *)arg; 647 | ssize_t bread = 0; 648 | int maxfd, status = 0; 649 | fd_set session_fds, master; 650 | 651 | if (!sess) { 652 | status = 1; 653 | goto exit; 654 | } 655 | 656 | /* Clear the file descriptor set that to be monitored by select */ 657 | FD_ZERO(&session_fds); 658 | /* Add queue FD to FD set to be monitored by select for notificaton */ 659 | FD_SET(sess->queue_ctx.qfd, &session_fds); 660 | /* Add conntrack FD to FD set to be monitored by select for notificaton */ 661 | FD_SET(sess->conntrack_ctx.ctfd, &session_fds); 662 | 663 | master = session_fds; 664 | maxfd = sess->conntrack_ctx.ctfd; 665 | /* Keep track of the biggest file descriptor */ 666 | if (maxfd < sess->queue_ctx.qfd) { 667 | maxfd = sess->queue_ctx.qfd; 668 | } 669 | 670 | while (1) { 671 | if (ATOMIC_OR(&g_need_to_quit, 0)) { 672 | break; 673 | } 674 | 675 | /* 676 | * Copy the master set back to readfds set so that both descriptors can be 677 | * monitored again for any notification 678 | */ 679 | session_fds = master; 680 | status = vmw_wait_for_event(maxfd, &session_fds, 0); 681 | /* select() is used to receive events from conntrack and nfqueue */ 682 | if (-1 == status) { 683 | if (EINTR == errno) { 684 | continue; 685 | } 686 | ERROR("Failed to read from netfilter channel, select failure error %s", 687 | strerror(errno)); 688 | goto exit; 689 | } else if (0 == status) { 690 | continue; 691 | } 692 | 693 | if (FD_ISSET(sess->queue_ctx.qfd, &session_fds)) { 694 | /* Read the packet from the netlink socket which is in NFQUEUE */ 695 | bread = recv(sess->queue_ctx.qfd, buf, sizeof(buf), 0); 696 | if (bread > 0) { 697 | pthread_mutex_lock(&sess->queue_lock); 698 | nfq_handle_packet(sess->queue_ctx.handle, buf, bread); 699 | pthread_mutex_unlock(&sess->queue_lock); 700 | } 701 | 702 | /* 703 | * To avoid ENOBUFS error , queue max length is increased with 704 | * nfq_set_queue_maxlen as per the netfilter documentation 705 | */ 706 | if ((bread < 0) && (ENOBUFS == errno)) { 707 | ERROR("Dropping packets due to insufficient memory!\n"); 708 | continue; 709 | } 710 | } 711 | 712 | if (FD_ISSET(sess->conntrack_ctx.ctfd, &session_fds)) { 713 | /* Read connection state from kernel connection table */ 714 | status = nfct_catch(sess->conntrack_ctx.cthandle); 715 | } 716 | } 717 | 718 | exit: 719 | return NULL; 720 | } 721 | 722 | /* 723 | * Callback to handle the received conntrack events in nfct_catch thread 724 | * context. 725 | */ 726 | static int 727 | vmw_net_conntrack_callback(enum nf_conntrack_msg_type type, 728 | struct nf_conntrack *ct, 729 | void *data) 730 | { 731 | struct vmw_conn_identity_data *conn_data = NULL; 732 | struct vmw_net_session *sess = (struct vmw_net_session *)data; 733 | uint8_t family; 734 | char state; 735 | 736 | /* Ignore all TCP states other than ESTABLISH, Close, Last Ack, Time Wait */ 737 | state = nfct_get_attr_u8(ct, ATTR_TCP_STATE); 738 | if ((state != TCP_CONNTRACK_CLOSE) && 739 | (state != TCP_CONNTRACK_TIME_WAIT) && 740 | (state != TCP_CONNTRACK_LAST_ACK) && 741 | (state != TCP_CONNTRACK_ESTABLISHED)) { 742 | goto exit; 743 | } 744 | 745 | conn_data = (struct vmw_conn_identity_data *) 746 | calloc(1, sizeof(struct vmw_conn_identity_data)); 747 | if (!conn_data) { 748 | ERROR("Memory allocation failed for msg data"); 749 | goto exit; 750 | } 751 | 752 | /* Retrieve L3 protocol address and L4 protocol port number */ 753 | family = nfct_get_attr_u8(ct, ATTR_L3PROTO); 754 | switch(family) { 755 | case AF_INET: 756 | conn_data->src.ss_family = AF_INET; 757 | conn_data->dst.ss_family = AF_INET; 758 | struct sockaddr_in *src = (struct sockaddr_in *)&conn_data->src; 759 | struct sockaddr_in *dst = (struct sockaddr_in *)&conn_data->dst; 760 | src->sin_addr.s_addr = nfct_get_attr_u32(ct, ATTR_IPV4_SRC); 761 | dst->sin_addr.s_addr = nfct_get_attr_u32(ct, ATTR_IPV4_DST); 762 | src->sin_port = htons(nfct_get_attr_u32(ct, ATTR_PORT_SRC)); 763 | dst->sin_port = htons(nfct_get_attr_u32(ct, ATTR_PORT_DST)); 764 | break; 765 | 766 | case AF_INET6: 767 | conn_data->src.ss_family = AF_INET6; 768 | conn_data->dst.ss_family = AF_INET6; 769 | struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&conn_data->src; 770 | struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)&conn_data->dst; 771 | memcpy(&src6->sin6_addr.s6_addr, 772 | nfct_get_attr(ct, ATTR_IPV6_SRC), 773 | sizeof(uint32_t) * 4); 774 | memcpy(&dst6->sin6_addr.s6_addr, 775 | nfct_get_attr(ct, ATTR_IPV6_DST), 776 | sizeof(uint32_t) * 4); 777 | src6->sin6_port = htons(nfct_get_attr_u32(ct, ATTR_PORT_SRC)); 778 | dst6->sin6_port = htons(nfct_get_attr_u32(ct, ATTR_PORT_DST)); 779 | break; 780 | 781 | default: 782 | DEBUG("Invalid protocol family %uhh", family); 783 | goto exit; 784 | } 785 | 786 | if (TCP_CONNTRACK_ESTABLISHED == state) { 787 | conn_data->event_type = POSTCONNECT; 788 | } else { 789 | conn_data->event_type = DISCONNECT; 790 | } 791 | conn_data->event_id = 0; 792 | conn_data->protocol = nfct_get_attr_u8(ct, ATTR_L4PROTO); 793 | /* Send the packet to client */ 794 | (void)vmw_client_notify(conn_data, sess, 0); 795 | 796 | exit: 797 | if (conn_data) { 798 | free(conn_data); 799 | conn_data = NULL; 800 | } 801 | /* Break nfct_catch loop as process is being stopped */ 802 | if (ATOMIC_OR(&g_need_to_quit, 0)) { 803 | return NFCT_CB_STOP; 804 | } 805 | return NFCT_CB_CONTINUE; 806 | } 807 | 808 | /* Register callback with conntrack to receive network events */ 809 | int 810 | vmw_net_conntrack_init(void *arg) 811 | { 812 | struct nfct_handle *h; 813 | struct nfct_filter *filter; 814 | struct vmw_net_session *sess = (struct vmw_net_session *)arg; 815 | int flags, ret = 0; 816 | 817 | if (!sess) { 818 | ret = -1; 819 | goto exit; 820 | } 821 | 822 | h = nfct_open(CONNTRACK, NF_NETLINK_CONNTRACK_UPDATE); 823 | if (!h) { 824 | ERROR("Error %s during nfct_open()", strerror(errno)); 825 | ret = -1; 826 | goto exit; 827 | } 828 | 829 | filter = nfct_filter_create(); 830 | if (!filter) { 831 | ERROR("Error %s during nfct_create_filter", strerror(errno)); 832 | ret = -1; 833 | goto exit; 834 | } 835 | 836 | nfct_filter_add_attr_u32(filter, NFCT_FILTER_L4PROTO, IPPROTO_TCP); 837 | nfct_filter_add_attr_u32(filter, NFCT_FILTER_L4PROTO, IPPROTO_UDP); 838 | 839 | /* Instruct conntrack to deliver IPv4 events in host-byte order */ 840 | struct nfct_filter_ipv4 filter_ipv4 = { 841 | .addr = ntohl(inet_addr(VMW_LOOPBACK_ADDRESS)), 842 | .mask = VMW_LOOPBACK_MASK, 843 | }; 844 | 845 | /* Ignore whatever that comes from 127.0.0.1 */ 846 | nfct_filter_set_logic(filter, 847 | NFCT_FILTER_SRC_IPV4, 848 | NFCT_FILTER_LOGIC_NEGATIVE); 849 | nfct_filter_add_attr(filter, NFCT_FILTER_SRC_IPV4, &filter_ipv4); 850 | 851 | /* Instruct conntrack to deliver IPv6 events in host-byte order */ 852 | struct nfct_filter_ipv6 filter_ipv6 = { 853 | .addr = { 0x0, 0x0, 0x0, 0x1 }, 854 | .mask = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }, 855 | }; 856 | 857 | /* Ignore whatever that comes from ::1 (loopback) */ 858 | nfct_filter_set_logic(filter, 859 | NFCT_FILTER_SRC_IPV6, 860 | NFCT_FILTER_LOGIC_NEGATIVE); 861 | nfct_filter_add_attr(filter, NFCT_FILTER_SRC_IPV6, &filter_ipv6); 862 | 863 | ret = nfct_filter_attach(nfct_fd(h), filter); 864 | if (-1 == ret) { 865 | ERROR("Error %s during nfct_filter_attach", strerror(errno)); 866 | goto exit; 867 | } 868 | 869 | /* Register a callback with netfilter conntrack library */ 870 | ret = nfct_callback_register(h, 871 | NFCT_T_ALL, 872 | vmw_net_conntrack_callback, 873 | (void *)sess); 874 | if (-1 == ret) { 875 | ERROR("Error %s during nfct_callback_register", strerror(errno)); 876 | goto exit; 877 | } 878 | sess->conntrack_ctx.cthandle = h; 879 | sess->conntrack_ctx.ctfd = nfct_fd(h); 880 | 881 | /* Make conntack fd as non-blocking fd so that it can be passed to select */ 882 | flags = fcntl(sess->conntrack_ctx.ctfd, F_GETFL, 0); 883 | if (-1 == ret) { 884 | ERROR("Error %s during fctnl while accessing flag", strerror(errno)); 885 | goto exit; 886 | } 887 | 888 | ret = fcntl(sess->conntrack_ctx.ctfd, F_SETFL, flags | O_NONBLOCK); 889 | if (-1 == ret) { 890 | ERROR("Error %s during fctnl while setting flag", strerror(errno)); 891 | goto exit; 892 | } 893 | 894 | exit: 895 | if (filter) { 896 | nfct_filter_destroy(filter); 897 | filter = NULL; 898 | } 899 | return ret; 900 | } 901 | 902 | /* 903 | * A callback which gets invoked in the context of receiver thread from 904 | * nfq_handle_packet function 905 | */ 906 | static int 907 | vmw_net_queue_callback(struct nfq_q_handle *qh, 908 | struct nfgenmsg *nfmsg, 909 | struct nfq_data *nfa, 910 | void *arg) 911 | { 912 | struct nfqnl_msg_packet_hdr *ph; 913 | struct iphdr *ipinfo = NULL; 914 | struct ip6_hdr *ip6info = NULL; 915 | struct tcphdr *tcp_info = NULL; 916 | struct udphdr *udp_info = NULL; 917 | struct vmw_conn_identity_data *conn_data = NULL; 918 | struct vmw_net_session *sess = (struct vmw_net_session*)arg; 919 | int ret = -1; 920 | enum vmw_conn_event_type event_type = 0; 921 | unsigned char *data = NULL; 922 | uint32_t event_id; 923 | uint32_t packet_mark; 924 | uint32_t hdrLen = 0; 925 | 926 | sess = vmw_net_sess_handle; 927 | if (!nfa|| !sess) { 928 | ret = -1; 929 | goto exit; 930 | } 931 | 932 | conn_data = (struct vmw_conn_identity_data *) 933 | calloc(1, sizeof(struct vmw_conn_identity_data)); 934 | if (!conn_data) { 935 | ERROR("Memory allocation failed for msg data"); 936 | ret = -1; 937 | goto exit; 938 | } 939 | 940 | ph = nfq_get_msg_packet_hdr(nfa); 941 | if (ph) { 942 | 943 | /* event_id is identified with packet_id */ 944 | event_id = ntohl(ph->packet_id); 945 | DEBUG("hw_protocol=0x%04x, hook=%u, id=%u", ntohs(ph->hw_protocol), 946 | ph->hook, event_id); 947 | conn_data->event_id = event_id; 948 | } 949 | 950 | ret = nfq_get_payload(nfa, &data); 951 | if (-1 == ret || ret > VNET_BUFSIZE) { 952 | ERROR("Invalid packet length: %d, packet id: %u", ret, event_id); 953 | ret = -1; 954 | goto exit; 955 | } 956 | 957 | /* Get the iphdr from the payload */ 958 | ipinfo = (struct iphdr *)data; 959 | 960 | if (IPVERSION == ipinfo->version) { 961 | conn_data->src.ss_family = AF_INET; 962 | conn_data->dst.ss_family = AF_INET; 963 | struct sockaddr_in *src = (struct sockaddr_in *)&conn_data->src; 964 | struct sockaddr_in *dst = (struct sockaddr_in *)&conn_data->dst; 965 | src->sin_addr.s_addr = ipinfo->saddr; 966 | dst->sin_addr.s_addr = ipinfo->daddr; 967 | 968 | if (IPPROTO_TCP == ipinfo->protocol) { 969 | /* Get the tcphdr from the payload */ 970 | tcp_info = (struct tcphdr *)(data + sizeof(*ipinfo)); 971 | src->sin_port = ntohs(tcp_info->source); 972 | dst->sin_port = ntohs(tcp_info->dest); 973 | } else if (IPPROTO_UDP == ipinfo->protocol) { 974 | /* Get the tcphdr from the payload */ 975 | udp_info = (struct udphdr *)(data + sizeof(*ipinfo)); 976 | src->sin_port = ntohs(udp_info->source); 977 | dst->sin_port = ntohs(udp_info->dest); 978 | if (DNS_PORT == src->sin_port) { 979 | hdrLen = ntohs(udp_info->len); 980 | } 981 | } else { 982 | INFO("Non tcp/ip traffic: %d, id=%u", ipinfo->protocol, event_id); 983 | ret = -1; 984 | goto exit; 985 | } 986 | conn_data->protocol = ipinfo->protocol; 987 | vmw_log_ipv4_address((struct sockaddr_in *)src); 988 | vmw_log_ipv4_address((struct sockaddr_in *)dst); 989 | } else { 990 | ip6info = (struct ip6_hdr *)data; 991 | conn_data->src.ss_family = AF_INET6; 992 | conn_data->dst.ss_family = AF_INET6; 993 | struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&(conn_data->src); 994 | struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)&(conn_data->dst); 995 | 996 | if (IPPROTO_TCP == ip6info->ip6_nxt) { 997 | tcp_info = (struct tcphdr *)(data + sizeof(*ip6info)); 998 | src6->sin6_port = ntohs(tcp_info->source); 999 | dst6->sin6_port = ntohs(tcp_info->dest); 1000 | 1001 | } else if (IPPROTO_UDP == ip6info->ip6_nxt) { 1002 | udp_info = (struct udphdr *)(data + sizeof(*ip6info)); 1003 | src6->sin6_port = ntohs(udp_info->source); 1004 | dst6->sin6_port = ntohs(udp_info->dest); 1005 | if (DNS_PORT == src6->sin6_port) { 1006 | hdrLen = ntohs(udp_info->len); 1007 | } 1008 | } else { 1009 | INFO("Non tcp/ipv6 traffic: %d, id=%u", ip6info->ip6_nxt, event_id); 1010 | ret = -1; 1011 | goto exit; 1012 | } 1013 | memcpy(src6->sin6_addr.s6_addr, 1014 | ip6info->ip6_src.s6_addr, 1015 | 16); 1016 | memcpy(dst6->sin6_addr.s6_addr, 1017 | ip6info->ip6_dst.s6_addr, 1018 | 16); 1019 | 1020 | conn_data->protocol = ip6info->ip6_nxt; 1021 | } 1022 | 1023 | event_type = get_event_type(nfa); 1024 | 1025 | /* Don't add packet depicting invalid tcp state to the thread pool queue */ 1026 | if (tcp_info) { 1027 | if ((!tcp_info->syn) || (tcp_info->ack)) { 1028 | DEBUG("Invalid tcp packet, ignoring packet: id=%u", event_id); 1029 | ret = -1; 1030 | goto exit; 1031 | } 1032 | } 1033 | 1034 | /* Don't add packet received on invalid hook to the thread pool queue */ 1035 | if (0 == event_type) { 1036 | INFO("Invalid event type, ignoring packet: id=%u", event_id); 1037 | ret = -1; 1038 | goto exit; 1039 | } 1040 | 1041 | /* Mark the packet with our mark */ 1042 | packet_mark = update_packet_mark(nfa); 1043 | 1044 | conn_data->event_type = event_type; 1045 | conn_data->event_id = event_id; 1046 | 1047 | if ((event_type == INBOUND_PRECONNECT) && (hdrLen != 0)) { 1048 | conn_data->dns_payload->len = hdrLen - sizeof(struct udphdr); 1049 | conn_data->dns_payload->payload = malloc(conn_data->dns_payload->len); 1050 | if (NULL != conn_data->dns_payload->payload) { 1051 | memcpy(conn_data->dns_payload->payload, 1052 | (void*)((unsigned long)udp_info + sizeof(struct udphdr)), 1053 | conn_data->dns_payload->len); 1054 | } 1055 | } 1056 | /* Deliver packet to client */ 1057 | ret = vmw_client_notify(conn_data, sess, packet_mark); 1058 | if (ret <= 0) { 1059 | goto exit; 1060 | } 1061 | 1062 | DEBUG("Event %u is queued for delivery, packet id=%u", 1063 | event_type, event_id); 1064 | 1065 | exit: 1066 | if (conn_data->dns_payload->payload != NULL) { 1067 | free(conn_data->dns_payload->payload); 1068 | conn_data->dns_payload->payload = NULL; 1069 | } 1070 | 1071 | /* If there are no clients connected ret = 0 */ 1072 | if (ret <= 0 && sess) { 1073 | /* 1074 | * Re-inject the packet by providing NF_REPEAT verdict with mask bit set 1075 | * so that the packet can be iterated through rest of the rules in the 1076 | * ip chain. 1077 | */ 1078 | nfq_set_verdict2(sess->queue_ctx.qhandle, 1079 | event_id, 1080 | NF_REPEAT, 1081 | packet_mark, 1082 | 0, 1083 | NULL); 1084 | } 1085 | 1086 | if (conn_data) { 1087 | free(conn_data); 1088 | } 1089 | return ret; 1090 | } 1091 | 1092 | /* Register callback with netfilter Queue library */ 1093 | int 1094 | vmw_net_queue_init(struct vmw_net_session *sess) 1095 | { 1096 | int ret = 0; 1097 | struct nfq_q_handle *qh; 1098 | 1099 | if (!sess) { 1100 | ret = -1; 1101 | goto exit; 1102 | } 1103 | sess->queue_ctx.handle = nfq_open(); 1104 | if (!sess->queue_ctx.handle) { 1105 | ERROR("Error %s during nfq_open()", strerror(errno)); 1106 | ret = -1; 1107 | goto exit; 1108 | } 1109 | 1110 | ret = nfq_unbind_pf(sess->queue_ctx.handle, AF_INET); 1111 | if (ret < 0) { 1112 | ERROR("Error %s during nfq_unbind_pf() with AF_INET", strerror(errno)); 1113 | goto exit; 1114 | } 1115 | ret = nfq_bind_pf(sess->queue_ctx.handle, AF_INET); 1116 | if (ret < 0) { 1117 | ERROR("Error %s during nfq_bind_pf() with AF_INET", strerror(errno)); 1118 | goto exit; 1119 | } 1120 | 1121 | ret = nfq_unbind_pf(sess->queue_ctx.handle, AF_INET6); 1122 | if (ret < 0) { 1123 | ERROR("Error %s during nfq_unbind_pf() with AF_INET6", strerror(errno)); 1124 | exit(1); 1125 | } 1126 | ret = nfq_bind_pf(sess->queue_ctx.handle, AF_INET6); 1127 | if (ret < 0) { 1128 | ERROR("Error %s during nfq_bind_pf() with AF_INET6", strerror(errno)); 1129 | goto exit; 1130 | } 1131 | 1132 | qh = nfq_create_queue(sess->queue_ctx.handle, 1133 | 0, 1134 | &vmw_net_queue_callback, 1135 | NULL); 1136 | if (!qh) { 1137 | ERROR("Error %s during nfq_create_queue()", strerror(errno)); 1138 | ret = -1; 1139 | goto exit; 1140 | } 1141 | 1142 | pthread_mutex_init(&sess->queue_lock, NULL); 1143 | ret = nfq_set_mode(qh, NFQNL_COPY_PACKET, VNET_BUFSIZE); 1144 | if (ret < 0) { 1145 | ERROR("Error %s during nfq_set_mode", strerror(errno)); 1146 | goto exit; 1147 | } 1148 | 1149 | /* Set kernel queue maximum length to handle connection burst */ 1150 | ret = nfq_set_queue_maxlen(qh, VNET_NFQLENGTH); 1151 | if (ret < 0) { 1152 | ERROR("Error %s during nfq_set_queue_maxlen()", strerror(errno)); 1153 | ret = 0; 1154 | /* don't fail, continue */ 1155 | } 1156 | 1157 | sess->queue_ctx.qhandle = qh; 1158 | sess->queue_ctx.qfd = nfq_fd(sess->queue_ctx.handle); 1159 | 1160 | exit: 1161 | 1162 | /* 1163 | * Clean-up is done by vmw_net_cleaup() which is executed in the main thread 1164 | * context after the completion of vsetNetUserInit thread. 1165 | */ 1166 | return ret; 1167 | } 1168 | 1169 | /* 1170 | * Unregister with netfilter conntrack library; close conntrack library and 1171 | * netfilter queue library handle and lock. 1172 | */ 1173 | void 1174 | vmw_net_cleanup(struct vmw_net_session *sess) 1175 | { 1176 | if (!sess) { 1177 | goto exit; 1178 | } 1179 | 1180 | /* Unregister conntrack callback and close handle */ 1181 | if (sess->conntrack_ctx.cthandle) { 1182 | nfct_callback_unregister(sess->conntrack_ctx.cthandle); 1183 | nfct_close(sess->conntrack_ctx.cthandle); 1184 | } 1185 | 1186 | /* Close netfilter queue library handle */ 1187 | if (sess->queue_ctx.handle) { 1188 | nfq_close(sess->queue_ctx.handle); 1189 | } 1190 | if (sess->queue_ctx.qhandle) { 1191 | nfq_destroy_queue(sess->queue_ctx.qhandle); 1192 | pthread_mutex_destroy(&sess->queue_lock); 1193 | } 1194 | 1195 | exit: 1196 | return; 1197 | } 1198 | 1199 | /* 1200 | * Register with notification framework and wait for network event notification 1201 | * and process them on reception. This is invoked during thread creation and 1202 | * executed in a thread contex`. 1203 | */ 1204 | void * 1205 | vmw_init(void *arg) 1206 | { 1207 | pthread_t client_msg_recv_thread; 1208 | pthread_t netfilter_event_handler_thread; 1209 | int ret; 1210 | 1211 | vmw_net_sess_handle = (struct vmw_net_session *) 1212 | malloc( sizeof(struct vmw_net_session)); 1213 | if (!vmw_net_sess_handle) { 1214 | ERROR("Failed to allocate memory for vnet session"); 1215 | goto exit; 1216 | } 1217 | 1218 | /* Register a callback with netfilter queue library */ 1219 | if (vmw_net_queue_init(vmw_net_sess_handle) < 0) { 1220 | ERROR("Error in intialising netfilter queue library handle"); 1221 | goto exit; 1222 | } 1223 | 1224 | /* Register a callback with netfilter conntrack library */ 1225 | if (vmw_net_conntrack_init(vmw_net_sess_handle) < 0) { 1226 | ERROR("Error in intialising netfilter conntrack library handle"); 1227 | goto exit; 1228 | } 1229 | 1230 | ATOMIC_OR(&g_vmw_init_done, 1); 1231 | INFO("%s is running", PROG_NAME); 1232 | 1233 | pthread_mutex_init(&clientUDSLock, NULL); 1234 | ret = pthread_create(&client_msg_recv_thread, 1235 | NULL, 1236 | vmw_client_msg_recv, 1237 | (void *)vmw_net_sess_handle); 1238 | if (0 != ret) { 1239 | ERROR("Could not create vmw_client_msg_recv thread"); 1240 | goto exit; 1241 | } 1242 | 1243 | /* Process notfilcation received from netfilter libraries */ 1244 | ret = pthread_create(&netfilter_event_handler_thread, 1245 | NULL, 1246 | vmw_netfilter_event_handler, 1247 | (void *)vmw_net_sess_handle); 1248 | if (0 != ret) { 1249 | ERROR("Could not create vmw_netfilter_event_handler thread"); 1250 | vmw_notify_exit(); 1251 | pthread_join(client_msg_recv_thread, NULL); 1252 | goto exit; 1253 | } 1254 | 1255 | pthread_join(client_msg_recv_thread, NULL); 1256 | pthread_join(netfilter_event_handler_thread, NULL); 1257 | 1258 | exit: 1259 | vmw_net_cleanup(vmw_net_sess_handle); 1260 | pthread_mutex_destroy(&clientUDSLock); 1261 | 1262 | /* Free struct vmw_net_session */ 1263 | if (vmw_net_sess_handle) { 1264 | free(vmw_net_sess_handle); 1265 | vmw_net_sess_handle = NULL; 1266 | } 1267 | return NULL; 1268 | } 1269 | 1270 | --------------------------------------------------------------------------------