Skip to content

Commit a460f78

Browse files
committed
Extend Fritzbox support to older firmware versions
Tested with: - 06.06 (7270v3) - 06.35 (7320) - 06.56 (7320) - 06.87 (7412) - 07.18 (7362sl) - 08.02 (7530)
1 parent c8471aa commit a460f78

7 files changed

Lines changed: 269 additions & 73 deletions

File tree

fritzbox/data.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ type rawDataOverview struct {
1313
Data string
1414
UpdateData string
1515
Legacy bool
16+
Ancient bool
1617
}
1718

1819
type rawDataStats struct {
19-
Data string
20-
Legacy bool
20+
Data string
21+
Legacy bool
22+
Ancient bool
2123
}
2224

2325
type rawDataSpectrum struct {
24-
Data string
25-
Legacy bool
26+
Data string
27+
Legacy bool
28+
Ancient bool
2629
}
2730

2831
type rawDataTR064 struct {

fritzbox/overview.go

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,22 @@ type overviewDataJSON struct {
3636
type overviewDataLegacy struct {
3737
DSLAM string `json:"dslam"`
3838
Lines []struct {
39-
State string `json:"state"`
40-
Mode string `json:"mode"`
41-
Time string `json:"time"`
39+
State string `json:"state"`
40+
TrainState string `json:"train_state"`
41+
Mode string `json:"mode"`
42+
Time string `json:"time"`
4243
} `json:"line"`
4344
}
4445

4546
func parseOverview(status *models.Status, d *rawDataOverview) {
4647
if !d.Legacy {
4748
parseOverviewJSON(status, d.Data)
4849
} else {
49-
parseOverviewLegacy(status, d.Data)
50+
if d.Ancient {
51+
parseOverviewLegacyAncient(status, d.Data)
52+
} else {
53+
parseOverviewLegacy(status, d.Data)
54+
}
5055
parseOverviewDataLegacy(status, d.UpdateData)
5156
}
5257
}
@@ -65,6 +70,19 @@ func interpretOverviewState(state string) models.State {
6570
return models.StateUnknown
6671
}
6772

73+
func interpretOverviewStateAncient(trainState string) models.State {
74+
trainState = strings.ToLower(trainState)
75+
switch {
76+
case strings.HasPrefix(trainState, "dsl aktiv"):
77+
return models.StateShowtime
78+
case strings.HasPrefix(trainState, "training"):
79+
return models.StateInit
80+
case strings.HasPrefix(trainState, "nicht verbunden"):
81+
return models.StateDown
82+
}
83+
return models.StateUnknown
84+
}
85+
6886
func interpretOverviewTime(timeStr string) (out models.Duration) {
6987
split := strings.Fields(timeStr)
7088

@@ -120,6 +138,42 @@ func parseOverviewJSON(status *models.Status, dslOverview string) {
120138
status.FarEndInventory.Version = strings.TrimPrefix(data.ExternApValue, "Version ")
121139
}
122140

141+
func parseOverviewLegacyAncient(status *models.Status, dslOverview string) {
142+
doc, err := html.Parse(strings.NewReader(dslOverview))
143+
if err != nil {
144+
return
145+
}
146+
147+
dslVersionNode := htmlutil.FindFirstNode(doc, htmlutil.MatcherTagNameAndClass("td", "dsl_txt_info"))
148+
if dslVersionNode != nil {
149+
nearEndVersionNode := htmlutil.FindLastNode(dslVersionNode, func(n *html.Node) bool {
150+
return n.Type == html.TextNode && strings.TrimSpace(n.Data) != ""
151+
})
152+
if nearEndVersionNode != nil {
153+
status.NearEndInventory.Vendor = "AVM"
154+
status.NearEndInventory.Version = strings.TrimSpace(nearEndVersionNode.Data)
155+
}
156+
}
157+
158+
dslamVersionNode := htmlutil.FindFirstNode(doc, htmlutil.MatcherTagNameAndClass("td", "dsl_txt_info_dslam"))
159+
if dslamVersionNode != nil {
160+
farEndInventoryNodes := htmlutil.FindAllNodes(dslamVersionNode, func(n *html.Node) bool {
161+
return n.Type == html.TextNode && strings.TrimSpace(n.Data) != ""
162+
})
163+
if len(farEndInventoryNodes) >= 2 {
164+
vendor := farEndInventoryNodes[0].Data
165+
vendorStr := strings.TrimSpace(vendor)
166+
version := farEndInventoryNodes[1].Data
167+
versionStr := strings.TrimSpace(version)
168+
169+
if !strings.Contains(vendorStr, "---") && versionStr != "0" {
170+
status.FarEndInventory.Vendor = vendorStr
171+
status.FarEndInventory.Version = versionStr
172+
}
173+
}
174+
}
175+
}
176+
123177
func parseOverviewLegacy(status *models.Status, dslOverview string) {
124178
doc, err := html.Parse(strings.NewReader(dslOverview))
125179
if err != nil {
@@ -147,10 +201,10 @@ func parseOverviewLegacy(status *models.Status, dslOverview string) {
147201
farEndInventoryNodes := htmlutil.FindAllNodes(columns[2], func(n *html.Node) bool {
148202
return n.Type == html.TextNode && strings.TrimSpace(n.Data) != ""
149203
})
150-
if len(farEndInventoryNodes) >= 2 {
151-
vendor := farEndInventoryNodes[len(farEndInventoryNodes)-2].Data
204+
if len(farEndInventoryNodes) >= 3 {
205+
vendor := farEndInventoryNodes[1].Data
152206
vendorStr := strings.TrimSuffix(strings.TrimSpace(vendor), ":")
153-
version := farEndInventoryNodes[len(farEndInventoryNodes)-1].Data
207+
version := farEndInventoryNodes[2].Data
154208
versionStr := strings.TrimSpace(version)
155209

156210
if !strings.Contains(vendorStr, "---") && !strings.Contains(versionStr, "---") {
@@ -169,6 +223,10 @@ func parseOverviewDataLegacy(status *models.Status, dslOverviewData string) {
169223

170224
if len(data.Lines) > 0 {
171225
status.State = interpretOverviewState(data.Lines[0].State)
226+
if status.State == models.StateUnknown {
227+
status.State = interpretOverviewStateAncient(data.Lines[0].TrainState)
228+
}
229+
172230
status.Mode = helpers.ParseMode(data.Lines[0].Mode)
173231

174232
if status.State == models.StateShowtime {

fritzbox/session.go

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ import (
3131
"3e8.eu/go/dsl/internal/httpdigest"
3232
)
3333

34+
type httpError struct {
35+
Err error
36+
StatusCode int
37+
}
38+
39+
func (e *httpError) Error() string {
40+
return e.Err.Error()
41+
}
42+
43+
func (e *httpError) Unwrap() error {
44+
return e.Err
45+
}
46+
3447
type session struct {
3548
host string
3649
username string
@@ -227,7 +240,10 @@ func (s *session) get(path string) ([]byte, error) {
227240
defer resp.Body.Close()
228241

229242
if resp.StatusCode != 200 {
230-
err = fmt.Errorf("request for %s failed with status %d", path, resp.StatusCode)
243+
err = &httpError{
244+
Err: fmt.Errorf("request for %s failed with status %d", path, resp.StatusCode),
245+
StatusCode: resp.StatusCode,
246+
}
231247

232248
if resp.StatusCode == 303 {
233249
return nil, &dsl.ConnectionError{Err: err}
@@ -247,7 +263,10 @@ func (s *session) postForm(path string, data url.Values) ([]byte, error) {
247263
defer resp.Body.Close()
248264

249265
if resp.StatusCode != 200 {
250-
err = fmt.Errorf("request for %s failed with status %d", path, resp.StatusCode)
266+
err = &httpError{
267+
Err: fmt.Errorf("request for %s failed with status %d", path, resp.StatusCode),
268+
StatusCode: resp.StatusCode,
269+
}
251270

252271
if resp.StatusCode == 303 {
253272
return nil, &dsl.ConnectionError{Err: err}
@@ -272,12 +291,16 @@ func (s *session) loadPost(path string, data url.Values) (string, error) {
272291
return string(body), err
273292
}
274293

275-
func (s *session) loadSupportData() (string, error) {
294+
func (s *session) loadSupportDataInternal(tryDiagnosisData bool) (string, error) {
276295
// this needs to use multipart/form-data and the order of the fields is important
277296
var body bytes.Buffer
278297
mpart := multipart.NewWriter(&body)
279298
mpart.WriteField("sid", s.sid)
280-
mpart.WriteField("DiagnosisData", "")
299+
if tryDiagnosisData {
300+
mpart.WriteField("DiagnosisData", "")
301+
} else {
302+
mpart.WriteField("SupportData", "")
303+
}
281304

282305
req, err := http.NewRequest(http.MethodPost, s.host+"/cgi-bin/firmwarecfg", &body)
283306
if err != nil {
@@ -293,7 +316,10 @@ func (s *session) loadSupportData() (string, error) {
293316
defer resp.Body.Close()
294317

295318
if resp.StatusCode != 200 {
296-
err = fmt.Errorf("request for support data failed with status %d", resp.StatusCode)
319+
err = &httpError{
320+
Err: fmt.Errorf("request for support data failed with status %d", resp.StatusCode),
321+
StatusCode: resp.StatusCode,
322+
}
297323

298324
if resp.StatusCode == 303 {
299325
return "", &dsl.ConnectionError{Err: err}
@@ -309,23 +335,46 @@ func (s *session) loadSupportData() (string, error) {
309335
for scanner.Scan() {
310336
line := scanner.Text()
311337

312-
if !foundBeginSection && strings.HasPrefix(line, "#### BEGIN SECTION DSLManager_port") {
313-
foundBeginSection = true
338+
if strings.HasPrefix(line, "<!DOCTYPE") {
339+
resp.Body.Close()
340+
341+
if tryDiagnosisData {
342+
return s.loadSupportDataInternal(false)
343+
} else {
344+
return "", fmt.Errorf("got HTML response instead of support data")
345+
}
346+
}
347+
348+
if !foundBeginSection {
349+
if (strings.HasPrefix(line, "#### BEGIN SECTION DSLManager_port") &&
350+
!strings.HasPrefix(line, "#### BEGIN SECTION DSLManager_port_undependent")) ||
351+
strings.HasPrefix(line, "DSL Overview") {
352+
foundBeginSection = true
353+
}
314354
}
315355

316356
if foundBeginSection {
357+
if strings.HasPrefix(line, "DSL Configs") {
358+
resp.Body.Close()
359+
break
360+
}
361+
317362
fmt.Fprintln(&b, line)
318-
}
319363

320-
if foundBeginSection && strings.HasPrefix(line, "#### END SECTION DSLManager_port") {
321-
resp.Body.Close()
322-
break
364+
if strings.HasPrefix(line, "#### END SECTION DSLManager_port") {
365+
resp.Body.Close()
366+
break
367+
}
323368
}
324369
}
325370

326371
return b.String(), scanner.Err()
327372
}
328373

374+
func (s *session) loadSupportData() (string, error) {
375+
return s.loadSupportDataInternal(true)
376+
}
377+
329378
func (s *session) getHostWithoutPort() string {
330379
bracketIndex := strings.LastIndexByte(s.host, ']')
331380
if bracketIndex != -1 {
@@ -399,7 +448,10 @@ func (s *session) loadTR064(path, serviceType, action string) (string, error) {
399448
}
400449

401450
if resp.StatusCode != 200 {
402-
err = fmt.Errorf("request for %s failed with status %d - make sure that TR-064 (access for apps) is enabled", soapAction, resp.StatusCode)
451+
err = &httpError{
452+
Err: fmt.Errorf("request for %s failed with status %d - make sure that TR-064 (access for apps) is enabled", soapAction, resp.StatusCode),
453+
StatusCode: resp.StatusCode,
454+
}
403455

404456
if resp.StatusCode == 401 {
405457
return "", &dsl.ConnectionError{Err: err}

0 commit comments

Comments
 (0)