-
Notifications
You must be signed in to change notification settings - Fork 893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gRPC Error: Register reads are not supported yet (UNIMPLEMENTED) #497
Comments
It is something that might change in the future, yes, and I hope it does. This is the root cause of the issue: p4lang/PI#376. As you can see, there have been a volunteer or two since 2018 that has thought about taking up the task of making this enhancement, but no one has completed it yet. As a workaround, it is possible to use PacketIn and PacketOut packets between controller and network device to inject packets into the P4-programmable device that read and/or write the register, and return read values back to the controller in response packets. A demonstration of that workaround can be found here: https://github.com/jafingerhut/p4-guide/tree/master/ptf-tests/registeraccess |
@jafingerhut Thanks for the response earlier. I wanted to use the PackinIn and PacketOut approach, but I've got three questions:
|
When you specify
|
Many thanks for the fast response!
Alright, so that means the |
"this bit of code" I mentioned is included as part of the simple_switch_grpc code. You do not need to write any code to make it work, other than use It is up to you and your desired use case which packets you want to send to the CPU port and what their contents are, e.g. what custom packet metadata fields do you want to include with such packets? What headers are included vs. not included? Similarly it is up to you what packets you want your controller software to send as PacketOut messages (if any), and for any of those, to write your P4 code such that it can process them when they arrive on the CPU port in a way that makes sense for your use case. I do not know what you mean by "knows how to fit in the pipes automatically". |
Just that it knows packets whose egress_spec is the CPU_PORT must be forwarded to the controller, and that packets received from the controller are supposed to be injected onto the switch with ingress_port=CPU_PORT.
Understood. I studied the examples in packetinout and registeraccess. In our P4 code, these custom packet metadata fields are defined in special Thank you for all the help! 😃 |
Everything you said in your previous comment looks correct to me. |
I was about to post one more question, but I managed to solve it in the meantime. For anyone attempting this workaround using the utils provided by the tutorials environment, here's how I went about it:
@controller_header("packet_in")
header packet_in_h {
packet_type_t packet_type;
bit<48> m2;
bit<16> m3;
bit<32> m4;
} //No need for further annotations: the switch knows it must send 4 metadata fields
def start(self, controllers):
args.append("--cpu-port 510") #Added by me after all other args.append() calls
def fetchMessage(self):
return self.stream_msg_resp.next() #Blocking call
// This message is already defined in p4runtime.proto file.
// I'm just quoting it for reference
message StreamMessageResponse {
oneof update {
MasterArbitrationUpdate arbitration = 1;
PacketIn packet = 2;
DigestList digest = 3;
IdleTimeoutNotification idle_timeout_notification = 4;
.google.protobuf.Any other = 5;
// Used by the server to asynchronously report errors which occur when
// processing StreamMessageRequest messages.
StreamError error = 6;
}
} # In your Python controller, define a new function and write:
msg = sw.fetchMessage()
msg_type = msg.WhichOneof('update') #gRPC method to find which of the oneof fields is set
if msg_type == 'packet':
print('Correct packet_in stream response detected! Processing packet...')
metadata = msg.packet.metadata
payload = msg.packet.payload
...
else:
print('Message type was ' + msg_type + '. Only \'packet\' is accepted. Skipping.') IMPORTANT: Do not forget to invoke The question (meanwhile solved) had to do with metadata fields being apparently the wrong size. For example, if I write
Giving the impression that the last metadata field is wrong in size (1 byte, when it should be 4 per our definition above). If metadata fields are sent with the most significant bits set, it correctly prints all 4 bytes. I suspect this is just Python3 abbreviating the output. Keep in mind also that the notation used is apparently octal and may print ASCII characters, which was confusing at first, since I was sending raw hexadecimal output through Scapy like: If something here appears wrong, let me know! |
@rmiguelc If you get all of this working, and you are interested, it might be of interest to add a new exercise in the |
@jafingerhut Sounds like a good idea! Is it okay for people following the tutorials to edit files in the utility directories? I may have missed it, but there doesn't seem to be a way to set the cpu port through the makefile or the topology JSON. |
I think that requiring a tutorial follower to edit such files is a reasonable way to start for you writing up such steps. You could even supply those changes as a patch file, e.g. the output of a command like "diff -c orig_version edited_version > changes-to-make.patch", and then your instructions could say "run this command in the appropriate directory: patch -p1 < changes-to-make.patch". There may be good ways to avoid such steps, e.g. if we found that there was already a way to provide the cpu port through the makefile or topology JSON (there might be in the topology JSON file a way to provide additional command line arguments to simple_switch_grpc processes, but I am not sure). Or we could consider making changes to various Python files that enabled such options for every exercise in the future. |
I'm just looking the same problem of reading registers by controller, this does help me a lot, though I didn't finish my own test yet. Many thanks to you all! |
I was about to use part of code in https://github.com/jafingerhut/p4-guide/tree/master/ptf-tests/registeraccess, but I just met an error 'ModuleNotFoundError: No module named 'p4runtime_shell_utils'. Before meeting it, I finished installing p4runtime-shell. How can I solve this problem by adding some source files or installing some package? |
Sorry, I find this file in 'testlib', maybe it's because I don't add files correctly. Btw, thanks a lot for this magic solution of packet_in and packet_out, which is another function I need. |
Now I have a new problem. After I successfully made packet_in and packet_out by 'import p4runtime_sh.shell as sh', and I test my function that write and read value. I can see from s1.log that the register was set to right value, but another operation can not find this value. For example, I set index 1 to 12, while reading the value before set, as 0. |
I would recommend double-checking those logs to ensure that (a) nothing ELSE made writes to that register between those two times, and (b) all of the accesses you are looking at are from the log for the SAME simple_switch_grpc process. If you are running a mininet network of multiple simple_switch_grpc processes, for example, each of those has independent contents for all tables and P4 registers. If one simple_switch_grpc process is killed or exits for any reason, and you start a new one, then the initial contents of all P4 registers is by default 0 when the P4 program is loaded into the software switch. |
If I remember correctly, the |
Yes, I have another python file which is based on tutorial/p4runtime, and that file directly calls |
Hey there! I'm glad this thread has helped others. I still intend to write an exercise for In my work, I've come across the need to also inject packets onto the switch from the controller. Unfortunately, my attempts so far have been unsuccessful. Here is the Python3 code I programmed into def pktOut(self, payload, *metadata):
# The metadata parameter is a series of tuples whose first and second elements
# are its value and byte length respectively.
# e.g. sw.pktOut(payload, (20, 2), (47, 8))
request = p4runtime_pb2.StreamMessageRequest()
request.packet.payload = payload
for i in range(len(metadata)):
m = request.packet.metadata.add()
m.metadata_id = i+1
m.value = int.to_bytes(metadata[i][0], metadata[i][1], 'big')
print(request)
self.requests_stream.put(request) StreamMessageRequests are the same messages used to issue Master Arbitration Updates. When running the controller and using this function, the switch prints the following to its log:
I searched p4lang for this message, "p4::tmp::P4DeviceConfig is deprecated", and it found: https://github.com/p4lang/PI/blob/dc9c75f3017535f1892cdd758a80a46ded6ce4b6/proto/frontend/src/device_mgr.cpp#LL476C30-L476C30 Since both the message and that function refer to device config, I begin to suspect the request was treated as an arbitration update and not as a PacketOut, even after confirming the type is |
I have a PTF test here: https://github.com/jafingerhut/p4-guide/tree/master/ptf-tests/packetinout that exercises PacketOut (i.e. from controller to switch) and PacketIn (i.e. from switch to controller) P4Runtime API messages. I would recommend going through the code there, and in the p4-guide/testlib/base_test.py file that it uses, to see what you can learn about how it generates PacketOut messages, and parses incoming PacketIn messages. |
I managed to do it! For those interested, here's what I implemented in def pktOut(self, p4info_helper, payload, *metadata):
"""
Sends a PACKET_OUT from the controller to the switch.
The metadata parameter corresponds to the PACKET_OUT fields defined by
you in your P4 program, and should be filled with tuples containing
value and length (in bytes) of the field respectively.
For example, imagine PACKET_OUT was defined in the P4 program as:
@controller_header("packet_out")
header pktout_h {
bit<8> opcode;
bit<48> m1;
}
Then this method should be invoked as follows:
sw.pktOut(p4info_helper, payload, (1,1), (200, 6))
i.e. (opcode=1 in 1 byte, m1=200 in 6 bytes)
"""
packet_out = p4runtime_pb2.PacketOut()
packet_out.payload = payload
metadata_list = []
for metadata_id, (m_val, m_len) in enumerate(metadata, 1):
# Enumerate is just an easy way to combine a for-loop with a range-loop
# 'metadata' is the iterable we wish to iterate on, 1 is the starting
# value for 'metadata_id'.
item = p4runtime_pb2.PacketMetadata()
item.metadata_id = metadata_id
item.value = m_val.to_bytes(m_len, 'big')
metadata_list.append(item)
packet_out.metadata.extend(metadata_list)
request = p4runtime_pb2.StreamMessageRequest()
request.packet.CopyFrom(packet_out)
self.requests_stream.put(request) |
I studied the examples in packetinout and registeraccess.
|
I am not aware of any P4 implementation that can read the entire contents of the register in a single API call, atomically. There are ways to have an "active copy" of a register and a "currently unused" copy of the same number of elements, and swap those at control-plane-time chosen events, and then read the "currently unused" copy one element at a time. Because it is the currently unused copy and not being updated by the data plane, you can read all of its elements one at a time, and known that none of them are changing, and so get a consistent snapshot of all entries, but of course it is the currently unused copy, so is gradually getting more and more stale over time. |
I was toying with the P4Runtime exercise and attempted to make it read a register, by editing
switch.py
with the following code and calling it from my controller:Unfortunately, the response was that:
Is this something that might change in the future? How would one go about implementing this RPC call?
The text was updated successfully, but these errors were encountered: