net/portmapper: support legacy "urn:dslforum-org" portmapping services
These are functionally the same as the "urn:schemas-upnp-org" services with a few minor changes, and are still used by older devices. Support them to improve our ability to obtain an external IP on such networks. Updates #10911 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I05501fad9d6f0a3b8cf19fc95eee80e7d16cc2cf
This commit is contained in:
parent
75f1d3e7d7
commit
fd94d96e2b
|
@ -0,0 +1,303 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !js
|
||||
|
||||
// (no raw sockets in JS/WASM)
|
||||
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
"github.com/tailscale/goupnp/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
urn_LegacyWANPPPConnection_1 = "urn:dslforum-org:service:WANPPPConnection:1"
|
||||
urn_LegacyWANIPConnection_1 = "urn:dslforum-org:service:WANIPConnection:1"
|
||||
)
|
||||
|
||||
// legacyWANPPPConnection1 is the same as internetgateway2.WANPPPConnection1,
|
||||
// except using the old URN that starts with "urn:dslforum-org".
|
||||
//
|
||||
// The definition for this can be found in older documentation about UPnP; for
|
||||
// the purposes of this implementation, we're referring to "DSL Forum TR-064:
|
||||
// LAN-Side DSL CPE Configuration", which, while deprecated, can be found at:
|
||||
//
|
||||
// https://www.broadband-forum.org/wp-content/uploads/2018/11/TR-064_Corrigendum-1.pdf
|
||||
// https://www.broadband-forum.org/pdfs/tr-064-1-0-1.pdf
|
||||
type legacyWANPPPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) AddPortMapping(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
NewInternalPort string
|
||||
NewInternalClient string
|
||||
NewEnabled string
|
||||
NewPortMappingDescription string
|
||||
NewLeaseDuration string
|
||||
}{}
|
||||
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalPort, err = soap.MarshalUi2(NewInternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalClient, err = soap.MarshalString(NewInternalClient); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewEnabled, err = soap.MarshalBoolean(NewEnabled); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewPortMappingDescription, err = soap.MarshalString(NewPortMappingDescription); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewLeaseDuration, err = soap.MarshalUi4(NewLeaseDuration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
}{}
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewExternalIPAddress string
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewExternalIPAddress, err = soap.UnmarshalString(response.NewExternalIPAddress); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewConnectionStatus string
|
||||
NewLastConnectionError string
|
||||
NewUpTime string // NOTE: the "T" is capitalized here, per the spec, though it's lowercase in the newer UPnP spec
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewConnectionStatus, err = soap.UnmarshalString(response.NewConnectionStatus); err != nil {
|
||||
return
|
||||
}
|
||||
if NewLastConnectionError, err = soap.UnmarshalString(response.NewLastConnectionError); err != nil {
|
||||
return
|
||||
}
|
||||
if NewUptime, err = soap.UnmarshalUi4(response.NewUpTime); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// legacyWANIPConnection1 is the same as internetgateway2.WANIPConnection1,
|
||||
// except using the old URN that starts with "urn:dslforum-org".
|
||||
//
|
||||
// See legacyWANPPPConnection1 for details on where this is defined.
|
||||
type legacyWANIPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) AddPortMapping(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
NewInternalPort string
|
||||
NewInternalClient string
|
||||
NewEnabled string
|
||||
NewPortMappingDescription string
|
||||
NewLeaseDuration string
|
||||
}{}
|
||||
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalPort, err = soap.MarshalUi2(NewInternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalClient, err = soap.MarshalString(NewInternalClient); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewEnabled, err = soap.MarshalBoolean(NewEnabled); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewPortMappingDescription, err = soap.MarshalString(NewPortMappingDescription); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewLeaseDuration, err = soap.MarshalUi4(NewLeaseDuration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
}{}
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewExternalIPAddress string
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewExternalIPAddress, err = soap.UnmarshalString(response.NewExternalIPAddress); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewConnectionStatus string
|
||||
NewLastConnectionError string
|
||||
NewUpTime string // NOTE: the "T" is capitalized here, per the spec, though it's lowercase in the newer UPnP spec
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewConnectionStatus, err = soap.UnmarshalString(response.NewConnectionStatus); err != nil {
|
||||
return
|
||||
}
|
||||
if NewLastConnectionError, err = soap.UnmarshalString(response.NewLastConnectionError); err != nil {
|
||||
return
|
||||
}
|
||||
if NewUptime, err = soap.UnmarshalUi4(response.NewUpTime); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1199,6 +1199,10 @@ var (
|
|||
// received a UPnP response from a port other than the UPnP port.
|
||||
metricUPnPResponseAlternatePort = clientmetric.NewCounter("portmap_upnp_response_alternate_port")
|
||||
|
||||
// metricUPnPSelectLegacy counts the number of times that a legacy
|
||||
// service was found in a UPnP response.
|
||||
metricUPnPSelectLegacy = clientmetric.NewCounter("portmap_upnp_select_legacy")
|
||||
|
||||
// metricUPnPSelectSingle counts the number of times that only a single
|
||||
// UPnP device was available in selectBestService.
|
||||
metricUPnPSelectSingle = clientmetric.NewCounter("portmap_upnp_select_single")
|
||||
|
|
|
@ -289,6 +289,19 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
|||
clients = append(clients, v)
|
||||
}
|
||||
|
||||
// These are legacy services that were deprecated in 2015, but are
|
||||
// still in use by older devices; try them just in case.
|
||||
legacyClients, _ := goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANPPPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANPPPConnection1{client})
|
||||
}
|
||||
legacyClients, _ = goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANIPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANIPConnection1{client})
|
||||
}
|
||||
|
||||
// If we have no clients, then return right now; if we only have one,
|
||||
// just select and return it.
|
||||
if len(clients) == 0 {
|
||||
|
|
|
@ -331,6 +331,86 @@ const (
|
|||
<presentationURL>http://127.0.0.1</presentationURL>
|
||||
</device>
|
||||
</root>
|
||||
`
|
||||
|
||||
noSupportedServicesRootDesc = `<?xml version="1.0"?>
|
||||
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:InternetGatewayDevice:1</deviceType>
|
||||
<friendlyName>Fake Router</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Fake Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-microsoft-com:service:OSInfo:1</serviceType>
|
||||
<serviceId>urn:microsoft-com:serviceId:OSInfo1</serviceId>
|
||||
<SCPDURL>/osinfo.xml</SCPDURL>
|
||||
<controlURL>/upnp/control/aaaaaaaaaa/osinfo</controlURL>
|
||||
<eventSubURL>/upnp/event/aaaaaaaaaa/osinfo</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
|
||||
<friendlyName>WANDevice</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Tailscale Test Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
|
||||
<controlURL>/ctl/bbbbbbbb</controlURL>
|
||||
<eventSubURL>/evt/bbbbbbbb</eventSubURL>
|
||||
<SCPDURL>/WANCfg.xml</SCPDURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
|
||||
<friendlyName>WANConnectionDevice</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Tailscale Test Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:tailscale:service:SomethingElse:1</serviceType>
|
||||
<serviceId>urn:upnp-org:serviceId:TailscaleSomethingElse</serviceId>
|
||||
<SCPDURL>/desc/SomethingElse.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/SomethingElse_1</controlURL>
|
||||
<eventSubURL>/evt/SomethingElse_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
<presentationURL>http://127.0.0.1</presentationURL>
|
||||
</device>
|
||||
</root>
|
||||
`
|
||||
)
|
||||
|
||||
|
@ -402,7 +482,12 @@ func TestGetUPnPClient(t *testing.T) {
|
|||
{
|
||||
"huawei",
|
||||
huaweiRootDescXML,
|
||||
// services not supported and thus returns nil, but shouldn't crash
|
||||
"*portmapper.legacyWANPPPConnection1",
|
||||
"saw UPnP type *portmapper.legacyWANPPPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; HG531 V1 (Huawei Technologies Co., Ltd.), method=single\n",
|
||||
},
|
||||
{
|
||||
"not_supported",
|
||||
noSupportedServicesRootDesc,
|
||||
"<nil>",
|
||||
"",
|
||||
},
|
||||
|
@ -563,7 +648,7 @@ func TestGetUPnPPortMapping_NoValidServices(t *testing.T) {
|
|||
|
||||
igd.SetUPnPHandler(&upnpServer{
|
||||
t: t,
|
||||
Desc: huaweiRootDescXML,
|
||||
Desc: noSupportedServicesRootDesc,
|
||||
})
|
||||
|
||||
c := newTestClient(t, igd)
|
||||
|
@ -591,6 +676,57 @@ func TestGetUPnPPortMapping_NoValidServices(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Tests the legacy behaviour with the pre-UPnP standard portmapping service.
|
||||
func TestGetUPnPPortMapping_Legacy(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer igd.Close()
|
||||
|
||||
// This is a very basic fake UPnP server handler.
|
||||
handlers := map[string]any{
|
||||
"AddPortMapping": testLegacyAddPortMappingResponse,
|
||||
"GetExternalIPAddress": testLegacyGetExternalIPAddressResponse,
|
||||
"GetStatusInfo": testLegacyGetStatusInfoResponse,
|
||||
"DeletePortMapping": "", // Do nothing for test
|
||||
}
|
||||
|
||||
igd.SetUPnPHandler(&upnpServer{
|
||||
t: t,
|
||||
Desc: huaweiRootDescXML,
|
||||
Control: map[string]map[string]any{
|
||||
"/ctrlt/WANPPPConnection_1": handlers,
|
||||
},
|
||||
})
|
||||
|
||||
c := newTestClient(t, igd)
|
||||
defer c.Close()
|
||||
c.debug.VerboseLogs = true
|
||||
|
||||
ctx := context.Background()
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Probe: %v", err)
|
||||
}
|
||||
if !res.UPnP {
|
||||
t.Errorf("didn't detect UPnP")
|
||||
}
|
||||
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
t.Fatalf("could not get gateway and self IP")
|
||||
}
|
||||
|
||||
ext, ok := c.getUPnPPortMapping(ctx, gw, netip.AddrPortFrom(myIP, 12345), 0)
|
||||
if !ok {
|
||||
t.Fatal("could not get UPnP port mapping")
|
||||
}
|
||||
if got, want := ext.Addr(), netip.MustParseAddr("123.123.123.123"); got != want {
|
||||
t.Errorf("bad external address; got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUPnPPortMappingNoResponses(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
|
@ -892,3 +1028,33 @@ const testGetStatusInfoResponse = `<?xml version="1.0"?>
|
|||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyAddPortMappingResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:AddPortMappingResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1"/>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyGetExternalIPAddressResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetExternalIPAddressResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1">
|
||||
<NewExternalIPAddress>123.123.123.123</NewExternalIPAddress>
|
||||
</u:GetExternalIPAddressResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyGetStatusInfoResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetStatusInfoResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1">
|
||||
<NewConnectionStatus>Connected</NewConnectionStatus>
|
||||
<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>
|
||||
<NewUpTime>9999</NewUpTime>
|
||||
</u:GetStatusInfoResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
|
Loading…
Reference in New Issue