精华内容
下载资源
问答
  • Linux网络设备分析

    千次阅读 2011-12-02 16:31:40
    Linux网络设备分析 潘纲 9811536 浙江大学计算机系 pg@ccnt.zju.edu.cn [摘要] 在本文中,首先概括了网络设备总体特征和工作原理,接着在分析了一个 重要的数据结构device后,重点剖析了网络设备的整个初始...
    Linux网络设备分析
    
    潘纲    9811536
    浙江大学计算机系    pg@ccnt.zju.edu.cn

    [摘要] 在本文中,首先概括了网络设备总体特征和工作原理,接着在分析了一个
    重要的数据结构device后,重点剖析了网络设备的整个初始化工作过程;简单地分
    析了设备的打开和关闭的操作后,是有关数据包的传输和接收的分析;在最后,本
    文对写网络设备驱动程序做了一个总结。以上的每部分的分析,都是在NE2000以太
    网卡的基础上进行的。在附录中是一个虚拟的字符设备驱动程序以及写这个程序的
    体会,该程序已成功使用过,它是在网络设备分析之前本人做的一个小小的试验。


    一. 网络设备概述
    在LINUX中,为了简化对设备的管理,所有外围的硬件设备被归结为三类:字符设
    备(如键盘、鼠标等)、块设备(如硬盘、光驱、软驱等)和网络设备(也称为网
    络接口,network inferface),如以太网卡。在本文中,我们将等效使用"网络设
    备"和"网络接口"这两个概念,而对某个具体的网络设备,我们将称之为"物理设备
    "或"物理网络设备"。
    为了屏蔽网络环境中物理网络设备的多样性,LINUX对所有的物理设备进行抽象并
    定义了一个统一的概念,称之为接口(Interface)。所有对网络硬件的访问都是
    通过接口进行的,接口提供了一个对所有类型的硬件一致化的操作集合来处理基本
    数据的发送和接收。一个网络接口被看作是一个发送和接收数据包(packets)的
    实体。对于每个网络接口,都用一个device的数据结构表示,有关该数据结构的具
    体内容,将在本文的后面详细介绍。通常,网络设备是一个物理设备如以太网卡,
    但软件也可以作为网络设备,如回送设备(loopback)。在内核启动时,通过网络
    设备驱动程序,将登记存在的网络设备。设备用标准的支持网络的机制来转递收到
    的数据到相应的网络层。所有被发送和接收的包都用数据结构sk_buff表示。这是
    一个具有很好的灵活性的数据结构,可以很容易增加或删除网络协议数据包的首部

    网络设备作为其中的三类设备之一,它有其非常特殊的地方。它与字符设备及块设
    备都有很大的不同:

    §  网络接口不存在于Linux的文件系统中,而是在核心中用一个device数据结构表
    示的。每一个字符设备或块设备则在文件系统中都存在一个相应的特殊设备文件来
    表示该设备,如/dev/hda1、/dev/sda1、/dev/tty1等。网络设备在做数据包发送
    和接收时,直接通过接口访问,不需要进行文件的操作;而对字符设备和块设备的
    访问都需通过文件操作界面。
    §  网络接口是在系统初始化时实时生成的,对于核心支持的但不存在的物理网络
    设备,将不可能有与之相对应的device结构。而对于字符设备和块设备,即使该物
    理设备不存在,在/dev下也必定有相应的特殊文件与之相对应。且在系统初始化时
    ,核心将会对所有内核支持的字符设备和块设备进行登记,初始化该设备的文件操
    作界面(struct file_operations),而不管该设备在物理上是否存在。

    以上两点是网络设备与其他设备之间存在的最主要的不同。然而,它们之间又有一
    些共同之处,如在系统中一个网络设备的角色和一个安装的块设备相似。一个块设
    备在blk_dev数组及核心其他的数据结构中登记自己,然后根据请求,通过自己的
    request_function函数"发送"和"接收"数据块。相似地,为了能与外面世界进行数
    据交流,一个网络接口也必须在一个特殊的数据结构中登记自己。
    在系统内核中,存在字符设备管理表chardevs和块设备管理表blkdevs,这两张保
    存着指向file_operations结构的指针的设备管理表,分别用来描述各种字符驱动
    程序和块设备驱动程序。类似地,在内核中也存在着一张网络接口管理表
    dev_base,但与前两张表不同,dev_base是指向device结构的指针,因为网络设备
    是通过device数据结构来表示的。dev_base实际上是一条device结构链表的表头,
    在系统初始化完成以后,系统检测到的网络设备将自动地保存在这张链表中,其中
    每一个链表单元表示一个存在的物理网络设备。当要发送数据时,网络子系统将根
    据系统路由表选择相应的网络接口进行数据传输,而当接收到数据包时,通过驱动
    程序登记的中断服务程序进行数据的接收处理(软件网络接口除外)。以下是网络
    设备工作原理图:
     
    图一   Linux网络设备工作原理图

    每一个具体的网络接口都应该有一个名字,以在系统中能唯一标识一个网络接口。
    通常一个名字仅表明该接口的类型。Linux对网络设备命名有以下约定:(其中N为
    一个非负整数)
    ethN        以太网接口,包括10Mbps和100Mbps;
    trN     令牌环接口;
    slN         SLIP网络接口;
    pppN    PPP网络接口,包括同步和异步;
    plipN   PLIP网络接口,其中N与打印端口号相同;
    tunlN   IPIP压缩频道网络接口;
    nrN         NetROM虚拟设备接口;
    isdnN   ISDN网络接口;
    dummyN      空设备;
    lo      回送网络接口。

    二. 重要数据结构--struct device
    结构device存储一个网络接口的重要信息,是网络驱动程序的核心。在逻辑上,它
    可以分割为两个部分:可见部分和隐藏部分。可见部分是由外部赋值;隐藏部分的
    域段仅面向系统内部,它们可以随时被改变。下面我们将对之进行详细的分析和解
    剖。

    /*  from  include/linux/netdevice.h  */
    struct device
    {
    1.  属性
      char              *name;
    设备的名字。如果第一字符为NULL(即'\0'),register_netdev
    (drivers/net/net_init.c)将会赋给它一个n最小的可用网络设备名ethn。

      unsigned long     rmem_end;       /* shmem "recv" end */
      unsigned long     rmem_start;     /* shmem "recv" start   */
      unsigned long     mem_end;        /* shared mem end   */
      unsigned long     mem_start;      /* shared mem start */
    这些域段标识被设备使用的共享内存的首地址及尾地址。如果设备用来接收和发送
    的内存块不同,则mem域段用来标识发送的内存位置,rmem用来标识接收的内存位
    置。mem_start和mem_end可在系统启动时用内核的命令行指定,用ifconfig可以查
    看它们的值。rmem域段从来不被驱动程序以外的程序所引用。

      unsigned long     base_addr;      /* device I/O address   */
      unsigned char     irq;            /* device IRQ number    */
    I/O基地址和中断号。它们都是在设备检测期间被赋值的,但也可以在系统启动时
    指定传入(如传给LILO)。ifconfig命令可显示及修改他们的当前值。

      volatile unsigned char        start;     /* start an operation   */
      volatile unsigned char        interrupt;      /* interrupt arrived    */
    这是两个二值的低层状态标志。通常在设备打开时置start标志,在设备关闭时清
    start标志。当interrupt置位时,表示有一个中断已到达且正在进行中断服务程序
    理。

      unsigned long     tbusy;      /* transmitter busy must be long for bitops
    */
    标识"发送忙"。在驱动程序不能接受一个新的需传输的包时,该域段应该为非零。


      struct device     *next;
    指向下一个网络设备,用于维护链表。

      unsigned char     if_port;
    记录哪个硬件I/O端口正在被接口所用,如BNC,AUI,TP等(drivers/net/de4x5.
    h)。
      unsigned char     dma;    
    设备用的DMA通道。
    一些设备可能需要以上两个域段,但非必需的。

      unsigned long     trans_start;    /* Time (in jiffies) of last Tx */
    上次传输的时间点(in jiffies)
      unsigned long     last_rx;    /* Time of last Rx      */
    上次接收的时间点(in jiffies)。如trans_start可用来帮助内核检测数据传输
    的死锁(lockup)。

      unsigned short        flags;  /* interface flags (a la BSD)   */
    该域描述了网络设备的能力和特性。它包括以下flags:(include/linux/if.h)

    IFF_UP
    表示接口在运行中。当接口被激活时,内核将置该标志位。
    IFF_BROADCAST
    表示设备中的广播地址时有效的。以太网支持广播。
    IFF_DEBUG
    调试模式,表示设备调试打开。当想控制printk及其他一些基于调试目的的信息显
    示时,可利用这个标志位。虽然当前没有正式的驱动程序使用它,但它可以在程序
    中通过ioctl来设置从而使用它。
    IFF_LOOPBACK
    表示这是一个回送(loopback)设备,回送接口应该置该标志位。核心是通过检查
    此标志位来判断设备是否是回送设备的,而不是看设备的名字是否是lo。
    IFF_POINTTOPOINT
    表示这是一个点对点链接(SLIP and PPP),点对点接口必须置该标志位。
    Ifconfig也可以置此标志位及清除它。若置上该标志位,则dev->dstaddr应也相应
    的置为链接对方的地址。
    IFF_MASTER      /* master of a load balancer    */
    IFF_SLAVE       /* slave of a load balancer */
    此两个标志位在装入平等化中要用到。
    IFF_NOARP
    表示不支持ARP协议。通常的网络接口能传输ARP包,如果想让接口不执行ARP,可
    置上该标志位。如点对点接口不需要运行ARP。
    IFF_PROMISC
    全局接受模式。在该模式下,设备将接受所有的包,而不关这些包是发给谁的。在
    缺省情况下,以太网接口会使用硬件过滤,以保证只接受广播包及发给本网络接口
    的包。Sniff的原理就是通过设置网络接口为全局接受模式,接受所有到达本接口
    媒介的包,来"偷听"本子网的"秘密"。
    IFF_MULTICAST
    能接收多点传送的IP包,具有多点传输的能力。ether_setup缺省是置该标志位的
    ,故若不想支持多点传送,必须在初始化时清除该标志位。
    IFF_ALLMULTI
    接收所有多点传送的IP包。
    IFF_NOTRAILERS  /*无网络TRAILER*/
    IFF_RUNNING     /*资源被分配*/
    此标志在Linux中没什么用,只是为了与BSD兼容。

      unsigned short        family; /* address family ID (AF_INET)  */
    该域段标识本设备支持的协议地址簇。大部分为AF_INET(英特网IP协议),接口
    通常不需要用这个域段或赋值给它。

      unsigned short        metric; /* routing metric (not used)    */
      unsigned short        mtu;    
    不包括数据链路层帧首帧尾的最大传输单位(Maximum Transfer Unit)。网络层
    在包传输时要用到。对以太网而言,该域段为1500,不包括MAC帧的帧首和帧尾(
    MAC帧格式稍后所示)。

      unsigned short        type;       /* interface hardware type  */
    接口的硬件类型,描述了与该网络接口绑在一起的媒介类型。Linux网络设备支持
    许多不同种类的媒介,如以太网,X.25,令牌环,SLIP,PPP,Apple Localtalk等
    。ARP在判定接口支持哪种类型的物理地址时要用到该域段。若是以太网接口,则
    在ether_setup中将之设为ARPHRD_ETHER(Ethernet 10Mbps)。

      unsigned short        hard_header_len;    /* hardware hdr length  */
    在被传送的包中IP头之前的字节数。对于以太网接口,该域段为14(ETH_HLEN,
    include\linux\if_ether.h),这个值可由MAC帧的格式得出:
    MAC帧格式:
    目的地址(6字节)+ 源地址(6字节)+ 数据长度(2字节)+ 数据(46~~1500)
    +FCS

      void              *priv;  /* pointer to private data  */
    该指针指向私有数据,通常该数据结构中包括struct enet_statistics。类似于
    struct file的private_data指针,但priv指针是在设备初始化时被分配内存空间
    的(而不是在设备打开时),因为该指针指向的内容包括设备接口的统计数据,而
    这些数据即使在接口卸下(down)时也应可以得到的,如用户通过ifconfig查看。


      unsigned char     pad;                /* make dev_addr aligned to 8 bytes */
      unsigned char     broadcast[MAX_ADDR_LEN];    /* hw bcast add */
    广播地址由六个0xff构成,即表示255.255.255.255。
    memset(dev->broadcast,0xFF, ETH_ALEN); (drivers/net/net_init.c)

      unsigned char     dev_addr[MAX_ADDR_LEN]; /* hw address   */
    设备的物理地址。当包传送给驱动程序传输时,要用物理地址来产生正确的帧首。


      unsigned char     addr_len;       /* hardware address length  */
    物理地址的长度。以太网网卡的物理地址为6字节(ETH_ALEN)。

      unsigned long     pa_addr;        /* protocol address     */
      unsigned long     pa_brdaddr; /* protocol broadcast addr  */
      unsigned long     pa_mask;        /* protocol netmask  */
    该三个域段分别描述接口的协议地址、协议广播地址和协议的网络掩码。若
    dev->family为AF_INET,则它们即为IP地址。这些域段可用ifconfig赋值。

      unsigned short        pa_alen;        /* protocol address length  */
    协议地址的长度。AF_INET的为4。

      unsigned long     pa_dstaddr; /* protocol P-P other side addr */
    点对点协议接口(如SLIP、PPP)用这个域记录连接另一边的IP值。

      struct dev_mc_list    *mc_list;       /* Multicast mac addresses  */
      int               mc_count;   /* Number of installed mcasts   */
      struct ip_mc_list     *ip_mc_list;    /* IP multicast filter chain    */
    这三个域段用于处理多点传输。其中mc_count表示mc_list中的项目数。

      __u32             tx_queue_len;   /* Max frames per queue allowed */
    一个设备的传输队列能容纳的最大的帧数。对以太网,缺省为100;而plip则为节
    省系统资源,仅设为10。
        
      /* For load balancing driver pair support */
      unsigned long     pkt_queue;  /* Packets queued */
      struct device     *slave;     /* Slave device */
      struct net_alias_info *alias_info;    /* main dev alias info */
      struct net_alias      *my_alias;  /* alias devs */
     
      struct sk_buff_head   buffs[DEV_NUMBUFFS];
    指向网络接口缓冲区的指针。
    2.  服务处理程序
    以下是一些对网络接口的操作,类似与字符设备和块设备。网络接口操作可以分为
    两部分,一部分为基本操作,即每个网络接口都必须有的操作;另一部分是可选操
    作。

    /* 基本操作 */
      int   (*init) (struct device *dev);  /* Called only once. */
    初始化函数的指针,仅被调用一次。当登记一个设备时,核心一般会让驱动程序初
    始化该设备。初始化函数功能包括以下内容:检测设备是否存在;自动检测该设备
    的I/O端口和中断号;填写该设备device结构的大部分域段;用kmalloc分配所需的
    内存空间等。若初始化失败,该设备的device结构就不会被链接到全局的网络设备
    表上。在系统启动时,每个驱动程序都试图登记自己,当只有那些实际存在的设备
    才会登记成功。这与用主设备号及次设备号索引的字符设备和块设备不同。

      int   (*open) (struct device *dev);
    打开网络接口。每当接口被ifconfig激活时,网络接口都要被打开。Open操作做以
    下工作:登记一些需要的系统资源,如IRQ、DMA、I/O端口等;打开硬件;将
    module使用计数器加一。

      int   (*stop) (struct device *dev);
    停止网络接口。操作内容与open相逆。

      int   (*hard_start_xmit) (struct sk_buff *skb,  struct device *dev);
    硬件开始传输。这个操作请求对一个包的传输,这个包原保存在一个socket缓冲区
    结构中(sk_buff)。

      int   (*hard_header) (struct sk_buff *skb,  struct device *dev,  
    unsigned short type,
      void *daddr,    void *saddr,  unsigned len);
    这个函数可根据先前得到的源物理地址和目的物理地址建立硬件头(hardware
    header)。以太网接口的缺省函数是eth_header。

      int   (*rebuild_header)(void *eth, struct device *dev,  unsigned long
    raddr, struct sk_buff *skb);
    在一个包被发送之前重建硬件头。对于以太网设备,若有未知的信息,缺省函数将
    使用ARP填写。

      struct enet_statistics*       (*get_stats)(struct device *dev);
    当一个应用程序需要知道网络接口的一些统计数据时,可调用该函数,如
    ifconfig、netstat等。

    /* 可选操作 */
      void  (*set_multicast_list)(struct device *dev);
    设置多点传输的地址链表(*mc_list)。

      int   (*set_mac_address)(struct device *dev, void *addr);
    改变硬件的物理地址。如果网络接口支持改变它的硬件物理地址,就可用这个操作
    。许多硬件不支持该功能。

      int   (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd);
    执行依赖接口的ioctl命令。

      int   (*set_config)(struct device *dev, struct ifmap *map);
    改变接口配置。设备的I/O地址和中断号可以通过该函数进行实时修改。

      void  (*header_cache_bind)(struct hh_cache **hhp,  struct device *dev,
     
      unsigned short htype,  __u32 daddr);
      void  (*header_cache_update)(struct hh_cache *hh, struct device *dev,
    unsigned char * haddr);

      int   (*change_mtu) (struct device *dev, int new_mtu);
    这个函数负责使接口MTU改变后生效。如果当MTU改变时驱动程序要作一些特殊的事
    情,就应该写这个函数。

      struct iw_statistics* (*get_wireless_stats) (struct device *dev);
    };

    三. 网络设备的初始化
    网络设备的初始化主要工作是检测设备的存在、初始化设备的device结构及在系统
    中登记该设备。类似于字符设备和快块设备,系统内核中也存在着一张网络接口管
    理表dev_base,但与dev_base是指向device结构的,因为网络设备是通过device数
    据结构来表示的。dev_base实际上是一条device结构链表的表头,在系统初始化完
    成以后,系统检测到的网络设备将自动地保存在这张链表中,其中每一个链表单元
    表示一个存在的物理网络设备。登记成功的网络设备必定可在dev_base链表中找到

    网络设备的初始化从触发角度看可分为两类:一类是由shell命令insmod触发的模
    块化驱动程序(module),只有模块化的网络设备驱动程序才能用这种方式对设备
    进行初始化,称为"模块初始化模式";另一类是系统驱动时由核心自动检测网络设
    备并进行初始化,我们称为"启动初始化模式"。显然,这两种初始化模式存在许多
    不同之处,以下我们对两者分别进行分析。
    1.  "模块初始化模式"的分析
    §  概述
    insmod命令将调用相应模块的init_module(),装载模块。init_module函数在初
    始化dev->init函数指针后,将调用register_netdev()在系统登记该设备。若登
    记成功,则模块装载成功,否则返回出错信息。register_netdev首先检查设备名
    是否已确定,若没赋值则给它一个缺省的值ethN,N为最小的可用以太网设备号注
    ;然后,网络设备自己的init_function,即刚在init_module中赋值的dev->init
    ,将被调用,用来实现对网络接口的实际的初始化工作。若初始化成功,则将该网
    络接口加到网络设备管理表dev_base的尾部。整个函数调用关系图如下所示。下面
    我们以用得最广泛以太网卡之一--NE2000兼容网卡为例子进行分析。NE2000网卡的
    主要驱动程序在文件drivers/net/ne.c中。
     
    图二  "模块初始化模式"的函数调用关系图

    §  init_module
    init_module---模块初始化函数,当装载模块时,核心将自动调用该函数。在次此
    函数中一般处理以下内容:
    1. 处理用户可能传入的参数name、ports及irq的值。若有,则赋给相应的接口(
    注意:未登记);
    2. 对dev->init函数指针进行赋值,对于任何网络设备这一步必不可少!!因为
    在register_netdev中要用到该函数指针;
    3. 调用register_netdev,完成检测、初始化及设备登记等工作。

    /* from  drivers/net/ne.c */
    init_module(void)
    {
        int this_dev, found = 0;

    /* 对所有可能存在的以太网接口进行检测并试图去登记,MAX_NE_CARDS为4,
     * 即最多可以使用4块NE2000兼容网卡。 */
        for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++) {
            struct device *dev = &dev_ne[this_dev];
            /* 可能有用户传入的参数:指定的name、ports及irq的值 */
            dev->name = namelist+(NAMELEN*this_dev);
            dev->irq = irq[this_dev];
            dev->base_addr = io[this_dev];
            dev->init = ne_probe;       /* NE2000的检测和初始化函数 */
            dev->mem_end = bad[this_dev];
            if (register_netdev(dev) == 0) { /* 试图登记该设备 */
                found++;
                continue;               /* 设备登记成功,继续登记下一个设备 */
            }
            /* 第一次发生登记不成功事件 */
            if (found != 0)         /* 在这之前没有成功登记NE2000接口,返回 */
                return 0;
            /* 显示出错信息 */
            if (io[this_dev] != 0)
                printk(KERN_WARNING "ne.c: No NE*000 card found at i/o = %#x\n",
    io[this_dev]);
            else
                printk(KERN_NOTICE "ne.c: No PCI cards found. Use \"io=0xNNN\"
    value(s) for
    …………

    §  register_netdev
    该函数实现对网络接口的登记功能。其实现步骤如下:
    1. 首先检查设备名是否已确定,若没赋值则以以太网设备待之并给它一个缺省的
    值ethN,N为最小的可用以太网设备号;
    2. 然后,网络设备自己的init_function,即刚在init_module中赋值的
    dev->init,将被调用,用来实现对网络接口的实际的初始化工作。
    3. 若初始化成功,则将该网络接口加到网络设备管理表dev_base的尾部

    /* from  drivers/net/net_init.c  */
    int register_netdev(struct device *dev)
    {
    struct device *d = dev_base; /* 取得网络设备管理表的表头指针 */
    …………
    if (dev && dev->init) {
            /*若设备名字没确定,则将之看作是以太网设备!!*/
            if (dev->name &&
                ((dev->name[0] == '\0') || (dev->name[0] == ' '))) {
                /* 找到下一个最小的空闲可用以太网设备名字 */
                for (i = 0; i < MAX_ETH_CARDS; ++i)
                    if (ethdev_index[i] == NULL) {
                        sprintf(dev->name, "eth%d", i);
                        printk("loading device '%s'...\n", dev->name);
                        ethdev_index[i] = dev;
                        break;
                    }
            }
    …………
    /* 调用初始化函数进行设备的初始化 */
            if (dev->init(dev) != 0) {
    …………
            /* 将设备加到网络设备管理表中,加在最后 */
            if (dev_base) {
                /* 找到链表尾部 */
                while (d->next)
                    d = d->next;
                d->next = dev;
            }
            else
                dev_base = dev;
            dev->next = NULL;
    …………

    §  init_function
    函数原型:int init_function (struct device *dev);
    当系统登记一个网络设备时,核心一般会请求该设备的驱动程序初始化自己。初始
    化函数功能包括以下内容:
    1.检测设备是否存在,一般和第二步一起作;
    2.自动检测该设备的I/O地址和中断号;
    对于可以与其他共享中断号的设备,我们应尽量避免在初始化函数中登记I/O地址
    和中断号,I/O地址和中断号的登记最好在设备被打开的时候,因为中断号有可能
    被其他设备所共享。若不准备和其他设备共享,则可在此调用request_irq和
    request_region马上向系统登记。
    3.填写传入的该设备device结构的大部分域段;
    对于以太网接口,device结构中许多有关网络接口信息都是通过调用ether_setup
    函数(driver/net/net_init.c)统一来设置的,因为以太网卡有很好的共性。对
    于非以太网接口,也有一些类似于ether_setup的函数,如tr_setup(令牌网),
    fddi_setup。若添加的网络设备都不属于这些类型,就需要自己填写device结构的
    各个分量。
    4.kmalloc需要的内存空间。

    若初始化失败,该设备的device结构就不会被链接到全局的网络设备表上。在系统
    启动时,每个驱动程序都试图登记自己,当只有那些实际存在的设备才会登记成功
    。这与用主设备号及次设备号索引的字符设备和块设备不同。
    物理设备NE2000兼容网卡的初始化函数是由ne_probe和ne_probe1及ethdev_init共
    同实现。

    /* from  drivers/net/ne.c */
    int ne_probe(struct device *dev)
    {
       …………
       int base_addr = dev ? dev->base_addr : 0;
       /* I/O地址. User knows best. <cough> */
       if (base_addr > 0x1ff)       /* I/O地址有指定值 */
          return ne_probe1(dev, base_addr);  /* 这个函数在下面分析 */
       else if (base_addr != 0) /* 不自动检测I/O */
          return ENXIO;
       …………
       /* base_addr=0,自动检测,若有第二块ISA网卡则是一个冒险!J
       * 对所有NE2000可能的I/O地址都进行检测,可能的I/O地址在存在
       * netcard_portlist数组中:
       * static unsigned int netcard_portlist[]={ 0x300, 0x280, 0x320,
    0x340, 0x360, 0};
       */
       for (i = 0; netcard_portlist[i]; i++) {
          int ioaddr = netcard_portlist[i];
          if (check_region(ioaddr, NE_IO_EXTENT))
             continue;
          /* 检测到一个I/O端口地址 */
          if (ne_probe1(dev, ioaddr) == 0)
             return 0;
    …………

    /* from  drivers/net/ne.c */
    static int ne_probe1(struct device *dev, int ioaddr)
    {
       …………
       /* 检测、确认I/O地址;初始化8390 */
       …………
       /* 自动检测中断号,非常巧妙!! */
       if (dev->irq < 2) {
          autoirq_setup(0); /* 自动检测准备 */
          outb_p(0x50, ioaddr + EN0_IMR);       /* 中断使能 */
          outb_p(0x00, ioaddr + EN0_RCNTLO);
          outb_p(0x00, ioaddr + EN0_RCNTHI);
          outb_p(E8390_RREAD+E8390_START, ioaddr); /* 触发中断 */
          outb_p(0x00, ioaddr + EN0_IMR);       /* 屏蔽中断 */
          dev->irq = autoirq_report(0); /* 获得刚才产生的中断号 */
          …………
        …………
      /* 登记中断号,中断服务程序为ei_interrupt。
       * 因为ISA网卡不能和其他设备共享中断。*/
       int irqval = request_irq(dev->irq, ei_interrupt,
    pci_irq_line ? SA_SHIRQ : 0, name, dev);
       if (irqval) {
          printk (" unable to get IRQ %d (irqval=%d).\n", dev->irq,
    irqval);
          return EAGAIN;
       }
       dev->base_addr = ioaddr;     /* 设置I/O地址--已经过确认 */

       /* 调用ethdev_init初始化dev结构 */
       if (ethdev_init(dev)) { /* 该函数下面将分析 */
          printk (" unable to get memory for dev->priv.\n");
          free_irq(dev->irq, NULL); /* 初始化不成功,释放登记的中断号! */
          return -ENOMEM;
       }
       /* 向系统登记I/O地址 */
       request_region(ioaddr, NE_IO_EXTENT, name);
       /* 将硬件的物理地址赋给dev->dev_add */
       for(i = 0; i < ETHER_ADDR_LEN; i++) {
          printk(" %2.2x", SA_prom[i]);
          dev->dev_addr[i] = SA_prom[i];
       }
       printk("\n%s: %s found at %#x, using IRQ %d.\n",
                        dev->name, name, ioaddr, dev->irq);
       …………
       /* 向dev结构登记设备打开和关闭函数 */
       dev->open = &ne_open;
       dev->stop = &ne_close;
    …………

    /* from  drivers/net/8390.c  */
    int ethdev_init(struct device *dev)
    {
       …………
       if (dev->priv == NULL) {
          struct ei_device *ei_local;
          /* 申请私有数据结构空间,用于记录设备的状态等 */
          dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL);
        …………
       dev->hard_start_xmit = &ei_start_xmit;
       dev->get_stats = get_stats;
       dev->set_multicast_list = &set_multicast_list;
       ether_setup(dev);
    …………
    §  ether_setup
    ether_setup是一个通用于以太网接口的网络接口设置函数。由于以太网卡有很好
    的共性,device结构中许多有关的网络接口信息都是通过调用ether_setup函数统
    一来设置。那么让我们看看它到底会缺省设哪些域段及设为什么值。若你满意这些
    缺省设置,那么在写驱动程序时只要调用一下这个函数就可以将这些域段的设置工
    作"置之不理了"J,否则,也可在调用该函数之后再改过。

    /* from  drivers/net/net_init.c */
    void ether_setup(struct device *dev)
    {
       int i;
       /* 初始化缓冲队列链表,这是一个双向链表 */
       for (i = 0; i < DEV_NUMBUFFS; i++)   /* DEV_NUMBUFFS=3 */
          skb_queue_head_init(&dev->buffs[i]);
       …………
       /* 一些处理函数的初始化,驱动程序可以不写这些函数了 */
       dev->change_mtu= eth_change_mtu;
       dev->hard_header= eth_header;
       dev->rebuild_header = eth_rebuild_header;
       dev->set_mac_address = eth_mac_addr;
       dev->header_cache_bind = eth_header_cache_bind;
       dev->header_cache_update= eth_header_cache_update;

       dev->type            = ARPHRD_ETHER; /* Ethernet 10Mbps */
       dev->hard_header_len= ETH_HLEN;  /* MAC层协议头的大小 14 */
       dev->mtu         = 1500;             /* 最大传输单位  */
       dev->addr_len        = ETH_ALEN; /* 协议地址长度 4 */
       dev->tx_queue_len    = 100;          /* 传输队列的长度 */
       memset(dev->broadcast,0xFF, ETH_ALEN);/* 物理地址长度 6 */

                                /* 广播地址有效及支持多点传输 */
       dev->flags       = IFF_BROADCAST|IFF_MULTICAST;
       dev->family  = AF_INET;  /* 英特网IP协议簇 */
       dev->pa_addr = 0;            /* 以后用ifconfig命令设置 */
       dev->pa_brdaddr= 0;          /* 以后用ifconfig命令设置 */
       dev->pa_mask = 0;            /* 以后用ifconfig命令设置 */
       dev->pa_alen = 4;            /* 协议地址长度 4  */
    }

    至此模块化网络设备的初始化就完成了。
    2.  "启动初始化模式"的分析
    §  初始化策略
    "启动初始化模式"与"模块初始化模式"不同,前者要对所有内核支持的网络设备进
    行检测和初始化,而后者仅需检测和初始化被装载模块的网络设备。为了实现在启
    动时对所有可能存在的设备进行初始化,系统在启动之前将所有内核支持的网络设
    备的名字及相应的初始化函数都挂在网络设备管理表(dev_base)上。启动后,
    net_dev_int()将依次对网络设备管理表dev_base中的每个设备,调用该设备本
    身的init_function进行初始化。若init_function失败,即该设备不存在或I/O、
    IRQ不能获得,则将该设备从dev_base去掉。这样,最后网络设备管理表中剩下的
    网络接口都是存在的,显然也已是被初始化过的。我们看一下dev_base的初始化情
    况。    
    图三  网络设备表的初始化后的示意图

    /* 网络设备管理表的初始化 */
    /* from  drivers/net/Space.c */
    …………
    static struct device eth7_dev = {
        "eth7", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, NEXT_DEV, ethif_probe
     };
    static struct device eth6_dev = {
        "eth6", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, &eth7_dev,
    ethif_probe };
    …………
    static struct device eth0_dev = {
        "eth0", 0, 0, 0, 0, ETH0_ADDR, ETH0_IRQ, 0, 0, 0, &eth1_dev,
    ethif_probe };
    /* 在八个eth接口中,只有eth0将I/O设为0,让其进行自动检测,其他
    * eth接口的I/O都设为0xffe0,不进行检测。Linux缺省的内核在启动时
    * 只能自动检测到一块eth网卡,就是这个原因 */

    #   undef NEXT_DEV
    #   define NEXT_DEV (&eth0_dev)
    #if defined(PLIP) || defined(CONFIG_PLIP)
    extern int plip_init(struct device *);
    static struct device plip2_dev = {
            "plip2", 0, 0, 0, 0, 0x278, 2, 0, 0, 0, NEXT_DEV, plip_init, };
    …………
    static struct device plip0_dev = {
        "plip0", 0, 0, 0, 0, 0x3BC, 5, 0, 0, 0, &plip1_dev, plip_init, };
    …………
    extern int loopback_init(struct device *dev);
    struct device loopback_dev = {
        "lo", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, loopback_init };

    struct device dev_base = &loopback_dev; /* 关键的一个语句 */

    §  函数调用关系
    系统转入核心后,start_kernel将会创建一个init进程,该init进程则会通过系统
    调用sys_steup进行所有尚未初始化的设备(有一些设备如内存、PCI等系统已先于
    此进行了初始化)。device_setup不仅要初始化内核支持的字符设备、块设备,也
    调用net_dev_init初始化所有内核支持的且实际存在的网络设备。net_dev_init会
    对每个内核支持的网络设备调用该设备的init_functions进行具体的物理设备的初
    始化工作。整个函数调用关系图如下:

     
    图四  "启动初始化模式"的函数调用关系图

    §  具体流程
    LINUX启动时,完成了实模式下的系统初始化(arch/i386/boot/setup.S)与保护
    模式下的核心初始化包括初始化寄存器和数据区
    (arch/i386/boot/compressed/head.S)、核心代码解压缩、页表初始化(
    arch/i386/kernel/head.S)、初始化idt、gdt和ldt等工作后,系统转入了核心。
    调用函数start_kernel启动核心(init/main.c)后,将继续各方面的初始化工作
    ,其中与网络子系统有关的部分为:调用sock_init()(net/socket.c)初始化网
    络模块。
    sock_init()函数主要做以下动作:
    ①  初始状态设为不支持任何网络协议:
    static struct proto_ops *pops[NPROTO];   // #define NPROTO 16
    ……
    for (i = 0; i < NPROTO; ++i) pops[i] = NULL;
    ②  调用init_netlink(),登记一个网络字符设备(即把netlink看作一种字符设备
    ),主设备号为NETLINK_MAJOR(36): (net/netlink.c)
    register_chrdev(NETLINK_MAJOR,"netlink", &netlink_fops);
    for(ct=0;ct<MAX_LINKS;ct++){
            skb_queue_head_init(&skb_queue_rd[ct]);
            netlink_handler[ct]=netlink_err;
    }
    其中static int (*netlink_handler[MAX_LINKS])(struct sk_buff *skb);
    MAX_LINKS为次设备数,定义为11。
    ③  调用netlink_attach(),在netlink_handler登记路由设备的回调函数:(
    net/netlink.c)
    netlink_attach(NETLINK_ROUTE, netlink_donothing);
    其中netlink_attach为:{
    active_map|=(1<<unit);
    netlink_handler[unit]=function;}
    ④  调用fwchain_init(),初始化防火墙,设为FW_ACCEPT。
    ⑤  调用proto_init(),执行核心支持的各种网络协议的初始化函数:
    void proto_init(void){
    /* 核心支持的网络协议在net/protocols.c中定义 */
    extern struct net_proto protocols[];
    struct net_proto *pro;
    pro = protocols;
    while (pro->name != NULL) {
    (*pro->init_func)(pro);
    pro++;
    }
    }
    ⑥  调用export_net_symbols(),向系统登记发布network  symbols,以供系统的
    模块(modules)使用,因为module中的函数所调用的外部函数只能是已分布登记
    (export)的函数:
    /* from  net/netsyms.c   */
    void export_net_symbols(void)
    {
    /* 该函数为宏,定义在(include/linux/module.h)*/
    register_symtab(&net_syms);
    }
    其中net_syms静态结构变量初始化为:(net/netsyms.c)
    static struct symbol_table net_syms = {
    #include <linux/symtab_begin.h>

    /* Socket layer registration */
    X(sock_register),
    X(sock_unregister),
    X(sock_alloc),
    X(sock_release),
    …………
    …………
    #include <linux/symtab_end.h>
    };
    至此,系统完成了一些有关网络模块的初始化工作。但请注意,目前系统还没涉及
    任何有关网络设备的初始化内容(别急 J)。有些特别:网络协议的初始化竟是在
    网络设备的初始化之前!!由于本文主要着重于网络设备的分析,故对整个网络子
    系统的组成和结构,及网络上层协议(如TCP/IP、SOCKET等)和网络设备之间的缝
    合未作深入的代码级分析,望能有后来者弥补这方面的工作(?)。题外话了,言
    归正传。
    start_kernel最后将调用kernel_thread (init, NULL, 0),创建init进程进行系
    统配置(其中包括所有设备的初始化工作)。
    /*  from  init/main.c  */
    static int init(void * unused)
    {
    …………
    /* 创建后台进程bdflush,以不断循环写出文件系统缓冲区中"脏"的内容 */
    kernel_thread(bdflush, NULL, 0);
    /* 创建后台进程kswapd,专门处理页面换出工作  */
    kswapd_setup();
    kernel_thread(kswapd, NULL, 0);
    …………
    / *  哈哈,原来在这里!终于逮到了!!
     *  该setup() 函数即系统调用sys_setup()
     *  关于这一点,作者已作过以下试验:在setup()调用以前和调用之后,
     *  以及在sys_setup()函数内部的开始和结束加入printk语句(以使系统
     *  启动时能输出信息)后,重新编译内核。发现用新的内核后,通过
     *  启动时的信息显示,发现setup()函数的确就是系统调用sys_setup()。
     *  至于为什么是这样,尚待分析。(?)
     */
    setup();
    …………
    }

    由于是调用sys_setup,那么让我们来看一下sys_setup。在此函数中,将调用
    device_setup对所有的设备进行初始化工作。
    /* from  fs/filesystems.c  */
    asmlinkage int sys_setup(void)
    {
    static int callable = 1;
    if (!callable)
    return -1;
    callable = 0;       /* 通过静态变量,限制该函数最多只能被调用一次 */

    device_setup(); /* 调用device_setup(),初始化所有设备 */
    …………
    }
    在device_setup中将对字符设备、块设备、网络设备、SCSI设备等进行初始化
    /* from  drivers/block/genhd.c  */
    void device_setup(void) {
    …………
    chr_dev_init();     /* 字符设备的初始化 drivers/cha/mem.c      */
    blk_dev_init();     /* 块设备的初始化   drivers/block/ll_rw_blk.c */
    sti();
    #ifdef CONFIG_SCSI
    scsi_dev_init();    /* SCSI设备的初始化 drivers/scsi/scsi.c */
    #endif
    #ifdef CONFIG_INET
    net_dev_init();     /* 网络设备的初始化 net/core/dev.c   */
    #endif
    …………
    }

    /* from  net/core/dev.c   */
    int net_dev_init(void) {
    …………
    /* 初始化数据包接收队列 */
    skb_queue_head_init(&backlog);
    /* 网桥必须在其他设备之前初始化 */
    #ifdef CONFIG_BRIDGE     
    br_init();
    #endif
    …………
    /* 以下进行网络设备初始化检测,如果dev->init初始化失败(大多数是
     * 因为设备不存在),则从网络设备链dev_base上除去该设备。*/

    /* dev_base是drivers/net/Space.c中的一个指向device结构的静态指针变量,
    * 并已初始化为核心支持的所有网络设备的链表,其中包括每个设备的probe
    * 函数指针。 */
    dp = &dev_base;
    while ((dev = *dp) != NULL)
    {
    int i;
    for (i = 0; i < DEV_NUMBUFFS; i++)  { /* #define DEV_NUMBUFFS 3 */
    skb_queue_head_init(dev->buffs + i);
    }
    if (dev->init && dev->init(dev))
    /* 初始化失败,将该设备从链表中删除,并准备初始化下一个 */
    *dp = dev->next;
    else
    dp = &dev->next;    /* 成功,准备初始化下一个 */
    }
    …………

    四. 网络设备的打开和关闭
    至此,通过分析我们知道了一个网络接口是如何在模块装载或内核启动时被检测和
    初始化的。为了使用网络设备,下一步就是解决如何激活(打开)初始化好了的接
    口这个问题了。打开和关闭一个接口是由shell命令ifconfig调用的,而
    ifconfig 则要调用一个通用的设备打开函数dev_open(net/core/dev.c),相应
    地还有一个dev_close函数。这两个函数提供了独立于设备的操作接口的打开和关
    闭的功能。
    显然,这二个提供独立于设备界面的接口操作函数,一定也需要调用网络接口dev
    的open、stop函数,同时它们还需置上dev->flags的IFF_UP标志。下面是
    dev_open的源代码,dev_close函数与以下的类似,但它另外还要释放接口所有的
    sk_buff空间。

    /* from net/core/dev.c */
    int dev_open(struct device *dev)
    {
        int ret = -ENODEV;
        if (dev->open)
            ret = dev->open(dev);   /* 调用接口的open函数 */
        if (ret == 0)               /* 接口打开成功 */
        {
            dev->flags |= (IFF_UP | IFF_RUNNING); /* 置标志位 */
            /*初始化有关多点传输的一些状态 */
            dev_mc_upload(dev);
            notifier_call_chain(&netdev_chain, NETDEV_UP, dev);
        }
        return(ret);
    }

    对于网络接口自己的dev->open函数一般包括以下几方面内容:
    1. 若没有在初始化函数中登记中断号和I/O地址,则在设备打开时要进行登记分
    别用request_irq request_region这两个函数进行登记。
    2. 若要分配DMA通道,则用request_dma进行分配登记;
    3. 将该设备挂到irq2dev_map中。若是使用基于中断的接收数据方式,以后就可
    以通过中断号直接索引到相应的设备了;
    4. 初始化物理设备的寄存器的状态;
    5. 设置接口相应dev的私有数据结构(dev->priv)中的一些域段;
    6. 设置dev中的tbusy、interrupt、start等域段;
    7. 在返回之前嵌入宏MOD_INC_USE_COUNT。
    dev->stop函数则与以上动作恰好相逆,如第六步要改为MOD_DEC_USE_COUNT。以下
    我们同样以NE2000作为例子,由上一节可知,NE2000的dev->open和dev->stop分别
    对应ne_open和ne_close。由于NE2000驱动程序是在初始化就登记IRQ和I/O地址的
    ,故在这里就不需要登记了。
    /* from  drivers/net/ne.c */
    static int ne_open(struct device *dev)
    {
    ei_open(dev);       /* 下面将分析 */
    MOD_INC_USE_COUNT;  /* 对应于第7项内容 */
    return 0;
    }
    static int ne_close(struct device *dev)
    {
    if (ei_debug > 1)
    printk("%s: Shutting down ethercard.\n", dev->name);
    ei_close(dev);      /* 下面将分析 */
    MOD_DEC_USE_COUNT;  /* 对应于第7项内容 */
    return 0;
    }
    由上容易看到,ne_close几乎就是ne_open在镜中的像。

    /* from  drivers/net/8390.c  */
    int ei_open(struct device *dev)
    {
    struct ei_device *ei_local = (struct ei_device *) dev->priv;
    if (ei_local == NULL){  /* 只有没调用ethdev_init(),才会出现以下的错误
    */
    printk(KERN_EMERG "%s: ei_open passed a non-existent device!\n",
    dev->name);
    return -ENXIO;
    }
    irq2dev_map[dev->irq] = dev;    /* 对应于上面所列内容的第3项 */
    NS8390_init(dev, 1);            /* 下面将分析 */
    dev->start = 1;                 /* 对应于第6项,表示接口UP */
    ei_local->irqlock = 0;          /* 对应于第5项 */
    return 0;
    }
    int ei_close(struct device *dev)
    {
    NS8390_init(dev, 0);
    dev->start = 0;             /* 对应于第6项内容,表示接口DOWN */
    return 0;
    }

    void NS8390_init(struct device *dev, int startp)
    {
    …………
    /* 设置8390的各种寄存器的状态 */
    …………
    dev->tbusy = 0;
    dev->interrupt = 0;             /* 对应于第6项内容 */
    ei_local->tx1 = ei_local->tx2 = 0;
    ei_local->txing = 0;                /* 对应于第5项内容 */
    …………
    }

    另外,文件net/core/dev.c还提供一系列界面独立于具体网络设备的操作函数,如

    dev_ifsioc(void *arg, unsigned int getset)
    它可以处理许多ioctl SIGNAL:读取和修改接口网络地址(对TCP/IP就是IP地址)
    、读取和修改接口的dev->flags、读取和设置MTU、读取和设置广播地址等等。
    ifconfig的功能大部分是通过该文件提供的函数实现的。

    五. 数据包的传输和接收
    当物理网络设备接收到数据时,系统是如何知道并读取数据的呢?当前可通过两种
    途径解决这个问题。一种方法是轮询方式,系统每隔一定的时间间隔就去检查一次
    物理设备,若设备"报告"说有数据到达,就调用读取数据的程序。在Linux中,轮
    询方式可通过定时器实现,但该方法存在一个明显的缺点:不管设备是否有数据,
    系统总是要固定地花CPU时间去查看设备,且可能延迟对一些紧急数据的处理,因
    为网络设备有数据时可能不能马上得到CPU的响应。在这种方式下,设备完全处于
    一种被动的状态,而CPU又负担过重。无论从资源的利用率上还是从效率上看,这
    种方法都不是最优的。另一种方法是中断方式,中断方式利用硬件体系结构的中断
    机制实现设备和系统的应答对话,即当物理设备需要CPU处理数据时,设备就发一
    个中断信号给系统,系统则在收到信号后调用相应的中断服务程序响应对设备中断
    的处理。中断方式有效地解决了设备与CPU的对话交流问题,并将CPU从繁重的设备
    轮询中解脱出来,大大提高了CPU的利用率。当前不管是Linux平台还是Windows平
    台,它们的网络设备驱动程序几乎都是使用中断方式的。故在此我们主要讨论基于
    中断方式的网络设备驱动程序。
    网络分层引起的一个问题是,每层的协议在发送数据包时要加协议头和协议尾到原
    数据中,在收到数据包时则要将本层的协议头和协议尾从数据包中去掉。这使得在
    不同层协议间传输时,每层都需要知道自己这一层的协议头和协议尾在数据包的哪
    里。一种解决方法是在每层都复制缓冲区,但显然效率太低。Linux的做法是用一
    种数据结构sk_buff在不同协议层及网络设备驱动程序之间传送数据。sk_buff 包
    括指针和长度域段,允许每个协议层通过标准的函数操作传送的数据包。该数据结
    构在整个Linux的网络子系统包括网络设备中扮演了一个十分重要的角色,故我们
    在分析数据包的传输和接收之前,首先来看看sk_buff这个数据结构的内容及系统
    提供的相关操作。因为对该数据结构的了解将大大有助于对Linux整个网络子系统
    的理解。
    1.  Socket缓冲区及相关操作
    与块设备的缓冲区处理方式不同,网络设备发送与接收数据包用的缓冲区是一个统
    一的数据结构sk_buff (include/linux/skbuff.h)。对该数据结构,核心提供了
    一系列低层的操作函数,从而使该数据结构具有网络协议传输需要的通常的缓冲功
    能和流控制能力,并可方便、灵活地处理数据包首尾的增加和删除。

    右图是sk_buff结构的一个示意图,其中每个sk_buff 都带有一块数据区,并有四
    个数据指针指向相应的位置:
    unsigned char  *head;
    指向被分配的内存空间的首地址;
    unsigned char  *data;
    指向当前数据包的首地址;
    unsigned char  *tail;
    指向当前数据包的末地址;
    unsigned char  *end;
    指向被分配的内存空间的末地址;
    unsigned long  len;
    当前数据包的大小。
    len=skb->tail - skb->data;
    unsigned long  truesize
    分配到的内存空间大小。
    len=skb->end - skb->head;
    由于数据包的大小会随着自己在不同协议层间的传送而会不断地变化,故data和
    tail指针也将会不断地改变,即依赖于skb当前所在的协议层;head和end指针则在
    内存空间分配后就固定不变。
    对缓冲区的操作,核心提供了一个比较完整的函数界面,下面将列出用的最多的几
    个函数并作分析说明。
    /* from  net/core/skbuff.c  */
    struct sk_buff  *alloc_skb (unsigned int len, int priority);
    struct sk_buff  *dev_alloc_skb (unsigned int len);
    申请一个sk_buff缓冲区。alloc_skb函数分配一个缓冲区并将skb->data和
    skb->tail初始化为skb->head;dev_alloc_skb函数是alloc_skb函数的一个快捷方
    式,它用priority= GFP_ATOMIC调用alloc_skb并在skb->data和skb->head之间保
    留16字节的空间。这16字节也用来填写硬件头(hardware header)。
    void kfree_skb (struct sk_buff *skb, int rw);
    void dev_kfree_skb (struct sk_buff *skb, int rw);
    释放一个sk_buff缓冲区。kfree_skb供核心内部调用,驱动程序应该用
    dev_kfree_skb,因为它能正确处理缓冲区加锁。参数rw可用FREE_READ或
    FREE_WRITE。用于发送的缓冲区应该用FREE_WRITE,用于接收的则用FREE_READ。

    unsigned char  *skb_put (struct sk_buff *skb, int len);
    当有数据要加到缓冲区的尾部时,用于增加skb->tail和skb->len。返回值是修改
    之前的skb->tail指针。
    unsigned char  *skb_push (struct sk_buff *skb, int len);
    当有数据要加到缓冲区的首部时,用于减少skb->data及增大skb->len。返回值是
    修改之后的skb->data。
    int skb_tailroom (struct sk_buff *skb);
    该函数返回在sk_buff 中可用于put的空间大小(尾部空余空间)。若缓冲区被正
    确分配到空间,驱动程序通常不需要检查缓冲区中空余空间的大小。由于驱动程序
    在申请空间之前可得到数据包的大小,故只有严重出错的驱动程序才会put太多的
    数据到缓冲区中。
    int skb_headroom (struct sk_buff *skb);
    类似于skb_tailroom,该函数返回可用的push的空间大小,即首部空余空间。
    void skb_reserve (struct sk_buff *skb, int len);
    该函数既增加skb->data又增加skb->tail,即在首部留出len大小的空间。在填充
    缓冲区之前,可用该函数保留一部分首部空间。许多以太网卡在首部保留2字节空
    间,这样在14字节的以太网头的后面,IP头就能以16字节对齐了。
    unsigned char  *skb_pull (struct sk_buff *skb, int len);
    从数据包的头部剔除数据。它减少skb->len并增加skb->data。以太网的头就是这
    样从接收到的数据包中被剔除的。
    void skb_trim(struct sk_buff *skb, int len)
    从数据包的尾部剔除数据。它将skb->len设为len,并改变skb->tail。

    2.  数据包的传输
    由于网络分层的原因,当用户要传输数据时,数据包是沿着网络协议由上往下逐层
    下传的。如本文开头的网络设备工作原理图所示,最后,数据包将通过
    dev_queue_xmit()[net/core/dev.c]函数传送给网络接口。网络接口的任务就是
    将数据包传送给硬件,让物理网络设备完成最终的物理传输。从device结构中我们
    可以看到,每个网络接口都应有一个叫dev->hard_start_xmit的硬件传输函数指针
    ,Linux正是通过这个函数指针来完成实际的数据传输的。
    硬件传输函数hard_start_xmit函数的一般流程如下:

    1. 通过标志位tbusy判断上次数据包的传输是否完成。若tbusy=0就做下一步;否
    则,看上次传输是否已超时,若未超时,就不成功返回,若已超时,则初始化芯片
    寄存器、置tbusy=0,然后继续下一步;
    2. 将tbusy标志位打开;
    3. 将数据包传给硬件发送;
    4. 释放缓冲区skb;
    5. 修改接口的一些统计信息。

    由上几节对NE2000的分析,我们可以发现,该网络设备的传输函数
    dev->hard_start_xmit为ei_start_xmit()。下面我们将继续以NE2000为例,对
    网络接口的数据传输过程进行分析。在本文中分析时,作者试图尽量避免依赖于硬
    件的那一部分代码,以便加深对整个网络设备管理机制的把握,并试图强调、突出
    一般网络设备所共有的东西。

    /* from drivers/net/8390.c drivers/net/8390.h */
    /* 传输函数 */
    static int ei_start_xmit(struct sk_buff *skb, struct device *dev)
    {
        int e8390_base = dev->base_addr;
        struct ei_device *ei_local = (struct ei_device *) dev->priv;
        int length, send_length, output_page;
        
        /* 若设备忙,就判断上次传输是否已超时 */
        if (dev->tbusy) {   
            /* 读取传输状态寄存器的值 */
            int txsr = inb(e8390_base+EN0_TSR), isr;    /* EN0_TSR 传输状态寄存器(读
    )*/
            int tickssofar = jiffies - dev->trans_start;
            
            /* #define TX_TIMEOUT (20*HZ/100) */
            if (tickssofar < TX_TIMEOUT ||  (tickssofar < (TX_TIMEOUT+5) &&
    ! (txsr & ENTSR_PTX))) {
                return 1;   /* 未超时,或超时一点点且发送时出错 */
            }
            /* 已超时 */
            …………
            /* 重新初始化芯片寄存器 */
            ei_reset_8390(dev);
            NS8390_init(dev, 1);
            /* 将开始传输域段置为的当前时间坐标点 */
            dev->trans_start = jiffies;
        }
        …………
        /* 屏蔽网络设备的硬件中断 */
        outb_p(0x00, e8390_base + EN0_IMR); /* EN0_IMR 中断屏蔽寄存器(WR)*/
        if (dev->interrupt) {
            /* 正在运行中断服务程序 */
            printk("%s: Tx request while isr active.\n",dev->name);
    /* 恢复中断屏蔽,失败返回; EN0_IMR 中断屏蔽寄存器(WR)*/
            outb_p(ENISR_ALL, e8390_base + EN0_IMR);
            return 1;
        }
        ei_local->irqlock = 1;
        send_length = ETH_ZLEN < length ? length : ETH_ZLEN;
        /* 硬件传输 */
        ei_block_output(dev, length, skb->data, ei_local->tx_start_page);
        ei_local->txing = 1;
        NS8390_trigger_send(dev, send_length, ei_local->tx_start_page);
        /* 设置开始传输时间坐标点,打开"忙"标志 */
        dev->trans_start = jiffies;
        dev->tbusy = 1;
        …………
        ei_local->irqlock = 0;
        /* 恢复中断屏蔽 */
        outb_p(ENISR_ALL, e8390_base + EN0_IMR);/* EN0_IMR 中断屏蔽寄存器(WR)
    */
        /* 释放缓冲区skb */
        dev_kfree_skb (skb, FREE_WRITE);
        return 0;
    }

    3.  数据包的接收
    由于使用了硬件中断请求机制,当物理网络设备接收到新数据时,它将发送一个硬
    件中断请求给系统。系统在侦察到有物理设备发出中断请求,就会调用相应的中断
    服务程序来处理中断请求。在这里,系统首先要知道哪个中断对应哪个中断服务程
    序。为了让系统知道网络设备的中断服务程序,一般在网络设备初始化的时候或设
    备被打开时,要向系统登记中断号及相应的中断服务程序(用request_irq这个函
    数登记)。基于中断方式的设备驱动程序若在设备初始化和设备打开时都没向系统
    登记中断服务程序,则该设备肯定不能正常工作。由上几节的NE2000代码分析知,
    NE2000的中断号登记是在设备初始化的时候,并登记中断服务程序为
    ei_interrupt。
    一个网络接口的中断服务程序的工作步骤一般有以下几步:

    1. 确定发生中断的具体网络接口,是rq2dev_map[irq]还是 (struct device*)
    dev_id;
    2. 打开标志位dev->interrupt,表示本服务程序正在被使用;
    3. 读取中断状态寄存器,根据寄存器判断中断发生的原因。有两种可能:一种是
    有新数据包到达;另一种是上次的数据传输已完成。
    4. 若是因为有新数据包到达,则调用接收数据包的子函数;
    5. 若中断由上次传输引起,则通知协议的上一层、修改接口的统计信息、关闭标
    志位tbusy为下次传输做准备;
    6. 关闭标志位interrupt。

    当中断服务程序明确物理网络设备有数据包收到时,将调用数据接收子程序来完成
    实际的依赖于硬件的数据接收工作,并在接收完成后通过函数netif_rx()
    [net/core/dev.c]将收到的数据包往上层传。数据接收子程序的内容可以由以下四
    点来概括:

    1. 申请skb缓冲区给新的数据包存储;
    2. 从硬件中读取新到达的数据;
    3. 调用netif_rx(),将新的数据包往网络协议的上一层传送;
    4. 修改接口的统计数据。

    有上几节的网络设备的初始化分析可知,NE2000中断服务程序ei_interrupt(),
    同时我们还将发现NE2000的数据接收子程序是ei_receive()。下面我们将继续以
    NE2000为例,对网络接口的接收过程进行分析。

    /* from drivers/net/8390.c drivers/net/8390.h */
    /* 中断服务程序 */
    void ei_interrupt(int irq, void *dev_id, struct pt_regs * regs)
    {
        struct device *dev = (struct device *)(irq2dev_map[irq]);
        …………    
        e8390_base = dev->base_addr;
        ei_local = (struct ei_device *) dev->priv;
        if (dev->interrupt || ei_local->irqlock) {
            /* 有其他进程运行中断服务程序 */
            return;
        }
        /* 打开中断处理标志,阻止再次进入 */
        dev->interrupt = 1;
        …………    
        /* 读取中断状态寄存器(RD WR)*/
        while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0
            && ++nr_serviced < MAX_SERVICE) {
            …………
                if (interrupts & ENISR_OVER) { /* ENISR_OVER 接收overrun标志 */
                /* 接收超出物理设备的承受能力 */
                ei_rx_overrun(dev); /* 恢复设备的正确状态 */
            } else if (interrupts & (ENISR_RX+ENISR_RX_ERR)) {
                /* ENISR_RX 正确接收标志;ENISR_RX_ERR 接收有错标志 */
                /* 接收到数据包,包括正确接收和接收出错 */
                ei_receive(dev); /* 从物理设备的缓存中取出数据 */
            }
            if (interrupts & ENISR_TX) {
                /* 正确发送了数据包, */
                ei_tx_intr(dev); /* 为下一次传输做准备 */
            } else if (interrupts & ENISR_TX_ERR) {
                /* 发送数据包的过程中出错 */
                ei_tx_err(dev); /* 错误处理 */
            }
            …………
        }
        
        …………
        /* 关闭中断处理标志 */
        dev->interrupt = 0;
        return;
    }

    /* 数据接收子程序,被中断服务程序所调用 */
    static void ei_receive(struct device *dev)
    {
        …………
        /* 申请物理空间--skb缓冲区,为读取新的数据包作准备 */
        skb = dev_alloc_skb(pkt_len+2);
        …………
        /* 由于MAC头是14字节的,为了与 IP头的16字节对齐规则 一致,
     * 特意保留了2字节 */
        skb_reserve(skb,2);
        skb->dev = dev;
        /* 腾出逻辑空间给新的数据包 */
        skb_put(skb, pkt_len);  /* Make room */
        /* 从硬件中读取新到达的数据 */
        ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame));
        skb->protocol=eth_type_trans(skb,dev);
        /* 通过netif_rx函数,将收到的数据包往网络协议的上一层传送 */
        netif_rx(skb);
        /* 修改接口的统计数据。*/
        ei_local->stat.rx_packets++;
        …………
    }

    六. 总结--写网络设备驱动程序
    至此我们知道网络设备(或网络接口)是通过一个数据结构struct device来表示
    的。在系统中,每一个实际存在的物理网络设备都对应于一个device结构。而所有
    这些device结构联成一张链表并由一个全局变量指针dev_base指向表头,从而使系
    统能够随时得到每个网络接口的信息。
    概括来说,一个最简单的网络设备驱动程序,至少应该具有以下的内容:

    1. 该网络设备的检测及初始化函数,供核心启动初始化时调用
    2. 该网络设备的初始化函数,供register_netdev调用(可以写成与第1项的共用
    ,即用同一个);若是写成module兼容方式的,还需写该设备的init_module和
    cleanup_module函数;
    3. 提供该网络设备的打开和关闭操作。供设备被打开或被关闭时调用(一般用
    shell命令ifconfig调用);
    4. 提供该网络设备的数据传输函数,负责向硬件发送数据包。当上层协议需要传
    输数据时,供dev_queue_xmit调用;
    5. 提供该网络设备的中断服务程序,处理数据传输完毕的善后事宜和数据的接收
    。当物理网络设备有新数据到达或数据传输完毕时,将向系统发送硬件中断请求,
    该函数就是用来响应该中断请求的。

    有关以上的各个函数具体应该做什么事情,以及该如何写、为什么这样写等问题,
    请参考本文上面部分的分析和讨论。
    若本文所做的工作对那些对Linux网络设备驱动程序感兴趣的人有一点点启示,或
    一点点帮助,或能够加快到达深入了解Linux网络子系统的境界,那么作者也就感
    到心满意足了。本文中的错误固定不少J,若有兴趣,不妨飞鸽传书pg@ccnt.zju.
    edu.cn,让我们一起来讨论、学习。

    七. 建议及致谢
    1.  建议
    2.  致谢
    八. 参考文献
    [1].David A. Rusling,"The Linux Kernel"Version 0.8-3,1999
    [2].Alessandro Rubini,"Linux Device Drivers",O'Reily&Associates,USA
    ,1998
    [3].Michael K. Johnson,"Writing Linux Device Drivers",DECUS '95 in
    Washington,1995
    [4].Michael K. Johnson and Others,"Linux Kernel Hackers' Guide",
    1998
    [5].Ori Pomerantz,"Linux Kernel Module Programming Guide",1998
    九. 附录--一个虚拟的字符设备驱动程序
    下面是本人写的一个虚拟的字符设备的驱动程序,该字符设备简单地实现两个进程
    间的数据交流,该两个进程不需要同时在运行。即提供数据的进程只要已对设备写
    数据,即使该进程结束后,读数据的进程也可读取数据。
    我们的做法是建立两个同一类型的虚拟字符设备,一个是专门接收数据的设备
    drgnw,一个是专门提供数据的设备drgnr。同时我们简单地规定,不能同时有两个
    进程对设备进行读或对设备进行写。
    写这个字符设备驱动程序及建立该设备的基本步骤如下:

    1.  确定设备的设备名称和主设备号
    我们将这个虚拟的字符设备命名为drgn,主设备号定为30(在2.0.34的内核中还没
    有以30作为主设备号的字符设备)。以后我们的这个字符设备的操作函数都将以
    "drgn_"打头;

    2.  确定编写需要的file_operations中的操作函数及初始化函数,包括:
    void drgn_init(void)
    static int drgn_open(struct inode * inode,struct file * file)
    static void drgn_release(struct inode * inode,struct file * file)
    static int drgn_write(struct inode * inode,struct file * file,const char
     * buffer,int count)
    static int drgn_read(struct inode * inode , struct file * file,char *
    buffer, int count)
    static int drgn_ioctl(struct inode * inode, struct file * file,
    unsigned int cmd, unsigned long arg)
    3.  在drivers/char/mem.c中添加相应语句;
    在chr_dev_init函数之前添加drgn_init的原型说明:
    void drgn_init(void);
    在chr_dev_init函数的return语句之前添加以下语句:
    drgn_init(); /* added by XX */
    4.  修改drivers/char/Makefile;
    找到"L_OBJS   := tty_io.o n_tty.o console.o \"行,将"drgn.o"加到其中。
    5.  将该设备私有的*.c,*.h复制到目录drivers/char下。
    将drgn.c和drgn.h复制到drivers/char下
    6.  重新编译内核;
    用命令:make clean;make dep;make zImage
    7.  使新内核生效;
    8.  在目录/dev下建立相应的特殊文件。
    建立两个字符设备的特殊文件"drgnr"和"drgnw":
    mknod drgnw c 30 0
    mknod drgnr c 30 1
    9.  万事已OK!将这里提供的测试程序writeto.c和readfrom.c用gcc编译一下就可
    测试使用该设备啦。

    /* drgn.h */
    #ifdef  KERNEL

    #define TRACE_TXT(text) {if(drgn_trace) {console_print(text);
    console_print("\n");}}
    #define TRACE_CHR(chr) {if(drgn_trace) console_print(chr);}

    #define DRGN_READ  1
    #define DRGN_WRITE 0

    #endif

    #define FALSE 0
    #define TRUE  1
    #define MAX_BUF   120
    #define DRGN_TRON  (('M' << 8)|0x01)
    #define DRGN_TROFF (('M' << 8)|0x02)

    struct drgn_buf
    {
       int buf_size;
       char buffer[MAX_BUF];
       struct drgn_buf *link;
    };

    /* drgn.c */
    #define KERNEL

    #include <linux/kernel.h>
    #include <linux/sched.h>
    #include <linux/tty.h>
    #include <linux/signal.h>
    #include <linux/errno.h>
    #include <linux/malloc.h>
    #include <linux/mm.h>

    #include <asm/io.h>
    #include <asm/segment.h>
    #include <asm/system.h>
    #include <asm/irq.h>

    #include "drgn.h"

    static int drgn_trace;
    static int write_busy;
    static int read_busy;
    static struct drgn_buf * qhead;
    static struct drgn_buf * qtail;

    static int  drgn_read(struct inode * , struct file * , char * , int );
    static int  drgn_write(struct inode * , struct file * , const char *,
    int );
    static int  drgn_ioctl(struct inode * , struct file * , unsigned int ,
    unsigned long );
    static int  drgn_open(struct inode *,struct file *);
    static void drgn_release(struct inode *,struct file *);
    /* extern void console_print(char *);*/

    struct file_operations drgn_fops=
    {
       NULL,
       drgn_read,
       drgn_write,
       NULL,
       NULL,
       drgn_ioctl,
       NULL,
       drgn_open,
       drgn_release,
       NULL,
       NULL,
       NULL,
       NULL
    };

    void drgn_init(void)
    {
       drgn_trace=TRUE;
       
       if(register_chrdev(30,"drgn",&drgn_fops))
          TRACE_TXT("Cannot register drgn driver as major device 30.")
       else
          TRACE_TXT("Tiny devie driver registered successfully.")

       qhead=0;
       write_busy=FALSE;
       read_busy=FALSE;
     /*  drgn_trace=FALSE;*/
       return;
    }

    static int drgn_open(struct inode * inode,struct file * file)
    {
       TRACE_TXT("drgn_open")

       switch (MINOR(inode->i_rdev))
       {
          case DRGN_WRITE:
             if(write_busy)
                return -EBUSY;
             else{
                write_busy=TRUE;
                return 0;
             }
          case DRGN_READ:
             if(read_busy)
                return -EBUSY;
             else{
                read_busy=TRUE;
                return 0;
             }
          default:
             return -ENXIO;
       }
    }

    static void drgn_release(struct inode * inode,struct file * file)
    {
       TRACE_TXT("drgn_release")

       switch (MINOR(inode->i_rdev))
       {
          case DRGN_WRITE:
             write_busy=FALSE;
             return;
          case DRGN_READ:
             read_busy=FALSE;
             return;
       }
    }

    static int drgn_write(struct inode * inode,struct file * file,
                const char * buffer,int count)
    {
       int i,len;
       struct drgn_buf * ptr;
       
       TRACE_TXT("drgn_write")
       
       if (MINOR(inode->i_rdev)!=DRGN_WRITE)
          return -EINVAL;

       if ((ptr=kmalloc(sizeof(struct drgn_buf),GFP_KERNEL))==0)
          return -ENOMEM;

       len=count < MAX_BUF?count:MAX_BUF;

       if (verify_area(VERIFY_READ,buffer,len))
          return -EFAULT;

       for(i=0;i < count && i<MAX_BUF;++i)
       {
          ptr->buffer[i]=(char) get_user((char*)(buffer+i));
          TRACE_CHR("w")
       }

       ptr->link=0;

       if(qhead==0)
          qhead=ptr;
       else
          qtail->link=ptr;
       qtail=ptr;
       TRACE_CHR("\n")

       ptr->buf_size=i;
       return i;
    }

    static int drgn_read(struct inode * inode , struct file * file,
                 char * buffer, int count)
    {
       int i,len;
       struct drgn_buf * ptr;

       TRACE_TXT("drgn_read")

       if(MINOR(inode->i_rdev)!=DRGN_READ)
          return -EINVAL;
       
       if (qhead==0)
          return -ENODATA;

       ptr=qhead;
       qhead=qhead->link;

       len=count < ptr->buf_size?count:ptr->buf_size;

       if (verify_area(VERIFY_WRITE,buffer,len))
          return -EFAULT;

       for (i=0; i<count && i<ptr->buf_size; ++i)
       {
          put_user((char) ptr->buffer[i],(char *)(buffer+i));
          TRACE_CHR("r")
       }

       TRACE_CHR("\n")

       kfree_s(ptr,sizeof(struct drgn_buf));
       return i;
    }

    static int drgn_ioctl(struct inode * inode, struct file * file,
                 unsigned int cmd, unsigned long arg)
    {
       TRACE_TXT("drgn_ioctl")

    /*   if (cmd==DRGN_TRON){
           drgn_trace=TRUE;
           return 0;
       }
       else
           if (cmd==DRGN_TROFF){
               drgn_trace=FALSE;
               return 0;
           }
           else
               return -EINVAL;*/
       switch(cmd)
       {
          case DRGN_TRON:
             drgn_trace=TRUE;
             return 0;
          case DRGN_TROFF:
             drgn_trace=FALSE;
             return 0;
          default:
             return -EINVAL;
       }
    }

    /* writeto.c */
    #include <stdio.h>

    void main(int argc, char * argv[]){
    FILE *fp;

    if((fp=fopen("/dev/drgnw","w"))==NULL){
      printf("File Open Error!");
      return;
    }
    if (argc > 1)
      fputs(argv[1],fp);
    fclose(fp);
    }

    /* readfrom.c */
    #include <stdio.h>

    void main(){
    char ss[25]; /*you can change 25 to any number not greater than
    MAX_BUF */
    FILE * fp;

    if((fp=fopen("/dev/drgnr","r"))==NULL){
      printf("File open Error!");
      return;
    }
    fgets(ss,25,fp);
    printf("READ: %s\n",ss);
    fclose(fp);
    }
    展开全文
  • CentOS7 网络设置与控制网络设备名称

    千次阅读 2016-12-04 22:53:58
    安装完CentOS 7后,若安装时没有设置网络,...本文除了介绍如何手动设置网络外,也介绍如何更改网络设备名称。 在安装套件时,如果选择了”Minimal Install”,ifconfig/netstat等常用网络工具不会被安装,用”nmcl

    安装完CentOS 7后,若安装时没有设置网络,并发现所在的网络没有DHCP Server时,则需要在控制台下,编辑/etc/sysconfig/network-script/ifcfg-en* 。本文除了介绍如何手动设置网络外,也介绍如何更改网络设备名称。

    在安装套件时,如果选择了”Minimal Install”,ifconfig/netstat等常用网络工具不会被安装,用”nmcli c up ifname $interface”启用你的网络,然后再用yum安装”net-tools”。

    一、“en*”这个名称是在CentOS 7的Kernel里预制的,这里要把他改成“ens160”。

    [root@CentOS-7 ~]# cat /etc/sysconfig/network-scripts/ifcfg-ens160
    HWADDR=00:50:56:BE:48:0D
    TYPE=Ethernet
    BOOTPROTO=dhcp
    DEFROUTE=yes
    PEERDNS=yes
    PEERROUTES=yes
    IPV4_FAILURE_FATAL=yes
    IPV6INIT=yes
    IPV6_AUTOCONF=yes
    IPV6_DEFROUTE=yes
    IPV6_PEERDNS=yes
    IPV6_PEERROUTES=yes
    IPV6_FAILURE_FATAL=no
    NAME=ens160                                                              
    ONBOOT=no

    “静态分配”设置项:

    [root@CentOS-7 ~]#cat /etc/sysconfig/network-scripts/ifcfg-ens160
    NAME=ens160
    HWADDR=00:50:56:BE:48:0D
    ONBOOT=yes
    TYPE=Ethernet
    BOOTPROTO=static
    DEFROUTE=yes
    PEERDNS=yes
    PEERROUTES=yes
    IPV4_FAILURE_FATAL=yes
    IPV6INIT=no
    IPADDR=172.21.102.109
    PREFIX=24
    #   the GATEWAY is sometimes in: /etc/sysconfig/network
    GATEWAY=172.21.102.254
    DNS1=8.8.8.8
    DNS2=168.95.1.1

    另外,通用项目如主机名称和DNS服务器可选择性地放置在:

    [root@CentOS-7 ~]#cat /etc/sysconfig/network
    HOSTNAME=acme.example.com
    DNS1=10.16.1.112
    DNS2=8.8.8.8
    ## DNS2=76.242.0.28
    SEARCH=example.com

    二、使用传统名称 eth0,1,2…:

    1.编辑 /etc/default/grub 将 “net.ifnames=0” 与 “net.ifnames=0” 加进GRUB_CMDLINE_LINUX后双引号内最后面。

    GRUB_TIMEOUT=5
    GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
    GRUB_DEFAULT=saved
    GRUB_DISABLE_SUBMENU=true
    GRUB_TERMINAL_OUTPUT="console"
    GRUB_CMDLINE_LINUX="vconsole.keymap=us crashkernel=auto
     vconsole.font=latarcyrheb-sun16 rhgb quiet net.ifnames=0 biosdevname=0"
    GRUB_DISABLE_RECOVERY="true"

    2.然后运行以下命令来重新生成GRUB配置与更新的内核参数。

    grub2-mkconfig -o /boot/grub2/grub.cfg

    centos7_net

    3.在 /etc/sysconfig/network-scripts/ 內把你的网卡设置名称改为ifcfg-ethX,并设置ifcfg-eth0 DEVICE=eth0

    centos7_net

    4.重启,就可以设置初始的网络名称。

    centos7_net

    三、如果你想设置网络设备名称,儿不是由系统的Kernel决定,则需要通过/etc/udev/rules.d/60-net.rules覆盖/usr/lib/udev/rules.d/60-net.rules

    1.完成了上面的步骤,执行下面的命令:

    [root@centos-7 ~]#cp -a /usr/lib/udev/rules.d/60-net.rules
     /etc/udev/rules.d/60-net.rules
    
    [root@centos-7 ~]#vi /etc/udev/rules.d/60-net.rules
    #ACTION=="add", SUBSYSTEM=="net", DRIVERS=="?*", ATTR{type}=="1",
     PROGRAM="/lib/udev/rename_device", RESULT=="?*", NAME="$result"
    ACTION=="add", SUBSYSTEM=="net", DRIVERS=="?*", ATTR{type}=="1",
    ATTR{address}=="00:50:56:be:48:0d" , KERNEL=="eth*" , NAME="nic0"
    
    [root@centos-7 ~]#vi /etc/sysconfig/network-script/ifcfg-eth0
    DEVICE=eth0 => DEVICE=nic0
    
    [root@centos-7 ~]# reboot

    centos7_net

    如果网络配置(如,IPv4 setting,firewall rules)是基于旧名称(更改前),则需要更新的网络配置,以显示新更改的名称。



    全文:http://www.androidstar.cn/centos7-网络设置与控制网络设备名称/

    展开全文
  • Linux网络设备驱动架构

    千次阅读 2018-02-01 23:10:09
    Linux网络设备驱动程序体系结构分为四层:网络协议接口层、网络设备接口层、提供实际功能的设备驱动层以及网络设备与媒介层。 (1)网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是...

    https://www.cnblogs.com/laoyaodada/p/8397590.html

    Linux网络设备驱动程序体系结构分为四层:网络协议接口层、网络设备接口层、提供实际功能的设备驱动层以及网络设备与媒介层。

    (1)网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。

    (2)网络设备接口层向协议接口层提供的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层各函数的容器。

    (3)设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过nto_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。

    (4)网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。

    驱动工程师的工作:在设计具体的网络设备驱动程序时,需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册入内核。

    1  网络协议接口层

    网络协议接口层最主要的功能是给上层协议提供透明的数据包发送和接收接口。当上层ARP或IP需要发送数据包时,它将调用网络协议接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向struct sk_buff数据结构的指针。dev_queue_xmit()函数的原形为:

    int dev_queue_xmit(struct sk_buff *skb);

    上层通过对数据包的接收也通过向netif_rx()函数传递一个struct sk_buff数据结构的指针来完成。netif_rx()函数的原形为:

    int netif_rx(struct sk_buff *skb);

    sk_buff定义于include/linux/skbuff.h文件中,含义为“套接字缓冲区”用于在Linux网络子系统各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。

    当发送数据包时,Linux内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff递交给上层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒体上接收数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。

    复制代码
     1 struct sk_buff { 2     struct sk_buff      *next;    // sk_buff是双向链表,所以有前去后继,这是指向后面的sk_buff结构体指针 3     struct sk_buff      *prev;    // 这是指向前一个sk_buff结构体指针 4     ... 5     unsigned int        len,    // 表示数据区的长度(tail-data)与分片结构体数据区的长度之和。 6                 data_len;    // 只表示分片结构体数据区的长度,所以len=(tail - data) + data_len; 7     __u16           mac_len,    // mac报头的长度 8                 hdr_len;    // 用于clone时,表示clone的skb的头长度 9     ...10     __u32           priority;    // 优先级,主要用于QOS11     ...12     __be16          protocol;    // 包的协议类型,标识是IP包还是ARP包还是其他数据包13 14     ...15 16     __be16          inner_protocol;17     __u16           inner_transport_header;    18     __u16           inner_network_header;    19     __u16           inner_mac_header;    20     __u16           transport_header;    // 指向传输包头21     __u16           network_header;    // 指向传输层包头22     __u16           mac_header;    // 指向链路层包头23     /* These elements must be at the end, see alloc_skb() for details.  */24     sk_buff_data_t      tail;25     sk_buff_data_t      end;    // 数据缓冲区的结束地址26     unsigned char       *head,    // 数据缓冲区的开始地址27                 *data;    // 28     ...29 };
    复制代码

    【温馨提示】head和end指向缓冲区的头部和尾部,而data和tail指向实际数据的头部和尾部。每一层会在head和data之间填充协议头,或者在tail和end之间添加协议数据。

     

    下面分析套接字缓冲区涉及的操作函数,Linux套接字缓冲区支持分配、释放、变更等功能函数。

    (1)分配:

    Linux内核中用于分配套接字缓冲区的函数有:

    函数原形

    struct sk_buff *alloc_skb(unsigned int len, gfp_t priority);

    struct sk_buff *dev_alloc_skb(unsigned len);

    函数参数

    len:为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对于ARM为32)对齐

    priority:为内存分配的优先级

    返回值

    成功:返回分配好的sk_buff指针;失败:返回NULL

    【温馨提示】dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。

    (2)释放:

    Linux内核内部用于释放套接字缓冲区的函数有:

    函数原形

    void kfree_skb(struct sk_buff *skb);

    void dev_kfree_skb(struct sk_buff *skb);

    void dev_kfree_skb_irq(struct sk_buff *skb);

    void dev_kfree_skb_any(struct sk_buff, *skb);

    函数参数

    sk_buff:套接字缓冲区

    Linux内核内部使用kree_skb()函数,而在网络设备驱动程序中则最好用dev_kfree_skb()、dev_kfree_skb_irq()或dev_kfree_skb_any()函数进行套接字缓冲区的释放。其中,dev_kfree_skb()函数用于非中断上下文,dev_kfree_skb_irq()函数用于中断上下文,而dev_kfree_skb_any()函数在中断和非中断上下文都可采用。

    (3)变更

    在Linux内核中可以用如下函数在缓冲区尾部增加数据:

    unsigned char *skb_put(struct sk_buff *skb, unsigned int len);

    它会导致skb->tail后移len(skb->tail += len),而skb->len会增加len的大小(skb->len += len)。通常,在设备驱动的接收数据处理中会调用此函数。

    在Linux内核中可以用以下函数在缓冲区开头增加数据:

    unsigned char *skb_push(struct sk_buff *skb, unsigned int len);

    它会导致skb->data前移len(skb->data -= len),而skb->len会增加len的大小(skb->len += len)。

    对于一个空的缓冲区而言,调用如下函数可以调整缓冲区的头部:

    static inline void skb_reserve(struct sk_buff *skb, int len);

    它会将skb->data和skb->tail同时后移len,执行skb->data += len、skb->tail += len。内核里存在许多这样的代码:

    skb = alloc_skb(len + headspace, GFP_KERNEL);skb_reserve(skb, headspace);skb_put(skb, len);memcpy_fromfs(skb->data, data, len);pass_to_m_protocol(skb);

    上述代码先分配一个全新的sk_buff,接着调用skb_reserve()腾出头部空间,之后调用skb_put()腾出数据空间,然后把数据复制进来,最后把sk_buff传给协议栈。

    2  网络设备接口层

    网络设备接口层的主要功能是为千变万化的网络设备定义统一、抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。

    net_device结构体在内核中指代一个网络设备,它定义在include/linux/netdevice.h文件中,网络设备驱动程序只需通过填充net_device的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。

    (1)全局信息

    char name[IFNAMESIZ];    // name是网络设备名

    (2)硬件信息

    复制代码
    unsigned long mem_end;    // 设备使用的共享内存的结束地址unsigned long mem_start;    // 设备使用的共享内存的起始地址unsigned long base_addr;    // base_addr为网络设备I/O基地址unsigned char irq;    // irq为设备使用的中断号unsigned char if_port;    // 指定多端口设备使用哪一个端口,该字段仅针对多端口设备。例如,如果设备同时支持IF_PORT_10BASE2(同轴电缆)和IF_PORT_10BASET(双绞线),则可使用该字段unsigned char dma;    // dma指定分配给设备的DMA通道
    复制代码

    (3)接口信息

    unsigned short hard_header_len;    // 网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋值为ETH_HLEN,即14unsigned short type;    // 接口的硬件类型unsigned mtu;    // 最大传输单元(MTU)unsigned char *dev_addr;    // 用于存放设备的硬件地址,驱动可能提供了设置MAC地址的接口,这会导致用户设置的MAC地址等存入该成员

    dev_addr范例使用代码:

    复制代码
     1 static int moxart_set_mac_address(struct net_device *ndev, void *addr) { 2     struct sockaddr *address = addr; 3  4     if (!is_valid_ether_addr(address->sa_data)) { 5         return -EADDRNOTAVAIL; 6     } 7     memcpy(ndev->dev_addr, address->sa_data, ndev->addr_len); 8     moxart_update_mac_address(ndev); 9 10     return 0;11 }
    复制代码

    接口信息继续:

    unsigned short flags;    // 网络接口标志

    网络接口标志以IFF_开头,部分标志由内核来管理,其他的在接口初始化时被设置以说明设备接口的能力和特性。接口标志包括:

    复制代码
    IFF_UP(当设备被激活并可以开始发送数据包时,内核设置该标志)IFF_AUTOMEDIA(设备可在多种媒介间切换)IFF_BROADCAST(允许广播)IFF_DEBUG(调试模式,可用于控制prink调用的详细程度)IFF_LOOPBACK(回环)IFF_MULTICAST(允许组播)IFF_NOARP(接口不能执行ARP)IFF_POINTOPOINT(接口连接到点到点链路)
    复制代码

    (4)设备操作函数

    const struct net_device_ops *netdev_ops;

    具体内容为:

    复制代码
     1 struct net_device_ops { 2     int         (*ndo_init)(struct net_device *dev); 3     void            (*ndo_uninit)(struct net_device *dev); 4     int         (*ndo_open)(struct net_device *dev); 5     int         (*ndo_stop)(struct net_device *dev); 6     netdev_tx_t     (*ndo_start_xmit) (struct sk_buff *skb, 7                            struct net_device *dev); 8     u16         (*ndo_select_queue)(struct net_device *dev, 9                             struct sk_buff *skb,10                             void *accel_priv,11                             select_queue_fallback_t fallback);12     void            (*ndo_change_rx_flags)(struct net_device *dev,13                                int flags);14     void            (*ndo_set_rx_mode)(struct net_device *dev);15     int         (*ndo_set_mac_address)(struct net_device *dev,16                                void *addr);17     int         (*ndo_validate_addr)(struct net_device *dev);18     int         (*ndo_do_ioctl)(struct net_device *dev,19                             struct ifreq *ifr, int cmd);20 ...21 };
    复制代码

    ndo_open()函数的作用是打开网络接口设备,获得设备需要的I/O地址、IRQ、DMA通道等。stop()函数的作用是停止网络接口设备,与open()函数的作用相反。

    int (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);

    ndo_start_xmit()函数会启动数据包的发送,当系统调用驱动程序的xmit函数时,需要向其传入一个sk_buff结构体指针,以使得驱动程序能获取从上层传递下来的数据包。

    void (*ndo_tx_timeout) (struct net_device *dev);

    当数据包的发送超时时,ndo_tx_timeout()函数会被调用,该函数需采取重新启动数据包发送过程或重新启动硬件等措施来恢复网络设备到正常状态。

    struct net_device_status* (*ndo_get_stats)(struct net_device *dev);

    ndo_get_status()函数用于获得网络设备的状态信息,它返回一个net_device_stats结构体指针。net_device_stats结构体保存了详细的网络设备流量统计信息,如发送和接收的数据包数、字节数等。

    int (*ndo_do_ioctl) (struct net_device *dev, struct ifreq *ifr, int cmd);int (*ndo_set_config) (struct net_device *dev, struct ifmap *map);int (*ndo_set_mac_address) (struct net_device *dev, void *adddr);

    ndo_do_ioctl()函数用于进行设备特定的I/O控制。

    ndo_set_config()函数用于配置接口,也可用于改变设备的I/O地址和中断号。

    ndo_set_mac_address()函数用于设置设备的MAC地址。

    除了netdev_ops以外,在net_device中还存在类似于ethool_ops、header_ops这样的操作集:

    const struct ethtool_ops *ethool_ops;const struct header_ops *header_ops;

    ethool_ops成员函数与用户空间ethool工具的各个命令选项对应,ethool提供了网卡及网卡驱动管理能力,能够为Linux网络开发人员和管理人员提供对网卡硬件、驱动程序和网络协议栈的设置、查看以及调试等功能。

    header_ops对应于硬件头部操作,主要是完成创建硬件头部和从给定的sk_buff分析出硬件头部等操作。

    (5)辅助成员

    unsigned long trans_start;unsigned long last_rx;

    trans_start记录最后的数据包开始发送时的时间戳,last_rx记录最后一次接收到数据包时的时间戳,这两个时间戳记录的都是jiffies,驱动程序应维护这两个成员。

    通常情况下,网络设备驱动以中断方式接收数据包,而poll_controller()则采用纯轮询方式,另外一种数据接收方式是NAPI(New API),其数据接收流程为“接收中断来临->关闭接收中断->以轮询方式接收所有数据包直到收空->开启接收中断->接收中断来临……”,内核提供了如下与NAPI相关的API:

    void netif_napi_add(struct net_device *dev, struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight);void netif_napi_del(struct napi_struct *napi);

    以上两个函数分别用于初始化和移除一个NAPI,netif_napi_add()的poll参数是NAPI要调度执行的轮询函数。

    static inline void napi_enable(struct napi_struct *n);static inline void napi_disable(struct napi_struct *n);

    以上两个函数分别用于使能和禁止NAPI调度。

    该函数用于检查NAPI是否可以调度,而napi_schedule()函数用于调度轮询实例的运行。

    其原形为:

    static inline void napi_schedule(struct napi_struct *n);

    在NAPI处理完成的时候应该调用:

    void napi_complete(struct napi_struct *n);

    3  设备驱动功能层

    net_device结构体的成员(属性和net_device_ops结构体中的函数指针)需要被设备驱动功能层赋予具体的数值和函数。对于具体的设备xxx,工程师应该编写相应的设备驱动功能层的函数,这些函数形如xxx_open()、xxx_stop()、xxx_tx()、xxx_hard_header()、xxx_get_stats()和xxx_tx_timeout()等。

    由于网络数据包的接收可由中断引发,设备驱动功能层的另一个主体部分将是中断处理函数,它负责读取硬件上接收到的数据包并传送给上层协议,因此可能包含xxx_interrupt()和xxx_rx()函数,前者完成中断类型判断等基本工作,后者则需完成数据包的生成及将其递交给上层等复杂工作。

    对于特定的设备,我们还可以定义相关的私有数据和操作,并封装为一个私有信息结构体xxx_private,让其指针赋值给net_device的私有成员。在xxx_private结构体中可包含设备的特殊属性和操作、自旋锁与信号量、定时器以及统计信息等,这都由工程师自定义。在驱动中,要用到私有数据的时候,则使用在netdevice.h中定义的接口:

    static inline void *netdev_priv(const struct net_device *dev);

    比如在驱动drivers/net/ethernet/davicom/dm9000.c的dm9000_probe()函数中,使用alloc_etherdev(sizeof(struct board_info))分配网络设备,board_info结构体就成了这个网络设备的私有数据,在其他函数中可以简单地提取这个私有数据。例如:

    1 static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) {2     unsigned long flags;3     board_info_t *db = netdev_priv(dev);4     ...5 }
    展开全文
  • 网络安全等级保护网络设备、安全设备知识点汇总 本文主要内容: 防火墙、防毒墙、入侵防御、统一安全威胁网关UTM IPSEC VPN、网闸、SSL VPN、WAF 网络安全审计、数据库安全审计、日志审计、运维安全...

    导读:之前推荐过一篇等级保护安全设备配置方案,那么,这些安全设备都是什么样的设备呢?有什么功能?如何部署?给大家推荐这篇安全设备知识点汇总。

     

    网络安全等级保护网络设备、安全设备知识点汇总

    本文主要内容:

    • 防火墙、防毒墙、入侵防御、统一安全威胁网关UTM

    • IPSEC VPN、网闸、SSL VPN、WAF

    • 网络安全审计、数据库安全审计、日志审计、运维安全审计(堡垒机)

    • 入侵检测(IDS)、上网行为管理、负载均衡

    • 漏洞扫描、异常流量清洗、VPN

     

    1防火墙( Firewall)

     

    定义:相信大家都知道防火墙是干什么用的,我觉得需要特别提醒一下,防火墙抵御的是外部的攻击,并不能对内部的病毒 ( 如ARP病毒 ) 或攻击有什么太大作用。

     

    功能:防火墙的功能主要是两个网络之间做边界防护,企业中更多使用的是企业内网与互联网的NAT、包过滤规则、端口映射等功能。生产网与办公网中做逻辑隔离使用,主要功能是包过滤规则的使用。

     

    部署方式:网关模式、透明模式:

    网关模式是现在用的最多的模式,可以替代路由器并提供更多的功能,适用于各种类型企业透明部署是在不改变现有网络结构的情况下,将防火墙以透明网桥的模式串联到企业的网络中间,通过包过滤规则进行访问控制,做安全域的划分。 

    至于什么时候使用网关模式或者使用透明模式,需要根据自身需要决定,没有绝对的部署方式。需不需要将服务器部署在 DMZ区,取决于服务器的数量、重要性。

    总之怎么部署都是用户自己的选择!

    高可用性:为了保证网络可靠性,现在设备都支持主 - 主、主- 备,等各种部署。

     

    2防毒墙

     

    定义:相对于防毒墙来说,一般都具有防火墙的功能,防御的对象更具有针对性,那就是病毒。

     

    功能:同防火墙,并增加病毒特征库,对数据进行与病毒特征库进行比对,进行查杀病毒。

     

    部署方式:同防火墙,大多数时候使用透明模式部署在防火墙或路由器后或部署在服务器之前,进行病毒防范与查杀。

     

    3入侵防御 (IPS)

     

    定义:相对于防火墙来说,一般都具有防火墙的功能,防御的对象更具有针对性, 那就是攻击。

    防火墙是通过对五元组进行控制,达到包过滤的效果,而入侵防御 IPS,则是将数据包进行检测 (深度包检测 DPI)对蠕虫、病毒、木马、拒绝服务等攻击进行查杀。

     

    功能:同防火墙,并增加 IPS 特征库,对攻击行为进行防御。

     

    部署方式:同防毒墙。

    特别说明一下:防火墙允许符合规则的数据包进行传输,对数据包中是否有病毒代码或攻击代码并不进行检查,而防毒墙和入侵防御则通过更深的对数据包的检查弥补了这一点。

     

    4统一威胁安全网关 (UTM)

     

    定义:简单的理解,把威胁都统一了,其实就是把上面三个设备整合到一起了。

     

    功能:同时具备防火墙、防毒墙、入侵防护三个设备的功能。

     

    部署方式:因为可以代替防火墙功能,所以部署方式同防火墙

    现在大多数厂商,防病毒和入侵防护已经作为防火墙的模块来用,在不考虑硬件性能以及费用的情况下,开启了防病毒模块和入侵防护模块的防火墙,和UTM其实是一样的。至于为什么网络中同时会出现 UTM和防火墙、防病毒、入侵检测同时出现。

    第一,实际需要,在服务器区前部署防毒墙, 防护外网病毒的同时,也可以检测和防护内网用户对服务器的攻击。

    第二,花钱,大家都懂的。

    总之还是那句话,设备部署还是看用户。

     

    5IPSEC VPN

     

    把 IPSECVPN放到网络安全防护里,其实是因为大多数情况下, IPSEC VPN的使用都是通过上述设备来做的,而且通过加密隧道访问网络,本身也是对网络的一种安全防护。

     

    定义:采用 IPSec 协议来实现远程接入的一种 VPN技术,至于什么是 IPSEC 什么是 VPN,小伙伴们请自行百度吧。

     

    功能:通过使用 IPSECVPN使客户端或一个网络与另外一个网络连接起来,多数用在分支机构与总部连接。

     

    部署方式:网关模式、旁路模式

    鉴于网关类设备基本都具备 IPSECVPN功能,所以很多情况下都是直接在网关设备上启用 IPSEC VPN功能,也有个别情况新购买IPSEC VPN设备,在对现有网络没有影响的情况下进行旁路部署,部署后需要对IPSECVPN设备放通安全规则,做端口映射等等。

    也可以使用 windows server 部署 VPN,需要的同学也请自行百度,相比硬件设备,自己部署没有什么花费,但 IPSECVPN受操作系统影响,相比硬件设备稳定性会差一些。

     

    以上设备常见厂家( 不排名啊不排名 )

    JuniperCheck Point Fortinet (飞塔)思科 天融信 山石网科 启明星辰 深信服 绿盟网御星云 网御神州 华赛 梭子鱼 迪普H3C

     

    6网闸

     

    定义:全称安全隔离网闸。安全隔离网闸是一种由带有多种控制功能专用硬件在电路上切断网络之间的链路层连接, 并能够在网络间进行安全适度的应用数据交换的网络安全设备。

     

    功能:主要是在两个网络之间做隔离并需要数据交换,网闸是具有中国特色的产品。

     

    部署方式:两套网络之间

    防火一般在两套网络之间做逻辑隔离,而网闸符合相关要求,可以做物理隔离,阻断网络中 tcp 等协议,使用私有协议进行数据交换,一般企业用的比较少,在对网络要求稍微高一些的单位会用到网闸。

     

    7SSL VPN

    定义:采用 SSL协议的一种 VPN技术,相比 IPSEC VPN使用起来要更加方便,毕竟 SSL VPN使用浏览器即可使用。

     

    功能:随着移动办公的快速发展,SSL VPN的使用也越来越多,除了移动办公使用,通过浏览器登录SSLVPN连接到其他网络也十分方便, IPSEC VPN更倾向网络接入,而 SSL VPN更倾向对应用发布。

     

    部署:SSL VPN的部署一般采用旁路部署方式,在不改变用户网络的状况下实现移动办公等功能。

     

    8WAF (Web Application Firewall) web 应用防护系统

     

    定义:名称就可以看出,WAF的防护方面是 web应用,说白了防护的对象是网站及 B/S 结构的各类系统。

     

    功能:针对 HTTP/HTTPS协议进行分析,对 SQL注入攻击、XSS攻击 Web攻击进行防护,并具备基于 URL 的访问控制; HTTP协议合规;Web敏感信息防护;文件上传下载控制;Web 表单关键字过滤。网页挂马防护,Webshell 防护以及 web应用交付等功能。

     

    部署:通常部署在 web应用服务器前进行防护IPS也能检测出部分web攻击,但没有WAF针对性强,所以根据防护对象不同选用不同设备,效果更好。

     

    9网络安全审计

     

    定义:审计网络方面的相关内容。

     

    功能:针对互联网行为提供有效的行为审计、内容审计、行为报警、行为控制及相关审计功能。

    满足用户对互联网行为审计备案及安全保护措施的要求,提供完整的上网记录,便于信息追踪、系统安全管理和风险防范。

     

    部署:采用旁路部署模式,通过在核心交换机上设置镜像口,将镜像数据发送到审计设备。

     

    10数据库安全审计

     

    定义:数据库安全审计系统主要用于监视并记录对数据库服务器的各类操作行为。

     

    功能:审计对数据库的各类操作,精确到每一条 SQL命令,并有强大的报表功能。

     

    部署:采用旁路部署模式,通过在核心交换机上设置镜像口,将镜像数据发送到审计设备。

     

    11日志审计

     

    定义:集中采集信息系统中的系统安全事件、用户访问记录、系统运行日志、系统运行状态等各类信息,经过规范化、过滤、归并和告警分析等处理后,以统一格式的日志形式进行集中存储和管理,结合丰富的日志统计汇总及关联分析功能,实现对信息系统日志的全面审计。

     

    功能:通过对网络设备、安全设备、主机和应用系统日志进行全面的标准化处理,及时发现各种安全威胁、异常行为事件,为管理人员提供全局的视角,确保客户业务的不间断运营安全部署:旁路模式部署。通常由设备发送日志到审计设备,或在服务器中安装代理,由代理发送日志到审计设备。

     

    12运维安全审计 ( 堡垒机 )

     

    定义:在一个特定的网络环境下, 为了保障网络和数据不受来自内部合法用户的不合规操作带来的系统损坏和数据泄露,而运用各种技术手段实时收集和监控网络环境中每一个组成部分的系统状态、安全事件、网络活动,以便集中报警、记录、分析、处理的一种技术手段。

     

    功能:主要是针对运维人员维护过程的全面跟踪、 控制、记录、回放,以帮助内控工作事前规划预防、事中实时监控、违规行为响应、事后合规报告、事故追踪回放。

     

    部署:旁路模式部署。使用防火墙对服务器访问权限进行限制,只能通过堡垒机对网络设备/服务器/数据库等系统操作。

    可以看出审计产品最终的目的都是审计,只不过是审计的内容不同而已,根据不同需求选择不同的审计产品,一旦出现攻击、非法操作、违规操作、误操作等行为,对事后处理提供有利证据。

     

    13    入侵检测( IDS)

     

    定义:对入侵攻击行为进行检测

     

    功能:通过对计算机网络或计算机系统中若干关键点收集信息并对其进行分析, 从中发现网络或系统中是否有违反安全策略的行为和被攻击的迹象

     

    部署:采用旁路部署模式,通过在核心交换机上设置镜像口,将镜像数据发送到入侵检测设备。

    入侵检测虽然是入侵攻击行为检测, 但监控的同时也对攻击和异常数据进行了审计,所以把入侵检测系统也放到了审计里一起介绍。

    入侵检测系统是等保三级中必配设备。

     

    14上网行为管理

     

    定义:顾名思义,就是对上网行为进行管理

     

    功能:对上网用户进行流量管理、上网行为日志进行审计、对应用软件或站点进行阻止或流量限制、关键字过滤等

     

    部署:网关部署、透明部署、旁路部署

    网关部署:中小型企业网络较为简单,可使用上网行为管理作为网关,代替路由器或防火墙并同时具备上网行为管理功能

    透明部署:大多数情况下,企业会选择透明部署模式,将设备部署在网关与核心交换之间,对上网数据进行管理

    旁路部署:仅需要上网行为管理审计功能时,也可选择旁路部署模式,在核心交换机上配置镜像口将数据发送给上网行为管理

    个人觉得上网行为管理应该属于网络优化类产品,流控功能是最重要的功能,随着技术的发展,微信认证、防便携式 wifi 等功能不断完善使之成为了网络管理员的最爱 ~

     

    15负载均衡

     

    定义:将网络或应用多个工作分摊进行并同时完成,一般分为链路负载和应用负载 ( 服务器负载 )

     

    功能:确保用户的业务应用能够快速、安全、可靠地交付给内部员工和外部服务群,扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性

     

    部署:旁路模式、网关模式、代理模式

    旁路模式:通常使用负载均衡进行应用负载时,旁路部署在相关应用服务器交换机上,进行应用负载

    网关模式:通常使用链路负载时,使用网关模式部署

    随着各种业务的增加,负载均衡的使用也变得广泛, web应用负载,数据库负载都是比较常见的服务器负载。鉴于国内运营商比较恶心,互联互通问题较为严重,使用链路负载的用户也比越来越多。

     

    16漏洞扫描

     

    定义:漏洞扫描是指基于漏洞数据库,通过扫描等手段对指定的远程或者本地计算机系统的安全脆弱性进行检测,发现可利用的漏洞的一种安全检测(渗透攻击)行为。

     

    功能:根据自身漏洞库对目标进行脆弱性检测,并生产相关报告,提供漏洞修复意见等。一般企业使用漏洞扫描较少,主要是大型网络及等、分保检测机构使用较多。

     

    部署:旁路部署, 通常旁路部署在核心交换机上, 与检测目标网络可达即可。

     

    17异常流量清洗

     

    定义:看名字吧

     

    功能:对异常流量的牵引、 DDoS流量清洗、 P2P带宽控制、流量回注,也是现有针对 DDOS攻击防护的主要设备

     

    部署:旁路部署

     

    18VPN

     

    定义:VPN ( Virtual Private Network )虚拟专用网络 ,简单的讲就是因为一些特定的需求, 在网络与网络之间,或终端与网络之间建立虚拟的专用网络,通过加密隧道进行数据传输 ( 当然也有不加密的 ) ,因其部署方便且具有一定的安全保障,被广泛运用到各个网络环境中。

     

    VPN分类

    常见的VPN有L2TP VPN 、PPTP VPN、IPSEC、VPN、SSL、VPN以及 MPLS VPN。

    MPLS VPN运营商使用较多,使用场景多为总部与分支机构之间。其他VPN因部署方便,成本较低成为现在使用最为广泛的VPN。

    L2TP VPN与 PPTP VPN因使用的隧道协议都属于二层协议,所以也称为二层 VPN。IPSEC VPN则采用IPSec 协议,属于三层VPN。

    SSLVPN是以 HTTPS协议为基础 VPN技术,使用维护简单安全,成为 VPN技术中的佼佼者

     

    VPN部署与实现方式及应用场景介绍

    1、部署模式主要采用网关模式和旁路模式部署两种,使用专业VPN硬件设备建立VPN时多为旁路部署模式, 对现有网络不需要改动, 即使 VPN设备出现问题也不会影响现有网络。使用防火墙 / 路由器自带VPN功能建立 VPN时, 随其硬件部署模式部署。

    2、实现方式主要有以下几种 :

    (1) 通过使用专业 VPN软件来建立VPN

    优点 : 投入较少,部署比较灵活

    缺点 : 稳定性受服务器硬件、操作系统等因素影响

    (2) 通过使用服务器自行架设 VPN服务器建立 VPN,windows 服务器为主

    优点 : 投入较少,部署比较灵活, windows 系统自带

    缺点 : 稳定性受服务器硬件、操作系统等因素影响,需自行配置

    (3) 通过使用防火墙 / 路由器设备自带 VPN功能建立 VPN。

    优点 : 投入较少,稳定性好

    缺点 : 因使用现有设备自带 VPN模块,对 VPN访问量较大的需求不建议使用,以免出现设备性能不足影响防火墙 / 路由器使用。作为现有设备的自带模块,无法满足用户特殊要求。部署方式相对固定

    (4) 通过使用专业的 VPN硬件设备建立 VPN。

    优点 : 产品成熟适用于各种 VPN场景应用,功能强大,性能稳定。

    缺点 : 比较花钱 ~~~~~硬件设备需要花钱, 用户授权需要花钱, 反正啥都需要花钱

    3、让更多人纠结的应该是在何场景下选择哪种方式来建立VPN,根据本人工作经验为大家介绍一些常见的 VPN应用场景。

    场景一 总部与分支机构互联

    大型企业总部与分支结构通常使用 MPLS VPN进行互联,相对于大型企业中小型企业则选择使用投入较低的 IPSECVPN进行互联。

    例如 : 某大型超配 ( 超市配送 ) 企业,总部与自营大型超市之间使用MPLS VPN进行数据传输,总部与自营小型超市则使用 IPSEC VPN进行数据传输。根据各超市数据传输需要选择不同的 vpn 技术相结合使用,节约投入成本的同时最大限度的满足与总部的数据传输。

    场景二 移动办公

    网管远程维护设备、 员工访问内网资源、 老总出差电子签批等移动办公已成为企业信息化建设的重要组成部分,同时也为VPN市场带来了巨大商机。 SSL VPN因其使用维护方便成为了移动办公的不二之选,打开浏览器即可使用。在没有 SSLVPN条件下,网管进行设备远程维护则通常使用 L2TP VPN或 PPTP VPN。

    场景三 远程应用发布

    SSLVPN中的功能,主要针对需要安装客户端的 C/S服务访问需求,手机和平板因屏幕大小、鼠标、键盘使用等因素体验感欠佳。特定场景下 PC访问效果较好。

    例如 : 某公司财务部门对使用的财务软件做远程应用发布,其他用户无需安装财务软件客户端也可以方便使用。

    场景四 手机APP与 VPN结合

    随着手机的普及,大家都希望通过手机能解决很多工作中的问题,手机 APP解决了远程应用发布中存在的一些使用问题, 但考虑到安全方面的问题,用户希望通过手机 APP与 VPN技术相结合,方便使用的同时也降低了安全风险。

    例如 : 某集团 OA手机 APP,直接通过互联网地址映射访问存在一定安全隐患,配合 VPN技术使用,先将手机与集团建立 VPN隧道,再通过APP访问集团内部 OA APP服务器。

    最后做一个的总结 : 究竟使用哪一种 VPN技术来满足用户的需求,还是要根据用户业务需求来考虑,所以希望大家要对相关业务有一个初步的了解, 对症下药。 还有一些如 VPDN、DMVPN等内容没有。

    (来源:SPOTO思博网络)

    展开全文
  • 网络设备之间的相互通信

    千次阅读 2018-08-01 14:09:06
    计算机与网络设备要相互通信,双方就必须基于相同的方法。比如,如何探测到通信目标、由哪一边先发起通信、使用哪种语言进行通信、怎样结束通信等规则都需要事先确定。不同的硬件、操作系统之间的通信,所有的这一切...
  • 网络设备配置与管理课程实训

    千次阅读 多人点赞 2019-03-28 15:03:02
    三:网络设计 3.1网络拓扑结构介绍 3.2 IP地址的规划 3.3拓扑结构图 四:项目实施 4.1设置路由器 4.2设置VLAN 4.3设置路由 4.4动态分配以及MAC绑定 4.5设置安全 4.6设置NAT 五:项目测试 六:项目总结 ...
  • 常用网络设备简介

    千次阅读 2004-11-26 23:23:00
    集线器 集线器(HUB)属于数据通信系统中的基础设备,它和双绞线等传输介质一样,是一种不需任何软件支持或只需很少管理软件管理的硬件设备。它被广泛应用到各种场合。集线器工作在局域网(LAN)环境,像网卡一样,...
  • 协议简介 虽然对于网络的正式介绍一般都参考了 OSI(Open Systems Interconnection)模型,但是本文对 Linux 中...链路层是指提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备。链路层上
  • CISCO网络设备用户权限级别

    万次阅读 2014-09-03 12:12:20
    CISCO网络设备用户权限级别     在思科路由器中定义用户权限级别两种方式: 第一种是直接定义不同级别的enable密码,用户都用默认的级别level 1, 然后用户在登陆时用相应级别的...
  • cisco思科网络设备的MIB信息访问

    千次阅读 2018-07-05 12:56:30
    一、实验目的本实验的主要目的是学习SNMP...并通过SNMP来实现监控和配置网络设备。二、实验内容1、交换机和路由器SNMP配置;2、通过SNMP来实现监控和配置网络设备。三、实验工具Packet tracer模拟器。四、实验环境 ...
  • Linux-虚拟网络设备-veth pair

    万次阅读 多人点赞 2017-08-28 17:24:33
    基本概念 Virtual Ethernet ... Linux container 中用到一个叫做veth的东西,这是一种新的设备,专门为 container 所建。veth 从名字上来看是 Virtual ETHernet 的缩写,它的作用很简单,就是要把从一个 network n
  • Python可以在不同的网络层上与网络设备进行交互。 首先,Python可以通过套接字编程和socket模块操纵底层网络,从而为Python所在的操作系统和网络设备之间搭建一个低层次的网络接口。此外,Python模块还可以通过...
  • 计算机网络中,几个通信设备或者说网络设备名词出现的频率相当的高,它们是:中继器、集线器、网桥、交换机、路由器和网关。现在梳理一下它们各自的含义和作用,以及它们之间的联系。这些网络设备对于实际工作中并...
  • H3C网络设备环路检测详细说明及配置

    万次阅读 多人点赞 2018-08-19 23:29:43
    网络连接错误或配置错误都容易导致二层网络中出现转发环路,使网络设备对广播、组播报文进行重复发送,这样就会造成网络资源和设备硬件资源的严重浪费,将会造成设备卡顿运维缓慢甚至导致网络瘫痪。 为了能够及时...
  • 使用SNMP进行网络设备的监控管理

    万次阅读 2013-11-20 14:24:38
    最近个机房监控的小项目,甲方要求增加对核心交换机进行基本的网络状态监控,于是抽时间研究了一下snmp协议。由于不是做专业网管系统,所以研究不深,再此仅对之前的调研做个总结,以供新人参考。  一、简介  ...
  •  ... Linux Host 侧使用的网络元素简介 Linux 主要使用以下三种设备模型:Bridge、TAP、VETH、VLAN。Bridge 设备是基于内核实现的...TAP 设备是一种工作在二层协议的点对点网络设备,每一个 TAP 设备都一个对应
  • 编写Linux网络设备驱动(上)

    万次阅读 2011-11-25 09:38:44
    我选择了Realtek芯片两个原因:首先,Realtek提供免费的芯片技术手册; 第二,芯片相当便宜。 本文介绍的驱动程序是最基本的,它只有发送和接收数据包功能,和做一些简单的统计。对于一个全面和专业级的驱动程序...
  • Centos7安装时提示,没有可用的网络设备 这两天发神经又想做做Linux运维了!买了几本Linux 高可用 负载均衡的书看了看,看多了头真晕还是得实践这东西。安装了一个CentOS 7.0的虚拟机 ,安装时没太注意是...
  • @echo offecho set sh=WScript.CreateObject("WScript.Shell") >telnet_tmp.vbsecho WScript.Sleep 300 >>telnet_tmp.vbsecho sh.SendKeys "open 你的网络设备telnet登录IP" >>telnet_tmp.vbsecho WScript.Sleep 300
  • 在Linux中你正在使用的...以下参考: http://blog.chinaunix.net/uid-11829250-id-5748402.html 联想服务器ThinkStation P310,在安装完ubuntu14.04(64bit)LTS后,不能上网。这是由于网卡驱动没有安装成功,需要自己
  • 网络关键设备选型及介绍

    万次阅读 2018-08-31 16:46:38
    网络设备最好选择同一厂商的成熟主流产品,方便后期的安装,调试和维护。 2)网络的可拓展性 网络的主干设备一定要留余量,提高系统的可拓展性,适合业务发展。 3)网络技术的先进性 网络技术和设备更新...
  • 计算机网络由哪些硬件设备组成?

    千次阅读 2020-02-07 05:01:39
    网络是计算机或类似计算机的网络设备的集合,它们之间通过各种传输介质进行连接。无论设备之间如何连接,网络都是将来自于其中一台网络设备上的数据,通过传输介质传输到另外一台网络设备上。本节将基...
  • 在Android开发过程中,对于一个需要连接网络的Android设备,对设备网络状态检测是很必要的!很多的App都需要连接网络。判断设备是否已经连接网络,并且在连接网络的状态下判断是wifi无线连接还是GPRS手机网络...
  • 安卓设备网络adb调试设置

    万次阅读 2018-02-02 14:52:24
    1、安卓的adb调试模式两种:一、使用usb线;二、使用网络。...3、设置网络adb的监听端口,设置的方法有以下几种: 方法一:先是使用usb线连接电脑跟安卓设备,打开电脑的cmd窗口,输入命令:adb tcpip 5555

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 420,838
精华内容 168,335
关键字:

以下属于网络设备的有