一次诡异的网络故障

今天碰到一个诡异的网络故障,在复杂的网络条件下,保证条理的清楚是很重要的,为了警醒自己,特记录如下。

早上碰到一个事情,说IDC之间的VPN挂了,但是看了监控,发现2个IDC之间ping是没有问题的。

先描述一下网络环境。 2个IDC,一个在国内一个在国外,暂时是用pptp连接的,2边都有硬件防火墙。硬件防火墙在上周做了一次策略升级。pptp两边都是之前用linux server搭建的,2边出和进的pptp server都是单独的。基本架构就是下面这个草图了。

内嵌图片 1

现在问题是,2个IDC之间的服务器有些可以ping通,有些不行,刚好加监控的都可以。 端口也可以telnet, 但是无法建立连接,一建立连接就被reset, tcpdump的结果就是如下这样的,下面这个是建立rsync的一次连接。

内嵌图片 2
好了,情况都描述完成了,如何处理吧看。

这台rsync服务器检查了本身,发现没有问题,因为从本地机房进行rsync没有问题。 那问题就应该是在pptp 或者firewall上面了。

由于上周firewall做了一次升级,恢复了配置,发现问题依旧。

于是我思维凌乱了,这个是什么情况呢?

两边可以ping,但是建立被reset, 让我感觉怎么像是gfw干的事情呢。

抱着最后一丝希望,就一点点排错吧。

首先确定了2边IDC各自内部都没有问题。 于是从pptp client上进行查,这下发现了奇怪的现象。

从pptp client服务器到另外IDC的rsync居然是可以建立连接的,那问题就应该是pptp client上了, 回想了一下之前的操作,发现了问题所在。

因为这个pptp client还拨到了c机房,之前在iptalbes中nat链里的out interface是写死了ppp0和ppp1的,但是由于不定时断线,所以哪条是ppp0,哪条是ppp1是不固定的。

于是这次本来去B IDC应该是走ppp1的,结果走了ppp0,从而导致了异常的故障发生。 从这个可以看出来, iptables的nat链是有点问题的,正常情况out interface指错了的话应该就是不通的。 但是查了man一下,发现如果没有指对的话它就会乱走,有没有规律那就再看源码了。

cleardot.gif

Advertisements

iptables filter

最近发现dns请求里有一些不正常的域名请求,由于bind9早期的版本在这方面比较弱,而且再越上层做越好,那就在iptables上做最好了,当然在外网防火墙上做也行,2层过滤更好。

在iptables有一个string模块就是用来做这个事情,支持很多种模式,u32, string, hex-string 都有,效率最高的是u32了。 具体看看自己iptables支持那些模式可以查看以下文件

/proc/net/ip_tables_matches

具体的过滤命令很容易,就一句, 意思就是过滤input链的udp的53端口,匹配里面的数据含有packetdevil字符的直接drop掉。

-A INPUT -p udp --dport 53 -m string --algo bm --string "packetdevil" -j DROP

这里面的algo表示实用那种匹配的算法,man一下看到bm和kmp两种算法,但是还是不甚了解。google了一下发现这个阮一峰很久以前就写过了关于2个算法的详细解释,这个wiki上也有详细解释。具体就不说了,大家看一下就能明白了。

–algo {bm|kmp}
Select the pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)

http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

 

但是我也发现了一个问题,其实我之前是想要匹配的是packetdevil.com, 但是网上都是说直接写这个就可以了,可是我加上.com后无法进行匹配,然后怕是 . 这个特殊字符导致的,于是用hex-string来匹配,可是也同样无法匹配到,这个就奇怪了,u32模式由于要重新编译就没有做,有谁看到的就麻烦指点一下,谢谢了!

iptables之端口转发

网上有很多文章写的是iptables通过nat模块进行端口映射,其实这是错误的概念,只是在ipables上做了来和回的2个forward而已。

因为有个项目要一个端口转发,而转发的还刚好是ssh端口。基本架构如下:

A:  1.1.1.1

B:  公网地址 2.2.2.2

内网地址 10.10.10.2

C: 10.10.10.3

现在在B上面对10.10.10.3的22端口映射成2.2.2.2:3322端口。

 

当A ssh 到2.2.2.2:3322端口的时候每次都是通过2.2.2.2来进行内外网转发的。而A 得到的know_hosts里面的key也是真是10.10.10.3的公钥。但是在A的know_hosts文件里10.10.10.3的公钥对应的IP却是2.2.2.2,而不是10.10.10.3。.所以会出现一个情况,当你以后ssh 2.2.2.2的22端口的时候会提示key出错了。

 

这个也是没有办法的,A一直连接的都是2.2.2.2。

 

由于一般做forward的机器都是一个公网,一个内网,而tcpdump不能在混杂模式下监听所有端口,所以必须开2个tcpdump进程,后期再用wireshark进行merge后再进行分析。

 

从分析结果来看,只是在所有的连接中当中会多一步而已,而不会有其他更多的,key交换的时候也同样会进行forward的。

使用nginx和iptables针对不同域名挂载维护页面

在现在的网站架构中经常会前端是LVS,中间是NGINX,后端才是真实服务器。在这种情况下,在用我之前的重定向维护方法就会比较麻烦。因为几乎所有域名都是都是指向在LVS上,而有时候wap和pc都会指向同一个lvs的虚拟IP。这个时候就需要结合iptables和nginx来做根据域名的重定向维护页面了。首先需要让LVS把NGINX服务器IP转向到新的NGINX服务器IP,也可以是同一IP,这样2个NGINX需要跑在不同的端口上。在本例中,正常的NGINX跑在80端口,而维护页面的NGINX在81端口上。
下面正式开工了。

首先在iptables中加入nat转向,目的是为了让测试人员和开发人员能够正常访问网站进行测试和调整,而普通用户只能看到静态维护页面。
下面的Iptables内容中1.1.1.1是我公司的出口ip,而2.2.2.2是网站的公网IP,10.2.2.2是网站的内网IP。为什么要把公网用户转到内网IP是为了不在防火墙上开放新的公网开放端口。而eth2就是网站的公网出口

# Generated by iptables-save v1.3.5 on Fri Mar 26 12:59:51 2010
*nat
:PREROUTING ACCEPT [131:18361]
:POSTROUTING ACCEPT [14:1169]
:OUTPUT ACCEPT [14:1169]
#公司自己的访问转向到80口
-A PREROUTING -s 1.1.1.0/24 -i eth2 -p tcp --dport 80 -j DNAT --to-destination 2.2.2.2:80
#其它访问转向到81口
-A PREROUTING -s 0.0.0.0/0 -i eth2 -p tcp --dport 80 -j DNAT --to-destination 10.2.2.2:81
-A POSTROUTING -s 0.0.0.0/0 -p tcp --dport 81 -d 10.2.2.2/32 -j SNAT --to-source 2.2.2.2
-A POSTROUTING -s 0.0.0.0/0 -p tcp --dport 80 -d 10.2.2.2/32 -j SNAT --to-source 2.2.2.2
-A POSTROUTING -o eth2 -j MASQUERADE
COMMIT

修改/etc/sysctl.conf中的net.ipv4.ip_forward = 1,这一步是为了让内核支持转发。
执行

sysctl -p /etc/sysctl.conf

下面就是nginx登场了。新建一个新的nginx_81.conf文件。这样就能区分原来的。下面是这个nginx_81.conf的主要内容。其实主要就是其中的rewrite规则。由于我们域名所有所有wap域名都是m.abc.com, bb.m.abc.com这样形式。所以就只区分了wap和pc两种。但是为了防止有些pc域名如farm.abc.com存在,所以写了3条if规则。本来想用rewrite来进行重定性,但是一直写不好,不是这个有了问题就是出那个问题,还是直接用error_page来更直接一点。

server {
listen 81;

location / {

server_name_in_redirect off;

index maintain.html;
if ($host ~* ^(m)\.(abc)\.(com))
{
root /opt/update/wap/;
# rewrite !(html|css|gif|jpg|js|htm)$ http://$host/maintain.htm permanent;
error_page 404 http://$host/maintain.html;
break;
}

if ($host ~* (.*)\.(m)\.(abc)\.(com))
{
root /opt/update/wap/;
# rewrite !(html|css|gif|jpg|js|htm)$ http://$host/maintain.html permanent;
error_page 404 http://$host/maintain.html;
break;
}

if ($host ~* (.*)\.(abc)\.(com))
{
root /opt/update/www/;
# rewrite !(html|css|gif|jpg|js|htm)$ http://$host/maintain.html permanent;
error_page 404 http://$host/maintain.html;
break;
}
expires 0;
access_log /data/log/nginx/maintain/access.log main;
}
}

2010-04-13

各种域名跳转方式

一个是使用bind的view功能。
这个首先就需要定义一个ACL列表,然后在name.conf中include一下就可以了。Acl定义的格式如下:

acl "company"{
1.1.1.1/32;
2.2.2.2/32;
};

每行定义记得用分号结尾就行。

下面这个是named.conf的配置。View也就是这里进行配置完成的。记得所有的zone必须在view中。

options {
directory "/usr/local/bind/var";
};
include "/usr/local/bind/var/company_acl.conf";
view "view_company"{
match-clients { company; };
#这里的company就是你前面acl中定义的acl名字

zone "abc.com" {
       type master;
       file "abc.com.company";
       allow-update { none; };
};
zone "." in {
       type hint;
       file "named.root";
};
zone "0.0.127.in-addr.arpa" in {
       type master;
       file "empty";
};
};

view "view_any"{
match-clients { any; };
zone "abc.com" {
       type master;
       file "abc.com.ns";
       allow-update { none; };
};
zone "." in {
       type hint;
       file "named.root";
};
zone "0.0.127.in-addr.arpa" in {
       type master;
       file "empty";
};
};

这些定义完成之后就是添加zone记录了。
下面这个是view_company的实例,其他类似就成。

$TTL 3h
@ IN SOA abc.com. ns.abc.com. (
 2009071601; serial
 3h  ; Refresh after 3hours
 1h  ; Retry after 1 hour
 1w  ; Expire fter 1 week
 1h )  ; Negative caching TTL of 1 hour
;
;name server
 IN NS ns.abc.com.
;
;address
;address
@  in mx 30   mail
abc.com. IN MX 10 mail.abc.com.
mail IN A 10.2.9.98
ns IN A 10.2.9.99
* IN CNAME update.microsoft.com.

总结一下,使用view功能就很容易可以把自己访问和其他访问进行区分对待,这个CDN的原理的一部分就是这样的。但是由于DNS本身是需要所有DNS来进行学习的。所以每次生效和失效当中的间隔比较长,如果只是短期更改,那最好不要使用DNS来进行。

第二种方式是用IPTABLES的转发功能来进行。
这里我们假设有2台机器。10.2.9.118是我本机。这个IPTABLES是设置在10.2.9.33上,10.2.9.99是被转发的机器,也就是除了我10.2.9.118能够正常访问10.2.9.33,其他人的访问都转发到10.2.9.99:80上。这里主要用到iptables得nat功能和filter功能。在redhat中就是替换/etc/sysconfig/iptables的内容。

# Firewall configuration written by system-config-securitylevel
*nat
:--PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:--POSTROUTING ACCEPT [0:0]
#上面3行都没有--这个符号,这里没办法才加的
-A PREROUTING -s ! 10.2.9.118 -p tcp --dport 80 -j DNAT --to-destination 10.2.9.99:80
#这个表示除了10.2.9.118来的访问10.2.9.33的80端口全部转发到10.2.9.99:80上
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Firewall-1-INPUT - [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -i eth1 -j ACCEPT
-A FORWARD -i eth0 -p tcp --dport 80 -d 10.2.9.99 -j ACCEPT
#这个表示允许10.2.9.33可以转发到10.2.9.99上。
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
#上面2个表示开通本机的22和80端口允许任何人访问
COMMIT

上面修改完成后海不能算最后生效,还需要打开10.2.9.33的ip转发功能。修改

/etc/sysctl.conf 中net.ipv4.ip_forward = 1 

默认这个值为0,表示禁止转发,改成1就行,然后执行

sysctl –p /etc/sysctl.conf

就行了。
最后我们还要在10.2.9.99上建立一个默认网站。因为iptables中转发过来的是ip+端口的形式。所以必须让10.2.9.99有默认网站。这个默认网站上。在nginx中,在配置文件最上面的网站就是默认网站。

    server {
        listen       80;
        server_name  update.abc.com;

        location / {
            root   /opt/update/;
            expires      180d;
            index  index.html index.htm;
            access_log  logs/defalut.access.log  main;
        }
        error_page   500 502 503 504  /index.htm;
        error_page   404          /index.htm;
        location = /index.htm {
            root   /opt/update/;
        }
        }

这里设置了所有的404和500错误全部被转向到index.htm上了。当然你可以使用rewrite来完成这样的功能。这样无论用户访问10.2.9.33上的任何网站都会被转到10.2.9.99的默认网站,除了我自己以外,哈哈!

最后一种重定向方式就是使用nginx的geo模块。这个在以前的日志已经有写,可以参考https://zauc.wordpress.com/2008/11/22/nginx-ge/