diff --git a/sys/net/ipfw3_basic/ip_fw3_state.c b/sys/net/ipfw3_basic/ip_fw3_state.c --- a/sys/net/ipfw3_basic/ip_fw3_state.c +++ b/sys/net/ipfw3_basic/ip_fw3_state.c @@ -419,119 +419,82 @@ } int -ip_fw3_ctl_state_get(struct sockopt *sopt) +ip_fw3_ctl_state_get_dispatch(netmsg_t nmsg) { - struct ipfw3_state_context *state_ctx; - struct ipfw3_state *s; + struct ipfw3_state_get_msg *m = (struct ipfw3_state_get_msg *)nmsg; + struct ipfw3_state_context *state_ctx = fw3_state_ctx[mycpuid]; + struct ipfw3_state *s, *tmp; + struct ipfw3_ioc_state *ioc = m->cur; + + /* + * DF-0633: this dispatch runs on the owning CPU's netisr, so it has + * exclusive access to the per-CPU state trees. Use RB_FOREACH_SAFE + * for additional robustness against concurrent cleanup_dispatch. + */ +#define EMIT(tree, proto) \ + RB_FOREACH_SAFE(s, fw3_state_tree, &state_ctx->tree, tmp) { \ + if (m->remaining <= 0) { \ + m->overflow = 1; \ + break; \ + } \ + ioc->src_addr.s_addr = ntohl(s->src_addr); \ + ioc->dst_addr.s_addr = ntohl(s->dst_addr); \ + ioc->src_port = ntohs(s->src_port); \ + ioc->dst_port = ntohs(s->dst_port); \ + ioc->cpu_id = mycpuid; \ + ioc->rule_id = s->stub ? s->stub->rulenum : 0; \ + ioc->proto = proto; \ + ioc->life = s->timestamp + sysctl_var_udp_timeout - time_uptime; \ + ioc++; \ + m->remaining--; \ + } + + EMIT(rb_icmp_in, IPPROTO_ICMP); + EMIT(rb_icmp_out, IPPROTO_ICMP); + EMIT(rb_tcp_in, IPPROTO_TCP); + EMIT(rb_tcp_out, IPPROTO_TCP); + EMIT(rb_udp_in, IPPROTO_UDP); + EMIT(rb_udp_out, IPPROTO_UDP); +#undef EMIT - size_t sopt_size, total_len = 0; - struct ipfw3_ioc_state *ioc; + m->cur = ioc; + netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); +} + +int +ip_fw3_ctl_state_get(struct sockopt *sopt) +{ + struct ipfw3_ioc_state *user_buf; + size_t sopt_size; int ioc_rule_id; ioc_rule_id = *((int *)(sopt->sopt_val)); sopt_size = sopt->sopt_valsize; - ioc = (struct ipfw3_ioc_state *)sopt->sopt_val; - /* icmp states only in CPU 0 */ - int cpu = 0; - - /* icmp states */ - for (cpu = 0; cpu < ncpus; cpu++) { - state_ctx = fw3_state_ctx[cpu]; - RB_FOREACH(s, fw3_state_tree, &state_ctx->rb_icmp_in) { - total_len += LEN_IOC_FW3_STATE; - if (total_len > sopt_size) - goto nospace; - ioc->src_addr.s_addr = ntohl(s->src_addr); - ioc->dst_addr.s_addr = ntohl(s->dst_addr); - ioc->src_port = ntohs(s->src_port); - ioc->dst_port = ntohs(s->dst_port); - ioc->cpu_id = cpu; - ioc->rule_id = s->stub->rulenum; - ioc->proto = IPPROTO_ICMP; - ioc->life = s->timestamp + - sysctl_var_udp_timeout - time_uptime; - ioc++; - } - RB_FOREACH(s, fw3_state_tree, &state_ctx->rb_icmp_out) { - total_len += LEN_IOC_FW3_STATE; - if (total_len > sopt_size) - goto nospace; - ioc->src_addr.s_addr = ntohl(s->src_addr); - ioc->dst_addr.s_addr = ntohl(s->dst_addr); - ioc->src_port = ntohs(s->src_port); - ioc->dst_port = ntohs(s->dst_port); - ioc->cpu_id = cpu; - ioc->rule_id = s->stub->rulenum; - ioc->proto = IPPROTO_ICMP; - ioc->life = s->timestamp + - sysctl_var_udp_timeout - time_uptime; - ioc++; - } - RB_FOREACH(s, fw3_state_tree, &state_ctx->rb_tcp_in) { - total_len += LEN_IOC_FW3_STATE; - if (total_len > sopt_size) - goto nospace; - ioc->src_addr.s_addr = ntohl(s->src_addr); - ioc->dst_addr.s_addr = ntohl(s->dst_addr); - ioc->src_port = ntohs(s->src_port); - ioc->dst_port = ntohs(s->dst_port); - ioc->cpu_id = cpu; - ioc->rule_id = s->stub->rulenum; - ioc->proto = IPPROTO_TCP; - ioc->life = s->timestamp + - sysctl_var_udp_timeout - time_uptime; - ioc++; - } - RB_FOREACH(s, fw3_state_tree, &state_ctx->rb_tcp_out) { - total_len += LEN_IOC_FW3_STATE; - if (total_len > sopt_size) - goto nospace; - ioc->src_addr.s_addr = ntohl(s->src_addr); - ioc->dst_addr.s_addr = ntohl(s->dst_addr); - ioc->src_port = ntohs(s->src_port); - ioc->dst_port = ntohs(s->dst_port); - ioc->cpu_id = cpu; - ioc->rule_id = s->stub->rulenum; - ioc->proto = IPPROTO_TCP; - ioc->life = s->timestamp + - sysctl_var_udp_timeout - time_uptime; - ioc++; - } - RB_FOREACH(s, fw3_state_tree, &state_ctx->rb_udp_in) { - total_len += LEN_IOC_FW3_STATE; - if (total_len > sopt_size) - goto nospace; - ioc->src_addr.s_addr = ntohl(s->src_addr); - ioc->dst_addr.s_addr = ntohl(s->dst_addr); - ioc->src_port = ntohs(s->src_port); - ioc->dst_port = ntohs(s->dst_port); - ioc->cpu_id = cpu; - ioc->rule_id = s->stub->rulenum; - ioc->proto = IPPROTO_UDP; - ioc->life = s->timestamp + - sysctl_var_udp_timeout - time_uptime; - ioc++; - } - RB_FOREACH(s, fw3_state_tree, &state_ctx->rb_udp_out) { - total_len += LEN_IOC_FW3_STATE; - if (total_len > sopt_size) - goto nospace; - ioc->src_addr.s_addr = ntohl(s->src_addr); - ioc->dst_addr.s_addr = ntohl(s->dst_addr); - ioc->src_port = ntohs(s->src_port); - ioc->dst_port = ntohs(s->dst_port); - ioc->cpu_id = cpu; - ioc->rule_id = s->stub->rulenum; - ioc->proto = IPPROTO_UDP; - ioc->life = s->timestamp + - sysctl_var_udp_timeout - time_uptime; - ioc++; - } - } + user_buf = (struct ipfw3_ioc_state *)sopt->sopt_val; - sopt->sopt_valsize = total_len; - return 0; -nospace: + /* + * DF-0633 fix: previously this function ran on netisr CPU 0 and walked + * every other CPU's RB tree with plain RB_FOREACH and no + * synchronization, racing concurrent RB_INSERT/RB_REMOVE on the + * owning CPUs. Dispatch the per-CPU traversal via netmsg so each + * tree is only touched from its owning CPU. + */ + size_t cap = sopt_size / sizeof(struct ipfw3_ioc_state); + struct ipfw3_state_get_msg { + struct netmsg_base base; + struct ipfw3_ioc_state *cur; + size_t remaining; + int overflow; + } msg; + netmsg_init(&msg.base, NULL, &curthread->td_msgport, 0, + ip_fw3_ctl_state_get_dispatch); + msg.cur = user_buf; + msg.remaining = cap; + msg.overflow = 0; + netisr_domsg(&msg.base, 0); + + size_t written = (cap - msg.remaining) * sizeof(struct ipfw3_ioc_state); + sopt->sopt_valsize = written; return 0; }