分类目录归档:网络技术

阿尔卡特全系一体机升级-780wl

手中有一个腾达的一体机W548D功能太弱,想升级一下,看到配置和780wl一样,只是内存和闪存不同,所以想刷一下试试,找到了个阿尔卡特的升级软件和固件的下载网站,不敢独享。

刷机程序
http://www.speedtouch.nl/drivers.html

  Thomson ST122g
  SpeedTouch 516/546 en 516i/546i
  SpeedTouch 585 en 585i
  SpeedTouch 605s
  SpeedTouch 608, 608i, 608 WL en 608i WL (R5)
  SpeedTouch 620 en 620i
  SpeedTouch 706, 706i, 706 WL en 706i WL
  SpeedTouch 780, 780i, 780 WL en 780i WL
Eerdere modellen (niet meer leverbaar):
  SpeedTouch 110 en 120
  SpeedTouch 110g en 120g
  SpeedTouch 121g
  SpeedTouch Home en Home ISDN
  SpeedTouch USB en USB ISDN
  SpeedTouch 330
  SpeedTouch 510 en 510i
  SpeedTouch 570 en 570i R2 (tot jan 2004)
  SpeedTouch 570/545 en 570i/545i R4 (vanaf jan 2004)
  SpeedTouch 580 en 580i
  SpeedTouch 608 en 608i (R4)
  SpeedTouch 610, 610i en 610s
  SpeedTouch 716g R1.1
  SpeedTouch 716v5, 716iv5, 716v5 WL en 716iv5 WL
Hulpprogramma's:
  SpeedTouch NAT-poort Manager
  SpeedTouch Surftool voor ADSL Tijdsurfen

以上为荷兰阿尔卡特官方网站提供的刷机程序,绝对保险,该程序一般会带有一个最新的固件,刷机程序有的为英文,有的为荷兰文,看不懂的可以到http://www.google.co.jp/language_tools?hl=zh-CN选择荷兰语翻译为英语

下面这个就NB了,包含speedtouch各品牌各版本的固件
http://mirror.opensourcehub.com/pub/speedtouch/routers/

ReleaseNotes/           23-May-2007 02:47      – 
ST510v4/                27-Jul-2006 03:16      – 
ST510v5/                27-Jul-2006 03:16      – 
ST510v6/                27-Jul-2006 03:16      – 
ST511v5/                08-Feb-2006 14:26      – 
ST516/                  27-Jul-2006 03:16      – 
ST516v6/                23-May-2007 02:21      – 
ST530v4/                27-Jul-2006 03:16      – 
ST530v5/                27-Jul-2006 03:16      – 
ST530v6/                17-Mar-2007 03:58      – 
ST536/                  27-Jul-2006 03:16      – 
ST536v6/                23-May-2007 02:20      – 
ST546/                  27-Jul-2006 03:16      – 
ST546v6/                23-May-2007 02:22      – 
ST576/                  27-Jul-2006 03:16      – 
ST580/                  27-Jul-2006 03:16      – 
ST585/                  27-Jul-2006 03:16      – 
ST585v6/                23-May-2007 02:23      – 
ST605/                  05-Jan-2007 00:28      – 
ST608/                  23-May-2007 02:24      – 
ST608WL/                23-May-2007 02:27      – 
ST610/                  27-Jul-2006 03:16      – 
ST620/                  05-Jan-2007 00:28      – 
ST706WL/                23-May-2007 02:32      – 
ST716v5WL/              23-May-2007 02:35      – 
ST780DXT/               23-May-2007 02:38      – 
ST780WL/                23-May-2007 02:47      – 

DLINK 624+A 修复记

DLINK 624+A 修复记

前几天入手了一个624+A   C1版的。开机状态灯狂闪,于是上TTL

定义如下,网口向上灯向自己 6个针分别为

GND   VCC

RX       TX

VCC   GND

显示为

  AMIT R3210 RECOVERY SYSTEM 
 ROM ID =
[00] 444C4236-30363100-00000000-00000000 | DLB6 061. …. ….
 MAC address =
[00] 001B119D-19F4                       | …. ..           
 IP address =
[00] C0A80001-                           | ….              
Flash_id ,0x5b
$Data = 807f0600
PHY Vendor ID=243

在网上细心查找,发现这是reload状态,和开机按住reset是一样的

此情况为刷固件没刷中间过渡版本所致

能DHCP,也能ping通192.168.0.1,但进不了web

又在网上找,TFTP工具

Cisco TFTP Server

将TFTP工具解压,在dos窗口里,进入该目录,发命令  tftp -i 192.168.0.1 put 固件路径

下载固件在dlink

624+A_3.0_C1

下载后发现里边有个20070531_A1,C1_624+A_V3.00Patch01CN.EXE

双击,升级之,不用TFTP工具了,然后路由自动重启,OK了

fedora13 安装 SpoonWep2 心得

今天在fedora13下破解wep,懒得总是复制MAC了,想能不能在fedora上也安装上SpoonWep2呢,找了半天也没有rpm的包.后来发现直接把deb的包解出来,把文件直接复制到系统对应目录就可以运行了,很简单嘛:) .后来又发现问题了.在shell中运行,找不到网出现这个错误 /usr/local/bin/wifispoonfeeder/spoonwep/tmp/wscapture-01.txt 在网上找找了,发现在出现这个错误时,再打开一个shell运行ln -s /usr/local/bin/wifispoonfeeder/spoonwep/tmp/wscapture-01.csv /usr/local/bin/wifispoonfeeder/spoonwep/tmp/wscapture-01.txt 命令就可以解决了,但是每次搜网都要运行一次.

spoonwep-2-1-i686.pkg.tar

IP 分片与重组

IP:分片与重组

1. 引言
为了与互联网上的任意计算机通信,每个应用TCP/IP的计算机必须具有分片和重组的代码,一个设计良好的应用软件,会生成足够小的数据报,
因此主机并不需要经常执行分片任务.

2. 数据报的分片
分片发生在选择路由之后,以及放入接口队列之前.IP把数据报长度与MTU进行比较,确定是否需要分片.
如需分片,IP首先生成多个数据报,并将每个数据报中的分片位置1,将源数据报中的数据按顺序分片,并将它们装入这些数据报中.
它还在同一源数据报产生的所有数据报片中将MF位置为1,末尾片除外.IP一边分片,一边将它们传递给网络接口发送出去.

2.1 为一个数据报片再次分片
对于MF未置1的片,和上边说的没区别,除了最后一个片外,其它全置1.但对于MF为1的源片,再次分片后的所有分片MF全置1

3. 分片的实现

/* ipputp.c – ipputp */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * ipputp  –  发送一个数据包到一个接口的输出队列
 *————————————————————————
 */
int
ipputp(unsigned ifn, IPaddr nh, struct ep *pep)
{
    struct    netif    *pni = &nif[ifn];        /* 接口指针*/
    struct    ip    *pip;                /* IP报指针*/
    int        hlen, maxdlen, tosend, offset, offindg;

    if (pni->ni_state == NIS_DOWN) {            /* 接口关闭则返回*/
        freebuf(pep);
        return SYSERR;
    }
    pip = (struct ip *)pep->ep_data;            /* IP报指向*/
    if (pip->ip_len <= pni->ni_mtu) {        /* IP报长度小于等于接口MTU,则直接发送数据报*/
        pep->ep_nexthop = nh;
        pip->ip_cksum = 0;
        iph2net(pip);
        pep->ep_order &= ~EPO_IP;
        pip->ip_cksum = cksum((WORD *)pip, IP_HLEN(pip));
        return netwrite(pni, pep, EP_HLEN+net2hs(pip->ip_len));
    }
    /* 否则,我们必须分片 */

    if (pip->ip_fragoff & IP_DF) {            /* 能否分片,不能分片则报错并返回*/
        IpFragFails++;
        icmp(ICT_DESTUR, ICC_FNADF, pip->ip_src, pep, 0);
        return OK;
    }
    maxdlen = (pni->ni_mtu – IP_HLEN(pip)) &~ 7;    /* 最大长度为MTU减去IP头长度,并增加到8的倍数*/
    offset = 0;                    /* 偏移量*/
    offindg = (pip->ip_fragoff & IP_FRAGOFF)<<3;    /* 偏移量,保存控制位*/
    tosend = pip->ip_len – IP_HLEN(pip);        /* 要发送的IP报长度,不包括IP报头*/

    while (tosend > maxdlen) {            /* 仅当剩余的数据大于可发送的最大数据量时才发送*/
        if (ipfsend(pni,nh,pep,offset,maxdlen,offindg) != OK) {    /* 生成并发送分片*/
            IpOutDiscards++;
            freebuf(pep);
            return SYSERR;
        }
        IpFragCreates++;
        tosend -= maxdlen;
        offset += maxdlen;
        offindg += maxdlen;            /* */
    }
    IpFragOKs++;
    IpFragCreates++;
    hlen = ipfhcopy(pep, pep, offindg);        /* 首部拷贝处理,返回首部长度*/
    pip = (struct ip *)pep->ep_data;            /* 取IP数据报*/
    /*处理最后一个分片,当最后剩余的数据小于等于可发送的最大数据量 */
    memcpy(&pep->ep_data[hlen], &pep->ep_data[IP_HLEN(pip)+offset],
        tosend);                    /* 修改源报,使之变为最后一个报片*/
    /*非末尾的数据报片再次分片时,保证MF全为1 */
    pip->ip_fragoff = (pip->ip_fragoff & IP_MF)|(offindg>>3);
    pip->ip_len = tosend + hlen;
    pip->ip_cksum = 0;
    iph2net(pip);
    pep->ep_order &= ~EPO_IP;
    pip->ip_cksum = cksum((WORD *)pip, hlen);
    pep->ep_nexthop = nh;
    return netwrite(pni, pep, EP_HLEN+net2hs(pip->ip_len));
}

3.1 发送一个数据报片

/* ipfsend.c – ipfsend */

#include <conf.h>
#include <kernel.h>
#include <network.h>

int ipfhcopy(struct ep *, struct ep *, unsigned);

/*————————————————————————
 *  ipfsend –  发送一个IP数据报的分片
 *————————————————————————
 */
int
ipfsend(struct netif *pni, IPaddr nexthop, struct ep *pep,
    unsigned offset, unsigned maxdlen, unsigned offindg)
{
    struct    ep    *pepnew;
    struct    ip    *pip, *pipnew;
    int        hlen, len;

    pepnew = (struct ep *)getbuf(Net.netpool);        /* 申请一个新帧的缓冲区*/
    if (pepnew == (struct ep *)SYSERR)
        return SYSERR;
    pepnew->ep_order = ~0;
    hlen = ipfhcopy(pepnew, pep, offindg);    /* 复制头 */

    pip = (struct ip *)pep->ep_data;        /* 源IP报的指向*/
    pipnew = (struct ip *)pepnew->ep_data;    /* 新生成的IP报*/
    pipnew->ip_fragoff = IP_MF | (offindg>>3);
    pipnew->ip_len = len = maxdlen + hlen;
    pipnew->ip_cksum = 0;

    iph2net(pipnew);
    pepnew->ep_order &= ~EPO_IP;        /* 清除字节顺序*/
    pipnew->ip_cksum = cksum((WORD *)pipnew, hlen);

    memcpy(&pepnew->ep_data[hlen],        /* 复制数据*/
        &pep->ep_data[IP_HLEN(pip)+offset], maxdlen);
    pepnew->ep_nexthop = nexthop;

    return netwrite(pni, pepnew, EP_HLEN+len);    /* 发送并返回 */
}

3.2 复制数据报首部

/* ipfhcopy.c – ipfhcopy */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 *  ipfhcopy –  copy the hardware, IP header, and options for a fragment
 *————————————————————————
 */
int
ipfhcopy(struct ep *pepto, struct ep *pepfrom, unsigned offindg)
{
    struct    ip    *pipfrom = (struct ip *)pepfrom->ep_data;    /* 旧IP报*/
    unsigned    i, maxhlen, olen, otype;
    unsigned    hlen = (IP_MINHLEN<<2);        /* IP头最小长度*4 ,就是以字节为单位了*/

    if (offindg == 0) {    /* 偏移为0,说明是第一个分片,复制帧的头和IP头到新的帧*/
        memcpy(pepto, pepfrom, EP_HLEN+IP_HLEN(pipfrom));
        return IP_HLEN(pipfrom);
    }
    /* 以下就说明不是第一个IP分片了*/
    memcpy(pepto, pepfrom, EP_HLEN+hlen);        /* 复制帧的头和除了IP选项外的报头*/

    /*复制选项 */

    maxhlen = IP_HLEN(pipfrom);        /* 包括选项的IP头的长度*/
    i = hlen;                /* 不包括选项的IP头长度*/
    while (i < maxhlen) {            /* 最小头比最大头小,说明有IP选项*/
        otype = pepfrom->ep_data[i];    /* IP选项*/
        olen = pepfrom->ep_data[++i];    /* 选项长度*/
        if (otype & IPO_COPY) {        /* 如果复制位为1*/
            memcpy(&pepto->ep_data[hlen],
                &pepfrom->ep_data[i-1], olen);        /* 复制这个选项到所有新的帧*/
            hlen += olen;
        } else if (otype == IPO_NOP || otype == IPO_EOOP) {    /* 选项结束或没有操作*/
            pepto->ep_data[hlen++] = otype;
            olen = 1;
        }
        i += olen-1;
        
        if (otype == IPO_EOOP)
            break;
    }
    /* pad to a multiple of 4 octets */
    while (hlen % 4)            /* 填充到4字节的整数倍*/
        pepto->ep_data[hlen++] = IPO_NOP;
    return hlen;
}

4. 数据报的重组

4.1 数据结构

/* ipreass.h */

/* Internet Protocol (IP)  reassembly support */

#define IP_FQSIZE    10    /* 分片队列的数大数量 */
#define IP_MAXNF    10    /* 分片/数据报 的最大数量 */
#define    IP_FTTL        60    /* 生存时间(秒)*/

/* ipf_state flags */

#define    IPFF_VALID    1    /* 内容是有效的 */
#define    IPFF_BOGUS    2    /* 丢弃        */
#define    IPFF_FREE    3    /* 这个队列可以自由分配    */

struct    ipfq    {
    char    ipf_state;        /* 队列状态,值为上边 3种        */
    IPaddr    ipf_src;        /* 源IP地址 */
    short    ipf_id;            /* 数据报ID */
    int    ipf_ttl;        /* 数据报重组的生存时间 */
    int    ipf_q;            /* 分片存储的链表 */
};

extern    int    ipfmutex;        /* 互斥 mutex for ipfqt[] */
extern    struct    ipfq    ipfqt[];    /* IP分片队列表        */

int ipfsend(struct netif *, IPaddr, struct ep *, unsigned, unsigned,
    unsigned);
int ipfhcopy(struct ep *, struct ep *, unsigned);

4.2 互斥操作

为了何证进程在数据报片的链表时不会互相干扰,重组程序代码使用了一个互斥信号量ipfmutex.在ipreass.h中

4.3 在链表中加入一个数据报片

/* ipreass.c – ipreass */

#include <conf.h>
#include <kernel.h>
#include <network.h>

int ipfadd(struct ipfq *, struct ep *);
struct    ep    *ipfjoin(struct ipfq *);

/*————————————————————————
 *  ipreass  –  reassemble an IP datagram, if necessary
 *    returns packet, if complete; 0 otherwise
 *  IP数据报重组,如果报片到齐则重组该包,并返回完整的数据报,否则返回0
 *————————————————————————
 */
struct ep *
ipreass(struct ep *pep)
{
    struct    ep    *pep2;
    struct    ip    *pip;
    int        firstfree;
    int        i;

    pip = (struct ip *)pep->ep_data;        /* 得到IP数据报的*/

    wait(ipfmutex);                /* 互斥*/

    if ((pip->ip_fragoff & (IP_FRAGOFF|IP_MF)) == 0) {    /*如果不是分片,返回当前帧*/
        signal(ipfmutex);
        return pep;
    }
    IpReasmReqds++;
    firstfree = -1;
    /* 以下情况为是分片 */
    for (i=0; i<IP_FQSIZE; ++i) {
        struct    ipfq    *piq = &ipfqt[i];

        if (piq->ipf_state == IPFF_FREE) {        /* 队列未使用,则继续*/
            if (firstfree == -1)
                firstfree = i;
            continue;
        }
        if (piq->ipf_id != pip->ip_id)            /* 队列ID不等于分片ID,则继续*/
            continue;
        if (piq->ipf_src != pip->ip_src)            /* 源地址不同,则继续*/
            continue;
        /* 找到匹配 */
        if (ipfadd(piq, pep) == 0) {            /* 把该分片加入匹配的队列*/
            signal(ipfmutex);
            return 0;
        }
        pep2 = ipfjoin(piq);                /* 检查是否可以重组数据报*/
        signal(ipfmutex);
        return pep2;
        
    }
    /* 没有匹配 */

    if (firstfree < 0) {                    /* 检查是否有空闲队列*/
        /* no room– drop */                /* 没有空闲则丢弃*/
        freebuf(pep);
        signal(ipfmutex);
        return 0;
    }
    ipfqt[firstfree].ipf_q = newq(IP_FQSIZE, QF_WAIT);    /* 分配一个空闲表项*/
    if (ipfqt[firstfree].ipf_q < 0) {
        freebuf(pep);
        signal(ipfmutex);
        return 0;
    }
    ipfqt[firstfree].ipf_src = pip->ip_src;            /* 填充*/
    ipfqt[firstfree].ipf_id = pip->ip_id;
    ipfqt[firstfree].ipf_ttl = IP_FTTL;
    ipfqt[firstfree].ipf_state = IPFF_VALID;
    ipfadd(&ipfqt[firstfree], pep);
    signal(ipfmutex);
    return 0;
}

int    ipfmutex;
struct    ipfq    ipfqt[IP_FQSIZE];

4.4 溢出时的丢弃

/* ipfadd.c – ipfadd */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <network.h>

/*————————————————————————
 *  ipfadd  –  增加一个分片到一个IP碎片队列
 *————————————————————————
 */
Bool
ipfadd(struct ipfq *iq, struct ep *pep)
{
    struct    ip    *pip;
    int        fragoff;

    if (iq->ipf_state != IPFF_VALID) {            /* 分片队列无效,则丢弃*/
        freebuf(pep);
        return FALSE;
    }
    pip = (struct ip *)pep->ep_data;                /* 得到IP数据报的*/
    fragoff = pip->ip_fragoff & IP_FRAGOFF;            /* 得到偏移量*/
    /* -fragoff用作关键值,越大越靠前 */
    if (enq(iq->ipf_q, pep, -fragoff) < 0) {            /* 举出丢弃并释放内存*/
        /* overflow– free all frags and drop */
        freebuf(pep);
        IpReasmFails++;
        while (pep = (struct ep *)deq(iq->ipf_q)) {    /* 从队列删除帧并释放帧*/
            freebuf(pep);
            IpReasmFails++;
        }
        freeq(iq->ipf_q);                /* 释放队列*/
        iq->ipf_state = IPFF_BOGUS;
        return FALSE;
    }
    iq->ipf_ttl = IP_FTTL;        /* 重置生存时间 */
    return TRUE;
}

4.5 测试一个完整的数据据报

/* ipfjoin.c – ipfjoin */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <network.h>

struct    ep    *ipfcons(struct ipfq *);

/*————————————————————————
 *  ipfjoin  –  join fragments, if all collected
 *————————————————————————
 */
struct ep *
ipfjoin(struct ipfq *iq)
{
    struct    ep    *pep;
    struct    ip     *pip = 0;
    int        off, packoff;

    if (iq->ipf_state == IPFF_BOGUS)
        return 0;
    /* see if we have the whole datagram */
    /* 看我们是否有整个的数据包 */

    off = 0;
    while (pep=(struct ep *)seeq(iq->ipf_q)) {        /* 取出帧*/
        pip = (struct ip *)pep->ep_data;            /* 取出IP报*/
        packoff =  (pip->ip_fragoff & IP_FRAGOFF)<<3;
        if (off < packoff) {                /* 偏移大于0*/
            while(seeq(iq->ipf_q))            /* 一个不满足,说没没全到*/
                /*empty*/;
            return 0;
        }
        off = packoff + pip->ip_len – IP_HLEN(pip);    /*计算总长度,不含头*/
    }
    /* 这里利用off来测试,首先ipfjoin查看当前数据报片的偏移量是否与off值相符。
       如果当前数据报片的偏移量超过了off的值,那么必定有尚未到达的数据报片,
       因此ipfjoin返回0。如果偏移量与off值一致,那么ipfjoin通过将off值加上当前
       数据报片长度减去首部计算出下一个数据报片的偏移量。*/
    if (off > MAXLRGBUF) {        /* 超过缓冲区则丢弃 – too big for us to handle */
        while (pep = (struct ep *)deq(iq->ipf_q))
            freebuf(pep);
        freeq(iq->ipf_q);
        iq->ipf_state = IPFF_FREE;
        return 0;
    }
    if (pip == 0 || (pip->ip_fragoff & IP_MF) == 0)        /* 没有IP报或没有更多的分片*/
        return ipfcons(iq);                /* 收集数据报片并重建完整的数据报 */

    return 0;
}

4.6 将数据报片组装成完整的数据报

/* ipfcons.c – ipfcons */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 *  ipfcons  –  从IP碎片队列构建一个分组
 *————————————————————————
 */
struct ep *
ipfcons(struct ipfq *iq)
{
    struct    ep    *pep, *peptmp;
    struct    ip    *pip;
    int        off, seq;

    pep = (struct ep *)getbuf(Net.lrgpool);            /* 申请缓存空间*/
    if (pep == (struct ep *)SYSERR) {            /* 申请失败则丢弃该报*/
        while (peptmp = (struct ep *)deq(iq->ipf_q)) {
            IpReasmFails++;
            freebuf(peptmp);
        }
        freeq(iq->ipf_q);                /* 释放队列*/
        iq->ipf_state = IPFF_FREE;
        return 0;
    }
    /*  复制帧和IP报头 */

    peptmp = (struct ep *)deq(iq->ipf_q);            /* 取出一个分片*/
    pip = (struct ip *)peptmp->ep_data;            /* 得到IP报*/
    off = IP_HLEN(pip);                    /* 得到IP头长度*/
    seq = 0;
    memcpy(pep, peptmp, EP_HLEN+off);            /* 复制IP报头到pep*/

    /* 复制数据部分 */
    while (peptmp != 0) {
        int dlen, doff;

        pip = (struct ip *)peptmp->ep_data;        /* 取IP报*/
        doff = IP_HLEN(pip) + seq
            – ((pip->ip_fragoff&IP_FRAGOFF)<<3);
        dlen = pip->ip_len – doff;
        memcpy(pep->ep_data+off, peptmp->ep_data+doff, dlen);
        off += dlen;
        seq += dlen;
        freebuf(peptmp);
        peptmp = (struct ep *)deq(iq->ipf_q);
    }

    /* 修复分组的头 */
    pip = (struct ip *)pep->ep_data;                /* 取出IP报*/
    pip->ip_len = off;                    /* 修复长度*/
    pip->ip_fragoff = 0;                    /* 修复偏移量*/

    /* 释放资源 */
    freeq(iq->ipf_q);
    iq->ipf_state = IPFF_FREE;
    IpReasmOKs++;
    return pep;
}

5. 数据报片链表的维护管理

/* ipftimer.c – ipftimer */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * ipftimer – 更新生存周期并删除过期的碎片
 *————————————————————————
 */
void
ipftimer(int gran)
{
    struct    ep    *pep;
    struct    ip    *pip;
    int        i;

    wait(ipfmutex);                        /* 申请互斥量 */
    for (i=0; i<IP_FQSIZE; ++i) {                /* 遍历队列*/
        struct ipfq *iq = &ipfqt[i];

        if (iq->ipf_state == IPFF_FREE)            /* 空闲则继续*/
            continue;
        iq->ipf_ttl -= gran;                /* 非空闲则ttl-1*/
        if (iq->ipf_ttl <= 0) {                /* 生存周期到达则*/
            if (iq->ipf_state == IPFF_BOGUS) {    /* 如果为丢弃状态,则继续*/
                /* resources already gone */
                iq->ipf_state = IPFF_FREE;
                continue;
            }
            if (pep = (struct ep *)deq(iq->ipf_q)) {    /* 取分片*/
                IpReasmFails++;
                pip = (struct ip *)pep->ep_data;    /* 取IP报*/
                icmp(ICT_TIMEX, ICC_FTIMEX,    /* 向源站报错*/
                    pip->ip_src, pep, 0);
            }
            while (pep = (struct ep *)deq(iq->ipf_q)) {    /* 释放资源*/
                IpReasmFails++;
                freebuf(pep);
            }
            freeq(iq->ipf_q);            /* 释放队列*/
            iq->ipf_state = IPFF_FREE;
        }
    }
    signal(ipfmutex);                    /* 互斥解锁*/
}

6. 初始化

/* ipfinit.c – ipfinit */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * ipfinit  –  initialize IP fragment queue data structures
 *————————————————————————
 */
void
ipfinit(void)
{
    int    i;

    ipfmutex = screate(1);            /* 不多说了,简单,太~~~,这个看不明白就不用看了. 🙂 */
    for (i=0; i<IP_FQSIZE; ++i)
        ipfqt[i].ipf_state = IPFF_FREE;
}
 

IP 选路表 和 选路算法

1 引言
讨论选择路由的细节,选路表的结构,以及实现算法,路由如何使用子网掩码,如何区分网络路由子网路由以及主机路由

2 路由维护和查找
应当选择能使查找路由花费最小的IP数据结构和算法,维护路由的花费则并不那么重要
利用桶散列(bucket hashing)迅速找到正确的散列表元

3 选路表结构
保存路由的主要数据结构是数组。数组的每个元素对应一个散列表元,并包含一个指针,指向被装入这个散列表元中的通主目的站的路由记录链表。表中每个记录包含一个IP目的地址,子网掩码,下一跳地址,用于向下一跳地址发送数据的网络接口,以及其他的路由管理中使用的信息。由于IP预先并不知道子网掩码,它公使用IP目的地址的网络部分计算散列函数,在一个链表中查找表项时,使用的是完整的目的地址来进行比较。

4 选路表数据结构

/* route.h – RTFREE */

/* 路由表项 */
struct route {
IPaddr rt_net; /* 路由的目的地址 */
IPaddr rt_mask; /* 路由的掩码 */
IPaddr rt_gw; /* 下一跳的IP */
u_short rt_metric; /* 距离度量 */
u_short rt_ifnum; /* 接口的序号 */
short rt_key; /* 排序关键字 */
short rt_ttl; /* 生存时间(秒) */
struct route *rt_next; /* 哈希值指向下一个条目 */
/* stats */
int rt_refcnt; /* 引用计数 */
int rt_usecnt; /* 记录该路由使用的次数 */
};

/* 路由表的全局数据 */
struct rtinfo {
struct route *ri_default; /* 包含默认路由的下一跳地址 */
int ri_bpool; /* 结点的地址??? Route.ri_bpool = mkpool(sizeof(struct route), RT_BPSIZE) 现在还不太明白 */
Bool ri_valid; /* 此路由的数据结构是否被初始化 */
int ri_mutex; /* 互斥信号 */
};

#define RT_DEFAULT ip_anyaddr /* 默认的网络地址 全0 */
#define RT_LOOPBACK ip_loopback /* 回环地址 */
#define RT_TSIZE 512 /* 散列表长度??-these are pointers; it’s cheap */
#define RT_INF 999 /* 路由永不超时 */

#define RTM_INF 16 /* 一个最大距离度量 – an infinite metric */

/* rtget()的第二个参数… */

#define RTF_REMOTE 0 /* 远程主机产生的通信 */
#define RTF_LOCAL 1 /* 本地产生的通信 */

#define RT_BPSIZE 100 /* 路由的最大数量 */

/* RTFREE – 删除一个路由引用(假定持有ri_mutex) */

#define RTFREE(prt)
if (–prt->rt_refcnt <= 0) { freebuf(prt); } extern struct rtinfo Route; extern struct route *rttable[]; int rtadd(IPaddr, IPaddr, IPaddr, unsigned, unsigned, unsigned); int rtdel(IPaddr, IPaddr), rthash(IPaddr), rtfree(struct route *); struct route *rtget(IPaddr, Bool); void rtinit(void); void ipredirect(struct ep *, unsigned, struct route *); void ipdbc(unsigned, struct ep *, struct route *); 5 路由的生成源及保持时间 选路表项的易失性与它的来源有关(从备份存储器中得到,ICMP或选路协议,网络管理员手动操作),但为了排除网络中的路由问题,管理员可以推翻任何路由并建立不可改变的永久的路由 6 为数据报选择路由 6.1 实用过程 /* netnum.c - netnum */ #include
#include
#include

/*————————————————————————
* netnum – 计算一个给定IP 地址的网络部分
*————————————————————————
*/
IPaddr
netnum(IPaddr ipa)
{
IPaddr mask = ~0;

if (IP_CLASSA(ipa)) mask = hl2net(0xff000000);
if (IP_CLASSB(ipa)) mask = hl2net(0xffff0000);
if (IP_CLASSC(ipa)) mask = hl2net(0xffffff00);
return ipa & mask; /* 返回一个主机地址字节为全0的地址*/
}

/* netmatch.c – netmatch */

#include
#include
#include

/*————————————————————————
* netmatch – 是否是这个网络上的地址?
*————————————————————————
*/
Bool
netmatch(IPaddr dst, IPaddr net, IPaddr mask, Bool islocal)
{
if ((dst & mask) != (net & mask)) /* 目的地址与选路表中地址比较*/
return FALSE;
/*
* 源地址为本地可以只匹配单播地址 (主机为路由)
*/
if (islocal) /* 如果是本地*/
if (isbrc(dst) || IP_CLASSD(dst)) /* 如果目的地址不是广播地址,或不是D类*/
return mask != ip_maskall; /* 并且掩码不为全1,则返回真,否则为假*/
return TRUE;
}

/* netmask.c – netmask */

#include
#include
#include

IPaddr netnum(IPaddr);

/*————————————————————————
* netmask – 为给定网络设定默认掩码
*————————————————————————
*/
IPaddr
netmask(IPaddr net)
{
IPaddr netpart;
int i;

if (net == 0) /* 目的地址为全0,则使用默认路由,返回全0掩码*/
return net;
/* 网络匹配检查 (子网) */

netpart = netnum(net); /* 提取出网络部分*/
for (i=0; i
#include
#include

/*————————————————————————
* rthash – 计算网络的哈希值
*————————————————————————
*/
int
rthash(IPaddr net)
{
int bc = IP_ALEN; /* # 字节数 */
unsigned int hv = 0; /* 哈希值 */

if (IP_CLASSA(net)) bc = 1;
else if (IP_CLASSB(net)) bc = 2;
else if (IP_CLASSC(net)) bc = 3;
else if (IP_CLASSD(net))
return ((net>>24) & 0xf0) % RT_TSIZE; /* D类前4比特乘16再与散列表长度取余数*/
while (bc–)
hv += ((char *)&net)[bc] & 0xff; /* 每8位相加*/
return hv % RT_TSIZE; /* 返回除以散列长度后的余数*/
}

6.2 获得一个路由

/* rtget.c – rtget */

#include
#include
#include

/*————————————————————————
* rtget – 获得一个给定的IP地址的路由
*————————————————————————
*/
struct route *
rtget(IPaddr dest, Bool local)
{
struct route *prt;
int hv;

if (!Route.ri_valid) /* 如果路由的数据结构没被初始化则应该先初始化*/
rtinit();
wait(Route.ri_mutex); /* 等待信号互斥量*/
hv = rthash(dest); /* 计算哈希值*/
for (prt=rttable[hv]; prt; prt=prt->rt_next) {
if (prt->rt_ttl <= 0) continue; /* 路由过期 */ if (netmatch(dest, prt->rt_net, prt->rt_mask, local)) /* 如果网络地址匹配*/
if (prt->rt_metric < RTM_INF) /* 并且距离量度小于最大的量度*/ break; /* 则退出for循环*/ } if (prt == 0) /* 如果没有找到匹配的网络地址*/ prt = Route.ri_default; /* 使用默认路由 */ if (prt != 0 && prt->rt_metric >= RTM_INF) /*如果找到匹配且路离量度大于最大的量度*/
prt = 0; /* prt指向0*/
if (prt) {
prt->rt_refcnt++; /* 引用计数加1*/
prt->rt_usecnt++; /* 统计计数加1*/
}
signal(Route.ri_mutex); /* 释放互斥量*/
return prt;
}

6.3 数据结构初始化

/* rtinit.c – rtinit */

#include
#include
#include
#include

struct rtinfo Route;
struct route *rttable[RT_TSIZE];

/*————————————————————————
* rtinit – 初始化路由表
*————————————————————————
*/
void
rtinit(void)
{
int i;

for (i=0; i
#include
#include #include

extern Bool dorip; /* TRUE 如果我们正在运行的RIP输出 */
extern int rippid; /* 输出的RIP的pid,如果运行 */

/*————————————————————————
* rttimer – 更新TTLS及删除过期路由
*————————————————————————
*/
void
rttimer(unsigned int delta)
{
struct route *prt, *prev;
Bool ripnotify;
int i;

if (!Route.ri_valid) /* 路由未被初始化则返回*/
return;
wait(Route.ri_mutex); /* 等待互斥量*/

ripnotify = FALSE; /* RIP通知关闭*/
for (i=0; irt_ttl != RT_INF) /* 如果生存时间不等于永久*/
prt->rt_ttl -= delta; /* 生存时间减去相应的时间*/
if (prt->rt_ttl <= 0) { /* 如果生存时间小于等于0*/ #ifdef RIP /* 条件编译,当打开RIP时*/ if (dorip && prt->rt_metric < RTM_INF) { /*RIP打开且距离小于最大距离*/ prt->rt_metric = RTM_INF; /* 设置距离为最大距离*/
prt->rt_ttl = RIPZTIME; /* 生存时间为无穷*/
ripnotify = TRUE; /* 打开RIP通知*/
continue;
}
#endif /* RIP */
if (prev) { /*如果prev不为空,删除prt指向的表项*/
prev->rt_next = prt->rt_next;
RTFREE(prt);
prt = prev->rt_next;
} else { /*否则说明prt就是第一个表项*/
rttable[i] = prt->rt_next; /*删除第一个表项*/
RTFREE(prt);
prt = rttable[i];
}
continue;
}
prev = prt; /* 指向下一个*/
prt = prt->rt_next; /* 指向下一个*/
}
}
prt = Route.ri_default; /* 使用默认路由*/
if (prt && (prt->rt_ttlrt_ttl -= delta) <= 0) { /* 默认路由不合适*/ #ifdef RIP if (dorip && prt->rt_metric < RTM_INF) { /* RIP打开且距离小于最大距离 */ prt->rt_metric = RTM_INF; /* 距离为最大距离量度 */
prt->rt_ttl = RIPZTIME; /* 生存时间为无穷*/
} else
#endif /* RIP */
{
RTFREE(Route.ri_default); /* 释放默认路由 */
Route.ri_default = 0; /* 清除默认路由指针*/
}
}
signal(Route.ri_mutex); /* 释放互斥量*/
#ifdef RIP
if (dorip && ripnotify) /* RIP打开并且RIP通知打开*/
send(rippid, 0); /* 发送任何东西,但超时 */
#endif /* RIP */
return;
}

7.1 增加路由

/* rtadd.c – rtadd */

#include
#include
#include #include

struct route *rtnew(IPaddr, IPaddr, IPaddr, unsigned,unsigned,unsigned);
void rtinit(void);
int rthash(IPaddr);

/*————————————————————————
* rtadd – 添加一个路由到路由表
*————————————————————————
*/
int
rtadd(IPaddr net, IPaddr mask, IPaddr gw, unsigned metric,
unsigned intf, unsigned ttl)
{
struct route *prt, *srt, *prev;
Bool isdup;
int hv, i;

if (!Route.ri_valid) /* 初始化*/
rtinit();

prt = rtnew(net, mask, gw, metric, intf, ttl); /* 建立新的结点*/
if (prt == (struct route *)SYSERR) /* 分配失败则返回错误*/
return SYSERR;

/* 计算,路由在队列中的键 */
prt->rt_key = 0;
for (i=0; i<32; ++i) prt->rt_key += (mask >> i) & 1;
wait(Route.ri_mutex); /* 等待互斥量*/

/* 默认路由的特殊情况 */
if (net == RT_DEFAULT) { /* 如果网络地址为任意,全0*/
if (Route.ri_default) /* 默认路由不为空*/
RTFREE(Route.ri_default); /* 释放默认路由*/
Route.ri_default = prt; /* 用新的路由代替*/
signal(Route.ri_mutex); /* 释放互斥量*/
return OK;
}
prev = NULL;
hv = rthash(net); /* 计算哈希值*/
isdup = FALSE; /* 是否有相同地址的路由表项*/
/* 查找是否有相同的路由*/
for (srt=rttable[hv]; srt; srt = srt->rt_next) { /*srt 为空则退出for*/
if (prt->rt_key > srt->rt_key) /* */
break;
if (srt->rt_net == prt->rt_net && /* 网络地址和掩码均相同,找到相同的地址的路由*/
srt->rt_mask == prt->rt_mask) {
isdup = TRUE; /* isdup为真*/
break;
}
prev = srt;
}
if (isdup) { /* isdup为真,说明有相同的路由表项*/
struct route *tmprt;

if (srt->rt_gw == prt->rt_gw) { /* 源路由表与新路由表的下一跳相同 */
/* 更新新由表 */
#ifdef RIP /* RIP打开的情况,上边说过很多回了,下边不再多说*/
if (dorip) {
srt->rt_ttl = ttl;
if (srt->rt_metric != metric) {
if (metric == RTM_INF)
srt->rt_ttl = RIPZTIME;
send(rippid, 0);
}
}
#endif /* RIP */
srt->rt_metric = metric; /* 更新距离量度*/
RTFREE(prt); /* 释放prt*/
signal(Route.ri_mutex); /* 释放互斥量*/
return OK;
}
/* else, someone else has a route there… */
if (srt->rt_metric <= prt->rt_metric) { /* 源路由比新路由具有更小的距离并且下一跳地址不同*/
/* 没有更好的路由线路,放弃新的路由 */

RTFREE(prt);
signal(Route.ri_mutex); /* 释放互斥量*/
return OK;
}
#ifdef RIP
else if (dorip)
send(rippid, 0);
#endif /* RIP */
tmprt = srt; /* 执行到这里说明新路由更优,释放旧的路由*/
srt = srt->rt_next;
RTFREE(tmprt);
}
#ifdef RIP
else if (dorip)
send(rippid, 0);
#endif /* RIP */
prt->rt_next = srt; /* 插入新的路由*/
if (prev) /* 插入到链表的中间*/
prev->rt_next = prt;
else /* 插入到链表的开始*/
rttable[hv] = prt;
signal(Route.ri_mutex);
return OK;
}

建立新的结点,很简单不多说了

/* rtnew.c – rtnew */

#include
#include
#include

/*————————————————————————
* rtnew – 创建一个路由结构
*————————————————————————
*/
struct route *
rtnew(IPaddr net, IPaddr mask, IPaddr gw, unsigned metric,
unsigned ifnum, unsigned ttl)
{
struct route *prt;

prt = (struct route *)getbuf(Route.ri_bpool);
if (prt == (struct route *)SYSERR) {
IpRoutingDiscards++;
return (struct route *)SYSERR;
}

prt->rt_net = net;
prt->rt_mask = mask;
prt->rt_gw = gw;
prt->rt_metric = metric;
prt->rt_ifnum = ifnum;
prt->rt_ttl = ttl;
prt->rt_refcnt = 1; /* our caller */
prt->rt_usecnt = 0;
prt->rt_next = NULL;
return prt;
}

7.2 删除路由

/* rtdel.c – rtdel */

#include
#include
#include

/*————————————————————————
* rtdel – 删除给定网络的路由和掩码
*————————————————————————
*/
int
rtdel(IPaddr net, IPaddr mask)
{
struct route *prt, *prev;
int hv, i;

if (!Route.ri_valid) /* 未被初始化则返回出错*/
return SYSERR;
wait(Route.ri_mutex); /* 等待互斥量*/
if (Route.ri_default && /* 默认路由存在并且net参数与之相同,则删除默认路由*/
net == Route.ri_default->rt_net) {
RTFREE(Route.ri_default);
Route.ri_default = 0;
signal(Route.ri_mutex);
return OK;
}
hv = rthash(net); /* 计算哈希值*/

prev = NULL;
/* 查找匹配的路由*/
for (prt = rttable[hv]; prt; prt = prt->rt_next) {
if (net == prt->rt_net &&
mask == prt->rt_mask)
break;
prev = prt;
}
if (prt == NULL) { /* 没有找到匹配的路由*/
signal(Route.ri_mutex);
return SYSERR;
}
if (prev) /* prev不为空,说明匹配的路由在链表中间,并删除结点*/
prev->rt_next = prt->rt_next;
else /* 路由在链表开头*/
rttable[hv] = prt->rt_next;
RTFREE(prt); /* 释放结点*/
signal(Route.ri_mutex); /* 释放互斥量*/
return OK;
}

释放结点用的,没什么可说的,就是使用互斥量,调用一个宏
/* rtfree.c – rtfree */

#include
#include
#include

/*————————————————————————
* rtfree – remove one reference to a route
*————————————————————————
*/
int
rtfree(struct route *prt)
{
if (!Route.ri_valid)
return SYSERR;
wait(Route.ri_mutex);
RTFREE(prt);
signal(Route.ri_mutex);
return OK;
}

8. IP选项处理
没做什么工做,只是分析了一下IP首部中选面长度八位组的信息
/* ipdoopts.c – ipdoopts */

#include
#include
#include

/*————————————————————————
* ipdoopts – do gateway handling of IP options
*————————————————————————
*/
int
ipdoopts(struct netif *pni, struct ep *pep)
{
return OK; /* not implemented yet */
}

/* ipdstopts.c – ipdstopts */

#include
#include
#include

/*————————————————————————
* ipdstopts – do host handling of IP options
*————————————————————————
*/
int
ipdstopts(struct netif *pni, struct ep *pep)
{
struct ip *pip = (struct ip *)pep->ep_data;
u_char *popt, *popend;
int len;

if (IP_HLEN(pip) == IPMHLEN)
return OK;
popt = pip->ip_data;
popend = (u_char *)&pep->ep_data[IP_HLEN(pip)];

/* NOTE: options not implemented yet */

/* delete the options */
len = pip->ip_len-IP_HLEN(pip); /* data length */
if (len)
memcpy(pip->ip_data, &pep->ep_data[IP_HLEN(pip)], len);
pip->ip_len = IPMHLEN + len;
pip->ip_verlen = (pip->ip_verlen&0xf0) | IP_MINHLEN;
return OK;
}

问题:rtdel调用rtfree而不是使用宏 RTFREE,结果会怎么样? 会发生死锁

IP 总体结构 详解

1.中心环节

IP是整个协议软件的中心环节,不要把IP软件简单的分割成输入和输出两部分,因为输入和输出的界限很模糊.以下把重点放在网关上.

2. IP软件设计思想

 * 统一的输入队列以及统一的选路过程
 * 独立的IP进程
 * 本地主机接口

3. IP软件结构和数据报流程

4. IP软件结构和数据报流程

4.1 选择传入数据报的策略
挑选数据报并为其选择路由的IP程序段实现了一个重要策略:它决定了数据报来源的相对优先级.
公平分配优先权,例用策略为循环法(round-robin)

/* ipgetp.c – ipgetp */

#include <conf.h>
#include <kernel.h>
#include <network.h>
#include <proc.h>

static    int    ifnext = NI_LOCAL;        //接口索引

/*————————————————————————
 * ipgetp  —  choose next IP input queue and extract a packet
 *————————————————————————
 */
struct ep *
ipgetp(int *pifnum)
{
    struct    ep    *pep;                        //以太帧结构体
    int        i;

    recvclr();                            /* 确保没有旧分组正在等待 */
    while (TRUE) {
        for (i=0; i < Net.nif; ++i, ++ifnext) {        //遍历每个接口
            if (ifnext >= Net.nif)            //等于或大于最大接口数
                ifnext = 0;
            if (nif[ifnext].ni_state == NIS_DOWN)    //此接口关闭
                continue;
            if (pep = NIGET(ifnext)) {            /*NIGET宏,提取并返回第一个数据*/
                *pifnum = ifnext;            //返回接口的序号
                return pep;                //返回以太帧的结构体指针
            }
        }
        ifnext = receive();    //如没有数据则阻塞,当有数据到达时,返回数据报到达的接口指向
    }
    /* can't reach here */
}

4.2 IP使用的常量定义

/* ip.h – IP_HLEN, IP_CLASS{A,B,C,D,E} */

/* 互联网协议(IP)的常量和数据报格式        */

#define    IP_ALEN    4        /* 以字节为单位的IP地址的长度(字节)    */
typedef    unsigned long IPaddr;    /* 互联网地址                */

#if    BYTE_ORDER == BIG_ENDIAN
#define    IP_CLASSA(x) (((x) & 0x80000000) == 0)        /* A类的IP */
#define    IP_CLASSB(x) (((x) & 0xc0000000) == 0x80000000)    /* B类的IP */
#define    IP_CLASSC(x) (((x) & 0xe0000000) == 0xc0000000)    /* C类的IP */
#define    IP_CLASSD(x) (((x) & 0xf0000000) == 0xe0000000)    /* D类的IP */
#define    IP_CLASSE(x) (((x) & 0xf8000000) == 0xf0000000)    /* E类的IP */
#else    /* BYTE_ORDER */
#define    IP_CLASSA(x)    (((x) & 0x80) == 0x00)    /* A类的IP地址    */
#define    IP_CLASSB(x)    (((x) & 0xc0) == 0x80)    /* B类的IP地址    */
#define    IP_CLASSC(x)    (((x) & 0xe0) == 0xc0)    /* C类的IP地址    */
#define    IP_CLASSD(x)    (((x) & 0xf0) == 0xe0)    /* D类的IP地址    */
#define    IP_CLASSE(x)    (((x) & 0xf8) == 0xf0)    /* E类的IP地址    */
#endif    /* BYTE_ORDER */

/* 部分分配协议编号 */

#define    IPT_ICMP    1    /* ICMP数据包协议类型    */
#define    IPT_IGMP    2    /* IGMP数据包协议类型    */
#define    IPT_TCP        6    /* TCP数据包协议类型    */
#define IPT_EGP        8    /* EGP数据包协议类型    */
#define    IPT_UDP        17    /* UDP数据包的协议类型    */
#define    IPT_OSPF    89    /* OSPF数据包协议类型    */

struct    ip    {
    u_char    ip_verlen;    /*  IP版本及头长度 (in longs)*/
    u_char    ip_tos;        /* 服务类型    */
    u_short    ip_len;        /* 包总长度(字节)    */
    short    ip_id;        /* 数据包的id                */
    short     ip_fragoff;    /* 碎片偏移(8字节的)    */
    u_char    ip_ttl;        /* 生存时间,在网关的跳    */
    u_char    ip_proto;    /*  IP协议(见IPT_ *)*/
    short    ip_cksum;    /* 头校验和             */
    IPaddr    ip_src;        /* 源IP地址            */
    IPaddr    ip_dst;        /* 目的地IP地址        */
    u_char    ip_data[1];    /* 可变长度的数据            */
};

#define    IP_VERSION    4    /* 当前版本值        */
#define    IP_MINHLEN    5    /* 最小的IP头长度 (in longs)    */
#define    IP_TTL        255    /* 生存时间初始值        */

#define    IP_MF        0x2000    /* 更多的碎片位            */
#define    IP_DF        0x4000    /* 不分片位            */
#define    IP_FRAGOFF    0x1fff    /* 碎片偏移 fragment offset mask*/
#define    IP_PREC        0xe0    /* 优先的服务 precedence portion of TOS*/

/* IP优先值 */

#define    IPP_NETCTL    0xe0    /* 网络控制            */
#define    IPP_INCTL    0xc0    /* Internet控制            */
#define    IPP_CRIT    0xa0    /* 关键 critical                */
#define    IPP_FLASHO    0x80    /* flash over-ride            */
#define    IPP_FLASH    0x60    /* flash                 */
#define    IPP_IMMED    0x40    /* immediate                */
#define    IPP_PRIO    0x20    /* 优先                */
#define    IPP_NORMAL    0x00    /* 正常                */

/*宏来计算一个数据报的报头的长度(字节)        */
#define    IP_HLEN(pip)    ((pip->ip_verlen & 0xf)<<2)
#define    IPMHLEN        20    /* 最小的IP头长度(字节)    */

/* IP选项 */
#define    IPO_COPY    0x80    /* 分片复制 copy on fragment mask        */
#define IPO_CLASS    0x60    /* 选项类 option class                */
#define    IPO_NUM        0x17    /* 选项编号option number        */

#define    IPO_EOOP    0x00    /* 选项结束            */
#define    IPO_NOP        0x01    /* 没有操作 no operation        */
#define    IPO_SEC        0x82    /* DoD security/compartmentalization    */
#define    IPO_LSRCRT    0x83    /* 松散源路由 loose source routing        */
#define    IPO_SSRCRT    0x89    /* 严格的源路由strict source routing        */
#define    IPO_RECRT    0x07    /* 记录路线                */
#define    IPO_STRID    0x88    /* 流编号                */
#define    IPO_TIME    0x44    /* 互联网时间戳            */

#define    IP_MAXLEN    BPMAXB-EP_HLEN    /*  IP数据报的最大长度    */

/*IP进程的信息 */

extern    PROCESS        ipproc();
#define    IPSTK        512    /* IP过程堆栈大小        */
#define    IPPRI        100    /* IP运行在高优先级        */
#define    IPNAM        "ip"    /* IP过程的名字            */
#define    IPARGC        0    /* count of args to IP             */

extern IPaddr    ip_maskall;    /* = 255.255.255.255            */
extern IPaddr    ip_anyaddr;    /* = 0.0.0.0                */
extern IPaddr    ip_loopback;    /* = 127.0.0.1                */

extern    int    ippid, gateway;

struct ip *iph2net(struct ip *), *ipnet2h(struct ip *);
unsigned short cksum(WORD *, unsigned);
int ipsend(IPaddr, struct ep *, unsigned, u_char, u_char, u_char);
int ipputp(unsigned, IPaddr, struct ep *);
Bool isbrc(IPaddr);

4.3 IP进程的结构 (IP进程允许被阻塞)

/* ipproc.c – ipproc */

#include <conf.h>
#include <kernel.h>
#include <network.h>

struct    ep    *ipgetp(int *);
struct    route    *rtget(IPaddr, Bool);

/*————————————————————————
 *  ipproc  –   处理一个从网络中来的IP数据包
 *————————————————————————
 */
PROCESS
ipproc(void)
{
    struct    ep    *pep;
    struct    ip    *pip;
    struct    route    *prt;
    Bool        nonlocal;
    int        ifnum;

    ippid = getpid();                        /* 得到进程的ID    */

    signal(Net.sema);                        /* 信号进行初始化*/

    while (TRUE) {
        pep = ipgetp(&ifnum);                /* 选取一个以太网数据报*/
        pip = (struct ip *)pep->ep_data;            /* 得到此报包含的更高级报地址*/

        if ((pip->ip_verlen>>4) != IP_VERSION) {        /* 判断是否为IP数据报*/
            IpInHdrErrors++;
            freebuf(pep);
            continue;
        }
        if (IP_CLASSE(pip->ip_dst)) {            /* 判断是否为E类地址*/
            IpInAddrErrors++;
            freebuf(pep);
            continue;
        }
        if (ifnum != NI_LOCAL) {                /* 此报不是本机产生的*/
            if (cksum((WORD *)pip, IP_HLEN(pip))) {    /* 计算检验和*/
                IpInHdrErrors++;
                freebuf(pep);
                continue;
            }
            ipnet2h(pip);
            pep->ep_order |= EPO_IP;
        }
        prt = rtget(pip->ip_dst, (ifnum == NI_LOCAL));    /* 选择路由*/

        if (prt == NULL) {                    /* 如果路由不存在*/
            if (gateway) {                /* 如网关存在发送ICMP不可达*/
                iph2net(pip);
                pep->ep_order &= ~EPO_IP;
                icmp(ICT_DESTUR, ICC_NETUR,
                        pip->ip_src, pep, 0);
            } else {
                IpOutNoRoutes++;
                freebuf(pep);
            }
            continue;
        }
        nonlocal = ifnum != NI_LOCAL && prt->rt_ifnum != NI_LOCAL; /* 不是本地地址*/
        if (!gateway && nonlocal) {                /* 不是网关也不是本地地址*/
            IpInAddrErrors++;
            freebuf(pep);
            rtfree(prt);
            continue;
        }
        if (nonlocal)
            IpForwDatagrams++;
        /* 如果我们发件人,填写在源IP */

        if (ifnum == NI_LOCAL) {                /* 如果网络接口为本地*/
            if (pip->ip_src == ip_anyaddr)       
                if (prt->rt_ifnum == NI_LOCAL)    /* 路由的接口为本地*/
                    pip->ip_src = pip->ip_dst;
                else
                    pip->ip_src =
                        nif[prt->rt_ifnum].ni_ip;
        } else if (–(pip->ip_ttl) == 0 &&        /* ttl 为零,且路由接口不为本地*/
                prt->rt_ifnum != NI_LOCAL) {
            IpInHdrErrors++;
            iph2net(pip);
            pep->ep_order &= ~EPO_IP;
            icmp(ICT_TIMEX, ICC_TIMEX, pip->ip_src, pep, 0);    /*发送ICMP超时报文*/
            rtfree(prt);
            continue;
        }
        ipdbc(ifnum, pep, prt);    /* 处理定向广播    */
        ipredirect(pep, ifnum, prt); /* 做重定向,如果需要的话*/
        if (prt->rt_metric != 0)
            ipputp(prt->rt_ifnum, prt->rt_gw, pep);
        else
            ipputp(prt->rt_ifnum, pip->ip_dst, pep);
        rtfree(prt);
    }
}

int    ippid, gateway, bsdbrc;

4.4 校验和的计算

/* cksum.c – cksum */

/*————————————————————————
 *  cksum  –  Return 16-bit ones complement of 16-bit ones complement sum
 *————————————————————————
 */
unsigned short
cksum(buf, nbytes)
unsigned short    *buf;
int        nbytes;
{
    unsigned long    sum;
    unsigned short    tmp;
    int        nwords;

    nwords = nbytes / 2;            /* 将字节转成字,也就是16个的个数 */
    for (sum=0; nwords>0; nwords–)    /* 将16位为单位做累加*/
        sum += *buf++;
    if (nbytes & 1) {            /* 如长度不是16位的倍数则补齐*/
        tmp = *(unsigned char *)buf;
        sum += tmp;
    }
    sum = (sum >> 16) + (sum & 0xffff);    /* 高位低位相加   */
    sum += (sum >> 16);            /* 上一步溢出时,将溢出位也加到sum中 */
    return (unsigned short)~sum;        /* 返回sum的反码*/
}

4.5 处理定向广播

主要做了两件事,复制一个副本,然后正常发送
/* ipdbc.c – ipdbc */

#include <conf.h>
#include <kernel.h>
#include <network.h>

struct    route *rtget(IPaddr, Bool);

/*————————————————————————
 * ipdbc – handle IP directed broadcast copying
 *————————————————————————
 */
void
ipdbc(unsigned ifnum, struct ep *pep, struct route *prt)
{
    struct    ip    *pip = (struct ip *)pep->ep_data;
    struct    ep    *pep2;
    struct    route    *prt2;
    int        len;

    if (prt->rt_ifnum != NI_LOCAL)
        return;            /* 不是我们        */
    if (!isbrc(pip->ip_dst))
        return;            /* 不是广播    */

    prt2 = rtget(pip->ip_dst, RTF_LOCAL);    /* 选择一个路由 */
    if (prt2 == NULL)
        return;
    if (prt2->rt_ifnum == ifnum) {    /* 没有针对接口        */
        rtfree(prt2);
        return;
    }

    /* 定向广播,建立一个副本 */

    /* len = ether header + IP packet */

    len = EP_HLEN + pip->ip_len;
    if (len > EP_MAXLEN)                    /* 为副本分配缓冲区*/
        pep2 = (struct ep *)getbuf(Net.lrgpool);
    else
        pep2 = (struct ep *)getbuf(Net.netpool);
    if (pep2 == (struct ep *)SYSERR) {
        rtfree(prt2);
        return;
    }
    memcpy(pep2, pep, len);                /* 复制副本*/
    /* 发送一份拷贝到网络 */

    ipputp(prt2->rt_ifnum, pip->ip_dst, pep2);
    rtfree(prt2);

    return;        /* continue; "pep" goes locally in IP    */
}

4.6 识别一个广播地址

/* isbrc.c – isbrc */

#include <conf.h>
#include <kernel.h>
#include <sleep.h>
#include <network.h>

/*————————————————————————
 *  isbrc  –  Is "dest" a broadcast address?
 *————————————————————————
 */
Bool
isbrc(IPaddr dest)
{
    int    inum;

    /* 所有的0和全1的是广播地址 */

    if (dest == ip_anyaddr || dest == ip_maskall)    /* 如果为全0 或 全1 则返回真*/
        return TRUE;

    /* 检查真正的广播地址和BSD风格的网和子网 */

    for (inum=0; inum < Net.nif; ++inum)
        if (dest == nif[inum].ni_brc ||
            dest == nif[inum].ni_nbrc ||
            dest == nif[inum].ni_subnet ||
            dest == nif[inum].ni_net)
            return TRUE;

    return FALSE;
}

5. IP首部中的字节顺序

/* iph2net.c – iph2net */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 *  iph2net –  iph2net – 转换成一个IP数据包头从主机到网络字节顺序
 *————————————————————————
 */
struct ip *
iph2net(struct ip *pip)
{
    /* 注:不包括IP选项    */

    pip->ip_len = hs2net(pip->ip_len);
    pip->ip_id = hs2net(pip->ip_id);
    pip->ip_fragoff = hs2net(pip->ip_fragoff);
    return pip;
}

/* ipnet2h.c – ipnet2h */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 *  ipnet2h – 转换成一个IP数据包头从网络到主机字节顺序
 *————————————————————————
 */
struct ip *
ipnet2h(struct ip *pip)
{
    /* 注:不包括IP选项    */

    pip->ip_len = net2hs(pip->ip_len);
    pip->ip_id = net2hs(pip->ip_id);
    pip->ip_fragoff = net2hs(pip->ip_fragoff);
    return pip;
}

6. 向IP发送数据报

6.1 发送本地生成的数据报

/* ipsend.c – ipsend */

#include <conf.h>
#include <kernel.h>
#include <network.h>
#include <proc.h>

static ipackid = 1;            /* 静态变量 标识*/

/*————————————————————————
 *   ipsend – 发送一个IP数据报到指定地址
 *————————————————————————
 */
int
ipsend(IPaddr faddr, struct ep *pep, unsigned datalen,
    u_char proto, u_char ptos, u_char ttl)
{
    struct    ip *pip = (struct ip *) pep->ep_data;

    pep->ep_type = EPT_IP;                    /* 类型为网际协议*/
    pep->ep_order |= EPO_IP|EPO_NET;                /* 网络层 协议等级为1*/
    pip->ip_verlen = (IP_VERSION<<4) | IP_MINHLEN;        /* 协议版本 最小的IP头长度*/
    pip->ip_tos = ptos;                    /* 设置服务类型与优先级*/
    pip->ip_len = datalen+IP_HLEN(pip);            /* IP分组的长度*/
    pip->ip_id = ipackid++;                    /* 分组的标识*/
    pip->ip_fragoff = 0;                    /* 分片的偏移*/
    pip->ip_ttl = ttl;                    /* 生存时间*/
    pip->ip_proto = proto;                    /* 协议类型*/
    pip->ip_dst = faddr;                    /* 目标地址*/

    /*
     * 特殊情况的ICMP协议,因此源匹配目的地
     * on multi-homed hosts. 多穴主机??
     */
    if (pip->ip_proto != IPT_ICMP)                /* 不是ICMP分组*/
        pip->ip_src = ip_anyaddr;            /* IP的源地址为任意地址*/

    if (enq(nif[NI_LOCAL].ni_ipinq, pep, 0) < 0) {        /* 将分组放入本地主机接口队列中*/
        freebuf(pep);
        IpOutDiscards++;
    }
    send(ippid, NI_LOCAL);                    /* IP数据报阻塞并等待,发送报文给IP进程*/
    IpOutRequests++;
    return OK;
}
/* 特殊的IP地址 */

IPaddr    ip_anyaddr = 0;
#if    BYTE_ORDER == LITTLE_ENDIAN
IPaddr    ip_loopback = 0x0100007F;    /* 127.0.0.1 */
#else    /* BYTE_ORDER */
IPaddr    ip_loopback = 0x7F000001;    /* 127.0.0.1 */
#endif    /* BYTE_ORDER */

6.2 发送传入数据报

/* ip_in.c – ip_in */

#include <conf.h>
#include <kernel.h>
#include <network.h>
#include <proc.h>

/*————————————————————————
 *  ip_in – IP input function
 *————————————————————————
 */
int
ip_in(struct netif *pni, struct ep *pep)
{
    struct    ip    *pip = (struct ip *)pep->ep_data;

    IpInReceives++;
    if (enq(pni->ni_ipinq, pep, pip->ip_tos & IP_PREC) < 0) {
        IpInDiscards++;                        /* 如队列满则记录溢出差错并丢则报文*/
        freebuf(pep);
    }
    send(ippid, (pni-&nif[0]));                    /* 向IP进程发送消息*/
    return OK;
}

7. 表格的维护

/* slowtimer.c – slowtimer */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <sleep.h>
#include <date.h>
#include <network.h>

#define    STGRAN    1        /* 定时器粒度(延迟秒)    */

void arptimer(unsigned), ipftimer(unsigned), rttimer(unsigned);
#ifdef OSPF
void ospftimer(unsigned);
#endif /*OSPF*/

/*————————————————————————
 * slowtimer – 处理长期定期维护网络表
 *————————————————————————
 */
PROCESS
slowtimer(void)
{
    unsigned long    lasttime, now;    /* prev and current times (secs)*/
    int        delay;        /* 几秒钟内实际延迟    */

    signal(Net.sema);

    gettime(&lasttime);
    while (1) {
        sleep(STGRAN);        /* 延时*/
        gettime(&now);
        delay = now – lasttime;
        if (delay <= 0 || delay > 4*STGRAN)
            delay = STGRAN;    /* 时钟复位-likely clock reset */
        lasttime = now;
        arptimer(delay);
        ipftimer(delay);
        rttimer(delay);
#ifdef OSPF
        ospftimer(delay);
#endif /* OSPF */
    }
}
 

TCP 状态机 理解TCP重要的环节

1.问:TCP使用有限长的字段来记录流序号。研究协议规范,找出在两台机器之间允许任意长度流传输的方法

答:目前我只知道使用,时间戳选项来解决这个问题,具体的还不太清楚,有待提高

2.问:TCP有一个选项允许接收方指明它愿意接受的最大报文段长度。在TCP已具有窗口通告机制的情况下,为什么还要支持这个选项来规定最大报文段长度?

答:第一,最大报文段长度规定了最大报文的长度,避免在物理网络上对TCP数据报进行分组,以提高传输效率。

第二,在避免糊涂窗口综合证时还使用了最大报文段长度做为量度。

3.问:在什么样的时延,带宽,负载以及分组丢失率情况下,TCP没有必要重传大量的数据?

答:时延低且稳定,带宽高,负载低,分组丢失率低的情况下(我不太明白这题的意思,可能不太对 🙂 )

4.问:一个丢失的TCP确认并不一定会导致重传,试解释原因。

答:第一种,TCP采用累积确认,如果发送方确认丢失也不会重传。

第二种,两台主机通信,主机1主动关闭发送FIN,主机2 发送确认,但是确认丢失。主机2被动关闭发送FIN,ACK.主机1确定,并进入TIMED_WAIT,这个丢失的确认不用重传,因为主机2发送关闭时,包含有主机1发送的信息,所以已经可以确定收到。

5.问:考虑检验和的计算。假设报文段中的检验和字段并未置零,而检验和计算的结果却是零。你能从中得出什么结论?

答:结论是这次的检验和应该是上一次检验和减1

6.问:如果两个程序使用TCP来发送数据,但每次只发送含一个字符的报文段,那么它们的数据最多有多大的网络带宽利用率?

答:首先是多层封装,TCP封在IP报里,IP报封在帧中。

帧:最少要6octet 源地址,6octet 目的地址,2octet帧类型,和4octet的CRC,一共是18octet (1octet=8bit)

IP数据报与TCP数据报的报头最少都是20byte,两种加起来是40byte,加上18byte+1byte的数据。

1/(40+18+1)=0.016949 约合1.7%

浏览器 定制 扩展 (转)

前言

  由于本人在开发中经常要在程序中嵌入浏览器,为了符合自己的需求经常要对浏览器进行扩展和定制, 解决这些问题需在网上找资料和学习的过程,我想可能很多开发者或许会遇到同样的问题,特写此文,以供大家参考。

在MFC中使用浏览器

  在MFC中微软为我们提供了CHtmlView、CDHtmlDialog类让我们的程序很方便的嵌入浏览器和进行浏览器的二次开发,这比直 接使用WebBrowser控件要方便很多,所以本文中讨论的浏览器的问题都是针对CHtmlView来讨论的。文中将提到一个类CLhpHtmlView, 它是CHtmlView的派生类,文中提及的扩展或定制都将在CLhpHtmlView类(或派生类)上实现。

怎样扩展或定制浏览器

  浏览器定义了一些扩展接口(如IDocHostUIHandler可以定制浏览器界面有关的行为),以便开发者进行定制和扩展。浏览 器会在需要的时候向他的控制站点查询这些接口,在控制站点里实现相应的接口就可以进行相应的扩展。在MFC7.01类 库中,CHtmlView使用的控制站点是CHtmlControlSite的,在CHtmlControlSite类中 只实现了接口IDocHostUIHandler, 而要实现更多的扩展接口,必须用自定义的控制站类来取代CHtmlControlSite,在下文中提及的类CDocHostSite即为自定义 的控制站类。

关于接口的介绍请参考:

http://dev.csdn.net/develop/article/48/48483.shtm 

如何使自定义的控制站点来替换默认的控制站点呢?在MFC7.0中只需重载CHtmlView的虚函数CreateControlSite即可:
 

BOOL CLhpHtmlView::CreateControlSite(COleControlContainer * pContainer, 
COleControlSite ** ppSite, UINT /*nID*/, REFCLSID /*clsid*/)
{
	*ppSite = new CDocHostSite(pContainer, this);// 创建自己的控制站点实例
	return (*ppSite) ? TRUE : FALSE;
}

VC6.0要替换控制站要复杂的多,这里就不讨论了,如需要6.0版本的请给我发邮件到yourshine@21cn.com。

定制鼠标右键弹出出菜单

  要定制浏览器的鼠标右键弹出菜单,必须在自定义的控制站点类中实现IDocHostUIHandler2接口,并且IE的 版本是5.5或以上。在接口IDocHostUIHandler2的ShowContextMenu方法中调用浏览器类的OnShowContextMenu虚函数,我们 在浏览器类的派生类重载此虚函数即可实现右键菜单的定制,参见代码
 

HRESULT CDocHostSite::XDocHostUIHandler::ShowContextMenu(DWORD dwID,
                                                         POINT * ppt,
                                                         IUnknown * pcmdtReserved,
                                                         IDispatch * pdispReserved)
{
	METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
	return pThis->m_pView->OnShowContextMenu( dwID, ppt, pcmdtReserved,pdispReserved );
}

HRESULT CLhpHtmlView::OnShowContextMenu(DWORD dwID, 
                                        LPPOINT ppt,
                                        LPUNKNOWN pcmdtReserved, 
                                        LPDISPATCH pdispReserved)
{
	HRESULT result = S_FALSE;

	switch(m_ContextMenuMode)
	{
	case NoContextMenu:			// 无菜单
		result=S_OK;
		break;
	case DefaultMenu:				// 默认菜单
		break;
	case TextSelectionOnly:			// 仅文本选择菜单
		if(!(dwID == CONTEXT_MENU_TEXTSELECT || dwID == CONTEXT_MENU_CONTROL))
			result=S_OK;
		break;
	case CustomMenu:				// 自定义菜单
		if(dwID!=CONTEXT_MENU_TEXTSELECT)
			result=OnShowCustomContextMenu(ppt,pcmdtReserved,pdispReserved);
		break;
	}

	return result;
}		

在CLhpHtmlView中定义的枚举类型CONTEXT_MENU_MODE举出了定制右键弹出菜单的四种类型
 

enum CONTEXT_MENU_MODE		// 上下文菜单
{
	NoContextMenu,		// 无菜单
	DefaultMenu,		// 默认菜单
	TextSelectionOnly,		// 仅文本选择菜单
	CustomMenu		// 自定义菜单
};

  通过CLhpHtmlView的函数SetContextMenuMode来设置右键菜单的类型。如果设定的右键弹出菜单是“自定义菜单”类型, 我们只要在CLhpHtmlView的派生类中重载OnShowCustomContextMenu虚函数即可,如下代码 CDemoView是CLhpHtmlView的派生类

HRESULT CDemoView::OnShowCustomContextMenu(LPPOINT ppt, LPUNKNOWN pcmdtReserved,LPDISPATCH pdispReserved)
{
	if ((ppt==NULL)||(pcmdtReserved==NULL)||(pcmdtReserved==NULL))
		return S_OK;

	HRESULT hr=0;
	IOleWindow *oleWnd=NULL;
    	hr=pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
	if((hr != S_OK)||(oleWnd == NULL))
		return S_OK;

	HWND hwnd=NULL;
	hr=oleWnd->GetWindow(&hwnd);
	if((hr!=S_OK)||(hwnd==NULL))
	{
		oleWnd->Release();
		return S_OK;
	}

	IHTMLElementPtr	pElem=NULL;
	hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)&pElem);
	if(hr != S_OK)
	{
		oleWnd->Release();
		return S_OK;
	}

	IHTMLElementPtr	pParentElem=NULL;

	_bstr_t	tagID;
	BOOL go=TRUE;

	pElem->get_id(&tagID.GetBSTR());
	
	while(go && tagID.length()==0)
	{
		hr=pElem->get_parentElement(&pParentElem);
		if(hr==S_OK && pParentElem!=NULL)
		{
			pElem->Release();
			pElem=pParentElem;
			pElem->get_id(&tagID.GetBSTR());
		}
		else
			go=FALSE;
	};
	if(tagID.length()==0)
		tagID="no id";

	CMenu Menu,SubMenu;
	Menu.CreatePopupMenu();
	
	CString strTagID = ToStr(tagID);

	if(strTagID == "red")
		Menu.AppendMenu(MF_BYPOSITION, ID_RED, "您点击的是红色");
	else if(strTagID == "green")
		Menu.AppendMenu(MF_BYPOSITION, ID_GREEN, "您点击的是绿色");
	else if(strTagID == "blue")
		Menu.AppendMenu(MF_BYPOSITION, ID_BLUE, "您点击的是蓝色");
	else
		Menu.AppendMenu(MF_BYPOSITION, ID_NONE, "你点了也白点,请在指定的地方点击");
		
	int MenuID=Menu.TrackPopupMenu(TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON,ppt->x, ppt->y, this);
	switch(MenuID)
	{
	case ID_RED:
		MessageBox("红色");
		break;
	case ID_GREEN:
		MessageBox("红色");
		break;
	case ID_BLUE:
		MessageBox("红色");
		break;
	case ID_NONE:
		MessageBox("haha");
		break;
	}

	oleWnd->Release();
	pElem->Release();

	return S_OK;
}		


实现脚本扩展(很重要的external接口)

  在你嵌入了浏览器的工程中,如果网页的脚本中能调用C++代码,那将是一件很惬意的事情,要实现这种交互,就必须实现脚本扩展。实现脚本扩展就是在程序中实现一个IDispatch接口,通过CHtmlView类的OnGetExternal虚函数返回此接口指针,这样就可以在脚本中通过window.external.XXX(关键字window可以省略)来 引用接口暴露的方法或属性(XXX为方法或属性名)。在MFC中从CCmdTarget派生的类都可以实现自动化,而不必在MFC工程中引入繁杂的ATL。从CCmdTarget派生的类实现自动化接口的时候不要忘了在构造函数中调用EnableAutomation函数。
  要使虚函数OnGetExternal发挥作用必须在 自定义的控制站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetExternal方法中调用浏览器类的OnGetExternal虚函数,我们在浏览器类的派生类重载OnGetExternal虚函数, 通过参数lppDispatch返回一个IDispatch指针,这样脚本中引用window.external时就是引用的返回的接口,参见代码

HRESULT CDocHostSite::XDocHostUIHandler::GetExternal(IDispatch ** ppDispatch)
{
	METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
	return pThis->m_pView->OnGetExternal( ppDispatch );
}

CLhpHtmlView::CLhpHtmlView(BOOL isview)
{
	......
	EnableAutomation();// 允许自动化
}

HRESULT CLhpHtmlView::OnGetExternal(LPDISPATCH *lppDispatch)
{
	*lppDispatch = GetIDispatch(TRUE);// 返回自身的IDispatch接口
	return S_OK;
}      

请注意上面代码中,在OnGetExternal返回的是自身IDispatch接口, 这样就不比为脚本扩展而另外写一个从CCmdTarget派生的新类, CLhpHtmlView本身就是从CCmdTarget派生,直接在上面实现接口就是。

下用具体示例来说明怎样实现脚本扩展

示例会在网页上点击一个按钮而使整个窗口发生抖动

从CLhpHtmlView派生一个类CDemoView,在类中实现IDispatch, 并通过IDispatch暴露方法WobbleWnd

---------------------------------------------------------------------------
文件 DemoView.h
---------------------------------------------------------------------------
.......
class CDemoView : public CLhpHtmlView
{
	......
	DECLARE_DISPATCH_MAP() // 构建dispatch映射表以暴露方法或属性
	......
	void WobbleWnd();// 抖动窗口
};

---------------------------------------------------------------------------
文件 DemoView.cpp
---------------------------------------------------------------------------

......

// 把成员函数映射到Dispatch映射表中,暴露方法给脚本
BEGIN_DISPATCH_MAP(CDemoView, CLhpHtmlView)
	DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

......

void CDemoView::WobbleWnd()
{
	// 在这里实现抖动窗口
	......
}

---------------------------------------------------------------------------
文件 Demo.htm
---------------------------------------------------------------------------

...... onclick="external.WobbleWnd()" ......		

这里我要介绍一下DISP_FUNCTION宏,它的作用是将一个函数映射到Dispatch映射表中,我们看

DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)

CDemoView是宿主类名, "WobbleWnd"是暴露给外面的名字(脚本调用时使用的名字), VT_EMPTY是返回值得类型为空,VTS_NONE说明此方法没有参数,如果要映射的函数有返回值和参数该 如何映射,通过下面举例来说明
 

DISP_FUNCTION(CCalendarView,"TestFunc",TestFunc,VT_BOOL,VTS_BSTR VTS_I4 VTS_I4)

BOOL TestFunc(LPCSTR param1, int param2, int param3)
{
	.....
}		

  参数表VTS_BSTR VTS_I4 VTS_I4是用空格分隔,他们的类型映射请参考MSDN,这要提醒的是不要把VTS_BSTR与CString对应,而应与LPCSTR对应。

C++代码中如何调用网页脚本中的函数

  IHTMLDocument2::scripts属性表示HTML文档中所有脚本对象。使用脚本对象的IDispatch接口的GetIDsOfNames方法可以得到脚本函数的 DispID,得到DispID后,使用IDispatch的Invoke函数可以调用对应的脚本函数。CLhpHtmlView提供了方便的调用JavaScript的函数,请参考CLhpHtmlView中有关键字“JScript”的代码。

定制消息框的标题

  我们在脚本中调用alert弹出消息框时,消息框的标题是微软预定义的“Microsoft Internet Explorer”,如下图:

  在自定义的控制站点类中实现IDocHostShowUI接口,在接口的ShowMessage方法中调用浏览器的OnShowMessage,我们重载 OnShowMessage虚函数即可定制消息框的标题,实现代码如下:
 

// 窗口标题"Microsoft Internet Explorer"的资源标识
#define IDS_MESSAGE_BOX_TITLE 2213
HRESULT CLhpHtmlView::OnShowMessage(HWND hwnd,
                                    LPOLESTR lpstrText,
                                    LPOLESTR lpstrCaption,
                                    DWORD dwType,
                                    LPOLESTR lpstrHelpFile,
                                    DWORD dwHelpContext,
                                    LRESULT * plResult)
{
    //载入Shdoclc.dll 和IE消息框标题字符串
    HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
    if (hinstSHDOCLC == NULL)
        return S_FALSE;

	CString strBuf,strCaption(lpstrCaption);
	strBuf.LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE);

    // 比较IE消息框标题字符串和lpstrCaption
    // 如果相同,用自定义标题替换
	if(strBuf==lpstrCaption)
        strCaption = m_DefaultMsgBoxTitle;

    // 创建自己的消息框并且显示
    *plResult = MessageBox(CString(lpstrText), strCaption, dwType);

    //卸载Shdoclc.dll并且返回
    FreeLibrary(hinstSHDOCLC);
    return S_OK;
}		

从代码中可以看到通过设定m_DefaultMsgBoxTitle的值来改变消息宽的标题,修改此值是同过SetDefaultMsgBoxTitle来实现

void CLhpHtmlView::SetDefaultMsgBoxTitle(CString strTitle)
{
	m_DefaultMsgBoxTitle=strTitle;
}


怎样定制、修改浏览器向Web服务器发送的HTTP请求头

  在集成了WebBrowser控件的应用中,Web服务器有时可能希望客户端(浏览器)发送的HTTP请求中附带一些额外的信息或自定义的 HTTP头字段,这样就必须在浏览器中控制向Web服务器发送的HTTP请求。 下面是捕获的一个普通的用浏览器发送的HTTP请求头:

GET /text7.htm HTTP/1.0
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, 
application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Poco 0.31; LHP Browser 1.01; 
.NET CLR 1.1.4322)
Host: localhost
Connection: Keep-Alive

CHtmlView的 
void Navigate2(
   LPCTSTR lpszURL,
   DWORD dwFlags = 0,
   LPCTSTR lpszTargetFrameName = NULL,
   LPCTSTR lpszHeaders = NULL,
   LPVOID lpvPostData = NULL,
   DWORD dwPostDataLen = 0 
);

函数参数lpszHeaders可以指定HTTP请求头,示例如下:

Navigate2(_T("http://localhost"),NULL,NULL, "MyDefineField: TestValue");

我们捕获的HTTP头如下:

怎样修改浏览器标识

  在HTTP请求头中User-Agent字段表明了浏览器的版本以及操作系统的版本等信息。WEB服务器经常需要知道用户请求页面时是来自IE还是来自自己的客户端中的WebBrowser控件, 以便分开处理,而WebBrowser控件向WEB服务器发送的浏览器标识(User-Agent字段)跟用IE发送的是一样的,怎样区分自己的浏览器和IE呢? 微软没有提供现成的方法,要自己想法解决。 前面讨论的定制HTTP请求头就是为这一节准备的。 思路是这样的: 在自己的浏览器里处理每一个U页面请求,把请求头User-Agent改成自己想要的。 在CHtmlView的OnBeforeNavigate2虚函数里来修改HTTP请求是再好不过了,

#define WM_NVTO		(WM_USER+1000)

class NvToParam
{
public:
	CString URL;
	DWORD Flags;
	CString TargetFrameName;
	CByteArray PostedData;
	CString Headers;
};


void CDemoView::OnBeforeNavigate2(LPCTSTR lpszURL, 
                                  DWORD nFlags, 
                                  LPCTSTR lpszTargetFrameName, 
                                  CByteArray& baPostedData, 
                                  LPCTSTR lpszHeaders, 
                                  BOOL* pbCancel)
{
	CString strHeaders(lpszHeaders);
	if(strHeaders.Find("User-Agent:LHPBrowser 1.0") < 0)// 检查头里有没有自定义的User-Agent串
	{
		*pbCancel = TRUE;// 没有,取消这次导航

		if(!strHeaders.IsEmpty())
			strHeaders += "rn";
		strHeaders += "User-Agent:LHPBrowser 1.0";// 加上自定义的User-Agent串

		NvToParam* pNvTo = new NvToParam;
		pNvTo->URL = lpszURL;
		pNvTo->Flags = nFlags;
		pNvTo->TargetFrameName = lpszTargetFrameName;
		baPostedData.Copy(pNvTo->PostedData);
		pNvTo->Headers = strHeaders;

		// 发送一个自定义的导航消息,并把参数发过去
		PostMessage(WM_NVTO,(WPARAM)pNvTo);
		return;
	}

	CHtmlView::OnBeforeNavigate2(lpszURL, 
	                             nFlags, 
	                             lpszTargetFrameName, 
	                             baPostedData, 
	                             lpszHeaders, 
	                             pbCancel);
}

LRESULT CDemoView::OnNvTo(WPARAM wParam, LPARAM lParam)
{
	NvToParam* pNvTo = (NvToParam*)wParam;
	Navigate2((LPCTSTR)pNvTo->URL, 
	           pNvTo->Flags, 
	           pNvTo->PostedData, 
	           (LPCTSTR)pNvTo->TargetFrameName, 
	           (LPCTSTR)pNvTo->Headers);
	delete pNvTo;
	return 1;
}

  在OnBeforeNavigate2中如果发现没有自定义的User-Agent串,就加上这个串,并取消本次导航,再Post一个消息(一定要POST,让OnBeforeNavigate2跳出以后再进行导航 ),在消息中再次导航,再次导航时请求头已经有了自己的标识,所以能正常的导航。

去掉讨厌的异常警告

  在程序中使用了CHtmlView以后,我们在调整窗口大小的时候经常会看到输出窗口输出的异常警告: ReusingBrowser.exe 中的 0x77e53887 处最可能的异常: Microsoft C++ exception: COleException @ 0x0012e348 。

Warning: constructing COleException, scode = DISP_E_MEMBERNOTFOUND($80020003).

这是由于CHtmlView在处理WM_SIZE消息时的一点小问题引起的,采用如下代码处理WM_SIZE消息就不会有此警告了

void CLhpHtmlView::OnSize(UINT nType, int cx, int cy)
{
	CFormView::OnSize(nType, cx, cy);

	if (::IsWindow(m_wndBrowser.m_hWnd)) 
	{ 
		CRect rect; 
		GetClientRect(rect); 
		// 就这一句与CHtmlView的不同
		::AdjustWindowRectEx(rect, GetStyle(), FALSE, WS_EX_CLIENTEDGE);
		m_wndBrowser.SetWindowPos(NULL, 
		                          rect.left, 
		                          rect.top, 
		                          rect.Width(), 
		                          rect.Height(), 
		                          SWP_NOACTIVATE | SWP_NOZORDER); 
	} 
}


怎样处理浏览器内的拖放

  有时可能有这样的需求,我们希望在资源管理器里托一个文件到浏览器而做出相应的处理,甚至是将文件拖到某一个网页元素上来做出相应的处理,而浏览器默认的处理拖放文件操作是将文件打开,但WebBrowser控件给了我们一个自己处理拖放的机会。 那就是在自定义的控制站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetDropTarget方法中调用 浏览器类的OnGetDropTarget虚函数。要处理网页内的拖放,必需在OnGetDropTarget函数中返回一个自己定义的IDropTarget接口指针, 所以我们自己写一个类CMyOleDropTarget从COleDropTarget类派生,并且在实现IDropTarget接口,此类的代码在这就不列出了,请下载演示 程序,参考文件MyOleDropTarget.h和MyOleDropTarget.cpp。我们看CLhpHtmlView中OnGetDropTarget的代码

HRESULT CLhpHtmlView::OnGetDropTarget(LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget )
{
	m_DropTarget.SetIEDropTarget(pDropTarget);

	LPDROPTARGET pMyDropTarget;
	pMyDropTarget = (LPDROPTARGET)m_DropTarget.GetInterface(&IID_IDropTarget);
	if(pMyDropTarget)
	{
		*ppDropTarget = pMyDropTarget;
		pMyDropTarget->AddRef();
		return S_OK;
	}

	return S_FALSE;
}

  m_DropTarget即为自定义的处理拖放的对象。这样就能通过在从CLhpHtmlView派生的类中重载OnDragEnter、OnDragOver、 OnDrop、OnDragLeave虚函数来处理拖放了。在这里顺带讲一下视图是怎样处理拖放的。 要使视图处理拖放,首先在视图里添加一个COleDropTarget(或派生类)成员变量,如CLhpHtmlView中的“CMyOleDropTarget m_DropTarget;”,再在 视图创建时调用COleDropTarget对象的Register,即把视图与COleDropTarget对象关联起来,如CLhpHtmlView中的“m_DropTarget.Register(this);”,再对拖放 触发的事件进行相应的处理, OnDragEnter 把某对象拖入到视图时触发,在此检测拖入的对象是不是视图想接受的对象,如是返回“DROPEFFECT_MOVE”表示接受此对象,如

if(pDataObject->IsDataAvailable(CF_HDROP))// 被拖对象是文件吗?
	return DROPEFFECT_MOVE;

  OnDragOver 被拖对象在视图上移动,同OnDragEnter一样检测拖入对象,如果要接受此对象返回“DROPEFFECT_MOVE”。 OnDrop 拖着被拖对象在视图上放开鼠标,在这里对拖入对象做出处理; OnDragLeave 拖着被拖对象离开视图。 C++的代码写好了,但事情还没完,还必须在网页里用脚本对拖放事件进行处理, 即页面里哪个元素要接受拖放对象哪个元素就要处理ondragenter、ondragover、ondrop,代码其实很简单,让事件的返回值为false即可,这样 C++的代码才有机会处理拖放事件,代码如下:

......
<td ondragenter="event.returnValue = false" ondragover="event.returnValue = false" 
ondrop="event.returnValue = false">
......

  如果要使整个视图都接受拖放,则在Body元素中处理此三个事件。 注意:别忘了让工程对OLE的支持即在初始化应用程序时调用AfxOleInit()。

怎样禁止网页元素的选取

  用网页做界面时多数情况下是不希望网页上的元素是能够被鼠标选中的, 要使网页元素不能被选中做法是:给浏览器的“宿主信息标记”加上DOCHOSTUIFLAG_DIALOG标记。

“宿主信息标记”用N个标记位来控制浏览器的许多性质,如:

  • 禁用浏览器的3D的边缘;
  • 禁止滚动条;
  • 禁用脚本;
  • 定义双击处理的方式;
  • 禁用浏览器的自动完成功能;

…… 更多详情请参考MSDN的DOCHOSTUIFLAG帮助。

怎样修改“宿主信息标记”?

在CDocHostSite中实现IDocHostUIHandler, 在GetHostInfo方法中调用浏览器的OnGetHostInfo虚函数,在虚函数OnGetHostInfo中便可指定“宿主信息标记”,如:

HRESULT CLhpHtmlView::OnGetHostInfo(DOCHOSTUIINFO * pInfo)
{
	pInfo->cbSize = sizeof(DOCHOSTUIINFO);
	pInfo->dwFlags = DOCHOSTUIFLAG_DIALOG | 
	                    DOCHOSTUIFLAG_THEME  | 
	                    DOCHOSTUIFLAG_NO3DBORDER | 
	                    DOCHOSTUIFLAG_SCROLL_NO;
	pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;

	return S_OK;
}

用脚本也可实现: 在Head中加入脚本:

document.onselectstart=new Function(''return false'');

或者

<body onselectstart="return false">。


其它

在CLhpHtmlView中还提供了几个函数, 修改网页元素的内容:

BOOL PutElementHtml(CString ElemID,CString Html);

取表单元素的值:

BOOL GetElementValue(CString ElemID,CString& Value);

设置表单元素的值:

BOOL PutElementValue(CString ElemID,CString Value);

给表单元素设置焦点:

void ElementSetFocus(CString EleName);

代码下载:reusingbrowser

修改浏览器信息 USER AGENT

测试页面http://www.kumouse.com/ietest.php

先来看看我的

GATEWAY_INTERFACE: CGI/1.1
HTTP_ACCEPT_LANGUAGE: zh-cn
HTTP_USER_AGENT: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
REMOTE_ADDR: 114.252.140.239
REMOTE_PORT: 1265

—————- User-Agents列表—————–

Internet Explorer 6
Internet Explorer 7
Internet Explorer 8
Firefox
Chrome
Safari
Netscape
Opera
Maxthon
360SE
OmniWeb

更多的User-Agents信息尽在useragents.xml

有了这个文件中的信息,什么样的浏览器和操作系系统都能伪装出来。

使用User-Agents的方法:

FireFox:[推荐使用User-Agent switcher 这个火狐扩展]
地址栏键入:about:config 回车
设置:general.useragent.override–>自定义的 UA 信息 。

Chrome
启动EXE图标属性中加上启动参数:–user-agent=”UA信息” 即可。

Safari
菜单栏->Edit->Preferences->Advanced->Show Develop menu in menu bar;
菜单栏->Develop->User-Agent->UA信息。

Maxthon
工具栏->工具->遨游设置中心->高级选项->自定义UA信息。

Opera
地址栏键入:opera:config 回车
设置:User-Agent->自定义ID 0—5
注: [0 Default 1 Opera 2 Mozilla, Opera detectable 3 Internet Explorer, Opera detectable 4 Mozilla, Opera hidden 5 Internet Explorer, Opera hidden ]

iPhone
替换 /System/Library/Frameworks/WebKit.framework/WebKit 这个文件的 Mozilla/5.0字符串 为自定义UA信息

IE浏览器的设置需要修改注册表

User-Agent在注册表的位置
[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionInternet SettingsUser-AgentPost Platform] 下,新建字符串值,内容留空,名字为你想在 User-Agent 中加入的字符,这里以 “myok″为例。修改注册表对应的 REG 文件如下:
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionInternet SettingsUser-AgentPost Platform]
"myok"=”"

修改后重启IE浏览器即可。

IE的user-agent取值,下面是下列地址的组合+一些默认值
HKCUSoftwareMicrosoftWindowsCurrentVersionInternet SettingsUser-AgentPost Platform
HKLMSoftwareMicrosoftWindowsCurrentVersionInternet SettingsUser-AgentPost Platform
HKLMSoftwareMicrosoftWindowsCurrentVersionInternet Settings5.0User-AgentPost Platform

IE8的User-Agents:

IE8 on Windows Vista (兼容浏览)
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)

IE8 on Windows Vista
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)

IE8 on Windows 7
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)

64-bit IE on 64-bit Windows 7:
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0)

32-bit IE on 64-bit Windows 7:
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0)

通过修改User-Agent,你还可以完美的伪装:操作系统、浏览器、蜘蛛、邮件客户端、链接检查、分析器、RSS 阅读器等客户端的信息。