tests: Add test suite for packets.h.
[openvswitch] / lib / cfm.c
1 /*
2  * Copyright (c) 2010 Nicira Networks.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <config.h>
18 #include "cfm.h"
19
20 #include <stdint.h>
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include "flow.h"
25 #include "hash.h"
26 #include "hmap.h"
27 #include "ofpbuf.h"
28 #include "packets.h"
29 #include "poll-loop.h"
30 #include "timeval.h"
31 #include "vlog.h"
32
33 VLOG_DEFINE_THIS_MODULE(cfm);
34
35 #define CCM_OPCODE 1              /* CFM message opcode meaning CCM. */
36 #define DEST_ADDR  UINT64_C(0x0180C2000030) /* MD level 0 CCM destination. */
37
38 struct cfm_internal {
39     struct cfm cfm;
40     uint32_t seq;          /* The sequence number of our last CCM. */
41
42     uint8_t ccm_interval;  /* The CCM transmission interval. */
43     int ccm_interval_ms;   /* 'ccm_interval' in milliseconds. */
44
45     long long ccm_sent;    /* The time we last sent a CCM. */
46     long long fault_check; /* The time we last checked for faults. */
47 };
48
49 static int
50 ccm_interval_to_ms(uint8_t interval)
51 {
52     switch (interval) {
53     case 0:  NOT_REACHED(); /* Explicitly not supported by 802.1ag. */
54     case 1:  return 3;      /* Not recommended due to timer resolution. */
55     case 2:  return 10;     /* Not recommended due to timer resolution. */
56     case 3:  return 100;
57     case 4:  return 1000;
58     case 5:  return 10000;
59     case 6:  return 60000;
60     case 7:  return 600000;
61     default: NOT_REACHED(); /* Explicitly not supported by 802.1ag. */
62     }
63
64     NOT_REACHED();
65 }
66
67 static uint8_t
68 ms_to_ccm_interval(int interval_ms)
69 {
70     uint8_t i;
71
72     for (i = 7; i > 0; i--) {
73         if (ccm_interval_to_ms(i) <= interval_ms) {
74             return i;
75         }
76     }
77
78     return 1;
79 }
80
81 static struct cfm_internal *
82 cfm_to_internal(struct cfm *cfm)
83 {
84     return CONTAINER_OF(cfm, struct cfm_internal, cfm);
85 }
86
87 static uint32_t
88 hash_mpid(uint8_t mpid)
89 {
90     return hash_int(mpid, 0);
91 }
92
93 static bool
94 cfm_is_valid_mpid(uint32_t mpid)
95 {
96     /* 802.1ag specification requires MPIDs to be within the range [1, 8191] */
97     return mpid >= 1 && mpid <= 8191;
98 }
99
100 static struct remote_mp *
101 lookup_remote_mp(const struct hmap *hmap, uint16_t mpid)
102 {
103     struct remote_mp *rmp;
104
105     HMAP_FOR_EACH_IN_BUCKET (rmp, node, hash_mpid(mpid), hmap) {
106         if (rmp->mpid == mpid) {
107             return rmp;
108         }
109     }
110
111     return NULL;
112 }
113
114 static struct ofpbuf *
115 compose_ccm(struct cfm_internal *cfmi)
116 {
117     struct ccm *ccm;
118     struct ofpbuf *packet;
119     struct eth_header *eth;
120
121     packet = ofpbuf_new(ETH_HEADER_LEN + CCM_LEN + 2);
122
123     ofpbuf_reserve(packet, 2);
124
125     eth = ofpbuf_put_zeros(packet, ETH_HEADER_LEN);
126     ccm = ofpbuf_put_zeros(packet, CCM_LEN);
127
128     eth_addr_from_uint64(DEST_ADDR, eth->eth_dst);
129     memcpy(eth->eth_src, cfmi->cfm.eth_src, sizeof eth->eth_src);
130     eth->eth_type = htons(ETH_TYPE_CFM);
131
132     ccm->mdlevel_version = 0;
133     ccm->opcode          = CCM_OPCODE;
134     ccm->tlv_offset      = 70;
135     ccm->seq             = htonl(++cfmi->seq);
136     ccm->mpid            = htons(cfmi->cfm.mpid);
137     ccm->flags           = cfmi->ccm_interval;
138     memcpy(ccm->maid, cfmi->cfm.maid, sizeof ccm->maid);
139     return packet;
140 }
141
142 /* Allocates a 'cfm' object.  This object should have its 'mpid', 'maid',
143  * 'eth_src', and 'interval' filled out.  When changes are made to the 'cfm'
144  * object, cfm_configure should be called before using it. */
145 struct cfm *
146 cfm_create(void)
147 {
148     struct cfm *cfm;
149     struct cfm_internal *cfmi;
150
151     cfmi = xzalloc(sizeof *cfmi);
152     cfm  = &cfmi->cfm;
153
154     hmap_init(&cfm->remote_mps);
155     hmap_init(&cfm->x_remote_mps);
156     hmap_init(&cfm->x_remote_maids);
157     return cfm;
158 }
159
160 void
161 cfm_destroy(struct cfm *cfm)
162 {
163     struct remote_mp *rmp, *rmp_next;
164     struct remote_maid *rmaid, *rmaid_next;
165
166     if (!cfm) {
167         return;
168     }
169
170     HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) {
171         hmap_remove(&cfm->remote_mps, &rmp->node);
172         free(rmp);
173     }
174
175     HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->x_remote_mps) {
176         hmap_remove(&cfm->x_remote_mps, &rmp->node);
177         free(rmp);
178     }
179
180     HMAP_FOR_EACH_SAFE (rmaid, rmaid_next, node, &cfm->x_remote_maids) {
181         hmap_remove(&cfm->x_remote_maids, &rmaid->node);
182         free(rmaid);
183     }
184
185     hmap_destroy(&cfm->remote_mps);
186     hmap_destroy(&cfm->x_remote_mps);
187     hmap_destroy(&cfm->x_remote_maids);
188     free(cfm_to_internal(cfm));
189 }
190
191 /* Should be run periodically to update fault statistics and generate CCM
192  * messages.  If necessary, returns a packet which the caller is responsible
193  * for sending, un-initing, and deallocating.  Otherwise returns NULL. */
194 struct ofpbuf *
195 cfm_run(struct cfm *cfm)
196 {
197     long long now = time_msec();
198     struct cfm_internal *cfmi = cfm_to_internal(cfm);
199
200     /* According to the 802.1ag specification we should assume every other MP
201      * with the same MAID has the same transmission interval that we have.  If
202      * an MP has a different interval, cfm_process_heartbeat will register it
203      * as a fault (likely due to a configuration error).  Thus we can check all
204      * MPs at once making this quite a bit simpler.
205      *
206      * According to the specification we should check when (ccm_interval_ms *
207      * 3.5)ms have passed.  We changed the multiplier to 4 to avoid messy
208      * floating point arithmetic and add a bit of wiggle room. */
209     if (now >= cfmi->fault_check + cfmi->ccm_interval_ms * 4) {
210         bool fault;
211         struct remote_mp *rmp, *rmp_next;
212         struct remote_maid *rmaid, *rmaid_next;
213
214         fault = false;
215
216         HMAP_FOR_EACH (rmp, node, &cfm->remote_mps) {
217             rmp->fault = rmp->fault || cfmi->fault_check > rmp->recv_time;
218             fault      = rmp->fault || fault;
219         }
220
221         HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->x_remote_mps) {
222             if (cfmi->fault_check > rmp->recv_time) {
223                 hmap_remove(&cfm->x_remote_mps, &rmp->node);
224                 free(rmp);
225             }
226         }
227
228         HMAP_FOR_EACH_SAFE (rmaid, rmaid_next, node, &cfm->x_remote_maids) {
229             if (cfmi->fault_check > rmaid->recv_time) {
230                 hmap_remove(&cfm->x_remote_maids, &rmaid->node);
231                 free(rmaid);
232             }
233         }
234
235         fault = (fault || !hmap_is_empty(&cfm->x_remote_mps)
236                  || !hmap_is_empty(&cfm->x_remote_maids));
237
238         cfm->fault        = fault;
239         cfmi->fault_check = now;
240     }
241
242     if (now >= cfmi->ccm_sent + cfmi->ccm_interval_ms) {
243         cfmi->ccm_sent = now;
244         return compose_ccm(cfmi);
245     }
246
247     return NULL;
248 }
249
250 void
251 cfm_wait(struct cfm *cfm)
252 {
253     long long wait;
254     struct cfm_internal *cfmi = cfm_to_internal(cfm);
255
256     wait = MIN(cfmi->ccm_sent + cfmi->ccm_interval_ms,
257                cfmi->fault_check + cfmi->ccm_interval_ms * 4);
258     poll_timer_wait_until(wait);
259 }
260
261 /* Should be called whenever a client of the cfm library changes the internals
262  * of 'cfm'. Returns true if 'cfm' is valid. */
263 bool
264 cfm_configure(struct cfm *cfm)
265 {
266     struct cfm_internal *cfmi;
267
268     if (!cfm_is_valid_mpid(cfm->mpid) || !cfm->interval) {
269         return false;
270     }
271
272     cfmi                  = cfm_to_internal(cfm);
273     cfmi->ccm_interval    = ms_to_ccm_interval(cfm->interval);
274     cfmi->ccm_interval_ms = ccm_interval_to_ms(cfmi->ccm_interval);
275
276     /* Force a resend and check in case anything changed. */
277     cfmi->ccm_sent    = 0;
278     cfmi->fault_check = 0;
279     return true;
280 }
281
282 /* Given an array of MPIDs, updates the 'remote_mps' map of 'cfm' to reflect
283  * it.  Invalid MPIDs are skipped. */
284 void
285 cfm_update_remote_mps(struct cfm *cfm, const uint16_t *mpids, size_t n_mpids)
286 {
287     size_t i;
288     struct hmap new_rmps;
289     struct remote_mp *rmp, *rmp_next;
290
291     hmap_init(&new_rmps);
292
293     for (i = 0; i < n_mpids; i++) {
294         uint16_t mpid = mpids[i];
295
296         if (!cfm_is_valid_mpid(mpid)
297             || lookup_remote_mp(&new_rmps, mpid)) {
298             continue;
299         }
300
301         if ((rmp = lookup_remote_mp(&cfm->remote_mps, mpid))) {
302             hmap_remove(&cfm->remote_mps, &rmp->node);
303         } else if ((rmp = lookup_remote_mp(&cfm->x_remote_mps, mpid))) {
304             hmap_remove(&cfm->x_remote_mps, &rmp->node);
305         } else {
306             rmp = xzalloc(sizeof *rmp);
307             rmp->mpid = mpid;
308         }
309
310         hmap_insert(&new_rmps, &rmp->node, hash_mpid(mpid));
311     }
312
313     hmap_swap(&new_rmps, &cfm->remote_mps);
314
315     HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &new_rmps) {
316         hmap_remove(&new_rmps, &rmp->node);
317         free(rmp);
318     }
319
320     hmap_destroy(&new_rmps);
321 }
322
323 /* Finds a 'remote_mp' with 'mpid' in 'cfm'.  If no such 'remote_mp' exists
324  * returns NULL. */
325 const struct remote_mp *
326 cfm_get_remote_mp(const struct cfm *cfm, uint16_t mpid)
327 {
328     return lookup_remote_mp(&cfm->remote_mps, mpid);
329 }
330
331 /* Generates 'maid' from 'md_name' and 'ma_name'.  A NULL parameter indicates
332  * the default should be used. Returns false if unsuccessful. */
333 bool
334 cfm_generate_maid(const char *md_name, const char *ma_name,
335                   uint8_t maid[CCM_MAID_LEN])
336 {
337     uint8_t *ma_p;
338     size_t md_len, ma_len;
339
340     if (!md_name) {
341         md_name = "ovs";
342     }
343
344     if (!ma_name) {
345         ma_name = "ovs";
346     }
347
348     memset(maid, 0, CCM_MAID_LEN);
349
350     md_len = strlen(md_name);
351     ma_len = strlen(ma_name);
352
353     if (!md_len || !ma_len || md_len + ma_len + 4 > CCM_MAID_LEN) {
354         return false;
355     }
356
357     maid[0] = 4;                       /* MD name string format. */
358     maid[1] = md_len;                  /* MD name size. */
359     memcpy(&maid[2], md_name, md_len); /* MD name. */
360
361     ma_p    = maid + 2 + md_len;
362     ma_p[0] = 2;                       /* MA name string format. */
363     ma_p[1] = ma_len;                  /* MA name size. */
364     memcpy(&ma_p[2], ma_name, ma_len); /* MA name. */
365     return true;
366 }
367
368 /* Returns true if the CFM library should process packets from 'flow'. */
369 bool
370 cfm_should_process_flow(const struct flow *flow)
371 {
372     return (ntohs(flow->dl_type) == ETH_TYPE_CFM
373             && eth_addr_to_uint64(flow->dl_dst) == DEST_ADDR);
374 }
375
376 /* Updates internal statistics relevant to packet 'p'.  Should be called on
377  * every packet whose flow returned true when passed to
378  * cfm_should_process_flow. */
379 void
380 cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p)
381 {
382     struct ccm *ccm;
383     uint16_t ccm_mpid;
384     uint32_t ccm_seq;
385     uint8_t ccm_interval;
386     struct remote_mp *rmp;
387
388     struct cfm_internal *cfmi        = cfm_to_internal(cfm);
389     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
390
391     ccm = ofpbuf_at(p, (uint8_t *)p->l3 - (uint8_t *)p->data, CCM_LEN);
392
393     if (!ccm) {
394         VLOG_INFO_RL(&rl, "Received an un-parseable 802.1ag CCM heartbeat.");
395         return;
396     }
397
398     if (ccm->opcode != CCM_OPCODE) {
399         VLOG_INFO_RL(&rl, "Received an unsupported 802.1ag message. "
400                      "(opcode %u)", ccm->opcode);
401         return;
402     }
403
404     if (memcmp(ccm->maid, cfm->maid, sizeof ccm->maid)) {
405         uint32_t hash;
406         struct remote_maid *rmaid;
407
408         hash = hash_bytes(ccm->maid, sizeof ccm->maid, 0);
409
410         HMAP_FOR_EACH_IN_BUCKET (rmaid, node, hash, &cfm->x_remote_maids) {
411             if (memcmp(rmaid->maid, ccm->maid, sizeof rmaid->maid) == 0) {
412                 rmaid->recv_time = time_msec();
413                 return;
414             }
415         }
416
417         rmaid            = xzalloc(sizeof *rmaid);
418         rmaid->recv_time = time_msec();
419         memcpy(rmaid->maid, ccm->maid, sizeof rmaid->maid);
420         hmap_insert(&cfm->x_remote_maids, &rmaid->node, hash);
421         return;
422     }
423
424     ccm_mpid     = ntohs(ccm->mpid);
425     ccm_seq      = ntohl(ccm->seq);
426     ccm_interval = ccm->flags & 0x7;
427
428     rmp = lookup_remote_mp(&cfm->remote_mps, ccm_mpid);
429
430     if (!rmp) {
431         rmp = lookup_remote_mp(&cfm->x_remote_mps, ccm_mpid);
432     }
433
434     if (!rmp) {
435         rmp       = xzalloc(sizeof *rmp);
436         rmp->mpid = ccm_mpid;
437         hmap_insert(&cfm->x_remote_mps, &rmp->node, hash_mpid(ccm_mpid));
438     }
439
440     rmp->recv_time = time_msec();
441     rmp->fault     = ccm_interval != cfmi->ccm_interval;
442 }