using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace EduNetworkBuilder
{
    [Serializable]
    public class NetworkCard
    {
        public string MAC = NB.GenerateMACAddress();  //Technically we should make sure it is unique
        public List<NetworkInterface> interfaces = new List<NetworkInterface>();
        public bool UsesDHCP = false;
        public bool CanUseDHCP = false;
        public bool MustUseDHCP = false;
        public NicType myNicType = NicType.eth;
        public HostNicID myID;
        public int ConnectedLink=-1; //The link that is connected to this nic.
        public int UniqueIdentifier = NB.GetUniqueIdentifier();
        public string _nic_name="";
        public NB_IPAddress TunnelEndpoint;
        public string EncryptionKey;
        public string SSID;
        public string WirelessKey
        {
            get { return EncryptionKey; }
            set { EncryptionKey = value; }
        }

        public int IFCount { get { return interfaces.Count; } }

        public NetworkCard() { }

        public NetworkCard(int index, int HostID, string hostname, NicType theType = NicType.eth)
        {
            myNicType = theType;
            _nic_name = myNicType.ToString() + index.ToString();
            myID = new HostNicID(HostID, UniqueIdentifier, hostname, _nic_name);
            NetworkInterface nInterface = new NetworkInterface(NicName(), NB.ZeroIPString, NB.ZeroIPString, myID);
            if(theType == NicType.lo)
                nInterface = new NetworkInterface(NicName(), "127.0.0.1", "255.0.0.0", myID);
            interfaces.Add(nInterface);
            ApplyNicRules();
            SetIPForDHCP();
        }

        public NetworkCard(NetworkCard old, bool duplicateID = false)
        {
            if (old == null) return;
            MAC = old.MAC;
            foreach(NetworkInterface one in old.interfaces)
            {
                NetworkInterface nInterface = new NetworkInterface(one);
                interfaces.Add(nInterface);
            }
            UsesDHCP = old.UsesDHCP;
            CanUseDHCP = old.CanUseDHCP;
            MustUseDHCP = old.MustUseDHCP;
            myNicType = old.myNicType;
            myID = old.myID;
            if(duplicateID == false)
                myID = new HostNicID(old.myID.HostID, UniqueIdentifier, old.myID.HostName, old.NicName());
            ConnectedLink = old.ConnectedLink;
            if (duplicateID == false)
                ConnectedLink = -1; //It should be a new card, not connected to anything.
            TunnelEndpoint = old.TunnelEndpoint;
            EncryptionKey = old.EncryptionKey;
            SSID = old.SSID;
        }

        public NetworkCard(XmlNode theNode)
        {
            foreach (XmlNode Individual in theNode.ChildNodes)
            {
                XmlNodeType myNodetype = Individual.NodeType;
                if (myNodetype == XmlNodeType.Element)
                {
                    switch (Individual.Name.ToLower())
                    {
                        case "nictype":
                            myNicType = NB.ParseEnum<NicType>(Individual.InnerText);
                            break;
                        case "nicname":
                            _nic_name = Individual.InnerText;
                            break;
                        case "myid":
                            myID = new HostNicID(Individual);
                            break;
                        case "usesdhcp":
                            bool.TryParse(Individual.InnerText, out UsesDHCP);
                            break;
                        case "uniqueidentifier":
                            int.TryParse(Individual.InnerText, out UniqueIdentifier);
                            break;
                        case "interface":
                            NetworkInterface newIF = new NetworkInterface(Individual,myID);
                            interfaces.Add(newIF);
                            break;
                        case "mac":
                            string tmac = Individual.InnerText;
                            if (tmac.Length == MAC.Length)
                                MAC = tmac.ToUpper(); //Make sure it is all uppercase
                            break;
                        case "tunnelendpoint":
                            TunnelEndpoint = new NB_IPAddress(Individual);
                            break;
                        case "encryptionkey":
                            EncryptionKey = Individual.InnerText;
                            break;
                        case "ssid":
                            SSID = Individual.InnerText;
                            break;
                    }
                }
            }
            ApplyNicRules();
            SetIPForDHCP();
        }

        public void ChangeType(NicType newtype)
        {
            NicType oldType = myNicType;
            myNicType = newtype;
            string index = Regex.Match(_nic_name, @"\d+$").Value;
            if (index == "") index = "0";
            string OldName = oldType.ToString() + index.ToString();
            _nic_name = myNicType.ToString() + index.ToString();
            foreach (NetworkInterface NI in interfaces)
            {
                //NI.nic_name =  Regex.Replace(NI.nic_name, OldName, _nic_name);
                NI.nic_name = _nic_name; //This does not work when we have eth0:1...
                NI.AttachedToHostNic.NicName = _nic_name;
            }
            myID.NicName = _nic_name;
            if (newtype == NicType.port || newtype == NicType.wport)
                interfaces.Clear();
        }

        public bool Equals(NetworkCard compareWith)
        {
            if (MAC != compareWith.MAC) return false;

            //List<NetworkInterface> interfaces = new List<NetworkInterface>();
            if (interfaces.Count != compareWith.interfaces.Count) return false;
            for (int i = 0; i < interfaces.Count; i++)
                if (!interfaces[i].Equals(compareWith.interfaces[i])) return false;
            if (UsesDHCP != compareWith.UsesDHCP) return false;
            if (CanUseDHCP != compareWith.CanUseDHCP) return false;
            if (MustUseDHCP != compareWith.MustUseDHCP) return false;
            if (myNicType != compareWith.myNicType) return false;
            if (myID.HostID != compareWith.myID.HostID) return false;
            if (ConnectedLink != compareWith.ConnectedLink) return false;
            if (UniqueIdentifier != compareWith.UniqueIdentifier) return false;
            if (_nic_name != compareWith._nic_name) return false;
            if (TunnelEndpoint != compareWith.TunnelEndpoint) return false;
            if (EncryptionKey != compareWith.EncryptionKey) return false;
            if (SSID != compareWith.SSID) return false;
            return true;
    }

        private void ApplyNicRules()
        {
            //These are the defaults for the various nic types.  They can be overridden by the device.
            if(myNicType == NicType.eth || myNicType == NicType.management_interface || myNicType == NicType.wlan)
            {
                CanUseDHCP = true;
            }
            else
            { 
                CanUseDHCP = false; 
            }
        }

        public  void Save(XmlWriter writer)
        {
            writer.WriteStartElement("nic");
            writer.WriteElementString("nictype", myNicType.ToString());
            writer.WriteElementString("nicname", _nic_name);
            myID.Save(writer, "myid");
            writer.WriteElementString("nictype", myNicType.ToString());
            writer.WriteElementString("uniqueidentifier", UniqueIdentifier.ToString());
            writer.WriteElementString("usesdhcp", UsesDHCP.ToString());
            if(EncryptionKey != "")
                writer.WriteElementString("encryptionkey", EncryptionKey);
            if (SSID != "")
                writer.WriteElementString("ssid", SSID);
            if (TunnelEndpoint != null)
            {
                TunnelEndpoint.Save(writer, "tunnelendpoint");
            }
            foreach (NetworkInterface nIF in interfaces)
            {
                nIF.Save(writer);
            }
            writer.WriteEndElement();
        }

        public int GetUniqueIdentifier
        {
            get { return UniqueIdentifier; }
        }

        public NicType GetNicType
        {
            get { return myNicType; }
        }

        public bool isWireless()
        {
            if (myNicType == NicType.wport || myNicType == NicType.wlan)
                return true;
            return false;
        }

        public bool HasIP(UInt32 IP)
        {
            if (myNicType == NicType.port) return false;
            if (myNicType == NicType.none) return false;
            foreach (NetworkInterface IF in interfaces)
            {
                if (IF.myIP.Equals(IP))
                    return true;
            }
            return false;
        }

        public string NicName()
        {
            return _nic_name;
        }

        public string NicString(int index)
        {
            string connected = " ";
            if (isConnected(true)) connected = "*";
            if (myNicType == NicType.port || myNicType == NicType.wport)
            {
                NetworkDevice ND = ConnectedTo();
                if(ND != null)
                    return NicName() + connected + " -> " + ND.hostname;
                return NicName() + connected;
            }
            return NicName() + connected + "  " + MAC;
        }

        public List<string> NICRouteStrings(string GW)
        {
            List<string> thestrings = new List<string>();
            if (myNicType == NicType.port) return thestrings;
            if (myNicType == NicType.none) return thestrings;
            foreach (NetworkInterface iface in interfaces)
            {
                thestrings.Add(iface.RouteString(_nic_name,GW));
            }
            return thestrings;
        }

        public List<string> IPAddresses(bool UseCidr = false)
        {
            List<string> theIPs = new List<string>();
            if (myNicType == NicType.port) return theIPs;
            if (myNicType == NicType.wport) return theIPs;
            if (myNicType == NicType.none) return theIPs;
            string DHCPString = "";
            if (UsesDHCP && CanUseDHCP) DHCPString = "DHCP: ";
            foreach (NetworkInterface iface in interfaces)
            {
                theIPs.Add(DHCPString + iface.InterfaceString(UseCidr));
            }
            return theIPs;
        }

        public List<NB_IPAddress> IPAddressList()
        {
            List<NB_IPAddress> theIPs = new List<NB_IPAddress>();
            if (myNicType == NicType.port) return theIPs;
            if (myNicType == NicType.none) return theIPs;
            foreach (NetworkInterface iface in interfaces)
            {
                theIPs.Add(iface.myIP);
            }
            return theIPs;
        }

        public bool HasIPAddresses(NB_IPAddress dest)
        {
            if (myNicType == NicType.port) return false;
            if (myNicType == NicType.none) return false;
            if (dest == null) return false;
            foreach (NetworkInterface iface in interfaces)
            {
                if (iface.myIP.GetIP == dest.GetIP)
                    return true;
            }
            return false;
        }

        public bool HasBroadcastAddresses(NB_IPAddress dest)
        {
            if (myNicType == NicType.port) return false;
            if (myNicType == NicType.none) return false;
            if (myNicType == NicType.lo) return false;
            if (dest == null) return false;
            foreach (NetworkInterface iface in interfaces)
            {
                if (iface.myIP.BroadcastAddress == 0) continue; //a netmask of 0.0.0.0 causes grief
                if (iface.myIP.BroadcastAddress == dest.GetIP)
                    return true;//If they are pinging the broadcast IP
                if (iface.myIP.GetIPString == NB.BroadcastIPString)
                    return true;
            }
            return false;
        }

        public void EditInterface(int index, Form ParentForm)
        {
            if (index < 0 || index > interfaces.Count())
                return;
            interfaces[index].EditAddress(ParentForm);
        }
        public void DeleteInterface(int index)
        {
            if (index < 0 || index > interfaces.Count())
                return;
            if (interfaces.Count < 2)
                return; //We cannot delete the sole remaining interface
            interfaces.RemoveAt(index);
        }
        private int NextInterfaceNum()
        {
            string NICName = NicName();
            string testname = "";
            bool foundone = true;
            int which = 0;
            while(foundone)
            {
                foundone = false;
                testname = NICName;
                if (which != 0)
                    testname = testname + ":" + which.ToString();
                foreach(NetworkInterface oneif in interfaces)
                {
                    if(oneif.nic_name == testname)
                    {
                        foundone = true;
                        break;
                    }
                }
                if (foundone)
                    which++;
            }
            return which;
        }
        public void AddInterface()
        {
            string NICName = NicName();
            int Plus = NextInterfaceNum();
            if (Plus != 0) NICName += ":"+Plus.ToString();
            NetworkInterface iface = new NetworkInterface(NICName, NB.ZeroIPString, NB.ZeroIPString, myID);
            interfaces.Add(iface);
        }

        public NetworkInterface GetInterface(int index)
        {
            if (index < 0 || index > interfaces.Count())
                return null;
            return interfaces[index];
        }

        public bool isConnected(bool CheckForBrokenLink)
        {
            if(ConnectedLink != -1)
            {
                if (!CheckForBrokenLink) return true; //We only care if there is any link
                Network myNet = NB.GetNetwork();
                NetworkLink NL = myNet.GetLinkFromID(ConnectedLink);
                if(NL != null)
                {
                    if (NL.theLinkType != LinkType.broken) return true;
                    else return false;
                }
            }
            return false;
        }

        public NetworkDevice ConnectedTo()
        {
            if (ConnectedLink != -1)
            {
                Network myNet = NB.GetNetwork();
                NetworkLink NL = myNet.GetLinkFromID(ConnectedLink);
                if (NL != null)
                {
                    if (NL.IsSource(myID.HostID)) return myNet.GetDeviceFromID(NL.Dst);
                    else return myNet.GetDeviceFromID(NL.Src);
                }
            }
            return null;
        }
        public void Edit()
        {
            NetworkCardEditor nce = new NetworkCardEditor(this);
            nce.ShowDialog();
            //Now. update the interfaces if we need to do so.
            SetIPForDHCP();
        }

        public void SetIPForDHCP()
        {
            if (UsesDHCP && CanUseDHCP)
            {
                //remove any extra interfaces.
                //set the one interface to 0.0.0.0
                switch(myNicType)
                {
                    case NicType.eth:
                    case NicType.wlan:
                        interfaces.Clear();  //empty out all interfaces
                        //Give it an interface with an empty IP
                        NetworkInterface nInterface = new NetworkInterface(NicName(), NB.ZeroIPString, NB.ZeroIPString,myID);
                        interfaces.Add(nInterface);
                        break;
                }
            }
        }
        public void SetIPForDHCP(NB_IPAddress newIP)
        {
            if (UsesDHCP && CanUseDHCP)
            {
                bool doIt = true;
                if (interfaces.Count > 0 && interfaces[0].myIP.GetIPString == NB.ZeroIPString)
                {
                    doIt = true;
                }
                else
                {
                    if(interfaces.Count > 0)
                    {
                        //The IP address is not zero.  This means it already has an IP.
                        Random rnd = NB.GetRandom();
                        int chance = rnd.Next(6);
                        if (chance > 3)
                            doIt = false;
                    }
                }
                if (doIt && interfaces.Count > 0)
                {
                    interfaces[0].myIP = newIP;
                }
            }
        }

        public bool HasLocalInterface(NB_IPAddress theIP)
        {
            foreach(NetworkInterface nIF in interfaces)
            {
                if (nIF.isLocal(theIP))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Return the interface that is considered "local" to the IP address we are trying to reach
        /// </summary>
        /// <param name="theIP">An IP address we are trying to send out</param>
        /// <returns>null if no interface is local.  Otherwise, it returns the one that matches the packet</returns>
        public NetworkInterface LocalInterface(NB_IPAddress theIP, PacketMessage Tracker)
        {
            if (myNicType == NicType.port)
            {
                if (interfaces.Count == 1) return interfaces[0];
                return null; //ports have no local interfaces
            }
            foreach (NetworkInterface nIF in interfaces)
            {
                if (nIF.isLocal(theIP))
                {
                    if(Tracker != null)
                        Tracker.AddMessage(DebugLevel.routing, myID.HostName, string.Format(NB.Translate("NC_FoundLocalIF"), nIF.myIP.GetIP.ToIpString(), nIF.myIP.GetMask.ToIpString()));
                    if (Tracker != null && theIP != null)
                        Tracker.AddMessage(DebugLevel.routing, myID.HostName, "   " + string.Format(NB.Translate("NC_IfOfLocal"),theIP.GetIPString, theIP.GetGateway.ToIpString()));
                    return nIF;
                }
            }
            return null;
        }
        public NetworkInterface PrimaryInterface()
        {
            if (interfaces.Count == 1)
                return interfaces[0];
            return null;
        }


        public bool NicCanDoVLANs()
        {
            if (myNicType == NicType.eth) return true;
            if (myNicType == NicType.port) return true;
            if (myNicType == NicType.wan) return true;
            return false;
        }

        /***************************************
         * 
         * *************************************/
        public bool SendPacketOutNIC(Packet tPacket)
        {
            bool madeprogress = false;
            Packet nPacket = null;
            Network myNet = NB.GetNetwork();
            NetworkLink nl;
            if (NB.GetComponentType(tPacket.WhereAmI) != GeneralComponentType.device) return false;  //we cannot do this.
            NetworkDevice WhereFrom = (NetworkDevice)tPacket.WhereAmI;
            NicType what = GetNicType;
            if (!tPacket.isFresh && WhereFrom.IsWirelessForwarder() && (what == NicType.wlan || (WhereFrom.GetNetType() == NetworkComponentType.wap && what == NicType.eth)))
                what = NicType.wport;

            if (myNet.ItemHasTest(WhereFrom.hostname, NicName(), NetTestType.DeviceNICSprays))
            {
                if (!myNet.ItemTestIsComplete(WhereFrom.hostname, NicName(), NetTestType.DeviceNICSprays))
                {
                    if (tPacket.MyType == PacketType.bad_packet) return false;
                    //We should drop the current packet.
                    tPacket.AddMessage(DebugLevel.info, NB.Translate("N_ProssShouldContinTime"));
                    tPacket.Tracking.Status = NB.Translate("N_ProssShouldContinTime");
                    tPacket.MyStatus = PacketStatus.finished_failed;
                    //Then, we make a bad packet go out from here
                    WhereFrom.BadSprayCount = NB.NumBadPackets;
                    WhereFrom.BadPacketFromHere();
                    return false;
                }
            }


            switch (what)
            {
                case NicType.lo:
                case NicType.management_interface:
                case NicType.none:
                    break; //Do nothing
                case NicType.eth:
                case NicType.wlan:
                    //see if it the packet dest is local to this nic
                    //if (tPacket.MyType == PacketType.dhcp_answer)
                    //    Console.WriteLine("DHCP Answer");                    
                    foreach (NetworkInterface nf in interfaces.ToList())
                    {
                        if (tPacket.InboundNic != null && tPacket.InboundNic == this &&
                            tPacket.InboundInterface != null && tPacket.InboundInterface == nf)
                            continue; //skip sending it out this interface if it came in this interface.
                        if (tPacket.MyType == PacketType.dhcp_answer && tPacket.isFresh && tPacket.payloadIP != null && !nf.isLocal(tPacket.payloadIP))
                            continue; //If it is a dhcp broadcast, only send it out the interface it belongs to
                        if (tPacket.MyType == PacketType.arp_request && !nf.isLocal(tPacket.destIP))
                            continue;  //only send out arp requests on local networks
                        nPacket = new Packet(tPacket);//Creates a new packet but sets isfresh=false
                        nPacket.OutboundIF = nf;
                        if(nf != null)
                            nPacket.TsourceIP = nf.myIP;
                        nPacket.InboundInterface = tPacket.InboundInterface;
                        nf.ProcessOutboundPacket(nPacket);
                        if (nPacket.MyStatus == PacketStatus.finished || nPacket.MyStatus == PacketStatus.finished_failed || nPacket.MyStatus == PacketStatus.finished_ok)
                            continue; //If the packet cannot be sent out (VLAN stuff)
                        if (tPacket.OutboundIP == null || (nf.isLocal(tPacket.OutboundIP) || (tPacket.OutboundIP.GetIPString == NB.BroadcastIPString && tPacket.isFresh)))
                        {
                            if ((nf != null && nf.myIP.GetIPString != NB.ZeroIPString) || nPacket.MyType == PacketType.dhcp_request)
                            {
                                //this means we have a local interface to send it out of
                                //if(nPacket.sourceMAC == null || nPacket.sourceMAC == "" || nPacket.isFresh)
                                nPacket.sourceMAC = MAC;
                                //If the source IP is empty then it is a new packet.  We set the source to be us
                                if (nPacket.sourceIP == null || nPacket.sourceIP.GetIPString == NB.ZeroIPString)
                                    nPacket.sourceIP = nf.myIP;
                                nPacket.TsourceIP = nf.myIP;
                                if (nPacket.destMAC == null || nPacket.destMAC == "")
                                {
                                    nPacket.destMAC = WhereFrom.LookupArpFromIP(tPacket.OutboundIP.GetIPString);
                                    if (nPacket.MyType == PacketType.arp_request)
                                    {
                                        nPacket.destMAC = NB.BroadcastMACString;
                                    }

                                    if(nPacket.destMAC == "")
                                    {
                                        nPacket.AddMessage(DebugLevel.info, string.Format(NB.Translate("NC_NoIPOnSubStr"), nPacket.destIP.GetIPString));
                                        Network mynet = NB.GetNetwork();
                                        NetworkDevice nd = mynet.GetDeviceFromID(myID);
                                        string hostname = NB.Translate("NC_NoHost");
                                        if (nd != null) hostname = nd.hostname;
                                        nPacket.Tracking.Status = NB.LeftPad(hostname) + string.Format(NB.Translate("NC_NoIPOnSubStr"), hostname, nPacket.destIP.GetIPString);
                                        nPacket.Tracking.Finished = true;
                                        nPacket.MyStatus = PacketStatus.finished_failed;
                                        return false;
                                    }
                                }
                                nl = myNet.GetLinkFromID(ConnectedLink);
                                nPacket.StartOnLink(nl, WhereFrom); //This sends the packet down the link.
                                //Store outbound information here - som we expect the returning packet
                                WhereFrom.StoreOutgoingPacketInfo(nPacket);
                                myNet.addPacket(nPacket);
                                nPacket.PacketDump(myID.HostName + "-" + _nic_name, DebugPausePoint.packet_out);
                                madeprogress = true;
                            }
                        }
                    }
                    break;
                case NicType.wan:
                    //see if it the packet dest is local to this nic
                    if (tPacket.MyType == PacketType.dhcp_answer && tPacket.isFresh)
                        break; //We do not broadcast out the WAN port
                    foreach (NetworkInterface nf in interfaces.ToList())
                    {
                        nPacket = new Packet(tPacket);//Creates a new packet but sets isfresh=false
                        nPacket.OutboundIF = nf;
                        nPacket.InboundInterface = tPacket.InboundInterface;
                        if (nf != null)
                            nPacket.TsourceIP = nf.myIP;

                        nf.ProcessOutboundPacket(nPacket);
                        if (nPacket.MyStatus == PacketStatus.finished || nPacket.MyStatus == PacketStatus.finished_failed || nPacket.MyStatus == PacketStatus.finished_ok)
                            continue; //If the packet cannot be sent out (VLAN stuff)

                        if (tPacket.OutboundIP != null && (nf.isLocal(tPacket.OutboundIP) || tPacket.OutboundIP.GetIPString == NB.BroadcastIPString))
                        {
                            if ((nf != null && nf.myIP != null && nf.myIP.GetIPString != NB.ZeroIPString) || nPacket.MyType == PacketType.dhcp_request)
                            {
                                //this means we have a local interface to send it out of
                                nPacket.sourceMAC = MAC;
                                //If the source IP is empty then it originated from here.  We set the source to be us
                                if (nPacket.sourceIP == null || nPacket.sourceIP.GetIPString == NB.ZeroIPString)
                                {
                                    nPacket.sourceIP = nf.myIP;
                                    WhereFrom.StoreOutgoingPacketInfo(nPacket); //the packet is not masqueraded, just accepted
                                }
                                else
                                {
                                    //When we leave the WAN port, we are masqueraded.  Track that.
                                    WhereFrom.StoreOutgoingPacketInfo(nPacket, ResponseToPacket.masq);
                                    tPacket.TraversalInformation.AddPath(WhereFrom.hostname, TraversalTechnology.masquerade,""); //We just masqueraded
                                    //Now, we masquerade the packet so it looks like it comes fromhere
                                    nPacket.Tracking.AddMessage(DebugLevel.natting, WhereFrom.hostname, string.Format(NB.Translate("NC_ChangeIPStr"), nf.myIP.GetIPString));
                                    nPacket.sourceIP = nf.myIP;
                                }
                                nPacket.TsourceIP = nf.myIP;
                                if (nPacket.destMAC == null || nPacket.destMAC == "")
                                {
                                    nPacket.destMAC = WhereFrom.LookupArpFromIP(tPacket.OutboundIP.GetIPString);

                                    if (nPacket.destMAC == "")
                                    {
                                        nPacket.AddMessage(DebugLevel.debug, string.Format(NB.Translate("NC_NoIPOnSubStr"), nPacket.destIP.GetIPString));
                                        Network mynet = NB.GetNetwork();
                                        NetworkDevice nd = mynet.GetDeviceFromID(myID);
                                        string hostname = NB.Translate("NC_NoHost");
                                        if (nd != null) hostname = nd.hostname;
                                        nPacket.Tracking.Status = NB.LeftPad(hostname) + string.Format(NB.Translate("NC_NoIPOnSubStr"), nPacket.destIP.GetIPString);
                                        nPacket.MyStatus = PacketStatus.finished_failed;
                                        return false;
                                    }
                                }
                                nl = myNet.GetLinkFromID(ConnectedLink);
                                nPacket.StartOnLink(nl, WhereFrom); //This sends the packet down the link.
                                //Store outbound information here - som we expect the returning packet
                                WhereFrom.StoreOutgoingPacketInfo(nPacket);
                                myNet.addPacket(nPacket);
                                nPacket.PacketDump(myID.HostName + "-" + _nic_name, DebugPausePoint.packet_out);
                                madeprogress = true;
                            }
                        }
                    }
                    break;
                case NicType.tun:
                case NicType.vpn:
                    foreach (NetworkInterface nf in interfaces.ToList())
                    {
                        //make sure the firewall allows this.
                        if (tPacket.WhereAmI != null && tPacket.WhereAmI is NetworkDevice)
                        {
                            NetworkDevice ND = (NetworkDevice)tPacket.WhereAmI;
                            if (tPacket.InboundInterface != null && nf != null && !ND.FirewallAllows(tPacket.InboundInterface.nic_name, nf.nic_name))
                            {
                                //The firewall might block it.  Check to see if it is a response packet
                                ResponseToPacket rtp = ND.HowToRespondToPacket(tPacket);
                                if (rtp != ResponseToPacket.accept)
                                {
                                    //If we are here, the packet is rejected.
                                    string message = string.Format(NB.Translate("P_FirewallDropped"), ND.hostname);
                                    tPacket.AddMessage(DebugLevel.filtering, message);
                                    tPacket.Tracking.Status = message;
                                    tPacket.Tracking.AddMessage(DebugLevel.info, ND, message);
                                    tPacket.AddMessage(DebugLevel.info, message);
                                    tPacket.MyStatus = PacketStatus.finished_ok;
                                    break;
                                }
                            }
                        }
                        if (nf != null && nf.myIP != null && nf.myIP.GetIPString != NB.ZeroIPString)
                        {
                            //If the source IP is empty then it originated from here.   We set the source to be us
                            if (tPacket.sourceIP == null || tPacket.sourceIP.GetIPString == NB.ZeroIPString)
                            {
                                tPacket.sourceIP = nf.myIP;
                                WhereFrom.StoreOutgoingPacketInfo(tPacket); //the packet is not yet tunneled
                            }
                        }
                        if (nf.isLocal(tPacket.OutboundIP, false))
                        {
                            //We need to tell the original packet that it is inside another packet
                            tPacket.MyStatus = PacketStatus.encapsulated;
                            tPacket.TraversalInformation.AddPath(WhereFrom.hostname, TraversalTechnology.vpn, NicName()); //We just started a VPN
                            tPacket.TsourceIP = nf.myIP;
                            tPacket.destMAC = WhereFrom.LookupArpFromIP(tPacket.OutboundIP.GetIPString);
                            if (nf != null)
                                tPacket.TsourceIP = nf.myIP;

                            //We need to make a new, tunnel packet
                            if (myNicType == NicType.tun)
                                EncryptionKey = "";
                            Packet rnPacket = new Packet(tPacket);
                            WhereFrom.TunnelPacketFromHere(TunnelEndpoint, rnPacket, EncryptionKey);
                            //We need to send the new packet on (pass it back to the device to process)
                            madeprogress = true;
                            if(EncryptionKey != "")
                                tPacket.TraversalInformation.AddPath(WhereFrom.hostname, TraversalTechnology.vpn_encryption,NicName()); //We the packet is "encrypted"
                        }
                    }
                    break;
                case NicType.port:
                case NicType.wport:
                    if (tPacket.InboundNic == this) 
                        break; //This is the port we came in on.  Do not sent it back out this port
                    foreach (NetworkInterface nf in interfaces.ToList())
                    {
                        nPacket = new Packet(tPacket);
                        nPacket.OutboundIF = nf;
                        nPacket.InboundInterface = tPacket.InboundInterface;

                        nf.ProcessOutboundPacket(nPacket);
                        if (nPacket.MyStatus == PacketStatus.finished || nPacket.MyStatus == PacketStatus.finished_failed || nPacket.MyStatus == PacketStatus.finished_ok)
                            continue; //If the packet cannot be sent out (VLAN stuff)

                        if ((tPacket.InboundNic != null && tPacket.InboundNic.GetNicType == NicType.wan) || tPacket.InboundNic == null || tPacket.destMAC == "")
                        {
                            //We need to find destination MAC and set source MAC
                            if (nPacket.sourceMAC == null || nPacket.sourceMAC == "" || nPacket.isFresh)
                                nPacket.sourceMAC = MAC;
                            //Update the MAC
                            string getMAC = "";
                            if (tPacket.OutboundIP != null)
                                getMAC = WhereFrom.LookupArpFromIP(tPacket.OutboundIP.GetIPString);
                            else if (tPacket.destIP != null)
                                getMAC = WhereFrom.LookupArpFromIP(tPacket.destIP.GetIPString);
                            if (getMAC != "") nPacket.destMAC = getMAC;
                            if (nPacket.MyType == PacketType.arp_request)
                            {
                                nPacket.destMAC = NB.BroadcastMACString;
                            }

                            if (nPacket.destMAC == "")
                            {
                                nPacket.AddMessage(DebugLevel.debug, String.Format(NB.Translate("NC_NoIPOnSubStr"), nPacket.destIP.GetIPString));
                                Network mynet = NB.GetNetwork();
                                NetworkDevice nd = mynet.GetDeviceFromID(myID);
                                string hostname = NB.Translate("NC_NoHost");
                                if (nd != null) hostname = nd.hostname;
                                nPacket.Tracking.Status = NB.LeftPad(hostname) + String.Format(NB.Translate("NC_NoIPOnSubStr"), nPacket.destIP.GetIPString);
                                nPacket.MyStatus = PacketStatus.finished_failed;
                                return false;
                            }
                        }
                        if (HasBroadcastAddresses(tPacket.destIP))
                        {
                            //Broadcast packets will go to everything and we want a response from all of them.
                            nPacket.Tracking = PacketMessage.Clone(tPacket.Tracking);
                        }
                        if (nPacket.sourceMAC == null || nPacket.sourceMAC == "")
                        {
                            //Need to find the managament interface MAC
                            nPacket.sourceMAC = WhereFrom.HubManagementMAC();
                        }
                        if ((nPacket.sourceIP == null || nPacket.sourceIP.GetIPString == NB.ZeroIPString) && nPacket.MyType != PacketType.dhcp_request)
                        {
                            //set it to be the ip of management interface
                            nPacket.sourceIP = WhereFrom.HubManagementIP();
                            nPacket.sourceMAC = MAC;
                        }
                        if (nPacket.destMAC == null || nPacket.destMAC == "" && tPacket.OutboundIP != null)
                        {
                            nPacket.destMAC = WhereFrom.LookupArpFromIP(tPacket.OutboundIP.GetIPString);
                        }
                        if (nPacket.TsourceIP == null)
                            nPacket.TsourceIP = WhereFrom.HubManagementIP();
                        nl = myNet.GetLinkFromID(ConnectedLink);
                        if (nl == null)
                            break;
                        if ((nPacket.sourceIP == null || nPacket.sourceIP.GetIPString == NB.ZeroIPString) && tPacket.MyType != PacketType.dhcp_request) return false; //We still have no IP.  Do not send the packet out.
                        nPacket.StartOnLink(nl, WhereFrom); //This sends the packet down the link.
                        myNet.addPacket(nPacket);
                        if (tPacket.isFresh)
                            WhereFrom.StoreOutgoingPacketInfo(nPacket); //if it originated from here...
                        madeprogress = true;
                        nPacket.PacketDump(myID.HostName, DebugPausePoint.packet_out);
                    }
                    break;
            }
            return madeprogress;
        }

       

        public void ProcessInboundPacket(Packet tPacket)
        {
            Network mynet;
            NetworkDevice nd;
            //We make sure the MAC matches.
            mynet = NB.GetNetwork();
            nd = mynet.GetDeviceFromID(myID);

            if (mynet.ItemHasTest(nd.hostname, NicName(), NetTestType.DeviceNICSprays))
            {
                if (!mynet.ItemTestIsComplete(nd.hostname, NicName(), NetTestType.DeviceNICSprays))
                {
                    //We should drop the current packet.
                    tPacket.AddMessage(DebugLevel.info, NB.Translate("N_ProssShouldContinTime"));
                    tPacket.Tracking.Status = NB.Translate("N_ProssShouldContinTime");
                    tPacket.MyStatus = PacketStatus.finished_failed;
                    //Then, we make a bad packet go out from here
                    nd.BadPacketFromHere();
                    nd.BadSprayCount = NB.NumBadPackets;
                }
            }
            if (mynet.ItemHasTest(nd.hostname,NetTestType.DeviceIsFrozen))
            {
                if(!mynet.ItemTestIsComplete(nd.hostname, NetTestType.DeviceIsFrozen))
                {
                    //the device is busted and nonfunctional.  No response.
                    tPacket.AddMessage(DebugLevel.info, NB.Translate("N_ProssShouldContinTimeout"));
                    tPacket.Tracking.Status = NB.Translate("N_ProssShouldContinTimeout");
                    tPacket.MyStatus = PacketStatus.finished_failed;
                }
            }
            if (nd.IsBurned)
            {
                    //the device is busted and nonfunctional.  No response.
                    tPacket.AddMessage(DebugLevel.info, NB.Translate("NC_Burned"));
                    tPacket.Tracking.Status = NB.Translate("NC_Burned");
                    tPacket.MyStatus = PacketStatus.finished_failed;
            }

            if (tPacket == null) return;
            tPacket.InboundNic = this; //track which nic we came in on.
            if (myNicType == NicType.port || myNicType == NicType.wport || (nd.IsWirelessForwarder() && (myNicType == NicType.wlan || 
                (myNicType == NicType.eth && nd.GetNetType() == NetworkComponentType.wap ))))
            {
                //Try tracking the arp if we can
                if (tPacket.TsourceIP == null) tPacket.TsourceIP = tPacket.sourceIP;
                nd.StoreArp(tPacket.sourceMAC, tPacket.TsourceIP.GetIP.ToIpString(), myID);
                //If it is a return DHCP packet.  We should try to update the MAC
                if (tPacket.MyType == PacketType.dhcp_answer)
                {
                    HostNicID otherid = nd.NicIDFromArp(tPacket.destMAC);
                    if (otherid.HostID != -1)
                    {
                        string ipstring = NB.ZeroIPString;
                        if (tPacket.payloadIP != null)
                            ipstring = tPacket.payloadIP.GetIPString;
                        nd.StoreArp(tPacket.destMAC, ipstring, otherid);
                    }
                }
                return;
            }
            if(myNicType == NicType.wan)
            {
                mynet = NB.GetNetwork();
                nd = mynet.GetDeviceFromID(myID);

                if (nd.HowToRespondToPacket(tPacket) == ResponseToPacket.masq)
                {
                    NB_IPAddress oAddress = nd.PacketMasqueradeSource(tPacket);
                    if(oAddress != null)
                    {
                        tPacket.Tracking.AddMessage(DebugLevel.natting, nd.hostname, string.Format(NB.Translate("NC_ChangeIPBackStr"), oAddress.GetIPString));
                        tPacket.destIP = oAddress;
                    }
                }
                else if(!HasIP(tPacket.destIP.GetIP))
                {
                    tPacket.AddMessage(DebugLevel.routing, NB.Translate("NC_ProssInPackReject"));
                    tPacket.AddMessage(DebugLevel.debug, NB.Translate("NC_ProssInPackExpect"));
                    mynet = NB.GetNetwork();
                    nd = mynet.GetDeviceFromID(myID);
                    string hostname = NB.Translate("NC_NoHost");
                    if (nd != null) hostname = nd.hostname;
                    tPacket.Tracking.Status = string.Format(NB.Translate("NC_PackRejectStr"), hostname) ;
                    tPacket.MyStatus = PacketStatus.finished_failed;
                }
            }
            if(tPacket.destMAC == MAC || tPacket.destMAC == NB.BroadcastMACString || myNicType == NicType.port)
            {
                //It matches.  We are ok.  Anything to do?
                //If the NIC is a vpn, do that here.
            }
            else
            {
                tPacket.AddMessage(DebugLevel.routing,NB.Translate("NC_ProcessInboundPacket_DifferentMachine1"));
                tPacket.AddMessage(DebugLevel.debug, string.Format("  " + NB.Translate("NC_ProcessInboundPacket_DifferentMachine2"), MAC, tPacket.destMAC));
                mynet = NB.GetNetwork();
                nd = mynet.GetDeviceFromID(myID);
                string hostname = NB.Translate("NC_NoHost");
                if (nd != null) hostname = nd.hostname;
                tPacket.Tracking.Status = NB.LeftPad(hostname) + "  " + NB.Translate("NC_ProcessInboundPacket_DifferentMachine3");
                tPacket.MyStatus = PacketStatus.finished_failed;
            }
        }

        public void ClearIPs()
        {
            foreach(NetworkInterface nf in interfaces)
            {
                if (myNicType != NicType.lo)
                {
                    if(myNicType != NicType.port)
                        nf.myIP = new NB_IPAddress(NB.ZeroIPString); //do not do this for ports (have no IP)
                    nf.SetVLANTag(1, VLANTagType.Untagged);
                }
            }
        }

        public void UpdateAllNicsVLANInfoAfterClone()
        {
            foreach (NetworkInterface nf in interfaces)
            {
                nf.UpdateVLANsAfterClone();
            }
        }

        public void LockUsOutOfCard()
        {
            foreach (NetworkInterface nf in interfaces)
            {
                if (myNicType != NicType.lo)
                {
                    nf.myIP = new NB_IPAddress(NB.ZeroIPString);
                    nf.LockOutVLANInterface();
                }
            }
        }

        public NetworkInterface InterfaceFromVlanTag(Packet tPacket)
        {
            int ID = tPacket.VLANID;
            return InterfaceFromVlanTag(ID);
        }
        public NetworkInterface InterfaceFromVlanTag(int ID)
        {
            foreach (NetworkInterface oneIF in interfaces)
            {
                if (oneIF.GetVLANTag(ID) != VLANTagType.Forbidden)
                    return oneIF;
            }
            return null;
        }

        public NetworkInterface InterfaceFromName(string InterfaceName)
        {
            foreach (NetworkInterface oneIF in interfaces)
            {
                if (oneIF.nic_name == InterfaceName)
                    return oneIF;
            }
            return null;
        }

        public NB_IPAddress FirstIP()
        {
            List<NB_IPAddress> addresses = IPAddressList();
            if (addresses.Count > 0)
                return addresses[0];
            else
                return new NB_IPAddress(NB.ZeroIPString);
        }

        public static T Clone<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException(NB.Translate("NC_CloneSerialzable"), NB.Translate("_source"));
            }

            // Don't serialize a null object, simply return the default for that object
            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
        }

    }
}
