| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | #include "RuntimeErrorWithSourceLocation.h" |
| 3 | #include "IpRuleManager.h" |
| 4 | #include "NetlinkTransactionError.h" |
| 5 | #include "Utils.h" |
| 6 | #include "common.h" |
| 7 | |
| 8 | struct LoopbackInterfaceAttribute |
| 9 | { |
| 10 | rtattr header; |
| 11 | // A buffer of size 4 is used for alignment purposes (aligning to NLA_ALIGNTO). |
| 12 | // The loopback interface name is hardcoded as 'lo'. |
| 13 | char interface[4] = {'l', 'o', '\0', '\0'}; |
| 14 | } __attribute__((packed)); |
| 15 | |
| 16 | template <DerivedRuleMessage TMessage> |
| 17 | void IpRuleManager::SendMessage(unsigned char family, int routingTable, int operation, int flags, const std::function<void(TMessage&)>& routine) |
| 18 | { |
| 19 | TMessage message{}; |
| 20 | message.rule.rtm_family = family; |
| 21 | message.rule.rtm_table = 0; |
| 22 | message.rule.rtm_protocol = RTPROT_BOOT; |
| 23 | message.rule.rtm_type = RTN_UNICAST; |
| 24 | message.rule.rtm_scope = RT_SCOPE_UNIVERSE; |
| 25 | |
| 26 | utils::InitializeIntegerAttribute(message.tableId, routingTable, FRA_TABLE); |
| 27 | |
| 28 | routine(message); |
| 29 | |
| 30 | auto transaction = m_channel.CreateTransaction(message, operation, flags); |
| 31 | try |
| 32 | { |
| 33 | transaction.Execute(); |
| 34 | } |
| 35 | catch (const NetlinkTransactionError& transactionErr) |
| 36 | { |
| 37 | auto errorCode = transactionErr.Error(); |
| 38 | if (errorCode.has_value()) |
| 39 | { |
| 40 | // Errors "file exists" and "file not found" are ignored in order to avoid keeping |
| 41 | // track in GnsDaemon of what rules were added/deleted and allow the same rule |
| 42 | // to be added/deleted multiple times. |
| 43 | if (errorCode.value() == -EEXIST || errorCode.value() == -ENOENT) |
| 44 | { |
| 45 | return; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | throw; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | void IpRuleManager::ModifyLoopbackRule(const Rule& rule, Operation operation) |
| 54 | { |
| 55 | if (!rule.protocol.has_value()) |
| 56 | { |
| 57 | throw RuntimeErrorWithSourceLocation("Loopback rule missing protocol"); |
| 58 | } |
| 59 | if (operation != Operation::Create && operation != Operation::Remove) |
| 60 | { |
| 61 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected operation: {}", static_cast<int>(operation))); |
| 62 | } |
| 63 | if (rule.iif.empty()) |
| 64 | { |
| 65 | throw RuntimeErrorWithSourceLocation("Loopback rule has empty iif name"); |
| 66 | } |
| 67 | |
| 68 | GNS_LOG_INFO("{} rule {}", operation == Operation::Create ? "Add" : "Remove", utils::Stringify(rule).c_str()); |
| 69 | |
| 70 | struct Message |
| 71 | { |
| 72 | rtmsg rule; |
| 73 | utils::IntegerAttribute tableId; |
| 74 | utils::IntegerAttribute priority; |
| 75 | utils::Attribute<uint8_t> protocol; |
| 76 | utils::Attribute<char> iifName; |
| 77 | } __attribute__((packed)); |
| 78 | |
| 79 | // In case of Remove, there are no additional flags needed besides NLM_F_REQUEST | NLM_F_ACK. |
| 80 | int flags = 0; |
| 81 | if (operation == Create) |
| 82 | { |
| 83 | flags = NLM_F_CREATE; |
| 84 | } |
| 85 | |
| 86 | int netlinkOperation = operation == Remove ? RTM_DELRULE : RTM_NEWRULE; |
| 87 | |
| 88 | auto buffer = std::vector<char>(RTA_ALIGN(offsetof(Message, iifName.value) + rule.iif.size())); |
| 89 | auto* message = gslhelpers::get_struct<Message>(gsl::make_span(buffer)); |
| 90 | |
| 91 | message->rule.rtm_family = rule.family; |
| 92 | message->rule.rtm_table = 0; |
| 93 | message->rule.rtm_protocol = RTPROT_BOOT; |
| 94 | message->rule.rtm_type = RTN_UNICAST; |
| 95 | message->rule.rtm_scope = RT_SCOPE_UNIVERSE; |
| 96 | |
| 97 | utils::InitializeIntegerAttribute(message->tableId, rule.routingTable, FRA_TABLE); |
| 98 | utils::InitializeIntegerAttribute(message->priority, rule.priority, RTA_PRIORITY); |
| 99 | |
| 100 | message->protocol.header.rta_len = RTA_LENGTH(sizeof(uint8_t)); |
| 101 | message->protocol.header.rta_type = FRA_IP_PROTO; |
| 102 | message->protocol.value = rule.protocol.value() == Tcp ? IPPROTO_TCP : IPPROTO_UDP; |
| 103 | |
| 104 | message->iifName.header.rta_len = RTA_SPACE(rule.iif.size()); |
| 105 | message->iifName.header.rta_type = FRA_IIFNAME; |
| 106 | auto iifNameBuffer = gsl::make_span(buffer).subspan(offsetof(Message, iifName.value)); |
| 107 | gsl::copy(gsl::make_span(rule.iif), iifNameBuffer); |
| 108 | |
| 109 | // The SendMessage helper cannot be used here because we are sending a variable sized message |
| 110 | // (the message contains the iifName field with variable length) |
| 111 | auto transaction = m_channel.CreateTransaction(buffer.data(), buffer.size(), netlinkOperation, flags); |
| 112 | try |
| 113 | { |
| 114 | transaction.Execute(); |
| 115 | } |
| 116 | catch (const NetlinkTransactionError& transactionErr) |
| 117 | { |
| 118 | auto errorCode = transactionErr.Error(); |
| 119 | if (errorCode.has_value()) |
| 120 | { |
| 121 | // Errors "file exists" and "file not found" are ignored in order to avoid keeping |
| 122 | // track in GnsDaemon of what rules were added/deleted and allow the same rule |
| 123 | // to be added/deleted multiple times. |
| 124 | if (errorCode.value() == -EEXIST || errorCode.value() == -ENOENT) |
| 125 | { |
| 126 | return; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | throw; |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | void IpRuleManager::ModifyLoopbackRuleWithSourceAddress(const Rule& rule, Operation action) |
| 135 | { |
| 136 | if (rule.family != AF_INET && rule.family != AF_INET6) |
| 137 | { |
| 138 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected address family: {}", rule.family)); |
| 139 | } |
| 140 | if (action != Operation::Create && action != Operation::Remove) |
| 141 | { |
| 142 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected operation: {}", static_cast<int>(action))); |
| 143 | } |
| 144 | |
| 145 | if (rule.family == AF_INET) |
| 146 | { |
| 147 | ModifyLoopbackRuleWithSourceAddressImpl<in_addr>(rule, action); |
| 148 | } |
| 149 | else |
| 150 | { |
| 151 | ModifyLoopbackRuleWithSourceAddressImpl<in6_addr>(rule, action); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | template <typename TAddr> |
| 156 | void IpRuleManager::ModifyLoopbackRuleWithSourceAddressImpl(const Rule& rule, Operation operation) |
| 157 | { |
| 158 | if (!rule.protocol.has_value()) |
| 159 | { |
| 160 | throw RuntimeErrorWithSourceLocation("Rule missing protocol"); |
| 161 | } |
| 162 | if (!rule.sourceAddress.has_value()) |
| 163 | { |
| 164 | throw RuntimeErrorWithSourceLocation("Rule missing source IP"); |
| 165 | } |
| 166 | |
| 167 | GNS_LOG_INFO("{} rule {}", operation == Operation::Create ? "Add" : "Remove", utils::Stringify(rule).c_str()); |
| 168 | |
| 169 | int flags = (operation == Create) ? NLM_F_CREATE : 0; |
| 170 | |
| 171 | struct Message : RuleMessage |
| 172 | { |
| 173 | LoopbackInterfaceAttribute devName; |
| 174 | utils::AddressAttribute<TAddr> from; |
| 175 | utils::IntegerAttribute priority; |
| 176 | utils::Attribute<uint8_t> protocol; |
| 177 | } __attribute__((packed)); |
| 178 | |
| 179 | int netlinkOperation = operation == Remove ? RTM_DELRULE : RTM_NEWRULE; |
| 180 | |
| 181 | SendMessage<Message>(rule.family, rule.routingTable, netlinkOperation, flags, [&](Message& message) { |
| 182 | message.devName.header.rta_len = sizeof(message.devName); |
| 183 | message.devName.header.rta_type = FRA_IIFNAME; |
| 184 | |
| 185 | // Set source address in the rule |
| 186 | message.rule.rtm_src_len = rule.sourceAddress->PrefixLength(); |
| 187 | utils::InitializeAddressAttribute<TAddr>(message.from, rule.sourceAddress.value(), FRA_SRC); |
| 188 | |
| 189 | utils::InitializeIntegerAttribute(message.priority, rule.priority, RTA_PRIORITY); |
| 190 | |
| 191 | message.protocol.header.rta_len = RTA_LENGTH(sizeof(uint8_t)); |
| 192 | message.protocol.header.rta_type = FRA_IP_PROTO; |
| 193 | message.protocol.value = rule.protocol.value() == Tcp ? IPPROTO_TCP : IPPROTO_UDP; |
| 194 | }); |
| 195 | } |
| 196 | |
| 197 | void IpRuleManager::ModifyRoutingTablePriority(const Rule& rule, Operation operation) |
| 198 | { |
| 199 | if (operation != Operation::Create && operation != Operation::Remove) |
| 200 | { |
| 201 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected operation: {}", static_cast<int>(operation))); |
| 202 | } |
| 203 | |
| 204 | GNS_LOG_INFO("{} rule {}", operation == Operation::Create ? "Add" : "Remove", utils::Stringify(rule)); |
| 205 | |
| 206 | struct Message : RuleMessage |
| 207 | { |
| 208 | utils::IntegerAttribute priority; |
| 209 | } __attribute__((packed)); |
| 210 | |
| 211 | // In case of Remove, there are no additional flags needed besides NLM_F_REQUEST | NLM_F_ACK. |
| 212 | int flags = 0; |
| 213 | if (operation == Create) |
| 214 | { |
| 215 | flags = NLM_F_CREATE; |
| 216 | } |
| 217 | |
| 218 | int netlinkOperation = operation == Remove ? RTM_DELRULE : RTM_NEWRULE; |
| 219 | |
| 220 | SendMessage<Message>(rule.family, rule.routingTable, netlinkOperation, flags, [&](Message& message) { |
| 221 | utils::InitializeIntegerAttribute(message.priority, rule.priority, RTA_PRIORITY); |
| 222 | }); |
| 223 | } |
| 224 | |
| 225 | void IpRuleManager::ModifyRoutingTablePriorityWithProtocol(const Rule& rule, Operation operation) |
| 226 | { |
| 227 | if (operation != Operation::Create && operation != Operation::Remove) |
| 228 | { |
| 229 | throw RuntimeErrorWithSourceLocation(std::format("Unexpected operation: {}", static_cast<int>(operation))); |
| 230 | } |
| 231 | if (!rule.protocol.has_value()) |
| 232 | { |
| 233 | throw RuntimeErrorWithSourceLocation("Rule missing protocol"); |
| 234 | } |
| 235 | |
| 236 | GNS_LOG_INFO("{} rule {}", operation == Operation::Create ? "Add" : "Remove", utils::Stringify(rule)); |
| 237 | |
| 238 | struct Message : RuleMessage |
| 239 | { |
| 240 | utils::IntegerAttribute priority; |
| 241 | utils::Attribute<uint8_t> protocol; |
| 242 | } __attribute__((packed)); |
| 243 | |
| 244 | // In case of Remove, there are no additional flags needed besides NLM_F_REQUEST | NLM_F_ACK. |
| 245 | int flags = 0; |
| 246 | if (operation == Create) |
| 247 | { |
| 248 | flags = NLM_F_CREATE; |
| 249 | } |
| 250 | |
| 251 | int netlinkOperation = operation == Remove ? RTM_DELRULE : RTM_NEWRULE; |
| 252 | |
| 253 | SendMessage<Message>(rule.family, rule.routingTable, netlinkOperation, flags, [&](Message& message) { |
| 254 | utils::InitializeIntegerAttribute(message.priority, rule.priority, RTA_PRIORITY); |
| 255 | |
| 256 | message.protocol.header.rta_len = RTA_LENGTH(sizeof(uint8_t)); |
| 257 | message.protocol.header.rta_type = FRA_IP_PROTO; |
| 258 | message.protocol.value = rule.protocol.value() == Tcp ? IPPROTO_TCP : IPPROTO_UDP; |
| 259 | }); |
| 260 | } |
| 261 | |
| 262 | std::vector<Rule> IpRuleManager::ListRules(int family, int tableId) |
| 263 | { |
| 264 | struct Message |
| 265 | { |
| 266 | rtmsg rule; |
| 267 | } __attribute__((packed)); |
| 268 | |
| 269 | std::vector<Rule> rules{}; |
| 270 | |
| 271 | auto onMessage = [&](const NetlinkResponse& response) { |
| 272 | for (const auto& e : response.Messages<rtmsg>(RTM_NEWRULE)) |
| 273 | { |
| 274 | const auto msg = e.Payload(); |
| 275 | const auto priorityAttr = e.UniqueAttribute<int>(FRA_PRIORITY); |
| 276 | const auto oifAttr = e.UniqueAttribute<char>(FRA_OIFNAME); |
| 277 | const auto protocolAttr = e.UniqueAttribute<uint8_t>(FRA_IP_PROTO); |
| 278 | const auto tableAttr = e.UniqueAttribute<int>(FRA_TABLE); |
| 279 | |
| 280 | int priority = priorityAttr.has_value() ? *priorityAttr.value() : -1; |
| 281 | std::string oif(oifAttr.value_or("")); |
| 282 | uint8_t proto = protocolAttr.has_value() ? *protocolAttr.value() : -1; |
| 283 | std::optional<Protocol> protocol = |
| 284 | (proto == IPPROTO_TCP) ? std::optional(Tcp) : ((proto == IPPROTO_UDP) ? std::optional(Udp) : std::optional<Protocol>()); |
| 285 | int tableAttrId = tableAttr.has_value() ? *tableAttr.value() : msg->rtm_table; |
| 286 | |
| 287 | rules.emplace_back(Rule(msg->rtm_family, tableAttrId, priority, oif, protocol)); |
| 288 | } |
| 289 | }; |
| 290 | |
| 291 | rtmsg message{}; |
| 292 | message.rtm_family = family; |
| 293 | message.rtm_table = 0; |
| 294 | |
| 295 | auto transaction = m_channel.CreateTransaction(message, RTM_GETRULE, NLM_F_DUMP); |
| 296 | transaction.Execute(onMessage); |
| 297 | |
| 298 | return rules; |
| 299 | } |