Linux网络协议栈4--bridge收发包

2023-05-16

bridge 是linux上的虚拟交换机,具有交换机的功能。

网卡收到包后,走到__netif_receive_skb_core后,剥完vlan找到vlan子接口(如果有的话),如果skb->dev是bridge成员口,就会走到bridge成员口的接收处理函数。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
......
	/*
	bridge、ovs的接口,都会走到。
	如果一个dev被添加到一个bridge(做为bridge的一个接口),这个接口设备的rx_handler将被设置为,
	br_handle_frame函数这是在br_add_if函数中设置的,而br_add_if (net/bridge/br_if.c)是在向
	网桥设备上添加接口时设置的。进入br_handle_frame也就进入了bridge的逻辑代码。*/
	rx_handler = rcu_dereference(skb->dev->rx_handler);
	if (rx_handler) {
		if (pt_prev) {
			ret = deliver_skb(skb, pt_prev, orig_dev);
			pt_prev = NULL;
		}
		switch (rx_handler(&skb)) {
		case RX_HANDLER_CONSUMED:  // 报文已经被消费,结束处理
			ret = NET_RX_SUCCESS;
			goto out;
		case RX_HANDLER_ANOTHER:  // skb->dev 被修改,重新走一次
			goto another_round;
		case RX_HANDLER_EXACT: /* 精确传递到ptype->dev == skb->dev */
			deliver_exact = true;
		case RX_HANDLER_PASS:
			break;
		default:
			BUG();
		}
	}

	......
}

bridge 的接收处理函数为br_handler_frame,在为bridge添加接口操作时,如brctl addif命令行将一个接口加入到bridge中,就会为接口的 net_device挂载此处理函数。

int br_add_if(struct net_bridge *br, struct net_device *dev)
{
	......
	err = netdev_rx_handler_register(dev, br_handle_frame, p);
	if (err)
		goto err4;

	......
}

int netdev_rx_handler_register(struct net_device *dev,
			       rx_handler_func_t *rx_handler,
			       void *rx_handler_data)
{
	ASSERT_RTNL();

	if (dev->rx_handler)
		return -EBUSY;

	/* Note: rx_handler_data must be set before rx_handler */
	rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
	rcu_assign_pointer(dev->rx_handler, rx_handler);

	return 0;
}

bridge接收处理函数br_handle_frame,非linklocal地址的情况下,主要做:
1、ebtables 的BROUTING 表处理,这是在bridge协议栈中将二层转发切换到主机协议栈的三层转发的hook点;
2、bridge NF_BR_PRE_ROUTING hook点处理,配置net.bridge.bridge-nf-call-iptables 系统配置的情况下还会 调用iptables规则处理;
3、进br_handle_frame_finish函数,根据src mac学习fdb表项,继续做转发处理或本机报文上送三层协议栈。

rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{
	struct net_bridge_port *p;
	struct sk_buff *skb = *pskb;
	const unsigned char *dest = eth_hdr(skb)->h_dest;
	br_should_route_hook_t *rhook;

	if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
		return RX_HANDLER_PASS;

	if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
		goto drop;
	// 如果skb被其它流程共享,clone一份
	skb = skb_share_check(skb, GFP_ATOMIC);
	if (!skb)
		return RX_HANDLER_CONSUMED;

	p = br_port_get_rcu(skb->dev);

	if (unlikely(is_link_local_ether_addr(dest))) {
		u16 fwd_mask = p->br->group_fwd_mask_required;

		/*
		 * See IEEE 802.1D Table 7-10 Reserved addresses
		 *
		 * Assignment		 		Value
		 * Bridge Group Address		01-80-C2-00-00-00
		 * (MAC Control) 802.3		01-80-C2-00-00-01
		 * (Link Aggregation) 802.3	01-80-C2-00-00-02
		 * 802.1X PAE address		01-80-C2-00-00-03
		 *
		 * 802.1AB LLDP 		01-80-C2-00-00-0E
		 *
		 * Others reserved for future standardization
		 */
		// 使用linklocal地址的一些场景的处理,__br_handle_local_finish 会学习src mac的fdb表项
		switch (dest[5]) {
		case 0x00:	/* Bridge Group Address */
			/* If STP is turned off,
			   then must forward to keep loop detection */
			if (p->br->stp_enabled == BR_NO_STP ||
			    fwd_mask & (1u << dest[5]))
				goto forward;
			*pskb = skb;
			__br_handle_local_finish(skb);
			return RX_HANDLER_PASS;

		case 0x01:	/* IEEE MAC (Pause) */
			goto drop;

		case 0x0E:	/* 802.1AB LLDP */
			fwd_mask |= p->br->group_fwd_mask;
			if (fwd_mask & (1u << dest[5]))
				goto forward;
			*pskb = skb;
			__br_handle_local_finish(skb);
			return RX_HANDLER_PASS;

		default:
			/* Allow selective forwarding for most other protocols */
			fwd_mask |= p->br->group_fwd_mask;
			if (fwd_mask & (1u << dest[5]))
				goto forward;
		}

		/* Deliver packet to local host only */
		NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, dev_net(skb->dev),
			NULL, skb, skb->dev, NULL, br_handle_local_finish);
		return RX_HANDLER_CONSUMED;
	}

forward:
	switch (p->state) {
	case BR_STATE_FORWARDING:
		rhook = rcu_dereference(br_should_route_hook);
		/* ebtables 的BROUTING 表处理,rhook 返回1,表示上送三层协议栈。我的一个项目中用过这个特性,识别用户的部分不重要的业务,走internet(配置有跨公网tunnel),而其它业务流量走加入bridge中的专线口。*/
		if (rhook) {
			if ((*rhook)(skb)) {
				*pskb = skb;
				return RX_HANDLER_PASS;
			}
			dest = eth_hdr(skb)->h_dest;
		}
		/* fall through */
	case BR_STATE_LEARNING:
 		// mac地址是br的mac地址,表示送本机的报文。
		if (ether_addr_equal(p->br->dev->dev_addr, dest))
			skb->pkt_type = PACKET_HOST;
		/* NFPROTO_BRIDGE 协议类型的netfilter包括:
		    1、ebtable模块注册的钩子,定义在ebt_ops_filter、ebt_ops_nat;
		    2、bridge 模块注册的br_nf_ops 中定义的处理,其会根据 net.bridge.bridge-nf-call-iptables 系统配置决定是否调用iptables规则过滤,即在二层转发中做上层协议处理。
		         曾今工作中遇到过bridge-nf-call-iptables 被打开,导致流量不通的一个问题,看看半天,痛苦。
		*/
		NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,
			dev_net(skb->dev), NULL, skb, skb->dev, NULL,
			br_handle_frame_finish);
		break;
	default:
drop:
		kfree_skb(skb);
	}
	return RX_HANDLER_CONSUMED;
}

br_handle_frame_finish函数主要做:
1、根据报文的源MAC地址,入接口,刷新fdb表;
2、实现arp带答功能(带答比代理更合适);
3、识别报文是单播、广播、还是组播,单播和组播查对应的fdb表;
4、单播:
1)送往本机的,走 br_pass_frame_up,local_in流程;
2)非本机,走 br_forward,forward流程;
广播报文: 走br_flood ,会在除入接口外的其它接口广播;
组播报文:走br_multicast_flood,根据组播表转发。

int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
	const unsigned char *dest = eth_hdr(skb)->h_dest;
	enum br_pkt_type pkt_type = BR_PKT_UNICAST;
	struct net_bridge_fdb_entry *dst = NULL;
	struct net_bridge_mdb_entry *mdst;
	bool local_rcv, mcast_hit = false;
	struct net_bridge *br;
	u16 vid = 0;

	if (!p || p->state == BR_STATE_DISABLED)
		goto drop;

	if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid))
		goto out;

	nbp_switchdev_frame_mark(p, skb);

	/* insert into forwarding database after filtering to avoid spoofing */
	br = p->br;
	if (p->flags & BR_LEARNING)  
		// 根据报文的源MAC地址,入接口,刷新fdb表(mac表)
		br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, false);
  	// 混杂模式下,会上送协议栈一份(为什么是!!)
	local_rcv = !!(br->dev->flags & IFF_PROMISC);
	if (is_multicast_ether_addr(dest)) {
		/* by definition the broadcast is also a multicast address */
		if (is_broadcast_ether_addr(dest)) {
			pkt_type = BR_PKT_BROADCAST;
			local_rcv = true;
		} else {
			pkt_type = BR_PKT_MULTICAST;
			if (br_multicast_rcv(br, p, skb, vid))
				goto drop;
		}
	}

	if (p->state == BR_STATE_LEARNING)
		goto drop;

	BR_INPUT_SKB_CB(skb)->brdev = br->dev;

	if (IS_ENABLED(CONFIG_INET) && skb->protocol == htons(ETH_P_ARP))
		// 一个比较重要的特性,arp proxy,接口如果是能了此功能,对于收到的arp request,可以通过查询本地的arp表构造arp reply报文回应。SDN网络经常用到。
		br_do_proxy_arp(skb, br, vid, p);
	// 根据报文类型是组播还是单播,查转发表
	switch (pkt_type) {
	case BR_PKT_MULTICAST:
		mdst = br_mdb_get(br, skb, vid);
		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
		    br_multicast_querier_exists(br, eth_hdr(skb))) {
			if ((mdst && mdst->mglist) ||
			    br_multicast_is_router(br)) {
				local_rcv = true;
				br->dev->stats.multicast++;
			}
			mcast_hit = true;
		} else {
			local_rcv = true;
			br->dev->stats.multicast++;
		}
		break;
	case BR_PKT_UNICAST:
		dst = __br_fdb_get(br, dest, vid);
	default:
		break;
	}
	// 单播
	if (dst) {
		// 本地转发表,则上送协议栈
		if (dst->is_local)
			return br_pass_frame_up(skb);
		// 否则,走二层转发
		dst->used = jiffies;
		br_forward(dst->dst, skb, local_rcv, false);
	} else {
		if (!mcast_hit)
			// 未知的单播,泛流
			br_flood(br, skb, pkt_type, local_rcv, false);
		else
			// 组播报文发送
			br_multicast_flood(mdst, skb, local_rcv, false);
	}
	// br混杂模式、广播、某些情况下组播报文(懒得看了)会上送协议栈一份
	if (local_rcv)
		return ·(skb);

out:
	return 0;
drop:
	kfree_skb(skb);
	goto out;
}
EXPORT_SYMBOL_GPL(br_handle_frame_finish);

这里有一个比较重要的特性,arp proxy,接口如果是能了此功能,对于收到的arp request,可以通过查询本地的arp表构造arp reply报文回应。SDN网络经常用到。
功能测试注意项记录:1、必须事先配置好代答mac地址的fdb表,否则无法代答ARP。如下面br_do_proxy_arp代码分析; 2、使用ip link set dev veth20 type bridge_slave proxy_arp on 使能proxy arp功能,和/proc/sys/net/ipv4/conf/veth2/proxy_arp 配置的功能不是一个东西;3、低版本的内核不支持,3.10 的centos可以配置但是没效果。

static void br_do_proxy_arp(struct sk_buff *skb, struct net_bridge *br,
			    u16 vid, struct net_bridge_port *p)
{
	struct net_device *dev = br->dev;
	struct neighbour *n;
	struct arphdr *parp;
	u8 *arpptr, *sha;
	__be32 sip, tip;

	BR_INPUT_SKB_CB(skb)->proxyarp_replied = false;

	if ((dev->flags & IFF_NOARP) ||
	    !pskb_may_pull(skb, arp_hdr_len(dev)))
		return;

	parp = arp_hdr(skb);
	// 过滤 arp 请求报文
	if (parp->ar_pro != htons(ETH_P_IP) ||
	    parp->ar_op != htons(ARPOP_REQUEST) ||
	    parp->ar_hln != dev->addr_len ||
	    parp->ar_pln != 4)
		return;

	arpptr = (u8 *)parp + sizeof(struct arphdr);
	sha = arpptr;
	arpptr += dev->addr_len;	/* sha */
	memcpy(&sip, arpptr, sizeof(sip));
	arpptr += sizeof(sip);
	arpptr += dev->addr_len;	/* tha */
	memcpy(&tip, arpptr, sizeof(tip));

	if (ipv4_is_loopback(tip) ||
	    ipv4_is_multicast(tip))
		return;
 	// 存在 br口的目的ip的arp表项,这根据arp表信息封装arp reply
	n = neigh_lookup(&arp_tbl, &tip, dev);
	if (n) {
		struct net_bridge_fdb_entry *f;

		if (!(n->nud_state & NUD_VALID)) {
			neigh_release(n);
			return;
		}

		f = __br_fdb_get(br, n->ha, vid);
		/* 注意这里需要满足的条件:
		  1、对应fdb表必须存在,所以sdn网络需要事先静态添加fdb表
		  2、接口配置了BR_PROXYARP
		*/ 
		if (f && ((p->flags & BR_PROXYARP) ||
			  (f->dst && (f->dst->flags & BR_PROXYARP_WIFI)))) {
			arp_send(ARPOP_REPLY, ETH_P_ARP, sip, skb->dev, tip,
				 sha, n->ha, sha);
			BR_INPUT_SKB_CB(skb)->proxyarp_replied = true;
		}

		neigh_release(n);
	}
}

这里只看上送本机 和 转发流程。
上送本机走 br_pass_frame_up,重点是将skb->dev替换成了bridge口,然后经过LOCAL_IN hook点过滤处理后,调用 netif_receive_skb函数再次走一次收包流程。
netif_receive_skb 这个函数上节已经讲过了,br_handler_frame 也是走这个函数过来的。但是这次 skb->dev 已经替换成bridge口了,它的skb->dev->rx_handler 为空,所以不会再次进入br_handler_frame,而是会进上层协议栈。


static int br_pass_frame_up(struct sk_buff *skb)
{
	struct net_device *indev, *brdev = BR_INPUT_SKB_CB(skb)->brdev;
	struct net_bridge *br = netdev_priv(brdev);
	struct net_bridge_vlan_group *vg;
	struct pcpu_sw_netstats *brstats = this_cpu_ptr(br->stats);

	u64_stats_update_begin(&brstats->syncp);
	brstats->rx_packets++;
	brstats->rx_bytes += skb->len;
	u64_stats_update_end(&brstats->syncp);

	vg = br_vlan_group_rcu(br);
	/* Bridge is just like any other port.  Make sure the
	 * packet is allowed except in promisc modue when someone
	 * may be running packet capture.
	 */
	if (!(brdev->flags & IFF_PROMISC) &&
	    !br_allowed_egress(vg, skb)) {
		kfree_skb(skb);
		return NET_RX_DROP;
	}

	indev = skb->dev;
	// 因为要上送本机协议栈,bridge 和本机的联系是通过br口的,所以这里将dev 替换成了 bridge 口
	skb->dev = brdev;
	skb = br_handle_vlan(br, vg, skb);
	if (!skb)
		return NET_RX_DROP;
	/* update the multicast stats if the packet is IGMP/MLD */
	br_multicast_count(br, NULL, skb, br_multicast_igmp_type(skb),
			   BR_MCAST_DIR_TX);
	// local_in流程,先走 NF_BR_LOCAL_IN hook点,同PRE_ROUTING点,包含ebtables过滤,根据配置可能包含iptables过滤
	return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN,
		       dev_net(indev), NULL, skb, indev, NULL,
		       br_netif_receive_skb);
}

static int
br_netif_receive_skb(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	br_drop_fake_rtable(skb);
	return netif_receive_skb(skb);
}

转发流程,__br_forward 函数 重新设置skb->dev为bridge出接口,然后分别经过 NF_BR_FORWARD 和 NF_BR_POST_ROUTING两个hook点处理,走到接口发送函数 dev_queue_xmit,这个是接口发送的入口。



static void __br_forward(const struct net_bridge_port *to,
			 struct sk_buff *skb, bool local_orig)
{
	struct net_bridge_vlan_group *vg;
	struct net_device *indev;
	struct net *net;
	int br_hook;

	vg = nbp_vlan_group_rcu(to);
	skb = br_handle_vlan(to->br, vg, skb);
	if (!skb)
		return;

	indev = skb->dev;
	skb->dev = to->dev;
	if (!local_orig) {
		if (skb_warn_if_lro(skb)) {
			kfree_skb(skb);
			return;
		}
		br_hook = NF_BR_FORWARD;
		skb_forward_csum(skb);
		net = dev_net(indev);
	} else {
		if (unlikely(netpoll_tx_running(to->br->dev))) {
			if (!is_skb_forwardable(skb->dev, skb)) {
				kfree_skb(skb);
			} else {
				skb_push(skb, ETH_HLEN);
				br_netpoll_send_skb(to, skb);
			}
			return;
		}
		br_hook = NF_BR_LOCAL_OUT;
		net = dev_net(skb->dev);
		indev = NULL;
	}
	// NF_BR_FORWARD hook点处理
	NF_HOOK(NFPROTO_BRIDGE, br_hook,
		net, NULL, skb, indev, skb->dev,
		br_forward_finish);
}

int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	// NF_BR_POST_ROUTING hook点处理
	return NF_HOOK(NFPROTO_BRIDGE, NF_BR_POST_ROUTING,
		       net, sk, skb, NULL, skb->dev,
		       br_dev_queue_push_xmit);

}
EXPORT_SYMBOL_GPL(br_forward_finish);

int br_dev_queue_push_xmit(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	if (!is_skb_forwardable(skb->dev, skb))
		goto drop;

	skb_push(skb, ETH_HLEN);
	br_drop_fake_rtable(skb);

	if (skb->ip_summed == CHECKSUM_PARTIAL &&
	    (skb->protocol == htons(ETH_P_8021Q) ||
	     skb->protocol == htons(ETH_P_8021AD))) {
		int depth;

		if (!__vlan_get_protocol(skb, skb->protocol, &depth))
			goto drop;

		skb_set_network_header(skb, depth);
	}
	// 接口发送,入队列、QoS、再调接口发送驱动处理函数
	dev_queue_xmit(skb);

	return 0;

drop:
	kfree_skb(skb);
	return 0;
}
EXPORT_SYMBOL_GPL(br_dev_queue_push_xmit);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux网络协议栈4--bridge收发包 的相关文章

  • Vue3使用axios请求数据后使用v-for显示不出数据

    getAccount let that 61 this axios url 39 http localhost 8080 api GetAllUniAccounts 39 method 39 post 39 then res 61 gt t
  • Axios请求中Content-Type的使用总结

    Axios请求头中的Content Type常见的有3种 xff1a 1 Content Type application json 2 Content Type application x www form urlencoded 3 Co
  • mycat全局序列号数据库方式

    1 server xml lt property name 61 34 sequnceHandlerType 34 gt 1 lt property gt 2 schema xml lt table name 61 34 z test 34
  • Warning: Packets out of order. Expected 8 received 0

    环境 xff1a linux 43 mycat 43 hyperf 43 es 原因 xff1a mycat进行了分片 解决 xff1a 修改文件路径 xff1a vendor hyperf database src Connectors
  • Ubuntu下不能切换中文,qt creator无法输入中文,sogo输入法(详细步骤)

    目录 xff1a 1 解决ubuntu 不支持切换中文 xff0c 并安装sogo输入法步骤 xff1b 2 解决Qt Creator不支持中文输入 xff1a 详细步骤 xff1a 一 解决ubuntu 不支持切换中文 xff0c 并安装
  • wordpress插件API入口

    lt php 64 package Moodo Zhong 64 version 1 0 0 Plugin Name Moodo Zhong Plugin URI http wordpress org plugins hello dolly
  • PHP源码目录说明

    1 build 和编译有关的目录 2 ext 扩展库代码 xff0c 例如 MySQL zlib iconv 等我们熟悉的扩展库 其中 ext standard 目录下是常用的标准函数集 3 main 主目录包含主要的 PHP 宏和定义 4
  • 终端程序定义示例

    interrupt 0 外部中断0 interrupt 1 T0中断 interrupt 2 外部中断1 interrupt 3 T1中断 interrupt 4 串口中断 我来告诉你实质 xff1a 单片机的中断处理是这样工作的 xff0
  • lisp学习资料

    中文LISP学习网站 CL HTTP franz com 217条消息 使用hunchentoot搭建Lisp web 服务器 keyboardOTA的博客 CSDN博客
  • GO调用C语言之字符串传递

    C xff1a hello h include lt stdlib h gt include lt stdio h gt include lt string h gt char abc int strnum char str C hello
  • C++-基础语法-cin.getline() 与 cin.get() 的区别,以及getline()函数使用方法

    参考声明 xff1a C 43 43 primer plus https blog csdn net best fiends zxh article details 53064771 https blog csdn net u0114216
  • Kotlin开发中的一些Tips(二)

    接着上一篇 xff0c 最近又整理了一些 1 作用域函数选择 目前有let run with apply 和 also五个作用域函数 官方文档有张表来说明它们之间的区别 xff1a 总结一下有几点区别 xff1a apply和also返回上
  • Jetpack Compose 从入门到入门(一)

    Jetpack Compose 是用于构建原生 Android 界面的新工具包 它使用更少的代码 强大的工具和直观的 Kotlin API xff0c 可以帮助您简化并加快 Android 界面开发 xff0c 打造生动而精彩的应用 它可让
  • Jetpack Compose 从入门到入门(二)

    开始布局部分 这部分我个人感觉没有必要每个组件 属性都详细说到 xff0c 否则篇幅会很长 建立起Compose中的组件与 Android Views的一个对应关系就够了 具体还是需要在实际的使用中去熟悉 1 Column 子元素按竖直顺序
  • Jetpack Compose 从入门到入门(三)

    本篇开始介绍Jetpack Compose 中的修饰符Modifier 修饰符可以用来执行以下操作 xff1a 更改可组合项的大小 布局 行为和外观 添加信息 xff0c 如无障碍标签 处理用户输入 添加高级互动 xff0c 如使元素可点击
  • 【操作系统】Linux系统中直接优化提升CPU性能(已解决)

    文章目录 问题 xff1a 服务器CPU没有调用最高性能 xff0c 导致跑算法的时候处理速度慢一 BIOS方法二 终端直接设置CPU调节器方法1 查看当前CPU调节器2 安装各种依赖库3 最后安装cpufrequtis工具包并设置CPU调
  • Jetpack Compose 从入门到入门(四)

    本篇开始介绍Jetpack Compose 中常用的组件 有一部分之前的文章中也出现过 xff0c 今天详细说明一下 1 Text 日常最常用的应该就是显示文字 xff0c 所以有必要说一下Text控件 首先源码如下 xff1a span
  • Jetpack Compose 从入门到入门(五)

    应用中的状态是指可以随时间变化的任何值 这是一个非常宽泛的定义 xff0c 从 Room 数据库到类的变量 xff0c 全部涵盖在内 由于Compose是声明式UI xff0c 会根据状态变化来更新UI xff0c 因此状态的处理至关重要
  • Jetpack Compose 从入门到入门(六)

    本篇说说Compose中的Canvas 1 Canvas span class token annotation builtin 64 Composable span span class token keyword fun span sp
  • Jetpack Compose 从入门到入门(七)

    本篇进入Compose 动画部分 1 动画预览 在本系列第一篇中我们提到过 xff0c 64 Preview可以帮我们实现UI的预览功能 xff0c 简单的交互和播放动画 在Android Studio Bumblebee xff08 大黄

随机推荐

  • Android 12 变更及适配攻略

    这几个月有点忙 xff0c 一年一篇的适配文章来的有点晚了 但其实也还好 xff0c 因为我们项目也是下半年才适配 我这边也是提前调研踩坑 xff0c 评估一下工作量 这个时间点也完全跟得上Google Play的审核要求 xff08 11
  • Jetpack Compose 从入门到入门(八)

    接着上一篇的动画部分 xff0c 本篇主要是自定义动画与Animatable AnimationSpec 上一篇中 xff0c 出现了多次animationSpec属性 xff0c 它是用来自定义动画规范的 例如 xff1a span cl
  • Jetpack Compose 从入门到入门(九)

    本篇是Compose的手势部分 点击 添加clickable修饰符就可以轻松实现元素的点击 此外它还提供无障碍功能 xff0c 并在点按时显示水波纹效果 span class token annotation builtin 64 Comp
  • 记参加 2022 Google开发者大会

    前几天有幸参加了2022年Google 开发者大会 Google Developer Summit xff0c 上一次参加Google开发者大会还是2019年 这期间因为众所周知的原因 xff0c 开发者大会都改为了线上举办 和上次相比可以
  • Jetpack Compose 从入门到入门(十)

    本篇介绍如何将Jetpack Compose 添加到已有应用中 xff0c 毕竟大多数情况都是在现有项目中使用 Jetpack Compose 旨在配合既有的基于 View 的界面构造方式一起使用 如果您要构建新应用 xff0c 最好的选择
  • Flutter状态管理之Riverpod 2.0

    两年前分享过一篇Flutter状态管理之Riverpod xff0c 当时riverpod的版本还是0 8 0 xff08 后来文章更新到0 14版本 xff09 当时提到过有一些不足之处 xff1a 毕竟诞生不久 xff0c 它还不能保证
  • Python:元组和字典简述

    目录 1 列表的方法2 for循环遍历列表2 1 语法2 2 range 函数 3 元组3 1 元组的基本概念3 2 元组的创建3 3 元组的解包3 3 1 号在解包中的用法 4 字典4 1 字典的基本概念4 2 字典的使用4 2 1 字典
  • 七种常见软件开发模型

    目录 瀑布模型 xff08 面向文档的软件开发模型 xff09 演化模型 螺旋模型 增量模型 构件组装模型 统一过程 xff08 up xff09 xff08 迭代的软件过程 xff0c 以架构为中心 xff09 敏捷开发模型 瀑布模型 x
  • IP安全策略:只允许指定IP连接远程桌面,限制IP登录

    一 xff0c 新建IP安全策略 WIN 43 R打开运行对话框 xff0c 输入gpedit msc进入组策略编辑器 依次打开 本地计算机 策略 计算机配置 Windows设置 安全设置 IP安全策略 在 本地计算机上 在右面的空白处右击
  • 2022年终总结

    不知不觉就到了年末 xff0c 感叹时间过的真快 我自己坚持写了七年多的博客 xff0c 但这其实是我第一次去写年终总结 也不知道怎么写 xff0c 就简单聊聊 写博客的初衷就是个人收获 xff0c 学习的记录 xff0c 分享出来如果能帮
  • Rust库交叉编译以及在Android与iOS中使用

    本篇是关于交叉编译Rust库 xff0c 生成Android和iOS的二进制文件 xff08 so与a文件 xff09 xff0c 以及简单的集成使用 1 环境 系统 xff1a macOS 13 0 M1 Pro xff0c Window
  • 利用Rust与Flutter开发一款小工具

    1 起因 起因是年前看到了一篇Rust 43 iOS amp Android xff5c 未入门也能用来造轮子 xff1f 的文章 xff0c 作者使用Rust做了个实时查看埋点的工具 其中作者的一段话给了我启发 xff1a 无论是 Loo
  • 在Android与iOS中使用LLDB调试Rust程序

    在Rust中通过println 打印的日志信息在Xcode中可以显示 xff0c 但是Android Studio里不显示 所以Android可以使用android logger实现日志输出 但是开发中仅使用打印日志的方式进行调试还是不够的
  • 使用jni-rs实现Rust与Android代码互相调用

    本篇主要是介绍如何使用jni rs 有关jni rs内容基于版本0 20 0 xff0c 新版本写法有所不同 入门用法 在Rust库交叉编译以及在Android与iOS中使用中我简单说明了jni rs及demo代码 xff0c 现在接着补充
  • Android 13 变更及适配攻略

    准备工作 首先将我们项目中的 targetSdkVersion和compileSdkVersion 升至 33 影响Android 13上所有应用 1 通知受限 对新安装的应用的影响 xff1a 如果用户在搭载 Android 13 或更高
  • 洛谷 P1185 绘制二叉树

    一道极为恐怖的模拟题 xff0c 以定义函数的方式确定每个点的x xff0c y就能轻松的做出这道题 xff0c 参考神犇题解 洛谷 P1185 KH 39 s blog 洛谷博客 遇到这种题估计就是放弃了 AC代码 xff08 抄的 xf
  • 洛谷 P3366 【模板】最小生成树#Kruskal+并查集

    说了最小生成树 xff0c 那么就用经典的Prim或者Kruskal xff0c 不过Prim实现代码有点多 xff0c 这里用Kruskal举例 注意事项 1 Kruskal是用来找最小生成树的 根据树的定义可以知道 树是无向图 所以Kr
  • STM32MP157AAA3裸机点灯(汇编)

    STM32MP157AAA3裸机点灯 汇编 MP157的A7核裸机点灯 使用的开发板为华清远见的MP157开发板 xff0c 默认板内emmc已经烧写好了uboot 这篇就只记录一下汇编点灯过程 xff0c uboot等内容暂不涉及 xff
  • 用tkinter写出you-get下载器界面,并用pyinstaller打包成exe文件

    写在前面 xff1a 本文为笔者最早于 2019 05 11 23 15 以 64 拼命三郎 的身份发表于博客园 本文为原创文章 xff0c 转载请标明出处 一 you get介绍 you get是一个基于 python3 的下载工具 xf
  • Linux网络协议栈4--bridge收发包

    bridge 是linux上的虚拟交换机 xff0c 具有交换机的功能 网卡收到包后 xff0c 走到 netif receive skb core后 xff0c 剥完vlan找到vlan子接口 xff08 如果有的话 xff09 xff0