




已阅读5页,还剩20页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
循序渐进学WinPcap去年开始学习winpcap,当时翻译了一点,现在打算把这个工作完成了。我的水平比较差,翻译的很不到位,不过对于初次接触winpcap的人应该还是有点帮助吧。不过不知道我这样来翻译是不是侵犯了人家的版权?如果有这个嫌疑,请大家告诉我,我对这方面的法律不是很了解。建议对这方面有兴趣的人还是去 下载文档和资料看。下面开始吧:WinPcap tutorial: a step by step guide to using WinPcap详细说明这部分展示了怎样使用WinPcap API。这个教程通过一系列的课程,从基本的函数(取得网卡列表,开始抓包,等等)到最高级的应用(处理数据包包发送队列和统计网络流量),一步一步地教会读者如何用WinPcap来编程。这里提供了几个虽然简单但却完整的程序段作为参考:所有的源代码都有其余部分的链接,只需要点击一下函数和数据结构就可以跳转到相应的文档。这些例子都是使用c语言写的,所以在读本教程前要了解一些基本的c语言的知识。而且,这是一个关于处理原始网络包的库的教程,所以假定读者具有良好的网络和网络协议方面的知识。 WinPcap tutorial: a step by step guide to using WinPcap(1)获取网络设备列表基本上所有基于Winpcap的应用程序所做的第一件事情都是获取一个已经绑定的网卡列表。为此,libcap和winpcap都提供了pcap_findalldevs_ex()函数:这个函数返回一个指向pcap_if结构的链表,其中的每一项都包含了一个已经绑定的适配器的全部信息。其中name和description这两项分别包含了相应设备的名称和描述。下面的代码取得适配器列表并在屏幕上显示出来,如果适配器没有被发现就把显示错误。i nclude pcap.hmain() pcap_if_t *alldevs; pcap_if_t *d; int i=0; char errbufPCAP_ERRBUF_SIZE; /* 取得本机的网络设备列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* 这个参数在这里不需要 */, &alldevs, errbuf) = -1) fprintf(stderr,Error in pcap_findalldevs_ex: %sn, errbuf); exit(1); /* 显示列表 */ for(d= alldevs; d != NULL; d= d-next) printf(%d. %s, +i, d-name); if (d-description) printf( (%s)n, d-description); else printf( (No description available)n); if (i = 0) printf(nNo interfaces found! Make sure WinPcap is installed.n); return; /* We dont need any more the device list. Free it */ pcap_freealldevs(alldevs);关于这段代码的说明。首先,就象其他的libpcap函数,pcap_findalldevs_ex()有一个errbuf参数。如果发生错误,libcap就把一个错误说明放到这个参数指向的字符串中。其次,并不是所有的操作系统都支持libpcap提供的网络接口描述,因此如果我们想写一个可移植的程序,我们必须考虑description为null的情况:这个时候我们就输出字符串No description available 。最后要提醒一下:一旦我们完成了这些动作,就应该释放用pcap_freealldevs()列表。让我们编译并运行这段简单的代码。在unix或者cygwin中编译的话,打入下面的命令:gcc -o testaprog testprog.c -lpcap在windows中,你需要创建一个project,照着手册中的Using WinPcap in your programs 那一章做就可以了。但是,我们建议你使用the WinPcap developers pack(可以在 下载),因为这个开发包提供了许多教程中使用的代码示例,这些示例都已经配置好了,其中包含了编译执行例子所需要的include文件和lib文件。编译好了程序后,在我的winxp工作站上运行的结果:1. 4E273621-5161-46C8-895A-48D0E52A0B83 (Realtek RTL8029(AS) Ethernet Adapter)2. 5D24AE04-C486-4A96-83FB-8B5EC6C7F430 (3Com EtherLink PCI)就象你看到的一样,在windows系统中网络适配器的名称(在打开网络适配器时将被传递给libcap)根本没有办法理解,所以附加说明可能是非常有帮助的。WinPcap tutorial: a step by step guide to using WinPcap(2)Obtaining advanced information about installed devices课程1(Obtaining the device list)说明了怎样得到可用适配器的基本信息(比如设备名和说明)。实际上,winpcap也提供其他的高级信息。每个由pcap_findalldevs_ex()返回的pcap_if 结构体都包含了一个pcap_addr结构列表,里面包含的内容有:一个接口的地址列表。一个子网掩码列表(每一个都与地址列表中的条目一一对应)。一个广播地址列表(每一个都与地址列表中的条目一一对应)。一个目的地址列表(每一个都与地址列表中的条目一一对应)。除此之外,pcap_findalldevs_ex() 也能返回远程的适配器和任给的本地文件夹的pcap文件列表。下面的例子提供了一个ifprint() 函数来打印出一个pcap_if 结构中的所有内容。程序对每一个pcap_findalldevs_ex()返回的条目都调用一次这个函数。/* Copyright (c) 1999 - 2003* NetGroup, Politecnico di Torino (Italy)* All rights reserved.* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met:* * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer.* 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Politecnico di Torino nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.* */i nclude pcap.h#ifndef WIN32 i nclude i nclude #else i nclude #endif/ Function prototypesvoid ifprint(pcap_if_t *d);char *iptos(u_long in);char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen);int main() pcap_if_t *alldevs; pcap_if_t *d; char errbufPCAP_ERRBUF_SIZE+1; char sourcePCAP_ERRBUF_SIZE+1; printf(Enter the device you want to list:n rpcap:/ = lists interfaces in the local machinen rpcap:/hostname:port = lists interfaces in a remote machinen (rpcapd daemon must be up and runningn and it must accept null authentication)n file:/foldername = lists all pcap files in the give foldernn Enter your choice: ); fgets(source, PCAP_ERRBUF_SIZE, stdin); sourcePCAP_ERRBUF_SIZE = 0; /* Retrieve the interfaces list */ if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) = -1) fprintf(stderr,Error in pcap_findalldevs: %sn,errbuf); exit(1); /* Scan the list printing every entry */ for(d=alldevs;d;d=d-next) ifprint(d); pcap_freealldevs(alldevs); return 1;/* Print all the available information on the given interface */void ifprint(pcap_if_t *d) pcap_addr_t *a; char ip6str128; /* Name */ printf(%sn,d-name); /* Description */ if (d-description) printf(tDescription: %sn,d-description); /* Loopback Address*/ printf(tLoopback: %sn,(d-flags & PCAP_IF_LOOPBACK)?yes:no); /* IP addresses */ for(a=d-addresses;a;a=a-next) printf(tAddress Family: #%dn,a-addr-sa_family); switch(a-addr-sa_family) case AF_INET: printf(tAddress Family Name: AF_INETn); if (a-addr) printf(tAddress: %sn,iptos(struct sockaddr_in *)a-addr)-sin_addr.s_addr); if (a-netmask) printf(tNetmask: %sn,iptos(struct sockaddr_in *)a-netmask)-sin_addr.s_addr); if (a-broadaddr) printf(tBroadcast Address: %sn,iptos(struct sockaddr_in *)a-broadaddr)-sin_addr.s_addr); if (a-dstaddr) printf(tDestination Address: %sn,iptos(struct sockaddr_in *)a-dstaddr)-sin_addr.s_addr); break; case AF_INET6: printf(tAddress Family Name: AF_INET6n); if (a-addr) printf(tAddress: %sn, ip6tos(a-addr, ip6str, sizeof(ip6str); break; default: printf(tAddress Family Name: Unknownn); break; printf(n);/* From tcptraceroute, convert a numeric IP address to a string */#define IPTOSBUFFERS 12char *iptos(u_long in) static char outputIPTOSBUFFERS3*4+3+1; static short which; u_char *p; p = (u_char *)∈ which = (which + 1 = IPTOSBUFFERS ? 0 : which + 1); sprintf(outputwhich, %d.%d.%d.%d, p0, p1, p2, p3); return outputwhich;char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen) socklen_t sockaddrlen; #ifdef WIN32 sockaddrlen = sizeof(struct sockaddr_in6); #else sockaddrlen = sizeof(struct sockaddr_storage); #endif if(getnameinfo(sockaddr, sockaddrlen, address, addrlen, NULL, 0, NI_NUMERICHOST) != 0) address = NULL; return address;WinPcap tutorial: a step by step guide to using WinPcap(3)Opening an adapter and capturing the packets打开一个适配器开始抓取现在我们已经知道了怎样去获取一个适配器并使用它,让我们开始真正的工作-开始抓取网络数据包吧。在这一课中我们将写一个程序,这个程序将在我们选择的适配器上监听,并抓取通过这个适配器上的每一个数据包,打印其中的一些信息。我们主要使用的函数是pcap_open(),这个函数的功能是打开一个抓取设备。在这里有必要对其中的几个参数snaplen, flags and to_ms作一下说明。snaplen指定了我们所要抓取的包的长度(译者:也就是我们想抓多长就设置多长)。在一些操作系统(如xBSD和Win32)中,底层驱动可以通过配置只抓取数据包的开始部分:这样就减少了拷贝给应用程序的数据量,因此可以提高抓取效率。在这个例子里我们使用65536这个比我们所能遇到的最大的MTU还大的数字。这样我们就能确保我们的程序可以抓到整个数据包。flags:最重要的标志是一个指示适配器是否工作在混杂模式下的。在正常状况下,一个适配器仅仅抓取网络中目的地是它自己的数据包;因此其他主机交换的数据包都被忽略。相反,当适配器处在混杂模式下的时候它就会抓取所有的数据包而不管是不是发给它的。这就意味着在共享媒体(如非交换的以太网)上,WinPcap将能够抓取其他主机的数据包。混杂模式是大部分抓取程序的默认模式,所以在下面的例子中我们就开启它。to_ms以豪秒为单位指定了读取操作的超时界限。在适配器上一个读取操作(比如,pcap_dispatch() 或者 pcap_next_ex()将总是在to_ms豪秒后返回,即使网络中没有数据包可供抓取。如果适配器工作在统计模式(如果对此不了解,请看课程9),to_ms还定义了统计报告之间的间隔。把tm_ms设置为0意味着没有时间限制,如果没有数据包到达适配器,读取操作将永远不会返回。反过来,把tm_ms设置为-1将使读取操作总是立即返回。 #include pcap.h/* 数据包处理程序,回调函数 */void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);main()pcap_if_t *alldevs;pcap_if_t *d;int inum;int i=0;pcap_t *adhandle;char errbufPCAP_ERRBUF_SIZE; /* Retrieve the device list on the local machine */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) = -1) fprintf(stderr,Error in pcap_findalldevs: %sn, errbuf); exit(1); /* Print the list */ for(d=alldevs; d; d=d-next) printf(%d. %s, +i, d-name); if (d-description) printf( (%s)n, d-description); else printf( (No description available)n); if(i=0) printf(nNo interfaces found! Make sure WinPcap is installed.n); return -1; printf(Enter the interface number (1-%d):,i); scanf(%d, &inum); if(inum i) printf(nInterface number out of range.n); /* Free the device list */ pcap_freealldevs(alldevs); return -1; /* Jump to the selected adapter */ for(d=alldevs, i=0; inext, i+); /* Open the device */ if ( (adhandle= pcap_open(d-name, / name of the device 65536, / portion of the packet to capture / 65536 guarantees that the whole packet will be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, / promiscuous mode 1000, / read timeout NULL, / authentication on the remote machine errbuf / error buffer ) ) = NULL) fprintf(stderr,nUnable to open the adapter. %s is not supported by WinPcapn, d-name); /* Free the device list */ pcap_freealldevs(alldevs); return -1; printf(nlistening on %s.n, d-description); /* At this point, we dont need any more the device list. Free it */ pcap_freealldevs(alldevs); /* start the capture */ pcap_loop(adhandle, 0, packet_handler, NULL); return 0;/* Callback function invoked by libpcap for every incoming packet */void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) struct tm *ltime; char timestr16; /* convert the timestamp to readable format */ ltime=localtime(&header-ts.tv_sec); strftime( timestr, sizeof timestr, %H:%M:%S, ltime); printf(%s,%.6d len:%dn, timestr, header-ts.tv_usec, header-len); 一旦打开了适配器,就由pcap_dispatch() 或者pcap_loop()开始抓取。这两个函数都非常慢,所不同的是pcap_ dispatch()一旦超时就可以返回(尽管不能保证)而pcap_loop() 会一直等到cnt包被抓到才会返回,所以这个函数在没有数据包的网络中会阻塞任意的时间。在这个例子中pcap_loop()就足够了,而pcap_dispatch() 则可以在一个更复杂的程序中使用。这两个函数都有一个回调函数作为参数,packet_handler,这个参数指定的函数将收到数据包。这个函数在每一个新的数据包从网络中到达时都会被libpcap调用,并且会收到一个反映pcap_loop() 和 pcap_dispatch()函数的生成状态,和一个结构体header。这个header中带有一些数据包中的信息,比如时间戳和长度、包括所有协议头的实际数据包。注意结构体中是没有CRC的,因为在数据确认后已经被适配器去掉了。也要注意大部分适配器丢弃了CRC错误的数据包,因此Winpcap不能抓取这些包。上面的例子从pcap_pkthdr中提取每个数据包的时间戳和长度并显示它们。请注意使用pcap_loop()可能有一个缺点,就是使用这个函数时包处理函数要被包抓取驱动程序来调用;因此应用程序不能直接控制它。另一种方法(并且更容易理解)是使用函数pcap_next_ex(),这个函数我们将在下一个例子中使用。4.Capturing the packets without the callback这节课程中的例子程序完成的功能和上节课的一样,但是使用的是pcap_next_ex()而不是pcap_loop().基于回调捕获机制的 pcap_loop()是非常优雅的,在很多情况下都是一个不错的选择。不过,有时候处理一个回调函数显得不太现实 - 通常这会使程序更加复杂,在使用多线程或者c+类的时候尤其如此。在这种情况下,可以直接调用 pcap_next_ex() 来返回一个数据包 - 这样程序员可以在仅仅想使用它们的时候再处理 pcap_next_ex() 返回的数据包。这个函数的参数和回调函数 pcap_loop() 的一样 - 由一个网络适配器描述符作为入口参数和两个指针作为出口参数,这两个指针将在函数中被初始化,然后再返回给用户(一个指向pcap_pkthdr 结构,另一个指向一个用作数据缓冲区的内存区域)。在下面的程序中,我们继续使用上一节课中的例子的数据处理部分的代码,把这些代码拷贝到main()函数中pcap_next_ex()的后面。#include pcap.hmain()pcap_if_t *alldevs;pcap_if_t *d;int inum;int i=0;pcap_t *adhandle;int res;char errbufPCAP_ERRBUF_SIZE;struct tm *ltime;char timestr16;struct pcap_pkthdr *header;u_char *pkt_data; /* Retrieve the device list on the local machine */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) = -1) fprintf(stderr,Error in pcap_findalldevs: %sn, errbuf); exit(1); /* Print the list */ for(d=alldevs; d; d=d-next) printf(%d. %s, +i, d-name); if (d-description) printf( (%s)n, d-description); else printf( (No description available)n); if(i=0) printf(nNo interfaces found! Make sure WinPcap is installed.n); return -1; printf(Enter the interface number (1-%d):,i); scanf(%d, &inum); if(inum i) printf(nInterface number out of range.n); /* Free the device list */ pcap_freealldevs(alldevs); return -1; /* Jump to the selected adapter */ for(d=alldevs, i=0; inext, i+); /* Open the device */ if ( (adhandle= pcap_open(d-name, / name of the device 65536, / portion of the packet to capture. / 65536 guarantees that the whole packet will be captured on all the link layers PCAP_OPENFLAG_PROMISCUOUS, / promiscuous mode 1000, / read timeout NULL, / authentication on the remote machine errbuf / error buffer ) ) = NULL) fprintf(stderr,nUnable to open the adapter. %s is not supported by WinPcapn, d-name); /* Free the device list */ pcap_freealldevs(alldevs); return -1; printf(nlistening on %s.n, d-description); /* At this point, we dont need any more the device list. Free it */ pcap_freealldevs(alldevs); /* Retrieve the packets */ while(res = pcap_next_ex( adhandle, &header, &pkt_data) = 0) if(res = 0) /* Timeout elapsed */ continue; /* convert the timestamp to readable format */ ltime=localtime(&header-ts.tv_sec); strftime( timestr, sizeof timestr, %H:%M:%S, ltime); printf(%s,%.6d len:%dn, timestr, header-ts.tv_usec, header-len); if(res = -1) printf(Error reading the packets: %sn, pcap_geterr(adhandle); return -1; return 0;为什么我们使用 pcap_next_ex() 而不是 pcap_next()?因为 pcap_next() 有一些缺陷。首先,它的效率不高,因为它虽然隐藏了回调模式但是仍然依赖于 pcap_dispatch()。其次,它不能检测EOF(译者注:end of file,意思是文件结束标志),所以当我们从一个文件中收集包的时候不是很有用(译者注:winpcap可以把捕获的数据包以很高的效率存在文件中,留待以后分析,这一点以后的课程中也会讲到)。也要注意 pcap_next_ex() 对于成功调用、超时、错误和EOF状态会返回不同的值。5.Filtering the trafficWinPcap提供的最强大的特性之一就是过滤引擎。它是被集成到了winpcap的捕获机制中的,提供了一种非常高效的方法来获取部分网络数据。被用来过滤数据包的函数是 pcap_compile() 和 pcap_setfilter()。pcap_compile() 接受一个包含布尔表达式的字符串,生成可以被捕获包驱动中的过滤引擎解释的代码。布尔表达式的语法在这个文档的Filtering expression syntax 那一节(译者注:其实和tcpdump的一样,如果了解tcpdump,可以直接按照tcpdump的语法来写)。pcap_setfilter() 绑定一个过滤器到一个在核心驱动中的捕获进程中。一旦 pcap_setfilter() 被调用,这个过滤器就会对网络来的所有数据包进行过滤,所有符合条件的数据包(按照布尔表达式来计算出结果是真的数据包)都会被拷贝给进行捕获的应用程序。下面的代码说明了怎样编译和设置一个过滤器。注意我们必须得到说明适配器的 pcap_if 结构中的子网掩码,因为一些被 pcap_compile() 生成的过滤器需要它。这个过滤器中传递给 pcap_compile() 的字符串是 ip and tcp,意思是“仅仅把IPv4 and TCP 数据包保存下来并交付给应用程序”。if (d-addresses != NULL) /* Retrieve the mask of the first address of the interface */ netmask=(struct sockaddr_in *)(d-addresses-netmask)-sin_addr.S_un.S_addr; else /* If the interface is without an address we suppose to be in a C class network */ netmask=0xffffff; /compile the filter if (pcap_compile(adhandle, &fcode, ip and tcp, 1, netmask) 0) fprintf(stderr,nUnable to compile the packet filter. Check the syntax.n); /* Free the device list */ pcap_freealldevs(alldevs); return -1; /set the filter if (pcap_setfilter(adhandle, &fcode) 0) fprintf(stderr,nError setting the filter.n); /* Free the device list */ pcap_freealldevs(alldevs); return -1; 如果你想看一些在这节课中讲述的使用过滤功能的代码,请看下节课中的例子,Interpreting the packets.6.Interpreting the packets.现在我们已经能够捕获并且过滤网络数据包,下面我们就把我们的知识运用到一个简单的“真实的”应用程序中去。在这节课中我们将从前面的课程中拷贝代码并用它们来构造出一个更有用途的程序。这个程序主要的目的就是说明怎样分析和解释我们已经捕获的数据包的协议结构。最终的应用程序,叫做UDPdump,会打印出一个在我们的网络中的UDP数据包的概要。在开始阶段我们选择分析并显示UDP协议,因为UDP协议比其他的协议比如TCP协议更容易理解,从而非常适合作为初始阶段的例子。还是让我们开始看代码吧:/* Copyright (c) 1999 - 2003* NetGroup, Polit
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025至2030中国天丝坯布行业发展分析及发展前景与投资报告
- 2025至2030中国男鞋行业发展趋势分析与未来投资战略咨询研究报告
- 2025至2030中国触控一体机行业市场发展前景及发展战略与投资报告
- 2025至2030中国咖啡桌行业发展趋势分析与未来投资战略咨询研究报告
- 2025至2030中国原发性进行性多发性硬化症治疗行业产业运行态势及投资规划深度研究报告
- 2025至2030长锥输尿管扩张器行业产业运行态势及投资规划深度研究报告
- 2025至2030中国全地面起重机行业发展趋势分析与未来投资战略咨询研究报告
- 2025至2030中国电动晾衣机行业产业运行态势及投资规划深度研究报告
- 2025至2030中国钢铁物流行业市场发展分析及前景趋势及有效策略与实施路径评估报告
- 2025至2030中国膝关节置换术行业发展研究与产业战略规划分析评估报告
- 儿童口腔健康宣教课件
- 工程造价咨询手册模板
- 设备维护服务方案(2篇)
- 医院检验科实验室生物安全程序文件SOP
- 监所防疫知识培训
- DL∕T 781-2021电力用高频开关整流模块-PDF解密
- T∕CACM 024-2017 中医临床实践指南 穴位埋线减肥
- 【ZYJ7型电液转辙机道岔工作原理与故障维修11000字(论文)】
- 学生心理健康一人一档、一人一案表
- 毕业设计(论文)-水果自动分拣机设计
- 食品科技的未来2024年的食品创新与食品安全
评论
0/150
提交评论