hodu/hodu.go

217 lines
5.7 KiB
Go
Raw Normal View History

package hodu
2024-12-23 01:27:47 +09:00
import "net"
import "net/http"
import "net/netip"
import "os"
import "runtime"
import "strings"
import "sync"
import "time"
const HODU_RPC_VERSION uint32 = 0x010000
type LogLevel int
type LogMask int
const (
LOG_DEBUG LogLevel = 1 << iota
LOG_INFO
LOG_WARN
LOG_ERROR
)
const LOG_ALL LogMask = LogMask(LOG_DEBUG | LOG_INFO | LOG_WARN | LOG_ERROR)
const LOG_NONE LogMask = LogMask(0)
var IPV4_PREFIX_ZERO = netip.MustParsePrefix("0.0.0.0/0")
var IPV6_PREFIX_ZERO = netip.MustParsePrefix("::/0")
type Logger interface {
Write(id string, level LogLevel, fmtstr string, args ...interface{})
WriteWithCallDepth(id string, level LogLevel, call_depth int, fmtstr string, args ...interface{})
Rotate()
Close()
}
type Service interface {
RunTask(wg *sync.WaitGroup) // blocking. run the actual task loop. it must call wg.Done() upon exit from itself.
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
WaitForTermination() // blocking. must wait until all services are stopped
WriteLog(id string, level LogLevel, fmtstr string, args ...interface{})
}
func TcpAddrStrClass(addr string) string {
// the string is supposed to be addr:port
if len(addr) > 0 {
var ap netip.AddrPort
var err error
ap, err = netip.ParseAddrPort(addr)
if err == nil {
if ap.Addr().Is6() { return "tcp6" }
if ap.Addr().Is4() { return "tcp4" }
}
}
return "tcp"
}
func TcpAddrClass(addr *net.TCPAddr) string {
if addr.AddrPort().Addr().Is4() {
return "tcp4"
} else {
return "tcp6"
}
}
2024-12-13 02:25:27 +09:00
func word_to_route_option(word string) RouteOption {
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)
}
return RouteOption(ROUTE_OPTION_UNSPEC)
}
2024-12-13 02:25:27 +09:00
func string_to_route_option(desc string) RouteOption {
var fld string
2024-12-13 02:25:27 +09:00
var option RouteOption
var p RouteOption
2024-12-13 02:25:27 +09:00
option = RouteOption(0)
for _, fld = range strings.Fields(desc) {
2024-12-13 02:25:27 +09:00
p = word_to_route_option(fld)
if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p }
2024-12-13 02:25:27 +09:00
option |= p
}
2024-12-13 02:25:27 +09:00
return option
}
2024-12-13 02:25:27 +09:00
func (option RouteOption) string() string {
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" }
if str == "" { return str }
return str[1:] // remove the leading space
}
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))
log.Close()
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
}
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]
}
func parse_duration_string(dur string) (time.Duration, error) {
// 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)
}
func WriteJsonRespHeader(w http.ResponseWriter, status_code int) int {
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
}
func WriteCssRespHeader(w http.ResponseWriter, status_code int) int {
w.Header().Set("Content-Type", "text/css")
w.WriteHeader(status_code)
return status_code
}
func WriteHtmlRespHeader(w http.ResponseWriter, status_code int) int {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(status_code)
return status_code
}
func WriteEmptyRespHeader(w http.ResponseWriter, status_code int) int {
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,
}
}