在网卡驱动里写入数据,是否要修改以太网帧头?

爆板流 2013-06-08 10:56:57
我用单片机控制ENC28J60来实现网络的通信,我在PC机的TCP客户端发送数据到单片机TCP服务器,当TCP服务器接到数据后,再发回去。就是 PC机发送“123”到单片机,单片机接受到后,就将这串数据修改后发回去。
只是这个数据的修改不是在上层改动的,是在网卡驱动里面改的,我修改好数据后,需要重新构建IP、TCP的头,以及校验和的计算;一开始TCP客户端发送“123456”然后服务器接收到后,进行处理将修改数据的值,但是数据的长度是不变的。这样可以实现通信,没有卡住的现象。
但是当我修改了数据,改变了数据的长度后(比如说一开始是 data[6],改后变为data[7],就是在后面加了一位数),同样进行了IP、TCP头构建,以及校验和计算,虽然可以实现通信,但是只能实现一次(比如:PC机TCP客户端发送“123”,然后单片机接受到后就返回了“1234”,可以实现一次通信,第二次TCP客户端发送数据就出现了未知错误了如下图:)
不知道哪里出错了?不知道要不要修改以太网帧头啊?
我试过修改,就是把 以太网帧头的那个 数据类型长度 加1,但是结果第一次都发送不了·请高手指点啊~
...全文
695 33 打赏 收藏 转发到动态 举报
写回复
用AI写文章
33 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
楼主问题解决了?
爆板流 2013-06-13
  • 打赏
  • 举报
回复
顶起~~~~~~~~~~~~~~~
爆板流 2013-06-09
  • 打赏
  • 举报
回复

/*------------------------------------------------------------------------------------*/
// BUF 是定义为了TCPIP的头格式的结构体
void
lzm_tcpchksum(void)
{
  u16_t hsum=0, sum=0;

  BUF->len[0] = (((uip_len-0x000e)) >> 8);
  BUF->len[1] = (((uip_len-0x000e)) & 0xff);

  BUF->tcpchksum = 0;
  BUF->ipchksum = 0;

  lzm_ipchksum();
  hsum=lzm_tcp_psh_chksum();
  if((sum += hsum) < hsum) {
    ++sum;
  }
  hsum=lzm_tcp_hd_chksum();
  if((sum += hsum) < hsum) {
    ++sum;
  }
  hsum=lzm_tcp_data_chksum();
  if((sum += hsum) < hsum) {
    ++sum;
}
  BUF->tcpchksum = ~sum;
}
爆板流 2013-06-09
  • 打赏
  • 举报
回复
引用 16 楼 huwji_stanley_apple 的回复:
我理解uip_buf是IP层递交给链路层的数据,将作为以太网帧的数据被封装,uip_appdata为应用层数据,也就是TCP里面封装的数据,我发现你的代码只修改了uip_len这个值,但实际上应用层数据长度已经修改,那TCP头和IP头里的长度也应该做修改吧?
那个我都改了,在我的lzm_tcpchksum()函数里面处理的。在里面我修改了: 我把数据长度重新填入了 IP和TCP 相关的位置。
  • 打赏
  • 举报
回复
我理解uip_buf是IP层递交给链路层的数据,将作为以太网帧的数据被封装,uip_appdata为应用层数据,也就是TCP里面封装的数据,我发现你的代码只修改了uip_len这个值,但实际上应用层数据长度已经修改,那TCP头和IP头里的长度也应该做修改吧?
  • 打赏
  • 举报
回复
引用 13 楼 kaly_liu 的回复:
类型应该不用改吧,因为你携带的就是ip数据包啊 如果是帧校验问题,那你第一次通信不可能正常啊 tcp包头那些个序号,你封包的时候填对没有?
我的TCP包头的序号我没有动它, 我的程序代功能是:TCP客户端(PC端)发送数据到TCP服务器(单片机端),TCP服务器就将这串数据返回去;(数据的第一位是‘C’这是我的条件判断) 如果按照正常的情况是这样的,在上层数据修改的话就直接将收到的数据发送就可以了:
if(uip_appdata[0]=='C')	//假如收到的数据 第一个是‘C’就将这串数据返回去	 
	  	{
		 uip_send(&uip_appdata[0],uip_len);
		}
这个是在上层的,如果我后面没有做其他动作是绝对可行的,而且没哟问题,执行了上面这个函数后,实际上要发送数据的话是转到网卡驱动进行发送数据的:dev_send()

void dev_send(void)
{	if(uip_appdata[1]=='1') 
	{
	 // uip_len = uip_len+1;
	//  uip_buf[uip_len-1]='9';
	  lzm_tcpchksum();
         }

    enc28j60Packet_len(uip_len);
    enc28j60Packet_data(uip_len, uip_buf);
    enc28j60Packet_Send();
}
上面的代码表示,我在PC机TCP客户端向TCP服务器发送数据,第一个数据是'C'(这样才能够进入发送数据函数),第二个数据是 '1'那么就进行校验和计算 lzm_tcpchksum()(这里我重新计算 IP头校验和、TCP头校验和(TCP伪头部、TCP Head、还有数据)); 1.如果if里面的那两个注释掉的语句没有执行,那么这个代码执行过程没有出错,不会卡住的。 2.但是假如我把那两个注释掉的语句去掉注释,执行代码后,就只能发送一次了。 3.如果我把那两句注释掉的代码 // uip_len = uip_len+1; // uip_buf[uip_len-1]='9'; 改成 uip_appdata[1]='8';//就吧这个数据随便改一个,来验证我的校验和程序代码是否可行 那么这个代码执行过程没有出错,不会卡住的。 详细问题我只能描述这样了,不知道大家看懂了没有哦。那这个和TCP的 顺序号 填充有关系么?求指教啦~~~[/quote] lzm_tcpchksum这个的代码能贴上来吗?
爆板流 2013-06-09
  • 打赏
  • 举报
回复
引用 28 楼 huwji_stanley_apple 的回复:
明白你的意思,就是说其实uip_len是不包含以太网CRC的,以太网CRC是下面硬件自动计算并添加的。
对于校验和我不是很懂,也不多说,
你使用抓包工具来分析过吗

抓包,发现了个问题额。
我代码修改如下:

如果收到数据 "C229" 原样返回;
如果收到数据"C12"就把数据改为"C229"返回(这几就修改了数据的长度了)

我发送数据 "C229"正常返回"C229";如图片中的1、2所示;
发送"C12"第一次正常返回 "C229",但是后面会多一条单片机给PC机的包;(图片中的 3 所示)

然后我再点击发送,发现还可以发送数据"C12"出去,而且返回的数据包显示的是数据时9,但是没有显示在TCP客户端上;


哎,要放假了,先祝大家节日快乐啊~~
爆板流 2013-06-09
  • 打赏
  • 举报
回复
引用 29 楼 huwji_stanley_apple 的回复:
引用 27 楼 kaly_liu 的回复:
[quote=引用 24 楼 tiger9991 的回复:] 楼主你又不玩RawSocket,研究那么多干啥呢?
我是用单片机控制ENC28J60来实现单片机和PC机间通过网络进行通信,现在,由于单片机的RAM不大吧,而有时候需要发送一串叫大的数据比如说900字节的,那单片机只能分多次写入 网卡驱动,然后再一次性发送出去了。所以必须在网卡驱动层进行动手了,所以在发送长长的数据前,必须先构造 IP TCP的头 那些校验和什么的,然后将数据 多次写入网卡发送缓冲区,最后再一次发送,只有这样才可以实现啊。要不我也不想弄的这么麻烦了额。
使用tcp协议的话,你每次发送1字节,发送10次跟每次发送10字节,发送1次,效果不是一样的吗?所以的900字节其实可以分多次发送的吧,不会有任何影响吧?[/quote] 主要是接收端那边的,接收端发送一串命令过来,说明要读取的数据的长度;接收端就在那里等待,直到收到的数据长度够了,才开始往下执行。那么单片机这边就必须一次发送出去,(此时接收端可以一次性的将前面固定的几个字节去掉,后据面的一串就都是数了);要是分几次发送,那接收端接收到数据的时候,会把数据的 头信息什么的都算到数据长度里面了,会出错了。 ps:这个接收端的软件是现成的,我改不了,所以只有按照接收端的格式进行发送数据。
  • 打赏
  • 举报
回复
引用 27 楼 kaly_liu 的回复:
引用 24 楼 tiger9991 的回复:
楼主你又不玩RawSocket,研究那么多干啥呢?
我是用单片机控制ENC28J60来实现单片机和PC机间通过网络进行通信,现在,由于单片机的RAM不大吧,而有时候需要发送一串叫大的数据比如说900字节的,那单片机只能分多次写入 网卡驱动,然后再一次性发送出去了。所以必须在网卡驱动层进行动手了,所以在发送长长的数据前,必须先构造 IP TCP的头 那些校验和什么的,然后将数据 多次写入网卡发送缓冲区,最后再一次发送,只有这样才可以实现啊。要不我也不想弄的这么麻烦了额。
使用tcp协议的话,你每次发送1字节,发送10次跟每次发送10字节,发送1次,效果不是一样的吗?所以的900字节其实可以分多次发送的吧,不会有任何影响吧?
  • 打赏
  • 举报
回复
明白你的意思,就是说其实uip_len是不包含以太网CRC的,以太网CRC是下面硬件自动计算并添加的。 对于校验和我不是很懂,也不多说, 你使用抓包工具来分析过吗
爆板流 2013-06-09
  • 打赏
  • 举报
回复
引用 24 楼 tiger9991 的回复:
楼主你又不玩RawSocket,研究那么多干啥呢?
我是用单片机控制ENC28J60来实现单片机和PC机间通过网络进行通信,现在,由于单片机的RAM不大吧,而有时候需要发送一串叫大的数据比如说900字节的,那单片机只能分多次写入 网卡驱动,然后再一次性发送出去了。所以必须在网卡驱动层进行动手了,所以在发送长长的数据前,必须先构造 IP TCP的头 那些校验和什么的,然后将数据 多次写入网卡发送缓冲区,最后再一次发送,只有这样才可以实现啊。要不我也不想弄的这么麻烦了额。
爆板流 2013-06-09
  • 打赏
  • 举报
回复
其实不论发送和接收数据,都是有会有 “14字节的以太网帧头+IP+TCP头+数据”如果接收到数据,uip_len表示的长度应该是这一串的长度,我把lzm_tcpchksum()里面调用的各个函数贴出来吧,这个应该看了会比较明白点。如下:UIP_LLH_LEN =14

u16_t
lzm_ipchksum(void)
{
BUF->ipchksum = ~(uip_chksum((u16_t *)&uip_buf[UIP_LLH_LEN], 20));//IP从 14 字节开始,长度是20
return 1;
}
/*-----------------------------------------------------------------------------------*/
u16_t
lzm_tcp_psh_chksum(void)//这是TCP伪首部 校验和
{

u16_t hsum=0, sum=0;


if((sum += BUF->srcipaddr[0]) < BUF->srcipaddr[0]) {
++sum;
}
if((sum += BUF->srcipaddr[1]) < BUF->srcipaddr[1]) {
++sum;
}
if((sum += BUF->destipaddr[0]) < BUF->destipaddr[0]) {
++sum;
}
if((sum += BUF->destipaddr[1]) < BUF->destipaddr[1]) {
++sum;
}
if((sum += (u16_t)htons((u16_t)IP_PROTO_TCP)) < (u16_t)htons((u16_t)IP_PROTO_TCP)) {
++sum;
}

hsum = (u16_t)htons((((u16_t)(BUF->len[0]) << 8) + BUF->len[1]) - 20);

if((sum += hsum) < hsum) {
++sum;
}

return sum;
}
/*------------------------------------------------------------------------------------*/
u16_t
lzm_tcp_hd_chksum(void)//这是TCP头部
{
u16_t sum=0;
sum = uip_chksum((u16_t *)&uip_buf[20 + UIP_LLH_LEN], 20);//TCP 头 的数据从 14+20 位置开始,长度是20
return sum;
}
/*------------------------------------------------------------------------------------*/
u16_t
lzm_tcp_data_chksum(void)
{
u16_t sum=0;
/* Compute the checksum of the data in the TCP packet */
sum = uip_chksum((u16_t *)uip_appdata,
(u16_t)(((((u16_t)(BUF->len[0]) << 8) + BUF->len[1]) - 40)));//数据的长度其实是 uip_len-14-20-20了
return sum;
}
/*------------------------------------------------------------------------------------*/

//数据的长度其实是 uip_len-14-20-20了

不知道讲明白了没有啊,请看看吧~~
爆板流 2013-06-09
  • 打赏
  • 举报
回复
引用 22 楼 huwji_stanley_apple 的回复:
uip_buf如果是以太网包,你怎么能够简单在以太网包后面加个‘9’呢? uip_buf如果是IP包,那uip_len怎么在函数lzm_tcpchksum里又变成以太网包的长度?
uip_buf[]是一个全局变量,不论接收数据,还是发送数据,都是存在uip_buf[]中的。额··现在要这么说呢··额:这个代码是来自 uip0.9的 ,它是一个典型的微信TCP/IP协议。在里面是这样定义的: 一开始是定义为:u8_t uip_buf[UIP_BUFSIZE+2];就是定义下最大的长度 然后在程序中,到处定义和使用这个uip_buf但是在使用前会定义成各种形式,比如: #define BUF ((uip_tcpip_hdr *)&uip_buf[UIP_LLH_LEN])//在这个文件中定义成这样 TCP IP 头格式 #define BUF ((struct arp_hdr *)&uip_buf[0]) //这个有定义成ARP头格式

struct arp_hdr {
  struct uip_eth_hdr ethhdr;
  u16_t hwtype;
  u16_t protocol;
  u8_t hwlen;
  u8_t protolen;
  u16_t opcode;
  struct uip_eth_addr shwaddr;
  u16_t sipaddr[2];
  struct uip_eth_addr dhwaddr;
  u16_t dipaddr[2]; 
};

struct uip_eth_hdr {
  struct uip_eth_addr dest;
  struct uip_eth_addr src;
  u16_t type;
};

typedef struct {
  /* IP header. */
  u8_t vhl,
    tos,          
    len[2],       
    ipid[2],        
    ipoffset[2],  
    ttl,          
    proto;     
  u16_t ipchksum;
  u16_t srcipaddr[2], 
    destipaddr[2];
  
  /* TCP header. */
  u16_t srcport,
    destport;
  u8_t seqno[4],  
    ackno[4],
    tcpoffset,
    flags,
    wnd[2];     
  u16_t tcpchksum;
  u8_t urgp[2];
  u8_t optdata[4];
} uip_tcpip_hdr;
这前面的 14 字节应该是uip0.9程序中自己有处理的,在发送的时候自己添加以太网帧头的,这个uip_buf应该是 以太网帧头(14B)+IP头(20B)+TCP头(20B)+数据; 这个数据在程序中就定义为了: uip_appdata = &uip_buf[40 + UIP_LLH_LEN];//UIP_LLH_LEN = 14 这个不知道说明白了没有,要不我源代码弄上来吧,http://pan.baidu.com/share/link?shareid=2327368036&uk=604832080或许是我对这个 uip0.9 理解的不够,因为代码里面涉及到的都是TCP/IP协议这类的东西,我还不是很熟。真是麻烦您啦。
傻X 2013-06-09
  • 打赏
  • 举报
回复
楼主你又不玩RawSocket,研究那么多干啥呢?
爆板流 2013-06-09
  • 打赏
  • 举报
回复
我觉得应该不是这个问题,因为我改变 数值,不改变长度 是可以正常运行的;只是我改变了长度uip_len后,才出现问题,而且还可以发送一次成功···真的很纠结。

其实一开始我还有在怀疑是以太网帧 尾部的 FCS 的问题,因为改变了数据值,这个FCS 应该也要重新修改的,不过通过我的测试以及和手册上的说明,发现应该不是,因为我用的网卡控制器 ENC28J60 ,在初始化的时候已经设置为了自动添加CRC,我查看了手册:

我想如果说改变了数据什么的应该进行上面的处理应该可以了。似乎不必去理会帧的尾部FCS那些东东了。
到这里为止,现在有木有分析出来问题是出在那个环节上了?
到底是不是TCP/IP校验和的问题了?或者还是以太网帧的FCS问题···

真的是有劳各位高手的耐心解答了,在此时万分感激啊~~
  • 打赏
  • 举报
回复
uip_buf如果是以太网包,你怎么能够简单在以太网包后面加个‘9’呢? uip_buf如果是IP包,那uip_len怎么在函数lzm_tcpchksum里又变成以太网包的长度?
  • 打赏
  • 举报
回复

void dev_send(void)
{	if(uip_appdata[1]=='1') 
	{
	 // uip_len = uip_len+1;
	//  uip_buf[uip_len-1]='9';
	  lzm_tcpchksum();
         }

    enc28j60Packet_len(uip_len);
    enc28j60Packet_data(uip_len, uip_buf);
    enc28j60Packet_Send();
}
这里的uip_buf是代表IP包还是以太网包
爆板流 2013-06-09
  • 打赏
  • 举报
回复
引用 19 楼 huwji_stanley_apple 的回复:
BUF->len[0] = (((uip_len-0x000e)) >> 8); BUF->len[1] = (((uip_len-0x000e)) & 0xff); 这里为什么要减去14呢?
这里的uip_len是接收到的总长度的,指的是uip_buf中的数据长度,而uip_buf是 前面14字节是uip_eth_hdr,接下去的20字节是IP头的,接着就是20字节的 TCP头,接下去才是数据data,上面的uip_appdata的开始地址就是uip_buf[14+20+20].
  • 打赏
  • 举报
回复
BUF->len[0] = (((uip_len-0x000e)) >> 8); BUF->len[1] = (((uip_len-0x000e)) & 0xff); 这里为什么要减去14呢?
爆板流 2013-06-08
  • 打赏
  • 举报
回复
引用 11 楼 huwji_stanley_apple 的回复:
[quote=引用 8 楼 kaly_liu 的回复:] [quote=引用 7 楼 huwji_stanley_apple 的回复:] [quote=引用 3 楼 tiger9991 的回复:] 你自定义的协议,每个通信帧竟然没有长度? 一般都有长度控制字节,这样才能帮助达到不粘包之类的功能啊。不然你怎么知道这帧数据有多长呢?这和变长不变长没关系的。 CRC怎么计算都有直接的函数的。就是验证是否通信帧是否由于通信问题导致错误。解释原理也很简单的。
他那个图是以太网的帧格式,以太网的帧是没有长度的,是通过以太网物理层的方式分帧的 参见http://en.wikipedia.org/wiki/Ethernet_frame#Preamble_and_start_frame_delimiter Preamble and start frame delimiter [edit] See also: Syncword A frame starts with a 7-octet preamble and 1-octet start frame delimiter (SFD).[note 3] Prior to Fast Ethernet, the on-the-wire bit pattern for this portion of the frame is 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101011.[3] Since octets are transmitted least-significant bit first the corresponding hexadecimal representation is 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0xD5. PHY transceiver chips used for Fast Ethernet feature a 4-bit (one nibble) Media Independent Interface. Therefore the preamble will consist of 14 instances of 0x5, and the start frame delimiter 0x5 0xD. Gigabit Ethernet transceiver chips use a Gigabit Media Independent Interface that works 8-bits at a time, and 10 Gbit/s (XGMII) PHY works with 32-bits at a time. 很多抓包工具都是抓不到这些前导码的,网卡在物理层过滤掉了[/quote] 不知道我查的资料有木有错,这是我总结的: 以太网帧中的那2个字节标识出以太网帧所携带的上层数据类型: 0x0800代表IP协议数据 0x809B代表AppleTalk协议数据 0x8138代表Novell类型协议数据 0x0806表示ARP包 小于0x0600的表示数据长度 现在是一团乱了额···· [/quote] 你总结的没有错,但实际上这是两种不同协议,一种是标准以太网协议,一种是IEEE802.3mac协议 以上图片来自TCP/IP协议卷1[/quote]恩,好的~O(∩_∩)O谢谢指导
加载更多回复(13)

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧