1package hcn
2
3import (
4	"encoding/json"
5	"errors"
6
7	"github.com/Microsoft/go-winio/pkg/guid"
8	"github.com/Microsoft/hcsshim/internal/interop"
9	"github.com/sirupsen/logrus"
10)
11
12// IpConfig is assoicated with an endpoint
13type IpConfig struct {
14	IpAddress    string `json:",omitempty"`
15	PrefixLength uint8  `json:",omitempty"`
16}
17
18// EndpointFlags are special settings on an endpoint.
19type EndpointFlags uint32
20
21var (
22	// EndpointFlagsNone is the default.
23	EndpointFlagsNone EndpointFlags
24	// EndpointFlagsRemoteEndpoint means that an endpoint is on another host.
25	EndpointFlagsRemoteEndpoint EndpointFlags = 1
26)
27
28// HostComputeEndpoint represents a network endpoint
29type HostComputeEndpoint struct {
30	Id                   string           `json:"ID,omitempty"`
31	Name                 string           `json:",omitempty"`
32	HostComputeNetwork   string           `json:",omitempty"` // GUID
33	HostComputeNamespace string           `json:",omitempty"` // GUID
34	Policies             []EndpointPolicy `json:",omitempty"`
35	IpConfigurations     []IpConfig       `json:",omitempty"`
36	Dns                  Dns              `json:",omitempty"`
37	Routes               []Route          `json:",omitempty"`
38	MacAddress           string           `json:",omitempty"`
39	Flags                EndpointFlags    `json:",omitempty"`
40	SchemaVersion        SchemaVersion    `json:",omitempty"`
41}
42
43// EndpointResourceType are the two different Endpoint settings resources.
44type EndpointResourceType string
45
46var (
47	// EndpointResourceTypePolicy is for Endpoint Policies. Ex: ACL, NAT
48	EndpointResourceTypePolicy EndpointResourceType = "Policy"
49	// EndpointResourceTypePort is for Endpoint Port settings.
50	EndpointResourceTypePort EndpointResourceType = "Port"
51)
52
53// ModifyEndpointSettingRequest is the structure used to send request to modify an endpoint.
54// Used to update policy/port on an endpoint.
55type ModifyEndpointSettingRequest struct {
56	ResourceType EndpointResourceType `json:",omitempty"` // Policy, Port
57	RequestType  RequestType          `json:",omitempty"` // Add, Remove, Update, Refresh
58	Settings     json.RawMessage      `json:",omitempty"`
59}
60
61type PolicyEndpointRequest struct {
62	Policies []EndpointPolicy `json:",omitempty"`
63}
64
65func getEndpoint(endpointGuid guid.GUID, query string) (*HostComputeEndpoint, error) {
66	// Open endpoint.
67	var (
68		endpointHandle   hcnEndpoint
69		resultBuffer     *uint16
70		propertiesBuffer *uint16
71	)
72	hr := hcnOpenEndpoint(&endpointGuid, &endpointHandle, &resultBuffer)
73	if err := checkForErrors("hcnOpenEndpoint", hr, resultBuffer); err != nil {
74		return nil, err
75	}
76	// Query endpoint.
77	hr = hcnQueryEndpointProperties(endpointHandle, query, &propertiesBuffer, &resultBuffer)
78	if err := checkForErrors("hcnQueryEndpointProperties", hr, resultBuffer); err != nil {
79		return nil, err
80	}
81	properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
82	// Close endpoint.
83	hr = hcnCloseEndpoint(endpointHandle)
84	if err := checkForErrors("hcnCloseEndpoint", hr, nil); err != nil {
85		return nil, err
86	}
87	// Convert output to HostComputeEndpoint
88	var outputEndpoint HostComputeEndpoint
89	if err := json.Unmarshal([]byte(properties), &outputEndpoint); err != nil {
90		return nil, err
91	}
92	return &outputEndpoint, nil
93}
94
95func enumerateEndpoints(query string) ([]HostComputeEndpoint, error) {
96	// Enumerate all Endpoint Guids
97	var (
98		resultBuffer   *uint16
99		endpointBuffer *uint16
100	)
101	hr := hcnEnumerateEndpoints(query, &endpointBuffer, &resultBuffer)
102	if err := checkForErrors("hcnEnumerateEndpoints", hr, resultBuffer); err != nil {
103		return nil, err
104	}
105
106	endpoints := interop.ConvertAndFreeCoTaskMemString(endpointBuffer)
107	var endpointIds []guid.GUID
108	err := json.Unmarshal([]byte(endpoints), &endpointIds)
109	if err != nil {
110		return nil, err
111	}
112
113	var outputEndpoints []HostComputeEndpoint
114	for _, endpointGuid := range endpointIds {
115		endpoint, err := getEndpoint(endpointGuid, query)
116		if err != nil {
117			return nil, err
118		}
119		outputEndpoints = append(outputEndpoints, *endpoint)
120	}
121	return outputEndpoints, nil
122}
123
124func createEndpoint(networkId string, endpointSettings string) (*HostComputeEndpoint, error) {
125	networkGuid, err := guid.FromString(networkId)
126	if err != nil {
127		return nil, errInvalidNetworkID
128	}
129	// Open network.
130	var networkHandle hcnNetwork
131	var resultBuffer *uint16
132	hr := hcnOpenNetwork(&networkGuid, &networkHandle, &resultBuffer)
133	if err := checkForErrors("hcnOpenNetwork", hr, resultBuffer); err != nil {
134		return nil, err
135	}
136	// Create endpoint.
137	endpointId := guid.GUID{}
138	var endpointHandle hcnEndpoint
139	hr = hcnCreateEndpoint(networkHandle, &endpointId, endpointSettings, &endpointHandle, &resultBuffer)
140	if err := checkForErrors("hcnCreateEndpoint", hr, resultBuffer); err != nil {
141		return nil, err
142	}
143	// Query endpoint.
144	hcnQuery := defaultQuery()
145	query, err := json.Marshal(hcnQuery)
146	if err != nil {
147		return nil, err
148	}
149	var propertiesBuffer *uint16
150	hr = hcnQueryEndpointProperties(endpointHandle, string(query), &propertiesBuffer, &resultBuffer)
151	if err := checkForErrors("hcnQueryEndpointProperties", hr, resultBuffer); err != nil {
152		return nil, err
153	}
154	properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
155	// Close endpoint.
156	hr = hcnCloseEndpoint(endpointHandle)
157	if err := checkForErrors("hcnCloseEndpoint", hr, nil); err != nil {
158		return nil, err
159	}
160	// Close network.
161	hr = hcnCloseNetwork(networkHandle)
162	if err := checkForErrors("hcnCloseNetwork", hr, nil); err != nil {
163		return nil, err
164	}
165	// Convert output to HostComputeEndpoint
166	var outputEndpoint HostComputeEndpoint
167	if err := json.Unmarshal([]byte(properties), &outputEndpoint); err != nil {
168		return nil, err
169	}
170	return &outputEndpoint, nil
171}
172
173func modifyEndpoint(endpointId string, settings string) (*HostComputeEndpoint, error) {
174	endpointGuid, err := guid.FromString(endpointId)
175	if err != nil {
176		return nil, errInvalidEndpointID
177	}
178	// Open endpoint
179	var (
180		endpointHandle   hcnEndpoint
181		resultBuffer     *uint16
182		propertiesBuffer *uint16
183	)
184	hr := hcnOpenEndpoint(&endpointGuid, &endpointHandle, &resultBuffer)
185	if err := checkForErrors("hcnOpenEndpoint", hr, resultBuffer); err != nil {
186		return nil, err
187	}
188	// Modify endpoint
189	hr = hcnModifyEndpoint(endpointHandle, settings, &resultBuffer)
190	if err := checkForErrors("hcnModifyEndpoint", hr, resultBuffer); err != nil {
191		return nil, err
192	}
193	// Query endpoint.
194	hcnQuery := defaultQuery()
195	query, err := json.Marshal(hcnQuery)
196	if err != nil {
197		return nil, err
198	}
199	hr = hcnQueryEndpointProperties(endpointHandle, string(query), &propertiesBuffer, &resultBuffer)
200	if err := checkForErrors("hcnQueryEndpointProperties", hr, resultBuffer); err != nil {
201		return nil, err
202	}
203	properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
204	// Close endpoint.
205	hr = hcnCloseEndpoint(endpointHandle)
206	if err := checkForErrors("hcnCloseEndpoint", hr, nil); err != nil {
207		return nil, err
208	}
209	// Convert output to HostComputeEndpoint
210	var outputEndpoint HostComputeEndpoint
211	if err := json.Unmarshal([]byte(properties), &outputEndpoint); err != nil {
212		return nil, err
213	}
214	return &outputEndpoint, nil
215}
216
217func deleteEndpoint(endpointId string) error {
218	endpointGuid, err := guid.FromString(endpointId)
219	if err != nil {
220		return errInvalidEndpointID
221	}
222	var resultBuffer *uint16
223	hr := hcnDeleteEndpoint(&endpointGuid, &resultBuffer)
224	if err := checkForErrors("hcnDeleteEndpoint", hr, resultBuffer); err != nil {
225		return err
226	}
227	return nil
228}
229
230// ListEndpoints makes a call to list all available endpoints.
231func ListEndpoints() ([]HostComputeEndpoint, error) {
232	hcnQuery := defaultQuery()
233	endpoints, err := ListEndpointsQuery(hcnQuery)
234	if err != nil {
235		return nil, err
236	}
237	return endpoints, nil
238}
239
240// ListEndpointsQuery makes a call to query the list of available endpoints.
241func ListEndpointsQuery(query HostComputeQuery) ([]HostComputeEndpoint, error) {
242	queryJson, err := json.Marshal(query)
243	if err != nil {
244		return nil, err
245	}
246
247	endpoints, err := enumerateEndpoints(string(queryJson))
248	if err != nil {
249		return nil, err
250	}
251	return endpoints, nil
252}
253
254// ListEndpointsOfNetwork queries the list of endpoints on a network.
255func ListEndpointsOfNetwork(networkId string) ([]HostComputeEndpoint, error) {
256	hcnQuery := defaultQuery()
257	// TODO: Once query can convert schema, change to {HostComputeNetwork:networkId}
258	mapA := map[string]string{"VirtualNetwork": networkId}
259	filter, err := json.Marshal(mapA)
260	if err != nil {
261		return nil, err
262	}
263	hcnQuery.Filter = string(filter)
264
265	return ListEndpointsQuery(hcnQuery)
266}
267
268// GetEndpointByID returns an endpoint specified by Id
269func GetEndpointByID(endpointId string) (*HostComputeEndpoint, error) {
270	hcnQuery := defaultQuery()
271	mapA := map[string]string{"ID": endpointId}
272	filter, err := json.Marshal(mapA)
273	if err != nil {
274		return nil, err
275	}
276	hcnQuery.Filter = string(filter)
277
278	endpoints, err := ListEndpointsQuery(hcnQuery)
279	if err != nil {
280		return nil, err
281	}
282	if len(endpoints) == 0 {
283		return nil, EndpointNotFoundError{EndpointID: endpointId}
284	}
285	return &endpoints[0], err
286}
287
288// GetEndpointByName returns an endpoint specified by Name
289func GetEndpointByName(endpointName string) (*HostComputeEndpoint, error) {
290	hcnQuery := defaultQuery()
291	mapA := map[string]string{"Name": endpointName}
292	filter, err := json.Marshal(mapA)
293	if err != nil {
294		return nil, err
295	}
296	hcnQuery.Filter = string(filter)
297
298	endpoints, err := ListEndpointsQuery(hcnQuery)
299	if err != nil {
300		return nil, err
301	}
302	if len(endpoints) == 0 {
303		return nil, EndpointNotFoundError{EndpointName: endpointName}
304	}
305	return &endpoints[0], err
306}
307
308// Create Endpoint.
309func (endpoint *HostComputeEndpoint) Create() (*HostComputeEndpoint, error) {
310	logrus.Debugf("hcn::HostComputeEndpoint::Create id=%s", endpoint.Id)
311
312	if endpoint.HostComputeNamespace != "" {
313		return nil, errors.New("endpoint create error, endpoint json HostComputeNamespace is read only and should not be set")
314	}
315
316	jsonString, err := json.Marshal(endpoint)
317	if err != nil {
318		return nil, err
319	}
320
321	logrus.Debugf("hcn::HostComputeEndpoint::Create JSON: %s", jsonString)
322	endpoint, hcnErr := createEndpoint(endpoint.HostComputeNetwork, string(jsonString))
323	if hcnErr != nil {
324		return nil, hcnErr
325	}
326	return endpoint, nil
327}
328
329// Delete Endpoint.
330func (endpoint *HostComputeEndpoint) Delete() error {
331	logrus.Debugf("hcn::HostComputeEndpoint::Delete id=%s", endpoint.Id)
332
333	if err := deleteEndpoint(endpoint.Id); err != nil {
334		return err
335	}
336	return nil
337}
338
339// ModifyEndpointSettings updates the Port/Policy of an Endpoint.
340func ModifyEndpointSettings(endpointId string, request *ModifyEndpointSettingRequest) error {
341	logrus.Debugf("hcn::HostComputeEndpoint::ModifyEndpointSettings id=%s", endpointId)
342
343	endpointSettingsRequest, err := json.Marshal(request)
344	if err != nil {
345		return err
346	}
347
348	_, err = modifyEndpoint(endpointId, string(endpointSettingsRequest))
349	if err != nil {
350		return err
351	}
352	return nil
353}
354
355// ApplyPolicy applies a Policy (ex: ACL) on the Endpoint.
356func (endpoint *HostComputeEndpoint) ApplyPolicy(requestType RequestType, endpointPolicy PolicyEndpointRequest) error {
357	logrus.Debugf("hcn::HostComputeEndpoint::ApplyPolicy id=%s", endpoint.Id)
358
359	settingsJson, err := json.Marshal(endpointPolicy)
360	if err != nil {
361		return err
362	}
363	requestMessage := &ModifyEndpointSettingRequest{
364		ResourceType: EndpointResourceTypePolicy,
365		RequestType:  requestType,
366		Settings:     settingsJson,
367	}
368
369	return ModifyEndpointSettings(endpoint.Id, requestMessage)
370}
371
372// NamespaceAttach modifies a Namespace to add an endpoint.
373func (endpoint *HostComputeEndpoint) NamespaceAttach(namespaceId string) error {
374	return AddNamespaceEndpoint(namespaceId, endpoint.Id)
375}
376
377// NamespaceDetach modifies a Namespace to remove an endpoint.
378func (endpoint *HostComputeEndpoint) NamespaceDetach(namespaceId string) error {
379	return RemoveNamespaceEndpoint(namespaceId, endpoint.Id)
380}
381