├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── efvicap.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build 3 | cmake-build-*/ 4 | *~ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | set(CMAKE_EXPORT_COMPILE_COMMANDS "true") 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic") 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | project(efvicap VERSION "1.1.0" LANGUAGES CXX) 10 | 11 | include(FindPackageHandleStandardArgs) 12 | 13 | find_path(PCAP_INCLUDE_DIR NAMES pcap.h) 14 | find_library(PCAP_LIBRARY NAMES pcap) 15 | find_package_handle_standard_args(PCAP DEFAULT_MSG 16 | PCAP_LIBRARY 17 | PCAP_INCLUDE_DIR) 18 | 19 | find_path(EFVI_INCLUDE_DIR NAMES etherfabric/ef_vi.h) 20 | find_library(EFVI_LIBRARY NAMES ciul1) 21 | find_package_handle_standard_args(EFVI DEFAULT_MSG 22 | EFVI_LIBRARY 23 | EFVI_INCLUDE_DIR) 24 | 25 | add_executable(efvicap efvicap.cpp) 26 | target_include_directories(efvicap 27 | PRIVATE 28 | ${PCAP_INCLUDE_DIR} 29 | ${EFVI_INCLUDE_DIR}) 30 | target_link_libraries(efvicap 31 | ${PCAP_LIBRARY} 32 | ${EFVI_LIBRARY}) 33 | 34 | install(TARGETS efvicap RUNTIME DESTINATION bin) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Erik Rigtorp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # efvicap 2 | 3 | [![GitHub](https://img.shields.io/github/license/rigtorp/efvicap.svg)](https://github.com/rigtorp/efvicap/blob/master/LICENSE) 4 | 5 | *efvicap* is a packet capture tool for network adapters 6 | from [Solarflare](http://solarflare.com/). It uses the ef_vi API for 7 | direct access to DMA memory and can capture traffic intended for 8 | applications accelerated with [OpenOnload](http://www.openonload.org/) 9 | 10 | ## Usage 11 | 12 | ```sh 13 | usage: efvicap [-i iface] [-w file] [filters...] 14 | 15 | -i iface Interface to capture packets from 16 | -w file Write packets in pcap format to file 17 | ``` 18 | 19 | Using an empty filter captures all packets (requiers a SolarCapture 20 | license). 21 | 22 | ## Example 23 | 24 | Print info about all received packets: 25 | 26 | ```sh 27 | $ efvicap -i eth0 28 | ``` 29 | 30 | Save packets to *out.pcap*: 31 | 32 | ```sh 33 | $ efvicap -i eth0 -w out.pcap 34 | ``` 35 | 36 | Pipe packets to *tcpdump* and print only UDP packets: 37 | 38 | ```sh 39 | $ efvicap -i eth0 -w - | tcpdump -r - udp 40 | ``` 41 | 42 | Save UDP packets sent to 230.0.0.1:5000 and 230.0.0.2:6000: 43 | 44 | ```sh 45 | $ efvicap -i eth0 -w out.pcap 230.0.0.1:5000 230.0.0.2:6000 46 | ``` 47 | 48 | ## Building & Installing 49 | 50 | *efvicap* requires [CMake](https://cmake.org/) 3.6 or higher to build 51 | and install. 52 | 53 | Building: 54 | 55 | ```sh 56 | $ cd efvicap 57 | $ mkdir build 58 | $ cd build 59 | $ cmake .. 60 | $ make 61 | ``` 62 | 63 | Installing: 64 | 65 | ```sh 66 | $ make install 67 | ``` 68 | 69 | ## TODO 70 | 71 | - Hardware timestamping 72 | - Multiple interfaces 73 | - Separate thread for writing to disk 74 | - Compression 75 | 76 | ## About 77 | 78 | This project was created by [Erik Rigtorp](http://rigtorp.se) 79 | <[erik@rigtorp.se](mailto:erik@rigtorp.se)>. 80 | -------------------------------------------------------------------------------- /efvicap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017 Erik Rigtorp 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | // Per Solarflare documentation 46 | constexpr size_t pktBufSize = 2048; 47 | 48 | // Should be multiple of 8 according to Solarflare documentation 49 | constexpr int refillBatchSize = 16; 50 | 51 | // Huge page size for your platform 52 | #if defined(__x86_64__) || defined(__i386__) 53 | constexpr size_t hugePageSize = 2 * 1024 * 1024; 54 | #endif 55 | 56 | static std::ostream &operator<<(std::ostream &os, const in_addr addr) { 57 | std::array str = {}; 58 | inet_ntop(AF_INET, &addr, str.data(), str.size()); 59 | os.write(str.data(), strnlen(str.data(), str.size())); 60 | return os; 61 | } 62 | 63 | static void printPacket(const void *buf, int /*len*/) { 64 | auto p = reinterpret_cast(buf); 65 | auto eth = reinterpret_cast(p); 66 | if (ntohs(eth->ether_type) == ETHERTYPE_IP) { 67 | auto ip = reinterpret_cast(p + sizeof(ether_header)); 68 | if (ip->version == 4) { 69 | if (ip->protocol == IPPROTO_TCP) { 70 | auto tcp = reinterpret_cast(p + sizeof(ether_header) + 71 | sizeof(iphdr)); 72 | std::cout << "ip tcp from " << in_addr{ip->saddr} << ":" 73 | << ntohs(tcp->source) << " to " << in_addr{ip->daddr} << ":" 74 | << ntohs(tcp->dest) << std::endl; 75 | } else if (ip->protocol == IPPROTO_UDP) { 76 | auto udp = reinterpret_cast(p + sizeof(ether_header) + 77 | sizeof(iphdr)); 78 | std::cout << "ip udp from " << in_addr{ip->saddr} << ":" 79 | << ntohs(udp->source) << " to " << in_addr{ip->daddr} << ":" 80 | << ntohs(udp->dest) << std::endl; 81 | } else { 82 | std::cout << "ip " << ip->protocol << std::endl; 83 | } 84 | } 85 | } else if (ntohs(eth->ether_type) == ETHERTYPE_ARP) { 86 | std::cout << "arp" << std::endl; 87 | } else { 88 | std::cout << ntohs(eth->ether_type) << std::endl; 89 | } 90 | } 91 | 92 | std::atomic active = {true}; 93 | 94 | extern "C" void signalHandler(int /*signal*/) { active = false; } 95 | 96 | int main(int argc, char *argv[]) { 97 | static const char usage[] = 98 | " [-i iface] [-w file] [-t] [-o] maddr\n" 99 | "\n" 100 | " -i iface Interface to capture packets from\n" 101 | " -w file Write packets in pcap format to file\n" 102 | " -t Use hardware timestamps if available (or fail)\n" 103 | " -o Capture outgoing packets if available (or fail)"; 104 | 105 | std::string interface; 106 | std::string filename; 107 | bool hw_timestamps = false; 108 | bool sniff_transmit = false; 109 | int c = 0; 110 | while ((c = getopt(argc, argv, "i:w:to")) != -1) { 111 | switch (c) { 112 | case 'i': 113 | interface = optarg; 114 | break; 115 | case 'w': 116 | filename = optarg; 117 | break; 118 | case 't': 119 | hw_timestamps = true; 120 | break; 121 | case 'o': 122 | sniff_transmit = true; 123 | break; 124 | default: 125 | std::cerr << "usage: " << argv[0] << usage << std::endl; 126 | return 1; 127 | } 128 | } 129 | 130 | struct Filter { 131 | in_addr addr; 132 | in_port_t port; 133 | }; 134 | std::vector filters; 135 | for (int i = optind; i < argc; i++) { 136 | Filter filter = {}; 137 | char *sep = strchr(argv[i], ':'); 138 | if (sep) { 139 | *sep = 0; 140 | filter.port = htons(atoi(sep + 1)); 141 | } 142 | if (inet_aton(argv[i], &filter.addr) == 0) { 143 | std::runtime_error("invalid address"); 144 | } 145 | filters.push_back(filter); 146 | } 147 | 148 | std::signal(SIGINT, signalHandler); 149 | 150 | struct { 151 | // Resource handles 152 | ef_driver_handle dh; 153 | struct ef_pd pd; 154 | struct ef_vi vi; 155 | int rxPrefixLen; 156 | 157 | // DMA memory 158 | void *pktBufs; 159 | int nPktBufs; 160 | struct ef_memreg memreg; 161 | std::vector freePktBufs; 162 | std::vector pktBufAddrs; 163 | } res = {}; 164 | 165 | if (ef_driver_open(&res.dh) < 0) { 166 | throw std::system_error(errno, std::generic_category(), "ef_driver_open"); 167 | } 168 | if (ef_pd_alloc_by_name(&res.pd, res.dh, interface.c_str(), EF_PD_DEFAULT) < 169 | 0) { 170 | throw std::system_error(errno, std::generic_category(), 171 | "ef_pd_alloc_by_name"); 172 | } 173 | unsigned vi_flags = EF_VI_FLAGS_DEFAULT; 174 | if (hw_timestamps) { 175 | vi_flags |= EF_VI_RX_TIMESTAMPS; 176 | } 177 | if (ef_vi_alloc_from_pd(&res.vi, res.dh, &res.pd, res.dh, -1, -1, 0, NULL, -1, 178 | (enum ef_vi_flags) vi_flags) < 0) { 179 | throw std::system_error(errno, std::generic_category(), 180 | "ef_vi_alloc_from_pd"); 181 | } 182 | 183 | // Length of prefix before actual packet data 184 | res.rxPrefixLen = ef_vi_receive_prefix_len(&res.vi); 185 | 186 | // Allocate memory for DMA transfers. Try to get huge pages. 187 | res.nPktBufs = ef_vi_receive_capacity(&res.vi); 188 | const size_t bytesNeeded = res.nPktBufs * pktBufSize; 189 | // Round up to nearest huge page size 190 | const size_t bytesRounded = (bytesNeeded / hugePageSize + 1) * hugePageSize; 191 | res.pktBufs = mmap(NULL, bytesRounded, PROT_READ | PROT_WRITE, 192 | MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0); 193 | if (res.pktBufs == MAP_FAILED) { 194 | std::cerr << "warning: failed to allocate hugepages for DMA buffers" 195 | << std::endl; 196 | posix_memalign(&res.pktBufs, 4096, bytesRounded); 197 | } 198 | 199 | // Register the memory for use with DMA 200 | if (ef_memreg_alloc(&res.memreg, res.dh, &res.pd, res.dh, res.pktBufs, 201 | bytesRounded) < 0) { 202 | throw std::system_error(errno, std::generic_category(), "ef_memreg_alloc"); 203 | } 204 | // Store the DMA address for each packet buffer 205 | for (int i = 0; i < res.nPktBufs; ++i) { 206 | res.pktBufAddrs.push_back(ef_memreg_dma_addr(&res.memreg, i * pktBufSize)); 207 | res.freePktBufs.push_back(i); 208 | } 209 | 210 | // Fill the RX descriptor ring 211 | while (ef_vi_receive_space(&res.vi) > 0 && !res.freePktBufs.empty()) { 212 | const int pktBufId = res.freePktBufs.back(); 213 | res.freePktBufs.resize(res.freePktBufs.size() - 1); 214 | ef_vi_receive_init(&res.vi, res.pktBufAddrs[pktBufId], pktBufId); 215 | } 216 | ef_vi_receive_push(&res.vi); 217 | 218 | for (auto filter : filters) { 219 | // Match multicast 220 | ef_filter_spec filter_spec; 221 | ef_filter_spec_init(&filter_spec, EF_FILTER_FLAG_NONE); 222 | if (filter.port == 0) { 223 | uint8_t mac[6] = {0x01, 224 | 0x00, 225 | 0x5e, 226 | uint8_t(filter.addr.s_addr >> 8 & 0x7f), 227 | uint8_t(filter.addr.s_addr >> 16 & 0xff), 228 | uint8_t(filter.addr.s_addr >> 24 & 0xff)}; 229 | if (ef_filter_spec_set_eth_local(&filter_spec, EF_FILTER_VLAN_ID_ANY, 230 | mac) < 0) { 231 | throw std::system_error(errno, std::generic_category(), 232 | "ef_filter_spec_set_port_sniff"); 233 | } 234 | } else { 235 | if (ef_filter_spec_set_ip4_local(&filter_spec, IPPROTO_UDP, 236 | filter.addr.s_addr, filter.port) < 0) { 237 | throw std::system_error(errno, std::generic_category(), 238 | "ef_filter_spec_set_port_sniff"); 239 | } 240 | } 241 | if (ef_vi_filter_add(&res.vi, res.dh, &filter_spec, NULL) < 0) { 242 | throw std::system_error(errno, std::generic_category(), 243 | "ef_vi_filter_add"); 244 | } 245 | } 246 | if (filters.empty()) { 247 | // Match all packets that also match another filter 248 | ef_filter_spec filter_spec; 249 | ef_filter_spec_init(&filter_spec, EF_FILTER_FLAG_NONE); 250 | if (ef_filter_spec_set_port_sniff(&filter_spec, 1) < 0) { 251 | throw std::system_error(errno, std::generic_category(), 252 | "ef_filter_spec_set_port_sniff"); 253 | } 254 | if (ef_vi_filter_add(&res.vi, res.dh, &filter_spec, NULL) < 0) { 255 | throw std::system_error(errno, std::generic_category(), 256 | "ef_vi_filter_add"); 257 | } 258 | } 259 | if (sniff_transmit) { 260 | ef_filter_spec filter_spec; 261 | ef_filter_spec_init(&filter_spec, EF_FILTER_FLAG_NONE); 262 | if (ef_filter_spec_set_tx_port_sniff(&filter_spec)) { 263 | throw std::system_error(errno, std::generic_category(), 264 | "ef_filter_spec_set_tx_port_sniff"); 265 | } 266 | if (ef_vi_filter_add(&res.vi, res.dh, &filter_spec, NULL) < 0) { 267 | throw std::system_error(errno, std::generic_category(), 268 | "ef_filter_spec_set_tx_port_sniff/ef_vi_filter_add"); 269 | } 270 | } 271 | 272 | // Open pcap output 273 | pcap_t *pcap = nullptr; 274 | pcap_dumper_t *pcapDumper = nullptr; 275 | if (!filename.empty()) { 276 | int link_type = DLT_EN10MB; 277 | int snap_len = 65535; 278 | u_int precision = PCAP_TSTAMP_PRECISION_MICRO; 279 | if (hw_timestamps) { 280 | precision = PCAP_TSTAMP_PRECISION_NANO; 281 | } 282 | pcap = pcap_open_dead_with_tstamp_precision(link_type, snap_len, precision); 283 | pcapDumper = pcap_dump_open(pcap, filename.c_str()); 284 | if (!pcapDumper) { 285 | throw std::runtime_error("pcap_dump_open: " + 286 | std::string(pcap_geterr(pcap))); 287 | } 288 | } 289 | 290 | std::array evs = {}; 291 | while (active.load(std::memory_order_acquire)) { 292 | const int nev = ef_eventq_poll(&res.vi, evs.data(), evs.size()); 293 | if (nev > 0) { 294 | for (int i = 0; i < nev; ++i) { 295 | switch (EF_EVENT_TYPE(evs[i])) { 296 | case EF_EVENT_TYPE_RX: { 297 | res.freePktBufs.push_back(EF_EVENT_RX_RQ_ID(evs[i])); 298 | if (EF_EVENT_RX_SOP(evs[i]) == 0 || EF_EVENT_RX_CONT(evs[i]) != 0) { 299 | // Ignore jumbo packets 300 | break; 301 | } 302 | const char *buf = (const char *)res.pktBufs + 303 | pktBufSize * EF_EVENT_RX_RQ_ID(evs[i]) + 304 | res.rxPrefixLen; 305 | const int bufLen = EF_EVENT_RX_BYTES(evs[i]) - res.rxPrefixLen; 306 | if (pcapDumper) { 307 | pcap_pkthdr pktHdr = {}; 308 | pktHdr.caplen = bufLen; 309 | pktHdr.len = bufLen; 310 | if (hw_timestamps) { 311 | timespec ts; 312 | unsigned flags; 313 | const void *pkt = (const char *) res.pktBufs + pktBufSize * EF_EVENT_RX_RQ_ID(evs[i]); 314 | if (ef_vi_receive_get_timestamp_with_sync_flags(&res.vi, pkt, &ts, &flags)) { 315 | throw std::runtime_error("failed to get hw timestamp"); 316 | } 317 | pktHdr.ts.tv_sec = ts.tv_sec; 318 | pktHdr.ts.tv_usec = ts.tv_nsec; // magic number should be set for nanos 319 | } else { 320 | gettimeofday(&pktHdr.ts, nullptr); 321 | } 322 | pcap_dump(reinterpret_cast(pcapDumper), &pktHdr, 323 | reinterpret_cast(buf)); 324 | } else { 325 | printPacket(buf, bufLen); 326 | } 327 | break; 328 | } 329 | default: 330 | throw std::runtime_error("ef_eventq_poll: unknown event type"); 331 | break; 332 | } 333 | } 334 | // Refill the RX descriptor ring 335 | if (ef_vi_receive_space(&res.vi) > refillBatchSize && 336 | res.freePktBufs.size() > refillBatchSize) { 337 | for (int i = 0; i < refillBatchSize; ++i) { 338 | const int pkt_buf_id = 339 | res.freePktBufs[res.freePktBufs.size() - refillBatchSize + i]; 340 | ef_vi_receive_init(&res.vi, res.pktBufAddrs[pkt_buf_id], pkt_buf_id); 341 | } 342 | res.freePktBufs.resize(res.freePktBufs.size() - refillBatchSize); 343 | ef_vi_receive_push(&res.vi); 344 | } 345 | } 346 | } 347 | 348 | if (pcapDumper) { 349 | pcap_dump_close(pcapDumper); 350 | pcap_close(pcap); 351 | } 352 | 353 | return 0; 354 | } 355 | --------------------------------------------------------------------------------