Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/hsrp.py: 82%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

82 statements  

1# SPDX-License-Identifier: GPL-2.0-only 

2# This file is part of Scapy 

3# See https://scapy.net/ for more information 

4# Copyright (C) Mathieu RENARD <mathieu.renard(at)gmail.com> 

5 

6# scapy.contrib.description = Hot Standby Router Protocol (HSRP) 

7# scapy.contrib.status = loads 

8 

9""" 

10HSRP (Hot Standby Router Protocol) 

11A proprietary redundancy protocol for Cisco routers. 

12 

13- HSRP Version 1: 

14 https://tools.ietf.org/html/rfc2281 

15- HSRP Version 2: 

16 http://www.smartnetworks.jp/2006/02/hsrp_8_hsrp_version_2.html 

17""" 

18 

19import struct 

20from scapy.config import conf 

21from scapy.fields import ( 

22 ByteEnumField, 

23 ByteField, 

24 ConditionalField, 

25 IntField, 

26 IP6Field, 

27 IPField, 

28 MultipleTypeField, 

29 ShortEnumField, 

30 ShortField, 

31 SourceIPField, 

32 StrField, 

33 StrFixedLenField, 

34 XIntField, 

35 XShortField, 

36) 

37from scapy.packet import Packet, bind_layers, bind_bottom_up 

38from scapy.utils import valid_net, valid_net6 

39 

40from scapy.layers.l2 import SourceMACField 

41from scapy.layers.inet import DestIPField, UDP 

42 

43_HSRP_OPCODES = {0: "Hello", 1: "Coup", 2: "Resign", 3: "Advertise"} 

44_HSRP_STATES = { 

45 0: "Initial", 

46 1: "Learn", 

47 2: "Listen", 

48 4: "Speak", 

49 8: "Standby", 

50 16: "Active", 

51} 

52_HSRP_ADVERTISE_TYPES = {1: "HSRP interface state"} 

53_HSRP_ADVERTISE_STATES = {1: "Active", 2: "Passive"} 

54 

55 

56class HSRP(Packet): 

57 """ 

58 HSRP version 1 

59 """ 

60 

61 name = "HSRPv1" 

62 fields_desc = [ 

63 ByteField("version", 0), 

64 ByteEnumField("opcode", 0, _HSRP_OPCODES), 

65 ByteEnumField("state", 16, _HSRP_STATES), 

66 ByteField("hellotime", 3), 

67 ByteField("holdtime", 10), 

68 ByteField("priority", 120), 

69 ByteField("group", 1), 

70 ByteField("reserved", 0), 

71 StrFixedLenField("auth", b"cisco" + b"\x00" * 3, 8), 

72 IPField("virtualIP", "192.168.1.1"), 

73 ] 

74 

75 @classmethod 

76 def dispatch_hook(cls, _pkt=None, *args, **kargs): 

77 """ 

78 Dissects proper version of HSRP packet 

79 based on the first byte. 

80 """ 

81 if _pkt: 

82 if _pkt[0] == 0: 

83 if len(_pkt) >= 2 and _pkt[1] == 3: 

84 return HSRPAdvertise 

85 return HSRP 

86 if _pkt[0] == 1: 

87 return HSRPv2 

88 return HSRP 

89 

90 def guess_payload_class(self, payload): 

91 if ( 

92 self.underlayer is not None 

93 and getattr(self.underlayer, "len", None) is not None 

94 and self.underlayer.len > 28 

95 ): 

96 return HSRPmd5 

97 return Packet.guess_payload_class(self, payload) 

98 

99 

100class HSRPAdvertise(Packet): 

101 name = "HSRP Advertise" 

102 fields_desc = [ 

103 ByteField("version", 0), 

104 ByteEnumField("opcode", 3, _HSRP_OPCODES), 

105 ShortEnumField("type", 1, _HSRP_ADVERTISE_TYPES), 

106 ShortField("length", 10), 

107 ByteEnumField("state", 1, _HSRP_ADVERTISE_STATES), 

108 ByteField("reserved1", 0), 

109 ShortField("activegroups", 0), 

110 ShortField("passivegroups", 0), 

111 IntField("reserved2", 0), 

112 ] 

113 

114 @classmethod 

115 def dispatch_hook(cls, _pkt=None, *args, **kargs): 

116 if _pkt and len(_pkt) >= 2 and _pkt[1] != 3: 

117 return HSRP 

118 return cls 

119 

120 

121class HSRPv2(Packet): 

122 """ 

123 HSRP version 2. 

124 """ 

125 

126 name = "HSRPv2" 

127 fields_desc = [ 

128 ByteEnumField("type", 1, {1: "Group state TLV"}), 

129 ByteField("len", None), 

130 ByteField("version", 2), 

131 ByteEnumField( 

132 "opcode", 0, {0: "Hello", 1: "Coup", 2: "Resign", 3: "Advertise"} 

133 ), 

134 ByteEnumField( 

135 "state", 

136 16, 

137 { 

138 0: "Initial", 

139 1: "Learn", 

140 2: "Listen", 

141 4: "Speak", 

142 8: "Standby", 

143 16: "Active", 

144 }, 

145 ), 

146 ByteEnumField("ipVer", None, {4: "IPv4", 6: "IPv6"}), 

147 XShortField("group", 1), 

148 SourceMACField("identifier"), 

149 IntField("priority", 100), 

150 IntField("hellotime", 3000), 

151 IntField("holdtime", 10000), 

152 MultipleTypeField( 

153 [ 

154 ( 

155 IPField("virtualIP", "0.0.0.0"), 

156 ( 

157 lambda p: p.ipVer == 4, 

158 lambda p, val: p.ipVer != 6 and (val is None or valid_net(val)), 

159 ), 

160 ), 

161 ( 

162 IP6Field("virtualIP", "::"), 

163 ( 

164 lambda p: p.ipVer == 6, 

165 lambda p, val: p.ipVer != 4 

166 and (val is None or valid_net6(val)), 

167 ), 

168 ), 

169 ], 

170 StrField("virtualIP", None), # By default 

171 ), 

172 # The virtualIP field's expected size is always the size of an IPv6 

173 # address. If IPv4 is used, padding is required. 

174 ConditionalField( 

175 StrFixedLenField("padding", b"\x00" * 12, 12), 

176 lambda pkt: valid_net(pkt.virtualIP), 

177 ), 

178 ] 

179 

180 def post_build(self, pkt, pay): 

181 if self.ipVer is None: 

182 ip_ver = 4 

183 

184 if valid_net6(self.virtualIP): 

185 ip_ver = 6 

186 

187 pkt = pkt[:5] + struct.pack("B", ip_ver) + pkt[6:] 

188 

189 if self.len is None: 

190 pkt = pkt[:1] + struct.pack("B", len(pkt) - 2) + pkt[2:] 

191 return pkt + pay 

192 

193 def guess_payload_class(self, payload): 

194 if payload: 

195 hsrp_auth_payload_type = payload[0] 

196 if hsrp_auth_payload_type == 3: 

197 return HSRPv2TextAuth 

198 elif hsrp_auth_payload_type == 4: 

199 return HSRPmd5 

200 return Packet.guess_payload_class(self, payload) 

201 

202 

203class HSRPmd5(Packet): 

204 """ 

205 MD5 Authentication header for HSRP (version 1 and 2). 

206 """ 

207 

208 name = "HSRP MD5 Authentication" 

209 fields_desc = [ 

210 ByteEnumField("type", 4, {4: "MD5 authentication"}), 

211 ByteField("len", None), 

212 ByteEnumField("algo", 1, {1: "MD5"}), 

213 ByteField("padding", 0x00), 

214 XShortField("flags", 0x00), 

215 SourceIPField("sourceip"), 

216 XIntField("keyid", 0x00), 

217 StrFixedLenField("authdigest", b"\x00" * 16, 16), 

218 ] 

219 

220 def post_build(self, pkt, pay): 

221 if self.len is None: 

222 pkt = pkt[:1] + struct.pack("B", len(pkt) - 2) + pkt[2:] 

223 return pkt + pay 

224 

225 

226class HSRPv2TextAuth(Packet): 

227 """ 

228 Default plain text authentication header. 

229 This is only used with HSRP version 2. 

230 """ 

231 

232 name = "HSRP Authentication" 

233 fields_desc = [ 

234 ByteEnumField("type", 3, {3: "Text Authentication TLV"}), 

235 ByteField("len", 8), 

236 StrFixedLenField("auth", b"cisco" + b"\x00" * 3, 8), 

237 ] 

238 

239 

240bind_bottom_up(UDP, HSRP, dport=1985) 

241bind_bottom_up(UDP, HSRP, sport=1985) 

242bind_bottom_up(UDP, HSRPv2, dport=1985) 

243bind_bottom_up(UDP, HSRPv2, sport=1985) 

244bind_bottom_up(UDP, HSRPv2, dport=2029) 

245bind_bottom_up(UDP, HSRPv2, sport=2029) 

246 

247bind_layers(UDP, HSRP, dport=1985, sport=1985) 

248bind_layers(UDP, HSRPv2, dport=1985, sport=1985) # HSRP v2 with IPv4 

249bind_layers(UDP, HSRPv2, dport=2029, sport=2029) # HSRP v2 with IPv6 

250 

251DestIPField.bind_addr(UDP, "224.0.0.2", dport=1985) 

252DestIPField.bind_addr(UDP, "224.0.0.102", dport=1985) 

253 

254if conf.ipv6_enabled: 

255 from scapy.layers.inet6 import DestIP6Field 

256 

257 DestIP6Field.bind_addr(UDP, "ff02::66", dport=2029)