Line data Source code
1 : /* This stage disables the "tx-udp-segmentation" offload on the loopback
2 : interface. If left enabled, AF_XDP will drop loopback UDP packets sent
3 : by processes that enable TX segmentation via SOL_UDP/UDP_SEGMENT sockopt
4 : or cmsg.
5 :
6 : TLDR tx-udp-segmentation and AF_XDP are incompatible. */
7 :
8 : #include "configure.h"
9 :
10 : #include <errno.h>
11 : #include <net/if.h>
12 : #include <stdio.h>
13 : #include <unistd.h>
14 : #include <sys/ioctl.h>
15 : #include <sys/stat.h>
16 : #include <linux/if.h>
17 : #include <linux/ethtool.h>
18 : #include <linux/sockios.h>
19 :
20 0 : #define NAME "ethtool-loopback"
21 0 : #define MAX_FEATURES (1024)
22 :
23 : #define UDPSEG_FEATURE "tx-udp-segmentation"
24 : static char const udpseg_feature[] = UDPSEG_FEATURE;
25 :
26 : #define ETHTOOL_CMD_SZ( base_t, data_t, data_len ) ( sizeof(base_t) + (sizeof(data_t)*(data_len)) )
27 :
28 : static int
29 0 : enabled( config_t const * config ) {
30 :
31 : /* if we're running in a network namespace, we configure ethtool on
32 : the virtual device as part of netns setup, not here */
33 0 : if( config->development.netns.enabled ) return 0;
34 :
35 : /* only enable if network stack is XDP */
36 0 : if( 0!=strcmp( config->net.provider, "xdp" ) ) return 0;
37 :
38 0 : return 1;
39 0 : }
40 :
41 : static void
42 : init_perm( fd_cap_chk_t * chk,
43 0 : config_t const * config FD_PARAM_UNUSED ) {
44 0 : fd_cap_chk_root( chk, NAME, "disable loopback " UDPSEG_FEATURE " with `ethtool --offload lo " UDPSEG_FEATURE " off`" );
45 0 : }
46 :
47 : /* ethtool_ioctl wraps ioctl(sock,SIOCETHTOOL,"lo",*) */
48 :
49 : static int
50 : ethtool_ioctl( int sock,
51 0 : void * data ) {
52 0 : struct ifreq ifr = {0};
53 0 : strcpy( ifr.ifr_name, "lo" );
54 0 : ifr.ifr_data = data;
55 0 : return ioctl( sock, SIOCETHTOOL, &ifr );
56 0 : }
57 :
58 : /* find_feature_index finds the index of an ethtool feature. */
59 :
60 : static int
61 : find_feature_index( int sock,
62 0 : char const * feature ) {
63 :
64 0 : union {
65 0 : struct ethtool_sset_info r;
66 0 : uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sset_info, uint, 1 ) ];
67 0 : } set_info = { .r = {
68 0 : .cmd = ETHTOOL_GSSET_INFO,
69 0 : .sset_mask = fd_ulong_mask_bit( ETH_SS_FEATURES )
70 0 : } };
71 0 : if( FD_UNLIKELY( ethtool_ioctl( sock, &set_info ) ) ) {
72 0 : FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSSET_INFO) failed (%i-%s)",
73 0 : errno, fd_io_strerror( errno ) ));
74 0 : }
75 0 : fd_msan_unpoison( set_info.r.data, sizeof(uint) );
76 0 : uint const feature_cnt = fd_uint_min( set_info.r.data[0], MAX_FEATURES );
77 :
78 0 : static union {
79 0 : struct ethtool_gstrings r;
80 0 : uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gstrings, uchar, MAX_FEATURES*ETH_GSTRING_LEN ) ];
81 0 : } get_strings;
82 0 : get_strings.r = (struct ethtool_gstrings) {
83 0 : .cmd = ETHTOOL_GSTRINGS,
84 0 : .string_set = ETH_SS_FEATURES,
85 0 : .len = feature_cnt
86 0 : };
87 0 : if( FD_UNLIKELY( ethtool_ioctl( sock, &get_strings ) ) ) {
88 0 : FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSTRINGS) failed (%i-%s)",
89 0 : errno, fd_io_strerror( errno ) ));
90 0 : }
91 0 : fd_msan_unpoison( get_strings.r.data, ETH_GSTRING_LEN*feature_cnt );
92 :
93 0 : for( uint j=0UL; j<feature_cnt; j++ ) {
94 0 : uchar const * str = get_strings.r.data + (j*ETH_GSTRING_LEN);
95 0 : if( 0==strncmp( (char const *)str, feature, ETH_GSTRING_LEN ) ) return (int)j;
96 0 : }
97 0 : return -1;
98 0 : }
99 :
100 : /* get_feature_state checks if the ethtool feature at index is set.
101 : Returns 1 if enabled, 0 if disabled. Terminates app on failure. */
102 :
103 : static _Bool
104 : get_feature_state( int sock,
105 0 : int index ) {
106 0 : FD_TEST( index>0 && index<MAX_FEATURES );
107 :
108 0 : union {
109 0 : struct ethtool_gfeatures r;
110 0 : uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gfeatures, struct ethtool_get_features_block, (MAX_FEATURES+31)/32 ) ];
111 0 : } get_features;
112 0 : get_features.r = (struct ethtool_gfeatures) {
113 0 : .cmd = ETHTOOL_GFEATURES,
114 0 : .size = (MAX_FEATURES+31)/32
115 0 : };
116 0 : if( FD_UNLIKELY( ethtool_ioctl( sock, &get_features ) ) ) {
117 0 : FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GFEATURES) failed (%i-%s)",
118 0 : errno, fd_io_strerror( errno ) ));
119 0 : }
120 0 : fd_msan_unpoison( get_features.r.features, get_features.r.size*sizeof(struct ethtool_get_features_block) );
121 :
122 0 : uint bucket = (uint)index / 32u;
123 0 : uint offset = (uint)index % 32u;
124 0 : return fd_uint_extract_bit( get_features.r.features[ bucket ].active, (int)offset );
125 0 : }
126 :
127 : /* change_feature updates the ethtool feature at the specified index.
128 : state==1 implies enable, state==0 implies disable. Terminates app on
129 : failure. */
130 :
131 : static void
132 : change_feature( int sock,
133 : int index,
134 0 : _Bool state ) {
135 0 : FD_TEST( index>0 && index<MAX_FEATURES );
136 0 : uint bucket = (uint)index / 32u;
137 0 : uint offset = (uint)index % 32u;
138 :
139 0 : union {
140 0 : struct ethtool_sfeatures r;
141 0 : uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sfeatures, struct ethtool_set_features_block, (MAX_FEATURES+31)/32 ) ];
142 0 : } set_features = {0};
143 0 : set_features.r = (struct ethtool_sfeatures) {
144 0 : .cmd = ETHTOOL_SFEATURES,
145 0 : .size = bucket+1,
146 0 : };
147 :
148 0 : set_features.r.features[ bucket ].valid = 1u<<offset;
149 0 : set_features.r.features[ bucket ].requested = ((uint)state)<<offset;
150 :
151 0 : if( FD_UNLIKELY( ethtool_ioctl( sock, &set_features ) ) ) {
152 0 : FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%i-%s)",
153 0 : errno, fd_io_strerror( errno ) ));
154 0 : }
155 0 : }
156 :
157 : static void
158 0 : init( config_t const * config FD_PARAM_UNUSED ) {
159 0 : int sock = socket( AF_INET, SOCK_DGRAM, 0 );
160 0 : if( FD_UNLIKELY( sock < 0 ) )
161 0 : FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
162 0 : errno, fd_io_strerror( errno ) ));
163 :
164 0 : int feature_idx = find_feature_index( sock, udpseg_feature );
165 0 : if( feature_idx<0 ) return;
166 :
167 0 : FD_LOG_NOTICE(( "RUN: `ethtool --offload lo " UDPSEG_FEATURE " off`" ));
168 :
169 0 : change_feature( sock, feature_idx, 0 ); /* disable */
170 :
171 0 : if( FD_UNLIKELY( close( sock ) ) )
172 0 : FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
173 0 : }
174 :
175 : static configure_result_t
176 0 : check( config_t const * config FD_PARAM_UNUSED ) {
177 0 : int sock = socket( AF_INET, SOCK_DGRAM, 0 );
178 0 : if( FD_UNLIKELY( sock < 0 ) )
179 0 : FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
180 0 : errno, fd_io_strerror( errno ) ));
181 :
182 0 : int feature_idx = find_feature_index( sock, udpseg_feature );
183 0 : if( feature_idx<0 ) {
184 0 : FD_LOG_INFO(( "device `lo` missing ethtool offload `" UDPSEG_FEATURE "`, ignoring" ));
185 0 : CONFIGURE_OK(); /* returns */
186 0 : }
187 :
188 0 : _Bool udpseg_enabled = get_feature_state( sock, feature_idx );
189 :
190 0 : if( FD_UNLIKELY( close( sock ) ) )
191 0 : FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
192 :
193 0 : if( udpseg_enabled ) {
194 0 : NOT_CONFIGURED( "device `lo` has " UDPSEG_FEATURE " enabled. Should be disabled" );
195 0 : }
196 :
197 0 : CONFIGURE_OK();
198 0 : }
199 :
200 : configure_stage_t fd_cfg_stage_ethtool_loopback = {
201 : .name = NAME,
202 : .always_recreate = 0,
203 : .enabled = enabled,
204 : .init_perm = init_perm,
205 : .fini_perm = NULL,
206 : .init = init,
207 : .fini = NULL,
208 : .check = check,
209 : };
210 :
211 : #undef NAME
|