/* $Id$ * UDP Gateway utility packet assembly and disassembly layer * Copyright (C) 2004 Jan Kratochvil * * 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; exactly version 2 of June 1991 is required * * 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Protocol documentation: * 8 bytes: "UDPGate0" (55 44 50 47 61 74 65 30) = Packet identifier * Follows arbitrary number of elements in no particular order. * Structure of each such element: * 4 bytes: element identifier; network byte order * bit 31 (0x80000000) means mandatory element: * If the element is not recognized the packet must be dropped. * bits 0..30 (0x7FFFFFFF) specify element id. * 4 bytes: element data length; network byte order * X bytes: element data; minimal data alignment is 1 byte (=no alignment) * Known elements; see: http://cvs.jankratochvil.net/viewcvs/udpgate/src/packet.h?rev=HEAD * PROGRAM_VERSION: data=string (no zero terminator); currently "1.0" * This element is currently ignored. * CLIENT_INADDR: Client IPv4 address; network byte order; length=4B * CLIENT_PORT: Client IPv4 port; network byte order; length=2B * DATA_GUINT32: Session unique number; network byte order; length=4B * Reply packet will copy this element intact. * Step 1: * Client sends from port A (35545 here) to port 8201 (probe port) packet: * PROGRAM_VERSION (of the client udpgate) * CLIENT_PORT (chosen port; default is 9201) * DATA_GUINT32 (chosen random number) * Example: * 09:07:01.966123 127.0.0.1.35545 > 127.0.0.1.8201: udp 44 (DF) * 16=$0010: 55 44 50 47 | UDPG * 32=$0020: 61 74 65 30 80 00 00 01 00 00 00 06 31 2E 30 63 | ate0........1.0c * 48=$0030: 76 73 80 00 00 03 00 00 00 02 1A 0A 80 00 00 04 | vs.............. * 64=$0040: 00 00 00 04 99 E1 4B 04 | ......K. * Step 2: * Probe server replies from port B (currently always 8201) to the packet * originating address CLIENT_INADDR and the received CLIENT_PORT: * PROGRAM_VERSION (of the probe server) * CLIENT_INADDR (detected from the originating packet address) * DATA_GUINT32 (copied from the request packet) * Example: * 09:07:01.970252 127.0.0.1.8201 > 127.0.0.1.6666: udp 46 (DF) * 16=$0010: 55 44 50 47 | UDPG * 32=$0020: 61 74 65 30 80 00 00 01 00 00 00 06 31 2E 30 63 | ate0........1.0c * 48=$0030: 76 73 80 00 00 02 00 00 00 04 7F 00 00 01 80 00 | vs.............. * 64=$0040: 00 04 00 00 00 04 99 E1 4B 04 | ........K. * Step 3: * Client must ignore probe replies with nonmatching DATA_GUINT32. * Client will stop if no probe reply is received in PROBE_TIMEOUT_SEC. * Client displays the received CLIENT_INADDR to the user. */ #include "config.h" #include #include #include #include #include #include #include "packet.h" #include "main.h" /* for optarg_verbose */ #define PACKET_VERSION_STRING (VERSION "-gnome") static void packet_assembly_guint32(GString *gstring,guint32 value_guint32) { guint32 value_guint32_htonl; g_return_if_fail(gstring!=NULL); value_guint32_htonl=htonl(value_guint32); g_assert(4==sizeof(value_guint32_htonl)); g_string_append_len(gstring,(const gchar *)&value_guint32_htonl,sizeof(value_guint32_htonl)); } static void packet_assembly_guint16(GString *gstring,guint16 value_guint16) { guint16 value_guint16_htonl; g_return_if_fail(gstring!=NULL); value_guint16_htonl=htons(value_guint16); g_assert(2==sizeof(value_guint16_htonl)); g_string_append_len(gstring,(const gchar *)&value_guint16_htonl,sizeof(value_guint16_htonl)); } static void packet_assembly_data_guint32(GString *gstring,guint32 value_guint32) { g_return_if_fail(gstring!=NULL); g_assert(4==sizeof(value_guint32)); packet_assembly_guint32(gstring,sizeof(value_guint32)); packet_assembly_guint32(gstring,value_guint32); } static void packet_assembly_data_guint16(GString *gstring,guint16 value_guint16) { g_return_if_fail(gstring!=NULL); g_assert(2==sizeof(value_guint16)); packet_assembly_guint32(gstring,sizeof(value_guint16)); packet_assembly_guint16(gstring,value_guint16); } static void packet_assembly_data_string(GString *gstring,const gchar *string) { size_t string_length; #ifndef G_DISABLE_ASSERT size_t gstring_length_orig; #endif /* !G_DISABLE_ASSERT */ g_return_if_fail(gstring!=NULL); g_return_if_fail(string!=NULL); string_length=strlen(string); packet_assembly_guint32(gstring,string_length); #ifndef G_DISABLE_ASSERT gstring_length_orig=gstring->len; #endif /* !G_DISABLE_ASSERT */ g_string_append(gstring,string); #ifndef G_DISABLE_ASSERT g_assert(gstring_length_orig+string_length==gstring->len); #endif /* !G_DISABLE_ASSERT */ } static void packet_assembly_foreach(gpointer elem_type_gpointer,gpointer value,GString *gstring /* user_data */) /* GHFunc */ { guint32 elem_type_uint32; g_return_if_fail(gstring!=NULL); elem_type_uint32=GPOINTER_TO_UINT(elem_type_gpointer); packet_assembly_guint32(gstring,elem_type_uint32); switch (elem_type_uint32) { case PACKET_ELEM_TYPE_PROGRAM_VERSION: packet_assembly_data_string(gstring,value); if (optarg_verbose) g_message(_("Assembled PROGRAM_VERSION: %s"),(const char *)value); break; case PACKET_ELEM_TYPE_CLIENT_INADDR: packet_assembly_data_guint32(gstring,GPOINTER_TO_UINT(value)); if (optarg_verbose) g_message(_("Assembled CLIENT_INADDR: %s"),HOSTIP_GUINT32_TO_STRING(GPOINTER_TO_UINT(value))); break; case PACKET_ELEM_TYPE_DATA_GUINT32: packet_assembly_data_guint32(gstring,GPOINTER_TO_UINT(value)); if (optarg_verbose) g_message(_("Assembled DATA_GUINT32: 0x%08X"),(unsigned)GPOINTER_TO_UINT(value)); break; case PACKET_ELEM_TYPE_CLIENT_PORT: packet_assembly_data_guint16(gstring,GPOINTER_TO_UINT(value)); if (optarg_verbose) g_message(_("Assembled CLIENT_PORT: %d"),(int)GPOINTER_TO_UINT(value)); break; default: g_assert_not_reached(); } } void *packet_assembly(size_t *packet_length_pointer,GHashTable *values) { GString *gstring; g_return_val_if_fail(packet_length_pointer!=NULL,NULL); g_return_val_if_fail(values!=NULL,NULL); if (optarg_verbose) g_message(_("Assembling packet...")); gstring=g_string_new(NULL); g_string_append(gstring,PACKET_HEADER); packet_assembly_foreach(GINT_TO_POINTER(PACKET_ELEM_TYPE_PROGRAM_VERSION),PACKET_VERSION_STRING,gstring); g_hash_table_foreach( values, /* hash_table */ (GHFunc)packet_assembly_foreach, /* func */ gstring); /* user_data */ if (optarg_verbose) g_message(_("Packet assembly done.")); *packet_length_pointer=gstring->len; return g_string_free(gstring, FALSE); /* free_segment */ } static gboolean packet_disassembly_guint32 (guint32 *elem_type_guint32_pointer,gconstpointer *packet_pointer,gconstpointer packet_end) { g_return_val_if_fail(elem_type_guint32_pointer!=NULL,FALSE); g_return_val_if_fail(packet_pointer!=NULL,FALSE); g_return_val_if_fail(packet_end!=NULL,FALSE); g_return_val_if_fail(*packet_pointer<=packet_end,FALSE); if ((*packet_pointer)+sizeof(*elem_type_guint32_pointer)>packet_end) return FALSE; *elem_type_guint32_pointer=ntohl(*((guint32 *)*packet_pointer)); (*packet_pointer)+=sizeof(*elem_type_guint32_pointer); return TRUE; } static void packet_disassembly_value_destroy_func(gpointer data) /* GDestroyNotify */ { } /* map: (GHashTable *) -> (GSList *) */ static GHashTable *packet_disassembly_destroy_registry; static void packet_disassembly_destroy_key_destroy_func(GHashTable *hash) /* GDestroyNotify */ { g_return_if_fail(hash!=NULL); g_hash_table_destroy(hash); } static void packet_disassembly_destroy_value_destroy_func(GSList *items) /* GDestroyNotify */ { /* 'items' may be NULL */ g_slist_foreach( items, /* list */ (GFunc)g_free, /* func */ NULL); /* user_data */ } static void packet_disassembly_destroy_registry_init(void) { if (!packet_disassembly_destroy_registry) { packet_disassembly_destroy_registry=g_hash_table_new_full( g_direct_hash, /* hash_func */ g_direct_equal, /* key_equal_func */ (GDestroyNotify)packet_disassembly_destroy_key_destroy_func, /* key_destroy_func */ (GDestroyNotify)packet_disassembly_destroy_value_destroy_func); /* value_destroy_func */ } } void packet_disassembly_destroy(GHashTable *hash) { gboolean errgboolean; g_return_if_fail(hash!=NULL); packet_disassembly_destroy_registry_init(); errgboolean=g_hash_table_remove(packet_disassembly_destroy_registry,hash); g_assert(errgboolean==TRUE); } static gboolean packet_disassembly_data_string (gpointer *value_pointer,gconstpointer elem_data,guint32 elem_data_length_guint32,GSList **items_pointer) { gchar *string; g_return_val_if_fail(value_pointer!=NULL,FALSE); g_return_val_if_fail(elem_data!=NULL,FALSE); g_return_val_if_fail(items_pointer!=NULL,FALSE); udpgate_newn(string,elem_data_length_guint32+1); *items_pointer=g_slist_prepend(*items_pointer,string); memcpy(string,elem_data,elem_data_length_guint32); string[elem_data_length_guint32]='\0'; *value_pointer=string; return TRUE; } static gboolean packet_disassembly_data_guint32 (gpointer *value_pointer,gconstpointer elem_data,guint32 elem_data_length_guint32,GSList **items_pointer) { guint32 value_guint32; g_return_val_if_fail(value_pointer!=NULL,FALSE); g_return_val_if_fail(elem_data!=NULL,FALSE); g_return_val_if_fail(items_pointer!=NULL,FALSE); g_assert(4==sizeof(value_guint32)); if (elem_data_length_guint32!=sizeof(value_guint32)) return FALSE; value_guint32=ntohl(*(guint32 *)elem_data); *value_pointer=GUINT_TO_POINTER(value_guint32); return TRUE; } static gboolean packet_disassembly_data_guint16 (gpointer *value_pointer,gconstpointer elem_data,guint32 elem_data_length_guint32,GSList **items_pointer) { guint16 value_guint16; g_return_val_if_fail(value_pointer!=NULL,FALSE); g_return_val_if_fail(elem_data!=NULL,FALSE); g_return_val_if_fail(items_pointer!=NULL,FALSE); g_assert(2==sizeof(value_guint16)); if (elem_data_length_guint32!=sizeof(value_guint16)) return FALSE; value_guint16=ntohs(*(guint16 *)elem_data); *value_pointer=GUINT_TO_POINTER((guint)value_guint16); return TRUE; } gboolean packet_recognized(gconstpointer packet,size_t packet_length) { if (strlen(PACKET_HEADER)>packet_length) return FALSE; if (memcmp(packet,PACKET_HEADER,strlen(PACKET_HEADER))) return FALSE; return TRUE; } GHashTable *packet_disassembly(gconstpointer packet,size_t packet_length) { GHashTable *r; gconstpointer packet_end; GSList *items; g_return_val_if_fail(packet!=NULL,NULL); if (optarg_verbose) g_message(_("Decoding packet...")); r=g_hash_table_new_full( g_direct_hash, /* hash_func */ g_direct_equal, /* key_equal_func */ NULL, /* key_destroy_func */ packet_disassembly_value_destroy_func); /* value_destroy_func */ packet_end=packet+packet_length; if (packet+strlen(PACKET_HEADER)>packet_end) { err_g_hash_table_destroy_r: g_hash_table_destroy(r); return NULL; } if (memcmp(packet,PACKET_HEADER,strlen(PACKET_HEADER))) goto err_g_hash_table_destroy_r; packet+=strlen(PACKET_HEADER); items=NULL; while (packet