Changeset ebb54a5


Ignore:
Timestamp:
09/15/15 11:13:07 (5 years ago)
Author:
Shane Alcock <salcock@…>
Branches:
4.0.1-hotfixes, cachetimestamps, develop, dpdk-ndag, etsilive, libtrace4, master, ndag_format, pfring, rc-4.0.1, rc-4.0.2, rc-4.0.3, rc-4.0.4, ringdecrementfix, ringperformance, ringtimestampfixes
Children:
aafdc55
Parents:
6264e8e
Message:

Improved parallel traceanon

  • Updated to use new parallel API.
  • Use libcrypto for AES operations.
  • Rename -f option to -F to avoid confusion with normal filtering options.
  • Add OO code for implementing anonymisation methods.
  • Add ability to anonymise IPv6 addresses using cryptopan.
  • Make sure ICMPv6 checksums are replaced.
  • Remove unnecessary testing code, e.g. hash functions, debug output.
  • Add maximum threads CLI option.
  • Replaced useless trace_help option with conventional -h option.
Location:
tools/traceanon
Files:
2 added
2 edited

Legend:

Unmodified
Added
Removed
  • tools/traceanon/Makefile.am

    r29bbef0 rebb54a5  
    1 bin_PROGRAMS = traceanon traceanon_parallel
     1bin_PROGRAMS = traceanon
    22
    33man_MANS = traceanon.1
     
    55
    66include ../Makefile.tools
    7 traceanon_SOURCES = traceanon.c rijndael.h rijndael.c panon.h panon.c ipenc.c ipenc.h
    8 traceanon_parallel_SOURCES = traceanon_parallel.c rijndael.h rijndael.c panon.h panon.c ipenc.c ipenc.h
     7#traceanon_SOURCES = traceanon.c rijndael.h rijndael.c panon.h panon.c ipenc.c ipenc.h
     8traceanon_SOURCES = traceanon.cc Anon.cc Anon.h
    99
    10 # rijndael.c does lots of nasty casting that is going to be a nightmare to fix
    11 # and debug - I'm going to disable this warning in the interim, as it's really
    12 # messy and hopefully isn't actually an issue.
    13 traceanon_CFLAGS = $(AM_CFLAGS)
    14 traceanon_parallel_CFLAGS = $(AM_CFLAGS)
     10traceanon_CPPFLAGS = $(AM_CPPFLAGS)
  • tools/traceanon/traceanon.cc

    r8b7f254 rebb54a5  
    1 #define _GNU_SOURCE
     1#include "config.h"
     2#include "Anon.h"
    23#include "libtrace_parallel.h"
    34#include <stdio.h>
     
    1819char *key = NULL;
    1920
     21int level = -1;
     22trace_option_compresstype_t compress_type = TRACE_OPTION_COMPRESSTYPE_NONE;
    2023
    2124struct libtrace_t *trace = NULL;
     
    2326static void cleanup_signal(int signal)
    2427{
    25         static int s = 0;
    2628        (void)signal;
    27     //trace_interrupt();
    2829        // trace_pstop isn't really signal safe because its got lots of locks in it
    29     trace_pstop(trace);
    30     /*if (s == 0) {
    31                 if (trace_ppause(trace) == -1)
    32                         trace_perror(trace, "Pause failed");
    33         }
    34         else {
    35                 if (trace_pstart(trace, NULL, NULL, NULL) == -1)
    36                         trace_perror(trace, "Start failed");
    37     }*/
    38         s = !s;
     30        trace_pstop(trace);
    3931}
    4032
     
    4941        "-c --cryptopan=key     Encrypt the addresses with the cryptopan\n"
    5042        "                       prefix preserving\n"
    51         "-f --keyfile=file      A file containing the cryptopan key\n"
     43        "-F --keyfile=file      A file containing the cryptopan key\n"
    5244        "-p --prefix=C.I.D.R/bits Substitute the prefix of the address\n"
    53         "-H --libtrace-help     Print libtrace runtime documentation\n"
     45        "-h --help              Print this usage information\n"
    5446        "-z --compress-level    Compress the output trace at the specified level\n"
    5547        "-Z --compress-type     Compress the output trace using the specified"
    5648        "                       compression algorithm\n"
     49        "-t --threads=max       Use this number of threads for packet processing\n"
    5750        ,argv0);
    5851        exit(1);
     
    6053
    6154/* Incrementally update a checksum */
    62 static void update_in_cksum(uint16_t *csum, uint16_t old, uint16_t new)
    63 {
    64         uint32_t sum = (~htons(*csum) & 0xFFFF) 
    65                      + (~htons(old) & 0xFFFF) 
    66                      + htons(new);
     55static void update_in_cksum(uint16_t *csum, uint16_t old, uint16_t newval)
     56{
     57        uint32_t sum = (~htons(*csum) & 0xFFFF)
     58                     + (~htons(old) & 0xFFFF)
     59                     + htons(newval);
    6760        sum = (sum & 0xFFFF) + (sum >> 16);
    6861        *csum = htons(~(sum + (sum >> 16)));
    6962}
    7063
    71 static void update_in_cksum32(uint16_t *csum, uint32_t old, uint32_t new)
    72 {
    73         update_in_cksum(csum,(uint16_t)(old>>16),(uint16_t)(new>>16));
    74         update_in_cksum(csum,(uint16_t)(old&0xFFFF),(uint16_t)(new&0xFFFF));
     64UNUSED static void update_in_cksum32(uint16_t *csum, uint32_t old,
     65                uint32_t newval)
     66{
     67        update_in_cksum(csum,(uint16_t)(old>>16),(uint16_t)(newval>>16));
     68        update_in_cksum(csum,(uint16_t)(old&0xFFFF),(uint16_t)(newval&0xFFFF));
    7569}
    7670
     
    8579 * source instead of the source and destination!
    8680 */
    87 static void encrypt_ips(struct libtrace_ip *ip,bool enc_source,bool enc_dest)
    88 {
    89         struct libtrace_tcp *tcp;
    90         struct libtrace_udp *udp;
    91         struct libtrace_icmp *icmp;
    92 
    93         tcp=trace_get_tcp_from_ip(ip,NULL);
    94         udp=trace_get_udp_from_ip(ip,NULL);
    95         icmp=trace_get_icmp_from_ip(ip,NULL);
     81static void encrypt_ips(Anonymiser *anon, struct libtrace_ip *ip,
     82                bool enc_source,bool enc_dest)
     83{
     84        libtrace_icmp_t *icmp=trace_get_icmp_from_ip(ip,NULL);
    9685
    9786        if (enc_source) {
    98                 uint32_t old_ip=ip->ip_src.s_addr;
    99                 uint32_t new_ip=htonl(enc_ip(
    100                                         htonl(ip->ip_src.s_addr)
    101                                         ));
    102                 update_in_cksum32(&ip->ip_sum,old_ip,new_ip);
    103                 if (tcp) update_in_cksum32(&tcp->check,old_ip,new_ip);
    104                 if (udp) update_in_cksum32(&udp->check,old_ip,new_ip);
     87                uint32_t new_ip=htonl(anon->anonIPv4(ntohl(ip->ip_src.s_addr)));
    10588                ip->ip_src.s_addr = new_ip;
    10689        }
    10790
    10891        if (enc_dest) {
    109                 uint32_t old_ip=ip->ip_dst.s_addr;
    110                 uint32_t new_ip=htonl(enc_ip(
    111                                         htonl(ip->ip_dst.s_addr)
    112                                         ));
    113                 update_in_cksum32(&ip->ip_sum,old_ip,new_ip);
    114                 if (tcp) update_in_cksum32(&tcp->check,old_ip,new_ip);
    115                 if (udp) update_in_cksum32(&udp->check,old_ip,new_ip);
     92                uint32_t new_ip=htonl(anon->anonIPv4(ntohl(ip->ip_dst.s_addr)));
    11693                ip->ip_dst.s_addr = new_ip;
    11794        }
     
    126103                                || icmp->type == 11) {
    127104                        char *ptr = (char *)icmp;
    128                         encrypt_ips(
     105                        encrypt_ips(anon,
    129106                                (struct libtrace_ip*)(ptr+
    130107                                        sizeof(struct libtrace_icmp)),
     
    138115}
    139116
    140 
    141 UNUSED static uint64_t bad_hash(UNUSED libtrace_packet_t * pkt)
    142 {
    143         return 0;
    144 }
    145 
    146 
    147 UNUSED static uint64_t rand_hash(UNUSED libtrace_packet_t * pkt)
    148 {
    149         return rand();
    150 }
    151 
    152 
    153 static void* per_packet(libtrace_t *trace, libtrace_thread_t *t,
    154                         int mesg, libtrace_generic_t data,
    155                         libtrace_thread_t *sender UNUSED)
    156 {
     117static void encrypt_ipv6(Anonymiser *anon, libtrace_ip6_t *ip6,
     118                bool enc_source, bool enc_dest) {
     119
     120        uint8_t previp[16];
     121
     122        if (enc_source) {
     123                memcpy(previp, &(ip6->ip_src.s6_addr), 16);
     124                anon->anonIPv6(previp, (uint8_t *)&(ip6->ip_src.s6_addr));
     125        }
     126
     127        if (enc_dest) {
     128                memcpy(previp, &(ip6->ip_dst.s6_addr), 16);
     129                anon->anonIPv6(previp, (uint8_t *)&(ip6->ip_dst.s6_addr));
     130        }
     131
     132}
     133
     134
     135static libtrace_packet_t *per_packet(libtrace_t *trace, libtrace_thread_t *t,
     136        void *global, void *tls, libtrace_packet_t *packet) {
     137
    157138        struct libtrace_ip *ipptr;
     139        libtrace_ip6_t *ip6;
    158140        libtrace_udp_t *udp = NULL;
    159141        libtrace_tcp_t *tcp = NULL;
    160         libtrace_stat_t *stats = NULL;
    161         switch (mesg) {
    162         case MESSAGE_PACKET:
    163                 ipptr = trace_get_ip(data.pkt);
    164 
    165                 if (ipptr && (enc_source || enc_dest)) {
    166                         encrypt_ips(ipptr,enc_source,enc_dest);
    167                         ipptr->ip_sum = 0;
    168                 }
    169 
    170                 /* Replace checksums so that IP encryption cannot be
    171                  * reversed */
    172 
    173                 /* XXX replace with nice use of trace_get_transport() */
    174 
    175                 udp = trace_get_udp(data.pkt);
    176                 if (udp && (enc_source || enc_dest)) {
    177                         udp->check = 0;
    178                 }
    179 
    180                 tcp = trace_get_tcp(data.pkt);
    181                 if (tcp && (enc_source || enc_dest)) {
    182                         tcp->check = 0;
    183                 }
    184 
    185                 /* TODO: Encrypt IP's in ARP packets */
    186                
    187                 // Send our result keyed with the time
    188                 // Arg don't copy packets
    189                 //libtrace_packet_t * packet_copy = trace_copy_packet(packet);
    190                 //libtrace_packet_t * packet_copy = trace_result_packet(trace, pkt);
    191                 //trace_publish_result(trace, trace_packet_get_order(pkt), pkt);
    192 
    193                 trace_publish_result(trace, t, trace_packet_get_order(data.pkt), data, RESULT_PACKET);
    194                 break;
    195         case MESSAGE_STARTING:
    196                 enc_init(enc_type,key);
    197                 break;
    198         case MESSAGE_TICK_INTERVAL:
    199                 trace_publish_result(trace, t, data.uint64, (libtrace_generic_t){0}, RESULT_TICK_INTERVAL);
    200                 break;
    201         case MESSAGE_TICK_COUNT:
    202                 trace_publish_result(trace, t, data.uint64, (libtrace_generic_t){0}, RESULT_TICK_COUNT);
    203                 break;
    204         case MESSAGE_STOPPING:
    205                 stats = trace_create_statistics();
    206                 trace_get_thread_statistics(trace, t, stats);
    207                 trace_print_statistics(stats, stderr, NULL);
    208                 free(stats);
    209                 stats = trace_get_statistics(trace, NULL);
    210                 trace_print_statistics(stats, stderr, NULL);
    211                 //fprintf(stderr, "tracestats_parallel:\t Stopping thread - publishing results\n");
    212                 break;
    213         }
    214         return NULL;
    215 }
    216 
    217 struct libtrace_out_t *writer = 0;
    218 
    219 static void write_out(libtrace_t *trace UNUSED, int mesg,
    220                       libtrace_generic_t data,
    221                       libtrace_thread_t *sender UNUSED) {
    222         static uint64_t packet_count = 0; // TESTING PURPOSES, this is not going to work with a live format
    223 
    224         switch (mesg) {
    225         case MESSAGE_RESULT:
    226                 if (data.res->type == RESULT_PACKET) {
    227                         libtrace_packet_t *packet = (libtrace_packet_t*) data.res->value.pkt;
    228                         assert(data.res->key >= packet_count);
    229                         packet_count = data.res->key;
    230                         if (trace_write_packet(writer,packet)==-1) {
    231                                 trace_perror_output(writer,"writer");
    232                                 trace_interrupt();
    233                         }
    234                         trace_free_packet(trace, packet);
    235 
    236                 } else {
    237                         assert(data.res->type == RESULT_TICK_COUNT || data.res->type == RESULT_TICK_INTERVAL);
    238                         // Ignore it
    239                 }
    240         }
    241 }
    242 
     142        libtrace_icmp6_t *icmp6 = NULL;
     143        Anonymiser *anon = (Anonymiser *)tls;
     144        libtrace_generic_t result;
     145
     146        ipptr = trace_get_ip(packet);
     147        ip6 = trace_get_ip6(packet);
     148
     149        if (ipptr && (enc_source || enc_dest)) {
     150                encrypt_ips(anon, ipptr,enc_source,enc_dest);
     151                ipptr->ip_sum = 0;
     152        } else if (ip6 && (enc_source || enc_dest)) {
     153                encrypt_ipv6(anon, ip6, enc_source, enc_dest);
     154        }
     155
     156
     157        /* Replace checksums so that IP encryption cannot be
     158         * reversed -- TODO allow checksums to be updated and remain valid
     159         * for the new addresses */
     160
     161        /* XXX replace with nice use of trace_get_transport() */
     162
     163        udp = trace_get_udp(packet);
     164        if (udp && (enc_source || enc_dest)) {
     165                udp->check = 0;
     166        }
     167
     168        tcp = trace_get_tcp(packet);
     169        if (tcp && (enc_source || enc_dest)) {
     170                tcp->check = 0;
     171        }
     172
     173        icmp6 = trace_get_icmp6(packet);
     174        if (icmp6 && (enc_source || enc_dest)) {
     175                icmp6->checksum = 0;
     176        }
     177
     178        /* TODO: Encrypt IP's in ARP packets */
     179        result.pkt = packet;
     180        trace_publish_result(trace, t, trace_packet_get_order(packet), result, RESULT_PACKET);
     181
     182        return NULL;
     183}
     184
     185static void *start_anon(libtrace_t *trace, libtrace_thread_t *t, void *global)
     186{
     187        if (enc_type == ENC_PREFIX_SUBSTITUTION) {
     188                PrefixSub *sub = new PrefixSub(key, NULL);
     189                return sub;
     190        }
     191
     192        if (enc_type == ENC_CRYPTOPAN) {
     193#ifdef HAVE_LIBCRYPTO               
     194                CryptoAnon *anon = new CryptoAnon((uint8_t *)key,
     195                        (uint8_t)strlen(key), 20);
     196                return anon;
     197#else
     198                /* TODO nicer way of exiting? */
     199                fprintf(stderr, "Error: requested CryptoPan anonymisation but "
     200                        "libtrace was built without libcrypto support!\n");
     201                exit(1);
     202#endif
     203        }
     204
     205        return NULL;
     206}
     207
     208static void end_anon(libtrace_t *trace, libtrace_thread_t *t, void *global,
     209                void *tls) {
     210        Anonymiser *anon = (Anonymiser *)tls;
     211        delete(anon);
     212
     213}
     214
     215static void *init_output(libtrace_t *trace, libtrace_thread_t *t, void *global)
     216{
     217        libtrace_out_t *writer = NULL;
     218        char *outputname = (char *)global;
     219       
     220        writer = trace_create_output(outputname);
     221
     222        if (trace_is_err_output(writer)) {
     223                trace_perror_output(writer,"trace_create_output");
     224                trace_destroy_output(writer);
     225                return NULL;
     226        }
     227       
     228        /* Hopefully this will deal nicely with people who want to crank the
     229         * compression level up to 11 :) */
     230        if (level > 9) {
     231                fprintf(stderr, "WARNING: Compression level > 9 specified, setting to 9 instead\n");
     232                level = 9;
     233        }
     234
     235        if (level >= 0 && trace_config_output(writer,
     236                        TRACE_OPTION_OUTPUT_COMPRESS, &level) == -1) {
     237                trace_perror_output(writer, "Configuring compression level");
     238                trace_destroy_output(writer);
     239                return NULL;
     240        }
     241
     242        if (trace_config_output(writer, TRACE_OPTION_OUTPUT_COMPRESSTYPE,
     243                                &compress_type) == -1) {
     244                trace_perror_output(writer, "Configuring compression type");
     245                trace_destroy_output(writer);
     246                return NULL;
     247        }
     248
     249        if (trace_start_output(writer)==-1) {
     250                trace_perror_output(writer,"trace_start_output");
     251                trace_destroy_output(writer);
     252                return NULL;
     253        }
     254
     255        return writer;
     256
     257}
     258
     259static void write_packet(libtrace_t *trace, libtrace_thread_t *sender,
     260                      void *global, void *tls, libtrace_result_t *result) {
     261        libtrace_packet_t *packet = (libtrace_packet_t*) result->value.pkt;
     262        libtrace_out_t *writer = (libtrace_out_t *)tls;
     263
     264        if (writer != NULL && trace_write_packet(writer,packet)==-1) {
     265                trace_perror_output(writer,"writer");
     266                trace_interrupt();
     267        }
     268        trace_free_packet(trace, packet);
     269}
     270
     271static void end_output(libtrace_t *trace, libtrace_thread_t *t, void *global,
     272                void *tls) {
     273        libtrace_out_t *writer = (libtrace_out_t *)tls;
     274
     275        trace_destroy_output(writer);
     276}
    243277
    244278int main(int argc, char *argv[])
     
    247281        struct sigaction sigact;
    248282        char *output = 0;
    249         int level = -1;
    250283        char *compress_type_str=NULL;
    251         trace_option_compresstype_t compress_type = TRACE_OPTION_COMPRESSTYPE_NONE;
    252         char *config = NULL;
    253         char *config_file = NULL;
     284        int maxthreads = 4;
     285        libtrace_callback_set_t *pktcbs = NULL;
     286        libtrace_callback_set_t *repcbs = NULL;
     287        int exitcode = 0;
    254288
    255289        if (argc<2)
     
    262296                        { "encrypt-dest",       0, 0, 'd' },
    263297                        { "cryptopan",          1, 0, 'c' },
    264                         { "cryptopan-file",     1, 0, 'f' },
     298                        { "cryptopan-file",     1, 0, 'F' },
    265299                        { "prefix",             1, 0, 'p' },
     300                        { "threads",            1, 0, 't' },
    266301                        { "compress-level",     1, 0, 'z' },
    267302                        { "compress-type",      1, 0, 'Z' },
    268                         { "libtrace-help",      0, 0, 'H' },
    269                         { "config",             1, 0, 'u' },
    270                     { "config-file",            1, 0, 'U' },
     303                        { "help",               0, 0, 'h' },
    271304                        { NULL,                 0, 0, 0   },
    272305                };
    273306
    274                 int c=getopt_long(argc, argv, "Z:z:sc:f:dp:Hu:U:",
     307                int c=getopt_long(argc, argv, "Z:z:sc:f:dp:ht:",
    275308                                long_options, &option_index);
    276309
     
    291324                                  enc_type = ENC_CRYPTOPAN;
    292325                                  break;
    293                         case 'f':
     326                        case 'F': {
    294327                                  if(key != NULL) {
    295328                                    fprintf(stderr,"You can only have one encryption type and one key\n");
     
    310343                                  enc_type = ENC_CRYPTOPAN;
    311344                                  break;
     345                        }
    312346                        case 'p':
    313347                                  if (key!=NULL) {
     
    318352                                  enc_type = ENC_PREFIX_SUBSTITUTION;
    319353                                  break;
    320                         case 'H':
    321                                   trace_help();
    322                                   exit(1);
    323                                   break;
    324                         case 'u':
    325                                 config = optarg;
    326                                 break;
    327                         case 'U':
    328                                 config_file = optarg;
    329                                 break;
     354                        case 'h':
     355                                  usage(argv[0]);
     356                        case 't':
     357                                  maxthreads=atoi(optarg);
     358                                  if (maxthreads <= 0)
     359                                          maxthreads = 1;
     360                                  break;
    330361                        default:
    331362                                fprintf(stderr,"unknown option: %c\n",c);
     
    368399        if (trace_is_err(trace)) {
    369400                trace_perror(trace,"trace_create");
    370                 trace_destroy(trace);
    371                 return 1;
     401                exitcode = 1;
     402                goto exitanon;
    372403        }
    373404
     
    377408                 */
    378409                output = strdup("erf:-");
    379                 writer = trace_create_output(output);
    380410        } else {
    381                 writer = trace_create_output(argv[optind +1]);
    382         }
    383         if (trace_is_err_output(writer)) {
    384                 trace_perror_output(writer,"trace_create_output");
    385                 trace_destroy_output(writer);
    386                 trace_destroy(trace);
    387                 return 1;
    388         }
    389        
    390         /* Hopefully this will deal nicely with people who want to crank the
    391          * compression level up to 11 :) */
    392         if (level > 9) {
    393                 fprintf(stderr, "WARNING: Compression level > 9 specified, setting to 9 instead\n");
    394                 level = 9;
    395         }
    396 
    397         if (level >= 0 && trace_config_output(writer,
    398                         TRACE_OPTION_OUTPUT_COMPRESS, &level) == -1) {
    399                 trace_perror_output(writer, "Configuring compression level");
    400                 trace_destroy_output(writer);
    401                 trace_destroy(trace);
    402                 return 1;
    403         }
    404 
    405         if (trace_config_output(writer, TRACE_OPTION_OUTPUT_COMPRESSTYPE,
    406                                 &compress_type) == -1) {
    407                 trace_perror_output(writer, "Configuring compression type");
    408                 trace_destroy_output(writer);
    409                 trace_destroy(trace);
    410                 return 1;
    411         }
    412 
    413         if (trace_start_output(writer)==-1) {
    414                 trace_perror_output(writer,"trace_start_output");
    415                 trace_destroy_output(writer);
    416                 trace_destroy(trace);
    417                 return 1;
    418         }
    419 
     411                output = argv[optind +1];
     412        }
    420413        // OK parallel changes start here
    421414
     
    424417         * special case to have.
    425418         */
    426 
    427         /* Apply config */
    428         if (config) {
    429                 trace_set_configuration(trace, config);
    430         }
    431 
    432         if (config_file) {
    433                 FILE * f = fopen(optarg, "r");
    434                 if (f != NULL) {
    435                         trace_set_configuration_file(trace, f);
    436                         fclose(f);
    437                 } else {
    438                         perror("Failed to open configuration file\n");
    439                         usage(argv[0]);
    440                 }
    441         }
    442 
    443419        trace_set_combiner(trace, &combiner_ordered, (libtrace_generic_t){0});
    444420
    445         //trace_set_hasher(trace, HASHER_CUSTOM, rand_hash, NULL);
    446        
    447         if (trace_pstart(trace, NULL, &per_packet, &write_out)==-1) {
     421        pktcbs = trace_create_callback_set();
     422        trace_set_packet_cb(pktcbs, per_packet);
     423        trace_set_stopping_cb(pktcbs, end_anon);
     424        trace_set_starting_cb(pktcbs, start_anon);
     425
     426        repcbs = trace_create_callback_set();
     427        trace_set_result_cb(repcbs, write_packet);
     428        trace_set_stopping_cb(repcbs, end_output);
     429        trace_set_starting_cb(repcbs, init_output);
     430
     431        trace_set_perpkt_threads(trace, maxthreads);
     432
     433        if (trace_pstart(trace, output, pktcbs, repcbs)==-1) {
    448434                trace_perror(trace,"trace_start");
    449                 trace_destroy_output(writer);
    450                 trace_destroy(trace);
    451                 return 1;
     435                exitcode = 1;
     436                goto exitanon;
    452437        }
    453438
     
    461446        // Wait for the trace to finish
    462447        trace_join(trace);
    463        
    464         //trace_destroy_packet(packet);
    465         //print_contention_stats(trace);
    466         trace_destroy(trace);
    467         trace_destroy_output(writer);
    468         return 0;
    469 }
     448
     449exitanon:
     450        if (pktcbs)
     451                trace_destroy_callback_set(pktcbs);
     452        if (repcbs)
     453                trace_destroy_callback_set(repcbs);
     454        if (trace)
     455                trace_destroy(trace);
     456        return exitcode;
     457}
Note: See TracChangeset for help on using the changeset viewer.