forked from FeNoMeNa/cwmp-proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
proxy.go
135 lines (107 loc) · 3.39 KB
/
proxy.go
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
package main
import (
"flag"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"github.com/FeNoMeNa/goha"
)
var (
port = flag.Int("port", 0, "CWMP proxy port")
backend = flag.String("backend", "", "The backend ACS server")
)
func main() {
flag.Parse()
if *port == 0 || *backend == "" {
flag.PrintDefaults()
return
}
p, err := NewProxy(*port, *backend)
if err != nil {
log.Fatalf("The CWMP proxy cannot be created - %v", err)
return
}
err = p.Start()
if err != nil {
log.Fatalf("The CWMP proxy cannot be started - %v", err)
return
}
}
// Proxy represents an CWMP proxy server. There may be multiple backend endpoints that will accept
// the incoming requests.
type Proxy struct {
listener net.Listener
backend *url.URL
}
// NewProxy creates and initializes a new CWMP proxy. If the desired port is not free an error
// will be returned.
func NewProxy(port int, backend string) (*Proxy, error) {
u, err := url.Parse(backend)
if err != nil {
return new(Proxy), err
}
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return new(Proxy), err
}
return &Proxy{l, u}, nil
}
// Start starts the CWMP proxy. It registers two main http handlers, the first one is the proxy
// handler, the second one is related with the CPE waking up.
func (p *Proxy) Start() error {
http.Handle("/", p.handler())
http.Handle("/client", basicAuthHandler(wakeupHandler))
return http.Serve(p.listener, nil)
}
// Close terminates the CWMP proxy. It simply closes the TCP server listener.
func (p *Proxy) Close() error {
return p.listener.Close()
}
// handler is the core of the CWMP proxy. It uses the internal ReverseProxy to implement
// the proxy logic that will send the incoming requests the backend servers. We should
// notice that the handler modifies the received CWMP content. If the request contains a
// connection url it will be replaced with a custom defined.
func (p *Proxy) handler() http.Handler {
return &httputil.ReverseProxy{
Director: func(req *http.Request) {
cwmp := newCwmpMessage(req)
cwmp.replaceConnectionUrl(req.Host)
req.URL.Scheme = p.backend.Scheme
req.URL.Host = p.backend.Host
},
}
}
// wakeupHandler wakes up a concrete CPE. When the wakeupHandler endpoint is called with
// a valid connection url, an authorized GET request is send to that url.
func wakeupHandler(username, password string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
origin := r.FormValue("origin")
if origin == "" {
http.Error(w, "The origin connection URL should be provided!", http.StatusBadRequest)
return
}
resp, err := goha.NewClient(username, password).Get(origin)
if err != nil {
http.Error(w, "An error occurred with the CPE communication!", http.StatusBadRequest)
return
}
w.WriteHeader(resp.StatusCode)
}
}
// basicAuthHandler is a handler wrapper that will obtain the username and password encoded
// with basic access authentication scheme. If the Authorization header is not provided the
// wrapper will respond with 401 status code.
func basicAuthHandler(handler func(string, string) http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
w.Header().Add("WWW-Authenticate", `Basic realm="cwmp-proxy"`)
w.WriteHeader(http.StatusUnauthorized)
return
}
handler(username, password).ServeHTTP(w, r)
}
}