mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-03 03:25:20 +00:00
conf: support abstracted interface info in network XML
The network XML is updated in the following ways: 1) The <forward> element can now contain a list of forward interfaces: <forward .... > <interface dev='eth10'/> <interface dev='eth11'/> <interface dev='eth12'/> <interface dev='eth13'/> </forward> The first of these takes the place of the dev attribute that is normally in <forward> - when defining a network you can specify either one, and on output both will be present. If you specify both on input, they must match. 2) In addition to forward modes of 'nat' and 'route', these new modes are supported: private, passthrough, vepa - when this network is referenced by a domain's interface, it will have the same effect as if the interface had been defined as type='direct', e.g.: <interface type='direct'> <source mode='${mode}' dev='${dev}> ... </interface> where ${mode} is one of the three new modes, and ${dev} is an interface selected from the list given in <forward>. bridge - if a <forward> dev (or multiple devs) is defined, and forward mode is 'bridge' this is just like the modes 'private', 'passthrough', and 'vepa' above. If there is no forward dev specified but a bridge name is given (e.g. "<bridge name='br0'/>"), then guest interfaces using this network will use libvirt's "host bridge" mode, equivalent to this: <interface type='bridge'> <source bridge='${bridge-name}'/> ... </interface> 3) A network can have multiple <portgroup> elements, which may be selected by the guest interface definition (by adding "portgroup='${name}'" in the <source> element along with the network name). Currently a portgroup can only contain a virtportprofile, but the intent is that other configuration items may be put there int the future (e.g. bandwidth config). When building a guest's interface, if the <interface> XML itself has no virtportprofile, and if the requested network has a portgroup with a name matching the name given in the <interface> (or if one of the network's portgroups is marked with the "default='yes'" attribute), the virtportprofile from that portgroup will be used by the interface. 4) A network can have a virtportprofile defined at the top level, which will be used by a guest interface when connecting in one of the 'direct' modes if the guest interface XML itself hasn't specified any virtportprofile, and if there are also no matching portgroups on the network.
This commit is contained in:
parent
07f4136993
commit
40fd7073be
@ -7,6 +7,7 @@
|
||||
</start>
|
||||
|
||||
<include href='basictypes.rng'/>
|
||||
<include href='networkcommon.rng'/>
|
||||
|
||||
<define name="network">
|
||||
|
||||
@ -77,12 +78,48 @@
|
||||
<choice>
|
||||
<value>nat</value>
|
||||
<value>route</value>
|
||||
<value>bridge</value>
|
||||
<value>passthrough</value>
|
||||
<value>private</value>
|
||||
<value>vepa</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</optional>
|
||||
<zeroOrMore>
|
||||
<element name='interface'>
|
||||
<attribute name='dev'>
|
||||
<ref name='deviceName'/>
|
||||
</attribute>
|
||||
</element>
|
||||
</zeroOrMore>
|
||||
</element>
|
||||
</optional>
|
||||
|
||||
<!-- <virtualport> element -->
|
||||
<optional>
|
||||
<ref name="virtualPortProfile"/>
|
||||
</optional>
|
||||
|
||||
<!-- <portgroup> elements -->
|
||||
<zeroOrMore>
|
||||
<element name="portgroup">
|
||||
<attribute name="name">
|
||||
<ref name="deviceName"/>
|
||||
</attribute>
|
||||
<optional>
|
||||
<attribute name="default">
|
||||
<choice>
|
||||
<value>yes</value>
|
||||
<value>no</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<ref name="virtualPortProfile"/>
|
||||
</optional>
|
||||
</element>
|
||||
</zeroOrMore>
|
||||
|
||||
<!-- <domain> element -->
|
||||
<optional>
|
||||
<element name="domain">
|
||||
|
@ -50,7 +50,7 @@ VIR_ENUM_DECL(virNetworkForward)
|
||||
|
||||
VIR_ENUM_IMPL(virNetworkForward,
|
||||
VIR_NETWORK_FORWARD_LAST,
|
||||
"none", "nat", "route" )
|
||||
"none", "nat", "route", "bridge", "private", "vepa", "passthrough" )
|
||||
|
||||
#define virNetworkReportError(code, ...) \
|
||||
virReportErrorHelper(VIR_FROM_NETWORK, code, __FILE__, \
|
||||
@ -87,6 +87,19 @@ virNetworkObjPtr virNetworkFindByName(const virNetworkObjListPtr nets,
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
virPortGroupDefClear(virPortGroupDefPtr def)
|
||||
{
|
||||
VIR_FREE(def->name);
|
||||
VIR_FREE(def->virtPortProfile);
|
||||
}
|
||||
|
||||
static void
|
||||
virNetworkForwardIfDefClear(virNetworkForwardIfDefPtr def)
|
||||
{
|
||||
VIR_FREE(def->dev);
|
||||
}
|
||||
|
||||
static void virNetworkIpDefClear(virNetworkIpDefPtr def)
|
||||
{
|
||||
int ii;
|
||||
@ -135,14 +148,23 @@ void virNetworkDefFree(virNetworkDefPtr def)
|
||||
|
||||
VIR_FREE(def->name);
|
||||
VIR_FREE(def->bridge);
|
||||
VIR_FREE(def->forwardDev);
|
||||
VIR_FREE(def->domain);
|
||||
|
||||
for (ii = 0 ; ii < def->nForwardIfs && def->forwardIfs ; ii++) {
|
||||
virNetworkForwardIfDefClear(&def->forwardIfs[ii]);
|
||||
}
|
||||
VIR_FREE(def->forwardIfs);
|
||||
|
||||
for (ii = 0 ; ii < def->nips && def->ips ; ii++) {
|
||||
virNetworkIpDefClear(&def->ips[ii]);
|
||||
}
|
||||
VIR_FREE(def->ips);
|
||||
|
||||
for (ii = 0; ii < def->nPortGroups && def->portGroups; ii++) {
|
||||
virPortGroupDefClear(&def->portGroups[ii]);
|
||||
}
|
||||
VIR_FREE(def->portGroups);
|
||||
|
||||
virNetworkDNSDefFree(def->dns);
|
||||
|
||||
VIR_FREE(def);
|
||||
@ -735,14 +757,63 @@ error:
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
virNetworkPortGroupParseXML(virPortGroupDefPtr def,
|
||||
xmlNodePtr node,
|
||||
xmlXPathContextPtr ctxt)
|
||||
{
|
||||
/*
|
||||
* virPortGroupDef object is already allocated as part of an array.
|
||||
* On failure clear it out, but don't free it.
|
||||
*/
|
||||
|
||||
xmlNodePtr save;
|
||||
xmlNodePtr virtPortNode;
|
||||
char *isDefault = NULL;
|
||||
|
||||
int result = -1;
|
||||
|
||||
save = ctxt->node;
|
||||
ctxt->node = node;
|
||||
|
||||
/* grab raw data from XML */
|
||||
def->name = virXPathString("string(./@name)", ctxt);
|
||||
isDefault = virXPathString("string(./@default)", ctxt);
|
||||
def->isDefault = isDefault && STRCASEEQ(isDefault, "yes");
|
||||
|
||||
virtPortNode = virXPathNode("./virtualport", ctxt);
|
||||
if (virtPortNode &&
|
||||
(virVirtualPortProfileParseXML(virtPortNode,
|
||||
&def->virtPortProfile) < 0)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
result = 0;
|
||||
error:
|
||||
if (result < 0) {
|
||||
virPortGroupDefClear(def);
|
||||
}
|
||||
VIR_FREE(isDefault);
|
||||
|
||||
ctxt->node = save;
|
||||
return result;
|
||||
}
|
||||
|
||||
static virNetworkDefPtr
|
||||
virNetworkDefParseXML(xmlXPathContextPtr ctxt)
|
||||
{
|
||||
virNetworkDefPtr def;
|
||||
char *tmp;
|
||||
char *stp = NULL;
|
||||
xmlNodePtr *ipNodes = NULL;
|
||||
xmlNodePtr *portGroupNodes = NULL;
|
||||
xmlNodePtr *forwardIfNodes = NULL;
|
||||
xmlNodePtr dnsNode = NULL;
|
||||
int nIps;
|
||||
xmlNodePtr virtPortNode = NULL;
|
||||
xmlNodePtr forwardNode = NULL;
|
||||
int nIps, nPortGroups, nForwardIfs;
|
||||
char *forwardDev = NULL;
|
||||
xmlNodePtr save = ctxt->node;
|
||||
|
||||
if (VIR_ALLOC(def) < 0) {
|
||||
virReportOOMError();
|
||||
@ -779,9 +850,7 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt)
|
||||
|
||||
/* Parse bridge information */
|
||||
def->bridge = virXPathString("string(./bridge[1]/@name)", ctxt);
|
||||
tmp = virXPathString("string(./bridge[1]/@stp)", ctxt);
|
||||
def->stp = (tmp && STREQ(tmp, "off")) ? 0 : 1;
|
||||
VIR_FREE(tmp);
|
||||
stp = virXPathString("string(./bridge[1]/@stp)", ctxt);
|
||||
|
||||
if (virXPathULong("string(./bridge[1]/@delay)", ctxt, &def->delay) < 0)
|
||||
def->delay = 0;
|
||||
@ -805,6 +874,36 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt)
|
||||
goto error;
|
||||
}
|
||||
|
||||
virtPortNode = virXPathNode("./virtualport", ctxt);
|
||||
if (virtPortNode &&
|
||||
(virVirtualPortProfileParseXML(virtPortNode,
|
||||
&def->virtPortProfile) < 0)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
nPortGroups = virXPathNodeSet("./portgroup", ctxt, &portGroupNodes);
|
||||
if (nPortGroups < 0)
|
||||
goto error;
|
||||
|
||||
if (nPortGroups > 0) {
|
||||
int ii;
|
||||
|
||||
/* allocate array to hold all the portgroups */
|
||||
if (VIR_ALLOC_N(def->portGroups, nPortGroups) < 0) {
|
||||
virReportOOMError();
|
||||
goto error;
|
||||
}
|
||||
/* parse each portgroup */
|
||||
for (ii = 0; ii < nPortGroups; ii++) {
|
||||
int ret = virNetworkPortGroupParseXML(&def->portGroups[ii],
|
||||
portGroupNodes[ii], ctxt);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
def->nPortGroups++;
|
||||
}
|
||||
}
|
||||
VIR_FREE(portGroupNodes);
|
||||
|
||||
nIps = virXPathNodeSet("./ip", ctxt, &ipNodes);
|
||||
if (nIps < 0)
|
||||
goto error;
|
||||
@ -828,17 +927,16 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt)
|
||||
}
|
||||
VIR_FREE(ipNodes);
|
||||
|
||||
/* IPv4 forwarding setup */
|
||||
if (virXPathBoolean("count(./forward) > 0", ctxt)) {
|
||||
if (def->nips == 0) {
|
||||
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
|
||||
"%s", _("Forwarding requested, but no IP address provided"));
|
||||
goto error;
|
||||
}
|
||||
tmp = virXPathString("string(./forward[1]/@mode)", ctxt);
|
||||
forwardNode = virXPathNode("./forward", ctxt);
|
||||
if (!forwardNode) {
|
||||
def->forwardType = VIR_NETWORK_FORWARD_NONE;
|
||||
def->stp = (stp && STREQ(stp, "off")) ? 0 : 1;
|
||||
} else {
|
||||
ctxt->node = forwardNode;
|
||||
tmp = virXPathString("string(./@mode)", ctxt);
|
||||
if (tmp) {
|
||||
if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) {
|
||||
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("unknown forwarding type '%s'"), tmp);
|
||||
VIR_FREE(tmp);
|
||||
goto error;
|
||||
@ -848,17 +946,116 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt)
|
||||
def->forwardType = VIR_NETWORK_FORWARD_NAT;
|
||||
}
|
||||
|
||||
forwardDev = virXPathString("string(./@dev)", ctxt);
|
||||
|
||||
def->forwardDev = virXPathString("string(./forward[1]/@dev)", ctxt);
|
||||
} else {
|
||||
def->forwardType = VIR_NETWORK_FORWARD_NONE;
|
||||
/* all of these modes can use a pool of physical interfaces */
|
||||
nForwardIfs = virXPathNodeSet("./interface", ctxt, &forwardIfNodes);
|
||||
if (nForwardIfs < 0)
|
||||
goto error;
|
||||
|
||||
if ((nForwardIfs > 0) || forwardDev) {
|
||||
int ii;
|
||||
|
||||
/* allocate array to hold all the portgroups */
|
||||
if (VIR_ALLOC_N(def->forwardIfs, MAX(nForwardIfs, 1)) < 0) {
|
||||
virReportOOMError();
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (forwardDev) {
|
||||
def->forwardIfs[0].usageCount = 0;
|
||||
def->forwardIfs[0].dev = forwardDev;
|
||||
forwardDev = NULL;
|
||||
def->nForwardIfs++;
|
||||
}
|
||||
|
||||
/* parse each forwardIf */
|
||||
for (ii = 0; ii < nForwardIfs; ii++) {
|
||||
forwardDev = virXMLPropString(forwardIfNodes[ii], "dev");
|
||||
if (!forwardDev) {
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("Missing required dev attribute in network '%s' forward interface element"),
|
||||
def->name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((ii == 0) && (def->nForwardIfs == 1)) {
|
||||
/* both forwardDev and an interface element are present.
|
||||
* If they don't match, it's an error. */
|
||||
if (STRNEQ(forwardDev, def->forwardIfs[0].dev)) {
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("forward dev '%s' must match first interface element dev '%s' in network '%s'"),
|
||||
def->forwardIfs[0].dev,
|
||||
forwardDev, def->name);
|
||||
goto error;
|
||||
}
|
||||
VIR_FREE(forwardDev);
|
||||
continue;
|
||||
}
|
||||
|
||||
def->forwardIfs[ii].dev = forwardDev;
|
||||
forwardDev = NULL;
|
||||
def->forwardIfs[ii].usageCount = 0;
|
||||
def->nForwardIfs++;
|
||||
}
|
||||
}
|
||||
VIR_FREE(forwardIfNodes);
|
||||
|
||||
switch (def->forwardType) {
|
||||
case VIR_NETWORK_FORWARD_ROUTE:
|
||||
case VIR_NETWORK_FORWARD_NAT:
|
||||
/* It's pointless to specify L3 forwarding without specifying
|
||||
* the network we're on.
|
||||
*/
|
||||
if (def->nips == 0) {
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("%s forwarding requested, but no IP address provided for network '%s'"),
|
||||
virNetworkForwardTypeToString(def->forwardType),
|
||||
def->name);
|
||||
goto error;
|
||||
}
|
||||
if (def->nForwardIfs > 1) {
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("multiple forwarding interfaces specified for network '%s', only one is supported"),
|
||||
def->name);
|
||||
goto error;
|
||||
}
|
||||
def->stp = (stp && STREQ(stp, "off")) ? 0 : 1;
|
||||
break;
|
||||
case VIR_NETWORK_FORWARD_PRIVATE:
|
||||
case VIR_NETWORK_FORWARD_VEPA:
|
||||
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
||||
if (def->bridge) {
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("bridge name not allowed in %s mode (network '%s'"),
|
||||
virNetworkForwardTypeToString(def->forwardType),
|
||||
def->name);
|
||||
goto error;
|
||||
}
|
||||
/* fall through to next case */
|
||||
case VIR_NETWORK_FORWARD_BRIDGE:
|
||||
if (def->delay || stp) {
|
||||
virNetworkReportError(VIR_ERR_XML_ERROR,
|
||||
_("bridge delay/stp options only allowed in route, nat, and isolated mode, not in %s (network '%s')"),
|
||||
virNetworkForwardTypeToString(def->forwardType),
|
||||
def->name);
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VIR_FREE(stp);
|
||||
ctxt->node = save;
|
||||
return def;
|
||||
|
||||
error:
|
||||
VIR_FREE(stp);
|
||||
virNetworkDefFree(def);
|
||||
VIR_FREE(ipNodes);
|
||||
VIR_FREE(portGroupNodes);
|
||||
VIR_FREE(forwardIfNodes);
|
||||
VIR_FREE(forwardDev);
|
||||
ctxt->node = save;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1043,6 +1240,19 @@ error:
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
virPortGroupDefFormat(virBufferPtr buf,
|
||||
const virPortGroupDefPtr def)
|
||||
{
|
||||
virBufferAsprintf(buf, " <portgroup name='%s'", def->name);
|
||||
if (def->isDefault) {
|
||||
virBufferAddLit(buf, " default='yes'");
|
||||
}
|
||||
virBufferAddLit(buf, ">\n");
|
||||
virVirtualPortProfileFormat(buf, def->virtPortProfile, " ");
|
||||
virBufferAddLit(buf, " </portgroup>\n");
|
||||
}
|
||||
|
||||
char *virNetworkDefFormat(const virNetworkDefPtr def)
|
||||
{
|
||||
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
||||
@ -1058,24 +1268,48 @@ char *virNetworkDefFormat(const virNetworkDefPtr def)
|
||||
virBufferAsprintf(&buf, " <uuid>%s</uuid>\n", uuidstr);
|
||||
|
||||
if (def->forwardType != VIR_NETWORK_FORWARD_NONE) {
|
||||
const char *dev = virNetworkDefForwardIf(def, 0);
|
||||
const char *mode = virNetworkForwardTypeToString(def->forwardType);
|
||||
if (mode) {
|
||||
if (def->forwardDev) {
|
||||
virBufferEscapeString(&buf, " <forward dev='%s'",
|
||||
def->forwardDev);
|
||||
} else {
|
||||
virBufferAddLit(&buf, " <forward");
|
||||
|
||||
if (!mode) {
|
||||
virNetworkReportError(VIR_ERR_INTERNAL_ERROR,
|
||||
_("Unknown forward type %d in network '%s'"),
|
||||
def->forwardType, def->name);
|
||||
goto error;
|
||||
}
|
||||
virBufferAddLit(&buf, " <forward");
|
||||
if (dev)
|
||||
virBufferEscapeString(&buf, " dev='%s'", dev);
|
||||
virBufferAsprintf(&buf, " mode='%s'%s>\n", mode,
|
||||
def->nForwardIfs ? "" : "/");
|
||||
|
||||
if (def->nForwardIfs) {
|
||||
for (ii = 0; ii < def->nForwardIfs; ii++) {
|
||||
if (def->forwardIfs[ii].dev) {
|
||||
virBufferEscapeString(&buf, " <interface dev='%s'/>\n",
|
||||
def->forwardIfs[ii].dev);
|
||||
}
|
||||
}
|
||||
virBufferAsprintf(&buf, " mode='%s'/>\n", mode);
|
||||
virBufferAddLit(&buf, " </forward>\n");
|
||||
}
|
||||
}
|
||||
|
||||
virBufferAddLit(&buf, " <bridge");
|
||||
if (def->bridge)
|
||||
virBufferEscapeString(&buf, " name='%s'", def->bridge);
|
||||
virBufferAsprintf(&buf, " stp='%s' delay='%ld' />\n",
|
||||
def->stp ? "on" : "off",
|
||||
def->delay);
|
||||
if (def->forwardType == VIR_NETWORK_FORWARD_NONE ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_NAT ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_ROUTE) {
|
||||
|
||||
virBufferAddLit(&buf, " <bridge");
|
||||
if (def->bridge)
|
||||
virBufferEscapeString(&buf, " name='%s'", def->bridge);
|
||||
virBufferAsprintf(&buf, " stp='%s' delay='%ld' />\n",
|
||||
def->stp ? "on" : "off",
|
||||
def->delay);
|
||||
} else if (def->forwardType == VIR_NETWORK_FORWARD_BRIDGE &&
|
||||
def->bridge) {
|
||||
virBufferEscapeString(&buf, " <bridge name='%s' />\n", def->bridge);
|
||||
}
|
||||
|
||||
|
||||
if (def->mac_specified) {
|
||||
char macaddr[VIR_MAC_STRING_BUFLEN];
|
||||
virFormatMacAddr(def->mac, macaddr);
|
||||
@ -1093,6 +1327,11 @@ char *virNetworkDefFormat(const virNetworkDefPtr def)
|
||||
goto error;
|
||||
}
|
||||
|
||||
virVirtualPortProfileFormat(&buf, def->virtPortProfile, " ");
|
||||
|
||||
for (ii = 0; ii < def->nPortGroups; ii++)
|
||||
virPortGroupDefFormat(&buf, &def->portGroups[ii]);
|
||||
|
||||
virBufferAddLit(&buf, "</network>\n");
|
||||
|
||||
if (virBufferError(&buf))
|
||||
@ -1107,6 +1346,22 @@ char *virNetworkDefFormat(const virNetworkDefPtr def)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virPortGroupDefPtr virPortGroupFindByName(virNetworkDefPtr net,
|
||||
const char *portgroup)
|
||||
{
|
||||
int ii;
|
||||
for (ii = 0; ii < net->nPortGroups; ii++) {
|
||||
if (portgroup) {
|
||||
if (STREQ(portgroup, net->portGroups[ii].name))
|
||||
return &net->portGroups[ii];
|
||||
} else {
|
||||
if (net->portGroups[ii].isDefault)
|
||||
return &net->portGroups[ii];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int virNetworkSaveXML(const char *configDir,
|
||||
virNetworkDefPtr def,
|
||||
const char *xml)
|
||||
@ -1209,11 +1464,16 @@ virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Generate a bridge if none is specified, but don't check for collisions
|
||||
* if a bridge is hardcoded, so the network is at least defined
|
||||
*/
|
||||
if (virNetworkSetBridgeName(nets, def, 0))
|
||||
goto error;
|
||||
if (def->forwardType == VIR_NETWORK_FORWARD_NONE ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_NAT ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_ROUTE) {
|
||||
|
||||
/* Generate a bridge if none is specified, but don't check for collisions
|
||||
* if a bridge is hardcoded, so the network is at least defined.
|
||||
*/
|
||||
if (virNetworkSetBridgeName(nets, def, 0))
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!(net = virNetworkAssignDef(nets, def)))
|
||||
goto error;
|
||||
|
@ -33,11 +33,14 @@
|
||||
# include "network.h"
|
||||
# include "util.h"
|
||||
|
||||
/* 2 possible types of forwarding */
|
||||
enum virNetworkForwardType {
|
||||
VIR_NETWORK_FORWARD_NONE = 0,
|
||||
VIR_NETWORK_FORWARD_NAT,
|
||||
VIR_NETWORK_FORWARD_ROUTE,
|
||||
VIR_NETWORK_FORWARD_BRIDGE,
|
||||
VIR_NETWORK_FORWARD_PRIVATE,
|
||||
VIR_NETWORK_FORWARD_VEPA,
|
||||
VIR_NETWORK_FORWARD_PASSTHROUGH,
|
||||
|
||||
VIR_NETWORK_FORWARD_LAST,
|
||||
};
|
||||
@ -107,6 +110,21 @@ struct _virNetworkIpDef {
|
||||
virSocketAddr bootserver;
|
||||
};
|
||||
|
||||
typedef struct _virNetworkForwardIfDef virNetworkForwardIfDef;
|
||||
typedef virNetworkForwardIfDef *virNetworkForwardIfDefPtr;
|
||||
struct _virNetworkForwardIfDef {
|
||||
char *dev; /* name of device */
|
||||
int usageCount; /* how many guest interfaces are bound to this device? */
|
||||
};
|
||||
|
||||
typedef struct _virPortGroupDef virPortGroupDef;
|
||||
typedef virPortGroupDef *virPortGroupDefPtr;
|
||||
struct _virPortGroupDef {
|
||||
char *name;
|
||||
bool isDefault;
|
||||
virVirtualPortProfileParamsPtr virtPortProfile;
|
||||
};
|
||||
|
||||
typedef struct _virNetworkDef virNetworkDef;
|
||||
typedef virNetworkDef *virNetworkDefPtr;
|
||||
struct _virNetworkDef {
|
||||
@ -121,12 +139,21 @@ struct _virNetworkDef {
|
||||
bool mac_specified;
|
||||
|
||||
int forwardType; /* One of virNetworkForwardType constants */
|
||||
char *forwardDev; /* Destination device for forwarding */
|
||||
|
||||
/* If there are multiple forward devices (i.e. a pool of
|
||||
* interfaces), they will be listed here.
|
||||
*/
|
||||
size_t nForwardIfs;
|
||||
virNetworkForwardIfDefPtr forwardIfs;
|
||||
|
||||
size_t nips;
|
||||
virNetworkIpDefPtr ips; /* ptr to array of IP addresses on this network */
|
||||
|
||||
virNetworkDNSDefPtr dns; /* ptr to dns related configuration */
|
||||
virVirtualPortProfileParamsPtr virtPortProfile;
|
||||
|
||||
size_t nPortGroups;
|
||||
virPortGroupDefPtr portGroups;
|
||||
};
|
||||
|
||||
typedef struct _virNetworkObj virNetworkObj;
|
||||
@ -179,6 +206,16 @@ virNetworkDefPtr virNetworkDefParseNode(xmlDocPtr xml,
|
||||
|
||||
char *virNetworkDefFormat(const virNetworkDefPtr def);
|
||||
|
||||
static inline const char *
|
||||
virNetworkDefForwardIf(const virNetworkDefPtr def, size_t n)
|
||||
{
|
||||
return ((def->forwardIfs && (def->nForwardIfs > n))
|
||||
? def->forwardIfs[n].dev : NULL);
|
||||
}
|
||||
|
||||
virPortGroupDefPtr virPortGroupFindByName(virNetworkDefPtr net,
|
||||
const char *portgroup);
|
||||
|
||||
virNetworkIpDefPtr
|
||||
virNetworkDefGetIpByIndex(const virNetworkDefPtr def,
|
||||
int family, size_t n);
|
||||
|
@ -747,6 +747,7 @@ virNetworkRemoveInactive;
|
||||
virNetworkSaveConfig;
|
||||
virNetworkSetBridgeMacAddr;
|
||||
virNetworkSetBridgeName;
|
||||
virPortGroupFindByName;
|
||||
|
||||
|
||||
# node_device_conf.h
|
||||
|
@ -898,6 +898,7 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
virNetworkIpDefPtr ipdef)
|
||||
{
|
||||
int prefix = virNetworkIpDefPrefix(ipdef);
|
||||
const char *forwardIf = virNetworkDefForwardIf(network->def, 0);
|
||||
|
||||
if (prefix < 0) {
|
||||
networkReportError(VIR_ERR_INTERNAL_ERROR,
|
||||
@ -911,7 +912,7 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev) < 0) {
|
||||
forwardIf) < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to allow forwarding from '%s'"),
|
||||
network->def->bridge);
|
||||
@ -925,7 +926,7 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev) < 0) {
|
||||
forwardIf) < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to allow forwarding to '%s'"),
|
||||
network->def->bridge);
|
||||
@ -959,11 +960,13 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
if (iptablesAddForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
NULL) < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to enable masquerading to '%s'"),
|
||||
network->def->forwardDev ? network->def->forwardDev : NULL);
|
||||
forwardIf ?
|
||||
_("failed to add iptables rule to enable masquerading to %s") :
|
||||
_("failed to add iptables rule to enable masquerading"),
|
||||
forwardIf);
|
||||
goto masqerr3;
|
||||
}
|
||||
|
||||
@ -971,11 +974,13 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
if (iptablesAddForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
"udp") < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to enable UDP masquerading to '%s'"),
|
||||
network->def->forwardDev ? network->def->forwardDev : NULL);
|
||||
forwardIf ?
|
||||
_("failed to add iptables rule to enable UDP masquerading to %s") :
|
||||
_("failed to add iptables rule to enable UDP masquerading"),
|
||||
forwardIf);
|
||||
goto masqerr4;
|
||||
}
|
||||
|
||||
@ -983,11 +988,13 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
if (iptablesAddForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
"tcp") < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to enable TCP masquerading to '%s'"),
|
||||
network->def->forwardDev ? network->def->forwardDev : NULL);
|
||||
forwardIf ?
|
||||
_("failed to add iptables rule to enable TCP masquerading to %s") :
|
||||
_("failed to add iptables rule to enable TCP masquerading"),
|
||||
forwardIf);
|
||||
goto masqerr5;
|
||||
}
|
||||
|
||||
@ -997,26 +1004,26 @@ networkAddMasqueradingIptablesRules(struct network_driver *driver,
|
||||
iptablesRemoveForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
"udp");
|
||||
masqerr4:
|
||||
iptablesRemoveForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
NULL);
|
||||
masqerr3:
|
||||
iptablesRemoveForwardAllowRelatedIn(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
masqerr2:
|
||||
iptablesRemoveForwardAllowOut(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
masqerr1:
|
||||
return -1;
|
||||
}
|
||||
@ -1027,34 +1034,35 @@ networkRemoveMasqueradingIptablesRules(struct network_driver *driver,
|
||||
virNetworkIpDefPtr ipdef)
|
||||
{
|
||||
int prefix = virNetworkIpDefPrefix(ipdef);
|
||||
const char *forwardIf = virNetworkDefForwardIf(network->def, 0);
|
||||
|
||||
if (prefix >= 0) {
|
||||
iptablesRemoveForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
"tcp");
|
||||
iptablesRemoveForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
"udp");
|
||||
iptablesRemoveForwardMasquerade(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->forwardDev,
|
||||
forwardIf,
|
||||
NULL);
|
||||
|
||||
iptablesRemoveForwardAllowRelatedIn(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
iptablesRemoveForwardAllowOut(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1064,6 +1072,7 @@ networkAddRoutingIptablesRules(struct network_driver *driver,
|
||||
virNetworkIpDefPtr ipdef)
|
||||
{
|
||||
int prefix = virNetworkIpDefPrefix(ipdef);
|
||||
const char *forwardIf = virNetworkDefForwardIf(network->def, 0);
|
||||
|
||||
if (prefix < 0) {
|
||||
networkReportError(VIR_ERR_INTERNAL_ERROR,
|
||||
@ -1077,7 +1086,7 @@ networkAddRoutingIptablesRules(struct network_driver *driver,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev) < 0) {
|
||||
forwardIf) < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to allow routing from '%s'"),
|
||||
network->def->bridge);
|
||||
@ -1089,7 +1098,7 @@ networkAddRoutingIptablesRules(struct network_driver *driver,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev) < 0) {
|
||||
forwardIf) < 0) {
|
||||
networkReportError(VIR_ERR_SYSTEM_ERROR,
|
||||
_("failed to add iptables rule to allow routing to '%s'"),
|
||||
network->def->bridge);
|
||||
@ -1103,7 +1112,7 @@ routeerr2:
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
routeerr1:
|
||||
return -1;
|
||||
}
|
||||
@ -1114,19 +1123,20 @@ networkRemoveRoutingIptablesRules(struct network_driver *driver,
|
||||
virNetworkIpDefPtr ipdef)
|
||||
{
|
||||
int prefix = virNetworkIpDefPrefix(ipdef);
|
||||
const char *forwardIf = virNetworkDefForwardIf(network->def, 0);
|
||||
|
||||
if (prefix >= 0) {
|
||||
iptablesRemoveForwardAllowIn(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
|
||||
iptablesRemoveForwardAllowOut(driver->iptables,
|
||||
&ipdef->address,
|
||||
prefix,
|
||||
network->def->bridge,
|
||||
network->def->forwardDev);
|
||||
forwardIf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2171,10 +2181,18 @@ static virNetworkPtr networkCreate(virConnectPtr conn, const char *xml) {
|
||||
if (virNetworkObjIsDuplicate(&driver->networks, def, 1) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (virNetworkSetBridgeName(&driver->networks, def, 1))
|
||||
goto cleanup;
|
||||
/* Only the three L3 network types that are configured by libvirt
|
||||
* need to have a bridge device name / mac address provided
|
||||
*/
|
||||
if (def->forwardType == VIR_NETWORK_FORWARD_NONE ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_NAT ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_ROUTE) {
|
||||
|
||||
virNetworkSetBridgeMacAddr(def);
|
||||
if (virNetworkSetBridgeName(&driver->networks, def, 1))
|
||||
goto cleanup;
|
||||
|
||||
virNetworkSetBridgeMacAddr(def);
|
||||
}
|
||||
|
||||
if (!(network = virNetworkAssignDef(&driver->networks,
|
||||
def)))
|
||||
@ -2216,10 +2234,18 @@ static virNetworkPtr networkDefine(virConnectPtr conn, const char *xml) {
|
||||
if (virNetworkObjIsDuplicate(&driver->networks, def, 0) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (virNetworkSetBridgeName(&driver->networks, def, 1))
|
||||
goto cleanup;
|
||||
/* Only the three L3 network types that are configured by libvirt
|
||||
* need to have a bridge device name / mac address provided
|
||||
*/
|
||||
if (def->forwardType == VIR_NETWORK_FORWARD_NONE ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_NAT ||
|
||||
def->forwardType == VIR_NETWORK_FORWARD_ROUTE) {
|
||||
|
||||
virNetworkSetBridgeMacAddr(def);
|
||||
if (virNetworkSetBridgeName(&driver->networks, def, 1))
|
||||
goto cleanup;
|
||||
|
||||
virNetworkSetBridgeMacAddr(def);
|
||||
}
|
||||
|
||||
if (!(network = virNetworkAssignDef(&driver->networks,
|
||||
def)))
|
||||
|
14
tests/networkxml2xmlin/8021Qbh-net.xml
Normal file
14
tests/networkxml2xmlin/8021Qbh-net.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<network>
|
||||
<name>8021Qbh-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8c</uuid>
|
||||
<forward mode="private" dev="eth1">
|
||||
<interface dev="eth1"/>
|
||||
<interface dev="eth2"/>
|
||||
<interface dev="eth3"/>
|
||||
<interface dev="eth4"/>
|
||||
<interface dev="eth5"/>
|
||||
</forward>
|
||||
<virtualport type="802.1Qbh">
|
||||
<parameters profileid="spongebob24"/>
|
||||
</virtualport>
|
||||
</network>
|
7
tests/networkxml2xmlin/direct-net.xml
Normal file
7
tests/networkxml2xmlin/direct-net.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<network>
|
||||
<name>direct-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8f</uuid>
|
||||
<forward mode="bridge">
|
||||
<interface dev="eth10"/>
|
||||
</forward>
|
||||
</network>
|
6
tests/networkxml2xmlin/host-bridge-net.xml
Normal file
6
tests/networkxml2xmlin/host-bridge-net.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<network>
|
||||
<name>host-bridge-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8e</uuid>
|
||||
<forward mode="bridge"/>
|
||||
<bridge name="br0"/>
|
||||
</network>
|
22
tests/networkxml2xmlin/vepa-net.xml
Normal file
22
tests/networkxml2xmlin/vepa-net.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<network>
|
||||
<name>vepa-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8b</uuid>
|
||||
<forward mode="vepa">
|
||||
<interface dev="eth1"/>
|
||||
<interface dev="eth2"/>
|
||||
<interface dev="eth3"/>
|
||||
</forward>
|
||||
<virtualport type="802.1Qbg">
|
||||
<parameters managerid="11" typeid="1193047" typeidversion="2" instanceid="b153fa89-1b87-9719-ec12-99e0054fb844"/>
|
||||
</virtualport>
|
||||
<portgroup name="bob" default="yes">
|
||||
<virtualport type="802.1Qbg">
|
||||
<parameters managerid="12" typeid="2193047" typeidversion="3" instanceid="5d00e0ba-e15c-959c-fbb6-b595b0655735"/>
|
||||
</virtualport>
|
||||
</portgroup>
|
||||
<portgroup name="alice">
|
||||
<virtualport type="802.1Qbg">
|
||||
<parameters managerid="13" typeid="3193047" typeidversion="4" instanceid="70bf45f9-01a8-f5ee-3c0f-e25a0a2e44a6"/>
|
||||
</virtualport>
|
||||
</portgroup>
|
||||
</network>
|
14
tests/networkxml2xmlout/8021Qbh-net.xml
Normal file
14
tests/networkxml2xmlout/8021Qbh-net.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<network>
|
||||
<name>8021Qbh-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8c</uuid>
|
||||
<forward dev='eth1' mode='private'>
|
||||
<interface dev='eth1'/>
|
||||
<interface dev='eth2'/>
|
||||
<interface dev='eth3'/>
|
||||
<interface dev='eth4'/>
|
||||
<interface dev='eth5'/>
|
||||
</forward>
|
||||
<virtualport type='802.1Qbh'>
|
||||
<parameters profileid='spongebob24'/>
|
||||
</virtualport>
|
||||
</network>
|
7
tests/networkxml2xmlout/direct-net.xml
Normal file
7
tests/networkxml2xmlout/direct-net.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<network>
|
||||
<name>direct-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8f</uuid>
|
||||
<forward dev='eth10' mode='bridge'>
|
||||
<interface dev='eth10'/>
|
||||
</forward>
|
||||
</network>
|
6
tests/networkxml2xmlout/host-bridge-net.xml
Normal file
6
tests/networkxml2xmlout/host-bridge-net.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<network>
|
||||
<name>host-bridge-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8e</uuid>
|
||||
<forward mode='bridge'/>
|
||||
<bridge name='br0' />
|
||||
</network>
|
@ -1,7 +1,9 @@
|
||||
<network>
|
||||
<name>default</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a9c</uuid>
|
||||
<forward dev='eth0' mode='nat'/>
|
||||
<forward dev='eth0' mode='nat'>
|
||||
<interface dev='eth0'/>
|
||||
</forward>
|
||||
<bridge name='virbr0' stp='on' delay='0' />
|
||||
<dns>
|
||||
<host ip='192.168.122.1'>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<network>
|
||||
<name>default</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
|
||||
<forward dev='eth1' mode='nat'/>
|
||||
<forward dev='eth1' mode='nat'>
|
||||
<interface dev='eth1'/>
|
||||
</forward>
|
||||
<bridge name='virbr0' stp='on' delay='0' />
|
||||
<dns>
|
||||
<txt name='example' value='example value' />
|
||||
|
@ -1,7 +1,9 @@
|
||||
<network>
|
||||
<name>default</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
|
||||
<forward dev='eth1' mode='nat'/>
|
||||
<forward dev='eth1' mode='nat'>
|
||||
<interface dev='eth1'/>
|
||||
</forward>
|
||||
<bridge name='virbr0' stp='on' delay='0' />
|
||||
<ip address='192.168.122.1' netmask='255.255.255.0'>
|
||||
<dhcp>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<network>
|
||||
<name>local</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid>
|
||||
<forward dev='eth1' mode='route'/>
|
||||
<forward dev='eth1' mode='route'>
|
||||
<interface dev='eth1'/>
|
||||
</forward>
|
||||
<bridge name='virbr1' stp='on' delay='0' />
|
||||
<mac address='12:34:56:78:9A:BC'/>
|
||||
<ip address='192.168.122.1' netmask='255.255.255.0'>
|
||||
|
22
tests/networkxml2xmlout/vepa-net.xml
Normal file
22
tests/networkxml2xmlout/vepa-net.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<network>
|
||||
<name>vepa-net</name>
|
||||
<uuid>81ff0d90-c91e-6742-64da-4a736edb9a8b</uuid>
|
||||
<forward dev='eth1' mode='vepa'>
|
||||
<interface dev='eth1'/>
|
||||
<interface dev='eth2'/>
|
||||
<interface dev='eth3'/>
|
||||
</forward>
|
||||
<virtualport type='802.1Qbg'>
|
||||
<parameters managerid='11' typeid='1193047' typeidversion='2' instanceid='b153fa89-1b87-9719-ec12-99e0054fb844'/>
|
||||
</virtualport>
|
||||
<portgroup name='bob' default='yes'>
|
||||
<virtualport type='802.1Qbg'>
|
||||
<parameters managerid='12' typeid='2193047' typeidversion='3' instanceid='5d00e0ba-e15c-959c-fbb6-b595b0655735'/>
|
||||
</virtualport>
|
||||
</portgroup>
|
||||
<portgroup name='alice'>
|
||||
<virtualport type='802.1Qbg'>
|
||||
<parameters managerid='13' typeid='3193047' typeidversion='4' instanceid='70bf45f9-01a8-f5ee-3c0f-e25a0a2e44a6'/>
|
||||
</virtualport>
|
||||
</portgroup>
|
||||
</network>
|
@ -88,6 +88,10 @@ mymain(void)
|
||||
DO_TEST("netboot-proxy-network");
|
||||
DO_TEST("nat-network-dns-txt-record");
|
||||
DO_TEST("nat-network-dns-hosts");
|
||||
DO_TEST("8021Qbh-net");
|
||||
DO_TEST("direct-net");
|
||||
DO_TEST("host-bridge-net");
|
||||
DO_TEST("vepa-net");
|
||||
|
||||
return (ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user