qemu/net/colo-compare.c
<<
>>
Prefs
   1/*
   2 * COarse-grain LOck-stepping Virtual Machines for Non-stop Service (COLO)
   3 * (a.k.a. Fault Tolerance or Continuous Replication)
   4 *
   5 * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
   6 * Copyright (c) 2016 FUJITSU LIMITED
   7 * Copyright (c) 2016 Intel Corporation
   8 *
   9 * Author: Zhang Chen <zhangchen.fnst@cn.fujitsu.com>
  10 *
  11 * This work is licensed under the terms of the GNU GPL, version 2 or
  12 * later.  See the COPYING file in the top-level directory.
  13 */
  14
  15#include "qemu/osdep.h"
  16#include "qemu/error-report.h"
  17#include "trace.h"
  18#include "qemu-common.h"
  19#include "qapi/qmp/qerror.h"
  20#include "qapi/error.h"
  21#include "net/net.h"
  22#include "net/eth.h"
  23#include "qom/object_interfaces.h"
  24#include "qemu/iov.h"
  25#include "qom/object.h"
  26#include "qemu/typedefs.h"
  27#include "net/queue.h"
  28#include "sysemu/char.h"
  29#include "qemu/sockets.h"
  30#include "qapi-visit.h"
  31#include "net/colo.h"
  32
  33#define TYPE_COLO_COMPARE "colo-compare"
  34#define COLO_COMPARE(obj) \
  35    OBJECT_CHECK(CompareState, (obj), TYPE_COLO_COMPARE)
  36
  37#define COMPARE_READ_LEN_MAX NET_BUFSIZE
  38#define MAX_QUEUE_SIZE 1024
  39
  40/* TODO: Should be configurable */
  41#define REGULAR_PACKET_CHECK_MS 3000
  42
  43/*
  44  + CompareState ++
  45  |               |
  46  +---------------+   +---------------+         +---------------+
  47  |conn list      +--->conn           +--------->conn           |
  48  +---------------+   +---------------+         +---------------+
  49  |               |     |           |             |          |
  50  +---------------+ +---v----+  +---v----+    +---v----+ +---v----+
  51                    |primary |  |secondary    |primary | |secondary
  52                    |packet  |  |packet  +    |packet  | |packet  +
  53                    +--------+  +--------+    +--------+ +--------+
  54                        |           |             |          |
  55                    +---v----+  +---v----+    +---v----+ +---v----+
  56                    |primary |  |secondary    |primary | |secondary
  57                    |packet  |  |packet  +    |packet  | |packet  +
  58                    +--------+  +--------+    +--------+ +--------+
  59                        |           |             |          |
  60                    +---v----+  +---v----+    +---v----+ +---v----+
  61                    |primary |  |secondary    |primary | |secondary
  62                    |packet  |  |packet  +    |packet  | |packet  +
  63                    +--------+  +--------+    +--------+ +--------+
  64*/
  65typedef struct CompareState {
  66    Object parent;
  67
  68    char *pri_indev;
  69    char *sec_indev;
  70    char *outdev;
  71    CharBackend chr_pri_in;
  72    CharBackend chr_sec_in;
  73    CharBackend chr_out;
  74    SocketReadState pri_rs;
  75    SocketReadState sec_rs;
  76
  77    /* connection list: the connections belonged to this NIC could be found
  78     * in this list.
  79     * element type: Connection
  80     */
  81    GQueue conn_list;
  82    /* hashtable to save connection */
  83    GHashTable *connection_track_table;
  84    /* compare thread, a thread for each NIC */
  85    QemuThread thread;
  86    /* Timer used on the primary to find packets that are never matched */
  87    QEMUTimer *timer;
  88    QemuMutex timer_check_lock;
  89} CompareState;
  90
  91typedef struct CompareClass {
  92    ObjectClass parent_class;
  93} CompareClass;
  94
  95enum {
  96    PRIMARY_IN = 0,
  97    SECONDARY_IN,
  98};
  99
 100static int compare_chr_send(CharBackend *out,
 101                            const uint8_t *buf,
 102                            uint32_t size);
 103
 104/*
 105 * Return 0 on success, if return -1 means the pkt
 106 * is unsupported(arp and ipv6) and will be sent later
 107 */
 108static int packet_enqueue(CompareState *s, int mode)
 109{
 110    ConnectionKey key;
 111    Packet *pkt = NULL;
 112    Connection *conn;
 113
 114    if (mode == PRIMARY_IN) {
 115        pkt = packet_new(s->pri_rs.buf, s->pri_rs.packet_len);
 116    } else {
 117        pkt = packet_new(s->sec_rs.buf, s->sec_rs.packet_len);
 118    }
 119
 120    if (parse_packet_early(pkt)) {
 121        packet_destroy(pkt, NULL);
 122        pkt = NULL;
 123        return -1;
 124    }
 125    fill_connection_key(pkt, &key);
 126
 127    conn = connection_get(s->connection_track_table,
 128                          &key,
 129                          &s->conn_list);
 130
 131    if (!conn->processing) {
 132        g_queue_push_tail(&s->conn_list, conn);
 133        conn->processing = true;
 134    }
 135
 136    if (mode == PRIMARY_IN) {
 137        if (g_queue_get_length(&conn->primary_list) <=
 138                               MAX_QUEUE_SIZE) {
 139            g_queue_push_tail(&conn->primary_list, pkt);
 140        } else {
 141            error_report("colo compare primary queue size too big,"
 142                         "drop packet");
 143        }
 144    } else {
 145        if (g_queue_get_length(&conn->secondary_list) <=
 146                               MAX_QUEUE_SIZE) {
 147            g_queue_push_tail(&conn->secondary_list, pkt);
 148        } else {
 149            error_report("colo compare secondary queue size too big,"
 150                         "drop packet");
 151        }
 152    }
 153
 154    return 0;
 155}
 156
 157/*
 158 * The IP packets sent by primary and secondary
 159 * will be compared in here
 160 * TODO support ip fragment, Out-Of-Order
 161 * return:    0  means packet same
 162 *            > 0 || < 0 means packet different
 163 */
 164static int colo_packet_compare(Packet *ppkt, Packet *spkt)
 165{
 166    trace_colo_compare_ip_info(ppkt->size, inet_ntoa(ppkt->ip->ip_src),
 167                               inet_ntoa(ppkt->ip->ip_dst), spkt->size,
 168                               inet_ntoa(spkt->ip->ip_src),
 169                               inet_ntoa(spkt->ip->ip_dst));
 170
 171    if (ppkt->size == spkt->size) {
 172        return memcmp(ppkt->data, spkt->data, spkt->size);
 173    } else {
 174        return -1;
 175    }
 176}
 177
 178/*
 179 * Called from the compare thread on the primary
 180 * for compare tcp packet
 181 * compare_tcp copied from Dr. David Alan Gilbert's branch
 182 */
 183static int colo_packet_compare_tcp(Packet *spkt, Packet *ppkt)
 184{
 185    struct tcphdr *ptcp, *stcp;
 186    int res;
 187
 188    trace_colo_compare_main("compare tcp");
 189    if (ppkt->size != spkt->size) {
 190        if (trace_event_get_state(TRACE_COLO_COMPARE_MISCOMPARE)) {
 191            trace_colo_compare_main("pkt size not same");
 192        }
 193        return -1;
 194    }
 195
 196    ptcp = (struct tcphdr *)ppkt->transport_header;
 197    stcp = (struct tcphdr *)spkt->transport_header;
 198
 199    /*
 200     * The 'identification' field in the IP header is *very* random
 201     * it almost never matches.  Fudge this by ignoring differences in
 202     * unfragmented packets; they'll normally sort themselves out if different
 203     * anyway, and it should recover at the TCP level.
 204     * An alternative would be to get both the primary and secondary to rewrite
 205     * somehow; but that would need some sync traffic to sync the state
 206     */
 207    if (ntohs(ppkt->ip->ip_off) & IP_DF) {
 208        spkt->ip->ip_id = ppkt->ip->ip_id;
 209        /* and the sum will be different if the IDs were different */
 210        spkt->ip->ip_sum = ppkt->ip->ip_sum;
 211    }
 212
 213    res = memcmp(ppkt->data + ETH_HLEN, spkt->data + ETH_HLEN,
 214                (spkt->size - ETH_HLEN));
 215
 216    if (res != 0 && trace_event_get_state(TRACE_COLO_COMPARE_MISCOMPARE)) {
 217        trace_colo_compare_pkt_info_src(inet_ntoa(ppkt->ip->ip_src),
 218                                        ntohl(stcp->th_seq),
 219                                        ntohl(stcp->th_ack),
 220                                        res, stcp->th_flags,
 221                                        spkt->size);
 222
 223        trace_colo_compare_pkt_info_dst(inet_ntoa(ppkt->ip->ip_dst),
 224                                        ntohl(ptcp->th_seq),
 225                                        ntohl(ptcp->th_ack),
 226                                        res, ptcp->th_flags,
 227                                        ppkt->size);
 228
 229        qemu_hexdump((char *)ppkt->data, stderr,
 230                     "colo-compare ppkt", ppkt->size);
 231        qemu_hexdump((char *)spkt->data, stderr,
 232                     "colo-compare spkt", spkt->size);
 233    }
 234
 235    return res;
 236}
 237
 238/*
 239 * Called from the compare thread on the primary
 240 * for compare udp packet
 241 */
 242static int colo_packet_compare_udp(Packet *spkt, Packet *ppkt)
 243{
 244    int ret;
 245
 246    trace_colo_compare_main("compare udp");
 247    ret = colo_packet_compare(ppkt, spkt);
 248
 249    if (ret) {
 250        trace_colo_compare_udp_miscompare("primary pkt size", ppkt->size);
 251        qemu_hexdump((char *)ppkt->data, stderr, "colo-compare", ppkt->size);
 252        trace_colo_compare_udp_miscompare("Secondary pkt size", spkt->size);
 253        qemu_hexdump((char *)spkt->data, stderr, "colo-compare", spkt->size);
 254    }
 255
 256    return ret;
 257}
 258
 259/*
 260 * Called from the compare thread on the primary
 261 * for compare icmp packet
 262 */
 263static int colo_packet_compare_icmp(Packet *spkt, Packet *ppkt)
 264{
 265    int network_length;
 266
 267    trace_colo_compare_main("compare icmp");
 268    network_length = ppkt->ip->ip_hl * 4;
 269    if (ppkt->size != spkt->size ||
 270        ppkt->size < network_length + ETH_HLEN) {
 271        return -1;
 272    }
 273
 274    if (colo_packet_compare(ppkt, spkt)) {
 275        trace_colo_compare_icmp_miscompare("primary pkt size",
 276                                           ppkt->size);
 277        qemu_hexdump((char *)ppkt->data, stderr, "colo-compare",
 278                     ppkt->size);
 279        trace_colo_compare_icmp_miscompare("Secondary pkt size",
 280                                           spkt->size);
 281        qemu_hexdump((char *)spkt->data, stderr, "colo-compare",
 282                     spkt->size);
 283        return -1;
 284    } else {
 285        return 0;
 286    }
 287}
 288
 289/*
 290 * Called from the compare thread on the primary
 291 * for compare other packet
 292 */
 293static int colo_packet_compare_other(Packet *spkt, Packet *ppkt)
 294{
 295    trace_colo_compare_main("compare other");
 296    trace_colo_compare_ip_info(ppkt->size, inet_ntoa(ppkt->ip->ip_src),
 297                               inet_ntoa(ppkt->ip->ip_dst), spkt->size,
 298                               inet_ntoa(spkt->ip->ip_src),
 299                               inet_ntoa(spkt->ip->ip_dst));
 300    return colo_packet_compare(ppkt, spkt);
 301}
 302
 303static int colo_old_packet_check_one(Packet *pkt, int64_t *check_time)
 304{
 305    int64_t now = qemu_clock_get_ms(QEMU_CLOCK_HOST);
 306
 307    if ((now - pkt->creation_ms) > (*check_time)) {
 308        trace_colo_old_packet_check_found(pkt->creation_ms);
 309        return 0;
 310    } else {
 311        return 1;
 312    }
 313}
 314
 315static void colo_old_packet_check_one_conn(void *opaque,
 316                                           void *user_data)
 317{
 318    Connection *conn = opaque;
 319    GList *result = NULL;
 320    int64_t check_time = REGULAR_PACKET_CHECK_MS;
 321
 322    result = g_queue_find_custom(&conn->primary_list,
 323                                 &check_time,
 324                                 (GCompareFunc)colo_old_packet_check_one);
 325
 326    if (result) {
 327        /* do checkpoint will flush old packet */
 328        /* TODO: colo_notify_checkpoint();*/
 329    }
 330}
 331
 332/*
 333 * Look for old packets that the secondary hasn't matched,
 334 * if we have some then we have to checkpoint to wake
 335 * the secondary up.
 336 */
 337static void colo_old_packet_check(void *opaque)
 338{
 339    CompareState *s = opaque;
 340
 341    g_queue_foreach(&s->conn_list, colo_old_packet_check_one_conn, NULL);
 342}
 343
 344/*
 345 * Called from the compare thread on the primary
 346 * for compare connection
 347 */
 348static void colo_compare_connection(void *opaque, void *user_data)
 349{
 350    CompareState *s = user_data;
 351    Connection *conn = opaque;
 352    Packet *pkt = NULL;
 353    GList *result = NULL;
 354    int ret;
 355
 356    while (!g_queue_is_empty(&conn->primary_list) &&
 357           !g_queue_is_empty(&conn->secondary_list)) {
 358        qemu_mutex_lock(&s->timer_check_lock);
 359        pkt = g_queue_pop_tail(&conn->primary_list);
 360        qemu_mutex_unlock(&s->timer_check_lock);
 361        switch (conn->ip_proto) {
 362        case IPPROTO_TCP:
 363            result = g_queue_find_custom(&conn->secondary_list,
 364                     pkt, (GCompareFunc)colo_packet_compare_tcp);
 365            break;
 366        case IPPROTO_UDP:
 367            result = g_queue_find_custom(&conn->secondary_list,
 368                     pkt, (GCompareFunc)colo_packet_compare_udp);
 369            break;
 370        case IPPROTO_ICMP:
 371            result = g_queue_find_custom(&conn->secondary_list,
 372                     pkt, (GCompareFunc)colo_packet_compare_icmp);
 373            break;
 374        default:
 375            result = g_queue_find_custom(&conn->secondary_list,
 376                     pkt, (GCompareFunc)colo_packet_compare_other);
 377            break;
 378        }
 379
 380        if (result) {
 381            ret = compare_chr_send(&s->chr_out, pkt->data, pkt->size);
 382            if (ret < 0) {
 383                error_report("colo_send_primary_packet failed");
 384            }
 385            trace_colo_compare_main("packet same and release packet");
 386            g_queue_remove(&conn->secondary_list, result->data);
 387            packet_destroy(pkt, NULL);
 388        } else {
 389            /*
 390             * If one packet arrive late, the secondary_list or
 391             * primary_list will be empty, so we can't compare it
 392             * until next comparison.
 393             */
 394            trace_colo_compare_main("packet different");
 395            qemu_mutex_lock(&s->timer_check_lock);
 396            g_queue_push_tail(&conn->primary_list, pkt);
 397            qemu_mutex_unlock(&s->timer_check_lock);
 398            /* TODO: colo_notify_checkpoint();*/
 399            break;
 400        }
 401    }
 402}
 403
 404static int compare_chr_send(CharBackend *out,
 405                            const uint8_t *buf,
 406                            uint32_t size)
 407{
 408    int ret = 0;
 409    uint32_t len = htonl(size);
 410
 411    if (!size) {
 412        return 0;
 413    }
 414
 415    ret = qemu_chr_fe_write_all(out, (uint8_t *)&len, sizeof(len));
 416    if (ret != sizeof(len)) {
 417        goto err;
 418    }
 419
 420    ret = qemu_chr_fe_write_all(out, (uint8_t *)buf, size);
 421    if (ret != size) {
 422        goto err;
 423    }
 424
 425    return 0;
 426
 427err:
 428    return ret < 0 ? ret : -EIO;
 429}
 430
 431static int compare_chr_can_read(void *opaque)
 432{
 433    return COMPARE_READ_LEN_MAX;
 434}
 435
 436/*
 437 * Called from the main thread on the primary for packets
 438 * arriving over the socket from the primary.
 439 */
 440static void compare_pri_chr_in(void *opaque, const uint8_t *buf, int size)
 441{
 442    CompareState *s = COLO_COMPARE(opaque);
 443    int ret;
 444
 445    ret = net_fill_rstate(&s->pri_rs, buf, size);
 446    if (ret == -1) {
 447        qemu_chr_fe_set_handlers(&s->chr_pri_in, NULL, NULL, NULL,
 448                                 NULL, NULL, true);
 449        error_report("colo-compare primary_in error");
 450    }
 451}
 452
 453/*
 454 * Called from the main thread on the primary for packets
 455 * arriving over the socket from the secondary.
 456 */
 457static void compare_sec_chr_in(void *opaque, const uint8_t *buf, int size)
 458{
 459    CompareState *s = COLO_COMPARE(opaque);
 460    int ret;
 461
 462    ret = net_fill_rstate(&s->sec_rs, buf, size);
 463    if (ret == -1) {
 464        qemu_chr_fe_set_handlers(&s->chr_sec_in, NULL, NULL, NULL,
 465                                 NULL, NULL, true);
 466        error_report("colo-compare secondary_in error");
 467    }
 468}
 469
 470static void *colo_compare_thread(void *opaque)
 471{
 472    GMainContext *worker_context;
 473    GMainLoop *compare_loop;
 474    CompareState *s = opaque;
 475
 476    worker_context = g_main_context_new();
 477
 478    qemu_chr_fe_set_handlers(&s->chr_pri_in, compare_chr_can_read,
 479                             compare_pri_chr_in, NULL, s, worker_context, true);
 480    qemu_chr_fe_set_handlers(&s->chr_sec_in, compare_chr_can_read,
 481                             compare_sec_chr_in, NULL, s, worker_context, true);
 482
 483    compare_loop = g_main_loop_new(worker_context, FALSE);
 484
 485    g_main_loop_run(compare_loop);
 486
 487    g_main_loop_unref(compare_loop);
 488    g_main_context_unref(worker_context);
 489    return NULL;
 490}
 491
 492static char *compare_get_pri_indev(Object *obj, Error **errp)
 493{
 494    CompareState *s = COLO_COMPARE(obj);
 495
 496    return g_strdup(s->pri_indev);
 497}
 498
 499static void compare_set_pri_indev(Object *obj, const char *value, Error **errp)
 500{
 501    CompareState *s = COLO_COMPARE(obj);
 502
 503    g_free(s->pri_indev);
 504    s->pri_indev = g_strdup(value);
 505}
 506
 507static char *compare_get_sec_indev(Object *obj, Error **errp)
 508{
 509    CompareState *s = COLO_COMPARE(obj);
 510
 511    return g_strdup(s->sec_indev);
 512}
 513
 514static void compare_set_sec_indev(Object *obj, const char *value, Error **errp)
 515{
 516    CompareState *s = COLO_COMPARE(obj);
 517
 518    g_free(s->sec_indev);
 519    s->sec_indev = g_strdup(value);
 520}
 521
 522static char *compare_get_outdev(Object *obj, Error **errp)
 523{
 524    CompareState *s = COLO_COMPARE(obj);
 525
 526    return g_strdup(s->outdev);
 527}
 528
 529static void compare_set_outdev(Object *obj, const char *value, Error **errp)
 530{
 531    CompareState *s = COLO_COMPARE(obj);
 532
 533    g_free(s->outdev);
 534    s->outdev = g_strdup(value);
 535}
 536
 537static void compare_pri_rs_finalize(SocketReadState *pri_rs)
 538{
 539    CompareState *s = container_of(pri_rs, CompareState, pri_rs);
 540
 541    if (packet_enqueue(s, PRIMARY_IN)) {
 542        trace_colo_compare_main("primary: unsupported packet in");
 543        compare_chr_send(&s->chr_out, pri_rs->buf, pri_rs->packet_len);
 544    } else {
 545        /* compare connection */
 546        g_queue_foreach(&s->conn_list, colo_compare_connection, s);
 547    }
 548}
 549
 550static void compare_sec_rs_finalize(SocketReadState *sec_rs)
 551{
 552    CompareState *s = container_of(sec_rs, CompareState, sec_rs);
 553
 554    if (packet_enqueue(s, SECONDARY_IN)) {
 555        trace_colo_compare_main("secondary: unsupported packet in");
 556    } else {
 557        /* compare connection */
 558        g_queue_foreach(&s->conn_list, colo_compare_connection, s);
 559    }
 560}
 561
 562
 563/*
 564 * Return 0 is success.
 565 * Return 1 is failed.
 566 */
 567static int find_and_check_chardev(CharDriverState **chr,
 568                                  char *chr_name,
 569                                  Error **errp)
 570{
 571    *chr = qemu_chr_find(chr_name);
 572    if (*chr == NULL) {
 573        error_setg(errp, "Device '%s' not found",
 574                   chr_name);
 575        return 1;
 576    }
 577
 578    if (!qemu_chr_has_feature(*chr, QEMU_CHAR_FEATURE_RECONNECTABLE)) {
 579        error_setg(errp, "chardev \"%s\" is not reconnectable",
 580                   chr_name);
 581        return 1;
 582    }
 583
 584    return 0;
 585}
 586
 587/*
 588 * Check old packet regularly so it can watch for any packets
 589 * that the secondary hasn't produced equivalents of.
 590 */
 591static void check_old_packet_regular(void *opaque)
 592{
 593    CompareState *s = opaque;
 594
 595    timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
 596              REGULAR_PACKET_CHECK_MS);
 597    /* if have old packet we will notify checkpoint */
 598    /*
 599     * TODO: Make timer handler run in compare thread
 600     * like qemu_chr_add_handlers_full.
 601     */
 602    qemu_mutex_lock(&s->timer_check_lock);
 603    colo_old_packet_check(s);
 604    qemu_mutex_unlock(&s->timer_check_lock);
 605}
 606
 607/*
 608 * Called from the main thread on the primary
 609 * to setup colo-compare.
 610 */
 611static void colo_compare_complete(UserCreatable *uc, Error **errp)
 612{
 613    CompareState *s = COLO_COMPARE(uc);
 614    CharDriverState *chr;
 615    char thread_name[64];
 616    static int compare_id;
 617
 618    if (!s->pri_indev || !s->sec_indev || !s->outdev) {
 619        error_setg(errp, "colo compare needs 'primary_in' ,"
 620                   "'secondary_in','outdev' property set");
 621        return;
 622    } else if (!strcmp(s->pri_indev, s->outdev) ||
 623               !strcmp(s->sec_indev, s->outdev) ||
 624               !strcmp(s->pri_indev, s->sec_indev)) {
 625        error_setg(errp, "'indev' and 'outdev' could not be same "
 626                   "for compare module");
 627        return;
 628    }
 629
 630    if (find_and_check_chardev(&chr, s->pri_indev, errp) ||
 631        !qemu_chr_fe_init(&s->chr_pri_in, chr, errp)) {
 632        return;
 633    }
 634
 635    if (find_and_check_chardev(&chr, s->sec_indev, errp) ||
 636        !qemu_chr_fe_init(&s->chr_sec_in, chr, errp)) {
 637        return;
 638    }
 639
 640    if (find_and_check_chardev(&chr, s->outdev, errp) ||
 641        !qemu_chr_fe_init(&s->chr_out, chr, errp)) {
 642        return;
 643    }
 644
 645    net_socket_rs_init(&s->pri_rs, compare_pri_rs_finalize);
 646    net_socket_rs_init(&s->sec_rs, compare_sec_rs_finalize);
 647
 648    g_queue_init(&s->conn_list);
 649    qemu_mutex_init(&s->timer_check_lock);
 650
 651    s->connection_track_table = g_hash_table_new_full(connection_key_hash,
 652                                                      connection_key_equal,
 653                                                      g_free,
 654                                                      connection_destroy);
 655
 656    sprintf(thread_name, "colo-compare %d", compare_id);
 657    qemu_thread_create(&s->thread, thread_name,
 658                       colo_compare_thread, s,
 659                       QEMU_THREAD_JOINABLE);
 660    compare_id++;
 661
 662    /* A regular timer to kick any packets that the secondary doesn't match */
 663    s->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, /* Only when guest runs */
 664                            check_old_packet_regular, s);
 665    timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
 666                        REGULAR_PACKET_CHECK_MS);
 667
 668    return;
 669}
 670
 671static void colo_compare_class_init(ObjectClass *oc, void *data)
 672{
 673    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
 674
 675    ucc->complete = colo_compare_complete;
 676}
 677
 678static void colo_compare_init(Object *obj)
 679{
 680    object_property_add_str(obj, "primary_in",
 681                            compare_get_pri_indev, compare_set_pri_indev,
 682                            NULL);
 683    object_property_add_str(obj, "secondary_in",
 684                            compare_get_sec_indev, compare_set_sec_indev,
 685                            NULL);
 686    object_property_add_str(obj, "outdev",
 687                            compare_get_outdev, compare_set_outdev,
 688                            NULL);
 689}
 690
 691static void colo_compare_finalize(Object *obj)
 692{
 693    CompareState *s = COLO_COMPARE(obj);
 694
 695    qemu_chr_fe_deinit(&s->chr_pri_in);
 696    qemu_chr_fe_deinit(&s->chr_sec_in);
 697    qemu_chr_fe_deinit(&s->chr_out);
 698
 699    g_queue_free(&s->conn_list);
 700
 701    if (qemu_thread_is_self(&s->thread)) {
 702        /* compare connection */
 703        g_queue_foreach(&s->conn_list, colo_compare_connection, s);
 704        qemu_thread_join(&s->thread);
 705    }
 706
 707    if (s->timer) {
 708        timer_del(s->timer);
 709    }
 710
 711    qemu_mutex_destroy(&s->timer_check_lock);
 712
 713    g_free(s->pri_indev);
 714    g_free(s->sec_indev);
 715    g_free(s->outdev);
 716}
 717
 718static const TypeInfo colo_compare_info = {
 719    .name = TYPE_COLO_COMPARE,
 720    .parent = TYPE_OBJECT,
 721    .instance_size = sizeof(CompareState),
 722    .instance_init = colo_compare_init,
 723    .instance_finalize = colo_compare_finalize,
 724    .class_size = sizeof(CompareClass),
 725    .class_init = colo_compare_class_init,
 726    .interfaces = (InterfaceInfo[]) {
 727        { TYPE_USER_CREATABLE },
 728        { }
 729    }
 730};
 731
 732static void register_types(void)
 733{
 734    type_register_static(&colo_compare_info);
 735}
 736
 737type_init(register_types);
 738