1# This file is part of Scapy.
2# See http://www.secdev.org/projects/scapy for more information.
3#
4# Scapy is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 2 of the License, or
7# (at your option) any later version.
8#
9# Scapy is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Scapy.  If not, see <http://www.gnu.org/licenses/>.
16#
17# Copyright (C) 2020 Rahul Jadhav <nyrahul@gmail.com>
18
19# RFC 6551
20# scapy.contrib.description = Routing Metrics used for Path Calc in LLNs
21# scapy.contrib.status = loads
22
23"""
24RFC 6551 - Routing Metrics Used for Path Calculation in LLNs
25
26+----------------------------+
27| Metrics & Constraint Types |
28+----------------------------+
29| DAGMC Option               |
30+----------------------------+
31| RPL-DIO                    |
32+----------------------------+
33"""
34
35import struct
36from scapy.compat import orb
37from scapy.packet import Packet
38from scapy.fields import ByteEnumField, ByteField, ShortField, BitField, \
39    BitEnumField, FieldLenField, StrLenField, IntField
40from scapy.layers.inet6 import _PhantomAutoPadField, _OptionsField
41from scapy.contrib.rpl import RPLOPTSSTR, RPLOPTS
42
43
44class _DAGMetricContainer(Packet):
45    name = 'Dummy DAG Metric container'
46
47    def post_build(self, pkt, pay):
48        pkt += pay
49        tmp_len = self.len
50        if self.len is None:
51            tmp_len = len(pkt) - 2
52        pkt = pkt[:1] + struct.pack("B", tmp_len) + pkt[2:]
53        return pkt
54
55
56DAGMC_OBJTYPE = {1: "Node State and Attributes",
57                 2: "Node Energy",
58                 3: "Hop Count",
59                 4: "Link Throughput",
60                 5: "Link Latency",
61                 6: "Link Quality Level",
62                 7: "Link ETX",
63                 8: "Link Color"}
64
65
66class DAGMCObjUnknown(Packet):
67    """
68    Dummy unknown metric/constraint
69    """
70    name = 'Unknown DAGMC Object Option'
71    fields_desc = [ByteEnumField("otype", 3, DAGMC_OBJTYPE),
72                   FieldLenField("olen", None, length_of="odata", fmt="B"),
73                   StrLenField("odata", "",
74                               length_from=lambda pkt: pkt.olen)]
75
76    @classmethod
77    def dispatch_hook(cls, _pkt=None, *_, **kargs):
78        """
79        Dispatch hook for DAGMC sub-fields
80        """
81        if _pkt:
82            opt_type = orb(_pkt[0])  # Option type
83            if opt_type in DAGMC_CLS:
84                return DAGMC_CLS[opt_type]
85        return cls
86
87
88AGG_RTMETRIC = {0: "additive",
89                1: "maximum",
90                2: "minimum",
91                3: "multiplicative"}  # RFC 6551
92
93
94class DAGMCObj(Packet):
95    """
96    Set the length field in DAG Metric Constraint Control Option
97    """
98    name = 'Dummy DAG MC Object'
99    # RFC 6551 - 2.1
100    fields_desc = [ByteEnumField("otype", 0, DAGMC_OBJTYPE),
101                   BitField("resflags", 0, 5),
102                   BitField("P", 0, 1),
103                   BitField("C", 0, 1),
104                   BitField("O", 0, 1),
105                   BitField("R", 0, 1),
106                   BitEnumField("A", 0, 3, AGG_RTMETRIC),
107                   BitField("prec", 0, 4),
108                   ByteField("len", None)]
109
110    def post_build(self, pkt, pay):
111        pkt += pay
112        tmp_len = self.len
113        if self.len is None:
114            tmp_len = len(pkt) - 4
115        pkt = pkt[:3] + struct.pack("B", tmp_len) + pkt[4:]
116        return pkt
117
118
119class RPLDAGMCNSA(DAGMCObj):
120    """
121    DAG Metric: Node State and Attributes
122    """
123    name = "Node State and Attributes"
124    otype = 1
125    # RFC 6551 - 3.1
126    fields_desc = DAGMCObj.fields_desc + [
127        # NSA Object Body Format
128        ByteField("res", 0),
129        BitField("flags", 0, 6),
130        BitField("Agg", 0, 1),  # A
131        BitField("Overload", 0, 1),  # O
132    ]
133
134
135class RPLDAGMCNodeEnergy(DAGMCObj):
136    """
137    DAG Metric: Node Energy
138    """
139    name = "Node Energy"
140    otype = 2
141    # RFC 6551 - 3.2
142    fields_desc = DAGMCObj.fields_desc + [
143        # NE Sub-Object Format
144        BitField("flags", 0, 4),
145        BitField("I", 0, 1),
146        BitField("T", 0, 2),
147        BitField("E", 0, 1),
148        ByteField("E_E", 0)
149    ]
150
151
152class RPLDAGMCHopCount(DAGMCObj):
153    """
154    DAG Metric: Hop Count
155    """
156    name = "Hop Count"
157    otype = 3
158    # RFC 6551 - 3.3
159    fields_desc = DAGMCObj.fields_desc + [
160        # Sub-Object Format
161        BitField("res", 0, 4),
162        BitField("flags", 0, 4),
163        ByteField("HopCount", 1)
164    ]
165
166
167class RPLDAGMCLinkThroughput(DAGMCObj):
168    """
169    DAG Metric: Link Throughput
170    """
171    name = "Link Throughput"
172    otype = 4
173    # RFC 6551 - 4.1
174    fields_desc = DAGMCObj.fields_desc + [
175        # Sub-Object Format
176        IntField("Throughput", 1)
177    ]
178
179
180class RPLDAGMCLinkLatency(DAGMCObj):
181    """
182    DAG Metric: Link Latency
183    """
184    name = "Link Latency"
185    otype = 5
186    # RFC 6551 - 4.2
187    fields_desc = DAGMCObj.fields_desc + [
188        # NE Sub-Object Format
189        IntField("Latency", 1)
190    ]
191
192
193class RPLDAGMCLinkQualityLevel(DAGMCObj):
194    """
195    DAG Metric: Link Quality Level (LQL)
196    """
197    name = "Link Quality Level"
198    otype = 6
199    # RFC 6551 - 4.3.1
200    fields_desc = DAGMCObj.fields_desc + [
201        # Sub-Object Format
202        ByteField("res", 0),
203        BitField("val", 0, 3),
204        BitField("counter", 0, 5)
205    ]
206
207
208class RPLDAGMCLinkETX(DAGMCObj):
209    """
210    DAG Metric: Link ETX
211    """
212    name = "Link ETX"
213    otype = 7
214    # RFC 6551 - 4.3.2
215    fields_desc = DAGMCObj.fields_desc + [
216        # Sub-Object Format
217        ShortField("ETX", 1)
218    ]
219
220
221# Note: Wireshark shows warning decoding LinkColor.
222# This seems to be wireshark issue!
223class RPLDAGMCLinkColor(DAGMCObj):
224    """
225    DAG Metric: Link Color
226    """
227    name = "Link Color"
228    otype = 8
229    # RFC 6551 - 4.4.1
230    fields_desc = DAGMCObj.fields_desc + [
231        # Sub-Object Format
232        ByteField("res", 0),
233        BitField("color", 1, 10),
234        BitField("counter", 1, 6)
235    ]
236
237
238DAGMC_CLS = {1: RPLDAGMCNSA,
239             2: RPLDAGMCNodeEnergy,
240             3: RPLDAGMCHopCount,
241             4: RPLDAGMCLinkThroughput,
242             5: RPLDAGMCLinkLatency,
243             6: RPLDAGMCLinkQualityLevel,
244             7: RPLDAGMCLinkETX,
245             8: RPLDAGMCLinkColor}
246
247
248class RPLOptDAGMC(_DAGMetricContainer):
249    """
250    Control Option: DAG Metric Container
251    """
252    name = "DAG Metric Container"
253    fields_desc = [ByteEnumField("otype", 2, RPLOPTSSTR),
254                   ByteField("len", None),
255                   _PhantomAutoPadField("autopad", 0),
256                   _OptionsField("options", [], DAGMCObjUnknown, 8,
257                                 length_from=lambda pkt: 8 * pkt.len)]
258
259
260# https://www.iana.org/assignments/rpl/rpl.xhtml#control-message-options
261RPLOPTS.update({2: RPLOptDAGMC})
262