/* * Process an I/O event. */ static void multi_process_io_udp (struct multi_context *m) { const unsigned int status = m->top.c2.event_set_status; const unsigned int mpp_flags = m->top.c2.fast_io ? (MPP_CONDITIONAL_PRE_SELECT | MPP_CLOSE_ON_SIGNAL) : (MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL); #ifdef MULTI_DEBUG_EVENT_LOOP char buf[16]; buf[0] = 0; if (status & SOCKET_READ) strcat (buf, "SR/"); else if (status & SOCKET_WRITE) strcat (buf, "SW/"); else if (status & TUN_READ) strcat (buf, "TR/"); else if (status & TUN_WRITE) strcat (buf, "TW/"); printf ("IO %s\n", buf); #endif #ifdef ENABLE_MANAGEMENT if (status & (MANAGEMENT_READ|MANAGEMENT_WRITE)) { ASSERT (management); management_io (management); } #endif /* UDP port ready to accept write */ if (status & SOCKET_WRITE) { multi_process_outgoing_link (m, mpp_flags); } /* TUN device ready to accept write */ else if (status & TUN_WRITE) { multi_process_outgoing_tun (m, mpp_flags); } /* Incoming data on UDP port */ else if (status & SOCKET_READ) { read_incoming_link (&m->top); multi_release_io_lock (m); if (!IS_SIG (&m->top)) multi_process_incoming_link (m, NULL, mpp_flags); } /* Incoming data on TUN device */ else if (status & TUN_READ) { read_incoming_tun (&m->top); multi_release_io_lock (m); if (!IS_SIG (&m->top)) multi_process_incoming_tun (m, mpp_flags); } }
void process_io (struct context *c) { const unsigned int status = c->c2.event_set_status; #ifdef ENABLE_MANAGEMENT if (status & (MANAGEMENT_READ|MANAGEMENT_WRITE)) { ASSERT (management); management_io (management); } #endif /* TCP/UDP port ready to accept write */ if (status & SOCKET_WRITE) { process_outgoing_link (c); } /* TUN device ready to accept write */ else if (status & TUN_WRITE) { process_outgoing_tun (c); } /* Incoming data on TCP/UDP port */ else if (status & SOCKET_READ) { read_incoming_link (c); if (!IS_SIG (c)) process_incoming_link (c); } /* Incoming data on TUN device */ else if (status & TUN_READ) { read_incoming_tun (c); if (!IS_SIG (c)) process_incoming_tun (c); } }
/** * Main event loop for OpenVPN in client mode, where only one VPN tunnel * is active. * @ingroup eventloop * * @param c - The context structure of the single active VPN tunnel. */ static void tunnel_point_to_point(struct context *c) { context_clear_2(c); /* set point-to-point mode */ c->mode = CM_P2P; /* initialize tunnel instance */ init_instance_handle_signals(c, c->es, CC_HARD_USR1_TO_HUP); if (IS_SIG(c)) { return; } /* main event loop */ while (true) { perf_push(PERF_EVENT_LOOP); /* process timers, TLS, etc. */ pre_select(c); P2P_CHECK_SIG(); /* set up and do the I/O wait */ io_wait(c, p2p_iow_flags(c)); P2P_CHECK_SIG(); /* timeout? */ if (c->c2.event_set_status == ES_TIMEOUT) { perf_pop(); continue; } /* process the I/O which triggered select */ process_io(c); P2P_CHECK_SIG(); perf_pop(); } uninit_management_callback(); /* tear down tunnel instance (unless --persist-tun) */ close_instance(c); }
/** * OpenVPN's main init-run-cleanup loop. * @ingroup eventloop * * This function contains the two outer OpenVPN loops. Its structure is * as follows: * - Once-per-process initialization. * - Outer loop, run at startup and then once per \c SIGHUP: * - Level 1 initialization * - Inner loop, run at startup and then once per \c SIGUSR1: * - Call event loop function depending on client or server mode: * - \c tunnel_point_to_point() * - \c tunnel_server() * - Level 1 cleanup * - Once-per-process cleanup. * * @param argc - Commandline argument count. * @param argv - Commandline argument values. */ int main (int argc, char *argv[]) { struct context c; #if PEDANTIC fprintf (stderr, "Sorry, I was built with --enable-pedantic and I am incapable of doing any real work!\n"); return 1; #endif CLEAR (c); /* signify first time for components which can only be initialized once per program instantiation. */ c.first_time = true; /* initialize program-wide statics */ if (init_static ()) { /* * This loop is initially executed on startup and then * once per SIGHUP. */ do { /* enter pre-initialization mode with regard to signal handling */ pre_init_signal_catch (); /* zero context struct but leave first_time member alone */ context_clear_all_except_first_time (&c); /* static signal info object */ CLEAR (siginfo_static); c.sig = &siginfo_static; /* initialize garbage collector scoped to context object */ gc_init (&c.gc); /* initialize environmental variable store */ c.es = env_set_create (NULL); #ifdef WIN32 env_set_add_win32 (c.es); #endif #ifdef ENABLE_MANAGEMENT /* initialize management subsystem */ init_management (&c); #endif /* initialize options to default state */ init_options (&c.options, true); /* parse command line options, and read configuration file */ parse_argv (&c.options, argc, argv, M_USAGE, OPT_P_DEFAULT, NULL, c.es); #ifdef ENABLE_PLUGIN /* plugins may contribute options configuration */ init_verb_mute (&c, IVM_LEVEL_1); init_plugins (&c); open_plugins (&c, true, OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE); #endif /* init verbosity and mute levels */ init_verb_mute (&c, IVM_LEVEL_1); /* set dev options */ init_options_dev (&c.options); /* openssl print info? */ if (print_openssl_info (&c.options)) break; /* --genkey mode? */ if (do_genkey (&c.options)) break; /* tun/tap persist command? */ if (do_persist_tuntap (&c.options)) break; /* sanity check on options */ options_postprocess (&c.options); /* show all option settings */ show_settings (&c.options); /* print version number */ msg (M_INFO, "%s", title_string); /* misc stuff */ pre_setup (&c.options); /* test crypto? */ if (do_test_crypto (&c.options)) break; #ifdef ENABLE_MANAGEMENT /* open management subsystem */ if (!open_management (&c)) break; #endif /* set certain options as environmental variables */ setenv_settings (c.es, &c.options); /* finish context init */ context_init_1 (&c); do { /* run tunnel depending on mode */ switch (c.options.mode) { case MODE_POINT_TO_POINT: tunnel_point_to_point (&c); break; #if P2MP_SERVER case MODE_SERVER: tunnel_server (&c); break; #endif default: ASSERT (0); } /* indicates first iteration -- has program-wide scope */ c.first_time = false; /* any signals received? */ if (IS_SIG (&c)) print_signal (c.sig, NULL, M_INFO); /* pass restart status to management subsystem */ signal_restart_status (c.sig); } while (c.sig->signal_received == SIGUSR1); uninit_options (&c.options); gc_reset (&c.gc); } while (c.sig->signal_received == SIGHUP); } context_gc_free (&c); env_set_destroy (c.es); #ifdef ENABLE_MANAGEMENT /* close management interface */ close_management (); #endif /* uninitialize program-wide statics */ uninit_static (); openvpn_exit (OPENVPN_EXIT_STATUS_GOOD); /* exit point */ return 0; /* NOTREACHED */ }
/** * Main event loop for OpenVPN in UDP server mode. * @ingroup eventloop * * This function implements OpenVPN's main event loop for UDP server mode. * At this time, OpenVPN does not yet support multithreading. This * function's name is therefore slightly misleading. * * @param top - Top-level context structure. */ static void tunnel_server_udp_single_threaded (struct context *top) { struct multi_context multi; top->mode = CM_TOP; context_clear_2 (top); /* initialize top-tunnel instance */ init_instance_handle_signals (top, top->es, CC_HARD_USR1_TO_HUP); if (IS_SIG (top)) return; /* initialize global multi_context object */ multi_init (&multi, top, false, MC_SINGLE_THREADED); /* initialize our cloned top object */ multi_top_init (&multi, top, true); /* initialize management interface */ init_management_callback_multi (&multi); /* finished with initialization */ initialization_sequence_completed (top, ISC_SERVER); /* --mode server --proto udp */ /* per-packet event loop */ while (true) { perf_push (PERF_EVENT_LOOP); /* set up and do the io_wait() */ multi_get_timeout (&multi, &multi.top.c2.timeval); io_wait (&multi.top, p2mp_iow_flags (&multi)); MULTI_CHECK_SIG (&multi); /* check on status of coarse timers */ multi_process_per_second_timers (&multi); /* timeout? */ if (multi.top.c2.event_set_status == ES_TIMEOUT) { multi_process_timeout (&multi, MPP_PRE_SELECT|MPP_CLOSE_ON_SIGNAL); } else { /* process I/O */ multi_process_io_udp (&multi); MULTI_CHECK_SIG (&multi); } perf_pop (); } /* shut down management interface */ uninit_management_callback_multi (&multi); /* save ifconfig-pool */ multi_ifconfig_pool_persist (&multi, true); /* tear down tunnel instance (unless --persist-tun) */ multi_uninit (&multi); multi_top_free (&multi); close_instance (top); }
/**************** * Create notations and other stuff. It is assumed that the stings in * STRLIST are already checked to contain only printable data and have * a valid NAME=VALUE format. */ static void mk_notation_policy_etc (PKT_signature *sig, PKT_public_key *pk, PKT_public_key *pksk) { const char *string; char *p = NULL; strlist_t pu = NULL; struct notation *nd = NULL; struct expando_args args; log_assert (sig->version >= 4); memset (&args, 0, sizeof(args)); args.pk = pk; args.pksk = pksk; /* Notation data. */ if (IS_SIG(sig) && opt.sig_notations) nd = opt.sig_notations; else if (IS_CERT(sig) && opt.cert_notations) nd = opt.cert_notations; if (nd) { struct notation *item; for (item = nd; item; item = item->next) { item->altvalue = pct_expando (item->value,&args); if (!item->altvalue) log_error (_("WARNING: unable to %%-expand notation " "(too large). Using unexpanded.\n")); } keygen_add_notations (sig, nd); for (item = nd; item; item = item->next) { xfree (item->altvalue); item->altvalue = NULL; } } /* Set policy URL. */ if (IS_SIG(sig) && opt.sig_policy_url) pu = opt.sig_policy_url; else if (IS_CERT(sig) && opt.cert_policy_url) pu = opt.cert_policy_url; for (; pu; pu = pu->next) { string = pu->d; p = pct_expando (string, &args); if (!p) { log_error(_("WARNING: unable to %%-expand policy URL " "(too large). Using unexpanded.\n")); p = xstrdup(string); } build_sig_subpkt (sig, (SIGSUBPKT_POLICY | ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)), p, strlen (p)); xfree (p); } /* Preferred keyserver URL. */ if (IS_SIG(sig) && opt.sig_keyserver_url) pu = opt.sig_keyserver_url; for (; pu; pu = pu->next) { string = pu->d; p = pct_expando (string, &args); if (!p) { log_error (_("WARNING: unable to %%-expand preferred keyserver URL" " (too large). Using unexpanded.\n")); p = xstrdup (string); } build_sig_subpkt (sig, (SIGSUBPKT_PREF_KS | ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)), p, strlen (p)); xfree (p); } /* Set signer's user id. */ if (IS_SIG (sig) && !opt.flags.disable_signer_uid) { char *mbox; /* For now we use the uid which was used to locate the key. */ if (pksk->user_id && (mbox = mailbox_from_userid (pksk->user_id->name))) { if (DBG_LOOKUP) log_debug ("setting Signer's UID to '%s'\n", mbox); build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, mbox, strlen (mbox)); xfree (mbox); } else if (opt.sender_list) { /* If a list of --sender was given we scan that list and use * the first one matching a user id of the current key. */ /* FIXME: We need to get the list of user ids for the PKSK * packet. That requires either a function to look it up * again or we need to extend the key packet struct to link * to the primary key which in turn could link to the user * ids. Too much of a change right now. Let's take just * one from the supplied list and hope that the caller * passed a matching one. */ build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, opt.sender_list->d, strlen (opt.sender_list->d)); } } }
/**************** * Create notations and other stuff. It is assumed that the stings in * STRLIST are already checked to contain only printable data and have * a valid NAME=VALUE format. */ static void mk_notation_policy_etc( PKT_signature *sig, PKT_public_key *pk, PKT_secret_key *sk ) { const char *string; char *s=NULL; STRLIST pu=NULL; struct notation *nd=NULL; struct expando_args args; assert(sig->version>=4); memset(&args,0,sizeof(args)); args.pk=pk; args.sk=sk; /* notation data */ if(IS_SIG(sig) && opt.sig_notations) nd=opt.sig_notations; else if( IS_CERT(sig) && opt.cert_notations ) nd=opt.cert_notations; if(nd) { struct notation *i; for(i=nd;i;i=i->next) { i->altvalue=pct_expando(i->value,&args); if(!i->altvalue) log_error(_("WARNING: unable to %%-expand notation " "(too large). Using unexpanded.\n")); } keygen_add_notations(sig,nd); for(i=nd;i;i=i->next) { xfree(i->altvalue); i->altvalue=NULL; } } /* set policy URL */ if( IS_SIG(sig) && opt.sig_policy_url ) pu=opt.sig_policy_url; else if( IS_CERT(sig) && opt.cert_policy_url ) pu=opt.cert_policy_url; for(;pu;pu=pu->next) { string = pu->d; s=pct_expando(string,&args); if(!s) { log_error(_("WARNING: unable to %%-expand policy URL " "(too large). Using unexpanded.\n")); s=xstrdup(string); } build_sig_subpkt(sig,SIGSUBPKT_POLICY| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0), s,strlen(s)); xfree(s); } /* preferred keyserver URL */ if( IS_SIG(sig) && opt.sig_keyserver_url ) pu=opt.sig_keyserver_url; for(;pu;pu=pu->next) { string = pu->d; s=pct_expando(string,&args); if(!s) { log_error(_("WARNING: unable to %%-expand preferred keyserver URL" " (too large). Using unexpanded.\n")); s=xstrdup(string); } build_sig_subpkt(sig,SIGSUBPKT_PREF_KS| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0), s,strlen(s)); xfree(s); } }
/**************** * Create a notation. It is assumed that the stings in STRLIST * are already checked to contain only printable data and have a valid * NAME=VALUE format. */ static void mk_notation_and_policy( PKT_signature *sig, PKT_public_key *pk, PKT_secret_key *sk ) { const char *string; char *s=NULL; byte *buf; unsigned n1, n2; STRLIST nd=NULL,pu=NULL; struct expando_args args; memset(&args,0,sizeof(args)); args.pk=pk; args.sk=sk; /* notation data */ if(IS_SIG(sig) && opt.sig_notation_data) { if(sig->version<4) log_info("can't put notation data into v3 signatures\n"); else nd=opt.sig_notation_data; } else if( IS_CERT(sig) && opt.cert_notation_data ) { if(sig->version<4) log_info("can't put notation data into v3 key signatures\n"); else nd=opt.cert_notation_data; } for( ; nd; nd = nd->next ) { char *expanded; string = nd->d; s = strchr( string, '=' ); if( !s ) BUG(); /* we have already parsed this */ n1 = s - string; s++; expanded=pct_expando(s,&args); if(!expanded) { log_error(_("WARNING: unable to %%-expand notation " "(too large). Using unexpanded.\n")); expanded=m_strdup(s); } n2 = strlen(expanded); buf = m_alloc( 8 + n1 + n2 ); buf[0] = 0x80; /* human readable */ buf[1] = buf[2] = buf[3] = 0; buf[4] = n1 >> 8; buf[5] = n1; buf[6] = n2 >> 8; buf[7] = n2; memcpy(buf+8, string, n1 ); memcpy(buf+8+n1, expanded, n2 ); build_sig_subpkt( sig, SIGSUBPKT_NOTATION | ((nd->flags & 1)? SIGSUBPKT_FLAG_CRITICAL:0), buf, 8+n1+n2 ); m_free(expanded); m_free(buf); } if(opt.show_notation) show_notation(sig,0); /* set policy URL */ if( IS_SIG(sig) && opt.sig_policy_url ) { if(sig->version<4) log_info("can't put a policy URL into v3 signatures\n"); else pu=opt.sig_policy_url; } else if( IS_CERT(sig) && opt.cert_policy_url ) { if(sig->version<4) log_info("can't put a policy URL into v3 key signatures\n"); else pu=opt.cert_policy_url; } for(;pu;pu=pu->next) { string = pu->d; s=pct_expando(string,&args); if(!s) { log_error(_("WARNING: unable to %%-expand policy url " "(too large). Using unexpanded.\n")); s=m_strdup(string); } build_sig_subpkt(sig,SIGSUBPKT_POLICY| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0), s,strlen(s)); m_free(s); } if(opt.show_policy_url) show_policy_url(sig,0); }
/* * Top level event loop for single-threaded operation. * TCP mode. */ void tunnel_server_tcp(struct context *top) { struct multi_context multi; int status; top->mode = CM_TOP; context_clear_2(top); /* initialize top-tunnel instance */ init_instance_handle_signals(top, top->es, CC_HARD_USR1_TO_HUP); if (IS_SIG(top)) { return; } /* initialize global multi_context object */ multi_init(&multi, top, true, MC_SINGLE_THREADED); /* initialize our cloned top object */ multi_top_init(&multi, top); /* initialize management interface */ init_management_callback_multi(&multi); /* finished with initialization */ initialization_sequence_completed(top, ISC_SERVER); /* --mode server --proto tcp-server */ #ifdef ENABLE_ASYNC_PUSH multi.top.c2.inotify_fd = inotify_init(); if (multi.top.c2.inotify_fd < 0) { msg(D_MULTI_ERRORS | M_ERRNO, "MULTI: inotify_init error"); } #endif /* per-packet event loop */ while (true) { perf_push(PERF_EVENT_LOOP); /* wait on tun/socket list */ multi_get_timeout(&multi, &multi.top.c2.timeval); status = multi_tcp_wait(&multi.top, multi.mtcp); MULTI_CHECK_SIG(&multi); /* check on status of coarse timers */ multi_process_per_second_timers(&multi); /* timeout? */ if (status > 0) { /* process the I/O which triggered select */ multi_tcp_process_io(&multi); MULTI_CHECK_SIG(&multi); } else if (status == 0) { multi_tcp_action(&multi, NULL, TA_TIMEOUT, false); } perf_pop(); } #ifdef ENABLE_ASYNC_PUSH close(top->c2.inotify_fd); #endif /* shut down management interface */ uninit_management_callback_multi(&multi); /* save ifconfig-pool */ multi_ifconfig_pool_persist(&multi, true); /* tear down tunnel instance (unless --persist-tun) */ multi_uninit(&multi); multi_top_free(&multi); close_instance(top); }
static void multi_tcp_process_io(struct multi_context *m) { struct multi_tcp *mtcp = m->mtcp; int i; for (i = 0; i < mtcp->n_esr; ++i) { struct event_set_return *e = &mtcp->esr[i]; /* incoming data for instance? */ if (e->arg >= MTCP_N) { struct multi_instance *mi = (struct multi_instance *) e->arg; if (mi) { if (e->rwflags & EVENT_WRITE) { multi_tcp_action(m, mi, TA_SOCKET_WRITE_READY, false); } else if (e->rwflags & EVENT_READ) { multi_tcp_action(m, mi, TA_SOCKET_READ, false); } } } else { #ifdef ENABLE_MANAGEMENT if (e->arg == MTCP_MANAGEMENT) { ASSERT(management); management_io(management); } else #endif /* incoming data on TUN? */ if (e->arg == MTCP_TUN) { if (e->rwflags & EVENT_WRITE) { multi_tcp_action(m, NULL, TA_TUN_WRITE, false); } else if (e->rwflags & EVENT_READ) { multi_tcp_action(m, NULL, TA_TUN_READ, false); } } /* new incoming TCP client attempting to connect? */ else if (e->arg == MTCP_SOCKET) { struct multi_instance *mi; ASSERT(m->top.c2.link_socket); socket_reset_listen_persistent(m->top.c2.link_socket); mi = multi_create_instance_tcp(m); if (mi) { multi_tcp_action(m, mi, TA_INITIAL, false); } } /* signal received? */ else if (e->arg == MTCP_SIG) { get_signal(&m->top.sig->signal_received); } #ifdef ENABLE_ASYNC_PUSH else if (e->arg == MTCP_FILE_CLOSE_WRITE) { multi_process_file_closed(m, MPP_PRE_SELECT | MPP_RECORD_TOUCH); } #endif } if (IS_SIG(&m->top)) { break; } } mtcp->n_esr = 0; /* * Process queued mbuf packets destined for TCP socket */ { struct multi_instance *mi; while (!IS_SIG(&m->top) && (mi = mbuf_peek(m->mbuf)) != NULL) { multi_tcp_action(m, mi, TA_SOCKET_WRITE, true); } } }
static void multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll) { bool tun_input_pending = false; do { dmsg(D_MULTI_DEBUG, "MULTI TCP: multi_tcp_action a=%s p=%d", pract(action), poll); /* * If TA_SOCKET_READ_RESIDUAL, it means we still have pending * input packets which were read by a prior TCP recv. * * Otherwise do a "lite" wait, which means we wait with 0 timeout * on I/O events only related to the current instance, not * the big list of events. * * On our first pass, poll will be false because we already know * that input is available, and to call io_wait would be redundant. */ if (poll && action != TA_SOCKET_READ_RESIDUAL) { const int orig_action = action; action = multi_tcp_wait_lite(m, mi, action, &tun_input_pending); if (action == TA_UNDEF) { msg(M_FATAL, "MULTI TCP: I/O wait required blocking in multi_tcp_action, action=%d", orig_action); } } /* * Dispatch the action */ { struct multi_instance *touched = multi_tcp_dispatch(m, mi, action); /* * Signal received or TCP connection * reset by peer? */ if (touched && IS_SIG(&touched->context)) { if (mi == touched) { mi = NULL; } multi_close_instance_on_signal(m, touched); } } /* * If dispatch produced any pending output * for a particular instance, point to * that instance. */ if (m->pending) { mi = m->pending; } /* * Based on the effects of the action, * such as generating pending output, * possibly transition to a new action state. */ action = multi_tcp_post(m, mi, action); /* * If we are finished processing the original action, * check if we have any TUN input. If so, transition * our action state to processing this input. */ if (tun_input_pending && action == TA_UNDEF) { action = TA_TUN_READ; mi = NULL; tun_input_pending = false; poll = false; } else { poll = true; } } while (action != TA_UNDEF); }
static struct multi_instance * multi_tcp_dispatch(struct multi_context *m, struct multi_instance *mi, const int action) { const unsigned int mpp_flags = MPP_PRE_SELECT|MPP_RECORD_TOUCH; struct multi_instance *touched = mi; m->mpp_touched = &touched; dmsg(D_MULTI_DEBUG, "MULTI TCP: multi_tcp_dispatch a=%s mi=" ptr_format, pract(action), (ptr_type)mi); switch (action) { case TA_TUN_READ: read_incoming_tun(&m->top); if (!IS_SIG(&m->top)) { multi_process_incoming_tun(m, mpp_flags); } break; case TA_SOCKET_READ: case TA_SOCKET_READ_RESIDUAL: ASSERT(mi); ASSERT(mi->context.c2.link_socket); set_prefix(mi); read_incoming_link(&mi->context); clear_prefix(); if (!IS_SIG(&mi->context)) { multi_process_incoming_link(m, mi, mpp_flags); if (!IS_SIG(&mi->context)) { stream_buf_read_setup(mi->context.c2.link_socket); } } break; case TA_TIMEOUT: multi_process_timeout(m, mpp_flags); break; case TA_TUN_WRITE: multi_process_outgoing_tun(m, mpp_flags); break; case TA_TUN_WRITE_TIMEOUT: multi_process_drop_outgoing_tun(m, mpp_flags); break; case TA_SOCKET_WRITE_READY: ASSERT(mi); multi_tcp_process_outgoing_link_ready(m, mi, mpp_flags); break; case TA_SOCKET_WRITE: multi_tcp_process_outgoing_link(m, false, mpp_flags); break; case TA_SOCKET_WRITE_DEFERRED: multi_tcp_process_outgoing_link(m, true, mpp_flags); break; case TA_INITIAL: ASSERT(mi); multi_tcp_set_global_rw_flags(m, mi); multi_process_post(m, mi, mpp_flags); break; default: msg(M_FATAL, "MULTI TCP: multi_tcp_dispatch, unhandled action=%d", action); } m->mpp_touched = NULL; return touched; }