OVS 源碼分析整理


OVS 核心代碼

  • datapath 目錄
  • ovs-switchd
  • OVS數(shù)據(jù)庫管理
  • ofproto

OVS 架構(gòu)


OVS 主要的數(shù)據(jù)結(jié)構(gòu)


數(shù)據(jù)結(jié)構(gòu)關(guān)系圖

主要的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)結(jié)構(gòu)的參數(shù)

數(shù)據(jù)結(jié)構(gòu)代碼

vvport

/**
- struct vport - one port within a datapath
- @rcu: RCU callback head for deferred destruction.
- @dp: Datapath to which this port belongs.
- @upcall_portids: RCU protected 'struct vport_portids'.
- @port_no: Index into @dp's @ports array.
- @hash_node: Element in @dev_table hash table in vport.c.
- @dp_hash_node: Element in @datapath->ports hash table in datapath.c.
- @ops: Class structure.
- @percpu_stats: Points to per-CPU statistics used and maintained by vport
- @err_stats: Points to error statistics used and maintained by vport
 */
struct vport {  
    struct rcu_head rcu; // 一種鎖機(jī)制  
    struct datapath *dp; // 網(wǎng)橋結(jié)構(gòu)體指針又碌,表示該端口是屬于哪個(gè)網(wǎng)橋的  
    u32 upcall_portid; // Netlink端口收到的數(shù)據(jù)包時(shí)使用的端口id  
    u16 port_no; // 端口號(hào)吊圾,唯一標(biāo)識(shí)該端口  
  
// 因?yàn)橐粋€(gè)網(wǎng)橋上有多個(gè)端口链烈,而這些端口都是用哈希鏈表來存儲(chǔ)的滩字,  
// 所以這是鏈表元素(里面沒有數(shù)據(jù)爷光,只有next和prev前驅(qū)后繼指針柱徙,數(shù)據(jù)部分就是vport結(jié)構(gòu)體中的其他成員)  
    struct hlist_node hash_node;   
    struct hlist_node dp_hash_node; // 這是網(wǎng)橋的哈希鏈表元素  
    const struct vport_ops *ops; // 這是端口結(jié)構(gòu)體的操作函數(shù)指針結(jié)構(gòu)體,結(jié)構(gòu)體里面存放了很多操作函數(shù)的函數(shù)指針  

    struct pcpu_tstats __percpu *percpu_stats;// vport指向每個(gè)cpu的統(tǒng)計(jì)數(shù)據(jù)使用和維護(hù)  
  
    spinlock_t stats_lock; // 自旋鎖畜普,防止異步操作期丰,保護(hù)下面的兩個(gè)成員  
    struct vport_err_stats err_stats; // 錯(cuò)誤狀態(tài)(錯(cuò)誤標(biāo)識(shí))指出錯(cuò)誤vport使用和維護(hù)的統(tǒng)計(jì)數(shù)字   
    struct ovs_vport_stats offset_stats; // 添加到實(shí)際統(tǒng)計(jì)數(shù)據(jù),部分原因是為了兼容  
};  

vport_parms

/**
- struct vport_parms - parameters for creating a new vport
 *
- @name: New vport's name.
- @type: New vport's type.
- @options: %OVS_VPORT_ATTR_OPTIONS attribute from Netlink message, %NULL if
- none was supplied.
- @dp: New vport's datapath.
- @port_no: New vport's port number.
 */
struct vport_parms {  
    const char *name; // 新端口的名字  
    enum ovs_vport_type type; // 新端口的類型(端口不僅僅只有一種類型,后面會(huì)分析到)   
    struct nlattr *options; // 這個(gè)沒怎么用到過吃挑,好像是從Netlink消息中得到的OVS_VPORT_ATTR_OPTIONS屬性  
  
    /* For ovs_vport_alloc(). */  
    struct datapath *dp; // 新的端口屬于哪個(gè)網(wǎng)橋的  
    u16 port_no; // 新端口的端口號(hào)  
    u32 upcall_portid; // 和Netlink通信時(shí)使用的端口id  
};  

vport_ops

/**
- struct vport_ops - definition of a type of virtual port
 *
- @type: %OVS_VPORT_TYPE_* value for this type of virtual port.
- @create: Create a new vport configured as specified.  On success returns
- a new vport allocated with ovs_vport_alloc(), otherwise an ERR_PTR() value.
- @destroy: Destroys a vport.  Must call vport_free() on the vport but not
- before an RCU grace period has elapsed.
- @set_options: Modify the configuration of an existing vport.  May be %NULL
- if modification is not supported.
- @get_options: Appends vport-specific attributes for the configuration of an
- existing vport to a &struct sk_buff.  May be %NULL for a vport that does not
- have any configuration.
- @get_name: Get the device's name.
- @send: Send a packet on the device.  Returns the length of the packet sent,
- zero for dropped packets or negative for error.
- @get_egress_tun_info: Get the egress tunnel 5-tuple and other info for
- a packet.
 */

struct vport_ops {  
    enum ovs_vport_type type; // 端口的類型  

    // 新vport端口的創(chuàng)建函數(shù)和銷毀端口的函數(shù)  
    struct vport *(*create)(const struct vport_parms *); // 根據(jù)指定的參數(shù)配置創(chuàng)建個(gè)新的vport咐汞,成功返回新端口指針  
    void (*destroy)(struct vport *); // 銷毀端口函數(shù)  
  
    // 得到和設(shè)置option成員函數(shù)  
    int (*set_options)(struct vport *, struct nlattr *);  
    int (*get_options)(const struct vport *, struct sk_buff *);  
  
    // 得到端口名稱和配置以及發(fā)送數(shù)據(jù)包函數(shù)     
    const char *(*get_name)(const struct vport *); //
    int (*send)(struct vport *, struct sk_buff *); // 發(fā)送數(shù)據(jù)包到設(shè)備上  
};  

vport_ops_list

/* List of statically compiled vport implementations.  Don't forget to also
- add yours to the list at the bottom of vport.h.
 */
static const struct vport_ops *vport_ops_list[] = {  
    &ovs_netdev_vport_ops,
    &ovs_internal_vport_ops,
    &ovs_geneve_vport_ops,
#if IS_ENABLED(CONFIG_NET_IPGRE_DEMUX)
    &ovs_gre_vport_ops,
    &ovs_gre64_vport_ops,
#endif
    &ovs_vxlan_vport_ops,
    &ovs_lisp_vport_ops,
};

datapath

struct datapath {  
    struct rcu_head rcu; // RCU調(diào)延遲破壞。  
    struct list_head list_node; // 網(wǎng)橋哈希鏈表元素儒鹿,里面只有next和prev前驅(qū)后繼指針化撕,數(shù)據(jù)時(shí)該結(jié)構(gòu)體其他成員  
  
    /* Flow table. */  
    struct flow_table __rcu *table;// 這是哈希流表,里面包含了哈希桶的地址指針约炎。該哈希表受_rcu機(jī)制保護(hù)  
  
    /* Switch ports. */  
    struct hlist_head *ports;// 一個(gè)網(wǎng)橋有多個(gè)端口植阴,這些端口都是用哈希鏈表來鏈接的  
  
    /* Stats. */  
    struct dp_stats_percpu __percpu *stats_percpu;  
  
#ifdef CONFIG_NET_NS  
    /* Network namespace ref. */  
    struct net *net;  
#endif  
};  

sw_flow_key

struct sw_flow_key {  
       // 這是隧道相關(guān)的變量  
    struct ovs_key_ipv4_tunnel tun_key;  /* Encapsulating tunnel key. */  
    struct {  
       // 包的優(yōu)先級(jí)  
        u32 priority; // 包的優(yōu)先級(jí)  
        u32 skb_mark; // 包的mark值  
        u16 in_port; // 包進(jìn)入的端口號(hào)  
    } phy; // 這是包的物理層信息結(jié)構(gòu)體提取到的  
    struct {  
        u8     src[ETH_ALEN]; // 源mac地址  
        u8     dst[ETH_ALEN]; // 目的mac地址  
        __be16 tci; // 這好像是局域網(wǎng)組號(hào)  
        __be16 type; // 包的類型,即:是IP包還是ARP包  
    } eth; // 這是包的二層幀頭信息結(jié)構(gòu)體提取到的   
    struct {  
        u8     proto; // 協(xié)議類型 TCP:6圾浅;UDP:17掠手;ARP類型用低8位表示  
        u8     tos; // 服務(wù)類型  
        u8     ttl; // 生存時(shí)間,經(jīng)過多少跳路由  
        u8     frag; // 一種OVS中特有的OVS_FRAG_TYPE_*.   
    } ip; // 這是包的三層IP頭信息結(jié)構(gòu)體提取到的  
       // 下面是共用體狸捕,有IPV4和IPV6兩個(gè)結(jié)構(gòu)喷鸽,為了后期使用IPV6適應(yīng)  
    union {  
        struct {  
            struct {  
                __be32 src; // 源IP地址  
                __be32 dst; // 目標(biāo)IP地址  
            } addr; // IP中地址信息  
                        // 這又是個(gè)共用體,有ARP包和TCP包(包含UDP)兩種  
            union {  
                struct {  
                    __be16 src; // 源端口灸拍,應(yīng)用層發(fā)送數(shù)據(jù)的端口  
                    __be16 dst; // 目的端口做祝,也是指應(yīng)用層傳輸數(shù)據(jù)端口  
                } tp; // TCP(包含UDP)地址提取  
                struct {  
                    u8 sha[ETH_ALEN]; // ARP頭中源Mac地址  
                    u8 tha[ETH_ALEN]; // ARP頭中目的Mac地址  
                } arp;ARP頭結(jié)構(gòu)地址提取  
            };  
        } ipv4;  
               // 下面是IPV6的相關(guān)信息砾省,基本和IPV4類似,這里不講  
        struct {  
            struct {  
                struct in6_addr src;    /* IPv6 source address. */  
                struct in6_addr dst;    /* IPv6 destination address. */  
            } addr;  
            __be32 label;           /* IPv6 flow label. */  
            struct {  
                __be16 src;     /* TCP/UDP source port. */  
                __be16 dst;     /* TCP/UDP destination port. */  
            } tp;  
            struct {  
                struct in6_addr target; /* ND target address. */  
                u8 sll[ETH_ALEN];   /* ND source link layer address. */  
                u8 tll[ETH_ALEN];   /* ND target link layer address. */  
            } nd;  
        } ipv6;  
    };  
};  

flow_table

struct flow_table {  
    struct flex_array *buckets; //哈希桶地址指針  
    unsigned int count, n_buckets; // 哈希桶個(gè)數(shù)  
    struct rcu_head rcu; // rcu包含機(jī)制  
    struct list_head *mask_list; // struct sw_flow_mask鏈表頭指針  
    int node_ver;  
    u32 hash_seed; //哈希算法需要的種子混槐,后期匹配時(shí)要用到  
    bool keep_flows; //是否保留流表項(xiàng)  
};  

};

sw_flow

struct sw_flow {  
    struct rcu_head rcu; // rcu保護(hù)機(jī)制  
    struct hlist_node hash_node[2]; // 兩個(gè)節(jié)點(diǎn)指針编兄,用來鏈接作用,前驅(qū)后繼指針  
    u32 hash; // hash值  
  
    struct sw_flow_key key; // 流表中的key值  
    struct sw_flow_key unmasked_key; // 也是流表中的key  
    struct sw_flow_mask *mask; // 要匹配的mask結(jié)構(gòu)體  
    struct sw_flow_actions __rcu *sf_acts; // 相應(yīng)的action動(dòng)作  
  
    spinlock_t lock; // 保護(hù)機(jī)制自旋鎖  
    unsigned long used; // 最后使用的時(shí)間  
    u64 packet_count; // 匹配過的數(shù)據(jù)包數(shù)量  
    u64 byte_count; // 匹配字節(jié)長度  
    u8 tcp_flags; // TCP標(biāo)識(shí)  
};    

sw_flow_mask

struct sw_flow_mask {  
        int ref_count;  
        struct rcu_head rcu;  
        struct list_head list;// mask鏈表元素声登,因?yàn)閙ask結(jié)構(gòu)是個(gè)雙鏈表結(jié)構(gòu)體  
        struct sw_flow_key_range range;// 操作范圍結(jié)構(gòu)體狠鸳,因?yàn)閗ey值中有些數(shù)據(jù)時(shí)不要用來匹配的  
        struct sw_flow_key key;// 要和數(shù)據(jù)包操作的key,將要被用來匹配的key值  
};  

datapath 模塊


datapath 簡介

datapath為 ovs內(nèi)核模塊悯嗓,負(fù)責(zé)執(zhí)行數(shù)據(jù)交換件舵,也就是把從接收端口收到的數(shù)據(jù)包在流表中進(jìn)行匹配,并執(zhí)行匹配到的動(dòng)作脯厨。

一個(gè)datapath可以對(duì)應(yīng)多個(gè)vport铅祸,一個(gè)vport類似物理交換機(jī)的端口概念。一個(gè)datapth關(guān)聯(lián)一個(gè)flow table俄认,一個(gè)flow table包含多個(gè)條目,每個(gè)條目包括兩個(gè)內(nèi)容:一個(gè)match/key和一個(gè)action


datapath 代碼

  • dp_init()
  • ovs_dp_process_received_packet()

dp_init 代碼

static int __init dp_init(void)
{
    int err;

    BUILD_BUG_ON(sizeof(struct ovs_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));

    pr_info("Open vSwitch switching datapath %s, built "__DATE__" "__TIME__"\n",
        VERSION);
    err = ovs_flow_init();//申請(qǐng) flow_cache和 flow_stats_cache
    if (err)
        goto error;

    err = ovs_vport_init();//vport 數(shù)據(jù)結(jié)構(gòu)初始化洪乍,申請(qǐng) dev_table
    if (err)
        goto error_flow_exit;

    err = register_pernet_device(&ovs_net_ops);//注冊(cè)網(wǎng)絡(luò)名字空間設(shè)備
    if (err)
        goto error_vport_exit;

    err = register_netdevice_notifier(&ovs_dp_device_notifier);//注冊(cè)設(shè)備通知事件
    if (err)
        goto error_netns_exit;

    err = dp_register_genl();//dp_register_genl 初始化 dp 相關(guān)的 netlink 的 family和ops
    if (err < 0)
        goto error_unreg_notifier;

    return 0;

error_unreg_notifier:  
    unregister_netdevice_notifier(&ovs_dp_device_notifier);
error_netns_exit:  
    unregister_pernet_device(&ovs_net_ops);
error_vport_exit:  
    ovs_vport_exit();
error_flow_exit:  
    ovs_flow_exit();
error:  
    return err;
}

vswitchd 模塊


vswitchd 代碼

set_program_name(argv)

設(shè)置程序名稱眯杏、版本、編譯日期等信息

proctitle_int(argh,argv)

復(fù)制出輸入的參數(shù)列表到新的存儲(chǔ)中壳澳,讓argv指向這塊內(nèi)存【主要是為了后面的proctitle_set()函數(shù)準(zhǔn)備】

service_start(&argc,&argv)

注冊(cè)回調(diào)和服務(wù)管理器出現(xiàn)故障錯(cuò)誤時(shí)操作的配置

remote=parse_options(argh,argv,&unixctl_path)

解析參數(shù)岂贩,其中unixctl_path存儲(chǔ)unixctrl域的sock名,作為接受外部控制命令的渠道巷波;而remote存儲(chǔ)連接到ovsdb的信息萎津,即連接到配置數(shù)據(jù)庫的sock名

ovsrec_init()

數(shù)據(jù)表結(jié)構(gòu)初始化,包括13張數(shù)據(jù)表

daemonize_start()

如果系統(tǒng)守護(hù)進(jìn)程被配置了抹镊,啟動(dòng)系統(tǒng)守護(hù)進(jìn)程锉屈,通過派生和在返回的子進(jìn)程。父進(jìn)程徘徊垮耳,直到
讓子進(jìn)程知道它完成啟動(dòng)成功(通過調(diào)用daemon_complete())颈渊,或者它沒有啟動(dòng)(用非零退出
退出代碼。

unixctl_server_create(unixctl_path,&unixctl)

創(chuàng)建一個(gè)unixctl_server(存放在unixctl)终佛,并監(jiān)聽在unixctl_path指定的punix路徑俊嗽,該路徑作為ovs-appctl發(fā)送命令給ovsd的通道

unixctl_command_register

注冊(cè)u(píng)nixctl命令

bridge_init

從remote數(shù)據(jù)庫獲取配置信息,并初始化bridge

主循環(huán)
memory_run()

運(yùn)行內(nèi)存監(jiān)視器铃彰,客戶端調(diào)用memory_should_report()绍豁。此函數(shù)以及該模塊的接口的剩余部分乌逐,僅被一個(gè)線程調(diào)用疆虚。

bridge_run

主要對(duì)網(wǎng)包進(jìn)行完整處理過程幕屹。包括完成必要的配置更新【在配置更新中會(huì)從數(shù)據(jù)庫中讀取配置信息痕鳍,生成必要的bridge和dp等數(shù)據(jù)結(jié)構(gòu)】

ovsdb_idl_run(idl);

處理了一批從'IDL'數(shù)據(jù)庫服務(wù)器的消息。這可能會(huì)導(dǎo)致IDL的內(nèi)容發(fā)生變化鬼佣∈还埃客戶端可以檢查與ovsdb_idl_get_seqno()。

system_stats_enable(false);

因?yàn)槲覀儾贿\(yùn)行system_stats的run()在這個(gè)進(jìn)程中有多個(gè)OVS-vswitchd守護(hù)進(jìn)程的現(xiàn)狀晶衷,關(guān)閉系統(tǒng)自動(dòng)統(tǒng)計(jì)信息收集蓝纲。

bridge_init_ofproto(cfg)

初始化ofproto庫。這僅需要執(zhí)行一次晌纫,但配置設(shè)置之后它必須要做的税迷。如果已經(jīng)出現(xiàn)了初始化,bridge_init_ofproto()立即返回锹漱。

bridge_run__(void)

#######ofproto_run中的p->ofproto_class->run(p)上的run函數(shù)依次調(diào)用函數(shù)

  • 必選調(diào)用dpif_run()處理所有注冊(cè)的netlink notifier的匯報(bào)事件
  • 必選調(diào)用run_fast()處理常見的周期事件箭养,包括對(duì)upcalls的處理等
  • 可選調(diào)用netflow_run()和sflow_run(),進(jìn)行對(duì)netflow和sflow的支持

可選調(diào)用較多哥牍,自行查看

#######connmr_run函數(shù)處理與控制器的周期性交互

  • 首先檢查是否存在in_band的控制器
  • 調(diào)用ofconn_run()處理對(duì)ofproto的協(xié)議解析和行動(dòng)
  • rconn_run(ofconn->rconn)負(fù)責(zé)連接到controller
  • rconn_recv(ofconn->rconn)負(fù)責(zé)從controller收取消息
  • handle_openflow()最終調(diào)用handle_openflow__()(ofproto/ofproto.c)來完成對(duì)各個(gè)Of消息的處理

以 PACKET_OUT消息為例毕泌,調(diào)用的是handle_packout 函數(shù)

首先調(diào)用ofputil_decode_packet_out()對(duì)of消息進(jìn)行解析
調(diào)用ofconn_pktbuf_retrieve()獲取payload信息
利用ofproto_class->packet_out()將網(wǎng)包發(fā)出
packet_out()
{   
    ofproto_dpif_execute_actions()
        {
        dpif_flow_stats_extract() 流狀態(tài)提取
        xlate_actions()將ofpacts轉(zhuǎn)化為dp的行動(dòng)格式odp_actions
        調(diào)用dpif_execute()函數(shù)讓dpif執(zhí)行給定的action構(gòu)建OVS_PACKET_CMD_EXECUTE netlink消息并發(fā)給datapath
        datapath中將對(duì)應(yīng)調(diào)用ovs_packet_cmd_execute函數(shù)處理收到的nlmsg
        ovs_packet_cmd_execute的調(diào)用過程
        ovs_packet_cmd_execute()->ovs_execute_actions()->do_execute_actions()
        }
}
重新配置SSL

通過主循環(huán)每一次遍歷,而不是只當(dāng)數(shù)據(jù)庫的變化嗅辣,因?yàn)槊荑€和證書文件的內(nèi)容可以更改在數(shù)據(jù)庫不更改中撼泛。我們完成這些在bridge_reconfigure()之前,因?yàn)樵摴δ芸赡軙?huì)啟動(dòng)SSL連接之前做到這一點(diǎn)澡谭,因此需要SSL進(jìn)行配置愿题。

對(duì)all_bidge上的每個(gè)bridge的ofproto執(zhí)行ofproto_run()
netdev_run()

如果打開了一些netted,則執(zhí)行對(duì)應(yīng)在netdev_classes上定義的每個(gè)netdev_class實(shí)體蛙奖,調(diào)用它們的run()包括處理網(wǎng)卡注冊(cè)的各個(gè)通知事件潘酗,獲取網(wǎng)卡的最新的信息等

unixctl_server_run(unixctl)

從unixctl指定的server中獲取來自ovs-appctl發(fā)出的命令數(shù)據(jù),并執(zhí)行對(duì)應(yīng)的命令

循環(huán)等待事件處理

包括memory雁仲、bridge仔夺、unixctl_server、netted等事件攒砖,被poll_fd_wait()注冊(cè)的最短時(shí)間

poll_block(void)

阻塞知道之前被poll_fd_wait()注冊(cè)過的事件發(fā)生囚灼,或者等待時(shí)間超過poll_timer_wait()注冊(cè)的最短時(shí)間

清理工作

退出bridge,關(guān)閉unixctl連接

動(dòng)態(tài)過程分析


數(shù)據(jù)流流向


一般的數(shù)據(jù)包在 Linux網(wǎng)絡(luò)協(xié)議中的流向?yàn)楹谏^流向:網(wǎng)卡收到數(shù)據(jù)包后層層網(wǎng)上分析祭衩,最后離開內(nèi)核態(tài)灶体,把數(shù)據(jù)傳送到用戶態(tài)。
有 OVS時(shí):數(shù)據(jù)流流向不同
(1)創(chuàng)網(wǎng)橋(ovs-vsctl add-br br0)
(2)綁網(wǎng)卡(ovs-vsctl add-port bro eth0 默認(rèn)為 eth0)
數(shù)據(jù)流:
從網(wǎng)卡 eth0到 ovs 的 vport 進(jìn)入OVS掐暮,根據(jù) key值流表匹配
成功——>執(zhí)行流表 action
失敗——>upcall處理

添加網(wǎng)橋


1. 鍵入命令ovs-vsctl add-br testBR
2. 內(nèi)核中的 openvswitch.ko 收到一個(gè)添加網(wǎng)橋的命令時(shí)候——即收到 OVS_DATAPATH_FAMILY通道的 OVS_DP_CMD_NEW命令蝎抽。該命令綁定的回調(diào)函數(shù)為 ovs_dp_cmd_new
3. ovs_dp_cmd_new 函數(shù)除了初始化 dp 結(jié)構(gòu)外,調(diào)用 new_vport 函數(shù)來生成新的 vport
4. new_vport 函數(shù)調(diào)用 ovs_vport_add()來嘗試生成一個(gè)新的 vport
5. ovs_vport_add()函數(shù)會(huì)檢查 vport 類型(通過 vport_ops_list[]數(shù)組),并調(diào)用相關(guān)的 create()函數(shù)來生成 vport 結(jié)構(gòu)
6. 當(dāng)dp是網(wǎng)絡(luò)設(shè)備時(shí)(vport_netdev.c)樟结,最終由 ovs_vport_add()函數(shù)調(diào)用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
7. netdev_create()函數(shù)最關(guān)鍵的一步是注冊(cè)了收到網(wǎng)包時(shí)的回調(diào)函數(shù)
8. err=netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);
9. 操作是將 netdev_vport->dev 收到網(wǎng)包時(shí)的相關(guān)數(shù)據(jù)由 netdev_frame_hook()函數(shù)來處理养交,都是些輔助處理,依次調(diào)用各處理函數(shù)瓢宦,在 netdev_port_receive()【這里會(huì)進(jìn)行數(shù)據(jù)包的拷貝碎连,避免損壞】進(jìn)入 ovs_vport_receive()回到 vport.c,從 ovs_dp_process_receive_packet()回到 datapath.c驮履,進(jìn)行統(tǒng)一處理
10. 流程:netdev_frame_hook()->netdev_port_receive->ovs_vport_receive->ovs_dp_process_received_packet()
11. net_port_receive()首先檢測是否 skb 被共享鱼辙,若是則得到 packet 的拷貝。
12. net_port_receive()其調(diào)用ovs_vport_receive()玫镐,檢查包的校驗(yàn)和倒戏,然后交付給我們的vport通用層來處理。

netdev_rx_handler_register()
linux 內(nèi)核實(shí)現(xiàn)的一個(gè)函數(shù)恐似,為網(wǎng)絡(luò)設(shè)備 dev 注冊(cè)一個(gè)handler_frame_hook,rx_handle_data 指向的是handler_frame_hook 內(nèi)存的區(qū)域杜跷,這個(gè) handler 以后會(huì)被__netif_receive_skb()呼叫,就是說netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);在收到packet 后會(huì)調(diào)用 netdev_frame_hook 函數(shù)處理

收包處理


1.ovs_vport_receive_packets()調(diào)用ovs_flow_extract基于skb生成key值矫夷,并檢查是否有錯(cuò),然后調(diào)用ovs_dp_process_packet葛闷。交付給datapath處理
2.ovs_flow_tbl_lookup_stats∷海基于前面生成的key值進(jìn)行流表查找淑趾,返回匹配的流表項(xiàng),結(jié)構(gòu)為sw_flow蔓彩。 
3.若不存在匹配治笨,則調(diào)用ovs_dp_upcall上傳至userspace進(jìn)行匹配驳概。 (包括包和key都要上傳) 
若存在匹配赤嚼,則直接調(diào)用ovs_execute_actions執(zhí)行對(duì)應(yīng)的action,比如添加vlan頭顺又,轉(zhuǎn)發(fā)到某個(gè)port等更卒。

流表匹配


1. flow_lookup()查找對(duì)應(yīng)的流表項(xiàng)
2. for 循環(huán)調(diào)用 rcu_dereference_ovs 對(duì)流表結(jié)構(gòu)體中的 mask_list 成員遍歷,找到對(duì)應(yīng)的的 成員
3. flow=masked_flow_lookup()遍歷進(jìn)行下一級(jí) hmap查找稚照,找到為止
4. 進(jìn)入 包含函數(shù) ovs_flow_mask_key(&masked_key,unmasked,mask)蹂空,將最開始提取的 Key 值和 mask 的 key 值進(jìn)行“與”操作,結(jié)果存放在 masked_key 中果录,用來得到后面的 Hash 值
5. hash=flow_hash(&masked_key,key_start,key_end)key 值的匹配字段只有部分
6. ovs_vport_add()函數(shù)會(huì)檢查 vport 類型(通過 vport_ops_list[]數(shù)組)上枕,并調(diào)用相關(guān)的 create()函數(shù)來生成 vport 結(jié)構(gòu)
7. 可見,當(dāng) dp 時(shí)網(wǎng)絡(luò)設(shè)備時(shí)(vport_netdev.c)弱恒,最終由 ovs_vport_add()函數(shù)調(diào)用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
8. netdev_vport->dev 收到網(wǎng)包時(shí)的相關(guān)數(shù)據(jù)由 netdev_frame_hook()函數(shù)來處理辨萍,都是些輔助處理,依次調(diào)用各處理函數(shù)返弹,在 netdev_port_receive()【這里會(huì)進(jìn)行數(shù)據(jù)包的拷貝锈玉,避免損壞】進(jìn)入 ovs_vport_receive()回到 vport.c爪飘,從 ovs_dp_process_receive_packet()回到 datapath.c,進(jìn)行統(tǒng)一處理

upcall 消息處理


1. ovs_dp_upcall()首先調(diào)用 err=queue_userspace_packet()將信息排隊(duì)發(fā)到用戶空間去
2. dp_ifindex=get_dpifindex(dp)獲取網(wǎng)卡設(shè)備索引號(hào)
3. 調(diào)整 VLAN的 MAC 地址頭指針
4. 網(wǎng)絡(luò)鏈路屬性拉背,如果不需要填充則調(diào)用此函數(shù)
5. len=upcall_msg_size()师崎,獲得 upcall 發(fā)送消息的大小
6. user_skb=genlmsg_new_unicast,創(chuàng)建一個(gè)新的 netlink 消息
7. upcall=genlmsg_put()增加一個(gè)新的 netlink 消息到 skb
8. err=genlmsg_unicast(),發(fā)送消息到用戶空間去處理

相關(guān)內(nèi)容


Linux RCU鎖機(jī)制分析


RCU是linux的新型鎖機(jī)制(RCU是在linux 2.6內(nèi)核版本中開始正式使用)

傳統(tǒng)讀寫鎖rwlock運(yùn)行機(jī)制

讀鎖(共享鎖):若請(qǐng)求是讀數(shù)據(jù)時(shí),上讀鎖椅棺,多個(gè)讀鎖不排斥(即訪問數(shù)據(jù)的讀者上限未達(dá)到時(shí)犁罩,可以對(duì)數(shù)據(jù)區(qū)再上讀鎖),若請(qǐng)求是寫數(shù)據(jù)時(shí),不能馬上上寫鎖,得等數(shù)據(jù)區(qū)的所有鎖(包括讀鎖和寫鎖)都釋放才能上寫鎖

寫鎖(獨(dú)占鎖):要操作的數(shù)據(jù)區(qū)上了寫鎖,不管什么請(qǐng)求都要等到數(shù)據(jù)區(qū)的寫鎖釋放掉后才能上鎖訪問

RCU 鎖機(jī)制——RCU(read\copy\update)對(duì)數(shù)據(jù)的讀土陪、復(fù)制昼汗、修改的保護(hù)鎖機(jī)制

寫數(shù)據(jù):(1)不需讀寫鎖那樣等待所有鎖釋放【拷貝一份數(shù)據(jù)區(qū)的副本,在副本中修改鬼雀,修改完后顷窒,用副本替代原來的數(shù)據(jù)區(qū)】(2)替換的時(shí)候需要讀寫鎖上寫鎖那樣,等到數(shù)據(jù)區(qū)上所有訪問者退出后源哩,才進(jìn)行數(shù)據(jù)的替換
(3)RCU鎖可以有多個(gè)寫者鞋吉,拷貝多份數(shù)據(jù)區(qū)數(shù)據(jù),修改后励烦,各個(gè)數(shù)據(jù)區(qū)陸續(xù)替換掉原數(shù)據(jù)區(qū)內(nèi)容
讀數(shù)據(jù):不用上任何鎖谓着,幾乎不需要等待(讀寫鎖需要等寫鎖釋放)就可以直接訪問數(shù)據(jù)
,“幾乎”,因?yàn)閷憯?shù)據(jù)中替換原數(shù)據(jù)坛掠,只需修改個(gè)指針赊锚,消耗的時(shí)間幾乎不算

RCU 鎖機(jī)制特性

? 允許多個(gè)讀者和多個(gè)寫者同時(shí)訪問共享數(shù)據(jù)區(qū)內(nèi)容
? 對(duì)多讀少寫的數(shù)據(jù)來說非常高效,可以減少 CPU 開銷
? 寫數(shù)據(jù)操作多了屉栓,就不如讀寫鎖那么好了舷蒲,因?yàn)镽CU 對(duì)寫數(shù)據(jù)開銷大,需要拷貝數(shù)據(jù)友多,修改牲平,等待替換

RCU 機(jī)制 API

rcu_read_lock();
? 這不是和上讀寫鎖的那種上鎖域滥,這僅僅只是標(biāo)識(shí)了臨界區(qū)的開始位置纵柿。表明在臨界區(qū)內(nèi)不能阻塞和休眠,也不能讓寫者進(jìn)行數(shù)據(jù)的替換(其實(shí)這功能遠(yuǎn)不止這些)启绰。rcu _read_unlock()則是和上面rcu_read_lock()對(duì)應(yīng)的昂儒,用來界定一個(gè)臨界區(qū)(就是要用鎖保護(hù)起來的數(shù)據(jù)區(qū))。
synchronize_rcu()委可;
? 當(dāng)該函數(shù)被一個(gè)CPU調(diào)用時(shí)(一般是有寫者替換數(shù)據(jù)時(shí)調(diào)用)渊跋,而其他的CPU都在RCU保護(hù)的臨界區(qū)讀數(shù)據(jù),那么synchronize_rcu()將會(huì)保證阻塞寫者,直到所有其它讀數(shù)據(jù)的CPU都退出臨界區(qū)時(shí)刹枉,才中止阻塞叽唱,讓寫著開始替換數(shù)據(jù)。該函數(shù)作用就是保證在替換數(shù)據(jù)前微宝,所有讀數(shù)據(jù)的CPU能夠安全的退出臨界區(qū)棺亭。同樣,還有個(gè)call_rcu()函數(shù)功能也是類似的蟋软。如果call_rcu()被一個(gè)CPU調(diào)用镶摘,而其他的CPU都在RCU保護(hù)的臨界區(qū)內(nèi)讀數(shù)據(jù),相應(yīng)的RCU回調(diào)的調(diào)用將被推遲到其他讀臨界區(qū)數(shù)據(jù)的CPU全部安全退出后才執(zhí)行(可以看linux內(nèi)核源文件的注釋岳守,在Rcupdate.h文件中rcu_read_look()函數(shù)前面的注釋)凄敢。
rcu_dereference();
? 獲取在一個(gè)RCU保護(hù)的指針湿痢,指向RCU讀端臨界區(qū)涝缝。他的指針以后可能會(huì)被安全地解除引用。說到底就是一個(gè)RCU保護(hù)指針譬重。
list_add_rcu()拒逮;
? 往RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu)中添加一個(gè)數(shù)據(jù)節(jié)點(diǎn)進(jìn)去。這個(gè)和一般的往鏈表中增加一個(gè)節(jié)點(diǎn)操作是類似的臀规,唯一不同的是多了這條代碼:rcu_assign_pointer(prev->next, new); 代碼大概含義:分配指向一個(gè)新初始化的結(jié)構(gòu)指針滩援,將由RCU讀端臨界區(qū)被解除引用,返回指定的值塔嬉。
list_for_each_entry_rcu()玩徊;
? 這是個(gè)遍歷RCU鏈表的操作,和一般的鏈表遍歷差不多谨究。不同點(diǎn)就是必須要進(jìn)入RCU保護(hù)的CPU(即:調(diào)用了rcu_read_lock()函數(shù)的CPU)才能調(diào)用這個(gè)操作恩袱,可以和其他CPU共同遍歷這個(gè)RCU鏈表。

Generic Netlink 通信機(jī)制


    +---------------------+      +---------------------+
      | (3) application "A" |      | (3) application "B" |
      +------+--------------+      +--------------+------+
             |                                    |
             \                                    /
              \                                  /
               |                                |
       +-------+--------------------------------+-------+
       |        :                               :       |   user-space
  =====+        :   (5)  Kernel socket API      :       +================
       |        :                               :       |   kernel-space
       +--------+-------------------------------+-------+
                |                               |
          +-----+-------------------------------+----+
          |        (1)  Netlink subsystem            |
          +---------------------+--------------------+
                                |
          +---------------------+--------------------+
          |       (2) Generic Netlink bus            |
          +--+--------------------------+-------+----+
             |                          |       |
     +-------+---------+                |       |
     |  (4) Controller |               /         \
     +-----------------+              /           \
                                      |           |
                   +------------------+--+     +--+------------------+
                   | (3) kernel user "X" |     | (3) kernel user "Y" |
                   +---------------------+     +---------------------+

(5)API向用戶空間和內(nèi)核空間分別提供接口记盒。
Netlink子系統(tǒng)(1)是所有g(shù)enl通信的基礎(chǔ)憎蛤。Netlink子系統(tǒng)中收到的所有Generic類型的netlink數(shù)據(jù)都被送到genl總線(2)上外傅;從內(nèi)核發(fā)出的數(shù)據(jù)也經(jīng)由genl總線送至netlink子系統(tǒng)纪吮,再打包送至用戶空間。
Generic Netlink控制器(4)作為內(nèi)核的一部分萎胰,負(fù)責(zé)動(dòng)態(tài)地分配genl通道(即genl family id)碾盟,并管理genl任務(wù)。genl控制器是一個(gè)特殊的genl內(nèi)核用戶技竟,它負(fù)責(zé)監(jiān)聽genl bus上的通信通道冰肴。genl通信建立在一系列的通信通道的基礎(chǔ)上,每個(gè)genl family對(duì)應(yīng)多個(gè)通道,這些通道由genl控制器動(dòng)態(tài)分配熙尉。

Generic Netlink相關(guān)結(jié)構(gòu)體

genl family

Generic Netlink是基于客戶端-服務(wù)端模型的通信機(jī)制联逻。服務(wù)端注冊(cè)family(family是對(duì)genl服務(wù)的各項(xiàng)定義的集合)〖焯担控制器和客戶端都通過已注冊(cè)的信息與服務(wù)端通信包归。
genl family的結(jié)構(gòu)體如下:

struct genl_family
{
      unsigned int            id;
      unsigned int            hdrsize;
      char                    name[GENL_NAMSIZ];
      unsigned int            version;
      unsigned int            maxattr;
      struct nlattr **        attrbuf;
      struct list_head        ops_list;
      struct list_head        family_list;
};
  • id: family id。當(dāng)新注冊(cè)一個(gè)family的時(shí)候铅歼,應(yīng)該用GENL_ID_GENERATE宏(0x0)公壤,表示請(qǐng)控制器自動(dòng)為family分配的一個(gè)id。0x10保留供genl控制器使用椎椰。
  • hdrsize: 用戶自定議頭部長度厦幅。即圖2中User Msg的長度。如果沒有用戶自定義頭部慨飘,這個(gè)值被賦為0确憨。
  • version: 版本號(hào),一般填1即可瓤的。
  • name: family名缚态,要求不同的family使用不同的名字。以便控制器進(jìn)行正確的查找堤瘤。
  • maxattr:genl使用netlink標(biāo)準(zhǔn)的attr來傳輸數(shù)據(jù)玫芦。此字段定義了最大attr類型數(shù)。(注意:不是一次傳輸多少個(gè)attr本辐,而是一共有多少種attr桥帆,因此,這個(gè)值可以被設(shè)為0慎皱,為0代表不區(qū)分所收到的數(shù)據(jù)的attr type)老虫。在接收數(shù)據(jù)時(shí),可以根據(jù)attr type茫多,獲得指定的attr type的數(shù)據(jù)在整體數(shù)據(jù)中的位置祈匙。
  • struct nlattr **attrbuf
  • struct list_head ops_list
  • struct list_head family_list
    以上的三個(gè)字段為私有字段,由系統(tǒng)自動(dòng)配置天揖,開發(fā)者不需要做配置夺欲。
genl 報(bào)文格式
genl_ops 結(jié)構(gòu)體
struct genl_ops
{
      u8                      cmd;
      unsigned int            flags;
      struct nla_policy       *policy;
      int                     (*doit)(struct sk_buff *skb,
                                      struct genl_info *info);
      int                     (*dumpit)(struct sk_buff *skb,
                                          struct netlink_callback *cb);
      struct list_head        ops_list;
};

cmd: 命令名。用于識(shí)別各genl_ops
flag: 各種設(shè)置屬性今膊,以“或”連接些阅。在需要admin特權(quán)級(jí)別時(shí),使用GENL_ADMIN_PERM
policy:定義了attr規(guī)則斑唬。如果此指針非空市埋,genl在觸發(fā)事件處理程序之前黎泣,會(huì)使用這個(gè)字段來對(duì)幀中的attr做校驗(yàn)(見nlmsg_parse函數(shù))。該字段可以為空缤谎,表示在觸發(fā)事件處理程序之前抒倚,不做校驗(yàn)。

doit:這是一個(gè)回調(diào)函數(shù)坷澡。在generic netlink收到數(shù)據(jù)時(shí)觸發(fā)衡便,運(yùn)行在進(jìn)程上下文。
doit傳入兩個(gè)參數(shù)洋访,skb為觸發(fā)此回調(diào)函數(shù)的socket buffer镣陕。第二個(gè)參數(shù)是一個(gè)genl_info結(jié)構(gòu)體

struct genl_info
        {
             u32                     snd_seq;
             u32                     snd_pid;
             struct nlmsghdr *       nlhdr;
             struct genlmsghdr *     genlhdr;
             void *                  userhdr;
             struct nlattr **        attrs;
        };
  • snd_seq:發(fā)送序號(hào)
  • snd_pid:發(fā)送客戶端的PID
  • nlhdr:netlink header的指針
  • genlmsghdr:genl頭部的指針(即family頭部)
  • userhdr:用戶自定義頭部指針
  • attrs:attrs,如果定義了genl_ops->policy姻政,這里的attrs是被policy過濾以后的結(jié)果呆抑。在完成了操作以后,如果執(zhí)行正確汁展,返回0鹊碍;否則,返回一個(gè)負(fù)數(shù)食绿。負(fù)數(shù)的返回值會(huì)觸發(fā)NLMSG_ERROR消息侈咕。當(dāng)genl_ops的flag標(biāo)志被添加了NLMSG_ERROR時(shí),即使doit返回0器紧,也會(huì)觸發(fā)NLMSG_ERROR消息耀销。

dumpit
這是一個(gè)回調(diào)函數(shù),當(dāng)genl_ops的flag標(biāo)志被添加了NLM_F_DUMP以后铲汪,每次收到genl消息即會(huì)回觸發(fā)這個(gè)函數(shù)熊尉。dumpit與doit的區(qū)別是:dumpit的第一個(gè)參數(shù)skb不會(huì)攜帶從客戶端發(fā)來的數(shù)據(jù)。相反地掌腰,開發(fā)者應(yīng)該在skb中填入需要傳給客戶端的數(shù)據(jù)狰住,然后,并skb的數(shù)據(jù)長度(可以用skb->len)return齿梁。skb中攜帶的數(shù)據(jù)會(huì)被自動(dòng)送到客戶端催植。只要dumpit的返回值大于0,dumpit函數(shù)就會(huì)再次被調(diào)用勺择,并被要求在skb中填入數(shù)據(jù)创南。當(dāng)服務(wù)端沒有數(shù)據(jù)要傳給客戶端時(shí),dumpit要返回0酵幕。如果函數(shù)中出錯(cuò)扰藕,要求返回一個(gè)負(fù)值缓苛。關(guān)于doit和dumpit的觸發(fā)過程芳撒,可以查看源碼中的genl_rcv_msg函數(shù)邓深。

ops_list
為私有字段,由系統(tǒng)自動(dòng)配置笔刹,開發(fā)者不需要做配置芥备。

Generic Netlink 服務(wù)端(內(nèi)核)初始化

初始化Generic Netlink的過程分為以下四步:定義family,定義operation舌菜,注冊(cè)family萌壳,注冊(cè)operation。
Datapath使用 generic netlink

在 dp_init()函數(shù)(datapath.c)中,調(diào)用 dp_register_genl()完成對(duì)四種類型的 family 以 及相應(yīng)操作的注冊(cè),包括 datapath日月、vport袱瓮、flow 和 packet。前三種 family,都對(duì)應(yīng)四種操 作都包括 NEW爱咬、DEL尺借、GET、SET,而 packet 的操作僅為 EXECUTE精拟。
這些 family 和操作的定義均在 datapath.c 中燎斩。 以 flow family 為例。代碼為

static const struct nla_policy flow_policy[OVS_FLOW_ATTR_MAX + 1] = { 
    [OVS_FLOW_ATTR_KEY] = { .type = NLA_NESTED }, 
    [OVS_FLOW_ATTR_ACTIONS] = { .type = NLA_NESTED }, 
    [OVS_FLOW_ATTR_CLEAR] = { .type = NLA_FLAG },
};
static struct genl_family dp_flow_genl_family = { 
    .id = GENL_ID_GENERATE,
    .hdrsize = sizeof(struct ovs_header),
    .name = OVS_FLOW_FAMILY,
    .version = OVS_FLOW_VERSION,
    .maxattr = OVS_FLOW_ATTR_MAX, SET_NETNSOK
};

綁定的 ops 的定義

static struct genl_ops dp_flow_genl_ops[] = { 
    { .cmd = OVS_FLOW_CMD_NEW,
      .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */  
      .policy = flow_policy,
      .doit = ovs_flow_cmd_new_or_set
    },
    { .cmd = OVS_FLOW_CMD_DEL,
      .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ 
      .policy = flow_policy,
      .doit = ovs_flow_cmd_del 
    },
    { .cmd = OVS_FLOW_CMD_GET,
      .flags = 0, /* OK for unprivileged users. */ 
      .policy = flow_policy,
      .doit = ovs_flow_cmd_get,
      .dumpit = ovs_flow_cmd_dump
    },
    { .cmd = OVS_FLOW_CMD_SET,
      .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ 
      .policy = flow_policy,
      .doit = ovs_flow_cmd_new_or_set,
    }, 
};

ovsd 使用 netlink
ovsd 對(duì)于 netlink 的實(shí)現(xiàn),主要在 lib/netlink-socket.c 文件中蜂绎。而對(duì)這些 netlink 操作的 調(diào)用,主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 為例)中對(duì)于各個(gè)行為的處理,各 種可能的消息類型在 datapath 模塊中事先進(jìn)行了內(nèi)核注冊(cè)栅表。
datapath 中對(duì) netlink family 類型進(jìn)行了注冊(cè),ovsd 在使用這些 netlink family 之前需要 獲取它們的信息,這一過程主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 為例), dpif_linux_init()函數(shù)。代碼為

static int dpif_linux_init(void) 
{
    static int error = -1;
    if (error < 0) {
        unsigned int ovs_vport_mcgroup;
        error = nl_lookup_genl_family(OVS_DATAPATH_FAMILY,&ovs_datapath_family);
        if (error) {
            VLOG_ERR("Generic Netlink family '%s' does not exist. ""The Open vSwitch kernel module is probably not loaded.",OVS_DATAPATH_FAMILY); }
        if (!error) {
            error = nl_lookup_genl_family(OVS_VPORT_FAMILY, &ovs_vport_family);}
        if (!error) {
            error = nl_lookup_genl_family(OVS_FLOW_FAMILY, &ovs_flow_family); }
        if (!error) {
            error = nl_lookup_genl_family(OVS_PACKET_FAMILY,&ovs_packet_family);}
        if (!error) {
            error = nl_sock_create(NETLINK_GENERIC, &genl_sock); }
        if (!error) {
            error = nl_lookup_genl_mcgroup(OVS_VPORT_FAMILY, OVS_VPORT_MCGROUP,&ovs_vport_mcgroup, OVS_VPORT_MCGROUP_FALLBACK_ID);}
        if (!error) {
            static struct dpif_linux_vport vport;
            nln = nln_create(NETLINK_GENERIC, ovs_vport_mcgroup,
            dpif_linux_nln_parse, &vport);}
     }
    return error; 
}

完成這些查找后,ovsd 即可利用 dpif 中的 api,通過發(fā)出這些 netlink 消息給 datapath 實(shí)現(xiàn)對(duì) datapath 的操作师枣。

相關(guān)的中間層 API 定義主要在 dpif_class(位于 lib/dpif-provider.h)的抽象類型中
dpif_class結(jié)構(gòu)體的注釋:

/* Datapath interface class structure, to be defined by each implementation of
-a datapath interface.
 *
- These functions return 0 if successful or a positive errno value on failure,
- except where otherwise noted.
 *
- These functions are expected to execute synchronously, that is, to block as
- necessary to obtain a result.  Thus, they may not return EAGAIN or
- EWOULDBLOCK or EINPROGRESS.  We may relax this requirement in the future if
- and when we encounter performance problems. */

一共有兩種dpif_class實(shí)例化類型怪瓶,分別為dpif_netlink_class和dpif_netdev_class。dpif_netlink_class表示的是通過netlink和本地的datapath通信践美,而dpif_netdev_class通過網(wǎng)絡(luò)協(xié)議和遠(yuǎn)程的datapath通信
ovsd使用netlink進(jìn)行消息發(fā)送的過程:


參考內(nèi)容


庾志輝OVS 專欄
http://blog.csdn.net/column/details/openvswitch.html
datapath 模塊分析
http://vinllen.com/ovs-datapathbi-ji/
Baohua Yang的OpenvSwitch 代碼分析
OpenvSwitch2.4.0源碼解讀
http://www.cnblogs.com/cotyb/p/5103035.html?utm_source=tuicool&utm_medium=referral
GenerRic Netlink 詳解
http://www.tuicool.com/articles/jE7nim

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劳殖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拨脉,更是在濱河造成了極大的恐慌哆姻,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玫膀,死亡現(xiàn)場離奇詭異矛缨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)帖旨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門箕昭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人解阅,你說我怎么就攤上這事落竹。” “怎么了货抄?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵述召,是天一觀的道長朱转。 經(jīng)常有香客問我,道長积暖,這世上最難降的妖魔是什么藤为? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮夺刑,結(jié)果婚禮上缅疟,老公的妹妹穿的比我還像新娘。我一直安慰自己遍愿,他們只是感情好存淫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沼填,像睡著了一般纫雁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倾哺,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天轧邪,我揣著相機(jī)與錄音,去河邊找鬼羞海。 笑死忌愚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的却邓。 我是一名探鬼主播硕糊,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腊徙!你這毒婦竟也來了简十?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤撬腾,失蹤者是張志新(化名)和其女友劉穎螟蝙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體民傻,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胰默,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漓踢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牵署。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖喧半,靈堂內(nèi)的尸體忽然破棺而出奴迅,到底是詐尸還是另有隱情,我是刑警寧澤挺据,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布取具,位于F島的核電站脖隶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏者填。R本人自食惡果不足惜浩村,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一做葵、第九天 我趴在偏房一處隱蔽的房頂上張望占哟。 院中可真熱鬧,春花似錦酿矢、人聲如沸榨乎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜜暑。三九已至,卻和暖如春策肝,著一層夾襖步出監(jiān)牢的瞬間肛捍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工之众, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拙毫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓棺禾,卻偏偏與公主長得像缀蹄,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膘婶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容