近日帮一个兄弟查代码问题,再处理完一系列问题以后,发现程序某些时候工作还是不正常,甚至会崩溃。因为环境所限,不能使用gdb,所以我只能review他的代码。最终发现原来是sendto和recvfrom挖的坑。

让我们看一下sendto和recvfrom的原型:

 #include <sys/types.h>
#include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

众所周知,对于TCP/IP的socket来说,也就是AF_INET的socket,其地址类型应该为struct sockaddr_in,在调用sendto和recvfrom不得不进行强制类型转换——万恶的强制类型转换!!!代码要写成这样:

sendto(sock, buf, len, (const struct sockaddr *)&dest_addr, sizeof(dest_addr)); 

而这次的bug就是由于这个强制类型转换造成的。

原来的代码大致如下:

int send_func(int sock, const char *buf, int len)
{
struct sockaddr_in dst;
/* 省略初始化dst */
/* 省略其它业务逻辑 */
return sendto(sock, buf, len, 0, (const struct sockaddr*)&dst, sizeof(struct sockaddr_in));
}

对代码进行了重构,目的地址变为了参数,结果代码变成了下面这个样子:

int send_func(int sock, const char *buf, int len, struct sockaddr_in *dst)
{
/* 省略其它业务逻辑 */
return sendto(sock, buf, len, 0, (const struct sockaddr*)&dst, sizeof(struct sockaddr_in));
}

错误就在于dst从原来的struct sockaddr_in类型,变为了struct sockaddr_in指针,但是sendto的语句却忘了做相应的改动,这样sendto写入到了错误的地址&dst,导致内存错误的发生。这个bug在当前的示例中,好像很容易找到,但是在真正的工程中,在大量的代码中找到这个问题就不是那么容易的事情了。

那么我们如何避免这样类似的bug呢?从这个教训中,我们要得到经验——强制类型转换是万恶之源。为什么要强制转换呢?肯定是因为你的代码不满足被调用者的要求,这已经意味着危险了。另外,由于使用了强制类型转换,这意味着gcc编译器无法发现类型不匹配的错误,这也是上面例子中,问题没有在编译阶段发现的原因。

这样的话,我们就要问一下,为什么sendto和recvfrom要这样设计呢?这是因为sendto和recvfrom面对的所有类型的socket。因此地址不仅仅是AF_INET的地址类型struct sockaddr_in,比如对于Unix域socket,地址类型则是struct sockaddr_un。面对这么多不同地址类型,C本身又不支持函数重载,因此只能定义一个“通用”的结构。

最后如何来避免这种类似问题呢?我们无法改变sendto的用法,但是可以通过封装来解决这一问题——封装本来就是用于隔离变化,隔离问题的手段。

static inline int inet_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr_in *dest_addr)
{
return sendto(sockfd, buf, len, flags, (const struct sockaddr*)dest_addr, sizeof(*dest_addr));
} static inline int un_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr_un *dest_addr)
{
return sendto(sockfd, buf, len, flags, (const struct sockaddr*)dest_addr, sizeof(*dest_addr));
}

通过定义inline函数,来封装强制类型转换,对于每一种具体的socket,都调用封装的inline函数,虽然看上去多写了几行代码,但却是一劳永逸。

将问题或者潜在的问题消除,才能真正提高工作效率和产品质量。

程序编写安全代码——sendto和recvfrom的大坑的更多相关文章

  1. 初识Java程序,编写简单代码?

    Dear All: 初识Java程序,编写简单代码? 首先小编在这里说下我们今天编写Java程序使用的是 eclipse 开发工具! 1.下载eclipse 官网地址:http://www.eclip ...

  2. [python]通过微信公众号“Python程序员”,编写python代码

    今天发现微信公众号中,居然可以编写python代码,很是惊喜,觉得蛮有趣的. 步骤如下: 1.关注微信公众号“Python程序员” 2.关注成功后,点击右下角的“潘多拉”->"Pyth ...

  3. Linux下的 sniff-andthen-spoof程序编写

    Linux下的 sniff-andthen-spoof程序编写 一.任务描述 在本任务中,您将结合嗅探和欺骗技术来实现以下嗅探然后欺骗程序.你需要两台机器在同一个局域网.从机器A ping IP_X, ...

  4. [转]通过Visual Studio为Linux编写C++代码

    Build 2016大会上Microsoft首次公布的Visual Studio 2015扩展提供了在VS2015中编写C++代码,随后通过Linux/UNIX计算机进行编译和执行的能力.这种想法非常 ...

  5. 基于CkEditor实现.net在线开发之路(2)编写C#代码,怎么调用它。

    上一章简约的介绍了CkEditor编辑器,可以编辑js逻辑代码,css,html,C#代码,这章我根据实际例子,讲解怎么编写C#代码和怎么调用它. 大家都还记得刚刚接触程序编时的hello Word吧 ...

  6. Linux进程间通信(九):数据报套接字 socket()、bind()、sendto()、recvfrom()、close()

    前一篇文章,Linux进程间通信——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用. 一.简单回顾——什么是数据报套 ...

  7. (十四)UDP协议的两个主要方法sendto和recvfrom详解

    在网络编程中,UDP运用非常广泛.很多网络协议是基于UDP来实现的,如SNMP等.大家常常用到的局域网文件传输软件飞鸽传书也是基于UDP实现的. 本篇文章跟大家分享linux下UDP的使用和实现,主要 ...

  8. 解决VS2012编写JQuery代码不能智能提示的问题(其他js库的代码提示设置估计类似)

    VS默认设置下编写jQuery代码是这样的: 解决办法: 1.在项目的"管理NuGet程序包"中安装JQuery: 2.打开:工具 -> 选项 -> 文本编辑器 -&g ...

  9. UNIX网络编程-send、recv、sendto、recvfrom详解

    send.recv和sendto.recvfrom,一般情况下,send.recv在TCP协议下使用,sendto.recvfrom在UDP协议下使用,也可以在TCP协议下使用,不过用的很少. 1.s ...

随机推荐

  1. 【转载】linux之sed用法

    linux之sed用法 原文地址:http://www.cnblogs.com/dong008259/archive/2011/12/07/2279897.html   sed是一个很好的文件处理工具 ...

  2. [转载] Laya性能优化精选内容整理

    第一是性能统计工具,这是LayaAir引擎内置的性能统计工具,在代码加入Laya.Stat.show(); 引擎内置的性能统计工具 打开这个工具后,可以用于观察性能,除了FPS越高越好外,其它的值越低 ...

  3. Python之注册表增删改查(干货)

    在Windows平台下,对注册表的增删改查的需求比较多,微软提供了很多用于访问,修改注册表等的API,我们可以使用诸如bat,或者C++等各种方式去访问修改注册表.无所不能的python下如何完成这些 ...

  4. HDU——3786找出直系亲属(DFS+回溯)

    找出直系亲属 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  5. Wing IDE 6.0 算号器注册机代码

    我开发Python时喜欢用Wing IDE, 然后最近发现Wing IDE升级到6.0版本了, 但是之前能在5.1上用的算号器代码不能用在6.0上了, 所以就上网搜搜是否有相关算号器, 果然, 找到了 ...

  6. 论文笔记:Ten years of pedestrian detection, what have we learned?

    最近正在研究行人检测,学习了一篇2014年发表在ECCV上的一篇综述性的文章,是对行人检测过去十年的一个回顾,从dataset,main approaches的角度分析了近10年的40多篇论文提出的方 ...

  7. AFNetworking实时监测网络连接

    
// 网络变化消息 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkNetwork) ...

  8. 自定义Navigation按钮及Title

    导航栏自带的按钮,时常不能满足要求,所以深深需要进行各种定制. 写一个UINavigationItem的category // UINavigationItem+CB_ChangeButton.h 
 ...

  9. Scrapy学习-5-下载图片实例

    1. 在项目下创建一个images文件用于存放图片 2. 载图片相关模块 pip install pillow 3.修改配置文件,激活pipelines ITEM_PIPELINES = { 'Art ...

  10. BZOJ——1611: [Usaco2008 Feb]Meteor Shower流星雨

    http://www.lydsy.com/JudgeOnline/problem.php?id=1611 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1 ...