Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Add dynamic VMM Driven/Guest Driven mode switch in VNET devices
[palacios.git] / palacios / src / palacios / vmm_vnet_core.c
1 /* 
2  * This file is part of the Palacios Virtual Machine Monitor developed
3  * by the V3VEE Project with funding from the United States National 
4  * Science Foundation and the Department of Energy.  
5  *
6  * The V3VEE Project is a joint project between Northwestern University
7  * and the University of New Mexico.  You can find out more at 
8  * http://www.v3vee.org
9  *
10  * Copyright (c) 2010, Lei Xia <lxia@northwestern.edu> 
11  * Copyright (c) 2009, Yuan Tang <ytang@northwestern.edu>  
12  * Copyright (c) 2009, The V3VEE Project <http://www.v3vee.org> 
13  * All rights reserved.
14  *
15  * Author: Lei Xia <lxia@northwestern.edu>
16  *         Yuan Tang <ytang@northwestern.edu>
17  *
18  * This is free software.  You are permitted to use,
19  * redistribute, and modify it as specified in the file "V3VEE_LICENSE".
20  */
21  
22 #include <palacios/vmm_vnet.h>
23 #include <palacios/vm_guest_mem.h>
24 #include <palacios/vmm_lock.h>
25 #include <palacios/vmm_queue.h>
26 #include <palacios/vmm_sprintf.h>
27 #include <palacios/vmm_ethernet.h>
28
29 #ifndef CONFIG_DEBUG_VNET
30 #undef PrintDebug
31 #define PrintDebug(fmt, args...)
32 #endif
33
34 struct eth_hdr {
35     uint8_t dst_mac[ETH_ALEN];
36     uint8_t src_mac[ETH_ALEN];
37     uint16_t type; /* indicates layer 3 protocol type */
38 } __attribute__((packed));
39
40
41 struct vnet_dev {
42     int dev_id;
43     uint8_t mac_addr[ETH_ALEN];
44     struct v3_vm_info * vm;
45     struct v3_vnet_dev_ops dev_ops;
46     void * private_data;
47
48     int active;
49     nic_poll_type_t mode;  /*vmm_drivern or guest_drivern */
50
51     uint64_t bytes_tx, bytes_rx;
52     uint32_t pkts_tx, pkt_rx;
53     
54     struct list_head node;
55 } __attribute__((packed));
56
57
58 struct vnet_brg_dev {
59     struct v3_vm_info * vm;
60     struct v3_vnet_bridge_ops brg_ops;
61
62     uint8_t type;
63     nic_poll_type_t mode;
64     int active;
65     void * private_data;
66 } __attribute__((packed));
67
68
69
70 struct vnet_route_info {
71     struct v3_vnet_route route_def;
72
73     struct vnet_dev * dst_dev;
74     struct vnet_dev * src_dev;
75
76     struct list_head node;
77     struct list_head match_node; // used for route matching
78 };
79
80
81 struct route_list {
82     uint8_t hash_buf[VNET_HASH_SIZE];
83
84     uint32_t num_routes;
85     struct vnet_route_info * routes[0];
86 } __attribute__((packed));
87
88
89 static struct {
90     struct list_head routes;
91     struct list_head devs;
92     
93     int num_routes;
94     int num_devs;
95
96     struct vnet_brg_dev * bridge;
97
98     v3_lock_t lock;
99     struct vnet_stat stats;
100
101     struct hashtable * route_cache;
102 } vnet_state;
103
104
105
106 #ifdef CONFIG_DEBUG_VNET
107 static inline void mac_to_string(uint8_t * mac, char * buf) {
108     snprintf(buf, 100, "%2x:%2x:%2x:%2x:%2x:%2x", 
109              mac[0], mac[1], mac[2],
110              mac[3], mac[4], mac[5]);
111 }
112
113 static void print_route(struct v3_vnet_route * route){
114     char str[50];
115
116     mac_to_string(route->src_mac, str);
117     PrintDebug("Src Mac (%s),  src_qual (%d)\n", 
118                str, route->src_mac_qual);
119     mac_to_string(route->dst_mac, str);
120     PrintDebug("Dst Mac (%s),  dst_qual (%d)\n", 
121                str, route->dst_mac_qual);
122     PrintDebug("Src dev id (%d), src type (%d)", 
123                route->src_id, 
124                route->src_type);
125     PrintDebug("Dst dev id (%d), dst type (%d)\n", 
126                route->dst_id, 
127                route->dst_type);
128 }
129
130 static void dump_routes(){
131     struct vnet_route_info *route;
132
133     int i = 0;
134     PrintDebug("\n========Dump routes starts ============\n");
135     list_for_each_entry(route, &(vnet_state.routes), node) {
136         PrintDebug("\nroute %d:\n", i++);
137                 
138         print_route(&(route->route_def));
139         if (route->route_def.dst_type == LINK_INTERFACE) {
140             PrintDebug("dst_dev (%p), dst_dev_id (%d), dst_dev_ops(%p), dst_dev_data (%p)\n",
141                 route->dst_dev,
142                 route->dst_dev->dev_id,
143                 (void *)&(route->dst_dev->dev_ops),
144                 route->dst_dev->private_data);
145         }
146     }
147
148     PrintDebug("\n========Dump routes end ============\n");
149 }
150
151 #endif
152
153
154 /* 
155  * A VNET packet is a packed struct with the hashed fields grouped together.
156  * This means we can generate the hash from an offset into the pkt struct
157  */
158 static inline uint_t hash_fn(addr_t hdr_ptr) {    
159     uint8_t * hdr_buf = (uint8_t *)hdr_ptr;
160
161     return v3_hash_buffer(hdr_buf, VNET_HASH_SIZE);
162 }
163
164 static inline int hash_eq(addr_t key1, addr_t key2) {   
165     return (memcmp((uint8_t *)key1, (uint8_t *)key2, VNET_HASH_SIZE) == 0);
166 }
167
168 static int add_route_to_cache(const struct v3_vnet_pkt * pkt, struct route_list * routes) {
169     memcpy(routes->hash_buf, pkt->hash_buf, VNET_HASH_SIZE);    
170
171     if (v3_htable_insert(vnet_state.route_cache, (addr_t)routes->hash_buf, (addr_t)routes) == 0) {
172         PrintError("VNET/P Core: Failed to insert new route entry to the cache\n");
173         return -1;
174     }
175     
176     return 0;
177 }
178
179 static int clear_hash_cache() {
180     v3_free_htable(vnet_state.route_cache, 1, 1);
181     vnet_state.route_cache = v3_create_htable(0, &hash_fn, &hash_eq);
182
183     return 0;
184 }
185
186 static int look_into_cache(const struct v3_vnet_pkt * pkt, struct route_list ** routes) {
187     *routes = (struct route_list *)v3_htable_search(vnet_state.route_cache, (addr_t)(pkt->hash_buf));
188    
189     return 0;
190 }
191
192
193 static struct vnet_dev * dev_by_id(int idx) {
194     struct vnet_dev * dev = NULL; 
195
196     list_for_each_entry(dev, &(vnet_state.devs), node) {
197         int dev_id = dev->dev_id;
198
199         if (dev_id == idx)
200             return dev;
201     }
202
203     return NULL;
204 }
205
206 static struct vnet_dev * dev_by_mac(uint8_t * mac) {
207     struct vnet_dev * dev = NULL; 
208     
209     list_for_each_entry(dev, &(vnet_state.devs), node) {
210         if (!compare_ethaddr(dev->mac_addr, mac)){
211             return dev;
212         }
213     }
214
215     return NULL;
216 }
217
218
219 int v3_vnet_find_dev(uint8_t  * mac) {
220     struct vnet_dev * dev = NULL;
221
222     dev = dev_by_mac(mac);
223
224     if(dev != NULL) {
225         return dev->dev_id;
226     }
227
228     return -1;
229 }
230
231
232 int v3_vnet_add_route(struct v3_vnet_route route) {
233     struct vnet_route_info * new_route = NULL;
234     unsigned long flags; 
235
236     new_route = (struct vnet_route_info *)V3_Malloc(sizeof(struct vnet_route_info));
237     memset(new_route, 0, sizeof(struct vnet_route_info));
238
239 #ifdef CONFIG_DEBUG_VNET
240     PrintDebug("VNET/P Core: add_route_entry:\n");
241     print_route(&route);
242 #endif
243     
244     memcpy(new_route->route_def.src_mac, route.src_mac, ETH_ALEN);
245     memcpy(new_route->route_def.dst_mac, route.dst_mac, ETH_ALEN);
246     new_route->route_def.src_mac_qual = route.src_mac_qual;
247     new_route->route_def.dst_mac_qual = route.dst_mac_qual;
248     new_route->route_def.dst_type = route.dst_type;
249     new_route->route_def.src_type = route.src_type;
250     new_route->route_def.src_id = route.src_id;
251     new_route->route_def.dst_id = route.dst_id;
252
253     if (new_route->route_def.dst_type == LINK_INTERFACE) {
254         new_route->dst_dev = dev_by_id(new_route->route_def.dst_id);
255     }
256
257     if (new_route->route_def.src_type == LINK_INTERFACE) {
258         new_route->src_dev = dev_by_id(new_route->route_def.src_id);
259     }
260
261
262     flags = v3_lock_irqsave(vnet_state.lock);
263
264     list_add(&(new_route->node), &(vnet_state.routes));
265     clear_hash_cache();
266
267     v3_unlock_irqrestore(vnet_state.lock, flags);
268    
269
270 #ifdef CONFIG_DEBUG_VNET
271     dump_routes();
272 #endif
273
274     return 0;
275 }
276
277
278 /* delete all route entries with specfied src or dst device id */ 
279 static void inline del_routes_by_dev(int dev_id){
280     struct vnet_route_info * route = NULL;
281     unsigned long flags; 
282
283     flags = v3_lock_irqsave(vnet_state.lock);
284
285     list_for_each_entry(route, &(vnet_state.routes), node) {
286         if((route->route_def.dst_type == LINK_INTERFACE &&
287              route->route_def.dst_id == dev_id) ||
288              (route->route_def.src_type == LINK_INTERFACE &&
289               route->route_def.src_id == dev_id)){
290               
291             list_del(&(route->node));
292             list_del(&(route->match_node));
293             V3_Free(route);    
294         }
295     }
296
297     v3_unlock_irqrestore(vnet_state.lock, flags);
298 }
299
300 /* At the end allocate a route_list
301  * This list will be inserted into the cache so we don't need to free it
302  */
303 static struct route_list * match_route(const struct v3_vnet_pkt * pkt) {
304     struct vnet_route_info * route = NULL; 
305     struct route_list * matches = NULL;
306     int num_matches = 0;
307     int max_rank = 0;
308     struct list_head match_list;
309     struct eth_hdr * hdr = (struct eth_hdr *)(pkt->data);
310  //   uint8_t src_type = pkt->src_type;
311  //   uint32_t src_link = pkt->src_id;
312
313 #ifdef CONFIG_DEBUG_VNET
314     {
315         char dst_str[100];
316         char src_str[100];
317
318         mac_to_string(hdr->src_mac, src_str);  
319         mac_to_string(hdr->dst_mac, dst_str);
320         PrintDebug("VNET/P Core: match_route. pkt: SRC(%s), DEST(%s)\n", src_str, dst_str);
321     }
322 #endif
323
324     INIT_LIST_HEAD(&match_list);
325     
326 #define UPDATE_MATCHES(rank) do {                               \
327         if (max_rank < (rank)) {                                \
328             max_rank = (rank);                                  \
329             INIT_LIST_HEAD(&match_list);                        \
330                                                                 \
331             list_add(&(route->match_node), &match_list);        \
332             num_matches = 1;                                    \
333         } else if (max_rank == (rank)) {                        \
334             list_add(&(route->match_node), &match_list);        \
335             num_matches++;                                      \
336         }                                                       \
337     } while (0)
338     
339
340     list_for_each_entry(route, &(vnet_state.routes), node) {
341         struct v3_vnet_route * route_def = &(route->route_def);
342
343 /*
344         // CHECK SOURCE TYPE HERE
345         if ( (route_def->src_type != LINK_ANY) && 
346              ( (route_def->src_type != src_type) || 
347                ( (route_def->src_id != src_link) &&
348                  (route_def->src_id != -1)))) {
349             continue;
350         }
351 */
352
353         if ((route_def->dst_mac_qual == MAC_ANY) &&
354             (route_def->src_mac_qual == MAC_ANY)) {      
355             UPDATE_MATCHES(3);
356         }
357         
358         if (memcmp(route_def->src_mac, hdr->src_mac, 6) == 0) {
359             if (route_def->src_mac_qual != MAC_NOT) {
360                 if (route_def->dst_mac_qual == MAC_ANY) {
361                     UPDATE_MATCHES(6);
362                 } else if (route_def->dst_mac_qual != MAC_NOT &&
363                            memcmp(route_def->dst_mac, hdr->dst_mac, 6) == 0) {
364                     UPDATE_MATCHES(8);
365                 }
366             }
367         }
368             
369         if (memcmp(route_def->dst_mac, hdr->dst_mac, 6) == 0) {
370             if (route_def->dst_mac_qual != MAC_NOT) {
371                 if (route_def->src_mac_qual == MAC_ANY) {
372                     UPDATE_MATCHES(6);
373                 } else if ((route_def->src_mac_qual != MAC_NOT) && 
374                            (memcmp(route_def->src_mac, hdr->src_mac, 6) == 0)) {
375                     UPDATE_MATCHES(8);
376                 }
377             }
378         }
379             
380         if ((route_def->dst_mac_qual == MAC_NOT) &&
381             (memcmp(route_def->dst_mac, hdr->dst_mac, 6) != 0)) {
382             if (route_def->src_mac_qual == MAC_ANY) {
383                 UPDATE_MATCHES(5);
384             } else if ((route_def->src_mac_qual != MAC_NOT) && 
385                        (memcmp(route_def->src_mac, hdr->src_mac, 6) == 0)) {     
386                 UPDATE_MATCHES(7);
387             }
388         }
389         
390         if ((route_def->src_mac_qual == MAC_NOT) &&
391             (memcmp(route_def->src_mac, hdr->src_mac, 6) != 0)) {
392             if (route_def->dst_mac_qual == MAC_ANY) {
393                 UPDATE_MATCHES(5);
394             } else if ((route_def->dst_mac_qual != MAC_NOT) &&
395                        (memcmp(route_def->dst_mac, hdr->dst_mac, 6) == 0)) {
396                 UPDATE_MATCHES(7);
397             }
398         }
399         
400         // Default route
401         if ( (memcmp(route_def->src_mac, hdr->src_mac, 6) == 0) &&
402              (route_def->dst_mac_qual == MAC_NONE)) {
403             UPDATE_MATCHES(4);
404         }
405     }
406
407     PrintDebug("VNET/P Core: match_route: Matches=%d\n", num_matches);
408
409     if (num_matches == 0) {
410         return NULL;
411     }
412
413     matches = (struct route_list *)V3_Malloc(sizeof(struct route_list) + 
414                                 (sizeof(struct vnet_route_info *) * num_matches));
415
416     matches->num_routes = num_matches;
417
418     {
419         int i = 0;
420         list_for_each_entry(route, &match_list, match_node) {
421             matches->routes[i++] = route;
422         }
423     }
424
425     return matches;
426 }
427
428
429 int v3_vnet_send_pkt(struct v3_vnet_pkt * pkt, void * private_data) {
430     struct route_list * matched_routes = NULL;
431     unsigned long flags;
432     int i;
433
434 #ifdef CONFIG_DEBUG_VNET
435    {
436         int cpu = V3_Get_CPU();
437        PrintDebug("VNET/P Core: cpu %d: pkt (size %d, src_id:%d, src_type: %d, dst_id: %d, dst_type: %d)\n",
438                   cpu, pkt->size, pkt->src_id, 
439                   pkt->src_type, pkt->dst_id, pkt->dst_type);
440        //v3_hexdump(pkt->data, pkt->size, NULL, 0);
441    }
442 #endif
443
444     flags = v3_lock_irqsave(vnet_state.lock);
445
446     vnet_state.stats.rx_bytes += pkt->size;
447     vnet_state.stats.rx_pkts++;
448
449     look_into_cache(pkt, &matched_routes);
450     if (matched_routes == NULL) {  
451         PrintDebug("VNET/P Core: send pkt Looking into routing table\n");
452         
453         matched_routes = match_route(pkt);
454         
455         if (matched_routes) {
456             add_route_to_cache(pkt, matched_routes);
457         } else {
458             PrintDebug("VNET/P Core: Could not find route for packet... discards packet\n");
459             v3_unlock_irqrestore(vnet_state.lock, flags);
460             return 0; /* do we return -1 here?*/
461         }
462     }
463
464     v3_unlock_irqrestore(vnet_state.lock, flags);
465
466     PrintDebug("VNET/P Core: send pkt route matches %d\n", matched_routes->num_routes);
467
468     for (i = 0; i < matched_routes->num_routes; i++) {
469         struct vnet_route_info * route = matched_routes->routes[i];
470         
471         if (route->route_def.dst_type == LINK_EDGE) {
472             struct vnet_brg_dev *bridge = vnet_state.bridge;
473             pkt->dst_type = LINK_EDGE;
474             pkt->dst_id = route->route_def.dst_id;
475
476             if (bridge == NULL || (bridge->active == 0)) {
477                 PrintDebug("VNET/P Core: No active bridge to sent data to\n");
478                  continue;
479             }
480
481             if(bridge->brg_ops.input(bridge->vm, pkt, bridge->private_data) < 0){
482                 PrintDebug("VNET/P Core: Packet not sent properly to bridge\n");
483                 continue;
484             }         
485             vnet_state.stats.tx_bytes += pkt->size;
486             vnet_state.stats.tx_pkts ++;
487         } else if (route->route_def.dst_type == LINK_INTERFACE) {
488             if (route->dst_dev == NULL || route->dst_dev->active == 0){
489                 PrintDebug("VNET/P Core: No active device to sent data to\n");
490                 continue;
491             }
492
493             if(route->dst_dev->dev_ops.input(route->dst_dev->vm, pkt, route->dst_dev->private_data) < 0) {
494                 PrintDebug("VNET/P Core: Packet not sent properly\n");
495                 continue;
496             }
497             vnet_state.stats.tx_bytes += pkt->size;
498             vnet_state.stats.tx_pkts ++;
499         } else {
500             PrintError("VNET/P Core: Wrong dst type\n");
501         }
502     }
503     
504     return 0;
505 }
506
507 int v3_vnet_add_dev(struct v3_vm_info * vm, uint8_t * mac, 
508                     struct v3_vnet_dev_ops *ops,
509                     void * priv_data){
510     struct vnet_dev * new_dev = NULL;
511     unsigned long flags;
512
513     new_dev = (struct vnet_dev *)V3_Malloc(sizeof(struct vnet_dev)); 
514
515     if (new_dev == NULL) {
516         PrintError("Malloc fails\n");
517         return -1;
518     }
519    
520     memcpy(new_dev->mac_addr, mac, 6);
521     new_dev->dev_ops.input = ops->input;
522     new_dev->dev_ops.poll = ops->poll;
523     new_dev->private_data = priv_data;
524     new_dev->vm = vm;
525     new_dev->dev_id = 0;
526     new_dev->active = 1;
527     new_dev->mode = GUEST_DRIVERN;
528
529     flags = v3_lock_irqsave(vnet_state.lock);
530
531     if (dev_by_mac(mac) == NULL) {
532         list_add(&(new_dev->node), &(vnet_state.devs));
533         new_dev->dev_id = ++vnet_state.num_devs;
534     }
535
536     v3_unlock_irqrestore(vnet_state.lock, flags);
537
538     /* if the device was found previosly the id should still be 0 */
539     if (new_dev->dev_id == 0) {
540         PrintError("VNET/P Core: Device Already exists\n");
541         return -1;
542     }
543
544     PrintDebug("VNET/P Core: Add Device: dev_id %d\n", new_dev->dev_id);
545
546     return new_dev->dev_id;
547 }
548
549
550
551 int v3_vnet_del_dev(int dev_id){
552     struct vnet_dev * dev = NULL;
553     unsigned long flags;
554
555     flags = v3_lock_irqsave(vnet_state.lock);
556         
557     dev = dev_by_id(dev_id);
558     if (dev != NULL){
559         list_del(&(dev->node));
560         del_routes_by_dev(dev_id);
561     }
562         
563     v3_unlock_irqrestore(vnet_state.lock, flags);
564
565     V3_Free(dev);
566
567     PrintDebug("VNET/P Core: Remove Device: dev_id %d\n", dev_id);
568
569     return 0;
570 }
571
572 int v3_vnet_stat(struct vnet_stat * stats){
573         
574     stats->rx_bytes = vnet_state.stats.rx_bytes;
575     stats->rx_pkts = vnet_state.stats.rx_pkts;
576     stats->tx_bytes = vnet_state.stats.tx_bytes;
577     stats->tx_pkts = vnet_state.stats.tx_pkts;
578
579     return 0;
580 }
581
582 static void free_devices(){
583     struct vnet_dev * dev = NULL; 
584
585     list_for_each_entry(dev, &(vnet_state.devs), node) {
586         list_del(&(dev->node));
587         V3_Free(dev);
588     }
589 }
590
591 static void free_routes(){
592     struct vnet_route_info * route = NULL; 
593
594     list_for_each_entry(route, &(vnet_state.routes), node) {
595         list_del(&(route->node));
596         list_del(&(route->match_node));
597         V3_Free(route);
598     }
599 }
600
601 int v3_vnet_add_bridge(struct v3_vm_info * vm,
602                        struct v3_vnet_bridge_ops * ops,
603                        uint8_t type,
604                        void * priv_data) {
605     unsigned long flags;
606     int bridge_free = 0;
607     struct vnet_brg_dev * tmp_bridge = NULL;    
608     
609     flags = v3_lock_irqsave(vnet_state.lock);
610
611     if (vnet_state.bridge == NULL) {
612         bridge_free = 1;
613         vnet_state.bridge = (void *)1;
614     }
615
616     v3_unlock_irqrestore(vnet_state.lock, flags);
617
618     if (bridge_free == 0) {
619         PrintError("VNET/P Core: Bridge already set\n");
620         return -1;
621     }
622
623     tmp_bridge = (struct vnet_brg_dev *)V3_Malloc(sizeof(struct vnet_brg_dev));
624
625     if (tmp_bridge == NULL) {
626         PrintError("Malloc Fails\n");
627         vnet_state.bridge = NULL;
628         return -1;
629     }
630     
631     tmp_bridge->vm = vm;
632     tmp_bridge->brg_ops.input = ops->input;
633     tmp_bridge->brg_ops.poll = ops->poll;
634     tmp_bridge->private_data = priv_data;
635     tmp_bridge->active = 1;
636     tmp_bridge->mode = GUEST_DRIVERN;
637     tmp_bridge->type = type;
638         
639     /* make this atomic to avoid possible race conditions */
640     flags = v3_lock_irqsave(vnet_state.lock);
641     vnet_state.bridge = tmp_bridge;
642     v3_unlock_irqrestore(vnet_state.lock, flags);
643
644     return 0;
645 }
646
647
648 void v3_vnet_do_poll(struct v3_vm_info * vm){
649     struct vnet_dev * dev = NULL; 
650
651     /* TODO: run this on separate threads
652       * round-robin schedule, with maximal budget for each poll
653       */
654     list_for_each_entry(dev, &(vnet_state.devs), node) {
655         if(dev->mode == VMM_DRIVERN){
656             dev->dev_ops.poll(vm, -1, dev->private_data);
657        }
658     }
659 }
660
661
662 int v3_init_vnet() {
663     memset(&vnet_state, 0, sizeof(vnet_state));
664         
665     INIT_LIST_HEAD(&(vnet_state.routes));
666     INIT_LIST_HEAD(&(vnet_state.devs));
667
668     vnet_state.num_devs = 0;
669     vnet_state.num_routes = 0;
670
671     if (v3_lock_init(&(vnet_state.lock)) == -1){
672         PrintError("VNET/P Core: Fails to initiate lock\n");
673     }
674
675     vnet_state.route_cache = v3_create_htable(0, &hash_fn, &hash_eq);
676
677     if (vnet_state.route_cache == NULL) {
678         PrintError("VNET/P Core: Fails to initiate route cache\n");
679         return -1;
680     }
681
682     PrintDebug("VNET/P Core is initiated\n");
683
684     return 0;
685 }
686
687
688 void v3_deinit_vnet(){
689
690     v3_lock_deinit(&(vnet_state.lock));
691
692     free_devices();
693     free_routes();
694
695     v3_free_htable(vnet_state.route_cache, 1, 1);
696     V3_Free(vnet_state.bridge);
697 }
698
699