I just submitted a pull request to correct a connectivity issue I was 
having with OSSEC servers running on FreeBSD 11.1. I mentioned this on the 
email list a few weeks ago, but it took some time to do the work to 
identify and correct the problem. I encountered a significant issue in 
OSSEC 2.9.3 and the current OSSEC beta (GitHub repository) release that 
affects the ability of OSSEC clients to be able to connect to FreeBSD 
servers. The issue did not exist for OSSEC 2.8.3 possibly due to the fact 
that it was IPv4 only. This connectivity issue probably affects all current 
BSD derivatives and possibly some of the more obscure releases of Linux as 
well. I have identified and resolved the issue in OSSEC by rewriting some 
of the networking code, however, before I describe the solution, I wanted 
to provide some background information on the problem first.

*Problem Symptoms*

The server is running FreeBSD 11.1 with all of the current updates. This is 
a standard OSSEC server installation with all standard options enabled and 
no bells and whistles (no MySQL and no MQ). After correcting for some 
BSD-specific issues for header files and libraries, OSSEC installs normally 
on the server with the install.sh script and the compilation completes 
without any fatal errors (GCC does generate a few warnings on 
analysisd/decoders/syscheck.c and analysisd/decoders/syscheck-test.c, but 
these are not show stoppers). The server has an assigned IP address of 
192.168.1.80 (IPv4 private IP space) and all of the clients it associates 
with are on the local 192.168.1.0/24 network.

The OSSEC 2.9.3 client software is installed on each Windows workstation 
and valid client keys are obtained for each client system. Everything 
appears fine, except that there are no alerts logged on the server for any 
of the configured clients. In short, the problem manifests itself as an 
inability for the client systems to connect to the OSSEC server. In the 
ossec.log file on each client there are messages like this that repeat over 
and over:

2018/03/08 12:11:06 ossec-agentd: INFO: Trying to connect to server 192.168.
1.80, port 1514.
2018/03/08 12:11:06 INFO: Connected to 192.168.1.80 at address 192.168.1.80:
1514, port 1514
2018/03/08 12:11:27 ossec-agentd(4101): WARN: Waiting for server reply (not 
started). Tried: '192.168.1.80'.


2018/03/08 12:12:41 ossec-agentd: INFO: Trying to connect to server 192.168.
1.80, port 1514.
2018/03/08 12:12:41 INFO: Connected to 192.168.1.80 at address 192.168.1.80:
1514, port 1514
2018/03/08 12:13:02 ossec-agentd(4101): WARN: Waiting for server reply (not 
started). Tried: '192.168.1.80'.


Because the clients use UDP, and UDP is a connectionless protocol, there is 
no protocol handshake to ensure a connection has actually been established 
like there would be with TCP. As long as the sendto() function returns a 
positive integer (the number of bytes sent), the call is deemed successful. 
This is true even when packets are not received on the destination host. To 
compensate for this, the server is supposed to return a control message to 
indicate the packet was received. Because the client never receives this 
confirmation, a warning is generated in the client log "Waiting for server 
reply (not started)".

On the server, remoted starts fine. It displays the maximum number of 
agents allowed (2048) and logs the fact that it successfully read the 
authentication keys file. However, the server logs hint that there is an 
issue when remoted starts because each client in the client.keys file 
results in a message stating "Assigning counter for agent Workstation01: 
'0:0'", or "Assigning sender counter: 0:0". These messages are generated 
when a client is defined in the etc/client.keys file, but no initial 
connection has ever occurred. This is normal for a first time start of 
OSSEC, but if this happens every time you restart OSSEC for every client 
defined in the system, your clients are not communicating.

To identify whether remoted was listening on UDP port 1514, I used the 
"netstat -an" command and the FreeBSD "sockstat" command (sockstat is like 
"lsof" for sockets). The only address bound to port 1514 was an IPv6 
address, not the IPv4 address. The server uses only a single IPv4 address 
for network communication and no IPv6 addresses. However, it turns out that 
FreeBSD (and many other systems) create a link-local IPv6 address based on 
the MAC address of each interface when IPv6 is not specifically configured 
for the interface. This is specified in RFC 2462.

The standard solution for this would be to specify an IP address for the 
"secure" connection in the remote configuration using the <local_ip> 
option. However, when that is used, OSSEC ignores it and the system 
continues to bind to the IPv6 link-local address. While I could have 
experimented with disabling IPv6 in the operating system, this could have 
an adverse impact on other applications running on the server.

Normally, the system should simply query all available addresses on the 
system using getaddrinfo(), create a socket for each address, and bind the 
address to the socket. If the protocol is TCP, then you also need to use 
listen() to establish a queue. Then select() can be used to handle the 
dispatch of the individual sockets as messages arrive. I have a lot of 
experience with writing network interfaces in C, so I decided I would take 
a look at what was going on inside OSSEC that was creating this issue for 
me.

*Initial Analysis*

The OSSEC remoted process is responsible for handling the connections 
between the clients and the server. All of this code, except for the actual 
networking code, is found in the src/remoted directory. The initial 
connections for the UDP secure server and the syslog server are initiated 
in the HandleRemote() function inside remoted.c through calls to 
OS_Bindportudp() or OS_Bindporttcp() which are found in os_net/os_net.c. 
What is immediately obvious is that only one socket is returned as an 
integer value, instead of a group of sockets (one for each interface and 
protocol family).

The actual processing of incoming messages is divided between secure.c for 
secure UDP connections, syslog.c for syslog UDP connections, and 
syslogtcp.c for syslog TCP connections. Most of the outgoing connections 
are handled by send_msg.c (with some exceptions). Because there is only one 
socket used per communication strategy (secure UDP, syslog UDP, and syslog 
TCP), all of the routines use a while(1) loop with blocking reads to 
process incoming messages. To handle multiple sockets without dragging the 
CPU into the mud, these would have to be modified to use select().

The next thing I looked at was the code that was doing the network calls, 
which is found in the file os_net/os_net.c. Both the OS_Bindportudp() and 
OS_Bindporttcp() calls referred to a function called OS_Bindport() to 
handle all of the network initialization code including querying the 
available addresses, creating the socket, binding the address, invoking 
listen() for the queue (TCP only), and returning the active socket to the 
caller.

The code in os_net/os_net.c clearly showed my dilemma; the call to 
getaddrinfo() was setup to use AF_INET6 for the address family and 
AI_V4MAPPED, AI_PASSIVE, and AI_ADDRCONFIG for flags. Under this 
specification, IPv6 is preferred over IPv4, and if no IPv6 addresses exist 
and IPv4 addresses do exist, the IPv4 address is to be returned as a mapped 
IPv4 address using IPv6. A better solution is to use PF_UNSPEC for the 
address family with AI_PASSIVE and AI_ADDRCONFIG to pick up all addressed 
on configured interfaces.

The original author of this module recognized this as a problem early on, 
but s/he assumed that if they had AI_V4MAPPED defined in netdb.h, then the 
system would handle it properly. However, if IPv4 is preferred and there 
are any IPv6 addresses configured on the system, AI_V4MAPPED will not yield 
any IPv4 addresses. There is a good article by IBM on this that you can 
read here:

https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.hale001/ipv6d0131000748.htm

This is further compounded because OSSEC is returning only a single socket 
bound to a single address making the process of supporting both IPv4 and 
IPv6 at the same time is impossible. The only correct solution is to modify 
OSSEC to handle multiple IP addresses under both IPv4 and IPv6 
simultaneously.

*The Solution*

The overall solution to this problem was to return all of the addresses 
that meet the selection criteria and use the select() function to return 
sockets that are ready for reading. While this change affected a number of 
modules in the OSSEC system, the change was relatively straight forward to 
implement. A list of modules affected is provided at the end of this 
document.

Under the current version of OSSEC, a function named OS_Bindport() is used 
to locate the first address that is returned from getaddrinfo(). Then a 
socket for the appropriate connection type is created (SOCK_DGRAM for UDP 
or SOCK_STREAM for TCP), and the address is bound to the socket. If the 
connection is a TCP connection, a queue is established for the bound socket 
using listen(). Then the integer value socket is returned. If the socket 
value is -1, an error is assumed and various error actions are taken by the 
caller.

Under the modified version, OS_Bindport() was modified in a number of ways. 
First, instead of returning an integer value representing a bound socket to 
the caller, the routine now returns a pointer to an OSNetInfo structure. 
The OSNetInfo structure is defined in os_net/os_net.h as follows:

typedef struct _OSNetInfo {
  fd_set fdset;                         /* set of sockets used by select 
()*/
  int fdmax;                            /* fd max socket for select() */
  int fds[FD_SETSIZE];                  /* array of bound sockets for 
send() */
  int fdcnt;                            /* number of sockets in array */
  int status;                           /* return status (-1 is error) */
  int retval;                           /* return value (additional info) */
} OSNetInfo;


In order to multiplex multiple sockets efficiently, we need to use the 
select() function. The select() function operates on a struct called fd_set 
that is built on the successful acquisition of a socket with a bound 
address. Each successful acquisition is added to the set using the FD_SET 
macro. We also track the highest value socket number returned in fdmax. 
When all processing is completed, fdmax is incremented by 1 so it can be 
used directly with the select() function in other modules. We also track 
the individual bound sockets in an integer table called fds, and a count of 
the number of sockets in the table using fdcnt.

Because the original implementation relied upon a return value of -1 to 
indicate an error, we include an element in the struct called status that 
performs the same function. If status is -1, then an error has occurred. 
Upon a successful return, the status element will be 0. If status is -1, 
the return value element retval will contain the error number associated 
with the error status. The OSNetInfo struct made the process of modifying 
the OSSEC networking code relatively simple because it preserved all of the 
functionality that was inherent with the original code.

In addition to the OSNetInfo structure as a return value, the second major 
change to os_net/os_net.c was the definition of hints to the getaddrinfo() 
function. The getaddrinfo() function was created by the POSIX standards 
committee to provide support both IPv4 and IPv6 simultaneously. The 
getaddrinfo() function modifies a struct that returns a pointer to a linked 
list of addresses that match the selection criteria passed to getaddrinfo() 
through the hints parameter struct.

Under the original OSSEC code, if the operating system platform had 
AI_V4MAPPED defined in <netdb.h> (the vast majority of modern OS's support 
this), then the hints structure passed to getaddrinfo() used AF_INET6 for 
the protocol family. This essentially guaranteed that only IPv6 addresses 
would be returned if they were defined on the system. Even if there are no 
IPv6 addresses configured on the system, the AF_INET6 protocol family with 
the AI_V4MAPPED flag will return a mapped IPv4 address in IPv6 format.

For current *BSD systems, if an IPv6 address is not defined on an 
interface, the system will create a link-local IPv6 address based on the 
MAC address of each interface. IPv6 link-local addresses are essentially 
useless for external communication, so the net result is that *BSD systems 
(and others) are not able to communicate using IPv4.

By modifying this to use AF_UNSPEC instead of AF_INET6, eliminating the 
AI_V4MAPPED flag, and utilizing all of the addresses returned by the call 
to getaddrinfo(), we have the ability to communicate through all of the 
interfaces defined on the system using both IPv4 nd IPv6. Nonetheless, 
there are a number of Linux systems that are working fine for OSSEC with 
the AF_INET6 family and the AI_V4MAPPED flag. As such, an "#if defined" 
definition was placed in the code to support the original behavior on Linux 
systems, with the PF_UNSPEC behavior defined for non-Linux systems. In the 
event you do want to use the PF_UNSPEC code with a Linux system (I suspect 
this will become the norm as more versions of Linux adopt RFC 2462 and the 
link-local address), all you need to do is to uncomment the line in the 
Makefile that defines NOV4MAP and the system will use AF_UNSPEC instead of 
AF_INET6.

Once the call is made to getaddrinfo(), we utilize a loop to process each 
of the addresses returned in the linked list of addresses. Each socket that 
is bound successfully is added to the fd_set for subsequent select() 
processing, and also added to an array of integers containing bound 
sockets. The only other substantive change that was made to this module is 
that we set a socket option for the SO_REUSEADDR flag. In the event OSSEC 
is stopped and restarted again, the use of the SO_REUSEADDR socket option 
allows OSSEC to restart immediately without having to wait for a lingering 
socket to close. It also improves performance for short duration 
transactions.

The other changes in the os_net/os_net.c module include changing the return 
parameter from integer to a pointer to an OSNetInfo struct for 
OS_Bindportudp() and OS_Bindporttcp(), and a number of helper functions 
were added to the bottom of the module to provide additional information as 
the addresses are retrieved, sockets created, and addresses are bound to 
sockets.

Once the changes were made to os_net/os_net.c, the remoted struct in 
config/remote-config.h was modified to add a pointer called netinfo to the 
remoted struct using a typedef of OSNetInfo (directly below where the sock 
element is defined), and remoted/remoted.c was modified to accept the new 
OSNetInfo pointer and store it in the netinfo element in the logr 
configuration struct.

Next, I modified the routines that use the sockets to use the logr.netinfo 
element instead of the logr.sock element that was originally used for 
reads. Blocking reads were replaced with select() to multiplex the sockets 
that became available for reading. This change affected secure.c, syslog.c, 
and syslogtcp.c in the remoted subdirectory, and main-server.c in the 
os_auth subdirectory.

The remoted/sendmsg.c module was modified to use multiple sockets for 
sending. In the event a message was received on one of the sockets, that 
socket was used for sending the packet out. In the event no packet had been 
received, the routine was modified to loop through the list of available 
sockets to ensure the packet is sent.

The Makefile was also modified to added a define for NOV4MAP, which is 
commented out by default. When this is defined, Linux systems can build 
remoted with AF_UNSPEC instead of AF_INET6 and the AI_V4MAPPED attribute 
that was described earlier. As more operating systems adopt the link-local 
specification to establish an IPv6 address based on the MAC address, this 
could help Linux users with being able to support IPv4 on their systems.

There are two other changes I made that are not related to the IP address 
issue but relate to successfully compiling and installing OSSEC on FreeBSD. 
Because I had to modify the Makefile for the NOV4MAP define, it made sense 
to include these changes as part of this pull request. Both of these 
changes are to make files that are provided with the distribution.

The first change is to the Makefile in the top src directory. BSD systems 
use the /usr/local directory for managing files and programs that are not 
part of the standard operating system distribution. MySQL, Postgres, MQ, 
readline, and other applications are extensions that get installed under 
/usr/local. As such, it is necessary to add /usr/local/include directory 
for C header files and /usr/local/lib for object libraries. This already 
existed in Makefile for OpenBSD, but not for FreeBSD or NetBSD.

The second Makefile change I made was to the LUA Makefile for BSD 
distributions. Like the Makefile in the src directory described above, the 
LUA build needs to have the same definitions for header files and 
libraries. The file I modified under the src directory is 
external/lua/src/Makefile. The modification added parameters for 
"-I/usr/local/include" and "-L/usr/local/lib". With these changes, FreeBSD 
and other BSD systems will compile without problems using gmake and GCC.

*Header Files Modified*

os_net/os_net.h - Defined a new typedef struct called OSNetInfo that is 
used to return a list of bound sockets back to the caller, and changed the 
return values defined for OS_Bindporttcp() and OS_Bindportudp() to use a 
pointer to the OSNetInfo struct instead of the integer value they used to 
return. The pointer to the OSNetInfo struct is stored in the remoted struct 
that is defined as "logr" in the code. The pointer to the OSNetInfo pointer 
is named netinfo.

config/remote-config.h - Added a #include for "os_net/os_net.h" to ensure 
the appropriate network function headers were defined for select() and 
added a pointer to an OSNetInfo struct named netinfo to the remoted struct. 
The netinfo pointer serves the same purpose as the old sock element, except 
that it handles multiple sockets instead of just a single socket. The 
remoted struct, which is usually referred to as "logr" in the remoted code, 
is used to pass a number of configuration elements between the remoted 
modules.

*Source Modules Modified*

os_net/os_net.c - Modified the module to return a struct called OSNetInfo 
that contains the bound sockets for each of the addresses in a format ready 
for use by select(), and an array of sockets that can be referenced for 
individual socket usage. The OSNetInfo struct is described earlier in this 
document. Also added code to use PF_UNSPEC for all interfaces except those 
that are for Linux and also have AI_V4MAPPED defined in <netdb.h>. Also 
added a number of helper functions to display addresses and other status 
information in the ossec.log file using verbose().

remoted/remoted.c - Modified the module to use the new OSNetInfo struct for 
return values from calls to OS_Bindporttcp() and OS_Bindportudp().

remoted/secure.c - Modified secure.c to utilize OSNetInfo data with 
multiple bound sockets via logr.netinfo instead of the original single 
bound socket with logr.sock. Also changed the message receipt loop to use 
select() to multiplex the sockets, instead of blocking on a read for a 
single socket.

remoted/syslog.c - Modified syslog.c to support UDP messages the same way 
we modified secure.c, including using the OSNetInfo struct for multiple 
sockets and the select() call to support multiplexing.

remoted/syslogtcp.c - Modified syslogtcp.c to support TCP messages the same 
way we modified secure.c and syslog.c, including using the OSNetInfo struct 
for multiple sockets and the select() call to support multiplexing.

remoted/sendmsg.c - Modified sendmsg.c to use the bound socket array in the 
netinfo structure. If we have previously identified a valid socket, we use 
that socket to send on. Otherwise, we cycle through the array of valid 
bound sockets until we get a successful completion on the sendto() call. 
This approach appears to work well.

os_auth/main-server.c - This module was an outlier, but because it called 
OS_Bindporttcp() it had to be modified to support the new multi-address 
approach. Modified main-server.c to use new OSNetInfo struct for data 
returned by OS_Bindporttcp and implemented the select() call to multiplex 
the group of sockets.

*Makefile Modifications*

Makefile - Found in the src directory at the top of the directory tree, I 
added /usr/local/include to CFLAGS and /usr/local/lib OSSEC_LDFLAGS to 
support successful compilation on FreeBSD and NetBSD. Also added the define 
for NOV4MAP that is commented out. When NOV4MAP is defined, Linux 
distributions will use AF_UNSPEC to bind sockets instead of AF_INET6 with 
the AI_V4MAPPED flag.

external/lua/src/Makefile - LUA compilation fails with the current Makefile 
because the /usr/local/include directory is not specified for headers, and 
the /usr/local/lib is not specified for object libraries. I added 
parameters to the definitions for bsd and freebsd in the Makefile to make 
sure those directories were included. This allows the LUA compile process 
to complete without errors.

*Modification Summary and Pull Request*

I had originally performed this modification for the OSSEC 2.9.3 release 
and tested it thoroughly on FreeBSD 11.1 for several weeks. I then ported 
these changes into the current code branch of OSSEC that I retrieved from 
GitHub on May 1, 2018. This required my changes to be re-ported into newer 
versions of os_auth/main-server.c, os_net/os_net.c, and os_net/os_net.h. I 
also had to re-port changes into the two Makefiles I identified earlier. I 
only have a day of use on this, but it seemd to be running ithout issues.

If anyone is interested in the changes that work for the 2.9.3 release, you 
can download the modified OSSEC 2.9.3 release from

https://networkalarmcorp.com/dnld/ossec-hids-2.9.3.1.tgz

I should have the 2.9.3 code up on my server by Thursday noon EDT. I have 
submitted a pull request for this modification to be incorporated into the 
OSSEC code base. If anyone has any questions or suggestions related to this 
modification, I am happy to discuss them. I hope this modification helps 
the OSSEC community at large.

Dave Stoddard
Network Alarm Corporation
https://networkalarmcorp.com
https://redgravity.net
Office: 301-850-0668 x101
Email: dgs at networkalarmcorp dot com

-- 

--- 
You received this message because you are subscribed to the Google Groups 
"ossec-list" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to ossec-list+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to