2017年5月1日星期一

虚拟机中网桥和vlan tag的配置

为提高物理机做虚拟化的利用率,有时物理机的上联交换机端口需要是trunk模式;为保证高可用,物理机的两块网卡需要做bonding;虚拟机的网络一般是用桥接模式;为了保证安全,虚拟机网卡只桥接到指定的bridge上。bonding,vlan tag,bridge的配置一般如下:

eth0和eth1配置:

DEVICE=eth0
ONBOOT=yes
MASTER=bond0
SLAVE=yes

bond0 配置:

#配置默认bonding,用以配置宿主机默认bridge以及宿主机管理地址
DEVICE=bond0
ONBOOT=yes
BRIDGE=bridge0
VLAN=yes
ONBOOT=yes
ARP=no

bond0.X 配置:

#配置带有tag的bonding,用以配置对应的bridge
DEVICE=bond0.X
ONBOOT=yes
BRIDGE=bridgeX
VLAN=yes
ONBOOT=yes
ARP=no

bridge0配置:

#配置默认bridge,用以配置宿主机管理地址
DEVICE=bridge0
ONBOOT=yes
TYPE=Bridge
BOOTPROTO=none
IPADDR=IPADDRESS
NETMASK=NETMASK
STP=off
DELAY=0
BRIDGING_OPTS="max_age=0 ageing_time=0"
GATEWAY=GATEWAY

bridgeX配置:

#配置业务bridge,每个bridge只转发包含相应tag的流量
#虚拟机根据业务不同,桥接到不同的bridgeX即可
DEVICE=bridgeX
ONBOOT=yes
TYPE=Bridge
BOOTPROTO=none
STP=off
DELAY=0
BRIDGING_OPTS="max_age=0 ageing_time=0"

以上配置在bond0.X处解tag,然后将流量分发到不同的bridgeX上,每个bridgeX只能转发解除tag后vlanX的流量。虚拟机桥接到bridgeX,因此也只能获取到指定的vlanX的流量,保证虚拟机隔离在一个vlan中。
还有一种配置是不在bond0上做解tag动作,而是在bridge0上设定tag,创建bridge0.X,bridge0从而可转发所有流量,虚拟机桥接到bridge0,虚拟机可以通过配置不同的vlan接口来获取所有流量,这样会带来一定的不安全因素。

PXE和iPXE

1. PXE原理概述

PXE(preboot execute environment)是Intel公司开发的技术,目前普遍应用于服务器中。PXE通过网络启动操作系统,在启动流程如下:
  • 服务器发送dhcp请求
  • dhcp-server 响应dhcp请求,分配ip地址,并且返回next-server(tftp服务器地址)和filename(一般为pxelinux.0)
  • 服务器获得ip地址,然后通过tftp协议从next-server下载pxelinux.0
  • pxelinux.0读取tftp-server上pxelinux.cfg目录下的配置
  • pxelinux.0展示界面,根据用户输入的选项进行安装或者启动
dhcp-server上/etc/dhcpd.conf的配置:
  next-server 10.127.201.199;
  filename pxelinux.0;
tftp-server一般通过xinet.d服务启动,/etc/xinetd.d/tftp配置如下:
  service tftp
  {
          socket_type             = dgram
          protocol                = udp
          wait                    = yes
          user                    = root
          server                  = /usr/sbin/in.tftpd
          server_args             = -s /var/lib/tftpboot
          disable                 = no
          per_source              = 11
          cps                     = 100 2
          flags                   = IPv4
  }
  
/var/lib/tftpboot 一般包含文件:
  • pxelinux.cfg目录,文件读取顺序为服务器MAC地址,服务器dhcp获取的ip和网段,default文件,即如下顺序:
    • pxelinux.cfg/01-MACADDRESS
    • pxelinux.cfg/C0A8000F
    • pxelinux.cfg/C0A8000
    • pxelinux.cfg/C0A800
    • pxelinux.cfg/C0A80
    • pxelinux.cfg/C0A8
    • pxelinux.cfg/C0A
    • pxelinux.cfg/C0
    • pxelinux.cfg/C
    • pxelinux.cfg/default
  • pxelinux.0
  • 操作系统kernel和initrd文件

2. IPXE 原理概述

PXE有诸多不变之处,IPXE是对PXE的修改,对PXE做了更好的扩展,支持获取服务器上更多的信息,如序列号等等。但是由于物理机目前只支持PXE的方式,所以首先要将PXE启动转换为IPXE启动,才能使用IPXE的便利之处。IPXE流程如下:
  • 服务器 第一次 发送dhcp请求
  • dhcp-server 响应dhcp请求,分配ip地址,并且返回next-server(tftp服务器地址)和filename(一般为undionly.kpxe)
  • 服务器通过tftp协议下载unidonly.kpxe文件
  • undionly.kpxe 第二次 发送dhcp请求
  • dhcp-server 响应dhcp请求,分配ip地址,并且返回一个URL
  • 服务器访问URL,并将硬件信息(如sn)作为参数传递,然后获取根据sn生成的ipxe脚本
  • 服务器执行ipxe脚本,通过http协议下载操作系统kernel和initrd
  • 服务器利用kernel和initrd正常引导
dhcp-server上/etc/dhcpd.conf的配置:
  # 如果是ipxe发送的dhcp请求,表示服务器是在第二次dhcp请求过程中,返回URL,要求服务器将sn作为参数来访问此URL
  if exists user-class and ( option user-class = "iPXE" ) {
      filename "http://10.127.201.97/cgi-bin/ipxe.py?sn=${serial}";
  }
  # 如果不是ipxe发送的dhcp请求,表示服务器是在第一次dhcp请求过程中,返回unidonly.kpxe 文件
  else {
      filename "undionly.kpxe";
  }
  
ipxe.py接收到服务器sn,然后返回对应的ipxe脚本信息,ipxe脚本信息一般如下:
  #!ipxe
  
  set net0/ip IPADDRESS
  set net0/netmask 255.255.255.0
  set net0/gateway GATEWAY
  set dns DNSSERVER
  
  :START
  menu Please choose an operating system to boot
  item --gap --          ------------------------- OS Installation --------------------------------
  item OPERATIONSYSTEM OPERATIONSYSTEM
  choose --default OPERATIONSYSTEM --timeout 5000 os
  goto ${os}
  
  :OPERATIONSYSTEM
  echo Starting install for OPERATIONSYSTEM
  set baseurl http://ks.vip.split.hdb.com/base/OPERATIONSYSTEM/images/pxeboot
  kernel ${baseurl}/vmlinuz
  initrd ${baseurl}/initrd.img
  imgargs vmlinuz text ksdevice=link ramdisk_size=8192 biosdevname=0 sshd=1 ks=http://ks.vip.split.hdb.com/ksconf/OPERATIONSYSTEM.cfg noipv6 ip=IPADDRESS netmask=255.255.255.0 gateway=GATEWAY dns=DNSSERVER
  boot || goto FAILED
  goto START
  

2014年3月27日星期四

利用virsh和virt-install安装管理虚拟机

配置环境+构建思想:

1 宿主机采用kvm做虚拟化
2 网络采用bridge模式
3 硬盘采用lvm管理
4 根据宿主机配置,每台宿主机上分配12个计算单元,根据计算单元计算虚拟机使用的内存,cpu个数,lv数量
5 宿主机初始化的时候创建12个lvfree-XX命名的lv,假定虚拟机使用4个部署单元
6 创建虚拟机的时候,删除4个lvfree-XX,创建一个lvused-XX-4
7 根据统计lvfree-XX的个数,可以知道宿主机的剩余资源
8 虚拟机下线的时候,删除配置文件,删除lvused-XX-4,创建4个lvfree-XX

 

宿主机安装:

前期基本与普通机器安装相同,所有操作在ks的%post 阶段处理。

1 创建bonding和桥,配置:

内核启动参数加上biosdevname=0,避免将网卡识别为em

ifcfg-eth0:
DEVICE=eth0
ONBOOT=yes
MASTER=bond0
SLAVE=yes

ifcfg-bond0:
DEVICE=bond0
ONBOOT=yes
BONDING_OPTS="''"
BRIDGE=br0

ifcfg-br0:
DEVICE=br0
ONBOOT=yes
TYPE=Bridge
IPADDR=192.168.0.1
NETMASK=255.255.255.0
STP=off
DELAY=0
BRIDGING_OPTS="max_age=0 ageing_time=0"

/etc/modprobe.d/bond0.conf
alias bond0 bonding
options bond0 miimon=100 mode=1 primary=eth0

a) bonding 配置需要加入到modprobe.d 中,否则网络有问题
b) BRIDGE需要加入max-age和ageing-time的配置,否则虚拟机安装时,dhcp无法取得地址

2 创建lvm

vgcreate pool /dev/sda1
for i in `1 12`; do lvcreate -n free$i -L120G pool ; done

3 启动messagebus和libvirtd服务

 若使用默认网络配置libvirtd还依赖avahi-daemon服务,并会同时启动dnsmasq
/etc/init.d/messagebus start
/etc/init.d/libvirtd start

4 删除默认的nat网络

virsh net-destroy default
virsh net-undefine default

5 初始化 storage-pool,设置pool自动启动,pool-refresh中,lv自动转化为storage volume.

virsh pool-create-as pool logical --target /dev/pool
virsh pool-dumpxml pool >pool.xml
virsh pool-define pool.xml
virsh pool-autostart pool
virsh pool-refresh pool

6 添加virsh console支持

echo ttyS0 >>/etc/securetty

7 宿主机内核参数调整:

for i in /proc/sys/net/bridge/bridge-nf-*; do echo 0 >$i  ; done

宿主机安装完毕,重启即可。
 

虚拟机安装

假定虚拟机使用4个计算单元

1 删除lvfree,创建lvused

for i in `seq 1 4`; do  virsh vol-delete --pool pool lvfree$i  ;done
virsh vol-create-as --pool pool --name lvused-XX-4 --capacity=480G

2 virt-install

virt-install --hvm                                        #全虚拟化
--wait=-1                                                 #virt-install运行时的超时时间,不设置为-1,虚拟机安装完毕无法自动启动
--graphics vnc,listen=0.0.0.0,port=5900      #vnc设置
--virt-type=kvm                                         #虚拟化软件
--autostart                                                #宿主机启动后,虚拟机自动启动
--noautoconsole                                        #安装时候不连接console
-n guest_v1                                               # virsh list 时候显示的名字,为方便,直接设置为主机名
-r 4096                                                     #内存4G
--vcpus 8                                                  #使用 8个cpu
--disk vol=pool/lvused-XX-4,bus=virtio         #硬盘格式为storage volume,指定方式为 POOL/VOLUME,使用virtio驱动,在虚拟机中识别为vda
--mac==00:16:3e:aa:bb:cc                        #mac地址
--network bridge=br0,model=virtio              #网络采用桥接,使用virtio驱动
-l http://192.168.0.1/repo/rh62-x86-64        #指定安装源,系统会自动寻找kernel和initrd文件
-x "console=ttyS0 serial pip=192.168.1.1 phn=guest_v1 ks=http://192.168.0.1/ksconf/rh62-x86-64.cfg papp=guest" #内核额外参数,console保证可以连接;后面设置了ip,主机名,ks.cfg路径,以及ks安装的时候采用的应用配置

3 虚拟机调整/etc/adjtime 最后一行由LOCAL为UTC

虚拟机下线

删除lvused,创建lvfree

virsh destroy guest_v1
virsh undefine guest_v1
virsh vol-delete --pool pool lvused-XX-4
for i in `seq 1 4`; do  virsh vol-create-as --pool pool --name lvfree-$i --capacity=120G

其他

1 virt-install 高版本支持--clock=LOCAL参数,加此参数后,无需调整虚拟机的/etc/adjtime
2 virt-install 和 virsh加入--connect=qemu+ssh://192.168.1.2/system 后,可以通过ssh通道远程安装和管理虚拟机,除了采用ssh外,还可以采用TLS验证,原理是证书验证,需要下发server和client证书

2012年4月17日星期二

DNS 主从同步的NOTIFY机制

DNS的NOTIFY机制是主从同步使用的,master所控制的zone的序列号变动了以后,会主动通知slave更新,详情可见rfc1996 

1 过程
master slave使用 udp 53端口进行通讯,首先 master 向 slave 的udp53发送一个NOTIFY请求包,slave接收后,向master的udp53发送一个NOTIFY响应包,然后 slave 连接到 master 查询SOA信息,根据序列号判断是否请求进行IXFR传输。

2 相关配置
NOTIFY 的对象默认是有变动的zone的所有NS记录。除此之外,在bind中还通过配置also-notify 指定需要将NOTIFY请求发送到哪些服务器,这些服务器被称为 Stealth Server。bind中还有个配置选项notify,取值可以为yes,no,explicit。yes是默认选项,no表示不发送NOTIFY请求,explicit表示仅发送NOTIFY请求给also-notify中的服务器。

master如果配置了多个IP,则可以通过 notify-source 指定源IP地址。否则如果slave配置了其它的IP为自己的master,则会造成slave误以为收到了错误的NOTIFY请求而无法同步。(rfc1996 3.10)为保证安全,还可以配置allow-notify,来限制哪些master可以向本机发送NOTIFY请求。

如果使用udp进行通讯,master为了避免同时并发量太高,会间隔一段时间(60秒)再发送NOTIFY请求,如果发送请求次数超过5次,则不再向slave发送NOTIFY请求。但是在bind中,我未发现配置选项。

如果配置了多个master,这些master数据由于延时原因可能会不一致,因此slave会向发送NOTIFY请求的master进行同步。

slave 收到相同的NOTIFY请求后,在本次更新结束前,将暂缓响应其它的NOTIFY请求。

2012年4月8日星期日

shell 按行读取变量的值或者命令的输出

shell 中可以使用 while 按行读取一个文件,同时也可以使用 while 按行读取一个变量的值,或者一个命令的输出。方法有4种,分别是进程替换,管道,here document 和here string.

进程替换:  

#! /bin/bash
var=$(cat urfile)
echo "Process Substitution"
while read line
do
    echo "$line"
done < <(echo "$var")

管道:

#! /bin/bash
var=$(cat urfile)
echo "Pipe"
echo "$var" | while read line
do
    echo "$line"
done

here document:

#! /bin/bash
var=$(cat urfile)
echo "Here Document"
while read line
do
    echo "$line"
done <<EOF
$var
EOF


here string:

#! /bin/bash
var=$(cat urfile)
echo "Here String"
while read line
do
    echo "$line"
done <<< "$var"

2012年3月18日星期日

Xwindow 的相关概念



X 就像http,ftp一样是个网络协议,目前的版本是11,release号是7。该协议同样具有Server和Client的概念。X Server的作用是接收客户的请求,绘制客户请求的图形,并把客户的消息传递给X Client端。即X Server的用途是绘图,具体该如何绘制,则是由X Client端提供了绘图数据。根据X Server接收请求和绘制图形这两个功能,可以清楚的看出X Server管理的是客户的输入设备(鼠标键盘)和输出设备(显示器)。所以运行在本地机器上的程序是X Server,远程服务器上的程序是X Client。

设想如下情况:用户点击了FireFox的关闭按钮之后的事情:X Server 通知 FireFox(Client端),客户点击关闭按钮了,你不打算做点神马事情?FireFox 于是开始执行退出命令,并告诉X Server,让我消失吧。于是X Server 不再显示FireFox的窗口。

再设想如下情况:用户通过ssh到远程服务器A,启动一个 X Client 程序(比如FireFox),让该程序通过网络连接到本地的X Server绘制图形;然后用户通过ssh到远程服务器B,启动一个X Client程序(比如xterm),也让该程序通过网络连接到本地的X Server绘制图形。这时,用户可以在自己的终端上同时看到A机器的FF和B机器的xterm。

X Client只提供了绘图数据,至于窗口在显示器的什么位置,X Client是完全不知道的。同时启动两个X client后,X Server 该如何排列窗口呢?这就需要Window Manage了。 最简单的Window Manage是twm。

以上是概念,openSUSE 使用的 XServer是 Xorg,配置文件在/etc/X11/xorg.conf.d,使用的WM是Kwin。X client 就很明显了,进行图形输出的程序都是X client。

可以运行 startx 启动 X server。 startx 是个shell脚本,主要用来读取 Xserver 和 X Client 的配置,然后调用 xinit 启动X server 和 X Client。读取参数的顺序:
  • X server 的参数方面:
    1. 使用 startx 后面接的参数;
    2. 若无参数,则找寻使用者HOME目录的文件,亦即 ~/.xserverrc
    3. 若无上述两者,则以 /etc/X11/xinit/xserverrc
    4. 若无上述三者,则运行 /usr/bin/X
  • X client 的参数方面:
    1. 使用 startx 后面接的参数;
    2. 若无参数,则找寻使用者家目录的文件,亦即 ~/.xinitrc
    3. 若无上述两者,则以 /etc/X11/xinit/xinitrc
    4. 若无上述三者,则运行 xterm
分析openSUSE的 /etc/X11/xinit/xserverc 文件,可以看出实际就是调用X命令来启动X Server的,只是在调用命令前,又通过读取一些文件,配置了环境变量。调用 X 启动 X Server 后,Xorg 会去读取 /etc/X11/xorg.conf 这个配置文件。单纯的 X 启动时,会看到画面漆黑,中心有个可以移动的鼠标的光标。

分析openSUSE的 /etc/X11/xinit/xinitrc 文件,可以看出最后实际调用startkde 或者 gnome-session 来启动桌面环境。

直接调用 X 命令自行启动X Server:X :1 &。:1 表示将X Server启动在哪个终端上,调用该命令后,通过切换终端可以看到一个黑色背景加一个光标。
运行 xterm -display :1 &,可以在终端上看到一个xterm,但是这个xterm没有标题栏,无法移动,可以在里面敲几个命令观察一下。
再次运行 xterm -display :1 &,可以在终端上看到一个全新的xterm,这个xterm覆盖了第一次启动的xterm,同样没有标题栏,无法移动,第一次启动的xterm找不到了。
可以通过启动一个窗口管理器(如twm)来解决此问题。运行twm -display :1 &,可以看到xterm有了标题栏,可以关闭,拖动等等。
这就是X Server,X Client 和 WM 之间的关系。

本地的 X Server 和 X Client 通过socket来通讯,因此6000端口默认是看不到开放的。

更详细的可以参考鸟哥:http://vbird.dic.ksu.edu.tw/linux_basic/0590xwindow.php



2012年3月1日星期四

主从dns服务器view中的数据利用Tsig同步

TSIG同步有两个要点,搞清楚这两个要点,就不会配置错了。
1 每个view需要match一个不同的key,并且allow-transfer中也配置此key。
2 slave的ip不能在master的任何一个view中(any这个view例外)。原因:slave在同步的时候会向master查询对应zone的SOA序列号,如果slave的ip在master中的某个view中,将导致每次查询的soa记录都是此view中的记录,而并非对应的key的view中的记录,因此可能造成无法同步数据,或者同步了其它view中的数据。
 
简单的方式可以这么做:master 的 match-clients 只有 key 而没有任何 ip。这种情况下 master 只能用来更新dns记录并传输记录,而不能作为授权 dns 或者 local dns 使用。这样就避免了在master的match-clients中配置多个诸如 !10.0.0.1 这种acl了。

EXAMPLE:

Master 10.20.149.60
Slave 10.20.149.36

主dns配置:
view "view1" {
        match-clients {key idc-key;};
        allow-transfer {key idc-key;};
        allow-update {key idc-key;};
        also-notify {10.20.149.36;};
        server 10.20.149.36 { keys {idc-key;}; };
        zone "example.com" IN {
        type master;
        file "view/idc/hosts.example.com";
    };
};

从dns配置:
view "view1" {
        match-clients {key idc-key; 10.0.0.0/8; };
        allow-transfer {key idc-key;};
        server 10.20.149.60 {keys idc-key;};
        zone "example.com" IN {
        type slave;
        masters { 10.20.149.60; };
        file "view/idc/hosts.example.com";
    };
};

从dns也可以如此配置:
view "view1" {
        match-clients {key idc-key; 10.0.0.0/8; };
        allow-transfer {key idc-key;};
        zone "example.com" IN {
        type slave;
        masters { 10.20.149.60 keys idc-key;};
        file "view/idc/hosts.example.com";
    };
};

2012年2月25日星期六

在linux下创建winpe启动环境


1 配置dhcp服务,下载ris-linux-0.4(略)

2 配置smb服务,配置文件大概如下:
[reminst]
path = /opt/kickstart/tftpboot
public = yes
browsable = true
read only = no
guest ok = yes

3 配置tftp服务,由于linux对大小写敏感,需要将所有以大写字母命名的文件转换成小写字母命名,转换脚本如下:

find ./ -depth |perl -ne 'chomp;m</[^/]*$>;$d=$`;$_=$f=$&;''s/([\x80-\xFF].)|(\w)/$1\l$2/g;system "mv",$d.$f,$d.$_ if $f ne $_'

配置/opt/kickstart/tftpboot/rules文件,对文件名进行映射,内容如下:
[root@linux_install /opt/kickstart/tftpboot]
#cat rules
ri ^[a-z]: # Remove ¡°drive letters¡±
rg \\ / # Convert backslashes to slashes
rg \# @ # Convert hash marks to @ signs
rg /../ /..no../ # Convert /../ to /..no../
rg A a
rg B b
rg C c
rg D d
rg E e
rg F f
rg G g
rg H h
rg I i
rg J j
rg K k
rg L l
rg M m
rg N n
rg O o
rg P p
rg Q q
rg R r
rg S s
rg T t
rg U u
rg V v
rg W w
rg X x
rg Y y
rg Z z
r ^/(.*) \1

启动tftp服务时,需要指定-m参数:
/opt/kickstart/hpa-tftp/sbin/in.tftpd -l -v -s /opt/kickstart/tftpboot/ -m /opt/kickstart/tftpboot/rules

创建winpe的启动环境:
1 找到 STARTROM.N1_ 文件,并重命名为/opt/kickstart/tftpboot/winpe.0
2 运行sed -i -e 's/NTLDR/peldr/gi' /opt/kickstart/tftpboot/winpe.0
3 找到setupldr.exe 文件(i386目录下),并重命名为/opt/kickstart/tftpboot/peldr
4 运行sed -i -e 's/winnt\.sif/winpe\.sif/gi' /opt/kickstart/tftpboot/peldr
5 运行sed -i -e 's/ntdetect\.com/ntdetect\.wpe/gi' /opt/kickstart/tftpboot/peldr
6 找到NTDETECT.COM文件(i386目录下),并重命名为/opt/kickstart/tftpboot/ntdetect.wpe
7 运行 ris-linux-0.4/fixloader.py /opt/kickstart/tftpboot/peldr (此处仅为pxe启动winpe使用,如配置WinXP的安装源则无需这一操作)
8 配置/opt/kickstart/tftpbootwinpe.sif文件如下:

[Intranet root@KickStart /opt/kickstart/tftpboot]
#cat winpe.sif
[SetupData]
OsLoadOptions = "/fastdetect /minint"
SetupSourceDevice = "\Device\LanmanRedirector\10.20.129.9\reminst\windows\winpe"
[UserData]
ComputerName = WindowsPE

KickStart关于%include的使用方法


我们可以将ks文件中相同的内容独立出来写在同一个文件common当中,然后通过ks中的%include /tmp/ks/common选项包含进来。但是common文件的位置该如何处理呢?
首先解释一下ks文件被安装程序anaconda解析的过程:
1pre脚本运行之前,anacondaks文件进行解析,这个时候anaconda会忽略掉所有它不能找到的文件。
2 运行pre脚本。
3 重新解析那些没有找到的文件。

在第1步中使anaconda访问到include所包含的文件是比较困难的,但是通过运行pre脚本可以使anaconda在第3步中对缺失的文件进行解析。pre脚本可以写成如下的形式:

%pre
#!/bin/bash
###ks服务器的IP192.168.0.1common文件存在于ks服务器上的/kickstart/ks/ 目录下
mount 192.168.0.1:/kickstart /mnt
mkdir –p /tmp/ks
cp /mnt/ks/common /tmp/ks/common

通过以上三个命令anaconda可以直接访问到/tmp/ks/common文件。在第1步对ks配置文件解析时被忽略的%include /tmp/ks/common 顺利的在第3步中被解析了。

可以通过以上方法对系统的分区行为根据需要进行动态设置,方法如下:
1 ks文件中的分区信息独立出来,写进partation文件,然后在ks中添加%include /tmp/ks/partation
2 pre脚本中用fdisk等命令分析硬盘信息
3 pre脚本中运行命令,修改/tmp/ks/partation文件
经过以上三步,anaconda就可以根据硬盘类型,容量的不同对分区信息进行动态调整了

2012年2月23日星期四

qmail对虚拟域用户的处理过程

qmail对域的管理分为两种,一种为本地域,一种为虚拟域。本地域指的是系统用户,虚拟域可以通过手工单独创建,更快捷的方式是通过vpopmail进行管理。如果将信投递给虚拟域用户,qmail处理如下:

假定service.com是一个定义在virtualdomain文件中的虚拟域,该文件包含如下一行
service.com:service.com
qmail收到发给用户user@service.com的信件后首先检查service.com是否存在于locals文件中(service.com是否本地域?),结果不存在,qmail继续检查user@service.com是否在virtualdomains中(user@service.com是否作为一个单独的用户不与service.com域有关?),结果仍然不存在,qmail继续检查service.com是否存在于virtualdomains中,结果是存在的。qmail将user@service.com转化为service.com-user@service.com进行投递。
投递的具体方式定义在assign文件中,assign文件中包含如下的一行:
+service.com-:service.com:89:89:/disk2/vpopmail/domains/service.com:-::
+service.com-定义了匹配service.com-为前缀的邮件,将会以service.com这个用户进行传送,service.com这个用户的uid和gid都是89,/disk2/vpopmail/domains/service.com则定义了用户service.com所在的目录。而最后两个域则定义了.qmail文件实际为.qmail-。
qmail检查service.com-user这个用户是否存在,结果失败,然后qmail继续检查是否存在service.com这个用户,根据对assign文件的说明,可知目前系统存在这个用户,且这个用户的uid和gid都是89,是非0的,用户service.com的家目录/disk2/vpopmail/domains/service.com的uid和gid也都为89。因此把信件交给service.com处理。
service.com用户检查/disk2/vpopmail/domains/service.com是否存在.qmail-user文件,如果失败,那么直接采用.qmail-default文件定义的方式进行处理。

2012年2月20日星期一

加密签名和gpg

为了研究apk的签名,看了下签名和加密的问题,不过本文和apk的签名毫无关系。

加密算法有两种,对称密钥算法和非对称密钥算法。对称密钥算法包括DES,AES等,非对称密钥算法包括RSA,DSA等。对称密钥算法在加密和解密的时候使用同一个密钥。加密解密速度快,如果同时和多方通信,密钥管理成本高;非对称密钥算法则有私钥和公钥之分。私钥保留在用户手中,不对外透漏,公钥公布在互联网上,比如可以放在keys.gnupg.net。公钥和私钥都可以用来加密数据。密钥管理成本低,但是加密解密速度慢。Gnupg简称gpg,是生成和管理密钥的工具。Gnome用seahorse做前端,KDE用Kgpg做前端。

签名是指身份认证,即证明和B通讯的另一方就是A,而不是C冒充的。即A使用私钥签名,B使用A的公钥进行解密。解密成功,则证明对方就是A。
加密是指将信息加密,即使其他用户获得加密后的信息,也无法获得信息正确内容。即B使用A的公钥进行加密,再将加密数据传给A,A收到数据后,利用私钥解密,C即使获得了加密数据,但是由于C没有A的私钥,因此信息依然是安全的。

gpg 命令:
生成key: gpg --gen-key
修改key信息: gpg --edit-key ilout
查看当前的公钥: gpg --list-keys
查看当前的私钥: gpg --list-secret-keys
删除私钥:gpg --delete-secret-keys ilout
删除公钥:gpg --delete-keys ilout
加密信息:gpg -e -r ilout file
解密信息:gpg -d file.asc  >file
导出公钥:gpg --export -a ilout >ilout.pk
将公钥上传到网站: gpg --import --keyserver keys.gnupg.net --send-key E7201101
从文件导入公钥:gpg --import -a ilout.pk
从网站导入公钥:gpg --import --keyserver keys.gnupg.net --recv-keys E7201101
导入公钥后需要对公钥签名,以表示信任该公钥: gpg --sign-key E7201101

上述的签名过程有个漏洞:如果C谎称自己是A,然后将自己的公钥发送给B,B直接导入假冒的公钥,也可以顺利的完成签名认证的过程。为了解决此问题,就产生了数字证书来保证A的公钥无法被任何人冒充。

2012年1月18日星期三

linux利用无线网卡做热点

这是一篇失败的文档,只留作记录,无太大参考价值。

操作系统:opensuse12.1 i586
内核:3.1.0-1.2-desktop
硬件: thinkpad x201i
网卡:Intel Corporation Centrino Advanced-N 6200
软件:hostapd-0.7.3

参考:http://linuxwireless.org/en/users/Documentation/hostapd
过程:

1 创建了最简单的一个配置文件 /tmp/hostapd.min.conf:
interface=wlan0
driver=nl80211
ssid=test
channel=1

2 启动hostapd,hostapd -dd /tmp/hostapd.min.conf,报错:
nl80211: Register Action command failed: ret=-114 (Operation already in progress)
nl80211: Register Action match - hexdump(len=1): 06
nl80211: Failed to register Action frame processing - ignore for now
nl80211: Add own interface ifindex 3
nl80211: Failed to set interface 3 to mode 3: -95 (Operation not supported)
nl80211: Failed to set interface 3 to mode 3: -95 (Operation not supported)
nl80211: Interface mode change to 3 from 0 failed
nl80211: Failed to set interface wlan0 into AP mode
nl80211 driver initialization failed.
ELOOP: remaining socket: sock=4 eloop_data=0x80c5958 user_data=0x80c5e38 handler=0x80751f0
ELOOP: remaining socket: sock=6 eloop_data=0x80c7ae0 user_data=(nil) handler=0x807f2f0

经 google 有人解答是linux驱动不支持AP,不清楚该如何验证这种说法。(理论依据?)基本死翘翘了,不再折腾了。

2012年1月13日星期五

android手机利用Linux主机的网络上网

PC利用android手机的网络联网,英语称为usb tethering;如果反过来,手机利用PC的网络上网,则称为reverse-tethering。如果PC为windows或者MAC,则在安装htc sync后,可以直接进行reverse-tethering;如果PC的操作系统为Linux,则有两种方案:
1 将Linux配置为路由器,利用NAT联网。
2 在Linux上启动bridge,然后联网。
本文简述利用NAT上网的办法,bridge方式与此大同小异。

思路:
1 利用usb线将root过的手机连接到PC上,并在PC上创建虚拟设备usb0
2 在android手机上设定usb0设备的IP地址,dns,路由信息
3 在PC上设定新创建的网卡usb0的IP地址(该地址即为android设备的网关地址)
4 在PC上打开ip_forward,并配置NAT

软硬件要求:
1 root过的android手机
2 Gscript(只为方便,非必须)

配置过程:
1 修改/sys/devices/platform/msm_hsusb/usb_function_switch 的值:
经测试,该文件中值的含义如下:

==== 1 ==== usb_mass_storage:enable
==== 2 ==== adb:enable
==== 4 ==== ether:enable
==== 8 ==== diag:enable
==== 16 ==== serial:enable
==== 32 ==== projector:enable
==== 128 ==== adb:enable
==== 256 ==== modem:enable
==== 512 ==== cdc_ethernet:enable
比如echo 1 >usb_function_switch,表示将android设备作为存储;echo 6>usb_function_switch,表示在PC上创建网卡usb0,并打开adb调试开关。为方便调试,最好将值修改为6,否则直接设定为4也可以。

2 利用setprop配置android的网络信息
dnsserver="8.8.8.8"
setprop net.usb0.ps.ip 192.168.99.1
setprop net.usb0.ps.mask 255.255.255.0
setprop net.usb0.ps.gw 192.168.99.254
setprop net.usb0.ps.dns $dnsserver
setprop net.dns1 $dnsserver
ifconfig usb0 192.168.99.1 netmask 255.255.255.0
ip route add default via 192.168.99.254 dev usb0

3 在Linux主机上,设置usb0的IP地址为192.168.99.254,掩码为24位。可利用ifconfig,或者直接在NetworkManager中设置,不再详述。

4 在Linux主机上设定NAT以及ip_forward
iptables -t nat -A POSTROUTING -j MASQUERADE
echo 1>/proc/sys/net/ipv4/ip_forward

经过以上操作后,android手机理论上就可以上网了。但经过测试,发现不是所有应用程序都可以连接网络,原因见该链接,但是没有解决方案 http://android.stackexchange.com/questions/14046/android-usb-reverse-tethering-how-to-fool-the-apps 。 可以连接网络的程序有:支付宝,电子市场,uc浏览器;无法连接网络的程序有:gtalk,新浪微博,安全隧道。

为方便输入以上命令,最好安装Gscript,创建一个脚本net.sh,内容如下:

#!/bin/sh
dnsserver="8.8.8.8"
echo 6 >/sys/devices/platform/msm_hsusb/usb_function_switch
sleep 3
setprop net.usb0.ps.ip 192.168.99.1
setprop net.usb0.ps.mask 255.255.255.0
setprop net.usb0.ps.gw 192.168.99.254
setprop net.usb0.ps.dns $dnsserver
setprop net.dns1 $dnsserver
ifconfig usb0 192.168.99.1 netmask 255.255.255.0
ip route add default via 192.168.99.254 dev usb0

附录:
如果需要将android手机设置成为硬盘,除了需要
echo 3  >/sys/devices/platform/msm_hsusb/usb_function_switch 以外,还需要设定存储设备的路径,对于我的手机,需要:
echo "/dev/block/vold/179:64" >/sys/devices/platform/usb_mass_storage/lun0/file
具体的值,可以先将手机挂载为硬盘设备,然后记录下 /sys/devices/platform/usb_mass_storage/lun0/file的内容,最后写个disk.sh 文件利用Gscript运行:

#!/bin/sh
echo 3  >/sys/devices/platform/msm_hsusb/usb_function_switch
echo "/dev/block/vold/179:64" >/sys/devices/platform/usb_mass_storage/lun0/file

2011年11月7日星期一

sysctl 中 vm.overcommit_memory 的含义



vm.overcommit_memory 表示内核在分配内存时候做检查的方式。这个变量可以取到0,1,2三个值。对取不同的值时的处理方式都定义在内核源码 mm/mmap.c 的 __vm_enough_memory 函数中。

取 1 的时候 :
此时宏为 OVERCOMMIT_ALWAYS,函数直接 return 0,分配成功。

取 2 的时候:
此时宏为 OVERCOMMIT_NEVER,内核计算:内存总量×vm.overcommit_ratio/100+SWAP 的总量,如果申请空间超过此数值,则分配失败。vm.overcommit_ratio 的默认值为50。

取 0 的时候:
此时宏为 OVERCOMMIT_GUESS,内核计算:NR_FILE_PAGES 总量+SWAP总量+slab中可以释放的内存总量,如果申请空间超过此数值,则将此数值与空闲内存总量减掉 totalreserve_pages(?) 的总量相加。如果申请空间依然超过此数值,则分配失败。

以上为粗略描述,在实际计算时,如果非root进程,则在计算时候会保留3%的空间,而root进程则没有该限制。详细过程可看源码。

大概过程如上所述,但还有很多数据结构还没搞清楚。

2011年11月4日星期五

resolv.conf 的配置和dns解析的关系

本文为笔记,部分地方不准确。目的:resolv.conf 中配置了 search 选项和 domain 选项后,检查系统查询域名的步骤和行为。

0 若不存在 resolv.conf,则将本机做为dns服务器,然后取主机名的domain,然后附加到域名中进行查询。查询时首先查询AAAA记录,再查询A记录。以下均为查询nxyi-dns2.hst.xyi.cn.xdc.net时,得到的不同结果。

1 若没有search(最长为6个)和domain选项,则取主机名的domain,然后附加到域名中进行查询:

17-Oct-2011 23:35:17.536 queries: client 172.22.41.176#44119: query: nxyi-dns2.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:35:17.536 queries: client 172.22.41.176#56562: query: nxyi-dns2.hst.xyi.cn.xdc.net.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:35:17.536 queries: client 172.22.41.176#53616: query: nxyi-dns2.hst.xyi.cn.xdc.net IN A +

2 如有domain,(domain xdc.net)则,附加domain到域名中进行查询:

17-Oct-2011 23:38:02.031 queries: client 172.22.41.176#43411: query: nxyi-dns2.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:38:02.031 queries: client 172.22.41.176#41697: query: nxyi-dns2.hst.xyi.cn.xdc.net.xdc.net IN AAAA +
17-Oct-2011 23:38:02.031 queries: client 172.22.41.176#35978: query: nxyi-dns2.hst.xyi.cn.xdc.net IN A +

3 若有多个search (search hst.xyi.cn.xdc.net hst.dsl.crm.xdc.net hst.x.dw.xdc.net) 则依次附加,然后查询,顺序依然首先查询AAAA记录,再查A记录:

17-Oct-2011 23:39:25.272 queries: client 172.22.41.176#49875: query: nxyi-dns2.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:39:25.272 queries: client 172.22.41.176#36371: query: nxyi-dns2.hst.xyi.cn.xdc.net.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:39:25.272 queries: client 172.22.41.176#33367: query: nxyi-dns2.hst.xyi.cn.xdc.net.hst.dsl.crm.xdc.net IN AAAA +
17-Oct-2011 23:39:25.272 queries: client 172.22.41.176#42772: query: nxyi-dns2.hst.xyi.cn.xdc.net.hst.x.dw.xdc.net IN AAAA +
17-Oct-2011 23:39:25.273 queries: client 172.22.41.176#45355: query: nxyi-dns2.hst.xyi.cn.xdc.net.xdc.net IN AAAA +
17-Oct-2011 23:39:25.273 queries: client 172.22.41.176#35669: query: nxyi-dns2.hst.xyi.cn.xdc.net IN A +

4 若nxyi-dns5.hst.xyi.cn.xdc.net没有找到,则附加search进行查询A记录:

17-Oct-2011 23:52:21.527 queries: client 172.22.41.176#56298: query: nxyi-dns5.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:52:21.528 queries: client 172.22.41.176#40880: query: nxyi-dns5.hst.xyi.cn.xdc.net.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:52:21.528 queries: client 172.22.41.176#40359: query: nxyi-dns5.hst.xyi.cn.xdc.net IN A +
17-Oct-2011 23:52:21.528 queries: client 172.22.41.176#36129: query: nxyi-dns5.hst.xyi.cn.xdc.net.xyi.cn.xdc.net IN A +

5 若存在ndots:6,则由于域名仅仅包含了5个点,则顺序改变,首先附加search查找,然后再查找域名本身。默认为ndots值为1,即域名只要包含一个点,就不会首先附加search:

17-Oct-2011 23:53:04.260 queries: client 172.22.41.176#50630: query: nxyi-dns5.hst.xyi.cn.xdc.net.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:53:04.260 queries: client 172.22.41.176#38081: query: nxyi-dns5.hst.xyi.cn.xdc.net IN AAAA +
17-Oct-2011 23:53:04.260 queries: client 172.22.41.176#36043: query: nxyi-dns5.hst.xyi.cn.xdc.net.xyi.cn.xdc.net IN A +
17-Oct-2011 23:53:04.260 queries: client 172.22.41.176#58401: query: nxyi-dns5.hst.xyi.cn.xdc.net IN A +

总结:
系统查询域名主要使用 gethostbyname(过时) 和 getaddrinfo 两个函数,二者接收的参数和返回的数据类型都不相同。其中 gethostbyname 实际调用 gethostbyname2,根据传递的参数可以查询AAAA或者A记录。getaddrinfo 功能更强大,可以控制是查询AAAA,A,或者先AAAA后A记录。详细可见glibc中 resolv/gethnamaddr.c 和 sysdeps/posix/getaddrinfo.c 中的实现。

2011年10月20日星期四

Linux 字体的一些概念

google + baidu 得到的结果,不保证准确。

字体有矢量字体和位图字体两种类型。矢量字体中的字形由数学曲线描述,这种字体由于需要计算,因此显示速度慢,但是该字体可以任意缩放,主要包括Truetype,Type1,OpenType等;位图字体又叫点阵字体,字形由点组成,显示速度快,但是缩放后会有锯齿。显示字形时要将矢量字体转换为位图字体,这个过程称为光栅化。

FreeType 是字体函数库。应用程序通过FreeType可以访问字体文件,获取字体信息,字形数据,而无需关心字体文件的位置。
相关文档参见:http://www.unixresources.net/linux/clf/kylix/archive/00/00/59/21/592188.html

X包含两种字体系统:原始的核心X11字体系统和Xft系统。 Xft比核心X11字体系统对缩放字形支持要好,并且提供了更多的功能。另外,Xft和核心X11字体系统是不兼容的。Xft 是 FreeType 之上的库,编写 Xft 库是为了给X应用程序提供一个能访问 FreeType 字体光栅化引擎和X渲染的接口。对于不支持渲染的X服务器,还要提供一个访问原始核心X11字体系统的功能。
相关文档参见:http://www.x.org/archive/X11R6.8.2/doc/fonts.html
https://wiki.archlinux.org/index.php/Font_Configuration_%28%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%29#X.E7.9A.84.E5.AD.97.E4.BD.93.E9.85.8D.E7.BD.AE.E5.92.8C.E7.BE.8E.E5.8C.96
http://it.china-b.com/olbf/468958.html

Fontconfig 配置了程序如何选取字形,主要配置文件为 /etc/fonts/fonts.conf。除了字体信息外,还定义了字体的cache目录以及用户的字体目录。
具体配置方法参见:http://www.xfree86.org/~dawes/4.3.0/fonts2.html#4
https://wiki.archlinux.org/index.php/Font_Configuration_%28%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%29

最后记录两个命令:
fc-cache 添加新字体后刷新cache,否则新字体无法使用。
fc-list 查看所有配置过的字体。
xlsfont 查看所有的X字体。

快速配置字体的方案:http://www.ikde.org/news/linux_font_config_from_beginner_to_expert_1/

2011年10月17日星期一

vsftpd 配置虚拟用户

最近配置两遍VSFTPD的虚拟用户了,第一次去google,第二次ssh到第一次的机器上运行history。。。现在记录一下vsftpd使用虚拟用户需要的相关配置:

1 配置vsftpd使用的虚拟用户,以及虚拟用户配置文件的目录。
本示例中虚拟用户映射到系统真实的notes用户,虚拟用户的配置目录在 /etc/vsftpd/vsftpd_user_conf  

chroot_local_user=YES
guest_enable=YES
guest_username=notes
user_config_dir=/etc/vsftpd/vsftpd_user_conf
virtual_use_local_privs=YE

2 在一个文件中配置虚拟用户的账户名和密码,该文件奇数行为用户名,偶数行为密码。
本示例中该文件为 /etc/vsftpd/userlist,虚拟用户名为tom,密码为456b7016a91

#cat /etc/vsftpd/userlist
tom
456b7016a91

3 利用该文件生成数据库文件,可能需要安装 db4-utils

yum -y install db4-utils
db_load -T -t hash -f /etc/vsftpd/userlist /etc/vsftpd/vsftpd_login.db

4 配置虚拟用户 tom 的具体权限

#cat /etc/vsftpd/vsftpd_user_conf/tom
local_root=/home/video_files
write_enable=YES
anon_other_write_enable=YES
anon_umask=022
allow_writeable_chroot=YES

4 配置 vsftpd 使用 PAM 验证:

pam_service_name=vsftpd

5 PAM验证使用刚才生成的数据库文件:

#cat /etc/pam.d/vsftpd
auth required /lib64/security/pam_userdb.so db=/etc/vsftpd/vsftpd_login
account required /lib64/security/pam_userdb.so db=/etc/vsftpd/vsftpd_login

2011年6月14日星期二

rsync delete 参数

抄的抄的,原文见: http://superuser.com/questions/156664/rsync-delete-options
我好奇的是什么时候才需要考虑具体的delete方式。

--del/--delete_during Deletes files from the destination dir as they are copied (saves memory compared to --delete-before: --delete-before makes a separate scan to look for deleteables)
--delete Deletes files in the destination directory if they don't exist in the source directory.
--delete-before Delete files in the destination directory before coping file-with-same-name from source directory
--delete-during Delete files in the destination directory WHILE copying file-with-same-name from source directory
--delete-delay Mark deletes during transfer, but wait until transfer is complete
--delete-after Receiver deletes after transfer, not before...If some other part of the rsync moved extra files elsewhere, you'd want this instead of --delete-delay, because --delete-delay decides what its going to delete in the middle of transfer, whereas --delete-after checks the directory for files that should be deleted AFTER everything is finished.
--delete-excluded Deletes files from the destination directory that are explicitly excluded from transferring from the source directory.


The point of rsync is not copying, it is archiving. This is an important distinction. Processing deleted/changed files is critical, and in many cases nuanced.

Most of the other options are space or performance related. When you delete the files is important if you want to make sure the transfer is successful before you do anything, but if your device is too small to handle 2 copies of all the information, you need to delete as you go, etc. It's a little wacky because of it's long history across multiple platforms: some options have been added so that people who were used to certain behaviour wouldn't be confused.

2011年6月1日星期三

linux的系统时间与硬件时间

用了 opensuse 11.4 后发现系统时间一直比当前时间快8个小时,明显是时区有问题,但是/etc/localtime 又没有错误,今天查了查资料,搞清楚了几个概念。
1 GMT:格林威治时间。基本可以理解为一个标准时间,时区的原点,按照经线将地球分为24个时区。
2 UTC:世界协调时间。由于地球不是正圆的,自传速度也不一样,因此按照经线来均分地球时区有些不太精确。此时间相当于对GMT的修正,实际还是GMT时间。中国位于东8区,又称为CST(China Standard Time) CST时间 = UTC时间 + 8小时。
3 硬件时间:即通过电池在BIOS里记录的时间。Linux 对此时间的判断有两种可能:a 该时间表示本地时间;b 该时间表示 UTC 时间。Linux启动时会读取硬件时间,并根据 /etc/sysconfig/clock 中的 HWCLOCK 判断该时间记录的是本地时间还是 UTC 时间。
4 系统时间:就是 Linux 系统中显示的本地时间,该时间取决于系统开机后读取的时间以及当前的时区设置。时区配置文件为 /etc/localtime。/usr/share/zoneinfo 下保存了所有时区的配置文件,找个合适的,复制到 /etc/localtime 就可以了。

有了以上概念就不难理解了,在时区配置没有问题的情况下,一定是 Linux 在启动时读取硬件时间出了问题。只要将硬件时钟配置正确,并让 Linux 正确的读出来就问题就可以解决了。考虑到 windows 在启动时会将硬件时间设定为系统时间,因此 Linux 最好也采用此配置,否则启动 windows 后,windows 时钟可能会不正确。另外,系统时间是本地时间,而硬件时间则是 UTC 时间,看起来也不舒服。

修正方式如下:

方法1,读取硬件时间作为本地时间
1 hwclock --localtime 读取硬件时间。
2 如果要将该时间设置为系统时间,则将该时间写回系统 hwclock --localtime --hctosys,这时系统时间应该就变正确了
3 对于openSUSE12.2之前的版本:修改/etc/sysconfig/clock 中的HWCLOCK,由--utc 为 --localtime 否则下次重启还会出现问题。对于openSUSE 12.2:修改/etc/adjtime 将第三行从UTC更改为LOCAL。

办法 2,直接修改系统时间
1 date -s 设置系统时间
2 将系统时间写回硬件时钟 hwclock --localtime --systohc
3 修改/etc/sysconfig/clock


查看和设置时间的几个例子:

1 显示本地时间
# date
Thu Jun 2 21:48:44 CST 2011

2 查看硬件时间(指定硬件时间即为本地时间)
# hwclock --localtime
Thu Jun 2 21:48:47 2011 -0.239341 seconds

3 查看硬件时间 (指定硬件时间为 UTC 时间,则会对该时间+8,转换为本地时间后再显示出来)
# hwclock --utc
Fri Jun 3 05:48:49 2011 -0.125543 seconds

4 指明硬件时间为本地时间,并将该时间设置为系统时间
# hwclock --localtime --hctosys

5 指明硬件时间为本地时间,并将系统时间写回到 BIOS 中
# hwclock --localtime --systohc

2011年4月25日星期一

Mail Exchangers 避免循环转发

比如 abc.com 有以下3条 mx 记录:

abc.com. IN MX 0 0.abc.com.
abc.com. IN MX 10 11.abc.com.
abc.com. IN MX 10 12.abc.com.
abc.com. IN MX 20 21.abc.com.


向 0.abc.com 投递失败后,发送方会向 11 或者 12 进行投递,若 11 和 12 只有转发功能,则依然需要查询 mx 记录,然后再次进行投递。11 收到邮件后可能会投递给 12,12 收到邮件后会投递给 11,循环由此产生。
为解决此问题,转发服务器会自动 disable 优先级不高于自己的 mx 记录(即只保留优先级比自己高的记录),从而达到此目的。

比如对于 abc.com,11 在收到邮件后,根据自己的优先级10,会仅仅向 0 转发邮件,而停止向 12 和 21 的转发,这样就避免了循环投递的情况。

不过可以假设一个存在view情况,在 view1 中 11 的优先级高于 12,但是在 view2中 12 的优先级高于 11,这样是不是依然可能存在循环投递的情况。