Go源码解析-dispatcher

简介

dispatcher是caddy之下的一个模块,其主要作用是对http请求进行屏蔽,代理,转发和加工等操作。

所在路径

https://github.com/inverse-inc/packetfence/tree/devel/go/httpdispatcher

基本参数和函数解析

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//Proxy 代理,是一个复杂结构体
//Proxy中包含白名单和黑名单的正则表达式数组
//白名单和黑名单的用途:
//对Proxy的修改是互斥的

type Proxy struct {
endpointWhiteList []*regexp.Regexp
endpointBlackList []*regexp.Regexp
mutex sync.Mutex
}


//passthrough是一个十分复杂的结构体
//它用于制定一系列的监测规则
//被检查到符合规则的请求会被403 或 代理转发
//对它的访问是互斥的
type passthrough struct {
proxypassthrough []*regexp.Regexp //代理转发规则,匹配主机
detectionmechanisms []*regexp.Regexp //侦测机制,匹配域名
DetectionMecanismBypass bool //是否让检测机通过-直接通往外网
mutex sync.Mutex
PortalURL map[int]map[*net.IPNet]*url.URL //二重映射表
URIException *regexp.regexp //例外,匹配url
SecureRedirect bool //安全定向,若为真采用htpps协议,否则采用http协议
}

//fqdn是ip和URL的映射表
// fqdn (fully qualified domain name : 正式域名)
type fqdn struct {

FQDN map[*net.IPNet]*url.URL
}

//passThrough 是监测规则实例
var passThrough *passthrough



// NewProxy creates a new instance of proxy.
// It sets request logger using rLogPath as output file or os.Stdout by default.
// If whitePath of blackPath is not empty they are parsed to set endpoint lists.
//NewProxy返回一个新的Proxy实例
func NewProxy(ctx context.Context) *Proxy

//更新代理
func (p *Proxy) Refresh(ctx context.Context)

// addToEndpointList compiles regex and adds it to an endpointList
// if regex is valid
// use t to choose list type: true for whitelist false for blacklist
//添加终端列表
//传入一个字符串最为正则表达式,根据t值决定添加到白名单还是黑名单
func (p *Proxy) addToEndpointList(ctx context.Context, r string) error

// checkEndpointList looks if r is in whitelist or blackllist
// returns true if endpoint is allowed
//检查e是否在白名单或黑名单
func (p *Proxy) checkEndpointList(ctx context.Context, e string) bool

// ServeHTTP satisfy HandlerFunc interface and
// log, authorize and forward requests
//HTTP服务器主体
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

//Configure add default target in the deny list
//添加默认目标到拒绝名单
func (p *Proxy) Configure(ctx context.Context, port string)

//读取配置信息
func (p *passthrough) readConfig(ctx context.Context)

// newProxyPassthrough instantiate a passthrough and return it
//生成代理路径实例
func newProxyPassthrough(ctx context.Context) *passthrough

// addFqdnToList add all the passthrough fqdn in a list
//添加所有经过路径的域名到一个列表
func (p *passthrough) addFqdnToList(ctx context.Context, r string) error

// addDetectionMechanismsToList add all detection mechanisms in a list
//添加所有的监测机到一个列表
func (p *passthrough) addDetectionMechanismsToList(ctx context.Context, r string) error

// checkProxyPassthrough compare the host to the list of regex
//将目标主机与列表中正则表示式比较。查看目标主机是否存在于列表中
func (p *passthrough) checkProxyPassthrough(ctx context.Context, e string) bool

// checkDetectionMechanisms compare the url to the detection mechanisms regex
// 将URL和监测机进行比对,查看url是否存在于监测机中
func (p *passthrough) checkDetectionMechanisms(ctx context.Context, e string) bool

完整文件解析

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
package httpdispatcher

import (
"context"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
"sync"
"text/template"
"time"

"github.com/inverse-inc/packetfence/go/log"
"github.com/inverse-inc/packetfence/go/pfconfigdriver"
)

type Proxy struct {
endpointWhiteList []*regexp.Regexp
endpointBlackList []*regexp.Regexp
mutex sync.Mutex
}

type passthrough struct {
proxypassthrough []*regexp.Regexp
detectionmechanisms []*regexp.Regexp
DetectionMecanismBypass bool
mutex sync.Mutex
PortalURL map[int]map[*net.IPNet]*url.URL
URIException *regexp.Regexp
SecureRedirect bool
}

type fqdn struct {
FQDN map[*net.IPNet]*url.URL
}

var passThrough *passthrough

// NewProxy creates a new instance of proxy.
// It sets request logger using rLogPath as output file or os.Stdout by default.
// If whitePath of blackPath is not empty they are parsed to set endpoint lists.
func NewProxy(ctx context.Context) *Proxy {
var p Proxy

passThrough = newProxyPassthrough(ctx)
passThrough.readConfig(ctx)
return &p
}

func (p *Proxy) Refresh(ctx context.Context) {
passThrough.readConfig(ctx)
}

// addToEndpointList compiles regex and adds it to an endpointList
// if regex is valid
// use t to choose list type: true for whitelist false for blacklist
func (p *Proxy) addToEndpointList(ctx context.Context, r string) error {
rgx, err := regexp.Compile(r)
if err == nil {
p.mutex.Lock()
p.endpointBlackList = append(p.endpointBlackList, rgx)
p.mutex.Unlock()
}
return err
}

// checkEndpointList looks if r is in whitelist or blackllist
// returns true if endpoint is allowed
func (p *Proxy) checkEndpointList(ctx context.Context, e string) bool {
if p.endpointBlackList == nil && p.endpointWhiteList == nil {
return true
}

for _, rgx := range p.endpointBlackList {
if rgx.MatchString(e) {
return false
}
}

return true
}

// ServeHTTP satisfy HandlerFunc interface and
// log, authorize and forward requests
//ServeHTTP执行http服务,如果请求主机或请求域名或请求url被检测为不在相关检测列表中
//则放行,之后修改请求的url的destination参数,返回302的跳转响应,之后返回。
//若不放行则进行进一步判断
//如果主机在黑名单中则直接返回403
//若不在黑名单中,则进行反向代理,将请求转发到目标主机
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

//请求的目标主机
host := r.Host
var fqdn url.URL
fqdn.Scheme = r.Header.Get("X-Forwarded-Proto")
fqdn.Host = r.Host
fqdn.Path = r.RequestURI
fqdn.ForceQuery = false
fqdn.RawQuery = ""

if host == "" {
w.WriteHeader(http.StatusBadGateway)
return
}

if r.URL.Path == "/kindle-wifi/wifistub.html" {
log.LoggerWContext(ctx).Debug(fmt.Sprintln(host, "KINDLE WIFI PROBE HANDLING"))
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Kindle Reachability Probe Page</title>
<META http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<!--81ce4465-7167-4dcb-835b-dcc9e44c112a created with python 2.5 uuid.uuid4()-->
</head>
<body bgcolor="#ffffff" text="#000000">
81ce4465-7167-4dcb-835b-dcc9e44c112a
</body>
</html>

`))
return
}


//如果请求目标主机在检测列表中 或者
//请求域名在监测机中 或者
//请求域名不在检测机中,但请求url与例外匹配
//则跳过if语句
if !(passThrough.checkProxyPassthrough(ctx, host) || ((passThrough.checkDetectionMechanisms(ctx, fqdn.String()) || passThrough.URIException.MatchString(r.RequestURI)) && passThrough.DetectionMecanismBypass)) {
if r.Method != "GET" && r.Method != "HEAD" {
log.LoggerWContext(ctx).Debug(fmt.Sprintln(host, "FORBIDDEN"))
w.WriteHeader(http.StatusNotImplemented)
return
}

var PortalURL url.URL
var found bool
found = false

//X-Forwarded-For 记录请求来源的IP地址
//在测试时需要在请求头添加X-ForWarded-For 参数,用于激发下述循环
srcIP := net.ParseIP(r.Header.Get("X-Forwarded-For"))
for i := 0; i <= len(passThrough.PortalURL); i++ {
if found {
break
}
for c, d := range passThrough.PortalURL[i] {
if c.Contains(srcIP) {
PortalURL = *d
found = true
break
}
}
}
/*
type IPNet struct {
IP IP // network number
Mask IPMask // network mask
}

*/

if (passThrough.checkDetectionMechanisms(ctx, fqdn.String()) || passThrough.URIException.MatchString(r.RequestURI)) && passThrough.SecureRedirect {
PortalURL.Scheme = "http"
}
log.LoggerWContext(ctx).Debug(fmt.Sprintln(host, "Redirect to the portal"))
//PortalURL.RawQuery是http的请求参数与对应值,在url的?后面
PortalURL.RawQuery = "destination_url=" + r.Header.Get("X-Forwarded-Proto") + "://" + host + r.RequestURI
w.Header().Set("Location", PortalURL.String())
w.WriteHeader(http.StatusFound)
//HEAD方法类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
if r.Method != "HEAD" {
t := template.New("foo")
t, _ = t.Parse(`
<html>
<head><title>302 Moved Temporarily</title></head>
<body>
<h1>Moved</h1>
<p>The document has moved <a href="{.String}">here</a>.</p>
<!--<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.wballiance.net/wispr/wispr_2_0.xsd">
<Redirect>
<MessageType>100</MessageType>
<ResponseCode>0</ResponseCode>
<AccessProcedure>1.0</AccessProcedure>
<VersionLow>1.0</VersionLow>
<VersionHigh>2.0</VersionHigh>
<AccessLocation>CDATA[[isocc=,cc=,ac=,network=PacketFence,]]</AccessLocation>
<LocationName>CDATA[[PacketFence]]</LocationName>
<LoginURL>{.String}</LoginURL>
</Redirect>
</WISPAccessGatewayParam>-->
</body>
</html>`)
t.Execute(w, &PortalURL)
}
log.LoggerWContext(ctx).Debug(fmt.Sprintln(host, "REDIRECT"))
return
}
//end-if

//检测目标主机是否在黑名单中,在现情况下,我们并没有对用户访问主机进行限制,可以不处理
if !p.checkEndpointList(ctx, host) {
log.LoggerWContext(ctx).Info(fmt.Sprintln(host, "FORBIDDEN host in blacklist"))
w.WriteHeader(http.StatusForbidden)
return
}


log.LoggerWContext(ctx).Debug(fmt.Sprintln(host, "REVERSE"))

//添加反向代理,将请求转发到 http://host
rp := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: host,
})


// Uses most defaults of http.DefaultTransport with more aggressive timeouts
//配置代理参数
//rp.Transport用于执行代理的请求
rp.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 2 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 2 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

t := time.Now()

// Pass the context in the request
r = r.WithContext(ctx)

//执行反向代理
rp.ServeHTTP(w, r)
log.LoggerWContext(ctx).Info(fmt.Sprintln(host, time.Since(t)))
}

// Configure add default target in the deny list
//因为添加了127.0.0.1.所以访问127.0.0.1:2015会返回404 ???? 是吗,还未确认
//Configure把本机添加到了黑名单
//但是在哪里调用了???
func (p *Proxy) Configure(ctx context.Context, port string) {
p.addToEndpointList(ctx, "localhost")
p.addToEndpointList(ctx, "127.0.0.1")
}


//readConfig在哪里调用???
func (p *passthrough) readConfig(ctx context.Context) {
fencing := pfconfigdriver.Config.PfConf.Fencing
general := pfconfigdriver.Config.PfConf.General
portal := pfconfigdriver.Config.PfConf.CaptivePortal

var scheme string

p.proxypassthrough = make([]*regexp.Regexp, 0)
for _, v := range fencing.ProxyPassthroughs {
p.addFqdnToList(ctx, v)
}

p.detectionmechanisms = make([]*regexp.Regexp, 0)
for _, v := range portal.DetectionMecanismUrls {
p.addDetectionMechanismsToList(ctx, v)
}

p.DetectionMecanismBypass = portal.DetectionMecanismBypass == "enabled"

rgx, _ := regexp.Compile("CaptiveNetworkSupport")
p.URIException = rgx

if portal.SecureRedirect == "enabled" {
p.SecureRedirect = true
scheme = "https"
} else {
p.SecureRedirect = false
scheme = "http"
}

index := 0

var interfaces pfconfigdriver.ListenInts
pfconfigdriver.FetchDecodeSocket(ctx, &interfaces)

var keyConfNet pfconfigdriver.PfconfigKeys
keyConfNet.PfconfigNS = "config::Network"
keyConfNet.PfconfigHostnameOverlay = "yes"
pfconfigdriver.FetchDecodeSocket(ctx, &keyConfNet)

var NetIndexDefault net.IPNet
var portalURLDefault url.URL

p.PortalURL = make(map[int]map[*net.IPNet]*url.URL)

for _, key := range keyConfNet.Keys {
var NetIndex net.IPNet
var portalURL url.URL

var portal_url fqdn
portal_url.FQDN = make(map[*net.IPNet]*url.URL)

var ConfNet pfconfigdriver.NetworkConf
ConfNet.PfconfigHashNS = key
pfconfigdriver.FetchDecodeSocket(ctx, &ConfNet)

var portal string
if ConfNet.PortalFQDN != "" {
portal = ConfNet.PortalFQDN
} else {
portal = general.Hostname + "." + general.Domain
}
portalURL.Host = portal
portalURL.Path = "/captive-portal"
portalURL.Scheme = scheme

NetIndex.Mask = net.IPMask(net.ParseIP(ConfNet.Netmask))
NetIndex.IP = net.ParseIP(key)

p.PortalURL[index] = make(map[*net.IPNet]*url.URL)
p.PortalURL[index][&NetIndex] = &portalURL
index++
}
NetIndexDefault.Mask = net.IPMask(net.IPv4zero)
NetIndexDefault.IP = net.IPv4zero

portalURLDefault.Host = general.Hostname + "." + general.Domain
portalURLDefault.Path = "/captive-portal"
portalURLDefault.Scheme = scheme

p.PortalURL[index] = make(map[*net.IPNet]*url.URL)

p.PortalURL[index][&NetIndexDefault] = &portalURLDefault
}

// newProxyPassthrough instantiate a passthrough and return it
func newProxyPassthrough(ctx context.Context) *passthrough {
var p passthrough
return &p
}

// addFqdnToList add all the passthrough fqdn in a list
func (p *passthrough) addFqdnToList(ctx context.Context, r string) error {
rgx, err := regexp.Compile(r)
if err == nil {
p.mutex.Lock()
p.proxypassthrough = append(p.proxypassthrough, rgx)
p.mutex.Unlock()
}
return err
}

// addDetectionMechanismsToList add all detection mechanisms in a list
func (p *passthrough) addDetectionMechanismsToList(ctx context.Context, r string) error {
rgx, err := regexp.Compile(r)
if err == nil {
p.mutex.Lock()
p.detectionmechanisms = append(p.detectionmechanisms, rgx)
p.mutex.Unlock()
}
return err
}

// checkProxyPassthrough compare the host to the list of regex
func (p *passthrough) checkProxyPassthrough(ctx context.Context, e string) bool {
if p.proxypassthrough == nil {
return false
}

for _, rgx := range p.proxypassthrough {
if rgx.MatchString(e) {
return true
}
}
return false
}

// checkDetectionMechanisms compare the url to the detection mechanisms regex
func (p *passthrough) checkDetectionMechanisms(ctx context.Context, e string) bool {
if p.detectionmechanisms == nil {
return false
}

for _, rgx := range p.detectionmechanisms {
if rgx.MatchString(e) {
return true
}
}
return false
}