python-sagemcom-api icon indicating copy to clipboard operation
python-sagemcom-api copied to clipboard

Cannot connect to host - Sagemcom F@st 5359 (KPN Box 12)

Open frankhuurman opened this issue 9 months ago • 4 comments

Versions

Key Value
Package version 1.4.1
Python Version 3.13.2
F@st Router Model Sagemcom F@st 5359

Describe the bug

Using the quickstart script I can't seem to set up a connection to my KPN Box 12 from my laptop.

The output: Cannot connect to host http:80 ssl:default [Name or service not known]

I can ping the machine and get a reply back so it is reachable.

When I use only my IP address instead of http://192.168.2.254 I get an empty printed line(it still ends up in the client.login() Exception handler):

Image

To Reproduce

Steps to reproduce the behavior:

  1. pip install this package and copy the quickstart script
  2. fill in http://192.168.2.254 as HOST(I've tried the IP without http:// as well and http://mymodem.kpn, didn't work either)
  3. use "admin" as USERNAME and my admin's password as PASSWORD
  4. tried both encryption methods, neither works even though it should be SHA512 according to the table
  5. tried both with/without validating SSL cert
  6. run the script
  7. it runs into the connection exception while trying to client.login() and exits

Expected behavior

I expected to connect to my KPN Box 12 and get my device info and connected devices and such.

Screenshots

Image

Image

Additional context

I'm wondering if it's not working because KPN recently rolled out their own KPN software instead of the slow "original" interface it had before that perhaps the calls or xpaths are different now, but I don't expect it.

[bug]

frankhuurman avatar Feb 21 '25 00:02 frankhuurman

Added a general Exception to debug some more here:

Image

And got a more descriptive error when retrying the connection: 'NoneType' object is not subscriptable

So something in the result = await self.__post(api_host, form_data) request doesn't seem to be going right because an object is None. But it just doesn't return any of the expected Errors and seems to silently fail somehow.

frankhuurman avatar Feb 21 '25 00:02 frankhuurman

Went a level deeper and added a print statement over here:

Image

which gave me the output:

<ClientResponse(http://192.168.2.254/cgi/json-req) [404 Not Found]>
<CIMultiDictProxy('Content-Length': '0')>

So it seems that this endpoint is not available anymore, perhaps due to the new KPN software on it.

Also found out there are some endpoints that show the entire configuration in .yaml format, e.g.: http://192.168.2.254/assets/ensemble/apps/kpnApp/Modem%2520instellingen.yaml http://192.168.2.254/assets/ensemble/apps/kpnApp/Security.yaml

Probably new since this Flutter app was released

frankhuurman avatar Feb 21 '25 01:02 frankhuurman

Well I got some response working with this quick whipped up example:

import requests

BASE_URL = "http://192.168.2.254/ws/NeMo/Intf/lan:getMIBs"
USERNAME = "admin"
PASSWORD = "my-modem-password"

headers = {
    "Content-Type": "application/x-sah-ws-4-call+json",
    "Authorization": "X-Sah-Login"
}

payload = {
    "service": "sah.Device.Information",
    "method": "createContext",
    "parameters": {
        "applicationName": "webui",
        "username": USERNAME,
        "password": PASSWORD
    }
}

session = requests.Session()
response = session.post(BASE_URL, json=payload, headers=headers)

if response.status_code == 200:
    data = response.json()
    if "errors" in data:
        print("Login failed:", data["errors"])
    else:
        context_id = data.get("data", {}).get("contextID")
        if context_id:
            print("login successful!")
            session.headers.update({"X-Context": context_id})
        else:
            print("Login response did not contain a context ID.")
else:
    print(f"HTTP error {response.status_code}: {response.text}")


### Now we probably have a context_id

headers = {
    "Content-Type": "application/x-sah-ws-4-call+json",
    "Authorization": f"X-Sah {context_id}",
    "X-Context": context_id
}

payload = {
    "service": "DeviceInfo",
    "method": "get",
    "parameters": ""
}

response = requests.post(BASE_URL, json=payload, headers=headers)

if response.status_code == 200:
    print("Response:", response.json())
else:
    print(f"HTTP error {response.status_code}: {response.text}")

Gives an output like(anonimized the values somewhat):

login successful!
Response: {'status': {'ProductClass': 'F1234s', 'SerialNumber': 'LK12345ME123456', 'SoftwareVersion': 'V12.C.24.05.08', 'AdditionalSoftwareVersion': '', 'BaseMAC': 'xx:xx:xx:xx:xx:xx'}}

Though haven't had much success with other endpoints/services yet

frankhuurman avatar Feb 21 '25 01:02 frankhuurman

Seems like cookies are key. Keeping the session around works for me:

import requests

BASE_URL = "http://192.168.2.254/ws/NeMo/Intf/lan:getMIBs"
USERNAME = "admin"
PASSWORD = ""

headers = {
    "Content-Type": "application/x-sah-ws-4-call+json",
    "Authorization": "X-Sah-Login"
}

payload = {
    "service": "sah.Device.Information",
    "method": "createContext",
    "parameters": {
        "applicationName": "webui",
        "username": USERNAME,
        "password": PASSWORD
    }
}

session = requests.Session()
response = session.post(BASE_URL, json=payload, headers=headers)

if response.status_code == 200:
    data = response.json()
    if "errors" in data:
        print("Login failed:", data["errors"])
    else:
        context_id = data.get("data", {}).get("contextID")
        if context_id:
            print("login successful!")
            session.headers.update({"X-Context": context_id})
        else:
            print("Login response did not contain a context ID.")
else:
    print(f"HTTP error {response.status_code}: {response.text}")


### Now we probably have a context_id

headers = {
    "Content-Type": "application/x-sah-ws-4-call+json",
    "Authorization": f"X-Sah {context_id}",
    "X-Context": context_id
}

payload = {
    "service": "Tessares",
    "method": "get",
    "parameters": ""
}

payloads = [
    {"service":"NMC.Devices","method":"findSSW","parameters":{}},
    {"service":"LEDs","method":"getRootLEDs","parameters":""},
    {"service":"LEDs","method":"getRootLEDs","parameters":""},
    {"service":"LEDs","method":"getRootLEDs","parameters":""},
    {"service":"sah.Device.Information","method":"createContext","parameters":{"applicationName":"webui","username":"admin","password":f"{PASSWORD}"}},
    {"service":"Tessares","method":"get","parameters":{}},
    {"service":"Devices","method":"get","parameters":{"expression":"not interface and not self and ssw","flags":"full_links"}},
    {"service":"Devices.Device.lan","method":"topology","parameters":{"expression":"not logical","flags":"no_recurse|no_actions"}},
    {"service":"NMC.Wifi","method":"get","parameters":{}},
    {"service":"Devices","method":"get","parameters":{"expression":"not interface and not self and ssw","flags":"alternatives"}},
    {"service":"NeMo.Intf.wwan","method":"get","parameters":{}},
    {"service":"NeMo.Intf.data","method":"getMIBs","parameters":{"mibs":"base ppp dhcp"}},
    {"service":"Tessares","method":"get","parameters":{}},
    {"service":"NeMo.Intf.data","method":"luckyAddrAddress","parameters":{"flag":"ipv6 && global && @gua","traverse":"down"}},
    {"service":"Devices","method":"get","parameters":{"expression":"not interface and stb and .Active==true","flags":"full_links"}},
    {"service":"VoiceService.VoiceApplication","method":"listTrunks","parameters":{}},
    {"service":"DeviceInfo","method":"get","parameters":""},
    {"service":"Devices.Device.HGW","method":"get","parameters":{}},
    {"service":"WebuiupgradeService","method":"getLatestVersion","parameters":{}},
    {"service":"Firewall","method":"getFirewallLevel","parameters":{}},
    {"service":"NeMo.Intf.lan","method":"getMIBs","parameters":{"mibs":"wlanvap","flag":"!backhaul"}},
    {"service":"Firewall","method":"getPortForwarding","parameters":{"origin":"webui"}},
    {"service":"Scheduler","method":"getCompleteSchedules","parameters":{"type":"ToD"}},
    {"service":"Firewall","method":"getDMZ","parameters":{}},
    {"service":"NeMo.Intf.lan","method":"getMIBs","parameters":{"mibs":"wlanvap","flag":"!backhaul"}},
    {"service":"NeMo.Intf.brguest","method":"getMIBs","parameters":{"mibs":"wlanvap","flag":"!backhaul","traverse":"one level down"}},
    {"service":"NeMo.Intf.wwan","method":"get","parameters":{}},
    {"service":"ToD","method":"listMST","parameters":{}},
    {"service":"Firewall","method":"getPinhole","parameters":{}},
    {"service":"NMC","method":"getWANStatus","parameters":{}},
    {"service":"Devices","method":"get","parameters":{"expression":{"wifi":"not interface and wifi and .Active==true","ethernet":"not interface and eth and .Active==true"},"flags":"full_links"}},
    {"service":"ToD","method":"getMST","parameters":{"id":"4827EAD8A633"}},
    {"service":"ToD","method":"getMST","parameters":{"id":"b827eb11000e"}},
    {"service":"ToD","method":"getMST","parameters":{"id":"001132c2617f"}},
    {"service":"NeMo.Intf.iptv","method":"get","parameters":{}},
    {"service":"NeMo.Intf.iptv","method":"luckyAddrAddress","parameters":{"flag":"ipv4","traverse":"down"}},
    {"service":"HTTPService","method":"getCurrentUser","parameters":{}},
    {"service":"HTTPService","method":"getCurrentUser","parameters":{}},
    {"service":"HTTPService","method":"getCurrentUser","parameters":{}},
]

for payload in payloads:
    print(payload)
    response = session.post(BASE_URL, json=payload, headers=headers)
    if response.status_code == 200:
        data = response.json()
        if "errors" in data:
            print("Request failed:", data["errors"])
        else:
            print("Response:", data)
    else:
        print(f"HTTP error {response.status_code}: {response.text}")

This was just a quick export from the landing pages requests, I'm sure there are more.

bas-net avatar May 05 '25 07:05 bas-net