Wednesday, November 21, 2012

OpenvSwitch 代码分析(二)


本小节分析vswitchd模块,该模块作为管理底层datapath的重要模块,实现了openflow的主要逻辑,以及对交换机的具体管理和除简单转发外的其他逻辑。可见,该模块十分重要,主要生成ovs-vswitchd文件,其中主文件为vswitchd/ovs-vswitchd.c。

整体分析

Vswitchd模块主要包括bridge、ofproto等子模块。作为主要逻辑实现模块,负责解析和执行其他各个openflow命令。

bridge模块

负责所管理的所有datapath,对外的接口很简单,包括
void bridge_init(const char *remote);
void bridge_exit(void);

void bridge_run(void);
void bridge_run_fast(void);
void bridge_wait(void);

void bridge_get_memory_usage(struct simap *usage);
数据结构主要在bridge.c中定义了bridge结构,定义为
struct bridge {
    struct hmap_node node;      /* In 'all_bridges'. */
    char *name;                 /* User-specified arbitrary name. */
    char *type;                 /* Datapath type. */
    uint8_t ea[ETH_ADDR_LEN];   /* Bridge Ethernet Address. */
    uint8_t default_ea[ETH_ADDR_LEN]; /* Default MAC. */
    const struct ovsrec_bridge *cfg;

    /* OpenFlow switch processing. */
    struct ofproto *ofproto;    /* OpenFlow switch. */

    /* Bridge ports. */
    struct hmap ports;          /* "struct port"s indexed by name. */
    struct hmap ifaces;         /* "struct iface"s indexed by ofp_port. */
    struct hmap iface_by_name;  /* "struct iface"s indexed by name. */

    struct list ofpp_garbage;   /* "struct ofpp_garbage" slated for removal. */
    struct hmap if_cfg_todo;    /* "struct if_cfg"s slated for creation.
                                   Indexed on 'cfg->name'. */

    /* Port mirroring. */
    struct hmap mirrors;        /* "struct mirror" indexed by UUID. */

    /* Synthetic local port if necessary. */
    struct ovsrec_port synth_local_port;
    struct ovsrec_interface synth_local_iface;
    struct ovsrec_interface *synth_local_ifacep;
};
其中,最重要的是ofproto指针,指向一个openflow switch,负责进行openflow switch的所有处理。实际上,vswitchd的主要功能就是不断检测并调用所有bridge上的ofproto,执行其上的处理函数。

ofproto

类型定义在ofproto/ofproto-provider.h中。
struct ofproto {
    struct hmap_node hmap_node; /* In global 'all_ofprotos' hmap. */
    const struct ofproto_class *ofproto_class;
    char *type;                 /* Datapath type. */
    char *name;                 /* Datapath name. */

    /* Settings. */
    uint64_t fallback_dpid;     /* Datapath ID if no better choice found. */
    uint64_t datapath_id;       /* Datapath ID. */
    unsigned flow_eviction_threshold; /* Threshold at which to begin flow
                                       * table eviction. Only affects the
                                       * ofproto-dpif implementation */
    bool forward_bpdu;          /* Option to allow forwarding of BPDU frames
                                 * when NORMAL action is invoked. */
    char *mfr_desc;             /* Manufacturer. */
    char *hw_desc;              /* Hardware. */
    char *sw_desc;              /* Software version. */
    char *serial_desc;          /* Serial number. */
    char *dp_desc;              /* Datapath description. */
    enum ofp_config_flags frag_handling; /* One of OFPC_*.  */

    /* Datapath. */
    struct hmap ports;          /* Contains "struct ofport"s. */
    struct shash port_by_name;

    /* Flow tables. */
    struct oftable *tables;
    int n_tables;

    /* OpenFlow connections. */
    struct connmgr *connmgr;

    /* Flow table operation tracking. */
    int state;                  /* Internal state. */
    struct list pending;        /* List of "struct ofopgroup"s. */
    unsigned int n_pending;     /* list_size(&pending). */
    struct hmap deletions;      /* All OFOPERATION_DELETE "ofoperation"s. */

    /* Flow table operation logging. */
    int n_add, n_delete, n_modify; /* Number of unreported ops of each kind. */
    long long int first_op, last_op; /* Range of times for unreported ops. */
    long long int next_op_report;    /* Time to report ops, or LLONG_MAX. */
    long long int op_backoff;        /* Earliest time to report ops again. */

    /* Linux VLAN device support (e.g. "eth0.10" for VLAN 10.)
     *
     * This is deprecated.  It is only for compatibility with broken device
     * drivers in old versions of Linux that do not properly support VLANs when
     * VLAN devices are not used.  When broken device drivers are no longer in
     * widespread use, we will delete these interfaces. */
    unsigned long int *vlan_bitmap; /* 4096-bit bitmap of in-use VLANs. */
    bool vlans_changed;             /* True if new VLANs are in use. */
    int min_mtu;                    /* Current MTU of non-internal ports. */
};
其中最关键的是ofproto_class,是ofproto交换机的具体实现,定义了对于of协议的处理(包括run和run_fast函数,前者处理更为全面,调用了后者)。处理函数的实现在ofproto/ofproto-dpif.c中。

run_fast()

位于ofproto-dpif.c中。
分析run_fast函数,主要完成了两个需要周期性及时完成的事情。
首先对各个port上调用port_run_fast,检查是否要发送连续性检查的网包消息(CCM,参考IEEE 802.1aq),如果是,则发出。
HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
        port_run_fast(ofport);
    }
然后,检查是否有upcall,对所有的来自datapath的upcall进行处理。
while (work < FLOW_MISS_MAX_BATCH) {
        int retval = handle_upcalls(ofproto, FLOW_MISS_MAX_BATCH - work);
        if (retval <= 0) {
            return -retval;
        }
        work += retval;
    }

handle_upcalls()

位于ofproto-dpif.c中。
该函数从对应的dpif中获取到upcalls后,对upcalls进行类型检查。对于SFLOW_UPCALL和BAD_UPCALL,进行对应处理后释放存有upcall消息的buf,而对于MISS_UPCALL类型,则调用handle_miss_upcalls进行后续的处理。
其中,upcall的类型为dpif_upcall(lib/dpif.h),定义为
/* A packet passed up from the datapath to userspace.
 *
 * If 'key' or 'actions' is nonnull, then it points into data owned by
 * 'packet', so their memory cannot be freed separately.  (This is hardly a
 * great way to do things but it works out OK for the dpif providers and
 * clients that exist so far.)
 */
struct dpif_upcall {
    /* All types. */
    enum dpif_upcall_type type;
    struct ofpbuf *packet;      /* Packet data. */
    struct nlattr *key;         /* Flow key. */
    size_t key_len;             /* Length of 'key' in bytes. */

    /* DPIF_UC_ACTION only. */
    uint64_t userdata;          /* Argument to OVS_ACTION_ATTR_USERSPACE. */
};

handle_miss_upcalls ()

位于ofproto-dpif.c中。
该函数从upcall中提取相关的流信息,把属于同一个key的网包放到一起,最后放进todo list中。最后检查todo list中的每个元素,调用handle_flow_miss()进行处理。
HMAP_FOR_EACH (miss, hmap_node, &todo) {
        handle_flow_miss(ofproto, miss, flow_miss_ops, &n_ops);
}
处理完毕后,调用dpif_operate()(位于vswitchd/dpif.c)执行查找到的行动。
for (i = 0; i < n_ops; i++) {
        dpif_ops[i] = &flow_miss_ops[i].dpif_op;
    }
    dpif_operate(ofproto->dpif, dpif_ops, n_ops);

handle_miss_upcall ()

位于ofproto/ofproto-dpif.c中。
该函数处理给定的某个miss_upcall。首先,先判断是否发生了精确匹配,如果发生了,则直接按照匹配结果调用handle_flow_miss_with_facet();如果没有精确匹配结果,则调用handle_flow_miss_without_facet()。

unixctl_server相关

主循环中调用了unixctl_server_run()函数。该函数首先获取到远端server的连接,然后,执行连接中的命令,代码为。
LIST_FOR_EACH_SAFE (conn, next, node, &server->conns) {
        int error = run_connection(conn);
        if (error && error != EAGAIN) {
            kill_connection(conn);
        }
    }

ovs-vswitchd.c

主文件,其中main()为入口主函数,执行一系列的初始化,并配置各个队列,最后是主循环,进行任务处理。分析主要代码如下
Int main(int argc, char *argv[])
{
    char *unixctl_path = NULL;
    struct unixctl_server *unixctl;
    struct signal *sighup;
    char *remote;
    bool exiting;
    int retval;

    proctitle_init(argc, argv); //backup orignal argvs
    set_program_name(argv[0]);
    stress_init_command(); //register stress cmds to the commands
    remote = parse_options(argc, argv, &unixctl_path);
    signal(SIGPIPE, SIG_IGN); //ignore the pipe read end signal
    sighup = signal_register(SIGHUP); //register the SIGHUP signal handler
    process_init(); //create notification pipe and register signal for child process exit
    ovsrec_init(); //todo: make clear here
    daemonize_start(); //daemonize the process

    if (want_mlockall) {
#ifdef HAVE_MLOCKALL
        if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
            VLOG_ERR("mlockall failed: %s", strerror(errno));
        }
#else
        VLOG_ERR("mlockall not supported on this system");
#endif
    }
    worker_start(); //start a worker subprocess, call worker_main (receive data and process)
    retval = unixctl_server_create(unixctl_path, &unixctl);//create a unix domain socket
    if (retval) {
        exit(EXIT_FAILURE);
    }
    unixctl_command_register("exit", "", 0, 0, ovs_vswitchd_exit, &exiting);

    bridge_init(remote);//ini the bridge, configure from the ovsdb server, register ctrl commands
    free(remote);
    exiting = false;
    while (!exiting) {
        worker_run(); //reply with the worker subprocess
        if (signal_poll(sighup)) {
            vlog_reopen_log_file();
        }
        memory_run();//monitor the memory
        if (memory_should_report()) {
            struct simap usage;
            simap_init(&usage);
            bridge_get_memory_usage(&usage);
            memory_report(&usage);
            simap_destroy(&usage);
        }
        bridge_run_fast(); //check each bridge and run it's handler
        bridge_run(); //main process part, process of pkts
        bridge_run_fast();
        unixctl_server_run(unixctl);
        netdev_run(); //run periodic functions by all network devices.
        worker_wait();
        signal_wait(sighup);
        memory_wait();
        bridge_wait();
        unixctl_server_wait(unixctl);
        netdev_wait();
        if (exiting) {
            poll_immediate_wake();
        }
        poll_block();
    }
    bridge_exit();
    unixctl_server_destroy(unixctl);
    signal_unregister(sighup);
    return 0;
}

proctitle_init(argc, argv)

复制出输入的参数列表到新的存储中,让argv指向这块内存,主要是为了后面的proctitle_set()函数(在daemonize_start()->monitor_daemon()中调用,可能修改原argv存储)做准备。

    set_program_name(argv[0])

设置程序名称、版本、编译日期等信息。

    stress_init_command()

注册stress 相关命令(list、set、enable、disable)到commands结构。

    remote = parse_options(argc, argv, &unixctl_path)

解析参数,其中unixctl_path存储unixctrl域的sock名,作为接受外部控制命令的渠道;而remote存储连接到ovsdb的信息,即连接到配置数据库的sock名。

    signal(SIGPIPE, SIG_IGN)

忽略pipe读结束的信号。

    sighup = signal_register(SIGHUP)

注册对 SIGHUP信号(终端挂起)的处理函数。处理函数为写到fds[1]中空字符。

    process_init()

注册对SIGCHLD信号(子进程结束)的处理函数。处理函数为执行all_process上的所有进程。

    ovsrec_init()

数据表结构初始化。包括13张数据表。表的具体结构请参考ovsdb的相关文档。

daemonize_start()

让进程变成守护程序。

worker_start()

开启一个worker子进程。子进程与主进程交互数据。

unixctl_server_create(unixctl_path, &unixctl)

创建一个unixctl server(存放在unixctl),并监听在unixctl_path指定的punix路径。

unixctl_command_register("exit", "", 0, 0, vs_vswitchd_exit, &exiting)

注册unixctl命令。

    bridge_init(remote)

从remote数据库获取配置信息,并初始化bridge。

主循环

exiting = false;
    while (!exiting) {
        worker_run(); //reply with the worker subprocess
        if (signal_poll(sighup)) {
            vlog_reopen_log_file();
        }
        memory_run();//monitor the memory
        if (memory_should_report()) {
            struct simap usage;

            simap_init(&usage);
            bridge_get_memory_usage(&usage);
            memory_report(&usage);
            simap_destroy(&usage);
        }
        bridge_run_fast(); //check each bridge and run it's handler
        bridge_run(); //main process part, process of pkts
        bridge_run_fast();
        unixctl_server_run(unixctl);
        netdev_run(); //run periodic functions by all network devices.

        worker_wait();
        signal_wait(sighup);
        memory_wait();
        bridge_wait();
        unixctl_server_wait(unixctl);
        netdev_wait();
        if (exiting) {
            poll_immediate_wake();
        }
        poll_block();
}

worker_run()

执行从worker子进程中获取的RPC reply,执行其中的cb_reply回调函数。主要过程为
rxbuf_run(&client_rx, client_sock, sizeof(struct worker_reply));
reply->reply_cb(&client_rx.payload, client_rx.fds,  client_rx.n_fds, reply->reply_aux);

bridge_run_fast()

执行在all_bridge上的每个bridge的ofproto上的run_fast。主要是监听和处理来自datapath的upcall,主要过程为
HMAP_FOR_EACH (br, node, &all_bridges) {
        ofproto_run_fast(br->ofproto);
    }

bridge_run()

主要的对网包进行慢速处理过程。包括完成必要的配置更新(在配置更新中会从数据库读入配置信息,生成必要的bridge和dp等数据结构),以及执行在all_bridge上的每个bridge的ofproto上的run(),并做相应的信息统计。
其中,ofproto上的run()主要依次调用了如下函数:
调用dpif_run()处理所有注册的netlink notifier的汇报事件。
调用run_fast()处理常见的周期性事件,包括对upcalls的处理等。
可选调用netflow_run()和sflow_run(),进行对netflow和sflow的支持
可选调用port_run()进行发送CCM。
可选调用bundle_run()处理LACP、bonding等杂项。
可选调用stp_run()进行STP支持。
mac_learning_run()获取超时的mac entry,并将其删除掉。
可选调用governor_run()进行限速处理。

unixctl_server_run(unixctl)

从unixctl指定的server中获取数据,并执行对应的配置命令。主要过程为
struct unixctl_conn *conn = xzalloc(sizeof *conn);
            list_push_back(&server->conns, &conn->node);
            conn->rpc = jsonrpc_open(stream);

netdev_run()

执行在netdev_classes上定义的每个netdev_class实体,调用它们的run()。主要过程为
SHASH_FOR_EACH(node, &netdev_classes) {
        const struct netdev_class *netdev_class = node->data;
        if (netdev_class->run) {
            netdev_class->run();
        }
}

循环等待事件处理

包括woker、signal、memory、bridge、unixctl_server、netdev等事件,被poll_fd_wait()注册。

poll_block(void)

阻塞,直到之前被poll_fd_wait()注册过的事件发生,或者等待时间超过poll_timer_wait()注册的最短时间。

清理工作

退出bridge,关闭unixctl 连接,取消对sighup信号的处理注册。
bridge_exit();
unixctl_server_destroy(unixctl);
signal_unregister(sighup);

通用类型

基础宏定义

首先分析下几个常见的基础宏。
CONTAINER_OF宏:返回拥有某个给定member的struct结构的起始地址。其中,struct为所定义的数据结构,其中有一个变量名字为member,pointer为指向member变量的一个指针。该宏返回整个数据结构的起始地址。定义为
#define CONTAINER_OF(POINTER, STRUCT, MEMBER)                           \
        ((STRUCT *) (void *) ((char *) (POINTER) - offsetof (STRUCT, MEMBER)))

OBJECT_CONTAINING宏:返回含有某个给定member的对象object的地址。
#define OBJECT_CONTAINING(POINTER, OBJECT, MEMBER)                      \
    ((OVS_TYPEOF(OBJECT)) (void *)                                      \
     ((char *) (POINTER) - OBJECT_OFFSETOF(OBJECT, MEMBER)))

ASSIGN_CONTAINER宏:返回含有某个给定member的对象object和1。
#define ASSIGN_CONTAINER(OBJECT, POINTER, MEMBER) \
    ((OBJECT) = OBJECT_CONTAINING(POINTER, OBJECT, MEMBER), 1)

普通列表

ovs代码中大量使用了列表的结构。列表的声明在lib/list.h中。列表的抽象结构用户不必关心,用户只需要维护好自己关心的节点的数据结构即可。
使用一个list: struct list L = LIST_INITIALIZER(&L)。
正向遍历:
#define LIST_FOR_EACH(ITER, MEMBER, LIST)                               \
    for (ASSIGN_CONTAINER(ITER, (LIST)->next, MEMBER);                  \
         &(ITER)->MEMBER != (LIST);                                     \
         ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.next, MEMBER))
逆向遍历
#define LIST_FOR_EACH_REVERSE(ITER, MEMBER, LIST)                       \
    for (ASSIGN_CONTAINER(ITER, (LIST)->prev, MEMBER);                  \
         &(ITER)->MEMBER != (LIST);                                     \
         ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER))
安全遍历
#define LIST_FOR_EACH_SAFE(ITER, NEXT, MEMBER, LIST)            \
    for (ASSIGN_CONTAINER(ITER, (LIST)->next, MEMBER);          \
         (&(ITER)->MEMBER != (LIST)                             \
          ? ASSIGN_CONTAINER(NEXT, (ITER)->MEMBER.next, MEMBER) \
          : 0);                                                 \
         (ITER) = (NEXT))

Hash列表

列表的声明在lib/hmap.h中。
Hmap列表,包含两个指针,指向其中含有的节点,定义为
/* A hash map. */
struct hmap {
    struct hmap_node **buckets; /* Must point to 'one' iff 'mask' == 0. */
    struct hmap_node *one;
    size_t mask;
    size_t n;
};
而hmap_node类型,包括一个hash值和一个后继指针,定义为
struct hmap_node {
    size_t hash;                /* Hash value. */
    struct hmap_node *next;     /* Next in linked list. */
};
对hmap的列表遍历似乎通过如下宏来实现的
#define HMAP_FOR_EACH(NODE, MEMBER, HMAP)                               \
    for (ASSIGN_CONTAINER(NODE, hmap_first(HMAP), MEMBER);              \
         &(NODE)->MEMBER != NULL;                                       \
         ASSIGN_CONTAINER(NODE, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER))
其中ASSIGN_CONTAINER有三个参数,分别是一个输出指针,一个输入指针和一个成员。输入指针指向该成员,而输出指针获取指向包含有该成员的类型的地址。
所以遍历宏实际上,遍历了hmap的每个节点,并将包含该节点的数据结构逐个返回。

ofproto

ofproto_class

log消息

#define VLOG_FATAL(...) vlog_fatal(THIS_MODULE, __VA_ARGS__)
#define VLOG_ABORT(...) vlog_abort(THIS_MODULE, __VA_ARGS__)
#define VLOG_EMER(...) VLOG(VLL_EMER, __VA_ARGS__)
#define VLOG_ERR(...) VLOG(VLL_ERR, __VA_ARGS__)
#define VLOG_WARN(...) VLOG(VLL_WARN, __VA_ARGS__)
#define VLOG_INFO(...) VLOG(VLL_INFO, __VA_ARGS__)
#define VLOG_DBG(...) VLOG(VLL_DBG, __VA_ARGS__)


Sunday, November 11, 2012

OpenvSwitch 代码分析(一)

本小节简要分析datapath模块。
datapath模块实现了最底层交换机机制的基本过程,该模块跟sdn其实并没有太大关系。
接收网包-->查表-->转发;如果表中没有,则通过upcall扔给ovsd。

datapath模块的代码主要包括如下几个关键子模块:主文件datapath.h/c,vport的实现vport-(generic/gre/capwap/netdev/internal/patch),genl子模块等。


action.c中定义了对网包执行操作的各个接口。
包括对vlan头的处理,对skb执行一系列给定的操作,发出网包,发skb给用户态(ovsd),采样,设置包头各个域的属性等。


flow模块包括flow.h和flow.c。
定义维护交换机本地流表相关的数据结构和操作,包括流表结构的创建、更新、删除,对每条流的管理等。


genl-exec.h中定义了对genl的相关操作,包括
typedef int (*genl_exec_func_t)(void *data);
int genl_exec(genl_exec_func_t func, void *data);
int genl_exec_init(void);
void genl_exec_exit(void);
genl_exec_family的定义为
static struct genl_family genl_exec_family = {
.id = GENL_ID_GENERATE, //channel number: will be assigned by the controller
.name = "ovs_genl_exec",
.version = 1,
};
genl_exec_ops[]的定义为
static struct genl_ops genl_exec_ops[] = {
{
.cmd = GENL_EXEC_RUN, //reference the operation
.doit = genl_exec_cmd, //the callback function
.flags = CAP_NET_ADMIN,
},
};


关键的逻辑实现在vport子模块中。在vport.h/c中定义了抽象的vport结构。对外的对vport进行操作的接口如下
int ovs_vport_init(void);
void ovs_vport_exit(void);

struct vport *ovs_vport_add(const struct vport_parms *);
void ovs_vport_del(struct vport *);

struct vport *ovs_vport_locate(struct net *net, const char *name);

int ovs_vport_set_addr(struct vport *, const unsigned char *);
void ovs_vport_set_stats(struct vport *, struct ovs_vport_stats *);
void ovs_vport_get_stats(struct vport *, struct ovs_vport_stats *);

int ovs_vport_set_options(struct vport *, struct nlattr *options);
int ovs_vport_get_options(const struct vport *, struct sk_buff *);

int ovs_vport_send(struct vport *, struct sk_buff *);
这些接口对外提供统一的用户操作界面。部分并没有立刻定义,即使定义的接口中,大部分依次或者单独调用某种类型的vport上绑定的vport_ops中提供的接口,对所有支持的vport进行操作。例如,初始化过程中,实际上是初始化了一个vport_ops_list[],依次初始化不同类型的vport,并放到该list中。再比如ovs_vport_add接口实际上先进行查找,找到给定的类型之后,进行对应的操作。
而具体到某个vport,其能进行操作的接口在vport_ops结构体中声明,为
struct vport_ops {
enum ovs_vport_type type;
u32 flags;

/* Called at module init and exit respectively. */
int (*init)(void);
void (*exit)(void);

/* Called with RTNL lock. */
struct vport *(*create)(const struct vport_parms *);
void (*destroy)(struct vport *);

int (*set_options)(struct vport *, struct nlattr *);
int (*get_options)(const struct vport *, struct sk_buff *);

int (*set_addr)(struct vport *, const unsigned char *);

/* Called with rcu_read_lock or RTNL lock. */
const char *(*get_name)(const struct vport *);
const unsigned char *(*get_addr)(const struct vport *);
void (*get_config)(const struct vport *, void *);
struct kobject *(*get_kobj)(const struct vport *);

unsigned (*get_dev_flags)(const struct vport *);
int (*is_running)(const struct vport *);
unsigned char (*get_operstate)(const struct vport *);

int (*get_ifindex)(const struct vport *);

int (*get_mtu)(const struct vport *);

int (*send)(struct vport *, struct sk_buff *);
};
目前,vport_ops支持5种类型,分别为
static const struct vport_ops *base_vport_ops_list[] = {
&ovs_netdev_vport_ops,
&ovs_internal_vport_ops,
&ovs_patch_vport_ops,
&ovs_gre_vport_ops,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
&ovs_capwap_vport_ops,
#endif
};
以ovs_netdev_vport_ops为例,定义了一系列的函数指针。
const struct vport_ops ovs_netdev_vport_ops = {
.type = OVS_VPORT_TYPE_NETDEV,
.flags          = VPORT_F_REQUIRED,
.init = netdev_init,
.exit = netdev_exit,
.create = netdev_create,
.destroy = netdev_destroy,
.set_addr = ovs_netdev_set_addr,
.get_name = ovs_netdev_get_name,
.get_addr = ovs_netdev_get_addr,
.get_kobj = ovs_netdev_get_kobj,
.get_dev_flags = ovs_netdev_get_dev_flags,
.is_running = ovs_netdev_is_running,
.get_operstate = ovs_netdev_get_operstate,
.get_ifindex = ovs_netdev_get_ifindex,
.get_mtu = ovs_netdev_get_mtu,
.send = netdev_send,
};



Wednesday, October 24, 2012

SIGCOMM 2012 论文选读 - A Smart Pre-Classifier to Reduce Power Consumption of TCAMs for Multi-dimensional Packet Classification

session:
Session 7: Network Formalism and Algorithmics

abstract:
Ternary Content-Addressable Memories (TCAMs) has become the industrial standard for high-throughput packet classification. However, one major drawback of TCAMs is their high power consumption, which is becoming critical with the boom of data centers, the growing classifiers and the deployment of IPv6. In this paper, we propose a practical and efficient solution which introduces a smart pre-classifier to reduce power consumption of TCAMs for multi-dimensional packet classification. We reduce the dimension of the problem through the pre-classifier which pre-classifies a packet on two header fields, source and destination IP addresses. We then return to the high dimension problem where only a small portion of a TCAM is activated and searched for a given packet. The smart pre-classifier is built in a way such that a given packet matches at most one entry in the pre-classifier, which make commodity TCAMs sufficient to implement the pre-classifier. Furthermore, each rule is stored only once in one of the TCAM blocks, which avoids rule replication. The presented solution uses commodity TCAMs, and the proposed algorithms are easy to implement. Our scheme achieves a median power reduction of 91% and an average power reduction of 88% on real and synthetic classifiers respectively.

阅读笔记:


计算机科学领域有一个很有趣的现象。一方面新的问题和技术层出不穷,另一方面,少数的几个问题被数年甚至数十年的研究着。不管是搞什么类型的网络,这几个问题始终绕不开。这样久经考验过的问题,往往是如同一堆泥沙中淘得的些许金粒一般珍贵,值得人们细细揣摩。
幸运地,也同时不幸地,这样的问题并不太多。网包分类问题,算是一个经典。
网包分类问题,作为网络安全领域的核心技术,已经被研究了十几年。2000年到2004年这段时间,是这个问题最火热的时候。那个时候,恰是网络设备硬件性能急剧上升的时代,爆炸式增长的带宽给网络处理带来了诸多新的诉求。作为规则查找类最为全面和典型的问题,网包分类得到大家的关注并不稀奇。经过略显平淡的沉寂后,近些年,这一话题开始被重新探讨起来。sigcomm10(Efficut)、11(TreeCAM)、12(SmartPC)连续三年都有这方面的文章。一方面,是随着从性能到智能的跨越,规则的复杂性、规模、内在的逻辑特性越来越复杂,另一方面,也是对规则查找的灵活性上要求越来越高。
自问题提出以来,大致就分为两大区域,一是利用软件算法的解决方案,即采用精妙设计的算法来满足诸多的限制和性能需求,部分算法也能部署到一些通用网络处理硬件平台,学术界讨论这方面的 文章颇多;另一类是基于硬件(多为CAM)的方案,这一类多为工业界所关注,要基于硬件的特殊结构,达到设计的目标。
今天要谈的这篇文章,是基于TCAM平台,研究的目标已经不是传统的如何提高分类速度,而是如何来减少TCAM的功耗。
我们知道,TCAM是个很神奇的东西,可以实现软件梦寐以求的并行匹配。激活单元越多,性能越强大,而激活单元越多,功耗也变成了问题。一般的,实现100K条规则匹配,达到1Gbps的吞吐量,大概功耗是15W。在能源问题颇受关注的今天,自然降低功耗有着诸多的好处。
那么,如何来降低功耗呢?很自然的想法,我查找的时候不用激活那么多单元不就可以了么?之前有文章采用软件预过滤,首先把规则集分为子集,然后网包只需要在子集中进行查找,这样激活的单元自然仅限制在子集中了。本文的思路大致类似,将TCAM划分为三部分,Pre-filter、Specific、General。Pre-filter是根据规则集生成的粗分类规则,相当于划分规则集为子集的规则(文中只考虑了源地址和目标地址两个域)。Specific则存储具体的各个子集。General是在划分中,部分可能出现在多个子集中的规则,比如很多wildcard的情况就容易出现。
因此,在最终的分类过程,只需要激活Pre-filter、少量的General和Specific块,自然省了功耗,从实验效果上来看,大概平均能变为原先功耗的10%左右,甚至更低。
这个方案遗留了几个比较有趣的问题,并没有进行进一步的探讨,一个是划分子集的效率问题。目前的方案采用了逐条规则比较的简单方案,一方面是复杂度高(最坏复杂度为O(n^2)),另外是不清楚优化度有多高。这里完全可以借鉴一些软件算法,比如优化度很高的各类Cuts、DBS/D^2BS等。另外,采用多级分类,自然可能导致分类的性能有所下降,比如latency,这方面并没有相关数据。最后,预分类性能的好坏跟规则集内部特性关系甚大,可能构造某些特殊规则集来让最终性能变得很差。当然,这一点很多现有算法都无法保障,除非能给出最坏的复杂度情况分析。

Sunday, October 14, 2012

SIGCOMM 2012 论文选读 - Optimizing Cost and Performance for Content Multihoming

session:
Session 8: Streaming and Content Networking

abstract:
Many large content publishers usemultiple content distribution networks to deliver their content, and many commercial systems have become available to help a broader set of content publishers to benefit from using multiple distribution networks, which we refer to as content multihoming. In this paper, we conduct the first systematic study on optimizing content multihoming, by introducing novel algorithms to optimize both performance and cost for content multihoming. In particular, we design a novel, efficient algorithm to compute assignments of content objects to content distribution networks for content publishers, considering both cost and performance. We also design a novel, lightweight client adaptation algorithm executing at individual content viewers to achieve scalable, fine-grained, fast online adaptation to optimize the quality of experience (QoE) for individual viewers. We prove the optimality of our optimization algorithms and conduct systematic, extensive evaluations, using real charging data, content viewer demands, and performance data, to demonstrate the effectiveness of our algorithms. We show that our content multihoming algorithms reduce publishing cost by up to 40%. Our client algorithm executing in browsers reduces viewer QoE degradation by 51%.

阅读笔记

本文研究的是“content multihoming”问题,即如何利用多个CDN来高效、省钱地提供内容给用户。文章主要从两个方面(publisher、viewer)研究了在对于不同内容选择CDN时候的算法,来优化cost和performance。
publisher主要面临的问题是:多家CDN对不同的内容收费不同(流量方案、带宽方案、功能、性能),该如何选择一个CDN来发布内容?提出的解决算法称为CMO。
本地的viewer则面临着:内容在多家CDN的不同服务器上都有提供,该选择哪个来提高用户体验(Quality of Experience,QoE)?
文章将这些问题抽象为优化问题(常见思路)。
所建立的模型中,利用一个集中式的优化器(central Optimizer),来响应对CDN的内容请求。根据请求客户端功能的不同,将客户端分为被动客户端和主动客户端。前者对某个内容只能连接到一台CDN服务器,因此,只能在server端进行优化。后者对一个内容可能利用多个CDN服务器。这样,当某台服务器服务能力不够时,主动客户端还可以利用其它的服务器。
因此,研究包括两个方面的优化,一个是服务器端的优化,一个是主动客户端的优化。
服务端优化问题最终抽象的模型是一个带有约束条件的最小优化问题。优化目标是整体的cost,约束条件是满足用户需求(Sec 5.1)。解决这一类问题的一般思路是线性规划或者凸优化。然而,当问题的规模比较大的时候,线性规划计算代价不可接受;目标函数是凹函数,无法直接进行凸优化。因此,作者提出可以转化为其他问题(分配问题),并进一步提出了优化的分配方案(考虑分配后的输出空间,使用凹优化),降低解决问题的复杂度。
在转化过程中,也应用了凸优化和凹优化的理论。近些年,类似优化理论以及图论理论在网络领域应用的越来越多,但国内相关领域还比较少见这方面的研究文章。
在主动客户端上,还借用了TCP的AIMD机制来进行流量调整。
在实验方面,采用了来自真实CDN的大量traffic做模拟。验证了提出的方案确实能降低cost。
整体来看,本文将一个实际问题成功抽象为理论问题。虽然模型比较简单,但解决的过程中降低复杂度的手法值得借鉴,体现出比较深的数学功底,同时对于阅读者数学背景要求也比较高。但部分细节说的不是太清楚,例如引理1并没有给出证明。