MODULE InitNetwork; (** AUTHOR "mvt"; PURPOSE "IP interface initialization and configuration"; *)

IMPORT Files, KernelLog, Modules, Plugins, Strings, XML, XMLObjects, XMLScanner,
		 XMLParser,
		 Network, IP, ICMP, DNSMod := DNS, DHCP, IPv4, IPv6; (* load all *)

CONST
	(** Error Codes *)
	Ok* = 0;
	NotFound* = 4001;
	NoConfigFile* = 4002;
	ConfigFileNotValid* = 4003;

	MaxNofInterfaces* = 10; (** Interface numbers vom 0 to 9 are accepted in Machine config *)


TYPE
	(* Interface and router config *)
	Config = POINTER TO RECORD
		interfaceConfigs: InterfaceConfig;
		routerConfigs: IPv6.RouterConfig;
		IPForwarding: BOOLEAN;
		EchoReply: BOOLEAN;
		PreferredProtocol: LONGINT;
		AutoNetConfigV4: BOOLEAN;
		AutoNetConfigV6: BOOLEAN;
		AutoNetConfigV6DNS: ARRAY DNSMod.MaxNofServer OF IP.Adr;
	END;


	(* A configuraton of a interface *)
	InterfaceConfig = POINTER TO RECORD
		Device: Plugins.Name;
		Protocol: LONGINT;
		Name: IP.Name;
		Domain: Strings.String;
		DHCP: BOOLEAN;
		LocalAdr: IP.Adr;
		Gateway: IP.Adr;
		Netmask: IP.Adr;
		Prefix: IP.Adr;
		DNS: ARRAY DNSMod.MaxNofServer OF IP.Adr;
		next: InterfaceConfig;
	END;

TYPE
	(* Active object that runs DHCP on the specified interface. *)
	RunnerDHCP = OBJECT
		VAR
			int: IP.Interface;
			res: LONGINT;

		PROCEDURE &Constr*(int: IP.Interface);
		BEGIN
			ASSERT(int # NIL);
			SELF.int := int;
		END Constr;

	BEGIN {ACTIVE}
		DHCP.RunDHCP(int, res);
		IF res = 0 THEN
			IP.OutInterface(int);
		END;
	END RunnerDHCP;


VAR
	hasXMLErrors: BOOLEAN;

	(* temporary variables used in module body *)
	res: LONGINT;


(* Error output for XML parser *)
PROCEDURE Error(pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR);
BEGIN
	KernelLog.String("Parse error in NetInit.XML at pos "); KernelLog.Int(pos, 5); KernelLog.String(" in line "); KernelLog.Int(line, 5);
	KernelLog.String(" row "); KernelLog.Int(row, 5); KernelLog.String(" - "); KernelLog.String(msg); KernelLog.Ln;
	hasXMLErrors := TRUE
END Error;


(**Get interface configurations from NetInit.XML for the specified device. *)
PROCEDURE GetConfig(CONST devName: ARRAY OF CHAR; VAR res:LONGINT): Config;
VAR
	netConfigElem: XML.Element;
	elem: XML.Element;
	elemStr: Strings.String;
	config: Config;
	interfaceConfig: InterfaceConfig;
	routerConfig: IPv6.RouterConfig;
	prefixConfig: IPv6.PrefixConfig;
	file: Files.File;
	reader: Files.Reader;
	scanner: XMLScanner.Scanner;
	parser: XMLParser.Parser;
	doc: XML.Document;
	ipv4Elem: XML.Element;
	ipv6Elem: XML.Element;
	i: LONGINT;
	interfaceElems: XMLObjects.ArrayCollection;
	routerElems: XMLObjects.ArrayCollection;
	prefixElems: XMLObjects.ArrayCollection;
	dnsElems: XMLObjects.ArrayCollection;
	intElem: XML.Element;
	routerElem: XML.Element;
	prefixElem: XML.Element;
	interfaceNbr: LONGINT;
	routerNbr: LONGINT;
	prefixNbr: LONGINT;
	attribute: XML.Attribute;
	p: ANY;


	(** Get a section with a specific name *)
	PROCEDURE GetSection(elem: XML.Element; CONST sectionName: ARRAY OF CHAR): XML.Element;
	VAR
		enum: XMLObjects.Enumerator;
		attribute: XML.Attribute;
		section: XML.Element;
		p: ANY;
		elemStr: Strings.String;

	BEGIN
		IF elem # NIL THEN
			enum := elem.GetContents();
			(* Search for elements equal "childName"  *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS XML.Element THEN
					elemStr := p(XML.Element).GetName();
					IF (elemStr^ = "Section") THEN
						attribute := p(XML.Element). GetAttribute("name");
						elemStr := attribute.GetValue();
						IF elemStr^ = sectionName THEN
							section := p(XML.Element);
						END;
					END;
				END;
			END;
		END;

		RETURN section;
	END GetSection;


	(** Sets a Boolean value from Setting of a section.
		If error is TRUE then hasXMLErrors is set to TRUE if child does not exist. *)
	PROCEDURE GetSettingBool(section: XML.Element; CONST settingName: ARRAY OF CHAR; error: BOOLEAN; VAR bool: BOOLEAN);
	VAR
		nameAttr: XML.Attribute;
		valueAttr: XML.Attribute;
		nameStr: Strings.String;
		valueStr: Strings.String;
		elemStr: Strings.String;
		settingFound: BOOLEAN;
		enum: XMLObjects.Enumerator;
		p: ANY;

	BEGIN
		settingFound := FALSE;

		IF section # NIL THEN
			enum := section.GetContents();
			(* Search for settings *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS (XML.Element) THEN
					elemStr := p(XML.Element).GetName();
					IF (elemStr^ = "Setting") THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						valueAttr := p(XML.Element).GetAttribute("value");
						IF (nameAttr # NIL) & (valueAttr # NIL) THEN
							nameStr := nameAttr.GetValue();
							valueStr := valueAttr.GetValue();
							IF nameStr^ = settingName THEN
								Strings.StrToBool(valueStr^, bool);
								settingFound := TRUE;
							END;
						END;
					END;
				END;
			END;
		ELSIF error THEN
			hasXMLErrors := TRUE;
		END;

		IF ~settingFound & error THEN
			hasXMLErrors := TRUE;
		END;
	END GetSettingBool;


	(** Sets a integer value from Setting of a section.
		If error is TRUE then hasXMLErrors is set to TRUE if child does not exist. *)
	PROCEDURE GetSettingInt(section: XML.Element; CONST settingName: ARRAY OF CHAR; error: BOOLEAN; VAR int : LONGINT);
	VAR
		nameAttr: XML.Attribute;
		valueAttr: XML.Attribute;
		nameStr: Strings.String;
		valueStr: Strings.String;
		elemStr: Strings.String;
		settingFound: BOOLEAN;
		enum: XMLObjects.Enumerator;
		p: ANY;

	BEGIN
		settingFound := FALSE;

		IF section # NIL THEN
			enum := section.GetContents();
			(* Search for settings *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS (XML.Element) THEN
					elemStr := p(XML.Element).GetName();
					IF (elemStr^ = "Setting") THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						valueAttr := p(XML.Element).GetAttribute("value");
						IF (nameAttr # NIL) & (valueAttr # NIL) THEN
							nameStr := nameAttr.GetValue();
							valueStr := valueAttr.GetValue();
							IF nameStr^ = settingName THEN
								Strings.StrToInt(valueStr^, int);
								settingFound := TRUE;
							END;
						END;
					END;
				END;
			END;
		ELSIF error THEN
			hasXMLErrors := TRUE;
		END;

		IF ~settingFound & error THEN
			hasXMLErrors := TRUE;
		END;
	END GetSettingInt;


	(** Sets a character array from Setting of a section.
		If error is TRUE then hasXMLErrors is set to TRUE if child does not exist. *)
	PROCEDURE GetSettingChars(section: XML.Element; CONST settingName: ARRAY OF CHAR; error: BOOLEAN; VAR chars: ARRAY OF CHAR);
	VAR
		nameAttr: XML.Attribute;
		valueAttr: XML.Attribute;
		nameStr: Strings.String;
		valueStr: Strings.String;
		elemStr: Strings.String;
		settingFound: BOOLEAN;
		enum: XMLObjects.Enumerator;
		p: ANY;

	BEGIN
		settingFound := FALSE;

		IF section # NIL THEN
			enum := section.GetContents();
			(* Search for settings *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS (XML.Element) THEN
					elemStr := p(XML.Element).GetName();
					IF (elemStr^ = "Setting") THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						valueAttr := p(XML.Element).GetAttribute("value");
						IF (nameAttr # NIL) & (valueAttr # NIL) THEN
							nameStr := nameAttr.GetValue();
							valueStr := valueAttr.GetValue();
							IF nameStr^ = settingName THEN
								COPY(valueStr^, chars);
								settingFound := TRUE;
							END;
						END;
					END;
				END;
			END;
		ELSIF error THEN
			hasXMLErrors := TRUE;
		END;

		IF ~settingFound & error THEN
			hasXMLErrors := TRUE;
		END;
	END GetSettingChars;


	(** Sets a string from Setting of a section.
		If error is TRUE then hasXMLErrors is set to TRUE if child does not exist. *)
	PROCEDURE GetSettingString(section: XML.Element; CONST settingName: ARRAY OF CHAR; error: BOOLEAN; VAR string: Strings.String);
	VAR
		nameAttr: XML.Attribute;
		valueAttr: XML.Attribute;
		nameStr: Strings.String;
		valueStr: Strings.String;
		elemStr: Strings.String;
		settingFound: BOOLEAN;
		enum: XMLObjects.Enumerator;
		p: ANY;

	BEGIN
		settingFound := FALSE;

		IF section # NIL THEN
			enum := section.GetContents();
			(* Search for settings *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS (XML.Element) THEN
					elemStr := p(XML.Element).GetName();
					IF (elemStr^ = "Setting") THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						valueAttr := p(XML.Element).GetAttribute("value");
						IF (nameAttr # NIL) & (valueAttr # NIL) THEN
							nameStr := nameAttr.GetValue();
							valueStr := valueAttr.GetValue();
							IF nameStr^ = settingName THEN
								string := valueStr;
								settingFound := TRUE;
							END;
						END;
					END;
				END;
			END;
		ELSIF error THEN
			hasXMLErrors := TRUE;
		END;

		IF ~settingFound & error THEN
			hasXMLErrors := TRUE;
		END;
	END GetSettingString;


	(** Sets an address from Setting of a section.
		If error is TRUE then hasXMLErrors is set to TRUE if child does not exist. *)
	PROCEDURE GetSettingAdr(section: XML.Element; CONST settingName: ARRAY OF CHAR; error: BOOLEAN; VAR adr: IP.Adr);
	VAR
		nameAttr: XML.Attribute;
		valueAttr: XML.Attribute;
		nameStr: Strings.String;
		valueStr: Strings.String;
		elemStr: Strings.String;
		settingFound: BOOLEAN;
		enum: XMLObjects.Enumerator;
		p: ANY;

	BEGIN
		settingFound := FALSE;

		IF section # NIL THEN
			enum := section.GetContents();
			(* Search for settings *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS (XML.Element) THEN
					elemStr := p(XML.Element).GetName();
					IF (elemStr^ = "Setting") THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						valueAttr := p(XML.Element).GetAttribute("value");
						IF (nameAttr # NIL) & (valueAttr # NIL) THEN
							nameStr := nameAttr.GetValue();
							valueStr := valueAttr.GetValue();
							IF nameStr^ = settingName THEN
								adr := IP.StrToAdr(valueStr^);
								settingFound := TRUE;
							END;
						END;
					END;
				END;
			END;
		ELSIF error THEN
			hasXMLErrors := TRUE;
		END;

		IF ~settingFound & error THEN
			hasXMLErrors := TRUE;
		END;
	END GetSettingAdr;


	(** Get a list of settings with specific name*)
	PROCEDURE GetSettings(elem: XML.Element; CONST settingName: ARRAY OF CHAR): XMLObjects.ArrayCollection;
	VAR
		settingCol: XMLObjects.ArrayCollection;
		enum: XMLObjects.Enumerator;
		p: ANY;
		elemName: Strings.String;
		nameAttr: XML.Attribute;
		nameStr: Strings.String;

	BEGIN
		IF elem # NIL THEN
			NEW(settingCol);
			enum := elem.GetContents();
			(* Search for settings equal "childName"  *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS XML.Element THEN
					elemName := p(XML.Element).GetName();
					IF elemName^ = "Setting" THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						IF nameAttr # NIL THEN
							nameStr := nameAttr.GetValue();
							IF nameStr^ = settingName THEN
								settingCol.Add(p(XML.Element));
							END;
						END;
					END;
				END;
			END;
		END;
		RETURN settingCol;
	END GetSettings;


	(** Get a section with a specific name *)
	PROCEDURE GetSections(elem: XML.Element; CONST sectionName: ARRAY OF CHAR): XMLObjects.ArrayCollection;
	VAR
		sectionCol: XMLObjects.ArrayCollection;
		enum: XMLObjects.Enumerator;
		p: ANY;
		elemName: Strings.String;
		nameAttr: XML.Attribute;
		nameStr: Strings.String;

	BEGIN
		IF elem # NIL THEN
			NEW(sectionCol);
			enum := elem.GetContents();
			(* Search for sections equal "childName"  *)
			WHILE enum.HasMoreElements() DO
				p := enum.GetNext();
				IF p IS XML.Element THEN
					elemName := p(XML.Element).GetName();
					IF elemName^ = "Section" THEN
						nameAttr := p(XML.Element).GetAttribute("name");
						IF nameAttr # NIL THEN
							nameStr := nameAttr.GetValue();
							IF nameStr^ = sectionName THEN
								sectionCol.Add(p(XML.Element));
							END;
						END;
					END;
				END;
			END;
		END;
		RETURN sectionCol;
	END GetSections;


	(* Read a IPv4 Interface *)
	PROCEDURE Readv4Interface;
	BEGIN
		interfaceElems := GetSections(ipv4Elem, "Interface");
		IF interfaceElems.GetNumberOfElements() # 0 THEN
			FOR interfaceNbr := 0 TO interfaceElems.GetNumberOfElements() - 1 DO
				p := interfaceElems.GetElement(interfaceNbr);
				intElem :=p (XML.Element);
				(* init config *)
				NEW(interfaceConfig);
				(*init config *)
				FOR i := 0 TO LEN(interfaceConfig.DNS) - 1 DO
					interfaceConfig.DNS[i] := IP.NilAdr;
				END;
				interfaceConfig.Device[0] := 0X;
				interfaceConfig.Protocol := IP.IPv4;
				interfaceConfig.Name := "";
				interfaceConfig.Domain := NIL;
				interfaceConfig.DHCP := TRUE;
				interfaceConfig.LocalAdr := IP.NilAdr;
				interfaceConfig.Gateway := IP.NilAdr;
				interfaceConfig.Netmask := IP.NilAdr;
				interfaceConfig.Prefix := IP.NilAdr;

				(* Device *)
				GetSettingChars(intElem, "Device", TRUE, interfaceConfig.Device);

				(* Name *)
				GetSettingChars(intElem, "Name", TRUE, interfaceConfig.Name);

				(* Domain *)
				GetSettingString(intElem, "Domain", FALSE, interfaceConfig.Domain);

				(* DHCP *)
				GetSettingBool(intElem, "DHCP", FALSE, interfaceConfig.DHCP);

				(* LocalAdr *)
				GetSettingAdr(intElem, "LocalAdr", FALSE, interfaceConfig.LocalAdr);

				(* Gateway *)
				GetSettingAdr(intElem, "Gateway", FALSE, interfaceConfig.Gateway);

				(* Netmask *)
				GetSettingAdr(intElem, "Netmask", FALSE, interfaceConfig.Netmask);

				(* DNS *)
				dnsElems := GetSettings(intElem, "DNS");
				FOR i := 0 TO Strings.Min(dnsElems.GetNumberOfElements(), DNSMod.MaxNofServer) - 1 DO
					p := dnsElems.GetElement(i);
					elem := p(XML.Element);
					attribute := elem.GetAttribute("value");
					IF attribute # NIL THEN
						elemStr := attribute.GetValue();
						IF elemStr # NIL THEN
							interfaceConfig.DNS[i] := IP.StrToAdr(elemStr^);
						END;
					END;
				END;

				(* IF configuration for right device save it *)
				IF (interfaceConfig # NIL) & (interfaceConfig.Device = devName) THEN
					interfaceConfig.next := config.interfaceConfigs;
					config.interfaceConfigs := interfaceConfig;
				END;
			END;
		END;
	END Readv4Interface;


	(* Read a IPv6 interface *)
	PROCEDURE Readv6Interface;
	BEGIN
		interfaceElems := GetSections(ipv6Elem, "Interface");
		IF interfaceElems.GetNumberOfElements() # 0 THEN
			FOR interfaceNbr := 0 TO interfaceElems.GetNumberOfElements() - 1 DO
				p := interfaceElems.GetElement(interfaceNbr);
				intElem :=p (XML.Element);
				(* init config *)
				NEW(interfaceConfig);
				(*init config *)
				FOR i := 0 TO LEN(interfaceConfig.DNS) - 1 DO
					interfaceConfig.DNS[i] := IP.NilAdr;
				END;
				interfaceConfig.Device[0] := 0X;
				interfaceConfig.Protocol := IP.IPv6;
				interfaceConfig.Name := "";
				interfaceConfig.Domain := NIL;
				interfaceConfig.DHCP := TRUE;
				interfaceConfig.LocalAdr := IP.NilAdr;
				interfaceConfig.Gateway := IP.NilAdr;
				interfaceConfig.Netmask := IP.NilAdr;
				interfaceConfig.Prefix := IP.NilAdr;

				(* Device *)
				GetSettingChars(intElem, "Device", TRUE, interfaceConfig.Device);

				(* Name *)
				GetSettingChars(intElem, "Name", TRUE, interfaceConfig.Name);

				(* Domain *)
				GetSettingString(intElem, "Domain", FALSE, interfaceConfig.Domain);

				(* DHCP *)
				GetSettingBool(intElem, "DHCP", FALSE, interfaceConfig.DHCP);

				(* LocalAdr *)
				GetSettingAdr(intElem, "LocalAdr", FALSE, interfaceConfig.LocalAdr);

				(*Prefix *)
				GetSettingAdr(intElem, "Prefix", FALSE, interfaceConfig.Prefix);

				(* DNS *)
				dnsElems := GetSettings(intElem, "DNS");
				FOR i := 0 TO Strings.Min(dnsElems.GetNumberOfElements(), DNSMod.MaxNofServer) - 1 DO
					p := dnsElems.GetElement(i);
					elem := p(XML.Element);
					attribute := elem.GetAttribute("value");
					IF attribute # NIL THEN
						elemStr := attribute.GetValue();
						IF elemStr # NIL THEN
							interfaceConfig.DNS[i] := IP.StrToAdr(elemStr^);
						END;
					END;
				END;

				(* IF configuration for right device save it *)
				IF (interfaceConfig # NIL) & (interfaceConfig.Device = devName) THEN
					interfaceConfig.next := config.interfaceConfigs;
					config.interfaceConfigs := interfaceConfig;
				END;
			END;
		END;
	END Readv6Interface;


	(* Read router configurations *)
	PROCEDURE ReadRouter;
	BEGIN
		routerElems := GetSections(ipv6Elem, "Router");
		IF routerElems.GetNumberOfElements() # 0 THEN
			FOR routerNbr := 0 TO routerElems.GetNumberOfElements() - 1 DO
				p := routerElems.GetElement(routerNbr);
				routerElem :=p (XML.Element);
				(* init router config *)
				NEW(routerConfig);

				(* Set defaults *)
				routerConfig.Device := "";
				routerConfig.SendRouterAdvertisements := FALSE;
				routerConfig.ManagedAddressConfig := FALSE;
				routerConfig.OtherStatefulConfig := FALSE;
				routerConfig.LinkMTU := 0;	(* zero means don't send MTU option *)
				routerConfig.ReachableTime := 0;
				routerConfig.RetransTimer := 0;
				routerConfig.CurrentHopLimit := 0; (* unspecified *)
				routerConfig.Lifetime := 3 * 600;	(* seconds *)
				routerConfig.Prefixes := NIL;

				(* Device *)
				GetSettingChars(routerElem, "Device", TRUE, routerConfig.Device);

				(* SendRouterAdvertisement *)
				GetSettingBool(routerElem, "SendRouterAdvertisements", FALSE, routerConfig.SendRouterAdvertisements);

				(* ManagedAddressConfig *)
				GetSettingBool(routerElem, "ManagedAddressConfig", FALSE,  routerConfig.ManagedAddressConfig);

				(* OtherStatefulConfig *)
				GetSettingBool(routerElem, "OtherStatefulConfig", FALSE, routerConfig.OtherStatefulConfig);

				(* LinkMTU *)
				GetSettingInt(routerElem, "LinkMTU", FALSE, routerConfig.LinkMTU);

				(* ReachableTime *)
				GetSettingInt(routerElem, "ReachableTime", FALSE, routerConfig.ReachableTime);

				(* RetransTimer *)
				GetSettingInt(routerElem, "RetransTimer", FALSE, routerConfig.RetransTimer);

				(* Current Hop Limit *)
				GetSettingInt(routerElem, "CurrentHopLimit", FALSE, routerConfig.CurrentHopLimit);

				(* Lifetime *)
				GetSettingInt(routerElem, "Lifetime", FALSE, routerConfig.Lifetime);

				(* Parse prefixes *)
				prefixElems := GetSections(routerElem, "Prefix");
				IF prefixElems.GetNumberOfElements() # 0 THEN
					FOR prefixNbr := 0 TO prefixElems.GetNumberOfElements() - 1 DO
						p := prefixElems.GetElement(prefixNbr);
						prefixElem :=p (XML.Element);
						(* init prefix config *)
						NEW(prefixConfig);

						(* Set defaults *)
						prefixConfig.Prefix := IP.NilAdr;
						prefixConfig.IsSitePrefix := FALSE;
						prefixConfig.ValidLifetime := 2592000; (* in seconds is 30 days *)
						prefixConfig.OnLink := TRUE;
						prefixConfig.PreferredLifetime := 604800; (* in seconds is 7 days *)
						prefixConfig.Autonomous := TRUE;

						(* Prefix *)
						GetSettingAdr(prefixElem, "Prefix", TRUE, prefixConfig.Prefix);

						(* IsSitePrefix *)
						GetSettingBool(prefixElem, "IsSitePrefix", FALSE, prefixConfig.IsSitePrefix);

						(* ValidLifetime *)
						GetSettingInt(prefixElem, "ValidLifetime", FALSE, prefixConfig.ValidLifetime);

						(* OnLink *)
						GetSettingBool(prefixElem, "OnLink", FALSE, prefixConfig.OnLink);

						(* PreferredLifetime *)
						GetSettingInt(prefixElem, "PreferredLifetime", FALSE, prefixConfig.PreferredLifetime);

						(* Autonomous *)
						GetSettingBool(prefixElem, "Autonomous", FALSE, prefixConfig.Autonomous);

						prefixConfig.next := routerConfig.Prefixes;
						routerConfig.Prefixes := prefixConfig;
					END;
				END;

				(* IF configuration for right device save it *)
				IF (routerConfig # NIL) & (routerConfig.Device = devName) THEN
					routerConfig.next := config.routerConfigs;
					config.routerConfigs := routerConfig;
				END;
			END;
		END;
	END ReadRouter;


BEGIN
	(* init *)
	hasXMLErrors := FALSE;
	res := Ok;

	NEW(config);
	config.IPForwarding:= FALSE;	(* defaults *)
	config.EchoReply := TRUE;
	config.AutoNetConfigV4 := TRUE;
	config.AutoNetConfigV6 := TRUE;
	config.PreferredProtocol := IP.IPv4;

	(* Load NetInit.XML *)
	file := Files.Old("Configuration.XML");
	IF file # NIL THEN
		Files.OpenReader(reader, file, 0);
		NEW(scanner, reader);
		scanner.reportError := Error;
		NEW(parser, scanner);
		parser.reportError := Error;
		doc := parser.Parse();
		netConfigElem := doc.GetRoot();
		netConfigElem := GetSection(netConfigElem, "NetConfig");

		IF hasXMLErrors THEN
			KernelLog.String("Net configuration not loaded"); KernelLog.Ln;
			res := ConfigFileNotValid;
			RETURN NIL;
		END;

		IF devName = "Loopback" THEN
			(* Make two loopback configuration (IPv4, IPv6) *)
			NEW(interfaceConfig);
			(*init config for IPv4 *)
			FOR i := 0 TO LEN(interfaceConfig.DNS) - 1 DO
				interfaceConfig.DNS[i] := IP.NilAdr;
			END;
			COPY(devName, interfaceConfig.Device);
			interfaceConfig.Protocol := IP.IPv4;
			interfaceConfig.Name := "Loopbackv4";
			interfaceConfig.Domain := NIL;
			interfaceConfig.DHCP := FALSE;
			interfaceConfig.LocalAdr := IP.StrToAdr("127.0.0.1");
			interfaceConfig.Gateway := IP.NilAdr;
			interfaceConfig.Netmask := IP.StrToAdr("255.255.0.0");
			interfaceConfig.Prefix := IP.NilAdr;

			interfaceConfig.next := config.interfaceConfigs;
			config.interfaceConfigs := interfaceConfig;

			(* init config for IPv6 *)
			NEW (interfaceConfig);
				FOR i := 0 TO LEN(interfaceConfig.DNS) - 1 DO
				interfaceConfig.DNS[i] := IP.NilAdr;
			END;
			COPY(devName, interfaceConfig.Device);
			interfaceConfig.Protocol := IP.IPv6;
			interfaceConfig.Name := "Loopbackv6";
			interfaceConfig.Domain := NIL;
			interfaceConfig.DHCP := FALSE;
			interfaceConfig.LocalAdr := IP.StrToAdr("::1");
			interfaceConfig.Gateway := IP.NilAdr;
			interfaceConfig.Netmask := IP.NilAdr;
			interfaceConfig.Prefix := IP.NilAdr;
			interfaceConfig.Prefix.data := 64;
			interfaceConfig.Prefix.usedProtocol := IP.IPv6;

			interfaceConfig.next := config.interfaceConfigs;
			config.interfaceConfigs := interfaceConfig;
		END;

		IF netConfigElem # NIL THEN
			(* IPForwarding *)
			GetSettingBool(netConfigElem, "IPForwarding", FALSE, config.IPForwarding);

			(* EchoReply *)
			GetSettingBool(netConfigElem, "EchoReply", FALSE, config.EchoReply);

			(* Preferred protocol *)
			GetSettingInt(netConfigElem, "PreferredProtocol", FALSE, config.PreferredProtocol);

			(* IPv4 *)
			ipv4Elem := GetSection(netConfigElem, "IPv4");

			IF ipv4Elem # NIL THEN
				(* AutoNetConfig *)
				elem := GetSection(ipv4Elem, "AutoNetConfig");
				IF elem # NIL THEN
					GetSettingBool(elem, "Enabled", TRUE, config.AutoNetConfigV4);
				ELSE
					hasXMLErrors := TRUE;
				END;

				Readv4Interface;

			ELSE
				hasXMLErrors := TRUE;
			END;

			(* IPv6 *)
			ipv6Elem := GetSection(netConfigElem, "IPv6");
			IF ipv6Elem # NIL THEN
				(* AutoNetConfig *)
				elem := GetSection(ipv6Elem, "AutoNetConfig");
				IF elem # NIL THEN
					(* Enabled *)
					GetSettingBool(elem, "Enabled", TRUE, config.AutoNetConfigV6);

					(* DNS *)
					dnsElems := GetSettings(elem, "DNS");
					FOR i := 0 TO Strings.Min(dnsElems.GetNumberOfElements(), DNSMod.MaxNofServer) - 1 DO
						p := dnsElems.GetElement(i);
						elem := p(XML.Element);
						attribute := elem.GetAttribute("value");
						IF attribute # NIL THEN
							elemStr := attribute.GetValue();
							IF elemStr # NIL THEN
								config.AutoNetConfigV6DNS[i] := IP.StrToAdr(elemStr^);
							END;
						END;
					END;
				ELSE
					hasXMLErrors := TRUE;
				END;
				Readv6Interface;
				ReadRouter;
			ELSE
				hasXMLErrors := TRUE;
			END;
		ELSE
			hasXMLErrors := TRUE;
		END;

		IF config.interfaceConfigs = NIL THEN
			(* No configuration for this device, deliver config with only IPForwarding, EchoReplay, AutoNetConfig *)
			NEW(interfaceConfig);
			(*init config *)
			FOR i := 0 TO LEN(interfaceConfig.DNS) - 1 DO
				interfaceConfig.DNS[i] := IP.NilAdr;
			END;
			interfaceConfig.Device[0] := 0X;
			interfaceConfig.Protocol := 0;
			interfaceConfig.Name := "";
			interfaceConfig.Domain := NIL;
			interfaceConfig.DHCP := TRUE;
			interfaceConfig.LocalAdr := IP.NilAdr;
			interfaceConfig.Gateway := IP.NilAdr;
			interfaceConfig.Netmask := IP.NilAdr;
			interfaceConfig.Prefix := IP.NilAdr;
			interfaceConfig.next := NIL;
			config.interfaceConfigs := interfaceConfig;
		END;
		RETURN config;
	ELSE
		KernelLog.String("Network configuration file (NetInit.XML) not found"); KernelLog.Ln;
		res := NoConfigFile;
		RETURN NIL;
	END;
	RETURN NIL;
END GetConfig;


PROCEDURE Added(dev: Network.LinkDevice);
VAR
	config: Config;
	interfaceConfigItem: InterfaceConfig;
	ipv4IntFound: BOOLEAN;
	runnerDHCP: RunnerDHCP;
	int: IP.Interface;	 (* if autoconf = true; there are two ip interfaces (v4 & v6) *)
	intv4: IPv4.Interface;
	intv6: IPv6.Interface;
	intName: IP.Name; (* if autoconf = true; there are two ip interfaces (v4 & v6) *)
	i, res: LONGINT;
	linkLocalAdr: IP.Adr;
	linkLocalPrefix: IP.Adr;
	routerConfigItem: IPv6.RouterConfig;

BEGIN
	KernelLog.String("InitNetwork: LinkDevice '"); KernelLog.String(dev.name); KernelLog.String("' found."); KernelLog.Ln;
	config := GetConfig(dev.name, res);
	KernelLog.String("InitNetwork: LinkDevice '"); KernelLog.String(dev.name);
	KernelLog.String("': Get interface configuration. Error code: "); KernelLog.Int(res, 0); KernelLog.Ln;

	IF res = Ok THEN
		(* auto configuration: an IPv4 interface per device *)
		ipv4IntFound := FALSE;

		IP.preferredProtocol := config.PreferredProtocol;
		IP.IPForwarding := config.IPForwarding;
		IP.EchoReply := config.EchoReply;

		interfaceConfigItem := config.interfaceConfigs;

		WHILE interfaceConfigItem # NIL DO
			(* IPv4 or IPv6 interface? *)
			CASE interfaceConfigItem.Protocol OF
				IP.IPv4:
					NEW(intv4, interfaceConfigItem.Name, dev, res);
					int := intv4;

				|IP.IPv6:
					NEW(intv6, interfaceConfigItem.Name, dev, res);
					int := intv6;

				ELSE

			END;
			IF (int # NIL) & (res = IP.Ok) THEN
				IF int IS IPv4.Interface THEN
					ipv4IntFound := TRUE;
					int.SetAdrs(interfaceConfigItem.LocalAdr, interfaceConfigItem.Netmask, interfaceConfigItem.Gateway, res);
				END;
				IF int IS IPv6.Interface THEN
					int.SetAdrs(interfaceConfigItem.LocalAdr, interfaceConfigItem.Prefix, interfaceConfigItem.Gateway, res);
				END;
				IF res = IP.Ok THEN
					FOR i := 0 TO DNSMod.MaxNofServer - 1 DO
						IF ~IP.IsNilAdr(interfaceConfigItem.DNS[i]) THEN
							int.DNSAdd(interfaceConfigItem.DNS[i]);
						END;
					END;
					i := 0;

					IF interfaceConfigItem.DHCP THEN
						NEW(runnerDHCP, int);
					END;

					KernelLog.String("InitNetwork: Add interface for LinkDevice '"); KernelLog.String(dev.name);
					KernelLog.String("'. Error code: "); KernelLog.Int(res, 0); KernelLog.Ln;
					IF (res = Ok) & ~interfaceConfigItem.DHCP THEN
					IP.OutInterface(int);
					END;
				END;
			END;

			interfaceConfigItem := interfaceConfigItem.next;
		END;

		IF config.AutoNetConfigV6 & (dev.name # "Loopback") THEN
			(* create a link-local IPv6 interface *)
			Strings.Concat("v6link-local", dev.name, intName);
			NEW (intv6, intName, dev, res);
			int := intv6;
			IF res = IP.Ok THEN
				int(IPv6.Interface).autoconfigurated := TRUE;

				linkLocalAdr := IP.NilAdr;
				linkLocalPrefix := IP.NilAdr;
				linkLocalPrefix.usedProtocol := IP.IPv6;

				int(IPv6.Interface).SetInterfaceID(linkLocalAdr);
				(* write link local prefix and prefix *)
				linkLocalAdr.ipv6Adr[0] := 0FEX;
				linkLocalAdr.ipv6Adr[1] := 80X;
				linkLocalPrefix.ipv6Adr[0] := 0FEX;
				linkLocalPrefix.ipv6Adr[1] := 80X;
				linkLocalPrefix.data := 64;

				int.SetAdrs(linkLocalAdr, linkLocalPrefix, IP.NilAdr, res);
				IF res = IP.Ok THEN
					FOR i := 0 TO DNSMod.MaxNofServer - 1 DO
						IF ~IP.IsNilAdr(config.AutoNetConfigV6DNS[i]) THEN
							int.DNSAdd(config.AutoNetConfigV6DNS[i]);
						END;
					END;
					KernelLog.String("InitNetwork: Add interface for LinkDevice '"); KernelLog.String(dev.name);
					KernelLog.String("'. Error code: "); KernelLog.Int(res, 0); KernelLog.Ln;
					IP.OutInterface(int);

					(* initiate Routers Solicitation for auto-address-configuration *)
					int(IPv6.Interface).createStatelessInterface := TRUE;
					int(IPv6.Interface).RouterSolicitation;

					(* Is this device a router? *)
					routerConfigItem := config.routerConfigs;
					(* search for current device *)
					WHILE (routerConfigItem # NIL) & (routerConfigItem.Device # dev.name) DO
						routerConfigItem := routerConfigItem.next;
					END;

					IF routerConfigItem # NIL THEN
						(* found a router configuration *)
						int(IPv6.Interface).ConfigAsRouter(routerConfigItem);
					END;
				END;
			END;
		END;

		IF config.AutoNetConfigV4  & (dev.name # "Loopback") THEN
			(* create automatic IPv4 interface if there is none *)
			IF ~ipv4IntFound THEN
				(* create an ipv4 interface (DHCP on) *)
				Strings.Concat("v4auto", dev.name, intName);
				NEW(intv4, intName, dev, res);
				int := intv4;

				IF res = IP.Ok THEN
					NEW(runnerDHCP, int);

					KernelLog.String("InitNetwork: Add interface for LinkDevice '"); KernelLog.String(dev.name);
					KernelLog.String("'. Error code: "); KernelLog.Int(res, 0); KernelLog.Ln;
				END;
			END;
		END;
	END;
END Added;


(* Called for each LinkDevice that was removed from the registry. Remove the according interfaces. *)
PROCEDURE Removed(dev: Network.LinkDevice);
VAR int: IP.Interface;
BEGIN
	KernelLog.String("InitNetwork: LinkDevice '"); KernelLog.String(dev.name); KernelLog.String("' removed."); KernelLog.Ln;
	int := IP.InterfaceByDevice(dev);
	WHILE int # NIL DO
		int.Close();
		KernelLog.String("InitNetwork: IP Interface '"); KernelLog.String(int.name); KernelLog.String("' removed."); KernelLog.Ln;
		int := IP.InterfaceByDevice(dev);
	END;
END Removed;


(* Handle events of installed/removed devices *)
PROCEDURE EventHandler(event: LONGINT; plugin: Plugins.Plugin);
BEGIN
	IF event = Plugins.EventAdd THEN
		Added(plugin(Network.LinkDevice));
	ELSIF event = Plugins.EventRemove THEN
		Removed(plugin(Network.LinkDevice));
	ELSE
		(* unknown event *)
	END;
END EventHandler;


(* Handler for Enumerate() *)
PROCEDURE PluginHandler(plugin: Plugins.Plugin);
BEGIN
	Added(plugin(Network.LinkDevice));
END PluginHandler;


(** Initialize the IP stack and configure all IP interfaces. *)
PROCEDURE Init*;
END Init;

PROCEDURE Cleanup;
BEGIN
	Network.registry.RemoveEventHandler(EventHandler, res);
	ASSERT(res = Plugins.Ok);
END Cleanup;

BEGIN
	ICMP.InitDelegates();

	Network.registry.AddEventHandler(EventHandler, res);
	ASSERT(res = Plugins.Ok);

	Modules.InstallTermHandler(Cleanup);

	(* Handle all previously installed devices *)
	KernelLog.String("InitNetwork: Module initialized. Searching for installed devices..."); KernelLog.Ln;
	Network.registry.Enumerate(PluginHandler);
	KernelLog.String("InitNetwork: Finished searching for installed devices."); KernelLog.Ln;
END InitNetwork.

(*
History:
01.11.2003	mvt	Created
02.05.2005	eb	Uses Configuration.XML
06.03.2006	sst	Procedure Removed: remove all interfaces that belong to the device that is removed, not just one
*)