blob: 420846daf969d9ff11c6aa8a0df780b6d6c8160d [file] [log] [blame]
Jian Li63430202018-08-30 16:24:09 +09001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.openstacknetworking.impl;
18
Jian Li46650932018-09-04 23:52:11 +090019import com.google.common.collect.ImmutableSet;
Jian Li293b04a2019-03-25 03:03:11 +090020import com.google.common.collect.Lists;
Jian Li63430202018-08-30 16:24:09 +090021import org.apache.commons.lang.StringUtils;
Jian Li63430202018-08-30 16:24:09 +090022import org.apache.http.Header;
23import org.apache.http.HttpEntityEnclosingRequest;
24import org.apache.http.HttpMessage;
25import org.apache.http.HttpRequest;
26import org.apache.http.HttpResponse;
27import org.apache.http.client.methods.CloseableHttpResponse;
Jian Li46650932018-09-04 23:52:11 +090028import org.apache.http.client.methods.HttpDelete;
Jian Li63430202018-08-30 16:24:09 +090029import org.apache.http.client.methods.HttpGet;
30import org.apache.http.client.methods.HttpPost;
Jian Li46650932018-09-04 23:52:11 +090031import org.apache.http.client.methods.HttpPut;
Jian Li63430202018-08-30 16:24:09 +090032import org.apache.http.client.methods.HttpRequestBase;
33import org.apache.http.impl.client.CloseableHttpClient;
34import org.apache.http.impl.client.HttpClientBuilder;
35import org.apache.http.message.BasicHeader;
36import org.apache.http.message.BasicHttpResponse;
37import org.onlab.packet.BasePacket;
38import org.onlab.packet.Data;
39import org.onlab.packet.Ethernet;
40import org.onlab.packet.IPv4;
41import org.onlab.packet.IpAddress;
42import org.onlab.packet.IpPrefix;
43import org.onlab.packet.TCP;
44import org.onlab.packet.TpPort;
Jian Li63430202018-08-30 16:24:09 +090045import org.onosproject.cluster.ClusterService;
46import org.onosproject.cluster.LeadershipService;
47import org.onosproject.cluster.NodeId;
48import org.onosproject.core.ApplicationId;
49import org.onosproject.core.CoreService;
50import org.onosproject.net.ConnectPoint;
51import org.onosproject.net.flow.DefaultTrafficSelector;
52import org.onosproject.net.flow.DefaultTrafficTreatment;
53import org.onosproject.net.flow.TrafficSelector;
54import org.onosproject.net.flow.TrafficTreatment;
55import org.onosproject.net.packet.DefaultOutboundPacket;
56import org.onosproject.net.packet.PacketContext;
57import org.onosproject.net.packet.PacketProcessor;
58import org.onosproject.net.packet.PacketService;
59import org.onosproject.openstacknetworking.api.Constants;
60import org.onosproject.openstacknetworking.api.InstancePort;
61import org.onosproject.openstacknetworking.api.InstancePortService;
62import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
63import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
64import org.onosproject.openstacknode.api.OpenstackNode;
65import org.onosproject.openstacknode.api.OpenstackNodeEvent;
66import org.onosproject.openstacknode.api.OpenstackNodeListener;
67import org.onosproject.openstacknode.api.OpenstackNodeService;
Jian Li293b04a2019-03-25 03:03:11 +090068import org.openstack4j.model.network.Network;
Jian Li63430202018-08-30 16:24:09 +090069import org.openstack4j.model.network.Port;
Jian Li34220ea2018-11-14 01:30:24 +090070import org.osgi.service.component.annotations.Activate;
71import org.osgi.service.component.annotations.Component;
72import org.osgi.service.component.annotations.Deactivate;
73import org.osgi.service.component.annotations.Reference;
74import org.osgi.service.component.annotations.ReferenceCardinality;
Jian Li63430202018-08-30 16:24:09 +090075import org.slf4j.Logger;
76
77import java.io.IOException;
78import java.nio.ByteBuffer;
Jian Li9f278e92019-03-24 14:23:16 +090079import java.util.Arrays;
Jian Li293b04a2019-03-25 03:03:11 +090080import java.util.List;
Jian Li63430202018-08-30 16:24:09 +090081import java.util.Objects;
Jian Li46650932018-09-04 23:52:11 +090082import java.util.Set;
Jian Li34220ea2018-11-14 01:30:24 +090083import java.util.concurrent.ExecutorService;
Jian Li63430202018-08-30 16:24:09 +090084
Jian Li34220ea2018-11-14 01:30:24 +090085import static java.util.concurrent.Executors.newSingleThreadExecutor;
86import static org.onlab.util.Tools.groupedThreads;
Jian Li5c09e212018-10-24 18:23:58 +090087import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
Jian Li63430202018-08-30 16:24:09 +090088import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
89import static org.onosproject.openstacknetworking.impl.OpenstackMetadataProxyHandler.Http.Type.RESPONSE;
90import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.hmacEncrypt;
91import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.parseHttpRequest;
92import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.unparseHttpResponseBody;
93import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.unparseHttpResponseHeader;
94import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
95import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.CONTROLLER;
96import static org.slf4j.LoggerFactory.getLogger;
97
98/**
99 * Handles metadata requests for the virtual instances.
100 */
101@Component(immediate = true)
102public class OpenstackMetadataProxyHandler {
103 protected final Logger log = getLogger(getClass());
104
105 private static final String METADATA_SERVER_IP = "169.254.169.254";
106 private static final int METADATA_SERVER_PORT = 8775;
107 private static final int HTTP_SERVER_PORT = 80;
108 private static final int PREFIX_LENGTH = 32;
109 private static final short WINDOW_SIZE = (short) 0x1000;
110 private static final short FIN_FLAG = (short) 0x01;
111 private static final short SYN_FLAG = (short) 0x02;
112 private static final short ACK_FLAG = (short) 0x10;
113 private static final short SYN_ACK_FLAG = (short) 0x12;
114 private static final short FIN_ACK_FLAG = (short) 0x11;
Jian Li293b04a2019-03-25 03:03:11 +0900115 private static final short FIN_ACK_PUSH_FLAG = (short) 0x19;
Jian Li63430202018-08-30 16:24:09 +0900116 private static final byte DATA_OFFSET = (byte) 0x5;
117 private static final short URGENT_POINTER = (short) 0x1;
118 private static final byte PACKET_TTL = (byte) 127;
119 private static final String HTTP_PREFIX = "http://";
120 private static final String COLON = ":";
121
Jian Li293b04a2019-03-25 03:03:11 +0900122 private static final int IP_HEADER_SIZE = 20;
123 private static final int TCP_HEADER_SIZE = 20;
124
Jian Li63430202018-08-30 16:24:09 +0900125 private static final String INSTANCE_ID_HEADER = "X-Instance-ID";
126 private static final String INSTANCE_ID_SIGNATURE_HEADER = "X-Instance-ID-Signature";
127 private static final String TENANT_ID_HEADER = "X-Tenant-ID";
128 private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For";
129
130 private static final String HTTP_GET_METHOD = "GET";
131 private static final String HTTP_POST_METHOD = "POST";
Jian Li46650932018-09-04 23:52:11 +0900132 private static final String HTTP_PUT_METHOD = "PUT";
133 private static final String HTTP_DELETE_METHOD = "DELETE";
Jian Li63430202018-08-30 16:24:09 +0900134
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700135 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900136 protected CoreService coreService;
137
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700138 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900139 protected PacketService packetService;
140
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700141 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900142 protected ClusterService clusterService;
143
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700144 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900145 protected LeadershipService leadershipService;
146
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700147 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900148 protected OpenstackNetworkService osNetworkService;
149
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900151 protected OpenstackNodeService osNodeService;
152
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900154 protected InstancePortService instancePortService;
155
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li63430202018-08-30 16:24:09 +0900157 protected OpenstackFlowRuleService osFlowRuleService;
158
Jian Li34220ea2018-11-14 01:30:24 +0900159 private final ExecutorService eventExecutor = newSingleThreadExecutor(
160 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
Jian Li63430202018-08-30 16:24:09 +0900161 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
162 private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
163
Jian Li46650932018-09-04 23:52:11 +0900164 private Set<String> excludedHeaders = ImmutableSet.of("content-type", "content-length");
165
Jian Li63430202018-08-30 16:24:09 +0900166 private ApplicationId appId;
167 private NodeId localNodeId;
168
169 @Activate
170 protected void activate() {
171 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
172 localNodeId = clusterService.getLocalNode().id();
Jian Li63430202018-08-30 16:24:09 +0900173 osNodeService.addListener(osNodeListener);
174 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
175 leadershipService.runForLeadership(appId.name());
176
177 log.info("Started");
178 }
179
180 @Deactivate
181 protected void deactivate() {
182 packetService.removeProcessor(packetProcessor);
Jian Li63430202018-08-30 16:24:09 +0900183 osNodeService.removeListener(osNodeListener);
184 leadershipService.withdraw(appId.name());
Jian Li34220ea2018-11-14 01:30:24 +0900185 eventExecutor.shutdown();
Jian Li63430202018-08-30 16:24:09 +0900186
187 log.info("Stopped");
188 }
189
Jian Li63430202018-08-30 16:24:09 +0900190 private class InternalPacketProcessor implements PacketProcessor {
191
192 @Override
193 public void process(PacketContext context) {
Jian Li46650932018-09-04 23:52:11 +0900194
Jian Li34220ea2018-11-14 01:30:24 +0900195 if (context.isHandled()) {
Jian Li46650932018-09-04 23:52:11 +0900196 return;
197 }
198
Jian Li34220ea2018-11-14 01:30:24 +0900199 // FIXME: need to find a way to spawn a new thread to check metadata proxy mode
200 if (!useMetadataProxy()) {
Jian Li63430202018-08-30 16:24:09 +0900201 return;
202 }
203
204 Ethernet ethPacket = context.inPacket().parsed();
205 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
206 return;
207 }
208
209 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
210 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_TCP ||
211 !IpAddress.valueOf(ipv4Packet.getDestinationAddress()).
212 equals(IpAddress.valueOf(METADATA_SERVER_IP))) {
213 return;
214 }
215
216 TCP tcpPacket = (TCP) ipv4Packet.getPayload();
217 if (tcpPacket.getDestinationPort() != HTTP_SERVER_PORT) {
218 return;
219 }
220
Jian Li46650932018-09-04 23:52:11 +0900221 // (three-way handshaking)
222 // reply TCP SYN-ACK packet with receiving TCP SYN packet
Jian Li63430202018-08-30 16:24:09 +0900223 if (tcpPacket.getFlags() == SYN_FLAG) {
224 Ethernet ethReply = buildTcpSynAckPacket(ethPacket, ipv4Packet, tcpPacket);
225 sendReply(context, ethReply);
226 return;
227 }
228
Jian Li46650932018-09-04 23:52:11 +0900229 // (four-way handshaking)
230 // reply TCP ACK and TCP FIN-ACK packets with receiving TCP FIN-ACK packet
Jian Li63430202018-08-30 16:24:09 +0900231 if (tcpPacket.getFlags() == FIN_ACK_FLAG) {
232 Ethernet ackReply = buildTcpAckPacket(ethPacket, ipv4Packet, tcpPacket);
233 sendReply(context, ackReply);
234 Ethernet finAckReply = buildTcpFinAckPacket(ethPacket, ipv4Packet, tcpPacket);
235 sendReply(context, finAckReply);
236 return;
237 }
238
Jian Li46650932018-09-04 23:52:11 +0900239 // normal TCP data transmission
Jian Li63430202018-08-30 16:24:09 +0900240 Data data = (Data) tcpPacket.getPayload();
241 byte[] byteData = data.getData();
242
243 if (byteData.length != 0) {
Jian Li34220ea2018-11-14 01:30:24 +0900244 eventExecutor.execute(() -> {
245 processHttpRequest(context, ethPacket, ipv4Packet, tcpPacket, byteData);
246 });
247 }
248 }
Jian Li63430202018-08-30 16:24:09 +0900249
Jian Li34220ea2018-11-14 01:30:24 +0900250 private void processHttpRequest(PacketContext context, Ethernet ethPacket,
251 IPv4 ipv4Packet, TCP tcpPacket, byte[] byteData) {
252 HttpRequest request = parseHttpRequest(byteData);
253 ConnectPoint cp = context.inPacket().receivedFrom();
254 InstancePort instPort = instancePortService.instancePort(cp.deviceId(), cp.port());
Jian Li63430202018-08-30 16:24:09 +0900255
Jian Li34220ea2018-11-14 01:30:24 +0900256 if (instPort == null || request == null) {
257 log.warn("Cannot send metadata request due to lack of information");
258 return;
259 }
Jian Li63430202018-08-30 16:24:09 +0900260
Jian Li34220ea2018-11-14 01:30:24 +0900261 // attempt to send HTTP request to the meta-data server (nova-api),
262 // obtain the HTTP response, relay the response to VM through packet-out
263 CloseableHttpResponse proxyResponse = proxyHttpRequest(request, instPort);
Jian Li63430202018-08-30 16:24:09 +0900264
Jian Li34220ea2018-11-14 01:30:24 +0900265 if (proxyResponse == null) {
266 log.warn("No response was received from metadata server");
267 return;
Jian Li9f278e92019-03-24 14:23:16 +0900268 } else {
269 log.debug("Metadata response headers {}", Arrays.toString(proxyResponse.getAllHeaders()));
270 log.debug("Metadata response entity {}", proxyResponse.getEntity().toString());
Jian Li34220ea2018-11-14 01:30:24 +0900271 }
Jian Li63430202018-08-30 16:24:09 +0900272
Jian Li34220ea2018-11-14 01:30:24 +0900273 HttpResponse response = new BasicHttpResponse(proxyResponse.getStatusLine());
274 response.setEntity(proxyResponse.getEntity());
275 response.setHeaders(proxyResponse.getAllHeaders());
Jian Li63430202018-08-30 16:24:09 +0900276
Jian Li293b04a2019-03-25 03:03:11 +0900277 Network osNetwork = osNetworkService.network(instPort.networkId());
278 int tcpPayloadSize = osNetwork.getMTU() - IP_HEADER_SIZE - TCP_HEADER_SIZE;
Jian Li63430202018-08-30 16:24:09 +0900279
Jian Li293b04a2019-03-25 03:03:11 +0900280 List<TCP> tcpReplies = buildTcpDataPackets(tcpPacket,
281 byteData.length, response, tcpPayloadSize);
282 List<Ethernet> ethReplies = buildEthFrames(ethPacket, ipv4Packet, tcpReplies);
283 ethReplies.forEach(e -> sendReply(context, e));
Jian Li34220ea2018-11-14 01:30:24 +0900284
285 try {
286 proxyResponse.close();
287 } catch (IOException e) {
288 log.warn("Failed to close the response connection due to {}", e);
Jian Li63430202018-08-30 16:24:09 +0900289 }
290 }
291
292 /**
293 * Builds an ethernet frame contains TCP sync-ack packet generated
294 * from the given TCP sync request packet.
295 *
296 * @param ethRequest ethernet request frame
297 * @param ipv4Request IPv4 request
298 * @param tcpRequest TCP request
299 * @return an ethernet frame contains newly generated TCP reply
300 */
301 private Ethernet buildTcpSynAckPacket(Ethernet ethRequest,
302 IPv4 ipv4Request, TCP tcpRequest) {
303
304 TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getSequence(),
305 tcpRequest.getSequence() + 1, SYN_ACK_FLAG);
306
307 return buildEthFrame(ethRequest, ipv4Request, tcpReply);
308 }
309
310 /**
311 * Builds a TCP ACK packet receiving SYN packet.
312 *
313 * @param ethRequest ethernet request frame
314 * @param ipv4Request IPv4 request
315 * @param tcpRequest TCP request
316 * @return an ethernet frame contains newly generated TCP reply
317 */
318 private Ethernet buildTcpAckPacket(Ethernet ethRequest,
319 IPv4 ipv4Request, TCP tcpRequest) {
320 TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getAcknowledge(),
321 tcpRequest.getSequence() + 1, ACK_FLAG);
322
323 return buildEthFrame(ethRequest, ipv4Request, tcpReply);
324 }
325
326 /**
327 * Builds a TCP FIN-ACK packet receiving FIN-ACK packet.
328 *
329 * @param ethRequest ethernet request frame
330 * @param ipv4Request IPv4 request
331 * @param tcpRequest TCP request
332 * @return an ethernet frame contains newly generated TCP reply
333 */
334 private Ethernet buildTcpFinAckPacket(Ethernet ethRequest,
335 IPv4 ipv4Request, TCP tcpRequest) {
336 TCP tcpReply = buildTcpSignalPacket(tcpRequest, tcpRequest.getAcknowledge(),
337 tcpRequest.getSequence() + 1, FIN_ACK_FLAG);
338
339 return buildEthFrame(ethRequest, ipv4Request, tcpReply);
340 }
341
342 /**
343 * Builds a TCP signaling packet.
344 *
345 * @param tcpRequest TCP request
346 * @param seq sequence number
347 * @param ack ack number
348 * @param flags TCP flags
349 * @return TCP signal packet
350 */
351 private TCP buildTcpSignalPacket(TCP tcpRequest, int seq, int ack, short flags) {
352 TCP tcpReply = new TCP();
353 tcpReply.setSourcePort(tcpRequest.getDestinationPort());
354 tcpReply.setDestinationPort(tcpRequest.getSourcePort());
355 tcpReply.setSequence(seq);
356 tcpReply.setAcknowledge(ack);
357 tcpReply.setDataOffset(DATA_OFFSET);
358 tcpReply.setFlags(flags);
359 tcpReply.setWindowSize(WINDOW_SIZE);
360 tcpReply.setUrgentPointer(URGENT_POINTER);
361
362 return tcpReply;
363 }
364
365 /**
366 * Builds a TCP data packet.
367 *
368 * @param tcpRequest TCP request
369 * @param requestLength TCP request data length
370 * @param response HTTP response
371 * @return a TCP data packet
372 */
Jian Li293b04a2019-03-25 03:03:11 +0900373 private List<TCP> buildTcpDataPackets(TCP tcpRequest, int requestLength,
374 HttpResponse response, int payloadSize) {
375 List<TCP> tcpReplies = Lists.newArrayList();
Jian Li63430202018-08-30 16:24:09 +0900376
377 Http httpResponse = new Http();
378 httpResponse.setType(RESPONSE);
379 httpResponse.setMessage(response);
380
Jian Li293b04a2019-03-25 03:03:11 +0900381 byte[] httpBytes = httpResponse.serialize();
Jian Li63430202018-08-30 16:24:09 +0900382
Jian Li293b04a2019-03-25 03:03:11 +0900383 int numOfSegments = (int) Math.ceil((double) httpBytes.length / payloadSize);
384
385 if (numOfSegments == 1) {
386 TCP tcpReply = new TCP();
387 tcpReply.setSourcePort(tcpRequest.getDestinationPort());
388 tcpReply.setDestinationPort(tcpRequest.getSourcePort());
389 tcpReply.setSequence(tcpRequest.getAcknowledge());
390 tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
391 tcpReply.setDataOffset(DATA_OFFSET); // no options, 20 bytes
392 tcpReply.setFlags(ACK_FLAG);
393 tcpReply.setWindowSize(WINDOW_SIZE);
394 tcpReply.setUrgentPointer(URGENT_POINTER);
395
396 Data data = new Data(httpBytes);
397 tcpReply.setPayload(data);
398
399 tcpReplies.add(tcpReply);
400 }
401
402 if (numOfSegments > 1) {
403
404 for (int i = 0; i < numOfSegments; i++) {
405
406 int byteStartIndex = i * payloadSize;
407 int byteEndIndex;
408
409 TCP tcpReply = new TCP();
410 tcpReply.setSourcePort(tcpRequest.getDestinationPort());
411 tcpReply.setDestinationPort(tcpRequest.getSourcePort());
412 tcpReply.setSequence(tcpRequest.getAcknowledge() + byteStartIndex);
413 tcpReply.setAcknowledge(tcpRequest.getSequence() + requestLength);
414 tcpReply.setDataOffset(DATA_OFFSET); // no options, 20 bytes
415 tcpReply.setWindowSize(WINDOW_SIZE);
416 tcpReply.setUrgentPointer(URGENT_POINTER);
417
418 if (i == numOfSegments - 1) {
419 tcpReply.setFlags(FIN_ACK_PUSH_FLAG);
420 byteEndIndex = httpBytes.length;
421 } else {
422 tcpReply.setFlags(ACK_FLAG);
423 byteEndIndex = (i + 1) * payloadSize;
424 }
425
426 byte[] httpSegmentBytes = Arrays.copyOfRange(httpBytes,
427 byteStartIndex, byteEndIndex);
428
429 Data data = new Data(httpSegmentBytes);
430 tcpReply.setPayload(data);
431
432 tcpReplies.add(tcpReply);
433 }
434 }
435
436 return tcpReplies;
Jian Li63430202018-08-30 16:24:09 +0900437 }
438
439 /**
440 * Builds an ethernet frame with the given IPv4 and TCP payload.
441 *
442 * @param ethRequest ethernet request frame
443 * @param ipv4Request IPv4 request
444 * @param tcpReply TCP reply
445 * @return an ethernet frame contains TCP payload
446 */
447 private Ethernet buildEthFrame(Ethernet ethRequest, IPv4 ipv4Request,
448 TCP tcpReply) {
449 Ethernet ethReply = new Ethernet();
450 ethReply.setSourceMACAddress(ethRequest.getDestinationMAC());
451 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
452 ethReply.setEtherType(ethRequest.getEtherType());
453
454 IPv4 ipv4Reply = new IPv4();
455 ipv4Reply.setSourceAddress(ipv4Request.getDestinationAddress());
456 ipv4Reply.setDestinationAddress(ipv4Request.getSourceAddress());
457 ipv4Reply.setTtl(PACKET_TTL);
458
459 ipv4Reply.setPayload(tcpReply);
460 ethReply.setPayload(ipv4Reply);
461
462 return ethReply;
463 }
464
465 /**
Jian Li293b04a2019-03-25 03:03:11 +0900466 * Builds a set of ethernet frames with the given IPv4 and TCP payload.
467 *
468 * @param ethRequest ethernet request frame
469 * @param ipv4Request IPv4 request
470 * @param tcpReplies TCP replies
471 * @return a set of ethernet frames
472 */
473 private List<Ethernet> buildEthFrames(Ethernet ethRequest,
474 IPv4 ipv4Request,
475 List<TCP> tcpReplies) {
476
477 List<Ethernet> ethReplies = Lists.newArrayList();
478
479 for (TCP tcpReply : tcpReplies) {
480
481 Ethernet ethReply = new Ethernet();
482 ethReply.setSourceMACAddress(ethRequest.getDestinationMAC());
483 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
484 ethReply.setEtherType(ethRequest.getEtherType());
485
486 IPv4 ipv4Reply = new IPv4();
487 ipv4Reply.setSourceAddress(ipv4Request.getDestinationAddress());
488 ipv4Reply.setDestinationAddress(ipv4Request.getSourceAddress());
489 ipv4Reply.setTtl(PACKET_TTL);
490
491 ipv4Reply.setProtocol(IPv4.PROTOCOL_TCP);
492 ipv4Reply.setPayload(tcpReply);
493
494 ethReply.setPayload(ipv4Reply);
495
496 ethReplies.add(ethReply);
497 }
498
499 return ethReplies;
500 }
501
502 /**
Jian Li92b6f292018-09-06 10:57:18 +0900503 * Obtains the metadata path.
504 *
505 * @param uri metadata request URI
506 * @return full metadata path
507 */
508 private String getMetadataPath(String uri) {
509 OpenstackNode controller = osNodeService.completeNodes(CONTROLLER).
510 stream().findFirst().orElse(null);
511 if (controller == null) {
512 return null;
513 }
514
515 String novaMetadataIpTmp = controller.neutronConfig().novaMetadataIp();
516 String novaMetadataIp = novaMetadataIpTmp != null
517 ? novaMetadataIpTmp : controller.managementIp().toString();
518 Integer novaMetadataPortTmp = controller.neutronConfig().novaMetadataPort();
519 int novaMetadataPort = novaMetadataPortTmp != null
520 ? novaMetadataPortTmp : METADATA_SERVER_PORT;
521
522 return HTTP_PREFIX + novaMetadataIp + COLON + novaMetadataPort + uri;
523 }
524
525 /**
Jian Li63430202018-08-30 16:24:09 +0900526 * Proxyies HTTP request.
527 *
528 * @param oldRequest HTTP request
529 * @param instPort instance port
530 * @return HTTP response
531 */
532 private CloseableHttpResponse proxyHttpRequest(HttpRequest oldRequest,
533 InstancePort instPort) {
534
535 CloseableHttpClient client = HttpClientBuilder.create().build();
Jian Li92b6f292018-09-06 10:57:18 +0900536 String url = getMetadataPath(oldRequest.getRequestLine().getUri());
Jian Li63430202018-08-30 16:24:09 +0900537
538 if (StringUtils.isEmpty(url)) {
539 log.warn("The metadata endpoint is not configured!");
540 return null;
541 }
542
Jian Li63430202018-08-30 16:24:09 +0900543 HttpRequestBase request;
544
Jian Li46650932018-09-04 23:52:11 +0900545 String method = oldRequest.getRequestLine().getMethod().toUpperCase();
546
547 log.info("Sending HTTP {} request to metadata endpoint {}...", method, url);
548
549 switch (method) {
Jian Li63430202018-08-30 16:24:09 +0900550 case HTTP_POST_METHOD:
551 request = new HttpPost(url);
Jian Li46650932018-09-04 23:52:11 +0900552 HttpEntityEnclosingRequest postRequest =
Jian Li63430202018-08-30 16:24:09 +0900553 (HttpEntityEnclosingRequest) oldRequest;
Jian Li46650932018-09-04 23:52:11 +0900554 ((HttpPost) request).setEntity(postRequest.getEntity());
555 break;
556 case HTTP_PUT_METHOD:
557 request = new HttpPut(url);
558 HttpEntityEnclosingRequest putRequest =
559 (HttpEntityEnclosingRequest) oldRequest;
560 ((HttpPut) request).setEntity(putRequest.getEntity());
561 break;
562 case HTTP_DELETE_METHOD:
563 request = new HttpDelete(url);
Jian Li63430202018-08-30 16:24:09 +0900564 break;
Jian Li6a47fd02018-11-27 21:51:03 +0900565 case HTTP_GET_METHOD:
Jian Li63430202018-08-30 16:24:09 +0900566 default:
567 request = new HttpGet(url);
568 break;
569 }
570
571 // configure headers from original HTTP request
572 for (Header header : oldRequest.getAllHeaders()) {
Jian Li46650932018-09-04 23:52:11 +0900573 if (method.equals(HTTP_POST_METHOD) ||
574 method.equals(HTTP_PUT_METHOD)) {
575 // we DO NOT add duplicated HTTP headers for POST and PUT methods
576 if (excludedHeaders.contains(header.getName().toLowerCase())) {
577 continue;
578 }
579 }
Jian Li63430202018-08-30 16:24:09 +0900580 request.addHeader(header);
581 }
582
583 request.setProtocolVersion(oldRequest.getProtocolVersion());
584
585 Port port = osNetworkService.port(instPort.portId());
586
587 request.addHeader(new BasicHeader(INSTANCE_ID_HEADER, port.getDeviceId()));
Jian Li63430202018-08-30 16:24:09 +0900588 request.addHeader(new BasicHeader(TENANT_ID_HEADER, port.getTenantId()));
Jian Li5ecfd1a2018-12-10 11:41:03 +0900589 request.addHeader(new BasicHeader(FORWARDED_FOR_HEADER,
590 instPort.ipAddress().toString()));
Jian Li46650932018-09-04 23:52:11 +0900591 if (metadataSecret() != null) {
592 request.addHeader(new BasicHeader(INSTANCE_ID_SIGNATURE_HEADER,
593 hmacEncrypt(metadataSecret(), port.getDeviceId())));
594 }
Jian Li63430202018-08-30 16:24:09 +0900595
596 try {
597 return client.execute(request);
598 } catch (IOException e) {
599 log.warn("Failed to get response from metadata server due to {}", e);
600 }
601
602 return null;
603 }
604
605 /**
606 * Sends out ethernet frame.
607 *
608 * @param context packet context
609 * @param ethReply ethernet frame
610 */
611 private void sendReply(PacketContext context, Ethernet ethReply) {
612 if (ethReply == null) {
613 return;
614 }
615 ConnectPoint srcPoint = context.inPacket().receivedFrom();
616 TrafficTreatment treatment = DefaultTrafficTreatment
617 .builder()
618 .setOutput(srcPoint.port())
619 .build();
620
621 packetService.emit(new DefaultOutboundPacket(
622 srcPoint.deviceId(),
623 treatment,
624 ByteBuffer.wrap(ethReply.serialize())));
625 context.block();
626 }
Jian Li6a47fd02018-11-27 21:51:03 +0900627
628 private String metadataSecret() {
629 OpenstackNode controller = osNodeService.completeNodes(CONTROLLER)
630 .stream().findFirst().orElse(null);
631
632 if (controller != null && controller.neutronConfig() != null) {
633 return controller.neutronConfig().metadataProxySecret();
634 }
635
636 return null;
637 }
Jian Li63430202018-08-30 16:24:09 +0900638 }
639
640 private class InternalNodeEventListener implements OpenstackNodeListener {
641 @Override
642 public boolean isRelevant(OpenstackNodeEvent event) {
Jian Li34220ea2018-11-14 01:30:24 +0900643 return event.subject().type() == COMPUTE;
644 }
645
646 private boolean isRelevantHelper() {
647 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()))
648 && useMetadataProxy();
Jian Li63430202018-08-30 16:24:09 +0900649 }
650
651 @Override
652 public void event(OpenstackNodeEvent event) {
653 OpenstackNode osNode = event.subject();
654 switch (event.type()) {
655 case OPENSTACK_NODE_COMPLETE:
Jian Li6a47fd02018-11-27 21:51:03 +0900656 eventExecutor.execute(() -> processNodeCompletion(osNode));
Jian Li63430202018-08-30 16:24:09 +0900657 break;
Jian Li63430202018-08-30 16:24:09 +0900658 case OPENSTACK_NODE_CREATED:
659 case OPENSTACK_NODE_UPDATED:
660 case OPENSTACK_NODE_REMOVED:
Jian Li238552d2019-09-24 22:32:02 +0900661 case OPENSTACK_NODE_INCOMPLETE:
Jian Li63430202018-08-30 16:24:09 +0900662 default:
663 break;
664 }
665 }
666
Jian Li6a47fd02018-11-27 21:51:03 +0900667 private void processNodeCompletion(OpenstackNode osNode) {
668 if (!isRelevantHelper()) {
669 return;
670 }
671
672 setMetadataRule(osNode, true);
673 }
674
675 private void processNodeIncompletion(OpenstackNode osNode) {
676 if (!isRelevantHelper()) {
677 return;
678 }
679
680 setMetadataRule(osNode, false);
681 }
682
Jian Li63430202018-08-30 16:24:09 +0900683 /**
684 * Installs metadata rule for receiving all metadata request packets.
685 *
686 * @param osNode openstack node
687 * @param install installation flag
688 */
689 private void setMetadataRule(OpenstackNode osNode, boolean install) {
690 TrafficSelector selector = DefaultTrafficSelector.builder()
691 .matchEthType(Ethernet.TYPE_IPV4)
692 .matchIPProtocol(IPv4.PROTOCOL_TCP)
693 .matchIPDst(IpPrefix.valueOf(
694 IpAddress.valueOf(METADATA_SERVER_IP), PREFIX_LENGTH))
695 .matchTcpDst(TpPort.tpPort(HTTP_SERVER_PORT))
696 .build();
697
698 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
699 .punt()
700 .build();
701
702 osFlowRuleService.setRule(
703 appId,
704 osNode.intgBridge(),
705 selector,
706 treatment,
707 PRIORITY_DHCP_RULE,
Jian Li5c09e212018-10-24 18:23:58 +0900708 DHCP_TABLE,
Jian Li63430202018-08-30 16:24:09 +0900709 install);
710 }
711 }
712
Jian Li46650932018-09-04 23:52:11 +0900713 private boolean useMetadataProxy() {
714 OpenstackNode gw = osNodeService.completeNodes(CONTROLLER)
715 .stream().findFirst().orElse(null);
716
717 if (gw != null && gw.neutronConfig() != null) {
718 return gw.neutronConfig().useMetadataProxy();
719 }
720
721 return false;
722 }
723
Jian Li63430202018-08-30 16:24:09 +0900724 /**
725 * Implements Http packet format.
726 */
727 protected static class Http extends BasePacket {
728
Jian Li411bf2e2018-11-29 00:08:54 +0900729 /**
730 * HTTP packet type.
731 */
Jian Li63430202018-08-30 16:24:09 +0900732 public enum Type {
733
734 /**
735 * Signifies that this is a Http REQUEST packet.
736 */
737 REQUEST,
738
739 /**
740 * Signifies that this is a Http RESPONSE packet.
741 */
742 RESPONSE,
743 }
744
745 private Type type;
746 private HttpMessage message;
747
Jian Li411bf2e2018-11-29 00:08:54 +0900748 Http() {
749 }
750
Jian Li63430202018-08-30 16:24:09 +0900751 /**
752 * Obtains the Http type.
753 *
754 * @return Http type
755 */
756 public Type getType() {
757 return type;
758 }
759
760 /**
761 * Configures the Http type.
762 *
763 * @param type Http type
764 */
765 public void setType(Type type) {
766 this.type = type;
767 }
768
769 /**
770 * Obtains the Http message.
771 *
772 * @return Http message
773 */
774 public HttpMessage getMessage() {
775 return message;
776 }
777
778 /**
779 * Configures the Http message.
780 *
781 * @param message Http message
782 */
783 public void setMessage(HttpMessage message) {
784 this.message = message;
785 }
786
787 @Override
788 public byte[] serialize() {
789 if (type == RESPONSE) {
790
791 byte[] header = unparseHttpResponseHeader((HttpResponse) message);
792 byte[] body = unparseHttpResponseBody((HttpResponse) message);
793
794 if (header == null || body == null) {
795 return new byte[0];
796 }
797
798 final byte[] data = new byte[header.length + body.length];
799 final ByteBuffer bb = ByteBuffer.wrap(data);
800 bb.put(header);
801 bb.put(body);
802
803 return data;
804 }
805 return new byte[0];
806 }
807
808 @Override
809 public boolean equals(Object o) {
810
811 if (this == o) {
812 return true;
813 }
814
815 if (o == null || getClass() != o.getClass()) {
816 return false;
817 }
818
819 if (!super.equals(o)) {
820 return false;
821 }
822
823 Http http = (Http) o;
824 return type == http.type &&
825 com.google.common.base.Objects.equal(message, http.message);
826 }
827
828 @Override
829 public int hashCode() {
830 return Objects.hash(type, message);
831 }
832 }
833}