目前在解决ipv6 路由bug的时候发现,内核ipv6 和ipv4 路由设计区别很大!
IPv6路由项添加
ip route add 3ffe::/64 via 3001::1对应的核心函数为:
- fib6_add_1 :负责在路由树中找到合适的插入节点,或者创建新的用于插入操作的节点(根据allow_create的值而定)
- fib6_add_rt2node:负责将rt路由添加到找到的路由节点(fn)中。操作成功之后,更新路由节点分支树的的序号
路由节点查找
遍历路由树,第一次时,遍历的为路由根节点root,如果要插入路由地址的前缀长度小于当前遍历的路由节点的前缀长度(fn_bit),或者,其大于当前路由节点的前缀长度,但是按照较短的前缀长度比较,两者的地址值不相等。以上两个条件成立其中一条,在当前路由节点前插入一个新的节点。
否则,以上条件都不成立(即两者地址相同,前缀长度不小于当前遍历的路由节点),在两者前缀长度相等的情况下,表面是完全相同的路由。如果当前遍历的路由节点是中间节点,释放其叶子节点。在随后的函数fib6_add_rt2node中,会将此节点设置为叶子节点。
对于非中间节点,如果其为根节点,并且其叶子为null路由项,将其叶子设置为空。由于要插入的路由与此节点完全匹配,以上两种情况都将此路由节点返回。
最后的情况是,插入地址前缀长度大于当前遍历的路由节点,继续向树的下方遍历。函数addr_bit_set决定遍历的反向:左子树或者右子树。
如果以上遍历完成,并没有发现合适的路由节点,而且allow_create为零,不允许新建节点。但是这里只是告警,还是会创建新节点。如果replace_required为真,就返回错误,因为没有找的现有节点,无法进行替换。
创建一个新的路由节点,将要插入地址的前缀赋值给其成员fn_bit,并初始化其父节点(pn在以上遍历过程中设置)。并且根据方向值dir,将新建节点赋值到父节点的左子树或者右子树上,返回新创建的路由节点。
对于遍历过程中的第一种情况,新插入地址的前缀长度小于当前路由节点长度,或者两者地址不相等(以断前缀为掩码)。前者表示要插入的路由范围更广;后者表示两者没有相同的前缀地址。
__ipv6_addr_diff找到插入地址和路由节点两者之间第一个不相等的bit的位置,根据其与插入地址前缀的比较,分成以下两种情况处理。
如果plen大于两者相同的地址部分的长度,分配两个路由节点:一个中间节点和一个新节点。其中,中间节点的前缀长度设置为两者相同的部分的长度(bit),并且中间节点(in)的父节点设置为当前遍历节点(fn)的父节点。并将fn的叶子节点赋值给in的叶子,之后,根据方向值dir,将中间节点in赋值给父节点pn的左子树或者右子树,这样新的中间节点完全取代了之前遍历节点(fn)在树中的位置。
之后,将新创建的节点ln和老节点fn的父节点都设置为中间节点in,新节点的前缀长度设置为插入地址的前缀长度,最后,将节点ln和fn赋值给中间节点in的左右子树(具体根据addr_bit_set的返回值决定)
第二种情况,对于plen小于等于bit长度时,新创建一个路由节点替换原来的遍历节点fn,并将老节点fn设置为新节点ln的子节点
/* ip6_ins_rt is called with FREE table->tb6_lock.
   It takes new route entry, the addition fails by any reason the
   route is freed. In any case, if caller does not hold it, it may
   be destroyed.
 */
static int __ip6_ins_rt(struct rt6_info *rt, struct nl_info *info,
            struct mx6_config *mxc)
{
    int err;
    struct fib6_table *table;
    table = rt->rt6i_table;
    write_lock_bh(&table->tb6_lock);
    err = fib6_add(&table->tb6_root, rt, info, mxc);
    write_unlock_bh(&table->tb6_lock);
    return err;
}
/*
 *    Add routing information to the routing tree.
 *    <destination addr>/<source addr>
 *    with source addr info in sub-trees
 */
int fib6_add(struct fib6_node *root, struct rt6_info *rt,
         struct nl_info *info, struct mx6_config *mxc)
{
    struct fib6_node *fn, *pn = NULL;
    int err = -ENOMEM;
    int allow_create = 1;
    int replace_required = 0;
    int sernum = fib6_new_sernum(info->nl_net);
    if (WARN_ON_ONCE((rt->dst.flags & DST_NOCACHE) &&
             !atomic_read(&rt->dst.__refcnt)))
        return -EINVAL;
    if (info->nlh) {
        if (!(info->nlh->nlmsg_flags & NLM_F_CREATE))
            allow_create = 0;
        if (info->nlh->nlmsg_flags & NLM_F_REPLACE)
            replace_required = 1;
    }
    if (!allow_create && !replace_required)
        pr_warn("RTM_NEWROUTE with no NLM_F_CREATE or NLM_F_REPLACE\n");
    fn = fib6_add_1(root, &rt->rt6i_dst.addr, rt->rt6i_dst.plen,
            offsetof(struct rt6_info, rt6i_dst), allow_create,
            replace_required, sernum);
    if (IS_ERR(fn)) {
        err = PTR_ERR(fn);
        fn = NULL;
        goto out;
    }
    pn = fn;
#ifdef CONFIG_IPV6_SUBTREES
    if (rt->rt6i_src.plen) {
        struct fib6_node *sn;
        if (!fn->subtree) {
            struct fib6_node *sfn;
            /*
             * Create subtree.
             *
             *        fn[main tree]
             *        |
             *        sfn[subtree root]
             *           \
             *            sn[new leaf node]
             */
            /* Create subtree root node */
            sfn = node_alloc();
            if (!sfn)
                goto failure;
            sfn->leaf = info->nl_net->ipv6.ip6_null_entry;
            atomic_inc(&info->nl_net->ipv6.ip6_null_entry->rt6i_ref);
            sfn->fn_flags = RTN_ROOT;
            sfn->fn_sernum = sernum;
            /* Now add the first leaf node to new subtree */
            sn = fib6_add_1(sfn, &rt->rt6i_src.addr,
                    rt->rt6i_src.plen,
                    offsetof(struct rt6_info, rt6i_src),
                    allow_create, replace_required, sernum);
            if (IS_ERR(sn)) {
                /* If it is failed, discard just allocated
                   root, and then (in failure) stale node
                   in main tree.
                 */
                node_free_immediate(sfn);
                err = PTR_ERR(sn);
                goto failure;
            }
            /* Now link new subtree to main tree */
            sfn->parent = fn;
            fn->subtree = sfn;
        } else {
            sn = fib6_add_1(fn->subtree, &rt->rt6i_src.addr,
                    rt->rt6i_src.plen,
                    offsetof(struct rt6_info, rt6i_src),
                    allow_create, replace_required, sernum);
            if (IS_ERR(sn)) {
                err = PTR_ERR(sn);
                goto failure;
            }
        }
        if (!fn->leaf) {
            fn->leaf = rt;
            atomic_inc(&rt->rt6i_ref);
        }
        fn = sn;
    }
#endif
    /*
    將路由信息rt掛載到上面查找到的結點fn上*
    */
    err = fib6_add_rt2node(fn, rt, info, mxc);
    if (!err) {
        fib6_start_gc(info->nl_net, rt);
        if (!(rt->rt6i_flags & RTF_CACHE))
            fib6_prune_clones(info->nl_net, pn);
        rt->dst.flags &= ~DST_NOCACHE;
    }
out:
    if (err) {
#ifdef CONFIG_IPV6_SUBTREES
        /*
         * If fib6_add_1 has cleared the old leaf pointer in the
         * super-tree leaf node we have to find a new one for it.
         */
        if (pn != fn && pn->leaf == rt) {
            pn->leaf = NULL;
            atomic_dec(&rt->rt6i_ref);
        }
        if (pn != fn && !pn->leaf && !(pn->fn_flags & RTN_RTINFO)) {
            pn->leaf = fib6_find_prefix(info->nl_net, pn);
#if RT6_DEBUG >= 2
            if (!pn->leaf) {
                WARN_ON(pn->leaf == NULL);
                pn->leaf = info->nl_net->ipv6.ip6_null_entry;
            }
#endif
            atomic_inc(&pn->leaf->rt6i_ref);
        }
#endif
        goto failure;
    }
    return err;
failure:
    /* fn->leaf could be NULL if fn is an intermediate node and we
     * failed to add the new route to it in both subtree creation
     * failure and fib6_add_rt2node() failure case.
     * In both cases, fib6_repair_tree() should be called to fix
     * fn->leaf.
     */
    if (fn && !(fn->fn_flags & (RTN_RTINFO|RTN_ROOT)))
        fib6_repair_tree(info->nl_net, fn);
    if (!(rt->dst.flags & DST_NOCACHE))
        dst_free(&rt->dst);
    return err;
}
/*
 *    Routing Table
 *
 *    return the appropriate node for a routing tree "add" operation
 *    by either creating and inserting or by returning an existing
 *    node.
 */
static struct fib6_node *fib6_add_1(struct fib6_node *root,
                     struct in6_addr *addr, int plen,
                     int offset, int allow_create,
                     int replace_required, int sernum)
{
    struct fib6_node *fn, *in, *ln;
    struct fib6_node *pn = NULL;
    struct rt6key *key;rt6_info;rt6i_dst;
    int    bit;
    __be32    dir = 0;
    RT6_TRACE("fib6_add_1\n");
    /* insert node in tree */
    fn = root;// root为主路由表中的根节点  
    /* 
    從root開始遍歷路由表結點的樹,根據目的地址及前綴找到合適的路由表項fib6_node 
    */
    do {
        key = (struct rt6key *)((u8 *)fn->leaf + offset);
        /*
         *    Prefix match
         *//* 
如果目的地址前綴小於該結點的fn_bit,
或結點中已有目的地址與要查找的目的地址不相等,則去該節點的父節點中找
*/
        if (plen < fn->fn_bit ||
               /*  要添加目的地址與結點中已有路由的目的地址不同 */
            !ipv6_prefix_equal(&key->addr, addr, fn->fn_bit)) {
            if (!allow_create) {
                if (replace_required) {
                    pr_warn("Can't replace route, no match found\n");
                    return ERR_PTR(-ENOENT);
                }
                pr_warn("NLM_F_CREATE should be set when creating new route\n");
            }
            goto insert_above;
        }
            /*
            向fn結點以上添加結點:
            以下代碼執行條件:目的地址前綴>=該結點的fn_bit,且結點中已有目的地址與要查找的目的地址相等
            */
        /*
         *    Exact match ?
         *//*
 如果目的地址前綴等於該節點的fn_bit則 把該路由項插入到這個fn子樹中 
 此處完全匹配,即目的網絡和前綴都相等
*/
        if (plen == fn->fn_bit) {
            /* clean up an intermediate node */
            if (!(fn->fn_flags & RTN_RTINFO)) {
                rt6_release(fn->leaf);
                fn->leaf = NULL;
            }
            fn->fn_sernum = sernum;
            return fn;
        }
        /*
         *    We have more bits to go
         */
        /* Try to walk down on tree. */
        fn->fn_sernum = sernum;
        //插入地址前缀长度大于当前遍历的路由节点,继续向树的下方遍历。函数addr_bit_set决定遍历的反向:左子树或者右子树。
        dir = addr_bit_set(addr, fn->fn_bit);
        pn = fn;
        fn = dir ? fn->right : fn->left;
    } while (fn);
    
//前缀长度不小于当前遍历的路由节点
//没有发现合适的路由节点,而且allow_create为零,不允许新建节点。但是这里只是告警,还是会创建新节点。
//如果replace_required为真,就返回错误,因为没有找的现有节点,无法进行替换
    if (!allow_create) {
        /* We should not create new node because
         * NLM_F_REPLACE was specified without NLM_F_CREATE
         * I assume it is safe to require NLM_F_CREATE when
         * REPLACE flag is used! Later we may want to remove the
         * check for replace_required, because according
         * to netlink specification, NLM_F_CREATE
         * MUST be specified if new route is created.
         * That would keep IPv6 consistent with IPv4
         */
        if (replace_required) {
            pr_warn("Can't replace route, no match found\n");
            return ERR_PTR(-ENOENT);
        }
        pr_warn("NLM_F_CREATE should be set when creating new route\n");
    }
    /*
     *    We walked to the bottom of tree.
     *    Create new leaf node without children.
     */
        /*
         如果目的地址前綴>該結點的fn_bit,則在fn結點以下添加路由結點。
        即fn結點的子結點。 
        */
   /*
   创建一个新的路由节点,将要插入地址的前缀赋值给其成员fn_bit,并初始化其父节点(pn在以上遍历过程中设置)。
   并且根据方向值dir,将新建节点赋值到父节点的左子树或者右子树上,返回新创建的路由节点。
   */
    ln = node_alloc();
    if (!ln)
        return ERR_PTR(-ENOMEM);
    ln->fn_bit = plen;
    ln->parent = pn;
    ln->fn_sernum = sernum;
    if (dir)
        pn->right = ln;
    else
        pn->left  = ln;
    return ln;
/*
    对于遍历过程中的第一种情况,新插入地址的前缀长度小于当前路由节点长度,或者两者地址不相等(以断前缀为掩码)。
    前者表示要插入的路由范围更广;后者表示两者没有相同的前缀地址。
    */
insert_above:
    /*
     * split since we don't have a common prefix anymore or
     * we have a less significant route.
     * we've to insert an intermediate node on the list
     * this new node will point to the one we need to create
     * and the current
     */
    pn = fn->parent;
    /* find 1st bit in difference between the 2 addrs.
       See comment in __ipv6_addr_diff: bit may be an invalid value,
       but if it is >= plen, the value is ignored in any case.
     */
        /*要添加的目的地址與結點中現有的目的地址,第一個不同的bit*/
    bit = __ipv6_addr_diff(addr, &key->addr, sizeof(*addr));
    /*如果plen大于两者相同的地址部分的长度,分配两个路由节点:一个中间节点和一个新节点。
    其中,中间节点的前缀长度设置为两者相同的部分的长度(bit),并且中间节点(in)的父节点设置为当前遍历节点(fn)的父节点。
    并将fn的叶子节点赋值给in的叶子,之后,根据方向值dir,将中间节点in赋值给父节点pn的左子树或者右子树,
    这样新的中间节点完全取代了之前遍历节点(fn)在树中的位置。
*/
    /*
    程序至此,說明:
    1 目的地址前綴plen小於fn->fn_bit(fn結點對應的前綴長度).
    此時bit<=plen<fn->fn_bit. 此時存在前綴長度爲bit的中間結點,它是新建結點和fn結點的上結點。
    或者:
     2 plen>=fn->fn_bit 且要添加目的地址與結點中已有路由的目的地址的前fn_bit位不同.
    注意此時,fn->fn_bit一定>bit,否則不會到該分支.
    此時:若plen>bit 則存在前綴長度爲bit的中間結點,它是新建結點和fn結點的上結點
    */
/*将新创建的节点ln和老节点fn的父节点都设置为中间节点in,新节点的前缀长度设置为插入地址的前缀长度,
最后,将节点ln和fn赋值给中间节点in的左右子树(具体根据addr_bit_set的返回值决定)。*/
    /*
     *        (intermediate)[in]
     *              /       \
     *    (new leaf node)[ln] (old node)[fn]
     */
    if (plen > bit) {
        in = node_alloc();
        ln = node_alloc();
        if (!in || !ln) {
            if (in)
                node_free_immediate(in);
            if (ln)
                node_free_immediate(ln);
            return ERR_PTR(-ENOMEM);
        }
        /*
         * new intermediate node.
         * RTN_RTINFO will
         * be off since that an address that chooses one of
         * the branches would not match less specific routes
         * in the other branch
         */
        in->fn_bit = bit;
        in->parent = pn;
        in->leaf = fn->leaf;
        atomic_inc(&in->leaf->rt6i_ref);
        in->fn_sernum = sernum;
        /* update parent pointer */
        if (dir)
            pn->right = in;
        else
            pn->left  = in;
        ln->fn_bit = plen;
        ln->parent = in;
        fn->parent = in;
        ln->fn_sernum = sernum;
        if (addr_bit_set(addr, bit)) {
            in->right = ln;
            in->left  = fn;
        } else {
            in->left  = ln;
            in->right = fn;
        }
    } else { /* plen <= bit对于plen小于等于bit长度时,新创建一个路由节点替换原来的遍历节点fn,
    并将老节点fn设置为新节点ln的子节点 */
        /*
         *        (new leaf node)[ln]
         *              /       \
         *         (old node)[fn] NULL
         */
        ln = node_alloc();
        if (!ln)
            return ERR_PTR(-ENOMEM);
        ln->fn_bit = plen;
        ln->parent = pn;
        ln->fn_sernum = sernum;
        if (dir)
            pn->right = ln;
        else
            pn->left  = ln;
        if (addr_bit_set(&key->addr, plen))
            ln->right = fn;
        else
            ln->left  = fn;
        fn->parent = ln;
    }
    return ln;
}
路由节点中增加路由信息
找到合适的路由节点之后,函数fib6_add_rt2node负责将路由信息添加到此节点
static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
                struct nl_info *info, struct mx6_config *mxc)
{
    struct rt6_info *iter = NULL;
    struct rt6_info **ins;
    struct rt6_info **fallback_ins = NULL;
    int replace = (info->nlh &&
               (info->nlh->nlmsg_flags & NLM_F_REPLACE));
    int add = (!info->nlh ||
           (info->nlh->nlmsg_flags & NLM_F_CREATE));
    int found = 0;
    bool rt_can_ecmp = rt6_qualify_for_ecmp(rt);
    int err;
    ins = &fn->leaf;
/*首先遍历此路由节点的叶子链表,查找相同的路由项。如果要插入路由的metric和当前遍历的叶子的metric相等,并且netlink设置了NLM_F_EXCL标志
找到重复的路由项,返回错误EEXIST。否则,如果是路由替换(ip link replace),两者都支持ECMP,设置found标志,退出遍历;如果其中之一不支持ECMP,
遍历链表中的下一个叶子,其中fallback_ins记录第一个符合替换的叶子。
之后,如果不是路由替换(replace为0),并且两者拥有相同的下一跳,返回错误EEXIST。
在这之前,如果已有路由iter已经到期,并且新插入路由没有到期,清除iter的到期标志,重新计时;
否则,如果两者都到期,设置iter的到期标志。
最后,如果两者的下一跳不同,并且都支持ECMP,设置为sibling。
*/
    for (iter = fn->leaf; iter; iter = iter->dst.rt6_next) {
        /*
         *    Search for duplicates
         */
        if (iter->rt6i_metric == rt->rt6i_metric) {
            /*
             *    Same priority level
             */
            if (info->nlh &&
                (info->nlh->nlmsg_flags & NLM_F_EXCL))
                return -EEXIST;
            if (replace) {
                if (rt_can_ecmp == rt6_qualify_for_ecmp(iter)) {
                    found++;
                    break;
                }
                if (rt_can_ecmp)
                    fallback_ins = fallback_ins ?: ins;
                goto next_iter;
            }
            if (rt6_duplicate_nexthop(iter, rt)) {
                if (rt->rt6i_nsiblings)
                    rt->rt6i_nsiblings = 0;
                if (!(iter->rt6i_flags & RTF_EXPIRES))
                    return -EEXIST;
                if (!(rt->rt6i_flags & RTF_EXPIRES))
                    rt6_clean_expires(iter);
                else
                    rt6_set_expires(iter, rt->dst.expires);
                iter->rt6i_pmtu = rt->rt6i_pmtu;
                return -EEXIST;
            }
            /* If we have the same destination and the same metric,
             * but not the same gateway, then the route we try to
             * add is sibling to this route, increment our counter
             * of siblings, and later we will add our route to the
             * list.
             * Only static routes (which don't have flag
             * RTF_EXPIRES) are used for ECMPv6.
             *
             * To avoid long list, we only had siblings if the
             * route have a gateway.
             */
            if (rt_can_ecmp &&
                rt6_qualify_for_ecmp(iter))
                rt->rt6i_nsiblings++;
        }
        if (iter->rt6i_metric > rt->rt6i_metric)
            break;
next_iter:
        ins = &iter->dst.rt6_next;
    }
//没有匹配的ECMP叶子,替换记录下的第一个叶子。
    if (fallback_ins && !found) {
        /* No ECMP-able route found, replace first non-ECMP one */
        ins = fallback_ins;
        iter = *ins;
        found++;
    }
    /* Reset round-robin state, if necessary */
    if (ins == &fn->leaf)
        fn->rr_ptr = NULL;
    /* Link this route to others same route. 如果要插入的路由可与其它已有路由组成ECMP,找到第一个具有相同metric值的叶子
    将要插入的路由信息链接到其fib6_siblings链表。之后,遍历sibling链表,有新插入路由信息开始,递增其fib6_nsiblings计数。
    新插入路由信息的fib6_nsiblings计数已经在函数开始的遍历循环中赋值。
所有sibling中的fib6_nsiblings计数在递加1之后都应等于rt中的计数,并且,所有sibling的总是等于新插入路由信息中的fib6_nsiblings计数。
*/
    if (rt->rt6i_nsiblings) {
        unsigned int rt6i_nsiblings;
        struct rt6_info *sibling, *temp_sibling;
        /* Find the first route that have the same metric */
        sibling = fn->leaf;
        while (sibling) {
            if (sibling->rt6i_metric == rt->rt6i_metric &&
                rt6_qualify_for_ecmp(sibling)) {
                list_add_tail(&rt->rt6i_siblings,
                          &sibling->rt6i_siblings);
                break;
            }
            sibling = sibling->dst.rt6_next;
        }
        /* For each sibling in the list, increment the counter of
         * siblings. BUG() if counters does not match, list of siblings
         * is broken!
         */
        rt6i_nsiblings = 0;
        list_for_each_entry_safe(sibling, temp_sibling,
                     &rt->rt6i_siblings, rt6i_siblings) {
            sibling->rt6i_nsiblings++;
            BUG_ON(sibling->rt6i_nsiblings != rt->rt6i_nsiblings);
            rt6i_nsiblings++;
        }
        BUG_ON(rt6i_nsiblings != rt->rt6i_nsiblings);
    }
    /*
     *    insert node
     */
    if (!replace) {
        if (!add)
            pr_warn("NLM_F_CREATE should be set when creating new route\n");
    add :
        err = fib6_commit_metrics(&rt->dst, mxc);
        if (err)
            return err;
        rt->dst.rt6_next = iter;
        *ins = rt;
        rcu_assign_pointer(rt->rt6i_node, fn);
        atomic_inc(&rt->rt6i_ref);
        inet6_rt_notify(RTM_NEWROUTE, rt, info, 0);
        info->nl_net->ipv6.rt6_stats->fib_rt_entries++;
        if (!(fn->fn_flags & RTN_RTINFO)) {
            info->nl_net->ipv6.rt6_stats->fib_route_nodes++;
            fn->fn_flags |= RTN_RTINFO;
        }
    } else {
        int nsiblings;
        if (!found) {
            if (add)
                goto add;
            pr_warn("NLM_F_REPLACE set, but no existing node found!\n");
            return -ENOENT;
        }
        err = fib6_commit_metrics(&rt->dst, mxc);
        if (err)
            return err;
        *ins = rt;
        rcu_assign_pointer(rt->rt6i_node, fn);
        rt->dst.rt6_next = iter->dst.rt6_next;
        atomic_inc(&rt->rt6i_ref);
        inet6_rt_notify(RTM_NEWROUTE, rt, info, NLM_F_REPLACE);
        if (!(fn->fn_flags & RTN_RTINFO)) {
            info->nl_net->ipv6.rt6_stats->fib_route_nodes++;
            fn->fn_flags |= RTN_RTINFO;
        }
        nsiblings = iter->rt6i_nsiblings;
        fib6_purge_rt(iter, fn, info->nl_net);
        if (fn->rr_ptr == iter)
            fn->rr_ptr = NULL;
        rt6_release(iter);
        if (nsiblings) {
            /* Replacing an ECMP route, remove all siblings */
            ins = &rt->dst.rt6_next;
            iter = *ins;
            while (iter) {
                if (iter->rt6i_metric > rt->rt6i_metric)
                    break;
                if (rt6_qualify_for_ecmp(iter)) {
                    *ins = iter->dst.rt6_next;
                    fib6_purge_rt(iter, fn, info->nl_net);
                    if (fn->rr_ptr == iter)
                        fn->rr_ptr = NULL;
                    rt6_release(iter);
                    nsiblings--;
                } else {
                    ins = &iter->dst.rt6_next;
                }
                iter = *ins;
            }
            WARN_ON(nsiblings != 0);
        }
    }
    return 0;
}
    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子
    
    
    










