W00t, my first patch got applied :) Thank you. Here goes version 0.3 of my packet capture patch. I rewritten it to be a custom VLANClient which implements the capturing part in its fd_read proceudre, rather than adding additional properties to the VLAN structure. Monitor support is also present.
Also, could somebody please give me any hints regarding my questions described here: http://lists.gnu.org/archive/html/qemu-devel/2007-12/msg00296.html (Qemu architectural questions)? Any pointers would be greatly appreciated. Best regards. __________________________________________________________ Sent from Yahoo! Mail - a smarter inbox http://uk.mail.yahoo.com
Index: vl.c =================================================================== RCS file: /sources/qemu/qemu/vl.c,v retrieving revision 1.388 diff -u -r1.388 vl.c --- vl.c 17 Dec 2007 04:42:28 -0000 1.388 +++ vl.c 18 Dec 2007 07:30:26 -0000 @@ -237,6 +237,19 @@ static CPUState *next_cpu; static int event_pending = 1; +/* File header which needs to be written at the start of each PCAP file*/ +static const PCAPHeader pcap_file_header = { + 0xa1b2c3d4, /* Magic header - used to determine file endianess */ + 2, /* Version major */ + 4, /* Version minor */ + 0, + 0, + MAX_CAPTURED_PACKET_SIZE, + 1 /* Ethernet */ +}; +/* Used to return in case a memory allocation fails (because obviously we can't allocate memory then!) */ +static char *pcap_memory_error = "Failed to allocate memory"; + #define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR) /***********************************************************/ @@ -4669,6 +4682,7 @@ char buf[1024]; int vlan_id, ret; VLANState *vlan; + char *error_message; p = str; q = device; @@ -4788,6 +4802,15 @@ } vlan->nb_host_devs++; } else + if (!strcmp(device, "capture")) { + error_message = pcap_add_capture(vlan_id, (char*)get_param_value(buf, sizeof(buf), "file", p)); + if (error_message) { + fprintf(stderr, "%s\n", error_message); + free(error_message); + return -1; + } + ret = 0; + } else { fprintf(stderr, "Unknown network device: %s\n", device); return -1; @@ -4799,6 +4822,152 @@ return ret; } +/* VLAN Packet capture */ + +static void pcap_receive(void *opaque, const uint8_t *buf, int size) { + PCAPStateInfo *pcap_state; + + pcap_state = (PCAPStateInfo*)opaque; + + pcap_state->packet_header.timestamp_sec = time(NULL); + if (pcap_state->packet_header.timestamp_sec == pcap_state->last_packet_time) { + if (pcap_state->packet_header.timestamp_usec < 1000000) + ++pcap_state->packet_header.timestamp_usec; + } else { + pcap_state->packet_header.timestamp_usec = 0; + pcap_state->last_packet_time = pcap_state->packet_header.timestamp_sec; + } + + pcap_state->packet_header.orig_len = size; + pcap_state->packet_header.saved_len = (size > MAX_CAPTURED_PACKET_SIZE) ? MAX_CAPTURED_PACKET_SIZE : size; + write(pcap_state->fh, &pcap_state->packet_header, sizeof(PCAPPacketHeader)); + write(pcap_state->fh, buf, pcap_state->packet_header.saved_len); +} + +/* Add capture to a given VLAN. */ +/* Returns NULL on success, the error message in case of an error. */ +/* If an error message is returned, the caller must "free" it */ +char* pcap_add_capture(int vlan_id, const char *filename) +{ + VLANState *vlan; + VLANClientState *vlan_client; + char *error_message; + PCAPStateInfo *pcap_state; + + if (NULL == filename) + filename = DEFAULT_CAPTURE_FILENAME; + + for(vlan = first_vlan; vlan != NULL; vlan = vlan->next) + if (vlan_id == vlan->id) + break; + if (!vlan) { + asprintf(&error_message, "Error locating VLAN %d to attach for capture.", vlan_id); + return error_message; + } + + //check if already capturing + vlan_client = vlan->first_client; + while (NULL != vlan_client) { + if (pcap_receive == vlan_client->fd_read) { + asprintf(&error_message, "VLAN %d is already capturing. " + "Only one capture per VLAN is possible.", vlan_id); + return error_message; + } + vlan_client = vlan_client->next; + } + + //not capturing, try to start / attach it + pcap_state = qemu_mallocz(sizeof(PCAPStateInfo)); + if (!pcap_state) + return pcap_memory_error; + vlan_client = qemu_new_vlan_client(vlan, &pcap_receive, NULL, pcap_state); + if (!vlan_client) { + free(pcap_state); + return pcap_memory_error; + } + + pcap_state->fh = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (pcap_state->fh < 0) { + free(pcap_state); + free(vlan_client); + + asprintf(&error_message, "Failed to open capture file \"%s\": %d\n", filename, errno); + return error_message; + } + + //write the header to the file + write(pcap_state->fh, &pcap_file_header, sizeof(pcap_file_header)); + + //success + return NULL; +} + +/* (Tries to) remove capture from a given VLAN */ +/* Returns NULL on success, the error message in case of an error. */ +/* If an error message is returned, the caller must "free" it */ +char* pcap_remove_capture(int vlan_id) +{ + VLANState *vlan; + VLANClientState *vlan_client, *vlan_temp; + char *error_message; + PCAPStateInfo *pcap_state; + + for(vlan = first_vlan; vlan != NULL; vlan = vlan->next) + if (vlan_id == vlan->id) + break; + if (!vlan) { + asprintf(&error_message, "Error locating VLAN %d to remove capture.", vlan_id); + return error_message; + } + + vlan_client = NULL; + //two cases + //if the first vlan client is the pcap one + if (pcap_receive == vlan->first_client->fd_read) { + vlan_client = vlan->first_client; + vlan->first_client = vlan_client->next; + } else { + //search through the list + vlan_client = vlan->first_client; + while (NULL != vlan_client->next) { + if (pcap_receive == vlan_client->next->fd_read) + break; + vlan_client = vlan_client->next; + } + if (NULL != vlan_client && NULL != vlan_client->next) { + vlan_temp = vlan_client->next; + vlan_client->next = vlan_temp->next; + vlan_client = vlan_temp; + } else { + vlan_client = NULL; + } + } + + if (NULL == vlan_client) { + asprintf(&error_message, "No active capture found for VLAN %d", vlan_id); + return error_message; + } + + pcap_state = (PCAPStateInfo*)vlan_client->opaque; + close(pcap_state->fh); + free(pcap_state); + free(vlan_client); + + return NULL; +} + +/* Removes the capture from all VLAN's */ +void pcap_remove_all_capture(void) +{ + VLANState *vlan; + char *error_message; + + for(vlan = first_vlan; vlan != NULL; vlan = vlan->next) { + error_message = pcap_remove_capture(vlan->id); + if (error_message) free(error_message); + } +} + void do_info_network(void) { VLANState *vlan; @@ -7540,6 +7709,10 @@ " connect the vlan 'n' to another VLAN using a socket connection\n" "-net socket[,vlan=n][,fd=h][,mcast=maddr:port]\n" " connect the vlan 'n' to multicast maddr and port\n" + "-net capture[,vlan=n][,file=filename]\n" + " captures the traffic flowing through VLAN 'n' to the file\n" + " 'filename' in tcpdump format. In case filename is not specified,\n" + " the capture will be saved to " DEFAULT_CAPTURE_FILENAME "\n" "-net none use it alone to have zero network devices; if no -net option\n" " is provided, the default is '-net nic -net user'\n" "\n" @@ -8948,6 +9121,9 @@ close(fd); } + + /* stop capturing packets (close capture files) on exit */ + atexit(pcap_remove_all_capture); main_loop(); quit_timers(); @@ -8969,5 +9145,6 @@ } } #endif + return 0; } Index: net.h =================================================================== RCS file: /sources/qemu/qemu/net.h,v retrieving revision 1.1 diff -u -r1.1 net.h --- net.h 17 Nov 2007 17:14:38 -0000 1.1 +++ net.h 18 Dec 2007 07:30:26 -0000 @@ -1,6 +1,41 @@ #ifndef QEMU_NET_H #define QEMU_NET_H +/* PCAP support */ +/* source: http://wiki.wireshark.org/Development/LibpcapFileFormat */ +typedef struct { + uint32_t magic_number; + uint16_t version_major; + uint16_t version_minor; + int32_t thiszone; + uint32_t sigfigs; + uint32_t snaplen; + uint32_t network; +} PCAPHeader; + +#define MAX_CAPTURED_PACKET_SIZE 0xFFFF +#define DEFAULT_CAPTURE_FILENAME "qemu.pcap" + +typedef struct { + uint32_t timestamp_sec; + uint32_t timestamp_usec; + uint32_t saved_len; + uint32_t orig_len; +} PCAPPacketHeader; + +/* used to keep the state for the packet capture process */ +typedef struct { + //filehandle of the capture file + int fh; + time_t last_packet_time; + //defined here to avoid reallocation in the code + PCAPPacketHeader packet_header; +} PCAPStateInfo; + +char* pcap_add_capture(int vlan_id, const char *filename); +char* pcap_remove_capture(int vlan_id); +void pcap_remove_all_capture(void); + /* VLANs support */ typedef struct VLANClientState VLANClientState; Index: monitor.c =================================================================== RCS file: /sources/qemu/qemu/monitor.c,v retrieving revision 1.93 diff -u -r1.93 monitor.c --- monitor.c 17 Dec 2007 03:15:51 -0000 1.93 +++ monitor.c 18 Dec 2007 07:30:27 -0000 @@ -1255,6 +1255,38 @@ } #endif +void do_net_capture (const char *path, + int has_vlan, int vlan_id) +{ + char *error_message; + + if (!has_vlan) vlan_id = 0; + + error_message = pcap_add_capture(vlan_id, path); + if (error_message) { + term_printf("%s\n", error_message); + free(error_message); + return; + } + + return; +} + +void do_stop_net_capture(int has_vlan, int vlan_id) +{ + char *error_message; + + if (has_vlan) { + error_message = pcap_remove_capture(vlan_id); + if (error_message) { + term_printf("%s\n", error_message); + free(error_message); + return; + } + } else + pcap_remove_all_capture(); +} + static term_cmd_t term_cmds[] = { { "help|?", "s?", do_help, "[cmd]", "show the help" }, @@ -1326,6 +1358,10 @@ "capture index", "stop capture" }, { "memsave", "lis", do_memory_save, "addr size file", "save to disk virtual memory dump starting at 'addr' of size 'size'", }, + { "netcapture", "si?", do_net_capture, + "[vlan]", "saves the traffic flowing through the given vlan to the file (default vlan=0)" }, + { "stopnetcapture", "i?", do_stop_net_capture, + "[vlan]", "stops the capture of the specified vlan. if no vlan specified, stops all the capture" }, { NULL, NULL, }, };