zoraxy/src/upnp.go
2023-04-13 22:07:38 +08:00

211 lines
4.5 KiB
Go

package main
import (
"encoding/json"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"time"
"imuslab.com/zoraxy/mod/upnp"
"imuslab.com/zoraxy/mod/utils"
)
var upnpEnabled = false
var preforwardMap map[int]string
func initUpnp() error {
go func() {
//Let UPnP discovery run in background
var err error
upnpClient, err = upnp.NewUPNPClient()
if err != nil {
log.Println("UPnP router discover error: ", err.Error())
return
}
if upnpEnabled {
//Forward all the ports
for port, policyName := range preforwardMap {
upnpClient.ForwardPort(port, policyName)
log.Println("Upnp forwarding ", port, " for "+policyName)
time.Sleep(300 * time.Millisecond)
}
}
}()
//Check if the upnp was enabled
sysdb.NewTable("upnp")
sysdb.Read("upnp", "enabled", &upnpEnabled)
//Load all the ports from database
portsMap := map[int]string{}
sysdb.Read("upnp", "portmap", &portsMap)
preforwardMap = portsMap
return nil
}
func handleUpnpDiscover(w http.ResponseWriter, r *http.Request) {
restart, err := utils.PostPara(r, "restart")
if err != nil {
type UpnpInfo struct {
ExternalIp string
RouterIp string
}
if upnpClient == nil {
utils.SendErrorResponse(w, "No UPnP router discovered")
return
}
parsedUrl, _ := url.Parse(upnpClient.Connection.Location())
ipWithPort := parsedUrl.Host
result := UpnpInfo{
ExternalIp: upnpClient.ExternalIP,
RouterIp: ipWithPort,
}
//Show if there is a upnpclient
js, _ := json.Marshal(result)
utils.SendJSONResponse(w, string(js))
} else {
if restart == "true" {
//Close the upnp client if exists
if upnpClient != nil {
saveForwardingPortsToDatabase()
upnpClient.Close()
}
//Restart a new one
initUpnp()
utils.SendOK(w)
}
}
}
func handleToggleUPnP(w http.ResponseWriter, r *http.Request) {
newMode, err := utils.PostPara(r, "mode")
if err != nil {
//Send the current mode to client side
js, _ := json.Marshal(upnpEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if newMode == "true" {
upnpEnabled = true
sysdb.Read("upnp", "enabled", true)
log.Println("UPnP Enabled. Forwarding all required ports")
//Mount all Upnp requests from preforward Map
for port, policyName := range preforwardMap {
upnpClient.ForwardPort(port, policyName)
log.Println("Upnp forwarding ", port, " for "+policyName)
time.Sleep(300 * time.Millisecond)
}
utils.SendOK(w)
return
} else if newMode == "false" {
upnpEnabled = false
sysdb.Read("upnp", "enabled", false)
log.Println("UPnP disabled. Closing all forwarded ports")
//Save the current forwarded ports
saveForwardingPortsToDatabase()
//Unmount all Upnp request
for _, port := range upnpClient.RequiredPorts {
upnpClient.ClosePort(port)
log.Println("UPnP port closed: ", port)
time.Sleep(300 * time.Millisecond)
}
//done
utils.SendOK(w)
return
}
}
}
func filterRFC2141(input string) string {
rfc2141 := regexp.MustCompile(`^[\w\-.!~*'()]*(\%[\da-fA-F]{2}[\w\-.!~*'()]*)*$`)
var result []rune
for _, char := range input {
if char <= 127 && rfc2141.MatchString(string(char)) {
result = append(result, char)
}
}
return string(result)
}
func handleAddUpnpPort(w http.ResponseWriter, r *http.Request) {
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
portNumber, err := strconv.Atoi(portString)
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
policyName, err := utils.PostPara(r, "name")
if err != nil {
utils.SendErrorResponse(w, "invalid policy name")
return
}
policyName = filterRFC2141(policyName)
err = upnpClient.ForwardPort(portNumber, policyName)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
saveForwardingPortsToDatabase()
utils.SendOK(w)
}
func handleRemoveUpnpPort(w http.ResponseWriter, r *http.Request) {
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
portNumber, err := strconv.Atoi(portString)
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
saveForwardingPortsToDatabase()
upnpClient.ClosePort(portNumber)
}
func saveForwardingPortsToDatabase() {
//Move the sync map to map[int]string
m := make(map[int]string)
upnpClient.PolicyNames.Range(func(key, value interface{}) bool {
if k, ok := key.(int); ok {
if v, ok := value.(string); ok {
m[k] = v
}
}
return true
})
preforwardMap = m
sysdb.Write("upnp", "portmap", &preforwardMap)
}