├── .gitmodules
├── README.md
├── jan21.png
├── make.sh
├── pom.xml
├── release-note.md
└── server
├── README.md
├── benchmark-all-versions
├── benchmark-other-dns-server
├── blackhole.sh
├── config
├── blackhole.conf
└── zones
├── pom.xml
└── src
├── main
├── java
│ └── us
│ │ └── codecraft
│ │ └── blackhole
│ │ ├── BlackHole.java
│ │ ├── answer
│ │ ├── AbstractAnswerHandler.java
│ │ ├── AnswerPatternProvider.java
│ │ ├── AnswerProvider.java
│ │ ├── CustomAnswerPatternProvider.java
│ │ ├── CustomTempAnswerProvider.java
│ │ ├── DomainPatternsContainer.java
│ │ ├── PostAnswerHandler.java
│ │ ├── PreAnswerHandler.java
│ │ ├── SafeHostAnswerProvider.java
│ │ └── TempAnswerProvider.java
│ │ ├── antipollution
│ │ ├── BlackListManager.java
│ │ └── SafeHostManager.java
│ │ ├── cache
│ │ ├── CacheClient.java
│ │ ├── CacheManager.java
│ │ ├── EhcacheClient.java
│ │ ├── MapCacheClient.java
│ │ └── UDPPackage.java
│ │ ├── concurrent
│ │ └── ThreadPools.java
│ │ ├── config
│ │ ├── ConfigFileLoader.java
│ │ ├── ConfigFileRefresher.java
│ │ ├── Configure.java
│ │ ├── DomainPattern.java
│ │ ├── ZonesFileLoader.java
│ │ ├── ZonesFileRefresher.java
│ │ └── ZonesPattern.java
│ │ ├── connector
│ │ ├── UDPConnectionResponser.java
│ │ ├── UDPConnectionWorker.java
│ │ └── UDPSocketMonitor.java
│ │ ├── container
│ │ ├── Handler.java
│ │ ├── HandlerManager.java
│ │ ├── HeaderHandler.java
│ │ ├── MessageWrapper.java
│ │ └── QueryProcesser.java
│ │ ├── context
│ │ ├── RequestContext.java
│ │ └── RequestContextProcessor.java
│ │ ├── forward
│ │ ├── ConnectionTimer.java
│ │ ├── DNSHostsContainer.java
│ │ ├── ForwardAnswer.java
│ │ ├── ForwardAnswerProcessor.java
│ │ ├── Forwarder.java
│ │ ├── MultiUDPForwarder.java
│ │ └── MultiUDPReceiver.java
│ │ └── utils
│ │ ├── DoubleKeyMap.java
│ │ ├── MultiKeyMapBase.java
│ │ ├── RecordBuilder.java
│ │ ├── RecordUtils.java
│ │ └── SpringLocator.java
└── resources
│ ├── ehcache.xml
│ ├── log4j.xml
│ └── spring
│ └── applicationContext-blackhole.xml
└── test
└── java
└── us
└── codecraft
└── blackhole
└── multiforward
├── ForwardTimerTest.java
└── MultiReceiverTest.java
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "wifesays"]
2 | path = wifesays
3 | url = git://github.com/code4craft/wifesays.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | BlackHole
2 | =========
3 |
4 | ### 1. 简介
5 |
6 | BlackHole是一个Java编写的DNS服务器,它可以进行DNS缓存,也支持自定义域名配置,并可以防止DNS污染。比起老牌的DNS软件pdnsd、BIND,BlackHole功能比较简单,但是更容易使用,性能也更好。
7 |
8 | BlackHole还包含一个Web管理模块[**Hostd**](https://github.com/code4craft/hostd),可以让每个用户管理自己的域名配置,并且彼此之间不冲突。
9 |
10 | ### 2. 用途
11 |
12 | #### DNS缓存
13 |
14 | BlackHole具有DNS缓存以及持久化的功能,可以作为一个DNS缓存服务器使用,以加速DNS访问。
15 |
16 | BlackHole缓存性能优秀,可以支持每秒50000次随机查询,平均响应时间0.3ms,高于pdnsd及BIND([测试报告](https://github.com/code4craft/blackhole/blob/master/server/benchmark-other-dns-server))。
17 |
18 | #### hosts风格自定义域名
19 |
20 | BlackHole也支持修改域名配置,配置域名的方式非常简单,与hosts文件一致,并且支持通配符(目前仅支持A记录)。
21 |
22 | 例如:
23 |
24 | 127.0.0.1 *.codecraft.us
25 |
26 | 表示将所有以.codecraft.us形式结尾的域名全部指向127.0.0.1。
27 |
28 | #### 防止DNS污染
29 |
30 | BlackHole还可以通过UDP特征判断的方式防止DNS污染攻击,对于某些无法访问的网站可以起到作用。BlackHole防止DNS的方式参见:[http://code4craft.github.com/blog/2013/02/25/blackhole-anti-dns-poison/](http://code4craft.github.com/blog/2013/02/25/blackhole-anti-dns-poison/)
31 |
32 |
33 | ### 3. 安装及配置
34 |
35 | 你使用自动脚本进行安装BlackHole:
36 |
37 | curl http://code4craft.github.io/blackhole/install.sh | [sudo] sh
38 |
39 | BlackHole的另一个编译后版本保存在[https://github.com/code4craft/blackhole-bin](https://github.com/code4craft/blackhole-bin),如果以上脚本对你所在环境不可用,那么可以clone这个项目到某一目录。
40 |
41 | git clone https://github.com/code4craft/blackhole-bin.git /usr/local/blackhole
42 |
43 | 通过sudo /usr/local/blackhole/blackhole.sh start可以启动BlackHole。
44 |
45 | Windows系统可将文件保存到任意目录,并运行start.bat(Win7下无需用管理员权限启动),若弹出终端界面并且持续运行,则启动成功。
46 |
47 | 各种问题解决、具体的设置以及技术细节请看[Blackhole Server Docs](https://github.com/code4craft/blackhole/blob/master/server/README.md)。
48 |
49 | ### 4. 协议
50 |
51 | BlackHole的连接部分参考了EagleDNS的代码,遵守LGPLv3协议。
52 |
53 | 作者邮箱:
54 | code4crafter@gmail.com
55 |
56 | [](https://bitdeli.com/free "Bitdeli Badge")
57 |
58 |
--------------------------------------------------------------------------------
/jan21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code4craft/blackhole/11bd95718cf107877ad3ee6b070dc390c3b98d4b/jan21.png
--------------------------------------------------------------------------------
/make.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | mkdir -p /usr/local/blackhole/
3 | mkdir -p /usr/local/blackhole/lib
4 | if [ ! -d /usr/local/blackhole/config ]
5 | then
6 | mkdir -p /usr/local/blackhole/config
7 | cp ./server/config/* /usr/local/blackhole/config/
8 | fi
9 | cp ./server/blackhole.sh /usr/local/blackhole/
10 | cp ./server/target/blackhole*.jar /usr/local/blackhole/blackhole.jar
11 | rsync -avz --delete ./server/target/lib/ /usr/local/blackhole/lib/
12 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | us.codecraft
4 | blackhole-module
5 | pom
6 | 0.0.1-SNAPSHOT
7 |
8 | ./wifesays
9 | ./server/
10 |
11 |
12 |
--------------------------------------------------------------------------------
/release-note.md:
--------------------------------------------------------------------------------
1 | Release Notes
2 | ----
3 | *2013-8-12* `version:1.2.3`
4 |
5 | bugfix: [issue#11 Forward的请求包不合规范](https://github.com/code4craft/blackhole/issues/11)
6 |
7 | *2013-7-16* `version:1.2.2`
8 |
9 | 极大优化了A记录配置的性能。
10 |
11 | 修复数万条配置的情况下,性能明显下降的问题[issue#9](https://github.com/code4craft/blackhole/issues/9)。
12 |
13 | 新增NS格式的配置。
14 |
15 | *2013-6-22* `version:1.2.1`
16 |
17 | 增加自定义缓存过期时间的功能。
18 |
19 | 优化了缓存目录地址。
20 |
21 | 修复了缓存持久化不生效的bug。
22 |
23 | 详细进行了性能测试,并更新了文档。
24 |
25 | *2013-5-31* `version:1.2.0`
26 |
27 | * [issues#5](https://github.com/code4craft/blackhole/issues/5) 对不同的用户组提供不同的DNS响应,从而可以让每个用户管理自己的DNS配置。
28 |
29 | * 开发了一个Web模块[Hostd](http://code4craft.github.io/hostd/),类似修改hosts,可以供所有用户进行自己DNS配置的管理。
30 |
31 | * [issues#8](https://github.com/code4craft/blackhole/issues/8) 对forward的外部DNS设定优先级 便于企业内网配置自己的DNS地址,防止被响应被覆盖。
32 |
33 | *2013-5-7*
34 |
35 | * 修复启动失败后进程不退出的bug。
36 |
37 | *2013-4-27* `version:1.1.3`
38 |
39 | 1.1.3发布。
40 |
41 | * 重写了代理模式,改为纯异步I/O实现,大大降低使用线程数,并提高了25%的效率。
42 |
43 | * 修复一个高并发下某些响应丢失的bug。
44 |
45 | *2013-4-2* `version:1.1.2`
46 |
47 | 1.1.2发布,修复了一个返回空响应体导致DNS查找失败的问题,从此稳定性大大提高[https://github.com/code4craft/blackhole/issues/3](https://github.com/code4craft/blackhole/issues/3)。
48 |
49 | BlackHole也迎来第一位企业级用户。争取发展成为一个公司内网使用的简单可配置的DNS服务器。
50 |
51 | *2013-3-24* `version:1.1.1`
52 |
53 | 1.1.1发布,增加NS配置功能,详情见[https://github.com/code4craft/blackhole/blob/master/server/README.md](https://github.com/code4craft/blackhole/blob/master/server/README.md)
54 |
55 | *2013-2-25* `version: 1.1`
56 |
57 | 1.1发布,偷偷增加反DNS污染功能。原理:
58 | [http://my.oschina.net/flashsword/blog/110276](http://my.oschina.net/flashsword/blog/110276)
59 |
60 | *2012-12-31* `version: 1.0-alpha`
61 |
62 | 1.0-alpha发布,加入功能:
63 |
64 | * DNS转发代理,并可缓存结果
65 | * 可配置多个转发服务器
66 | * 增加超时机制
67 |
68 | *2012-12-17* `version: 0.1 `
69 |
70 | 为了满足模拟邮件发送的需求,开发了第一个版本。可以拦截所有请求到某一IP。从EagelDNS的框架修改而来。
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | BlackHole
2 | =========
3 |
4 | ### 1. 技术结构
5 |
6 | BlackHole的连接处理部分参考了EagleDNS,使用了反应堆模式。
7 |
8 | BlackHole底层使用dnsjava进行DNS解析,并使用EhCache进行缓存和持久化。
9 |
10 | BlackHole的UDP代理部分使用了纯异步的逻辑,支持同时代理多个外部DNS服务器,并使用最快的响应结果。
11 |
12 | ### 2. 安装
13 |
14 | #### 下载编译版本
15 |
16 | BlackHole的编译后版本保存在[https://github.com/code4craft/blackhole-bin](https://github.com/code4craft/blackhole-bin),直接clone这个项目到某一目录即可。
17 |
18 | git clone https://github.com/code4craft/blackhole-bin.git /usr/local/blackhole
19 |
20 | 你也可以使用自动脚本进行安装:
21 |
22 | curl http://code4craft.github.io/blackhole/install.sh | sh
23 |
24 | #### 从源码编译
25 |
26 | 你也可以使用源码,进行修改和编译。
27 |
28 | git clone https://github.com/code4craft/blackhole.git --recursive
29 | cd blackhole
30 | mvn clean package
31 | sh make.sh
32 |
33 | ### 3. 启动
34 |
35 | 然后通过sudo /usr/local/blackhole/blackhole.sh start可以启动。
36 |
37 | 如果启动时提示53端口被占用,可以查看一下是否已在本地开启其他dns服务。
38 |
39 | Ubuntu下默认开启了dnsmasq,如果启动时提示53端口被占用,可以查看dnsmasq是否已开启:
40 |
41 | ps -ef | grep dnsmasq
42 |
43 | 禁用dnsmasq的方法:修改/etc/NetworkManager/NetworkManager.conf,注释掉dns=dnsmasq即可。
44 |
45 | ### 4. 配置BlackHole:
46 |
47 | 如果你没有将BlackHole安装到/usr/local/blackhole,则需要修改blackhole.sh,将HOME_DIR更改为你的安装目录。
48 |
49 | BlackHole目前有两个配置文件,分别是**config/blackhole.conf**和**config/zones**。
50 | **BlackHole的配置文件都是修改后动态生效的!**
51 |
52 | #### conf/blackhole.conf
53 |
54 | 是blackhole的主要配置文件。
55 |
56 | 配置格式如下:
57 |
58 | key=value
59 |
60 | 例如:
61 |
62 | DNS=192.168.0.1
63 |
64 | 目前可配置项如下:
65 |
66 | * **DNS**
67 |
68 | BlackHole并没有递归查找DNS的功能,如果遇到未在本地配置的域名请求,它会做一个UDP代理的工作,将请求发向一个已有的DNS服务器,并返回这个DNS服务器的结果。
69 |
70 | 支持多个DNS服务器配置,BlackHole会优先采用较前面的配置的结果。**如果你所在的内网配置了DNS拦截,将你的内网DNS服务器放到第一位可以保证这些拦截优先生效。**
71 |
72 | * **DNS_TIMEOUT**
73 |
74 | 请求外部DNS的超时时间,单位为毫秒。
75 |
76 | * **CACHE**
77 |
78 | 转发模式下,是否对DNS结果进行缓存。默认是true。
79 |
80 | * **CACHE_EXPIRE**
81 |
82 | 设置缓存的过期时间,单位是秒。不设置此项或设置为0,则使用默认值。默认缓存的失效时间是DNS结果的TLL值。
83 |
84 | * **TTL**
85 |
86 | BlackHole拦截的DNS请求的过期时间,单位是秒。
87 |
88 | * **LOG**
89 |
90 | 日志的等级。可选配置为('DEBUG','INFO','WARN'),对应log4j的等级。开启'DEBUG'级别的日志会显示每次请求和应答,但是会大大降低吞吐量。
91 |
92 | * **SAFE_BOX**
93 |
94 | 是否开启反DNS污染功能。关闭此项可增加吞吐量。
95 |
96 | * **FAKE_DNS**
97 |
98 | 伪造的DNS服务器,用于检测DNS污染。一般无须更改。
99 |
100 | #### conf/zones
101 |
102 | zones是DNS系统的域名配置文件。BlackHole简化了zones的配置,只支持A记录(因为作者觉得A记录够用了!)。
103 |
104 | BlackHole的配置跟Hosts文件是一样的,但是支持通配符"*"。
105 |
106 | 例如:
107 |
108 | 127.0.0.1 *.codecraf.us
109 |
110 | *可以匹配任意长度的任意字符(包括'.')。这项配置可以将所有以.codecraf.us形式结尾的域名全部指向127.0.0.1。
111 |
112 | 当然,你也可以这样配置:
113 |
114 | 127.0.0.1 *
115 |
116 | 这表示把所有请求导向本地。这个配置在某些场合(例如测试邮件发送服务)会有用。
117 |
118 | BlackHole还支持NS记录的配置。NS记录的意思是,对于某些域名的请求,总是向某个DNS服务器查找结果。例如,你可以使用组合配置:
119 |
120 | 173.194.72.103 *.google.com
121 | NS 8.8.8.8 docs.google.com
122 |
123 | 这两项配置的意思是:将*.google.com的地址都指向173.194.72.103,但是对于docs.google.com域名,向8.8.8.8进行查询并作为最终结果。
124 |
125 | 如果两条规则出现冲突,前面的规则会生效。
126 |
127 | ### 5. 动态管理BlackHole:
128 |
129 | blackhole的监控模块使用了作者的另一个开源项目[wifesays](https://github.com/flashsword20/wifesays)。wifesays是一个简单的Java进程内TCP服务器,使用40310端口作为监控端口,基于TCP协议上封装了一些简单的文本命令。
130 |
131 | 可以使用
132 |
133 | java -jar wifesays.jar -cCOMMAND
134 |
135 | 来通知BlackHole。你也可以直接用telnet来管理BlackHole:
136 |
137 | telnet 127.0.0.1 40310
138 | >COMMAND
139 |
140 | COMMAND为命令。目前支持的命令为:
141 |
142 | * reload
143 |
144 | 重新读取配置,包括zones和blackhole.conf。
145 |
146 | * shutdown
147 |
148 | 退出。
149 |
150 | * clear_cache
151 |
152 | 清除缓存(仅当使用缓存时有效)。
153 |
154 | * stat_cache
155 |
156 | 显示缓存状态。
157 |
158 | * dump_cache
159 |
160 | 将缓存输出到文件cache.dump,便于调试。
161 |
162 | 例如:
163 |
164 | 重新读取配置文件的命令为:
165 |
166 | java -jar wifesays.jar -creload
167 |
168 | 这些操作都集成到了blackhole.sh脚本中。支持SHELL的系统可以使用以下命令快速操作:
169 |
170 | blackhole.sh {start|stop|restart|reload|zones|config|cache}
171 |
172 | ### 6. 协议
173 |
174 | BlackHole的连接部分参考了EagleDNS的代码,遵守LGPLv3协议。
175 |
176 | 作者邮箱:
177 | code4crafter@gmail.com
178 |
--------------------------------------------------------------------------------
/server/benchmark-all-versions:
--------------------------------------------------------------------------------
1 | ###1.1.2
2 | Queries per second: 17020.533091 qps
3 |
4 | Queries per second: 17968.598926 qps
5 |
6 | Queries per second: 9929.740407 qps
7 |
8 | Queries per second: 6730.895729 qps
9 |
10 | Queries per second: 13743.787636 qps
11 |
12 | Queries per second: 17946.626016 qps
13 |
14 | Queries per second: 15181.713176 qps
15 |
16 | Queries per second: 8420.535018 qps
17 |
18 | Queries per second: 7355.708610 qps
19 |
20 | Queries per second: 7070.555627 qps
21 |
22 | Avg: 12137
23 |
24 | --------
25 |
26 | ###1.1.3
27 | Queries per second: 19214.104324 qps
28 |
29 | Queries per second: 8840.774761 qps
30 |
31 | Queries per second: 12835.476201 qps
32 |
33 | Queries per second: 20727.255323 qps
34 |
35 | Queries per second: 17055.128750 qps
36 |
37 | Queries per second: 20098.186952 qps
38 |
39 | Queries per second: 8976.868585 qps
40 |
41 | Queries per second: 16209.558405 qps
42 |
43 | Queries per second: 18838.347946 qps
44 |
45 | Queries per second: 10039.901489 qps
46 |
47 | Avg: 15284
48 |
49 | --------
50 |
51 | ###1.2.1
52 | memory: 136.7M
53 | threads: 58
54 |
55 | -------
56 |
57 | ###1.2.2
58 | memory: 129.3M
59 | threads: 34
--------------------------------------------------------------------------------
/server/benchmark-other-dns-server:
--------------------------------------------------------------------------------
1 | Benchmark
2 |
3 | -----------------------------------------------------
4 | -----------------------------------------------------
5 |
6 | Using queryperf in bind 9.9.3
7 |
8 | Environment:Mac Air MD231CH/A
9 |
10 | CPU: 1.8Ghz Intel Core i5
11 |
12 | -----------------------------------------------------
13 |
14 | blackhole 1.2.1
15 |
16 | Memory Usage: 140M
17 |
18 | Statistics:
19 |
20 | Parse input file: once
21 | Ended due to: reaching end of file
22 |
23 | Queries sent: 141300 queries
24 | Queries completed: 141300 queries
25 | Queries lost: 0 queries
26 | Queries delayed(?): 0 queries
27 |
28 | RTT max: 0.214731 sec
29 | RTT min: 0.000059 sec
30 | RTT average: 0.000282 sec
31 | RTT std deviation: 0.001746 sec
32 | RTT out of range: 0 queries
33 |
34 | Percentage completed: 100.00%
35 | Percentage lost: 0.00%
36 |
37 | Started at: Sat Jun 22 19:39:18 2013
38 | Finished at: Sat Jun 22 19:39:21 2013
39 | Ran for: 2.643923 seconds
40 |
41 | Queries per second: 53443.311322 qps
42 |
43 | -----------------------------------------------------
44 |
45 | BIND 9.9.3
46 |
47 | Memory Usage: 37M
48 |
49 | Statistics:
50 |
51 | Parse input file: once
52 | Ended due to: reaching end of file
53 |
54 | Queries sent: 141300 queries
55 | Queries completed: 141300 queries
56 | Queries lost: 0 queries
57 | Queries delayed(?): 0 queries
58 |
59 | RTT max: 0.231236 sec
60 | RTT min: 0.000018 sec
61 | RTT average: 0.000421 sec
62 | RTT std deviation: 0.001912 sec
63 | RTT out of range: 0 queries
64 |
65 | Percentage completed: 100.00%
66 | Percentage lost: 0.00%
67 |
68 | Started at: Sat Jun 22 20:24:01 2013
69 | Finished at: Sat Jun 22 20:24:04 2013
70 | Ran for: 3.473272 seconds
71 |
72 | Queries per second: 40682.100337 qps
73 |
74 | -----------------------------------------------------
75 |
76 | pdnsd
77 |
78 | Memory Usage: 1.5M
79 |
80 | Statistics:
81 |
82 | Parse input file: once
83 | Ended due to: reaching end of file
84 |
85 | Queries sent: 141300 queries
86 | Queries completed: 141300 queries
87 | Queries lost: 0 queries
88 | Queries delayed(?): 0 queries
89 |
90 | RTT max: 0.559270 sec
91 | RTT min: 0.000135 sec
92 | RTT average: 0.001076 sec
93 | RTT std deviation: 0.006703 sec
94 | RTT out of range: 0 queries
95 |
96 | Percentage completed: 100.00%
97 | Percentage lost: 0.00%
98 |
99 | Started at: Sat Jun 22 19:56:29 2013
100 | Finished at: Sat Jun 22 19:56:37 2013
101 | Ran for: 7.891417 seconds
102 |
103 | Queries per second: 17905.529514 qps
--------------------------------------------------------------------------------
/server/blackhole.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | HOME_DIR=/usr/local/blackhole
3 | PATH=$PATH:$HOME_DIR
4 | export PATH
5 | JVM_OPTION=""
6 |
7 | function doCache()
8 | {
9 | case "$1" in
10 | stat)
11 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -cstat_cache
12 | ;;
13 | dump)
14 | echo "Dump cache to $HOME_DIR/cache.dump"
15 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -cdump_cache > /dev/null
16 | ;;
17 | clear)
18 | echo "Clear cache"
19 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -cclear_cache
20 | ;;
21 | *)
22 | echo "Usage: $0 cache {dump|clear|stat}"
23 | ;;
24 | esac
25 | }
26 |
27 | case "$1" in
28 | start)
29 | echo "Starting blackhole..."
30 | java -jar ${JVM_OPTION} -Djava.io.tmpdir="$HOME_DIR/cache" $HOME_DIR/blackhole.jar -d"$HOME_DIR">> $HOME_DIR/log &
31 | ;;
32 | stop)
33 | echo "Stopping blackhole"
34 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -cshutdown > /dev/null
35 | ;;
36 | cache)
37 | doCache $2
38 | ;;
39 | restart)
40 | echo "Stopping blackhole..."
41 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -cshutdown > /dev/null;
42 | sleep 2;
43 | echo "Starting blackhole..."
44 | java -jar ${JVM_OPTION} -Djava.io.tmpdir="$HOME_DIR/cache" $HOME_DIR/blackhole.jar -d"$HOME_DIR">> $HOME_DIR/log &
45 | ;;
46 | reload)
47 | echo "Reloading blackhole"
48 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -creload > /dev/null
49 | ;;
50 | zones)
51 | vi $HOME_DIR/config/zones
52 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -creload > /dev/null
53 | ;;
54 | config)
55 | vi $HOME_DIR/config/blackhole.conf
56 | java -jar $HOME_DIR/lib/wifesays-1.0.0-alpha.jar -creload > /dev/null
57 | ;;
58 | *)
59 | echo "Usage: $0 {start|stop|reload|zones|config|restart|cache}"
60 | ;;
61 | esac
62 |
63 | exit 0
64 |
--------------------------------------------------------------------------------
/server/config/blackhole.conf:
--------------------------------------------------------------------------------
1 | #######################################Configure file of blackhole#####################################
2 |
3 | #######################################important#####################################
4 | #External dns. You can add more reliable dns.
5 | dns=8.8.8.8
6 | #Timeout of external dns in millisecond.
7 | dns_timeout=3000
8 |
9 | #ttl of intercepted answers.what is ttl? see http://en.wikipedia.org/wiki/Time_to_live#DNS_records
10 | ttl=1
11 |
12 | #Whether use ehcache for external dns records.When it is changed, the program must be restarted to take effort.
13 | cache=true
14 |
15 | #Cache expire time in seconds. Use ttl of respective DNS answer as default.
16 | cache_expire=864000
17 |
18 | #fake dns server.Used to detect dns poison.
19 | fake_dns=144.223.234.234
20 |
21 | #When turn it on, we will look for a valid address for the domains which are poisoned and save it to file "safebox"
22 | safe_box=true
23 |
24 | #Num of threads for process request. One thread is enough for local usage. Set it to 10 if you use it as a server for many users.
25 | thread_num=1
26 |
27 | #log4j level,there are 3 levels:[debug,info,warn]
28 | log=info
29 |
--------------------------------------------------------------------------------
/server/config/zones:
--------------------------------------------------------------------------------
1 | ###################### Black Hole Zones config file ############################
2 | #
3 | # Syntax:
4 | # address domain [type]
5 | # like: 127.0.0.1 codecraft.us
6 | # Char "*" matches all chars with any length.
7 | # Lines starting with "#" will be considered as comments.
8 | # Type field is optional.Line without type will be considered supporting all types.
9 | # If more than one line works for a query, the first line will have effect.
10 | #
11 | # Samples:
12 | #
13 | # 127.0.0.1 codecraft.us
14 | # response all domain query end with "codecraft.us" to 127.0.0.1
15 | #
16 | # 127.0.0.1 *
17 | # response all query with all domain to 127.0.0.1
18 | #
19 | # 127.0.0.1 flashsword20.*.*
20 | # response all query like "flashsword20.diandian.com" "flashsword20.github.com"
21 | #
22 |
--------------------------------------------------------------------------------
/server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | us.codecraft
6 | blackhole
7 | 1.2.2
8 | blackhole
9 | A simple DNS authoritative server.It can easily be configured to intercept some kind of request to one address.
10 |
11 | 3.1.1.RELEASE
12 |
13 |
14 |
15 | dnsjava
16 | dnsjava
17 | 2.1.1
18 |
19 |
20 | junit
21 | junit
22 | 4.7
23 | test
24 |
25 |
26 | us.codecraft
27 | wifesays
28 | 1.0.0-alpha
29 |
30 |
31 | net.sf.ehcache
32 | ehcache-core
33 | 2.6.2
34 |
35 |
36 | org.slf4j
37 | slf4j-log4j12
38 | 1.7.1
39 |
40 |
41 | org.apache.commons
42 | commons-lang3
43 | 3.1
44 |
45 |
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-dependency-plugin
51 |
52 |
53 | copy-dependencies
54 | package
55 |
56 | copy-dependencies
57 |
58 |
59 | ${project.build.directory}/lib
60 | false
61 | false
62 | true
63 |
64 |
65 |
66 |
67 |
68 | maven-compiler-plugin
69 |
70 | 1.6
71 | 1.6
72 | UTF-8
73 |
74 |
75 |
76 | org.apache.maven.plugins
77 | maven-resources-plugin
78 |
79 | UTF-8
80 |
81 |
82 |
83 | org.apache.maven.plugins
84 | maven-jar-plugin
85 |
86 |
87 |
88 | true
89 | ./lib/
90 | us.codecraft.blackhole.BlackHole
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/BlackHole.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole;
2 |
3 | import java.io.IOException;
4 | import java.net.UnknownHostException;
5 |
6 | import org.apache.commons.cli.CommandLine;
7 | import org.apache.commons.cli.CommandLineParser;
8 | import org.apache.commons.cli.Option;
9 | import org.apache.commons.cli.Options;
10 | import org.apache.commons.cli.ParseException;
11 | import org.apache.commons.cli.PosixParser;
12 | import org.apache.log4j.Logger;
13 | import org.springframework.context.support.ClassPathXmlApplicationContext;
14 | import org.springframework.stereotype.Component;
15 |
16 | import us.codecraft.blackhole.config.Configure;
17 | import us.codecraft.blackhole.connector.UDPSocketMonitor;
18 | import us.codecraft.blackhole.utils.SpringLocator;
19 |
20 | /**
21 | * Entry of application. aa
22 | *
23 | * @author yihua.huang@dianping.com
24 | * @date Dec 14, 2012
25 | */
26 | @Component
27 | public class BlackHole {
28 |
29 | private boolean isShutDown = false;
30 |
31 | private static Logger logger = Logger.getLogger(BlackHole.class);
32 |
33 | private UDPSocketMonitor udpSocketMonitor;
34 |
35 | public void start() throws UnknownHostException, IOException {
36 | udpSocketMonitor = SpringLocator.getBean(UDPSocketMonitor.class);
37 | udpSocketMonitor.start();
38 | }
39 |
40 | private static void parseArgs(String[] args) throws ParseException {
41 | Options options = new Options();
42 | options.addOption(new Option("d", true, "home path"));
43 | CommandLineParser commandLineParser = new PosixParser();
44 | CommandLine commandLine = commandLineParser.parse(options, args);
45 | readOptions(commandLine);
46 | }
47 |
48 | private static void readOptions(CommandLine commandLine) {
49 | if (commandLine.hasOption("d")) {
50 | String filename = commandLine.getOptionValue("d");
51 | Configure.FILE_PATH = filename;
52 | }
53 | }
54 |
55 | /**
56 | * @param args
57 | */
58 | public static void main(String[] args) {
59 | try {
60 | parseArgs(args);
61 | } catch (ParseException e1) {
62 | logger.warn("parse args error");
63 | }
64 | SpringLocator.applicationContext = new ClassPathXmlApplicationContext(
65 | "classpath*:/spring/applicationContext*.xml");
66 | BlackHole blackHole = SpringLocator.getBean(BlackHole.class);
67 | try {
68 | blackHole.start();
69 | } catch (UnknownHostException e) {
70 | logger.warn("init failed ", e);
71 | } catch (IOException e) {
72 | logger.warn("init failed ", e);
73 | }
74 | while (!blackHole.isShutDown) {
75 | try {
76 | Thread.sleep(10000000);
77 | } catch (InterruptedException e) {
78 | e.printStackTrace();
79 | }
80 | }
81 |
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/AbstractAnswerHandler.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.xbill.DNS.DClass;
5 | import org.xbill.DNS.Record;
6 | import org.xbill.DNS.Section;
7 | import org.xbill.DNS.Type;
8 | import us.codecraft.blackhole.container.Handler;
9 | import us.codecraft.blackhole.container.MessageWrapper;
10 | import us.codecraft.blackhole.utils.RecordBuilder;
11 |
12 | import java.util.List;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 | /**
17 | * @author yihua.huang@dianping.com
18 | * @date: 13-7-14
19 | * Time: 下午4:36
20 | */
21 | public abstract class AbstractAnswerHandler implements Handler {
22 |
23 | protected abstract List getaAnswerProviders();
24 |
25 | protected Logger logger = Logger.getLogger(getClass());
26 |
27 | // b._dns-sd._udp.0.129.37.10.in-addr.arpa.
28 | private final Pattern filterPTRPattern = Pattern
29 | .compile(".*\\.(\\d+\\.\\d+\\.\\d+\\.\\d+\\.in-addr\\.arpa\\.)");
30 |
31 | private String filterPTRQuery(String query) {
32 | Matcher matcher = filterPTRPattern.matcher(query);
33 | if (matcher.matches()) {
34 | return matcher.group(1);
35 | } else {
36 | return query;
37 | }
38 | }
39 |
40 | /*
41 | * (non-Javadoc)
42 | *
43 | * @see us.codecraft.blackhole.server.Handler#handle(org.xbill.DNS.Message,
44 | * org.xbill.DNS.Message)
45 | */
46 | @Override
47 | public boolean handle(MessageWrapper request, MessageWrapper response) {
48 | Record question = request.getMessage().getQuestion();
49 | String query = question.getName().toString();
50 | int type = question.getType();
51 | if (type == Type.PTR) {
52 | query = filterPTRQuery(query);
53 | }
54 | // some client will query with any
55 | if (type == Type.ANY) {
56 | type = Type.A;
57 | }
58 | if (logger.isDebugEnabled()) {
59 | logger.debug("query \t" + Type.string(type) + "\t"
60 | + DClass.string(question.getDClass()) + "\t" + query);
61 | }
62 | for (AnswerProvider answerProvider : getaAnswerProviders()) {
63 | String answer = answerProvider.getAnswer(query, type);
64 | if (answer != null) {
65 | try {
66 | Record record = new RecordBuilder()
67 | .dclass(question.getDClass())
68 | .name(question.getName()).answer(answer).type(type)
69 | .toRecord();
70 | response.getMessage().addRecord(record, Section.ANSWER);
71 | if (logger.isDebugEnabled()) {
72 | logger.debug("answer\t" + Type.string(type) + "\t"
73 | + DClass.string(question.getDClass()) + "\t"
74 | + answer);
75 | }
76 | response.setHasRecord(true);
77 | return false;
78 | } catch (Throwable e) {
79 | logger.warn("handling exception " + e);
80 | }
81 | }
82 | }
83 | return true;
84 | }
85 | }
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/AnswerPatternProvider.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import org.xbill.DNS.Address;
7 | import org.xbill.DNS.Type;
8 |
9 | /**
10 | * Read the config to domainPatterns and process the request record.
11 | *
12 | * @author yihua.huang@dianping.com
13 | * @date Dec 14, 2012
14 | */
15 | @Component
16 | public class AnswerPatternProvider implements AnswerProvider {
17 |
18 | private DomainPatternsContainer domainPatternsContainer = new DomainPatternsContainer();
19 |
20 | private Logger logger = Logger.getLogger(getClass());
21 |
22 | /**
23 | * When the address configured as "DO_NOTHING",it will not return any
24 | * address.
25 | */
26 | public static final String DO_NOTHING = "do_nothing";
27 | private static final String FAKE_MX_PREFIX = "mail.";
28 | private static final String FAKE_CANME_PREFIX = "cname.";
29 |
30 | @Autowired
31 | private TempAnswerProvider tempAnswerContainer;
32 |
33 | /*
34 | * (non-Javadoc)
35 | *
36 | * @see
37 | * us.codecraft.blackhole.answer.AnswerProvider#getAnswer(java.lang.String,
38 | * int)
39 | */
40 | @Override
41 | public String getAnswer(String query, int type) {
42 | if (type == Type.PTR) {
43 | return null;
44 | }
45 | String ip = domainPatternsContainer.getIp(query);
46 | if (ip == null || ip.equals(DO_NOTHING)) {
47 | return null;
48 | }
49 | if (type == Type.MX) {
50 | String fakeMXHost = fakeMXHost(query);
51 | tempAnswerContainer.add(fakeMXHost, Type.A, ip);
52 | return fakeMXHost;
53 | }
54 | if (type == Type.CNAME) {
55 | String fakeCNAMEHost = fakeCNAMEHost(query);
56 | tempAnswerContainer.add(fakeCNAMEHost, Type.A, ip);
57 | return fakeCNAMEHost;
58 | }
59 | try {
60 | tempAnswerContainer.add(reverseIp(ip), Type.PTR, query);
61 | } catch (Throwable e) {
62 | logger.info("not a ip, ignored");
63 | }
64 | return ip;
65 | }
66 |
67 | /**
68 | * generate a fake MX host
69 | *
70 | * @param domain
71 | * @return
72 | */
73 | private String fakeMXHost(String domain) {
74 | return FAKE_MX_PREFIX + domain;
75 | }
76 |
77 | /**
78 | * @param domain
79 | * @return
80 | */
81 | private String fakeCNAMEHost(String domain) {
82 | return FAKE_CANME_PREFIX + domain;
83 | }
84 |
85 | private String reverseIp(String ip) {
86 | int[] array = Address.toArray(ip);
87 | StringBuilder stringBuilder = new StringBuilder();
88 | for (int i = array.length - 1; i >= 0; i--) {
89 | stringBuilder.append(array[i] + ".");
90 | }
91 | stringBuilder.append("in-addr.arpa.");
92 | return stringBuilder.toString();
93 | }
94 |
95 | public void setDomainPatternsContainer(DomainPatternsContainer domainPatternsContainer) {
96 | this.domainPatternsContainer = domainPatternsContainer;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/AnswerProvider.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | /**
4 | * Provide the answer.An answerContainer must be registered in
5 | * {@link PreAnswerHandler#regitestProviders()} before it takes effect.
6 | *
7 | * @author yihua.huang@dianping.com
8 | * @date Dec 14, 2012
9 | */
10 | public interface AnswerProvider {
11 |
12 | /**
13 | *
14 | * @param query
15 | * @param type
16 | * @return
17 | */
18 | public String getAnswer(String query, int type);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/CustomAnswerPatternProvider.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import org.xbill.DNS.Address;
7 | import org.xbill.DNS.Type;
8 | import us.codecraft.blackhole.context.RequestContext;
9 | import us.codecraft.blackhole.utils.DoubleKeyMap;
10 |
11 | import java.util.HashMap;
12 | import java.util.LinkedHashMap;
13 | import java.util.Map;
14 | import java.util.Map.Entry;
15 | import java.util.concurrent.ConcurrentHashMap;
16 | import java.util.regex.Matcher;
17 | import java.util.regex.Pattern;
18 |
19 | /**
20 | * Read the config to domainPatterns and process the request record.
21 | *
22 | * @author yihua.huang@dianping.com
23 | * @date Dec 14, 2012
24 | */
25 | @Component
26 | public class CustomAnswerPatternProvider implements AnswerProvider {
27 |
28 |
29 | //TODO:domainPatternsContainer
30 | private volatile DoubleKeyMap domainPatterns = new DoubleKeyMap(new ConcurrentHashMap>(), LinkedHashMap.class);
31 |
32 | private volatile DoubleKeyMap domainTexts = new DoubleKeyMap(new ConcurrentHashMap>(), HashMap.class);
33 |
34 | private Logger logger = Logger.getLogger(getClass());
35 |
36 | /**
37 | * When the address configured as "DO_NOTHING",it will not return any
38 | * address.
39 | */
40 | public static final String DO_NOTHING = "do_nothing";
41 | private static final String FAKE_MX_PREFIX = "mail.";
42 | private static final String FAKE_CANME_PREFIX = "cname.";
43 |
44 | @Autowired
45 | private CustomTempAnswerProvider customTempAnswerProvider;
46 |
47 | /*
48 | * (non-Javadoc)
49 | *
50 | * @see
51 | * us.codecraft.blackhole.answer.AnswerProvider#getAnswer(java.lang.String,
52 | * int)
53 | */
54 | @Override
55 | public String getAnswer(String query, int type) {
56 | if (type == Type.PTR) {
57 | return null;
58 | }
59 | String clientIp = RequestContext.getClientIp();
60 | String ip = domainTexts.get(clientIp,query);
61 | if (ip!=null){
62 | return ip;
63 | }
64 | Map patternsForIp = domainPatterns.get(clientIp);
65 | if (patternsForIp == null) {
66 | return null;
67 | }
68 | for (Entry entry : patternsForIp.entrySet()) {
69 | Matcher matcher = entry.getKey().matcher(query);
70 | if (matcher.find()) {
71 | String answer = entry.getValue();
72 | if (answer.equals(DO_NOTHING)) {
73 | return null;
74 | }
75 | if (type == Type.MX) {
76 | String fakeMXHost = fakeMXHost(query);
77 | customTempAnswerProvider.add(clientIp, fakeMXHost, Type.A, answer);
78 | return fakeMXHost;
79 | }
80 | if (type == Type.CNAME) {
81 | String fakeCNAMEHost = fakeCNAMEHost(query);
82 | customTempAnswerProvider.add(clientIp, fakeCNAMEHost, Type.A, answer);
83 | return fakeCNAMEHost;
84 | }
85 | try {
86 | customTempAnswerProvider.add(clientIp, reverseIp(answer), Type.PTR, query);
87 | } catch (Throwable e) {
88 | logger.info("not a ip, ignored");
89 | }
90 | return answer;
91 | }
92 | }
93 | return null;
94 | }
95 |
96 | /**
97 | * generate a fake MX host
98 | *
99 | * @param domain
100 | * @return
101 | */
102 | private String fakeMXHost(String domain) {
103 | return FAKE_MX_PREFIX + domain;
104 | }
105 |
106 | /**
107 | * @param domain
108 | * @return
109 | */
110 | private String fakeCNAMEHost(String domain) {
111 | return FAKE_CANME_PREFIX + domain;
112 | }
113 |
114 | private String reverseIp(String ip) {
115 | int[] array = Address.toArray(ip);
116 | StringBuilder stringBuilder = new StringBuilder();
117 | for (int i = array.length - 1; i >= 0; i--) {
118 | stringBuilder.append(array[i] + ".");
119 | }
120 | stringBuilder.append("in-addr.arpa.");
121 | return stringBuilder.toString();
122 | }
123 |
124 | public void setDomainPatterns(DoubleKeyMap domainPatterns) {
125 | this.domainPatterns = domainPatterns;
126 | }
127 |
128 | public DoubleKeyMap getDomainPatterns() {
129 | return domainPatterns;
130 | }
131 |
132 | public DoubleKeyMap getDomainTexts() {
133 | return domainTexts;
134 | }
135 |
136 | public void setDomainTexts(DoubleKeyMap domainTexts) {
137 | this.domainTexts = domainTexts;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/CustomTempAnswerProvider.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.springframework.stereotype.Component;
4 | import us.codecraft.blackhole.context.RequestContext;
5 | import us.codecraft.blackhole.utils.DoubleKeyMap;
6 |
7 | import java.util.Map;
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | /**
11 | * @author yihua.huang@dianping.com
12 | * @date Dec 14, 2012
13 | */
14 | @Component
15 | public class CustomTempAnswerProvider implements AnswerProvider {
16 |
17 | private Map> container;
18 |
19 | public CustomTempAnswerProvider() {
20 | container = new ConcurrentHashMap>();
21 | }
22 |
23 | /*
24 | * (non-Javadoc)
25 | *
26 | * @see
27 | * us.codecraft.blackhole.answer.AnswerProvider#getAnswer(java.lang.String,
28 | * int)
29 | */
30 | @Override
31 | public String getAnswer(String query, int type) {
32 | String ip = RequestContext.getClientIp();
33 | DoubleKeyMap stringIntegerStringDoubleKeyMap = container.get(ip);
34 | if (stringIntegerStringDoubleKeyMap==null){
35 | return null;
36 | }
37 | return stringIntegerStringDoubleKeyMap.get(query, type);
38 | }
39 |
40 | public void add(String clientIp,String query, int type, String answer) {
41 | DoubleKeyMap stringIntegerStringDoubleKeyMap = container.get(clientIp);
42 | if (stringIntegerStringDoubleKeyMap==null){
43 | stringIntegerStringDoubleKeyMap = new DoubleKeyMap(ConcurrentHashMap.class);
44 | container.put(clientIp,stringIntegerStringDoubleKeyMap);
45 | }
46 | stringIntegerStringDoubleKeyMap.put(query, type, answer);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/DomainPatternsContainer.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.regex.Matcher;
6 | import java.util.regex.Pattern;
7 |
8 | /**
9 | * @author yihua.huang@dianping.com
10 | * @date: 13-7-15
11 | * Time: 上午8:18
12 | */
13 | public class DomainPatternsContainer {
14 |
15 | private volatile Map domainPatterns = new HashMap();
16 |
17 | private volatile Map domainTexts = new HashMap();
18 |
19 | public Map getDomainPatterns() {
20 | return domainPatterns;
21 | }
22 |
23 | public void setDomainPatterns(Map domainPatterns) {
24 | this.domainPatterns = domainPatterns;
25 | }
26 |
27 | public Map getDomainTexts() {
28 | return domainTexts;
29 | }
30 |
31 | public void setDomainTexts(Map domainTexts) {
32 | this.domainTexts = domainTexts;
33 | }
34 |
35 | public String getIp(String domain) {
36 | String ip = domainTexts.get(domain);
37 | if (ip != null) {
38 | return ip;
39 | }
40 | for (Map.Entry entry : domainPatterns.entrySet()) {
41 | Matcher matcher = entry.getKey().matcher(domain);
42 | if (matcher.find()) {
43 | return entry.getValue();
44 | }
45 | }
46 | return null;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/PostAnswerHandler.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.springframework.beans.factory.InitializingBean;
4 | import org.springframework.stereotype.Component;
5 |
6 | import java.util.LinkedList;
7 | import java.util.List;
8 |
9 | /**
10 | * @author yihua.huang@dianping.com
11 | * @date Dec 14, 2012
12 | */
13 | @Component("postAnswerHandler")
14 | public class PostAnswerHandler extends AbstractAnswerHandler implements InitializingBean {
15 |
16 | private List answerProviders;
17 |
18 | /*
19 | * (non-Javadoc)
20 | *
21 | * @see
22 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
23 | */
24 | @Override
25 | public void afterPropertiesSet() throws Exception {
26 | regitestProviders();
27 | }
28 |
29 | public void regitestProviders() {
30 | answerProviders = new LinkedList();
31 | }
32 |
33 | @Override
34 | protected List getaAnswerProviders() {
35 | return answerProviders;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/PreAnswerHandler.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.springframework.beans.factory.InitializingBean;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 |
7 | import java.util.LinkedList;
8 | import java.util.List;
9 |
10 | /**
11 | * @author yihua.huang@dianping.com
12 | * @date Dec 14, 2012
13 | */
14 | @Component("preAnswerHandler")
15 | public class PreAnswerHandler extends AbstractAnswerHandler implements InitializingBean {
16 |
17 | private List answerProviders;
18 |
19 | @Autowired
20 | private CustomTempAnswerProvider customTempAnswerProvider;
21 |
22 | @Autowired
23 | private CustomAnswerPatternProvider customAnswerPatternProvider;
24 |
25 | @Autowired
26 | private AnswerPatternProvider answerPatternProvider;
27 |
28 | @Autowired
29 | private TempAnswerProvider tempAnswerContainer;
30 |
31 | @Autowired
32 | private SafeHostAnswerProvider safeHostAnswerProvider;
33 |
34 |
35 | /*
36 | * (non-Javadoc)
37 | *
38 | * @see
39 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
40 | */
41 | @Override
42 | public void afterPropertiesSet() throws Exception {
43 | regitestProviders();
44 | }
45 |
46 | public void regitestProviders() {
47 | answerProviders = new LinkedList();
48 | answerProviders.add(customTempAnswerProvider);
49 | answerProviders.add(customAnswerPatternProvider);
50 | answerProviders.add(tempAnswerContainer);
51 | answerProviders.add(answerPatternProvider);
52 | answerProviders.add(safeHostAnswerProvider);
53 | }
54 |
55 | @Override
56 | protected List getaAnswerProviders() {
57 | return answerProviders;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/SafeHostAnswerProvider.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import org.xbill.DNS.Type;
7 | import us.codecraft.blackhole.antipollution.SafeHostManager;
8 | import us.codecraft.blackhole.config.Configure;
9 |
10 | /**
11 | * @author yihua.huang@dianping.com
12 | * @date Feb 20, 2013
13 | */
14 | @Component
15 | public class SafeHostAnswerProvider implements AnswerProvider {
16 |
17 | @Autowired
18 | private SafeHostManager safeBoxService;
19 |
20 | @Autowired
21 | private Configure configure;
22 |
23 | /*
24 | * (non-Javadoc)
25 | *
26 | * @see
27 | * us.codecraft.blackhole.answer.AnswerProvider#getAnswer(java.lang.String,
28 | * int)
29 | */
30 | @Override
31 | public String getAnswer(String query, int type) {
32 | if (!configure.isEnableSafeBox()) {
33 | return null;
34 | }
35 | if (type == Type.A || type == Type.AAAA) {
36 | return safeBoxService.get(StringUtils.removeEnd(query, "."));
37 | }
38 | return null;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/answer/TempAnswerProvider.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.answer;
2 |
3 | import org.springframework.stereotype.Component;
4 | import us.codecraft.blackhole.utils.DoubleKeyMap;
5 |
6 | import java.util.concurrent.ConcurrentHashMap;
7 |
8 | /**
9 | * @author yihua.huang@dianping.com
10 | * @date Dec 14, 2012
11 | */
12 | @Component
13 | public class TempAnswerProvider implements AnswerProvider {
14 |
15 | private DoubleKeyMap container;
16 |
17 | public TempAnswerProvider() {
18 | container = new DoubleKeyMap(
19 | ConcurrentHashMap.class);
20 | }
21 |
22 | /*
23 | * (non-Javadoc)
24 | *
25 | * @see
26 | * us.codecraft.blackhole.answer.AnswerProvider#getAnswer(java.lang.String,
27 | * int)
28 | */
29 | @Override
30 | public String getAnswer(String query, int type) {
31 | return container.get(query, type);
32 | }
33 |
34 | public void add(String query, int type, String answer) {
35 | container.put(query, type, answer);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/antipollution/BlackListManager.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.antipollution;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.InitializingBean;
5 | import org.springframework.stereotype.Component;
6 | import org.xbill.DNS.Message;
7 | import us.codecraft.blackhole.config.Configure;
8 | import us.codecraft.wifesays.me.ShutDownAble;
9 | import us.codecraft.wifesays.me.StandReadyWorker;
10 |
11 | import java.io.*;
12 | import java.util.HashSet;
13 | import java.util.Map;
14 | import java.util.Set;
15 | import java.util.concurrent.ConcurrentHashMap;
16 | import java.util.concurrent.locks.ReentrantReadWriteLock;
17 |
18 | /**
19 | * @author yihua.huang@dianping.com
20 | * @date Feb 19, 2013
21 | */
22 | @Component
23 | public class BlackListManager extends StandReadyWorker implements
24 | InitializingBean, ShutDownAble {
25 |
26 | private Logger logger = Logger.getLogger(getClass());
27 |
28 | private Map> invalidAddresses = new ConcurrentHashMap>();
29 |
30 | private Set blacklist = new HashSet();
31 |
32 | private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
33 |
34 | private static final String FLUSH_CMD = "flush";
35 |
36 | public void registerInvalidAddress(Message query, String address) {
37 | String questionName = query.getQuestion().getName().toString();
38 | logger.info("register error address " + address + " for query "
39 | + questionName);
40 | Set questionNames = invalidAddresses.get(address);
41 | if (questionNames == null) {
42 | questionNames = new HashSet();
43 | invalidAddresses.put(address, questionNames);
44 | }
45 | questionNames.add(questionName);
46 | if (questionNames.size() >= 2) {
47 | try {
48 | readWriteLock.writeLock().lock();
49 | blacklist.add(address);
50 | } finally {
51 | readWriteLock.writeLock().unlock();
52 | }
53 | }
54 | }
55 |
56 | public void addToBlacklist(String address) {
57 | try {
58 | readWriteLock.writeLock().lock();
59 | blacklist.add(address);
60 | } finally {
61 | readWriteLock.writeLock().unlock();
62 | }
63 | }
64 |
65 | public boolean inBlacklist(String address) {
66 | try {
67 | readWriteLock.readLock().lock();
68 | return blacklist.contains(address);
69 | } finally {
70 | readWriteLock.readLock().unlock();
71 | }
72 | }
73 |
74 | public void flushToFile(String filename) throws IOException {
75 | PrintWriter writer = new PrintWriter(new File(filename));
76 | for (String address : blacklist) {
77 | writer.println(address);
78 | }
79 | writer.close();
80 | }
81 |
82 | public void loadFromFile(String filename) throws IOException {
83 | BufferedReader bufferedReader = new BufferedReader(new FileReader(
84 | new File(filename)));
85 | String line = null;
86 | while ((line = bufferedReader.readLine()) != null) {
87 | line = line.trim();
88 | if (logger.isDebugEnabled()) {
89 | logger.debug("load blacklist address " + line);
90 | }
91 | blacklist.add(line);
92 | }
93 | bufferedReader.close();
94 | }
95 |
96 | /*
97 | * (non-Javadoc)
98 | *
99 | * @see us.codecraft.wifesays.me.ShutDownAble#shutDown()
100 | */
101 | @Override
102 | public void shutDown() {
103 | String filename = Configure.FILE_PATH + "/blacklist";
104 | try {
105 | flushToFile(filename);
106 | } catch (IOException e) {
107 | logger.warn("write to file " + filename + " error! " + e);
108 | }
109 | }
110 |
111 | /*
112 | * (non-Javadoc)
113 | *
114 | * @see
115 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
116 | */
117 | @Override
118 | public void afterPropertiesSet() throws Exception {
119 | String filename = Configure.FILE_PATH + "/blacklist";
120 | try {
121 | loadFromFile(filename);
122 | } catch (IOException e) {
123 | logger.warn("load file " + filename + " error! " + e);
124 | }
125 | }
126 |
127 | /*
128 | * (non-Javadoc)
129 | *
130 | * @see
131 | * us.codecraft.wifesays.me.StandReady#doWhatYouShouldDo(java.lang.String)
132 | */
133 | @Override
134 | public String doWhatYouShouldDo(String whatWifeSays) {
135 | if (FLUSH_CMD.equalsIgnoreCase(whatWifeSays)) {
136 | String filename = Configure.FILE_PATH + "/blacklist";
137 | try {
138 | flushToFile(filename);
139 | } catch (IOException e) {
140 | logger.warn("write to file " + filename + " error! " + e);
141 | }
142 | return "SUCCESS";
143 | }
144 | return null;
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/antipollution/SafeHostManager.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.antipollution;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.InitializingBean;
5 | import org.springframework.stereotype.Component;
6 | import us.codecraft.blackhole.config.Configure;
7 | import us.codecraft.wifesays.me.ShutDownAble;
8 | import us.codecraft.wifesays.me.StandReadyWorker;
9 |
10 | import java.io.*;
11 | import java.util.Map;
12 | import java.util.Map.Entry;
13 | import java.util.concurrent.ConcurrentHashMap;
14 |
15 | /**
16 | * @author yihua.huang@dianping.com
17 | * @date Feb 20, 2013
18 | */
19 | @Component
20 | public class SafeHostManager extends StandReadyWorker implements
21 | InitializingBean, ShutDownAble {
22 |
23 | private Logger logger = Logger.getLogger(getClass());
24 |
25 | private Map poisons = new ConcurrentHashMap();
26 |
27 | private Map answers = new ConcurrentHashMap();
28 |
29 | private static final String FLUSH_CMD = "flush";
30 |
31 | public void flushToFile(String filename) throws IOException {
32 | PrintWriter writer = new PrintWriter(new File(filename));
33 | for (Entry address : answers.entrySet()) {
34 | writer.println(address.getValue() + "\t" + address.getKey());
35 | }
36 | filename.charAt(1);
37 | writer.close();
38 | }
39 |
40 | public void loadFromFile(String filename) throws IOException {
41 | BufferedReader bufferedReader = new BufferedReader(new FileReader(
42 | new File(filename)));
43 | String line = null;
44 | while ((line = bufferedReader.readLine()) != null) {
45 | line = line.trim();
46 | if (line.startsWith("#")) {
47 | break;
48 | }
49 | String[] split = line.split("\\s");
50 | if (split.length <= 1) {
51 | logger.info("error record \"" + line + "\", ignored.");
52 | }
53 | answers.put(split[1], split[0]);
54 | poisons.put(split[1], Boolean.TRUE);
55 | if (logger.isDebugEnabled()) {
56 | logger.debug("load blacklist address " + line);
57 | }
58 | }
59 | bufferedReader.close();
60 | }
61 |
62 | public void add(String domain, String address) {
63 | answers.put(domain, address);
64 | }
65 |
66 | public boolean isPoisoned(String domain) {
67 | Boolean poisoned = poisons.get(domain);
68 | if (poisoned == null) {
69 | return false;
70 | }
71 | return poisoned;
72 | }
73 |
74 | public void setPoisoned(String domain) {
75 | poisons.put(domain, Boolean.TRUE);
76 | }
77 |
78 | public String get(String domain) {
79 | return answers.get(domain);
80 | }
81 |
82 | /*
83 | * (non-Javadoc)
84 | *
85 | * @see us.codecraft.wifesays.me.ShutDownAble#shutDown()
86 | */
87 | @Override
88 | public void shutDown() {
89 | String filename = Configure.FILE_PATH + "/safehost";
90 | try {
91 | flushToFile(filename);
92 | } catch (IOException e) {
93 | logger.warn("write to file " + filename + " error! " + e);
94 | }
95 | }
96 |
97 | /*
98 | * (non-Javadoc)
99 | *
100 | * @see
101 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
102 | */
103 | @Override
104 | public void afterPropertiesSet() throws Exception {
105 | String filename = Configure.FILE_PATH + "/safehost";
106 | try {
107 | loadFromFile(filename);
108 | } catch (IOException e) {
109 | logger.warn("load file " + filename + " error! " + e);
110 | }
111 | }
112 |
113 | /*
114 | * (non-Javadoc)
115 | *
116 | * @see
117 | * us.codecraft.wifesays.me.StandReady#doWhatYouShouldDo(java.lang.String)
118 | */
119 | @Override
120 | public String doWhatYouShouldDo(String whatWifeSays) {
121 | if (FLUSH_CMD.equalsIgnoreCase(whatWifeSays)) {
122 | String filename = Configure.FILE_PATH + "/safehost";
123 | try {
124 | flushToFile(filename);
125 | } catch (IOException e) {
126 | logger.warn("write to file " + filename + " error! " + e);
127 | }
128 | return "SUCCESS";
129 | }
130 | return null;
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/cache/CacheClient.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.cache;
2 |
3 | /**
4 | * @author yihua.huang@dianping.com
5 | * @date Dec 19, 2012
6 | */
7 | public interface CacheClient {
8 |
9 | /**
10 | * If "cache" configure is turned off, it can get the elemets already in
11 | * cache.
12 | *
13 | * @param key
14 | * @param value
15 | * @param expireTime
16 | * in s
17 | * @return
18 | */
19 | boolean set(String key, T value, int expireTime);
20 |
21 | /**
22 | * If "cache" configure is turned off, it can't be setted and return false.
23 | *
24 | * @param key
25 | * @return
26 | */
27 | T get(String key);
28 |
29 | /**
30 | * init cache
31 | *
32 | * @throws Exception
33 | */
34 | void init() throws Exception;
35 |
36 | void clearCache();
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/cache/CacheManager.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.cache;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.InitializingBean;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Qualifier;
7 | import org.springframework.stereotype.Component;
8 | import org.xbill.DNS.Message;
9 | import org.xbill.DNS.Section;
10 | import us.codecraft.blackhole.config.Configure;
11 | import us.codecraft.blackhole.utils.RecordUtils;
12 |
13 | import java.util.concurrent.ExecutorService;
14 | import java.util.concurrent.Executors;
15 |
16 | /**
17 | * @author yihua.huang@dianping.com
18 | * @date Dec 19, 2012
19 | */
20 | @Component
21 | public class CacheManager implements InitializingBean {
22 |
23 | @Autowired
24 | private Configure configure;
25 |
26 | private volatile ExecutorService cacheSaveExecutors;
27 |
28 | private Logger logger = Logger.getLogger(getClass());
29 |
30 | @Qualifier("EhcacheClient")
31 | @Autowired
32 | private CacheClient cacheClient;
33 |
34 | public byte[] getResponseFromCache(Message query) {
35 | if (!configure.isUseCache()) {
36 | return null;
37 | }
38 | UDPPackage udpPackage = cacheClient
39 | .get(getCacheKey(query));
40 | if (udpPackage == null) {
41 | return null;
42 | }
43 | byte[] bytes = udpPackage.getBytes(query.getHeader().getID());
44 | return bytes;
45 | }
46 |
47 | public boolean set(Message query, T value, int expireTime) {
48 | return cacheClient.set(getCacheKey(query), value, expireTime);
49 | }
50 |
51 | public T get(Message query) {
52 | return cacheClient.get(getCacheKey(query));
53 | }
54 |
55 | public boolean set(String key, T value, int expireTime) {
56 | return cacheClient.set(key, value, expireTime);
57 | }
58 |
59 | public T get(String key) {
60 | return cacheClient.get(key);
61 | }
62 |
63 | public String getCacheKey(Message query) {
64 | return RecordUtils.recordKey(query.getQuestion());
65 | }
66 |
67 | private int minTTL(Message response) {
68 | return (int) Math.min(RecordUtils.maxTTL(response
69 | .getSectionArray(Section.ANSWER)), RecordUtils.maxTTL(response
70 | .getSectionArray(Section.ADDITIONAL)));
71 | }
72 |
73 | public void setResponseToCache(final Message query, final byte[] responseBytes) {
74 | if (configure.isUseCache()) {
75 | getCacheSaveExecutors().execute(new Runnable() {
76 |
77 | @Override
78 | public void run() {
79 | try {
80 | Message response = new Message(responseBytes);
81 | int expireTime;
82 | if (configure.getCacheExpire() > 0) {
83 | expireTime = configure.getCacheExpire();
84 | } else {
85 | expireTime = minTTL(response);
86 | }
87 | cacheClient.set(getCacheKey(query), new UDPPackage(
88 | responseBytes), expireTime);
89 | } catch (Throwable e) {
90 | logger.warn("set to cache error ", e);
91 | }
92 |
93 | }
94 | });
95 | }
96 |
97 | }
98 |
99 | /**
100 | * @return the cacheSaveExecutors
101 | */
102 | private ExecutorService getCacheSaveExecutors() {
103 | if (cacheSaveExecutors == null) {
104 | synchronized (this) {
105 | cacheSaveExecutors = Executors.newFixedThreadPool(configure.getThreadNum());
106 | }
107 | }
108 | return cacheSaveExecutors;
109 | }
110 |
111 | /*
112 | * (non-Javadoc)
113 | *
114 | * @see
115 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
116 | */
117 | @Override
118 | public void afterPropertiesSet() throws Exception {
119 | if (configure.isUseCache()) {
120 | getCacheSaveExecutors();
121 | }
122 | }
123 |
124 | public void clearCache() {
125 | cacheClient.clearCache();
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/cache/EhcacheClient.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.cache;
2 |
3 | import net.sf.ehcache.Cache;
4 | import net.sf.ehcache.CacheManager;
5 | import net.sf.ehcache.Element;
6 | import org.apache.commons.lang3.StringUtils;
7 | import org.apache.log4j.Logger;
8 | import org.springframework.beans.factory.InitializingBean;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.core.io.ClassPathResource;
11 | import org.springframework.stereotype.Component;
12 | import us.codecraft.blackhole.config.Configure;
13 | import us.codecraft.wifesays.me.ShutDownAble;
14 | import us.codecraft.wifesays.me.StandReadyWorker;
15 |
16 | import java.io.FileWriter;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.PrintWriter;
20 | import java.util.List;
21 | import java.util.concurrent.Executors;
22 | import java.util.concurrent.ScheduledExecutorService;
23 | import java.util.concurrent.TimeUnit;
24 |
25 | /**
26 | * @author yihua.huang@dianping.com
27 | * @date Dec 19, 2012
28 | */
29 | @Component("EhcacheClient")
30 | public class EhcacheClient extends StandReadyWorker implements CacheClient,
31 | InitializingBean, ShutDownAble {
32 |
33 | private Logger logger = Logger.getLogger(EhcacheClient.class);
34 |
35 | @Autowired
36 | private Configure configure;
37 |
38 | private static volatile CacheManager manager;
39 |
40 | private static final String CACHE_NAME = "DNS";
41 |
42 | private static final String CACHE_CONF = "ehcache.xml";
43 |
44 | private static final String CLEAR = "clear_cache";
45 |
46 | private static final String DUMP = "dump_cache";
47 |
48 | private static final String STAT = "stat_cache";
49 |
50 | private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
51 |
52 | /**
53 | * (non-Jsdoc)
54 | *
55 | * @see com.CacheClient.mail.postman.cache.service.impl.CacheClient#init()
56 | */
57 | @Override
58 | public void init() {
59 | ClassPathResource classPathResource = new ClassPathResource(CACHE_CONF);
60 | InputStream inputStream = null;
61 | try {
62 | inputStream = classPathResource.getInputStream();
63 | if (manager == null) {
64 | synchronized (EhcacheClient.class) {
65 | if (manager == null) {
66 | manager = new CacheManager(inputStream);
67 | scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
68 | @Override
69 | public void run() {
70 | if (logger.isDebugEnabled()) {
71 | logger.debug("start to flush cache to disk");
72 | }
73 | try {
74 | Cache cache = manager.getCache(CACHE_NAME);
75 | cache.flush();
76 | } catch (Exception e) {
77 | logger.warn("flush cache error!", e);
78 | }
79 | }
80 | }, 1, 1, TimeUnit.MINUTES);
81 | }
82 | }
83 | }
84 | } catch (Exception e) {
85 | logger.error("init ehcache error!", e);
86 | } finally {
87 | try {
88 | inputStream.close();
89 | } catch (IOException e) {
90 | logger.warn("close error", e);
91 | }
92 | inputStream = null;
93 | }
94 | }
95 |
96 | /**
97 | * (non-Jsdoc)
98 | *
99 | * @see com.dianping.mail.postman.cache.service.CacheService#get(java.lang.String)
100 | */
101 | @SuppressWarnings("unchecked")
102 | @Override
103 | public T get(String key) {
104 | if (manager == null || key == null) {
105 | return null;
106 | }
107 | if (manager == null) {
108 | return null;
109 | }
110 | Cache cache = manager.getCache(CACHE_NAME);
111 | Element element = cache.get(key);
112 | if (element == null) {
113 | return null;
114 | }
115 | T value = (T) element.getObjectValue();
116 | return value;
117 | }
118 |
119 | /**
120 | * (non-Jsdoc)
121 | *
122 | * @see com.dianping.mail.postman.cache.service.CacheService#set(java.lang.String,
123 | * java.lang.Object, int)
124 | */
125 | @Override
126 | public boolean set(String key, T value, int expireTime) {
127 | if (!configure.isUseCache()) {
128 | return false;
129 | }
130 | if (key == null || value == null) {
131 | throw new IllegalArgumentException(
132 | "key and value should not be null");
133 | }
134 | if (manager == null) {
135 | return false;
136 | }
137 | Cache cache = manager.getCache(CACHE_NAME);
138 | Element element = new Element(key, value, Boolean.FALSE, 0, expireTime);
139 | cache.put(element);
140 | return true;
141 | }
142 |
143 | /**
144 | * telnet control for caches
145 | * @param whatWifeSays
146 | * @return
147 | */
148 | @Override
149 | public String doWhatYouShouldDo(String whatWifeSays) {
150 | if (manager == null) {
151 | return "CACHE NOT USED";
152 | }
153 | if (DUMP.equalsIgnoreCase(whatWifeSays)) {
154 | final String dumpFilename = Configure.FILE_PATH + "/cache.dump";
155 | Cache cache = manager.getCache(CACHE_NAME);
156 | List keys = (List) cache.getKeys();
157 | try {
158 | PrintWriter writer = new PrintWriter(new FileWriter(dumpFilename));
159 | for (String key : keys) {
160 | writer.println(key + "\t" + cache.get(key));
161 | }
162 | writer.close();
163 | } catch (IOException e) {
164 | logger.error("dumpfile error", e);
165 | }
166 | return keys.size() + "_caches_are_dumped_to_file_'" + dumpFilename + "'";
167 | } else if (CLEAR.equalsIgnoreCase(whatWifeSays)) {
168 | clearCache();
169 | return "REMOVE SUCCESS";
170 | } else if (STAT.equalsIgnoreCase(whatWifeSays)) {
171 | Cache cache = manager.getCache(CACHE_NAME);
172 | return cache.getSize()+" records in cache, try 'cache dump' to more info";
173 | }
174 | if (whatWifeSays.startsWith(CLEAR)) {
175 | String[] split = whatWifeSays.split(":");
176 | if (split.length >= 2) {
177 | String address = split[1];
178 | if (!address.endsWith(".")) {
179 | address += ".";
180 | }
181 | String type = "A";
182 | if (split.length > 2) {
183 | type = split[2];
184 | }
185 | String key = address + " " + type;
186 | if (manager == null) {
187 | return "CACHE NOT USED";
188 | }
189 | Cache cache = manager.getCache(CACHE_NAME);
190 | if (cache.get(key) == null) {
191 | return "KEY " + key + " NOT EXIST";
192 | }
193 | cache.remove(key);
194 | return "REMOVE SUCCESS";
195 | }
196 | }
197 | return null;
198 | }
199 |
200 | public void clearCache() {
201 | if (manager==null){
202 | return;
203 | }
204 | Cache cache = manager.getCache(CACHE_NAME);
205 | if (cache==null){
206 | return;
207 | }
208 | @SuppressWarnings("unchecked")
209 | List keys = cache.getKeys();
210 | logger.info(keys.size() + " cached records cleared");
211 | if (logger.isDebugEnabled()) {
212 | logger.debug("[" + StringUtils.join(keys, ",") + "]");
213 | }
214 | cache.removeAll();
215 | }
216 |
217 | /*
218 | * (non-Javadoc)
219 | *
220 | * @see
221 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
222 | */
223 | @Override
224 | public void afterPropertiesSet() throws Exception {
225 | if (configure.isUseCache()) {
226 | Thread thread = new Thread() {
227 | @Override
228 | public void run() {
229 | init();
230 | }
231 | };
232 | thread.setDaemon(true);
233 | thread.start();
234 | }
235 | }
236 |
237 | /*
238 | * (non-Javadoc)
239 | *
240 | * @see us.codecraft.wifesays.me.ShutDownAble#shutDown()
241 | */
242 | @Override
243 | public void shutDown() {
244 | try {
245 | Cache cache = manager.getCache(CACHE_NAME);
246 | cache.flush();
247 | manager.shutdown();
248 | logger.info("flush cache to disk success!");
249 | } catch (Exception e) {
250 | logger.warn("flush cache error!", e);
251 | }
252 | }
253 |
254 | }
255 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/cache/MapCacheClient.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.cache;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import java.util.Map;
6 | import java.util.concurrent.ConcurrentHashMap;
7 |
8 | /**
9 | * Cache in ConcurrentHashMap. Just for test.
10 | * Not in use.
11 | * @author code4crafer@gmail.com
12 | * Date: 13-6-23
13 | * Time: 下午12:08
14 | */
15 | @Component("MapCacheClient")
16 | public class MapCacheClient implements CacheClient{
17 |
18 | private Map map = new ConcurrentHashMap();
19 |
20 | @Override
21 | public boolean set(String key, T value, int expireTime) {
22 | map.put(key,value);
23 | return true;
24 | }
25 |
26 | @Override
27 | public T get(String key) {
28 | return (T)map.get(key);
29 | }
30 |
31 | @Override
32 | public void init() throws Exception {
33 | }
34 |
35 | @Override
36 | public void clearCache() {
37 | map = new ConcurrentHashMap();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/cache/UDPPackage.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.cache;
2 |
3 | import org.xbill.DNS.Message;
4 |
5 | import java.io.IOException;
6 | import java.io.Serializable;
7 | import java.util.Arrays;
8 |
9 | /**
10 | * @author yihua.huang@dianping.com
11 | * @date Dec 19, 2012
12 | */
13 | public class UDPPackage implements Serializable {
14 |
15 | private static final long serialVersionUID = 1L;
16 | private byte[] bytes;
17 | private static final int MASK = (1 << 8) - 1;
18 |
19 | public UDPPackage(byte[] bytes) {
20 | this.bytes = bytes;
21 | }
22 |
23 | /**
24 | * copy on write
25 | *
26 | * @param version
27 | * @return
28 | */
29 | public byte[] getBytes(int version) {
30 | byte[] bytes = Arrays.copyOf(this.bytes, this.bytes.length);
31 | bytes[1] = (byte) (version & MASK);
32 | bytes[0] = (byte) ((version >> 8) & MASK);
33 | return bytes;
34 | }
35 |
36 | /**
37 | * block
38 | *
39 | * @param version
40 | * @return
41 | */
42 | public synchronized byte[] getBytesSync(int version) {
43 | byte[] bytes = Arrays.copyOf(this.bytes, this.bytes.length);
44 | bytes[1] = (byte) (version & MASK);
45 | bytes[0] = (byte) ((version >> 8) & MASK);
46 | return bytes;
47 | }
48 |
49 | public static void main(String[] args) {
50 | byte[] b = new byte[] { 0, 1, 2 };
51 | UDPPackage package1 = new UDPPackage(b);
52 | package1.getBytes(128);
53 | System.out.println(package1);
54 |
55 | package1.getBytes(1);
56 | System.out.println(package1);
57 | }
58 |
59 | /*
60 | * (non-Javadoc)
61 | *
62 | * @see java.lang.Object#toString()
63 | */
64 | @Override
65 | public String toString() {
66 | try {
67 | final Message message = new Message(bytes);
68 | return message.toString();
69 | } catch (IOException e) {
70 |
71 | if (bytes == null || bytes.length == 0) {
72 | return "[]";
73 | }
74 | StringBuilder stringBuilder = new StringBuilder();
75 | stringBuilder.append("[");
76 | stringBuilder.append(bytes[0]);
77 | for (int i = 1; i < bytes.length; i++) {
78 | stringBuilder.append(",");
79 | stringBuilder.append(bytes[i]);
80 | }
81 | stringBuilder.append("]");
82 | return stringBuilder.toString();
83 | }
84 |
85 |
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/concurrent/ThreadPools.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.concurrent;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 | import org.springframework.beans.factory.InitializingBean;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Component;
8 | import us.codecraft.blackhole.config.Configure;
9 |
10 | import java.util.concurrent.ExecutorService;
11 | import java.util.concurrent.LinkedBlockingQueue;
12 | import java.util.concurrent.ThreadPoolExecutor;
13 | import java.util.concurrent.TimeUnit;
14 |
15 | /**
16 | * @author yihua.huang@dianping.com
17 | * @date Apr 11, 2013
18 | */
19 | @Component
20 | public class ThreadPools implements InitializingBean {
21 |
22 | private ThreadPoolExecutor mainProcessExecutor;
23 | private ThreadPoolExecutor udpReceiverExecutor;
24 | private int threadNum = 0;
25 | @Autowired
26 | private Configure configure;
27 | private Log logger = LogFactory.getLog(getClass());
28 |
29 | public void resize() {
30 | if (threadNum != configure.getThreadNum()) {
31 | threadNum = configure.getThreadNum();
32 | logger.info("Thread num changed, resize to " + threadNum);
33 | if (threadNum < configure.getThreadNum()) {
34 | mainProcessExecutor.setMaximumPoolSize(threadNum);
35 | mainProcessExecutor.setCorePoolSize(threadNum);
36 | udpReceiverExecutor.setMaximumPoolSize(threadNum);
37 | udpReceiverExecutor.setCorePoolSize(threadNum);
38 | } else {
39 | mainProcessExecutor.setCorePoolSize(threadNum);
40 | mainProcessExecutor.setMaximumPoolSize(threadNum);
41 | udpReceiverExecutor.setCorePoolSize(threadNum);
42 | udpReceiverExecutor.setMaximumPoolSize(threadNum);
43 | }
44 | }
45 | }
46 |
47 | public ExecutorService getMainProcessExecutor() {
48 | return mainProcessExecutor;
49 | }
50 |
51 | public ExecutorService getUdpReceiverExecutor() {
52 | return udpReceiverExecutor;
53 | }
54 |
55 | /*
56 | * (non-Javadoc)
57 | *
58 | * @see
59 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
60 | */
61 | @Override
62 | public void afterPropertiesSet() throws Exception {
63 | threadNum = configure.getThreadNum();
64 | mainProcessExecutor = new ThreadPoolExecutor(threadNum, threadNum, 0L,
65 | TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
66 | udpReceiverExecutor = new ThreadPoolExecutor(threadNum, threadNum, 0L,
67 | TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/ConfigFileLoader.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 | import org.apache.commons.lang3.BooleanUtils;
4 | import org.apache.log4j.Level;
5 | import org.apache.log4j.Logger;
6 | import org.springframework.beans.factory.InitializingBean;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Component;
9 | import us.codecraft.blackhole.concurrent.ThreadPools;
10 | import us.codecraft.blackhole.forward.DNSHostsContainer;
11 | import us.codecraft.wifesays.me.ReloadAble;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.FileReader;
15 | import java.net.InetSocketAddress;
16 |
17 | /**
18 | * @author yihua.huang@dianping.com
19 | * @date Dec 28, 2012
20 | */
21 | @Component
22 | public class ConfigFileLoader implements InitializingBean, ReloadAble {
23 |
24 | @Autowired
25 | private DNSHostsContainer dnsHostsContainer;
26 |
27 | @Autowired
28 | private Configure configure;
29 |
30 | @Autowired
31 | private ThreadPools threadPools;
32 |
33 | private boolean reloadOff = false;
34 |
35 | /**
36 | * @param reloadOff the reloadOff to set
37 | */
38 | public void setReloadOff(boolean reloadOff) {
39 | this.reloadOff = reloadOff;
40 | }
41 |
42 | private Logger logger = Logger.getLogger(getClass());
43 |
44 | public void readConfig(String filename) {
45 | try {
46 | BufferedReader bufferedReader = new BufferedReader(new FileReader(
47 | filename));
48 | String line = null;
49 | dnsHostsContainer.clearHosts();
50 | while ((line = bufferedReader.readLine()) != null) {
51 | line = line.trim();
52 | if (line.startsWith("#")) {
53 | continue;
54 | }
55 | try {
56 | String[] items = line.split("=");
57 | if (items.length < 2) {
58 | continue;
59 | }
60 | String key = items[0];
61 | String value = items[1];
62 | boolean configed = config(key, value);
63 | if (configed) {
64 | logger.info("read config success:\t" + line);
65 | }
66 | } catch (Exception e) {
67 | logger.warn("parse config line error:\t" + line, e);
68 | }
69 | }
70 | bufferedReader.close();
71 | } catch (Throwable e) {
72 | logger.warn("read config file failed:" + filename, e);
73 | }
74 | }
75 |
76 | private boolean config(String key, String value) {
77 | if (key.equalsIgnoreCase("ttl")) {
78 | configure.setTtl(Integer.parseInt(value));
79 | } else if (key.equalsIgnoreCase("dns")) {
80 | dnsHostsContainer.addHost(new InetSocketAddress(value,
81 | Configure.DNS_PORT));
82 | } else if (key.equalsIgnoreCase("cache")) {
83 | configure.setUseCache(BooleanUtils.toBooleanObject(value));
84 | } else if (key.equalsIgnoreCase("log")) {
85 | configLogLevel(value);
86 | } else if (key.equalsIgnoreCase("dns_timeout")) {
87 | int dnsTimeout = Integer.parseInt(value);
88 | configure.setDnsTimeOut(dnsTimeout);
89 | dnsHostsContainer.setTimeout(dnsTimeout);
90 | } else if (key.equalsIgnoreCase("fake_dns")) {
91 | configure.setFakeDnsServer(value);
92 | } else if (key.equalsIgnoreCase("safe_box")) {
93 | configure.setEnableSafeBox(BooleanUtils.toBooleanObject(value));
94 | } else if (key.equalsIgnoreCase("thread_num")) {
95 | configure.setThreadNum(Integer.parseInt(value));
96 | } else if (key.equalsIgnoreCase("cache_expire")) {
97 | configure.setCacheExpire(Integer.parseInt(value));
98 | } else {
99 | return false;
100 | }
101 | return true;
102 | }
103 |
104 | private void configLogLevel(String value) {
105 | Logger rootLogger = Logger.getRootLogger();
106 | if ("debug".equalsIgnoreCase(value)) {
107 | rootLogger.setLevel(Level.DEBUG);
108 | } else if ("info".equalsIgnoreCase(value)) {
109 | rootLogger.setLevel(Level.INFO);
110 | } else if ("warn".equalsIgnoreCase(value)) {
111 | rootLogger.setLevel(Level.WARN);
112 | } else if ("trace".equalsIgnoreCase(value)) {
113 | rootLogger.setLevel(Level.TRACE);
114 | } else {
115 | return;
116 | }
117 | configure.setLoggerLevel(value);
118 |
119 | }
120 |
121 | /*
122 | * (non-Javadoc)
123 | *
124 | * @see us.codecraft.wifesays.me.ReloadAble#reload()
125 | */
126 | @Override
127 | public void reload() {
128 | if (!reloadOff) {
129 | readConfig(Configure.getConfigFilename());
130 | }
131 | threadPools.resize();
132 | }
133 |
134 | /*
135 | * (non-Javadoc)
136 | *
137 | * @see
138 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
139 | */
140 | @Override
141 | public void afterPropertiesSet() throws Exception {
142 | readConfig(Configure.getConfigFilename());
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/ConfigFileRefresher.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 | import org.springframework.beans.factory.InitializingBean;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 |
7 | import java.io.File;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.ScheduledExecutorService;
10 | import java.util.concurrent.TimeUnit;
11 |
12 | /**
13 | * @author yihua.huang@dianping.com
14 | * @date Dec 29, 2012
15 | */
16 | @Component
17 | public class ConfigFileRefresher implements InitializingBean {
18 |
19 | @Autowired
20 | private ConfigFileLoader configFileLoader;
21 |
22 | private ScheduledExecutorService scheduledExecutorService = Executors
23 | .newScheduledThreadPool(1);
24 |
25 | private long lastFileModifiedTime;
26 |
27 | /*
28 | * (non-Javadoc)
29 | *
30 | * @see
31 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
32 | */
33 | public void afterPropertiesSet() throws Exception {
34 | File configFile = new File(Configure.getConfigFilename());
35 | lastFileModifiedTime = configFile.lastModified();
36 |
37 | scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
38 |
39 | public void run() {
40 | File configFile = new File(Configure.getConfigFilename());
41 | // When two files' last modify time not equal, we consider it is
42 | // changed.
43 | synchronized (this) {
44 | if (configFile.lastModified() != lastFileModifiedTime) {
45 | lastFileModifiedTime = configFile.lastModified();
46 | configFileLoader.reload();
47 | }
48 | }
49 | }
50 | }, 500, 500, TimeUnit.MILLISECONDS);
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/Configure.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import java.net.InetSocketAddress;
6 | import java.net.SocketAddress;
7 |
8 | /**
9 | * @author yihua.huang@dianping.com
10 | * @date Dec 14, 2012
11 | */
12 | @Component
13 | public class Configure {
14 | public final static long DEFAULT_TTL = 2000;
15 | public final static int DEFAULT_DNS_TIMEOUT = 2000;
16 | public static String FILE_PATH = "/usr/local/blackhole/";
17 |
18 | public final static int DEFAULT_MX_PRIORY = 10;
19 |
20 | private long ttl = DEFAULT_TTL;
21 |
22 | private int mxPriory = DEFAULT_MX_PRIORY;
23 |
24 | public final static int DNS_PORT = 53;
25 |
26 | private int dnsTimeOut = DEFAULT_DNS_TIMEOUT;
27 |
28 | private String loggerLevel;
29 |
30 | private boolean enableSafeBox = true;
31 |
32 | private SocketAddress fakeDnsServer;
33 |
34 | private boolean useCache = true;
35 |
36 | private int threadNum = 4;
37 |
38 | private static String configFilename;
39 |
40 | private int cacheExpire;
41 |
42 | public static String getConfigFilename() {
43 | if (configFilename == null) {
44 | return Configure.FILE_PATH + "/config/blackhole.conf";
45 | } else {
46 | return configFilename;
47 | }
48 | }
49 |
50 | public static void setConfigFilename(String name) {
51 | configFilename = name;
52 | }
53 |
54 | private static String zonesFilename;
55 |
56 | public static void setZonesFilename(String name) {
57 | zonesFilename = name;
58 | }
59 |
60 | public static String getZonesFilename() {
61 | if (zonesFilename == null) {
62 | return Configure.FILE_PATH + "/config/zones";
63 | } else {
64 | return zonesFilename;
65 | }
66 | }
67 |
68 | public boolean isEnableSafeBox() {
69 | return enableSafeBox;
70 | }
71 |
72 | public void setEnableSafeBox(boolean enableSafeBox) {
73 | this.enableSafeBox = enableSafeBox;
74 | }
75 |
76 | /**
77 | * @return the useCache
78 | */
79 | public boolean isUseCache() {
80 | return useCache;
81 | }
82 |
83 | /**
84 | *
85 | * @return
86 | */
87 | public long getTTL() {
88 | return ttl;
89 | }
90 |
91 | /**
92 | * @return the ttl
93 | */
94 | public long getTtl() {
95 | return ttl;
96 | }
97 |
98 | /**
99 | * @param ttl
100 | * the ttl to set
101 | */
102 | public void setTtl(long ttl) {
103 | this.ttl = ttl;
104 | }
105 |
106 | /**
107 | * @return the mxPriory
108 | */
109 | public int getMxPriory() {
110 | return mxPriory;
111 | }
112 |
113 | /**
114 | * @param mxPriory
115 | * the mxPriory to set
116 | */
117 | public void setMxPriory(int mxPriory) {
118 | this.mxPriory = mxPriory;
119 | }
120 |
121 | /**
122 | * @return the dnsTimeOut
123 | */
124 | public int getDnsTimeOut() {
125 | return dnsTimeOut;
126 | }
127 |
128 | /**
129 | * @return the loggerLevel
130 | */
131 | public String getLoggerLevel() {
132 | return loggerLevel;
133 | }
134 |
135 | /**
136 | * @param loggerLevel
137 | * the loggerLevel to set
138 | */
139 | public void setLoggerLevel(String loggerLevel) {
140 | this.loggerLevel = loggerLevel;
141 | }
142 |
143 | /**
144 | * @param dnsTimeOut
145 | * the dnsTimeOut to set
146 | */
147 | public void setDnsTimeOut(int dnsTimeOut) {
148 | this.dnsTimeOut = dnsTimeOut;
149 | }
150 |
151 | /**
152 | * @param useCache
153 | * the useCache to set
154 | */
155 | public void setUseCache(boolean useCache) {
156 | this.useCache = useCache;
157 | }
158 |
159 | public SocketAddress getFakeDnsServer() {
160 | return fakeDnsServer;
161 | }
162 |
163 | public void setFakeDnsServer(String fakeDnsServer) {
164 | this.fakeDnsServer = new InetSocketAddress(fakeDnsServer, DNS_PORT);
165 | }
166 |
167 | public int getThreadNum() {
168 | return threadNum;
169 | }
170 |
171 | public void setThreadNum(int threadNum) {
172 | this.threadNum = threadNum;
173 | }
174 |
175 | public int getCacheExpire() {
176 | return cacheExpire;
177 | }
178 |
179 | public void setCacheExpire(int cacheExpire) {
180 | this.cacheExpire = cacheExpire;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/DomainPattern.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 | import java.util.regex.Pattern;
4 |
5 | /**
6 | * Compile a pattern to regex or plain text.
7 | *
8 | * @author yihua.huang@dianping.com
9 | * @date: 13-7-15
10 | * Time: 上午7:27
11 | */
12 | public class DomainPattern {
13 |
14 | private Pattern regexPattern;
15 |
16 | private String fullTextMatch;
17 |
18 | private boolean useRegex;
19 |
20 | public Pattern getRegexPattern() {
21 | return regexPattern;
22 | }
23 |
24 | public void setRegexPattern(Pattern regexPattern) {
25 | this.regexPattern = regexPattern;
26 | }
27 |
28 | public String getFullTextMatch() {
29 | return fullTextMatch;
30 | }
31 |
32 | public void setFullTextMatch(String fullTextMatch) {
33 | this.fullTextMatch = fullTextMatch;
34 | }
35 |
36 | public boolean isUseRegex() {
37 | return useRegex;
38 | }
39 |
40 | public void setUseRegex(boolean useRegex) {
41 | this.useRegex = useRegex;
42 | }
43 |
44 | /**
45 | * @param domain
46 | * @return
47 | */
48 | public static DomainPattern parse(String domain) {
49 | DomainPattern domainPattern = new DomainPattern();
50 | if (domain.contains("*")) {
51 | Pattern pattern = compileStringToPattern(domain);
52 | domainPattern.setRegexPattern(pattern);
53 | domainPattern.setUseRegex(true);
54 | } else {
55 | domainPattern.setFullTextMatch(domain+".");
56 | domainPattern.setUseRegex(false);
57 | }
58 | return domainPattern;
59 | }
60 |
61 | private static Pattern compileStringToPattern(String patternStr) {
62 | patternStr = "^" + patternStr;
63 | patternStr += ".";
64 | patternStr = patternStr.replace(".", "\\.");
65 | patternStr = patternStr.replace("*", ".*");
66 | patternStr += "$";
67 | return Pattern.compile(patternStr);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/ZonesFileLoader.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.apache.log4j.Logger;
5 | import org.springframework.beans.factory.InitializingBean;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Component;
8 | import us.codecraft.blackhole.answer.AnswerPatternProvider;
9 | import us.codecraft.blackhole.answer.CustomAnswerPatternProvider;
10 | import us.codecraft.blackhole.answer.DomainPatternsContainer;
11 | import us.codecraft.blackhole.cache.CacheManager;
12 | import us.codecraft.blackhole.forward.DNSHostsContainer;
13 | import us.codecraft.blackhole.utils.DoubleKeyMap;
14 | import us.codecraft.wifesays.me.ReloadAble;
15 |
16 | import java.io.BufferedReader;
17 | import java.io.FileReader;
18 | import java.util.HashMap;
19 | import java.util.LinkedHashMap;
20 | import java.util.Map;
21 | import java.util.concurrent.ConcurrentHashMap;
22 | import java.util.regex.Pattern;
23 |
24 | /**
25 | * @author yihua.huang@dianping.com
26 | * @date Dec 28, 2012
27 | */
28 | @Component
29 | public class ZonesFileLoader implements InitializingBean, ReloadAble {
30 |
31 | @Autowired
32 | private Configure configure;
33 |
34 | @Autowired
35 | private AnswerPatternProvider answerPatternContainer;
36 |
37 | @Autowired
38 | private CacheManager cacheManager;
39 |
40 | @Autowired
41 | private CustomAnswerPatternProvider customAnswerPatternProvider;
42 |
43 | @Autowired
44 | private DNSHostsContainer dnsHostsContainer;
45 |
46 | private Logger logger = Logger.getLogger(getClass());
47 |
48 | public void readConfig(String filename) {
49 | try {
50 | DomainPatternsContainer domainPatternsContainer = new DomainPatternsContainer();
51 | DomainPatternsContainer nsDomainPatternContainer = new DomainPatternsContainer();
52 | DoubleKeyMap customAnswerPatternsTemp = new DoubleKeyMap(
53 | new ConcurrentHashMap>(), LinkedHashMap.class);
54 | DoubleKeyMap customAnswerTextsTemp = new DoubleKeyMap(
55 | new ConcurrentHashMap>(), HashMap.class);
56 | BufferedReader bufferedReader = new BufferedReader(new FileReader(filename));
57 | String line = null;
58 | while ((line = bufferedReader.readLine()) != null) {
59 | ZonesPattern zonesPattern = ZonesPattern.parse(line);
60 | if (zonesPattern == null) {
61 | continue;
62 | }
63 | try {
64 | if (zonesPattern.getUserIp() == null) {
65 | for (Pattern pattern : zonesPattern.getPatterns()) {
66 | domainPatternsContainer.getDomainPatterns().put(pattern, zonesPattern.getTargetIp());
67 | }
68 | for (String text : zonesPattern.getTexts()) {
69 | domainPatternsContainer.getDomainTexts().put(text, zonesPattern.getTargetIp());
70 | }
71 | } else {
72 | for (Pattern pattern : zonesPattern.getPatterns()) {
73 | customAnswerPatternsTemp.put(zonesPattern.getUserIp(), pattern, zonesPattern.getTargetIp());
74 | }
75 | for (String text : zonesPattern.getTexts()) {
76 | customAnswerTextsTemp.put(zonesPattern.getUserIp(), text, zonesPattern.getTargetIp());
77 | }
78 | }
79 | logger.info("read config success:\t" + line);
80 | } catch (Exception e) {
81 | logger.warn("parse config line error:\t" + line + "\t" , e);
82 | }
83 | // For NS
84 | if (line.startsWith("NS")) {
85 | line = StringUtils.removeStartIgnoreCase(line, "NS").trim();
86 | zonesPattern = ZonesPattern.parse(line);
87 | if (zonesPattern == null) {
88 | continue;
89 | }
90 | try {
91 | for (Pattern pattern : zonesPattern.getPatterns()) {
92 | nsDomainPatternContainer.getDomainPatterns().put(pattern,zonesPattern.getTargetIp());
93 | }
94 | for (String text : zonesPattern.getTexts()) {
95 | nsDomainPatternContainer.getDomainTexts().put(text,zonesPattern.getTargetIp());
96 | }
97 | logger.info("read config success:\t" + line);
98 | } catch (Exception e) {
99 | logger.warn("parse config line error:\t" + line + "\t" + e);
100 | }
101 | }
102 | }
103 | answerPatternContainer.setDomainPatternsContainer(domainPatternsContainer);
104 | customAnswerPatternProvider.setDomainPatterns(customAnswerPatternsTemp);
105 | customAnswerPatternProvider.setDomainTexts(customAnswerTextsTemp);
106 | dnsHostsContainer.setDomainPatternsContainer(nsDomainPatternContainer);
107 | bufferedReader.close();
108 | } catch (Throwable e) {
109 | logger.warn("read config file failed:" + filename, e);
110 | }
111 | }
112 |
113 | /*
114 | * (non-Javadoc)
115 | *
116 | * @see us.codecraft.wifesays.me.ReloadAble#reload()
117 | */
118 | @Override
119 | public void reload() {
120 | readConfig(Configure.getZonesFilename());
121 | }
122 |
123 | /*
124 | * (non-Javadoc)
125 | *
126 | * @see
127 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
128 | */
129 | @Override
130 | public void afterPropertiesSet() throws Exception {
131 | reload();
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/ZonesFileRefresher.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.springframework.beans.factory.InitializingBean;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Component;
7 | import us.codecraft.blackhole.answer.CustomAnswerPatternProvider;
8 | import us.codecraft.blackhole.utils.RecordUtils;
9 | import us.codecraft.wifesays.me.StandReadyWorker;
10 |
11 | import java.io.File;
12 | import java.net.UnknownHostException;
13 | import java.util.concurrent.Executors;
14 | import java.util.concurrent.ScheduledExecutorService;
15 | import java.util.concurrent.TimeUnit;
16 | import java.util.regex.Pattern;
17 |
18 | /**
19 | * @author yihua.huang@dianping.com
20 | * @date Dec 29, 2012
21 | */
22 | @Component
23 | public class ZonesFileRefresher extends StandReadyWorker implements InitializingBean {
24 |
25 | @Autowired
26 | private Configure configure;
27 |
28 | @Autowired
29 | private ZonesFileLoader zonesFileLoader;
30 |
31 | @Autowired
32 | private CustomAnswerPatternProvider customAnswerPatternProvider;
33 |
34 | private ScheduledExecutorService scheduledExecutorService = Executors
35 | .newScheduledThreadPool(1);
36 |
37 | private long lastFileModifiedTime;
38 |
39 | //delete_zones_ip_192.168.0.1
40 | private static final String DELETE_ZONES_IP = "delete_zones_ip_";
41 | //add_zones_ip_192.168.0.1:127.0.0.1 *.dianping.com
42 | private static final String ADD_ZONES_IP = "add_zones_ip_";
43 |
44 | /*
45 | * (non-Javadoc)
46 | *
47 | * @see
48 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
49 | */
50 | public void afterPropertiesSet() throws Exception {
51 | File zonesFile = new File(Configure.getZonesFilename());
52 | lastFileModifiedTime = zonesFile.lastModified();
53 |
54 | scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
55 |
56 | public void run() {
57 | File zonesFile = new File(Configure.getZonesFilename());
58 | // When two files' last modify time not equal, we consider it is
59 | // changed.
60 | synchronized (this) {
61 | if (zonesFile.lastModified() != lastFileModifiedTime) {
62 | lastFileModifiedTime = zonesFile.lastModified();
63 | zonesFileLoader.reload();
64 | }
65 | }
66 | }
67 | }, 500, 500, TimeUnit.MILLISECONDS);
68 | }
69 |
70 | @Override
71 | public String doWhatYouShouldDo(String whatWifeSays) {
72 | if (StringUtils.startsWithIgnoreCase(whatWifeSays, ADD_ZONES_IP)) {
73 | String line = StringUtils.removeStart(whatWifeSays, ADD_ZONES_IP);
74 | try {
75 | ZonesPattern zonesPattern = ZonesPattern.parse(line);
76 | if (zonesPattern == null) {
77 | return "PARSE ERROR";
78 | }
79 | for (Pattern pattern : zonesPattern.getPatterns()) {
80 | customAnswerPatternProvider.getDomainPatterns().put(zonesPattern.getUserIp(), pattern, zonesPattern.getTargetIp());
81 | }
82 | for (String text : zonesPattern.getTexts()) {
83 | customAnswerPatternProvider.getDomainTexts().put(zonesPattern.getUserIp(), text, zonesPattern.getTargetIp());
84 | }
85 | return "SUCCESS, " + zonesPattern.getPatterns().size() + " patterns added.";
86 | } catch (UnknownHostException e) {
87 | return "ERROR " + e;
88 | }
89 | } else if (StringUtils.startsWithIgnoreCase(whatWifeSays, DELETE_ZONES_IP)) {
90 | String ip = StringUtils.removeStart(whatWifeSays, DELETE_ZONES_IP);
91 | if (RecordUtils.isValidIpv4Address(ip)) {
92 | customAnswerPatternProvider.getDomainPatterns().remove(ip);
93 | customAnswerPatternProvider.getDomainTexts().remove(ip);
94 | return "REMOVE SUCCESS";
95 | } else {
96 | return "ERROR, invalid ip " + ip;
97 | }
98 | }
99 |
100 | return null;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/config/ZonesPattern.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.config;
2 |
3 |
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.xbill.DNS.Address;
6 | import us.codecraft.blackhole.answer.AnswerPatternProvider;
7 | import us.codecraft.blackhole.utils.RecordUtils;
8 |
9 | import java.net.UnknownHostException;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.regex.Pattern;
13 |
14 | /**
15 | * User: cairne
16 | * Date: 13-5-11
17 | * Time: 下午9:02
18 | */
19 | public class ZonesPattern {
20 |
21 | private String userIp;
22 |
23 | private String targetIp;
24 |
25 | private List patterns = new ArrayList();
26 |
27 | private List texts = new ArrayList();
28 |
29 | public List getPatterns() {
30 | return patterns;
31 | }
32 |
33 | public void setPatterns(List patterns) {
34 | this.patterns = patterns;
35 | }
36 |
37 | public String getTargetIp() {
38 | return targetIp;
39 | }
40 |
41 | public void setTargetIp(String targetIp) {
42 | this.targetIp = targetIp;
43 | }
44 |
45 | public String getUserIp() {
46 | return userIp;
47 | }
48 |
49 | public void setUserIp(String userIp) {
50 | this.userIp = userIp;
51 | }
52 |
53 | public List getTexts() {
54 | return texts;
55 | }
56 |
57 | public void setTexts(List texts) {
58 | this.texts = texts;
59 | }
60 |
61 | public static ZonesPattern parse(String line) throws UnknownHostException {
62 | ZonesPattern zonesPattern = new ZonesPattern();
63 | line = line.trim();
64 | if (line.startsWith("#")) {
65 | return null;
66 | }
67 | if (line.contains(":")) {
68 | String userIp = StringUtils.trim(StringUtils.substringBefore(line, ":"));
69 | zonesPattern.setUserIp(userIp);
70 | line = StringUtils.trim(StringUtils.substringAfter(line, ":"));
71 | Address.getByAddress(userIp);
72 | }
73 | String[] items = line.split("[\\s_]+");
74 | if (items.length < 2) {
75 | return null;
76 | }
77 | if (items[0].equalsIgnoreCase("NS")) {
78 | boolean configIp = RecordUtils
79 | .areValidIpv4Addresses(items[1]);
80 | zonesPattern.setTargetIp(AnswerPatternProvider.DO_NOTHING);
81 | for (int i = configIp ? 2 : 1; i < items.length; i++) {
82 | String pattern = items[i];
83 | parseDomainPattern(zonesPattern, pattern);
84 | }
85 |
86 | } else {
87 | String ip = items[0];
88 | Address.getByAddress(ip);
89 | zonesPattern.setTargetIp(ip);
90 | for (int i = 1; i < items.length; i++) {
91 | String pattern = items[i];
92 | parseDomainPattern(zonesPattern, pattern);
93 | }
94 | }
95 |
96 | return zonesPattern;
97 | }
98 |
99 | private static void parseDomainPattern(ZonesPattern zonesPattern, String pattern) {
100 | DomainPattern domainPattern = DomainPattern.parse(pattern);
101 | if (domainPattern.isUseRegex()) {
102 | zonesPattern.getPatterns().add(domainPattern.getRegexPattern());
103 | } else {
104 | zonesPattern.getTexts().add(domainPattern.getFullTextMatch());
105 | }
106 | }
107 |
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/connector/UDPConnectionResponser.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.connector;
2 |
3 | import org.apache.log4j.Logger;
4 |
5 | import java.io.IOException;
6 | import java.net.DatagramPacket;
7 | import java.net.DatagramSocket;
8 |
9 | public class UDPConnectionResponser {
10 |
11 | private static final Logger logger = Logger
12 | .getLogger(UDPConnectionResponser.class);
13 |
14 | private final DatagramSocket socket;
15 | private final DatagramPacket inDataPacket;
16 |
17 | public UDPConnectionResponser(DatagramSocket socket,
18 | DatagramPacket inDataPacket) {
19 | super();
20 | this.socket = socket;
21 | this.inDataPacket = inDataPacket;
22 | }
23 |
24 | public DatagramPacket getInDataPacket() {
25 | return inDataPacket;
26 | }
27 |
28 | public void response(byte[] response) {
29 |
30 | try {
31 |
32 | if (response == null) {
33 | return;
34 | }
35 | DatagramPacket outdp = new DatagramPacket(response,
36 | response.length, inDataPacket.getAddress(),
37 | inDataPacket.getPort());
38 |
39 | outdp.setData(response);
40 | outdp.setLength(response.length);
41 | outdp.setAddress(inDataPacket.getAddress());
42 | outdp.setPort(inDataPacket.getPort());
43 |
44 | try {
45 | socket.send(outdp);
46 | } catch (IOException e) {
47 |
48 | logger.debug("Error sending UDP response to "
49 | + inDataPacket.getAddress() + ", " + e);
50 | }
51 |
52 | } catch (Throwable e) {
53 |
54 | logger.warn(
55 | "Error processing UDP connection from "
56 | + inDataPacket.getSocketAddress() + ", ", e);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/connector/UDPConnectionWorker.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.connector;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.xbill.DNS.Message;
5 | import us.codecraft.blackhole.container.QueryProcesser;
6 | import us.codecraft.blackhole.context.RequestContextProcessor;
7 | import us.codecraft.blackhole.forward.Forwarder;
8 |
9 | import java.net.DatagramPacket;
10 |
11 | public class UDPConnectionWorker implements Runnable {
12 |
13 | private static final Logger logger = Logger
14 | .getLogger(UDPConnectionWorker.class);
15 |
16 | private final UDPConnectionResponser responser;
17 | private final DatagramPacket inDataPacket;
18 |
19 | private QueryProcesser queryProcesser;
20 | private Forwarder forwarder;
21 |
22 | public UDPConnectionWorker(DatagramPacket inDataPacket,
23 | QueryProcesser queryProcesser, UDPConnectionResponser responser,
24 | Forwarder forwarder) {
25 | super();
26 | this.responser = responser;
27 | this.inDataPacket = inDataPacket;
28 | this.queryProcesser = queryProcesser;
29 | this.forwarder = forwarder;
30 | }
31 |
32 | public void run() {
33 |
34 | try {
35 |
36 | RequestContextProcessor.processRequest(inDataPacket);
37 | byte[] response = queryProcesser.process(inDataPacket.getData());
38 | if (response != null) {
39 | responser.response(response);
40 | } else {
41 | Message query = new Message(
42 | inDataPacket.getData());
43 | forwarder.forward(query.toWire(), query, responser);
44 | }
45 | } catch (Throwable e) {
46 |
47 | logger.warn(
48 | "Error processing UDP connection from "
49 | + inDataPacket.getSocketAddress() + ", ", e);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/connector/UDPSocketMonitor.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.connector;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import us.codecraft.blackhole.concurrent.ThreadPools;
6 | import us.codecraft.blackhole.container.QueryProcesser;
7 | import us.codecraft.blackhole.forward.Forwarder;
8 |
9 | import java.io.IOException;
10 | import java.net.*;
11 | import java.util.concurrent.ExecutorService;
12 |
13 | /**
14 | * Authored by EagleDNS
15 | * http://www.unlogic.se/projects/eagledns
16 | *
17 | * @author yihua.huang@dianping.com
18 | * @date 2012-12-17
19 | */
20 | public class UDPSocketMonitor extends Thread {
21 |
22 | private Logger log = Logger.getLogger(this.getClass());
23 |
24 | private InetAddress addr;
25 | private int port;
26 | private static final short udpLength = 512;
27 | private DatagramSocket socket;
28 | @Autowired
29 | private QueryProcesser queryProcesser;
30 | @Autowired
31 | private Forwarder forwarder;
32 | @Autowired
33 | private ThreadPools threadPools;
34 |
35 | public UDPSocketMonitor(String host, int port) {
36 | super();
37 | try {
38 | this.addr = Inet4Address.getByName(host);
39 | this.port = port;
40 | socket = new DatagramSocket(port, addr);
41 | } catch (IOException e) {
42 | System.err.println("Startup fail, 53 port is taken or has no privilege. Check if you are running in root, or another DNS server is running.");
43 | log.error("Startup fail, 53 port is taken or has no privilege", e);
44 | System.exit(-1);
45 | }
46 |
47 | this.setDaemon(true);
48 | }
49 |
50 | @Override
51 | public void run() {
52 | ExecutorService executorService = threadPools.getMainProcessExecutor();
53 | log.info("Starting UDP socket monitor on address "
54 | + this.getAddressAndPort());
55 |
56 | while (true) {
57 | try {
58 |
59 | byte[] in = new byte[udpLength];
60 | DatagramPacket indp = new DatagramPacket(in, in.length);
61 | indp.setLength(in.length);
62 | socket.receive(indp);
63 | executorService.execute(new UDPConnectionWorker(indp,
64 | queryProcesser,
65 | new UDPConnectionResponser(socket, indp), forwarder));
66 | } catch (SocketException e) {
67 |
68 | // This is usally thrown on shutdown
69 | log.debug("SocketException thrown from UDP socket on address "
70 | + this.getAddressAndPort() + ", " + e);
71 | break;
72 | } catch (IOException e) {
73 |
74 | log.info("IOException thrown by UDP socket on address "
75 | + this.getAddressAndPort() + ", " + e);
76 | }
77 | }
78 | log.info("UDP socket monitor on address " + getAddressAndPort()
79 | + " shutdown");
80 | }
81 |
82 | public String getAddressAndPort() {
83 |
84 | return addr.getHostAddress() + ":" + port;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/container/Handler.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.container;
2 |
3 |
4 | /**
5 | *
6 | * Handlers process the request and generate the response.
7 | * If there are more than one handler, they are processed as a chain.A handler
8 | * must be registered in {@link HandlerManager#registerHandlers()} before it
9 | * takes effect.
10 | *
11 | * @author yihua.huang@dianping.com
12 | * @date Dec 14, 2012
13 | */
14 | public interface Handler {
15 |
16 | /**
17 | * Process the request and generate the response.
18 | * If there are more than one handler, they are processed as a chain.
19 | * Unlike java servlet-api, the request is completely constructed before
20 | * handling(not a stream),and the response will be sent to client only if
21 | * all handlers process completed.
22 | *
23 | * @param request
24 | * message from client
25 | * @param response
26 | * message to client
27 | * @return true: pass the request to next handler
28 | * false: finish the entire handle process.
29 | */
30 | public boolean handle(MessageWrapper request, MessageWrapper response);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/container/HandlerManager.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.container;
2 |
3 | import org.springframework.beans.factory.InitializingBean;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import us.codecraft.blackhole.answer.PostAnswerHandler;
7 | import us.codecraft.blackhole.answer.PreAnswerHandler;
8 |
9 | import java.util.Collections;
10 | import java.util.LinkedList;
11 | import java.util.List;
12 |
13 | /**
14 | * @author yihua.huang@dianping.com
15 | * @date Dec 14, 2012
16 | */
17 | @Component
18 | public class HandlerManager implements InitializingBean {
19 |
20 | private List preHandlers;
21 |
22 | private List postHandlers;
23 |
24 | @Autowired
25 | private PreAnswerHandler preAnswerHandler;
26 |
27 | @Autowired
28 | private PostAnswerHandler postAnswerHandler;
29 |
30 | @Autowired
31 | private HeaderHandler headerHandler;
32 |
33 | /*
34 | * (non-Javadoc)
35 | *
36 | * @see
37 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
38 | */
39 | @Override
40 | public void afterPropertiesSet() throws Exception {
41 | registerHandlers();
42 | }
43 |
44 | public void registerHandlers() {
45 | preHandlers = new LinkedList();
46 | preHandlers.add(headerHandler);
47 | preHandlers.add(preAnswerHandler);
48 | postHandlers = new LinkedList();
49 | postHandlers.add(postAnswerHandler);
50 | }
51 |
52 | /**
53 | * @return the handlers
54 | */
55 | public List getPreHandlers() {
56 | return Collections.unmodifiableList(preHandlers);
57 | }
58 |
59 | /**
60 | * @return the handlers
61 | */
62 | public List getPostHandlers() {
63 | return Collections.unmodifiableList(postHandlers);
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/container/HeaderHandler.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.container;
2 |
3 | import org.springframework.stereotype.Component;
4 | import org.xbill.DNS.Flags;
5 | import org.xbill.DNS.Record;
6 | import org.xbill.DNS.Section;
7 |
8 | /**
9 | * @author yihua.huang@dianping.com
10 | * @date Dec 14, 2012
11 | */
12 | @Component
13 | public class HeaderHandler implements Handler {
14 |
15 | /*
16 | * (non-Javadoc)
17 | *
18 | * @see us.codecraft.blackhole.server.Handler#handle(org.xbill.DNS.Message,
19 | * org.xbill.DNS.Message)
20 | */
21 | @Override
22 | public boolean handle(MessageWrapper request, MessageWrapper response) {
23 | response.getMessage().getHeader().setFlag(Flags.QR);
24 | if (request.getMessage().getHeader().getFlag(Flags.RD)) {
25 | response.getMessage().getHeader().setFlag(Flags.RD);
26 | }
27 | Record queryRecord = request.getMessage().getQuestion();
28 | response.getMessage().addRecord(queryRecord, Section.QUESTION);
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/container/MessageWrapper.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.container;
2 |
3 | import org.xbill.DNS.Message;
4 |
5 | /**
6 | * Wrap the message because it is hard to modify some part of the message.
7 | *
8 | * @author yihua.huang@dianping.com
9 | * @date Mar 24, 2013
10 | */
11 | public class MessageWrapper {
12 |
13 | private Message message;
14 |
15 | private boolean hasRecord;
16 |
17 | /**
18 | * @param message
19 | */
20 | public MessageWrapper(Message message) {
21 | if (message == null) {
22 | throw new IllegalArgumentException("Message should not be null!");
23 | }
24 | this.hasRecord = false;
25 | this.message = message;
26 | }
27 |
28 | public Message getMessage() {
29 | return message;
30 | }
31 |
32 | public boolean hasRecord() {
33 | return hasRecord;
34 | }
35 |
36 | public void setHasRecord(boolean hasRecord) {
37 | this.hasRecord = hasRecord;
38 | }
39 |
40 | public void setMessage(Message message) {
41 | if (message == null) {
42 | throw new IllegalArgumentException("Message should not be null!");
43 | }
44 | this.message = message;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/container/QueryProcesser.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.container;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import org.xbill.DNS.Message;
7 | import us.codecraft.blackhole.cache.CacheManager;
8 |
9 | import java.io.IOException;
10 |
11 | /**
12 | * Main logic of blackhole.
13 | * Process the DNS query and return the answer.
14 | *
15 | * @author yihua.huang@dianping.com
16 | * @date Dec 14, 2012
17 | */
18 | @Component
19 | public class QueryProcesser {
20 |
21 | @Autowired
22 | private HandlerManager handlerManager;
23 |
24 | private Logger logger = Logger.getLogger(getClass());
25 |
26 | @Autowired
27 | private CacheManager cacheManager;
28 |
29 | public byte[] process(byte[] queryData) throws IOException {
30 | Message query = new Message(queryData);
31 | if (logger.isDebugEnabled()) {
32 | logger.debug("get query "
33 | + query.getQuestion().getName().toString());
34 | }
35 | MessageWrapper responseMessage = new MessageWrapper(new Message(query
36 | .getHeader().getID()));
37 | for (Handler handler : handlerManager.getPreHandlers()) {
38 | boolean handle = handler.handle(new MessageWrapper(query),
39 | responseMessage);
40 | if (!handle) {
41 | break;
42 | }
43 | }
44 | byte[] response = null;
45 | if (responseMessage.hasRecord()) {
46 | response = responseMessage.getMessage().toWire();
47 | return response;
48 | }
49 |
50 | byte[] cache = cacheManager.getResponseFromCache(query);
51 | if (cache != null) {
52 | return cache;
53 | } else {
54 | for (Handler handler : handlerManager.getPostHandlers()) {
55 | boolean handle = handler.handle(new MessageWrapper(query),
56 | responseMessage);
57 | if (!handle) {
58 | break;
59 | }
60 | }
61 | if (responseMessage.hasRecord()) {
62 | response = responseMessage.getMessage().toWire();
63 | return response;
64 | } else {
65 | return null;
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/context/RequestContext.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.context;
2 |
3 | /**
4 | * User: cairne
5 | * Date: 13-5-11
6 | * Time: 下午7:56
7 | */
8 | public class RequestContext {
9 |
10 | private static ThreadLocal clientIps = new ThreadLocal();
11 |
12 | public static String getClientIp() {
13 | return clientIps.get();
14 | }
15 |
16 | public static void setClientIps(String clientIp){
17 | clientIps.set(clientIp);
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/context/RequestContextProcessor.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.context;
2 |
3 | import java.net.DatagramPacket;
4 |
5 | /**
6 | * User: cairne
7 | * Date: 13-5-11
8 | * Time: 下午7:59
9 | */
10 | public class RequestContextProcessor {
11 |
12 | public static void processRequest(DatagramPacket datagramPacket) {
13 | RequestContext.setClientIps(datagramPacket.getAddress().getHostAddress());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/ConnectionTimer.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.apache.log4j.Logger;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Component;
7 | import org.xbill.DNS.Message;
8 | import org.xbill.DNS.Record;
9 | import org.xbill.DNS.Section;
10 | import org.xbill.DNS.Type;
11 | import us.codecraft.blackhole.antipollution.SafeHostManager;
12 | import us.codecraft.blackhole.cache.CacheManager;
13 |
14 | import java.io.IOException;
15 | import java.net.*;
16 | import java.util.concurrent.ExecutorService;
17 | import java.util.concurrent.Executors;
18 |
19 | /**
20 | * Check the connecting time of host.Use to detect whether the DNS answer is
21 | * correct.
22 | *
23 | * @author yihua.huang@dianping.com
24 | * @date Feb 20, 2013
25 | */
26 | @Component
27 | public class ConnectionTimer {
28 |
29 | @Autowired
30 | private SafeHostManager safeBoxService;
31 | /**
32 | * If the server doesn't support ICMP protocol, try http instead.
33 | */
34 | private static final int PORT_FOR_TEST = 80;
35 |
36 | @Autowired
37 | private CacheManager cacheManager;
38 |
39 | /**
40 | * Time out for connection timer.
41 | */
42 | private static final int TIME_OUT = 1000;
43 | /**
44 | * expire time for connect time cache.
45 | */
46 | private static final int EXPIRE_TIME = 3600 * 1000 * 5;
47 |
48 | private ExecutorService checkExecutors = Executors.newFixedThreadPool(4);
49 |
50 | private Logger logger = Logger.getLogger(getClass());
51 |
52 | public void checkConnectTimeForAnswer(final Message query,
53 | final Message message) {
54 | checkExecutors.submit(new Runnable() {
55 |
56 | @Override
57 | public void run() {
58 | checkConnectTimeForAnswer0(query, message);
59 | }
60 | });
61 | }
62 |
63 | private void checkConnectTimeForAnswer0(Message query, Message message) {
64 | byte[] answerBytes = cacheManager.getResponseFromCache(query);
65 | Message answerInCache = null;
66 | if (answerBytes != null) {
67 | try {
68 | answerInCache = new Message(answerBytes);
69 | MessageCheckResult checkConnectionTime = checkConnectionTime(answerInCache);
70 | if (checkConnectionTime == MessageCheckResult.UNCHANGED) {
71 | return;
72 | } else if (checkConnectionTime == MessageCheckResult.CHANGEDE_BUT_USEFUL) {
73 | // remove unreachable address and save
74 | if (logger.isDebugEnabled()) {
75 | logger.debug("update record in cahce " + message);
76 | }
77 | cacheManager.setResponseToCache(query, answerInCache.toWire());
78 | return;
79 | }
80 | } catch (IOException e) {
81 | }
82 | }
83 | MessageCheckResult checkConnectionTime = checkConnectionTime(message);
84 | if (checkConnectionTime != MessageCheckResult.ALL_TIMEOUT) {
85 | if (logger.isDebugEnabled()) {
86 | logger.debug("set new message to cahce " + message);
87 | }
88 | cacheManager.setResponseToCache(query, message.toWire());
89 | }
90 | }
91 |
92 | private enum MessageCheckResult {
93 | ALL_TIMEOUT, CHANGEDE_BUT_USEFUL, UNCHANGED;
94 | }
95 |
96 | private MessageCheckResult checkConnectionTime(Message message) {
97 | boolean changed = false;
98 | Record[] answers = message.getSectionArray(Section.ANSWER);
99 | for (Record answer : answers) {
100 | if (answer.getType() == Type.A || answer.getType() == Type.AAAA
101 | || answer.getType() == Type.CNAME) {
102 | String address = StringUtils.removeEnd(answer.rdataToString(),
103 | ".");
104 | long connectTime = getConnectTime(address);
105 | if (connectTime >= TIME_OUT) {
106 | changed = true;
107 | message.removeRecord(answer, Section.ANSWER);
108 | } else {
109 | String domain = StringUtils.removeEnd(message.getQuestion()
110 | .getName().toString(), ".");
111 | if (safeBoxService.isPoisoned(domain)
112 | && safeBoxService.get(domain) == null
113 | && (answer.getType() == Type.A || answer.getType() == Type.AAAA)) {
114 | safeBoxService.add(domain, address);
115 | }
116 | }
117 | }
118 | }
119 | if (message.getSectionArray(Section.ANSWER).length == 0) {
120 | return MessageCheckResult.ALL_TIMEOUT;
121 | } else if (changed) {
122 |
123 | return MessageCheckResult.CHANGEDE_BUT_USEFUL;
124 | } else {
125 | return MessageCheckResult.UNCHANGED;
126 | }
127 | }
128 |
129 | private long getConnectTime(String address) {
130 | Long connectTime = cacheManager. get(address);
131 | if (connectTime == null) {
132 | connectTime = checkConnectTimeForAddress(address);
133 | cacheManager.set(address, connectTime, EXPIRE_TIME);
134 | }
135 | return connectTime;
136 | }
137 |
138 | public long checkConnectTimeForAddress(String address) {
139 | Socket socket = null;
140 | try {
141 | long startTime = System.currentTimeMillis();
142 | boolean icmpReachable = InetAddress.getByName(address).isReachable(
143 | TIME_OUT);
144 | long timeCost = System.currentTimeMillis() - startTime;
145 | if (icmpReachable) {
146 | return timeCost;
147 | }
148 | socket = new Socket();
149 | startTime = System.currentTimeMillis();
150 | socket.connect(new InetSocketAddress(address, PORT_FOR_TEST),
151 | TIME_OUT);
152 | timeCost = System.currentTimeMillis() - startTime;
153 | socket.close();
154 | return timeCost;
155 | } catch (UnknownHostException e) {
156 | logger.warn("unkown host " + address + " " + e);
157 | } catch (SocketTimeoutException e) {
158 | } catch (SocketException e) {
159 | } catch (IOException e) {
160 | logger.warn("connect " + address + " error " + e);
161 | } finally {
162 | if (socket != null) {
163 | try {
164 | socket.close();
165 | } catch (IOException e) {
166 | }
167 | }
168 | }
169 | return TIME_OUT;
170 | }
171 | }
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/DNSHostsContainer.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.stereotype.Component;
5 | import us.codecraft.blackhole.answer.DomainPatternsContainer;
6 |
7 | import java.net.InetSocketAddress;
8 | import java.net.SocketAddress;
9 | import java.util.*;
10 | import java.util.Map.Entry;
11 | import java.util.concurrent.ConcurrentHashMap;
12 |
13 | /**
14 | * @author yihua.huang@dianping.com
15 | * @date Dec 21, 2012
16 | */
17 | @Component
18 | public class DNSHostsContainer {
19 |
20 | private DomainPatternsContainer domainPatternsContainer;
21 |
22 | private int timeout = 3000;
23 |
24 | private int order;
25 |
26 | private Map requestTimes = new ConcurrentHashMap();
27 |
28 | private Logger logger = Logger.getLogger(getClass());
29 |
30 | public void clearHosts() {
31 | requestTimes = new ConcurrentHashMap();
32 | order = 0;
33 | }
34 |
35 | public void addHost(SocketAddress address) {
36 | requestTimes.put(address, order++);
37 | logger.info("add dns address " + address);
38 | }
39 |
40 | public int getOrder(SocketAddress socketAddress) {
41 | Integer order = requestTimes.get(socketAddress);
42 | return order == null ? 0 : order;
43 | }
44 |
45 | /**
46 | * @param timeout
47 | * the timeout to set
48 | */
49 | public void setTimeout(int timeout) {
50 | this.timeout = timeout;
51 | }
52 |
53 | public List getAllAvaliableHosts(String domain) {
54 | String ip = domainPatternsContainer.getIp(domain);
55 | if (ip != null) {
56 | List socketAddresses = new ArrayList();
57 | socketAddresses.add(new InetSocketAddress(ip, 53));
58 | return socketAddresses;
59 | }
60 | List results = new ArrayList();
61 | Iterator> iterator = requestTimes.entrySet().iterator();
62 | while (iterator.hasNext()) {
63 | Entry next = iterator.next();
64 | results.add(next.getKey());
65 | }
66 | return results;
67 | }
68 |
69 | public void setDomainPatternsContainer(DomainPatternsContainer domainPatternsContainer) {
70 | this.domainPatternsContainer = domainPatternsContainer;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/ForwardAnswer.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.xbill.DNS.Message;
4 | import us.codecraft.blackhole.connector.UDPConnectionResponser;
5 |
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | /**
9 | * @author yihua.huang@dianping.com
10 | * @date Apr 10, 2013
11 | */
12 | public class ForwardAnswer {
13 |
14 | private final Message query;
15 |
16 | private final UDPConnectionResponser responser;
17 |
18 | private final long startTime;
19 |
20 | private AtomicInteger minOrder = new AtomicInteger(Integer.MAX_VALUE);
21 |
22 | /**
23 | * Answer that not return immediately. It will be returned when countDown<=0 or ForwardAnswer is removed.
24 | */
25 | private Message tempAnswer;
26 |
27 | /**
28 | * Number of external DNS response which not recevied yet.
29 | */
30 | private final AtomicInteger countDown;
31 |
32 | /**
33 | * @param query
34 | * @param responser
35 | */
36 | public ForwardAnswer(Message query, UDPConnectionResponser responser, int initCount) {
37 | super();
38 | this.query = query;
39 | this.responser = responser;
40 | this.startTime = System.currentTimeMillis();
41 | this.countDown = new AtomicInteger(initCount);
42 | }
43 |
44 | public UDPConnectionResponser getResponser() {
45 | return responser;
46 | }
47 |
48 | public long getStartTime() {
49 | return startTime;
50 | }
51 |
52 | public Message getQuery() {
53 | return query;
54 | }
55 |
56 | /**
57 | * When the DNS server' order is less than minOrder, it will be processed.
58 | *
59 | * @param order
60 | * @return
61 | */
62 | public boolean shouldProcess(int order) {
63 | return minOrder.get() > order;
64 | }
65 |
66 | /**
67 | * Set the order as minOrder to prevent DNS server with larger order to be processed.
68 | *
69 | * @param order
70 | * @return
71 | */
72 | public boolean confirmProcess(int order) {
73 | int minOrderi = minOrder.get();
74 | while (order < minOrderi) {
75 | boolean b = minOrder.compareAndSet(minOrderi, order);
76 | if (b) {
77 | return true;
78 | } else {
79 | minOrderi = minOrder.get();
80 | }
81 | }
82 | return false;
83 | }
84 |
85 | public Message getTempAnswer() {
86 | return tempAnswer;
87 | }
88 |
89 | public void setTempAnswer(Message tempAnswer) {
90 | this.tempAnswer = tempAnswer;
91 | }
92 |
93 | public int decrCountDown() {
94 | return countDown.decrementAndGet();
95 | }
96 |
97 | public int getCountDown() {
98 | return countDown.get();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/ForwardAnswerProcessor.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.apache.log4j.Logger;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Service;
7 | import org.xbill.DNS.Message;
8 | import org.xbill.DNS.Record;
9 | import org.xbill.DNS.Section;
10 | import org.xbill.DNS.Type;
11 | import us.codecraft.blackhole.antipollution.BlackListManager;
12 | import us.codecraft.blackhole.antipollution.SafeHostManager;
13 | import us.codecraft.blackhole.cache.CacheManager;
14 | import us.codecraft.blackhole.config.Configure;
15 | import us.codecraft.blackhole.utils.RecordUtils;
16 |
17 | import java.io.IOException;
18 | import java.net.SocketAddress;
19 |
20 | /**
21 | * User: cairne
22 | * Date: 13-5-19
23 | * Time: 下午7:28
24 | */
25 | @Service
26 | public class ForwardAnswerProcessor {
27 |
28 | @Autowired
29 | private CacheManager cacheManager;
30 | @Autowired
31 | private BlackListManager blackListManager;
32 | @Autowired
33 | private DNSHostsContainer dnsHostsContainer;
34 |
35 | @Autowired
36 | private ConnectionTimer connectionTimer;
37 |
38 | @Autowired
39 | private SafeHostManager safeBoxService;
40 |
41 | private Logger logger = Logger.getLogger(getClass());
42 |
43 | @Autowired
44 | private Configure configure;
45 |
46 | public void handleAnswer(byte[] answer, Message message, SocketAddress remoteAddress, ForwardAnswer forwardAnswer)
47 | throws IOException {
48 | // fake dns server return an answer, it must be dns pollution
49 | if (configure.getFakeDnsServer() != null
50 | && remoteAddress.equals(configure.getFakeDnsServer())) {
51 | addToBlacklist(message);
52 | String domain = StringUtils.removeEnd(message.getQuestion()
53 | .getName().toString(), ".");
54 | safeBoxService.setPoisoned(domain);
55 | return;
56 | }
57 | if (logger.isTraceEnabled()) {
58 | logger.trace("get message from " + remoteAddress + "\n" + message);
59 | }
60 | if (forwardAnswer == null) {
61 | logger.info("Received messages for "
62 | + message.getQuestion().getName().toString()
63 | + " after timeout!");
64 | return;
65 | }
66 | if (configure.isEnableSafeBox()) {
67 | answer = removeFakeAddress(message, answer);
68 | }
69 |
70 | if (answer != null) {
71 | forwardAnswer.decrCountDown();
72 | int order = dnsHostsContainer.getOrder(remoteAddress);
73 | if (!RecordUtils.hasAnswer(message)) {
74 | forwardAnswer.setTempAnswer(message);
75 | if (forwardAnswer.getCountDown() <= 0) {
76 | forwardAnswer.getResponser().response(answer);
77 | forwardAnswer.setTempAnswer(null);
78 | }
79 | } else {
80 | if (forwardAnswer.confirmProcess(order)) {
81 | forwardAnswer.getResponser().response(answer);
82 | if (logger.isDebugEnabled()) {
83 | logger.debug("response message " + message.getHeader().getID()
84 | + " from " + remoteAddress + " to "
85 | + forwardAnswer.getResponser().getInDataPacket().getPort());
86 | }
87 | cacheManager.setResponseToCache(message, answer);
88 | }
89 | }
90 | }
91 | }
92 |
93 | private void addToBlacklist(Message message) {
94 | for (Record answer : message.getSectionArray(Section.ANSWER)) {
95 | String address = StringUtils.removeEnd(answer.rdataToString(), ".");
96 | if (!blackListManager.inBlacklist(address)) {
97 | logger.info("detected dns poisoning, add address " + address
98 | + " to blacklist");
99 | blackListManager.addToBlacklist(address);
100 | }
101 | }
102 | }
103 |
104 | private byte[] removeFakeAddress(Message message, byte[] bytes) {
105 | Record[] answers = message.getSectionArray(Section.ANSWER);
106 | boolean changed = false;
107 | for (Record answer : answers) {
108 | String address = StringUtils.removeEnd(answer.rdataToString(), ".");
109 | if ((answer.getType() == Type.A || answer.getType() == Type.AAAA)
110 | && blackListManager.inBlacklist(address)) {
111 | if (!changed) {
112 | // copy on write
113 | message = (Message) message.clone();
114 | }
115 | message.removeRecord(answer, Section.ANSWER);
116 | changed = true;
117 | }
118 | }
119 | if (changed && message.getQuestion().getType() == Type.A
120 | && (message.getSectionArray(Section.ANSWER) == null || message
121 | .getSectionArray(Section.ANSWER).length == 0)
122 | && (message.getSectionArray(Section.ADDITIONAL) == null || message
123 | .getSectionArray(Section.ADDITIONAL).length == 0)
124 | && (message.getSectionArray(Section.AUTHORITY) == null || message
125 | .getSectionArray(Section.AUTHORITY).length == 0)) {
126 | logger.info("remove message " + message.getQuestion());
127 | return null;
128 | }
129 | if (changed) {
130 | return message.toWire();
131 | }
132 | return bytes;
133 | }
134 |
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/Forwarder.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.xbill.DNS.Message;
4 | import us.codecraft.blackhole.connector.UDPConnectionResponser;
5 |
6 | import java.net.SocketAddress;
7 | import java.util.List;
8 |
9 | /**
10 | * @author yihua.huang@dianping.com
11 | * @date Jan 16, 2013
12 | */
13 | public interface Forwarder {
14 |
15 | /**
16 | * Forward query bytes to external DNS host(s) and get a valid DNS answer.
17 | *
18 | * @param queryBytes
19 | * @param query
20 | * @return
21 | */
22 | public void forward(final byte[] queryBytes, Message query,
23 | UDPConnectionResponser responser);
24 |
25 | /**
26 | * Forward query bytes to external DNS host(s) and get a valid DNS answer.
27 | *
28 | * @param queryBytes
29 | * @param query
30 | * @return
31 | */
32 | public void forward(final byte[] queryBytes, Message query,
33 | List hosts, UDPConnectionResponser responser);
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/MultiUDPForwarder.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import org.xbill.DNS.Message;
7 | import us.codecraft.blackhole.config.Configure;
8 | import us.codecraft.blackhole.connector.UDPConnectionResponser;
9 |
10 | import java.io.IOException;
11 | import java.net.SocketAddress;
12 | import java.nio.ByteBuffer;
13 | import java.nio.channels.DatagramChannel;
14 | import java.util.List;
15 |
16 | /**
17 | * Forward DNS query to hosts contained in {@link DNSHostsContainer}.Use the
18 | * same port 40311 for all UDP diagram and the instance of
19 | * {@link MultiUDPReceiver} will listen on the port 40311.Use wait/notify to
20 | * synchronize.
21 | *
22 | * @author yihua.huang@dianping.com
23 | * @date Jan 16, 2013
24 | */
25 | @Component
26 | public class MultiUDPForwarder implements Forwarder {
27 |
28 | private Logger logger = Logger.getLogger(getClass());
29 |
30 | @Autowired
31 | private MultiUDPReceiver multiUDPReceiver;
32 |
33 | @Autowired
34 | private DNSHostsContainer dnsHostsContainer;
35 |
36 | @Autowired
37 | private Configure configure;
38 |
39 | /*
40 | * (non-Javadoc)
41 | *
42 | * @see us.codecraft.blackhole.forward.Forwarder#forward(byte[],
43 | * org.xbill.DNS.Message)
44 | */
45 | @Override
46 | public void forward(byte[] queryBytes, Message query,
47 | UDPConnectionResponser responser) {
48 | // get address
49 | List allAvaliableHosts = dnsHostsContainer
50 | .getAllAvaliableHosts(query.getQuestion().getName().toString());
51 | forward(queryBytes, query, allAvaliableHosts, responser);
52 | }
53 |
54 | /*
55 | * (non-Javadoc)
56 | *
57 | * @see us.codecraft.blackhole.forward.Forwarder#forward(byte[],
58 | * org.xbill.DNS.Message, java.util.List)
59 | */
60 | @Override
61 | public void forward(byte[] queryBytes, Message query,
62 | List hosts, UDPConnectionResponser responser) {
63 | if (logger.isDebugEnabled()) {
64 | logger.debug("forward query " + query.getQuestion().getName() + "_"
65 | + query.getHeader().getID());
66 | }
67 | // send to all address
68 |
69 | int initCount = hosts.size();
70 | if (configure.getFakeDnsServer() != null) {
71 | // send fake dns query to detect dns poisoning
72 | hosts.add(0, configure.getFakeDnsServer());
73 | }
74 | ForwardAnswer forwardAnswer = new ForwardAnswer(query, responser, initCount);
75 | try {
76 | multiUDPReceiver.registerReceiver(query, forwardAnswer);
77 | try {
78 | for (SocketAddress host : hosts) {
79 | send(queryBytes, host);
80 | logger.debug("forward query "
81 | + query.getQuestion().getName() + "_"
82 | + query.getHeader().getID());
83 | }
84 | } catch (IOException e) {
85 | logger.warn("error", e);
86 | }
87 | } finally {
88 | multiUDPReceiver.delayRemoveAnswer(query, configure.getDnsTimeOut());
89 | }
90 | }
91 |
92 | private void send(byte[] queryBytes, SocketAddress host) throws IOException {
93 | DatagramChannel datagramChannel = multiUDPReceiver.getDatagramChannel();
94 | datagramChannel.send(ByteBuffer.wrap(queryBytes), host);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/forward/MultiUDPReceiver.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.forward;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.springframework.beans.factory.InitializingBean;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Component;
7 | import org.xbill.DNS.Message;
8 | import us.codecraft.blackhole.concurrent.ThreadPools;
9 |
10 | import java.io.IOException;
11 | import java.net.InetSocketAddress;
12 | import java.net.SocketAddress;
13 | import java.nio.ByteBuffer;
14 | import java.nio.channels.DatagramChannel;
15 | import java.util.Arrays;
16 | import java.util.Map;
17 | import java.util.concurrent.*;
18 |
19 | /**
20 | * Listen on port 40311 using reactor mode.
21 | *
22 | * @author yihua.huang@dianping.com
23 | * @date Jan 16, 2013
24 | */
25 | @Component
26 | public class MultiUDPReceiver implements InitializingBean {
27 |
28 | /**
29 | *
30 | */
31 | private static final int dnsPackageLength = 512;
32 |
33 | private Map answers = new ConcurrentHashMap();
34 |
35 | private DatagramChannel datagramChannel;
36 |
37 | private final static int PORT_RECEIVE = 40311;
38 |
39 | private DelayQueue delayRemoveQueue = new DelayQueue();
40 |
41 | private static class DelayStringKey implements Delayed {
42 |
43 | private final String key;
44 |
45 | private final long initDelay;
46 |
47 | private long startTime;
48 |
49 | /**
50 | * @param key
51 | * @param initDelay in ms
52 | */
53 | public DelayStringKey(String key, long initDelay) {
54 | this.startTime = System.currentTimeMillis();
55 | this.key = key;
56 | this.initDelay = initDelay;
57 | }
58 |
59 | public String getKey() {
60 | return key;
61 | }
62 |
63 | /*
64 | * (non-Javadoc)
65 | *
66 | * @see java.lang.Comparable#compareTo(java.lang.Object)
67 | */
68 | @Override
69 | public int compareTo(Delayed o) {
70 | long delayA = startTime + initDelay - System.currentTimeMillis();
71 | long delayB = o.getDelay(TimeUnit.MILLISECONDS);
72 | if (delayA > delayB) {
73 | return 1;
74 | } else if (delayA < delayB) {
75 | return -1;
76 | } else {
77 | return 0;
78 | }
79 | }
80 |
81 | /*
82 | * (non-Javadoc)
83 | *
84 | * @see
85 | * java.util.concurrent.Delayed#getDelay(java.util.concurrent.TimeUnit)
86 | */
87 | @Override
88 | public long getDelay(TimeUnit unit) {
89 | return unit.convert(
90 | startTime + initDelay - System.currentTimeMillis(),
91 | TimeUnit.MILLISECONDS);
92 | }
93 |
94 | }
95 |
96 | @Autowired
97 | private ThreadPools threadPools;
98 |
99 | @Autowired
100 | private ForwardAnswerProcessor forwardAnswerProcessor;
101 |
102 | private Logger logger = Logger.getLogger(getClass());
103 |
104 | /**
105 | *
106 | */
107 | public MultiUDPReceiver() {
108 | super();
109 | }
110 |
111 |
112 | private String getKey(Message message) {
113 | return message.getHeader().getID() + "_"
114 | + message.getQuestion().getName().toString() + "_"
115 | + message.getQuestion().getType();
116 | }
117 |
118 | public void registerReceiver(Message message, ForwardAnswer forwardAnswer) {
119 | answers.put(getKey(message), forwardAnswer);
120 | }
121 |
122 | public ForwardAnswer getAnswer(Message message) {
123 | return answers.get(getKey(message));
124 | }
125 |
126 | /**
127 | * Add answer to remove queue and remove when timeout.
128 | * @param message
129 | * @param timeOut
130 | */
131 | public void delayRemoveAnswer(Message message, long timeOut) {
132 | delayRemoveQueue.add(new DelayStringKey(getKey(message), timeOut));
133 | }
134 |
135 | private void receive() {
136 | ExecutorService processExecutors = threadPools.getUdpReceiverExecutor();
137 | final ByteBuffer byteBuffer = ByteBuffer.allocate(dnsPackageLength);
138 | while (true) {
139 | try {
140 | byteBuffer.clear();
141 | final SocketAddress remoteAddress = datagramChannel
142 | .receive(byteBuffer);
143 | final byte[] answer = Arrays.copyOfRange(byteBuffer.array(), 0,
144 | dnsPackageLength);
145 | processExecutors.submit(new Runnable() {
146 |
147 | @Override
148 | public void run() {
149 | try {
150 | final Message message = new Message(answer);
151 | forwardAnswerProcessor.handleAnswer(answer, message, remoteAddress, getAnswer(message));
152 | } catch (Throwable e) {
153 | logger.warn("forward exception " + e);
154 | }
155 | }
156 | });
157 |
158 | } catch (Throwable e) {
159 | logger.warn("receive exception" + e);
160 | }
161 | }
162 | }
163 |
164 | /*
165 | * (non-Javadoc)
166 | *
167 | * @see
168 | * org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
169 | */
170 | @Override
171 | public void afterPropertiesSet() throws Exception {
172 | datagramChannel = DatagramChannel.open();
173 | try {
174 | datagramChannel.socket().bind(new InetSocketAddress(PORT_RECEIVE));
175 | } catch (IOException e) {
176 | System.err.println("Startup fail, maybe another process is running.");
177 | logger.error("Startup fail, maybe another process is running.", e);
178 | System.exit(-1);
179 | }
180 | datagramChannel.configureBlocking(true);
181 | Thread threadForReceive = new Thread(new Runnable() {
182 |
183 | @Override
184 | public void run() {
185 | receive();
186 |
187 | }
188 | });
189 | threadForReceive.setDaemon(true);
190 | threadForReceive.start();
191 | Thread threadForRemove = new Thread(new Runnable() {
192 |
193 | @Override
194 | public void run() {
195 | removeAnswerFromQueue();
196 |
197 | }
198 | });
199 | threadForRemove.setDaemon(true);
200 | threadForRemove.start();
201 | }
202 |
203 | private void removeAnswerFromQueue() {
204 | while (true) {
205 | try {
206 | DelayStringKey delayRemoveKey = delayRemoveQueue.take();
207 | ForwardAnswer forwardAnswer = answers.get(delayRemoveKey.getKey());
208 | if (forwardAnswer != null && forwardAnswer.getTempAnswer() != null) {
209 | forwardAnswer.getResponser().response(forwardAnswer.getTempAnswer().toWire());
210 | }
211 | answers.remove(delayRemoveKey.getKey());
212 | if (logger.isDebugEnabled()) {
213 | logger.debug("remove key " + delayRemoveKey.getKey());
214 | }
215 | } catch (Exception e) {
216 | logger.warn("remove answer error", e);
217 | }
218 | }
219 | }
220 |
221 | /**
222 | * @return the datagramChannel
223 | */
224 | public DatagramChannel getDatagramChannel() {
225 | return datagramChannel;
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/utils/DoubleKeyMap.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.utils;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * @author yihua.huang@dianping.com
7 | * @date Dec 14, 2012
8 | */
9 | public class DoubleKeyMap extends MultiKeyMapBase {
10 | private Map> map;
11 |
12 | public DoubleKeyMap() {
13 | init();
14 | }
15 |
16 | public DoubleKeyMap(Map> map) {
17 | this(map,DEFAULT_CLAZZ);
18 | }
19 |
20 | public DoubleKeyMap(Class extends Map> protoMapClass) {
21 | super(protoMapClass);
22 | init();
23 | }
24 |
25 | private void init() {
26 | if (map == null) {
27 | map = this.>newMap();
28 | }
29 | }
30 |
31 | /**
32 | * init map with protoMapClass
33 | *
34 | * @param protoMapClass
35 | */
36 | @SuppressWarnings("rawtypes")
37 | public DoubleKeyMap(Map> map, Class extends Map> protoMapClass) {
38 | super(protoMapClass);
39 | this.map = map;
40 | init();
41 | }
42 |
43 | /**
44 | * @param key
45 | * @return
46 | */
47 | public Map get(K1 key) {
48 | return map.get(key);
49 | }
50 |
51 | /**
52 | * @param key1
53 | * @param key2
54 | * @return
55 | */
56 | public V get(K1 key1, K2 key2) {
57 | if (get(key1) == null) {
58 | return null;
59 | }
60 | return get(key1).get(key2);
61 | }
62 |
63 |
64 | /**
65 | * @param key1
66 | * @param submap
67 | * @return
68 | */
69 | public V put(K1 key1, Map submap) {
70 | return put(key1, submap);
71 | }
72 |
73 | /**
74 | * @param key1
75 | * @param key2
76 | * @param value
77 | * @return
78 | */
79 | public V put(K1 key1, K2 key2, V value) {
80 | if (map.get(key1) == null) {
81 | map.put(key1, this.newMap());
82 | }
83 | return get(key1).put(key2, value);
84 | }
85 |
86 | /**
87 | * @param key1
88 | * @param key2
89 | * @return
90 | */
91 | public V remove(K1 key1, K2 key2) {
92 | if (get(key1) == null) {
93 | return null;
94 | }
95 | V remove = get(key1).remove(key2);
96 | // 如果上一级map为空,把它也回收掉
97 | if (get(key1).size() == 0) {
98 | remove(key1);
99 | }
100 | return remove;
101 | }
102 |
103 | /**
104 | * @param key1
105 | * @return
106 | */
107 | public Map remove(K1 key1) {
108 | Map remove = map.remove(key1);
109 | return remove;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/utils/MultiKeyMapBase.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.utils;
2 |
3 | /**
4 | * @author yihua.huang@dianping.com
5 | * @date Dec 14, 2012
6 | */
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * multikey map, some basic objects *
13 | *
14 | * @author yihua.huang
15 | */
16 | public abstract class MultiKeyMapBase {
17 |
18 | protected static final Class extends Map> DEFAULT_CLAZZ = HashMap.class;
19 | @SuppressWarnings("rawtypes")
20 | private Class extends Map> protoMapClass = DEFAULT_CLAZZ;
21 |
22 | public MultiKeyMapBase() {
23 | }
24 |
25 | @SuppressWarnings("rawtypes")
26 | public MultiKeyMapBase(Class extends Map> protoMapClass) {
27 | this.protoMapClass = protoMapClass;
28 | }
29 |
30 | @SuppressWarnings("unchecked")
31 | protected Map newMap() {
32 | try {
33 | return (Map) protoMapClass.newInstance();
34 | } catch (InstantiationException e) {
35 | throw new IllegalArgumentException("wrong proto type map "
36 | + protoMapClass);
37 | } catch (IllegalAccessException e) {
38 | throw new IllegalArgumentException("wrong proto type map "
39 | + protoMapClass);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/utils/RecordBuilder.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.utils;
2 |
3 | import org.xbill.DNS.*;
4 | import us.codecraft.blackhole.config.Configure;
5 |
6 | import java.net.UnknownHostException;
7 |
8 | /**
9 | * @author yihua.huang@dianping.com
10 | * @date Dec 14, 2012
11 | */
12 | public class RecordBuilder {
13 |
14 | private Configure configure = SpringLocator.getBean(Configure.class);
15 |
16 | private int type;
17 |
18 | private int dclass;
19 |
20 | private String answer;
21 |
22 | private Name name;
23 |
24 | private int priority = configure.getMxPriory();
25 |
26 | private long ttl = configure.getTTL();
27 |
28 | public Record toRecord() throws UnknownHostException, TextParseException {
29 | switch (type) {
30 | case Type.A:
31 | return new ARecord(name, dclass, ttl, Address.getByAddress(answer));
32 | case Type.MX:
33 | return new MXRecord(name, dclass, ttl, priority, new Name(answer));
34 | case Type.PTR:
35 | return new PTRRecord(name, dclass, ttl, new Name(answer));
36 | case Type.CNAME:
37 | return new CNAMERecord(name, dclass, ttl, new Name(answer));
38 | default:
39 | throw new IllegalStateException("type " + Type.string(type)
40 | + " is not supported ");
41 | }
42 | }
43 |
44 | /**
45 | * @param type
46 | * the type to set
47 | */
48 | public RecordBuilder type(int type) {
49 | this.type = type;
50 | return this;
51 | }
52 |
53 | /**
54 | * @param dclass
55 | * the dclass to set
56 | */
57 | public RecordBuilder dclass(int dclass) {
58 | this.dclass = dclass;
59 | return this;
60 | }
61 |
62 | /**
63 | * @param name
64 | * the name to set
65 | */
66 | public RecordBuilder name(Name name) {
67 | this.name = name;
68 | return this;
69 | }
70 |
71 | /**
72 | * @param priority
73 | * the priority to set
74 | */
75 | public RecordBuilder priority(int priority) {
76 | this.priority = priority;
77 | return this;
78 | }
79 |
80 | /**
81 | * @param ttl
82 | * the ttl to set
83 | */
84 | public RecordBuilder ttl(long ttl) {
85 | this.ttl = ttl;
86 | return this;
87 | }
88 |
89 | /**
90 | * @param answer
91 | * the answer to set
92 | */
93 | public RecordBuilder answer(String answer) {
94 | this.answer = answer;
95 | return this;
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/utils/RecordUtils.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.utils;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.xbill.DNS.Message;
5 | import org.xbill.DNS.Record;
6 | import org.xbill.DNS.Section;
7 | import org.xbill.DNS.Type;
8 | import us.codecraft.blackhole.config.Configure;
9 |
10 | /**
11 | * @author yihua.huang@dianping.com
12 | * @date Dec 19, 2012
13 | */
14 | public class RecordUtils {
15 |
16 | public static long maxTTL(Record[] records) {
17 | if (records == null || records.length == 0) {
18 | return Configure.DEFAULT_TTL;
19 | }
20 | long max = 0;
21 | for (Record record : records) {
22 | if (record.getTTL() > max) {
23 | max = record.getTTL();
24 | }
25 | }
26 | return max;
27 | }
28 |
29 | public static boolean hasAnswer(Message message) {
30 | Record[] sectionArray = message.getSectionArray(Section.ANSWER);
31 | if (sectionArray == null || sectionArray.length == 0) {
32 | return false;
33 | } else {
34 | return true;
35 | }
36 |
37 | }
38 |
39 | public static long minTTL(Record[] records) {
40 | if (records == null || records.length == 0) {
41 | return Configure.DEFAULT_TTL;
42 | }
43 | long min = Integer.MAX_VALUE;
44 | for (Record record : records) {
45 | if (record.getTTL() < min) {
46 | min = record.getTTL();
47 | }
48 | }
49 | return min;
50 | }
51 |
52 | public static String recordKey(Record record) {
53 | return record.getName().toString() + " "
54 | + Type.string(record.getType());
55 | }
56 |
57 | public static boolean isValidIpv4Address(String address) {
58 | if (StringUtils.isBlank(address)) {
59 | return false;
60 | }
61 | String[] items = address.split("\\.");
62 | if (items.length != 4) {
63 | return false;
64 | }
65 | for (String item : items) {
66 | try {
67 | int parseInt = Integer.parseInt(item);
68 | if (parseInt < 0 || parseInt > 255) {
69 | return false;
70 | }
71 | } catch (NumberFormatException e) {
72 | return false;
73 | }
74 | }
75 | return true;
76 | }
77 |
78 | public static boolean areValidIpv4Addresses(String addresses) {
79 | if (StringUtils.isBlank(addresses)) {
80 | return false;
81 | }
82 | String[] items = addresses.split("\\,");
83 | for (String item : items) {
84 | if (!isValidIpv4Address(item)) {
85 | return false;
86 | }
87 | }
88 | return true;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/server/src/main/java/us/codecraft/blackhole/utils/SpringLocator.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.utils;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.beans.factory.NoSuchBeanDefinitionException;
5 | import org.springframework.context.ApplicationContext;
6 |
7 | /**
8 | * @author yihua.huang@dianping.com
9 | * @date Dec 15, 2012
10 | */
11 | public class SpringLocator {
12 |
13 | public static ApplicationContext applicationContext;
14 |
15 | public static Object getBean(String name) throws BeansException {
16 | return applicationContext.getBean(name);
17 | }
18 |
19 | public static T getBean(String name, Class requiredType)
20 | throws BeansException {
21 | return applicationContext.getBean(name, requiredType);
22 | }
23 |
24 | public static T getBean(Class requiredType) throws BeansException {
25 | return applicationContext.getBean(requiredType);
26 | }
27 |
28 | public static Object getBean(String name, Object... args)
29 | throws BeansException {
30 | return applicationContext.getBean(name, args);
31 | }
32 |
33 | public static boolean containsBean(String name) {
34 | return applicationContext.containsBean(name);
35 | }
36 |
37 | public static boolean isSingleton(String name)
38 | throws NoSuchBeanDefinitionException {
39 | return applicationContext.isSingleton(name);
40 | }
41 |
42 | public static boolean isPrototype(String name)
43 | throws NoSuchBeanDefinitionException {
44 | return applicationContext.isPrototype(name);
45 | }
46 |
47 | public static boolean isTypeMatch(String name, Class> targetType)
48 | throws NoSuchBeanDefinitionException {
49 | return applicationContext.isTypeMatch(name, targetType);
50 | }
51 |
52 | public static Class> getType(String name)
53 | throws NoSuchBeanDefinitionException {
54 | return applicationContext.getType(name);
55 | }
56 |
57 | public static String[] getAliases(String name) {
58 | return applicationContext.getAliases(name);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/server/src/main/resources/ehcache.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
20 |
27 |
--------------------------------------------------------------------------------
/server/src/main/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/server/src/main/resources/spring/applicationContext-blackhole.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/server/src/test/java/us/codecraft/blackhole/multiforward/ForwardTimerTest.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.multiforward;
2 |
3 | import org.junit.Test;
4 |
5 | import us.codecraft.blackhole.forward.ConnectionTimer;
6 |
7 | /**
8 | * @author yihua.huang@dianping.com
9 | * @date Feb 20, 2013
10 | */
11 | public class ForwardTimerTest {
12 |
13 | private ConnectionTimer forwardTimer = new ConnectionTimer();
14 |
15 | @Test
16 | public void testCheckForwardTimeForAddress() {
17 | System.out.println(forwardTimer
18 | .checkConnectTimeForAddress("173.194.72.106"));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/server/src/test/java/us/codecraft/blackhole/multiforward/MultiReceiverTest.java:
--------------------------------------------------------------------------------
1 | package us.codecraft.blackhole.multiforward;
2 |
3 | import java.io.IOException;
4 | import java.net.InetAddress;
5 |
6 | import org.junit.Before;
7 | import org.junit.Test;
8 |
9 | import us.codecraft.blackhole.forward.MultiUDPReceiver;
10 |
11 | /**
12 | * @author yihua.huang@dianping.com
13 | * @date Feb 16, 2013
14 | */
15 | public class MultiReceiverTest {
16 |
17 | private MultiUDPReceiver multireReceiver;
18 |
19 | @Before
20 | public void setUp() {
21 | multireReceiver = new MultiUDPReceiver();
22 | }
23 |
24 | @Test
25 | public void testReach() throws IOException {
26 | InetAddress inetAddress = InetAddress.getByName("203.161.230.171");
27 | System.out.println(inetAddress.isReachable(1000));
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------