Line data Source code
1 : #define _GNU_SOURCE
2 : #include "run.h"
3 :
4 : #include <sys/wait.h>
5 : #include "generated/main_seccomp.h"
6 : #if defined(__aarch64__)
7 : #include "generated/pidns.arm64_seccomp.h"
8 : #else
9 : #include "generated/pidns_seccomp.h"
10 : #endif
11 :
12 : #include "../../../disco/topo/fd_pod_format.h"
13 : #include "../../../disco/keyguard/fd_keyswitch.h"
14 : #include "../../../waltz/xdp/fd_xdp1.h"
15 : #if FD_HAS_NO_AGAVE
16 : #include "../../../flamenco/runtime/fd_blockstore.h"
17 : #include "../../../flamenco/runtime/fd_txncache.h"
18 : #include "../../../flamenco/runtime/fd_runtime.h"
19 : #endif
20 : #include "../../../funk/fd_funk_filemap.h"
21 : #include "../../../funk/fd_funk.h"
22 : #include "../../../waltz/ip/fd_fib4.h"
23 : #include "../../../waltz/mib/fd_dbl_buf.h"
24 : #undef FD_MAP_FLAG_BLOCKING
25 : #include "../../../waltz/neigh/fd_neigh4_map.h"
26 : #include "../configure/configure.h"
27 :
28 : #include <dirent.h>
29 : #include <sched.h>
30 : #include <stdio.h>
31 : #include <poll.h>
32 : #include <fcntl.h>
33 : #include <sys/prctl.h>
34 : #include <sys/mman.h>
35 : #include <sys/stat.h>
36 : #include <linux/capability.h>
37 : #include <linux/unistd.h>
38 :
39 : #include "../../../util/tile/fd_tile_private.h"
40 :
41 0 : #define NAME "run"
42 :
43 : void
44 : run_cmd_perm( args_t * args FD_PARAM_UNUSED,
45 : fd_caps_ctx_t * caps,
46 0 : config_t const * config ) {
47 0 : (void)args;
48 :
49 0 : ulong mlock_limit = fd_topo_mlock_max_tile( &config->topo );
50 :
51 0 : fd_caps_check_resource( caps, NAME, RLIMIT_MEMLOCK, mlock_limit, "call `rlimit(2)` to increase `RLIMIT_MEMLOCK` so all memory can be locked with `mlock(2)`" );
52 0 : fd_caps_check_resource( caps, NAME, RLIMIT_NICE, 40, "call `setpriority(2)` to increase thread priorities" );
53 0 : fd_caps_check_resource( caps, NAME, RLIMIT_NOFILE, CONFIGURE_NR_OPEN_FILES,
54 0 : "call `rlimit(2) to increase `RLIMIT_NOFILE` to allow more open files for Agave" );
55 0 : fd_caps_check_capability( caps, NAME, CAP_NET_RAW, "call `socket(2)` to bind to a raw socket for use by XDP" );
56 0 : fd_caps_check_capability( caps, NAME, CAP_SYS_ADMIN, "call `bpf(2)` with the `BPF_OBJ_GET` command to initialize XDP" );
57 0 : if( fd_sandbox_requires_cap_sys_admin( config->uid, config->gid ) )
58 0 : fd_caps_check_capability( caps, NAME, CAP_SYS_ADMIN, "call `unshare(2)` with `CLONE_NEWUSER` to sandbox the process in a user namespace" );
59 0 : if( FD_LIKELY( getuid() != config->uid ) )
60 0 : fd_caps_check_capability( caps, NAME, CAP_SETUID, "call `setresuid(2)` to switch uid to the sandbox user" );
61 0 : if( FD_LIKELY( getgid()!=config->gid ) )
62 0 : fd_caps_check_capability( caps, NAME, CAP_SETGID, "call `setresgid(2)` to switch gid to the sandbox user" );
63 0 : if( FD_UNLIKELY( config->development.netns.enabled ) )
64 0 : fd_caps_check_capability( caps, NAME, CAP_SYS_ADMIN, "call `setns(2)` to enter a network namespace" );
65 0 : if( FD_UNLIKELY( config->tiles.metric.prometheus_listen_port<1024 ) )
66 0 : fd_caps_check_capability( caps, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for serving metrics" );
67 0 : if( FD_UNLIKELY( config->tiles.gui.gui_listen_port<1024 ) )
68 0 : fd_caps_check_capability( caps, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for serving the GUI" );
69 0 : }
70 :
71 : struct pidns_clone_args {
72 : config_t * config;
73 : int * pipefd;
74 : int closefd;
75 : };
76 :
77 : extern char fd_log_private_path[ 1024 ]; /* empty string on start */
78 :
79 : static pid_t pid_namespace;
80 :
81 0 : #define FD_LOG_ERR_NOEXIT(a) do { long _fd_log_msg_now = fd_log_wallclock(); fd_log_private_1( 4, _fd_log_msg_now, __FILE__, __LINE__, __func__, fd_log_private_0 a ); } while(0)
82 :
83 : extern int * fd_log_private_shared_lock;
84 :
85 : static void
86 0 : parent_signal( int sig ) {
87 0 : if( FD_LIKELY( pid_namespace ) ) kill( pid_namespace, SIGKILL );
88 :
89 : /* A pretty gross hack. For the local process, clear the lock so that
90 : we can always print the messages without waiting on another process,
91 : particularly if one of those processes might have just died. The
92 : signal handler is re-entrant so this also avoids a deadlock since
93 : the log lock is not re-entrant. */
94 0 : int lock = 0;
95 0 : fd_log_private_shared_lock = &lock;
96 :
97 0 : if( -1!=fd_log_private_logfile_fd() ) FD_LOG_ERR_NOEXIT(( "Received signal %s\nLog at \"%s\"", fd_io_strsignal( sig ), fd_log_private_path ));
98 0 : else FD_LOG_ERR_NOEXIT(( "Received signal %s", fd_io_strsignal( sig ) ));
99 :
100 0 : if( FD_LIKELY( sig==SIGINT ) ) exit_group( 128+SIGINT );
101 0 : else exit_group( 0 );
102 0 : }
103 :
104 : static void
105 0 : install_parent_signals( void ) {
106 0 : struct sigaction sa = {
107 0 : .sa_handler = parent_signal,
108 0 : .sa_flags = 0,
109 0 : };
110 0 : if( FD_UNLIKELY( sigaction( SIGTERM, &sa, NULL ) ) )
111 0 : FD_LOG_ERR(( "sigaction(SIGTERM) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
112 0 : if( FD_UNLIKELY( sigaction( SIGINT, &sa, NULL ) ) )
113 0 : FD_LOG_ERR(( "sigaction(SIGINT) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
114 :
115 0 : sa.sa_handler = SIG_IGN;
116 0 : if( FD_UNLIKELY( sigaction( SIGUSR1, &sa, NULL ) ) )
117 0 : FD_LOG_ERR(( "sigaction(SIGUSR1) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
118 0 : if( FD_UNLIKELY( sigaction( SIGUSR2, &sa, NULL ) ) )
119 0 : FD_LOG_ERR(( "sigaction(SIGUSR2) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
120 0 : }
121 :
122 : void *
123 0 : create_clone_stack( void ) {
124 0 : ulong mmap_sz = FD_TILE_PRIVATE_STACK_SZ + 2UL*FD_SHMEM_NORMAL_PAGE_SZ;
125 0 : uchar * stack = (uchar *)mmap( NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0 );
126 0 : if( FD_UNLIKELY( stack==MAP_FAILED ) )
127 0 : FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
128 :
129 : /* Make space for guard lo and guard hi */
130 0 : if( FD_UNLIKELY( munmap( stack, FD_SHMEM_NORMAL_PAGE_SZ ) ) )
131 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
132 0 : stack += FD_SHMEM_NORMAL_PAGE_SZ;
133 0 : if( FD_UNLIKELY( munmap( stack + FD_TILE_PRIVATE_STACK_SZ, FD_SHMEM_NORMAL_PAGE_SZ ) ) )
134 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
135 :
136 : /* Create the guard regions in the extra space */
137 0 : void * guard_lo = (void *)(stack - FD_SHMEM_NORMAL_PAGE_SZ );
138 0 : if( FD_UNLIKELY( mmap( guard_lo, FD_SHMEM_NORMAL_PAGE_SZ, PROT_NONE,
139 0 : MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, (off_t)0 )!=guard_lo ) )
140 0 : FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
141 :
142 0 : void * guard_hi = (void *)(stack + FD_TILE_PRIVATE_STACK_SZ);
143 0 : if( FD_UNLIKELY( mmap( guard_hi, FD_SHMEM_NORMAL_PAGE_SZ, PROT_NONE,
144 0 : MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, (off_t)0 )!=guard_hi ) )
145 0 : FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
146 :
147 0 : return stack;
148 0 : }
149 :
150 :
151 : static int
152 : execve_agave( int config_memfd,
153 0 : int pipefd ) {
154 0 : if( FD_UNLIKELY( -1==fcntl( pipefd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
155 0 : pid_t child = fork();
156 0 : if( FD_UNLIKELY( -1==child ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
157 0 : if( FD_LIKELY( !child ) ) {
158 0 : char _current_executable_path[ PATH_MAX ];
159 0 : current_executable_path( _current_executable_path );
160 :
161 0 : char config_fd[ 32 ];
162 0 : FD_TEST( fd_cstr_printf_check( config_fd, sizeof( config_fd ), NULL, "%d", config_memfd ) );
163 0 : char * args[ 5 ] = { _current_executable_path, "run-agave", "--config-fd", config_fd, NULL };
164 :
165 0 : char * envp[] = { NULL, NULL };
166 0 : char * google_creds = getenv( "GOOGLE_APPLICATION_CREDENTIALS" );
167 0 : char provide_creds[ PATH_MAX+30UL ];
168 0 : if( FD_UNLIKELY( google_creds ) ) {
169 0 : FD_TEST( fd_cstr_printf_check( provide_creds, sizeof( provide_creds ), NULL, "GOOGLE_APPLICATION_CREDENTIALS=%s", google_creds ) );
170 0 : envp[ 0 ] = provide_creds;
171 0 : }
172 :
173 0 : if( FD_UNLIKELY( -1==execve( _current_executable_path, args, envp ) ) ) FD_LOG_ERR(( "execve() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
174 0 : } else {
175 0 : if( FD_UNLIKELY( -1==fcntl( pipefd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
176 0 : return child;
177 0 : }
178 0 : return 0;
179 0 : }
180 :
181 : static pid_t
182 : execve_tile( fd_topo_tile_t * tile,
183 : fd_cpuset_t * floating_cpu_set,
184 : int floating_priority,
185 : int config_memfd,
186 0 : int pipefd ) {
187 0 : FD_CPUSET_DECL( cpu_set );
188 0 : if( FD_LIKELY( tile->cpu_idx!=ULONG_MAX ) ) {
189 : /* set the thread affinity before we clone the new process to ensure
190 : kernel first touch happens on the desired thread. */
191 0 : fd_cpuset_insert( cpu_set, tile->cpu_idx );
192 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, -19 ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
193 0 : } else {
194 0 : fd_memcpy( cpu_set, floating_cpu_set, fd_cpuset_footprint() );
195 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, floating_priority ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
196 0 : }
197 :
198 0 : if( FD_UNLIKELY( fd_cpuset_setaffinity( 0, cpu_set ) ) ) {
199 0 : if( FD_LIKELY( errno==EINVAL ) ) {
200 0 : FD_LOG_ERR(( "Unable to set the thread affinity for tile %s:%lu on cpu %lu. It is likely that the affinity "
201 0 : "you have specified for this tile in [layout.affinity] of your configuration file contains a "
202 0 : "CPU (%lu) which does not exist on this machine.",
203 0 : tile->name, tile->kind_id, tile->cpu_idx, tile->cpu_idx ));
204 0 : } else {
205 0 : FD_LOG_ERR(( "sched_setaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
206 0 : }
207 0 : }
208 :
209 : /* Clear CLOEXEC on the side of the pipe we want to pass to the tile. */
210 0 : if( FD_UNLIKELY( -1==fcntl( pipefd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
211 0 : pid_t child = fork();
212 0 : if( FD_UNLIKELY( -1==child ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
213 0 : if( FD_LIKELY( !child ) ) {
214 0 : char _current_executable_path[ PATH_MAX ];
215 0 : current_executable_path( _current_executable_path );
216 :
217 0 : char kind_id[ 32 ], config_fd[ 32 ], pipe_fd[ 32 ];
218 0 : FD_TEST( fd_cstr_printf_check( kind_id, sizeof( kind_id ), NULL, "%lu", tile->kind_id ) );
219 0 : FD_TEST( fd_cstr_printf_check( config_fd, sizeof( config_fd ), NULL, "%d", config_memfd ) );
220 0 : FD_TEST( fd_cstr_printf_check( pipe_fd, sizeof( pipe_fd ), NULL, "%d", pipefd ) );
221 0 : char * args[ 9 ] = { _current_executable_path, "run1", tile->name, kind_id, "--pipe-fd", pipe_fd, "--config-fd", config_fd, NULL };
222 0 : if( FD_UNLIKELY( -1==execve( _current_executable_path, args, NULL ) ) ) FD_LOG_ERR(( "execve() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
223 0 : } else {
224 0 : if( FD_UNLIKELY( -1==fcntl( pipefd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
225 0 : return child;
226 0 : }
227 0 : return 0;
228 0 : }
229 :
230 : extern int * fd_log_private_shared_lock;
231 :
232 : int
233 0 : main_pid_namespace( void * _args ) {
234 0 : struct pidns_clone_args * args = _args;
235 0 : if( FD_UNLIKELY( close( args->pipefd[ 0 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
236 0 : if( FD_UNLIKELY( -1!=args->closefd ) ) {
237 0 : if( FD_UNLIKELY( close( args->closefd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
238 0 : }
239 :
240 0 : config_t * const config = args->config;
241 :
242 0 : fd_log_thread_set( "pidns" );
243 0 : ulong pid = fd_sandbox_getpid(); /* Need to read /proc again.. we got a new PID from clone */
244 0 : fd_log_private_group_id_set( pid );
245 0 : fd_log_private_thread_id_set( pid );
246 0 : fd_log_private_stack_discover( FD_TILE_PRIVATE_STACK_SZ,
247 0 : &fd_tile_private_stack0, &fd_tile_private_stack1 );
248 :
249 0 : if( FD_UNLIKELY( !config->development.sandbox ) ) {
250 : /* If no sandbox, then there's no actual PID namespace so we can't
251 : wait() grandchildren for the exit code. Do this as a workaround. */
252 0 : if( FD_UNLIKELY( -1==prctl( PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0 ) ) )
253 0 : FD_LOG_ERR(( "prctl(PR_SET_CHILD_SUBREAPER) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
254 0 : }
255 :
256 : /* Save the current affinity, it will be restored after creating any child tiles */
257 0 : FD_CPUSET_DECL( floating_cpu_set );
258 0 : if( FD_UNLIKELY( fd_cpuset_getaffinity( 0, floating_cpu_set ) ) )
259 0 : FD_LOG_ERR(( "fd_cpuset_getaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
260 :
261 0 : pid_t child_pids[ FD_TOPO_MAX_TILES+1 ];
262 0 : char child_names[ FD_TOPO_MAX_TILES+1 ][ 32 ];
263 0 : struct pollfd fds[ FD_TOPO_MAX_TILES+2 ];
264 :
265 0 : int config_memfd = fdctl_cfg_to_memfd( config );
266 :
267 0 : if( FD_UNLIKELY( config->development.debug_tile ) ) {
268 0 : fd_log_private_shared_lock[1] = 1;
269 0 : }
270 :
271 0 : ulong child_cnt = 0UL;
272 0 : if( FD_LIKELY( !config->development.no_agave ) ) {
273 0 : int pipefd[ 2 ];
274 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
275 0 : fds[ child_cnt ] = (struct pollfd){ .fd = pipefd[ 0 ], .events = 0 };
276 0 : child_pids[ child_cnt ] = execve_agave( config_memfd, pipefd[ 1 ] );
277 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
278 0 : strncpy( child_names[ child_cnt ], "agave", 32 );
279 0 : child_cnt++;
280 0 : }
281 :
282 0 : if( FD_UNLIKELY( config->development.netns.enabled ) ) {
283 0 : enter_network_namespace( config->tiles.net.interface );
284 0 : close_network_namespace_original_fd();
285 0 : }
286 :
287 0 : errno = 0;
288 0 : int save_priority = getpriority( PRIO_PROCESS, 0 );
289 0 : if( FD_UNLIKELY( -1==save_priority && errno ) ) FD_LOG_ERR(( "getpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
290 :
291 0 : fd_xdp_fds_t xdp_fds = fd_topo_install_xdp( &config->topo );
292 :
293 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
294 0 : fd_topo_tile_t * tile = &config->topo.tiles[ i ];
295 0 : if( FD_UNLIKELY( tile->is_agave ) ) continue;
296 :
297 0 : if( FD_UNLIKELY( strcmp( tile->name, "net" ) ) ) {
298 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds.xsk_map_fd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
299 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds.prog_link_fd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
300 0 : } else {
301 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds.xsk_map_fd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
302 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds.prog_link_fd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
303 0 : }
304 :
305 0 : int pipefd[ 2 ];
306 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
307 0 : fds[ child_cnt ] = (struct pollfd){ .fd = pipefd[ 0 ], .events = 0 };
308 0 : child_pids[ child_cnt ] = execve_tile( tile, floating_cpu_set, save_priority, config_memfd, pipefd[ 1 ] );
309 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
310 0 : strncpy( child_names[ child_cnt ], tile->name, 32 );
311 0 : child_cnt++;
312 0 : }
313 :
314 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, save_priority ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
315 0 : if( FD_UNLIKELY( fd_cpuset_setaffinity( 0, floating_cpu_set ) ) )
316 0 : FD_LOG_ERR(( "fd_cpuset_setaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
317 :
318 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
319 0 : if( FD_UNLIKELY( close( config->log.lock_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
320 0 : if( FD_UNLIKELY( close( xdp_fds.xsk_map_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
321 0 : if( FD_UNLIKELY( close( xdp_fds.prog_link_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
322 :
323 0 : int allow_fds[ 4+FD_TOPO_MAX_TILES ];
324 0 : ulong allow_fds_cnt = 0;
325 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
326 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
327 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
328 0 : allow_fds[ allow_fds_cnt++ ] = args->pipefd[ 1 ]; /* write end of main pipe */
329 0 : for( ulong i=0; i<child_cnt; i++ )
330 0 : allow_fds[ allow_fds_cnt++ ] = fds[ i ].fd; /* read end of child pipes */
331 :
332 0 : struct sock_filter seccomp_filter[ 128UL ];
333 0 : populate_sock_filter_policy_pidns( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd() );
334 :
335 0 : if( FD_LIKELY( config->development.sandbox ) ) {
336 0 : fd_sandbox_enter( config->uid,
337 0 : config->gid,
338 0 : 0,
339 0 : 0,
340 0 : 0,
341 0 : 0,
342 0 : 1UL+child_cnt, /* RLIMIT_NOFILE needs to be set to the nfds argument of poll() */
343 0 : 0UL,
344 0 : 0UL,
345 0 : allow_fds_cnt,
346 0 : allow_fds,
347 0 : sock_filter_policy_pidns_instr_cnt,
348 0 : seccomp_filter );
349 0 : } else {
350 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
351 0 : }
352 :
353 : /* The supervsior process should not share the log lock, because a
354 : child process might die while holding it and we still need to
355 : reap and print errors. */
356 0 : int lock = 0;
357 0 : fd_log_private_shared_lock = &lock;
358 :
359 : /* Reap child process PIDs so they don't show up in `ps` etc. All of
360 : these children should have exited immediately after clone(2)'ing
361 : another child with a huge page based stack. */
362 0 : for( ulong i=0; i<child_cnt; i++ ) {
363 0 : int wstatus;
364 0 : int exited_pid = wait4( child_pids[ i ], &wstatus, (int)__WALL, NULL );
365 0 : if( FD_UNLIKELY( -1==exited_pid ) ) {
366 0 : FD_LOG_ERR(( "pidns wait4() failed (%i-%s) %lu %hu", errno, fd_io_strerror( errno ), i, fds[i].revents ));
367 0 : } else if( FD_UNLIKELY( child_pids[ i ]!=exited_pid ) ) {
368 0 : FD_LOG_ERR(( "pidns wait4() returned unexpected pid %d %d", child_pids[ i ], exited_pid ));
369 0 : } else if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) {
370 0 : FD_LOG_ERR_NOEXIT(( "tile %lu (%s) exited while booting with signal %d (%s)\n", i, child_names[ i ], WTERMSIG( wstatus ), fd_io_strsignal( WTERMSIG( wstatus ) ) ));
371 0 : exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
372 0 : }
373 0 : if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) ) {
374 0 : FD_LOG_ERR_NOEXIT(( "tile %lu (%s) exited while booting with code %d\n", i, child_names[ i ], WEXITSTATUS( wstatus ) ));
375 0 : exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
376 0 : }
377 0 : }
378 :
379 0 : fds[ child_cnt ] = (struct pollfd){ .fd = args->pipefd[ 1 ], .events = 0 };
380 :
381 : /* We are now the init process of the pid namespace. If the init
382 : process dies, all children are terminated. If any child dies, we
383 : terminate the init process, which will cause the kernel to
384 : terminate all other children bringing all of our processes down as
385 : a group. The parent process will also die if this process dies,
386 : due to getting SIGHUP on the pipe. */
387 0 : while( 1 ) {
388 0 : if( FD_UNLIKELY( -1==poll( fds, 1+child_cnt, -1 ) ) ) FD_LOG_ERR(( "poll() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
389 :
390 0 : for( ulong i=0UL; i<1UL+child_cnt; i++ ) {
391 0 : if( FD_UNLIKELY( fds[ i ].revents ) ) {
392 : /* Must have been POLLHUP, POLLERR and POLLNVAL are not possible. */
393 0 : if( FD_UNLIKELY( i==child_cnt ) ) {
394 : /* Parent process died, probably SIGINT, exit gracefully. */
395 0 : exit_group( 0 );
396 0 : }
397 :
398 0 : char * tile_name = child_names[ i ];
399 0 : ulong tile_idx = 0UL;
400 0 : if( FD_LIKELY( i>0UL ) ) tile_idx = config->development.no_agave ? i : i-1UL;
401 0 : ulong tile_id = config->topo.tiles[ tile_idx ].kind_id;
402 :
403 : /* Child process died, reap it to figure out exit code. */
404 0 : int wstatus;
405 0 : int exited_pid = wait4( -1, &wstatus, (int)__WALL | (int)WNOHANG, NULL );
406 0 : if( FD_UNLIKELY( -1==exited_pid ) ) {
407 0 : FD_LOG_ERR(( "pidns wait4() failed (%i-%s) %lu %hu", errno, fd_io_strerror( errno ), i, fds[ i ].revents ));
408 0 : } else if( FD_UNLIKELY( !exited_pid ) ) {
409 : /* Spurious wakeup, no child actually dead yet. */
410 0 : continue;
411 0 : }
412 :
413 0 : if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) {
414 0 : FD_LOG_ERR_NOEXIT(( "tile %s:%lu exited with signal %d (%s)", tile_name, tile_id, WTERMSIG( wstatus ), fd_io_strsignal( WTERMSIG( wstatus ) ) ));
415 0 : exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
416 0 : } else {
417 0 : FD_LOG_ERR_NOEXIT(( "tile %s:%lu exited with code %d", tile_name, tile_id, WEXITSTATUS( wstatus ) ));
418 0 : exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
419 0 : }
420 0 : }
421 0 : }
422 0 : }
423 0 : return 0;
424 0 : }
425 :
426 : int
427 : clone_firedancer( config_t * const config,
428 : int close_fd,
429 0 : int * out_pipe ) {
430 : /* This pipe is here just so that the child process knows when the
431 : parent has died (it will get a HUP). */
432 0 : int pipefd[2];
433 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC | O_NONBLOCK ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
434 :
435 : /* clone into a pid namespace */
436 0 : int flags = config->development.sandbox ? CLONE_NEWPID : 0;
437 0 : struct pidns_clone_args args = { .config = config, .closefd = close_fd, .pipefd = pipefd, };
438 :
439 0 : void * stack = create_clone_stack();
440 :
441 0 : int pid_namespace = clone( main_pid_namespace, (uchar *)stack + FD_TILE_PRIVATE_STACK_SZ, flags, &args );
442 0 : if( FD_UNLIKELY( pid_namespace<0 ) ) FD_LOG_ERR(( "clone() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
443 :
444 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
445 :
446 0 : *out_pipe = pipefd[ 0 ];
447 0 : return pid_namespace;
448 0 : }
449 :
450 : static void
451 : workspace_path( config_t * const config,
452 : fd_topo_wksp_t * wksp,
453 0 : char out[ PATH_MAX ] ) {
454 0 : char * mount_path;
455 0 : switch( wksp->page_sz ) {
456 0 : case FD_SHMEM_HUGE_PAGE_SZ:
457 0 : mount_path = config->hugetlbfs.huge_page_mount_path;
458 0 : break;
459 0 : case FD_SHMEM_GIGANTIC_PAGE_SZ:
460 0 : mount_path = config->hugetlbfs.gigantic_page_mount_path;
461 0 : break;
462 0 : default:
463 0 : FD_LOG_ERR(( "invalid page size %lu", wksp->page_sz ));
464 0 : }
465 :
466 0 : FD_TEST( fd_cstr_printf_check( out, PATH_MAX, NULL, "%s/%s_%s.wksp", mount_path, config->name, wksp->name ) );
467 0 : }
468 :
469 : static void
470 : warn_unknown_files( config_t * const config,
471 0 : ulong mount_type ) {
472 0 : char const * mount_path;
473 0 : switch( mount_type ) {
474 0 : case 0UL:
475 0 : mount_path = config->hugetlbfs.huge_page_mount_path;
476 0 : break;
477 0 : case 1UL:
478 0 : mount_path = config->hugetlbfs.gigantic_page_mount_path;
479 0 : break;
480 0 : default:
481 0 : FD_LOG_ERR(( "invalid mount type %lu", mount_type ));
482 0 : }
483 :
484 : /* Check if there are any files in mount_path */
485 0 : DIR * dir = opendir( mount_path );
486 0 : if( FD_UNLIKELY( !dir ) ) {
487 0 : if( FD_UNLIKELY( errno!=ENOENT ) ) FD_LOG_ERR(( "error opening `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
488 0 : return;
489 0 : }
490 :
491 0 : struct dirent * entry;
492 0 : while(( FD_LIKELY( entry = readdir( dir ) ) )) {
493 0 : if( FD_UNLIKELY( !strcmp( entry->d_name, ".") || !strcmp( entry->d_name, ".." ) ) ) continue;
494 :
495 0 : char entry_path[ PATH_MAX ];
496 0 : FD_TEST( fd_cstr_printf_check( entry_path, PATH_MAX, NULL, "%s/%s", mount_path, entry->d_name ));
497 :
498 0 : int known_file = 0;
499 0 : for( ulong i=0UL; i<config->topo.wksp_cnt; i++ ) {
500 0 : fd_topo_wksp_t * wksp = &config->topo.workspaces[ i ];
501 :
502 0 : char expected_path[ PATH_MAX ];
503 0 : workspace_path( config, wksp, expected_path );
504 :
505 0 : if( !strcmp( entry_path, expected_path ) ) {
506 0 : known_file = 1;
507 0 : break;
508 0 : }
509 0 : }
510 :
511 0 : if( mount_type==0UL ) {
512 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
513 0 : fd_topo_tile_t * tile = &config->topo.tiles [ i ];
514 :
515 0 : char expected_path[ PATH_MAX ];
516 0 : FD_TEST( fd_cstr_printf_check( expected_path, PATH_MAX, NULL, "%s/%s_stack_%s%lu", config->hugetlbfs.huge_page_mount_path, config->name, tile->name, tile->kind_id ) );
517 :
518 0 : if( !strcmp( entry_path, expected_path ) ) {
519 0 : known_file = 1;
520 0 : break;
521 0 : }
522 0 : }
523 0 : }
524 :
525 0 : if( FD_UNLIKELY( !known_file ) ) FD_LOG_WARNING(( "unknown file `%s` found in `%s`", entry->d_name, mount_path ));
526 0 : }
527 :
528 0 : if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "error closing `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
529 0 : }
530 :
531 : static void
532 : fdctl_obj_new( fd_topo_t const * topo,
533 0 : fd_topo_obj_t const * obj ) {
534 0 : #define VAL(name) (__extension__({ \
535 0 : ulong __x = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, name ); \
536 0 : if( FD_UNLIKELY( __x==ULONG_MAX ) ) FD_LOG_ERR(( "obj.%lu.%s was not set", obj->id, name )); \
537 0 : __x; }))
538 :
539 0 : void * laddr = fd_topo_obj_laddr( topo, obj->id );
540 :
541 0 : if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
542 : /* No need to do anything, tiles don't have a new. */
543 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "mcache" ) ) ) {
544 0 : FD_TEST( fd_mcache_new( laddr, VAL("depth"), 0UL, 0UL ) );
545 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "dcache" ) ) ) {
546 0 : FD_TEST( fd_dcache_new( laddr, fd_dcache_req_data_sz( VAL("mtu"), VAL("depth"), VAL("burst"), 1 ), 0UL ) );
547 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "cnc" ) ) ) {
548 0 : FD_TEST( fd_cnc_new( laddr, 0UL, 0, fd_tickcount() ) );
549 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "fseq" ) ) ) {
550 0 : FD_TEST( fd_fseq_new( laddr, ULONG_MAX ) );
551 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "metrics" ) ) ) {
552 0 : FD_TEST( fd_metrics_new( laddr, VAL("in_cnt"), VAL("cons_cnt") ) );
553 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "opaque" ) ) ) {
554 0 : fd_memset( laddr, 0, VAL("footprint") );
555 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "ulong" ) ) ) {
556 0 : *(ulong*)laddr = 0;
557 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "dbl_buf" ) ) ) {
558 0 : FD_TEST( fd_dbl_buf_new( laddr, VAL("mtu"), 1UL ) );
559 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) {
560 0 : FD_TEST( fd_funk_new( laddr, VAL("wksp_tag"), VAL("seed"), VAL("txn_max"), VAL("rec_max") ) );
561 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "neigh4_hmap" ) ) ) {
562 0 : FD_TEST( fd_neigh4_hmap_new( laddr, VAL("ele_max"), VAL("lock_cnt"), VAL("probe_max"), VAL("seed") ) );
563 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "fib4" ) ) ) {
564 0 : FD_TEST( fd_fib4_new( laddr, VAL("route_max") ) );
565 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "keyswitch" ) ) ) {
566 0 : FD_TEST( fd_keyswitch_new( laddr, FD_KEYSWITCH_STATE_UNLOCKED ) );
567 : #if FD_HAS_NO_AGAVE
568 : } else if( FD_UNLIKELY( !strcmp( obj->name, "replay_pub" ) ) ) {
569 : FD_TEST( fd_runtime_public_new( laddr ) );
570 : } else if( FD_UNLIKELY( !strcmp( obj->name, "blockstore" ) ) ) {
571 : FD_TEST( fd_blockstore_new( laddr, VAL("wksp_tag"), VAL("seed"), VAL("shred_max"), VAL("block_max"), VAL("idx_max"), VAL("txn_max") ) );
572 : } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) {
573 : FD_TEST( fd_txncache_new( laddr, VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot"), FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ) );
574 : #endif /* FD_HAS_NO_AGAVE */
575 0 : } else {
576 0 : FD_LOG_ERR(( "unknown object `%s`", obj->name ));
577 0 : }
578 0 : #undef VAL
579 0 : }
580 :
581 : void
582 0 : initialize_workspaces( config_t * const config ) {
583 : /* Switch to non-root uid/gid for workspace creation. Permissions
584 : checks are still done as the current user. */
585 0 : uint gid = getgid();
586 0 : uint uid = getuid();
587 0 : if( FD_LIKELY( gid!=config->gid && -1==setegid( config->gid ) ) )
588 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
589 0 : if( FD_LIKELY( uid!=config->uid && -1==seteuid( config->uid ) ) )
590 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
591 :
592 0 : for( ulong i=0UL; i<config->topo.wksp_cnt; i++ ) {
593 0 : fd_topo_wksp_t * wksp = &config->topo.workspaces[ i ];
594 :
595 0 : char path[ PATH_MAX ];
596 0 : workspace_path( config, wksp, path );
597 :
598 0 : struct stat st;
599 0 : int result = stat( path, &st );
600 :
601 0 : int update_existing;
602 0 : if( FD_UNLIKELY( !result && config->is_live_cluster ) ) {
603 0 : if( FD_UNLIKELY( -1==unlink( path ) && errno!=ENOENT ) ) FD_LOG_ERR(( "unlink() failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
604 0 : update_existing = 0;
605 0 : } else if( FD_UNLIKELY( !result ) ) {
606 : /* Creating all of the workspaces is very expensive because the
607 : kernel has to zero out all of the pages. There can be tens or
608 : hundreds of gigabytes of zeroing to do.
609 :
610 : What would be really nice is if the kernel let us create huge
611 : pages without zeroing them, but it's not possible. The
612 : ftruncate and fallocate calls do not support this type of
613 : resize with the hugetlbfs filesystem.
614 :
615 : Instead.. to prevent repeatedly doing this zeroing every time
616 : we start the validator, we have a small hack here to re-use the
617 : workspace files if they exist. */
618 0 : update_existing = 1;
619 0 : } else if( FD_LIKELY( result && errno==ENOENT ) ) {
620 0 : update_existing = 0;
621 0 : } else {
622 0 : FD_LOG_ERR(( "stat failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
623 0 : }
624 :
625 0 : if( FD_UNLIKELY( -1==fd_topo_create_workspace( &config->topo, wksp, update_existing ) ) ) {
626 0 : FD_TEST( errno==ENOMEM );
627 :
628 0 : warn_unknown_files( config, wksp->page_sz!=FD_SHMEM_HUGE_PAGE_SZ );
629 :
630 0 : char path[ PATH_MAX ];
631 0 : workspace_path( config, wksp, path );
632 0 : FD_LOG_ERR(( "ENOMEM-Out of memory when trying to create workspace `%s` at `%s` "
633 0 : "with %lu %s pages. Firedancer reserves enough memory for all of its workspaces "
634 0 : "during the `hugetlbfs` configure step, so it is likely you have unknown files "
635 0 : "left over in this directory which are consuming memory, or another program on "
636 0 : "the system is using pages from the same mount.",
637 0 : wksp->name, path, wksp->page_cnt, fd_shmem_page_sz_to_cstr( wksp->page_sz ) ));
638 0 : }
639 0 : fd_topo_join_workspace( &config->topo, wksp, FD_SHMEM_JOIN_MODE_READ_WRITE );
640 0 : fd_topo_wksp_apply( &config->topo, wksp, fdctl_obj_new );
641 0 : fd_topo_leave_workspace( &config->topo, wksp );
642 0 : }
643 :
644 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
645 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
646 0 : }
647 :
648 : void
649 0 : initialize_stacks( config_t * const config ) {
650 : /* Switch to non-root uid/gid for workspace creation. Permissions
651 : checks are still done as the current user. */
652 0 : uint gid = getgid();
653 0 : uint uid = getuid();
654 0 : if( FD_LIKELY( gid!=config->gid && -1==setegid( config->gid ) ) )
655 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
656 0 : if( FD_LIKELY( uid!=config->uid && -1==seteuid( config->uid ) ) )
657 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
658 :
659 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
660 0 : fd_topo_tile_t * tile = &config->topo.tiles[ i ];
661 :
662 0 : char path[ PATH_MAX ];
663 0 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "%s/%s_stack_%s%lu", config->hugetlbfs.huge_page_mount_path, config->name, tile->name, tile->kind_id ) );
664 :
665 0 : int result = unlink( path );
666 0 : if( -1==result && errno!=ENOENT ) FD_LOG_ERR(( "unlink() failed when trying to create stack `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
667 :
668 : /* TODO: Use a better CPU idx for the stack if tile is floating */
669 0 : ulong stack_cpu_idx = 0UL;
670 0 : if( FD_LIKELY( tile->cpu_idx<65535UL ) ) stack_cpu_idx = tile->cpu_idx;
671 :
672 0 : char name[ PATH_MAX ];
673 0 : FD_TEST( fd_cstr_printf_check( name, PATH_MAX, NULL, "%s_stack_%s%lu", config->name, tile->name, tile->kind_id ) );
674 :
675 0 : ulong sub_page_cnt[ 1 ] = { 6 };
676 0 : ulong sub_cpu_idx [ 1 ] = { stack_cpu_idx };
677 0 : int err = fd_shmem_create_multi( name, FD_SHMEM_HUGE_PAGE_SZ, 1, sub_page_cnt, sub_cpu_idx, S_IRUSR | S_IWUSR ); /* logs details */
678 0 : if( FD_UNLIKELY( err && errno==ENOMEM ) ) {
679 0 : warn_unknown_files( config, 0UL );
680 :
681 0 : char path[ PATH_MAX ];
682 0 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "%s/%s_stack_%s%lu", config->hugetlbfs.huge_page_mount_path, config->name, tile->name, tile->kind_id ) );
683 0 : FD_LOG_ERR(( "ENOMEM-Out of memory when trying to create huge page stack for tile `%s` at `%s`. "
684 0 : "Firedancer reserves enough memory for all of its stacks during the `hugetlbfs` configure "
685 0 : "step, so it is likely you have unknown files left over in this directory which are "
686 0 : "consuming memory, or another program on the system is using pages from the same mount.",
687 0 : tile->name, path ));
688 0 : } else if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fd_shmem_create_multi failed" ));
689 0 : }
690 :
691 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
692 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
693 0 : }
694 :
695 : extern configure_stage_t fd_cfg_stage_hugetlbfs;
696 : extern configure_stage_t fd_cfg_stage_ethtool_channels;
697 : extern configure_stage_t fd_cfg_stage_ethtool_gro;
698 : extern configure_stage_t fd_cfg_stage_ethtool_loopback;
699 : extern configure_stage_t fd_cfg_stage_sysctl;
700 : extern configure_stage_t fd_cfg_stage_hyperthreads;
701 :
702 : void
703 0 : fdctl_check_configure( config_t * config ) {
704 0 : configure_result_t check = fd_cfg_stage_hugetlbfs.check( config );
705 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
706 0 : FD_LOG_ERR(( "Huge pages are not configured correctly: %s. You can run `fdctl configure init hugetlbfs` "
707 0 : "to create the mounts correctly. This must be done after every system restart before running "
708 0 : "Firedancer.", check.message ));
709 :
710 0 : if( FD_LIKELY( !config->development.netns.enabled ) ) {
711 0 : check = fd_cfg_stage_ethtool_channels.check( config );
712 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
713 0 : FD_LOG_ERR(( "Network %s. You can run `fdctl configure init ethtool-channels` to set the number of channels on the "
714 0 : "network device correctly.", check.message ));
715 :
716 0 : check = fd_cfg_stage_ethtool_gro.check( config );
717 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
718 0 : FD_LOG_ERR(( "Network %s. You can run `fdctl configure init ethtool-gro` to disable generic-receive-offload "
719 0 : "as required.", check.message ));
720 :
721 0 : check = fd_cfg_stage_ethtool_loopback.check( config );
722 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
723 0 : FD_LOG_ERR(( "Network %s. You can run `fdctl configure init ethtool-loopback` to disable tx-udp-segmentation "
724 0 : "on the loopback device.", check.message ));
725 0 : }
726 :
727 0 : check = fd_cfg_stage_sysctl.check( config );
728 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
729 0 : FD_LOG_ERR(( "Kernel parameters are not configured correctly: %s. You can run `fdctl configure init sysctl` "
730 0 : "to set kernel parameters correctly.", check.message ));
731 :
732 0 : check = fd_cfg_stage_hyperthreads.check( config );
733 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
734 0 : FD_LOG_ERR(( "Hyperthreading is not configured correctly: %s. You can run `fdctl configure init hyperthreads` "
735 0 : "to configure hyperthreading correctly.", check.message ));
736 0 : }
737 :
738 : void
739 : run_firedancer_init( config_t * config,
740 0 : int init_workspaces ) {
741 0 : struct stat st;
742 0 : int err = stat( config->consensus.identity_path, &st );
743 0 : if( FD_UNLIKELY( -1==err && errno==ENOENT ) ) FD_LOG_ERR(( "[consensus.identity_path] key does not exist `%s`. You can generate an identity key at this path by running `fdctl keys new identity --config <toml>`", config->consensus.identity_path ));
744 0 : else if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "could not stat [consensus.identity_path] `%s` (%i-%s)", config->consensus.identity_path, errno, fd_io_strerror( errno ) ));
745 :
746 0 : for( ulong i=0UL; i<config->consensus.authorized_voter_paths_cnt; i++ ) {
747 0 : err = stat( config->consensus.authorized_voter_paths[ i ], &st );
748 0 : if( FD_UNLIKELY( -1==err && errno==ENOENT ) ) FD_LOG_ERR(( "[consensus.authorized_voter_paths] key does not exist `%s`", config->consensus.authorized_voter_paths[ i ] ));
749 0 : else if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "could not stat [consensus.authorized_voter_paths] `%s` (%i-%s)", config->consensus.authorized_voter_paths[ i ], errno, fd_io_strerror( errno ) ));
750 0 : }
751 :
752 0 : fdctl_check_configure( config );
753 0 : if( FD_LIKELY( init_workspaces ) ) initialize_workspaces( config );
754 0 : initialize_stacks( config );
755 0 : }
756 :
757 : void
758 0 : fdctl_setup_netns( config_t * config ) {
759 0 : if( config->development.netns.enabled ) {
760 0 : enter_network_namespace( config->tiles.net.interface );
761 0 : close_network_namespace_original_fd();
762 :
763 0 : fd_cfg_stage_ethtool_channels.init( config );
764 0 : fd_cfg_stage_ethtool_gro .init( config );
765 0 : fd_cfg_stage_ethtool_loopback.init( config );
766 0 : }
767 0 : }
768 :
769 : /* The boot sequence is a little bit involved...
770 :
771 : A process tree is created that looks like,
772 :
773 : + main
774 : +-- pidns
775 : +-- agave
776 : +-- tile 0
777 : +-- tile 1
778 : ...
779 :
780 : What we want is that if any process in the tree dies, all other
781 : processes will also die. This is done as follows,
782 :
783 : (a) pidns is the init process of a PID namespace, so if it dies the
784 : kernel will terminate the child processes.
785 :
786 : (b) main is the parent of pidns, so it can issue a waitpid() on the
787 : child PID, and when it completes terminate itself.
788 :
789 : (c) pidns is the parent of agave and the tiles, so it could
790 : issue a waitpid() of -1 to wait for any of them to terminate,
791 : but how would it know if main has died?
792 :
793 : (d) main creates a pipe, and passes the write end to pidns. If main
794 : dies, the pipe will be closed, and pidns will get a HUP on the
795 : read end. Then pidns creates a pipe per child and passes the
796 : write end to the child. If any of the children die, the pipe
797 : will be closed, and pidns will get a HUP on the read end.
798 :
799 : Then pidns can call poll() on both the write end of the main
800 : pipe and the read end of all the child pipes. If any of them
801 : raises SIGHUP, then pidns knows that the parent or a child has
802 : died, and it can terminate itself, which due to (a) and (b)
803 : will kill all other processes. */
804 : void
805 : run_firedancer( config_t * const config,
806 : int parent_pipefd,
807 0 : int init_workspaces ) {
808 : /* dump the topology we are using to the output log */
809 0 : fd_topo_print_log( 0, &config->topo );
810 :
811 0 : run_firedancer_init( config, init_workspaces );
812 :
813 0 : #if defined(__x86_64__)
814 :
815 0 : #ifndef SYS_landlock_create_ruleset
816 0 : #define SYS_landlock_create_ruleset 444
817 0 : #endif
818 :
819 0 : #ifndef LANDLOCK_CREATE_RULESET_VERSION
820 0 : #define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
821 0 : #endif
822 :
823 0 : #endif
824 0 : long abi = syscall( SYS_landlock_create_ruleset, NULL, 0, LANDLOCK_CREATE_RULESET_VERSION );
825 0 : if( -1L==abi && (errno==ENOSYS || errno==EOPNOTSUPP ) ) {
826 0 : FD_LOG_WARNING(( "The Landlock access control system is not supported by your Linux kernel. Firedancer uses landlock to "
827 0 : "provide an additional layer of security to the sandbox, but it is not required." ));
828 0 : }
829 :
830 0 : if( FD_UNLIKELY( close( 0 ) ) ) FD_LOG_ERR(( "close(0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
831 0 : if( FD_UNLIKELY( fd_log_private_logfile_fd()!=1 && close( 1 ) ) ) FD_LOG_ERR(( "close(1) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
832 :
833 0 : int pipefd;
834 0 : pid_namespace = clone_firedancer( config, parent_pipefd, &pipefd );
835 :
836 : /* Print the location of the logfile on SIGINT or SIGTERM, and also
837 : kill the child. They are connected by a pipe which the child is
838 : polling so we don't strictly need to kill the child, but its helpful
839 : to do that before printing the log location line, else it might
840 : get interleaved due to timing windows in the shutdown. */
841 0 : install_parent_signals();
842 :
843 0 : if( FD_UNLIKELY( close( config->log.lock_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
844 :
845 0 : struct sock_filter seccomp_filter[ 128UL ];
846 0 : populate_sock_filter_policy_main( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd(), (uint)pid_namespace );
847 :
848 0 : int allow_fds[ 4 ];
849 0 : ulong allow_fds_cnt = 0;
850 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
851 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
852 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile, or maybe stdout */
853 0 : allow_fds[ allow_fds_cnt++ ] = pipefd; /* read end of main pipe */
854 0 : if( FD_UNLIKELY( parent_pipefd!=-1 ) )
855 0 : allow_fds[ allow_fds_cnt++ ] = parent_pipefd; /* write end of parent pipe */
856 :
857 0 : if( FD_LIKELY( config->development.sandbox ) ) {
858 0 : fd_sandbox_enter( config->uid,
859 0 : config->gid,
860 0 : 0,
861 0 : 0,
862 0 : 1, /* Keep controlling terminal for main so it can receive Ctrl+C */
863 0 : 0,
864 0 : 0UL,
865 0 : 0UL,
866 0 : 0UL,
867 0 : allow_fds_cnt,
868 0 : allow_fds,
869 0 : sock_filter_policy_main_instr_cnt,
870 0 : seccomp_filter );
871 0 : } else {
872 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
873 0 : }
874 :
875 : /* The supervsior process should not share the log lock, because a
876 : child process might die while holding it and we still need to
877 : reap and print errors. */
878 0 : int lock = 0;
879 0 : fd_log_private_shared_lock = &lock;
880 :
881 : /* the only clean way to exit is SIGINT or SIGTERM on this parent process,
882 : so if wait4() completes, it must be an error */
883 0 : int wstatus;
884 0 : if( FD_UNLIKELY( -1==wait4( pid_namespace, &wstatus, (int)__WALL, NULL ) ) )
885 0 : FD_LOG_ERR(( "main wait4() failed (%i-%s)\nLog at \"%s\"", errno, fd_io_strerror( errno ), fd_log_private_path ));
886 :
887 0 : if( FD_UNLIKELY( WIFSIGNALED( wstatus ) ) ) exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
888 0 : else exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
889 0 : }
890 :
891 : void
892 : run_cmd_fn( args_t * args,
893 0 : config_t * const config ) {
894 0 : (void)args;
895 :
896 0 : if( FD_UNLIKELY( !config->gossip.entrypoints_cnt && !config->development.bootstrap ) )
897 0 : FD_LOG_ERR(( "No entrypoints specified in configuration file under [gossip.entrypoints], but "
898 0 : "at least one is needed to determine how to connect to the Solana cluster. If "
899 0 : "you want to start a new cluster in a development environment, use `fddev` instead "
900 0 : "of `fdctl`. If you want to use an existing genesis, set [development.bootstrap] "
901 0 : "to \"true\" in the configuration file." ));
902 :
903 0 : for( ulong i=0; i<config->gossip.entrypoints_cnt; i++ ) {
904 0 : if( FD_UNLIKELY( !strcmp( config->gossip.entrypoints[ i ], "" ) ) )
905 0 : FD_LOG_ERR(( "One of the entrypoints in your configuration file under [gossip.entrypoints] is "
906 0 : "empty. Please remove the empty entrypoint or set it correctly. "));
907 0 : }
908 :
909 0 : run_firedancer( config, -1, 1 );
910 0 : }
|