/***** * * Copyright (C) 2003-2005 Nicolas Delon * All Rights Reserved * * This file is part of the Prelude program. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * *****/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "process_packet.h" #include "pflogger.h" struct packet_info { const char *ifname; unsigned int rnr; unsigned int reason; unsigned int action; unsigned int dir; char *source_address; char *target_address; uint8_t ttl; enum { ip_version_4 = AF_INET, ip_version_6 = AF_INET6 } ip_version; int ip_proto_type; union { struct { uint16_t sport; uint16_t dport; uint8_t flags; } tcp; struct { uint16_t sport; uint16_t dport; } udp; struct { uint8_t type; uint8_t code; } icmp; } ip_proto; }; static pcap_t *pcap; static int in_addr_to_string(int af, prelude_string_t *string, const void *src, char *dst, size_t size) { size_t len; if ( ! inet_ntop(af, src, dst, size) ) return prelude_error_from_errno(errno); len = strlen(dst); prelude_string_set_ref_fast(string, dst, len); return 0; } static int process_hdr_icmp(idmef_service_t *source_service, idmef_service_t *target_service, const struct icmp *icmp_hdr, struct packet_info *packet_info) { packet_info->ip_proto.icmp.type = icmp_hdr->icmp_type; packet_info->ip_proto.icmp.code = icmp_hdr->icmp_code; return 0; } static int process_hdr_icmp6(idmef_service_t *source_service, idmef_service_t *target_service, const struct icmp6_hdr *icmp6_hdr, struct packet_info *packet_info) { packet_info->ip_proto.icmp.type = icmp6_hdr->icmp6_type; packet_info->ip_proto.icmp.code = icmp6_hdr->icmp6_code; return 0; } static int process_hdr_tcp(idmef_service_t *source_service, idmef_service_t *target_service, const struct tcphdr *tcp_hdr, struct packet_info *packet_info) { uint16_t sport, dport; sport = ntohs(tcp_hdr->th_sport); dport = ntohs(tcp_hdr->th_dport); idmef_service_set_port(source_service, sport); idmef_service_set_port(target_service, dport); packet_info->ip_proto.tcp.flags = tcp_hdr->th_flags; packet_info->ip_proto.tcp.sport = sport; packet_info->ip_proto.tcp.dport = dport; return 0; } static int process_hdr_udp(idmef_service_t *source_service, idmef_service_t *target_service, const struct udphdr *udp_hdr, struct packet_info *packet_info) { uint16_t sport, dport; sport = ntohs(udp_hdr->uh_sport); dport = ntohs(udp_hdr->uh_dport); idmef_service_set_port(source_service, sport); idmef_service_set_port(target_service, dport); packet_info->ip_proto.udp.sport = sport; packet_info->ip_proto.udp.dport = dport; return 0; } static int build_address(idmef_address_t *address, struct packet_info *packet_info, const void *ip_addr, char *daddr, size_t daddr_len) { prelude_string_t *string; int ret; idmef_address_set_category(address, ((packet_info->ip_version == ip_version_4) ? IDMEF_ADDRESS_CATEGORY_IPV4_ADDR : IDMEF_ADDRESS_CATEGORY_IPV6_ADDR)); ret = idmef_address_new_address(address, &string); if ( ret < 0 ) return ret; return in_addr_to_string(packet_info->ip_version, string, ip_addr, daddr, daddr_len); } static int build_source(idmef_alert_t *alert, const void *ip_addr, struct packet_info *packet_info) { idmef_source_t *source; idmef_node_t *source_node; idmef_address_t *source_node_address; prelude_string_t *string; static char saddr[64]; int ret; ret = idmef_alert_new_source(alert, &source, 0); if ( ret < 0 ) return ret; ret = idmef_source_new_node(source, &source_node); if ( ret < 0 ) return ret; ret = idmef_node_new_address(source_node, &source_node_address, 0); if ( ret < 0 ) return ret; ret = build_address(source_node_address, packet_info, ip_addr, saddr, sizeof(saddr)); if ( ret < 0 ) return ret; if ( packet_info->dir == PF_IN ) { ret = idmef_source_new_interface(source, &string); if ( ret < 0 ) return ret; prelude_string_set_ref(string, packet_info->ifname); } packet_info->source_address = saddr; return 0; } static int build_target(idmef_alert_t *alert, const void *ip_addr, struct packet_info *packet_info) { idmef_target_t *target; idmef_node_t *target_node; idmef_address_t *target_node_address; prelude_string_t *string; static char daddr[64]; int ret; ret = idmef_alert_new_target(alert, &target, 0); if ( ret < 0 ) return ret; ret = idmef_target_new_node(target, &target_node); if ( ret < 0 ) return ret; ret = idmef_node_new_address(target_node, &target_node_address, 0); if ( ret < 0 ) return ret; ret = build_address(target_node_address, packet_info, ip_addr, daddr, sizeof(daddr)); if ( ret < 0 ) return ret; if ( packet_info->dir == PF_OUT ) { ret = idmef_target_new_interface(target, &string); if ( ret < 0 ) return ret; prelude_string_set_ref(string, packet_info->ifname); } packet_info->target_address = daddr; return 0; } static int build_service_base(idmef_service_t *service, struct packet_info *packet_info) { struct protoent *proto; prelude_string_t *string; int ret; if ( packet_info->ip_version == ip_version_4 ) idmef_service_set_ip_version(service, 4); else idmef_service_set_ip_version(service, 6); idmef_service_set_iana_protocol_number(service, packet_info->ip_proto_type); proto = getprotobynumber(packet_info->ip_proto_type); if ( ! proto ) return prelude_error_from_errno(errno); ret = prelude_string_new_dup(&string, proto->p_name); if ( ret < 0 ) return ret; idmef_service_set_iana_protocol_name(service, string); return ret; } static int build_service(idmef_source_t *source, idmef_target_t *target, const void *protocol_header, struct packet_info *packet_info) { idmef_service_t *source_service; idmef_service_t *target_service; int ret; ret = idmef_source_new_service(source, &source_service); if ( ret < 0 ) return ret; ret = build_service_base(source_service, packet_info); if ( ret < 0 ) return ret; ret = idmef_target_new_service(target, &target_service); if ( ret < 0 ) return ret; ret = build_service_base(target_service, packet_info); if ( ret < 0 ) return ret; switch ( packet_info->ip_proto_type ) { case IPPROTO_ICMP: return process_hdr_icmp(source_service, target_service, (const struct icmp *) protocol_header, packet_info); case IPPROTO_ICMPV6: return process_hdr_icmp6(source_service, target_service, (const struct icmp6_hdr *) protocol_header, packet_info); case IPPROTO_TCP: return process_hdr_tcp(source_service, target_service, (const struct tcphdr *) protocol_header, packet_info); case IPPROTO_UDP: return process_hdr_udp(source_service, target_service, (const struct udphdr *) protocol_header, packet_info); default: /* nop */; } return 0; } static int process_hdr_ipv4(idmef_alert_t *alert, const struct ip *ip_hdr, struct packet_info *packet_info) { int ret; packet_info->ip_version = ip_version_4; packet_info->ttl = ip_hdr->ip_ttl; packet_info->ip_proto_type = ip_hdr->ip_p; ret = build_source(alert, &ip_hdr->ip_src, packet_info); if ( ret < 0 ) return ret; ret = build_target(alert, &ip_hdr->ip_dst, packet_info); if ( ret < 0 ) return ret; return build_service(idmef_alert_get_next_source(alert, NULL), idmef_alert_get_next_target(alert, NULL), ((const void *) ip_hdr) + ip_hdr->ip_hl * 4, packet_info); } static int process_hdr_ipv6(idmef_alert_t *alert, const struct ip6_hdr *ip6_hdr, struct packet_info *packet_info) { int ret; packet_info->ip_version = ip_version_6; packet_info->ttl = ip6_hdr->ip6_hops; packet_info->ip_proto_type = ip6_hdr->ip6_nxt; ret = build_source(alert, &ip6_hdr->ip6_src, packet_info); if ( ret < 0 ) return ret; ret = build_target(alert, &ip6_hdr->ip6_dst, packet_info); if ( ret < 0 ) return ret; return build_service(idmef_alert_get_next_source(alert, NULL), idmef_alert_get_next_target(alert, NULL), ip6_hdr + 1, packet_info); } static int process_hdr_unknown_af(idmef_alert_t *alert, unsigned int af) { idmef_classification_t *classification; prelude_string_t *text; int ret; ret = idmef_alert_new_classification(alert, &classification); if ( ret < 0 ) return ret; ret = idmef_classification_new_text(classification, &text); if ( ret < 0 ) return ret; return prelude_string_sprintf(text, "unknown protocol %u", af); } static int build_impact_description_icmp(struct packet_info *packet_info, char *action_str, char *dir_str, char *icmp, char *buffer, size_t size) { return snprintf(buffer, size, "OpenBSD PF %s an %s %s packet %s -> %s type:%hhu code:%hhu on interface %s (TTL:%hhu)", action_str, dir_str, icmp, packet_info->source_address, packet_info->target_address, packet_info->ip_proto.icmp.type, packet_info->ip_proto.icmp.code, packet_info->ifname, packet_info->ttl); } static int build_impact_description_tcp(struct packet_info *packet_info, char *action_str, char *dir_str, char *buffer, size_t size) { static const char *tcp_flags_table[] = { "FIN", "SYN", "RST", "PUSH", "ACK", "URG", "ECE", "CWR" }; char tcp_flags[64]; int cnt; int len; int ret; len = 0; for ( cnt = 0; cnt < sizeof (tcp_flags_table) / sizeof (tcp_flags_table[0]); cnt++ ) { if ( packet_info->ip_proto.tcp.flags & (1 << cnt) ) { ret = snprintf(tcp_flags + len, sizeof(tcp_flags) - len, "%s%s", len ? "|" : "", tcp_flags_table[cnt]); if ( ret < 0 ) return ret; len += ret; } } return snprintf(buffer, size, "OpenBSD PF %s an %s TCP packet %s:%hu -> %s:%hu [%s] on interface %s (TTL:%hhu)", action_str, dir_str, packet_info->source_address, packet_info->ip_proto.tcp.sport, packet_info->target_address, packet_info->ip_proto.tcp.dport, tcp_flags, packet_info->ifname, packet_info->ttl); } static int build_impact_description_udp(struct packet_info *packet_info, char *action_str, char *dir_str, char *buffer, size_t size) { return snprintf(buffer, size, "OpenBSD PF %s an %s UDP packet %s:%hu -> %s:%hu on interface %s (TTL:%hhu)", action_str, dir_str, packet_info->source_address, packet_info->ip_proto.udp.sport, packet_info->target_address, packet_info->ip_proto.udp.dport, packet_info->ifname, packet_info->ttl); } static int build_impact_description_default(struct packet_info *packet_info, char *action_str, char *dir_str, char *buffer, size_t size) { struct { char *name; int n; } ip_proto_table[] = CTL_IPPROTO_NAMES; char *proto_name = "unknown"; if ( packet_info->ip_proto_type < sizeof (ip_proto_table) / sizeof (ip_proto_table[0]) && ip_proto_table[packet_info->ip_proto_type].name ) proto_name = ip_proto_table[packet_info->ip_proto_type].name; return snprintf(buffer, size, "OpenBSD PF %s an %s %s packet %s -> %s on interface %s (TTL:%hhu)", action_str, dir_str, proto_name, packet_info->source_address, packet_info->target_address, packet_info->ifname, packet_info->ttl); } static int build_impact_description(idmef_impact_t *impact, struct packet_info *packet_info, char *action_str) { static char impact_description[256]; char *dir_str; prelude_string_t *string; int ret; switch ( packet_info->dir ) { case PF_IN: dir_str = "incoming"; break; case PF_OUT: dir_str = "outgoing"; break; default: prelude_log(PRELUDE_LOG_ERR, "unknown direction %u\n", packet_info->dir); return -1; } switch ( packet_info->ip_proto_type ) { case IPPROTO_ICMP: ret = build_impact_description_icmp(packet_info, action_str, dir_str, "ICMP", impact_description, sizeof (impact_description)); break; case IPPROTO_ICMPV6: ret = build_impact_description_icmp(packet_info, action_str, dir_str, "ICMP6", impact_description, sizeof (impact_description)); break; case IPPROTO_TCP: ret = build_impact_description_tcp(packet_info, action_str, dir_str, impact_description, sizeof (impact_description)); break; case IPPROTO_UDP: ret = build_impact_description_udp(packet_info, action_str, dir_str, impact_description, sizeof (impact_description)); break; default: ret = build_impact_description_default(packet_info, action_str, dir_str, impact_description, sizeof (impact_description)); } if ( ret < 0 || ret >= sizeof (impact_description) ) return prelude_error(PRELUDE_ERROR_GENERIC); ret = idmef_impact_new_description(impact, &string); if ( ret < 0 ) return ret; prelude_string_set_ref(string, impact_description); return 0; } static int build_impact(idmef_alert_t *alert, struct packet_info *packet_info, char *action_str) { idmef_assessment_t *assessment; idmef_impact_t *impact; int ret; ret = idmef_alert_new_assessment(alert, &assessment); if ( ret < 0 ) return ret; ret = idmef_assessment_new_impact(assessment, &impact); if ( ret < 0 ) return ret; idmef_impact_set_severity(impact, IDMEF_IMPACT_SEVERITY_LOW); if ( packet_info->action == PF_DROP ) { idmef_impact_set_completion(impact, IDMEF_IMPACT_COMPLETION_FAILED); idmef_impact_set_type(impact, IDMEF_IMPACT_TYPE_RECON); } else { idmef_impact_set_completion(impact, IDMEF_IMPACT_COMPLETION_SUCCEEDED); } return build_impact_description(impact, packet_info, action_str); } static int build_classification(idmef_alert_t *alert, char *action_str) { static char classification_text[256]; idmef_classification_t *classification; prelude_string_t *string; int ret; ret = idmef_alert_new_classification(alert, &classification); if ( ret < 0 ) return ret; ret = snprintf(classification_text, sizeof(classification_text), "Packet %s by PF firewall", action_str); if ( ret < 0 || ret >= sizeof(classification_text) ) return prelude_error(PRELUDE_ERROR_GENERIC); ret = idmef_classification_new_text(classification, &string); if ( ret < 0 ) return ret; prelude_string_set_ref(string, classification_text); return 0; } static int build_additional_data(idmef_alert_t *alert, struct packet_info *packet_info) { idmef_additional_data_t *adata; prelude_string_t *string; int ret; ret = idmef_alert_new_additional_data(alert, &adata, 0); if ( ret < 0 ) return ret; ret = idmef_additional_data_new_meaning(adata, &string); if ( ret < 0 ) return ret; prelude_string_set_constant(string, "match rule number"); idmef_additional_data_set_integer(adata, packet_info->rnr); return 0; } static int build_others(idmef_alert_t *alert, struct packet_info *packet_info) { char *action_str; int ret; switch ( packet_info->action ) { case PF_PASS: action_str = "accepted"; break; case PF_DROP: action_str = "blocked"; break; case PF_SCRUB: action_str = "defragmented"; break; default: action_str = "processed"; } ret = build_classification(alert, action_str); if ( ret < 0 ) return ret; ret = build_impact(alert, packet_info, action_str); if ( ret < 0 ) return ret; ret = build_additional_data(alert, packet_info); if ( ret < 0 ) return ret; return 0; } static int process_hdr_pflog(idmef_alert_t *alert, const struct pfloghdr *pflog_hdr) { struct packet_info packet_info; unsigned int af; int ret; memset(&packet_info, 0, sizeof (packet_info)); packet_info.ifname = pflog_hdr->ifname; #ifdef DIOCOSFPGET /* PF OS fingerprinting is available, it meens that we run OpenBSD >= 3.4 */ af = pflog_hdr->af; packet_info.rnr = ntohl(pflog_hdr->rulenr); packet_info.reason = pflog_hdr->reason; packet_info.action = pflog_hdr->action; packet_info.dir = pflog_hdr->dir; #else /* OpenBSD < 3.4 */ af = ntohl(pflog_hdr->af); packet_info.rnr = ntohs(pflog_hdr->rnr); packet_info.reason = ntohs(pflog_hdr->reason); packet_info.action = ntohs(pflog_hdr->action); packet_info.dir = ntohs(pflog_hdr->dir); #endif /* DIOCOSFPGET */ switch ( af ) { case AF_INET: ret = process_hdr_ipv4(alert, (const struct ip *) (pflog_hdr + 1), &packet_info); break; case AF_INET6: ret = process_hdr_ipv6(alert, (const struct ip6_hdr *) (pflog_hdr + 1), &packet_info); break; default: return process_hdr_unknown_af(alert, af); } if ( ret < 0 ) return ret; ret = build_others(alert, &packet_info); if ( ret < 0 ) return ret; return 0; } static int process_hdr_pcap(idmef_alert_t *alert, const struct pcap_pkthdr *pcap_hdr) { idmef_time_t *detect_time; int ret; ret = idmef_alert_new_detect_time(alert, &detect_time); idmef_time_set_from_time(detect_time, &pcap_hdr->ts.tv_sec); idmef_time_set_usec(detect_time, pcap_hdr->ts.tv_usec); return 0; } static void process_packet(u_char *ptr, const struct pcap_pkthdr *pcap_hdr, const u_char *data) { const struct pfloghdr *pflog_hdr; idmef_message_t *message; idmef_alert_t *alert; int ret; ret = idmef_message_new(&message); if ( ret < 0 ) goto error; ret = idmef_message_new_alert(message, &alert); if ( ret < 0 ) goto error; pflog_hdr = (const struct pfloghdr *) data; if ( pcap_hdr ) { ret = process_hdr_pcap(alert, pcap_hdr); if ( ret < 0 ) goto error; } ret = process_hdr_pflog(alert, pflog_hdr); if ( ret < 0 ) goto error; ret = pflogger_send_alert(message); idmef_message_destroy(message); if ( ret < 0 ) goto error; return; error: prelude_perror(ret, "error while processing packet"); } int process_packet_init(const char *interface, int snaplen) { char buffer[PCAP_ERRBUF_SIZE]; pcap = pcap_open_live((char *) interface, snaplen, 0, 500, buffer); if ( ! pcap ) { prelude_log(PRELUDE_LOG_ERR, "Could not start Prelude PFlogger: %s\n", buffer); exit(1); } return 0; } void process_packet_stop(void) { struct pcap_stat ps; double ratio; if ( pcap_stats(pcap, &ps) < 0 ) { prelude_log(PRELUDE_LOG_ERR, "pcap_stats: %s\n", pcap_geterr(pcap)); return; } ratio = ps.ps_recv ? (((double) ps.ps_drop / (double) ps.ps_recv) * 100) : 0; prelude_log(PRELUDE_LOG_INFO, "%u packets received\n", ps.ps_recv); prelude_log(PRELUDE_LOG_INFO, "%u (%.2lf%%) packets dropped\n", ps.ps_drop, ratio); pcap_close(pcap); } /* * We use poll + pcap_dispatch instead of a simple pcap_loop * because the read syscall on bpf device won't give back to * the scheduler, due to the buggy implementation of thread * on OpenBSD */ int process_packet_mainloop(void) { int fd; struct pollfd pfd; struct pcap_stat ps_prev, ps_new; int packets_to_read; u_int immediate_on = 1; fd = pcap_fileno(pcap); pfd.fd = fd; pfd.events = POLLIN; memset(&ps_new, 0, sizeof (ps_new)); /* * Get packets as soon as they are available in kernel side */ if ( ioctl(fd, BIOCIMMEDIATE, &immediate_on) < 0 ) return prelude_error_from_errno(errno); while ( 1 ) { if ( poll(&pfd, 1, -1) < 0 ) return prelude_error_from_errno(errno); packets_to_read = 0; /* * Process the newly arrived packet and the other packets eventually * buffered by pcap */ do { ps_prev = ps_new; if ( pcap_dispatch(pcap, 1, process_packet, NULL) < 0 ) { prelude_log(PRELUDE_LOG_ERR, "pcap_dispatch: %s\n", pcap_geterr(pcap)); return -1; } if ( pcap_stats(pcap, &ps_new) < 0 ) { prelude_log(PRELUDE_LOG_ERR, "pcap_stats: %s\n", pcap_geterr(pcap)); return -1; } packets_to_read += ps_new.ps_recv - ps_prev.ps_recv - 1; } while ( packets_to_read > 0 ); } return 0; }