存档

‘转载’ 分类的存档

FreeBSD: Redundant DNS with CARP

2010年12月11日 16hot 没有评论

http://zi0r.com/2010/02/28/freebsd-redundant-dns-with-carp.html

Improve reliability with CARP and redundant DNS on two boxes. Almost everyone already has (at least) two nameservers, so why not add CARP into the mix to make it appear as if they’re always available?

This setup assumes two nameservers. Both nameservers will have a total of 3 IPs assigned to them, two of which are the floating CARP IPs and one of which is a unique management IP for each box. Each nameserver will be the CARP backup for the other nameservers primary nameserver IP. If one goes down, the other will assume responsibility for the IP.

You’ll need to assign your nameservers two new IPs (to be used for management) and take the original pair of IPs and we’ll use those for the carp interfaces.

1. Rebuild/install your kernel with:
device carp

2. Edit /etc/rc.conf to add:
cloned_interfaces=“carp0 carp1”
ifconfig_carp0=“create”
ifconfig_carp1=“create”

3. Setup the CARP interfaces:

I set the vhid’s to be the last octet of the floaty IP, however, you can set them to be whatever you want as long as they match on both boxes.

On ns1:
Create /etc/start_if.carp0 with the following:
#!/bin/sh
ifconfig carp0 vhid XX advbase 1 advskew 10 pass supersecretpasswordhere netmask

Create /etc/start_if.carp1 with the following:
#!/bin/sh
ifconfig carp1 vhid YY advbase 2 advskew 10 pass othersupersecretpasswordhere netmask

Then run:
chmod go-rwx /etc/start_if.carp*;chmod +x /etc/start_if.carp*

On ns2:
Create /etc/start_if.carp0 with the following:
#!/bin/sh
ifconfig carp0 vhid YY advbase 1 advskew 10 pass supersecretpasswordhere netmask

Create /etc/start_if.carp1 with the following:
#!/bin/sh
ifconfig carp1 vhid XX advbase 2 advskew 10 pass othersupersecretpasswordhere netmask

Then run:
chmod go-rwx /etc/start_if.carp*;chmod +x /etc/start_if.carp*

  • carp0 on both boxes will be its PRIMARY IP whereas carp1 will be the SECONDARY IP on both. In a non-failover scenario, this means that carp0 on both boxes should show up as MASTER and carp1 should show up as BACKUP.

4. Ensure named is configured to bind to the management IP (for zone transfers, etc.), ns1.ip and ns2.ip (on both boxes!) or ensure that it listens on *.

5. Add net.inet.carp.log=2 to /etc/sysctl.conf for some extra logging info.

6. Reboot. Your primary box should come up with ns1.ip as MASTER and ns2.ip as BACKUP. Your secondary box should come up with ns2.ip as MASTER and ns1.ip as BACKUP. Check ifconfig and dmesg to confirm.

  • Note: CARP traffic is multicast and you may need to alter firewalls as appropriate to allow it. The destination is VRRP.MCAST.NET/224.0.0.18.
    For defining masters/slaves, etc. in bind you will want to reference the management IPs of the boxes, not the floating CARP addresses.
  • Note: You can actually skip using the start_if.* files if you elect to put the ifconfig statements into rc.conf. In order to limit access to your CARP authentication key, you would need to change the permissions on rc.conf which could be bad in certain situations.
  • ESX Note: If you’re trying to do this with a box in VMware, you’ll need to disable the vSwitch security features (accept: promisc, forged transmits, mac changes). This is not advisable in production as any VM on that switch can sniff traffic from any other VM. For my setup at home, I simply allocated a second NIC and a second vSwitch and made the security changes on the dedicated vSwitch. No other VMs should share this other vSwitch where the security features have been disabled.
分类: BSD/linux, DNS, 转载 标签: , ,

freebsd8 mpd5配置pptp

2010年11月19日 16hot 没有评论

今天配置了个pptp,自己懒得写文档,就转一篇网文。

这是防火墙规则,做NAT转发:

lop_if = “lo0″
ext_if = “re0″
sync_if= “re0″
ext_carp = “carp0″

set block-policy drop
set loginterface $ext_if

set skip on $lop_if

scrub in

# nat/rdr
nat on $ext_if from !($ext_if) -> ($ext_if:0)
nat-anchor “ftp-proxy/*”
rdr-anchor “ftp-proxy/*”
#block in

#pass in keep state
pass out keep state

anchor “ftp-proxy/*”

pass quick on { $sync_if } proto pfsync keep state (no-sync)
pass on { $ext_if } proto carp keep state

下面是转载内容:

---------------------------

mpd5 on FreeBSD 8.0-RELEASE-p2
步驟1.安裝 mpd5 套件

切換至 Ports Tree 路徑安裝 mpd 套件

#cd  /usr/ports/net/mpd5          //切換到安裝路徑
#make install clean               //安裝套件並清除安裝過程中產生不必要檔案

步驟2.修改 mpd 設定檔 (mpd.conf)

修改 mpd 設定檔 (mpd.conf) 內容如下

#vi /usr/local/etc/mpd5/mpd.conf          //修改內容如下
startup:
set user weithenn weithenn1688     //使用 Web 登入查看 VPN 狀態時的登入帳號及密碼
set web self 61.60.59.58 5006      //Web 查看 VPN 狀態的 IP 及 Port (此例為 http://61.60.59.58:5006)
set web open                       //開啟 Web
default:
load pptp_server
### Server IP 為 Gateway IP (192.168.88.1)
### Client IP 為 VPN Client 屆時連通後發給的 IP (192.168.88.56 ~ 60)
pptp_server:
set ippool add LANPOOL 192.168.88.56 192.168.88.60
create bundle template VPN
set iface disable on-demand
set iface idle 0
set iface enable proxy-arp
set iface enable tcpmssfix
set ipcp yes vjcomp
set ipcp ranges 192.168.88.1/32 ippool LANPOOL
set bundle enable compression
set ccp yes mppc
set mppc no e40
set mppc yes e128
set mppc yes stateless
create link template VPNLINK pptp
set link action bundle VPN
set link enable multilink
set link yes acfcomp protocomp
set link no pap chap
set link enable chap
set link keep-alive 30 300
set link mtu 1460
set pptp self 61.60.59.58
set link enable incoming

步驟3.修改 mpd 密碼檔 (mpd.secret)

修改 mpd 密碼檔 (mpd.secret) 內容如下,由於此密碼檔為 明碼 的文字檔案,因此設定完成後建議將檔案權限設定為 600。

#/usr/local/etc/mpd/mpd.secret        //修改內容如下
weithenn    “vpn123″                  //設定 VPN 帳號及密碼
#chmod 600 mpd.secret                 //設定檔案權限

步驟4.修改 rc.conf

修改 /etc/rc.conf 檔以便系統重新開機時能自動啟動 mpd 服務

#vi /etc/rc.conf                      //修改 rc.conf 內容如下
gateway_enable=”YES”                  //啟動 Forwarding IP Packet (net.inet.ip.forwarding:1)
mpd_flags=”-b”                        //加入此行,背景執行
mpd_enable=”YES”                      //加入此行

步驟 5.建立 mpd.log

建立 mpd.log 及修改 /etc/syslog.conf 檔以便將 mpd5 運作相關訊息寫入 mpd.log 內

#vi /etc/syslog.conf                  //加入下列二行
!mpd
*.*                                             /var/log/mpd.log
#touch /var/log/mpd.log               //建立 Log 檔
#/etc/rc.d/syslogd reload             //重新載入 syslog.conf 設定

步驟6.啟動 mpd 服務

鍵入如下指令啟動 mpd 服務

#/usr/local/etc/rc.d/mpd5   start     //啟動
stop      //停止
restart   //重新啟動
rcvar     //顯示可加入至 rc.conf 的參數
status    //目前執行狀態

檢查 mpd 執行序是否執行

#ps aux |grep mpd
root      1751  0.0  0.3 13452  5596  ??  Is   12:26PM   0:00.24 /usr/local/sbin/mpd5 -p /var/run/mpd5.pid -b

檢查 mpd 服務是否開啟對應 Port 1723 (可參考 /etc/service) 及剛才指定 Web 觀看 VPN 狀態的 Port 5006

#sockstat | grep mpd
root     mpd5        23468 15 tcp4   61.60.59.58:1723      *:*
root     mpd5        23468 15 tcp4   61.60.59.58:5006      *:*

分类: BSD/linux, 网络技术, 转载 标签: ,

学习PAM模块开发

2010年11月13日 16hot 没有评论

原文地址: http://www.ibm.com/developerworks/cn/linux/l-pamdev/index.html

最近因为在处理SSHD登录,需要用到PAM模块,系统自带的没有能满足需求。只好自己照猫画老虎,模仿学习着写了个pam模块,工作很正常。pam确实很不错的一种认证技术。

=======================================

本文主要通过对Linux PAM源代码进行分析,阐述了PAM的内部实现机制和怎样在应用程序中应用PAM进行认证,以及怎样开发PAM服务模块。

1 引言

身份认证是操作系统安全的重要机制之一,系统通过认证机制核查用户的身份证明,并作为用户进入系统的判定条件,是防止恶意用户进入系统 的第一道门槛。近年来认证理论和技术得到了迅速发展,产生了各种认证机制,如口令机制,RSA, DCE, kerberos认证体制,S/Key和基于智能卡的身份认证等。然而,当系统中引入新的认证机制时,一些系统入口登录服务如login, rlogin和telnet等应用程序就必须改写以适应新的认证机制。为了解决这个问题,1995年Sun公司的Vipin Samar和 Charlie Lai提出了PAM(Pluggable Authentication Modules),并将其应用在Solaris系统上。PAM框架将应用程序与具体的认证机制分离,使得系统改变认证机制时,不再需要修改采用认证机制的 应用程序,而只要由管理员配置应用程序的认证服务模块,极大地提高了认证机制的通用性与灵活性。

现在大多数操作系统都采用PAM实现身份认证,有Linux系统的Linux-PAM和FreeBSD5.x采用的OpenPAM(FreeBSD 4.x采用的是Linux-PAM)等。它们的实现原理一样,只有实现细节不同而已。下面从PAM的应用开发开始介绍。


回页首

2 PAM的应用开发

2.1 PAM框架概览

PAM即可插拔认证模块。它提供了对所有服务进行认证的中央机制,适用于login,远程登录 (telnet,rlogin,fsh,ftp,点对点协议(PPP)),su等应用程序中。系统管理员通过PAM配置文件来制定不同应用程序的不同认证 策略;应用程序开发者通过在服务程序中使用PAM API(pam_xxxx( ))来实现对认证方法的调用;而PAM服务模块的开发者则利用PAM SPI来编写模块(主要是引出一些函数pam_sm_xxxx( )供PAM接口库调用),将不同的认证机制加入到系统中;PAM接口库(libpam)则读取配置文件,将应用程序和相应的PAM服务模块联系起来。 PAM框架结构如图所示。
图 PAM框架结构图
图 PAM框架结构图

其中,pamh是一个pam_handle类型的结构,它是一个非常重要的处理句柄,是PAM与应用程序通信的唯一数据结构,也是调用PAM接口库API的唯一句柄。pam_handle数据结构将在下面的源代码分析一节的介绍。

另外,如上图所示的服务模块分auth(认证管理)、account(账号管理)、session(会话管理)、passwd(口令管 理)四种类型,各个类型模块的作用以及配置文件的四个组成部分模块类型、控制标志、模块路径、模块参数等在很多讲PAM的配置管理的文章里都有介绍,这里 就不再赘述了。

2.2 在应用程序中使用PAM认证

每个使用PAM认证的应用程序都以pam_start开始,pam_end结束。PAM还提供了pam_get_item和 pam_set_item共享有关认证会话的某些公共信息,例如用户名,服务名,密码和会话函数。应用程序在调用了pam_start ()后也能够用这些APIs来改变状态信息。实际做认证工作的API函数有六个(以下将这六个函数简称为认证API):

  • 认证管理–包括pam_authenticate ()函数认证用户,pam_setcred ()设置,刷新,或销毁用户证书。
  • 账号管理–包括pam_acc_mgmt ()检查认证的用户是否可以访问他们的账户,该函数可以实现口令有效期,访问时间限制等。
  • 会话管理–包括pam_open_session ()和pam_close_session ()函数用来管理会话和记账。例如,系统可以存储会话的全部时间。
  • 口令管理–包括pam_chauthok ()函数用来改变密码。

下面看一个简单的login模拟程序:

 /* 使用PAM所必需的两个头文件*/
#include <security/pam_appl.h>
#include <security/pam_misc.h>
void main(int argc, char *argv[], char **renvp)
{
    /* 初始化,并提供一个回调函数 */
    if ((pam_start("login", user_name, &pam_conv, &pamh)) != PAM_SUCCESS)
        exit(1);
    /* 设置一些关于认证用户信息的参数 */
    pam_set_item(pamh, PAM_TTY, ttyn);
    pam_set_item(pamh, PAM_RHOST, remote_host);
    while (!authenticated && retry < MAX_RETRIES)
    {
        status = pam_authenticate(pamh, 0);/* 认证,检查用户输入的密码是否正确 */
}
/* 认证失败则应用程序退出*/
    if (status != PAM_SUCCESS)
    {
      	……
        exit(1);
}
    /*  通过了密码认证之后再调用帐号管理API,检查用户帐号是否已经过期 */
    if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
    {
        if (status == PAM_AUTHTOK_EXPIRED)
        {
            status = pam_chauthtok(pamh, 0);  /* 过期则要求用户更改密码 */
            if (status != PAM_SUCCESS)
                exit(1);
        }
    }
    /* 通过帐户管理检查之后则打开会话 */
    if (status = pam_open_session(pamh, 0) != PAM_SUCCESS)
        exit(status);
	……
    /* 建立认证服务的用户证书*/
    status = pam_setcred(pamh, PAM_ESTABLISH_CRED);
    if (status != PAM_SUCCESS)
       exit(status);
   	……
pam_end(pamh, PAM_SUCCESS);  /* PAM事务的结束 */
……
 }

从上面程序中,我们可以了解到使用PAM认证的一般流程,同时也可以看出PAM API使得使用认证的应用程序不仅不用关心底层使用的服务模块,而且编写起来简洁明了得多。

有关开发使用PAM的应用程序更加详细完整的阐述请参考The Linux-PAM Application Developers’ Guide。

2.3 怎样开发PAM服务模块

首先在编写的服务模块的源程序里要包含下列头文件:

#include <security/pam_modules.h>

PAM的服务模块是一个一个的动态链接库文件(也可以是静态库),PAM接口库通过dlopen来装载这些库。假设源程序名为pam_module-name.c,则需要用下列命令将其编译成动态链接库:

gcc -fPIC -c pam_module-name.c
ld -x --shared -o pam_module-name.so pam_module-name.o

选项-fPIC是指位置无关代码(Position Independent Code),这类代码支持大偏移。使用–shared选项将目标代码放进共享目标库中。

四种类型的模块各自要实现的函数如下表所示:

模块类型 要实现的函数 函数功能
认证管理 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 认证用户
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 设置用户证书
账号管理 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) 帐号管理
会话管理 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 打开会话
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 关闭会话
口令管理 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) 设置口令

当然同一个服务模块可以同时属于多种类型,只要这些类型模块要实现的函数都实现了就可以,比如PAM自带的经典口令认证机制模块pam_unix.so 就可以支持四种模块类型。

下面来看一个最简单的pam_deny模块的源程序pam_deny.c:

1.	#define PAM_SM_AUTH
2.	#define PAM_SM_ACCOUNT
3.	#define PAM_SM_SESSION
4.	#define PAM_SM_PASSWORD
5.	#include "../../libpam/include/security/pam_modules.h"
6.	/* --- 认证管理函数的实现--- */
7.	PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
8.	,const char **argv)
9.	{
10.	    return PAM_AUTH_ERR;
11.	}
12.	PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc
13.	,const char **argv)
14.	{
15.	    return PAM_CRED_UNAVAIL;
16.	}
17.	/* --- 账号管理函数的实现 --- */
18.	PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
19.	,const char **argv)
20.	{
21.	    return PAM_ACCT_EXPIRED;
22.	}
23.	/* --- 口令管理函数的实现 --- */
24.	PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc
25.	,const char **argv)
26.	{
27.	    return PAM_AUTHTOK_ERR;
28.	}
29.	/* --- 会话管理函数的实现 --- */
30.	PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
31.	,const char **argv)
32.	{
33.	    return PAM_SYSTEM_ERR;
34.	}
35.	PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
36.	,const char **argv)
37.	{
38.	    return PAM_SYSTEM_ERR;
39.	}
40.	/* 模块定义结束 */
41.	/* 静态模块数据 */
42.	#ifdef PAM_STATIC
43.	struct pam_module _pam_deny_modstruct = {
44.	   "pam_deny",
45.	    pam_sm_authenticate,
46.	    pam_sm_setcred,
47.	    pam_sm_acct_mgmt,
48.	    pam_sm_open_session,
49.	    pam_sm_close_session,
50.	    pam_sm_chauthtok
51.	};
52.	#endif

很容易看出,pam_deny模块支持四种模块类型。前4行包含静态模块的一些原型申明,第37-39行实现了四种模块类型的函数,因 为这只是一个简单的拒绝服务的模块,所以这些函数只是简单地返回认证错或系统错等PAM错误。最后几行定义了该程序被编译成静态模块所需的一个模块数据结 构。

因此,PAM SPI使得服务模块的开发也相当简单和专一,因为服务模块不再需要考虑和应用程序的交互,只要将自己采用的算法实现好就可以了。

模块源程序可用的flags参数值和返回值的定义这里不作全面介绍,有兴趣者请参考The Linux-PAM Module Writers’ Guide。


回页首

3 PAM接口库源代码分析

上面我们介绍了怎样使用PAM和怎样开发PAM服务模块,要想对PAM的内部机制有个透彻的理解,还需要进一步分析PAM接口库的代码。下面基于Linux 的pam-0.75-40.src.rpm包所得的源代码进行分析。

3.1 PAM接口库主要数据结构

先看一下PAM接口库用到的一些主要数据结构。pam_handle和其他几个主要的数据结构(见../libpam/pam_private.h)及其之间的关系如下图所示。

其中pam_handle包含认证的用户的token、用户名、应用程序名、终端名等信息,以及一个service结构(handlers);前面几节提到的pamh句柄就是一个pam_handle结构。service结构包含服务模块的相关信息,各个域的含义是:

1. module–该结构包含装载的模块的名字、类型(静态或动态模块)、链接句柄(装载模块时的句柄)。

2. modules_allocated–分配的模块数。

3. modules_used–已使用的模块数。

4. handlers_loaded–是否对操作(handlers结构)进行了初始化,handlers结构和初始化handlers见下面的介绍。

5. conf–由应用程序相对应的配置文件指定的服务模块的handlers。

6. other–为缺省配置文件指定的服务模块的handlers。

handlers结构包含六个handler结构链表的指针,六个指针分别对应六种不同的认证API;libpam通过这些指针找到对应模块的SPI服务函数。如下表所示:

handler指针 API函数 SPI函数
authenticate pam_authenticate( ) pam_sm_authenticate( )
setcred pam_setcred( ) pam_sm_setcred( )
acct_mgmt pam_acct_mgmt( ) pam_sm_acct_mgmt( )
open_session pam_open_session( ) pam_sm_open_session( )
close_session pam_close_session( ) pam_sm_close_session( )
chauthtok pam_chauthtok( ) pam_sm_chauthtok( )

handler数据结构是最直接保存服务模块的SPI服务函数的地址及参数的结构,其包含的主要的域的含义如下:

1. (*func)–该函数指针指向handlers所装载的服务模块的服务函数。

2. argc、**argv–分别为*func所指向的函数的参数个数和参数列表。

3. *next–指向堆栈模块中的下一个服务模块的服务函数。由此指针形成所有堆栈模块的服务函数链。

3.2 PAM接口库重要内部函数分析

PAM接口库中有一系列_pam开头的内部函数,那些APIs主要是调用这些内部函数来完成其功能的。

int _pam_add_handler(pam_handle_t *pamh, int must_fail, int other, int type, int *actions, const char *mod_path, int argc, char **argv, int argvlen)

该函数负责加载服务模块的SPI函数。这个函数代码很长有300多行,这里就不列举了。其主要步骤如下:

1. 根据mod_path提供的模块路径装载服务模块(dl_open)。

2. 由type确定的类型来决定要装入的SPI函数名并找到该函数(dlsym)的地址。type类型对应配置文件中的服务模块的四种类型标记,type的值及其对应的要装入的SPI函数名见下表。

模块类型 type SPI函数
认证管理模块 PAM_T_AUTH pam_sm_authenticate
pam_sm_setcred
会话管理模块 PAM_T_SESS pam_sm_open_session
pam_sm_close_session
帐号管理模块 PAM_T_ACCT pam_sm_acct_mgmt
口令管理模块 PAM_T_PASS pam_sm_chauthtok

3. 新分配一个handler结构,将dlsym找到的函数的地址赋给该handler结构的func域,并填充其他的结构信息。

4. 将新分配的handler结构插入到handlers的对应handler结构链表中。

_pam_parse_conf_file函数负责读并分析PAM配置文件,将相关的信息填充到pamh句柄中,并调用_pam_add_handlers加载服务模块的服务函数。

_pam_init_handlers函数主要做handler的初始化工作,先判断handler是否已初始化,若没有则调用_pam_parse_conf_file分析配置文件加载服务模块的服务函数。

_pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h,……)

该函数负责遍历执行模块堆栈中的每一个服务模块对应的SPI函数,即执行h指向的handler结构链表中的每一个func指向的函数,并返回模块堆栈的结果值。

int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)

该函数首先通过调用_pam_init_handlers将模块调度请求转换为指向实际要运行的模块堆栈函数链表的指针,并将该指针传 递给_pam_dispatch_aux函数来遍历模块堆栈执行服务函数。该函数是实现六个认证API函数的主要部分和公共调用的函数,通过choice 选项来区分是哪个认证API函数。

下面分析我们最关心的部分,也即那些认证API是怎样找到和调度配置文件中配置的服务模块的?

3.3 PAM认证API的实现

为了更加清楚地说明PAM接口库是怎样来调度使用模块的,下面给出了pam_authenticate函数执行的流程图:

其他的认证API函数(pam_open_session等)执行过程前面五个步骤同上图,只是在最后一步时传递给_pam_dispatch_aux的指针参数不同,传递3.1节表中每个API函数相对应的那个handler型指针,然后执行相对应的SPI服务函数链。


回页首

4 小结

无论从PAM的应用开发还是它的实现原理来看,这个框架及其思想都是非常完美的,所以几乎各种版本的 UNIX 系统都提供对 PAM 的支持。本文通过对Linux-PAM 进行了深入仔细的分析,阐述了它的内部实现机制,并且讲述了怎样在应用程序中使用PAM和怎样开发PAM服务模块,希望能对大家做认证相关的开发工作有所 帮助。

参考资料

  1. [Vipin Samar, Charlie Lai, 1995]
    Making Login Services Independent of Authentication Technologies. Sun Technical report.
  2. [Andrew G. Morgan, 2001]
    The Linux-PAM Module Writers’ Guide. Linux-PAM Documentation.
  3. [Andrew G. Morgan, 2001]
    The Linux-PAM Module Writers’ Guide. Linux-PAM Documentation.

关于作者

程卫芳,国防科技大学计算机学院在读硕士,研究方向为安全操作系统。 Email: desertbow@sina.com

分类: BSD/linux, C/C++, 转载 标签: , ,

指导性架构设计原则

2010年11月11日 16hot 没有评论

下面的指导性设计原则描述了我们的设计理念

  • 只要某一功能的缺失不会导致无法完成某个实际的应用程序, 就不新增该功能。
  • 决定系统不做成什么样子, 与决定将它做成什么样子同样重要。 不去满足所有的需要, 而是让系统具备可扩展性, 使其能够向上兼容。
  • 尽可能抽象代码中的通用部分, 除非没有可以用来抽象的实例。
  • 如果没有完全理解一个问题, 最好干脆不提供任何解决方案。
  • 如果能用 10% 的工作完成 90% 的工作, 则选择较简单的解决方案。
  • 尽可能隔离复杂性。
  • 提供机制而非策略。 具体而言, 将用户界面策略交由客户去选定。

摘自 Scheifler & Gettys: “X Window System”

http://cnsnap.cn.freebsd.org/doc/zh_CN.GB2312/books/developers-handbook/introduction-archguide.html

分类: BSD/linux, 转载 标签:

Bash Shell中命令行选项/参数处理

2010年11月11日 16hot 没有评论

转自: http://www.cnblogs.com/FrankTan/archive/2010/03/01/1634516.html

0.引言

写程序的时候经常要处理命令行参数,本文描述在Bash下的命令行处理方式。

选项与参数:

如下一个命令行:

./test.sh -f config.conf -v –prefix=/home

我们称-f为选项,它需要一个参数,即config.conf, -v 也是一个选项,但它不需要参数。

–prefix我们称之为一个长选项,即选项本身多于一个字符,它也需要一个参数,用等号连接,当然等号不是必须的,/home可以直接写在–prefix后面,即–prefix/home,更多的限制后面具体会讲到。
在bash中,可以用以下三种方式来处理命令行参数,每种方式都有自己的应用场景。

* 手工处理方式
* getopts
* getopt

下面我们依次讨论这三种处理方式。

1. 手工处理方式

在手工处理方式中,首先要知道几个变量,还是以上面的命令行为例:

*    $0 : ./test.sh,即命令本身,相当于C/C++中的argv[0]
*    $1 : -f,第一个参数.
*    $2 : config.conf
*    $3, $4 … :类推。
*    $#  参数的个数,不包括命令本身,上例中$#为4.
*    $@ :参数本身的列表,也不包括命令本身,如上例为 -f config.conf -v –prefix=/home
*    $* :和$@相同,但”$*” 和 “$@”(加引号)并不同,”$*”将所有的参数解释成一个字符串,而”$@”是一个参数数组。如下例所示:

1 #!/bin/bash
2
3 for arg in ”$*”
4 do
5     echo $arg
6 done
7
8 for arg in ”$@”
9 do
10     echo $arg
11 done
12

执行./test.sh -f config.conf -n 10 会打印:

-f config.conf -n 10    #这是”$*”的输出

-f   #以下为$@的输出

config.conf

-n

10

所以,手工处理的方式即对这些变量的处理。因为手工处理高度依赖于你在命令行上所传参数的位置,所以一般都只用来处理较简单的参数。如

./test.sh 10

而很少使用./test -n 10这种带选项的方式。 典型用法为:

#!/bin/bash

if [ x$1 != x ]
then
#…有参数
else
then
#…没有参数
fi

为什么要使用 x$1 != x 这种方式来比较呢?想像一下这种方式比较:

if [ -n $1 ]  #$1不为空

但如果用户不传参数的时候,$1为空,这时 就会变成 [ -n ] ,所以需要加一个辅助字符串来进行比较。

手工处理方式能满足大多数的简单需求,配合shift使用也能构造出强大的功能,但在要处理复杂选项的时候建议用下面的两种方法。

2. getopts/getopt

处理命令行参数是一个相似而又复杂的事情,为此,C提供了getopt/getopt_long等函数,
C++的boost提供了Options库,在shell中,处理此事的是getopts和getopt.

getopts和getopt功能相似但又不完全相同,其中getopt是独立的可执行文件,而getopts是由Bash内置的。

先来看看参数传递的典型用法:

* ./test.sh -a -b -c  : 短选项,各选项不需参数
* ./test.sh -abc   : 短选项,和上一种方法的效果一样,只是将所有的选项写在一起。
* ./test.sh -a args -b -c :短选项,其中-a需要参数,而-b -c不需参数。
* ./test.sh –a-long=args –b-long :长选项

我们先来看getopts,它不支持长选项。

使用getopts非常简单:
代码

#test.sh

#!/bin/bash

while getopts ”a:bc” arg #选项后面的冒号表示该选项需要参数
do
case $arg in
a)
echo ”a’s arg:$OPTARG” #参数存在$OPTARG中
;;
b)
echo ”b”
;;
c)
echo ”c”
;;
?)  #当有不认识的选项的时候arg为?
echo ”unkonw argument”
exit 1
;;
esac
done

现在就可以使用:
./test.sh -a arg -b -c

./test.sh -a arg -bc
来加载了。
应该说绝大多数脚本使用该函数就可以了,如果需要支持长选项以及可选参数,那么就需要使用getopt.
下面是getopt自带的一个例子:

#!/bin/bash

# A small example program for using the new getopt(1) program.
# This program will only work with bash(1)
# An similar program using the tcsh(1) script language can be found
# as parse.tcsh

# Example input and output (from the bash prompt):
# ./parse.bash -a par1 ’another arg’ –c-long ’wow!*\?’ -cmore -b ” very long ”
# Option a
# Option c, no argument
# Option c, argument `more’
# Option b, argument ` very long ’
# Remaining arguments:
# –> `par1′
# –> `another arg’
# –> `wow!*\?’

# Note that we use `”$@”‘ to let each command-line parameter expand to a
# separate word. The quotes around `$@’ are essential!
# We need TEMP as the `eval set –’ would nuke the return value of getopt.

#-o表示短选项,两个冒号表示该选项有一个可选参数,可选参数必须紧贴选项
#如-carg 而不能是-c arg
#–long表示长选项
#”$@”在上面解释过
# -n:出错时的信息
# – :举一个例子比较好理解:
#我们要创建一个名字为 ”-f”的目录你会怎么办?
# mkdir -f #不成功,因为-f会被mkdir当作选项来解析,这时就可以使用
# mkdir – -f 这样-f就不会被作为选项。

TEMP=`getopt -o ab:c:: –long a-long,b-long:,c-long:: \
-n ’example.bash’ – ”$@”`

if [ $? != 0 ] ; then echo ”Terminating…” >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP’: they are essential!
#set 会重新排列参数的顺序,也就是改变$1,$2…$n的值,这些值在getopt中重新排列过了
eval set – ”$TEMP”

#经过getopt的处理,下面处理具体选项。

while true ; do
case ”$1″ in
-a|–a-long) echo ”Option a” ; shift ;;
-b|–b-long) echo ”Option b, argument \`$2′” ; shift 2 ;;
-c|–c-long)
# c has an optional argument. As we are in quoted mode,
# an empty parameter will be generated if its optional
# argument is not found.
case ”$2″ in
“”) echo ”Option c, no argument”; shift 2 ;;
*)  echo ”Option c, argument \`$2′” ; shift 2 ;;
esac ;;
–) shift ; break ;;
*) echo ”Internal error!” ; exit 1 ;;
esac
done
echo ”Remaining arguments:”
for arg do
echo ’–> ’”\`$arg’” ;
done

比如我们使用
./test -a  -b arg arg1 -c
你可以看到,命令行中多了个arg1参数,在经过getopt和set之后,命令行会变为:
-a -b arg -c — arg1
$1指向-a,$2指向-b,$3指向arg,$4指向-c,$5指向–,而多出的arg1则被放到了最后。

3.总结

一般小脚本手工处理也许就够了,getopts能处理绝大多数的情况,getopt较复杂,功能也更强大。
有问题请指出,不胜感激。

分类: Shell, 转载 标签:

linux coredump配置与调试

2010年11月4日 16hot 没有评论

1.core文件的生成开关和大小限制
———————————
1
)使用ulimit -c命令可查看core文件的生成开关。若结果为0,则表示关闭了此功能,不会生成core文件。
2
) 使用ulimit -cfilesize命令,可以限制core文件的大小(filesize的单位为kbyte)。若ulimit -cunlimited,则表示core文件的大小不受限制。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此 core文件的时候,gdb会提示错误。


2.core
文件的名称和生成路径
—————————-
若系统生成的core文件不带其它任何扩展名称,则全部命名为core。新的core文件生成将覆盖原来的core文件。
1
/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core
可通过以下命令修改此文件:
echo “1″ > /proc/sys/kernel/core_uses_pid
2
proc/sys/kernel/core_pattern可以控制core文件保存位置和文件名格式。
可通过以下命令修改此文件:
echo “/corefile/core-%e-%p-%t” > core_pattern
,可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:
%p – insert pid into filename
添加pid
%u – insert current uid into filename
添加当前uid
%g – insert current gid into filename
添加当前gid
%s – insert signal that caused the coredump into the filename
添加导致产生core的信号
%t – insert UNIX time that the coredump occurred into filename
添加core文件生成时的unix时间
%h – insert hostname where the coredump happened into filename
添加主机名
%e – insert coredumping executable name into filename
添加命令名

3.用gdb查看core文件:
下面我们可以在发生运行时信号引起的错误时发生core dump.
发生core dump之后, gdb进行查看core文件的内容, 以定位文件中引发core dump的行.
gdb [exec file] [core file]
:
gdb ./test test.core
在进入gdb, bt命令查看backtrace以检查发生程序运行到哪里, 来定位core dump的文件->.


4.
开发板上使用core文件调试
—————————–
如果开发板的操作系统也是linuxcore调试方法依然适用。如果开发板上不支持gdb,可将开发板的环境(头文件、库)、可执行文件和core文件拷贝到PClinux下,运行相关命令即可。
注意:待调试的可执行文件,在编译的时候需要加-gcore文件才能正常显示出错信息!


注意的问题:

Linux下要保证程序崩溃时生成Coredump要注意这些问题:

一、要保证存放Coredump的目录存在且进程对该目 录有写权限。存放Coredump的目录即进程的当前目录,一般就是当初发出命令启动该进程时所在的目录。但如果是通过脚本启动,则脚本可能会修改当前目 录,这时进程真正的当前目录就会与当初执行脚本所在目录不同。这时可以查看”/proc/<进程pid>/cwd“符号链接的目标来确定进程 真正的当前目录地址。通过系统服务启动的进程也可通过这一方法查看。

二、若程序调用了seteuid()/setegid()改变 了进程的有效用户或组,则在默认情况下系统不会为这些进程生成Coredump。很多服务程序都会调用seteuid(),如MySQL,不论你用什么用 户运行mysqld_safe启动MySQLmysqld进行的有效用户始终是msyql用户。如果你当初是以用户A运行了某个程序,但在ps里看到的 这个程序的用户却是B的话,那么这些进程就是调用了seteuid了。为了能够让这些进程生成core dump,需要将/proc/sys/fs /suid_dumpable文件的内容改为1(一般默认是0)。

三、这个一般都知道,就是要设置足够大的Core文件大小限制 了。程序崩溃时生成的Core文件大小即为程序运行时占用的内存大小。但程序崩溃时的行为不可按平常时的行为来估计,比如缓冲区溢出等错误可能导致堆栈被 破坏,因此经常会出现某个变量的值被修改成乱七八糟的,然后程序用这个大小去申请内存就可能导致程序比平常时多占用很多内存。因此无论程序正常运行时占用 的内存多么少,要保证生成Core文件还是将大小限制设为unlimited为好。

分类: BSD/linux, 转载 标签:

判断两个链表有无交点,如果有请给出交点

2010年11月4日 16hot 没有评论
  1. /*
  2. * if list_a and list_b has cross point return the addrss of cross-point.
  3. * else return NULL
  4. */
  5. static List *has_cross(List *list_a, List *list_b)
  6. {
  7. List    *pa;
  8. List    *pb;
  9. int     len_a, len_b;
  10. int     i;
  11. len_a = len_b = 0;
  12. pa = list_a; /* 遍历链表a,并记录下此链表的长度 */
  13. while (pa->next != NULL) {
  14. pa = pa->next;
  15. len_a++;
  16. }
  17. pb = list_b; /* 遍历链表b,并记录下链表b的长度 */
  18. while (pb->next != NULL) {
  19. pb = pb->next;
  20. len_b++;
  21. }
  22. if (pa != pb) /* 如果指针pa,pb最后不相等,则两链表没有焦点 */
  23. return NULL;
  24. /* 较长的链表先向后调整到第N个元素(N = max(len_a, len_b) - min(len_a, len_b))
  25. 然后两链表同步向后调整,同时比较地址是否相等,如果相等则为首焦点。*/
  26. pa = list_a;
  27. pb = list_b;
  28. if (len_a > len_b) {
  29. for (i = len_a - len_b; i; i–)
  30. pa = pa->next;
  31. } else {
  32. for (i = len_b - len_a; i; i–)
  33. pb = pb->next;
  34. }
  35. while (pa != pb) {
  36. pa = pa->next;
  37. pb = pb->next;
  38. }
  39. return pa;
  40. }
分类: C/C++, 转载 标签:

删除C程序中的注释

2010年11月4日 16hot 没有评论
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(int argc, char **argv)
  5. {
  6. char start;
  7. char end;
  8. char cur;
  9. start = end = ’\0′;
  10. while ((cur = getchar()) != EOF) {
  11. if (cur == ’/' && start == ’/') {
  12. while ( (cur = getchar()) != EOF && cur != ’\n’) {
  13. start = cur;
  14. }
  15. start = ’\0′;
  16. } else if (start == ’/' && cur == ’*') {
  17. while ( (cur = getchar()) != EOF) {
  18. if (end == ’*' && cur == ’/') {
  19. start = end = cur = ’\0′;
  20. break;
  21. }
  22. end = cur;
  23. }
  24. }
  25. if (start != ’\0′)
  26. putchar(start);
  27. start = cur;
  28. }
  29. putchar(start);
  30. exit(EXIT_SUCCESS);
  31. }
分类: C/C++, 转载 标签:

管道(PIPE)

2010年10月28日 16hot 没有评论
进程通信(IPC)的几种方式及比较
撰文:黄显国080416
难得闲暇,抽空学习了一下进程通信的知识,现将这几天的所学做一下总结,以备遗忘时参考。
进程通信的方式:
Linux系统继承了三种系统的进程通信模式:
1、 基于system V IPC
2、 基于UNIX IPC
3、 基于POSIX IPC
同时还包含一种socket进程间通信,不过这种是不同处理器系统之间的一种网络通信方式,不是我所关心的。
方式一:管道(PIPE
管道分无名管道与有名管道两种
1、 无名管道。
无名管道用于具有亲缘关系的父子进程,子子进程之间的通讯。它的实现函数有
int pipe(int fd[2]);
//fd[2]为描述符数组,包含一个读描述符与一个写描述符,在使用管道通信时,关闭某些不需要的读或写描述符,建立起单向的读或写管道,然后用readwrite像操作文件一样去操作它即可。
如图便是进程1到进程2的一个读管道。
以下是我写的一个pipe的验证程序,分别在父进程和父子进程里向管道写数据,然后在子进程和子子进程里读数据,当尝试改变各子进程的sleep时间以实现渴望的同步时,会发现结果很有趣。注意创建子进程时将复制父进程的管道。
/*******************************************************************************************/
//pipe.c
//frome the example, we can see:
//pipe创建的无名管道,父子进程,子子进程之间都可以通信,由于read
//write默认为阻塞,而进程与进程之间又有某种意义上的同步方法;
//故而可以从下面的程序中得到一些启示。
/*****************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
int pipe_fd[2];
char buf_r[100];
char buf_rr[100];
if(pipe(pipe_fd)<0)
{
printf(“pipe create error\n”);
return -1;
}
else
printf(“pipe create success\n”);
printf(“pipe_fd[0]=%d,pipe_fd[1]=%d\n”,pipe_fd[0],pipe_fd[1]);
if(fork()==0)//子进程
{
close(pipe_fd[1]);//关闭子进程的写描述符
printf(“fork()”);
//sleep(2);
if(fork()==0)//子子进程
{
if(read(pipe_fd[0],buf_r,5)>0)
printf(“BUF_R: in child child process,read from the pipe is %s\n”,buf_r);
close(pipe_fd[0]);
exit(0);
}
Else //子进程
{
if(read(pipe_fd[0],buf_rr,3)>0)
printf(“BUF_RR: in child parent process,read from the pipe is %s\n”,buf_rr);
close(pipe_fd[0]);
exit(0);
}
}
else  //父进程
{
close(pipe_fd[0]);
sleep(5);
if(write(pipe_fd[1],”Hello “,5)!=-1)
printf(“write1 parent pipe success\n”);
if(fork()==0)  //父子进程
{
if(write(pipe_fd[1],”PIPE”,5)!=-1)
printf(“write parent child pipe success”);
close(pipe_fd[1]);
exit(0);
}
close(pipe_fd[1]);
exit(0);
}
}
2、 有名管道
有名管道可用于两个无关的进程之间的通信。它的实现函数是:
int mkfifo(const char *filename, mode_t mode)
//创建一个名为filename的管道,模式可选为读或写方式,阻塞或非阻塞方式等。
下面一个实例演示了mkfifo的使用。Fifo_read.c不断从管道文件里读数据,fifo_write.c往管道文件里写数据。改变sleep的值也会产生类似上面进程同步的问题,而会发现一些缓冲区的特性。
两个程序用gcc编译后在两个终端里运行。
//—————————————————————————————————
//fifo_read.c
//创建有名管道,演示两个不相关的进程之间的通信
//int mkfifo(const char *filename, mode_t mode)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO “/home/huang/myfifo”
int main(int argc,char **argv)
{
int fd;
int nread;
char buf_r[100];
if(mkfifo(“/home/huang/myfifo”,O_CREAT|O_EXCL)<0)//&&(errno!=EEXIST))
{
perror(“mkfifo:”);
printf(“cann’t create fifoserver\n”);
return -1;
}
printf(“Preparing for reading bytes…\n”);
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror(“open!\n”);
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,sizeof(buf_r)))==-1)
{
if(errno==EAGAIN)
printf(“no data yet\n”);
}
printf(“read %s from FIFO\n”,buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}
//——————————————————————————————————————
//fifo_write.c
//创建有名管道,演示两个不相关的进程之间的通信
//int mkfifo(const char *filename, mode_t mode)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO “/home/huang/myfifo”
int main(int argc, char **argv)
{
int fd;
char w_buf[100];
int nwrite;
if(argc==1)
{
printf(“please send some message\n”);
exit(1);
}
fd=open(FIFO,O_WRONLY|O_NONBLOCK,0);
if(fd==-1)
{
if(errno==ENXIO)
printf(“open error;no reading process\n”);
perror(“open:”);
return -1;
}
memset(w_buf,’a',sizeof(w_buf));
printf(“sizeof(w_buf)=%d\n”,sizeof(w_buf));
while(1)
{
if((nwrite=write(fd,w_buf,strlen(w_buf)))==-1)
{
if(errno==EAGAIN)
printf(“The FIFO has not been write yet.\n”);
perror(“write”);
//     else
//     printf(“error in writting!\n”);
}
else
printf(“write %s to the FIFO\n”,w_buf);
sleep(2);
}
close(fd);
}
分类: BSD/linux, C/C++, 转载 标签: ,

libevent的一些知识

2010年10月18日 16hot 没有评论

1、什么动作会触发EV_READ, 什么动作触发EV_WRITE?
2、为什么connection_accept()的eventflag有EV_PERSIST,而connection_time的没有?

通过查资料,终于搞明白了!
在《UNIX网络编程第1卷(第2版)》第6章 “I/O 复用:select和poll函数”(129页),有解释:

===============
描述字符在什么条件下准备好?
1、下列的4个条件中的任何一个满足时,socket准备好读(即有EV_READ):
a)套接口接收缓冲区中的数据字节大于等于套接口接收缓冲区低潮限度的当前值(可以设定,默认为1)
b)连接的读这一半关闭(即收到了FIN的TCP连接)。(也就是读到结尾了)
c)套接口是一个接听套接口且已完成的连接数为非0。
d)有一个套接口错误待处理。
2、下列的3个条件中的任何一个满足时,socket准备好写(即有EV_WRITE):
a)套接口发送缓冲区中的数据字节大于等于套接口发送缓冲区低潮限度的当前值(可以设定,默认为2048),且:
aa)套接口已连接 或 bb)套接口不需要连接(如UDP)
b)连接的写这一半关闭。
c)有一个套接口错误待处理。
===============

根 据刚才的time server中的例程,当client有socket连上来,应该是EV_READ,因为满足1中的c“套接口是一个接听套接口且已完成的连接数为非 0”,当该连接被acept以后,就有EV_WRITE了,因为它满足2中的a“套接口发送缓冲区中的数据字节大于等于套接口发送缓冲区低潮限度的当前 值,且套接口已连接”。所以,event_set的时候,EV_READ是有人连上来,回调函数是connection_accept(),而 EV_WRITE是在accept以后。

那EV_PERSIST呢?
EV_PERSIST,是在event发生了以后,不从队列中拿开,就是下次再有这个消息的时候,继续调那个回调函数,知道程序主动调用了event_del后,才从队列中删除--就算有那个消息也不回调函数。
connection_accept函数,只要有连接上来,就要调用的,所以它需要设置为EV_PERSIST。
但 connection_time函数,用途是在client连上来以后,给client一个返回,然后就断开连接。那它必须在连接连上了以后,只做一次, 所以不需要设置为EV_PERSIST,而且必须在accept中设置--如果没有accept就算有EV_WRITE也不应该调用它啊。

分类: C/C++, 转载 标签: