深度剖析WinPcap之(八)——打开与关闭适配器(3)

合集下载

使用winpcap的主要流程

使用winpcap的主要流程

使用winpcap的主要流程1. 安装winpcap
•下载winpcap安装包
•运行安装程序
•按照提示完成安装过程
2. 创建WinPcap的应用程序
•引用WinPcap的头文件
•在应用程序中初始化WinPcap
•打开网络适配器
•设置过滤器条件
3. 捕获网络数据包
•创建一个数据包捕获句柄
•设置过滤器条件(可选)
•进入捕获循环
–清空数据包缓冲区
–捕获一个数据包
–处理捕获到的数据包
4. 处理捕获到的数据包
•解析数据包的头部信息
•进行数据包的分析和处理
•可选地将数据包写入文件或发送数据包5. 关闭数据包捕获
•关闭数据包捕获句柄
•释放资源
•结束应用程序
6. WinPcap的高级用法
•配置超时和非阻塞模式
•对多个适配器进行操作
•同时捕获多个数据流
•进行软件过滤器和内核过滤器
•设置数据包缓冲区大小
•错误处理和异常情况处理
7. 示例代码
下面是一个使用WinPcap进行数据包捕获的示例代码:
```c #include <stdio.h> #include <pcap.h>
int main() { char errbuf[PCAP_ERRBUF_SIZE]; pcap_t handle; const u_char packet; struct pcap_pkthdr header;
// 打开网络适配器
handle = pcap_open_live(\。

WinPcap所涉及的Windows驱动基础知识(三)

WinPcap所涉及的Windows驱动基础知识(三)

深度剖析WinPcap之(三)——所涉及的Windows驱动基础知识1.1 Windows驱动的基础知识本节主要描述在WinPcap的NPF中经常使用一些编写Windows驱动程序所需掌握的部分基础知识,以便于后面的理解。

1.1.1 驱动对象(DRIVER_OBJECT)每个驱动程序都有唯一的驱动对象与之对应,该驱动对象在驱动程序被加载时由内核的对象管理程序所创建。

驱动对象用DRIVER_OBJECT数据结构表示,它作为驱动程序的一个实例被内核加载,对一个驱动程序内核I/O管理器只加载一个实例。

驱动对象数据结构在wdm.h文件中的定义如下。

typedef struct _DRIVER_OBJECT {CSHORT Type;CSHORT Size;/**DeviceObject为每个驱动程序所创建的一个或多个设备对象链表,*Flags提供一个扩展的标识定位驱动对象*/PDEVICE_OBJECT DeviceObject;ULONG Flags;/*下列各成员字段描述驱动程序从哪儿被加载*/PVOID DriverStart;ULONG DriverSize;PVOID DriverSection;PDRIVER_EXTENSION DriverExtension;/**DriverName成员被错误日志线程用来*确定一个I/O请求越界的驱动名称*/UNICODE_STRING DriverName;/*指向注册表中硬件信息的路径*/PUNICODE_STRING HardwareDatabase;/**如果驱动支持“fast I/O”,*就指向一个“fast I/O”的派遣函数数组*/PFAST_IO_DISPATCH FastIoDispatch;/**描述该特定驱动的入口点。

*主函数(major function)派遣函数表必须是对象最后的成员,*因此它仍然是可扩展的*/PDRIVER_INITIALIZE DriverInit;PDRIVER_STARTIO DriverStartIo;PDRIVER_UNLOAD DriverUnload;PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];} DRIVER_OBJECT;typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;下面分别描述驱动对象中驱动程序可访问的成员。

WinPcap编程

WinPcap编程

Packet.dll应用步骤
2) 打开指定的网卡 lpAdapter = PacketOpenAdapter(AdapterList [0 ]) ; if ( ! lpAdapter | | (lpAdapter - > hFile = = INVALID_HANDLE_VALUE) ) { dwErrorCode = GetLastError() ; sprintf ( szErr ,″Unable to open the adapter ,error code : %lx″, dwErrorCode) ; AfxMessageBox(szErr) ; return FALSE; }
1> LPPACKET PacketAllocatePacket(void) 如果运行成功,返回一个_PACKET结构的指针, 否则返回NULL。成功返回的结果将会传送到 PacketReceivePacket()函数,接收来自驱动的网络 数据报。 2> VOID PacketFreePacket(LPPACKET lpPacket) 释放参数提供的_PACKET结构。 3> VOID PacketCloseAdapter(LPADAPTER lpAdapter) 关闭参数中提供的网络适配器,释放相关的 ADAPTER结构。
WinPcap

WinPcap包括三个部分 第一个模块:内核级的包过滤驱动程序
NPF(Netgroup Packet Filter),是一个虚拟设备驱动程序文件, 是架构的核心(在Win95/98 中是一个VXD文件,在NT/2000 中是 一个SYS 文件) ,它的主要功能是过滤数据包,在包上附加时间戳、 数据包长度等信息。


第二个模块:低级动态链接库packet.dll,在Win32 平台 上提供了与NPF 的一个通用接口。 packet.dll数据包驱 动程序库是与libpcap 相兼容的一组用户级的函数库。 第三个模块:用户级的Wpcap.dll。通过调用packet.dll 提供的函数生成,它包括了过滤器生成等一系列可以被 用户级调用的高级函数,另外还有诸如数据包统计及发 送功能。

深度剖析WinPcap之(序言)

深度剖析WinPcap之(序言)

深度剖析WinPcap之(序言)——分析WinPcap源代码的缘由过去我一直在开发软件,包括Windows操作系统的应用软件,Linux操作系统的应用软件与驱动程序,也开发过一些嵌入式软件,并在后来的工作中逐渐专注于对软件的测试工作,主要从事软件测试技术与测试方法、软件工程的研究。

在此过程中与其他开发人员、测试人员一同工作,帮助他们构建达到工业级标准的软件,或者指导他们提高软件开发或测试的技术水平。

随着不断的遇见问题、解决问题,我也在思考一个问题:那就是软件开发人员与软件测试人员除了对应用程序需要清晰的理解之外,是否还需要对支撑应用软件运行的操作系统,共享库等有深入的了解?下面通过我亲身经历的两个案例来考虑该问题:案例1:嗅探软件掉包问题某自行开发的网络数据包嗅探软件,在对被测试设备进行数据包分析时,发现有掉包现象,但待测设备运行正常。

经过仔细分析,发现该嗅探软件设计有如下问题:针对高速网络流量,从软件架构上就没考虑到如何防止掉包的问题。

软件开发人员对该嗅探软件进行各种优化,比如增加缓冲区的大小,降低显示的复杂度、分析代码性能瓶颈,但都改善不大。

通过对所调用WinPcap库的仔细分析,获得下列解决方案:让WinPcap的内核驱动程序NPF实现协议过滤,而不是通过嗅探软件进行过滤,同时就在内核实现数据包的存储,而不导入应用层后进行存储。

该案例提醒我们,在测试人员开发测试工具时,需要对所用的支撑软件有深入的了解,才能更好的解决所遇到的实际问题。

内核层的过滤与转储功能是WinPcap的NPF驱动程序提供的优化功能,就是解决高速网络流量掉包问题的。

案例2:AD数据采集系统掉点问题某AD数据采集系统,基于嵌入式Linux系统实现,对所采集的数据进行分析,出现掉点现象。

经过仔细调试发现采样率还不到所要求一半时就开始出现掉点现象,采样率越高掉点现象越严重。

项目组相关人员对Linux驱动程序的机制了解不够深入,应用层软件开发人员对软件进行各种调优,收效甚微,曾一度怀疑是文件系统存储速度慢或硬件问题导致,项目陷入困境。

深度剖析WinPcap之

深度剖析WinPcap之

深度剖析WinPcap之WinPcap简介WinPcap是Windows平台下访问网络数据链路层的开源库,该库已达到工业标准的应用要求。

WinPcap允许应用程序绕开网络协议栈来捕获与传递网络数据包,并具有额外的有用特性,包括内核层的数据包过滤、一个网络统计引擎与支持远程数据包捕获。

1.1. 什么是WinPcap大多数网络应用程序通过被广泛使用的操作系统原语来访问网络,诸如sockets。

操作系统已经处理了底层的细节问题(如协议处理,数据包的封装等),并提供与读写文件类似的、熟悉的接口,这使得用该方法可以很容易的访问网络中的数据。

然而有些时候,这种“简单的方式”并不能满足任务要求,因为有些应用程序需要直接访问网络中的数据包。

也就是说,应用程序需要访问网络中的“原始”数据包,即没有被操作系统使用网络协议进行处理过的数据包。

WinPcap的目的就是为Win32应用程序提供这种访问方式。

WinPcap提供下列功能:•捕获原始数据包,无论是发送到运行WinPcap机器上的数据包,还是在其它主机(共享介质)上进行交换的数据包•在数据包分派给应用程序前,根据用户指定的规则过滤数据包•将原始数据包发送到网络•收集网络流量的统计信息1.2. WinPcap的优点自由 WinPcap遵循BSD open source licence发布。

这意味着你的应用程序拥有全部自由来修改与使用它,即使应用程序是商业化的。

高性能WinPcap实现了有关数据包捕获文献中描述的所有典型的优化方法(比如内核级的过滤与缓冲,减少上下文交换,数据包部分内容复制),加上一些原创的优化方法,如JIT过滤器编辑(JIT filter compilation)与内核级的统计过程。

因为这些原因,WinPcap胜过其它可比的方法。

普及WinPcap被许多工具作为网络接口使用——无论是自由软件还是商业软件。

包括网络协议分析器、网络监视器、网络入侵检测系统、网络嗅探器、网络流量生成器、网络测试器等等。

深度剖析WinPcap之(八)——打开与关闭适配器(8)

深度剖析WinPcap之(八)——打开与关闭适配器(8)

深度剖析(8)版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。

否则将追究法律责任。

http://es lxf.b lo g.51cto.co m/918801/2064201.5.2.1.1PacketOpenAdapterNPF函数函数PacketOpenAdapterNPF()的作用是打开一个使用NPF设备驱动的适配器。

该函数被PacketOpenAdapter()与AddAdapter()作为内部函数调用。

函数原型如下:LPADAPTER PacketOpenAdapterNPF(PCHAR AdapterNameA);参数AdapterNameA字符串包含待打开设备的名称。

函数如果成功,返回一个已经正确初始化的ADAPTER对象的指针。

否则返回NULL。

函数的主要代码如下:LPADAPTER PacketOpenAdapterNPF(PCHAR AdapterNameA){LPADAPTER lpAdapter;…CHAR SymbolicLinkA[MAX_PATH];//NPF_DRIVER_NAME定义为"NPF"CHAR NpfDriverName[MAX_WINPCAP_KEY_CHARS] = NPF_DRIVER_NAM E;CHAR NpfServiceLocation[MAX_WINPCAP_KEY_CHARS];/*连接到服务控制管理器*/scmHandle = OpenSCManager(NULL, NULL, GENERIC_READ);if(scmHandle == NULL){//连接到服务控制管理器失败error = GetLastError();}else{/**检查NPF服务是否已经存在,*如果存在则接下来可以分配并初始化ADAPTER对象*///设置NPF服务的注册表位置StringCchPrintfA(NpfServiceLocation, sizeof(NpfServiceLoc ation),"SYSTEM\\CurrentControlSet\\Services\\%s", NpfDriv erName);//检查NPF注册表的键值是已否存在,如果已经存在,//这意味着驱动已经安装,//我们不再需要调用PacketInstallDriver()函数安装驱动程序KeyRes=RegOpenKeyExA(HKEY_LOCAL_MACHINE,NpfServiceLocation,0,KEY_READ,&PathKey);if(KeyRes != ERROR_SUCCESS){//NPF注册表的键值不存在,调用PacketInstallDriver()函数Result = PacketInstallDriver();}else{//NPF注册表的键值已经存在,驱动已经安装Result = TRUE;RegCloseKey(PathKey);}if (Result){ //驱动已经存在,检查NPF服务是否正在运行//打开NPF服务svcHandle = OpenServiceA(scmHandle, NpfDriverName,SERVICE_START | SERVICE_QUERY_STATUS );if (svcHandle != NULL){ //获得服务状态QuerySStat = QueryServiceStatus(svcHandle, &SSta t);if(!QuerySStat || SStat.dwCurrentState != SERVICE_ RUNNING){//获得服务状态失败或驱动NPF没有运行,启动NPF服务if (StartService(svcHandle, 0, NULL)==0){ //如果不是服务正在运行或服务已经存在的状态,//就处理错误,函数返回NULLerror = GetLastError();if(error!=ERROR_SERVICE_ALREADY_RUNNING &&error!=ERROR_ALREADY_EXISTS){//处理错误,函数返回NULL…return NULL;}}}//驱动NPF已正常运行,关闭服务控制管理器的句柄CloseServiceHandle( svcHandle );svcHandle = NULL;}else{//打开NPF服务失败error = GetLastError();SetLastError(error);}}else{if(KeyRes != ERROR_SUCCESS){ //第一次安装驱动程序失败,并且NPF注册表的键值不存在//再次安装驱动Result = PacketInstallDriver();}elseResult = TRUE;if (Result) {//NPF的驱动程序已存在//打开NPF服务svcHandle = OpenServiceA(scmHandle,NpfDriverName,SERVICE_START);if (svcHandle != NULL){//打开NPF服务成功,获取NPF服务的状态QuerySStat = QueryServiceStatus(svcHandle, &SSt at);if(!QuerySStat ||SStat.dwCurrentState != SERVICE_RUNNING){//获得服务状态失败或驱动NPF没有运行,启动N PF服务if (StartService(svcHandle, 0, NULL)==0){//如果不是服务正在运行或服务已经存在的状态,//就处理错误,函数返回NULLif(error!=ERROR_SERVICE_ALREADY_RUNNING &&error!=ERROR_ALREADY_EXISTS){…return NULL;}}}//驱动NPF已正常运行,关闭服务控制管理器的句柄CloseServiceHandle( svcHandle );svcHandle = NULL;}else{//打开NPF服务失败,设置错误状态error = GetLastError();SetLastError(error);}}}}if (scmHandle != NULL) //关闭服务控制管理器的句柄CloseServiceHandle(scmHandle);/*分配ADAPTER结构体的的内存空间 */lpAdapter=(LPADAPTER)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZERO INIT,sizeof(ADAPTER));if (lpAdapter==NULL){//分配失败,函数返回NULL;…return NULL;}//设置单个数据包发送的次数为一次lpAdapter->NumWrites=1;//从原始的设备名创建NPF设备的名称#define DEVICE_PREFIX "\\Device\\"if (LOWORD(GetVersion()) == 4){//操作系统为Windows NT 4.0、Windows 95、Windows 98、或 Wi ndows Me的处理…}else{if (strlen(AdapterNameA) > strlen(DEVICE_PREFIX)){StringCchPrintfA(SymbolicLinkA, MAX_PATH,"\\\\.\\Global\\%s", AdapterNameA + strlen(DEVICE_ PREFIX));}else{ZeroMemory(SymbolicLinkA, sizeof(SymbolicLinkA));}}---------------待续---------------------------------本文出自“千江月” 博客,请务必保留此出处/918801/ 206420。

实验3:WinPcap技术的使用

实验3:WinPcap技术的使用

实验3:WinPcap技术的使用1实验目的和要求学习使用WinPcap开发包实现网络数据包的捕获、过滤和分析的功能,具体要求如下:1)WinPcap开发包的下载和安装;2)使用WinPcap获取与网络适配器绑定的设备列表;3)使用WinPcap获取网络适配器的高级属性信息;4)使用WinPcap打开网络适配器并实现抓包功能5)使用WinPcap过滤数据包、分析数据包。

2实验设备及材料1)Windows主机2)Visual Studio 2005或Visual Studio 20083实验内容本实验学习WinPcap开发包的使用,利用WinPcap实现网络数据包捕获、过滤和分析的功能,实验内容如下。

3.1 WinPcap开发包的下载和安装下载并安装WinPcap开发包,下载地址:/archive/。

1)4.1.1-WinPcap.exe的安装;2)4.1.1-WpdPack.zip的下载和使用。

3.2获取与网络适配器绑定的设备列表信息pcap_findalldevs_ex()函数的使用。

调用pcap_findalldevs_ex()函数,获取的网络设备信息将存储在结构体pcap_if_t中,然后打印网卡设备列表信息,包括网络适配器名称和描述。

3.3获取网络适配器的高级属性信息在3.2的基础上,除打印本地主机所有网络适配器的名称、描述外,还打印是否回环地址、协议簇类型、协议簇名称、IP地址、子网掩码、广播地址和目标地址等信息。

3.4打开网络适配器并通过事件处理器来捕获数据包pcap_open()函数和pcap_loop()函数的使用。

程序的运行过程如下:1)调用pcap_findalldevs_ex()函数获取并打印本机的网络设备列表。

2)要求用户选择用于捕获数据包的网络设备。

3)使用for语句跳转到选中的网络设备,以便在后面的程序中打开该设备,并在该设备上捕获数据。

4)调用pcap_open()函数打开选择的网络设备。

winpcap分析

winpcap分析
3
4.3.2 4.3.3 4.4 4.4.1 4.4.2
结构图............................................................................................................... 25 源码分析............................................................................................................29 系统特色............................................................................................................46 分布式网络监听 .................................................................................................46 简单网络管理.................................................................................................... 46
第 5 章 系统测试与使用说明书 ......................................................................................47 5.1 5.1.1 5.1.2 5.1.2 5.1.3 5.2 5.3 结论 致谢: 系统测试............................................................................................................47 监听功能............................................................................................................47 显示功能............................................................................................................48 管理功能............................................................................................................51 连接功能............................................................................................................52 工具软件的配置 .................................................................................................54 监听工具的使用 .................................................................................................55 .....................................................................................................................56 .....................................................................................................................61
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

深度剖析(3)
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。

否则将追究法律责任。

http://es lxf.b lo g.51cto.co m/918801/204274
1.4wpcap.dll中相应函数接口的实现
下面对各函数在wpcap.dll中的实现进行详细分析。

1.4.1pcap_open函数
函数首先确认参数source的长度不超过PCAP_BUF_SIZE,然后调用pc ap_parsesrcstr()函数分析源的种类,是文件,本地主机接口还是远程主机接口,并依据不同的源类型作不同的处理。

如果是文件类型,直接调用pcap_open_offline函数处理。

如果是远程主机接口类型,调用pcap_create()创建一个pcap_t结构体,然后调用函数pcap_opensource_remote()通过打开一个远程主机的适配器,最后给pcap_t结构体几个重要成员设置合适的值,包括捕获数据包长度、读取超时,与pcap_startcapture()需要使用的标识。

如果是本地主机接口类型,直接调用pcap_open_live()函数,然后给N PF驱动设置几个标识。

根据需要,可设置禁止回环数据包捕获,还可设置一次读操作所需获得的最小数据长度(字节为单位)。

最后函数返回一个pcap_t结构体指针,供后面的调用使用。

函数主要代码如下:
pcap_t *pcap_open(const char *source,
int snaplen, int flags, int read_timeout,
struct pcap_rmtauth *auth, char *errbuf)
{
char host[PCAP_BUF_SIZE], port[PCAP_BUF_SIZE], name[PCAP_BUF_SIZ E];
int type;
pcap_t *fp;
int result;
/*确认source的长度不超过PCAP_BUF_SIZE*/
if (strlen(source) > PCAP_BUF_SIZE)
{//错误,函数返回

}
/*分析源的种类,文件,本地主机接口还是远程主机接口)*/
if (pcap_parsesrcstr(source, &type, host, port, name, errbuf) == -1)
return NULL;
/*依据不同的源类型作不同的处理*/
switch (type)
{
//文件类型,直接调用pcap_open_offline函数处理
case PCAP_SRC_FILE:
fp = pcap_open_offline(name, errbuf);
break;
//远程主机接口类型,打开远程主机的适配器,设置几个重要标识的值。

case PCAP_SRC_IFREMOTE:
//创建pcap_t结构体
fp= pcap_create(source, errbuf);
if (fp == NULL)
{
return NULL;
}
//打开一个远程主机的适配器
result= pcap_opensource_remote(fp, auth);
if (result != 0)
{
pcap_close(fp);
return NULL;
}
//设置捕获数据包长度、读取超时,
//与pcap_startcapture()需要使用的标识。

fp->snapshot= snaplen;
fp->md.timeout= read_timeout;
fp->rmt_flags= flags;
break;
//本地主机接口类型,直接调用pcap_open_live()函数,
//然后给NPF驱动设置几个标识。

case PCAP_SRC_IFLOCAL:
fp = pcap_open_live(name, snaplen, (flags &
PCAP_OPENFLAG_PROMISCUOUS), read_timeout, errbu f);
#ifdef WIN32
//这些标识仅被Windows支持
if (fp != NULL && fp->adapter != NULL)
{
/* 如果需要,禁止回环数据包捕获*/
if(flags & PCAP_OPENFLAG_NOCAPTURE_LOCAL)
{
if(!PacketSetLoopbackBehavior(fp->adapter,
NPF_DISABLE_LOOPBACK))
{ //设置失败,函数返回

}
}
/*如果需要,设置一次读操作所需获得的最小数据长度(字节为单位)*/
if(flags & PCAP_OPENFLAG_MAX_RESPONSIVENESS)
{
if(!PacketSetMinToCopy(fp->adapter, 0))
{//设置失败,函数返回

}
}
}
#endif//WIN32
break;
default:
//不支持的源类型
strcpy(errbuf, "Source type not supported");
return NULL;
}
return fp;
}
其中函数pcap_parsesrcstr()通过解析一个源字符串,来分析源的种类,是文件,本地主机接口还是远程主机接口)并返回分离出来的内容(ty pe,host,port,name)。

函数原型如下:
int pcap_parsesrcstr(const char*source, int *type,
char *host, char*port, char*nam e, char*errbuf) 参数source为要分析的源名称。

参数host、port与name分别返回源的主机、端口与名称,针对不同的源类型,该三个参数可能为空值。

参数errbuf保存函数的错误信息。

其中参数type返回源的类型,WinPcap内部用来表示源的类型的各标识如下:
PCAP_SRC_FILE指明为一个文件,用户希望从一个本地文件获得数据包。

PCAP_SRC_IFLOCAL指明为一个本机接口,比如用户希望从一个本地主机接口获得数据包。

不采用RPCAP协议。

PCAP_SRC_IFREMOTE指明为一个远程接口,比如用户希望从一个远程主机接口获得数据包。

需要采用RPCAP协议。

其中函数pcap_opensource_remote()通过打开一个RPCAP连接等方式,打开一个远程主机的适配器。

函数原型如下:
int pcap_opensource_remote(pcap_t *fp, struct pcap_rmtauth *aut h)
参数fp是一个指向pcap_t结构体的指针,该结构体在前面由pcap_cr eate()函数所创建。

参数auth是认证权限。

如果函数成功,返回0值,fp指针能够被用来作为后续调用的参数(如pcap_compile()等等)。

如果函数失败,返回-1值,fp->errbuf中存储错误信息。

该函数为一个远程接口完成与pcap_open_live()基本类似的功能。

不同的是,此处,在调用pcap_startcapture_remote()之前,捕获线程并不启动。

因为,在远程捕获时,我们不能在“open adapter”命令一发送出去就开始捕获数据。

考虑远程适配器已经负载很重的时候,如果我们开始一个捕获(其默认情况下,具有一个NULL过滤器),新的网络流量将使网
络饱和。

作为替代方案,我们想先"open"适配器,接着仅当在我们准备好开始捕获时发送一个"start capture"的命令。

该函数作了这样的工作:它发送一个“open adapter”命令(根据RPCAP协议),但是它不开始捕获。

既然其它的libpcap函数并不一定共享该工作方式,我们不得不做一些并不优雅的事情,使得每件事情都能妥善的解决。

注意:万一在捕获还没有开始时,就调用了pcap_compile()函数,过滤器将被存储到pcap_t结构体中,稍后它将被发送到另一个主机(当pca p_startcapture_remote()被调用时)。

本文出自“千江月” 博客,请务必保留此出处/918801/ 204274。

相关文档
最新文档