github.com/vmware/govmomi@v0.51.0/simulator/simulator.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package simulator 6 7 import ( 8 "bytes" 9 "context" 10 "crypto/tls" 11 "crypto/x509" 12 "encoding/json" 13 "encoding/pem" 14 "fmt" 15 "io" 16 "log" 17 "net" 18 "net/http" 19 "net/url" 20 "os" 21 "path" 22 "reflect" 23 "sort" 24 "strconv" 25 "strings" 26 27 "github.com/google/uuid" 28 29 "github.com/vmware/govmomi/find" 30 "github.com/vmware/govmomi/object" 31 "github.com/vmware/govmomi/simulator/internal" 32 "github.com/vmware/govmomi/vim25" 33 "github.com/vmware/govmomi/vim25/mo" 34 "github.com/vmware/govmomi/vim25/soap" 35 "github.com/vmware/govmomi/vim25/types" 36 "github.com/vmware/govmomi/vim25/xml" 37 ) 38 39 var ( 40 // Trace when set to true, writes SOAP traffic to stderr 41 Trace = false 42 43 // TraceFile is the output file when Trace = true 44 TraceFile = os.Stderr 45 46 // DefaultLogin for authentication 47 DefaultLogin = url.UserPassword("user", "pass") 48 ) 49 50 // Method encapsulates a decoded SOAP client request 51 type Method struct { 52 Name string 53 This types.ManagedObjectReference 54 Header soap.Header 55 Body types.AnyType 56 } 57 58 // Service decodes incoming requests and dispatches to a Handler 59 type Service struct { 60 sdk map[string]*Registry 61 funcs []handleFunc 62 delay *DelayConfig 63 64 readAll func(io.Reader) ([]byte, error) 65 66 Context *Context 67 Listen *url.URL 68 TLS *tls.Config 69 ServeMux *http.ServeMux 70 // RegisterEndpoints will initialize any endpoints added via RegisterEndpoint 71 RegisterEndpoints bool 72 } 73 74 // Server provides a simulator Service over HTTP 75 type Server struct { 76 *internal.Server 77 URL *url.URL 78 Tunnel int 79 80 caFile string 81 } 82 83 // New returns an initialized simulator Service instance 84 func New(ctx *Context, instance *ServiceInstance) *Service { 85 s := &Service{ 86 Context: ctx, 87 readAll: io.ReadAll, 88 sdk: make(map[string]*Registry), 89 } 90 s.Context.svc = s 91 return s 92 } 93 94 func (s *Service) client() *vim25.Client { 95 c, _ := vim25.NewClient(context.Background(), s) 96 return c 97 } 98 99 type serverFaultBody struct { 100 Reason *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"` 101 } 102 103 func (b *serverFaultBody) Fault() *soap.Fault { return b.Reason } 104 105 func serverFault(msg string) soap.HasFault { 106 return &serverFaultBody{Reason: Fault(msg, &types.InvalidRequest{})} 107 } 108 109 // Fault wraps the given message and fault in a soap.Fault 110 func Fault(msg string, fault types.BaseMethodFault) *soap.Fault { 111 f := &soap.Fault{ 112 Code: "ServerFaultCode", 113 String: msg, 114 } 115 116 f.Detail.Fault = fault 117 118 return f 119 } 120 121 func tracef(format string, v ...any) { 122 if Trace { 123 log.Printf(format, v...) 124 } 125 } 126 127 func (s *Service) call(ctx *Context, method *Method) soap.HasFault { 128 handler := ctx.Map.Get(method.This) 129 session := ctx.Session 130 ctx.Caller = &method.This 131 132 if ctx.Map.Handler != nil { 133 h, fault := ctx.Map.Handler(ctx, method) 134 if fault != nil { 135 return &serverFaultBody{Reason: Fault("", fault)} 136 } 137 if h != nil { 138 handler = h 139 } 140 } 141 142 if session == nil { 143 switch method.Name { 144 case 145 "Login", "LoginByToken", "LoginExtensionByCertificate", "CloneSession", // SessionManager 146 "RetrieveServiceContent", "RetrieveInternalContent", "PbmRetrieveServiceContent", // ServiceContent 147 "Fetch", "RetrieveProperties", "RetrievePropertiesEx", // PropertyCollector 148 "List", // lookup service 149 "GetTrustedCertificates": // ssoadmin 150 // ok for now, TODO: authz 151 default: 152 fault := &types.NotAuthenticated{ 153 NoPermission: types.NoPermission{ 154 Object: &method.This, 155 PrivilegeId: "System.View", 156 }, 157 } 158 return &serverFaultBody{Reason: Fault("", fault)} 159 } 160 } else { 161 // Prefer the Session.Registry, ServiceContent.PropertyCollector filter field for example is per-session 162 if h := session.Get(method.This); h != nil { 163 handler = h 164 } 165 } 166 167 if handler == nil { 168 msg := fmt.Sprintf("managed object not found: %s", method.This) 169 log.Print(msg) 170 fault := &types.ManagedObjectNotFound{Obj: method.This} 171 return &serverFaultBody{Reason: Fault(msg, fault)} 172 } 173 174 // Lowercase methods can't be accessed outside their package 175 name := strings.Title(method.Name) 176 177 if strings.HasSuffix(name, vTaskSuffix) { 178 // Make golint happy renaming "Foo_Task" -> "FooTask" 179 name = name[:len(name)-len(vTaskSuffix)] + sTaskSuffix 180 } 181 182 m := reflect.ValueOf(handler).MethodByName(name) 183 if !m.IsValid() { 184 msg := fmt.Sprintf("%s does not implement: %s", method.This, method.Name) 185 log.Print(msg) 186 fault := &types.MethodNotFound{Receiver: method.This, Method: method.Name} 187 return &serverFaultBody{Reason: Fault(msg, fault)} 188 } 189 190 if e, ok := handler.(mo.Entity); ok { 191 for _, dm := range e.Entity().DisabledMethod { 192 if name == dm { 193 msg := fmt.Sprintf("%s method is disabled: %s", method.This, method.Name) 194 fault := &types.MethodDisabled{} 195 return &serverFaultBody{Reason: Fault(msg, fault)} 196 } 197 } 198 } 199 200 // We have a valid call. Introduce a delay if requested 201 if s.delay != nil { 202 s.delay.delay(method.Name) 203 } 204 205 var args, res []reflect.Value 206 if m.Type().NumIn() == 2 { 207 args = append(args, reflect.ValueOf(ctx)) 208 } 209 args = append(args, reflect.ValueOf(method.Body)) 210 ctx.Map.WithLock(ctx, handler, func() { 211 res = m.Call(args) 212 }) 213 214 return res[0].Interface().(soap.HasFault) 215 } 216 217 // internalSession is the session for use by the in-memory client (Service.RoundTrip) 218 var internalSession = &Session{ 219 UserSession: types.UserSession{ 220 Key: uuid.New().String(), 221 }, 222 Registry: NewRegistry(), 223 } 224 225 // RoundTrip implements the soap.RoundTripper interface in process. 226 // Rather than encode/decode SOAP over HTTP, this implementation uses reflection. 227 func (s *Service) RoundTrip(ctx context.Context, request, response soap.HasFault) error { 228 field := func(r soap.HasFault, name string) reflect.Value { 229 return reflect.ValueOf(r).Elem().FieldByName(name) 230 } 231 232 // Every struct passed to soap.RoundTrip has "Req" and "Res" fields 233 req := field(request, "Req") 234 235 // Every request has a "This" field. 236 this := req.Elem().FieldByName("This") 237 // Copy request body 238 body := reflect.New(req.Type().Elem()) 239 deepCopy(req.Interface(), body.Interface()) 240 241 method := &Method{ 242 Name: req.Elem().Type().Name(), 243 This: this.Interface().(types.ManagedObjectReference), 244 Body: body.Interface(), 245 } 246 247 res := s.call(&Context{ 248 Map: s.Context.Map, 249 Context: ctx, 250 Session: &Session{ 251 UserSession: internalSession.UserSession, 252 Registry: internalSession.Registry, 253 Map: s.Context.Map, 254 }, 255 }, method) 256 257 if err := res.Fault(); err != nil { 258 return soap.WrapSoapFault(err) 259 } 260 261 field(response, "Res").Set(field(res, "Res")) 262 263 return nil 264 } 265 266 // soapEnvelope is a copy of soap.Envelope, with namespace changed to "soapenv", 267 // and additional namespace attributes required by some client libraries. 268 // Go still has issues decoding with such a namespace, but encoding is ok. 269 type soapEnvelope struct { 270 XMLName xml.Name `xml:"soapenv:Envelope"` 271 Enc string `xml:"xmlns:soapenc,attr"` 272 Env string `xml:"xmlns:soapenv,attr"` 273 XSD string `xml:"xmlns:xsd,attr"` 274 XSI string `xml:"xmlns:xsi,attr"` 275 Body any `xml:"soapenv:Body"` 276 } 277 278 type faultDetail struct { 279 Fault types.AnyType 280 } 281 282 // soapFault is a copy of soap.Fault, with the same changes as soapEnvelope 283 type soapFault struct { 284 XMLName xml.Name `xml:"soapenv:Fault"` 285 Code string `xml:"faultcode"` 286 String string `xml:"faultstring"` 287 Detail struct { 288 Fault *faultDetail 289 } `xml:"detail"` 290 } 291 292 // MarshalXML renames the start element from "Fault" to "${Type}Fault" 293 func (d *faultDetail) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 294 kind := reflect.TypeOf(d.Fault).Elem().Name() 295 start.Name.Local = kind + "Fault" 296 start.Attr = append(start.Attr, 297 xml.Attr{ 298 Name: xml.Name{Local: "xmlns"}, 299 Value: "urn:" + vim25.Namespace, 300 }, 301 xml.Attr{ 302 Name: xml.Name{Local: "xsi:type"}, 303 Value: kind, 304 }) 305 return e.EncodeElement(d.Fault, start) 306 } 307 308 // response sets xml.Name.Space when encoding Body. 309 // Note that namespace is intentionally omitted in the vim25/methods/methods.go Body.Res field tags. 310 type response struct { 311 Namespace string 312 Body soap.HasFault 313 } 314 315 func (r *response) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 316 body := reflect.ValueOf(r.Body).Elem() 317 val := body.FieldByName("Res") 318 if !val.IsValid() { 319 return fmt.Errorf("%T: invalid response type (missing 'Res' field)", r.Body) 320 } 321 if val.IsNil() { 322 return fmt.Errorf("%T: invalid response (nil 'Res' field)", r.Body) 323 } 324 325 // Default response namespace 326 ns := "urn:" + r.Namespace 327 // Override namespace from struct tag if defined 328 field, _ := body.Type().FieldByName("Res") 329 if tag := field.Tag.Get("xml"); tag != "" { 330 tags := strings.Split(tag, " ") 331 if len(tags) > 0 && strings.HasPrefix(tags[0], "urn") { 332 ns = tags[0] 333 } 334 } 335 336 res := xml.StartElement{ 337 Name: xml.Name{ 338 Space: ns, 339 Local: val.Elem().Type().Name(), 340 }, 341 } 342 if err := e.EncodeToken(start); err != nil { 343 return err 344 } 345 if err := e.EncodeElement(val.Interface(), res); err != nil { 346 return err 347 } 348 return e.EncodeToken(start.End()) 349 } 350 351 // About generates some info about the simulator. 352 func (s *Service) About(w http.ResponseWriter, r *http.Request) { 353 var about struct { 354 Methods []string `json:"methods"` 355 Types []string `json:"types"` 356 } 357 358 seen := make(map[string]bool) 359 360 f := reflect.TypeOf((*soap.HasFault)(nil)).Elem() 361 362 for _, sdk := range s.sdk { 363 for _, obj := range sdk.objects { 364 kind := obj.Reference().Type 365 if seen[kind] { 366 continue 367 } 368 seen[kind] = true 369 370 about.Types = append(about.Types, kind) 371 372 t := reflect.TypeOf(obj) 373 for i := 0; i < t.NumMethod(); i++ { 374 m := t.Method(i) 375 if seen[m.Name] { 376 continue 377 } 378 seen[m.Name] = true 379 380 in := m.Type.NumIn() 381 if in < 2 || in > 3 { // at least 2 params (receiver and request), optionally a 3rd param (context) 382 continue 383 } 384 if m.Type.NumOut() != 1 || m.Type.Out(0) != f { // all methods return soap.HasFault 385 continue 386 } 387 388 about.Methods = append(about.Methods, strings.Replace(m.Name, "Task", "_Task", 1)) 389 } 390 } 391 } 392 393 sort.Strings(about.Methods) 394 sort.Strings(about.Types) 395 396 w.Header().Set("Content-Type", "application/json") 397 enc := json.NewEncoder(w) 398 enc.SetIndent("", " ") 399 _ = enc.Encode(&about) 400 } 401 402 var endpoints []func(*Service, *Registry) 403 404 // RegisterEndpoint funcs are called after the Server is initialized if Service.RegisterEndpoints=true. 405 // Such a func would typically register a SOAP endpoint via Service.RegisterSDK or REST endpoint via Service.Handle 406 func RegisterEndpoint(endpoint func(*Service, *Registry)) { 407 endpoints = append(endpoints, endpoint) 408 } 409 410 // Handle registers the handler for the given pattern with Service.ServeMux. 411 func (s *Service) Handle(pattern string, handler http.Handler) { 412 s.ServeMux.Handle(pattern, handler) 413 // Not ideal, but avoids having to add yet another registration mechanism 414 // so we can optionally use vapi/simulator internally. 415 if m, ok := handler.(tagManager); ok { 416 s.sdk[vim25.Path].tagManager = m 417 } 418 } 419 420 type muxHandleFunc interface { 421 HandleFunc(string, func(http.ResponseWriter, *http.Request)) 422 } 423 424 type handleFunc struct { 425 pattern string 426 handler func(http.ResponseWriter, *http.Request) 427 } 428 429 // HandleFunc dispatches to http.ServeMux.HandleFunc after all endpoints have been registered. 430 // This allows dispatching to an endpoint's HandleFunc impl, such as vapi/simulator for example. 431 func (s *Service) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { 432 s.funcs = append(s.funcs, handleFunc{pattern, handler}) 433 } 434 435 // RegisterSDK adds an HTTP handler for the Registry's Path and Namespace. 436 // If r.Path is already registered, r's objects are added to the existing Registry. 437 // An optional set of aliases can be provided to register the same handler for 438 // multiple paths. 439 func (s *Service) RegisterSDK(r *Registry, alias ...string) { 440 if existing, ok := s.sdk[r.Path]; ok { 441 for id, obj := range r.objects { 442 existing.objects[id] = obj 443 } 444 return 445 } 446 447 if s.ServeMux == nil { 448 s.ServeMux = http.NewServeMux() 449 } 450 451 s.sdk[r.Path] = r 452 s.ServeMux.HandleFunc(r.Path, s.ServeSDK) 453 454 for _, p := range alias { 455 s.sdk[p] = r 456 s.ServeMux.HandleFunc(p, s.ServeSDK) 457 } 458 } 459 460 // StatusSDK can be used to simulate an /sdk HTTP response code other than 200. 461 // The value of StatusSDK is restored to http.StatusOK after 1 response. 462 // This can be useful to test vim25.Retry() for example. 463 var StatusSDK = http.StatusOK 464 465 // ServeSDK implements the http.Handler interface 466 func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) { 467 if r.Method != http.MethodPost { 468 w.WriteHeader(http.StatusMethodNotAllowed) 469 return 470 } 471 472 if StatusSDK != http.StatusOK { 473 w.WriteHeader(StatusSDK) 474 StatusSDK = http.StatusOK // reset 475 return 476 } 477 478 body, err := s.readAll(r.Body) 479 _ = r.Body.Close() 480 if err != nil { 481 log.Printf("error reading body: %s", err) 482 w.WriteHeader(http.StatusBadRequest) 483 return 484 } 485 486 if Trace { 487 fmt.Fprintf(TraceFile, "Request: %s\n", string(body)) 488 } 489 490 ctx := &Context{ 491 req: r, 492 res: w, 493 svc: s, 494 495 Map: s.sdk[r.URL.Path], 496 Context: context.Background(), 497 } 498 499 var res soap.HasFault 500 var soapBody any 501 502 method, err := UnmarshalBody(ctx.Map.typeFunc, body) 503 if err != nil { 504 res = serverFault(err.Error()) 505 } else { 506 ctx.Header = method.Header 507 if method.Name == "Fetch" { 508 // Redirect any Fetch method calls to the PropertyCollector singleton 509 method.This = ctx.Map.content().PropertyCollector 510 } 511 ctx.Map.WithLock(ctx, ctx.sessionManager(), ctx.mapSession) 512 res = s.call(ctx, method) 513 } 514 515 if f := res.Fault(); f != nil { 516 w.WriteHeader(http.StatusInternalServerError) 517 518 // the generated method/*Body structs use the '*soap.Fault' type, 519 // so we need our own Body type to use the modified '*soapFault' type. 520 soapBody = struct { 521 Fault *soapFault 522 }{ 523 &soapFault{ 524 Code: f.Code, 525 String: f.String, 526 Detail: struct { 527 Fault *faultDetail 528 }{&faultDetail{f.Detail.Fault}}, 529 }, 530 } 531 } else { 532 w.WriteHeader(http.StatusOK) 533 534 soapBody = &response{ctx.Map.Namespace, res} 535 } 536 537 var out bytes.Buffer 538 539 fmt.Fprint(&out, xml.Header) 540 e := xml.NewEncoder(&out) 541 err = e.Encode(&soapEnvelope{ 542 Enc: "http://schemas.xmlsoap.org/soap/encoding/", 543 Env: "http://schemas.xmlsoap.org/soap/envelope/", 544 XSD: "http://www.w3.org/2001/XMLSchema", 545 XSI: "http://www.w3.org/2001/XMLSchema-instance", 546 Body: soapBody, 547 }) 548 if err == nil { 549 err = e.Flush() 550 } 551 552 if err != nil { 553 log.Printf("error encoding %s response: %s", method.Name, err) 554 return 555 } 556 557 if Trace { 558 fmt.Fprintf(TraceFile, "Response: %s\n", out.String()) 559 } 560 561 _, _ = w.Write(out.Bytes()) 562 } 563 564 func (s *Service) findDatastore(query url.Values) (*Datastore, error) { 565 ctx := context.Background() 566 567 finder := find.NewFinder(s.client(), false) 568 dc, err := finder.DatacenterOrDefault(ctx, query.Get("dcPath")) 569 if err != nil { 570 return nil, err 571 } 572 573 finder.SetDatacenter(dc) 574 575 ds, err := finder.DatastoreOrDefault(ctx, query.Get("dsName")) 576 if err != nil { 577 return nil, err 578 } 579 580 return s.Context.Map.Get(ds.Reference()).(*Datastore), nil 581 } 582 583 const folderPrefix = "/folder/" 584 585 // ServeDatastore handler for Datastore access via /folder path. 586 func (s *Service) ServeDatastore(w http.ResponseWriter, r *http.Request) { 587 ds, ferr := s.findDatastore(r.URL.Query()) 588 if ferr != nil { 589 log.Printf("failed to locate datastore with query params: %s", r.URL.RawQuery) 590 w.WriteHeader(http.StatusNotFound) 591 return 592 } 593 594 if strings.Contains(r.URL.Path, "..") { 595 w.WriteHeader(http.StatusBadRequest) 596 return 597 } 598 599 r.URL.Path = strings.TrimPrefix(r.URL.Path, folderPrefix) 600 p := ds.resolve(s.Context, r.URL.Path) 601 602 switch r.Method { 603 case http.MethodPost: 604 _, err := os.Stat(p) 605 if err == nil { 606 // File exists 607 w.WriteHeader(http.StatusConflict) 608 return 609 } 610 611 // File does not exist, fallthrough to create via PUT logic 612 fallthrough 613 case http.MethodPut: 614 dir := path.Dir(p) 615 _ = os.MkdirAll(dir, 0700) 616 617 f, err := os.Create(p) 618 if err != nil { 619 log.Printf("failed to %s '%s': %s", r.Method, p, err) 620 w.WriteHeader(http.StatusInternalServerError) 621 return 622 } 623 defer f.Close() 624 625 _, _ = io.Copy(f, r.Body) 626 default: 627 // ds.resolve() may have translated vsan friendly name to uuid, 628 // apply the same to the Request.URL.Path 629 r.URL.Path = strings.TrimPrefix(p, ds.Summary.Url) 630 631 fs := http.FileServer(http.Dir(ds.Summary.Url)) 632 633 fs.ServeHTTP(w, r) 634 } 635 } 636 637 // ServiceVersions handler for the /sdk/vimServiceVersions.xml path. 638 func (s *Service) ServiceVersions(w http.ResponseWriter, r *http.Request) { 639 const versions = xml.Header + `<namespaces version="1.0"> 640 <namespace> 641 <name>urn:vim25</name> 642 <version>%s</version> 643 <priorVersions> 644 <version>6.0</version> 645 <version>5.5</version> 646 </priorVersions> 647 </namespace> 648 </namespaces> 649 ` 650 fmt.Fprintf(w, versions, s.Context.Map.content().About.ApiVersion) 651 } 652 653 // ServiceVersionsVsan handler for the /sdk/vsanServiceVersions.xml path. 654 func (s *Service) ServiceVersionsVsan(w http.ResponseWriter, r *http.Request) { 655 const versions = xml.Header + `<namespaces version="1.0"> 656 <namespace> 657 <name>urn:vsan</name> 658 <version>%s</version> 659 <priorVersions> 660 <version>6.7</version> 661 <version>6.6</version> 662 </priorVersions> 663 </namespace> 664 </namespaces> 665 ` 666 fmt.Fprintf(w, versions, s.Context.Map.content().About.ApiVersion) 667 } 668 669 // defaultIP returns addr.IP if specified, otherwise attempts to find a non-loopback ipv4 IP 670 func defaultIP(addr *net.TCPAddr) string { 671 if !addr.IP.IsUnspecified() { 672 return addr.IP.String() 673 } 674 675 nics, err := net.Interfaces() 676 if err != nil { 677 return addr.IP.String() 678 } 679 680 for _, nic := range nics { 681 if nic.Name == "docker0" || strings.HasPrefix(nic.Name, "vmnet") { 682 continue 683 } 684 addrs, aerr := nic.Addrs() 685 if aerr != nil { 686 continue 687 } 688 for _, addr := range addrs { 689 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() { 690 if ip.IP.To4() != nil { 691 return ip.IP.String() 692 } 693 } 694 } 695 } 696 697 return addr.IP.String() 698 } 699 700 // NewServer returns an http Server instance for the given service 701 func (s *Service) NewServer() *Server { 702 ctx := s.Context 703 s.RegisterSDK(ctx.Map, ctx.Map.Path+"/vimService") 704 705 mux := s.ServeMux 706 mux.HandleFunc(ctx.Map.Path+"/vimServiceVersions.xml", s.ServiceVersions) 707 mux.HandleFunc(ctx.Map.Path+"/vsanServiceVersions.xml", s.ServiceVersionsVsan) 708 mux.HandleFunc(folderPrefix, s.ServeDatastore) 709 mux.HandleFunc(guestPrefix, ServeGuest) 710 mux.HandleFunc(nfcPrefix, ServeNFC) 711 mux.HandleFunc("/about", s.About) 712 713 if s.Listen == nil { 714 s.Listen = new(url.URL) 715 } 716 ts := internal.NewUnstartedServer(mux, s.Listen.Host) 717 addr := ts.Listener.Addr().(*net.TCPAddr) 718 port := strconv.Itoa(addr.Port) 719 u := &url.URL{ 720 Scheme: "http", 721 Host: net.JoinHostPort(defaultIP(addr), port), 722 Path: ctx.Map.Path, 723 } 724 if s.TLS != nil { 725 u.Scheme += "s" 726 } 727 728 // Redirect clients to this http server, rather than HostSystem.Name 729 ctx.sessionManager().ServiceHostName = u.Host 730 731 // Add vcsim config to OptionManager for use by SDK handlers (see lookup/simulator for example) 732 m := ctx.Map.OptionManager() 733 for i := range m.Setting { 734 setting := m.Setting[i].GetOptionValue() 735 736 if strings.HasSuffix(setting.Key, ".uri") { 737 // Rewrite any URIs with vcsim's host:port 738 endpoint, err := url.Parse(setting.Value.(string)) 739 if err == nil { 740 endpoint.Scheme = u.Scheme 741 endpoint.Host = u.Host 742 setting.Value = endpoint.String() 743 } 744 } 745 } 746 m.UpdateOptions(&types.UpdateOptions{ 747 ChangedValue: []types.BaseOptionValue{&types.OptionValue{ 748 Key: "vcsim.server.url", 749 Value: u.String(), 750 }}, 751 }) 752 753 u.User = s.Listen.User 754 if u.User == nil { 755 u.User = DefaultLogin 756 } 757 s.Listen = u 758 759 if s.RegisterEndpoints { 760 for i := range endpoints { 761 endpoints[i](s, ctx.Map) 762 } 763 } 764 765 for _, f := range s.funcs { 766 pattern := &url.URL{Path: f.pattern} 767 endpoint, _ := s.ServeMux.Handler(&http.Request{URL: pattern}) 768 769 if mux, ok := endpoint.(muxHandleFunc); ok { 770 mux.HandleFunc(f.pattern, f.handler) // e.g. vapi/simulator 771 } else { 772 s.ServeMux.HandleFunc(f.pattern, f.handler) 773 } 774 } 775 776 if s.TLS != nil { 777 ts.TLS = s.TLS 778 ts.TLS.ClientAuth = tls.RequestClientCert // Used by SessionManager.LoginExtensionByCertificate 779 ctx.Map.SessionManager().TLS = func() *tls.Config { return ts.TLS } 780 ts.StartTLS() 781 } else { 782 ts.Start() 783 } 784 785 return &Server{ 786 Server: ts, 787 URL: u, 788 } 789 } 790 791 // Certificate returns the TLS certificate for the Server if started with TLS enabled. 792 // This method will panic if TLS is not enabled for the server. 793 func (s *Server) Certificate() *x509.Certificate { 794 // By default httptest.StartTLS uses http/internal.LocalhostCert, which we can access here: 795 cert, _ := x509.ParseCertificate(s.TLS.Certificates[0].Certificate[0]) 796 return cert 797 } 798 799 // CertificateInfo returns Server.Certificate() as object.HostCertificateInfo 800 func (s *Server) CertificateInfo() *object.HostCertificateInfo { 801 info := new(object.HostCertificateInfo) 802 info.FromCertificate(s.Certificate()) 803 return info 804 } 805 806 // CertificateFile returns a file name, where the file contains the PEM encoded Server.Certificate. 807 // The temporary file is removed when Server.Close() is called. 808 func (s *Server) CertificateFile() (string, error) { 809 if s.caFile != "" { 810 return s.caFile, nil 811 } 812 813 f, err := os.CreateTemp("", "vcsim-") 814 if err != nil { 815 return "", err 816 } 817 defer f.Close() 818 819 s.caFile = f.Name() 820 cert := s.Certificate() 821 return s.caFile, pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) 822 } 823 824 // proxy tunnels SDK requests 825 func (s *Server) proxy(w http.ResponseWriter, r *http.Request) { 826 if r.Method != http.MethodConnect { 827 http.Error(w, "", http.StatusMethodNotAllowed) 828 return 829 } 830 831 dst, err := net.Dial("tcp", s.URL.Host) 832 if err != nil { 833 http.Error(w, err.Error(), http.StatusBadGateway) 834 return 835 } 836 w.WriteHeader(http.StatusOK) 837 838 src, _, err := w.(http.Hijacker).Hijack() 839 if err != nil { 840 http.Error(w, err.Error(), http.StatusBadRequest) 841 return 842 } 843 844 go io.Copy(src, dst) 845 go func() { 846 _, _ = io.Copy(dst, src) 847 _ = dst.Close() 848 _ = src.Close() 849 }() 850 } 851 852 // StartTunnel runs an HTTP proxy for tunneling SDK requests that require TLS client certificate authentication. 853 func (s *Server) StartTunnel() error { 854 tunnel := &http.Server{ 855 Addr: fmt.Sprintf("%s:%d", s.URL.Hostname(), s.Tunnel), 856 Handler: http.HandlerFunc(s.proxy), 857 } 858 859 l, err := net.Listen("tcp", tunnel.Addr) 860 if err != nil { 861 return err 862 } 863 864 if s.Tunnel == 0 { 865 s.Tunnel = l.Addr().(*net.TCPAddr).Port 866 } 867 868 // Set client proxy port (defaults to vCenter host port 80 in real life) 869 q := s.URL.Query() 870 q.Set("GOVMOMI_TUNNEL_PROXY_PORT", strconv.Itoa(s.Tunnel)) 871 s.URL.RawQuery = q.Encode() 872 873 go tunnel.Serve(l) 874 875 return nil 876 } 877 878 // Close shuts down the server and blocks until all outstanding 879 // requests on this server have completed. 880 func (s *Server) Close() { 881 s.Server.Close() 882 if s.caFile != "" { 883 _ = os.Remove(s.caFile) 884 } 885 } 886 887 var ( 888 vim25MapType = types.TypeFunc() 889 ) 890 891 func defaultMapType(name string) (reflect.Type, bool) { 892 typ, ok := vim25MapType(name) 893 if !ok { 894 // See TestIssue945, in which case Go does not resolve the namespace and name == "ns1:TraversalSpec" 895 // Without this hack, the SelectSet would be all nil's 896 kind := strings.SplitN(name, ":", 2) 897 if len(kind) == 2 { 898 typ, ok = vim25MapType(kind[1]) 899 } 900 } 901 return typ, ok 902 } 903 904 // Element can be used to defer decoding of an XML node. 905 type Element struct { 906 start xml.StartElement 907 inner struct { 908 Content string `xml:",innerxml"` 909 } 910 typeFunc func(string) (reflect.Type, bool) 911 } 912 913 func (e *Element) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 914 e.start = start 915 916 return d.DecodeElement(&e.inner, &start) 917 } 918 919 func (e *Element) decoder() *xml.Decoder { 920 decoder := xml.NewDecoder(strings.NewReader(e.inner.Content)) 921 decoder.TypeFunc = e.typeFunc // required to decode interface types 922 return decoder 923 } 924 925 func (e *Element) Decode(val any) error { 926 return e.decoder().DecodeElement(val, &e.start) 927 } 928 929 // UnmarshalBody extracts the Body from a soap.Envelope and unmarshals to the corresponding govmomi type 930 func UnmarshalBody(typeFunc func(string) (reflect.Type, bool), data []byte) (*Method, error) { 931 body := &Element{typeFunc: typeFunc} 932 req := soap.Envelope{ 933 Header: &soap.Header{ 934 Security: new(Element), 935 }, 936 Body: body, 937 } 938 939 err := xml.Unmarshal(data, &req) 940 if err != nil { 941 return nil, fmt.Errorf("xml.Unmarshal: %s", err) 942 } 943 944 var start xml.StartElement 945 var ok bool 946 decoder := body.decoder() 947 948 for { 949 tok, derr := decoder.Token() 950 if derr != nil { 951 return nil, fmt.Errorf("decoding: %s", derr) 952 } 953 if start, ok = tok.(xml.StartElement); ok { 954 break 955 } 956 } 957 958 if !ok { 959 return nil, fmt.Errorf("decoding: method token not found") 960 } 961 962 kind := start.Name.Local 963 rtype, ok := typeFunc(kind) 964 if !ok { 965 return nil, fmt.Errorf("no vmomi type defined for '%s'", kind) 966 } 967 968 val := reflect.New(rtype).Interface() 969 970 err = decoder.DecodeElement(val, &start) 971 if err != nil { 972 return nil, fmt.Errorf("decoding %s: %s", kind, err) 973 } 974 975 method := &Method{Name: kind, Header: *req.Header, Body: val} 976 977 field := reflect.ValueOf(val).Elem().FieldByName("This") 978 979 method.This = field.Interface().(types.ManagedObjectReference) 980 981 return method, nil 982 } 983 984 func newInvalidStateFault(format string, args ...any) *types.InvalidState { 985 msg := fmt.Sprintf(format, args...) 986 return &types.InvalidState{ 987 VimFault: types.VimFault{ 988 MethodFault: types.MethodFault{ 989 FaultCause: &types.LocalizedMethodFault{ 990 Fault: &types.SystemErrorFault{ 991 Reason: msg, 992 }, 993 LocalizedMessage: msg, 994 }, 995 }, 996 }, 997 } 998 }