summaryrefslogtreecommitdiffstats
path: root/src/vppinfra/unix-misc.c
blob: 54016ed74f4dc4c1c3330cab3da83dc930db410c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
 * Copyright (c) 2015 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
  Copyright (c) 2005 Eliot Dresselhaus

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <vppinfra/error.h>
#include <vppinfra/os.h>
#include <vppinfra/unix.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>		/* writev */
#include <fcntl.h>
#include <stdio.h>		/* for sprintf */

__thread uword __os_thread_index = 0;
__thread uword __os_numa_index = 0;

clib_error_t *
clib_file_n_bytes (char *file, uword * result)
{
  struct stat s;

  if (stat (file, &s) < 0)
    return clib_error_return_unix (0, "stat `%s'", file);

  if (S_ISREG (s.st_mode))
    *result = s.st_size;
  else
    *result = 0;

  return /* no error */ 0;
}

clib_error_t *
clib_file_read_contents (char *file, u8 * result, uword n_bytes)
{
  int fd = -1;
  uword n_done, n_left;
  clib_error_t *error = 0;
  u8 *v = result;

  if ((fd = open (file, 0)) < 0)
    return clib_error_return_unix (0, "open `%s'", file);

  n_left = n_bytes;
  n_done = 0;
  while (n_left > 0)
    {
      int n_read;
      if ((n_read = read (fd, v + n_done, n_left)) < 0)
	{
	  error = clib_error_return_unix (0, "open `%s'", file);
	  goto done;
	}

      /* End of file. */
      if (n_read == 0)
	break;

      n_left -= n_read;
      n_done += n_read;
    }

  if (n_left > 0)
    {
      error =
	clib_error_return (0,
			   " `%s' expected to read %wd bytes; read only %wd",
			   file, n_bytes, n_bytes - n_left);
      goto done;
    }

done:
  close (fd);
  return error;
}

clib_error_t *
clib_file_contents (char *file, u8 ** result)
{
  uword n_bytes;
  clib_error_t *error = 0;
  u8 *v;

  if ((error = clib_file_n_bytes (file, &n_bytes)))
    return error;

  v = 0;
  vec_resize (v, n_bytes);

  error = clib_file_read_contents (file, v, n_bytes);

  if (error)
    vec_free (v);
  else
    *result = v;

  return error;
}

clib_error_t *
unix_proc_file_contents (char *file, u8 ** result)
{
  u8 *rv = 0;
  uword pos;
  int bytes, fd;

  /* Unfortunately, stat(/proc/XXX) returns zero... */
  fd = open (file, O_RDONLY);

  if (fd < 0)
    return clib_error_return_unix (0, "open `%s'", file);

  vec_validate (rv, 4095);
  pos = 0;
  while (1)
    {
      bytes = read (fd, rv + pos, 4096);
      if (bytes < 0)
	{
	  close (fd);
	  vec_free (rv);
	  return clib_error_return_unix (0, "read '%s'", file);
	}

      if (bytes == 0)
	{
	  _vec_len (rv) = pos;
	  break;
	}
      pos += bytes;
      vec_validate (rv, pos + 4095);
    }
  *result = rv;
  close (fd);
  return 0;
}

void os_panic (void) __attribute__ ((weak));

void
os_panic (void)
{
  abort ();
}

void os_exit (int) __attribute__ ((weak));

void
os_exit (int code)
{
  exit (code);
}

void os_puts (u8 * string, uword string_length, uword is_error)
  __attribute__ ((weak));

void
os_puts (u8 * string, uword string_length, uword is_error)
{
  int cpu = os_get_thread_index ();
  int nthreads = os_get_nthreads ();
  char buf[64];
  int fd = is_error ? 2 : 1;
  struct iovec iovs[2];
  int n_iovs = 0;

  if (nthreads > 1)
    {
      snprintf (buf, sizeof (buf), "%d: ", cpu);

      iovs[n_iovs].iov_base = buf;
      iovs[n_iovs].iov_len = strlen (buf);
      n_iovs++;
    }

  iovs[n_iovs].iov_base = string;
  iovs[n_iovs].iov_len = string_length;
  n_iovs++;

  if (writev (fd, iovs, n_iovs) < 0)
    ;
}

void os_out_of_memory (void) __attribute__ ((weak));
void
os_out_of_memory (void)
{
  os_panic ();
}

uword os_get_nthreads (void) __attribute__ ((weak));
uword
os_get_nthreads (void)
{
  return 1;
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
_API_PROTO_UDP else: raise ValueError(f"proto error: {proto}") pattern = { u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"}, u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"}, u'src_port': {u'port': src_port, u'mask': 0xFFFF}, u'dst_port': {u'port': dst_port, u'mask': 0xFFFF}, u'protocol': {u'prot': flow_proto} } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip6_n_tuple_flow( node, src_ip, dst_ip, src_port, dst_port, proto, action, value=0): """Create IP6_N_TUPLE flow. :param node: DUT node. :param src_ip: Source IP6 address. :param dst_ip: Destination IP6 address. :param src_port: Source port. :param dst_port: Destination port. :param proto: TCP or UDP. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type src_ip: str :type dst_ip: str :type src_port: int :type dst_port: int :type proto: str :type action: str :type value: int :returns: flow_index. :rtype: int """ from vpp_papi import VppEnum flow = u"ip6_n_tuple" flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP6_N_TUPLE if proto == u"TCP": flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_TCP elif proto == u"UDP": flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP else: raise ValueError(f"proto error: {proto}") pattern = { u'src_addr': {u'addr': src_ip, \ u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"}, u'dst_addr': {u'addr': dst_ip, \ u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"}, u'src_port': {u'port': src_port, u'mask': 0xFFFF}, u'dst_port': {u'port': dst_port, u'mask': 0xFFFF}, u'protocol': {u'prot': flow_proto} } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip4_flow( node, src_ip, dst_ip, proto, action, value=0): """Create IP4 flow. :param node: DUT node. :param src_ip: Source IP4 address. :param dst_ip: Destination IP4 address. :param proto: TCP or UDP. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type src_ip: str :type dst_ip: str :type proto: str :type action: str :type value: int :returns: flow_index. :rtype: int """ from vpp_papi import VppEnum flow = u"ip4" flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP4 if proto == u"TCP": flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_TCP elif proto == u"UDP": flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP else: raise ValueError(f"proto error: {proto}") pattern = { u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"}, u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"}, u'protocol': {u'prot': flow_proto} } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip6_flow( node, src_ip, dst_ip, proto, action, value=0): """Create IP6 flow. :param node: DUT node. :param src_ip: Source IP6 address. :param dst_ip: Destination IP6 address. :param proto: TCP or UDP. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type src_ip: str :type dst_ip: str :type proto: str :type action: str :type value: int :returns: flow_index. :rtype: int """ from vpp_papi import VppEnum flow = u"ip6" flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP6 if proto == u"TCP": flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_TCP elif proto == u"UDP": flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP else: raise ValueError(f"proto error: {proto}") pattern = { u'src_addr': {u'addr': src_ip, \ u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"}, u'dst_addr': {'addr': dst_ip, \ u'mask': u"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"}, u'protocol': {u'prot': flow_proto} } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip4_gtpu_flow( node, src_ip, dst_ip, teid, action, value=0): """Create IP4_GTPU flow. :param node: DUT node. :param src_ip: Source IP4 address. :param dst_ip: Destination IP4 address. :param teid: Tunnel endpoint identifier. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type src_ip: str :type dst_ip: str :type teid: int :type action: str :type value: int :returns: flow_index. :rtype: int """ from vpp_papi import VppEnum flow = u"ip4_gtpu" flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP4_GTPU flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP pattern = { u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"}, u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"}, u'protocol': {u'prot': flow_proto}, u'teid': teid } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip4_ipsec_flow(node, proto, spi, action, value=0): """Create IP4_IPSEC flow. :param node: DUT node. :param proto: TCP or UDP. :param spi: Security Parameters Index. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type proto: str :type spi: int :type action: str :type value: int :returns: flow_index. :rtype: int """ from vpp_papi import VppEnum if proto == u"ESP": flow = u"ip4_ipsec_esp" flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_ESP flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP4_IPSEC_ESP elif proto == u"AH": flow = u"ip4_ipsec_ah" flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_AH flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP4_IPSEC_AH else: raise ValueError(f"proto error: {proto}") pattern = { u'protocol': {u'prot': flow_proto}, u'spi': spi } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip4_l2tp_flow(node, session_id, action, value=0): """Create IP4_L2TPV3OIP flow. :param node: DUT node. :param session_id: PPPoE session ID :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type session_id: int :type action: str :type value: int :returns: flow_index. :rtype: int """ from vpp_papi import VppEnum flow = u"ip4_l2tpv3oip" flow_proto = 115 # IP_API_PROTO_L2TP flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP4_L2TPV3OIP pattern = { u'protocol': {u'prot': flow_proto}, u'session_id': session_id } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_create_ip4_vxlan_flow(node, src_ip, dst_ip, vni, action, value=0): """Create IP4_VXLAN flow. :param node: DUT node. :param src_ip: Source IP4 address. :param dst_ip: Destination IP4 address. :param vni: Virtual network instance. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type src_ip: str :type dst_ip: str :type vni: int :type action: str :type value: int :returns: flow_index. """ from vpp_papi import VppEnum flow = u"ip4_vxlan" flow_type = VppEnum.vl_api_flow_type_t.FLOW_TYPE_IP4_VXLAN flow_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP pattern = { u'src_addr': {u'addr': src_ip, u'mask': u"255.255.255.255"}, u'dst_addr': {u'addr': dst_ip, u'mask': u"255.255.255.255"}, u'dst_port': {u'port': 4789, 'mask': 0xFFFF}, u'protocol': {u'prot': flow_proto}, u'vni': vni } flow_index = FlowUtil.vpp_flow_add( node, flow, flow_type, pattern, action, value) return flow_index @staticmethod def vpp_flow_add(node, flow, flow_type, pattern, action, value=0): """Flow add. :param node: DUT node. :param flow: Name of flow. :param flow_type: Type of flow. :param pattern: Pattern of flow. :param action: Mark, drop or redirect-to-queue. :param value: Action value. :type node: dict :type node: str :type flow_type: str :type pattern: dict :type action: str :type value: int :returns: flow_index. :rtype: int :raises ValueError: If action type is not supported. """ from vpp_papi import VppEnum cmd = u"flow_add" if action == u"redirect-to-queue": flow_rule = { u'type': flow_type, u'actions': VppEnum.vl_api_flow_action_t.\ FLOW_ACTION_REDIRECT_TO_QUEUE, u'redirect_queue': value, u'flow': {flow : pattern} } elif action == u"mark": flow_rule = { u'type': flow_type, u'actions': VppEnum.vl_api_flow_action_t.FLOW_ACTION_MARK, u'mark_flow_id': value, u'flow': {flow : pattern} } elif action == u"drop": flow_rule = { u'type': flow_type, u'actions': VppEnum.vl_api_flow_action_t.FLOW_ACTION_DROP, u'flow': {flow : pattern} } else: raise ValueError(f"Unsupported action type: {action}") err_msg = f"Failed to create {flow} flow on host {node[u'host']}." args = dict(flow=flow_rule) flow_index = -1 with PapiSocketExecutor(node) as papi_exec: reply = papi_exec.add(cmd, **args).get_reply(err_msg) flow_index = reply[u"flow_index"] return flow_index @staticmethod def vpp_flow_enable(node, interface, flow_index=0): """Flow enable. :param node: DUT node. :param interface: Interface sw_if_index. :param flow_index: Flow index. :type node: dict :type interface: int :type flow_index: int :returns: Nothing. """ from vpp_papi import VppEnum cmd = u"flow_enable" sw_if_index = Topology.get_interface_sw_index(node, interface) args = dict( flow_index=int(flow_index), hw_if_index=int(sw_if_index) ) err_msg = u"Failed to enable flow on host {node[u'host']}" with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args).get_reply(err_msg) @staticmethod def vpp_flow_disable(node, interface, flow_index=0): """Flow disable. :param node: DUT node. :param interface: Interface sw_if_index. :param flow_index: Flow index. :type node: dict :type interface: int :type flow_index: int :returns: Nothing. """ from vpp_papi import VppEnum cmd = u"flow_disable" sw_if_index = Topology.get_interface_sw_index(node, interface) args = dict( flow_index=int(flow_index), hw_if_index=int(sw_if_index) ) err_msg = u"Failed to disable flow on host {node[u'host']}" with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args).get_reply(err_msg) @staticmethod def vpp_flow_del(node, flow_index=0): """Flow delete. :param node: DUT node. :param flow_index: Flow index. :type node: dict :type flow_index: int :returns: Nothing. """ from vpp_papi import VppEnum cmd = u"flow_del" args = dict( flow_index=int(flow_index) ) err_msg = u"Failed to delete flow on host {node[u'host']}" with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args).get_reply(err_msg) @staticmethod def vpp_show_flow_entry(node): """Show flow entry. :param node: DUT node. :type node: dict :returns: flow entry. :rtype: str """ from vpp_papi import VppEnum cmd = u"vppctl show flow entry" err_msg = u"Failed to show flow on host {node[u'host']}" stdout, _ = exec_cmd_no_error( node, cmd, sudo=False, message=err_msg, retries=120 ) return stdout.strip() @staticmethod def vpp_verify_flow_action( node, action, value, src_mac=u"11:22:33:44:55:66", dst_mac=u"11:22:33:44:55:66", src_ip=None, dst_ip=None): """Verify the correctness of the flow action. :param node: DUT node. :param action: Action. :param value: Action value. :param src_mac: Source mac address. :param dst_mac: Destination mac address. :param src_ip: Source IP address. :param dst_ip: Destination IP address. :type node: dict :type action: str :type value: int :type src_mac: str :type dst_mac: str :type src_ip: str :type dst_ip: str :returns: Nothing. :raises RuntimeError: If the verification of flow action fails. :raises ValueError: If action type is not supported. """ from vpp_papi import VppEnum err_msg = f"Failed to show trace on host {node[u'host']}" cmd = u"vppctl show trace" stdout, _ = exec_cmd_no_error( node, cmd, sudo=False, message=err_msg, retries=120 ) err_info = f"Verify flow {action} failed" if src_ip is None: expected_str = f"{src_mac} -> {dst_mac}" else: src_ip = ip_address(src_ip) dst_ip = ip_address(dst_ip) expected_str = f"{src_ip} -> {dst_ip}" if action == u"drop": if expected_str in stdout: raise RuntimeError(err_info) elif action == u"redirect-to-queue": if f"queue {value}" not in stdout \ and f"qid {value}" not in stdout: raise RuntimeError(err_info) if expected_str not in stdout: raise RuntimeError(err_info) elif action == u"mark": if u"PKT_RX_FDIR" not in stdout and u"flow-id 1" not in stdout: raise RuntimeError(err_info) if expected_str not in stdout: raise RuntimeError(err_info) else: raise ValueError(f"Unsupported action type: {action}")