F-stack源码分析

前言

F-stack借鉴了libuinet的内容,不过这东西貌似已经七八年不更新了。

fstack makefile

  • 从makefile里面编译选项的“-D”可以看出,fstack默认支持并开启双栈。
  • 从bbr相关的内容看,fstack应该是支持这些较新的拥塞控制算法。
  • 从编译过程看,不在标准路径下搜索任何标准库文件(从编译选项-nostdinc可以看出。因为现在编的libfstack.a自己就是库),基本都是直接使用freeBSD目录下的头文件。
  • 从编译选项cc -c(只编译不链接)可以看出,的确是编译静态库的步骤,将所有源文件只编译不链接。
  • ar -cqs可以看出,这是打包静态库的步骤。

链接结构

lib下的编译链接过程

编译过程

PKGCONF ?= pkg-config

DPDK_CFLAGS= -Wall -Wno-deprecated-declarations -Werror $(shell $(PKGCONF) --cflags libdpdk)

由此看出,不少CFLAGS都是DPDK要使用的,所以不用全都查一遍。

-M

生成文件关联的信息。包含目标文件所依赖的所有源代码

-frename-registers

Attempt to avoid false dependencies in scheduled code by making use of registers left over after register allocation. This optimization most benefits processors with lots of registers. Depending on the debug information format adopted by the target, however, it can make debugging impossible, since variables no longer stay in a “home register”.

Enabled by default with ‘-funroll-loops’.

-funswitch-loops

Move branches with loop invariant conditions out of the loop, with duplicates of the loop on both branches (modified according to result of the condition).

-Werror

Make all warnings into errors.

-march=arch or cpu-type or native

-march=native

This selects the CPU to generate code for at compilation time by determining the processor type of the compiling machine. Using ‘-march=native’ enables all instruction subsets supported by the local machine (hence the result might not run on different machines). Using ‘-mtune=native’ produces code optimized for the local machine under the constraints of the selected instruction set.

-pipe

Use pipes rather than temporary files for communication between the various stages of compilation. This fails to work on some systems where the assembler is unable to read from a pipe; but the GNU assembler has no trouble.

-std=c99

Determine the language standard.

--param inline-unit-growth=100

inline-unit-growth

Specifies maximal overall growth of the compilation unit caused by inlining. The default value is 20 which limits unit growth to 1.2 times the original size. Cold functions (either marked cold via an attribute or by profile feedback) are not accounted into the unit size.

--param large-function-growth=1000

large-function-growth

Specifies maximal growth of large function caused by inlining in percents. The default value is 100 which limits large function growth to 2.0 times the original size.

-fstack-protector

Emit extra code to check for buffer overflows, such as stack smashing attacks. This is done by adding a guard variable to functions with vulnerable objects. This includes functions that call alloca, and functions with buffers larger than 8 bytes. The guards are initialized when a function is entered and then checked when the function exits. If a guard check fails, an error message is printed and the program exits.

-undef

Do not predefine any system-specific or GCC-specific macros. The standard predefined macros remain defined.

-imacros file

Exactly like ‘-include’, except that any output produced by scanning file is thrown away. Macros it defines remain defined. This allows you to acquire all the macros from a header without also processing its declarations.

All files specified by ‘-imacros’ are processed before all files specified by ‘-include’.

All ‘-imacros file’ and ‘-include file’ options are processed after all ‘-D’ and ‘-U’ options.

-include file

Process file as if #include "file" appeared as the first line of the primary source file. However, the first directory searched for file is the preprocessor’s working directory instead of the directory containing the main source file. If not found there, it is searched for in the remainder of the #include "..." search chain as normal.

If multiple ‘-include’ options are given, the files are included in the order they appear on the command line.

All ‘-imacros file’ and ‘-include file’ options are processed after all ‘-D’ and ‘-U’ options.

-nostdinc

Do not search the standard system directories for header files. Only the directo-ries explicitly specified with ‘-I’, ‘-iquote’, ‘-isystem’, and/or ‘-idirafter’ options (and the directory of the current file, if appropriate) are searched.

与DPDK环境配置和初始化有关的文件

ff_host_interface.c
ff_config.c
ff_ini_parser.c
ff_dpdk_if.c
ff_dpdk_pcap.c
ff_epoll.c
ff_init.c

# 注意,该文件有点不同,由于freeBSD系统不支持DPDK kni,所以如果在freeBSD系统上使用fstack
# 则不编译下面这个文件(从makefile里看出)
ff_dpdk_kni.c

想知道fstack是怎么使用DPDK的,就看上面这些文件,这些文件使用DPDK的过程相对纯粹,没有涉及太多freeBSD的东西。通过nm命令查看最终生成的“libfstack.a”发现,里面的“.o”文件只有上面这几个,freeBSD相关的那些.o文件(包括将DPDK和freeBSD具体粘和起来的文件)全都在libfstack.ro里面,可以通过nm libfstack.ro看到这些“xxx.o”文件的文件名(不仅仅是里面的符号内容),由于libfstack.a是由libfstack.ro和上面这些文件组成的,所以nm libfstack.a只能看到libfstack.ro和这几个文件的.o文件文件名,但是可以看到这些文件涉及到的全部符号名。

链接与最终过程

# 真正的链接命令就是ld -d -r -o libfstack.ro,剩下的全是之前生成的目标文件
# ld -d:即使已经指定ld生成可重定位文件(-r),它们能为公共符号(common symbol)分配空间。
#        脚本命令 FORCE_COMMON_ALLOCATION 起同样作用.
# ld -r:生成可重定位输出,就是说,生成的输出文件能够依次成为ld的输入,一般称之为
#        不完全(partial) 连接。它有一个副效应,在支持标准Unix幻数(magic number)的环
#        境中,这个选项把输出文件的幻数设置成OMAGIC.如果没有指定这个选项,linker生成绝对
#        定位的文件。连接 C++ 程序时,这个选项不会解析出对构造子(constructor)的
#        引用(reference); 不妨改用-Ur选项。
# 先将“freeBSD的源文件”和“fstack粘和DPDK和协议栈的源文件”链接到libfstack.ro中,生成一个
# 可以重定位的临时库文件libfstack.ro,后面还要继续用libfstack.ro来生成真正的库。
# 注意,生成libfstack.ro的过程中,并没有使用诸如ff_host_interface.o那几个与DPDK环境配置
# 和初始化有关的文件。libfstack.ro里面才是freeBSD协议栈和胶水代码的核心,需要支持重定位,
# 因为光”编译“freeBSD协议栈和胶水代码是不够的,必须得通过链接有了符号表知道该怎么重定位才行,
# 之后生成的“库”文件才能被外部直接当作静态库直接使用。
# 之前的编译过程使用“-c”选项,只编译不链接,而这里虽然链接,但是没有将DPDK库链接进来,即此时
# 其实是无法使用DPDK的,这里仅仅是把上面已经编译好的可重定位目标文件进行链接。由于freeBSD本身
# 不需要任何外部库,所以它自身没什么问题,关键就是需要使用DPDK库的胶水代码以及其他初始化DPDK
# 的代码,这里是没有链接DPKD库的,但是,后面编译链接fstack-nginx的时候,链接了DPDK库,这个
# 链接过程其实应该是给fstack使用的!也就是说,一个应用要想使用fstack,得在自己的makefile里
# 的链接过程里把DPDK库libdpdk(用pgk-config来确定使用哪些CFLAGS和LIBS)和libfstack.a链接
# 到自己的程序里,光链接libfstack.a是不行的!
ld -d -r -o libfstack.ro  ff_compat.o ff_glue.o ff_freebsd_init.o ff_init_main.o ff_kern_condvar.o ff_kern_environment.o ff_kern_intr.o ff_kern_subr.o ff_kern_synch.o ff_kern_timeout.o ff_subr_epoch.o ff_lock.o ff_syscall_wrapper.o ff_subr_prf.o ff_vfs_ops.o ff_veth.o ff_route.o sha1.o siphash.o kern_descrip.o kern_event.o kern_fail.o kern_khelp.o kern_hhook.o kern_linker.o kern_mbuf.o kern_module.o kern_mtxpool.o kern_ntptime.o kern_osd.o kern_sysctl.o kern_tc.o kern_uuid.o link_elf.o md5c.o subr_capability.o subr_counter.o subr_eventhandler.o subr_kobj.o subr_lock.o subr_module.o subr_param.o subr_pcpu.o subr_sbuf.o subr_taskqueue.o subr_unit.o subr_smr.o sys_capability.o sys_generic.o sys_socket.o uipc_accf.o uipc_mbuf.o uipc_mbuf2.o uipc_domain.o uipc_sockbuf.o uipc_socket.o uipc_syscalls.o bcd.o gsb_crc32.o inet_ntoa.o jenkins_hash.o strlcpy.o strnlen.o in_cksum.o linker_if.o cryptodev_if.o bpf.o bridgestp.o if.o if_bridge.o if_clone.o if_dead.o if_ethersubr.o if_loop.o if_llatbl.o if_media.o if_spppfr.o if_spppsubr.o if_vlan.o if_vxlan.o in_fib.o in_gif.o ip_reass.o netisr.o pfil.o radix.o raw_cb.o raw_usrreq.o route.o route_ctl.o route_tables.o route_helpers.o route_ifaddrs.o route_temporal.o nhop_utils.o nhop.o nhop_ctl.o rtsock.o slcompress.o if_ether.o if_gif.o igmp.o in.o in_mcast.o in_pcb.o in_proto.o in_rmx.o ip_carp.o ip_divert.o ip_ecn.o ip_encap.o ip_fastfwd.o ip_icmp.o ip_id.o ip_input.o ip_mroute.o ip_options.o ip_output.o raw_ip.o tcp_debug.o tcp_hostcache.o tcp_input.o tcp_lro.o tcp_offload.o tcp_output.o tcp_reass.o tcp_sack.o tcp_subr.o tcp_syncache.o tcp_timer.o tcp_timewait.o tcp_usrreq.o udp_usrreq.o cc.o cc_newreno.o cc_htcp.o cc_cubic.o alias.o alias_db.o alias_mod.o alias_proxy.o alias_sctp.o alias_util.o dest6.o frag6.o icmp6.o in6.o in6_ifattach.o in6_mcast.o in6_pcb.o in6_pcbgroup.o in6_proto.o in6_rmx.o in6_src.o ip6_forward.o ip6_id.o ip6_input.o ip6_fastfwd.o ip6_mroute.o ip6_output.o mld6.o nd6.o nd6_nbr.o nd6_rtr.o raw_ip6.o route6.o scope6.o send.o udp6_usrreq.o in6_cksum.o in6_fib.o in6_gif.o tcp_hpts.o subr_filter.o tcp_ratelimit.o arc4random_uniform.o sack_filter.o rack_bbr_common.o rack.o bbr.o uma_core.o


# 通过nm命令查看最终生成的“libfstack.a”发现,里面的“.o”文件有上面几个“与DPDK环境配置和
# 初始化有关的文件”和libfstack.ro,而freeBSD相关的那些.o文件全都没有,因为这些文件都在
# libfstack.ro里面。
# man nm,查看U表示“未定义的符号”
# libfstack_localize_list.tmp里只有nm libfstack.ro中第三列的数据,即符号名
nm libfstack.ro  | grep -v ' U ' | cut -d ' ' -f 3 > libfstack_localize_list.tmp


# --localize-symbol=symbolname
#   Convert a global or weak symbol called symbolname into a local symbol, 
#   so that it is not visible externally.  This option may be given more than 
#   once.  Note - unique symbols are not converted.
# --localize-symbols=filename
#   Apply --localize-symbol option to each symbol listed in the file filename.  
#   filename is simply a flat file, with one symbol name per line.  Line 
#   comments may be introduced by the hash character.
#   This option may be given more than once.
# 该操作的目的应该是让libfstack.ro里面的 global/weak symbol转换成local symbol,从而
# 让外部不可见这些符号。
objcopy --localize-symbols=libfstack_localize_list.tmp libfstack.ro 

rm libfstack_localize_list.tmp

# --globalize-symbol=symbolname
#   Give symbol symbolname global scoping so that it is visible outside of the 
#   file in which it is defined.  This option may be given more than once.
# --globalize-symbols=filename
#   Apply --globalize-symbol option to each symbol listed in the file filename.  
#   filename is simply a flat file, with one symbol name per line.  
#   Line comments may be introduced by the hash character.
# 该操作的目的应该是让libfstack.ro里面的f_api.symlist所涉及的符号全部拥有global scoping,
# 因此这些符号就可以被外部看到。这样的话,结合上面把其他符号全都变成local的操作,那么此时外部就
# 只能看到libfstack.a提供的API,而无法看到里面的其他符号。
# 从这里也看出ff_api.symlist文件的作用,其实查fstack提供了哪些API,不用看ff_api.h,直接看
# ff_api.symlist文件就行,除非查具体API怎么使用、有哪些参数和返回值时再看ff_api.h。
objcopy --globalize-symbols=ff_api.symlist libfstack.ro


# 删掉之前编译时生成的libfstack.a,其实一般情况下这里不会有libfstack.a的,
# make clean的时候就干掉了。
rm -f libfstack.a


# 静态库打包
ar -cqs libfstack.a libfstack.ro ff_host_interface.o ff_config.o ff_ini_parser.o ff_dpdk_if.o ff_dpdk_pcap.o ff_epoll.o ff_init.o ff_dpdk_kni.o

rm -f libfstack.ro

关于ld -r

Generate relocatable output---i.e., generate an output file that can in turn serve as input to ld. This is often called partial linking. As a side effect, in environments that support standard Unix magic numbers, this option also sets the output file's magic number to "OMAGIC". If this option is not specified, an absolute file is roduced. When linking C++ programs, this option will not resolve references to constructors; to do that, use -Ur. When an input file does not have the same format as the output file, partial linking is only supported if that input file does not contain any relocations. Different output formats can have further restrictions; for example some "a.out"-based formats do not support partial linking with input files in other formats at all. This option does the same thing as -i.

我的理解:应该是用于生成一个库文件A,而且该库文件很可能会继续作为生成另一个库B时需要引用的一个库,所以才必须保证生成的库A是可以重定位的,这样才能保证在生成最终的可执行文件C的时候所有的地址都是准确的。

# 验证libfstack.a不是动态库
$ ldd libfstack.a 
	不是动态可执行文件

fstack-nginx

fstack-nginx和kernel-nginx的大小

fstack-nginx(1.16.1)

34MB

kernel-nginx(1.16.1)

5MB

libfstack.a

2.8MB

由此初步证明nginx使用fstack时,链接过程使用的是静态链接。

链接过程分析

# 查看fstack-nginx使用了哪些动态库,这里没见到直接使用DPDK的库
# 使用ldd ./dpdk-helloworld查DPDK官网例子里面的代码,也没见到里面有直接使用DPDK的库
# 但ldd dperf却可以看到使用了哪些DPDK的库
# 原因应该是fstack-nginx在编译链接的时候,使用的全部都是DPDK的静态库!!!
$ pkg-config --cflags libdpdk
-include rte_config.h -march=native -I/usr/local/include
$ pkg-config --libs libdpdk
-L/usr/local/lib/x86_64-linux-gnu -Wl,--as-needed -lrte_node -lrte_graph -lrte_flow_classify -lrte_pipeline -lrte_table -lrte_pdump -lrte_port -lrte_fib -lrte_ipsec -lrte_vhost -lrte_stack -lrte_security -lrte_sched -lrte_reorder -lrte_rib -lrte_dmadev -lrte_regexdev -lrte_rawdev -lrte_power -lrte_pcapng -lrte_member -lrte_lpm -lrte_latencystats -lrte_kni -lrte_jobstats -lrte_ip_frag -lrte_gso -lrte_gro -lrte_gpudev -lrte_eventdev -lrte_efd -lrte_distributor -lrte_cryptodev -lrte_compressdev -lrte_cfgfile -lrte_bpf -lrte_bitratestats -lrte_bbdev -lrte_acl -lrte_timer -lrte_hash -lrte_metrics -lrte_cmdline -lrte_pci -lrte_ethdev -lrte_meter -lrte_net -lrte_mbuf -lrte_mempool -lrte_rcu -lrte_ring -lrte_eal -lrte_telemetry -lrte_kvargs
$ pkg-config --static --libs libdpdk
-L/usr/local/lib/x86_64-linux-gnu -Wl,--whole-archive -l:librte_common_cpt.a -l:librte_common_dpaax.a -l:librte_common_iavf.a -l:librte_common_octeontx.a -l:librte_common_octeontx2.a -l:librte_bus_auxiliary.a -l:librte_bus_dpaa.a -l:librte_bus_fslmc.a -l:librte_bus_ifpga.a -l:librte_bus_pci.a -l:librte_bus_vdev.a -l:librte_bus_vmbus.a -l:librte_common_cnxk.a -l:librte_common_qat.a -l:librte_common_sfc_efx.a -l:librte_mempool_bucket.a -l:librte_mempool_cnxk.a -l:librte_mempool_dpaa.a -l:librte_mempool_dpaa2.a -l:librte_mempool_octeontx.a -l:librte_mempool_octeontx2.a -l:librte_mempool_ring.a -l:librte_mempool_stack.a -l:librte_dma_cnxk.a -l:librte_dma_dpaa.a -l:librte_dma_hisilicon.a -l:librte_dma_idxd.a -l:librte_dma_ioat.a -l:librte_dma_skeleton.a -l:librte_net_af_packet.a -l:librte_net_ark.a -l:librte_net_atlantic.a -l:librte_net_avp.a -l:librte_net_axgbe.a -l:librte_net_bnx2x.a -l:librte_net_bnxt.a -l:librte_net_bond.a -l:librte_net_cnxk.a -l:librte_net_cxgbe.a -l:librte_net_dpaa.a -l:librte_net_dpaa2.a -l:librte_net_e1000.a -l:librte_net_ena.a -l:librte_net_enetc.a -l:librte_net_enetfec.a -l:librte_net_enic.a -l:librte_net_failsafe.a -l:librte_net_fm10k.a -l:librte_net_hinic.a -l:librte_net_hns3.a -l:librte_net_i40e.a -l:librte_net_iavf.a -l:librte_net_ice.a -l:librte_net_igc.a -l:librte_net_ionic.a -l:librte_net_ixgbe.a -l:librte_net_kni.a -l:librte_net_liquidio.a -l:librte_net_memif.a -l:librte_net_netvsc.a -l:librte_net_nfp.a -l:librte_net_ngbe.a -l:librte_net_null.a -l:librte_net_octeontx.a -l:librte_net_octeontx2.a -l:librte_net_octeontx_ep.a -l:librte_net_pcap.a -l:librte_net_pfe.a -l:librte_net_qede.a -l:librte_net_ring.a -l:librte_net_sfc.a -l:librte_net_softnic.a -l:librte_net_tap.a -l:librte_net_thunderx.a -l:librte_net_txgbe.a -l:librte_net_vdev_netvsc.a -l:librte_net_vhost.a -l:librte_net_virtio.a -l:librte_net_vmxnet3.a -l:librte_raw_cnxk_bphy.a -l:librte_raw_dpaa2_cmdif.a -l:librte_raw_dpaa2_qdma.a -l:librte_raw_ntb.a -l:librte_raw_skeleton.a -l:librte_crypto_bcmfs.a -l:librte_crypto_caam_jr.a -l:librte_crypto_ccp.a -l:librte_crypto_cnxk.a -l:librte_crypto_dpaa_sec.a -l:librte_crypto_dpaa2_sec.a -l:librte_crypto_nitrox.a -l:librte_crypto_null.a -l:librte_crypto_octeontx.a -l:librte_crypto_octeontx2.a -l:librte_crypto_openssl.a -l:librte_crypto_scheduler.a -l:librte_crypto_virtio.a -l:librte_compress_octeontx.a -l:librte_compress_zlib.a -l:librte_regex_octeontx2.a -l:librte_vdpa_ifc.a -l:librte_vdpa_sfc.a -l:librte_event_cnxk.a -l:librte_event_dlb2.a -l:librte_event_dpaa.a -l:librte_event_dpaa2.a -l:librte_event_dsw.a -l:librte_event_octeontx2.a -l:librte_event_opdl.a -l:librte_event_skeleton.a -l:librte_event_sw.a -l:librte_event_octeontx.a -l:librte_baseband_acc100.a -l:librte_baseband_fpga_5gnr_fec.a -l:librte_baseband_fpga_lte_fec.a -l:librte_baseband_la12xx.a -l:librte_baseband_null.a -l:librte_baseband_turbo_sw.a -l:librte_node.a -l:librte_graph.a -l:librte_flow_classify.a -l:librte_pipeline.a -l:librte_table.a -l:librte_pdump.a -l:librte_port.a -l:librte_fib.a -l:librte_ipsec.a -l:librte_vhost.a -l:librte_stack.a -l:librte_security.a -l:librte_sched.a -l:librte_reorder.a -l:librte_rib.a -l:librte_dmadev.a -l:librte_regexdev.a -l:librte_rawdev.a -l:librte_power.a -l:librte_pcapng.a -l:librte_member.a -l:librte_lpm.a -l:librte_latencystats.a -l:librte_kni.a -l:librte_jobstats.a -l:librte_ip_frag.a -l:librte_gso.a -l:librte_gro.a -l:librte_gpudev.a -l:librte_eventdev.a -l:librte_efd.a -l:librte_distributor.a -l:librte_cryptodev.a -l:librte_compressdev.a -l:librte_cfgfile.a -l:librte_bpf.a -l:librte_bitratestats.a -l:librte_bbdev.a -l:librte_acl.a -l:librte_timer.a -l:librte_hash.a -l:librte_metrics.a -l:librte_cmdline.a -l:librte_pci.a -l:librte_ethdev.a -l:librte_meter.a -l:librte_net.a -l:librte_mbuf.a -l:librte_mempool.a -l:librte_rcu.a -l:librte_ring.a -l:librte_eal.a -l:librte_telemetry.a -l:librte_kvargs.a -Wl,--no-whole-archive -Wl,--export-dynamic -lpcap -latomic -lcrypto -ldl -pthread -lelf -lz -Wl,--as-needed -lrte_node -lrte_graph -lrte_flow_classify -lrte_pipeline -lrte_table -lrte_pdump -lrte_port -lrte_fib -lrte_ipsec -lrte_vhost -lrte_stack -lrte_security -lrte_sched -lrte_reorder -lrte_rib -lrte_dmadev -lrte_regexdev -lrte_rawdev -lrte_power -lrte_pcapng -lrte_member -lrte_lpm -lrte_latencystats -lrte_kni -lrte_jobstats -lrte_ip_frag -lrte_gso -lrte_gro -lrte_gpudev -lrte_eventdev -lrte_efd -lrte_distributor -lrte_cryptodev -lrte_compressdev -lrte_cfgfile -lrte_bpf -lrte_bitratestats -lrte_bbdev -lrte_acl -lrte_timer -lrte_hash -lrte_metrics -lrte_cmdline -lrte_pci -lrte_ethdev -lrte_meter -lrte_net -lrte_mbuf -lrte_mempool -lrte_rcu -lrte_ring -lrte_eal -lrte_telemetry -lrte_kvargs -pthread -lm -ldl -lnuma -lpcap

$ ldd nginx
	linux-vdso.so.1 (0x00007ffd81e64000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd7cb304000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd7cb2e3000)
	libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fd7cb2a9000)
	libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fd7cb235000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fd7cb017000)
	libpcap.so.0.8 => /lib/x86_64-linux-gnu/libpcap.so.0.8 (0x00007fd7cadd5000)
	libatomic.so.1 => /lib/x86_64-linux-gnu/libatomic.so.1 (0x00007fd7cadc9000)
	libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fd7caae0000)
	libelf.so.1 => /lib/x86_64-linux-gnu/libelf.so.1 (0x00007fd7caac4000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd7ca941000)
	libnuma.so.1 => /lib/x86_64-linux-gnu/libnuma.so.1 (0x00007fd7ca933000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fd7ca929000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd7ca766000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fd7cd3e5000)
gcc -o...
	-ldl 
	-lpthread 
	-lcrypt 
	-lpcre 
	-lz 

# 以下的链接内容全部来自$(shell pkg-config --static --libs libdpdk)
	-L/usr/local/lib/x86_64-linux-gnu 
# 查gcc的帮助,-Wl是gcc的
# -Wl,<options>            Pass comma-separated <options> on to the linker.
	-Wl,--whole-archive 
# 这里链接了DPDK的静态库
	-l:librte_common_cpt.a 	-l:librte_common_dpaax.a 	-l:librte_common_iavf.a 	-l:librte_common_octeontx.a 	-l:librte_common_octeontx2.a 	-l:librte_bus_auxiliary.a 	-l:librte_bus_dpaa.a 	-l:librte_bus_fslmc.a 	-l:librte_bus_ifpga.a 	-l:librte_bus_pci.a 	-l:librte_bus_vdev.a 		-l:librte_bus_vmbus.a -l:librte_common_cnxk.a -l:librte_common_qat.a -l:librte_common_sfc_efx.a -l:librte_mempool_bucket.a -l:librte_mempool_cnxk.a -l:librte_mempool_dpaa.a -l:librte_mempool_dpaa2.a -l:librte_mempool_octeontx.a -l:librte_mempool_octeontx2.a -l:librte_mempool_ring.a -l:librte_mempool_stack.a -l:librte_dma_cnxk.a -l:librte_dma_dpaa.a -l:librte_dma_hisilicon.a -l:librte_dma_idxd.a -l:librte_dma_ioat.a -l:librte_dma_skeleton.a -l:librte_net_af_packet.a -l:librte_net_ark.a -l:librte_net_atlantic.a -l:librte_net_avp.a -l:librte_net_axgbe.a -l:librte_net_bnx2x.a -l:librte_net_bnxt.a -l:librte_net_bond.a -l:librte_net_cnxk.a -l:librte_net_cxgbe.a -l:librte_net_dpaa.a -l:librte_net_dpaa2.a -l:librte_net_e1000.a -l:librte_net_ena.a -l:librte_net_enetc.a -l:librte_net_enetfec.a -l:librte_net_enic.a -l:librte_net_failsafe.a -l:librte_net_fm10k.a -l:librte_net_hinic.a -l:librte_net_hns3.a -l:librte_net_i40e.a -l:librte_net_iavf.a -l:librte_net_ice.a -l:librte_net_igc.a -l:librte_net_ionic.a -l:librte_net_ixgbe.a -l:librte_net_kni.a -l:librte_net_liquidio.a -l:librte_net_memif.a -l:librte_net_netvsc.a -l:librte_net_nfp.a -l:librte_net_ngbe.a -l:librte_net_null.a -l:librte_net_octeontx.a -l:librte_net_octeontx2.a -l:librte_net_octeontx_ep.a -l:librte_net_pcap.a -l:librte_net_pfe.a -l:librte_net_qede.a -l:librte_net_ring.a -l:librte_net_sfc.a -l:librte_net_softnic.a -l:librte_net_tap.a -l:librte_net_thunderx.a -l:librte_net_txgbe.a -l:librte_net_vdev_netvsc.a -l:librte_net_vhost.a -l:librte_net_virtio.a -l:librte_net_vmxnet3.a -l:librte_raw_cnxk_bphy.a -l:librte_raw_dpaa2_cmdif.a -l:librte_raw_dpaa2_qdma.a -l:librte_raw_ntb.a -l:librte_raw_skeleton.a -l:librte_crypto_bcmfs.a -l:librte_crypto_caam_jr.a -l:librte_crypto_ccp.a -l:librte_crypto_cnxk.a -l:librte_crypto_dpaa_sec.a -l:librte_crypto_dpaa2_sec.a -l:librte_crypto_nitrox.a -l:librte_crypto_null.a -l:librte_crypto_octeontx.a -l:librte_crypto_octeontx2.a -l:librte_crypto_openssl.a -l:librte_crypto_scheduler.a -l:librte_crypto_virtio.a -l:librte_compress_octeontx.a -l:librte_compress_zlib.a -l:librte_regex_octeontx2.a -l:librte_vdpa_ifc.a -l:librte_vdpa_sfc.a -l:librte_event_cnxk.a -l:librte_event_dlb2.a -l:librte_event_dpaa.a -l:librte_event_dpaa2.a -l:librte_event_dsw.a -l:librte_event_octeontx2.a -l:librte_event_opdl.a -l:librte_event_skeleton.a -l:librte_event_sw.a -l:librte_event_octeontx.a -l:librte_baseband_acc100.a -l:librte_baseband_fpga_5gnr_fec.a -l:librte_baseband_fpga_lte_fec.a -l:librte_baseband_la12xx.a -l:librte_baseband_null.a -l:librte_baseband_turbo_sw.a -l:librte_node.a -l:librte_graph.a -l:librte_flow_classify.a -l:librte_pipeline.a -l:librte_table.a -l:librte_pdump.a -l:librte_port.a -l:librte_fib.a -l:librte_ipsec.a -l:librte_vhost.a -l:librte_stack.a -l:librte_security.a -l:librte_sched.a -l:librte_reorder.a -l:librte_rib.a -l:librte_dmadev.a -l:librte_regexdev.a -l:librte_rawdev.a -l:librte_power.a -l:librte_pcapng.a -l:librte_member.a -l:librte_lpm.a -l:librte_latencystats.a -l:librte_kni.a -l:librte_jobstats.a -l:librte_ip_frag.a -l:librte_gso.a -l:librte_gro.a -l:librte_gpudev.a -l:librte_eventdev.a -l:librte_efd.a -l:librte_distributor.a -l:librte_cryptodev.a -l:librte_compressdev.a -l:librte_cfgfile.a -l:librte_bpf.a -l:librte_bitratestats.a -l:librte_bbdev.a -l:librte_acl.a -l:librte_timer.a -l:librte_hash.a -l:librte_metrics.a -l:librte_cmdline.a -l:librte_pci.a -l:librte_ethdev.a -l:librte_meter.a -l:librte_net.a -l:librte_mbuf.a -l:librte_mempool.a -l:librte_rcu.a -l:librte_ring.a -l:librte_eal.a -l:librte_telemetry.a -l:librte_kvargs.a 	
	-Wl,--no-whole-archive 
	-Wl,--export-dynamic 
	-lpcap 
	-latomic 
	-lcrypto 
	-ldl 
	-pthread 
	-lelf -lz 
	-Wl,--as-needed 
# 下面这些库,同时具有静态版本和动态版本,只有后缀名不同(.so and .a),
# 编译器(链接器)默认使用动态库
# 经过与上面“-l:librte_xxxx.a -l:librte_xxx.a ...”的对比,发现这里欲链接的动态库,
# 在上面已经将相应的静态库链接进来了,所以这里链接动态库的动作应该是不会生效的,因为相应的静态库
# 已经都被链接进来了,并且由于是先链接的静态库,那些“.o”可重定位文件里面被挖空的地址已经被填上,
# 所有这里的动态库没有一个会被真正使用,也就是说,fstack-nginx使用的DPDK库全都是静态库。
# 这就解释了为什么上面使用“ldd nginx”(这个是fstack-nginx)的时候看不到任何DPDK相关的动态库。
	-lrte_node -lrte_graph -lrte_flow_classify -lrte_pipeline -lrte_table -lrte_pdump -lrte_port -lrte_fib -lrte_ipsec -lrte_vhost -lrte_stack -lrte_security -lrte_sched -lrte_reorder -lrte_rib -lrte_dmadev -lrte_regexdev -lrte_rawdev -lrte_power -lrte_pcapng -lrte_member -lrte_lpm -lrte_latencystats -lrte_kni -lrte_jobstats -lrte_ip_frag -lrte_gso -lrte_gro -lrte_gpudev -lrte_eventdev -lrte_efd -lrte_distributor -lrte_cryptodev -lrte_compressdev -lrte_cfgfile -lrte_bpf -lrte_bitratestats -lrte_bbdev -lrte_acl -lrte_timer -lrte_hash -lrte_metrics -lrte_cmdline -lrte_pci -lrte_ethdev -lrte_meter -lrte_net -lrte_mbuf -lrte_mempool -lrte_rcu -lrte_ring -lrte_eal -lrte_telemetry -lrte_kvargs 
	-pthread 
	-lm 
	-ldl 
	-lnuma 
	-lpcap 
# end

	-L/usr/local/lib 
	-Wl,--whole-archive,
# 将libfstack.a这个静态库链接了进来
	-lfstack,
	--no-whole-archive 
	-Wl,--no-whole-archive 
	-lrt -lm -ldl -lcrypto -lpthread -lnuma -Wl,-E

objs/Makefile

gcc -o ...
	-ldl 
	-lpthread 
	-lcrypt 	
	-lpcre 
	-lz 
	$(shell pkg-config --static --libs libdpdk) 
	-L/usr/local/lib 
	-Wl,--whole-archive,
	-lfstack,
	--no-whole-archive 
	-Wl,--no-whole-archive 
	-lrt -lm -ldl -lcrypto -lpthread -lnuma -Wl,-E

$(shell pkg-config --static --libs libdpdk)

这里面的--static是说:Output libraries suitable for static linking. That means including any private libraries in the output。意思应该是把可以静态链接的所有库也都列出来,效果就是把用到的所有库,包括动态库、静态库、私有库也全部都列出来。这句的功能是将整个DPDK库(libdpdk)用到的所有库(包含动态、静态,使用到的私有库)全部都列出来,链接fstack-nginx的时候全都要用全都要链接(视库本身是静态还是动态库决定使用静态链接还是动态链接),难怪fstack-nginx会特别大。

DPDK相关(rte_xxx)的库有的是静态库(librte_xxxx.a)有的是动态库,并且DPDK的库都是同时具有动态库和静态库。如下所示先后两个链接过程链接的DPDK的库,都同时具有静态库和动态库两个版本,A的库的数量、种类多于B,即B中涉及到的动态库,在A中都有对应的静态库。用ldd 查看fstack-nginx使用的动态库时,发现里面并没有使用任何DPDK 的动态库,这是因为A在B之前被链接,当B被链接的时候,目标文件中被挖空的地址已经被A中的静态库填充,所以根本没有用到B,因此ldd命令看不到任何DPDK相关的动态库。

# A
-l:librte_common_cpt.a 	-l:librte_common_dpaax.a 	-l:librte_common_iavf.a 	-l:librte_common_octeontx.a 	-l:librte_common_octeontx2.a 	-l:librte_bus_auxiliary.a 	-l:librte_bus_dpaa.a 	-l:librte_bus_fslmc.a 	-l:librte_bus_ifpga.a 	-l:librte_bus_pci.a 	-l:librte_bus_vdev.a 		-l:librte_bus_vmbus.a -l:librte_common_cnxk.a -l:librte_common_qat.a -l:librte_common_sfc_efx.a -l:librte_mempool_bucket.a -l:librte_mempool_cnxk.a -l:librte_mempool_dpaa.a -l:librte_mempool_dpaa2.a -l:librte_mempool_octeontx.a -l:librte_mempool_octeontx2.a -l:librte_mempool_ring.a -l:librte_mempool_stack.a -l:librte_dma_cnxk.a -l:librte_dma_dpaa.a -l:librte_dma_hisilicon.a -l:librte_dma_idxd.a -l:librte_dma_ioat.a -l:librte_dma_skeleton.a -l:librte_net_af_packet.a -l:librte_net_ark.a -l:librte_net_atlantic.a -l:librte_net_avp.a -l:librte_net_axgbe.a -l:librte_net_bnx2x.a -l:librte_net_bnxt.a -l:librte_net_bond.a -l:librte_net_cnxk.a -l:librte_net_cxgbe.a -l:librte_net_dpaa.a -l:librte_net_dpaa2.a -l:librte_net_e1000.a -l:librte_net_ena.a -l:librte_net_enetc.a -l:librte_net_enetfec.a -l:librte_net_enic.a -l:librte_net_failsafe.a -l:librte_net_fm10k.a -l:librte_net_hinic.a -l:librte_net_hns3.a -l:librte_net_i40e.a -l:librte_net_iavf.a -l:librte_net_ice.a -l:librte_net_igc.a -l:librte_net_ionic.a -l:librte_net_ixgbe.a -l:librte_net_kni.a -l:librte_net_liquidio.a -l:librte_net_memif.a -l:librte_net_netvsc.a -l:librte_net_nfp.a -l:librte_net_ngbe.a -l:librte_net_null.a -l:librte_net_octeontx.a -l:librte_net_octeontx2.a -l:librte_net_octeontx_ep.a -l:librte_net_pcap.a -l:librte_net_pfe.a -l:librte_net_qede.a -l:librte_net_ring.a -l:librte_net_sfc.a -l:librte_net_softnic.a -l:librte_net_tap.a -l:librte_net_thunderx.a -l:librte_net_txgbe.a -l:librte_net_vdev_netvsc.a -l:librte_net_vhost.a -l:librte_net_virtio.a -l:librte_net_vmxnet3.a -l:librte_raw_cnxk_bphy.a -l:librte_raw_dpaa2_cmdif.a -l:librte_raw_dpaa2_qdma.a -l:librte_raw_ntb.a -l:librte_raw_skeleton.a -l:librte_crypto_bcmfs.a -l:librte_crypto_caam_jr.a -l:librte_crypto_ccp.a -l:librte_crypto_cnxk.a -l:librte_crypto_dpaa_sec.a -l:librte_crypto_dpaa2_sec.a -l:librte_crypto_nitrox.a -l:librte_crypto_null.a -l:librte_crypto_octeontx.a -l:librte_crypto_octeontx2.a -l:librte_crypto_openssl.a -l:librte_crypto_scheduler.a -l:librte_crypto_virtio.a -l:librte_compress_octeontx.a -l:librte_compress_zlib.a -l:librte_regex_octeontx2.a -l:librte_vdpa_ifc.a -l:librte_vdpa_sfc.a -l:librte_event_cnxk.a -l:librte_event_dlb2.a -l:librte_event_dpaa.a -l:librte_event_dpaa2.a -l:librte_event_dsw.a -l:librte_event_octeontx2.a -l:librte_event_opdl.a -l:librte_event_skeleton.a -l:librte_event_sw.a -l:librte_event_octeontx.a -l:librte_baseband_acc100.a -l:librte_baseband_fpga_5gnr_fec.a -l:librte_baseband_fpga_lte_fec.a -l:librte_baseband_la12xx.a -l:librte_baseband_null.a -l:librte_baseband_turbo_sw.a -l:librte_node.a -l:librte_graph.a -l:librte_flow_classify.a -l:librte_pipeline.a -l:librte_table.a -l:librte_pdump.a -l:librte_port.a -l:librte_fib.a -l:librte_ipsec.a -l:librte_vhost.a -l:librte_stack.a -l:librte_security.a -l:librte_sched.a -l:librte_reorder.a -l:librte_rib.a -l:librte_dmadev.a -l:librte_regexdev.a -l:librte_rawdev.a -l:librte_power.a -l:librte_pcapng.a -l:librte_member.a -l:librte_lpm.a -l:librte_latencystats.a -l:librte_kni.a -l:librte_jobstats.a -l:librte_ip_frag.a -l:librte_gso.a -l:librte_gro.a -l:librte_gpudev.a -l:librte_eventdev.a -l:librte_efd.a -l:librte_distributor.a -l:librte_cryptodev.a -l:librte_compressdev.a -l:librte_cfgfile.a -l:librte_bpf.a -l:librte_bitratestats.a -l:librte_bbdev.a -l:librte_acl.a -l:librte_timer.a -l:librte_hash.a -l:librte_metrics.a -l:librte_cmdline.a -l:librte_pci.a -l:librte_ethdev.a -l:librte_meter.a -l:librte_net.a -l:librte_mbuf.a -l:librte_mempool.a -l:librte_rcu.a -l:librte_ring.a -l:librte_eal.a -l:librte_telemetry.a -l:librte_kvargs.a 	

# B
-lrte_node -lrte_graph -lrte_flow_classify -lrte_pipeline -lrte_table -lrte_pdump -lrte_port -lrte_fib -lrte_ipsec -lrte_vhost -lrte_stack -lrte_security -lrte_sched -lrte_reorder -lrte_rib -lrte_dmadev -lrte_regexdev -lrte_rawdev -lrte_power -lrte_pcapng -lrte_member -lrte_lpm -lrte_latencystats -lrte_kni -lrte_jobstats -lrte_ip_frag -lrte_gso -lrte_gro -lrte_gpudev -lrte_eventdev -lrte_efd -lrte_distributor -lrte_cryptodev -lrte_compressdev -lrte_cfgfile -lrte_bpf -lrte_bitratestats -lrte_bbdev -lrte_acl -lrte_timer -lrte_hash -lrte_metrics -lrte_cmdline -lrte_pci -lrte_ethdev -lrte_meter -lrte_net -lrte_mbuf -lrte_mempool -lrte_rcu -lrte_ring -lrte_eal -lrte_telemetry -lrte_kvargs 

可以通过nm命令查看fstack-nginx里面包含了哪些已经被定义了的符号来佐证DPDK库和fstack库是通过静态链接进入fstack-nginx的。

# 这个nginx是fstack-nginx
$ nm nginx
...
# 这些是fstack的
00000000012adeb0 T ff_accept
00000000012ae030 T ff_bind
00000000012ae0d0 T ff_connect
00000000013c77b0 T ff_dpdk_init
00000000013c8840 T ff_dpdk_run
00000000012ade50 T ff_fcntl
00000000012aa260 T ff_freebsd_init
00000000012ad060 T ff_getsockopt
00000000013c9670 T ff_init
00000000012adfe0 T ff_listen
...
# 这些是DPDK的
000000000125df70 T rte_eth_tx_buffer_init
0000000001201c20 T rte_event_eth_tx_adapter_free
00000000011fe580 T rte_event_eth_rx_adapter_start
...

fstack example 分析

编译链接过程

$ cat Makefile 
TOPDIR=..

ifeq ($(FF_PATH),)
	FF_PATH=${TOPDIR}
endif

ifneq ($(shell pkg-config --exists libdpdk && echo 0),0)
$(error "No installation of DPDK found, maybe you should export environment variable `PKG_CONFIG_PATH`")
endif

PKGCONF ?= pkg-config

# 使用libdpdk的cflags
CFLAGS += -O -gdwarf-2 $(shell $(PKGCONF) --cflags libdpdk)

# 注意 --static,这里应该还是把libdpdk的所有库全部静态链接了,因为这个过程会先显示静态库,
# 然后才是动态库,静态库先被链接。
# -lfstack 链接libfstack.a这个静态库
LIBS+= $(shell $(PKGCONF) --static --libs libdpdk)
LIBS+= -L${FF_PATH}/lib -Wl,--whole-archive,-lfstack,--no-whole-archive
LIBS+= -Wl,--no-whole-archive -lrt -lm -ldl -lcrypto -pthread -lnuma

# 最终生成两个可执行程序,helloworld和helloworld_epoll
# 这两个可执行文件功能相同,实现方式不同,一个用kqueue,一个用epoll。
TARGET="helloworld"
all:
	cc ${CFLAGS} -DINET6 -o ${TARGET} main.c ${LIBS}
	cc ${CFLAGS} -o ${TARGET}_epoll main_epoll.c ${LIBS}

.PHONY: clean
clean:
	rm -f *.o ${TARGET} ${TARGET}_epoll

fstack库总结

  1. fstack在编译后最终生成了一个名为libfstack.a的静态库;
  2. libfstack.a静态库里面包含了freeBSD协议栈的源码和fstack的胶水代码;
  3. libfstack.a里面没有链接DPDK库,接入fstack的应用程序在编译、链接的阶段必须链接DPDK库(具体使用静态还是动态方式链接视情况而定);
  4. 应用程序通过静态链接使用fstack库,最终的可执行文件较大。

fstack的几个特殊源文件

ff_host_interface.c
ff_config.c
ff_ini_parser.c
ff_dpdk_if.c
ff_dpdk_pcap.c
ff_epoll.c
ff_init.c
ff_dpdk_kni.c

fstack与freebsd协议栈报文处理流程

配置文件

FF_USE_PAGE_ARRAY开关,开启了这个开关,那么从bsd到dpdk的时候就不用mcopy了。默认不开启这个开关,要想开的话可以到fstack的Makefile里45行面去掉注释。RTE_PROC_PRIMARY相关的问题记录文档中,还有官网里这部分内容运行时候的命令行参数。

DPDK<-->freeBSD总流程

注册与初始化

DPDK作为“驱动”注册给freebsd的函数

如下图所示,该流程主要保证DPDK与freeBSD协议栈的对接。

在fstack中,freebsd协议栈将DPDK视为“网卡驱动”,那么该“网卡驱动”就需要将freebsd协议栈所需的各种函数都注册到freebsd协议栈提供的函数指针上,而这些函数指针挂在freebsd提供的结构体上,就比如ifp。这里最重要的执行注册流程的函数就是ff_veth_setup_interface()

freebsd注册自己的协议栈处理函数

freeBSD自己本身会通过SYSINIT宏在freeBSD操作系统启动的时候执行传给该宏的函数,所以一般注册某些函数的流程、某些执行初始化动作的函数都是通过这个宏来实现被调用动作的。下图所示为freeBSD自己内部在启动时注册各个协议层处理函数与初始化各个协议层的流程。

ether_init()被注册的过程

ether_init()通过SYSINIT()宏在系统启动阶段被初始化调用。该函数用netisr_register(&ether_nh)将二层协议的处理流程ether_nh_input()注册到struct netisr_proto netisr_proto[...]中,以便之后调用netisr_dispatch()的时候可以直接通过对应协议的函数指针来调用。

ip_init()被注册的过程

domain_init()函数通过DOMAIN_SET()SYSINIT()在系统初始化的时候被调用,如下所示,

freebsd/sys/domain.h

 83 #define DOMAIN_SET(name)                        \                                                                                                                                                               
 84     SYSINIT(domain_add_ ## name, SI_SUB_PROTO_DOMAIN,       \                                         
 85         SI_ORDER_FIRST, domain_add, & name ## domain);      \                                         
 86     SYSINIT(domain_init_ ## name, SI_SUB_PROTO_DOMAIN,      \                                         
 87         SI_ORDER_SECOND, domain_init, & name ## domain);                                              
 88 #ifdef VIMAGE                                                                                         
 89 #define VNET_DOMAIN_SET(name)                       \                                                 
 90     SYSINIT(domain_add_ ## name, SI_SUB_PROTO_DOMAIN,       \                                         
 91         SI_ORDER_FIRST, domain_add, & name ## domain);      \                                         
 92     VNET_SYSINIT(vnet_domain_init_ ## name, SI_SUB_PROTO_DOMAIN,    \                                 
 93         SI_ORDER_SECOND, vnet_domain_init, & name ## domain);   \                                     
 94     VNET_SYSUNINIT(vnet_domain_uninit_ ## name,         \                                             
 95         SI_SUB_PROTO_DOMAIN, SI_ORDER_SECOND, vnet_domain_uninit,   \                                 
 96         & name ## domain)                                                                             
 97 #else /* !VIMAGE */                                                                                   
 98 #define VNET_DOMAIN_SET(name)   DOMAIN_SET(name)                                                      
 99 #endif /* VIMAGE */                                                                                   

freebsd/kern/uipc_domain.c

 52 /*
 53  * System initialization
 54  *
 55  * Note: domain initialization takes place on a per domain basis
 56  * as a result of traversing a SYSINIT linker set.  Most likely,
 57  * each domain would want to call DOMAIN_SET(9) itself, which                                                                                                                                                   
 58  * would cause the domain to be added just after domaininit()
 59  * is called during startup.
 60  *
 61  * See DOMAIN_SET(9) for details on its use.
 62  */

domain_init()里面通过如下所示,

107 static void
108 protosw_init(struct protosw *pr)
109 {
110     struct pr_usrreqs *pu;
111 
112     pu = pr->pr_usrreqs;
113     KASSERT(pu != NULL, ("protosw_init: %ssw[%d] has no usrreqs!",
114         pr->pr_domain->dom_name,
115         (int)(pr - pr->pr_domain->dom_protosw)));
116 
117     /*
118      * Protocol switch methods fall into three categories: mandatory,
119      * mandatory but protosw_init() provides a default, and optional.
120      *
121      * For true protocols (i.e., pru_attach != NULL), KASSERT truly
122      * mandatory methods with no defaults, and initialize defaults for
123      * other mandatory methods if the protocol hasn't defined an
124      * implementation (NULL function pointer).
125      */
126 #if 0
127     if (pu->pru_attach != NULL) {
128         KASSERT(pu->pru_abort != NULL,
129             ("protosw_init: %ssw[%d] pru_abort NULL",
130             pr->pr_domain->dom_name,
131             (int)(pr - pr->pr_domain->dom_protosw)));
132         KASSERT(pu->pru_send != NULL,
133             ("protosw_init: %ssw[%d] pru_send NULL",
134             pr->pr_domain->dom_name,
135             (int)(pr - pr->pr_domain->dom_protosw)));
136     }
137 #endif                                                                                                                                                                                                          
138 
139 #define DEFAULT(foo, bar)   if ((foo) == NULL)  (foo) = (bar)
140     DEFAULT(pu->pru_accept, pru_accept_notsupp);
141     DEFAULT(pu->pru_aio_queue, pru_aio_queue_notsupp);
142     DEFAULT(pu->pru_bind, pru_bind_notsupp);
143     DEFAULT(pu->pru_bindat, pru_bindat_notsupp);
144     DEFAULT(pu->pru_connect, pru_connect_notsupp);
145     DEFAULT(pu->pru_connect2, pru_connect2_notsupp);
146     DEFAULT(pu->pru_connectat, pru_connectat_notsupp);
147     DEFAULT(pu->pru_control, pru_control_notsupp);
148     DEFAULT(pu->pru_disconnect, pru_disconnect_notsupp);
149     DEFAULT(pu->pru_listen, pru_listen_notsupp);
150     DEFAULT(pu->pru_peeraddr, pru_peeraddr_notsupp);
151     DEFAULT(pu->pru_rcvd, pru_rcvd_notsupp);
152     DEFAULT(pu->pru_rcvoob, pru_rcvoob_notsupp);
153     DEFAULT(pu->pru_sense, pru_sense_null);
154     DEFAULT(pu->pru_shutdown, pru_shutdown_notsupp);
155     DEFAULT(pu->pru_sockaddr, pru_sockaddr_notsupp);
156     DEFAULT(pu->pru_sosend, sosend_generic);
157     DEFAULT(pu->pru_soreceive, soreceive_generic);
158     DEFAULT(pu->pru_sopoll, sopoll_generic);                                                                                                                                                                    
159     DEFAULT(pu->pru_ready, pru_ready_notsupp);
160 #undef DEFAULT
161 
162     // 这里会将注册到inetsw[]中的所有初始化函数全部执行一遍。
163     if (pr->pr_init)
164         (*pr->pr_init)();
165 }
166 
167 /*
168  * Add a new protocol domain to the list of supported domains
169  * Note: you cant unload it again because a socket may be using it.
170  * XXX can't fail at this time.
171  * 该函数本质上是通过SYSINIT()得以在系统初始化的时候被执行,
172  * freebsd/sys/domain.h 中见宏DOMAIN_SET(name),注意里面有个条件编译选项VIMAGE,
173  * 注意在fstack中是没有使用该选项的,所以应该看下面的else,即VNET_DOMAIN_SET()其实就
174  * 是DOMAIN_SET(),搜索DOMAIN_SET()的被调用点时,应该同时关注VNET_DOMAIN_SET()的被调用点,
175  * 由此发现,真正注册并调用inetsw[]中各个初始化函数的其实是freebsd/netinet/in_proto.c文件
176  * 中的“VNET_DOMAIN_SET(inet);”,最终的调用方式为“DOMAIN_SET(inet);”,根据该宏定义内容判断,
177  * 传给domain_init()的参数为inetdomain,
178  */
179 void
180 domain_init(void *arg)
181 {
182     // freebsd/sys/domain.h 见struct domain
183     struct domain *dp = arg;
184     struct protosw *pr;
185 
186     if (dp->dom_init)
187         (*dp->dom_init)();
188     for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++)
189         protosw_init(pr);
190     /*
191      * update global information about maximums
192      */
193     max_hdr = max_linkhdr + max_protohdr;
194     max_datalen = MHLEN - max_hdr;
195     if (max_datalen < 1)
196         panic("%s: max_datalen < 1", __func__);
197 }

freebsd/netinet/in_proto.c

296 struct domain inetdomain = {                                                                                                                                                                                    
297     .dom_family =       AF_INET,
298     .dom_name =     "internet",
299     .dom_protosw =      inetsw,
300     .dom_protoswNPROTOSW =  &inetsw[nitems(inetsw)],
301     .dom_rtattach =     in_inithead,
302 #ifdef VIMAGE
303     .dom_rtdetach =     in_detachhead,
304 #endif
305     .dom_ifattach =     in_domifattach,
306     .dom_ifdetach =     in_domifdetach
307 };  
308
309 VNET_DOMAIN_SET(inet);

调用struct protosw inetsw[]中注册的每一个pr_init()函数来完成协议的初始化,而ip_init()就是直接被注册到全局结构体数组struct protosw inetsw[]中的pr_init()的。像inetsw[]中的其他init函数,比如udp_init()/tcp_init()等,都是在domain_init()中被调用执行的。

ip_init()netisr_register(&ip_nh)将三层协议的处理流程ip_input()注册到全局结构体数组struct netisr_proto netisr_proto[...]中,之后才可以在收包流程中调用ip_input()来处理三层协议。

循环运行业务流程图

具体的业务函数内部不用自己循环执行具体业务内容,main_loop()函数里面已经提供了一个while(1)循环专门执行对应的业务。

收包/发包流程图

ff_init分析

ff_init 使用方法

# argc:指明argv中的元素个数
# argv:指针数组,里面每一个元素都是字符串,每个字符串都是要传递给DPDK EAL的参数
int ff_init(int argc, char * const argv[]);

该函数用于加载、解析f-stack.conf配置文件,以便初始化整个DPDK环境和fstack、freeBSD环境。该配置文件可以从fstack的根目录下拷贝config.ini获得。运行基于fstack的程序时,必须加载该配置文件,并且在运行基于fstack的程序时还要在命令行添加最终会传给fstack的“命令选项”,包括但不限于:

--conf=xxx --proc-id=0 --proc-type=primary or secondary

如果不想在命令行下添加上面的这些内容,可以在调用ff_init()函数的时候,仿照fstack-nginx里面调用ff_init()的过程,将这些参数组成字符串作为ff_init()的入参,如下,

// proc_type, 1: primary, 0: secondary.
int
ff_mod_init(const char *conf, int proc_id, int proc_type) {                                                                                                                                                         
    int rc, i;
    int ff_argc = 4;
 
    char **ff_argv = malloc(sizeof(char *)*ff_argc);
    for (i = 0; i < ff_argc; i++) {
        ff_argv[i] = malloc(sizeof(char)*PATH_MAX);
    }
 
    sprintf(ff_argv[0], "nginx");
    sprintf(ff_argv[1], "--conf=%s", conf);   // f-stack.conf
    sprintf(ff_argv[2], "--proc-id=%d", proc_id);
    if (proc_type == 1) {
        sprintf(ff_argv[3], "--proc-type=primary");
    } else {
        sprintf(ff_argv[3], "--proc-type=secondary");
    }   
 
    rc = ff_init(ff_argc, ff_argv);
    if (rc == 0) {
        /* Ensure that the socket we converted
                does not exceed the maximum value of 'int' */
 
        if(ngx_max_sockets + (unsigned)ff_getmaxfd() > INT_MAX)
        {   
            rc = -1; 
        }   
 
        inited = 1;
    }   
 
    for (i = 0; i < ff_argc; i++) {
        free(ff_argv[i]);
    }   
 
    free(ff_argv);
 
    return rc; 
}

ff_init.c特殊源文件

 35 int
 36 ff_init(int argc, char * const argv[])
 37 {
 38     int ret;
 39 
 40     /* argv中包含可执行程序名称、带着路径的f-stack.conf配置文件名、proc-id、proc-type                                                                                                                           
 41      * 该函数里面解析f-stack.conf配置文件,并将DPDK EAL相关的参数全部挂到
 42      * 全局字符串指针数组dpdk-argv上。
 43      * */ 
 44     ret = ff_load_config(argc, argv);
 45     if (ret < 0)
 46         exit(1);
 47     
 48     // 调用rte_eal_init初始化DPDK EAL
 49     // 初始化lcore_conf相关内容,主要是收发包队列相关的东西
 50     // 初始化内存池,由RTE_PROC_PRIMARY来创建内存池
 51     // 创建 dispatch ring 和 msg_ring/message_pool,由RTE_PROC_PRIMARY来创建
 52     // 开关控制是否初始化kni
 53     // 开关控制是否做bsd到dpdk的映射以便发送报文的时候不用mcopy
 54     // 开关控制是否开启flow
 55     // 配置网卡设备、启动网卡、获取并显示网卡设备的连接信息,由RTE_PROC_PRIMARY开启
 56     // 初始化时钟
 57     // 添加flow rule(?!)
 58     ret = ff_dpdk_init(dpdk_argc, (char **)&dpdk_argv);
 59     if (ret < 0)
 60         exit(1);
 61     
 62     // 初始化freebsd,包括freebsd需要使用的环境变量、cpu以及进程线程、内存分配器等;
 63     // 启动freebsd
 64     // TODO 后期细看
 65     ret = ff_freebsd_init();
 66     if (ret < 0)
 67         exit(1);
 68     
 69     // 重点函数!!!衔接DPDK和freebsd的关键,将DPDK的m_buf转化为freebsd的mbuf。
 70     // 初始化veth_ctx数组里面的每一个元素,该数组的每个元素都与freebsd的ifp直接相关。
 71     // freebsd协议栈处理mbuf的函数就挂在这个上面,这里初始化的东西会在ff_run()里面使用。
 72     // freebsd使用的ifp相关的函数全部在这里面注册。
 73     ret = ff_dpdk_if_up();
 74     if (ret < 0)
 75         exit(1);
 76 
 77     return 0;
 78 }

ff_dpdk_if_up()

2189 int
2190 ff_dpdk_if_up(void) {                                                                                                                                                                                                             
2191     int i;
2192     struct lcore_conf *qconf = &lcore_conf;
2193 
2194     for (i = 0; i < qconf->nb_tx_port; i++) {
2195         uint16_t port_id = qconf->tx_port_id[i];
2196         struct ff_port_cfg *pconf = &qconf->port_cfgs[port_id];
2197 
2198         // veth_ctx数组里面的每个元素都是freebsd的ifp,freebsd怎么处理mbuf全在这个结构体
2199         // 里面了,就是ff_veth_attach()给初始化好的。
2200         veth_ctx[port_id] = ff_veth_attach(pconf);
2201         if (veth_ctx[port_id] == NULL) {
2202             rte_exit(EXIT_FAILURE, "ff_veth_attach failed");
2203         }
2204     }
2205 
2206     return 0;
2207 }

ff_veth_attach()

706 void *
707 ff_veth_attach(struct ff_port_cfg *cfg)
708 {
709     struct ff_veth_softc *sc = NULL;
710     int error;
711 
712     sc = malloc(sizeof(struct ff_veth_softc), M_DEVBUF, M_WAITOK);
713     if (NULL == sc) {
714         printf("ff_veth_softc allocation failed\n");
715         goto fail;
716     }   
717     memset(sc, 0, sizeof(struct ff_veth_softc));
718 
719     if(cfg->ifname){
720         snprintf(sc->host_ifname, sizeof(sc->host_ifname), "%s", cfg->ifname);
721     } else {
722         snprintf(sc->host_ifname, sizeof(sc->host_ifname), ff_IF_NAME, cfg->port_id);
723     }
724         
725     // 初始化sc中与mac、ip、掩码、网关地址相关的内容
726     error = ff_veth_config(sc, cfg);
727     if (0 != error) {
728         goto fail;
729     }
730 
731     // 重点函数!!!
732     // 与freebsd要使用的网络接口相关的初始化都由该函数来设置!!!
733     // 网卡的ip、网关等也在该函数里面设置,freebsd协议栈处理报文的
734     // 流程函数在这里注册!!!
735     if (0 != ff_veth_setup_interface(sc, cfg)) {                                                                                                                                                                
736         goto fail;
737     }
738 
739     // 返回的是struct ff_dpdk_if_context,但是这里已经挂上了sc和注册好的ifp
740     // ifp才是freebsd实际使用的定义网络接口的结构体,报文处理流程都注册在这里
741     return sc->host_ctx;
742 
743 fail:
744     if (sc) {
745         if (sc->host_ctx)
746             ff_dpdk_deregister_if(sc->host_ctx);
747 
748         free(sc, M_DEVBUF); 
749     }
750     
751     return NULL;
752 }   

ff_veth_setup_interface()

609 static int
610 ff_veth_setup_interface(struct ff_veth_softc *sc, struct ff_port_cfg *cfg)                                                                                                                                      
611 {   
612     struct ifnet *ifp;
613     
614     // if_alloc() freebsd的接口,创建一个ifp
615     ifp = sc->ifp = if_alloc(IFT_ETHER);
616     
617     ifp->if_init = ff_veth_init;
618     ifp->if_softc = sc;
619     
620     if_initname(ifp, sc->host_ifname, IF_DUNIT_NONE);
621     ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
622     ifp->if_ioctl = ff_veth_ioctl;
623     ifp->if_start = ff_veth_start;
624         
625     /* 该函数会被freeBSD协议栈的ether_output_frame()调用,最终作为离开freeBSD协议栈
626      * 并进入DPDK的入口。注意,注册给ifp->if_output()的ether_output()并不是最终的出
627      * 口,ether_output()里面会继续调用ether_output_frame(),而ether_output_frame()
628      * 里面会调用ifp->if_transmit(),也就是ff_veth_transmit(),从而将需要发送的报文
629      * 交给DPDK去发送。
630      * */  
631     ifp->if_transmit = ff_veth_transmit;
632     ifp->if_qflush = ff_veth_qflush;
633 
634     // 该函数继续初始化ifp里面的各种内容,注册各种处理函数,
635     // 这次会把ifp->if_output()和ifp->if_input()都注册好。
636     ether_ifattach(ifp, sc->mac);
......
653     ifp->if_capenable = ifp->if_capabilities;
654 
655     // sc->host_ctx 里面也有ifp,只不过是void*类型,但是实际用的时候还会转成struct ifnet
656     // 将sc、ifp(上面已经初始化好了)、cfg都挂到sc->host_ctx上!!!在进入freebsd协议栈
657     // 处理流程之前,其实用的都是这个host_ctx!
658     sc->host_ctx = ff_dpdk_register_if((void *)sc, (void *)sc->ifp, cfg);
659     if (sc->host_ctx == NULL) {
660         printf("%s: Failed to register dpdk interface\n", sc->host_ifname);                                                                                                                                     
661         return -1;
662     }
663 
664     // Set ip
665     // TODO 后期细看
666     int ret = ff_veth_setaddr(sc);
667     if (ret != 0) {
668         printf("ff_veth_setaddr failed\n");
669     }
670 
671     // TODO 后期细看
672     ret = ff_veth_set_gateway(sc);
673     if (ret != 0) {
674         printf("ff_veth_set_gateway failed\n");
675     }
676 
677     // TODO 后期细看
678     if (sc->nb_vip) {
679         ret = ff_veth_setvaddr(sc, cfg);
680     }
681 
682 #ifdef INET6
......
701 #endif /* INET6 */
702 
703     return (0);
704 }

ether_ifattach()

1042 void
1043 ether_ifattach(struct ifnet *ifp, const u_int8_t *lla)                                                                                                                                                         
1044 {
1045     int i;
1046     struct ifaddr *ifa;
1047     struct sockaddr_dl *sdl;
1048    
1049     ifp->if_addrlen = ETHER_ADDR_LEN;
1050     ifp->if_hdrlen = ETHER_HDR_LEN;
1051     ifp->if_mtu = ETHERMTU;
1052     if_attach(ifp);
1053        
1054     // 重点,freebsd从DPDK收包的接口在此注册的!!!
1055     ifp->if_output = ether_output;
1056     ifp->if_input = ether_input;
1057        
1058     ifp->if_resolvemulti = ether_resolvemulti;
1059     ifp->if_requestencap = ether_requestencap;
1060 #ifdef VIMAGE
1061     ifp->if_reassign = ether_reassign;
1062 #endif
1063     if (ifp->if_baudrate == 0)
1064         ifp->if_baudrate = IF_Mbps(10);     /* just a default */
1065     ifp->if_broadcastaddr = etherbroadcastaddr;
1066    
1067     ifa = ifp->if_addr;
1068     KASSERT(ifa != NULL, ("%s: no lladdr!\n", __func__));
1069     sdl = (struct sockaddr_dl *)ifa->ifa_addr;
1070     sdl->sdl_type = IFT_ETHER;
1071     sdl->sdl_alen = ifp->if_addrlen;
1072     bcopy(lla, LLADDR(sdl), ifp->if_addrlen);
1073 
1074     if (ifp->if_hw_addr != NULL)
1075         bcopy(lla, ifp->if_hw_addr, ifp->if_addrlen);
1076 
1077     bpfattach(ifp, DLT_EN10MB, ETHER_HDR_LEN);
1078     if (ng_ether_attach_p != NULL)
1079         (*ng_ether_attach_p)(ifp);
1080 
1081     /* Announce Ethernet MAC address if non-zero. */
1082     for (i = 0; i < ifp->if_addrlen; i++)
1083         if (lla[i] != 0)
1084             break;
1085     if (i != ifp->if_addrlen)
1086         if_printf(ifp, "Ethernet address: %6D\n", lla, ":");
1087 
1088     uuid_ether_add(LLADDR(sdl));
1089 
1090     /* Add necessary bits are setup; announce it now. */
1091     EVENTHANDLER_INVOKE(ether_ifattach_event, ifp);
1092     if (IS_DEFAULT_VNET(curvnet))
1093         devctl_notify("ETHERNET", ifp->if_xname, "IFATTACH", NULL);                                                                                                                                            
1094 }

ff_run()分析

void ff_run(loop_func_t loop, void *arg);

loop函数就是应用程序是用fstack协议栈处理自己具体业务的函数。应用程序通常会用一个循环来反复收包,处理自己的业务,ff_run函数内部会用一个while(1)来循环收包并且循环执行传入了loop,所以应用程序只需要将自己的业务代码封装成一个“执行一次”的函数,将其作为loop传递给ff_run即可。详细流程如下,

void
ff_run(loop_func_t loop, void *arg)
{
    ff_dpdk_run(loop, arg);                                                                                                                                                                        
}

ff_dpdk_run()

void    
ff_dpdk_run(loop_func_t loop, void *arg) {
    struct loop_routine *lr = rte_malloc(NULL,
        sizeof(struct loop_routine), 0);
    lr->loop = loop;
    lr->arg = arg;

    // 在所有lcores上启动main_loop函数,并且主lcore上也要
    // 启动main_loop函数(由第三个参数CALL_MAIN得知)
    // 自己程序的业务逻辑在main_loop里面通过一个while(1)
    // 被循环调用。
    rte_eal_mp_remote_launch(main_loop, lr, CALL_MAIN);                                                                                                                                            

    // 等待所有lcores完成工作
    rte_eal_mp_wait_lcore();

    rte_free(lr);
}   

main_loop()

2041 static int
2042 main_loop(void *arg)
2043 {
2044     struct loop_routine *lr = (struct loop_routine *)arg;
2045 
2046     struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
2047     uint64_t prev_tsc, diff_tsc, cur_tsc, usch_tsc, div_tsc, usr_tsc, sys_tsc, end_tsc, idle_sleep_tsc;
2048     int i, j, nb_rx, idle;
2049     uint16_t port_id, queue_id;
2050     struct lcore_conf *qconf;
2051     uint64_t drain_tsc = 0;
2052     struct ff_dpdk_if_context *ctx;
2053 
2054     if (pkt_tx_delay) {
2055         drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S * pkt_tx_delay;
2056     }
2057 
2058     prev_tsc = 0;
2059     usch_tsc = 0;
2060 
2061     qconf = &lcore_conf;
2062 
2063     while (1) {
2064         // 通过rdtsc指令获取CPU自启动以来的时钟周期数,即处理器的时间戳
2065         // 这里应该是用这种方式来计算cpu时间,计算出来的时间给ff_top命令使用
2066         // 这种方式貌似是不准确的,参见:
2067         //      https://blog.csdn.net/solstice/article/details/5196544
2068         // 具体看下linux最新版内核是怎么实现top命令来计算cpu时间的
2069         //      https://zhuanlan.zhihu.com/p/504537147
2070         // 该文章也提出DPDK的rdtsc计时不准确
2071         cur_tsc = rte_rdtsc(); // 获取tsc
2072         if (unlikely(freebsd_clock.expire < cur_tsc)) {
2073             rte_timer_manage();
2074         }
2075 
2076         idle = 1;
2077         sys_tsc = 0;
2078         usr_tsc = 0;
2079         usr_cb_tsc = 0;
2080 
2081         /*
2082          * TX burst queue drain                                                                                                                                                                                
2083          */
2084         diff_tsc = cur_tsc - prev_tsc;
2085         if (unlikely(diff_tsc >= drain_tsc)) {
2086             for (i = 0; i < qconf->nb_tx_port; i++) {
2087                 port_id = qconf->tx_port_id[i];
2088                 if (qconf->tx_mbufs[port_id].len == 0)
2089                     continue;
2090 
2091                 idle = 0;
2092                                                                                                                                                                                                                
2093                 send_burst(qconf,
2094                     qconf->tx_mbufs[port_id].len,
2095                     port_id);
2096                 qconf->tx_mbufs[port_id].len = 0;
2097             }
2098 
2099             prev_tsc = cur_tsc;
2100         }
2101 
2102         /*
2103          * Read packet from RX queues
2104          */
2105         for (i = 0; i < qconf->nb_rx_queue; ++i) {
2106             port_id = qconf->rx_queue_list[i].port_id;
2107             queue_id = qconf->rx_queue_list[i].queue_id;
2108             ctx = veth_ctx[port_id];
2109 
2110 #ifdef FF_KNI
2111             if (enable_kni && rte_eal_process_type() == RTE_PROC_PRIMARY) {
2112                 ff_kni_process(port_id, queue_id, pkts_burst, MAX_PKT_BURST);
2113             }
2114 #endif
2115 
2116             // (从ring上)收包
2117             // TODO 后期细看,这里面从ring中收包和下面直接从网卡收包有什么区别?为什么要这么搞?
2118             idle &= !process_dispatch_ring(port_id, queue_id, pkts_burst, ctx);
2119 
2120             // 收包
2121             nb_rx = rte_eth_rx_burst(port_id, queue_id, pkts_burst,
2122                 MAX_PKT_BURST);
2123             if (nb_rx == 0)
2124                 continue;
2125 
2126             idle = 0;
2127 
2128             // 将地址预取到CPU cache,以便提升速度和命中率,注意数量不能太多,
2129             // PREFETCH_OFFSET 别改
2130             /* Prefetch first packets */
2131             for (j = 0; j < PREFETCH_OFFSET && j < nb_rx; j++) {
2132                 rte_prefetch0(rte_pktmbuf_mtod(pkts_burst[j], void *));
2133             }
2134 
2135             /* Prefetch and handle already prefetched packets */
2136             for (j = 0; j < (nb_rx - PREFETCH_OFFSET); j++) {
2137                 // 在处理收到的包之前继续预取下面1条地址到CPU cache
2138                 rte_prefetch0(rte_pktmbuf_mtod(pkts_burst[j + PREFETCH_OFFSET],
2139                                                void *));
2140 
2141                 // 处理接收到的包,该函数应该就是整个fstack真正开始处理收到包的关键函数
2142                 // 实参count是1,看来每次只处理一个包
2143                 process_packets(port_id, queue_id, &pkts_burst[j], 1, ctx, 0);                                                                                                                                 
2144             }
2145 
2146             /* Handle remaining prefetched packets */
2147             for (; j < nb_rx; j++) {
2148                 process_packets(port_id, queue_id, &pkts_burst[j], 1, ctx, 0);
2149             }
2150         }
2151 
2152         process_msg_ring(qconf->proc_id, pkts_burst);
2153 
2154         div_tsc = rte_rdtsc();
2155 
2156         // 真正执行之前在ff_run()中注册的函数,即自己的业务逻辑是在这里被循环执行的,
2157         // 每次都在收包且处理完包之后再开始执行自己的业务
2158         if (likely(lr->loop != NULL && (!idle || cur_tsc - usch_tsc >= drain_tsc))) {
2159             usch_tsc = cur_tsc;
2160             lr->loop(lr->arg);
2161         }
2162 
2163         idle_sleep_tsc = rte_rdtsc();
2164         if (likely(idle && idle_sleep)) {
2165             usleep(idle_sleep);
2166             end_tsc = rte_rdtsc();
2167         } else {
2168             end_tsc = idle_sleep_tsc;
2169         }
2170 
2171         usr_tsc = usr_cb_tsc;
2172         if (usch_tsc == cur_tsc) {
2173             usr_tsc += idle_sleep_tsc - div_tsc;
2174         }
2175 
2176         if (!idle) {
2177             sys_tsc = div_tsc - cur_tsc - usr_cb_tsc;
2178             ff_top_status.sys_tsc += sys_tsc;
2179         }
2180 
2181         ff_top_status.usr_tsc += usr_tsc;
2182         ff_top_status.work_tsc += end_tsc - cur_tsc;
2183         ff_top_status.idle_tsc += end_tsc - cur_tsc - usr_tsc - sys_tsc;
2184 
2185         ff_top_status.loops++;
2186     }
2187 
2188     return 0;
2189 }

收包(无内存拷贝)

process_packets()
1455 static inline void
1456 process_packets(uint16_t port_id, uint16_t queue_id, struct rte_mbuf **bufs,
1457     uint16_t count, const struct ff_dpdk_if_context *ctx, int pkts_from_ring)
1458 {
1459     struct lcore_conf *qconf = &lcore_conf;
1460     uint16_t nb_queues = qconf->nb_queue_list[port_id];
1461 
1462     uint16_t i;
1463 
1464     // 解析每一个rte_mbuf
1465     for (i = 0; i < count; i++) {
1466         struct rte_mbuf *rtem = bufs[i];
1467 
1468         // 对应f-stack.conf配置文件(或者是config.ini)里面的抓包开关,默认关闭
1469         if (unlikely( ff_global_cfg.pcap.enable)) {
1470             if (!pkts_from_ring) {
1471                 // 抓包相关的处理函数,TODO 后期细看
1472                 ff_dump_packets(ff_global_cfg.pcap.save_path,
1473                                 rtem,
1474                                 ff_global_cfg.pcap.snap_len,
1475                                 ff_global_cfg.pcap.save_len);
1476             }
1477         }
1478 
1479         // 用 void *data 指针指向rtem这个原始包的数据段起始地址
1480         // 注意,data: (char*)mbuf->buf_addr + data_off
1481         // 从这里开始,这些操作有点类似skb的处理过程
1482         void *data = rte_pktmbuf_mtod(rtem, void*);
1483         uint16_t len = rte_pktmbuf_data_len(rtem);
1484 
1485         if (!pkts_from_ring) {
1486             // 记录报文分片(分段)的片数(段数)
1487             ff_traffic.rx_packets += rtem->nb_segs;
1488 
1489             // 记录本包的总长度                                                                                                                                                                                
1490             ff_traffic.rx_bytes += rte_pktmbuf_pkt_len(rtem);
1491         }
1492 
1493         // packet_dispatcher通过用户调用ff_regist_packet_dispatcher()来注册,
1494         // 但是fstack-nginx并没调用该接口注册该函数指针,所以这里应该进不来。
1495         // packet_dispatcher应该是个钩子
1496         // TODO 后期细看,钩子(packet_dispatcher)
1497         if (!pkts_from_ring && packet_dispatcher) {
1498             uint64_t cur_tsc = rte_rdtsc();
1499             int ret = (*packet_dispatcher)(data, &len, queue_id, nb_queues);
1500 
1501             usr_cb_tsc += rte_rdtsc() - cur_tsc;
1502 
1503             // Packet is handled by user, packet will be responsed.
1504             if (ret == FF_DISPATCH_RESPONSE) {
1505                 // 注意,data和len此时有可能已经被packet_dispatcher改了
1506                 // 这里直接把包的pkt_len和data_len全改成了len                                                                                                                                                  
1507                 rte_pktmbuf_pkt_len(rtem) = rte_pktmbuf_data_len(rtem) = len;
1508 
1509                 /*
1510                  * We have not support vlan out strip
1511                  */
1512                 ff_add_vlan_tag(rtem);
1513 
1514                 // 将这个包发出去
1515                 send_single_packet(rtem, port_id);
1516 
1517                 // 回到循环头,继续处理下一个包(mbuf)
1518                 continue;
1519             }
1520 
1521             // Error occurs or packet is handled by user, packet will be freed.
1522             if (ret == FF_DISPATCH_ERROR || ret >= nb_queues) {
1523                 rte_pktmbuf_free(rtem);
1524                 continue;
1525             }
1526 
1527             /* return 0 to (nb_queues - 1) 
1528              *      The queue id that the packet will be dispatched to.
1529              * */
1530             if (ret != queue_id) {
1531                 ret = rte_ring_enqueue(dispatch_ring[port_id][ret], rtem);
1532                 if (ret < 0)
1533                     rte_pktmbuf_free(rtem);
1534 
1535                 continue;
1536             }
1537         }
1538 
1539         // 临时解析一下协议,正式解析解析的流程在后面,这里只解析一下mac头先看看
1540         // 如果没有开启kni,但是要走freebsd,并且开启了ipv6,mac帧携带的是ip报文,
1541         // 在解析正常的情况下,这里返回 FILTER_UNKNOWN
1542         enum FilterReturn filter = protocol_filter(data, len);
1543 
1544         // 主要是处理ARP  TODO 后面再看
1545 #ifdef INET6
1546         if (filter == FILTER_ARP || filter == FILTER_NDP) {
1547 #else
1548         if (filter == FILTER_ARP) {
1549 #endif
......
1573 
1574 #ifdef FF_KNI
......
1583 #endif
1584             ff_veth_input(ctx, rtem);
1585 #ifdef FF_KNI
......
1604 #endif
1605         } else {
1606             // rtem虽然在前面解析过mac头,但并没有将其剥掉。
1607             // 如果里面是ip头,应该走这里
1608             // 注意,此时的rtem还是DPDK的rte_mbuf,不是freebsd的mbuf                                                                                                                                           
1609             ff_veth_input(ctx, rtem);
1610         }
1611     }
1612 }

ff_veth_input()
1277 static void
1278 ff_veth_input(const struct ff_dpdk_if_context *ctx, struct rte_mbuf *pkt)
1279 {
1280     uint8_t rx_csum = ctx->hw_features.rx_csum;
1281     if (rx_csum) {
1282         if (pkt->ol_flags & (RTE_MBUF_F_RX_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD)) {
1283             rte_pktmbuf_free(pkt);
1284             return;
1285         }
1286     }
1287 
1288     void *data = rte_pktmbuf_mtod(pkt, void*);
1289     uint16_t len = rte_pktmbuf_data_len(pkt);
1290 
1291     /* 里面会调用freebsd的接口新建一个mbuf并返回,这里由hdr来接收。rte_mbuf会被挂到这个
1292      * 新建的、给freebsd使用的mbuf上,并且rte_mbuf上保存收到的报文数据的内存区会被挂到
1293      * mbuf->m_data上,这样,该mbuf对freebsd而言就可以实际使用了。
1294      * */
1295     void *hdr = ff_mbuf_gethdr(pkt, pkt->pkt_len, data, len, rx_csum);
1296     if (hdr == NULL) {
1297         // 注意,如果freebsd新建的mbuf是NULL,那么这里把DPDK的pkt(即rte_mbuf)给释放掉了!!!
1298         // 注意释放的是谁!!!
1299         rte_pktmbuf_free(pkt);
1300         return;
1301     }
1302 
1303     // TODO vlan相关后续再看
1304     if (pkt->ol_flags & RTE_MBUF_F_RX_VLAN_STRIPPED) {
1305         ff_mbuf_set_vlan_info(hdr, pkt->vlan_tci);
1306     }
1307    
1308     // prev(即hdr)是携带了报文头部的那个mbuf,作为本次收到的报文的首个分组(一个报文到达本机
1309     // 时可能需要多个mbuf来保存报文数据,这些mbuf要用m->m_next链起来)。rte_mbuf也是这么处理
1310     // 的,所以必须将所有链起来的rte_mbuf逐个转换成mbuf并将这些mbuf链起来才能真正上送freebsd。
1311     struct rte_mbuf *pn = pkt->next;
1312     void *prev = hdr;
1313     while(pn != NULL) {
1314         data = rte_pktmbuf_mtod(pn, void*);
1315         len = rte_pktmbuf_data_len(pn);
1316 
1317         /* 将一个rte_mbuf中间分组转换成mbuf中间分组,并将该mbuf挂到上一个mbuf上 */
1318         void *mb = ff_mbuf_get(prev, pn, data, len);
1319         if (mb == NULL) {
1320             ff_mbuf_free(hdr);
1321             rte_pktmbuf_free(pkt);
1322             return;
1323         }                                                                                                                                                                                                      
1324         pn = pn->next;
1325         prev = mb;
1326     }
1327 
1328     /* 此时,DPDK本次收到的”一个“报文(对应一条rte_mbuf链)就全部转换成了freebsd可以使用的                                                                                                                     
1329      * 对应一个报文的一条mbuf链!!!该报文的首个分组就是hdr,其他分组全都依顺序链在后面。
1330      * */
1331 
1332     /* 由此进入freebsd协议栈。这个ifp是freebsd的“网络接口定义结构体”,里面挂了输入/输出包
1333      * 的处理函数,将hdr放到ifp->if_input()里面,其实就是把从DPDK收上来的报文放到freebsd
1334      * 协议栈去处理了。
1335      * */
1336     ff_veth_process_packet(ctx->ifp, hdr);
1337 }

ff_veth_process_packet()
360 void 
361 ff_veth_process_packet(void *arg, void *m)                                                                                                                                                                                         
362 {
363     // struct ifnet-->if_var.h,整个freebsd的"Structure defining a network interface",
364     // 即“定义网络接口的结构体”
365     struct ifnet *ifp = (struct ifnet *)arg;
366     struct mbuf *mb = (struct mbuf *)m;
367 
368     // rcv interface
369     mb->m_pkthdr.rcvif = ifp;
370 
371     // freebsd的接口
372     // Various procedures of the layer2 encapsulation and drivers
373     // input routine (from h/w driver)
374     // 就是从这里开始,正式进入freebsd协议栈
375     // 注册的入口在ff_init()里面的ff_dpdk_if_up(),一直调用到
376     // ff_veth_setup_interface()并在里面调用的ether_ifattach()里注册
377     // if_input()的实际函数是ether_input(),位于if_ethersubr.c
378     // 该函数类似linux kernel协议栈的netif_receive_skb()
379     ifp->if_input(ifp, mb);
380 }    

发包(有内存拷贝)

将mbuf转换成rte_mbuf时存在内存拷贝。

ff_veth_transmit()
400 static int
401 ff_veth_transmit(struct ifnet *ifp, struct mbuf *m)
402 {
403     struct ff_veth_softc *sc = (struct ff_veth_softc *)ifp->if_softc;
404     return ff_dpdk_if_send(sc->host_ctx, (void*)m, m->m_pkthdr.len);                                                                                                                                            
405 }
ff_dpdk_if_send()
  1925	int
  1926	ff_dpdk_if_send(struct ff_dpdk_if_context *ctx, void *m,
  1927	    int total)
  1928	{
  1929	#ifdef FF_USE_PAGE_ARRAY
  1930	    struct lcore_conf *qconf = &lcore_conf;
  1931	    int    len = 0;
  1932	
  1933	    len = ff_if_send_onepkt(ctx, m,total);
  1934	    if (unlikely(len == MAX_PKT_BURST)) {
  1935	        send_burst(qconf, MAX_PKT_BURST, ctx->port_id);
  1936	        len = 0;
  1937	    }
  1938	    qconf->tx_mbufs[ctx->port_id].len = len;
  1939	    return 0;
  1940	#endif
  1941	    struct rte_mempool *mbuf_pool = pktmbuf_pool[lcore_conf.socket_id];
  1942	    
  1943	    // 创建一个rte_mbuf作为报文的首个分组
  1944	    struct rte_mbuf *head = rte_pktmbuf_alloc(mbuf_pool);
  1945	    if (head == NULL) {
  1946	        ff_mbuf_free(m);
  1947	        return -1;
  1948	    }
  1949	
  1950	    head->pkt_len = total;
  1951	    head->nb_segs = 0;
  1952	
  1953	    /* 一个报文可能对应多个分组,每个分组对应一个mbuf,这些mbuf链接起来组成一个完整的报文。
  1954	     * 一条mbuf链对应一条报文,mbuf是给freebsd使用的,现在将这条mbuf链逐mbuf转换成rte_mbuf
  1955	     * 并将这些rte_mbuf链接起来构成该报文,这样就可以让DPDK将报文发送出去了。
  1956	     * 注意:
  1957	     * 1. 当初收包的时候,将rte_mbuf链转换成mbuf链,是没有内存拷贝动作的;
  1958	     * 现在要发包,需要将mbuf链转换成rte_mbuf链,必须把链上每个mbuf携带的报文数据都拷贝到对应的
  1959	     * rte_mbuf上去,并将这些rte_mbuf链起来,才能交给DPDK去发送。这哪还是”零拷贝“呀……
  1960	     * 2. 一个mbuf中用来缓冲报文的内存区一般小于一个rte_mbuf中缓冲报文数据的内存区,所以通常对于
  1961	     * 一条报文,其mbuf链上mbuf节点的个数应该会少于rte_mbuf链上的rte_mbuf节点个数。
  1962	     * */
  1963	    int off = 0;
  1964	    struct rte_mbuf *cur = head, *prev = NULL;
  1965	    while(total > 0) {
  1966	        if (cur == NULL) {
  1967	            cur = rte_pktmbuf_alloc(mbuf_pool);
  1968	            if (cur == NULL) {
  1969	                rte_pktmbuf_free(head);
  1970	                ff_mbuf_free(m);
  1971	                return -1;
  1972	            }
  1973	        }
  1974	
  1975	        if (prev != NULL) {
  1976	            prev->next = cur;
  1977	        }
  1978	        head->nb_segs++;
  1979	
  1980	        prev = cur;
  1981	        void *data = rte_pktmbuf_mtod(cur, void*);
  1982	        int len = total > RTE_MBUF_DEFAULT_DATAROOM ? RTE_MBUF_DEFAULT_DATAROOM : total;
  1983	        
  1984	        /* 将mbuf中保存的报文数据拷贝到rte_mbuf中,这里实实在在用到了内存拷贝,
  1985	         * 不能说是完全的”零拷贝“了!注意这里只传入了m,而没有使用m后面链的那些mbuf,
  1986	         * 因为这里是利用off实现将每一个mbuf中的报文数据拷贝到rte_mbuf中的,由于rte_mbuf
  1987	         * 缓存报文数据的内存比较大,所以很可能整条mbuf链上的数据都被拷贝到一个rte_mbuf中。
  1988	         * */ 
  1989	        int ret = ff_mbuf_copydata(m, data, off, len);
  1990	        if (ret < 0) {
  1991	            rte_pktmbuf_free(head);
  1992	            ff_mbuf_free(m);
  1993	            return -1;
  1994	        }
  1995	
  1996	
  1997	        cur->data_len = len;
  1998	        off += len;
  1999	        total -= len;
  2000	        cur = NULL;
  2001	    }
  2002	
  2003	    struct ff_tx_offload offload = {0};
  2004	    ff_mbuf_tx_offload(m, &offload);
  2005	
  2006	    void *data = rte_pktmbuf_mtod(head, void*);
  2007	
  2008	    if (offload.ip_csum) {
  2009	        /* ipv6 not supported yet */
  2010	        struct rte_ipv4_hdr *iph;
  2011	        int iph_len;
  2012	        iph = (struct rte_ipv4_hdr *)(data + RTE_ETHER_HDR_LEN);
  2013	        iph_len = (iph->version_ihl & 0x0f) << 2;
  2014	
  2015	        head->ol_flags |= RTE_MBUF_F_TX_IP_CKSUM | RTE_MBUF_F_TX_IPV4;
  2016	        head->l2_len = RTE_ETHER_HDR_LEN;
  2017	        head->l3_len = iph_len;
  2018	    }
  2019	
  2020	    if (ctx->hw_features.tx_csum_l4) {
  2021	        struct rte_ipv4_hdr *iph;
  2022	        int iph_len;
  2023	        iph = (struct rte_ipv4_hdr *)(data + RTE_ETHER_HDR_LEN);
  2024	        iph_len = (iph->version_ihl & 0x0f) << 2;
  2025	
  2026	        if (offload.tcp_csum) {
  2027	            head->ol_flags |= RTE_MBUF_F_TX_TCP_CKSUM;
  2028	            head->l2_len = RTE_ETHER_HDR_LEN;
  2029	            head->l3_len = iph_len;
  2030	        }
  2031	
  2032	        /*
  2033	         *  TCP segmentation offload.
  2034	         *
  2035	         *  - set the PKT_TX_TCP_SEG flag in mbuf->ol_flags (this flag
  2036	         *    implies PKT_TX_TCP_CKSUM)
  2037	         *  - set the flag PKT_TX_IPV4 or PKT_TX_IPV6
  2038	         *  - if it's IPv4, set the PKT_TX_IP_CKSUM flag and
  2039	         *    write the IP checksum to 0 in the packet
  2040	         *  - fill the mbuf offload information: l2_len,
  2041	         *    l3_len, l4_len, tso_segsz
  2042	         *  - calculate the pseudo header checksum without taking ip_len
  2043	         *    in account, and set it in the TCP header. Refer to
  2044	         *    rte_ipv4_phdr_cksum() and rte_ipv6_phdr_cksum() that can be
  2045	         *    used as helpers.
  2046	         */
  2047	        if (offload.tso_seg_size) {
  2048	            struct rte_tcp_hdr *tcph;
  2049	            int tcph_len;
  2050	            tcph = (struct rte_tcp_hdr *)((char *)iph + iph_len);
  2051	            tcph_len = (tcph->data_off & 0xf0) >> 2;
  2052	            tcph->cksum = rte_ipv4_phdr_cksum(iph, RTE_MBUF_F_TX_TCP_SEG);
  2053	
  2054	            head->ol_flags |= RTE_MBUF_F_TX_TCP_SEG;
  2055	            head->l4_len = tcph_len;
  2056	            head->tso_segsz = offload.tso_seg_size;
  2057	        }
  2058	
  2059	        if (offload.udp_csum) {
  2060	            head->ol_flags |= RTE_MBUF_F_TX_UDP_CKSUM;
  2061	            head->l2_len = RTE_ETHER_HDR_LEN;
  2062	            head->l3_len = iph_len;
  2063	        }
  2064	    }
  2065	
  2066	    // 将freebsd使用的mbuf(链)释放
  2067	    ff_mbuf_free(m);
  2068	
  2069	    return send_single_packet(head, ctx->port_id);
  2070	}

freebsd对接DPDK

收包:ether_input()

DPDK会调用该接口(该接口属于freebsd)将收到的包送进freebsd协议栈,让freebsd协议栈收包。该函数在ether_ifattach()(ff_init())中被注册,在ff_veth_process_packet()(ff_run())里面被调用。

 843 static void
 844 ether_input(struct ifnet *ifp, struct mbuf *m)                                                                                                                                                                 
 845 {  
 846     struct epoch_tracker et; 
 847     struct mbuf *mn;
 848     bool needs_epoch;
 849    
 850     needs_epoch = !(ifp->if_flags & IFF_KNOWSEPOCH);
 851    
 852     /*
 853      * The drivers are allowed to pass in a chain of packets linked with
 854      * m_nextpkt. We split them up into separate packets here and pass
 855      * them up. This allows the drivers to amortize the receive lock.
 856      */
 857     CURVNET_SET_QUIET(ifp->if_vnet);
 858     if (__predict_false(needs_epoch))
 859         NET_EPOCH_ENTER(et);
 860 
 861     // 逐包处理,单独处理当前包,下个循环再处理他的分片
 862     while (m) {
 863         mn = m->m_nextpkt;
 864         m->m_nextpkt = NULL;
 865    
 866         /*
 867          * We will rely on rcvif being set properly in the deferred
 868          * context, so assert it is correct here.
 869          */
 870         MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0);
 871         KASSERT(m->m_pkthdr.rcvif == ifp, ("%s: ifnet mismatch m %p "
 872             "rcvif %p ifp %p", __func__, m, m->m_pkthdr.rcvif, ifp));
 873 
 874         // 当前处理的报文mbuf还是个物理层的帧,没有剥离任何头部
 875         // 从 NETISR_ETHER 看出来的。
 876         // 二层帧的处理是通过ether_init()函数由SYSINIT()宏进行初始化的(未见到ether_init()被显式初
 877         // 始化,应该就是被SYSINIT()宏给初始化的)。在这个初始化过程中,
 878         // 其实是通过netisr_register()函数将全局的ether_nh结构体里面已经注册好的二层处理相
 879         // 关函数注册到全局结构体struct netisr_proto  netisr_proto中,后面每层在处理自己报
 880         // 文的时候,就可以通过netisr_proto[本层协议].nh_handler()来处理本层的报文。
 881         // 类似struct netisr_handler ether_nh,其他各层的处理函数也都是直接在全局的环境下
 882         // 直接写好注册函数的,详细见freebsd/netinet/in_proto.c和struct protosw inetsw[]。
 883         // 例如,ip_init(),udp_input(),tcp_input()的注册过程如下,
 884         //      * 直接在全局结构体struct protosw inetsw[]里注册好;
 885         //      * ip_input()直接在struct netisr_handler ip_nh全局结构体里面注册好,
 886         //  这和ether_nh的处理方式完全一致;
 887         //      * 在ip_init()里面通过netisr_register(&ip_nh)将里面注册的内容再注册
 888         //  到全局结构体struct netisr_proto  netisr_proto中。前面说到ip_init()是在
 889         //  全局结构体struct protosw inetsw[]里注册的,并且udp_input()/tcp_input()都是
 890         //  在这个里面注册的,整个四层协议都是通过inetsw来使用的,在ip_input()将ip协议
 891         //  处理完后,接下来的上层协议就不再通过netisr_proto来处理了,而是通过inetsw来
 892         //  处理。
 893 
 894         // 所以,既然此时处理的是二层帧,那么使用的就是struct netisr_handler ether_nh                                                                                                                          
 895         // 里面注册的函数,即ether_nh_input,查看具体调用了什么函数,进ether_nh里面看。
 896         netisr_dispatch(NETISR_ETHER, m);
 897 
 898         // 处理接下来的分片报文(如果有的话)
 899         m = mn;
 900     }
 901     if (__predict_false(needs_epoch))
 902         NET_EPOCH_EXIT(et);
 903     CURVNET_RESTORE();
 904 }
netisr_dispatch()
1245 int
1246 netisr_dispatch(u_int proto, struct mbuf *m)
1247 {                                                                                                                                                                                             
1248 
1249     return (netisr_dispatch_src(proto, 0, m));
1250 }

netisr_dispatch_src()
1098 int
1099 netisr_dispatch_src(u_int proto, uintptr_t source, struct mbuf *m)
1100 {
1101 #ifdef NETISR_LOCKING
1102     struct rm_priotracker tracker;
1103 #endif
1104     struct netisr_workstream *nwsp;
1105     struct netisr_proto *npp;
1106     struct netisr_work *npwp;
1107     int dosignal, error;
1108     u_int cpuid, dispatch_policy;
1109 
1110     NET_EPOCH_ASSERT();
1111     KASSERT(proto < NETISR_MAXPROT,
1112         ("%s: invalid proto %u", __func__, proto));
1113 #ifdef NETISR_LOCKING
1114     NETISR_RLOCK(&tracker);
1115 #endif
1116 
1117     // 获取与传入的proto对应的结构体,不同的协议对应不同的结构体,每个协议对应的结构体里面
1118     // 都注册好了该协议对应的处理函数。
1119     // 二层 <-----> struct netisr_handler ether_nh
1120     // 二层 <-----> struct netisr_handler arp_nh
1121     // 三层 <-----> struct netisr_handler ip_nh
1122     // 三层 <-----> struct netisr_handler ip6_nh
1123     // 三层 <-----> struct netisr_handler igmp_nh
1124     // npp->np_handler <-----> netisr_handler.nh_handler
1125     // netisr_proto[proto].np_handler <-----> struct netisr_handler.nh_handler
1126     npp = &netisr_proto[proto];
1127     KASSERT(npp->np_handler != NULL, ("%s: invalid proto %u", __func__,
1128         proto));
1129 
1130 #ifdef VIMAGE
1131     if (V_netisr_enable[proto] == 0) {
1132         m_freem(m);
1133         return (ENOPROTOOPT);
1134     }
1135 #endif
1136 
1137     // TODO 剩下的后期再细看,先看各个主要协议的处理过程
1138 
1139     // ip_nh.np_dispatch : NETISR_DISPATCH_HYBRID
1140     // ether_nh.np_dispatch : NETISR_DISPATCH_DIRECT                                                                                                                                                           
1141     dispatch_policy = netisr_get_dispatch(npp);
1142     if (dispatch_policy == NETISR_DISPATCH_DEFERRED)
1143         return (netisr_queue_src(proto, source, m));
1144 
1145     /* 二层协议走这里,三层协议(ip)不符合该判断!
1146      * If direct dispatch is forced, then unconditionally dispatch
1147      * without a formal CPU selection.  Borrow the current CPU's stats,
1148      * even if there's no worker on it.  In this case we don't update
1149      * nws_flags because all netisr processing will be source ordered due                                                                                                                                      
1150      * to always being forced to directly dispatch.
1151      */
1152     if (dispatch_policy == NETISR_DISPATCH_DIRECT) {
1153         nwsp = DPCPU_PTR(nws);
1154         npwp = &nwsp->nws_work[proto];
1155         npwp->nw_dispatched++;
1156         npwp->nw_handled++;
1157 
1158         // 执行对应proto的处理函数!!!
1159         // 如果是二层协议,就看ether_nh里面注册的ether_nh_input()。
1160         // 三层往上的协议,就不是看netisr_proto结构体了,看得是inetsw这个结构体里
1161         // 面注册的函数,所以这里应该只负责二三层协议。
1162         netisr_proto[proto].np_handler(m);
1163         error = 0;
1164         goto out_unlock;
1165     }
1166 
1167     KASSERT(dispatch_policy == NETISR_DISPATCH_HYBRID,
1168         ("%s: unknown dispatch policy (%u)", __func__, dispatch_policy));
1169 
1170     /*
1171      * Otherwise, we execute in a hybrid mode where we will try to direct
1172      * dispatch if we're on the right CPU and the netisr worker isn't
1173      * already running.
1174      */
1175     sched_pin();
1176     m = netisr_select_cpuid(&netisr_proto[proto], NETISR_DISPATCH_HYBRID,
1177         source, m, &cpuid);
1178     if (m == NULL) {
1179         error = ENOBUFS;
1180         goto out_unpin;
1181     }
1182     KASSERT(!CPU_ABSENT(cpuid), ("%s: CPU %u absent", __func__, cpuid));
1183     if (cpuid != curcpu)
1184         goto queue_fallback;
1185     nwsp = DPCPU_PTR(nws);
1186     npwp = &nwsp->nws_work[proto];
1187 
1188     /*-
1189      * We are willing to direct dispatch only if three conditions hold:
1190      *
1191      * (1) The netisr worker isn't already running,
1192      * (2) Another thread isn't already directly dispatching, and
1193      * (3) The netisr hasn't already been woken up.
1194      */
1195     NWS_LOCK(nwsp);
1196     if (nwsp->nws_flags & (NWS_RUNNING | NWS_DISPATCHING | NWS_SCHEDULED)) {
1197         error = netisr_queue_workstream(nwsp, proto, npwp, m,
1198             &dosignal);
1199         NWS_UNLOCK(nwsp);
1200         if (dosignal)                                                                                                                                                                                          
1201             NWS_SIGNAL(nwsp);
1202         goto out_unpin;
1203     }
1204 
1205     /*
1206      * The current thread is now effectively the netisr worker, so set
1207      * the dispatching flag to prevent concurrent processing of the
1208      * stream from another thread (even the netisr worker), which could
1209      * otherwise lead to effective misordering of the stream.
1210      */
1211     nwsp->nws_flags |= NWS_DISPATCHING;
1212     NWS_UNLOCK(nwsp);
1213 
1214     /* 如果是ip,直接看ip_input()就好了,ip_input()已经被注册给了np_handler()。
1215      * 三层往上的协议,就不是看netisr_proto结构体了,看得是inetsw这个结构体里
1216      * 面注册的函数,所以这里应该只负责二三层协议。
1217      * */
1218     netisr_proto[proto].np_handler(m);
1219 
1220     NWS_LOCK(nwsp);
1221     nwsp->nws_flags &= ~NWS_DISPATCHING;
1222     npwp->nw_handled++;
1223     npwp->nw_hybrid_dispatched++;
1224 
1225     /*
1226      * If other work was enqueued by another thread while we were direct
1227      * dispatching, we need to signal the netisr worker to do that work.
1228      * In the future, we might want to do some of that work in the
1229      * current thread, rather than trigger further context switches.  If
1230      * so, we'll want to establish a reasonable bound on the work done in
1231      * the "borrowed" context.
1232      */
1233     if (nwsp->nws_pendingbits != 0) {
1234         nwsp->nws_flags |= NWS_SCHEDULED;
1235         dosignal = 1;
1236     } else
1237         dosignal = 0;
1238     NWS_UNLOCK(nwsp);
1239     if (dosignal)
1240         NWS_SIGNAL(nwsp);
1241     error = 0;
1242     goto out_unpin;
1243 
1244 queue_fallback:
1245     error = netisr_queue_internal(proto, m, cpuid);
1246 out_unpin:
1247     sched_unpin();
1248 out_unlock:
1249 #ifdef NETISR_LOCKING
1250     NETISR_RUNLOCK(&tracker);
1251 #endif                                                                                                                                                                                                         
1252     return (error);
1253 }

发包:ether_output_frame()

该函数(该函数属于freebsd)会调用DPDK的接口来发包。

 493 int
 494 ether_output_frame(struct ifnet *ifp, struct mbuf *m)
 495 {
 496     uint8_t pcp;
......
 534     /*
 535      * Queue message on interface, update output statistics if
 536      * successful, and start output if interface not yet active.
 537      * 原型是ff_veth_transmit().
 538      */
 539     return ((ifp->if_transmit)(ifp, m));
 540 }

二层处理流程

上图来自《TCP/IP协议卷2》,ipintr就是现在的ip_input()。

入口:ether_nh_input()

 741 ether_nh_input(struct mbuf *m)                                                                                                                                                                
 742 {   
 743     // 二层处理函数
 744     M_ASSERTPKTHDR(m);
 745     KASSERT(m->m_pkthdr.rcvif != NULL,
 746         ("%s: NULL interface pointer", __func__));
 747     ether_input_internal(m->m_pkthdr.rcvif, m);
 748 }   
ether_input_internal()
 518 static void
 519 ether_input_internal(struct ifnet *ifp, struct mbuf *m)                                                                                                                                       
 520 {   
 521     struct ether_header *eh;
 522     u_short etype;
 523     
 ......
 544 
 545     // 解析二层头和携带的上层协议的类型
 546     eh = mtod(m, struct ether_header *);
 547     etype = ntohs(eh->ether_type);
 548     random_harvest_queue_ether(m, sizeof(*m));
 549 
 ......
 567 
 568     CURVNET_SET_QUIET(ifp->if_vnet);
 569 
 570     // 多播?!
 571     if (ETHER_IS_MULTICAST(eh->ether_dhost)) {
 572         if (ETHER_IS_BROADCAST(eh->ether_dhost))
 573             m->m_flags |= M_BCAST;
 574         else
 575             m->m_flags |= M_MCAST;
 576         if_inc_counter(ifp, IFCOUNTER_IMCASTS, 1);
 577     }
 578 
 ......
 586 
 587     /*
 588      * Give bpf a chance at the packet.
 589      */
 590     // TODO 后期详细分析下bpf相关的东西
 591     ETHER_BPF_MTAP(ifp, m);
 592 
 ......
 612 
 613     // TODO 后面详细研究
 614     /* Handle input from a lagg(4) port */
 615     if (ifp->if_type == IFT_IEEE8023ADLAG) {
 616         KASSERT(lagg_input_ethernet_p != NULL,
 617             ("%s: if_lagg not loaded!", __func__));
 618         m = (*lagg_input_ethernet_p)(ifp, m);
 619         if (m != NULL)
 620             ifp = m->m_pkthdr.rcvif;                                                                                                                                                                                              
 621         else {
 622             CURVNET_RESTORE();
 623             return;
 624         }
 625     }
 626 
 627     // TODO vlan相关,后期再看
 628     /*
 629      * If the hardware did not process an 802.1Q tag, do this now,
 630      * to allow 802.1P priority frames to be passed to the main input
 631      * path correctly.
 632      */
 633     if ((m->m_flags & M_VLANTAG) == 0 &&
 634         ((etype == ETHERTYPE_VLAN) || (etype == ETHERTYPE_QINQ))) {
 635         struct ether_vlan_header *evl;
 636 
 637         if (m->m_len < sizeof(*evl) &&
 638             (m = m_pullup(m, sizeof(*evl))) == NULL) {
 639 #ifdef DIAGNOSTIC
 640             if_printf(ifp, "cannot pullup VLAN header\n");
 641 #endif
 642             if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
 643             CURVNET_RESTORE();
 644             return;
 645         }
 646 
 647         evl = mtod(m, struct ether_vlan_header *);
 648         m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag);
 649         m->m_flags |= M_VLANTAG;
 650 
 651         bcopy((char *)evl, (char *)evl + ETHER_VLAN_ENCAP_LEN,
 652             ETHER_HDR_LEN - ETHER_TYPE_LEN);
 653         m_adj(m, ETHER_VLAN_ENCAP_LEN);
 654         eh = mtod(m, struct ether_header *);
 655     }
 656 
 ......
 671 
 672     // TODO “桥”相关,后期研究
 673     /*
 674      * Allow if_bridge(4) to claim this frame.
 675      * The BRIDGE_INPUT() macro will update ifp if the bridge changed it
 676      * and the frame should be delivered locally.                                                                                                                                                                                 
 677      */
 678     if (ifp->if_bridge != NULL) {
 679         m->m_flags &= ~M_PROMISC;
 680         BRIDGE_INPUT(ifp, m);
 681         if (m == NULL) {
 682             CURVNET_RESTORE();
 683             return;
 684         }
 685         eh = mtod(m, struct ether_header *);
 686     }
 687 
 ......
 715 
 716     // 上面都是处理二层相关的东西,该函数里面开始为处理ip做准备
 717     ether_demux(ifp, m);
 718     CURVNET_RESTORE();
 719 }

ether_demux()
 882 void
 883 ether_demux(struct ifnet *ifp, struct mbuf *m)                                                                                                                                                                                    
 884 {
 885     struct ether_header *eh;
 886     int i, isr;
 887     u_short ether_type;
 888 
 889     NET_EPOCH_ASSERT();
 890     KASSERT(ifp != NULL, ("%s: NULL interface pointer", __func__));
 891 
 892     // TODO 重点关注这里,应该是包过滤相关的钩子链!!!
 893     /* Do not grab PROMISC frames in case we are re-entered. */
 894     if (PFIL_HOOKED_IN(V_link_pfil_head) && !(m->m_flags & M_PROMISC)) {
 895         i = pfil_run_hooks(V_link_pfil_head, &m, ifp, PFIL_IN, NULL);
 896         if (i != 0 || m == NULL)
 897             return;
 898     }
 899 
 900     // 获取到上层协议的类型
 901     eh = mtod(m, struct ether_header *);
 902     ether_type = ntohs(eh->ether_type);
 903 
 904     /*
 905      * If this frame has a VLAN tag other than 0, call vlan_input()
 906      * if its module is loaded. Otherwise, drop.
 907      */
 908     if ((m->m_flags & M_VLANTAG) &&
 ......
 920         return;
 921     }
 922 
 923     // 混杂模式
 924     // 如果上层没开混杂模式,就直接丢包
 925     /*
 926      * Pass promiscuously received frames to the upper layer if the user
 927      * requested this by setting IFF_PPROMISC. Otherwise, drop them.
 928      */
 929     if ((ifp->if_flags & IFF_PPROMISC) == 0 && (m->m_flags & M_PROMISC)) {
 930         m_freem(m);
 931         return;
 932     }
 933 
 934     /*
 935      * Reset layer specific mbuf flags to avoid confusing upper layers.
 936      * Strip off Ethernet header.
 937      */
 938     m->m_flags &= ~M_VLANTAG;                                                                                                                                                                                                     
 939     m_clrprotoflags(m);
 940     m_adj(m, ETHER_HDR_LEN);
 941 
 942     /*
 943      * Dispatch frame to upper layer.
 944      */
 945     switch (ether_type) {
 946 #ifdef INET
 947     case ETHERTYPE_IP:
 948         // 开始处理ip协议
 949         isr = NETISR_IP;
 950         break;
 951 
 952     case ETHERTYPE_ARP:
 953         if (ifp->if_flags & IFF_NOARP) {
 954             /* Discard packet if ARP is disabled on interface */
 955             m_freem(m);
 956             return;
 957         }
 958         isr = NETISR_ARP;
 959         break;
 960 #endif
 961 #ifdef INET6
 962     case ETHERTYPE_IPV6:
 963         isr = NETISR_IPV6;
 964         break;
 965 #endif
 966     default:
 967         goto discard;
 968     }
 969 
 970     // 正式处理上层协议
 971     netisr_dispatch(isr, m);
 972     return;
 973 
 974 discard:
 ......
 991     m_freem(m);
 992 }

出口:ether_output()

   279	/*
   280	 * Ethernet output routine.
   281	 * Encapsulate a packet of type family for the local net.
   282	 * Use trailer local net encapsulation if enough data in first
   283	 * packet leaves a multiple of 512 bytes of data in remainder.
   284	 * 入参
   285	 * dst:当前报文要去的下一跳地址,可能是主机也可能是路由器。
   286	 * 注意,此时报文没有二层头,m->m_data指向ip头部。
   287	 */
   288	int
   289	ether_output(struct ifnet *ifp, struct mbuf *m,
   290		const struct sockaddr *dst, struct route *ro)
   291	{
   292		int error = 0;
   293		char linkhdr[ETHER_HDR_LEN], *phdr;
   294		struct ether_header *eh;
   295		struct pf_mtag *t;
   296		bool loop_copy;
   297		int hlen;	/* link layer header length */
   298		uint32_t pflags;
   299		struct llentry *lle = NULL;
   300		int addref = 0;
   301	
   302	    // 从路由缓存中获取链路层头部
   303		phdr = NULL;
   304		pflags = 0;
   305		if (ro != NULL) {
   306	        // TODO 后期细看BPF
   307			/* XXX BPF uses ro_prepend */
   308			if (ro->ro_prepend != NULL) {
   309				phdr = ro->ro_prepend;
   310				hlen = ro->ro_plen;
   311			} else if (!(m->m_flags & (M_BCAST | M_MCAST))) {
   312				// 表明有链路层缓存
   313	            if ((ro->ro_flags & RT_LLE_CACHE) != 0) {
   314					lle = ro->ro_lle;
   315					if (lle != NULL &&
   316					    (lle->la_flags & LLE_VALID) == 0) {
   317						LLE_FREE(lle);
   318						lle = NULL;	/* redundant */
   319						ro->ro_lle = NULL;
   320					}
   321					if (lle == NULL) {
   322						/* if we lookup, keep cache */
   323						addref = 1;
   324					} else
   325						/*
   326						 * Notify LLE code that
   327						 * the entry was used
   328						 * by datapath.
   329						 */
   330						llentry_mark_used(lle);
   331				}
   332				if (lle != NULL) {
   333					phdr = lle->r_linkdata;
   334					hlen = lle->r_hdrlen;
   335					pflags = lle->r_flags;
   336				}
   337			}
   338		}
   339	
   340	#ifdef MAC
   341		error = mac_ifnet_check_transmit(ifp, m);
   342		if (error)
   343			senderr(error);
   344	#endif
   345	
   346	    // TODO 后期细看M_PROFILE()
   347		M_PROFILE(m);
   348		if (ifp->if_flags & IFF_MONITOR)
   349			senderr(ENETDOWN);
   350		if (!((ifp->if_flags & IFF_UP) &&
   351		    (ifp->if_drv_flags & IFF_DRV_RUNNING)))
   352			senderr(ENETDOWN);
   353	
   354	    // 如果无法从路由缓存中获取二层头部,则这里手动查对应的二层相关信息。
   355		if (phdr == NULL) {
   356			/* No prepend data supplied. Try to calculate ourselves. */
   357			phdr = linkhdr;
   358			hlen = ETHER_HDR_LEN;
   359	
   360	        /* 根据ip地址,查对应的物理地址,并将其填充到phdr数组中组成一个二层头。
   361	         * TODO 后期细看。
   362	         * */ 
   363			error = ether_resolve_addr(ifp, m, dst, ro, phdr, &pflags,
   364			    addref ? &lle : NULL);
   365			if (addref && lle != NULL)
   366				ro->ro_lle = lle;
   367			if (error != 0)
   368				return (error == EWOULDBLOCK ? 0 : error);
   369		}
   370	
   371	    // TODO 后期细看
   372		if ((pflags & RT_L2_ME) != 0) {
   373			update_mbuf_csumflags(m, m);
   374			return (if_simloop(ifp, m, dst->sa_family, 0));
   375		}
   376		loop_copy = (pflags & RT_MAY_LOOP) != 0;
   377	
   378		/* TODO 后期细看
   379		 * Add local net header.  If no space in first mbuf,
   380		 * allocate another.
   381		 *
   382		 * Note that we do prepend regardless of RT_HAS_HEADER flag.
   383		 * This is done because BPF code shifts m_data pointer
   384		 * to the end of ethernet header prior to calling if_output().
   385	     * 这个宏里面实现m->m_data的偏移,腾出拼接二层头部的空间。
   386		 */
   387		M_PREPEND(m, hlen, M_NOWAIT);
   388		if (m == NULL)
   389			senderr(ENOBUFS);
   390		
   391	    // 如果还没有二层头部,就将二层头部拷贝进去
   392	    if ((pflags & RT_HAS_HEADER) == 0) {
   393			// 将二层头拷贝到当前报文
   394			eh = mtod(m, struct ether_header *);
   395	        memcpy(eh, phdr, hlen);
   396		}
   397	    
   398	    // 注意,此时当前报文已经具有了二层头了!!!
   399	
   400		/* TODO 后期细看有关这里单工网卡的内容
   401		 * If a simplex interface, and the packet is being sent to our
   402		 * Ethernet address or a broadcast address, loopback a copy.
   403		 * XXX To make a simplex device behave exactly like a duplex
   404		 * device, we should copy in the case of sending to our own
   405		 * ethernet address (thus letting the original actually appear
   406		 * on the wire). However, we don't do that here for security
   407		 * reasons and compatibility with the original behavior.
   408		 */
   409		if ((m->m_flags & M_BCAST) && loop_copy && (ifp->if_flags & IFF_SIMPLEX) &&
   410		    ((t = pf_find_mtag(m)) == NULL || !t->routed)) {
   411			struct mbuf *n;
   412	
   413			/*
   414			 * Because if_simloop() modifies the packet, we need a
   415			 * writable copy through m_dup() instead of a readonly
   416			 * one as m_copy[m] would give us. The alternative would
   417			 * be to modify if_simloop() to handle the readonly mbuf,
   418			 * but performancewise it is mostly equivalent (trading
   419			 * extra data copying vs. extra locking).
   420			 *
   421			 * XXX This is a local workaround.  A number of less
   422			 * often used kernel parts suffer from the same bug.
   423			 * See PR kern/105943 for a proposed general solution.
   424			 */
   425			if ((n = m_dup(m, M_NOWAIT)) != NULL) {
   426				update_mbuf_csumflags(m, n);
   427				(void)if_simloop(ifp, n, dst->sa_family, hlen);
   428			} else
   429				if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
   430		}
   431	
   432	    /* TODO 后期细看
   433		* Bridges require special output handling.
   434		*/
   435		if (ifp->if_bridge) {
   436			BRIDGE_OUTPUT(ifp, m, error);
   437			return (error);
   438		}
   439	
   440	#if defined(INET) || defined(INET6)
   441		// TODO 后期细看
   442	    if (ifp->if_carp &&
   443		    (error = (*carp_output_p)(ifp, m, dst)))
   444			goto bad;
   445	#endif
   446	
   447		/* TODO 后期细看
   448	     * Handle ng_ether(4) processing, if any */
   449		if (ifp->if_l2com != NULL) {
   450			KASSERT(ng_ether_output_p != NULL,
   451			    ("ng_ether_output_p is NULL"));
   452			if ((error = (*ng_ether_output_p)(ifp, &m)) != 0) {
   453	bad:			if (m != NULL)
   454					m_freem(m);
   455				return (error);
   456			}
   457			if (m == NULL)
   458				return (0);
   459		}
   460	
   461		/* Continue with link-layer output.
   462	     * 此时报文的二层头部已经拼接好。
   463	     * */
   464		return ether_output_frame(ifp, m);
   465	}

ip层处理流程

ip_init()

 306 /*
 307  * IP initialization: fill in IP protocol switch table.
 308  * All protocols not implemented in kernel go to raw IP protocol handler.
 309  * 如果报文使用了本内核没有实现的协议,则直接走 raw ip protocol 函数
 310  */
 311 void
 312 ip_init(void)
 313 {
 314     struct pfil_head_args args;
 315     struct protosw *pr;
 316     int i;
 317 
 318     CK_STAILQ_INIT(&V_in_ifaddrhead);
 319     V_in_ifaddrhashtbl = hashinit(INADDR_NHASH, M_IFADDR, &V_in_ifaddrhmask);
 320 
 321     // TODO 后期细看,ip分片与重组相关的内容。
 322     /* Initialize IP reassembly queue. */
 323     ipreass_init();
 324 
 325     // TODO 钩子相关,重点关注
 326     /* Initialize packet filter hooks. */
 327     args.pa_version = PFIL_VERSION;
 328     args.pa_flags = PFIL_IN | PFIL_OUT;
 329     args.pa_type = PFIL_TYPE_IP4;
 330     args.pa_headname = PFIL_INET_NAME;
 331     V_inet_pfil_head = pfil_head_register(&args);
 332 
 333     if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET,
 334         &V_ipsec_hhh_in[HHOOK_IPSEC_INET],
 335         HHOOK_WAITOK | HHOOK_HEADISINVNET) != 0)
 336         printf("%s: WARNING: unable to register input helper hook\n",
 337             __func__);
 338     if (hhook_head_register(HHOOK_TYPE_IPSEC_OUT, AF_INET,
 339         &V_ipsec_hhh_out[HHOOK_IPSEC_INET],
 340         HHOOK_WAITOK | HHOOK_HEADISINVNET) != 0)
 341         printf("%s: WARNING: unable to register output helper hook\n",
 342             __func__);
 343 
 344     /* Skip initialization of globals for non-default instances. */
 345 #ifdef VIMAGE
 346     if (!IS_DEFAULT_VNET(curvnet)) {
 347         netisr_register_vnet(&ip_nh);
 348 #ifdef  RSS
 349         netisr_register_vnet(&ip_direct_nh);
 350 #endif
 351         return;
 352     }
 353 #endif
 354                                                                                                                                                                                                                
 355     // AF_INET地址族    2
 356     // 这个pr应该是inetsw中的 pr_protocol = IPPROTO_RAW
 357     // pr_input = rip_input                                                                                                                                                                                    
 358     // pr_type  = SOCK_RAW
 359     pr = pffindproto(PF_INET, IPPROTO_RAW, SOCK_RAW);
 360     if (pr == NULL)
 361         panic("ip_init: PF_INET not found");
 362 
 363     // ip_protox[]里面记录的其实是inetsw中每个结构体元素在inetsw中的数组索引。
 364     // ip_protox[]的数组索引是协议号,以该协议号为索引在ip_protox[]中就可以找到该协议在inetsw中对应
 365     // 的索引,进而找到该协议在inetsw中对应的结构体。
 366     // 从这个逻辑看,之所以有ip_protox[]这个数组,完全是因为inetsw数组的索引和协议号不是对应的,所以
 367     // 无法直接将协议号作为索引在inetsw中找到该协议对应的结构体。用ip_protox[]作为媒介,实现将
 368     // 协议号作为索引间接寻找该协议对应的结构体。
 369     /* Initialize the entire ip_protox[] array to IPPROTO_RAW. */
 370     for (i = 0; i < IPPROTO_MAX; i++)
 371         ip_protox[i] = pr - inetsw;
 372     /*
 373      * Cycle through IP protocols and put them into the appropriate place
 374      * in ip_protox[].
 375      * 这里处理的不仅仅是ip协议,ip_protox[]里面同样记录了tcp/udp等的索引
 376      */
 377     for (pr = inetdomain.dom_protosw;
 378         pr < inetdomain.dom_protoswNPROTOSW; pr++)
 379         if (pr->pr_domain->dom_family == PF_INET &&
 380             pr->pr_protocol && pr->pr_protocol != IPPROTO_RAW) {
 381             /* Be careful to only index valid IP protocols. */
 382             if (pr->pr_protocol < IPPROTO_MAX)
 383                 ip_protox[pr->pr_protocol] = pr - inetsw;
 384         }
 385 
 386     // nh_handler = ip_input
 387     netisr_register(&ip_nh);
 388 #ifdef  RSS
 389     netisr_register(&ip_direct_nh);
 390 #endif
 391 }

入口:ip_input()

freebsd/netinet/ip_input.c

   466	void
   467	ip_input(struct mbuf *m)
   468	{
   469		struct rm_priotracker in_ifa_tracker;
   470		struct ip *ip = NULL;
   471		struct in_ifaddr *ia = NULL;
   472		struct ifaddr *ifa;
   473		struct ifnet *ifp;
   474		int    checkif, hlen = 0;
   475		uint16_t sum, ip_len;
   476		int dchg = 0;				/* dest changed after fw */
   477		struct in_addr odst;			/* original dst address */
   478	
   479	    // 检查包头是否存在
   480		M_ASSERTPKTHDR(m);
   481		NET_EPOCH_ASSERT();
   482	
   483	    // TODO 后期细看,后面结合二层协议处理流程里面打标的部分细看,这里应该是为了将报文快速上送本机
   484	    // 这里应该是“快转”流程,如果报文已经被打上了“快速上送本机”的标志,那这里就直接走上送本机的流程
   485		if (m->m_flags & M_FASTFWD_OURS) {
   486			m->m_flags &= ~M_FASTFWD_OURS;
   487			/* Set up some basics that will be used later. */
   488			ip = mtod(m, struct ip *);
   489			hlen = ip->ip_hl << 2;
   490			ip_len = ntohs(ip->ip_len);
   491	
   492	        // 上送本机
   493			goto ours;
   494		}
   495	
   496	    // 总的收包计数加一
   497		IPSTAT_INC(ips_total);
   498	
   499	    // ip 包的总长度 struct ip : freebsd/netinet/ip.h
   500	    // 该结构体只是20B的ip报文头部,不包含选项
   501		if (m->m_pkthdr.len < sizeof(struct ip))
   502			goto tooshort;
   503	
   504		if (m->m_len < sizeof (struct ip) &&
   505		    (m = m_pullup(m, sizeof (struct ip))) == NULL) {
   506			IPSTAT_INC(ips_toosmall);
   507			return;
   508		}
   509	    // 将mbuf->m_data强转为struct ip*类型
   510		ip = mtod(m, struct ip *); // mtod(): freebsd/sys/mbuf.h
   511	
   512	    // 检查ip包的版本号
   513		if (ip->ip_v != IPVERSION) {
   514			IPSTAT_INC(ips_badvers);
   515			goto bad;
   516		}
   517	
   518	    // 将ip首部中的“首部长度”乘以4得到实际的ip报文的首部长度,
   519	    // 该长度如果小于20B的标准长度,就视为异常包。
   520		hlen = ip->ip_hl << 2;
   521		if (hlen < sizeof(struct ip)) {	/* minimum header length */
   522			IPSTAT_INC(ips_badhlen);
   523			goto bad;
   524		}
   525	    /* TODO 后期细看分片相关内容。应该是首部包含了选项导致该ip包太大,一个mbuf
   526	     * 装不下,估计是分片了,具体业务场景后期细看。此时调用m_pullup()重新开辟一
   527	     * 块空间存储该报文,估计是为了后面做分片重组。
   528	     * */ 
   529		if (hlen > m->m_len) {
   530			if ((m = m_pullup(m, hlen)) == NULL) {
   531				IPSTAT_INC(ips_badhlen);
   532				return;
   533			}
   534			ip = mtod(m, struct ip *);
   535		}
   536	
   537		// TODO 后期细看,探针?!
   538	    IP_PROBE(receive, NULL, NULL, ip, m->m_pkthdr.rcvif, ip, NULL);
   539	
   540	    // TODO 后期细看
   541		/* IN_LOOPBACK must not appear on the wire - RFC1122 */
   542		ifp = m->m_pkthdr.rcvif;    // 获取当前报文的ifp,即接收该包的设备和网口等
   543		if (IN_LOOPBACK(ntohl(ip->ip_dst.s_addr)) ||
   544		    IN_LOOPBACK(ntohl(ip->ip_src.s_addr))) {
   545			if ((ifp->if_flags & IFF_LOOPBACK) == 0) {
   546				IPSTAT_INC(ips_badaddr);
   547				goto bad;
   548			}
   549		}
   550	
   551	    // 计算校验和
   552		if (m->m_pkthdr.csum_flags & CSUM_IP_CHECKED) {
   553			sum = !(m->m_pkthdr.csum_flags & CSUM_IP_VALID);
   554		} else {
   555			if (hlen == sizeof(struct ip)) {
   556				sum = in_cksum_hdr(ip);
   557			} else {
   558				sum = in_cksum(m, hlen);
   559			}
   560		}
   561	    // 未被破坏的首部其校验和应该是0
   562		if (sum) {
   563			IPSTAT_INC(ips_badsum);
   564			goto bad;
   565		}
   566	
   567	    // TODO 后期细看
   568	#ifdef ALTQ
   569		if (altq_input != NULL && (*altq_input)(m, AF_INET) == 0)
   570			/* packet is dropped by traffic conditioner */
   571			return;
   572	#endif
   573	
   574	    // 获取该报文的总长度,注意要转换成本机字节序
   575		ip_len = ntohs(ip->ip_len);
   576		if (ip_len < hlen) {
   577			IPSTAT_INC(ips_badlen);
   578			goto bad;
   579		}
   580	
   581		/*
   582		 * Check that the amount of data in the buffers
   583		 * is as at least much as the IP header would have us expect.
   584		 * Trim mbufs if longer than we expect.
   585		 * Drop packet if shorter than we expect.
   586	     * 要保证从ip头部计算出来的整个ip报文长度和实际收到的mbuf的数据长度一致,
   587	     * ip_len 是从ip头部计算出来的报文总长度;
   588	     * m->m_pkthdr.len 是mbuf中记录的实际收到的报文长度;
   589	     * m->m_pkthdr.len < ip_len,说明某些字节被丢弃,这个包不能要了;
   590	     * m->m_pkthdr.len > ip_len,说明可能是这个包比网络上要求的最小长度还小,设备会在发送该帧时
   591	     * 加上多余的字节,这种情况直接把多余的字节丢掉就好。
   592		 */
   593		if (m->m_pkthdr.len < ip_len) {
   594	tooshort:
   595			IPSTAT_INC(ips_tooshort);
   596	        // 丢包
   597			goto bad;
   598		}
   599		if (m->m_pkthdr.len > ip_len) {
   600			if (m->m_len == m->m_pkthdr.len) {
   601				m->m_len = ip_len;
   602				m->m_pkthdr.len = ip_len;
   603			} else
   604				m_adj(m, ip_len - m->m_pkthdr.len);    // TODO 后面细看
   605		}
   606	
   607	    /* 此时已经有了完整的IP首部;
   608	     * 分组的逻辑长度和物理长度相同;
   609	     * 检验和表明分组的首部此时已经无损地到达。
   610	     * 接下来可以开始用这个包做实际的业务了。
   611	     * */
   612	
   613		/*
   614		 * Try to forward the packet, but if we fail continue.
   615		 * ip_tryforward() does not generate redirects, so fall
   616		 * through to normal processing if redirects are required.
   617		 * ip_tryforward() does inbound and outbound packet firewall
   618		 * processing. If firewall has decided that destination becomes
   619		 * our local address, it sets M_FASTFWD_OURS flag. In this
   620		 * case skip another inbound firewall processing and update
   621		 * ip pointer.
   622		 */
   623	    // TODO 后期细看 ip_tryforward()
   624	    // V_ipforwarding 应该是本机是否开启转发功能的开关,
   625	    // 这里应该是说如果开启了转发功能,直接在此处就尝试转发该报文
   626		if (V_ipforwarding != 0
   627	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   628		    && (!IPSEC_ENABLED(ipv4) ||
   629		    IPSEC_CAPS(ipv4, m, IPSEC_CAP_OPERABLE) == 0)
   630	#endif
   631		    ) {
   632			if ((m = ip_tryforward(m)) == NULL)
   633				return;
   634			if (m->m_flags & M_FASTFWD_OURS) {
   635				m->m_flags &= ~M_FASTFWD_OURS;
   636				ip = mtod(m, struct ip *);
   637				goto ours;
   638			}
   639		}
   640	
   641	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   642		/*
   643		 * Bypass packet filtering for packets previously handled by IPsec.
   644		 */
   645		if (IPSEC_ENABLED(ipv4) &&
   646		    IPSEC_CAPS(ipv4, m, IPSEC_CAP_BYPASS_FILTER) != 0)
   647				goto passin;
   648	#endif
   649	
   650	    // TODO 后期细看,涉及到钩子!!!重点!!!
   651		/*
   652		 * Run through list of hooks for input packets.
   653		 *
   654		 * NB: Beware of the destination address changing (e.g.
   655		 *     by NAT rewriting).  When this happens, tell
   656		 *     ip_forward to do the right thing.
   657		 */
   658	
   659		/* Jump over all PFIL processing if hooks are not active. */
   660		if (!PFIL_HOOKED_IN(V_inet_pfil_head))
   661			goto passin;
   662	
   663	    // 由于钩子有可能会修改目的ip,所以在执行钩子之前,这里先记录报文的原始目的ip
   664		odst = ip->ip_dst;
   665	
   666	    // TODO 后期细看
   667	    // 遍历执行全部钩子,钩子应该是全部都注册在V_inet_pfil_head里面了
   668	    // 这里有点像netfilter框架pre_routing,因为钩子里面可以修改目的地址,
   669	    // 而修改了目的地址就意味着可以做目的nat了。
   670		if (pfil_run_hooks(V_inet_pfil_head, &m, ifp, PFIL_IN, NULL) !=
   671		    PFIL_PASS)
   672			return;
   673		// 钩子直接将该报文“吞掉”了
   674	    if (m == NULL)			/* consumed by filter */
   675			return;
   676	
   677	    // 这里又用了一次mtod(),估计是因为上面会执行钩子函数,里面可能会修改报文,所以这里修正一下
   678		ip = mtod(m, struct ip *);
   679	    // 判断钩子是否修改了报文中的目的ip,dhcg: dest changed
   680		dchg = (odst.s_addr != ip->ip_dst.s_addr);
   681	    // 因为上面执行了钩子,里面的rcvif可能被修改了,所以这里需要重新获取ifp
   682		ifp = m->m_pkthdr.rcvif;    
   683	
   684	    // 如果该判断成立,则该报文上送本机,否则就转发(如果开启了转发功能)。
   685	    // m->m_flags 里面“是否要上送本机”的标志应该是之前二层协议处理流程和钩子函数打的,这些流程通过
   686	    // 打标来真正决定一个报文是否可以上送本机,但是这些流程内部可能并不会将可以上送本机的报文直接上
   687	    // 送到本机,实际上送本机的流程或者直接转发的流程是在这里做的。当然,钩子也可以直接吞掉这个包,
   688	    // 那就无所谓上送本及还是转发了,防火墙或者内容审计这种业务有可能会做这种事情。
   689		if (m->m_flags & M_FASTFWD_OURS) {
   690			m->m_flags &= ~M_FASTFWD_OURS;
   691	
   692	        // 该报文上送本机
   693			goto ours;
   694		}
   695	    // 如果钩子里面修改了目的地址,并将m->m_flags打上了M_IP_NEXTHOP标签,
   696	    // 那就不用查路由了,这里可以直接将其转发。该流程特别适合做目的NAT
   697		if (m->m_flags & M_IP_NEXTHOP) {
   698	        // TODO 后期细看
   699			if (m_tag_find(m, PACKET_TAG_IPFORWARD, NULL) != NULL) {
   700				/*
   701				 * Directly ship the packet on.  This allows
   702				 * forwarding packets originally destined to us
   703				 * to some other directly connected host.
   704				 */
   705				ip_forward(m, 1);
   706				return;
   707			}
   708		}
   709	
   710	// 如果上面的流程无法决定上送本机还是转发,那么就在这里决定上送本机还是转发。    
   711	passin:
   712	
   713		/*
   714		 * Process options and, if not destined for us,
   715		 * ship it on.  ip_dooptions returns 1 when an
   716		 * error was detected (causing an icmp message
   717		 * to be sent and the original packet to be freed).
   718	     * 如果携带了ip选项(通过ip头长度来判断),就调用ip_dooptions()来处理,如果处理失败,就直接返回
   719	     * TODO 后期细看ip_dooptions()
   720	     * TODO 分析下处理失败的情况下直接返回后,该报文怎么处理?是丢包吗?
   721		 */
   722		if (hlen > sizeof (struct ip) && ip_dooptions(m, 0))
   723			return;
   724	
   725	    /* 处理完ip选项,继续处理该包 */
   726	
   727	    /* greedy RSVP, snatches any PATH packet of the RSVP protocol and no
   728	     * matter if it is destined to another node, or whether it is
   729	     * a multicast one, RSVP wants it! and prevents it from being forwarded
   730	     * anywhere else. Also checks if the rsvp daemon is running before
   731	     * grabbing the packet.
   732	     * TODO 后期细看rsvp的东西
   733	     */
   734		if (V_rsvp_on && ip->ip_p==IPPROTO_RSVP)
   735			goto ours;
   736	
   737	    /* ------------------------- 收包查路由 ------------------------- */
   738	    /* 判断是否要上送本机:
   739	     * 1. 是否完全匹配地址
   740	     * 2. 是否匹配某个接口(ifp)相关的广播地址
   741	     * 3. 是否为组播地址
   742	     * 4. 是否为广播地址(与ifp无关)
   743	     * 5. 是否为0.0.0.0,如果是,上送本机
   744	     * 判断是否转发以及如何转发:
   745	     * 1. 判断本机是否开启转发,如果没开,直接丢包;
   746	     * 2. 开启了转发,走ip_forward()转发该包。
   747	     * */
   748	
   749		/*
   750		 * Check our list of addresses, to see if the packet is for us.
   751		 * If we don't have any addresses, assume any unicast packet
   752		 * we receive might be for us (and let the upper layers deal
   753		 * with it).
   754	     * TODO 后期细看该判断条件的细节,该判断在干嘛?
   755		 */
   756		if (CK_STAILQ_EMPTY(&V_in_ifaddrhead) &&
   757		    (m->m_flags & (M_MCAST|M_BCAST)) == 0)
   758	        // 将该报文上送本机
   759			goto ours;
   760	
   761		/* TODO 后期细看RFC1122,尤其是这里的内容
   762	     * TODO 后期细看下这里checkif的作用
   763		 * Enable a consistency check between the destination address
   764		 * and the arrival interface for a unicast packet (the RFC 1122
   765		 * strong ES model) if IP forwarding is disabled and the packet
   766		 * is not locally generated and the packet is not subject to
   767		 * 'ipfw fwd'.
   768		 *
   769		 * XXX - Checking also should be disabled if the destination
   770		 * address is ipnat'ed to a different interface.
   771		 *
   772		 * XXX - Checking is incompatible with IP aliases added
   773		 * to the loopback interface instead of the interface where
   774		 * the packets are received.
   775		 *
   776		 * XXX - This is the case for carp vhost IPs as well so we
   777		 * insert a workaround. If the packet got here, we already
   778		 * checked with carp_iamatch() and carp_forus().
   779		 */
   780		checkif = V_ip_checkinterface && (V_ipforwarding == 0) &&
   781		    ifp != NULL && ((ifp->if_flags & IFF_LOOPBACK) == 0) &&
   782		    ifp->if_carp == NULL && (dchg == 0);
   783	
   784		/*
   785		 * Check for exact addresses in the hash bucket.
   786	     * 查路由表,查看包里面的目的地址是否和表里面的地址“完全”匹配
   787	     * 此处主要看的是“地址完全匹配”这种情况。
   788		 */
   789		IN_IFADDR_RLOCK(&in_ifa_tracker);   // 锁?
   790		LIST_FOREACH(ia, INADDR_HASH(ip->ip_dst.s_addr), ia_hash) {
   791			/*
   792			 * If the address matches, verify that the packet
   793			 * arrived via the correct interface if checking is
   794			 * enabled.
   795	         * ia->ia_ifp == ifp 表示“网口定义结构体”也匹配
   796			 */
   797			if (IA_SIN(ia)->sin_addr.s_addr == ip->ip_dst.s_addr &&
   798			    (!checkif || ia->ia_ifp == ifp)) {
   799				counter_u64_add(ia->ia_ifa.ifa_ipackets, 1);
   800				counter_u64_add(ia->ia_ifa.ifa_ibytes,
   801				    m->m_pkthdr.len);
   802				IN_IFADDR_RUNLOCK(&in_ifa_tracker);
   803	
   804	            // 包中的目的地址和路由表中的目的地址完全匹配,
   805	            // 且ifp也匹配(假设不管checkif),则上送本机
   806				goto ours;
   807			}
   808		}
   809		IN_IFADDR_RUNLOCK(&in_ifa_tracker);
   810	
   811		/*
   812		 * Check for broadcast addresses.
   813		 *
   814		 * Only accept broadcast packets that arrive via the matching
   815		 * interface.  Reception of forwarded directed broadcasts would
   816		 * be handled via ip_forward() and ether_output() with the loopback
   817		 * into the stack for SIMPLEX interfaces handled by ether_output().
   818	     * 查路由表,这次是查与接口相关的广播地址的匹配。
   819	     * 注意,一个interface可能有多个ip地址,而ifp->if_addrhead将一个interface的所有地址都链起来;
   820	     * 注意,当前包是从ifp这个interface上来的。
   821		 */
   822		if (ifp != NULL && ifp->if_flags & IFF_BROADCAST) {
   823			CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
   824	            // tcp/udp协议族
   825				if (ifa->ifa_addr->sa_family != AF_INET)
   826					continue;
   827				ia = ifatoia(ifa);
   828				
   829	            // 判断该网卡的广播地址和该包的目的地址是否匹配
   830	            if (satosin(&ia->ia_broadaddr)->sin_addr.s_addr ==
   831				    ip->ip_dst.s_addr) {
   832					counter_u64_add(ia->ia_ifa.ifa_ipackets, 1);
   833					counter_u64_add(ia->ia_ifa.ifa_ibytes,
   834					    m->m_pkthdr.len);
   835	
   836	                // 匹配,直接上送本机,本机需要处理这个广播包
   837					goto ours;
   838				}
   839	#ifdef BOOTP_COMPAT
   840				if (IA_SIN(ia)->sin_addr.s_addr == INADDR_ANY) {
   841					counter_u64_add(ia->ia_ifa.ifa_ipackets, 1);
   842					counter_u64_add(ia->ia_ifa.ifa_ibytes,
   843					    m->m_pkthdr.len);
   844					goto ours;
   845				}
   846	#endif
   847			}
   848			ia = NULL;
   849		}
   850	    
   851	    // 169.254.0.0/16,“链路本地地址”。DHCP分配失败的时候使用该地址作为临时地址。
   852		/* RFC 3927 2.7: Do not forward datagrams for 169.254.0.0/16. */
   853		if (IN_LINKLOCAL(ntohl(ip->ip_dst.s_addr))) {
   854			IPSTAT_INC(ips_cantforward);
   855			m_freem(m);
   856			return;
   857		}
   858	
   859	    // 多播/组播,224.0.0.0
   860	    // 判断目的地址是不是组播地址
   861		if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) {
   862			// TODO 该判断条件表示是否开启多播路由器功能?!
   863	        if (V_ip_mrouter) {
   864				/* multicast router:多播路由器
   865				 * If we are acting as a multicast router, all
   866				 * incoming multicast packets are passed to the
   867				 * kernel-level multicast forwarding function.
   868				 * The packet is returned (relatively) intact; if
   869				 * ip_mforward() returns a non-zero value, the packet
   870				 * must be discarded, else it may be accepted below.
   871	             * TODO 后期细看ip_mforward()相关内容,涉及多播转发流程
   872				 */
   873				if (ip_mforward && ip_mforward(ip, ifp, m, 0) != 0) {
   874	                // 丢弃该包,ip_mforward()返回失败说明出错或该分组通过分组隧道到达
   875					IPSTAT_INC(ips_cantforward);
   876					m_freem(m);
   877					return;
   878				}
   879	
   880				/*
   881				 * The process-level routing daemon needs to receive
   882				 * all multicast IGMP packets, whether or not this
   883				 * host belongs to their destination groups.
   884	             * 多播路由器必须接受所有IGMP分组,因为分组中有成员变化的信息。
   885				 */
   886				if (ip->ip_p == IPPROTO_IGMP)
   887	                // 上送本机
   888					goto ours;
   889				IPSTAT_INC(ips_forward);
   890			}
   891			/*
   892			 * Assume the packet is for us, to avoid prematurely taking
   893			 * a lock on the in_multi hash. Protocols must perform
   894			 * their own filtering and update statistics accordingly.
   895			 */
   896			goto ours;
   897		}
   898	    // 目的地址是否为广播地址(255.255.255.255),这次和ifp无关。
   899		if (ip->ip_dst.s_addr == (u_long)INADDR_BROADCAST)
   900	        // 上送本机
   901	        goto ours;
   902	    // 该包的目的地址全0,也上送本机
   903		if (ip->ip_dst.s_addr == INADDR_ANY)
   904			goto ours;
   905	
   906	    /* 走到这里,查路由过程中判断是否要上送本机的过程就全部结束了,要是可以上送本机的话,早就进ours
   907	     * 流程了,根本不会走到这里!所以接下来就要判断是否可以转发以及如何转发。
   908	     * */
   909	
   910		/*
   911		 * Not for us; forward if possible and desirable.
   912		 */
   913		if (V_ipforwarding == 0) {
   914	        // 该判断应该表示是否开启了转发功能,该判断成立则表示没有开启转发功能,
   915	        // 且该包又不能上送本机,所以直接丢包!
   916			IPSTAT_INC(ips_cantforward);
   917			m_freem(m);
   918		} else {
   919	        // 将该包转发!
   920	        // 注意这里的dchg,前面在将m扔进hook里面之后,目的地址可能被钩子函数修改了
   921	        // (有点netfilter框架路由前那意思了),dchg就是记录是否修改了包的目的地址,
   922	        // 所以这里进入转发流程时需要带着dchg。
   923			ip_forward(m, dchg);
   924		}
   925	
   926	    // 该上送本机的早就送上去了,没开转发的话也丢包了,开了转发的话也转发了,这里就可以返回了。
   927		return;
   928	
   929	// 上送本机的流程    
   930	ours:
   931	#ifdef IPSTEALTH
   932		/*
   933		 * IPSTEALTH: Process non-routing options only
   934		 * if the packet is destined for us.
   935		 */
   936	    // TODO 后期细看
   937		if (V_ipstealth && hlen > sizeof (struct ip) && ip_dooptions(m, 1))
   938			return;
   939	#endif /* IPSTEALTH */
   940	
   941		/*
   942		 * Attempt reassembly; if it succeeds, proceed.
   943		 * ip_reass() will return a different mbuf.
   944		 */
   945	    // TODO 后期细看,ip分片重组。
   946	    // 注意,这里可以看出,只有上送本机才会重组分片,单纯转发的话
   947	    // 是不会重组分片的。
   948		if (ip->ip_off & htons(IP_MF | IP_OFFMASK)) {
   949			/* XXXGL: shouldn't we save & set m_flags? */
   950			m = ip_reass(m);
   951			if (m == NULL)
   952				return;
   953			ip = mtod(m, struct ip *);
   954			/* Get the header length of the reassembled packet */
   955			hlen = ip->ip_hl << 2;
   956		}
   957	
   958	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   959	    // TODO 后期细看
   960		if (IPSEC_ENABLED(ipv4)) {
   961			if (IPSEC_INPUT(ipv4, m, hlen, ip->ip_p) != 0)
   962				return;
   963		}
   964	#endif /* IPSEC */
   965	
   966		/*
   967		 * Switch out to protocol's input routine.
   968		 */
   969		IPSTAT_INC(ips_delivered);
   970	
   971	    // 从这里开始处理icmp/igmp/tcp/udp/sctp等协议,注意这里用的全局结构体转换成了inetsw
   972	    // ip_protox[]的原理见ip_init()
   973		(*inetsw[ip_protox[ip->ip_p]].pr_input)(&m, &hlen, ip->ip_p);
   974		return;
   975	bad:
   976		m_freem(m);
   977	}

转发:ip_forward()三层转发

  1077	/*
  1078	 * Forward a packet.  If some error occurs return the sender
  1079	 * an icmp packet.  Note we can't always generate a meaningful
  1080	 * icmp message because icmp doesn't have a large enough repertoire
  1081	 * of codes and types.
  1082	 *
  1083	 * If not forwarding, just drop the packet.  This could be confusing
  1084	 * if ipforwarding was zero but some routing protocol was advancing
  1085	 * us as a gateway to somewhere.  However, we must let the routing
  1086	 * protocol deal with that.
  1087	 *
  1088	 * The srcrt parameter indicates whether the packet is being forwarded
  1089	 * via a source route.
  1090	 * 输入参数:
  1091	 * m:指向一个mbuf链,包含了要被转发的分组;
  1092	 * srcrt:非零,表示分组由于“源路由选项”正在被转发(《TCP/IP协议卷2》是这么说的),但是
  1093	 * 结合ip_input()的代码看,如果目的ip被钩子修改了,那么调用该函数的时候会将srcrt置为1。
  1094	 */
  1095	void
  1096	ip_forward(struct mbuf *m, int srcrt)
  1097	{
  1098		struct ip *ip = mtod(m, struct ip *);
  1099		struct in_ifaddr *ia;
  1100		struct mbuf *mcopy;
  1101		struct sockaddr_in *sin;
  1102		struct in_addr dest;
  1103		struct route ro;
  1104		uint32_t flowid;
  1105		int error, type = 0, code = 0, mtu = 0;
  1106	
  1107		// TODO 后期细看 net epoch相关内容
  1108	    NET_EPOCH_ASSERT();
  1109	
  1110	    /* 识别并丢弃以下分组:
  1111	     * 1. 链路层广播分组。驱动会在收到广播分组时将M_BCAST标志置位,如果分组寻址是到以太网广播
  1112	     * 地址,则ether_input()就把M_BCAST置位。
  1113	     * 2. 环回分组。对寻址到环回网络的分组, in_canforward()返回0。这些分组将被ip_input()提交给
  1114	     * ip_forward(),因为没有正确配置反馈接口。
  1115	     * 3. 网络0和E类地址(开头为240~255之间)。对这些分组,in_canforward()返回0。这些目的地址
  1116	     * 是无效的,而且因为没有主机接收这些分组,所以它们不应该继续在网络中流动。
  1117	     * 4. D类地址(开头为224~239之间)。寻址到D类地址的分组应该由多播函数ip_mforward()而不是由
  1118	     * ip_forward()处理,in_canforward()拒绝D类(多播)地址。
  1119	     * */ 
  1120		if (m->m_flags & (M_BCAST|M_MCAST) || in_canforward(ip->ip_dst) == 0) {
  1121			IPSTAT_INC(ips_cantforward);
  1122			m_freem(m);
  1123			return;
  1124		}
  1125	
  1126	    /* 处理TTL,如果本包的TTL已经是0或者是1,意味着此时本机已经是该包的最后一跳,
  1127	     * 但是该包还没有到达目的地,所以本机就只能发送回一个icmp然后丢包。
  1128	     * */ 
  1129		if (
  1130	#ifdef IPSTEALTH
  1131		    V_ipstealth == 0 &&
  1132	#endif
  1133		    ip->ip_ttl <= IPTTLDEC) {
  1134	        // 该函数里面会将此包丢掉
  1135			icmp_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, 0);
  1136			return;
  1137		}
  1138	
  1139	    /* ---------- 发送前查路由表,定位下一跳 ------------ */
  1140	
  1141		bzero(&ro, sizeof(ro));
  1142		sin = (struct sockaddr_in *)&ro.ro_dst;
  1143		sin->sin_family = AF_INET;
  1144		sin->sin_len = sizeof(*sin);
  1145		sin->sin_addr = ip->ip_dst;
  1146		flowid = m->m_pkthdr.flowid;
  1147	    // 调用fib4_lookup()函数查路由表,返回记录下一跳的结构体struct nhop_object
  1148		ro.ro_nh = fib4_lookup(M_GETFIB(m), ip->ip_dst, 0, NHR_REF, flowid);
  1149		if (ro.ro_nh != NULL) {
  1150	        // 进入这里,说明找到了下一跳路由结构体,这里是获取其中保存的网卡设备的地址。
  1151	        // 注意这里使用ifatoia()进行强制类型转换,目前尚不确定该转换是否真的存在踩内存的风险。
  1152	        // TODO 后期细看,确认是否存在风险。
  1153			ia = ifatoia(ro.ro_nh->nh_ifa);
  1154		} else
  1155			ia = NULL;
  1156	
  1157	    /* ----------- 为可能产生的差错做准备---------- */
  1158	
  1159		/* 在产生差错时, ip_output()要丢掉分组,所以m_copy复制分组的ip头部和前8个字
  1160	     * 节的ip负载,以便ip_forward()发送ICMP差错报文。如果m_copy失败,ip_forward()
  1161	     * 并不终止,在这种情况下,不发送差错报文。
  1162		 * Save the IP header and at most 8 bytes of the payload,
  1163		 * in case we need to generate an ICMP message to the src.
  1164		 *
  1165		 * XXX this can be optimized a lot by saving the data in a local
  1166		 * buffer on the stack (72 bytes at most), and only allocating the
  1167		 * mbuf if really necessary. The vast majority of the packets
  1168		 * are forwarded without having to send an ICMP back (either
  1169		 * because unnecessary, or because rate limited), so we are
  1170		 * really we are wasting a lot of work here.
  1171		 *
  1172		 * We don't use m_copym() because it might return a reference
  1173		 * to a shared cluster. Both this function and ip_output()
  1174		 * assume exclusive access to the IP header in `m', so any
  1175		 * data in a cluster may change before we reach icmp_error().
  1176		 */
  1177		mcopy = m_gethdr(M_NOWAIT, m->m_type);  // 创建一个mbuf
  1178	    // m_dup_pkthdr()会将m->pkthdr深度拷贝到mcopy->pkthdr中
  1179		if (mcopy != NULL && !m_dup_pkthdr(mcopy, m, M_NOWAIT)) {
  1180			/*
  1181			 * It's probably ok if the pkthdr dup fails (because
  1182			 * the deep copy of the tag chain failed), but for now
  1183			 * be conservative and just discard the copy since
  1184			 * code below may some day want the tags.
  1185			 */
  1186			m_free(mcopy);
  1187			mcopy = NULL;
  1188		}
  1189		if (mcopy != NULL) {
  1190			mcopy->m_len = min(ntohs(ip->ip_len), M_TRAILINGSPACE(mcopy));
  1191			mcopy->m_pkthdr.len = mcopy->m_len;
  1192			// 将m->m_data拷贝到mcopy->m_data
  1193	        m_copydata(m, 0, mcopy->m_len, mtod(mcopy, caddr_t));
  1194		}
  1195	#ifdef IPSTEALTH
  1196		if (V_ipstealth == 0)
  1197	#endif
  1198	        // TTL减1
  1199			ip->ip_ttl -= IPTTLDEC;
  1200	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
  1201		if (IPSEC_ENABLED(ipv4)) {
  1202			if ((error = IPSEC_FORWARD(ipv4, m)) != 0) {
  1203				/* mbuf consumed by IPsec */
  1204				RO_NHFREE(&ro);
  1205				m_freem(mcopy);
  1206				if (error != EINPROGRESS)
  1207					IPSTAT_INC(ips_cantforward);
  1208				return;
  1209			}
  1210			/* No IPsec processing required */
  1211		}
  1212	#endif /* IPSEC */
  1213	
  1214	    /* ---------------- 判断是否需要用ICMP重定向报文来重定向源主机的路由---------------- */
  1215	
  1216		/* 
  1217		 * If forwarding packet using same interface that it came in on,
  1218		 * perhaps should send a redirect to sender to shortcut a hop.
  1219		 * Only send redirect if source is sending directly to us(这句话的意思应该是,只有在源主机
  1220	     * 直接将包发送给本机的时候,本机才发送重定向报文;如果源主机是通过其他路由器将报文发送到本
  1221	     * 机的,那么本机就不应该发送重定向报文给源),
  1222		 * and if packet was not source routed (or has any options)(source routed:源路由选项).
  1223		 * Also, don't send redirect if forwarding using a default route
  1224		 * or a route modified by a redirect.
  1225	     * 当主机错误地选择某个路由器作为分组的第一跳路由器时,该路由器向源主机返回一个ICMP重定
  1226	     * 向报文。IP网络互连模型假定主机相对地并不知道整个互联网的拓扑结构,把维护正确路由选择
  1227	     * 的责任交给路由器。路由器发出重定向报文是向主机表明主机自身为分组选择了一个不正确的路由。
  1228		 */
  1229		dest.s_addr = 0;    // ICMP重定向报文中所包含的正确的下一个系统的地址。
  1230	
  1231	    /* 该判断应该是在决定是否发送重定向报文,路由器识别重定向情况的规则如下(《TCP/IP协议卷2》),
  1232	     * 1. 只有在同一接口上接收或重发分组时,才能使用重定向,即ia->ia_ifp == m->m_pkthdr.rcvif;
  1233	     * 2. 被选择的路由本身必须没有被ICMP重定向报文创建或修改过,即RTF_DYNAMIC|RTF_MODIFIED;
  1234	     * 3. 该路由不能是到默认目的地0.0.0.0的;
  1235	     * 由此保证系统在未授权时不会生成路由选择信息(?),并且不与其他系统共享自己的默认路由。
  1236	     * !srcrt是1:该报文中的目的ip没被钩子修改;
  1237	     * V_ipsendredirects:应该表示本机开启向发送该报文的一方发送重定向报文的功能;
  1238	     * ia != NULL:应该表示存在路由到下一跳的地址,即知道下一跳该去哪;
  1239	     * 该判断入口首先判断是否命中规则1。
  1240	     * */ 
  1241		if (!srcrt && V_ipsendredirects &&
  1242		    ia != NULL && ia->ia_ifp == m->m_pkthdr.rcvif) {
  1243			struct nhop_object *nh;
  1244	
  1245	        // 指向下一跳路由结构体
  1246			nh = ro.ro_nh;
  1247	
  1248	        // RTF_DYNAMIC|RTF_MODIFIED ----> NHF_REDIRECT
  1249	        // 若判断成立,说明命中规则2和3,需要重定向。
  1250			if (nh != NULL && ((nh->nh_flags & (NHF_REDIRECT|NHF_DEFAULT)) == 0)) {
  1251				// 小心这里的强制类型转换,搞不好会踩内存。nh->nh_ifa是“struct ifaddr* ”类型,
  1252	            // 这里要想在强转之后使用时完全没有踩内存的风险,除非当初往nh->nh_ifa上挂的时候
  1253	            // 原型就是struct in_ifaddr* 类型,只不过强转成struct ifaddr* 类型挂上去的,因为
  1254	            // struct ifaddr* nh_ifa是(必须是)ro_nh的第一个成员,所以可以强转。
  1255	            // TODO 后期细看下这里的情况,看看到底有没有踩内存的风险。
  1256	            struct in_ifaddr *nh_ia = (struct in_ifaddr *)(nh->nh_ifa);
  1257				u_long src = ntohl(ip->ip_src.s_addr);
  1258	
  1259	            /* nh_ia != NULL,说明拿到了本机网口设备地址(查到路由之后的);
  1260	             * 后面的条件用于判断该报文是否产生于本地子网(详见《TCP/IP协议卷2》),如果分组
  1261	             * 产生于其他子网,则前一系统是个路由器,本机就不应该发重定向报文;差错由路由选择
  1262	             * 协议纠正。
  1263	             * 注意,这里用的是src,即报文中的源ip,也就是发送该报文的设备地址,所谓“重定向”
  1264	             * 就是告诉发送该报文的设备“你不应该发给我,你要发给别人”,所以当然要用src来判断。
  1265	             * */ 
  1266				if (nh_ia != NULL &&
  1267				    (src & nh_ia->ia_subnetmask) == nh_ia->ia_subnet) {
  1268					/* ICMP重定向报文中需要包含正确的下一个系统的地址(dest.s_addr),如果目的主机
  1269	                 * 不在本机直接相连的网络上,则该地址是一个路由器的地址,源主机将被重定向到该地址;
  1270	                 * 如果目的主机在本机直接相连的网络中,则该地址就是目的主机地址。
  1271	                 * */
  1272	                if (nh->nh_flags & NHF_GATEWAY)
  1273	                    // 是个路由器地址
  1274						dest.s_addr = nh->gw4_sa.sin_addr.s_addr;
  1275					else
  1276	                    // 就是目的主机地址
  1277						dest.s_addr = ip->ip_dst.s_addr;
  1278					/* Router requirements says to only send host redirects */
  1279					type = ICMP_REDIRECT;
  1280					code = ICMP_REDIRECT_HOST;
  1281				}
  1282			}
  1283		}
  1284	
  1285	    /* 此时,已经拿到了该如何转发该报文的路由结构体,
  1286	     * 也判断出是否应该使用ICMP来重定向源主机的路由,
  1287	     * 下面就该实际转发该报文了!!!
  1288	     * 注意,就算需要给源主机发送ICMP重定向报文,本机也要老老实实将当前报文转发出去
  1289	     * (有可能是去下一跳路由,但最终肯定是到目的地址),即ICMP重定向并不影响当前报文
  1290	     * 的转发,只是源主机在拿到ICMP重定向报文并更新自己的路由表之后,下个报文(和当前
  1291	     * 报文的目的地址相同)就不经过本机了。《TCP/IP协议卷2》图8-18。
  1292	     * 注意:这里调用ip_output()的时候,mbuf->m_data是带着ip头部的!
  1293	     * */
  1294		error = ip_output(m, NULL, &ro, IP_FORWARDING, NULL, NULL);
  1295	
  1296		if (error == EMSGSIZE && ro.ro_nh)
  1297			mtu = ro.ro_nh->nh_mtu;
  1298		RO_NHFREE(&ro);
  1299	
  1300		if (error)
  1301			IPSTAT_INC(ips_cantforward);
  1302		else {
  1303			/* 注意,就算是需要发送ICMP重定向报文,由于重定向并不影响本机对当前报文的转发,
  1304	         * 即当前报文还是要被正常转发走的(去目的地而不是回源),所以这里的转发计数仍
  1305	         * 是要增加的。
  1306	         * */
  1307	        IPSTAT_INC(ips_forward);
  1308			if (type)
  1309				IPSTAT_INC(ips_redirectsent);
  1310			else {
  1311				if (mcopy)
  1312					m_freem(mcopy);
  1313				// 如果只是普通转发(不需要重定向),那么到这里就返回了
  1314	            return;
  1315			}
  1316		}
  1317		if (mcopy == NULL)
  1318			return;
  1319	
  1320		switch (error) {
  1321		case 0:				/* forwarded, but need redirect */
  1322			/* type, code set above */
  1323			break;
  1324	
  1325		case ENETUNREACH:
  1326		case EHOSTUNREACH:
  1327		case ENETDOWN:
  1328		case EHOSTDOWN:
  1329		default:
  1330			type = ICMP_UNREACH;
  1331			code = ICMP_UNREACH_HOST;
  1332			break;
  1333	
  1334		case EMSGSIZE:
  1335			type = ICMP_UNREACH;
  1336			code = ICMP_UNREACH_NEEDFRAG;
  1337			/*
  1338			 * If the MTU was set before make sure we are below the
  1339			 * interface MTU.
  1340			 * If the MTU wasn't set before use the interface mtu or
  1341			 * fall back to the next smaller mtu step compared to the
  1342			 * current packet size.
  1343			 */
  1344			if (mtu != 0) {
  1345				if (ia != NULL)
  1346					mtu = min(mtu, ia->ia_ifp->if_mtu);
  1347			} else {
  1348				if (ia != NULL)
  1349					mtu = ia->ia_ifp->if_mtu;
  1350				else
  1351					mtu = ip_next_mtu(ntohs(ip->ip_len), 0);
  1352			}
  1353			IPSTAT_INC(ips_cantfrag);
  1354			break;
  1355	
  1356		case ENOBUFS:
  1357		case EACCES:			/* ipfw denied packet */
  1358			m_freem(mcopy);
  1359			return;
  1360		}
  1361	
  1362	    // 对于重定向的情况,这里发送icmp重定向报文,初步观察其最终也是用ip_output()发送的。
  1363	    // TODO 后期细看
  1364		icmp_error(mcopy, type, code, dest.s_addr, mtu);
  1365	}

出口:ip_output()

   310	/* 调用ip_output()的时候,mbuf->m_data已经带着ip头了,只是这个ip头里面的
   311	 * 部分字段可能没有被填充,需要在这里填充这些字段。
   312	 * IP output.  The packet in mbuf chain m contains a skeletal IP
   313	 * header (with len, off, ttl, proto, tos, src, dst).
   314	 * The mbuf chain containing the packet will be freed.
   315	 * The mbuf opt, if present, will not be freed.
   316	 * If route ro is present and has ro_rt initialized, route lookup would be
   317	 * skipped and ro->ro_rt would be used. If ro is present but ro->ro_rt is NULL,
   318	 * then result of route lookup is stored in ro->ro_rt.
   319	 *
   320	 * In the IP forwarding case, the packet will arrive with options already
   321	 * inserted, so must have a NULL opt pointer.
   322	 * 参数列表,
   323	 * m    :要发送的分组;
   324	 * opt  :包含的ip选项;
   325	 * ro   :缓存的去目的地的路由,UDP/TCP会维护一个与各套接字相关的路由缓存;
   326	 * flags:IP_FORWARDING         大部分ip头部已经存在,这是个要被转发的分组;注意,该标志由
   327	 *                              ip_forward()和ip_mforward()设置并禁止ip_output()重设任何ip首部字段
   328	 *        IP_ROUTETOIF          忽略路由表,直接路由到接口;API send/sendto/sendmsg的MSG_DONTROUTE
   329	 *                              和套接字的SO_DONTROUTE使得IP_ROUTETOIF生效。
   330	 *        IP_ALLOWBROADCAST	    允许发送广播分组;可以被套接字的SO_BROADCAST选项设置,但是只被UDP提
   331	 *                              交,TCP不支持广播。
   332	 *        IP_RAWOUTPUT	        包含一个预构IP首部的分组。估计是由原始套接字API在用户态设置的。
   333	 * imo  :指向多播选项的指针。
   334	 * inp  :TODO 后期细看。
   335	 */
   336	int
   337	ip_output(struct mbuf *m, struct mbuf *opt, struct route *ro, int flags,
   338	    struct ip_moptions *imo, struct inpcb *inp)
   339	{
   340		struct rm_priotracker in_ifa_tracker;
   341		struct ip *ip;
   342		struct ifnet *ifp = NULL;	/* keep compiler happy */
   343		struct mbuf *m0;
   344		int hlen = sizeof (struct ip);
   345		int mtu = 0;
   346		int error = 0;
   347		int vlan_pcp = -1;
   348	    /* dst 当前报文的最终目的地“主机”地址。 */
   349		struct sockaddr_in *dst, sin; 
   350	    /* gw  既可以指向下一跳路由器(网关)的地址,也可以指向dst */
   351		const struct sockaddr_in *gw;
   352		struct in_ifaddr *ia = NULL;
   353		struct in_addr src;    /* src记录要给当前报文的ip头填充的源ip地址 */
   354		int isbroadcast;
   355		uint16_t ip_len, ip_off;
   356		uint32_t fibnum;
   357	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   358		int no_route_but_check_spd = 0;
   359	#endif
   360	
   361		M_ASSERTPKTHDR(m);
   362		NET_EPOCH_ASSERT();
   363	
   364	    // TODO 后期细看
   365		if (inp != NULL) {
   366			INP_LOCK_ASSERT(inp);
   367			M_SETFIB(m, inp->inp_inc.inc_fibnum);
   368			if ((flags & IP_NODEFAULTFLOWID) == 0) {
   369				m->m_pkthdr.flowid = inp->inp_flowid;
   370				M_HASHTYPE_SET(m, inp->inp_flowtype);
   371			}
   372			if ((inp->inp_flags2 & INP_2PCP_SET) != 0)
   373				vlan_pcp = (inp->inp_flags2 & INP_2PCP_MASK) >>
   374				    INP_2PCP_SHIFT;
   375	#ifdef NUMA
   376			m->m_pkthdr.numa_domain = inp->inp_numa_domain;
   377	#endif
   378		}
   379	
   380	    /* ----------------- 初始化ip首部 --------------------- */
   381	
   382	    /* IP首部的其他字段:长度、偏移、TTL、协议、TOS和目的地址,已经被传输层协议初始化了。
   383	     * 如果源地址没被设置,则在确定了到目的地的路由后再设置。
   384	     * */
   385	
   386	    // 处理ip选项字段
   387		if (opt) {
   388	        // 进入这里说明存在ip选项字段,需要将这些选项字段拼接到该包中
   389			int len = 0;
   390			m = ip_insertoptions(m, opt, &len);
   391	        // 如果len还是0,说明并没有成功将选项拼接到该包中。
   392			if (len != 0)
   393				hlen = len; /* ip->ip_hl is updated above */
   394		}
   395		ip = mtod(m, struct ip *);
   396		ip_len = ntohs(ip->ip_len);
   397		ip_off = ntohs(ip->ip_off);
   398	
   399	    // 判断能否修改ip首部
   400		if ((flags & (IP_FORWARDING|IP_RAWOUTPUT)) == 0) {
   401			ip->ip_v = IPVERSION;
   402			ip->ip_hl = hlen >> 2;
   403			
   404	        // 填充ip id
   405	        ip_fillid(ip);
   406		} else {
   407	        // 转发分组(IP_FORWARDING)和预先构造ip头的分组(IP_RAWOUTPUT),他们的ip首部不能被修改!
   408	        // 进入这里说明此时处理的是这两种分组。
   409			
   410	        /* Header already set, fetch hlen from there */
   411			hlen = ip->ip_hl << 2;
   412		}
   413	
   414	    // 如果匹配条件,说明当前报文不是需要转发的而是本机产生的。
   415		if ((flags & IP_FORWARDING) == 0)
   416			IPSTAT_INC(ips_localout);
   417	
   418	    /* ----------------------------- 确定一条到目的地的路由 ---------------------------------- */
   419	
   420		/* 
   421		 * dst/gw handling:
   422		 *
   423		 * gw is readonly but can point either to dst OR rt_gateway,
   424		 * therefore we need restore gw if we're redoing lookup.
   425		 */
   426	    // TODO 后期细看
   427		fibnum = (inp != NULL) ? inp->inp_inc.inc_fibnum : M_GETFIB(m);
   428	    
   429	    // 初始化dst和gw
   430	    if (ro != NULL)
   431			/* 获取下一跳路由的目的地址。无论下一跳路由是当前报文最终的目的主机还是路径中的一个
   432	         * 路由器,此时都先用下一跳路由的目的地址来初始化dst,即此时还分不清下一跳具体是目的
   433	         * 主机还是路由器呢。
   434	         * */
   435	        dst = (struct sockaddr_in *)&ro->ro_dst;
   436		else
   437			dst = &sin;
   438	    if (ro == NULL || ro->ro_nh == NULL) {
   439			bzero(dst, sizeof(*dst));
   440			dst->sin_family = AF_INET;
   441			dst->sin_len = sizeof(*dst);
   442	
   443	        // 没有缓存的路由,就直接用当前报文中的目的地址初始化dst。
   444			dst->sin_addr = ip->ip_dst;
   445		}
   446	
   447	    /* 用dst初始化gw,因为此时还不知道下一跳路由的目的地址是当前报文最终的
   448	     * 目的主机地址还是路径中的一个路由器的地址,所以先初始化了再说。
   449	     * gw和dst指向同一块地址。
   450	     * */ 
   451		gw = dst;
   452	
   453	again:
   454		/* TODO 后期细看
   455		 * Validate route against routing table additions;
   456		 * a better/more specific route might have been added.
   457		 */
   458		if (inp != NULL && ro != NULL && ro->ro_nh != NULL)
   459			NH_VALIDATE(ro, &inp->inp_rt_cookie, fibnum);
   460		
   461	    /* 在正式使用高速缓存中的路由ro之前,先要通过下面的判断验证一下是否可用。UDP/TCP会维护
   462	     * 一个与各套接字相关的路由缓存。如果高速缓存中的目的地(即当前路由结构体中的目的地址)
   463	     * 不是当前分组的目的地,就把该路由丢掉。新的目的地址放在dst中。注意,同时必须保证下一
   464	     * 跳路由结构体涉及的网卡设备得是up的,对应的协议族得是AF_INET。
   465		 * If there is a cached route,
   466		 * check that it is to the same destination
   467		 * and is still up.  If not, free it and try again.
   468		 * The address family should also be checked in case of sharing the
   469		 * cache with IPv6.
   470		 * Also check whether routing cache needs invalidation.
   471		 */
   472		if (ro != NULL && ro->ro_nh != NULL &&
   473		    ((!NH_IS_VALID(ro->ro_nh)) || dst->sin_family != AF_INET ||
   474		    dst->sin_addr.s_addr != ip->ip_dst.s_addr))
   475	        /* 如果dst->sin_addr.s_addr != ip->ip_dst.s_addr成立,说明下一跳不是最终的目的主机。 
   476	         * TODO 后期细看。这里貌似可以优化性能,因为ip_forward()里面本身就会查一遍路由表,
   477	         * 然后将查到的结果ro送进来,既然是转发,那么dst->sin_addr.s_addr != ip->ip_dst.s_addr
   478	         * 就很容易成立(dst是下一跳路由器/网关的地址而非目的主机的地址),这里将辛苦查出来
   479	         * 的东西“丢掉”,后面again那里的“ } else if (ro != NULL) { ”里面又会再查一遍路由表(
   480	         * 现在看,貌似查出来的结果与被“丢掉”的结果是相同的?!),这不是浪费性能么。
   481	         * */
   482			// 注意这里将缓存的路由“丢掉”的方式,不同寻常。
   483	        RO_INVALIDATE_CACHE(ro);
   484		ia = NULL;
   485		
   486	    /*
   487		 * If routing to interface only, short circuit routing lookup.
   488		 * The use of an all-ones broadcast address implies this; an
   489		 * interface is specified by the broadcast address of an interface,
   490		 * or the destination address of a ptp interface.
   491		 */
   492	    // TODO 后期细看该判断条件
   493		if (flags & IP_SENDONES) {
   494			if ((ia = ifatoia(ifa_ifwithbroadaddr(sintosa(dst),
   495							      M_GETFIB(m)))) == NULL &&
   496			    (ia = ifatoia(ifa_ifwithdstaddr(sintosa(dst),
   497							    M_GETFIB(m)))) == NULL) {
   498				IPSTAT_INC(ips_noroute);
   499				error = ENETUNREACH;
   500				goto bad;
   501			}
   502			ip->ip_dst.s_addr = INADDR_BROADCAST;
   503			dst->sin_addr = ip->ip_dst;
   504			ifp = ia->ia_ifp;
   505			mtu = ifp->if_mtu;
   506			ip->ip_ttl = 1;
   507			isbroadcast = 1;
   508			src = IA_SIN(ia)->sin_addr;
   509		} else if (flags & IP_ROUTETOIF) {
   510	        /* 旁路路由选择。
   511	         * 这个选项允许路由选择协议绕过本地路由表,并使分组通过某特定接口离开系统。
   512	         * 通过这个方法,即使本地路由表不正确,也可以与其他路由器交换路由选择信息。
   513	         * */ 
   514	
   515	        /* 上层API通过设置该标志从而禁止对分组进行路由选择。ip_output()必须找到一个
   516	         * 与分组中指定目的地网络直接相连的接口。
   517	         * ifa_ifwithdstaddr():搜索点到点接口设备;
   518	         * in_ifwithnet()     :搜索特定网络上的网卡设备。
   519	         * 如果俩函数都找不到与目的网络相连的接口,就返回 ENETUNREACH;
   520	         * 否则,ifp 指向选定的接口。
   521	         * */
   522	
   523	        // 通过dst确定当前报文应该从本机哪个网卡设备离开,绕过了路由表。
   524	        if ((ia = ifatoia(ifa_ifwithdstaddr(sintosa(dst),
   525							    M_GETFIB(m)))) == NULL &&
   526			    (ia = ifatoia(ifa_ifwithnet(sintosa(dst), 0,
   527							M_GETFIB(m)))) == NULL) {
   528				IPSTAT_INC(ips_noroute);
   529				error = ENETUNREACH;
   530				goto bad;
   531			}
   532	
   533	        // 走到这里,说明根据目的地址找到了相应的接口设备
   534	
   535	        // 指向对应的接口设备
   536			ifp = ia->ia_ifp;
   537	
   538			mtu = ifp->if_mtu;
   539	
   540	        /* 注意!!!将TTL设置为1,说明该报文最多再跳一次,即跳到对端就不能再跳了。
   541	         * 意味着下一跳必须得是当前报文的最终目的主机,否则就会被下一跳的设备丢包。
   542	         * 由此看出,IP_ROUTETOIF应该只适用于两台主机物理直连,中间不经过路由器。
   543	         * */
   544			ip->ip_ttl = 1;    
   545			isbroadcast = ifp->if_flags & IFF_BROADCAST ?
   546			    in_ifaddr_broadcast(dst->sin_addr, ia) : 0;
   547			
   548	        // 将该网口设备的ip地址设置为当前报文的源ip地址。
   549	        src = IA_SIN(ia)->sin_addr;
   550		} else if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) &&
   551		    imo != NULL && imo->imo_multicast_ifp != NULL) {
   552			/* TODO 后期细看
   553			 * Bypass the normal routing lookup for multicast
   554			 * packets if the interface is specified.
   555			 */
   556			ifp = imo->imo_multicast_ifp;
   557			mtu = ifp->if_mtu;
   558			IFP_TO_IA(ifp, ia, &in_ifa_tracker);
   559			isbroadcast = 0;	/* fool gcc */
   560			/* Interface may have no addresses. */
   561			if (ia != NULL)
   562				src = IA_SIN(ia)->sin_addr;
   563			else
   564				src.s_addr = INADDR_ANY;
   565		} else if (ro != NULL) {
   566			// 通常应该都会走到这里,因为在调用ip_output()之前已经查了一遍路由表并将ro传了进来。
   567	        if (ro->ro_nh == NULL) {
   568				/*
   569				 * We want to do any cloning requested by the link
   570				 * layer, as this is probably required in all cases
   571				 * for correct operation (as it is for ARP).
   572				 */
   573				uint32_t flowid;
   574				flowid = m->m_pkthdr.flowid;
   575	
   576	            /* 用dst中记录的目的地址来查路由表获取下一跳路由结构体。注意,此时dst不一定是
   577	             * 当前报文最终的目的主机地址,可能是下一跳路由器的地址,因为前面验证ro是否可
   578	             * 用的判断中,如果dst不是报文最终的目的主机地址,那么ro下的东西就会被“丢掉”,
   579	             * 正好符合当前流程中,ro!=NULL但是ro->ro_nh==NULL的情况。
   580	             * */ 
   581				ro->ro_nh = fib4_lookup(fibnum, dst->sin_addr, 0,
   582				    NHR_REF, flowid);
   583	
   584				if (ro->ro_nh == NULL || (!NH_IS_VALID(ro->ro_nh))) {
   585	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   586					/*
   587					 * There is no route for this packet, but it is
   588					 * possible that a matching SPD entry exists.
   589					 */
   590					no_route_but_check_spd = 1;
   591					goto sendit;
   592	#endif
   593					IPSTAT_INC(ips_noroute);
   594					error = EHOSTUNREACH;
   595					goto bad;
   596				}
   597			}
   598	
   599	        // 获取下一跳路由结构体中保存的本机网卡设备的ip地址(当前报文要从这里出去)
   600			ia = ifatoia(ro->ro_nh->nh_ifa);
   601	
   602	        // 获取下一跳路由结构体涉及的网卡设备
   603			ifp = ro->ro_nh->nh_ifp;
   604	
   605	        // counter_u64_add(c, v),Add v to c
   606			counter_u64_add(ro->ro_nh->nh_pksent, 1);
   607			
   608	        rt_update_ro_flags(ro);
   609	
   610	        // 下一跳是个路由器(网关)
   611			if (ro->ro_nh->nh_flags & NHF_GATEWAY)
   612	            // 注意,前面初始化的时候,gw和dst是指向同一块地址的!!!
   613				gw = &ro->ro_nh->gw4_sa;
   614			
   615	        // TODO 后期细看(广播相关)
   616	        if (ro->ro_nh->nh_flags & NHF_HOST)
   617				isbroadcast = (ro->ro_nh->nh_flags & NHF_BROADCAST);
   618			else if (ifp->if_flags & IFF_BROADCAST)
   619				isbroadcast = in_ifaddr_broadcast(gw->sin_addr, ia);
   620			else
   621				isbroadcast = 0;
   622			
   623	        if (ro->ro_nh->nh_flags & NHF_HOST)/* 下一跳是目的主机 */
   624				mtu = ro->ro_nh->nh_mtu;/* 下一跳主机的mtu?! */
   625			else
   626				mtu = ifp->if_mtu;/* 本机网卡(当前报文要从这里出去)的mtu?! */
   627	
   628			/* 将本机网卡设备的ip地址(当前报文从这里出去)作为当前报文的源ip */
   629	        src = IA_SIN(ia)->sin_addr;
   630		} else {
   631	        // 本地路由
   632	
   633			struct nhop_object *nh;
   634	
   635	        // 用当前报文中的目的地址来查路由表
   636			nh = fib4_lookup(M_GETFIB(m), ip->ip_dst, 0, NHR_NONE,
   637			    m->m_pkthdr.flowid);
   638			
   639	        // 说明没找到路由
   640	        if (nh == NULL) {
   641	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   642				/*
   643				 * There is no route for this packet, but it is
   644				 * possible that a matching SPD entry exists.
   645				 */
   646				no_route_but_check_spd = 1;
   647				goto sendit;
   648	#endif
   649				IPSTAT_INC(ips_noroute);
   650				
   651	            /* 1. 如果是ip_forward()调用ip_output(),就将EHOSTUNREACH转换成ICMP差错;
   652	             * 2. 如果是四层协议调用ip_output(),就将差错传回给进程。
   653	             * */
   654	            error = EHOSTUNREACH;
   655				goto bad;
   656			}
   657	
   658			ifp = nh->nh_ifp;
   659			mtu = nh->nh_mtu;
   660			
   661	        /*
   662			 * We are rewriting here dst to be gw actually, contradicting
   663			 * comment at the beginning of the function. However, in this
   664			 * case we are always dealing with on stack dst.
   665			 * In case if pfil(9) sends us back to beginning of the
   666			 * function, the dst would be rewritten by ip_output_pfil().
   667			 */
   668			MPASS(dst == &sin);
   669	
   670	        /* 如果下一跳不是分组的最终目的地,则将dst修正为下一跳路由器地址,而不再是分组
   671	         * 的最终目的地址。IP首部内的目的地址不变,但接口层必须把分组发送给dst,即下一
   672	         * 跳路由器。
   673	         * */ 
   674			if (nh->nh_flags & NHF_GATEWAY)
   675	            /* 注意,前面初始化的时候,dst和gw指向同一块内存地址,所以这里修改dst下面的
   676	             * 内容,也就是修改gw下面的内容。因为gw被const修饰,所以只能用这种影响可读
   677	             * 性的方法来修改内容。
   678	             * */ 
   679				dst->sin_addr = nh->gw4_sa.sin_addr;
   680			
   681	        // ia指向选定网卡接口的地址
   682	        ia = ifatoia(nh->nh_ifa);
   683	
   684			/* 将本机网卡设备的ip地址(当前报文从这里出去)作为当前报文的源ip */
   685			src = IA_SIN(ia)->sin_addr;
   686			
   687	        isbroadcast = (((nh->nh_flags & (NHF_HOST | NHF_BROADCAST)) ==
   688			    (NHF_HOST | NHF_BROADCAST)) ||
   689			    ((ifp->if_flags & IFF_BROADCAST) &&
   690			    in_ifaddr_broadcast(dst->sin_addr, ia)));
   691		}
   692	
   693	    /* ----------------------- 处理多播情况 ------------------------------- */
   694	
   695	    // TODO 后期细看
   696	    // 这里处理多播的流程的前提应该是将本机配置成了一个多播路由器了。
   697	
   698		/* Catch a possible divide by zero later. */
   699		KASSERT(mtu > 0, ("%s: mtu %d <= 0, ro=%p (nh_flags=0x%08x) ifp=%p",
   700		    __func__, mtu, ro,
   701		    (ro != NULL && ro->ro_nh != NULL) ? ro->ro_nh->nh_flags : 0, ifp));
   702	
   703		if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) {
   704			m->m_flags |= M_MCAST;
   705			/*
   706			 * IP destination address is multicast.  Make sure "gw"
   707			 * still points to the address in "ro".  (It may have been
   708			 * changed to point to a gateway address, above.)
   709			 */
   710			gw = dst;
   711			/*
   712			 * See if the caller provided any multicast options
   713			 */
   714			if (imo != NULL) {
   715				ip->ip_ttl = imo->imo_multicast_ttl;
   716				if (imo->imo_multicast_vif != -1)
   717					ip->ip_src.s_addr =
   718					    ip_mcast_src ?
   719					    ip_mcast_src(imo->imo_multicast_vif) :
   720					    INADDR_ANY;
   721			} else
   722				ip->ip_ttl = IP_DEFAULT_MULTICAST_TTL;
   723			/*
   724			 * Confirm that the outgoing interface supports multicast.
   725			 */
   726			if ((imo == NULL) || (imo->imo_multicast_vif == -1)) {
   727				if ((ifp->if_flags & IFF_MULTICAST) == 0) {
   728					IPSTAT_INC(ips_noroute);
   729					error = ENETUNREACH;
   730					goto bad;
   731				}
   732			}
   733			/*
   734			 * If source address not specified yet, use address
   735			 * of outgoing interface.
   736			 */
   737			if (ip->ip_src.s_addr == INADDR_ANY)
   738				ip->ip_src = src;
   739	
   740			if ((imo == NULL && in_mcast_loop) ||
   741			    (imo && imo->imo_multicast_loop)) {
   742				/*
   743				 * Loop back multicast datagram if not expressly
   744				 * forbidden to do so, even if we are not a member
   745				 * of the group; ip_input() will filter it later,
   746				 * thus deferring a hash lookup and mutex acquisition
   747				 * at the expense of a cheap copy using m_copym().
   748				 */
   749				ip_mloopback(ifp, m, hlen);
   750			} else {
   751				/*
   752				 * If we are acting as a multicast router, perform
   753				 * multicast forwarding as if the packet had just
   754				 * arrived on the interface to which we are about
   755				 * to send.  The multicast forwarding function
   756				 * recursively calls this function, using the
   757				 * IP_FORWARDING flag to prevent infinite recursion.
   758				 *
   759				 * Multicasts that are looped back by ip_mloopback(),
   760				 * above, will be forwarded by the ip_input() routine,
   761				 * if necessary.
   762				 */
   763				if (V_ip_mrouter && (flags & IP_FORWARDING) == 0) {
   764					/*
   765					 * If rsvp daemon is not running, do not
   766					 * set ip_moptions. This ensures that the packet
   767					 * is multicast and not just sent down one link
   768					 * as prescribed by rsvpd.
   769					 */
   770					if (!V_rsvp_on)
   771						imo = NULL;
   772					if (ip_mforward &&
   773					    ip_mforward(ip, ifp, m, imo) != 0) {
   774						m_freem(m);
   775						goto done;
   776					}
   777				}
   778			}
   779	
   780			/*
   781			 * Multicasts with a time-to-live of zero may be looped-
   782			 * back, above, but must not be transmitted on a network.
   783			 * Also, multicasts addressed to the loopback interface
   784			 * are not sent -- the above call to ip_mloopback() will
   785			 * loop back a copy. ip_input() will drop the copy if
   786			 * this host does not belong to the destination group on
   787			 * the loopback interface.
   788			 */
   789			if (ip->ip_ttl == 0 || ifp->if_flags & IFF_LOOPBACK) {
   790				m_freem(m);
   791				goto done;
   792			}
   793	
   794			goto sendit;
   795		}
   796	
   797	    /* ------------------------------- 源地址选择和分片 ---------------------------------- */
   798	
   799	    /* ip首部必须得有一个有效的源地址,然后把分组提交给与路由相关的网卡接口。
   800	     * 如果分组比接口的 MTU 大,就必须对分组分片,然后一片一片地发送。
   801	     * */
   802	
   803		/* 选择源地址。
   804		 * If the source address is not specified yet, use the address
   805		 * of the outgoing interface.
   806	     * 不能在之前填充其他IP首部字段时就填充好源地址,因为那时还没有选定路由。
   807	     * 需要发送的分组通常都有一个源地址,但是如果发送进程没有明确指定源地址,
   808	     * 产生于本地主机的分组就可能没有源地址。
   809		 */
   810		if (ip->ip_src.s_addr == INADDR_ANY)
   811	        /* 从上面的流程可知,通过查路由,当前报文就知道该从哪个网卡的网口离开,
   812	         * 这个src就是该网口的ip地址。
   813	         * */
   814			ip->ip_src = src;
   815	
   816		/* TODO 后期细看,这是与广播相关的流程。
   817		 * Look for broadcast address and
   818		 * verify user is allowed to send
   819		 * such a packet.
   820	     * 首先确定目的地址是一个广播地址。
   821		 */
   822		if (isbroadcast) {
   823	        // 网卡接口设备自身首先得支持广播
   824			if ((ifp->if_flags & IFF_BROADCAST) == 0) {
   825				error = EADDRNOTAVAIL;
   826				goto bad;
   827			}
   828	
   829	        // 调用方(用户态使用的API并最终在调用ip_output()时传入flags参数)必须明确使能广播
   830			if ((flags & IP_ALLOWBROADCAST) == 0) {
   831				error = EACCES;
   832				goto bad;
   833			}
   834	
   835	        /* 广播分组必须足够小,目的是不分片。如果广播分组适合网卡接口的MTU,那么就可以增加
   836	         * 广播分组被每个接口接收的机会,因为接收一个未损坏的分组的机会要远大于接收两个或多
   837	         * 个未损坏分组的机会。
   838	         * */ 
   839			/* don't allow broadcast messages to be fragmented */
   840			if (ip_len > mtu) {
   841				error = EMSGSIZE;
   842				goto bad;
   843			}
   844	
   845	        // 通过打M_BCAST标告诉接口输出函数把该分组作为链路级广播发送。
   846			m->m_flags |= M_BCAST;
   847		} else {
   848	        // 目的地址不是广播地址,则将M_BCAST标志清零。
   849			m->m_flags &= ~M_BCAST;
   850		}
   851	
   852	// 发送分组
   853	sendit:
   854	#if defined(IPSEC) || defined(IPSEC_SUPPORT)
   855		if (IPSEC_ENABLED(ipv4)) {
   856			if ((error = IPSEC_OUTPUT(ipv4, m, inp)) != 0) {
   857				if (error == EINPROGRESS)
   858					error = 0;
   859				goto done;
   860			}
   861		}
   862		/*
   863		 * Check if there was a route for this packet; return error if not.
   864		 */
   865		if (no_route_but_check_spd) {
   866			IPSTAT_INC(ips_noroute);
   867			error = EHOSTUNREACH;
   868			goto bad;
   869		}
   870		/* Update variables that are affected by ipsec4_output(). */
   871		ip = mtod(m, struct ip *);
   872		hlen = ip->ip_hl << 2;
   873	#endif /* IPSEC */
   874	
   875	    // TODO 重点!!!钩子函数,这里类似netfilter路由后的流程,post_routing点。
   876		/* Jump over all PFIL processing if hooks are not active. */
   877		if (PFIL_HOOKED_OUT(V_inet_pfil_head)) {
   878			switch (ip_output_pfil(&m, ifp, flags, inp, dst, &fibnum,
   879			    &error)) {
   880			case 1: /* Finished */
   881				goto done;
   882	
   883			case 0: /* Continue normally */
   884				ip = mtod(m, struct ip *);
   885				break;
   886	
   887			case -1: /* Need to try again */
   888				/* Reset everything for a new round */
   889				if (ro != NULL) {
   890					RO_NHFREE(ro);
   891					ro->ro_prepend = NULL;
   892				}
   893				gw = dst;
   894				ip = mtod(m, struct ip *);
   895				goto again;
   896			}
   897		}
   898	
   899	    // TODO 后期细看vlan
   900		if (vlan_pcp > -1)
   901			EVL_APPLY_PRI(m, vlan_pcp);
   902	
   903	    // TODO 后期细看
   904		/* IN_LOOPBACK must not appear on the wire - RFC1122. */
   905		if (IN_LOOPBACK(ntohl(ip->ip_dst.s_addr)) ||
   906		    IN_LOOPBACK(ntohl(ip->ip_src.s_addr))) {
   907			if ((ifp->if_flags & IFF_LOOPBACK) == 0) {
   908				IPSTAT_INC(ips_badaddr);
   909				error = EADDRNOTAVAIL;
   910				goto bad;
   911			}
   912		}
   913	
   914	    // TODO 后期细看,应该是计算校验和的流程
   915		m->m_pkthdr.csum_flags |= CSUM_IP;
   916		if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA & ~ifp->if_hwassist) {
   917			m = mb_unmapped_to_ext(m);
   918			if (m == NULL) {
   919				IPSTAT_INC(ips_odropped);
   920				error = ENOBUFS;
   921				goto bad;
   922			}
   923			in_delayed_cksum(m);
   924			m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
   925		} else if ((ifp->if_capenable & IFCAP_NOMAP) == 0) {
   926			m = mb_unmapped_to_ext(m);
   927			if (m == NULL) {
   928				IPSTAT_INC(ips_odropped);
   929				error = ENOBUFS;
   930				goto bad;
   931			}
   932		}
   933	#if defined(SCTP) || defined(SCTP_SUPPORT)
   934		if (m->m_pkthdr.csum_flags & CSUM_SCTP & ~ifp->if_hwassist) {
   935			m = mb_unmapped_to_ext(m);
   936			if (m == NULL) {
   937				IPSTAT_INC(ips_odropped);
   938				error = ENOBUFS;
   939				goto bad;
   940			}
   941			sctp_delayed_cksum(m, (uint32_t)(ip->ip_hl << 2));
   942			m->m_pkthdr.csum_flags &= ~CSUM_SCTP;
   943		}
   944	#endif
   945	
   946	    /* --------------------- 直接发送,如果有分片则交由网卡设备去处理 ------------------------ */
   947	
   948		/* TODO 后期细看TSO相关的内容
   949		 * If small enough for interface, or the interface will take
   950		 * care of the fragmentation for us, we can just send directly.
   951		 * Note that if_vxlan could have requested TSO even though the outer
   952		 * frame is UDP.  It is correct to not fragment such datagrams and
   953		 * instead just pass them on to the driver.
   954	     * 如果包小于MTU,或者网卡本身具有类似TSO这类功能(网卡可以去做分片这种事情),
   955	     * 那我们在这里就可以直接将当前分组送到下层协议去处理(下层协议会将该分组交给
   956	     * 网卡驱动,网卡自己去完成分片这类工作)。
   957		 */
   958		if (ip_len <= mtu ||
   959		    (m->m_pkthdr.csum_flags & ifp->if_hwassist &
   960		    (CSUM_TSO | CSUM_INNER_TSO)) != 0) {
   961			ip->ip_sum = 0;
   962			if (m->m_pkthdr.csum_flags & CSUM_IP & ~ifp->if_hwassist) {
   963				ip->ip_sum = in_cksum(m, hlen);
   964				m->m_pkthdr.csum_flags &= ~CSUM_IP;
   965			}
   966	
   967			/* TODO 后期细看。
   968	         * 如果这里直接将分组交给下层协议去处理,并由网卡去完成分片工作,那么这里统计
   969	         * 网卡发送包个数的数据与实际情况会有些许偏差。假设对于某个分组,我们这里会认
   970	         * 为此时只有一个分组,但是驱动那里有可能会将该分组分片,从而产生好几个分组,
   971	         * 这样的话这里统计分组的数量与实际发送分组的数量便有了些许偏差。
   972			 * Record statistics for this interface address.
   973			 * With CSUM_TSO the byte/packet count will be slightly
   974			 * incorrect because we count the IP+TCP headers only
   975			 * once instead of for every generated packet.
   976			 */
   977			if (!(flags & IP_FORWARDING) && ia) {
   978				if (m->m_pkthdr.csum_flags &
   979				    (CSUM_TSO | CSUM_INNER_TSO))
   980	                // 这里没有单纯“加1”,就是为了尽量弥补这个偏差。
   981					counter_u64_add(ia->ia_ifa.ifa_opackets,
   982					    m->m_pkthdr.len / m->m_pkthdr.tso_segsz);
   983				else
   984					counter_u64_add(ia->ia_ifa.ifa_opackets, 1);
   985	
   986				counter_u64_add(ia->ia_ifa.ifa_obytes, m->m_pkthdr.len);
   987			}
   988	#ifdef MBUF_STRESS_TEST
   989			if (mbuf_frag_size && m->m_pkthdr.len > mbuf_frag_size)
   990				m = m_fragment(m, M_NOWAIT, mbuf_frag_size);
   991	#endif
   992			/*
   993			 * Reset layer specific mbuf flags
   994			 * to avoid confusing lower layers.
   995			 */
   996			m_clrprotoflags(m);
   997			
   998	        // TODO 后期细看
   999	        IP_PROBE(send, NULL, NULL, ip, ifp, ip, NULL);
  1000			
  1001	        /* 实际发送分组,其实是将当前分组传给下一层协议去处理!
  1002	         * Tips: 根据上面不同的处理流程,此时的gw可能是当前报文最终目的主机的地址,也可能是
  1003	         * 下一跳路由器或网关的地址,就看之前的流程是怎么处理gw的了。总之,这里用gw貌似比用
  1004	         * dst要准确些。
  1005	         * */ 
  1006	        error = ip_output_send(inp, ifp, m, gw, ro,
  1007			    (flags & IP_NO_SND_TAG_RL) ? false : true);
  1008			goto done;
  1009		}
  1010	
  1011		/* Balk when DF bit is set or the interface didn't support TSO. 不允许分片或不支持TSO */
  1012		if ((ip_off & IP_DF) ||
  1013		    (m->m_pkthdr.csum_flags & (CSUM_TSO | CSUM_INNER_TSO))) {
  1014			error = EMSGSIZE;
  1015			IPSTAT_INC(ips_cantfrag);
  1016			goto bad;
  1017		}
  1018	
  1019	    /* ------------------------- 手动处理分片并发送分片链 ------------------------ */
  1020		
  1021	    /* TODO 后期细看。
  1022	     * 对于网卡设备不支持TSO以及分组太大必须得分片的情况,这里处理分片。
  1023		 * Too large for interface; fragment if possible. If successful,
  1024		 * on return, m will point to a list of packets to be sent.
  1025		 */
  1026		error = ip_fragment(ip, &m, mtu, ifp->if_hwassist);
  1027		if (error)
  1028			goto bad;
  1029		for (; m; m = m0) {
  1030	        // 虽然m是条mbuf链,但这里却要将每个mbuf摘链后再发送到下层协议去处理!!!
  1031			m0 = m->m_nextpkt;
  1032			m->m_nextpkt = 0;
  1033			
  1034	        if (error == 0) {
  1035				/* Record statistics for this interface address. */
  1036				if (ia != NULL) {
  1037					counter_u64_add(ia->ia_ifa.ifa_opackets, 1);
  1038					counter_u64_add(ia->ia_ifa.ifa_obytes,
  1039					    m->m_pkthdr.len);
  1040				}
  1041				/*
  1042				 * Reset layer specific mbuf flags
  1043				 * to avoid confusing upper layers.
  1044				 */
  1045				m_clrprotoflags(m);
  1046	
  1047				IP_PROBE(send, NULL, NULL, mtod(m, struct ip *), ifp,
  1048				    mtod(m, struct ip *), NULL);
  1049	
  1050	            /* 将该分片发送到下层协议去处理,注意该分片已经摘链。
  1051	             * Tips: 根据上面不同的处理流程,此时的gw可能是当前报文最终目的主机的地址,也可能是
  1052	             * 下一跳路由器或网关的地址,就看之前的流程是怎么处理gw的了。总之,这里用gw貌似比用
  1053	             * dst要准确些。
  1054	             * 注意,此时m->m_data指向ip头,而且报文中没有二层头相关数据。
  1055	             * */ 
  1056				error = ip_output_send(inp, ifp, m, gw, ro, true);
  1057			} else
  1058				m_freem(m);
  1059		}
  1060	
  1061		if (error == 0)
  1062			IPSTAT_INC(ips_fragmented);
  1063	
  1064	done:
  1065		return (error);
  1066	 bad:
  1067		m_freem(m);
  1068		goto done;
  1069	}
ip_output_send()
 212 static int
 213 ip_output_send(struct inpcb *inp, struct ifnet *ifp, struct mbuf *m,
 214     const struct sockaddr_in *gw, struct route *ro, bool stamp_tag)
 215 {
 216 #ifdef KERN_TLS
 217     struct ktls_session *tls = NULL;
 218 #endif
 219     struct m_snd_tag *mst;
 220     int error;
 221 
 222     MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0);
 223     mst = NULL;
 224 
 225 #ifdef KERN_TLS
 226     /* TODO 后期细看TLS相关内容。
 227      * If this is an unencrypted TLS record, save a reference to
 228      * the record.  This local reference is used to call
 229      * ktls_output_eagain after the mbuf has been freed (thus
 230      * dropping the mbuf's reference) in if_output.
 231      */
 232     if (m->m_next != NULL && mbuf_has_tls_session(m->m_next)) {
 233         tls = ktls_hold(m->m_next->m_epg_tls);
 234         mst = tls->snd_tag;
 235 
 236         /*
 237          * If a TLS session doesn't have a valid tag, it must
 238          * have had an earlier ifp mismatch, so drop this
 239          * packet.
 240          */
 241         if (mst == NULL) {
 242             error = EAGAIN;
 243             goto done;
 244         }
 245         /*
 246          * Always stamp tags that include NIC ktls.
 247          */
 248         stamp_tag = true;
 249     }                                                                                                                                                                                                          
 250 #endif
 251 #ifdef RATELIMIT
 252     // TODO 后期细看限速
 253     if (inp != NULL && mst == NULL) {
 254         if ((inp->inp_flags2 & INP_RATE_LIMIT_CHANGED) != 0 ||
 255             (inp->inp_snd_tag != NULL &&
 256             inp->inp_snd_tag->ifp != ifp))
 257             in_pcboutput_txrtlmt(inp, ifp, m);
 258 
 259         if (inp->inp_snd_tag != NULL)
 260             mst = inp->inp_snd_tag;
 261     }
 262 #endif
 263     if (stamp_tag && mst != NULL) {                                                                                                                                                                            
 264         KASSERT(m->m_pkthdr.rcvif == NULL,
 265             ("trying to add a send tag to a forwarded packet"));
 266         if (mst->ifp != ifp) {
 267             error = EAGAIN;
 268             goto done;
 269         }
 270 
 271         /* stamp send tag on mbuf */
 272         m->m_pkthdr.snd_tag = m_snd_tag_ref(mst);
 273         m->m_pkthdr.csum_flags |= CSUM_SND_TAG;
 274     }
 275 
 276     /* 真正将当前分组发送给下层协议的入口!!!
 277      * ifp->if_output() ----> ether_output(),在ether_ifattach()中注册。
 278      * */
 279     error = (*ifp->if_output)(ifp, m, (const struct sockaddr *)gw, ro);
 280 
 281 done:
 282     /* Check for route change invalidating send tags. */
 283 #ifdef KERN_TLS
 284     if (tls != NULL) {
 285         if (error == EAGAIN)
 286             error = ktls_output_eagain(inp, tls);
 287         ktls_free(tls);
 288     }
 289 #endif
 290 #ifdef RATELIMIT
 291     if (error == EAGAIN)
 292         in_pcboutput_eagain(inp);
 293 #endif
 294     return (error);
 295 }

四层协议处理流程

入口:inetsw

110 struct protosw inetsw[] = {                                                                                                                                                                                                        
111 {
112     .pr_type =      0,
113     .pr_domain =        &inetdomain,
114     .pr_protocol =      IPPROTO_IP,
        # 注意ip_init()是在这里被注册的!!!
115     .pr_init =      ip_init,
116     .pr_slowtimo =      ip_slowtimo,
117     .pr_drain =     ip_drain,
118     .pr_usrreqs =       &nousrreqs
119 },   
120 {
121     .pr_type =      SOCK_DGRAM,
122     .pr_domain =        &inetdomain,
123     .pr_protocol =      IPPROTO_UDP,
124     .pr_flags =     PR_ATOMIC|PR_ADDR,
125     .pr_input =     udp_input,
126     .pr_ctlinput =      udp_ctlinput,
127     .pr_ctloutput =     udp_ctloutput,
128     .pr_init =      udp_init,
129     .pr_usrreqs =       &udp_usrreqs
130 },
131 {
132     .pr_type =      SOCK_STREAM,
133     .pr_domain =        &inetdomain,
134     .pr_protocol =      IPPROTO_TCP,
135     .pr_flags =     PR_CONNREQUIRED|PR_IMPLOPCL|PR_WANTRCVD,
136     .pr_input =     tcp_input,
137     .pr_ctlinput =      tcp_ctlinput,
138     .pr_ctloutput =     tcp_ctloutput,
139     .pr_init =      tcp_init,
140     .pr_slowtimo =      tcp_slowtimo,
141     .pr_drain =     tcp_drain,
142     .pr_usrreqs =       &tcp_usrreqs
143 },
144 #ifdef SCTP
145 {
146     .pr_type =      SOCK_SEQPACKET,
147     .pr_domain =        &inetdomain,
148     .pr_protocol =      IPPROTO_SCTP,
149     .pr_flags =     PR_WANTRCVD|PR_LASTHDR,
150     .pr_input =     sctp_input,
151     .pr_ctlinput =      sctp_ctlinput,
152     .pr_ctloutput =     sctp_ctloutput,
153     .pr_init =      sctp_init,
154     .pr_drain =     sctp_drain,
155     .pr_usrreqs =       &sctp_usrreqs
156 },
157 {
158     .pr_type =      SOCK_STREAM,
159     .pr_domain =        &inetdomain,
160     .pr_protocol =      IPPROTO_SCTP,
161     .pr_flags =     PR_CONNREQUIRED|PR_WANTRCVD|PR_LASTHDR,
162     .pr_input =     sctp_input,
163     .pr_ctlinput =      sctp_ctlinput,
164     .pr_ctloutput =     sctp_ctloutput,
165     .pr_drain =     NULL, /* Covered by the SOCK_SEQPACKET entry. */
166     .pr_usrreqs =       &sctp_usrreqs                                                                                                                                                                                              
167 },
168 #endif /* SCTP */
169 {
170     .pr_type =      SOCK_DGRAM,
171     .pr_domain =        &inetdomain,
172     .pr_protocol =      IPPROTO_UDPLITE,
173     .pr_flags =     PR_ATOMIC|PR_ADDR,
174     .pr_input =     udp_input,
175     .pr_ctlinput =      udplite_ctlinput,
176     .pr_ctloutput =     udp_ctloutput,
177     .pr_init =      udplite_init,
178     .pr_usrreqs =       &udp_usrreqs
179 },
180 {
181     .pr_type =      SOCK_RAW,
182     .pr_domain =        &inetdomain,
183     .pr_protocol =      IPPROTO_RAW,
184     .pr_flags =     PR_ATOMIC|PR_ADDR,
185     .pr_input =     rip_input,
186     .pr_ctlinput =      rip_ctlinput,
187     .pr_ctloutput =     rip_ctloutput,
188     .pr_usrreqs =       &rip_usrreqs
189 },
190 {
191     .pr_type =      SOCK_RAW,
192     .pr_domain =        &inetdomain,
193     .pr_protocol =      IPPROTO_ICMP,
194     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
195     .pr_input =     icmp_input,
196     .pr_ctloutput =     rip_ctloutput,
197     .pr_usrreqs =       &rip_usrreqs
198 },
199 {
200     .pr_type =      SOCK_RAW,
201     .pr_domain =        &inetdomain,
202     .pr_protocol =      IPPROTO_IGMP,
203     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
204     .pr_input =     igmp_input,
205     .pr_ctloutput =     rip_ctloutput,
206     .pr_fasttimo =      igmp_fasttimo,
207     .pr_slowtimo =      igmp_slowtimo,
208     .pr_usrreqs =       &rip_usrreqs
209 },
210 {
211     .pr_type =      SOCK_RAW,
212     .pr_domain =        &inetdomain,
213     .pr_protocol =      IPPROTO_RSVP,
214     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
215     .pr_input =     rsvp_input,
216     .pr_ctloutput =     rip_ctloutput,
217     .pr_usrreqs =       &rip_usrreqs
218 },
219 {
220     .pr_type =      SOCK_RAW,
221     .pr_domain =        &inetdomain,
222     .pr_protocol =      IPPROTO_IPV4,                                                                                                                                                                                              
223     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
224     .pr_input =     encap4_input,
225     .pr_ctloutput =     rip_ctloutput,
226     .pr_usrreqs =       &rip_usrreqs
227 },
228 {
229     .pr_type =      SOCK_RAW,
230     .pr_domain =        &inetdomain,
231     .pr_protocol =      IPPROTO_MOBILE,
232     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
233     .pr_input =     encap4_input,
234     .pr_ctloutput =     rip_ctloutput,
235     .pr_usrreqs =       &rip_usrreqs
236 },
237 {
238     .pr_type =      SOCK_RAW,
239     .pr_domain =        &inetdomain,
240     .pr_protocol =      IPPROTO_ETHERIP,
241     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
242     .pr_input =     encap4_input,
243     .pr_ctloutput =     rip_ctloutput,
244     .pr_usrreqs =       &rip_usrreqs
245 },
246 {
247     .pr_type =      SOCK_RAW,
248     .pr_domain =        &inetdomain,
249     .pr_protocol =      IPPROTO_GRE,
250     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
251     .pr_input =     encap4_input,
252     .pr_ctloutput =     rip_ctloutput,
253     .pr_usrreqs =       &rip_usrreqs
254 },
255 # ifdef INET6
256 {
257     .pr_type =      SOCK_RAW,
258     .pr_domain =        &inetdomain,
259     .pr_protocol =      IPPROTO_IPV6,
260     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
261     .pr_input =     encap4_input,
262     .pr_ctloutput =     rip_ctloutput,
263     .pr_usrreqs =       &rip_usrreqs
264 },
265 #endif
266 {
267     .pr_type =      SOCK_RAW,
268     .pr_domain =        &inetdomain,
269     .pr_protocol =      IPPROTO_PIM,
270     .pr_flags =     PR_ATOMIC|PR_ADDR|PR_LASTHDR,
271     .pr_input =     encap4_input,
272     .pr_ctloutput =     rip_ctloutput,
273     .pr_usrreqs =       &rip_usrreqs
274 },
275 /* Spacer n-times for loadable protocols. */
276 IPPROTOSPACER,
277 IPPROTOSPACER,
278 IPPROTOSPACER,                                                                                                                                                                                                                     
279 IPPROTOSPACER,
280 IPPROTOSPACER,
281 IPPROTOSPACER,
282 IPPROTOSPACER,
283 IPPROTOSPACER,
284 /* raw wildcard */
285 {
286     .pr_type =      SOCK_RAW,
287     .pr_domain =        &inetdomain,
288     .pr_flags =     PR_ATOMIC|PR_ADDR,
289     .pr_input =     rip_input,
290     .pr_ctloutput =     rip_ctloutput,
291     .pr_init =      rip_init,
292     .pr_usrreqs =       &rip_usrreqs
293 },
294 };

TCP协议(后期细看)

# ip_input()中最后上送本机上层协议栈的代码
(*inetsw[ip_protox[ip->ip_p]].pr_input)(&m, &hlen, ip->ip_p);
                                                                                

struct protosw inetsw[]
# tcp 相关
110 struct protosw inetsw[] = {                                                                                                                                                                                                        
......
131 {
132     .pr_type =      SOCK_STREAM,      /* TCP提供字节流传输服务 */        
133     .pr_domain =        &inetdomain,  /* TCP属于internet协议族 */
134     .pr_protocol =      IPPROTO_TCP,  /* 6 填充ip头部的ip_p字段 */
135     .pr_flags =     PR_CONNREQUIRED|PR_IMPLOPCL|PR_WANTRCVD,/* socket层标志,貌似在协议处理中忽略 */
136     .pr_input =     tcp_input,        /* 从ip层接收消息 */
137     .pr_ctlinput =      tcp_ctlinput, /* 处理ICMP错误的控制输入函数 */
138     .pr_ctloutput =     tcp_ctloutput,/* 在进程中响应管理请求,setsockopt()会调用 */
139     .pr_init =      tcp_init,         /* 由domain_init通过SYSINIT在系统初始化时被调用 */
140     .pr_slowtimo =      tcp_slowtimo, /* 慢超时函数,每500ms(TODO 需要确定)调用一次 */
141     .pr_drain =     tcp_drain,        /* 内核mbuf耗尽时调用 */
142     .pr_usrreqs =       &tcp_usrreqs  /* 在进程中响应通信请求,bind/listen等接口在这里 */
143 },
......
294 };

struct pr_usrreqs tcp_usrreqs
/* 在进程中响应通信请求,bind/listen等接口在这里 */
// tcp_usrreqs.pru_soreceive = soreceive_stream;该注册过程在tcp_init()里面
1528 #ifdef INET
1529 struct pr_usrreqs tcp_usrreqs = {                                                                                                                                                                              
1530     .pru_abort =        tcp_usr_abort,
1531     .pru_accept =       tcp_usr_accept,
1532     .pru_attach =       tcp_usr_attach,   /* 创建socket */
1533     .pru_bind =     tcp_usr_bind,
1534     .pru_connect =      tcp_usr_connect,
1535     .pru_control =      in_control,
1536     .pru_detach =       tcp_usr_detach,
1537     .pru_disconnect =   tcp_usr_disconnect,
1538     .pru_listen =       tcp_usr_listen,
1539 #ifdef LVS_TCPOPT_TOA
1540     .pru_peeraddr =     toa_getpeeraddr,
1541 #else   
1542     .pru_peeraddr =     in_getpeeraddr,                                                              
1543 #endif
1544     .pru_rcvd =     tcp_usr_rcvd,
1545     .pru_rcvoob =       tcp_usr_rcvoob,
1546     .pru_send =     tcp_usr_send,
1547     .pru_ready =        tcp_usr_ready,                                                               
1548     .pru_shutdown =     tcp_usr_shutdown,
1549     .pru_sockaddr =     in_getsockaddr,                                                              
1550     .pru_sosetlabel =   in_pcbsosetlabel,
1551     .pru_close =        tcp_usr_close,                                                               
1552 };          
1553 #endif /* INET */   

tcp_init()(后期细看)

注册钩子函数,初始化tcp协议相关的全局变量,比如各种超时时间。该函数里面还有这句,

tcp_usrreqs.pru_soreceive = soreceive_stream;

UDP协议(后期细看)

struct protosw inetsw[]
111 struct protosw inetsw[] = {
......
121 {
122     .pr_type =      SOCK_DGRAM,
123     .pr_domain =        &inetdomain,                                                                                                                                                                            
124     .pr_protocol =      IPPROTO_UDP,
125     .pr_flags =     PR_ATOMIC|PR_ADDR,                                                                
126     .pr_input =     udp_input,                                                                        
127     .pr_ctlinput =      udp_ctlinput,                                                                 
128     .pr_ctloutput =     udp_ctloutput,                                                                
129     .pr_init =      udp_init,   /* 由domain_init通过SYSINIT在系统初始化时被调用 */                    
130     .pr_usrreqs =       &udp_usrreqs                                                                  
131 },    
......
295 };
struct pr_usrreqs udp_usrreqs
1762 struct pr_usrreqs udp_usrreqs = {                                                                                                                                                                              
1763     .pru_abort =        udp_abort,
1764     .pru_attach =       udp_attach,    
1765     .pru_bind =     udp_bind,
1766     .pru_connect =      udp_connect,
1767     .pru_control =      in_control,
1768     .pru_detach =       udp_detach,
1769     .pru_disconnect =   udp_disconnect,
1770     .pru_peeraddr =     in_getpeeraddr,
1771     .pru_send =     udp_send,
1772     .pru_soreceive =    soreceive_dgram,
1773     .pru_sosend =       sosend_dgram,
1774     .pru_shutdown =     udp_shutdown,
1775     .pru_sockaddr =     in_getsockaddr,
1776     .pru_sosetlabel =   in_pcbsosetlabel,
1777     .pru_close =        udp_close,
1778 }; 

fstack对外提供的API追踪

socket作为文件的主要操作接口struct fileops socketops

freebsd/kern/sys_socket.c

105 struct fileops  socketops = {                                                                                                                                                                                   
106     .fo_read = soo_read,
107     .fo_write = soo_write,
108     .fo_truncate = invfo_truncate,                                                                    
109     .fo_ioctl = soo_ioctl,                                                                            
110     .fo_poll = soo_poll,
111     .fo_kqfilter = soo_kqfilter,                                                                      
112     .fo_stat = soo_stat,
113     .fo_close = soo_close,                                                                            
114     .fo_chmod = invfo_chmod,
115     .fo_chown = invfo_chown,                                                                          
116     .fo_sendfile = invfo_sendfile,
117 #ifndef FSTACK
118     .fo_fill_kinfo = soo_fill_kinfo,
119     .fo_aio_queue = soo_aio_queue,
120 #endif       
121     .fo_flags = DFLAG_PASSABLE
122 };   

socket上挂的so_proto字段对应接口

就是inetsw中的tcp/udp/...!!!

ff_socket()

 529 int
 530 ff_socket(int domain, int type, int protocol)
 531 {   
 532     int rc;
 533     struct socket_args sa;
 534     sa.domain = domain == LINUX_AF_INET6 ? AF_INET6 : domain;
 535     sa.type = type;
 536     sa.protocol = protocol;
 537     if ((rc = sys_socket(curthread, &sa)))                                                                                                                                                                     
 538         goto kern_fail;
 539 
 540     return curthread->td_retval[0];
 541 kern_fail:
 542     ff_os_errno(rc);
 543     return (-1); 
 544 }       

sys_socket()

 125 int
 126 sys_socket(struct thread *td, struct socket_args *uap)                                                                                                                                                         
 127 {   
 128     
 129     return (kern_socket(td, uap->domain, uap->type, uap->protocol));
 130 }   

kern_socket()

 132 int
 133 kern_socket(struct thread *td, int domain, int type, int protocol)
 134 {
 135     struct socket *so;
 136     struct file *fp;
 137     int fd, error, oflag, fflag;
 138 
 139     AUDIT_ARG_SOCKET(domain, type, protocol);
 140 
 141     oflag = 0;
 142     fflag = 0;
 143     if ((type & SOCK_CLOEXEC) != 0) {
 144         type &= ~SOCK_CLOEXEC;
 145         oflag |= O_CLOEXEC;
 146     }
 147     if ((type & SOCK_NONBLOCK) != 0) {
 148         type &= ~SOCK_NONBLOCK;
 149         fflag |= FNONBLOCK;
 150     }
 151 
 152 #ifdef MAC
 153     error = mac_socket_check_create(td->td_ucred, domain, type, protocol);
 154     if (error != 0)
 155         return (error);
 156 #endif
 157 
 158     // 分配一块struct file内存作为fp结构体,并且给该fp分配一个文件描述符,这里面并没有初始化fp
 159     error = falloc(td, &fp, &fd, oflag);
 160     if (error != 0)
 161         return (error);
 162     /* An extra reference on `fp' has been held for us by falloc(). */
 163     // 创建struct socket结构体,里面会根据type/protocol从inetdomain中找到inetsw,并据此找到          
 164     // tcp/udp/...要使用的struct protosw结构体(里面包含了大量tcp/udp/...相关的处理函数),
 165     // 最后将该结构体挂到struct socket的so_proto字段上。                                                         
 164     error = socreate(domain, &so, type, protocol, td->td_ucred, td);
 165     if (error != 0) {
 166         fdclose(td, fp, fd);
 167     } else {
 168         // 将FREAD | FWRITE | fflag, DTYPE_SOCKET, so, &socketops全部挂到fp上,这里尤其要注意
 169         // socketops,里面是操作socket的各种实际函数,后面调用read/write/recv/send的时候要用到。
 170         // 可以说,这里才是真正初始化fp的地方。                                                                                                                                                                
 171         finit(fp, FREAD | FWRITE | fflag, DTYPE_SOCKET, so, &socketops);
 172         if ((fflag & FNONBLOCK) != 0)
 173             (void) fo_ioctl(fp, FIONBIO, &fflag, td->td_ucred, td);
 174         td->td_retval[0] = fd;
 175     }
 176     fdrop(fp, td);
 177     return (error);
 178 }

socreate()

311 struct protosw *
312 pffindproto(int family, int protocol, int type)                                                                                                                                                                 
313 {    
314     struct domain *dp;
315     struct protosw *pr;
316     struct protosw *maybe;                                                                            
317 
318     maybe = NULL;
319     if (family == 0)
320         return (NULL);
321 
322     // 如果是tcp/udp这类,那么这里就是根据family找到inetdomain(回顾“注册与初始化流程”)                                                
323     dp = pffinddomain(family);
324     if (dp == NULL)
325         return (NULL);
326 
327     // 如果是tcp,那么这里就是根据在inetdomain里面注册的inetsw找到tcp对应的那个struct protosw结构体
      	// 如果是udp,那么这里就是根据在inetdomain里面注册的inetsw找到udp对应的那个struct protosw结构体
328     for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) {
329         if ((pr->pr_protocol == protocol) && (pr->pr_type == type))                                   
330             return (pr);
331 
332         if (type == SOCK_RAW && pr->pr_type == SOCK_RAW &&
333             pr->pr_protocol == 0 && maybe == NULL)
334             maybe = pr;
335     }
336     return (maybe);
337 }    
 505 int
 506 socreate(int dom, struct socket **aso, int type, int proto,
 507     struct ucred *cred, struct thread *td)
 508 {
 509     struct protosw *prp;
 510     struct socket *so;
 511     int error;
 512 
 513     if (proto)
 514         // 找协议族
						 // 如果是tcp套接字,那么prp就是inetsw中的tcp那个struct protosw结构体
          	 // 如果是udp套接字,那么prp就是inetsw中的udp那个struct protosw结构体
 515         prp = pffindproto(dom, proto, type);
 516     else
 517         prp = pffindtype(dom, type);
 518 
......
 550     // 将找到的inetsw上的tcp/udp/...的那个struct protosw结构体挂上来。
 551     so->so_proto = prp;
......
 571     soref(so);
 572     *aso = so;
 573     return (0);
 574 }

ff_read()

kern_readv()

 281 int 
 282 kern_readv(struct thread *td, int fd, struct uio *auio)
 283 {
 284     struct file *fp;
 285     int error;
 286 
 287     // 通过fd获取fp
 288     error = fget_read(td, fd, &cap_read_rights, &fp);
 289     if (error)
 290         return (error);
 291     error = dofileread(td, fd, fp, auio, (off_t)-1, 0);                                                                                                                                                        
 292     fdrop(fp, td);
 293     return (error);
 294 }

dofileread()

 345 static int
 346 dofileread(struct thread *td, int fd, struct file *fp, struct uio *auio,
 347     off_t offset, int flags)
 348 {
 ......
 369     cnt = auio->uio_resid;
 370 
 371     // 内联函数,((*fp->f_ops->fo_read)(fp, uio, active_cred, flags, td))
 372     // 如果fp是个socket,那么fo_read()的本体是soo_read(),由struct fileops  socketops知                                                                                                                                                     
 373     if ((error = fo_read(fp, auio, td->td_ucred, flags, td))) {
 374         if (auio->uio_resid != cnt && (error == ERESTART ||
 375             error == EINTR || error == EWOULDBLOCK))
 376             error = 0;
 377     }
 378     cnt -= auio->uio_resid;
 ......
 385     td->td_retval[0] = cnt;
 386     return (error);
 387 }

soo_read()---->soreceive()

124 static int                                                                                            
125 soo_read(struct file *fp, struct uio *uio, struct ucred *active_cred,                                                                                                                                           
126     int flags, struct thread *td)                                                                     
127 {        
128     struct socket *so = fp->f_data;
129     int error;
130  
131 #ifdef MAC
132     error = mac_socket_check_receive(active_cred, so);
133     if (error)
134         return (error);                                                                               
135 #endif   
136     error = soreceive(so, 0, uio, 0, 0, 0);                                                           
137     return (error);
138 }

ff_recv()/ff_recvfrom()/ff_recvmsg()

 872 ssize_t
 873 ff_recvfrom(int s, void *buf, size_t len, int flags,
 874     struct linux_sockaddr *from, socklen_t *fromlen)
 875 {
 876     struct msghdr msg;
 877     struct iovec aiov;
 878     int rc;
 879     struct sockaddr_storage bsdaddr;
 ......
 886     msg.msg_name = &bsdaddr;
 887     msg.msg_iov = &aiov;
 888     msg.msg_iovlen = 1;
 889     aiov.iov_base = buf;
 890     aiov.iov_len = len;     // 调用者能接收多少数据                                                                                                                                                            
 891     msg.msg_control = 0;
 892     msg.msg_flags = flags;
 893     if ((rc = kern_recvit(curthread, s, &msg, UIO_SYSSPACE, NULL)))
 894         goto kern_fail;
 895     rc = curthread->td_retval[0];
 896     if (fromlen != NULL)
 897         *fromlen = msg.msg_namelen;
 898     
 899     if (from && msg.msg_namelen != 0)
 900         freebsd2linux_sockaddr(from, (struct sockaddr *)&bsdaddr);
 901     
 902     return (rc);
 903 kern_fail:
 904     ff_os_errno(rc);
 905     return (-1);
 906 }

kern_recvit()---->soreceive()

 923 int
 924 kern_recvit(struct thread *td, int s, struct msghdr *mp, enum uio_seg fromseg,
 925     struct mbuf **controlp)
 926 {
 927     struct uio auio;
 928     struct iovec *iov;
 929     struct mbuf *control, *m;
 930     caddr_t ctlbuf;
 931     struct file *fp;
 932     struct socket *so;
 933     struct sockaddr *fromsa = NULL;
 934 #ifdef KTRACE
 935     struct uio *ktruio = NULL;
 936 #endif
 937     ssize_t len;
 938     int error, i;
 ......
 945     // 根据文件描述符s获取fp
 946     error = getsock_cap(td, s, &cap_recv_rights,
 947         &fp, NULL, NULL);                                                                                                                                                                                      
 948     if (error != 0)
 949         return (error);
 950     // 获取struct socket结构体,在创建socket的时候被挂到了fp上,
 951     // 而struct socket结构体的so_proto字段挂着inetsw上的协议处理结构体。
 952     so = fp->f_data;
 ......
 962     // mp->msg_iov->iov_base 就是用户态传进来的要拷贝数据的内存,由调用者开辟,
 963     // 所以auio.uio_iov->iov_base和iov现在也都是这个了。
 964     auio.uio_iov = mp->msg_iov;
 965     auio.uio_iovcnt = mp->msg_iovlen;   // 1
 966     auio.uio_segflg = UIO_USERSPACE;
 967     auio.uio_rw = UIO_READ;
 968     auio.uio_td = td;
 969     auio.uio_offset = 0;            /* XXX */
 970     auio.uio_resid = 0;
 971     iov = mp->msg_iov;
 972     for (i = 0; i < mp->msg_iovlen; i++, iov++) {
 973         // auio.uio_resid 其实就是调用者能够接收多少数据
 974         if ((auio.uio_resid += iov->iov_len) < 0) {
 975             fdrop(fp, td);
 976             return (EINVAL);
 977         }
 978     }
 ......
 983     control = NULL;
 984     len = auio.uio_resid;
 985     error = soreceive(so, &fromsa, &auio, NULL,
 986         (mp->msg_control || controlp) ? &control : NULL,
 987         &mp->msg_flags);
 ......
1083 }

soreceive(),TCP/UDP用户态接口收包最终都走这里

2808 int                                                                                                 
2809 soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio,                                
2810     struct mbuf **mp0, struct mbuf **controlp, int *flagsp)                                         
2811 {                                                                                                   
2812     int error;                                                                                      
2813                                                                                                     
2814     CURVNET_SET(so->so_vnet);                                                                       
2815     if (!SOLISTENING(so))                                                                           
2816         /* 对于tcp协议,在创建套接字的时候就将inetsw上tcp对应的结构体挂                             
2817          * 到了so->so_proto字段,所以此时pru_soreceive就是默认函数,因为tcp_usrreqs并没有           
2818          * 将专门的函数注册给pru_soreceive,所以只能用默认的,即soreceive_stream(),                
2819          * 将soreceive_stream()注册给pru_soreceive的过程在tcp_init()中。                                                                                                                                       
2820          * */        
             /* 对于udp协议,在struct pr_usrreqs udp_usrreqs中显式注册了pru_soreceive,
              * 即“.pru_soreceive =    soreceive_dgram,”,所以此时的pru_soreceive就是soreceive_dgram()
          	  * */ 
2821         error = (so->so_proto->pr_usrreqs->pru_soreceive(so, psa, uio,                              
2822             mp0, controlp, flagsp));                                                                
2823     else                                                                                            
2824         error = ENOTCONN;                                                                           
2825     CURVNET_RESTORE();                                                                              
2826     return (error);                                                                                 
2827 }                                                                                                   

TCP:soreceive_stream()

2403 int                                                                                                 
2404 soreceive_stream(struct socket *so, struct sockaddr **psa, struct uio *uio,
2405     struct mbuf **mp0, struct mbuf **controlp, int *flagsp)                                         
2406 {
......
2416     // flagsp是从用户态调用接口传进来的,由调用接口的人决定具体是啥                                 
2417     if (flagsp != NULL)
2418         flags = *flagsp &~ MSG_EOR;
2419     else                                                                                            
2420         flags = 0;
2421     if (controlp != NULL)
2422         *controlp = NULL;   
2423     if (flags & MSG_OOB)
2424         return (soreceive_rcvoob(so, uio, flags));                                                  
2425     // mp0是NULL
2426     if (mp0 != NULL)
2427         *mp0 = NULL;
2428              
2429     // 接收缓存
2430     sb = &so->so_rcv;
......
2460     // uio->uio_resid: 调用者能够接收多少数据
2461     /* Easy one, no space to copyout anything. */
2462     if (uio->uio_resid == 0) {
2463         error = EINVAL;
2464         goto out;
2465     }
2466     oresid = uio->uio_resid;
2467 
2468     /* We will never ever get anything unless we are or were connected. */
2469     if (!(so->so_state & (SS_ISCONNECTED|SS_ISDISCONNECTED))) {
2470         error = ENOTCONN;
2471         goto out;
2472     }
......
2497     // SS_NBIO 非阻塞标志!                                                                                                                                                                                    
2498     /* Socket buffer is empty and we shall not block. */
2499     if (sbavail(sb) == 0 &&
2500         ((so->so_state & SS_NBIO) || (flags & (MSG_DONTWAIT|MSG_NBIO)))) {
2501         // 从返回值可以看出,这里就和用户态收包接口的说明一致了,
2502         // 即非阻塞套接字在没有数据可接收的情况下返回EAGAIN。
2503         error = EAGAIN;
2504         goto out;
2505     }
......
2531 // 正式将数据上送给用户态调用者
2532 deliver:
2533     SOCKBUF_LOCK_ASSERT(&so->so_rcv);
2534     KASSERT(sbavail(sb) > 0, ("%s: sockbuf empty", __func__));
2535     KASSERT(sb->sb_mb != NULL, ("%s: sb_mb == NULL", __func__));
2536 
2537     /* Statistics. */
2538     if (uio->uio_td)
2539         uio->uio_td->td_ru.ru_msgrcv++;
2540 
2541     // uio->uio_iov->iov_base 是用户态开辟的内存,用于拷贝数据。
2542     /* Fill uio until full or current end of socket buffer is reached. */
2543     len = min(uio->uio_resid, sbavail(sb));
2544     // mp0是NULL
2545     if (mp0 != NULL) {
......
2587     } else {
2588         /* NB: Must unlock socket buffer as uiomove may sleep. */
2589         SOCKBUF_UNLOCK(sb);
2590         // 将收到的数据拷贝到用户态调用者开辟的内存
2591         error = m_mbuftouio(uio, sb->sb_mb, len);
2592         SOCKBUF_LOCK(sb);
2593         if (error)
2594             goto out;
2595     }
......
2631 }

m_mbuftouio()报文数据拷贝

1878 /*
1879  * Copy an mbuf chain into a uio limited by len if set.
1880  */
1881 int
1882 m_mbuftouio(struct uio *uio, const struct mbuf *m, int len)
1883 {
1884     int error, length, total;
1885     int progress = 0;
1886 
1887     if (len > 0)
1888         total = min(uio->uio_resid, len);
1889     else
1890         total = uio->uio_resid;
1891 
1892     /* Fill the uio with data from the mbufs. */
1893     for (; m != NULL; m = m->m_next) {
1894         length = min(m->m_len, total - progress);
1895 
1896         if ((m->m_flags & M_EXTPG) != 0)
1897             error = m_unmappedtouio(m, 0, uio, length);                                                                                                                                                        
1898         else
1899             // 实际的拷贝动作,将mbuf上的数据拷贝到uio->uio_iov->iov_base
1900             error = uiomove(mtod(m, void *), length, uio);
1901         if (error)
1902             return (error);
1903 
1904         progress += length;
1905     }
1906 
1907     return (0);
1908 }   
162 int
163 uiomove(void *cp, int n, struct uio *uio)
164 {
165     struct thread *td = curthread;
166     struct iovec *iov;
167     u_int cnt;
168     int error = 0;
169     int save = 0;
170 
......
178     save = td->td_pflags & TDP_DEADLKTREAT;                                                                                                                                                                     
179     td->td_pflags |= TDP_DEADLKTREAT;
180 
181     while (n > 0 && uio->uio_resid) {
182         iov = uio->uio_iov;
183         cnt = iov->iov_len; 
184         if (cnt == 0) {
185             uio->uio_iov++;
186             uio->uio_iovcnt--;
187             continue;
188         }
189         if (cnt > n)
190             cnt = n;
191 
192         switch (uio->uio_segflg) {
193 
194         case UIO_USERSPACE:
195             if (ticks - PCPU_GET(switchticks) >= hogticks)
196                 uio_yield();
197             if (uio->uio_rw == UIO_READ)
198                 error = copyout(cp, iov->iov_base, cnt);
199             else
200                 error = copyin(iov->iov_base, cp, cnt);
201             if (error)
202                 goto out;
203             break;
204 
205         case UIO_SYSSPACE:
206             if (uio->uio_rw == UIO_READ)
207                 bcopy(cp, iov->iov_base, cnt);
208             else
209                 bcopy(iov->iov_base, cp, cnt);
210             break;
211         case UIO_NOCOPY:
212             break;
213         }                                                                                                                                                                                                       
214         iov->iov_base = (char *)iov->iov_base + cnt;
215         iov->iov_len -= cnt;
216         uio->uio_resid -= cnt;
217         uio->uio_offset += cnt;
218         cp = (char *)cp + cnt;
219         n -= cnt;
220     }
221 out:
222     if (save == 0)
223         td->td_pflags &= ~TDP_DEADLKTREAT;
224     return (error);
225 }
 

ff_read()与ff_recv()/ff_recvfrom()/ff_recvmsg()的区别

一样的,没有本质区别。soo_read()里面最终调用的就是soreceive_stream()

ff_write()

kern_writev()

 487 int
 488 kern_writev(struct thread *td, int fd, struct uio *auio)                                                                                                                                                       
 489 {
 490     struct file *fp;
 491     int error;
 492 
 493     // 通过fd获取fp
 494     error = fget_write(td, fd, &cap_write_rights, &fp);
 495     if (error)
 496         return (error);
 497     error = dofilewrite(td, fd, fp, auio, (off_t)-1, 0);
 498     fdrop(fp, td);
 499     return (error);
 500 }   

dofilewrite()

 551 static int
 552 dofilewrite(struct thread *td, int fd, struct file *fp, struct uio *auio,
 553     off_t offset, int flags)
 554 {  
 ......
 569     cnt = auio->uio_resid;
 570 
 571     // 内联函数((*fp->f_ops->fo_write)(fp, uio, active_cred, flags, td))
 572     // 对应的实体为soo_write(),由struct fileops  socketops知。                                                                                                                                                
 573     if ((error = fo_write(fp, auio, td->td_ucred, flags, td))) {
 574         if (auio->uio_resid != cnt && (error == ERESTART ||
 575             error == EINTR || error == EWOULDBLOCK))
 576             error = 0;
 577         /* Socket layer is responsible for issuing SIGPIPE. */
 578         if (fp->f_type != DTYPE_SOCKET && error == EPIPE) {
 579             PROC_LOCK(td->td_proc);
 580             tdsignal(td, SIGPIPE);
 581             PROC_UNLOCK(td->td_proc);
 582         }
 583     }
 584     cnt -= auio->uio_resid;
 ......
 591     td->td_retval[0] = cnt;
 592     return (error);
 593 }

soo_write()---->sosend()

140 static int
141 soo_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
142     int flags, struct thread *td)
143 {
144     struct socket *so = fp->f_data;
145     int error;
146 
147 #ifdef MAC
148     error = mac_socket_check_send(active_cred, so);
149     if (error)
150         return (error);
151 #endif
152     error = sosend(so, 0, uio, 0, 0, 0, uio->uio_td);                                                                                                                                                           
153     if (error == EPIPE && (so->so_options & SO_NOSIGPIPE) == 0) {
154         PROC_LOCK(uio->uio_td->td_proc);
155         tdsignal(uio->uio_td, SIGPIPE);
156         PROC_UNLOCK(uio->uio_td->td_proc);
157     }
158     return (error);
159 }

sosend(),TCP/UDP用户态接口发包最终都走这里

1815 int
1816 sosend(struct socket *so, struct sockaddr *addr, struct uio *uio,
1817     struct mbuf *top, struct mbuf *control, int flags, struct thread *td)
1818 {
1819     int error;
1820 
1821     CURVNET_SET(so->so_vnet);
1822     if (!SOLISTENING(so))
1823         /* TCP: tcp_usrreqs中并没有显式地注册pru_sosend,而是在domain_init()里面调用的
1824          * protosw_init()里通过一堆临时“宏”来注册一系列默认函数,比如就在这个过程里将
1825          * pru_sosend注册为sosend_generic(),这是个通用的发包函数。                                  
1826          * UDP: udp_usrreqs中已经显式注册了pru_sosend,“.pru_sosend = sosend_dgram”,                
1827          * 对应函数原型就是sosend_dgram()。                                                                                                                                                                    
1828          * */                                                      
1829         error = so->so_proto->pr_usrreqs->pru_sosend(so, addr, uio,
1830             top, control, flags, td);
1831     else {               
1832         m_freem(top);    
1833         m_freem(control);
1834         error = ENOTCONN;
1835     }                                                                     
1836     CURVNET_RESTORE();                                                    
1837     return (error);                                                       
1838 }                                                                         

TCP:sosend_generic()简述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值