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

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 self.underlayer.len > 28: 

92 return HSRPmd5 

93 else: 

94 return Packet.guess_payload_class(self, payload) 

95 

96 

97class HSRPAdvertise(Packet): 

98 name = "HSRP Advertise" 

99 fields_desc = [ 

100 ByteField("version", 0), 

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

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

103 ShortField("length", 10), 

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

105 ByteField("reserved1", 0), 

106 ShortField("activegroups", 0), 

107 ShortField("passivegroups", 0), 

108 IntField("reserved2", 0), 

109 ] 

110 

111 @classmethod 

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

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

114 return HSRP 

115 return cls 

116 

117 

118class HSRPv2(Packet): 

119 """ 

120 HSRP version 2. 

121 """ 

122 

123 name = "HSRPv2" 

124 fields_desc = [ 

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

126 ByteField("len", None), 

127 ByteField("version", 2), 

128 ByteEnumField( 

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

130 ), 

131 ByteEnumField( 

132 "state", 

133 16, 

134 { 

135 0: "Initial", 

136 1: "Learn", 

137 2: "Listen", 

138 4: "Speak", 

139 8: "Standby", 

140 16: "Active", 

141 }, 

142 ), 

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

144 XShortField("group", 1), 

145 SourceMACField("identifier"), 

146 IntField("priority", 100), 

147 IntField("hellotime", 3000), 

148 IntField("holdtime", 10000), 

149 MultipleTypeField( 

150 [ 

151 ( 

152 IPField("virtualIP", "0.0.0.0"), 

153 ( 

154 lambda p: p.ipVer == 4, 

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

156 ), 

157 ), 

158 ( 

159 IP6Field("virtualIP", "::"), 

160 ( 

161 lambda p: p.ipVer == 6, 

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

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

164 ), 

165 ), 

166 ], 

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

168 ), 

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

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

171 ConditionalField( 

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

173 lambda pkt: valid_net(pkt.virtualIP), 

174 ), 

175 ] 

176 

177 def post_build(self, pkt, pay): 

178 if self.ipVer is None: 

179 ip_ver = 4 

180 

181 if valid_net6(self.virtualIP): 

182 ip_ver = 6 

183 

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

185 

186 if self.len is None: 

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

188 return pkt + pay 

189 

190 def guess_payload_class(self, payload): 

191 if payload: 

192 hsrp_auth_payload_type = payload[0] 

193 if hsrp_auth_payload_type == 3: 

194 return HSRPv2TextAuth 

195 elif hsrp_auth_payload_type == 4: 

196 return HSRPmd5 

197 return Packet.guess_payload_class(self, payload) 

198 

199 

200class HSRPmd5(Packet): 

201 """ 

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

203 """ 

204 

205 name = "HSRP MD5 Authentication" 

206 fields_desc = [ 

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

208 ByteField("len", None), 

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

210 ByteField("padding", 0x00), 

211 XShortField("flags", 0x00), 

212 SourceIPField("sourceip"), 

213 XIntField("keyid", 0x00), 

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

215 ] 

216 

217 def post_build(self, pkt, pay): 

218 if self.len is None: 

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

220 return pkt + pay 

221 

222 

223class HSRPv2TextAuth(Packet): 

224 """ 

225 Default plain text authentication header. 

226 This is only used with HSRP version 2. 

227 """ 

228 

229 name = "HSRP Authentication" 

230 fields_desc = [ 

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

232 ByteField("len", 8), 

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

234 ] 

235 

236 

237bind_bottom_up(UDP, HSRP, dport=1985) 

238bind_bottom_up(UDP, HSRP, sport=1985) 

239bind_bottom_up(UDP, HSRPv2, dport=1985) 

240bind_bottom_up(UDP, HSRPv2, sport=1985) 

241bind_bottom_up(UDP, HSRPv2, dport=2029) 

242bind_bottom_up(UDP, HSRPv2, sport=2029) 

243 

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

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

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

247 

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

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

250 

251if conf.ipv6_enabled: 

252 from scapy.layers.inet6 import DestIP6Field 

253 

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