]> git.proxmox.com Git - mirror_frr.git/blame - lib/command_match.c
lib: Mostly complete matcher
[mirror_frr.git] / lib / command_match.c
CommitLineData
9d0662e0
QY
1#include <zebra.h>
2#include "memory.h"
18be0e59 3#include "vector.h"
9d0662e0
QY
4#include "command_match.h"
5
18be0e59
QY
6static enum match_type
7match_token (struct graph_node *node, char *token, enum filter_type filter)
8{
9 switch (node->type) {
10 case WORD_GN:
11 return cmd_word_match (node, filter, token);
12 case IPV4_GN:
13 return cmd_ipv4_match (token);
14 case IPV4_PREFIX_GN:
15 return cmd_ipv4_prefix_match (token);
16 case IPV6_GN:
17 return cmd_ipv6_match (token);
18 case IPV6_PREFIX_GN:
19 return cmd_ipv6_prefix_match (token);
20 case RANGE_GN:
21 return cmd_range_match (node, token);
22 case NUMBER_GN:
23 return node->value == atoi(token);
24 case VARIABLE_GN:
25 default:
26 return no_match;
27 }
28}
29
30/* Breaking up string into each command piece. I assume given
31 character is separated by a space character. Return value is a
32 vector which includes char ** data element. */
33static vector
34cmd_make_strvec (const char *string)
35{
36 const char *cp, *start;
37 char *token;
38 int strlen;
39 vector strvec;
40
41 if (string == NULL)
42 return NULL;
43
44 cp = string;
45
46 /* Skip white spaces. */
47 while (isspace ((int) *cp) && *cp != '\0')
48 cp++;
49
50 /* Return if there is only white spaces */
51 if (*cp == '\0')
52 return NULL;
53
54 if (*cp == '!' || *cp == '#')
55 return NULL;
56
57 /* Prepare return vector. */
58 strvec = vector_init (VECTOR_MIN_SIZE);
59
60 /* Copy each command piece and set into vector. */
61 while (1)
62 {
63 start = cp;
64 while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') &&
65 *cp != '\0')
66 cp++;
67 strlen = cp - start;
68 token = XMALLOC (MTYPE_STRVEC, strlen + 1);
69 memcpy (token, start, strlen);
70 *(token + strlen) = '\0';
71 vector_set (strvec, token);
72
73 while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') &&
74 *cp != '\0')
75 cp++;
76
77 if (*cp == '\0')
78 return strvec;
79 }
80}
81
82/**
83 * Adds all children that are reachable by one parser hop
84 * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
85 * nodes are treated as though their children are attached
86 * to their parent.
87 *
88 * @param[out] l the list to add the children to
89 * @param[in] node the node to get the children of
90 * @return the number of children added to the list
91 */
92static int
93add_nexthops(struct list *l, struct graph_node *node)
94{
95 int added = 0;
96 struct graph_node *child;
97 for (unsigned int i = 0; i < vector_active(node->children); i++)
98 {
99 child = vector_slot(node->children, i);
100 if (child->type == OPTION_GN || child->type == SELECTOR_GN || child->type == NUL_GN)
101 added += add_nexthops(l, child);
102 else {
103 listnode_add(l, child);
104 added++;
105 }
106 }
107 return added;
108}
109
110/**
111 * Compiles matches or next-hops for a given line of user input.
112 *
113 * Given a string of input and a start node for a matching DFA, runs the input
114 * against the DFA until the input is exhausted, there are no possible
115 * transitions, or both.
116 * If there are no further state transitions available, one of two scenarios is possible:
117 * - The end of input has been reached. This indicates a valid command.
118 * - The end of input has not yet been reached. The input does not match any command.
119 * If there are further transitions available, one of two scenarios is possible:
120 * - The current input is a valid prefix to a longer command
121 * - The current input matches a command
122 * - The current input matches a command, and is also a valid prefix to a longer command
123 *
124 * Any other states indicate a programming error.
125 *
126 * @param[in] start the start node of the DFA to match against
127 * @param[in] filter the filtering method
128 * @param[in] input the input string
129 * @return an array with two lists. The first list is
130 */
131struct list**
9d0662e0
QY
132match_command (struct graph_node *start, enum filter_type filter, const char *input)
133{
18be0e59
QY
134 // break command
135 vector command = cmd_make_strvec (input);
136
137 // pointer to next input token to match
138 char *token;
139
140 struct list *current = list_new(), // current nodes to match input token against
141 *matched = list_new(), // current nodes that match the input token
142 *next = list_new(); // possible next hops to current input token
143
144 // pointers used for iterating lists
145 struct graph_node *cnode;
146 struct listnode *node;
147
148 // add all children of start node to list
149 add_nexthops(next, start);
150
151 unsigned int idx;
152 for (idx = 0; idx < vector_active(command) && next->count > 0; idx++)
153 {
154 list_free (current);
155 current = next;
156 next = list_new();
157
158 token = vector_slot(command, idx);
159
160 list_delete_all_node(matched);
161
162 for (ALL_LIST_ELEMENTS_RO(current,node,cnode))
163 {
164 if (match_token(cnode, token, filter) == exact_match) {
165 listnode_add(matched, cnode);
166 add_nexthops(next, cnode);
167 }
168 }
169 }
170
171 /* Variable summary
172 * -----------------------------------------------------------------
173 * token = last input token processed
174 * idx = index in `command` of last token processed
175 * current = set of all transitions from the previous input token
176 * matched = set of all nodes reachable with current input
177 * next = set of all nodes reachable from all nodes in `matched`
178 */
179
180 struct list **result = malloc( 2 * sizeof(struct list *) );
181 result[0] = matched;
182 result[1] = next;
183
184 return result;
9d0662e0
QY
185}
186
187
188#define IPV4_ADDR_STR "0123456789."
189#define IPV4_PREFIX_STR "0123456789./"
190
191enum match_type
192cmd_ipv4_match (const char *str)
193{
194 struct sockaddr_in sin_dummy;
195
196 if (str == NULL)
197 return partly_match;
198
199 if (strspn (str, IPV4_ADDR_STR) != strlen (str))
200 return no_match;
201
202 if (inet_pton(AF_INET, str, &sin_dummy.sin_addr) != 1)
203 return no_match;
204
205 return exact_match;
206}
207
208enum match_type
209cmd_ipv4_prefix_match (const char *str)
210{
211 struct sockaddr_in sin_dummy;
212 const char *delim = "/\0";
213 char *dupe, *prefix, *mask, *context, *endptr;
214 int nmask = -1;
215
216 if (str == NULL)
217 return partly_match;
218
219 if (strspn (str, IPV4_PREFIX_STR) != strlen (str))
220 return no_match;
221
222 /* tokenize to address + mask */
223 dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
224 strncpy(dupe, str, strlen(str)+1);
225 prefix = strtok_r(dupe, delim, &context);
226 mask = strtok_r(NULL, delim, &context);
227
228 if (!mask)
229 return partly_match;
230
231 /* validate prefix */
232 if (inet_pton(AF_INET, prefix, &sin_dummy.sin_addr) != 1)
233 return no_match;
234
235 /* validate mask */
236 nmask = strtol (mask, &endptr, 10);
237 if (*endptr != '\0' || nmask < 0 || nmask > 32)
238 return no_match;
239
240 XFREE(MTYPE_TMP, dupe);
241
242 return exact_match;
243}
244
245#define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
246#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
247
248#ifdef HAVE_IPV6
249enum match_type
250cmd_ipv6_match (const char *str)
251{
252 struct sockaddr_in6 sin6_dummy;
253 int ret;
254
255 if (str == NULL)
256 return partly_match;
257
258 if (strspn (str, IPV6_ADDR_STR) != strlen (str))
259 return no_match;
260
261 ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
262
263 if (ret == 1)
264 return exact_match;
265
266 return no_match;
267}
268
269enum match_type
270cmd_ipv6_prefix_match (const char *str)
271{
272 struct sockaddr_in6 sin6_dummy;
273 const char *delim = "/\0";
274 char *dupe, *prefix, *mask, *context, *endptr;
275 int nmask = -1;
276
277 if (str == NULL)
278 return partly_match;
279
280 if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
281 return no_match;
282
283 /* tokenize to address + mask */
284 dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
285 strncpy(dupe, str, strlen(str)+1);
286 prefix = strtok_r(dupe, delim, &context);
287 mask = strtok_r(NULL, delim, &context);
288
289 if (!mask)
290 return partly_match;
291
292 /* validate prefix */
293 if (inet_pton(AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
294 return no_match;
295
296 /* validate mask */
297 nmask = strtol (mask, &endptr, 10);
298 if (*endptr != '\0' || nmask < 0 || nmask > 128)
299 return no_match;
300
301 XFREE(MTYPE_TMP, dupe);
302
303 return exact_match;
304}
305#endif
306
307enum match_type
308cmd_range_match (struct graph_node *rangenode, const char *str)
309{
310 char *endptr = NULL;
311 signed long long val;
312
313 if (str == NULL)
314 return 1;
315
316 val = strtoll (str, &endptr, 10);
317 if (*endptr != '\0')
318 return 0;
319 val = llabs(val);
320
321 if (val < rangenode->min || val > rangenode->max)
322 return no_match;
323 else
324 return exact_match;
325}
326
327enum match_type
328cmd_word_match(struct graph_node *wordnode,
329 enum filter_type filter,
330 const char *word)
331{
332 if (filter == FILTER_RELAXED)
333 if (!word || !strlen(word))
334 return partly_match;
335
336 if (!word)
337 return no_match;
338
339 if (filter == FILTER_RELAXED && !strncmp(wordnode->text, word, strlen(word)))
340 {
341 if (!strcmp(wordnode->text, word))
342 return exact_match;
343 return partly_match;
344 }
345 if (filter == FILTER_STRICT && !strcmp(wordnode->text, word))
346 return exact_match;
347
348 return no_match;
349}