<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40"><head><meta http-equiv=Content-Type content="text/html; charset=utf-8"><meta name=Generator content="Microsoft Word 15 (filtered medium)"><style><!--
/* Font Definitions */
@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
        {font-family:DengXian;
        panose-1:2 1 6 0 3 1 1 1 1 1;}
@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}
@font-face
        {font-family:DengXian;
        panose-1:2 1 6 0 3 1 1 1 1 1;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0cm;
        margin-bottom:.0001pt;
        text-align:justify;
        text-justify:inter-ideograph;
        font-size:10.5pt;
        font-family:"Calibri",sans-serif;
        mso-fareast-language:#0C00;}
a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:blue;
        text-decoration:underline;}
.MsoChpDefault
        {mso-style-type:export-only;
        mso-fareast-language:#0C00;}
/* Page Definitions */
@page WordSection1
        {size:612.0pt 792.0pt;
        margin:72.0pt 90.0pt 72.0pt 90.0pt;}
div.WordSection1
        {page:WordSection1;}
--></style></head><body lang=en-US link=blue vlink="#954F72"><div class=WordSection1><p class=MsoNormal><span lang=EN-US style='mso-fareast-language:ZH-CN'>Thanks for your detailed analysis, I will remake a patch</span><span style='font-size:11.0pt;mso-fareast-language:ZH-CN'><o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span style='font-size:11.0pt;mso-fareast-language:ZH-CN'><o:p> </o:p></span></p><div style='mso-element:para-border-div;border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0cm 0cm 0cm'><p class=MsoNormal style='border:none;padding:0cm'><b><span style='font-family:DengXian'>发件人</span><span lang=EN-US>: </span></b><span lang=EN-US><a href="mailto:laine@redhat.com">Laine Stump</a><br></span><b><span style='font-family:DengXian'>发送时间</span><span lang=EN-US>: </span></b><span lang=EN-US>Wednesday, June 17, 2020 7:46 AM<br></span><b><span style='font-family:DengXian'>收件人</span><span lang=EN-US>: </span></b><span lang=EN-US><a href="mailto:libvir-list@redhat.com">libvir-list@redhat.com</a><br></span><b><span style='font-family:DengXian'>抄送</span><span lang=EN-US>: </span></b><span lang=EN-US><a href="mailto:danielhb413@gmail.com">Daniel Henrique Barboza</a>; <a href="mailto:owen.si@ucloud.cn">Bingsong Si</a>; <a href="mailto:gongwei@smartx.com">Wei Gong</a><br></span><b><span style='font-family:DengXian'>主题</span><span lang=EN-US>: </span></b><span lang=EN-US>Re: [PATCH] network: Fix a race condition when shutdown & start vm at the same time</span></p></div><p class=MsoNormal><span style='font-size:11.0pt;mso-fareast-language:ZH-CN'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>(BTW, this other patch is also trying to solve the same problem:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>https://www.redhat.com/archives/libvir-list/2020-June/msg00525.html</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>I made comments there earlier, and have learned a bit more since then:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>https://www.redhat.com/archives/libvir-list/2020-June/msg00634.html</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>On 6/15/20 2:36 PM, Daniel Henrique Barboza wrote:</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>> On 6/11/20 6:58 AM, Bingsong Si wrote:</span></p><p class=MsoNormal><span lang=EN-US>>> when shutdown vm, the qemuProcessStop cleanup virtual interface in </span></p><p class=MsoNormal><span lang=EN-US>>> two steps:</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>> s/when/When</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>>> 1. qemuProcessKill kill qemu process, and vif disappeared</span></p><p class=MsoNormal><span lang=EN-US>>> 2. ovs-vsctl del-port from the brige</span></p><p class=MsoNormal><span lang=EN-US>>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>>> if start a vm in the middle of the two steps, the new vm will reused </span></p><p class=MsoNormal><span lang=EN-US>>> the vif,</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>> s/if/If</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>>> but removed from bridge by step 2</span></p><p class=MsoNormal><span lang=EN-US>>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>>> Signed-off-by: Bingsong Si <owen.si@ucloud.cn></span></p><p class=MsoNormal><span lang=EN-US>>> ---</span></p><p class=MsoNormal><span lang=EN-US>>>   src/qemu/qemu_process.c | 8 +++++---</span></p><p class=MsoNormal><span lang=EN-US>>>   1 file changed, 5 insertions(+), 3 deletions(-)</span></p><p class=MsoNormal><span lang=EN-US>>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>>> diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c</span></p><p class=MsoNormal><span lang=EN-US>>> index d36088ba98..706248815a 100644</span></p><p class=MsoNormal><span lang=EN-US>>> --- a/src/qemu/qemu_process.c</span></p><p class=MsoNormal><span lang=EN-US>>> +++ b/src/qemu/qemu_process.c</span></p><p class=MsoNormal><span lang=EN-US>>> @@ -7483,9 +7483,11 @@ void qemuProcessStop(virQEMUDriverPtr driver,</span></p><p class=MsoNormal><span lang=EN-US>>>               if (vport->virtPortType == </span></p><p class=MsoNormal><span lang=EN-US>>> VIR_NETDEV_VPORT_PROFILE_MIDONET) {</span></p><p class=MsoNormal><span lang=EN-US>>> ignore_value(virNetDevMidonetUnbindPort(vport));</span></p><p class=MsoNormal><span lang=EN-US>>>               } else if (vport->virtPortType == </span></p><p class=MsoNormal><span lang=EN-US>>> VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) {</span></p><p class=MsoNormal><span lang=EN-US>>> -                ignore_value(virNetDevOpenvswitchRemovePort(</span></p><p class=MsoNormal><span lang=EN-US>>> - virDomainNetGetActualBridgeName(net),</span></p><p class=MsoNormal><span lang=EN-US>>> -                                 net->ifname));</span></p><p class=MsoNormal><span lang=EN-US>>> +                virMacAddr mac;</span></p><p class=MsoNormal><span lang=EN-US>>> +                if (virNetDevGetMAC(net->ifname, &mac) < 0 ||  </span></p><p class=MsoNormal><span lang=EN-US>>> !virMacAddrCmp(&mac, &net->mac))</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>(Before anything else - virNetDevGetMAC() will actually *log* an error </span></p><p class=MsoNormal><span lang=EN-US>in libvirt's logs if the device isn't found (which will be in nearly </span></p><p class=MsoNormal><span lang=EN-US>100% of all cases). That will lead to people reporting it as a bug, </span></p><p class=MsoNormal><span lang=EN-US>which gets very time consuming and expensive for anyone providing </span></p><p class=MsoNormal><span lang=EN-US>commercial support for a product that uses libvirt. If it is really </span></p><p class=MsoNormal><span lang=EN-US>necessary to check the MAC address of a device that legitimately may or </span></p><p class=MsoNormal><span lang=EN-US>may not exist, then there will need to be a "Quiet" version of the </span></p><p class=MsoNormal><span lang=EN-US>function that doesn't log any errors.)(Update after thinking about it - </span></p><p class=MsoNormal><span lang=EN-US>I don't think we should be checking the MAC address anyway, as it </span></p><p class=MsoNormal><span lang=EN-US>doesn't reliably differentiate "new" tap from "old" tap).</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>> Extra space between "||" and "!virMacAddrCmp(.."</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>> With these nits fixed:</span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>> Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>This patch is attempting to solve a race condition between one guest </span></p><p class=MsoNormal><span lang=EN-US>starting at the same time another is stopping, and the mess that results </span></p><p class=MsoNormal><span lang=EN-US>if the new guest uses the same name for its tap device as the old guest </span></p><p class=MsoNormal><span lang=EN-US>used. For example, lets say libvirt thread A / guest A is doing this:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>A1) the QEMU process is terminated (and tap device, e.g. "vnet0", is </span></p><p class=MsoNormal><span lang=EN-US>implicitly deleted) either by libvirtd or by some external force </span></p><p class=MsoNormal><span lang=EN-US>(including possibly the QEMU process itself</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>A2) the tap's associated port (also "vnet0") is removed from the OVS </span></p><p class=MsoNormal><span lang=EN-US>switch by libvirt.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>While libvirt thread B is doing this:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>B1) a new tap device is created for a new QEMU process. If A1 has </span></p><p class=MsoNormal><span lang=EN-US>already happened, then the kernel will likely give the new tap the same </span></p><p class=MsoNormal><span lang=EN-US>name - "vnet0".</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>B2) the new tap device is attached to an OVS switch (or possibly a Linux </span></p><p class=MsoNormal><span lang=EN-US>host bridge).</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>The problem occurs when if B2 happens before A2, which could result in </span></p><p class=MsoNormal><span lang=EN-US>B2 attaching the new tap to the OVS switch, and then A2 disconnecting it </span></p><p class=MsoNormal><span lang=EN-US>from the switch. So libvirt thinks the new QEMU guest tap is attached to </span></p><p class=MsoNormal><span lang=EN-US>the switch, but it isn't.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>This patch attempts to eliminate the race by checking, prior to removing </span></p><p class=MsoNormal><span lang=EN-US>"old tap"s port on the switch, that 1) the tap device doesn't exist, or </span></p><p class=MsoNormal><span lang=EN-US>that if it does 2) that the MAC address of the tap device is unchanged.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>Assuming that the two guests would not use the same MAC address for </span></p><p class=MsoNormal><span lang=EN-US>their tap devices (which is probably the case, but isn't *required* to </span></p><p class=MsoNormal><span lang=EN-US>be true), this does significantly narrow the potential time  for a race </span></p><p class=MsoNormal><span lang=EN-US>condition, and in particular makes sure that we never remove a port that </span></p><p class=MsoNormal><span lang=EN-US>hasn't just been "re-added" by the new QEMU.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>However, this just creates a smaller window for the race, and different </span></p><p class=MsoNormal><span lang=EN-US>problem for the remainder of the time.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>1) smaller window - it would still be possible for the following to happen:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>    a) old qemu terminates, tap device "vnet0" is deleted</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>    b) libvirt checks MAC address and learns that there is no device </span></p><p class=MsoNormal><span lang=EN-US>"vnet0",</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>       so it calls virNetDevOpenvswitchRemovePort(), but before ovs-vsctl</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>       can be called...</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>    c) libvirt creates a new tap device for new QEMU, kernel names it </span></p><p class=MsoNormal><span lang=EN-US>"vnet0"</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>    d) libvirt calls virNetDevOpenvswitchAddPort() and the new tap</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>       device "vnet0" to the switch</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>    e) the ovs-vsctl from (b) is finally able to run, and removes "vnet0"</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>       from the switch.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>Granted, this is *highly* unlikely, since there is nothing extra between </span></p><p class=MsoNormal><span lang=EN-US>checking MAC address and removing the port, but there is nothing </span></p><p class=MsoNormal><span lang=EN-US>enforcing it.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>2) New Problem - I think the testing here was done with two guests who </span></p><p class=MsoNormal><span lang=EN-US>both attached their tap to the same (or another) OVS switch. It's </span></p><p class=MsoNormal><span lang=EN-US>relying on the ability to attach the tap device to a new switch/bridge </span></p><p class=MsoNormal><span lang=EN-US>even if it is already attached to some other switch bridge. Normally </span></p><p class=MsoNormal><span lang=EN-US>ovs-vsctl would refuse to add a port to a switch if a port by that same </span></p><p class=MsoNormal><span lang=EN-US>name was already on any OVS switch. I just looked it up though, and in </span></p><p class=MsoNormal><span lang=EN-US>this case libvirt is able to make this work by including "--if-exists </span></p><p class=MsoNormal><span lang=EN-US>del-port $ifname" in the ovs-vsctl command that *adds* the new port. </span></p><p class=MsoNormal><span lang=EN-US>However, if you have the same situation but the new switch device is </span></p><p class=MsoNormal><span lang=EN-US>instead a Linux bridge, the attempt to attach to the bridge fails.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>So, if thread "B" has already created the new tap device by the time </span></p><p class=MsoNormal><span lang=EN-US>thread "A" is deciding whether or not to remove the old port, the port </span></p><p class=MsoNormal><span lang=EN-US>won't be removed, and if the new guest "B" is using a Linux host bridge, </span></p><p class=MsoNormal><span lang=EN-US>libvirt will fail to attach the new tap to the bridge.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>So, a summary of the problems with this patch:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>1) The race window is reduced (and may be gone in practical terms), but </span></p><p class=MsoNormal><span lang=EN-US>not guaranteed to be eliminated.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>2) For the time during the "previous race window start" and "new race </span></p><p class=MsoNormal><span lang=EN-US>window start" a new problem has been created - if the new guest uses a </span></p><p class=MsoNormal><span lang=EN-US>Linux host bridge, the connection will fail</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>3) virNetDevGetMAC() will put an error in the system logs if the device </span></p><p class=MsoNormal><span lang=EN-US>doesn't exist (and it almost always will *not* exist, so this will be </span></p><p class=MsoNormal><span lang=EN-US>significant</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>4) Although it is almost always the case that two guests will not use </span></p><p class=MsoNormal><span lang=EN-US>the same MAC address for their network interfaces, there is nothing </span></p><p class=MsoNormal><span lang=EN-US>preventing it - we shouldn't assume that MAC addresses are unique. I </span></p><p class=MsoNormal><span lang=EN-US>think that check is actually superfluous, since the qemu process has </span></p><p class=MsoNormal><span lang=EN-US>always been terminated by the time we get to that place in the code, so </span></p><p class=MsoNormal><span lang=EN-US>the tap device should have been auto-deleted.</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>What do I think should be done? Good question. Possibly we could:</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>A) Call virNetDevTapReattachBridge() rather than </span></p><p class=MsoNormal><span lang=EN-US>virNetDevTapAttachBridge() in virNetDevTapCreateInBridgePort(). This </span></p><p class=MsoNormal><span lang=EN-US>would eliminate problem (2).</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>B) Instead of checking if the tap device MAC address matches, just call </span></p><p class=MsoNormal><span lang=EN-US>virNetDevExists() - if it exists, then skip the RemovePort() - this </span></p><p class=MsoNormal><span lang=EN-US>eliminates problems (3) and (4). (NB - this would fail if it turns out </span></p><p class=MsoNormal><span lang=EN-US>that tap device deletion isn't completed synchronously with qemu process </span></p><p class=MsoNormal><span lang=EN-US>termination!)</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>C) If we want to make it 100% sound, we need to make "check for </span></p><p class=MsoNormal><span lang=EN-US>interface existence + removeport" an atomic operation, and mutually </span></p><p class=MsoNormal><span lang=EN-US>exclusive with virNetDevTapCreate(). This would eliminate problem (1)</span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>>> + ignore_value(virNetDevOpenvswitchRemovePort(</span></p><p class=MsoNormal><span lang=EN-US>>> + virDomainNetGetActualBridgeName(net),</span></p><p class=MsoNormal><span lang=EN-US>>> +                                     net->ifname));</span></p><p class=MsoNormal><span lang=EN-US>>>               }</span></p><p class=MsoNormal><span lang=EN-US>>>           }</span></p><p class=MsoNormal><span lang=EN-US>>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US>><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p></div></body></html>