diff --git a/service/cziti/ctun.go b/service/cziti/ctun.go index 5b5bb3ff..b47ff40e 100644 --- a/service/cziti/ctun.go +++ b/service/cziti/ctun.go @@ -50,7 +50,6 @@ extern void ZLOG(int level, char* msg); */ import "C" import ( - "errors" "golang.zx2c4.com/wireguard/tun" "io" "net" @@ -78,15 +77,16 @@ type tunnel struct { tunCtx C.tunneler_context } -var devMap = make(map[string]*tunnel) +//var devMap = make(map[string]*tunnel) +var theTun *tunnel -func HookupTun(dev tun.Device, dns []net.IP) (Tunnel, error) { +func HookupTun(dev tun.Device, dns []net.IP) error { log.Debug("in HookupTun ") defer log.Debug("exiting HookupTun") name, err := dev.Name() if err != nil { log.Error(err) - return nil, err + return err } drv := makeDriver(name) @@ -98,7 +98,8 @@ func HookupTun(dev tun.Device, dns []net.IP) (Tunnel, error) { writeQ: make(chan []byte, 64), readQ: make(chan []byte, 64), } - devMap[name] = t + //devMap[name] = t + theTun = t opts := (*C.tunneler_sdk_options)(C.calloc(1, C.sizeof_tunneler_sdk_options)) opts.netif_driver = drv @@ -115,7 +116,7 @@ func HookupTun(dev tun.Device, dns []net.IP) (Tunnel, error) { log.Infof("DNS server - waiting for startup") <-ready log.Infof("DNS server - ready") - return t, nil + return nil } func makeDriver(name string) C.netif_driver { @@ -130,15 +131,14 @@ func makeDriver(name string) C.netif_driver { //export netifWrite func netifWrite(h C.netif_handle, buf unsafe.Pointer, length C.size_t) C.ssize_t { - t, found := devMap[C.GoString(h.id)] + /*t, found := devMap[C.GoString(h.id)] if !found { - panic("should not be here") - return -1 - } + log.Panicf("An unexpected and unrecoverable error has occurred while calling netifWrite") + }*/ b := C.GoBytes(buf, C.int(length)) - t.writeQ <- b + theTun.writeQ <- b return C.ssize_t(len(b)) } @@ -151,29 +151,29 @@ func netifClose(h C.netif_handle) C.int { //export netifSetup func netifSetup(h C.netif_handle, l *C.uv_loop_t, packetCb C.packet_cb, ctx unsafe.Pointer) C.int { - t, found := devMap[C.GoString(h.id)] + /*t, found := devMap[C.GoString(h.id)] if !found { log.Error("should not be here") return -1 - } + }*/ - t.read = (*C.uv_async_t)(C.calloc(1, C.sizeof_uv_async_t)) - C.uv_async_init(l, t.read, C.uv_async_cb(C.readAsync)) - t.read.data = unsafe.Pointer(h) + theTun.read = (*C.uv_async_t)(C.calloc(1, C.sizeof_uv_async_t)) + C.uv_async_init(l, theTun.read, C.uv_async_cb(C.readAsync)) + theTun.read.data = unsafe.Pointer(h) log.Debugf("in netifSetup netif[%s] handle[%p] before", C.GoString(h.id), h) - t.idleR = (*C.uv_prepare_t)(C.calloc(1, C.sizeof_uv_prepare_t)) - C.uv_prepare_init(l, t.idleR) - t.idleR.data = unsafe.Pointer(h) - C.uv_prepare_start(t.idleR, C.uv_prepare_cb(C.readIdle)) + theTun.idleR = (*C.uv_prepare_t)(C.calloc(1, C.sizeof_uv_prepare_t)) + C.uv_prepare_init(l, theTun.idleR) + theTun.idleR.data = unsafe.Pointer(h) + C.uv_prepare_start(theTun.idleR, C.uv_prepare_cb(C.readIdle)) log.Debugf("in netifSetup netif[%s] handle[%p] after", C.GoString(h.id), h) - t.onPacket = packetCb - t.onPacketCtx = ctx - t.loop = l + theTun.onPacket = packetCb + theTun.onPacketCtx = ctx + theTun.loop = l - go t.runWriteLoop() - go t.runReadLoop() + go theTun.runWriteLoop() + go theTun.runReadLoop() return C.int(0) } @@ -181,7 +181,7 @@ func netifSetup(h C.netif_handle, l *C.uv_loop_t, packetCb C.packet_cb, ctx unsa func (t *tunnel) runReadLoop() { mtu, err := t.dev.MTU() if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "getting the MTU", err) } log.Debugf("starting tun read loop mtu=%d", mtu) defer log.Debug("tun read loop is done") @@ -193,7 +193,7 @@ func (t *tunnel) runReadLoop() { //that's fine... return } - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "reading from the tun device", err) } if len(t.readQ) == cap(t.readQ) { @@ -209,21 +209,20 @@ func (t *tunnel) runReadLoop() { //export readIdle func readIdle(idler *C.uv_prepare_t) { - dev := (*C.netif_handle_t)(idler.data) + /*dev := (*C.netif_handle_t)(idler.data) id := C.GoString(dev.id) t, found := devMap[id] if !found { - log.Debug("should not be here id = [%s]", id) - panic(errors.New("where is my tunnel?")) - } + log.Panicf("An unexpected and unrecoverable error has occurred while looking for tun in devMap") + }*/ - np := len(t.readQ) + np := len(theTun.readQ) for i := np; i > 0; i-- { - b := <-t.readQ + b := <-theTun.readQ buf := C.CBytes(b) - C.call_on_packet(buf, C.ssize_t(len(b)), t.onPacket, t.onPacketCtx) + C.call_on_packet(buf, C.ssize_t(len(b)), theTun.onPacket, theTun.onPacketCtx) C.free(buf) } @@ -246,7 +245,7 @@ func (t *tunnel) runWriteLoop() { n, err := t.dev.Write(p, 0) if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "writing to tun device", err) } if n < len(p) { @@ -255,11 +254,21 @@ func (t *tunnel) runWriteLoop() { } } } - +/* func (t *tunnel) AddIntercept(svcId string, service string, host string, port int, ctx unsafe.Pointer) { log.Debugf("about to add intercept for: %s, %s, %d", service, host, port) res := C.ziti_tunneler_intercept_v1(t.tunCtx, ctx, C.CString(svcId), C.CString(service), C.CString(host), C.int(port)) log.Debugf("intercept added: %v", res) +}*/ + +func AddIntercept(svcId string, service string, host string, port int, ctx unsafe.Pointer) { + /*for _, t := range devMap { + log.Debug("adding intercept for: %s, %s, %d", service, host, port) + t.AddIntercept(svcId, service, host, int(port), unsafe.Pointer(ctx)) + } + */ + log.Debugf("about to add intercept for: %s[] at %s:%d", service, svcId, host, port) + _ = C.ziti_tunneler_intercept_v1(theTun.tunCtx, ctx, C.CString(svcId), C.CString(service), C.CString(host), C.int(port)) } type RemoveWG struct { @@ -280,20 +289,13 @@ func remove_intercepts(async *C.uv_async_t) { //c := (*C.char)(async.data) rwg := (*RemoveWG)(async.data) - for _, t := range devMap { + /*for _, t := range devMap { //C.ZLOG(C.INFO, C.CString("bbb on uv thread - inside remove_intercepts")) C.ziti_tunneler_stop_intercepting(t.tunCtx, C.CString(rwg.SvcId)) - } + }*/ + C.ziti_tunneler_stop_intercepting(theTun.tunCtx, C.CString(rwg.SvcId)) //C.ZLOG(C.INFO, C.CString("ccc on uv thread - inside remove_intercepts")) C.uv_close((*C.uv_handle_t)(unsafe.Pointer(async)), C.uv_close_cb(C.free_async)) C.ZLOG(C.INFO, C.CString("on uv thread - completed remove_intercepts")) rwg.Wg.Done() } - -func AddIntercept(svcId string, service string, host string, port uint16, ctx *CZitiCtx) { - for _, t := range devMap { - log.Debug("adding intercept for: %s, %s, %d", service, host, port) - res := C.ziti_tunneler_intercept_v1(t.tunCtx, unsafe.Pointer(ctx.zctx), C.CString(svcId), C.CString(service), C.CString(host), C.int(port)) - log.Debugf("intercept added: %v", res) - } -} diff --git a/service/cziti/dns-proxy.go b/service/cziti/dns-proxy.go index ca305cfc..d0ae91aa 100644 --- a/service/cziti/dns-proxy.go +++ b/service/cziti/dns-proxy.go @@ -146,7 +146,7 @@ func runListener(ip net.IP, port int, reqch chan dnsreq) { server, err := net.ListenUDP(network, laddr) if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "udp listening on network", err) } for { @@ -159,7 +159,7 @@ func runListener(ip net.IP, port int, reqch chan dnsreq) { break } else { _ = server.Close() - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "reading a udp message", err) } } @@ -298,7 +298,7 @@ func runDNSproxy(dnsServers []string) { now := time.Now() for k, r := range reqs { if now.After(r.exp) { - log.Warn("a DNS request has expired - enable trace logging and reproduce this issue for more information") + log.Debugf("a DNS request has expired - enable trace logging and reproduce this issue for more information") log.Tracef(" expired DNS req: %s %s", dns.Type(r.req.Question[0].Qtype), r.req.Question[0].Name) delete(reqs, k) diff --git a/service/cziti/dns.go b/service/cziti/dns.go index 92b98557..fad7c906 100644 --- a/service/cziti/dns.go +++ b/service/cziti/dns.go @@ -43,7 +43,7 @@ type dnsImpl struct { cidr uint32 ipCount uint32 - serviceMap map[intercept]ctxService + serviceMap map[string]ctxService // dnsName -> ip address hostnameMap map[string]ctxIp @@ -57,6 +57,9 @@ type intercept struct { port uint16 isIp bool } +func (i intercept) String() string { + return fmt.Sprintf("%s:%d", i.host, i.port) +} type ctxIp struct { ctx *CZitiCtx @@ -69,6 +72,7 @@ type ctxService struct { name string serviceId string count int + icept intercept } func normalizeDnsName(dnsName string) string { @@ -84,28 +88,27 @@ func normalizeDnsName(dnsName string) string { // RegisterService will return the next ip address in the configured range. If the ip address is not // assigned to a hostname an error will also be returned indicating why. func (dns *dnsImpl) RegisterService(svcId string, dnsNameToReg string, port uint16, ctx *CZitiCtx, svcName string) (net.IP, error) { - var ipOrDnsName string - //check to see if host is an ip address - if so we want to intercept the ip. otherwise treat host as a host //name and register it in dns, obtain an ip and all that... ip := net.ParseIP(dnsNameToReg) - int := intercept{isIp: false} + icept := intercept{isIp: false, port: port} if ip == nil { - int.host = normalizeDnsName(dnsNameToReg) + icept.host = normalizeDnsName(dnsNameToReg) } else { - int.host = ip.String() + icept.host = ip.String() + icept.isIp = true } - - log.Infof("adding DNS for %s. service name %s@%s.", dnsNameToReg, svcName, int) + key := icept.String() + log.Infof("adding DNS for %s. service name %s@%s", dnsNameToReg, svcName, key) currentNetwork := C.GoString(ctx.Options.controller) // check to see if the hostname is mapped... - if foundIp, found := dns.hostnameMap[ipOrDnsName]; found { + if foundIp, found := dns.hostnameMap[icept.host]; found { ip = foundIp.ip // now check to see if the host *and* port are mapped... - if foundContext, found := dns.serviceMap[int]; found { + if foundContext, found := dns.serviceMap[key]; found { if foundIp.network != currentNetwork { // means the host:port are mapped to some other *identity* already. that's an invalid state return ip, fmt.Errorf("service mapping conflict for service name %s. %s:%d in %s is already mapped by another identity in %s", svcName, dnsNameToReg, port, currentNetwork, foundIp.network) @@ -120,24 +123,18 @@ func (dns *dnsImpl) RegisterService(svcId string, dnsNameToReg string, port uint log.Debugf("DNS mapping used by another service. total services using %s = %d", dnsNameToReg, foundContext.count) } else { // good - means the service can be mapped - dns.serviceMap[int] = ctxService{ - ctx: ctx, - name: svcName, - serviceId: svcId, - count: 1, - } } } else { // if not used at all - map it - if int.isIp { + if icept.isIp { err := dns.tun.AddRoute( net.IPNet{IP: ip, Mask: net.IPMask{255, 255, 255, 255}}, net.IP{0,0,0,0}, 1) if err != nil { - log.Errorf("Unexpected error adding a route to %s: %v", ipOrDnsName, err) + log.Errorf("Unexpected error adding a route to %s: %v", icept.host, err) } else { - log.Infof("adding route for ip:%s", ipOrDnsName) + log.Infof("adding route for ip:%s", icept.host) } } else { nextAddr := dns.cidr | atomic.AddUint32(&dns.ipCount, 1) @@ -145,19 +142,20 @@ func (dns *dnsImpl) RegisterService(svcId string, dnsNameToReg string, port uint binary.BigEndian.PutUint32(ip, nextAddr) log.Infof("mapping hostname %s to ip %s", dnsNameToReg, ip.String()) - dns.hostnameMap[ipOrDnsName] = ctxIp { + dns.hostnameMap[icept.host] = ctxIp { ip: ip, ctx: ctx, network: currentNetwork, } } + } - dns.serviceMap[int] = ctxService{ - ctx: ctx, - name: svcName, - serviceId: svcId, - count: 1, - } + dns.serviceMap[key] = ctxService{ + ctx: ctx, + name: svcName, + serviceId: svcId, + count: 1, + icept: icept, } return ip, nil @@ -168,21 +166,23 @@ func (dns *dnsImpl) Resolve(toResolve string) net.IP { return dns.hostnameMap[dnsName].ip } -func (dns *dnsImpl) DeregisterService(ctx *CZitiCtx, name string) { - log.Debugf("DEREG SERVICE: before for loop") - for int, sc := range dns.serviceMap { - log.Debugf("DEREG SERVICE: iterating sc.ctx=ctx %p=%p, sc.name=name %s=%s", sc.ctx, ctx, sc.name, name) +func (dns *dnsImpl) UnregisterService(ctx *CZitiCtx, name string) { + log.Debugf("UnregisterService named %s called for controller %s and identity: %s.", name, ctx.Controller(), ctx.Name()) + for key, sc := range dns.serviceMap { if sc.ctx == ctx && sc.name == name { sc.count -- if sc.count < 1 { - log.Infof("removing service named %s from DNS mapping known as %s", name, int) - if int.isIp { - err := dns.tun.RemoveRoute(net.IPNet{IP: net.ParseIP(int.host)}, net.IP{0, 0, 0, 0}) + icept := sc.icept + log.Infof("removing service named %s from DNS mapping known as %s", name, icept) + if icept.isIp { + err := dns.tun.RemoveRoute(net.IPNet{IP: net.ParseIP(icept.host)}, net.IP{0, 0, 0, 0}) if err != nil { - log.Warnf("Unexpected error removing route for %s", int.host) + log.Warnf("Unexpected error removing route for %s", icept.host) } + } else { + delete(dns.hostnameMap, icept.host) } - delete(dns.serviceMap, int) + delete(dns.serviceMap, key) } else { // another service is using the mapping - can't remove it yet so decrement log.Debugf("cannot remove dns mapping for %s yet - %d other services still use this hostname", name, sc.count) @@ -192,7 +192,7 @@ func (dns *dnsImpl) DeregisterService(ctx *CZitiCtx, name string) { log.Debugf("service context %p does not match event context address %p", sc.ctx, ctx) } } - log.Warnf("DEREG SERVICE: for loop completed. Match was not found for context %p and name %s", ctx, name) + log.Warnf("UnregisterService completed. Match was not found for context %p and name %s", ctx, name) } func (this *dnsImpl) GetService(ip net.IP, port uint16) (*CZitiCtx, string, error) { @@ -214,7 +214,7 @@ func (this *dnsImpl) GetService(ip net.IP, port uint16) (*CZitiCtx, string, erro func DnsInit(tun api.DesktopEdgeIface, ip string, maskBits int) { initOnce.Do(func() { - DNS.serviceMap = make(map[intercept]ctxService) + DNS.serviceMap = make(map[string]ctxService) //DNS.ipMap = make(map[uint32]string) DNS.hostnameMap = make(map[string]ctxIp) i := net.ParseIP(ip).To4() diff --git a/service/cziti/sdk.go b/service/cziti/sdk.go index 956a7f42..a591b2bb 100644 --- a/service/cziti/sdk.go +++ b/service/cziti/sdk.go @@ -170,7 +170,7 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, t found, ok := ctx.Services.Load(svcId) if ok { fs := found.(Service) - DNS.DeregisterService(ctx, name) + DNS.UnregisterService(ctx, name) ctx.Services.Delete(svcId) ServiceChanges <- ServiceChange{ Operation: REMOVED, @@ -187,7 +187,7 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, t found, ok := ctx.Services.Load(svcId) if ok && found != nil { log.Infof("service with id: %s, name: %s exists. updating service.", service.id, service.name) - DNS.DeregisterService(ctx, name) + DNS.UnregisterService(ctx, name) ctx.Services.Delete(svcId) } else { log.Debugf("service not found with id: %s, name: %s in context %d", svcId, name, &ctx) @@ -199,10 +199,11 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, t // int ziti_service_get_config(ziti_service *service, const char *cfg_type, void *cfg, int (*parser)(void *, const char *, size_t)) r := C.ziti_service_get_config(service, C.CString("ziti-tunneler-server.v1"), unsafe.Pointer(&v1Config), (*[0]byte)(C.parse_ziti_server_cfg_v1)) if (r == 0) { - for _, t := range devMap { + /*for _, t := range devMap { log.Debugf("setting up hosted service for service %s on %v:%v", service.name, v1Config.hostname, v1Config.port) C.ziti_tunneler_host_v1(C.tunneler_context(t.tunCtx), unsafe.Pointer(ctx.zctx), service.name, v1Config.protocol, v1Config.hostname, v1Config.port) - } + }*/ + C.ziti_tunneler_host_v1(C.tunneler_context(theTun.tunCtx), unsafe.Pointer(ctx.zctx), service.name, v1Config.protocol, v1Config.hostname, v1Config.port) C.free_ziti_server_cfg_v1(&v1Config) } else { log.Infof("service is bindable but doesn't have config? %s. flags: %v.", name, service.perm_flags) @@ -229,9 +230,10 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, t ownsIntercept = false } else { log.Infof("service intercept beginning for service: %s@%s:%d on ip %s", name, host, port, ip.String()) - for _, t := range devMap { + /*for _, t := range devMap { t.AddIntercept(svcId, name, ip.String(), port, unsafe.Pointer(ctx.zctx)) - } + }*/ + AddIntercept(svcId, name, ip.String(), port, unsafe.Pointer(ctx.zctx)) } added := Service{ Name: name, diff --git a/service/cziti/windns.go b/service/cziti/windns.go index 6c15284c..78af9308 100644 --- a/service/cziti/windns.go +++ b/service/cziti/windns.go @@ -55,7 +55,7 @@ func GetConnectionSpecificDomains() []string { err := cmd.Run() if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while running the command: %s %v", script, err) } var names []string @@ -85,7 +85,7 @@ func GetUpstreamDNS() []string { err := cmd.Run() if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while running the command: %s %v", script, err) } var names []string diff --git a/service/ziti-tunnel/service/ipc.go b/service/ziti-tunnel/service/ipc.go index 79e8f8b3..49979d58 100644 --- a/service/ziti-tunnel/service/ipc.go +++ b/service/ziti-tunnel/service/ipc.go @@ -44,6 +44,7 @@ import ( "strings" "sync" "time" + "unsafe" ) type Pipes struct { @@ -229,7 +230,7 @@ func (p *Pipes) shutdownConnections() { } func initialize(ipv4 string, ipv4mask int) error { - err := rts.CreateTun(ipv4, 32) + err := rts.CreateTun(ipv4, ipv4mask) if err != nil { return err } @@ -279,7 +280,7 @@ func setTunInfo(s *dto.TunnelStatus, ipv4 string, ipv4mask int) { func ipv4MaskString(m []byte) string { if len(m) != 4 { - panic("ipv4Mask: len must be 4 bytes") + log.Panicf("An unexpected and unrecoverable error has occurred. ipv4Mask: len must be 4 bytes") } return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) @@ -686,7 +687,7 @@ func newIdentity(newId dto.AddIdentity, out *json.Encoder) { err = enrolled.Close() if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "enrolling an identity", err) } newPath := newId.Id.Path() @@ -733,7 +734,7 @@ func connectIdentity(id *dto.Identity) { } else { log.Debugf("id [%s] is already connected - not reconnecting", id.Name) for _, s := range id.Services { - cziti.AddIntercept(s.Id, s.Name, s.InterceptHost, s.InterceptPort, id.ZitiContext) + cziti.AddIntercept(s.Id, s.Name, s.InterceptHost, int(s.InterceptPort), unsafe.Pointer(id.ZitiContext)) } id.Connected = true } @@ -772,7 +773,7 @@ func disconnectIdentity(id *dto.Identity) error { } cziti.RemoveIntercept(rwg) wg.Wait() - cziti.DNS.DeregisterService(id.ZitiContext, s.Name) + cziti.DNS.UnregisterService(id.ZitiContext, s.Name) } id.Connected = false } else { diff --git a/service/ziti-tunnel/service/state.go b/service/ziti-tunnel/service/state.go index c88b123a..de340a82 100644 --- a/service/ziti-tunnel/service/state.go +++ b/service/ziti-tunnel/service/state.go @@ -65,7 +65,7 @@ func (t *RuntimeState) SaveState() { cfg, err := os.OpenFile(config.File(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "opening the config file", err) } w := bufio.NewWriter(bufio.NewWriter(cfg)) enc := json.NewEncoder(w) @@ -75,7 +75,7 @@ func (t *RuntimeState) SaveState() { err = cfg.Close() if err != nil { - panic(err) + log.Panicf("An unexpected and unrecoverable error has occurred while %s: %v", "closing the config file", err) } log.Debug("state saved") } @@ -189,9 +189,9 @@ func (t *RuntimeState) CreateTun(ipv4 string, ipv4mask int) error { cziti.DnsInit(&rts, ipv4, ipv4mask) cziti.Start() - _, err = cziti.HookupTun(tunDevice, dns) + err = cziti.HookupTun(tunDevice, dns) if err != nil { - panic(err) + log.Panicf("An unrecoverable error has occurred! %v", err) } return nil } @@ -318,17 +318,17 @@ func (t *RuntimeState) Close() { func (t *RuntimeState) InterceptDNS() { - panic("implement me") + log.Panicf("implement me") } func (t *RuntimeState) ReleaseDNS() { - panic("implement me") + log.Panicf("implement me") } func (t *RuntimeState) InterceptIP() { - panic("implement me") + log.Panicf("implement me") } func (t *RuntimeState) ReleaseIP() { - panic("implement me") + log.Panicf("implement me") } diff --git a/service/ziti-tunnel/version.go b/service/ziti-tunnel/version.go index faca9e6e..f19c3115 100644 --- a/service/ziti-tunnel/version.go +++ b/service/ziti-tunnel/version.go @@ -21,8 +21,8 @@ package main const ( Version = "1.2.10" - Revision = "6f63c6dcbb37" - Branch = "release-1.2.10" + Revision = "2cc1a23f1ca8" + Branch = "fix-ips-treated-as-dns" BuildUser = "travis" - BuildDate = "2020-10-16 04:11:24" + BuildDate = "2020-10-16 18:39:17" )