The Velodyne High Definition LiDAR (HDL) Grabber

The Velodyne HDL is a network-based 3D LiDAR system that produces 360 degree point clouds containing over 700,000 points every second.

The HDL Grabber provided in PCL mimics other Grabbers, making it almost plug-and-play. Because the HDL devices are network based, however, there are a few gotchas on some platforms.

The HDL Grabber supports the original HDL-64e as well as the HDL-32e. More information on those sensors can be found at Velodyne’s Web Site

Basic Network Setup

The Velodyne HDL uses network packets to provide range and intensity data for each of the lasers in the device. The HDL-64e consists of 64 lasers, while the HDL-32e consists of 32.

The HDL-64e and HDL-32e, by default, produce UDP network packets on the 192.168.3 subnet. Starting with the HDL-32e (Firmware Version 2), the user can customize this network subnet.

The HDL can be connected either directly into your computer, or into a network switch (to include a network switch with a built-in Wireless Access Point). Regardless, one of your computer’s Network Interface Cards (NIC) [whether hard-wired RJ-45 connection or wireless] needs to be configured to be on this 192.168.3 subnet. Consult your operating system documentation on how to perform this.

In addition to the NIC settings, you may need to alter your operating system’s firewall rules. The HDL produces packets on port 2368 (by default). The HDL-32e with Firmware Version 2 can be set to use a different port. Consult your firewall documentation to open this port in your firewall.

Lastly, modern Linux kernels have advanced network attack guards that go beyond basic firewall rules. The HDL-32e produces UDP packets that may be filtered by the OS using one of these attack guards. You will need to disable the rp_filter guard for the appropriate NIC. For more information on how to disable this filter, please see the section below entitled Disabling Reverse Path Filter

PCAP Files

Wireshark is a popular Network Packet Analyzer Program which is available for most platforms, including Linux, MacOS and Windows. This tool uses a defacto standard network packet capture file format called PCAP. Many publicly available Velodyne HDL packet captures use this PCAP file format as a means of recording and playback. These PCAP files can be used with the HDL Grabber if PCL is compiled with PCAP support.

Velodyne provides sample PCAP files on their website

Compiling the HDL Grabber with PCAP support

On Linux, this involves installing libpcap-dev (Ubuntu) or libpcap-devel (Fedora). CMake should find the pcap libraries, and automatically configure PCL to use them.

On Windows, this involves installing both the WinPCAP installer and the WinPCAP developer’s pack. You will also need to set an environment variable PCAPDIR to the directory where you unzipped the developer’s pack. Once that is done, you should be able to run CMake again, and it should locate the appropriate files.

Note - You do not need to compile the HDL Grabber with support for PCAP. It is only required if you will be replaying PCAP files through the grabber.

Sample Program

In visualization, there is a very short piece of code which contains all that is required to set up a pcl::PointCloud<XYZ>, pcl::PointCloud<XYZI> or *pcl::PointCloud<XYZRGB> cloud callback.

Here is a screenshot of the PCL HDL Viewer in action, which uses the HDL Grabber.

_images/pcl_hdl_viewer.png

So let’s look at the code. The following represents a simplified version of tools/hdl_viewer_simple.cpp

  1#include <pcl/point_cloud.h>
  2#include <pcl/point_types.h>
  3#include <pcl/io/hdl_grabber.h>
  4#include <pcl/visualization/point_cloud_color_handlers.h>
  5#include <pcl/visualization/cloud_viewer.h>
  6#include <pcl/console/parse.h>
  7
  8using namespace std::chrono_literals;
  9using namespace pcl::console;
 10using namespace pcl::visualization;
 11
 12class SimpleHDLViewer
 13{
 14  public:
 15    typedef pcl::PointCloud<pcl::PointXYZI> Cloud;
 16    typedef Cloud::ConstPtr CloudConstPtr;
 17
 18    SimpleHDLViewer (pcl::Grabber& grabber,
 19        pcl::visualization::PointCloudColorHandler<pcl::PointXYZI> &handler) :
 20        cloud_viewer_ (new pcl::visualization::PCLVisualizer ("PCL HDL Cloud")),
 21        grabber_ (grabber),
 22        handler_ (handler)
 23    {
 24    }
 25
 26    void cloud_callback (const CloudConstPtr& cloud)
 27    {
 28      std::lock_guard<std::mutex> lock (cloud_mutex_);
 29      cloud_ = cloud;
 30    }
 31
 32    void run ()
 33    {
 34      cloud_viewer_->addCoordinateSystem (3.0);
 35      cloud_viewer_->setBackgroundColor (0, 0, 0);
 36      cloud_viewer_->initCameraParameters ();
 37      cloud_viewer_->setCameraPosition (0.0, 0.0, 30.0, 0.0, 1.0, 0.0, 0);
 38      cloud_viewer_->setCameraClipDistances (0.0, 50.0);
 39
 40      std::function<void (const CloudConstPtr&)> cloud_cb =
 41          [this] (const CloudConstPtr& cloud) { cloud_callback (cloud); };
 42      boost::signals2::connection cloud_connection = grabber_.registerCallback (
 43          cloud_cb);
 44
 45      grabber_.start ();
 46
 47      while (!cloud_viewer_->wasStopped ())
 48      {
 49        CloudConstPtr cloud;
 50
 51        // See if we can get a cloud
 52        if (cloud_mutex_.try_lock ())
 53        {
 54          cloud_.swap (cloud);
 55          cloud_mutex_.unlock ();
 56        }
 57
 58        if (cloud)
 59        {
 60          handler_.setInputCloud (cloud);
 61          if (!cloud_viewer_->updatePointCloud (cloud, handler_, "HDL"))
 62            cloud_viewer_->addPointCloud (cloud, handler_, "HDL");
 63
 64          cloud_viewer_->spinOnce ();
 65        }
 66
 67        if (!grabber_.isRunning ())
 68          cloud_viewer_->spin ();
 69
 70        std::this_thread::sleep_for(100us);
 71      }
 72
 73      grabber_.stop ();
 74
 75      cloud_connection.disconnect ();
 76    }
 77
 78    pcl::visualization::PCLVisualizer::Ptr cloud_viewer_;
 79
 80    pcl::Grabber& grabber_;
 81    std::mutex cloud_mutex_;
 82
 83    CloudConstPtr cloud_;
 84    pcl::visualization::PointCloudColorHandler<pcl::PointXYZI> &handler_;
 85};
 86
 87int main (int argc, char ** argv)
 88{
 89  std::string hdlCalibration, pcapFile;
 90
 91  parse_argument (argc, argv, "-calibrationFile", hdlCalibration);
 92  parse_argument (argc, argv, "-pcapFile", pcapFile);
 93
 94  pcl::HDLGrabber grabber (hdlCalibration, pcapFile);
 95
 96  pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> color_handler ("intensity");
 97
 98  SimpleHDLViewer v (grabber, color_handler);
 99  v.run ();
100  return (0);
101}

Additional Details

The HDL Grabber offers more than one datatype, which is the reason we made the Grabber interface so generic, leading to the relatively complicated lambda line. In fact, we can register the following callback types as of this writing:

  • void (const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr&)

Compiling and running the program

Add the following lines to your CMakeLists.txt file:

 1cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
 2
 3project(pcl_hdl_viewer_simple)
 4
 5find_package(PCL 1.2 REQUIRED)
 6
 7include_directories(${PCL_INCLUDE_DIRS})
 8link_directories(${PCL_LIBRARY_DIRS})
 9add_definitions(${PCL_DEFINITIONS})
10
11add_executable(pcl_hdl_viewer_simple hdl_viewer_simple.cpp)
12target_link_libraries(pcl_hdl_viewer_simple ${PCL_LIBRARIES})

Disabling Reverse Path Filter

First off, let’s understand what the Reverse Path Filter is all about. A TCP or UDP packet contains, amongst other information, a DESTINATION IP ADDRESS and a SOURCE IP ADDRESS. The Destination IP Address represents where the packet will go. In the case of a UDP packet, this is usually the broadcast network IP Address (eg, 255.255.255.255 for a global broadcast, or x.y.z.255 for a Class C Network [where x.y.z are the first three octets of a Class C network, such as 192.168.1]).

The Source IP Address, on the other hand, indicates where the packet originated from. Packets can be hand-crafted for spoofing-type attacks (eg, pretending to come from somewhere they really didn’t). The Reverse Path Filter attempts to detect these instances. The default rule that it uses is that if a packet is received on Network Interface A, then if there is no route to the Source IP Address on Network Interface A, then it will be dropped by the kernel.

So, what does this mean for the HDL-32e? Well, by default, the Source IP Address is 192.168.X.Y, where X and Y are the last 4 digits of the device’s serial number. Remember, the default Destination IP address for the HDL-32e is the 192.168.3 network.

If you have a single Network Interface, you will have a default route (that is a route to all other networks) going out that single Network Interface. To receive the HDL packets, that Network Interface will need to be on the 192.168.3 subnet. And all will be good because there is a route from your single Network Interface to the packet’s Source IP Address, through your single Network Interface.

Ahh, but what happens when you have two Network Interfaces, for example, on to the internet, and one dedicated to the HDL? In that case, your primary NIC will have a default route to all other networks, but the one that is dedicated to the HDL won’t. By default, it won’t have a default route, and in fact, it will only have a route to the 192.168.3 subnet.

That means that when the HDL packet is received by the Linux Kernel, it will determine that there is no route from the secondary NIC back to the HDL packet Source IP Address, and drop the packet altogether.

The maddening thing about this is that if you were to run tcpdump or wireshark (two network packet sniffer programs), you would see that the HDL packets were arriving at the NIC card! The reason for this is that programs like tcpdump and wireshark use something called promiscuous mode that allows them to receive all packets BEFORE the Linux Kernel does.

So, there are a couple of solutions to this problem. First, you could use a single NIC, and your computer will be dedicated to the HDL. You won’t have to do anything except change network IP addresses when you want to connect to an alternate network. For those that desire a second NIC, there are several options. First, you can set up a route back to the source network that traverses the second NIC. Note, the Linux Kernel does not actually try to connect back to the source network, it just ensures that there is a path to it. This option works well in practice. The other option is to modify the RP Filter setting. There are two possible modes - turn it off completely, or relax the rules to see if there is a route back to that network via any NIC on the computer.

Here are the options again for a multi-NIC system, with corresponding Linux Commands.

  1. Add a route back to the HDL

First off, let’s look at the interface settings for our two NICS:

$ ifconfig

returns the following details (some items removed for brevity):

em1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
     inet 192.168.128.108  netmask 255.255.255.0  broadcast 192.168.128.255

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
     inet 192.168.3.1  netmask 255.255.255.0  broadcast 192.168.3.255

Next, let’s look at our routing table (again, some items removed for brevity):

$ route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.128.1   0.0.0.0         UG    0      0        0 em1
192.168.3.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.128.0   0.0.0.0         255.255.255.0   U     0      0        0 em1

To add a route to the HDL, assume that the HDL Source IP is 192.168.12.84. You would use the following command:

$ sudo route add -net 192.168.12.0/24 eth0

To verify that the route has been added, type the following:

$ route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.128.1   0.0.0.0         UG    0      0        0 em1
192.168.3.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.12.0    0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.128.0   0.0.0.0         255.255.255.0   U     0      0        0 em1

Now, there is a route back to the source IP address of the HDL on the same interface that the packet came from!

However, what if, for some reason (like you already use the 192.168.12 subnet on your computer or network, and setting the route won’t work). That’s what option #2 and #3 are for.

  1. Relaxing the Reverse Path Filter

TODO

  1. Disabling the Reverse Path Filter

TODO

Troubleshooting

Conclusion