近日帮一个兄弟查代码问题,再处理完一系列问题以后,发现程序某些时候工作还是不正常,甚至会崩溃。因为环境所限,不能使用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. Clickomania(区间DP)

    描述 Clickomania is a puzzle in which one starts with a rectangular grid of cells of different colours ...

  2. 【JavaScript 7—基础知识点】:BOM

    一.基础知识 1.1,什么是BOM BOM(browser object model):也叫浏览器对象模型,它提供了很多对象,用于访问浏览器的功能.BOM缺少规范,每个浏览器提供商又按照自己想法去扩展 ...

  3. POJ 2106-Boolean Expressions,双栈运用类似表达式求值!

    Boolean Expressions 首先声明此题后台可能极水(毕竟这种数据不好造!).昨天写了一天却总是找不到bug,讨论区各种数据都过了,甚至怀疑输入有问题,但看到gets也可以过,难道是思路错 ...

  4. 九度oj 题目1120:全排列

    题目描述: 给定一个由不同的小写字母组成的字符串,输出这个字符串的所有全排列. 我们假设对于小写字母有'a' < 'b' < ... < 'y' < 'z',而且给定的字符串中 ...

  5. 使用python实现简单的爬虫

    python爬虫的简单实现 开发环境的配置 python环境的安装 编辑器的安装 爬虫的实现 包的安装 简单爬虫的初步实现 将数据写入到数据库-简单的数据清洗-数据库的连接-数据写入到数据库 开发环境 ...

  6. LDP协议详解-上

    MPLS基础 模式 标签分配模式(label allocation)本地为一条路由前缀绑定一个label标签的条件.独立控制模式(independent control)本地RIB学习到的路由(除BG ...

  7. Terracotta

    Terracotta 3.2.1简介 (一) 博客分类: 企业应用面临的问题 Java&Socket 开源组件的应用 hibernatejava集群服务器EhcacheQuartzTerrac ...

  8. iOS学习笔记06-手势识别

    一.UIGestureRecognizer简单介绍 我们已经学习了触摸事件处理,但触摸事件处理起来很麻烦,每个触摸事件处理都需要实现3个touches方法,比较繁琐,实际上我们可以使用更加简单的触摸事 ...

  9. noip2017爆炸记——题解&总结&反省(普及组+提高组)

    相关链接: noip2018总结 noip2017是我见过的有史以来最坑爹的一场考试了. 今年北京市考点有一个是我们学校,我还恰好被分到了自己学校(还是自己天天上课的那个教室),于是我同时报了普及提高 ...

  10. 【前端学习笔记】关于CSS通过一个块改变另一个块的样式

    <body><div id="a" style="background:#0F0; height:100px; width:100px;"&g ...