2024-11-23 12:30:23 +09:00
|
|
|
package hodu
|
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
import "fmt"
|
2024-12-23 01:27:47 +09:00
|
|
|
import "net"
|
2024-12-08 00:57:58 +09:00
|
|
|
import "net/http"
|
2024-12-12 21:09:16 +09:00
|
|
|
import "net/netip"
|
2024-12-08 00:57:58 +09:00
|
|
|
import "os"
|
|
|
|
import "runtime"
|
2024-12-12 21:09:16 +09:00
|
|
|
import "strings"
|
2024-11-23 12:30:23 +09:00
|
|
|
import "sync"
|
2025-01-07 15:23:05 +09:00
|
|
|
import "time"
|
2024-11-23 12:30:23 +09:00
|
|
|
|
2024-12-07 21:24:06 +09:00
|
|
|
const HODU_RPC_VERSION uint32 = 0x010000
|
2024-11-23 12:30:23 +09:00
|
|
|
|
|
|
|
type LogLevel int
|
2024-12-09 01:51:04 +09:00
|
|
|
type LogMask int
|
2024-11-23 12:30:23 +09:00
|
|
|
|
|
|
|
const (
|
2024-12-09 01:51:04 +09:00
|
|
|
LOG_DEBUG LogLevel = 1 << iota
|
2024-11-23 12:30:23 +09:00
|
|
|
LOG_INFO
|
2024-12-09 01:51:04 +09:00
|
|
|
LOG_WARN
|
|
|
|
LOG_ERROR
|
2024-11-23 12:30:23 +09:00
|
|
|
)
|
|
|
|
|
2024-12-09 01:59:05 +09:00
|
|
|
const LOG_ALL LogMask = LogMask(LOG_DEBUG | LOG_INFO | LOG_WARN | LOG_ERROR)
|
|
|
|
const LOG_NONE LogMask = LogMask(0)
|
|
|
|
|
2024-12-12 21:09:16 +09:00
|
|
|
var IPV4_PREFIX_ZERO = netip.MustParsePrefix("0.0.0.0/0")
|
|
|
|
var IPV6_PREFIX_ZERO = netip.MustParsePrefix("::/0")
|
|
|
|
|
2025-01-28 00:44:02 +09:00
|
|
|
type Named struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2024-11-23 12:30:23 +09:00
|
|
|
type Logger interface {
|
2024-12-08 23:16:43 +09:00
|
|
|
Write(id string, level LogLevel, fmtstr string, args ...interface{})
|
2024-12-15 15:07:35 +09:00
|
|
|
WriteWithCallDepth(id string, level LogLevel, call_depth int, fmtstr string, args ...interface{})
|
|
|
|
Rotate()
|
2025-01-11 11:48:19 +09:00
|
|
|
Close()
|
2024-11-23 12:30:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
type Service interface {
|
2024-12-08 23:16:43 +09:00
|
|
|
RunTask(wg *sync.WaitGroup) // blocking. run the actual task loop. it must call wg.Done() upon exit from itself.
|
2024-11-23 12:30:23 +09:00
|
|
|
StartService(data interface{}) // non-blocking. spin up a service. it may be invokded multiple times for multiple instances
|
|
|
|
StopServices() // non-blocking. send stop request to all services spun up
|
2024-12-14 14:04:33 +09:00
|
|
|
FixServices() // do some fixup as needed
|
2024-11-23 12:30:23 +09:00
|
|
|
WaitForTermination() // blocking. must wait until all services are stopped
|
2024-11-23 20:13:07 +09:00
|
|
|
WriteLog(id string, level LogLevel, fmtstr string, args ...interface{})
|
2024-11-23 12:30:23 +09:00
|
|
|
}
|
2024-12-07 22:18:07 +09:00
|
|
|
|
2025-01-28 00:44:02 +09:00
|
|
|
type json_out_go_stats struct {
|
|
|
|
CPUs int `json:"cpus"`
|
|
|
|
Goroutines int `json:"goroutines"`
|
|
|
|
NumCgoCalls int64 `json:"num-cgo-calls"`
|
|
|
|
NumGCs uint32 `json:"num-gcs"`
|
|
|
|
AllocBytes uint64 `json:"memory-alloc-bytes"`
|
|
|
|
TotalAllocBytes uint64 `json:"memory-total-alloc-bytes"`
|
|
|
|
SysBytes uint64 `json:"memory-sys-bytes"`
|
|
|
|
Lookups uint64 `json:"memory-lookups"`
|
|
|
|
MemAllocs uint64 `json:"memory-num-allocs"`
|
|
|
|
MemFrees uint64 `json:"memory-num-frees"`
|
|
|
|
|
|
|
|
HeapAllocBytes uint64 `json:"memory-heap-alloc-bytes"`
|
|
|
|
HeapSysBytes uint64 `json:"memory-heap-sys-bytes"`
|
|
|
|
HeapIdleBytes uint64 `json:"memory-heap-idle-bytes"`
|
|
|
|
HeapInuseBytes uint64 `json:"memory-heap-inuse-bytes"`
|
|
|
|
HeapReleasedBytes uint64 `json:"memory-heap-released-bytes"`
|
|
|
|
HeapObjects uint64 `json:"memory-heap-objects"`
|
|
|
|
StackInuseBytes uint64 `json:"memory-stack-inuse-bytes"`
|
|
|
|
StackSysBytes uint64 `json:"memory-stack-sys-bytes"`
|
|
|
|
MSpanInuseBytes uint64 `json:"memory-mspan-inuse-bytes"`
|
|
|
|
MSpanSysBytes uint64 `json:"memory-mspan-sys-bytes"`
|
|
|
|
MCacheInuseBytes uint64 `json:"memory-mcache-inuse-bytes"`
|
|
|
|
MCacheSysBytes uint64 `json:"memory-mcache-sys-bytes"`
|
|
|
|
BuckHashSysBytes uint64 `json:"memory-buck-hash-sys-bytes"`
|
|
|
|
GCSysBytes uint64 `json:"memory-gc-sys-bytes"`
|
|
|
|
OtherSysBytes uint64 `json:"memory-other-sys-bytes"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Named) SetName(name string) {
|
|
|
|
n.name = name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Named) Name() string {
|
|
|
|
return n.name
|
|
|
|
}
|
|
|
|
|
2025-01-08 17:32:40 +09:00
|
|
|
func TcpAddrStrClass(addr string) string {
|
2024-12-12 21:09:16 +09:00
|
|
|
// the string is supposed to be addr:port
|
|
|
|
|
2024-12-07 23:03:23 +09:00
|
|
|
if len(addr) > 0 {
|
2024-12-12 21:09:16 +09:00
|
|
|
var ap netip.AddrPort
|
|
|
|
var err error
|
|
|
|
ap, err = netip.ParseAddrPort(addr)
|
|
|
|
if err == nil {
|
|
|
|
if ap.Addr().Is6() { return "tcp6" }
|
2025-01-16 01:26:58 +09:00
|
|
|
if ap.Addr().Is4() || ap.Addr().Is4In6() { return "tcp4" }
|
2024-12-07 23:03:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "tcp"
|
|
|
|
}
|
2024-12-08 00:57:58 +09:00
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func TcpAddrClass(addr *net.TCPAddr) string {
|
2025-01-16 01:26:58 +09:00
|
|
|
var netip_addr netip.Addr
|
|
|
|
netip_addr = addr.AddrPort().Addr()
|
|
|
|
if netip_addr.Is4() || netip_addr.Is4In6() {
|
2025-01-15 01:19:50 +09:00
|
|
|
return "tcp4"
|
|
|
|
} else {
|
|
|
|
return "tcp6"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-13 02:25:27 +09:00
|
|
|
func word_to_route_option(word string) RouteOption {
|
2024-12-12 21:09:16 +09:00
|
|
|
switch word {
|
|
|
|
case "tcp4":
|
|
|
|
return RouteOption(ROUTE_OPTION_TCP4)
|
|
|
|
case "tcp6":
|
|
|
|
return RouteOption(ROUTE_OPTION_TCP6)
|
|
|
|
case "tcp":
|
|
|
|
return RouteOption(ROUTE_OPTION_TCP)
|
|
|
|
case "tty":
|
|
|
|
return RouteOption(ROUTE_OPTION_TTY)
|
|
|
|
case "http":
|
|
|
|
return RouteOption(ROUTE_OPTION_HTTP)
|
|
|
|
case "https":
|
|
|
|
return RouteOption(ROUTE_OPTION_HTTPS)
|
2024-12-13 02:25:27 +09:00
|
|
|
case "ssh":
|
|
|
|
return RouteOption(ROUTE_OPTION_SSH)
|
2024-12-12 21:09:16 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return RouteOption(ROUTE_OPTION_UNSPEC)
|
|
|
|
}
|
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
func StringToRouteOption(desc string) RouteOption {
|
2024-12-12 21:09:16 +09:00
|
|
|
var fld string
|
2024-12-13 02:25:27 +09:00
|
|
|
var option RouteOption
|
2024-12-12 21:09:16 +09:00
|
|
|
var p RouteOption
|
|
|
|
|
2024-12-13 02:25:27 +09:00
|
|
|
option = RouteOption(0)
|
2024-12-12 21:09:16 +09:00
|
|
|
for _, fld = range strings.Fields(desc) {
|
2024-12-13 02:25:27 +09:00
|
|
|
p = word_to_route_option(fld)
|
2024-12-12 21:09:16 +09:00
|
|
|
if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p }
|
2024-12-13 02:25:27 +09:00
|
|
|
option |= p
|
2024-12-12 21:09:16 +09:00
|
|
|
}
|
2024-12-13 02:25:27 +09:00
|
|
|
return option
|
2024-12-12 21:09:16 +09:00
|
|
|
}
|
|
|
|
|
2025-01-16 01:26:58 +09:00
|
|
|
func (option RouteOption) String() string {
|
2024-12-12 21:09:16 +09:00
|
|
|
var str string
|
|
|
|
str = ""
|
2024-12-13 02:25:27 +09:00
|
|
|
if option & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_TCP) != 0 { str += " tcp" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_TTY) != 0 { str += " tty" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_SSH) != 0 { str += " ssh" }
|
2024-12-12 21:09:16 +09:00
|
|
|
if str == "" { return str }
|
|
|
|
return str[1:] // remove the leading space
|
|
|
|
}
|
|
|
|
|
2024-12-08 00:57:58 +09:00
|
|
|
func dump_call_frame_and_exit(log Logger, req *http.Request, err interface{}) {
|
|
|
|
var buf []byte
|
|
|
|
buf = make([]byte, 65536); buf = buf[:min(65536, runtime.Stack(buf, false))]
|
|
|
|
log.Write("", LOG_ERROR, "[%s] %s %s - %v\n%s", req.RemoteAddr, req.Method, req.URL.String(), err, string(buf))
|
2025-01-11 11:48:19 +09:00
|
|
|
log.Close()
|
2024-12-08 00:57:58 +09:00
|
|
|
os.Exit(99) // fatal error. treat panic() as a fatal runtime error
|
|
|
|
}
|
2024-12-23 01:27:47 +09:00
|
|
|
|
|
|
|
func svc_addr_to_dst_addr (svc_addr *net.TCPAddr) *net.TCPAddr {
|
|
|
|
var addr net.TCPAddr
|
|
|
|
|
|
|
|
addr = *svc_addr
|
|
|
|
if addr.IP.To4() != nil {
|
|
|
|
if addr.IP.IsUnspecified() {
|
|
|
|
addr.IP = net.IPv4(127, 0, 0, 1) // net.IPv4loopback is not defined. so use net.IPv4()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if addr.IP.IsUnspecified() {
|
|
|
|
addr.IP = net.IPv6loopback // net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &addr
|
|
|
|
}
|
2025-01-07 15:23:05 +09:00
|
|
|
|
|
|
|
func is_digit_or_period(r rune) bool {
|
|
|
|
return (r >= '0' && r <= '9') || r == '.'
|
|
|
|
}
|
|
|
|
|
|
|
|
func get_last_rune_of_non_empty_string(s string) rune {
|
|
|
|
var tmp []rune
|
|
|
|
// the string must not be blank for this to work
|
|
|
|
tmp = []rune(s)
|
|
|
|
return tmp[len(tmp) - 1]
|
|
|
|
}
|
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
func ParseDurationString(dur string) (time.Duration, error) {
|
2025-01-07 15:23:05 +09:00
|
|
|
// i want the input to be in seconds with resolution of 9 digits after
|
|
|
|
// the decimal point. For example, 0.05 to mean 500ms.
|
|
|
|
// however, i don't care if a unit is part of the input.
|
|
|
|
var tmp string
|
|
|
|
|
|
|
|
if dur == "" { return 0, nil }
|
|
|
|
|
|
|
|
tmp = dur
|
|
|
|
if is_digit_or_period(get_last_rune_of_non_empty_string(tmp)) { tmp = tmp + "s" }
|
|
|
|
return time.ParseDuration(tmp)
|
|
|
|
}
|
2025-01-13 20:17:49 +09:00
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
func DurationToSecString(d time.Duration) string {
|
|
|
|
return fmt.Sprintf("%.09f", d.Seconds())
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteJsonRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
|
|
|
func write_js_resp_header(w http.ResponseWriter, status_code int) int {
|
|
|
|
w.Header().Set("Content-Type", "application/javascript")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteCssRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "text/css")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteHtmlRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteEmptyRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
2025-01-14 14:09:52 +09:00
|
|
|
|
|
|
|
func server_route_to_proxy_info(r *ServerRoute) *ServerRouteProxyInfo {
|
|
|
|
return &ServerRouteProxyInfo{
|
|
|
|
SvcOption: r.SvcOption,
|
|
|
|
PtcName: r.PtcName,
|
|
|
|
PtcAddr: r.PtcAddr,
|
|
|
|
SvcAddr: r.SvcAddr,
|
|
|
|
SvcPermNet: r.SvcPermNet,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func proxy_info_to_server_route(pi *ServerRouteProxyInfo) *ServerRoute {
|
|
|
|
return &ServerRoute{
|
|
|
|
SvcOption: pi.SvcOption,
|
|
|
|
PtcName: pi.PtcName,
|
|
|
|
PtcAddr: pi.PtcAddr,
|
|
|
|
SvcAddr: pi.SvcAddr,
|
|
|
|
SvcPermNet: pi.SvcPermNet,
|
|
|
|
}
|
|
|
|
}
|
2025-01-28 00:44:02 +09:00
|
|
|
|
|
|
|
func (stats *json_out_go_stats) from_runtime_stats() {
|
|
|
|
var mstat runtime.MemStats
|
|
|
|
|
|
|
|
runtime.ReadMemStats(&mstat)
|
|
|
|
|
|
|
|
stats.CPUs = runtime.NumCPU()
|
|
|
|
stats.Goroutines = runtime.NumGoroutine()
|
|
|
|
stats.NumCgoCalls = runtime.NumCgoCall()
|
|
|
|
stats.NumGCs = mstat.NumGC
|
|
|
|
|
|
|
|
stats.AllocBytes = mstat.Alloc
|
|
|
|
stats.TotalAllocBytes = mstat.TotalAlloc
|
|
|
|
stats.SysBytes = mstat.Sys
|
|
|
|
stats.Lookups = mstat.Lookups
|
|
|
|
stats.MemAllocs = mstat.Mallocs
|
|
|
|
stats.MemFrees = mstat.Frees
|
|
|
|
|
|
|
|
stats.HeapAllocBytes = mstat.HeapAlloc
|
|
|
|
stats.HeapSysBytes = mstat.HeapSys
|
|
|
|
stats.HeapIdleBytes = mstat.HeapIdle
|
|
|
|
stats.HeapInuseBytes = mstat.HeapInuse
|
|
|
|
stats.HeapReleasedBytes = mstat.HeapReleased
|
|
|
|
stats.HeapObjects = mstat.HeapObjects
|
|
|
|
stats.StackInuseBytes = mstat.StackInuse
|
|
|
|
stats.StackSysBytes = mstat.StackSys
|
|
|
|
stats.MSpanInuseBytes = mstat.MSpanInuse
|
|
|
|
stats.MSpanSysBytes = mstat.MSpanSys
|
|
|
|
stats.MCacheInuseBytes = mstat.MCacheInuse
|
|
|
|
stats.MCacheSysBytes = mstat.MCacheSys
|
|
|
|
stats.BuckHashSysBytes = mstat.BuckHashSys
|
|
|
|
stats.GCSysBytes = mstat.GCSys
|
|
|
|
stats.OtherSysBytes = mstat.OtherSys
|
|
|
|
}
|