Files
openlan/pkg/switch/http.go
2022-10-04 20:41:15 +08:00

335 lines
8.0 KiB
Go
Executable File

package _switch
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/gorilla/mux"
"github.com/luscis/openlan/pkg/api"
"github.com/luscis/openlan/pkg/cache"
co "github.com/luscis/openlan/pkg/config"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/models"
"github.com/luscis/openlan/pkg/schema"
"io/ioutil"
"net/http"
"net/http/pprof"
"os"
"path"
"sort"
"strings"
"text/template"
"time"
)
type Http struct {
switcher api.Switcher
listen string
adminToken string
guestToken string
adminFile string
server *http.Server
crtFile string
keyFile string
pubDir string
router *mux.Router
}
func NewHttp(switcher api.Switcher) (h *Http) {
c := co.Manager.Switch
h = &Http{
switcher: switcher,
listen: c.Http.Listen,
adminFile: c.TokenFile,
pubDir: c.Http.Public,
}
if c.Cert != nil {
h.crtFile = c.Cert.CrtFile
h.keyFile = c.Cert.KeyFile
}
return
}
func (h *Http) Initialize() {
r := h.Router()
if h.server == nil {
h.server = &http.Server{
Addr: h.listen,
Handler: r,
ReadTimeout: 5 * time.Minute,
WriteTimeout: 10 * time.Minute,
}
}
h.LoadToken()
h.SaveToken()
h.LoadRouter()
}
func (h *Http) PProf(r *mux.Router) {
if r != nil {
r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
}
func (h *Http) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
libol.Info("Http.Middleware %s %s", r.Method, r.URL.Path)
if h.IsAuth(w, r) {
next.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", "Basic")
http.Error(w, "Authorization Required", http.StatusUnauthorized)
}
})
}
func (h *Http) Router() *mux.Router {
if h.router == nil {
h.router = mux.NewRouter()
h.router.Use(h.Middleware)
}
return h.router
}
func (h *Http) SaveToken() {
f, err := os.OpenFile(h.adminFile, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
libol.Error("Http.SaveToken: %s", err)
return
}
defer f.Close()
if _, err := f.Write([]byte(h.adminToken)); err != nil {
libol.Error("Http.SaveToken: %s", err)
return
}
}
func (h *Http) LoadRouter() {
router := h.Router()
router.HandleFunc("/", h.IndexHtml)
router.HandleFunc("/index.html", h.IndexHtml)
router.HandleFunc("/favicon.ico", h.PubFile)
h.PProf(router)
router.HandleFunc("/api/index", h.GetIndex).Methods("GET")
api.Add(router, h.switcher)
}
func (h *Http) LoadToken() {
token := ""
if _, err := os.Stat(h.adminFile); os.IsNotExist(err) {
libol.Info("Http.LoadToken: file:%s does not exist", h.adminFile)
} else {
contents, err := ioutil.ReadFile(h.adminFile)
if err != nil {
libol.Error("Http.LoadToken: file:%s %s", h.adminFile, err)
} else {
token = strings.TrimSpace(string(contents))
}
}
if token == "" {
token = libol.GenString(32)
}
h.SetToken(token)
}
func (h *Http) SetToken(value string) {
sum := md5.Sum([]byte(value))
h.adminToken = value
h.guestToken = hex.EncodeToString(sum[:16])[:12]
}
func (h *Http) Start() {
h.Initialize()
libol.Info("Http.Start %s", h.listen)
promise := &libol.Promise{
First: time.Second * 2,
MaxInt: time.Minute,
MinInt: time.Second * 10,
}
promise.Done(func() error {
if h.keyFile == "" || h.crtFile == "" {
if err := h.server.ListenAndServe(); err != nil {
libol.Error("Http.Start on %s: %s", h.listen, err)
return err
}
} else {
if err := h.server.ListenAndServeTLS(h.crtFile, h.keyFile); err != nil {
libol.Error("Http.Start on %s: %s", h.listen, err)
return err
}
}
return nil
})
}
func (h *Http) Shutdown() {
libol.Info("Http.Shutdown %s", h.listen)
if err := h.server.Shutdown(context.Background()); err != nil {
// Error from closing listeners, or context timeout:
libol.Error("Http.Shutdown: %v", err)
}
}
func (h *Http) IsAuth(w http.ResponseWriter, r *http.Request) bool {
token, pass, ok := r.BasicAuth()
libol.Debug("Http.IsAuth token: %s, pass: %s", token, pass)
if strings.HasPrefix(r.URL.Path, "/api/") {
if !ok || token != h.adminToken {
return false
}
} else if strings.HasPrefix(r.URL.Path, "/get/") {
if !ok || token != h.guestToken {
return false
}
}
return true
}
func (h *Http) getFile(name string) string {
return fmt.Sprintf("%s%s", h.pubDir, name)
}
func (h *Http) PubFile(w http.ResponseWriter, r *http.Request) {
realpath := h.getFile(r.URL.Path)
contents, err := ioutil.ReadFile(realpath)
if err != nil {
_, _ = fmt.Fprintf(w, "404")
return
}
_, _ = fmt.Fprintf(w, "%s\n", contents)
}
func (h *Http) getIndex(body *schema.Index) *schema.Index {
body.Version = schema.NewVersionSchema()
body.Worker = api.NewWorkerSchema(h.switcher)
// display accessed point.
for p := range cache.Point.List() {
if p == nil {
break
}
body.Points = append(body.Points, models.NewPointSchema(p))
}
sort.SliceStable(body.Points, func(i, j int) bool {
ii := body.Points[i]
jj := body.Points[j]
return ii.Network+ii.Remote > jj.Network+jj.Remote
})
// display neighbor.
for n := range cache.Neighbor.List() {
if n == nil {
break
}
body.Neighbors = append(body.Neighbors, models.NewNeighborSchema(n))
}
sort.SliceStable(body.Neighbors, func(i, j int) bool {
return body.Neighbors[i].IpAddr > body.Neighbors[j].IpAddr
})
// display links.
for l := range cache.Link.List() {
if l == nil {
break
}
body.Links = append(body.Links, models.NewLinkSchema(l))
}
sort.SliceStable(body.Links, func(i, j int) bool {
ii := body.Links[i]
jj := body.Links[j]
return ii.Network+ii.Server > jj.Network+jj.Server
})
// display online flow.
for l := range cache.Online.List() {
if l == nil {
break
}
body.OnLines = append(body.OnLines, models.NewOnLineSchema(l))
}
sort.SliceStable(body.OnLines, func(i, j int) bool {
return body.OnLines[i].HitTime < body.OnLines[j].HitTime
})
// display OpenVPN Clients.
for n := range cache.Network.List() {
if n == nil {
break
}
for c := range cache.VPNClient.List(n.Name) {
if c == nil {
break
}
body.Clients = append(body.Clients, *c)
}
sort.SliceStable(body.Clients, func(i, j int) bool {
return body.Clients[i].Name < body.Clients[j].Name
})
}
// display esp state
for s := range cache.EspState.List("") {
if s == nil {
break
}
body.States = append(body.States, models.NewEspStateSchema(s))
}
sort.SliceStable(body.States, func(i, j int) bool {
ii := body.States[i]
jj := body.States[j]
return ii.Spi > jj.Spi
})
return body
}
func (h *Http) ParseFiles(w http.ResponseWriter, name string, data interface{}) error {
file := path.Base(name)
tmpl, err := template.New(file).Funcs(template.FuncMap{
"prettyTime": libol.PrettyTime,
"prettyBytes": libol.PrettyBytes,
"getIpAddr": libol.GetIPAddr,
}).ParseFiles(name)
if err != nil {
_, _ = fmt.Fprintf(w, "template.ParseFiles %s", err)
return err
}
if err := tmpl.Execute(w, data); err != nil {
_, _ = fmt.Fprintf(w, "template.ParseFiles %s", err)
return err
}
return nil
}
func (h *Http) IndexHtml(w http.ResponseWriter, r *http.Request) {
body := schema.Index{
Points: make([]schema.Point, 0, 128),
Links: make([]schema.Link, 0, 128),
Neighbors: make([]schema.Neighbor, 0, 128),
OnLines: make([]schema.OnLine, 0, 128),
Clients: make([]schema.VPNClient, 0, 128),
States: make([]schema.EspState, 0, 128),
}
h.getIndex(&body)
file := h.getFile("/index.html")
if err := h.ParseFiles(w, file, &body); err != nil {
libol.Error("Http.Index %s", err)
}
}
func (h *Http) GetIndex(w http.ResponseWriter, r *http.Request) {
body := schema.Index{
Points: make([]schema.Point, 0, 128),
Links: make([]schema.Link, 0, 128),
Neighbors: make([]schema.Neighbor, 0, 128),
OnLines: make([]schema.OnLine, 0, 128),
Network: make([]schema.Network, 0, 128),
Clients: make([]schema.VPNClient, 0, 128),
}
h.getIndex(&body)
api.ResponseJson(w, body)
}