C#构造TCP报文

C# 构造TCP报文段
2009-07-28 15:17

 

public struct TCPHeader
{
    public ushort 源端口;    //16位源端口
    public ushort 目的端口;    //16位目的端口
    public uint 序列号;   //32位序列号
    public uint 确认号;   //32位确认号
    public byte 首部长度; //4位首部长度/6位保留字
    public byte 标志位;   //6位标志位
    public ushort 窗口大小;     //16位窗口大小
    public ushort 校验和;     //16位校验和
    public ushort 紧急指针;     //16位紧急数据偏移量
}
TCP选项
  每个选项的开始是1字节的kind字段,说明选项的类型。
   Kind=0:选项表结束(1字节)
   Kind=1:无操作(1字节)
   Kind=2:最大报文段长度(4字节)
   Kind=3:窗口扩大因子(4字节)
   Kind=8:时间戳(10字节)
3、窗口扩大选项:
  窗口扩大选项使TCP的窗口定义从16位增加到32位,这并不是通过修改TCP首部来实现的,TCP首部仍然使用16位,而是通过定义一个选项实现对16位的扩大操作来完成的。
4、时间戳选项:
  时间戳选项使发送方在每个报文段中放置一个时间戳值。接收方在确认中返回这个数值,从而允许发送方为每一个收到的ACK计算RTT。

其实如果要自己实现一个TCP通信过程是相当麻烦的一件事情:

(1)发送一个要建立连接的TCP报文;(2)接收到对方同意建立连接的报文;(3)发送一个确认连接的报文;(4)收发数据;(5)发送释放连接的报文;(6)接收对方同意释放连接的报文;(7)接收对方释放连接的报文;(8)发送一个同意释放连接的报文。

还有流量控制、差错控制、计时器的问题……

下面给出的函数仅仅是根据给出的所有参数构造TCP报文的字节流。


public byte[] 构造TCP报文段(UInt32 源IP, UInt32 目的IP, //这是为了计算校验和
UInt16 源端口号, UInt16 目的端口号, UInt32 序列号, UInt32 确认号,
byte URG, byte ACK, byte PSH, byte RST, byte SYN, byte FIN,
UInt16 窗口大小, UInt16 紧急指针,
byte[] 选项与填充部分,
Byte[] 数据部分)

UInt16 选项与填充部分的长度 = (UInt16)选项与填充部分.Length;
UInt16 数据部分的长度 = (UInt16)数据部分.Length;
UInt16 TCP总长度 = (UInt16)(20 + 选项与填充部分的长度 + 数据部分的长度);
Byte[] tcpbytes = new Byte[TCP总长度];
UInt16 校验和 = 0;

//网络字节顺序
源IP = (uint)IPAddress.HostToNetworkOrder((int)源IP);
目的IP = (uint)IPAddress.HostToNetworkOrder((int)目的IP);
源端口号 = (ushort)IPAddress.HostToNetworkOrder((Int16)源端口号);
目的端口号 = (ushort)IPAddress.HostToNetworkOrder((Int16)目的端口号);
序列号 = (uint)IPAddress.HostToNetworkOrder((int)序列号);
确认号 = (uint)IPAddress.HostToNetworkOrder((int)确认号);
窗口大小 = (ushort)IPAddress.HostToNetworkOrder((Int16)窗口大小);
紧急指针 = (ushort)IPAddress.HostToNetworkOrder((Int16)紧急指针);

//填充字节组
BitConverter.GetBytes(源端口号).CopyTo(tcpbytes, 0);//填入源端口号
BitConverter.GetBytes(目的端口号).CopyTo(tcpbytes, 2);//填入目的端口号
BitConverter.GetBytes(序列号).CopyTo(tcpbytes, 4);//填入序列号
BitConverter.GetBytes(确认号).CopyTo(tcpbytes, 8);//
//头部长度和标志位的处理
byte 头部长度 = (byte)((20 + 选项与填充部分的长度) / 4);
头部长度 <<= 4;//左移4位,余下部分为0
tcpbytes[12] = 头部长度;
URG <<= 5; ACK <<= 4; PSH <<= 3; RST <<= 2; SYN <<= 1; FIN = (byte)(URG + ACK + PSH + RST + SYN + FIN);
tcpbytes[13] = FIN;
BitConverter.GetBytes(窗口大小).CopyTo(tcpbytes, 14);//
BitConverter.GetBytes(校验和).CopyTo(tcpbytes, 16);//校验和
BitConverter.GetBytes(紧急指针).CopyTo(tcpbytes, 18);//
if (选项与填充部分的长度 > 0) 选项与填充部分.CopyTo(tcpbytes, 20);
if (数据部分的长度 > 0) 数据部分.CopyTo(tcpbytes, 20 + 选项与填充部分的长度);

//下面是计算校验和
byte[] 伪报文 = new byte[12 + (TCP总长度 + 1) / 2 * 2];//如果不够偶数个字节要补上0
BitConverter.GetBytes(源IP).CopyTo(伪报文, 0);//填入源端口号
BitConverter.GetBytes(目的IP).CopyTo(伪报文, 4);//填入目的端口号
伪报文[8] = 0;//填充0.网络字节顺序
伪报文[9] = 6;//和TCP协议号
TCP总长度 = (ushort)IPAddress.HostToNetworkOrder((Int16)TCP总长度);//网络字节顺序
BitConverter.GetBytes(TCP总长度).CopyTo(伪报文, 10);//填入总长度
tcpbytes.CopyTo(伪报文, 12);
//string ss = 网络字节串(伪报文 );//用于调试检测
unsafe
{
    fixed (byte* pt = 伪报文)
    {
        ushort* pp = (ushort*)pt;
        校验和 = 计算校验和(pp, 伪报文.Length / 2);
    }
}

//校验和 = (ushort)IPAddress.HostToNetworkOrder((Int16)校验和);
BitConverter.GetBytes(校验和).CopyTo(tcpbytes, 16);//校验和
return tcpbytes;
}

unsafe static UInt16 计算校验和(UInt16* buffer, int size)
{
     Int32 cksum = 0;
     int counter;
     counter = 0;

     while (size > 0)
     {
         UInt16 val = buffer[counter];

         cksum += Convert.ToInt32(buffer[counter]);
         counter += 1;
         size -= 1;
     }

     cksum = (cksum >> 16) + (cksum & 0xffff);
     cksum += (cksum >> 16);
     return (UInt16)(~cksum);
}

校验和不需要调整为网络字节顺序,不知道为什么,可能是调不调整校验的结果都一样吧

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值