1 //
2 // Copyright 2011 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 // Copyright 2019 Ettus Research, A National Instruments Brand
5 //
6 // SPDX-License-Identifier: GPL-3.0-or-later
7 //
8 
9 #pragma once
10 
11 #include <uhd/config.hpp>
12 #include <uhd/exception.hpp>
13 #include <uhd/rfnoc/constants.hpp>
14 #include <uhd/types/device_addr.hpp>
15 #include <uhd/utils/log.hpp>
16 #include <uhdlib/transport/links.hpp>
17 #include <uhdlib/utils/narrow.hpp>
18 #include <boost/asio.hpp>
19 #include <boost/format.hpp>
20 #include <thread>
21 
22 namespace uhd { namespace transport {
23 
24 // Jumbo frames can be up to 9600 bytes;
25 constexpr size_t MAX_ETHERNET_MTU = 9600;
26 
27 constexpr size_t UDP_DEFAULT_NUM_FRAMES = 1;
28 
29 // Based on common 1500 byte MTU for 1GbE
30 constexpr size_t UDP_DEFAULT_FRAME_SIZE = 1472;
31 
32 // 20ms of data for 1GbE link (in bytes)
33 constexpr size_t UDP_DEFAULT_BUFF_SIZE = 2500000;
34 
35 
36 #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
37 // MacOS limits socket buffer size to 1 Mib
38 static const size_t MAX_BUFF_SIZE_ETH_MACOS = 0x100000; // 1Mib
39 #endif
40 
41 typedef std::shared_ptr<boost::asio::ip::udp::socket> socket_sptr;
42 
43 /*!
44  * Wait for the socket to become ready for a receive operation.
45  * \param sock_fd the open socket file descriptor
46  * \param timeout_ms the timeout duration in milliseconds
47  * \return true when the socket is ready for receive
48  */
wait_for_recv_ready(int sock_fd,int32_t timeout_ms)49 UHD_INLINE bool wait_for_recv_ready(int sock_fd, int32_t timeout_ms)
50 {
51 #ifdef UHD_PLATFORM_WIN32 // select is more portable than poll unfortunately
52     // setup timeval for timeout
53     timeval tv;
54     // If the tv_usec > 1 second on some platforms, select will
55     // error EINVAL: An invalid timeout interval was specified.
56     tv.tv_sec  = int(timeout_ms / 1000);
57     tv.tv_usec = int(timeout_ms * 1000) % 1000000;
58 
59     // setup rset for timeout
60     fd_set rset;
61     FD_ZERO(&rset);
62     FD_SET(sock_fd, &rset);
63 
64 // http://www.gnu.org/s/hello/manual/libc/Interrupted-Primitives.html
65 // This macro is provided with gcc to properly deal with EINTR.
66 // If not provided, define an empty macro, assume that is OK
67 #    ifndef TEMP_FAILURE_RETRY
68 #        define TEMP_FAILURE_RETRY(x) (x)
69 #    endif
70 
71     // call select with timeout on receive socket
72     return TEMP_FAILURE_RETRY(::select(sock_fd + 1, &rset, NULL, NULL, &tv)) > 0;
73 #else
74     pollfd pfd_read;
75     pfd_read.fd     = sock_fd;
76     pfd_read.events = POLLIN;
77 
78     // call poll with timeout on receive socket
79     return ::poll(&pfd_read, 1, (int)timeout_ms) > 0;
80 #endif
81 }
82 
open_udp_socket(const std::string & addr,const std::string & port,boost::asio::io_service & io_service)83 UHD_INLINE socket_sptr open_udp_socket(
84     const std::string& addr, const std::string& port, boost::asio::io_service& io_service)
85 {
86     using udp = boost::asio::ip::udp;
87 
88     // resolve the address
89     udp::resolver resolver(io_service);
90     udp::resolver::query query(udp::v4(), addr, port);
91     udp::endpoint receiver_endpoint = *resolver.resolve(query);
92 
93     // create, open, and connect the socket
94     socket_sptr socket = socket_sptr(new udp::socket(io_service));
95     socket->open(udp::v4());
96     socket->connect(receiver_endpoint);
97 
98     return socket;
99 }
100 
recv_udp_packet(int sock_fd,void * mem,size_t frame_size,int32_t timeout_ms)101 UHD_INLINE size_t recv_udp_packet(
102     int sock_fd, void* mem, size_t frame_size, int32_t timeout_ms)
103 {
104     ssize_t len;
105 
106 #ifdef MSG_DONTWAIT // try a non-blocking recv() if supported
107     len = ::recv(sock_fd, (char*)mem, frame_size, MSG_DONTWAIT);
108     if (len > 0) {
109         return len;
110     }
111 #endif
112 
113     if (wait_for_recv_ready(sock_fd, timeout_ms)) {
114         len = uhd::narrow_cast<ssize_t>(::recv(sock_fd, (char*)mem, frame_size, 0));
115         if (len == 0) {
116             throw uhd::io_error("socket closed");
117         }
118         if (len < 0) {
119             throw uhd::io_error(
120                 str(boost::format("recv error on socket: %s") % strerror(errno)));
121         }
122         return len;
123     }
124 
125     return 0; // timeout
126 }
127 
send_udp_packet(int sock_fd,void * mem,size_t len)128 UHD_INLINE void send_udp_packet(int sock_fd, void* mem, size_t len)
129 {
130     // Retry logic because send may fail with ENOBUFS.
131     // This is known to occur at least on some OSX systems.
132     // But it should be safe to always check for the error.
133     while (true) {
134         const ssize_t ret =
135             uhd::narrow_cast<ssize_t>(::send(sock_fd, (const char*)mem, len, 0));
136         if (ret == ssize_t(len))
137             break;
138         if (ret == -1 and errno == ENOBUFS) {
139             std::this_thread::sleep_for(std::chrono::microseconds(1));
140             continue; // try to send again
141         }
142         if (ret == -1) {
143             throw uhd::io_error(
144                 str(boost::format("send error on socket: %s") % strerror(errno)));
145         }
146         UHD_ASSERT_THROW(ret == ssize_t(len));
147     }
148 }
149 
150 template <typename Opt>
get_udp_socket_buffer_size(socket_sptr socket)151 size_t get_udp_socket_buffer_size(socket_sptr socket)
152 {
153     Opt option;
154     socket->get_option(option);
155     return option.value();
156 }
157 
158 template <typename Opt>
resize_udp_socket_buffer(socket_sptr socket,size_t num_bytes)159 size_t resize_udp_socket_buffer(socket_sptr socket, size_t num_bytes)
160 {
161 #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
162     // limit buffer resize on macos or it will error
163     num_bytes = std::min(num_bytes, MAX_BUFF_SIZE_ETH_MACOS);
164 #endif
165     Opt option(num_bytes);
166     socket->set_option(option);
167     return get_udp_socket_buffer_size<Opt>(socket);
168 }
169 
resize_udp_socket_buffer_with_warning(std::function<size_t (size_t)> resize_fn,const size_t target_size,const std::string & name)170 UHD_INLINE size_t resize_udp_socket_buffer_with_warning(
171     std::function<size_t(size_t)> resize_fn,
172     const size_t target_size,
173     const std::string& name)
174 {
175     size_t actual_size = 0;
176     std::string help_message;
177 #if defined(UHD_PLATFORM_LINUX)
178     help_message = str(boost::format("Please run: sudo sysctl -w net.core.%smem_max=%d")
179                        % ((name == "recv") ? "r" : "w") % target_size);
180 #endif /*defined(UHD_PLATFORM_LINUX)*/
181 
182     // resize the buffer if size was provided
183     if (target_size > 0) {
184         actual_size = resize_fn(target_size);
185 
186         UHD_LOGGER_TRACE("UDP")
187             << boost::format("Target/actual %s sock buff size: %d/%d bytes") % name
188                    % target_size % actual_size;
189         if (actual_size < target_size)
190             UHD_LOGGER_WARNING("UDP")
191                 << boost::format(
192                        "The %s buffer could not be resized sufficiently.\n"
193                        "Target sock buff size: %d bytes.\n"
194                        "Actual sock buff size: %d bytes.\n"
195                        "See the transport application notes on buffer resizing.\n%s")
196                        % name % target_size % actual_size % help_message;
197     }
198 
199     return actual_size;
200 }
201 
202 /*!
203  * Determines a set of values to use for a UDP CHDR link based on defaults and
204  * any overrides that the user may have provided. In cases where both device
205  * and stream arguments can be used to override a value, note that the stream
206  * argument will always take precedence.
207  *
208  * \param link_type the link type (CTRL, RX, TX) to calculate parameters for
209  * \param send_mtu the MTU of link for Tx cases
210  * \param recv_mtu the MTU of link for Rx cases
211  * \param default_link_params default values to use for the link parameters
212  * \param device_args device-level argument dictionary for overrides
213  * \param link_args argument dictionary with stream-level overrides (come from
214  *        stream params)
215  * \return Parameters to apply
216  */
calculate_udp_link_params(const uhd::transport::link_type_t link_type,const size_t send_mtu,const size_t recv_mtu,const link_params_t & default_link_params,const uhd::device_addr_t & device_args,const uhd::device_addr_t & link_args)217 inline link_params_t calculate_udp_link_params(
218     const uhd::transport::link_type_t link_type,
219     const size_t send_mtu,
220     const size_t recv_mtu,
221     const link_params_t& default_link_params,
222     const uhd::device_addr_t& device_args,
223     const uhd::device_addr_t& link_args)
224 {
225     // Apply any device-level overrides to the default values first.
226     // If the MTU is overridden, it will be capped to the value provided by
227     // the caller.
228     const size_t constrained_send_mtu =
229         std::min(send_mtu, device_args.cast<size_t>("mtu", send_mtu));
230     const size_t constrained_recv_mtu =
231         std::min(recv_mtu, device_args.cast<size_t>("mtu", recv_mtu));
232 
233     link_params_t link_params;
234     link_params.num_send_frames =
235         device_args.cast<size_t>("num_send_frames", default_link_params.num_send_frames);
236     link_params.num_recv_frames =
237         device_args.cast<size_t>("num_recv_frames", default_link_params.num_recv_frames);
238     link_params.send_frame_size =
239         device_args.cast<size_t>("send_frame_size", default_link_params.send_frame_size);
240     link_params.recv_frame_size =
241         device_args.cast<size_t>("recv_frame_size", default_link_params.recv_frame_size);
242     link_params.send_buff_size =
243         device_args.cast<size_t>("send_buff_size", default_link_params.send_buff_size);
244     link_params.recv_buff_size =
245         device_args.cast<size_t>("recv_buff_size", default_link_params.recv_buff_size);
246 
247     // Now apply stream-level overrides based on the link type.
248     if (link_type == link_type_t::CTRL) {
249         // Control links typically do not allow the number of frames to be
250         // configured.
251         link_params.num_recv_frames =
252             uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE;
253     } else if (link_type == link_type_t::TX_DATA) {
254         // Note that the send frame size will be capped to the Tx MTU.
255         link_params.send_frame_size = link_args.cast<size_t>("send_frame_size",
256             std::min(link_params.send_frame_size, constrained_send_mtu));
257         link_params.num_send_frames =
258             link_args.cast<size_t>("num_send_frames", link_params.num_send_frames);
259         link_params.send_buff_size =
260             link_args.cast<size_t>("send_buff_size", link_params.send_buff_size);
261     } else if (link_type == link_type_t::RX_DATA) {
262         // Note that the receive frame size will be capped to the Rx MTU.
263         link_params.recv_frame_size = link_args.cast<size_t>("recv_frame_size",
264             std::min(link_params.recv_frame_size, constrained_recv_mtu));
265         link_params.num_recv_frames =
266             link_args.cast<size_t>("num_recv_frames", link_params.num_recv_frames);
267         link_params.recv_buff_size =
268             link_args.cast<size_t>("recv_buff_size", link_params.recv_buff_size);
269     }
270 
271 #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
272     // limit buffer size on OSX to avoid the warning issued by
273     // resize_buff_helper
274     if (link_params.recv_buff_size > MAX_BUFF_SIZE_ETH_MACOS) {
275         link_params.recv_buff_size = MAX_BUFF_SIZE_ETH_MACOS;
276     }
277     if (link_params.send_buff_size > MAX_BUFF_SIZE_ETH_MACOS) {
278         link_params.send_buff_size = MAX_BUFF_SIZE_ETH_MACOS;
279     }
280 #endif
281 
282     return link_params;
283 }
284 
285 
286 }} // namespace uhd::transport
287